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
17 changes: 17 additions & 0 deletions samples/AvalonDraw/GradientMeshEditorWindow.axaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Window xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="AvalonDraw.GradientMeshEditorWindow"
Width="400" Height="300"
Title="Edit Gradient Mesh">
<Canvas x:Name="MeshCanvas" Background="Gray">
<ItemsControl ItemsSource="{Binding Points}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse Width="10" Height="10" Fill="Red"
Canvas.Left="{Binding Position.X}"
Canvas.Top="{Binding Position.Y}" />
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Canvas>
</Window>
77 changes: 77 additions & 0 deletions samples/AvalonDraw/GradientMeshEditorWindow.axaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
using System.Collections.ObjectModel;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Svg.Model;

namespace AvalonDraw;

public partial class GradientMeshEditorWindow : Window
{
private readonly Canvas _canvas;
private GradientMeshPoint? _dragging;

public ObservableCollection<GradientMeshPoint> Points { get; } = new();

public GradientMeshEditorWindow(GradientMesh mesh)
{
InitializeComponent();
_canvas = this.FindControl<Canvas>("MeshCanvas");
foreach (var p in mesh.Points)
Points.Add(p);
_canvas.PointerPressed += OnPointerPressed;
_canvas.PointerReleased += OnPointerReleased;
_canvas.PointerMoved += OnPointerMoved;
_canvas.DataContext = this;
}

private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}

private void OnPointerPressed(object? sender, PointerPressedEventArgs e)
{
var pos = e.GetPosition(_canvas);
foreach (var p in Points)
{
var rect = new Rect(p.Position.X - 5, p.Position.Y - 5, 10, 10);
if (rect.Contains(pos))
{
_dragging = p;
break;
}
}
}

private void OnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
_dragging = null;
}

private void OnPointerMoved(object? sender, PointerEventArgs e)
{
if (_dragging is null)
return;

var pos = e.GetPosition(_canvas);
var index = Points.IndexOf(_dragging);
if (index >= 0)
{
Points[index] = _dragging with { Position = new ShimSkiaSharp.SKPoint((float)pos.X, (float)pos.Y) };
_dragging = Points[index];
}
}

public GradientMesh Result
{
get
{
var mesh = new GradientMesh();
mesh.Points.AddRange(Points);
return mesh;
}
}
}
10 changes: 10 additions & 0 deletions samples/AvalonDraw/MainWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,16 @@ public MainWindow()
};
return picker;
}
if (entry is GradientMeshEntry meshEntry)
{
var btn = new Button { Content = "Edit Mesh", VerticalAlignment = VerticalAlignment.Center };
btn.Click += async (_, _) =>
{
var dlg = new GradientMeshEditorWindow(meshEntry.Mesh);
await dlg.ShowDialog(this);
};
return btn;
}
if (entry is GradientStopsEntry gEntry)
{
var btn = new Button { Content = entry.Value ?? "Edit", VerticalAlignment = VerticalAlignment.Center };
Expand Down
20 changes: 20 additions & 0 deletions samples/AvalonDraw/Services/GradientMeshEntry.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using System;
using Svg.Model;

namespace AvalonDraw.Services;

public class GradientMeshEntry : PropertyEntry
{
public GradientMesh Mesh { get; }

public GradientMeshEntry(GradientMesh mesh)
: base("Mesh", string.Empty, (_, __) => { })
{
Mesh = mesh;
}

public override void Apply(object target)
{
// Placeholder for applying the mesh to an object.
}
}
22 changes: 22 additions & 0 deletions src/Svg.Model/GradientMesh.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Wiesław Šoltés. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
using System.Collections.Generic;
using ShimSkiaSharp;

namespace Svg.Model;

/// <summary>
/// Represents a simple gradient mesh consisting of colored points.
/// </summary>
public sealed class GradientMesh
{
/// <summary>
/// List of mesh points.
/// </summary>
public List<GradientMeshPoint> Points { get; } = new();
}

/// <summary>
/// Defines a single mesh point with position and color.
/// </summary>
public sealed record GradientMeshPoint(SKPoint Position, SKColor Color);
35 changes: 35 additions & 0 deletions src/Svg.Model/Services/GradientMeshService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (c) Wiesław Šoltés. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
using System.Collections.Generic;
using System.Linq;
using ShimSkiaSharp;

namespace Svg.Model.Services;

/// <summary>
/// Provides conversion helpers for <see cref="GradientMesh"/> instances.
/// </summary>
public static class GradientMeshService
{
/// <summary>
/// Convert gradient mesh points to a mesh shader if supported. Currently this
/// implementation falls back to a linear gradient created between the first
/// and last mesh points when mesh shaders are not available.
/// </summary>
public static SKShader? ToShader(GradientMesh mesh)
{
if (mesh.Points.Count < 2)
return null;

// TODO: use mesh gradient when available in ShimSkiaSharp
var first = mesh.Points.First();
var last = mesh.Points.Last();
return SKShader.CreateLinearGradient(
first.Position,
last.Position,
new[] { (SKColorF)first.Color, (SKColorF)last.Color },
SKColorSpace.Srgb,
new[] { 0f, 1f },
SKShaderTileMode.Clamp);
}
}
Loading