Skip to content

Commit 9074b65

Browse files
committed
refactor: introduce UVPoint helper to eliminate orientation-dependent code duplication
- Add UVPoint struct for abstraction of orientation-dependent coordinates - U represents primary axis (X for horizontal, Y for vertical) - V represents secondary axis - Add UVPointExtensions for Canvas positioning and element sizing - Merge DragThumb and DragThumbVertical into single unified method - Simplify SyncThumbs and SyncActiveRectangle using UVPoint - Refactor pointer event handlers to use UVPoint.FromPoint - Add CalculateNormalizedPosition helper for value conversion
1 parent 282b30f commit 9074b65

File tree

4 files changed

+355
-110
lines changed

4 files changed

+355
-110
lines changed
Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
namespace CommunityToolkit.WinUI.Controls;
6+
7+
/// <summary>
8+
/// A helper struct that abstracts orientation-dependent coordinate handling.
9+
/// U represents the primary axis (X for horizontal, Y for vertical).
10+
/// V represents the secondary axis (Y for horizontal, X for vertical).
11+
/// </summary>
12+
internal readonly struct UVPoint
13+
{
14+
/// <summary>
15+
/// Gets the orientation this point is configured for.
16+
/// </summary>
17+
public Orientation Orientation { get; }
18+
19+
/// <summary>
20+
/// Gets the primary axis coordinate (X for horizontal, Y for vertical).
21+
/// </summary>
22+
public double U { get; }
23+
24+
/// <summary>
25+
/// Gets the secondary axis coordinate (Y for horizontal, X for vertical).
26+
/// </summary>
27+
public double V { get; }
28+
29+
/// <summary>
30+
/// Initializes a new instance of the <see cref="UVPoint"/> struct.
31+
/// </summary>
32+
/// <param name="orientation">The orientation context.</param>
33+
/// <param name="u">The primary axis value.</param>
34+
/// <param name="v">The secondary axis value.</param>
35+
public UVPoint(Orientation orientation, double u, double v = 0)
36+
{
37+
Orientation = orientation;
38+
U = u;
39+
V = v;
40+
}
41+
42+
/// <summary>
43+
/// Gets the X coordinate (U for horizontal, V for vertical).
44+
/// </summary>
45+
public double X => Orientation == Orientation.Horizontal ? U : V;
46+
47+
/// <summary>
48+
/// Gets the Y coordinate (V for horizontal, U for vertical).
49+
/// </summary>
50+
public double Y => Orientation == Orientation.Horizontal ? V : U;
51+
52+
/// <summary>
53+
/// Creates a UVPoint from a Point with the specified orientation.
54+
/// </summary>
55+
/// <param name="point">The point in X/Y coordinates.</param>
56+
/// <param name="orientation">The orientation context.</param>
57+
/// <returns>A new UVPoint with converted coordinates.</returns>
58+
public static UVPoint FromPoint(Point point, Orientation orientation)
59+
{
60+
return orientation == Orientation.Horizontal
61+
? new UVPoint(orientation, point.X, point.Y)
62+
: new UVPoint(orientation, point.Y, point.X);
63+
}
64+
65+
/// <summary>
66+
/// Converts this UVPoint to a Point in X/Y coordinates.
67+
/// </summary>
68+
/// <returns>A Point with X/Y coordinates.</returns>
69+
public Point ToPoint() => new Point(X, Y);
70+
71+
/// <summary>
72+
/// Creates a new UVPoint with the specified U value, preserving V and orientation.
73+
/// </summary>
74+
/// <param name="u">The new U value.</param>
75+
/// <returns>A new UVPoint with the updated U value.</returns>
76+
public UVPoint WithU(double u) => new UVPoint(Orientation, u, V);
77+
78+
/// <summary>
79+
/// Creates a new UVPoint with the specified V value, preserving U and orientation.
80+
/// </summary>
81+
/// <param name="v">The new V value.</param>
82+
/// <returns>A new UVPoint with the updated V value.</returns>
83+
public UVPoint WithV(double v) => new UVPoint(Orientation, U, v);
84+
}
85+
86+
/// <summary>
87+
/// Extension methods for UVPoint operations on UI elements.
88+
/// </summary>
89+
internal static class UVPointExtensions
90+
{
91+
/// <param name="element">The element to position.</param>
92+
extension(UIElement? element)
93+
{
94+
/// <summary>
95+
/// Sets the Canvas position of an element using UVPoint coordinates.
96+
/// For horizontal: sets Canvas.Left from U.
97+
/// For vertical: sets Canvas.Top from U.
98+
/// </summary>
99+
/// <param name="point">The UVPoint containing position data.</param>
100+
public void SetCanvasU(UVPoint point)
101+
{
102+
if (element == null) return;
103+
104+
if (point.Orientation == Orientation.Horizontal)
105+
{
106+
Canvas.SetLeft(element, point.U);
107+
}
108+
else
109+
{
110+
Canvas.SetTop(element, point.U);
111+
}
112+
}
113+
114+
/// <summary>
115+
/// Gets the Canvas position of an element as a U coordinate.
116+
/// For horizontal: returns Canvas.Left.
117+
/// For vertical: returns Canvas.Top.
118+
/// </summary>
119+
/// <param name="orientation">The orientation context.</param>
120+
/// <returns>The position along the primary axis.</returns>
121+
public double GetCanvasU(Orientation orientation)
122+
{
123+
if (element == null) return 0;
124+
125+
return orientation == Orientation.Horizontal
126+
? Canvas.GetLeft(element)
127+
: Canvas.GetTop(element);
128+
}
129+
130+
/// <summary>
131+
/// Clears the Canvas position property for the primary axis.
132+
/// For horizontal: clears Canvas.Left.
133+
/// For vertical: clears Canvas.Top.
134+
/// </summary>
135+
/// <param name="orientation">The orientation context.</param>
136+
public void ClearCanvasU(Orientation orientation)
137+
{
138+
if (element == null) return;
139+
140+
if (orientation == Orientation.Horizontal)
141+
{
142+
element.ClearValue(Canvas.LeftProperty);
143+
}
144+
else
145+
{
146+
element.ClearValue(Canvas.TopProperty);
147+
}
148+
}
149+
150+
/// <summary>
151+
/// Clears the Canvas position property for the secondary axis.
152+
/// For horizontal: clears Canvas.Top.
153+
/// For vertical: clears Canvas.Left.
154+
/// </summary>
155+
/// <param name="orientation">The orientation context.</param>
156+
public void ClearCanvasV(Orientation orientation)
157+
{
158+
if (element == null) return;
159+
160+
if (orientation == Orientation.Horizontal)
161+
{
162+
element.ClearValue(Canvas.TopProperty);
163+
}
164+
else
165+
{
166+
element.ClearValue(Canvas.LeftProperty);
167+
}
168+
}
169+
}
170+
171+
/// <param name="element">The element to measure.</param>
172+
extension(FrameworkElement? element)
173+
{
174+
/// <summary>
175+
/// Gets the size along the primary axis (Width for horizontal, Height for vertical).
176+
/// </summary>
177+
/// <param name="orientation">The orientation context.</param>
178+
/// <returns>The size along the primary axis.</returns>
179+
public double GetSizeU(Orientation orientation)
180+
{
181+
if (element == null) return 0;
182+
183+
return orientation == Orientation.Horizontal
184+
? element.Width
185+
: element.Height;
186+
}
187+
188+
/// <summary>
189+
/// Gets the actual size along the primary axis (ActualWidth for horizontal, ActualHeight for vertical).
190+
/// </summary>
191+
/// <param name="orientation">The orientation context.</param>
192+
/// <returns>The actual size along the primary axis.</returns>
193+
public double GetActualSizeU(Orientation orientation)
194+
{
195+
if (element == null) return 0;
196+
197+
return orientation == Orientation.Horizontal
198+
? element.ActualWidth
199+
: element.ActualHeight;
200+
}
201+
}
202+
203+
/// <summary>
204+
/// Gets the desired size along the primary axis from DesiredSize.
205+
/// </summary>
206+
/// <param name="element">The element to measure.</param>
207+
/// <param name="orientation">The orientation context.</param>
208+
/// <returns>The desired size along the primary axis.</returns>
209+
public static double GetDesiredSizeU(this UIElement? element, Orientation orientation)
210+
{
211+
if (element == null) return 0;
212+
213+
return orientation == Orientation.Horizontal
214+
? element.DesiredSize.Width
215+
: element.DesiredSize.Height;
216+
}
217+
218+
/// <summary>
219+
/// Sets the size along the primary axis (Width for horizontal, Height for vertical).
220+
/// </summary>
221+
/// <param name="element">The element to resize.</param>
222+
/// <param name="orientation">The orientation context.</param>
223+
/// <param name="size">The size value to set.</param>
224+
public static void SetSizeU(this FrameworkElement? element, Orientation orientation, double size)
225+
{
226+
if (element == null) return;
227+
228+
if (orientation == Orientation.Horizontal)
229+
{
230+
element.Width = size;
231+
}
232+
else
233+
{
234+
element.Height = size;
235+
}
236+
}
237+
}

components/RangeSelector/src/RangeSelector.Input.Drag.cs

Lines changed: 46 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,12 @@ public partial class RangeSelector : Control
1111
{
1212
private void MinThumb_DragDelta(object sender, DragDeltaEventArgs e)
1313
{
14-
var isHorizontal = Orientation == Orientation.Horizontal;
15-
_absolutePosition += isHorizontal ? e.HorizontalChange : e.VerticalChange;
14+
_absolutePosition += Orientation == Orientation.Horizontal ? e.HorizontalChange : e.VerticalChange;
1615

17-
RangeStart = isHorizontal
18-
? DragThumb(_minThumb, 0, Canvas.GetLeft(_maxThumb), _absolutePosition)
19-
: DragThumbVertical(_minThumb, Canvas.GetTop(_maxThumb), DragWidth(), _absolutePosition);
16+
var maxThumbPos = _maxThumb.GetCanvasU(Orientation);
17+
RangeStart = Orientation == Orientation.Horizontal
18+
? DragThumb(_minThumb, 0, maxThumbPos, _absolutePosition)
19+
: DragThumb(_minThumb, maxThumbPos, DragWidth(), _absolutePosition);
2020

2121
if (_toolTipText != null)
2222
{
@@ -26,12 +26,12 @@ private void MinThumb_DragDelta(object sender, DragDeltaEventArgs e)
2626

2727
private void MaxThumb_DragDelta(object sender, DragDeltaEventArgs e)
2828
{
29-
var isHorizontal = Orientation == Orientation.Horizontal;
30-
_absolutePosition += isHorizontal ? e.HorizontalChange : e.VerticalChange;
29+
_absolutePosition += Orientation == Orientation.Horizontal ? e.HorizontalChange : e.VerticalChange;
3130

32-
RangeEnd = isHorizontal
33-
? DragThumb(_maxThumb, Canvas.GetLeft(_minThumb), DragWidth(), _absolutePosition)
34-
: DragThumbVertical(_maxThumb, 0, Canvas.GetTop(_minThumb), _absolutePosition);
31+
var minThumbPos = _minThumb.GetCanvasU(Orientation);
32+
RangeEnd = Orientation == Orientation.Horizontal
33+
? DragThumb(_maxThumb, minThumbPos, DragWidth(), _absolutePosition)
34+
: DragThumb(_maxThumb, 0, minThumbPos, _absolutePosition);
3535

3636
if (_toolTipText != null)
3737
{
@@ -73,49 +73,48 @@ private void Thumb_DragCompleted(object sender, DragCompletedEventArgs e)
7373

7474
private double DragWidth()
7575
{
76-
return Orientation == Orientation.Horizontal
77-
? _containerCanvas!.ActualWidth - _maxThumb!.Width
78-
: _containerCanvas!.ActualHeight - _maxThumb!.Height;
76+
return _containerCanvas.GetActualSizeU(Orientation) - _maxThumb.GetSizeU(Orientation);
7977
}
8078

8179
private double DragThumb(Thumb? thumb, double min, double max, double nextPos)
8280
{
83-
nextPos = Math.Max(min, nextPos);
84-
nextPos = Math.Min(max, nextPos);
85-
86-
Canvas.SetLeft(thumb, nextPos);
87-
88-
if (_toolTip != null && thumb != null)
89-
{
90-
var thumbCenter = nextPos + (thumb.Width / 2);
91-
_toolTip.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
92-
var ttWidth = _toolTip.ActualWidth / 2;
93-
94-
Canvas.SetLeft(_toolTip, thumbCenter - ttWidth);
95-
}
96-
97-
return Minimum + ((nextPos / DragWidth()) * (Maximum - Minimum));
98-
}
81+
var isHorizontal = Orientation == Orientation.Horizontal;
9982

100-
private double DragThumbVertical(Thumb? thumb, double min, double max, double nextPos)
101-
{
10283
nextPos = Math.Max(min, nextPos);
10384
nextPos = Math.Min(max, nextPos);
10485

105-
Canvas.SetTop(thumb, nextPos);
86+
// Position the thumb
87+
var thumbPos = new UVPoint(Orientation, nextPos);
88+
thumb.SetCanvasU(thumbPos);
10689

90+
// Position the tooltip
10791
if (_toolTip != null && thumb != null)
10892
{
109-
var thumbCenter = nextPos + (thumb.Height / 2);
93+
var thumbSize = thumb.GetSizeU(Orientation);
94+
var thumbCenter = nextPos + (thumbSize / 2);
11095
_toolTip.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
111-
var ttHeight = _toolTip.DesiredSize.Height / 2;
96+
var ttHalfSize = isHorizontal
97+
? _toolTip.ActualWidth / 2
98+
: _toolTip.DesiredSize.Height / 2;
11299

113-
Canvas.SetTop(_toolTip, thumbCenter - ttHeight);
114-
UpdateToolTipPositionForVertical();
100+
var toolTipPos = new UVPoint(Orientation, thumbCenter - ttHalfSize);
101+
_toolTip.SetCanvasU(toolTipPos);
102+
103+
if (!isHorizontal)
104+
{
105+
UpdateToolTipPositionForVertical();
106+
}
115107
}
116108

117-
// Invert: top position (0) = Maximum, bottom position (DragWidth) = Minimum
118-
return Maximum - ((nextPos / DragWidth()) * (Maximum - Minimum));
109+
// Calculate the range value
110+
// Horizontal: left (0) = Minimum, right (DragWidth) = Maximum
111+
// Vertical: top (0) = Maximum, bottom (DragWidth) = Minimum (inverted)
112+
var ratio = nextPos / DragWidth();
113+
var range = Maximum - Minimum;
114+
115+
return isHorizontal
116+
? Minimum + (ratio * range)
117+
: Maximum - (ratio * range);
119118
}
120119

121120
private void Thumb_DragStarted(Thumb thumb)
@@ -124,7 +123,7 @@ private void Thumb_DragStarted(Thumb thumb)
124123
var otherThumb = useMin ? _maxThumb : _minThumb;
125124
var isHorizontal = Orientation == Orientation.Horizontal;
126125

127-
_absolutePosition = isHorizontal ? Canvas.GetLeft(thumb) : Canvas.GetTop(thumb);
126+
_absolutePosition = thumb.GetCanvasU(Orientation);
128127
Canvas.SetZIndex(thumb, 10);
129128
Canvas.SetZIndex(otherThumb, 0);
130129
_oldValue = RangeStart;
@@ -147,15 +146,15 @@ private void Thumb_DragStarted(Thumb thumb)
147146

148147
_toolTip.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
149148

150-
if (isHorizontal)
151-
{
152-
var thumbCenter = _absolutePosition + (thumb.Width / 2);
153-
Canvas.SetLeft(_toolTip, thumbCenter - (_toolTip.DesiredSize.Width / 2));
154-
}
155-
else
149+
var thumbSize = thumb.GetSizeU(Orientation);
150+
var thumbCenter = _absolutePosition + (thumbSize / 2);
151+
var ttHalfSize = _toolTip.GetDesiredSizeU(Orientation) / 2;
152+
153+
var toolTipPos = new UVPoint(Orientation, thumbCenter - ttHalfSize);
154+
_toolTip.SetCanvasU(toolTipPos);
155+
156+
if (!isHorizontal)
156157
{
157-
var thumbCenter = _absolutePosition + (thumb.Height / 2);
158-
Canvas.SetTop(_toolTip, thumbCenter - (_toolTip.DesiredSize.Height / 2));
159158
UpdateToolTipPositionForVertical();
160159
}
161160
}

0 commit comments

Comments
 (0)