Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions samples/AvalonDraw/MainWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,10 @@
<ListBox x:Name="SwatchList" Margin="4" Height="80"
ItemsSource="{Binding Patterns}" SelectionChanged="SwatchList_OnSelectionChanged"/>
</TabItem>
<TabItem Header="Brushes">
<ListBox x:Name="BrushList" Margin="4" Height="80"
ItemsSource="{Binding BrushStyles}" SelectionChanged="BrushList_OnSelectionChanged"/>
</TabItem>
<TabItem Header="Layers">
<DockPanel>
<StackPanel Orientation="Horizontal" Margin="4" DockPanel.Dock="Top">
Expand Down
44 changes: 44 additions & 0 deletions samples/AvalonDraw/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,19 +55,23 @@ private struct DragInfo
private readonly PropertiesService _propertiesService = new();
private readonly LayerService _layerService = new();
private readonly PatternService _patternService = new();
private readonly BrushService _brushService = new();
public ObservableCollection<PropertyEntry> Properties => _propertiesService.Properties;
public ObservableCollection<PropertyEntry> FilteredProperties => _propertiesService.FilteredProperties;
private ObservableCollection<SvgNode> Nodes { get; } = new();
public ObservableCollection<string> Ids => _propertiesService.Ids;
public ObservableCollection<ArtboardInfo> Artboards { get; } = new();
public ObservableCollection<LayerService.LayerEntry> Layers => _layerService.Layers;
public ObservableCollection<PatternService.PatternEntry> Patterns => _patternService.Patterns;
public ObservableCollection<BrushService.BrushEntry> BrushStyles => _brushService.Brushes;
private ArtboardInfo? _selectedArtboard;
private LayerService.LayerEntry? _selectedLayer;
private PatternService.PatternEntry? _selectedPattern;
private BrushService.BrushEntry? _selectedBrush;
private ListBox? _artboardList;
private TreeView? _layerTree;
private ListBox? _swatchList;
private ListBox? _brushList;
private readonly HashSet<string> _expandedIds = new();
private HashSet<string> _filterBackup = new();

Expand Down Expand Up @@ -293,6 +297,24 @@ public MainWindow()
};
return btn;
}
if (entry is StrokeProfileEntry spEntry)
{
var btn = new Button { Content = entry.Value ?? "Edit", VerticalAlignment = VerticalAlignment.Center };
btn.Click += async (_, _) =>
{
var dlg = new StrokeProfileEditorWindow(spEntry.Points);
var result = await dlg.ShowDialog<bool>(this);
if (result)
{
spEntry.Points.Clear();
foreach (var p in dlg.Result)
spEntry.Points.Add(p);
spEntry.UpdateValue();
spEntry.NotifyChanged();
}
};
return btn;
}
var tb = new TextBox { VerticalContentAlignment = VerticalAlignment.Center };
tb[!TextBox.TextProperty] = new Binding("Value") { Mode = BindingMode.TwoWay };
return tb;
Expand Down Expand Up @@ -324,6 +346,7 @@ public MainWindow()
_artboardList = this.FindControl<ListBox>("ArtboardList");
_layerTree = this.FindControl<TreeView>("LayerTree");
_swatchList = this.FindControl<ListBox>("SwatchList");
_brushList = this.FindControl<ListBox>("BrushList");
_wireframeEnabled = false;
_filtersDisabled = false;
_snapToGrid = false;
Expand Down Expand Up @@ -1501,6 +1524,7 @@ private void BuildTree()
UpdateArtboards();
UpdateLayers();
UpdatePatterns();
UpdateBrushes();
}

private void ApplyPropertyFilter()
Expand Down Expand Up @@ -1561,6 +1585,17 @@ private void UpdatePatterns()
}
}

private void UpdateBrushes()
{
if (BrushStyles.Count > 0)
{
_selectedBrush = BrushStyles[0];
_toolService.CurrentStrokeWidth = (float)_selectedBrush.Profile.Points.First().Width;
if (_brushList is { })
_brushList.SelectedIndex = 0;
}
}


private SvgNode CreateNode(SvgElement element, SvgNode? parent = null)
{
Expand Down Expand Up @@ -2213,6 +2248,15 @@ private void SwatchList_OnSelectionChanged(object? sender, SelectionChangedEvent
}
}

private void BrushList_OnSelectionChanged(object? sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0 && e.AddedItems[0] is BrushService.BrushEntry info)
{
_selectedBrush = info;
_toolService.CurrentStrokeWidth = (float)info.Profile.Points.First().Width;
}
}

private void SelectToolButton_Click(object? sender, RoutedEventArgs e)
{
if (_pathService.IsEditing)
Expand Down
31 changes: 31 additions & 0 deletions samples/AvalonDraw/Services/BrushService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System.Collections.ObjectModel;

namespace AvalonDraw.Services;

public class BrushService
{
public class BrushEntry
{
public StrokeProfile Profile { get; }
public string Name { get; }

public BrushEntry(string name, StrokeProfile profile)
{
Name = name;
Profile = profile;
}

public override string ToString() => Name;
}

public ObservableCollection<BrushEntry> Brushes { get; } = new();
public BrushEntry? SelectedBrush { get; set; }

public BrushService()
{
var def = new StrokeProfile();
Brushes.Add(new BrushEntry("Default", def));
SelectedBrush = Brushes[0];
}
}

8 changes: 8 additions & 0 deletions samples/AvalonDraw/Services/PropertiesService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,14 @@ public void LoadProperties(SvgElement element)
Properties.Add(gEntry);
}

if (element is SvgVisualElement vis &&
vis.CustomAttributes.TryGetValue("stroke-profile", out var prof))
{
var sEntry = new StrokeProfileEntry(prof);
sEntry.PropertyChanged += OnEntryChanged;
Properties.Add(sEntry);
}

LoadAppearanceLayers(element);

ApplyFilter(_filter);
Expand Down
39 changes: 36 additions & 3 deletions samples/AvalonDraw/Services/RenderingService.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
using System.Collections.Generic;
using SK = SkiaSharp;
using System.Linq;
using AvalonDraw;
using AvalonDraw.Services;
using Svg;
using Svg.Model.Drawables;
using Svg.Pathing;
using Shim = ShimSkiaSharp;
using AvalonDraw;
using static AvalonDraw.Services.SelectionService;
using Shim = ShimSkiaSharp;
using SK = SkiaSharp;

// Provides rendering helpers for editor overlays

Expand All @@ -21,6 +24,24 @@ public class RenderingService

private const float HandleSize = 10f;

private static void DrawProfile(SK.SKCanvas canvas, SK.SKPath path, StrokeProfile profile, SK.SKPaint paint)
{
using var measure = new SK.SKPathMeasure(path, false);
var length = measure.Length;
var pts = profile.Points.OrderBy(p => p.Offset).ToList();
for (var i = 1; i < pts.Count; i++)
{
using var seg = new SK.SKPath();
var start = (float)pts[i - 1].Offset * length;
var end = (float)pts[i].Offset * length;
if (measure.GetSegment(start, end, seg, true))
{
paint.StrokeWidth = (float)((pts[i - 1].Width + pts[i].Width) / 2.0);
canvas.DrawPath(seg, paint);
}
}
}

public RenderingService(PathService pathService, ToolService toolService)
{
_pathService = pathService;
Expand Down Expand Up @@ -158,6 +179,18 @@ void DrawLayer(LayerService.LayerEntry info)
canvas.DrawCircle(info.RotHandle, hs, paint);
}

if (selectedDrawable.Element is SvgVisualElement vis &&
vis.CustomAttributes.TryGetValue("stroke-profile", out var prof))
{
var profile = StrokeProfile.Parse(prof);
var skPath = PathService.ElementToPath(vis);
if (skPath is { })
{
using var spaint = new SK.SKPaint { IsAntialias = true, Style = SK.SKPaintStyle.Stroke, Color = SK.SKColors.Black };
DrawProfile(canvas, skPath, profile, spaint);
}
}

if (_pathService.IsEditing && _pathService.EditDrawable == selectedDrawable)
{
using var segPaint = new SK.SKPaint
Expand Down
57 changes: 57 additions & 0 deletions samples/AvalonDraw/Services/StrokeProfile.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;

namespace AvalonDraw.Services;

public class StrokePointInfo
{
public double Offset { get; set; }
public double Width { get; set; }
}

public class StrokeProfile
{
public ObservableCollection<StrokePointInfo> Points { get; }

public StrokeProfile()
{
Points = new ObservableCollection<StrokePointInfo>
{
new() { Offset = 0.0, Width = 1.0 },
new() { Offset = 1.0, Width = 1.0 }
};
}

public static StrokeProfile Parse(string text)
{
var profile = new StrokeProfile();
profile.Points.Clear();
foreach (var part in text.Split(';'))
{
var items = part.Split(',');
if (items.Length != 2)
continue;
if (double.TryParse(items[0], NumberStyles.Float, CultureInfo.InvariantCulture, out var o) &&
double.TryParse(items[1], NumberStyles.Float, CultureInfo.InvariantCulture, out var w))
{
profile.Points.Add(new StrokePointInfo { Offset = o, Width = w });
}
}
if (profile.Points.Count == 0)
{
profile.Points.Add(new StrokePointInfo { Offset = 0.0, Width = 1.0 });
profile.Points.Add(new StrokePointInfo { Offset = 1.0, Width = 1.0 });
}
return profile;
}

public override string ToString()
{
var parts = new List<string>(Points.Count);
parts.AddRange(Points.Select(p => $"{p.Offset.ToString(CultureInfo.InvariantCulture)},{p.Width.ToString(CultureInfo.InvariantCulture)}"));
return string.Join(";", parts);
}
}

40 changes: 40 additions & 0 deletions samples/AvalonDraw/Services/StrokeProfileEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
using System.Collections.ObjectModel;
using System.Linq;
using Svg;

namespace AvalonDraw.Services;

public class StrokeProfileEntry : PropertyEntry
{
public ObservableCollection<StrokePointInfo> Points { get; }

public StrokeProfileEntry(string text)
: base("StrokeProfile", text, (_, __) => { })
{
Points = StrokeProfile.Parse(text).Points;
}

public override void Apply(object target)
{
if (target is SvgVisualElement element)
{
element.CustomAttributes["stroke-profile"] = ToString();
}
}

public void UpdateValue()
{
Value = ToString();
}

public override string ToString()
{
var profile = new StrokeProfile();
profile.Points.Clear();
foreach (var p in Points)
profile.Points.Add(new StrokePointInfo { Offset = p.Offset, Width = p.Width });
return profile.ToString();
}
}


12 changes: 7 additions & 5 deletions samples/AvalonDraw/Services/ToolService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ public enum Tool

public Tool CurrentTool { get; private set; } = Tool.Select;

public float CurrentStrokeWidth { get; set; } = 1f;

public event Action<Tool, Tool>? ToolChanged;

public void SetTool(Tool tool)
Expand All @@ -60,7 +62,7 @@ public void SetTool(Tool tool)
EndX = new SvgUnit(SvgUnitType.User, start.X),
EndY = new SvgUnit(SvgUnitType.User, start.Y),
Stroke = new SvgColourServer(System.Drawing.Color.Black),
StrokeWidth = new SvgUnit(1f)
StrokeWidth = new SvgUnit(CurrentStrokeWidth)
},
Tool.Rect => new SvgRectangle
{
Expand Down Expand Up @@ -90,7 +92,7 @@ public void SetTool(Tool tool)
new SvgUnit(SvgUnitType.User, start.X), new SvgUnit(SvgUnitType.User, start.Y)
},
Stroke = new SvgColourServer(System.Drawing.Color.Black),
StrokeWidth = new SvgUnit(1f)
StrokeWidth = new SvgUnit(CurrentStrokeWidth)
},
Tool.Polyline => new SvgPolyline
{
Expand All @@ -100,7 +102,7 @@ public void SetTool(Tool tool)
new SvgUnit(SvgUnitType.User, start.X), new SvgUnit(SvgUnitType.User, start.Y)
},
Stroke = new SvgColourServer(System.Drawing.Color.Black),
StrokeWidth = new SvgUnit(1f)
StrokeWidth = new SvgUnit(CurrentStrokeWidth)
},
Tool.Text => new SvgText
{
Expand Down Expand Up @@ -144,12 +146,12 @@ public void SetTool(Tool tool)
};
}

private static SvgPath CreatePath(ShimSkiaSharp.SKPoint start, Tool tool)
private SvgPath CreatePath(ShimSkiaSharp.SKPoint start, Tool tool)
{
var path = new SvgPath
{
Stroke = new SvgColourServer(System.Drawing.Color.Black),
StrokeWidth = new SvgUnit(1f)
StrokeWidth = new SvgUnit(CurrentStrokeWidth)
};
var list = new SvgPathSegmentList
{
Expand Down
20 changes: 20 additions & 0 deletions samples/AvalonDraw/StrokeProfileEditorWindow.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="AvalonDraw.StrokeProfileEditorWindow"
Width="400" Height="300"
Title="Edit Stroke Profile">
<DockPanel Margin="10" LastChildFill="True">
<DataGrid x:Name="PointsGrid" DockPanel.Dock="Top" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Offset" Binding="{Binding Offset, Mode=TwoWay}" Width="80"/>
<DataGridTextColumn Header="Width" Binding="{Binding Width, Mode=TwoWay}" Width="80"/>
</DataGrid.Columns>
</DataGrid>
<StackPanel DockPanel.Dock="Bottom" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="4">
<Button Content="Add" Click="AddButton_OnClick" Width="80"/>
<Button Content="Remove" Click="RemoveButton_OnClick" Width="80"/>
<Button Content="OK" Click="OkButton_OnClick" Width="80"/>
<Button Content="Cancel" Click="CancelButton_OnClick" Width="80"/>
</StackPanel>
</DockPanel>
</Window>
Loading
Loading