Skip to content
Open
6 changes: 6 additions & 0 deletions components/RangeSelector/samples/RangeSelector.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ A `RangeSelector` is pretty similar to a regular `Slider`, and shares some of it

> [!Sample RangeSelectorSample]

## Vertical Orientation

The `RangeSelector` also supports vertical orientation. Set the `Orientation` property to `Vertical` to display the range selector vertically.

> [!Sample RangeSelectorVerticalSample]

> [!NOTE]
> If you are using a RangeSelector within a ScrollViewer you'll need to add some codes. This is because by default, the ScrollViewer will block the thumbs of the RangeSelector to capture the pointer.

Expand Down
25 changes: 25 additions & 0 deletions components/RangeSelector/samples/RangeSelectorVerticalSample.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<!-- Licensed to the .NET Foundation under one or more agreements. The .NET Foundation licenses this file to you under the MIT license. See the LICENSE file in the project root for more information. -->
<Page x:Class="RangeSelectorExperiment.Samples.RangeSelectorVerticalSample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:RangeSelectorExperiment.Samples"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">

<Grid MinWidth="86"
MinHeight="320"
MaxHeight="560"
VerticalAlignment="Stretch">
<controls:RangeSelector x:Name="rangeSelector"
HorizontalAlignment="Center"
IsEnabled="{x:Bind Enable, Mode=OneWay}"
Maximum="{x:Bind Maximum, Mode=OneWay}"
Minimum="{x:Bind Minimum, Mode=OneWay}"
Orientation="Vertical"
RangeEnd="100"
RangeStart="0"
StepFrequency="{x:Bind StepFrequency, Mode=OneWay}" />
</Grid>
</Page>
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using CommunityToolkit.WinUI.Controls;

namespace RangeSelectorExperiment.Samples;

[ToolkitSampleNumericOption("Minimum", 0, 0, 100, 1, false, Title = "Minimum")]
[ToolkitSampleNumericOption("Maximum", 100, 0, 100, 1, false, Title = "Maximum")]
[ToolkitSampleNumericOption("StepFrequency", 1, 0, 10, 1, false, Title = "StepFrequency")]
[ToolkitSampleBoolOption("Enable", true, Title = "IsEnabled")]

[ToolkitSample(id: nameof(RangeSelectorVerticalSample), "Vertical RangeSelector", description: $"A sample for showing how to create and use a vertical {nameof(RangeSelector)} control.")]
public sealed partial class RangeSelectorVerticalSample : Page
{
public RangeSelectorVerticalSample()
{
this.InitializeComponent();
}
}
78 changes: 64 additions & 14 deletions components/RangeSelector/src/RangeSelector.Input.Drag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,12 @@ public partial class RangeSelector : Control
{
private void MinThumb_DragDelta(object sender, DragDeltaEventArgs e)
{
_absolutePosition += e.HorizontalChange;
var isHorizontal = Orientation == Orientation.Horizontal;
_absolutePosition += isHorizontal ? e.HorizontalChange : e.VerticalChange;

RangeStart = DragThumb(_minThumb, 0, DragWidth(), _absolutePosition);
RangeStart = isHorizontal
? DragThumb(_minThumb, 0, Canvas.GetLeft(_maxThumb), _absolutePosition)
: DragThumbVertical(_minThumb, Canvas.GetTop(_maxThumb), DragWidth(), _absolutePosition);

if (_toolTipText != null)
{
Expand All @@ -23,9 +26,12 @@ private void MinThumb_DragDelta(object sender, DragDeltaEventArgs e)

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

RangeEnd = DragThumb(_maxThumb, 0, DragWidth(), _absolutePosition);
RangeEnd = isHorizontal
? DragThumb(_maxThumb, Canvas.GetLeft(_minThumb), DragWidth(), _absolutePosition)
: DragThumbVertical(_maxThumb, 0, Canvas.GetTop(_minThumb), _absolutePosition);

if (_toolTipText != null)
{
Expand Down Expand Up @@ -67,7 +73,9 @@ private void Thumb_DragCompleted(object sender, DragCompletedEventArgs e)

private double DragWidth()
{
return _containerCanvas!.ActualWidth - _maxThumb!.Width;
return Orientation == Orientation.Horizontal
? _containerCanvas!.ActualWidth - _maxThumb!.Width
: _containerCanvas!.ActualHeight - _maxThumb!.Height;
}

private double DragThumb(Thumb? thumb, double min, double max, double nextPos)
Expand All @@ -89,26 +97,68 @@ private double DragThumb(Thumb? thumb, double min, double max, double nextPos)
return Minimum + ((nextPos / DragWidth()) * (Maximum - Minimum));
}

private double DragThumbVertical(Thumb? thumb, double min, double max, double nextPos)
{
nextPos = Math.Max(min, nextPos);
nextPos = Math.Min(max, nextPos);

Canvas.SetTop(thumb, nextPos);

if (_toolTip != null && thumb != null)
{
var thumbCenter = nextPos + (thumb.Height / 2);
_toolTip.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
var ttHeight = _toolTip.DesiredSize.Height / 2;

Canvas.SetTop(_toolTip, thumbCenter - ttHeight);
UpdateToolTipPositionForVertical();
}

// Invert: top position (0) = Maximum, bottom position (DragWidth) = Minimum
return Maximum - ((nextPos / DragWidth()) * (Maximum - Minimum));
}

private void Thumb_DragStarted(Thumb thumb)
{
var useMin = thumb == _minThumb;
var otherThumb = useMin ? _maxThumb : _minThumb;
var isHorizontal = Orientation == Orientation.Horizontal;

_absolutePosition = Canvas.GetLeft(thumb);
_absolutePosition = isHorizontal ? Canvas.GetLeft(thumb) : Canvas.GetTop(thumb);
Canvas.SetZIndex(thumb, 10);
Canvas.SetZIndex(otherThumb, 0);
_oldValue = RangeStart;

if (_toolTip != null)
{
_toolTip.Visibility = Visibility.Visible;
var thumbCenter = _absolutePosition + (thumb.Width / 2);
_toolTip.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
var ttWidth = _toolTip.ActualWidth / 2;
Canvas.SetLeft(_toolTip, thumbCenter - ttWidth);

if (_toolTipText != null)
UpdateToolTipText(this, _toolTipText, useMin ? RangeStart : RangeEnd);
if (!isHorizontal && VerticalToolTipPlacement == VerticalToolTipPlacement.None)
{
_toolTip.Visibility = Visibility.Collapsed;
}
else
{
_toolTip.Visibility = Visibility.Visible;

// Update tooltip text first so Measure gets accurate size
if (_toolTipText != null)
{
UpdateToolTipText(this, _toolTipText, useMin ? RangeStart : RangeEnd);
}

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

if (isHorizontal)
{
var thumbCenter = _absolutePosition + (thumb.Width / 2);
Canvas.SetLeft(_toolTip, thumbCenter - (_toolTip.DesiredSize.Width / 2));
}
else
{
var thumbCenter = _absolutePosition + (thumb.Height / 2);
Canvas.SetTop(_toolTip, thumbCenter - (_toolTip.DesiredSize.Height / 2));
UpdateToolTipPositionForVertical();
}
}
}

VisualStateManager.GoToState(this, useMin ? MinPressedState : MaxPressedState, true);
Expand Down
76 changes: 44 additions & 32 deletions components/RangeSelector/src/RangeSelector.Input.Key.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,56 +20,66 @@ public partial class RangeSelector : Control

private void MinThumb_KeyDown(object sender, KeyRoutedEventArgs e)
{
var isHorizontal = Orientation == Orientation.Horizontal;
var handled = false;

switch (e.Key)
{
case VirtualKey.Left:
case VirtualKey.Left when isHorizontal:
case VirtualKey.Down when !isHorizontal:
RangeStart -= StepFrequency;
SyncThumbs(fromMinKeyDown: true);
if (_toolTip != null)
{
_toolTip.Visibility = Visibility.Visible;
}

e.Handled = true;
handled = true;
break;
case VirtualKey.Right:
case VirtualKey.Right when isHorizontal:
case VirtualKey.Up when !isHorizontal:
RangeStart += StepFrequency;
SyncThumbs(fromMinKeyDown: true);
if (_toolTip != null)
{
_toolTip.Visibility = Visibility.Visible;
}

e.Handled = true;
handled = true;
break;
}

if (handled)
{
SyncThumbs(fromMinKeyDown: true);
ShowToolTip();
e.Handled = true;
}
}

private void MaxThumb_KeyDown(object sender, KeyRoutedEventArgs e)
{
var isHorizontal = Orientation == Orientation.Horizontal;
var handled = false;

switch (e.Key)
{
case VirtualKey.Left:
case VirtualKey.Left when isHorizontal:
case VirtualKey.Down when !isHorizontal:
RangeEnd -= StepFrequency;
SyncThumbs(fromMaxKeyDown: true);
if (_toolTip != null)
{
_toolTip.Visibility = Visibility.Visible;
}

e.Handled = true;
handled = true;
break;
case VirtualKey.Right:
case VirtualKey.Right when isHorizontal:
case VirtualKey.Up when !isHorizontal:
RangeEnd += StepFrequency;
SyncThumbs(fromMaxKeyDown: true);
if (_toolTip != null)
{
_toolTip.Visibility = Visibility.Visible;
}

e.Handled = true;
handled = true;
break;
}

if (handled)
{
SyncThumbs(fromMaxKeyDown: true);
ShowToolTip();
e.Handled = true;
}
}

private void ShowToolTip()
{
var isHorizontal = Orientation == Orientation.Horizontal;
if (!isHorizontal && VerticalToolTipPlacement == VerticalToolTipPlacement.None) return;
if (_toolTip != null)
{
_toolTip.Visibility = Visibility.Visible;
}
}

private void Thumb_KeyUp(object sender, KeyRoutedEventArgs e)
Expand All @@ -78,6 +88,8 @@ private void Thumb_KeyUp(object sender, KeyRoutedEventArgs e)
{
case VirtualKey.Left:
case VirtualKey.Right:
case VirtualKey.Up:
case VirtualKey.Down:
if (_toolTip != null)
{
keyDebounceTimer.Debounce(
Expand Down
42 changes: 31 additions & 11 deletions components/RangeSelector/src/RangeSelector.Input.Pointer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,12 @@ private void ContainerCanvas_PointerEntered(object sender, PointerRoutedEventArg

private void ContainerCanvas_PointerExited(object sender, PointerRoutedEventArgs e)
{
var position = e.GetCurrentPoint(_containerCanvas).Position.X;
var normalizedPosition = ((position / DragWidth()) * (Maximum - Minimum)) + Minimum;
var isHorizontal = Orientation == Orientation.Horizontal;
var point = e.GetCurrentPoint(_containerCanvas).Position;
var position = isHorizontal ? point.X : point.Y;
var normalizedPosition = isHorizontal
? ((position / DragWidth()) * (Maximum - Minimum)) + Minimum
: Maximum - ((position / DragWidth()) * (Maximum - Minimum));

if (_pointerManipulatingMin)
{
Expand All @@ -40,13 +44,17 @@ private void ContainerCanvas_PointerExited(object sender, PointerRoutedEventArgs
_toolTip.Visibility = Visibility.Collapsed;
}

VisualStateManager.GoToState(this, "Normal", false);
VisualStateManager.GoToState(this, NormalState, false);
}

private void ContainerCanvas_PointerReleased(object sender, PointerRoutedEventArgs e)
{
var position = e.GetCurrentPoint(_containerCanvas).Position.X;
var normalizedPosition = ((position / DragWidth()) * (Maximum - Minimum)) + Minimum;
var isHorizontal = Orientation == Orientation.Horizontal;
var point = e.GetCurrentPoint(_containerCanvas).Position;
var position = isHorizontal ? point.X : point.Y;
var normalizedPosition = isHorizontal
? ((position / DragWidth()) * (Maximum - Minimum)) + Minimum
: Maximum - ((position / DragWidth()) * (Maximum - Minimum));

if (_pointerManipulatingMin)
{
Expand Down Expand Up @@ -74,31 +82,43 @@ private void ContainerCanvas_PointerReleased(object sender, PointerRoutedEventAr

private void ContainerCanvas_PointerMoved(object sender, PointerRoutedEventArgs e)
{
var position = e.GetCurrentPoint(_containerCanvas).Position.X;
var normalizedPosition = ((position / DragWidth()) * (Maximum - Minimum)) + Minimum;
var isHorizontal = Orientation == Orientation.Horizontal;
var point = e.GetCurrentPoint(_containerCanvas).Position;
var position = isHorizontal ? point.X : point.Y;

if (_pointerManipulatingMin)
{
RangeStart = DragThumb(_minThumb, 0, DragWidth(), position);
RangeStart = isHorizontal
? DragThumb(_minThumb, 0, Canvas.GetLeft(_maxThumb), position)
: DragThumbVertical(_minThumb, Canvas.GetTop(_maxThumb), DragWidth(), position);

if (_toolTipText is not null)
{
UpdateToolTipText(this, _toolTipText, RangeStart);
}
}
else if (_pointerManipulatingMax)
{
RangeEnd = isHorizontal
? DragThumb(_maxThumb, Canvas.GetLeft(_minThumb), DragWidth(), position)
: DragThumbVertical(_maxThumb, 0, Canvas.GetTop(_minThumb), position);

if (_toolTipText is not null)
{
RangeEnd = DragThumb(_maxThumb, 0, DragWidth(), position);
UpdateToolTipText(this, _toolTipText, RangeEnd);
}
}
}

private void ContainerCanvas_PointerPressed(object sender, PointerRoutedEventArgs e)
{
var position = e.GetCurrentPoint(_containerCanvas).Position.X;
var normalizedPosition = position * Math.Abs(Maximum - Minimum) / DragWidth();
var isHorizontal = Orientation == Orientation.Horizontal;
var point = e.GetCurrentPoint(_containerCanvas).Position;
var position = isHorizontal ? point.X : point.Y;
var normalizedPosition = isHorizontal
? position * Math.Abs(Maximum - Minimum) / DragWidth()
: Maximum - ((position / DragWidth()) * (Maximum - Minimum));

double upperValueDiff = Math.Abs(RangeEnd - normalizedPosition);
double lowerValueDiff = Math.Abs(RangeStart - normalizedPosition);

Expand Down
Loading