Skip to content
Open
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
d133c31
Initial RangeSlider implementation
stephenquan Nov 28, 2025
8ce2ba7
Merge branch 'CommunityToolkit:main' into feature/rangeslider
stephenquan Dec 2, 2025
dd9ed67
Implemented various Copilot review suggestions
stephenquan Dec 2, 2025
bc29d3a
Merge branch 'main' into feature/rangeslider
stephenquan Dec 2, 2025
0c8df7e
Use BindableProperty source-gen. Use IsOneOf extension method. Update…
stephenquan Dec 3, 2025
99f7074
Update XML doc for RangeSlider focus mode as per Copilot's suggestion
stephenquan Dec 3, 2025
61cbc63
Corrected XML doc for FocusMode property as per Copilot's feedback
stephenquan Dec 3, 2025
00cb4c5
Various XML doc improvements. Refactor UpdateFocusedSliderLayout for …
stephenquan Dec 3, 2025
424f804
Update XML doc for PlatformThumbSize
stephenquan Dec 3, 2025
3b4fe7a
Implement maxValue calculation as suggested by Copilot
stephenquan Dec 3, 2025
73e1b60
Add RangeSliderTests.cs
stephenquan Dec 3, 2025
1962148
Implement consistent getter for static defaults
stephenquan Dec 3, 2025
f20faae
Add division by zero guard. Corrected label binding in sample.
stephenquan Dec 3, 2025
17f9568
Use consistent naming in event handlers
stephenquan Dec 3, 2025
1533249
Move BindableProperties to top of the file. Also moved SetFocusMode. …
stephenquan Dec 3, 2025
9d2e7a0
Use Loaded instead of Dispatcher.Dispatch to finalize initialization
stephenquan Dec 3, 2025
e211320
Revert Loaded back to Dispatcher.Dispatch
stephenquan Dec 4, 2025
6a85e2f
Provider alternate constructor workaround for unit tests
stephenquan Dec 4, 2025
ec6346a
Improve PlatformThumbSize XML doc. Corrected spelling in test method …
stephenquan Dec 4, 2025
dddf6a1
Merge branch 'main' into feature/rangeslider
stephenquan Dec 11, 2025
f0c2269
Use new BindableProperty initializer
stephenquan Dec 21, 2025
d4db602
Refactor CalculateValueFromSliderValue and CalculateValueFromSliderVa…
stephenquan Dec 21, 2025
760c451
Merge branch 'main' into feature/rangeslider
ne0rrmatrix Jan 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions samples/CommunityToolkit.Maui.Sample/AppShell.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ public partial class AppShell : Shell
CreateViewModelMapping<PopupSizingIssuesPage, PopupSizingIssuesViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<ShowPopupInOnAppearingPage, ShowPopupInOnAppearingPageViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<StylePopupPage, StylePopupViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<RangeSliderPage, RangeSliderViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<RatingViewCsharpPage, RatingViewCsharpViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<RatingViewShowcasePage, RatingViewShowcaseViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
CreateViewModelMapping<RatingViewXamlPage, RatingViewXamlViewModel, ViewsGalleryPage, ViewsGalleryViewModel>(),
Expand Down
1 change: 1 addition & 0 deletions samples/CommunityToolkit.Maui.Sample/MauiProgram.cs
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,7 @@ static void RegisterViewsAndViewModels(in IServiceCollection services)
services.AddTransientWithShellRoute<PopupsPage, PopupsViewModel>();
services.AddTransientWithShellRoute<PopupLayoutAlignmentPage, PopupLayoutAlignmentViewModel>();
services.AddTransientWithShellRoute<PopupPositionPage, PopupPositionViewModel>();
services.AddTransientWithShellRoute<RangeSliderPage, RangeSliderViewModel>();
services.AddTransientWithShellRoute<RatingViewCsharpPage, RatingViewCsharpViewModel>();
services.AddTransientWithShellRoute<RatingViewShowcasePage, RatingViewShowcaseViewModel>();
services.AddTransientWithShellRoute<RatingViewXamlPage, RatingViewXamlViewModel>();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
<?xml version="1.0" encoding="utf-8" ?>
<pages:BasePage
x:Class="CommunityToolkit.Maui.Sample.Pages.Views.RangeSliderPage"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:mct="http://schemas.microsoft.com/dotnet/2022/maui/toolkit"
xmlns:pages="clr-namespace:CommunityToolkit.Maui.Sample.Pages"
xmlns:sample="clr-namespace:CommunityToolkit.Maui.Sample"
xmlns:viewModels="clr-namespace:CommunityToolkit.Maui.Sample.ViewModels.Views"
Title="RangeSlider"
Padding="12,12,12,0"
x:DataType="viewModels:RangeSliderViewModel"
x:TypeArguments="viewModels:RangeSliderViewModel">

<ScrollView>
<VerticalStackLayout Spacing="10">
<VerticalStackLayout.Resources>
<ResourceDictionary>
<Style x:Key="Heading" TargetType="Label">
<Setter Property="VerticalTextAlignment" Value="Center" />
<Setter Property="HorizontalTextAlignment" Value="Center" />
<Setter Property="FontSize" Value="12" />
<Setter Property="HorizontalOptions" Value="Center" />
<Setter Property="LineBreakMode" Value="WordWrap" />
<Setter Property="Margin" Value="10" />
</Style>
<Style x:Key="HR" TargetType="Line">
<Setter Property="Stroke" Value="{AppThemeBinding Light=Black, Dark=White}" />
<Setter Property="X2" Value="300" />
<Setter Property="HorizontalOptions" Value="Center" />
</Style>
</ResourceDictionary>
</VerticalStackLayout.Resources>

<Label Style="{StaticResource Heading}" Text="Simple RangeSlider" />

<Grid ColumnDefinitions="50,*,50" ColumnSpacing="10">
<Label Text="{Binding SimpleLowerValue, StringFormat='{}{0:F1}'}" VerticalOptions="Center" />
<mct:RangeSlider
Grid.Column="1"
HorizontalOptions="Fill"
LowerValue="{Binding SimpleLowerValue, Mode=TwoWay}"
MaximumValue="200"
MinimumValue="100"
UpperValue="{Binding SimpleUpperValue, Mode=TwoWay}"
WidthRequest="-1" />
<Label
Grid.Column="2"
Text="{Binding SimpleUpperValue, StringFormat='{}{0:F1}'}"
VerticalOptions="Center" />
</Grid>

<Line Style="{StaticResource HR}" />

<Label Style="{StaticResource Heading}" Text="RangeSlider with StepSize" />

<Grid ColumnDefinitions="50,*,50" ColumnSpacing="10">
<Label Text="{Binding LowerValueWithStep, StringFormat='{}{0:F1}'}" VerticalOptions="Center" />
<Border Grid.Column="1" HorizontalOptions="Fill">
<mct:RangeSlider
HorizontalOptions="Fill"
LowerValue="{Binding LowerValueWithStep, Mode=TwoWay}"
MaximumValue="200"
MinimumValue="100"
StepSize="5"
UpperValue="{Binding UpperValueWithStep, Mode=TwoWay}"
WidthRequest="-1" />
</Border>
<Label
Grid.Column="2"
Text="{Binding UpperValueWithStep, StringFormat='{}{0:F1}'}"
VerticalOptions="Center" />
</Grid>

<Line Style="{StaticResource HR}" />

<Label Style="{StaticResource Heading}" Text="RangeSlider with Minimum/Maximum swapped for descending behavior" />

<Grid ColumnDefinitions="50,*,50" ColumnSpacing="10">
<Label Text="{Binding LowerValueDescending, StringFormat='{}{0:F1}'}" VerticalOptions="Center" />
<Border Grid.Column="1" HorizontalOptions="Fill">
<mct:RangeSlider
HorizontalOptions="Fill"
LowerValue="{Binding LowerValueDescending, Mode=TwoWay}"
MaximumValue="100"
MinimumValue="200"
UpperValue="{Binding UpperValueDescending, Mode=TwoWay}"
WidthRequest="-1" />
</Border>
<Label
Grid.Column="2"
Text="{Binding UpperValueDescending, StringFormat='{}{0:F1}'}"
VerticalOptions="Center" />
</Grid>

<Line Style="{StaticResource HR}" />

<Label Style="{StaticResource Heading}" Text="RangeSlider with Minimum/Maximum swapped for descending with step behavior" />

<Grid ColumnDefinitions="50,*,50" ColumnSpacing="10">
<Label Text="{Binding LowerValueDescendingWithStep, StringFormat='{}{0:F1}'}" VerticalOptions="Center" />
<Border Grid.Column="1" HorizontalOptions="Fill">
<mct:RangeSlider
HorizontalOptions="Fill"
LowerValue="{Binding LowerValueDescendingWithStep, Mode=TwoWay}"
MaximumValue="100"
MinimumValue="200"
StepSize="5"
UpperValue="{Binding UpperValueDescendingWithStep, Mode=TwoWay}"
WidthRequest="-1" />
</Border>
<Label
Grid.Column="2"
Text="{Binding UpperValueDescendingWithStep, StringFormat='{}{0:F1}'}"
VerticalOptions="Center" />
</Grid>

<Line Style="{StaticResource HR}" />

<Label Style="{StaticResource Heading}" Text="RangeSlider with custom thumb and track colors" />

<Grid ColumnDefinitions="50,*,50" ColumnSpacing="10">
<Label Text="{Binding LowerValueWithCustomStyle, StringFormat='{}{0:F1}'}" VerticalOptions="Center" />
<Border Grid.Column="1" HorizontalOptions="Fill">
<mct:RangeSlider
HorizontalOptions="Fill"
InnerTrackColor="Orange"
InnerTrackCornerRadius="5"
InnerTrackSize="10"
LowerThumbColor="Red"
LowerValue="{Binding LowerValueWithCustomStyle, Mode=TwoWay}"
MaximumValue="200"
MinimumValue="100"
OuterTrackColor="DarkGray"
OuterTrackCornerRadius="7"
OuterTrackSize="14"
UpperThumbColor="Green"
UpperValue="{Binding UpperValueWithCustomStyle, Mode=TwoWay}"
WidthRequest="-1" />
</Border>
<Label
Grid.Column="2"
Text="{Binding UpperValueWithCustomStyle, StringFormat='{}{0:F1}'}"
VerticalOptions="Center" />
</Grid>

</VerticalStackLayout>
</ScrollView>
</pages:BasePage>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using CommunityToolkit.Maui.Sample.ViewModels.Views;

namespace CommunityToolkit.Maui.Sample.Pages.Views;

public partial class RangeSliderPage : BasePage<RangeSliderViewModel>
{
public RangeSliderPage(RangeSliderViewModel viewModel) : base(viewModel)
{
InitializeComponent();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using CommunityToolkit.Mvvm.ComponentModel;

namespace CommunityToolkit.Maui.Sample.ViewModels.Views;

public partial class RangeSliderViewModel : BaseViewModel
{
[ObservableProperty]
public partial double SimpleLowerValue { get; set; } = 125;

[ObservableProperty]
public partial double SimpleUpperValue { get; set; } = 175;

[ObservableProperty]
public partial double LowerValueWithCustomStyle { get; set; } = 125;

[ObservableProperty]
public partial double UpperValueWithCustomStyle { get; set; } = 175;

[ObservableProperty]
public partial double LowerValueWithStep { get; set; } = 125;

[ObservableProperty]
public partial double UpperValueWithStep { get; set; } = 175;

[ObservableProperty]
public partial double LowerValueDescending { get; set; } = 175;

[ObservableProperty]
public partial double UpperValueDescending { get; set; } = 125;

[ObservableProperty]
public partial double LowerValueDescendingWithStep { get; set; } = 175;

[ObservableProperty]
public partial double UpperValueDescendingWithStep { get; set; } = 125;
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public sealed partial class ViewsGalleryViewModel() : BaseGalleryViewModel(
SectionModel.Create<MediaElementCarouselViewViewModel>("MediaElement in CarouselView", Colors.Red, "MediaElement can be used inside a DataTemplate in a CarouselView"),
SectionModel.Create<MediaElementCollectionViewViewModel>("MediaElement in CollectionView", Colors.Red, "MediaElement can be used inside a DataTemplate in a CollectionView"),
SectionModel.Create<MediaElementMultipleWindowsViewModel>("MediaElement in a Multi-Window Application", Colors.Red, "Demonstrates that MediaElement can be used inside a DataTemplate simultaneously on multiple windows"),
SectionModel.Create<RangeSliderViewModel>("RangeSlider Page", Colors.Red, "A page demonstrating RangeSlider in various styles."),
SectionModel.Create<RatingViewShowcaseViewModel>("RatingView Showcase Page", Colors.Red, "A page with showcase examples for the RatingView control."),
SectionModel.Create<RatingViewXamlViewModel>("RatingView XAML Page", Colors.Red, "A page demonstrating the RatingView control and possible uses using XAML"),
SectionModel.Create<RatingViewCsharpViewModel>("RatingView C# Page", Colors.Red, "A page demonstrating the RatingView control and possible uses using C#"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
namespace CommunityToolkit.Maui.Core;

/// <summary>
/// Default Values for RangeSlider
/// </summary>
public static class RangeSliderDefaults
{
/// <summary>
/// Default width request
/// </summary>
public const double WidthRequest = 200;

/// <summary>
/// Default for Minimum Value
/// </summary>
public const double MinimumValue = 0;

/// <summary>
/// Default for Maximum Value
/// </summary>
public const double MaximumValue = 1;

/// <summary>
/// Default for Lower Value
/// </summary>
public const double LowerValue = 0;

/// <summary>
/// Default for Upper Value
/// </summary>
public const double UpperValue = 1;

/// <summary>
/// Default for Step Size
/// </summary>
public const double StepSize = 0;

/// <summary>
/// Default value for Lower Thumb Color
/// </summary>
public static Color LowerThumbColor { get; } = Colors.Gray;

/// <summary>
/// Default value for Upper Thumb Color
/// </summary>
public static Color UpperThumbColor { get; } = Colors.Gray;

/// <summary>
/// Default value for Track Color
/// </summary>
public static Color InnerTrackColor { get; } = Colors.Pink;

/// <summary>
/// Default value for Track Size
/// </summary>
public const double InnerTrackSize = 6;

/// <summary>
/// Default value for Inner Track Radius
/// </summary>
public static CornerRadius InnerTrackCornerRadius { get; } = new(3);

/// <summary>
/// Default value for Outer Track Color
/// </summary>
public static Color OuterTrackColor { get; } = Colors.LightGray;

/// <summary>
/// Default value for Outer Track Size
/// </summary>
public const double OuterTrackSize = 6;

/// <summary>
/// Default value for Outer Track Radius
/// </summary>
public static CornerRadius OuterTrackCornerRadius { get; } = new(3);

/// <summary>
/// Default value for Focus Mode
/// </summary>
public const RangeSliderFocusMode FocusMode = RangeSliderFocusMode.Default;
}
20 changes: 20 additions & 0 deletions src/CommunityToolkit.Maui.Core/Primitives/RangeSliderFocusMode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace CommunityToolkit.Maui.Core;

/// <summary>
/// Enum for RangeSlider focus mode
/// </summary>
public enum RangeSliderFocusMode
{
/// <summary>
/// Balance the focus between the lower and upper thumbs
/// </summary>
Default,
/// <summary>
/// The lower thumb has focus
/// </summary>
Lower,
/// <summary>
/// The upper thumb has focus
/// </summary>
Upper,
}
Loading
Loading