Skip to content

Commit cfbe93f

Browse files
authored
Merge pull request #60 from TruePadawan/dev
Implement custom color picker and improve drawing tablet support
2 parents dc2789d + e996fea commit cfbe93f

17 files changed

+261
-37
lines changed

Directory.Packages.props

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@
66
<ItemGroup>
77
<!-- Avalonia packages -->
88
<!-- Important: keep version in sync! -->
9-
<PackageVersion Include="Avalonia" Version="11.3.4" />
10-
<PackageVersion Include="Avalonia.Controls.ColorPicker" Version="11.3.4" />
11-
<PackageVersion Include="Avalonia.Skia" Version="11.3.4" />
12-
<PackageVersion Include="Avalonia.Themes.Fluent" Version="11.3.4" />
13-
<PackageVersion Include="Avalonia.Fonts.Inter" Version="11.3.4" />
14-
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.4" />
15-
<PackageVersion Include="Avalonia.Desktop" Version="11.3.4" />
16-
<PackageVersion Include="Avalonia.iOS" Version="11.3.4" />
17-
<PackageVersion Include="Avalonia.Browser" Version="11.3.4" />
18-
<PackageVersion Include="Avalonia.Android" Version="11.3.4" />
9+
<PackageVersion Include="Avalonia" Version="11.3.12" />
10+
<PackageVersion Include="Avalonia.Controls.ColorPicker" Version="11.3.12" />
11+
<PackageVersion Include="Avalonia.Skia" Version="11.3.12" />
12+
<PackageVersion Include="Avalonia.Themes.Fluent" Version="11.3.12" />
13+
<PackageVersion Include="Avalonia.Fonts.Inter" Version="11.3.12" />
14+
<PackageVersion Include="Avalonia.Diagnostics" Version="11.3.12" />
15+
<PackageVersion Include="Avalonia.Desktop" Version="11.3.12" />
16+
<PackageVersion Include="Avalonia.iOS" Version="11.3.12" />
17+
<PackageVersion Include="Avalonia.Browser" Version="11.3.12" />
18+
<PackageVersion Include="Avalonia.Android" Version="11.3.12" />
1919
<PackageVersion Include="CommunityToolkit.Mvvm" Version="8.4.0" />
2020
<PackageVersion Include="MessageBox.Avalonia" Version="3.3.1.1" />
2121
<PackageVersion Include="Microsoft.AspNetCore.SignalR.Client" Version="10.0.2" />

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ A cross-platform (windows/linux) digital whiteboard application
1515
**I'm working on**:
1616
- UI/UX Improvements
1717

18+
<img width="1916" height="1048" alt="image" src="https://github.com/user-attachments/assets/9ba0d5c9-af75-4aa1-b8c6-5b954c4b161b" />
19+
1820
<img width="1920" height="1043" alt="Screenshot from 2026-02-10 07-11-14" src="https://github.com/user-attachments/assets/b8cf2baf-f565-4f2e-8f8c-0224a5eadef2" />
1921

2022
<img width="1920" height="1080" alt="image" src="https://github.com/user-attachments/assets/4a817026-a4a6-41fd-b684-1323a2f0f9d8" />

Scribble.Browser/Scribble.Browser.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88

99
<ItemGroup>
1010
<PackageReference Include="Avalonia.Browser"/>
11-
<PackageReference Include="Avalonia.Controls.ColorPicker" />
1211
</ItemGroup>
1312

1413
<ItemGroup>

Scribble.Desktop/DEBIAN/scribble.metainfo.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,6 @@
4141
<content_rating type="oars-1.1"/>
4242

4343
<releases>
44-
<release version="0.1.3-alpha" date="2026-02-26"/>
44+
<release version="0.1.4-alpha" date="2026-02-28"/>
4545
</releases>
4646
</component>

Scribble.Desktop/Scribble.Desktop.csproj

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
</PropertyGroup>
1515

1616
<ItemGroup>
17-
<PackageReference Include="Avalonia.Controls.ColorPicker" />
1817
<PackageReference Include="Avalonia.Desktop"/>
1918
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
2019
<PackageReference Include="Avalonia.Diagnostics">
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<UserControl xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
6+
x:Class="Scribble.Controls.ScribbleColorPicker.ScribbleColorPicker"
7+
x:Name="Root">
8+
<Button
9+
Name="ColorPickerButton"
10+
BorderBrush="Gray"
11+
Width="{Binding Width, ElementName=Root}"
12+
Height="{Binding Height, ElementName=Root}"
13+
CornerRadius="{Binding CornerRadius, ElementName=Root}"
14+
Padding="{Binding Padding, ElementName=Root}"
15+
HorizontalAlignment="Stretch"
16+
VerticalAlignment="Stretch">
17+
<Button.Background>
18+
<SolidColorBrush Color="{Binding SelectedColor, ElementName=Root}" />
19+
</Button.Background>
20+
<Button.Flyout>
21+
<Flyout Placement="Bottom">
22+
<StackPanel Spacing="12">
23+
24+
<ListBox Name="ColorListBox"
25+
ItemsSource="{Binding PaletteColors, ElementName=Root}"
26+
SelectedItem="{Binding SelectedColor, ElementName=Root, Mode=TwoWay}"
27+
SelectionChanged="ColorListBox_OnSelectionChanged"
28+
Background="Transparent"
29+
BorderThickness="0">
30+
31+
<ListBox.ItemsPanel>
32+
<ItemsPanelTemplate>
33+
<WrapPanel Orientation="Vertical" MaxHeight="320" />
34+
</ItemsPanelTemplate>
35+
</ListBox.ItemsPanel>
36+
37+
<ListBox.ItemTemplate>
38+
<DataTemplate>
39+
<Border Width="24" Height="24" CornerRadius="4" Margin="2">
40+
<Border.Background>
41+
<SolidColorBrush Color="{Binding}" />
42+
</Border.Background>
43+
</Border>
44+
</DataTemplate>
45+
</ListBox.ItemTemplate>
46+
47+
<ListBox.Styles>
48+
<Style Selector="ListBoxItem">
49+
<Setter Property="Padding" Value="0" />
50+
<Setter Property="Margin" Value="2" />
51+
<Setter Property="CornerRadius" Value="4" />
52+
</Style>
53+
</ListBox.Styles>
54+
</ListBox>
55+
56+
<StackPanel Orientation="Horizontal" Spacing="8" HorizontalAlignment="Center">
57+
<TextBlock Text="#" VerticalAlignment="Center" Foreground="#AAAAAA" FontWeight="Bold" />
58+
<TextBox Name="HexTextBox"
59+
Width="120"
60+
Watermark="FFFFFF"
61+
TextChanged="HexTextBox_OnTextChanged" />
62+
</StackPanel>
63+
64+
</StackPanel>
65+
</Flyout>
66+
</Button.Flyout>
67+
</Button>
68+
</UserControl>
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
using System.Collections.Generic;
2+
using Avalonia;
3+
using Avalonia.Controls;
4+
using Avalonia.Data;
5+
using Avalonia.Media;
6+
7+
namespace Scribble.Controls.ScribbleColorPicker;
8+
9+
public partial class ScribbleColorPicker : UserControl
10+
{
11+
private static readonly IReadOnlyList<Color> DefaultPalette = GenerateMaterialPalette();
12+
13+
private static List<Color> GenerateMaterialPalette()
14+
{
15+
var palette = new MaterialHalfColorPalette();
16+
var colors = new List<Color>();
17+
18+
// Loop through every base color (columns) and every shade (rows)
19+
for (int colorIndex = 0; colorIndex < palette.ColorCount; colorIndex++)
20+
{
21+
for (int shadeIndex = 0; shadeIndex < palette.ShadeCount; shadeIndex++)
22+
{
23+
colors.Add(palette.GetColor(colorIndex, shadeIndex));
24+
}
25+
}
26+
27+
return colors;
28+
}
29+
30+
// Properties
31+
public static readonly StyledProperty<IEnumerable<Color>> PaletteColorsProperty =
32+
AvaloniaProperty.Register<ScribbleColorPicker, IEnumerable<Color>>(
33+
nameof(PaletteColors),
34+
defaultValue: DefaultPalette);
35+
36+
public IEnumerable<Color> PaletteColors
37+
{
38+
get => GetValue(PaletteColorsProperty);
39+
set => SetValue(PaletteColorsProperty, value);
40+
}
41+
42+
public static readonly StyledProperty<Color> SelectedColorProperty =
43+
AvaloniaProperty.Register<ScribbleColorPicker, Color>(
44+
nameof(SelectedColor),
45+
Colors.White,
46+
defaultBindingMode: BindingMode.TwoWay);
47+
48+
public Color SelectedColor
49+
{
50+
get => GetValue(SelectedColorProperty);
51+
set => SetValue(SelectedColorProperty, value);
52+
}
53+
54+
// To prevent infinite loop from the textbox updating the color + the color pallet updating the textbox
55+
private bool _isUpdatingHex = false;
56+
57+
public ScribbleColorPicker()
58+
{
59+
InitializeComponent();
60+
}
61+
62+
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
63+
{
64+
base.OnPropertyChanged(change);
65+
if (change.Property == SelectedColorProperty)
66+
{
67+
UpdateHexTextBox(change.GetNewValue<Color>());
68+
}
69+
}
70+
71+
private void UpdateHexTextBox(Color color)
72+
{
73+
if (_isUpdatingHex) return;
74+
75+
_isUpdatingHex = true;
76+
HexTextBox.Text = color.ToString().TrimStart('#').ToUpper();
77+
_isUpdatingHex = false;
78+
}
79+
80+
private void HexTextBox_OnTextChanged(object? sender, TextChangedEventArgs e)
81+
{
82+
if (_isUpdatingHex) return;
83+
84+
var hex = HexTextBox.Text?.Trim();
85+
if (string.IsNullOrEmpty(hex)) return;
86+
87+
if (!hex.StartsWith('#')) hex = "#" + hex;
88+
89+
if (Color.TryParse(hex, out Color parsedColor))
90+
{
91+
_isUpdatingHex = true;
92+
SelectedColor = parsedColor;
93+
_isUpdatingHex = false;
94+
}
95+
}
96+
97+
private void ColorListBox_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
98+
{
99+
if (e.AddedItems.Count > 0)
100+
{
101+
ColorPickerButton.Flyout?.Hide();
102+
}
103+
}
104+
}

Scribble/Scribble.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515

1616
<ItemGroup>
1717
<PackageReference Include="Avalonia"/>
18-
<PackageReference Include="Avalonia.Controls.ColorPicker"/>
18+
<PackageReference Include="Avalonia.Controls.ColorPicker" />
1919
<PackageReference Include="Avalonia.Skia"/>
2020
<PackageReference Include="Avalonia.Themes.Fluent"/>
2121
<PackageReference Include="Avalonia.Fonts.Inter"/>

Scribble/Utils/Utilities.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,13 @@ public static SKPoint ToSkPoint(Point coord)
2626
{
2727
return new SKPoint((float)coord.X, (float)coord.Y);
2828
}
29+
30+
/// <summary>
31+
/// Returns true if two points are within a small epsilon of each other.
32+
/// Filters subpixel jitter from tablet pens without affecting intentional movement.
33+
/// </summary>
34+
public static bool AreSamePosition(Point a, Point b, double epsilon = 0.5)
35+
{
36+
return Math.Abs(a.X - b.X) < epsilon && Math.Abs(a.Y - b.Y) < epsilon;
37+
}
2938
}

Scribble/ViewModels/MainViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -380,7 +380,7 @@ private List<Guid> ReplayEvents()
380380
drawStrokes[ev.StrokeId] = new DrawStroke
381381
{
382382
Id = ev.StrokeId,
383-
Paint = ev.Paint,
383+
Paint = ev.Paint.Clone(),
384384
Path = textPath,
385385
ToolType = ToolType.Text,
386386
ToolOptions = ev.ToolOptions

0 commit comments

Comments
 (0)