11internal sealed class WidgetDragBehavior
13 private readonly WidgetBase _owner;
15 private bool _isPointerDown;
16 private bool _suppressNextPointerClick;
17 private float _dragThresholdPx = 3f;
18 private PointF _dragStartScreenPositionPx;
19 private Vector2 _dragStartWidgetPositionPx;
21 private int _dragPointerId;
22 private View? _dragView;
23 private Vector2 _dragStartPointerPositionPx;
25 internal WidgetDragBehavior(WidgetBase owner)
27 _owner = owner ??
throw new ArgumentNullException(nameof(owner));
30 internal bool IsDragEnabled {
get;
set; } =
true;
32 internal bool IsDragging {
get;
private set; }
34 internal float DragThresholdPx
36 get => _dragThresholdPx;
41 throw new ArgumentOutOfRangeException(
44 "The drag threshold cannot be negative.");
47 _dragThresholdPx = value;
51 internal bool CanStartDrag(
52 WidgetPointerEventArgs args)
54 return IsDragEnabled &&
58 internal void ProcessPointerDown(WidgetPointerEventArgs args,
59 Func<WidgetPointerEventArgs, bool> canStartDrag)
61 if (_isPointerDown || !canStartDrag(args))
66 _isPointerDown =
true;
67 _suppressNextPointerClick =
false;
70 _dragStartScreenPositionPx = args.ScreenPositionPx;
71 _dragStartWidgetPositionPx = _owner.GetPosition();
72 _dragButton = args.Button;
73 _dragPointerId = args.PointerId;
74 _dragView = args.View;
75 _dragStartPointerPositionPx = GetPointerPositionInWidgetSpace(args.ScreenPositionPx, args.View);
78 internal void ProcessPointerMove(WidgetPointerEventArgs args,
79 Func<Vector2, View, Vector2> convertDelta,
80 Func<Vector2, Vector2> constrainPosition,
81 Action<WidgetDragEventArgs> dragStarted,
82 Action<WidgetDragEventArgs> dragged)
84 if (!_isPointerDown || args.PointerId != _dragPointerId)
87 Vector2 totalScreenDeltaPx = GetTotalScreenDelta(args.ScreenPositionPx);
91 float thresholdSquared = DragThresholdPx * DragThresholdPx;
92 if (totalScreenDeltaPx.LengthSquared() < thresholdSquared)
96 _suppressNextPointerClick =
true;
98 dragStarted(CreateDragEventArgs(args));
105 ApplyDragPosition(totalScreenDeltaPx, _dragView ?? args.View, convertDelta, constrainPosition);
107 dragged(CreateDragEventArgs(args));
110 internal void ProcessPointerUp(WidgetPointerEventArgs args,
111 Func<Vector2, View, Vector2> convertDelta,
112 Func<Vector2, Vector2> constrainPosition,
113 Action<WidgetDragEventArgs> dragEnded)
115 if (!_isPointerDown || !IsMatchingRelease(args))
118 WidgetDragEventArgs? dragArgs =
null;
122 Vector2 totalScreenDeltaPx = GetTotalScreenDelta(args.ScreenPositionPx);
123 ApplyDragPosition(totalScreenDeltaPx, _dragView ?? args.View, convertDelta, constrainPosition);
125 dragArgs = CreateDragEventArgs(args);
128 bool completedDrag = IsDragging;
132 if (completedDrag && dragArgs is not
null)
134 _suppressNextPointerClick =
true;
139 internal bool ShouldDispatchPointerClick(WidgetPointerEventArgs args)
141 if (!_suppressNextPointerClick)
144 _suppressNextPointerClick =
false;
150 internal Vector2 ConvertScreenDeltaToPositionDelta(Vector2 totalScreenDeltaPx,
153 ArgumentNullException.ThrowIfNull(view);
156 return totalScreenDeltaPx;
158 var currentScreenPositionPx =
new PointF(_dragStartScreenPositionPx.X + totalScreenDeltaPx.X,
159 _dragStartScreenPositionPx.Y + totalScreenDeltaPx.Y);
161 Vector2 currentPointerPositionPx = GetPointerPositionInWidgetSpace(currentScreenPositionPx, view);
163 return currentPointerPositionPx - _dragStartPointerPositionPx;
166 internal void ResetPointerState(
bool clearClickSuppression =
false)
168 _isPointerDown =
false;
170 _dragStartScreenPositionPx = PointF.Empty;
171 _dragStartWidgetPositionPx = Vector2.Zero;
175 _dragStartPointerPositionPx = Vector2.Zero;
177 if (clearClickSuppression)
178 _suppressNextPointerClick =
false;
181 private Vector2 GetPointerPositionInWidgetSpace(PointF screenPositionPx,
185 return new Vector2(screenPositionPx.X, screenPositionPx.Y);
187 var sceneLayer = _owner.SceneLayer
188 ??
throw new InvalidOperationException(
"A scene-layer draggable widget must contain children attached to a SceneLayer.");
192 return new Vector2(worldPositionPx.X, worldPositionPx.Y);
195 private Vector2 GetTotalScreenDelta(PointF currentScreenPositionPx)
197 return new Vector2(currentScreenPositionPx.X - _dragStartScreenPositionPx.X,
198 currentScreenPositionPx.Y - _dragStartScreenPositionPx.Y);
201 private void ApplyDragPosition(Vector2 totalScreenDeltaPx,
203 Func<Vector2, View, Vector2> convertDelta,
204 Func<Vector2, Vector2> constrainPosition)
206 Vector2 positionDeltaPx = convertDelta(totalScreenDeltaPx, view);
207 Vector2 proposedPositionPx = _dragStartWidgetPositionPx + positionDeltaPx;
209 _owner.SetPosition(constrainPosition(proposedPositionPx));
212 private bool IsMatchingRelease(WidgetPointerEventArgs args)
214 return args.PointerId == _dragPointerId &&
215 (args.Button == WidgetPointerButtonEnum.None ||
216 args.Button == _dragButton);
219 private WidgetDragEventArgs CreateDragEventArgs(WidgetPointerEventArgs pointerArgs)
221 return new WidgetDragEventArgs(_owner,
222 _dragStartScreenPositionPx,
223 pointerArgs.ScreenPositionPx,
226 pointerArgs.PointerId);
PointF ScreenPxToWorldPx(SceneLayer layer, PointF screenPx)
Converts a screen-space pixel position into world-space coordinates for the specified scene layer,...
DirectDrawingMode
Specifies where direct drawing operations are applied within the rendering pipeline.
@ View
Performs direct drawing at the view level using screen-space coordinates.