using System;
using Artemis.Core;
using Artemis.UI.Shared.Events;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Media;
namespace Artemis.UI.Shared.Controls;
///
/// Visualizes an with optional per-LED colors
///
public class SelectionRectangle : Control
{
///
/// Defines the property.
///
public static readonly StyledProperty BackgroundProperty =
AvaloniaProperty.Register(nameof(Background), new SolidColorBrush(Colors.CadetBlue, 0.25));
///
/// Defines the property.
///
public static readonly StyledProperty BorderBrushProperty =
AvaloniaProperty.Register(nameof(BorderBrush), new SolidColorBrush(Colors.CadetBlue));
///
/// Defines the property.
///
public static readonly StyledProperty BorderThicknessProperty =
AvaloniaProperty.Register(nameof(BorderThickness), 1);
///
/// Defines the property.
///
public static readonly StyledProperty BorderRadiusProperty =
AvaloniaProperty.Register(nameof(BorderRadius));
///
/// Defines the property.
///
public static readonly StyledProperty InputElementProperty =
AvaloniaProperty.Register(nameof(InputElement), notifying: OnInputElementChanged);
///
/// Defines the property.
///
public static readonly StyledProperty ZoomRatioProperty =
AvaloniaProperty.Register(nameof(ZoomRatio), 1);
///
/// Defines the read-only property.
///
public static readonly DirectProperty IsSelectingProperty = AvaloniaProperty.RegisterDirect(nameof(IsSelecting), o => o.IsSelecting);
private Rect? _absoluteRect;
private Point _absoluteStartPosition;
private Rect? _displayRect;
private bool _isSelecting;
private IControl? _oldInputElement;
private Point _startPosition;
///
public SelectionRectangle()
{
AffectsRender(BackgroundProperty, BorderBrushProperty, BorderThicknessProperty);
IsHitTestVisible = false;
}
///
/// Gets or sets a brush used to paint the control's background.
///
public IBrush Background
{
get => GetValue(BackgroundProperty);
set => SetValue(BackgroundProperty, value);
}
///
/// Gets or sets a brush used to paint the control's border
///
public IBrush BorderBrush
{
get => GetValue(BorderBrushProperty);
set => SetValue(BorderBrushProperty, value);
}
///
/// Gets or sets the width of the control's border
///
public double BorderThickness
{
get => GetValue(BorderThicknessProperty);
set => SetValue(BorderThicknessProperty, value);
}
///
/// Gets or sets the radius of the control's border
///
public double BorderRadius
{
get => GetValue(BorderRadiusProperty);
set => SetValue(BorderRadiusProperty, value);
}
///
/// Gets or sets the element that captures input for the selection rectangle.
///
public IControl? InputElement
{
get => GetValue(InputElementProperty);
set => SetValue(InputElementProperty, value);
}
///
/// Gets or sets the zoom ratio to counteract when drawing
///
public double ZoomRatio
{
get => GetValue(ZoomRatioProperty);
set => SetValue(ZoomRatioProperty, value);
}
///
/// Gets a boolean indicating whether the selection rectangle is currently performing a selection.
///
public bool IsSelecting
{
get => _isSelecting;
private set => SetAndRaise(IsSelectingProperty, ref _isSelecting, value);
}
///
/// Occurs when the selection rect is being updated, indicating the user is dragging.
///
public event EventHandler? SelectionUpdated;
///
/// Occurs when the selection has finished, indicating the user stopped dragging.
///
public event EventHandler? SelectionFinished;
///
/// Invokes the event
///
///
protected virtual void OnSelectionUpdated(SelectionRectangleEventArgs e)
{
SelectionUpdated?.Invoke(this, e);
}
///
/// Invokes the event
///
///
protected virtual void OnSelectionFinished(SelectionRectangleEventArgs e)
{
SelectionFinished?.Invoke(this, e);
}
private static void OnInputElementChanged(IAvaloniaObject sender, bool before)
{
((SelectionRectangle) sender).SubscribeToInputElement();
}
private void ParentOnPointerPressed(object? sender, PointerPressedEventArgs e)
{
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
return;
e.Pointer.Capture(this);
_startPosition = e.GetPosition(Parent);
_absoluteStartPosition = e.GetPosition(VisualRoot);
_displayRect = null;
}
private void ParentOnPointerMoved(object? sender, PointerEventArgs e)
{
if (!ReferenceEquals(e.Pointer.Captured, this))
return;
Point currentPosition = e.GetPosition(Parent);
Point absoluteCurrentPosition = e.GetPosition(VisualRoot);
_displayRect = new Rect(
new Point(Math.Min(_startPosition.X, currentPosition.X), Math.Min(_startPosition.Y, currentPosition.Y)),
new Point(Math.Max(_startPosition.X, currentPosition.X), Math.Max(_startPosition.Y, currentPosition.Y))
);
_absoluteRect = new Rect(
new Point(Math.Min(_absoluteStartPosition.X, absoluteCurrentPosition.X), Math.Min(_absoluteStartPosition.Y, absoluteCurrentPosition.Y)),
new Point(Math.Max(_absoluteStartPosition.X, absoluteCurrentPosition.X), Math.Max(_absoluteStartPosition.Y, absoluteCurrentPosition.Y))
);
OnSelectionUpdated(new SelectionRectangleEventArgs(_displayRect.Value, _absoluteRect.Value, e.KeyModifiers));
InvalidateVisual();
IsSelecting = true;
}
private void ParentOnPointerReleased(object? sender, PointerReleasedEventArgs e)
{
if (!ReferenceEquals(e.Pointer.Captured, this))
return;
e.Pointer.Capture(null);
if (_displayRect != null && _absoluteRect != null)
{
OnSelectionFinished(new SelectionRectangleEventArgs(_displayRect.Value, _absoluteRect.Value, e.KeyModifiers));
e.Handled = true;
}
_displayRect = null;
InvalidateVisual();
IsSelecting = false;
}
private void SubscribeToInputElement()
{
if (_oldInputElement != null)
{
_oldInputElement.PointerPressed -= ParentOnPointerPressed;
_oldInputElement.PointerMoved -= ParentOnPointerMoved;
_oldInputElement.PointerReleased -= ParentOnPointerReleased;
}
_oldInputElement = InputElement;
if (InputElement != null)
{
InputElement.PointerPressed += ParentOnPointerPressed;
InputElement.PointerMoved += ParentOnPointerMoved;
InputElement.PointerReleased += ParentOnPointerReleased;
}
}
#region Overrides of Visual
///
public override void Render(DrawingContext drawingContext)
{
if (_displayRect != null)
drawingContext.DrawRectangle(Background, new Pen(BorderBrush, BorderThickness / ZoomRatio), _displayRect.Value, BorderRadius / ZoomRatio, BorderRadius / ZoomRatio);
}
///
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
SubscribeToInputElement();
base.OnAttachedToVisualTree(e);
}
///
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
if (_oldInputElement != null)
{
_oldInputElement.PointerPressed -= ParentOnPointerPressed;
_oldInputElement.PointerMoved -= ParentOnPointerMoved;
_oldInputElement.PointerReleased -= ParentOnPointerReleased;
_oldInputElement = null;
}
base.OnDetachedFromVisualTree(e);
}
#endregion
}