using Avalonia; using Avalonia.Controls; using Avalonia.Controls.PanAndZoom; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Media; using Avalonia.Media.Imaging; namespace Toolkit.UI.Controls.Avalonia; public class ContentColorPicker : ContentControl { public static readonly StyledProperty PeekOffsetProperty = AvaloniaProperty.Register(nameof(PeekOffset), 20); public static readonly StyledProperty PeekPixelsProperty = AvaloniaProperty.Register(nameof(PeekPixels), 20); private readonly Image image = new(); private Canvas? canvas; private (double X, double Y) lastPointerPosition; private Border? peekBorder; private ZoomBorder? zoomBorder; public double PeekOffset { get => GetValue(PeekOffsetProperty); set => SetValue(PeekOffsetProperty, value); } public int PeekPixels { get => GetValue(PeekPixelsProperty); set => SetValue(PeekPixelsProperty, value); } protected override void OnApplyTemplate(TemplateAppliedEventArgs args) { base.OnApplyTemplate(args); PointerMoved -= OnPointerMoved; PointerExited -= OnPointerExited; PointerEntered -= OnPointerEntered; PointerMoved += OnPointerMoved; PointerExited += OnPointerExited; PointerEntered += OnPointerEntered; canvas = args.NameScope.Find("Canvas"); zoomBorder = args.NameScope.Find("ZoomBorder"); if (zoomBorder is not null) { zoomBorder.ZoomChanged += OnZoomChanged; } peekBorder = args.NameScope.Find("PeekBorder"); if (peekBorder is not null) { peekBorder.Child = image; } } private void OnPointerEntered(object? sender, PointerEventArgs args) { if (peekBorder is not null) { peekBorder.IsVisible = true; } } private void OnPointerExited(object? sender, PointerEventArgs args) { if (peekBorder is not null) { peekBorder.IsVisible = false; } } private void OnPointerMoved(object? sender, PointerEventArgs args) { double relativeX = args.GetPosition(canvas).X; double relativeY = args.GetPosition(canvas).Y; lastPointerPosition = (relativeX, relativeY); UpdatePeekPosition(relativeX, relativeY); } private void OnZoomChanged(object sender, ZoomChangedEventArgs args) => UpdatePeekPreview(lastPointerPosition.X, lastPointerPosition.Y); private Bitmap RenderToBitmap(Visual visual, double centreX, double centreY) { int width = PeekPixels; int height = PeekPixels; double x = Math.Max(centreX - width / 2, 0); double y = Math.Max(centreY - height / 2, 0); x = Math.Min(x, visual.Bounds.Width - width); y = Math.Min(y, visual.Bounds.Height - height); PixelSize pixelSize = new(width, height); RenderTargetBitmap renderTarget = new(pixelSize); using (DrawingContext drawingContext = renderTarget.CreateDrawingContext()) { drawingContext.PushClip(new Rect(0, 0, width, height)); drawingContext.FillRectangle(new VisualBrush(visual), new Rect(-x, -y, visual.Bounds.Width, visual.Bounds.Height)); } return renderTarget; } private void UpdatePeekPosition(double relativeX, double relativeY) { if (canvas is null || peekBorder is null) { return; } double peekOffset = PeekOffset; double newX = relativeX + peekOffset; double newY = relativeY + peekOffset; newX = Math.Clamp(newX, -peekBorder.Bounds.Width, canvas.Bounds.Width); newY = Math.Clamp(newY, -peekBorder.Bounds.Height, canvas.Bounds.Height); Canvas.SetLeft(peekBorder, newX); Canvas.SetTop(peekBorder, newY); bool isPointerInside = relativeX >= -peekOffset && relativeX <= canvas.Bounds.Width + peekOffset && relativeY >= -peekOffset && relativeY <= canvas.Bounds.Height + peekOffset; peekBorder.IsVisible = isPointerInside; if (isPointerInside) { UpdatePeekPreview(relativeX, relativeY); } } private void UpdatePeekPreview(double relativeX, double relativeY) { if (zoomBorder is not null) { Bitmap bitmap = RenderToBitmap(zoomBorder, relativeX, relativeY); image.Source = bitmap; } } }