Files
kniip/src/Kniip/Editor/AnnotationSurface.cs
T
rogierandCursor 67c98c7c0a 1.1.4
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-11-09 16:36:28 +01:00

119 lines
3.9 KiB
C#

using System.Collections.Generic;
using System.Windows;
using System.Windows.Media;
using Kniip.Editor.Annotations;
namespace Kniip.Editor;
/// <summary>
/// Tekent het knipsel met alle annotaties, plus de selectie en het bijsnijkader.
/// Het element is precies zo groot als de afbeelding in pixels; zoomen gebeurt met een LayoutTransform erboven.
/// </summary>
public sealed class AnnotationSurface : FrameworkElement
{
private static readonly Brush AccentBrush = RenderHelpers.Freeze(new SolidColorBrush(Palette.Accent));
private static readonly Brush GhostHandleBrush = RenderHelpers.Freeze(new SolidColorBrush(Color.FromArgb(170, 255, 255, 255)));
private static readonly Brush CropShadeBrush = RenderHelpers.Freeze(new SolidColorBrush(Color.FromArgb(120, 0, 0, 0)));
public EditorDocument? Document { get; set; }
public Annotation? Selected { get; set; }
public Rect? CropRect { get; set; }
/// <summary>Schaal van de weergave (DIP per afbeeldingspixel), om grepen altijd even groot te tonen.</summary>
public double ViewScale { get; set; } = 1;
public double HandleSize => 9 / ViewScale;
public AnnotationSurface()
{
Focusable = true;
FocusVisualStyle = null;
ClipToBounds = true;
}
protected override Size MeasureOverride(Size availableSize) =>
Document == null ? new Size(0, 0) : new Size(Document.Width, Document.Height);
protected override void OnRender(DrawingContext dc)
{
if (Document == null)
{
return;
}
DocumentRenderer.Render(dc, Document);
if (Selected != null && !Selected.IsHidden && Document.Items.Contains(Selected))
{
DrawSelection(dc, Selected);
}
if (CropRect is Rect crop)
{
var full = new RectangleGeometry(new Rect(0, 0, Document.Width, Document.Height));
var shade = Geometry.Combine(full, new RectangleGeometry(crop), GeometryCombineMode.Exclude, null);
dc.DrawGeometry(CropShadeBrush, null, shade);
dc.DrawRectangle(null, CreateDashedPen(Brushes.White), crop);
dc.DrawRectangle(null, CreateDashedPen(AccentBrush, 4), crop);
}
}
private void DrawSelection(DrawingContext dc, Annotation item)
{
Rect bounds = item.Bounds;
double pad = 3 / ViewScale;
bounds.Inflate(pad, pad);
dc.DrawRectangle(null, CreateDashedPen(AccentBrush), bounds);
IReadOnlyList<Point> handles = item.GetHandles();
if (handles.Count == 0)
{
return;
}
double size = HandleSize;
var pen = new Pen(AccentBrush, 1.2 / ViewScale);
for (int i = 0; i < handles.Count; i++)
{
Point h = handles[i];
if (item.IsGhostHandle(i))
{
double radius = size * 0.35;
dc.DrawEllipse(GhostHandleBrush, pen, h, radius, radius);
continue;
}
dc.DrawRoundedRectangle(Brushes.White, pen, new Rect(h.X - size / 2, h.Y - size / 2, size, size),
1.5 / ViewScale, 1.5 / ViewScale);
}
}
private Pen CreateDashedPen(Brush brush, double offset = 0) => new(brush, 1 / ViewScale)
{
DashStyle = new DashStyle(new[] { 4.0, 4.0 }, offset)
};
public int HitHandle(Annotation item, Point p)
{
IReadOnlyList<Point> handles = item.GetHandles();
double tolerance = HandleSize / 2 + 2 / ViewScale;
int ghost = -1;
for (int i = handles.Count - 1; i >= 0; i--)
{
if (System.Math.Abs(handles[i].X - p.X) <= tolerance && System.Math.Abs(handles[i].Y - p.Y) <= tolerance)
{
if (!item.IsGhostHandle(i))
{
return i;
}
ghost = i;
}
}
return ghost;
}
}