diff --git a/MainDemo.Wpf/RatingBar.xaml b/MainDemo.Wpf/RatingBar.xaml
index 4290be8238..42109a3a6e 100644
--- a/MainDemo.Wpf/RatingBar.xaml
+++ b/MainDemo.Wpf/RatingBar.xaml
@@ -197,6 +197,55 @@
VerticalAlignment="Top"
Text="{Binding ElementName=CustomRatingBarFractionalPreview, Path=Value, StringFormat=Rating: {0}}" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/MaterialDesignThemes.Wpf.Tests/RatingBarTests.cs b/MaterialDesignThemes.Wpf.Tests/RatingBarTests.cs
index a3016d6804..66925cd1ba 100644
--- a/MaterialDesignThemes.Wpf.Tests/RatingBarTests.cs
+++ b/MaterialDesignThemes.Wpf.Tests/RatingBarTests.cs
@@ -231,6 +231,29 @@ public void TextBlockForegroundConverter_ShouldReturnFractionalGradientStops_Whe
Assert.Equal(brush.Color.WithAlphaChannel(RatingBar.TextBlockForegroundConverter.SemiTransparent), stop2.Color);
}
+ [Fact]
+ public void TextBlockForegroundConverter_ShouldReturnFractionalGradientStops_WhenValueCovers10PercentOfButtonValueAndDirectionIsInverted()
+ {
+ // Arrange
+ SolidColorBrush brush = Brushes.Red;
+ IMultiValueConverter converter = RatingBar.TextBlockForegroundConverter.Instance;
+ object[] values = Arrange_TextBlockForegroundConverterValues(brush, value: 1.1, buttonValue: 2, invertDirection: true);
+
+ // Act
+ var result = converter.Convert(values, typeof(Brush), null, CultureInfo.CurrentCulture) as Brush;
+
+ // Assert
+ Assert.IsAssignableFrom(result);
+ LinearGradientBrush resultBrush = (LinearGradientBrush)result!;
+ Assert.Equal(2, resultBrush.GradientStops.Count);
+ GradientStop stop1 = resultBrush.GradientStops[0];
+ GradientStop stop2 = resultBrush.GradientStops[1];
+ Assert.Equal(0.9, stop1.Offset, 10);
+ Assert.Equal(brush.Color.WithAlphaChannel(RatingBar.TextBlockForegroundConverter.SemiTransparent), stop1.Color);
+ Assert.Equal(0.9, stop2.Offset, 10);
+ Assert.Equal(brush.Color, stop2.Color);
+ }
+
[Fact]
public void TextBlockForegroundConverter_ShouldReturnFractionalGradientStops_WhenValueCovers42PercentOfButtonValue()
{
@@ -277,15 +300,15 @@ public void TextBlockForegroundConverter_ShouldReturnFractionalGradientStops_Whe
Assert.Equal(brush.Color.WithAlphaChannel(RatingBar.TextBlockForegroundConverter.SemiTransparent), stop2.Color);
}
- private static object[] Arrange_TextBlockForegroundConverterValues(SolidColorBrush brush, double value, int buttonValue, Orientation orientation = Orientation.Horizontal) =>
- new object[] { brush, orientation, value, buttonValue };
+ private static object[] Arrange_TextBlockForegroundConverterValues(SolidColorBrush brush, double value, int buttonValue, Orientation orientation = Orientation.Horizontal, bool invertDirection = false) =>
+ new object[] { brush, orientation, invertDirection, value, buttonValue };
[Fact]
public void PreviewIndicatorTransformXConverter_ShouldCenterPreviewIndicator_WhenFractionalValuesAreDisabledAndOrientationIsHorizontal()
{
// Arrange
IMultiValueConverter converter = RatingBar.PreviewIndicatorTransformXConverter.Instance;
- object[] values = Arrange_PreviewIndicatorTransformXConverterValues(100, 20, Orientation.Horizontal, false, 1, 1);
+ object[] values = Arrange_PreviewIndicatorTransformXConverterValues(100, 20, Orientation.Horizontal, false, false, 1, 1);
// Act
double? result = converter.Convert(values, typeof(double), null, CultureInfo.CurrentCulture) as double?;
@@ -295,19 +318,21 @@ public void PreviewIndicatorTransformXConverter_ShouldCenterPreviewIndicator_Whe
Assert.Equal(40.0, result); // 50% of 100 minus 20/2
}
- [Fact]
- public void PreviewIndicatorTransformXConverter_ShouldOffsetPreviewIndicatorByPercentage_WhenFractionalValuesAreEnabledAndOrientationIsHorizontal()
+ [Theory]
+ [InlineData(false, 15.0)] // 25% of 100 minus 20/2
+ [InlineData(true, 65.0)] // 75% of 100 minus 20/2
+ public void PreviewIndicatorTransformXConverter_ShouldOffsetPreviewIndicatorByPercentage_WhenFractionalValuesAreEnabledAndOrientationIsHorizontal(bool invertDirection, double expectedValue)
{
// Arrange
IMultiValueConverter converter = RatingBar.PreviewIndicatorTransformXConverter.Instance;
- object[] values = Arrange_PreviewIndicatorTransformXConverterValues(100, 20, Orientation.Horizontal, true, 1.25, 1);
+ object[] values = Arrange_PreviewIndicatorTransformXConverterValues(100, 20, Orientation.Horizontal, invertDirection, true, 1.25, 1);
// Act
double? result = converter.Convert(values, typeof(double), null, CultureInfo.CurrentCulture) as double?;
// Assert
Assert.NotNull(result);
- Assert.Equal(15.0, result); // 25% of 100 minus 20/2
+ Assert.Equal(expectedValue, result);
}
[Fact]
@@ -315,7 +340,7 @@ public void PreviewIndicatorTransformXConverter_ShouldPlacePreviewIndicatorWithS
{
// Arrange
IMultiValueConverter converter = RatingBar.PreviewIndicatorTransformXConverter.Instance;
- object[] values = Arrange_PreviewIndicatorTransformXConverterValues(100, 20, Orientation.Vertical, false, 1, 1);
+ object[] values = Arrange_PreviewIndicatorTransformXConverterValues(100, 20, Orientation.Vertical, false, false, 1, 1);
double expectedValue = -20 - RatingBar.PreviewIndicatorTransformXConverter.Margin;
// Act
@@ -331,7 +356,7 @@ public void PreviewIndicatorTransformXConverter_ShouldPlacePreviewIndicatorWithS
{
// Arrange
IMultiValueConverter converter = RatingBar.PreviewIndicatorTransformXConverter.Instance;
- object[] values = Arrange_PreviewIndicatorTransformXConverterValues(100, 20, Orientation.Vertical, true, 1.25, 1);
+ object[] values = Arrange_PreviewIndicatorTransformXConverterValues(100, 20, Orientation.Vertical, false, true, 1.25, 1);
double expectedValue = -20 - RatingBar.PreviewIndicatorTransformXConverter.Margin;
// Act
@@ -344,15 +369,15 @@ public void PreviewIndicatorTransformXConverter_ShouldPlacePreviewIndicatorWithS
- private static object[] Arrange_PreviewIndicatorTransformXConverterValues(double ratingBarButtonActualWidth, double previewValueActualWidth, Orientation orientation, bool isFractionalValueEnabled, double previewValue, int buttonValue) =>
- new object[] { ratingBarButtonActualWidth, previewValueActualWidth, orientation, isFractionalValueEnabled, previewValue, buttonValue };
+ private static object[] Arrange_PreviewIndicatorTransformXConverterValues(double ratingBarButtonActualWidth, double previewValueActualWidth, Orientation orientation, bool invertDirection, bool isFractionalValueEnabled, double previewValue, int buttonValue) =>
+ new object[] { ratingBarButtonActualWidth, previewValueActualWidth, orientation, invertDirection, isFractionalValueEnabled, previewValue, buttonValue };
[Fact]
public void PreviewIndicatorTransformYConverter_ShouldPlacePreviewIndicatorWithSmallMargin_WhenFractionalValuesAreDisabledAndOrientationIsHorizontal()
{
// Arrange
IMultiValueConverter converter = RatingBar.PreviewIndicatorTransformYConverter.Instance;
- object[] values = Arrange_PreviewIndicatorTransformYConverterValues(100, 20, Orientation.Horizontal, false, 1, 1);
+ object[] values = Arrange_PreviewIndicatorTransformYConverterValues(100, 20, Orientation.Horizontal, false, false, 1, 1);
double expectedValue = -20 - RatingBar.PreviewIndicatorTransformYConverter.Margin;
// Act
@@ -368,7 +393,7 @@ public void PreviewIndicatorTransformYConverter_ShouldPlacePreviewIndicatorWithS
{
// Arrange
IMultiValueConverter converter = RatingBar.PreviewIndicatorTransformYConverter.Instance;
- object[] values = Arrange_PreviewIndicatorTransformYConverterValues(100, 20, Orientation.Horizontal, true, 1.25, 1);
+ object[] values = Arrange_PreviewIndicatorTransformYConverterValues(100, 20, Orientation.Horizontal, false, true, 1.25, 1);
double expectedValue = -20 - RatingBar.PreviewIndicatorTransformYConverter.Margin;
// Act
@@ -384,7 +409,7 @@ public void PreviewIndicatorTransformYConverter_ShouldCenterPreviewIndicator_Whe
{
// Arrange
IMultiValueConverter converter = RatingBar.PreviewIndicatorTransformYConverter.Instance;
- object[] values = Arrange_PreviewIndicatorTransformYConverterValues(100, 20, Orientation.Vertical, false, 1, 1);
+ object[] values = Arrange_PreviewIndicatorTransformYConverterValues(100, 20, Orientation.Vertical, false, false, 1, 1);
// Act
double? result = converter.Convert(values, typeof(double), null, CultureInfo.CurrentCulture) as double?;
@@ -394,23 +419,25 @@ public void PreviewIndicatorTransformYConverter_ShouldCenterPreviewIndicator_Whe
Assert.Equal(40.0, result); // 50% of 100 minus 20/2
}
- [Fact]
- public void PreviewIndicatorTransformYConverter_ShouldPreviewIndicatorByPercentage_WhenFractionalValuesAreEnabledAndOrientationIsVertical()
+ [Theory]
+ [InlineData(false, 15.0)] // 25% of 100 minus 20/2
+ [InlineData(true, 65.0)] // 75% of 100 minus 20/2
+ public void PreviewIndicatorTransformYConverter_ShouldPreviewIndicatorByPercentage_WhenFractionalValuesAreEnabledAndOrientationIsVertical(bool invertDirection, double expectedValue)
{
// Arrange
IMultiValueConverter converter = RatingBar.PreviewIndicatorTransformYConverter.Instance;
- object[] values = Arrange_PreviewIndicatorTransformYConverterValues(100, 20, Orientation.Vertical, true, 1.25, 1);
+ object[] values = Arrange_PreviewIndicatorTransformYConverterValues(100, 20, Orientation.Vertical, invertDirection, true, 1.25, 1);
// Act
double? result = converter.Convert(values, typeof(double), null, CultureInfo.CurrentCulture) as double?;
// Assert
Assert.NotNull(result);
- Assert.Equal(15.0, result); // 25% of 100 minus 20/2
+ Assert.Equal(expectedValue, result);
}
- private static object[] Arrange_PreviewIndicatorTransformYConverterValues(double ratingBarButtonActualHeight, double previewValueActualHeight, Orientation orientation, bool isFractionalValueEnabled, double previewValue, int buttonValue) =>
- new object[] { ratingBarButtonActualHeight, previewValueActualHeight, orientation, isFractionalValueEnabled, previewValue, buttonValue };
+ private static object[] Arrange_PreviewIndicatorTransformYConverterValues(double ratingBarButtonActualHeight, double previewValueActualHeight, Orientation orientation, bool invertDirection, bool isFractionalValueEnabled, double previewValue, int buttonValue) =>
+ new object[] { ratingBarButtonActualHeight, previewValueActualHeight, orientation, invertDirection, isFractionalValueEnabled, previewValue, buttonValue };
}
internal static class ColorExtensions
diff --git a/MaterialDesignThemes.Wpf/RatingBar.cs b/MaterialDesignThemes.Wpf/RatingBar.cs
index e5ce3246fc..1f3e033f43 100644
--- a/MaterialDesignThemes.Wpf/RatingBar.cs
+++ b/MaterialDesignThemes.Wpf/RatingBar.cs
@@ -45,7 +45,29 @@ private double GetValueAtMousePosition(RatingBarButton ratingBarButton)
{
// Get mouse offset inside source
Point p = Mouse.GetPosition(ratingBarButton);
- double percentSelected = Orientation == Orientation.Horizontal ? p.X / ratingBarButton.ActualWidth : p.Y / ratingBarButton.ActualHeight;
+ double percentSelected;
+ switch (Orientation)
+ {
+ case Orientation.Horizontal:
+ if (InvertDirection)
+ {
+ percentSelected = 1 - (p.X / ratingBarButton.ActualWidth);
+ break;
+ }
+ percentSelected = p.X / ratingBarButton.ActualWidth;
+ break;
+ case Orientation.Vertical:
+ if (InvertDirection)
+ {
+ percentSelected = 1 - (p.Y / ratingBarButton.ActualHeight);
+ break;
+ }
+ percentSelected = p.Y / ratingBarButton.ActualHeight;
+ break;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+
return ratingBarButton.Value - 1 + percentSelected;
}
@@ -243,6 +265,15 @@ public Orientation Orientation
set => SetValue(OrientationProperty, value);
}
+ public static readonly DependencyProperty InvertDirectionProperty = DependencyProperty.Register(
+ nameof(InvertDirection), typeof(bool), typeof(RatingBar), new PropertyMetadata(default(bool)));
+
+ public bool InvertDirection
+ {
+ get => (bool) GetValue(InvertDirectionProperty);
+ set => SetValue(InvertDirectionProperty, value);
+ }
+
public static readonly DependencyProperty IsReadOnlyProperty = DependencyProperty.Register(
nameof(IsReadOnly), typeof(bool), typeof(RatingBar), new PropertyMetadata(default(bool)));
@@ -289,21 +320,44 @@ private void RebuildButtons()
// When fractional values are enabled, the first rating button represents the value Min when not selected at all and Min+1 when fully selected;
// thus we start with the value Min+1 for the values of the rating buttons.
int start = IsFractionalValueEnabled ? Min + 1 : Min;
- for (int i = start; i <= Max; i++)
+
+ if (InvertDirection)
{
- var ratingBarButton = new RatingBarButton
+ for (int i = Max; i >= start; i--)
{
- Content = i,
- ContentTemplate = ValueItemTemplate,
- ContentTemplateSelector = ValueItemTemplateSelector,
+ var ratingBarButton = new RatingBarButton
+ {
+ Content = i,
+ ContentTemplate = ValueItemTemplate,
+ ContentTemplateSelector = ValueItemTemplateSelector,
#pragma warning disable CS0618 // Type or member is obsolete
- IsWithinSelectedValue = i <= Value,
+ IsWithinSelectedValue = i <= Value,
#pragma warning restore CS0618 // Type or member is obsolete
- Style = ValueItemContainerButtonStyle,
- Value = i,
- };
- ratingBarButton.MouseMove += RatingBarButton_MouseMove;
- _ratingButtonsInternal.Add(ratingBarButton);
+ Style = ValueItemContainerButtonStyle,
+ Value = i,
+ };
+ ratingBarButton.MouseMove += RatingBarButton_MouseMove;
+ _ratingButtonsInternal.Add(ratingBarButton);
+ }
+ }
+ else
+ {
+ for (int i = start; i <= Max; i++)
+ {
+ var ratingBarButton = new RatingBarButton
+ {
+ Content = i,
+ ContentTemplate = ValueItemTemplate,
+ ContentTemplateSelector = ValueItemTemplateSelector,
+#pragma warning disable CS0618 // Type or member is obsolete
+ IsWithinSelectedValue = i <= Value,
+#pragma warning restore CS0618 // Type or member is obsolete
+ Style = ValueItemContainerButtonStyle,
+ Value = i,
+ };
+ ratingBarButton.MouseMove += RatingBarButton_MouseMove;
+ _ratingButtonsInternal.Add(ratingBarButton);
+ }
}
}
@@ -333,11 +387,12 @@ internal class TextBlockForegroundConverter : IMultiValueConverter
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
- if (values?.Length == 4
+ if (values?.Length == 5
&& values[0] is SolidColorBrush brush
&& values[1] is Orientation orientation
- && values[2] is double value
- && values[3] is int buttonValue)
+ && values[2] is bool invertDirection
+ && values[3] is double value
+ && values[4] is int buttonValue)
{
if (value >= buttonValue)
return brush;
@@ -348,20 +403,38 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur
if (value > buttonValue - 1.0)
{
double offset = value - buttonValue + 1;
+ if (invertDirection)
+ {
+ offset = 1 - offset;
+ }
return new LinearGradientBrush
{
StartPoint = orientation == Orientation.Horizontal ? new Point(0, 0.5) : new Point(0.5, 0),
EndPoint = orientation == Orientation.Horizontal ? new Point(1, 0.5) : new Point(0.5, 1),
- GradientStops = new()
- {
- new GradientStop { Color = originalColor, Offset = offset },
- new GradientStop { Color = semiTransparent, Offset = offset }
- }
+
+ GradientStops = CreateGradientStopCollection(originalColor, semiTransparent, offset, invertDirection)
};
}
return new SolidColorBrush(semiTransparent);
}
+ GradientStopCollection CreateGradientStopCollection(Color originalColor, Color semiTransparent, double offset, bool invertDirection)
+ {
+ if (invertDirection)
+ {
+ return new()
+ {
+ new GradientStop {Color = semiTransparent, Offset = offset},
+ new GradientStop {Color = originalColor, Offset = offset},
+ };
+ }
+ return new()
+ {
+ new GradientStop {Color = originalColor, Offset = offset},
+ new GradientStop {Color = semiTransparent, Offset = offset}
+ };
+ }
+
// This should never happen (returning actual brush to avoid the compilers squiggly line warning)
return Brushes.Transparent;
}
@@ -377,13 +450,14 @@ internal class PreviewIndicatorTransformXConverter : IMultiValueConverter
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
- if (values.Length >= 6
+ if (values.Length >= 7
&& values[0] is double ratingBarButtonActualWidth
&& values[1] is double previewValueActualWidth
&& values[2] is Orientation ratingBarOrientation
- && values[3] is bool isFractionalValueEnabled
- && values[4] is double previewValue
- && values[5] is int ratingButtonValue)
+ && values[3] is bool ratingBarInvertDirection
+ && values[4] is bool isFractionalValueEnabled
+ && values[5] is double previewValue
+ && values[6] is int ratingButtonValue)
{
if (!isFractionalValueEnabled)
{
@@ -404,7 +478,10 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur
return ratingBarOrientation switch
{
- Orientation.Horizontal => percent * ratingBarButtonActualWidth - (previewValueActualWidth / 2),
+ Orientation.Horizontal => ratingBarInvertDirection ?
+ (1 - percent) * ratingBarButtonActualWidth - (previewValueActualWidth / 2) :
+ percent * ratingBarButtonActualWidth - (previewValueActualWidth / 2)
+ ,
Orientation.Vertical => -previewValueActualWidth - Margin,
_ => throw new ArgumentOutOfRangeException()
};
@@ -423,13 +500,14 @@ internal class PreviewIndicatorTransformYConverter : IMultiValueConverter
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
- if (values.Length >= 6
+ if (values.Length >= 7
&& values[0] is double ratingBarButtonActualHeight
&& values[1] is double previewValueActualHeight
&& values[2] is Orientation ratingBarOrientation
- && values[3] is bool isFractionalValueEnabled
- && values[4] is double previewValue
- && values[5] is int ratingButtonValue)
+ && values[3] is bool ratingBarInvertDirection
+ && values[4] is bool isFractionalValueEnabled
+ && values[5] is double previewValue
+ && values[6] is int ratingButtonValue)
{
if (!isFractionalValueEnabled)
{
@@ -451,7 +529,9 @@ public object Convert(object[] values, Type targetType, object parameter, Cultur
return ratingBarOrientation switch
{
Orientation.Horizontal => -previewValueActualHeight - Margin,
- Orientation.Vertical => percent * ratingBarButtonActualHeight - (previewValueActualHeight / 2),
+ Orientation.Vertical => ratingBarInvertDirection ?
+ (1 - percent) * ratingBarButtonActualHeight - (previewValueActualHeight / 2) :
+ percent * ratingBarButtonActualHeight - (previewValueActualHeight / 2),
_ => throw new ArgumentOutOfRangeException()
};
}
diff --git a/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.RatingBar.xaml b/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.RatingBar.xaml
index 0d16fcb538..389cca72f5 100644
--- a/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.RatingBar.xaml
+++ b/MaterialDesignThemes.Wpf/Themes/MaterialDesignTheme.RatingBar.xaml
@@ -49,6 +49,7 @@
+
@@ -70,6 +71,7 @@
+
@@ -81,6 +83,7 @@
+
@@ -124,6 +127,7 @@
+
@@ -135,6 +139,7 @@
+
@@ -245,6 +250,7 @@
+
@@ -278,4 +284,4 @@
-
\ No newline at end of file
+