You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
168 lines
4.5 KiB
168 lines
4.5 KiB
using System; |
|
using System.Collections.Generic; |
|
using System.Linq; |
|
using Avalonia; |
|
using Avalonia.Controls; |
|
using Avalonia.Controls.Documents; |
|
using Avalonia.Controls.Primitives; |
|
using Avalonia.Data; |
|
using Avalonia.Layout; |
|
using Avalonia.Markup.Xaml.MarkupExtensions; |
|
using Avalonia.Media; |
|
using Avalonia.VisualTree; |
|
using FluentIcons.Common; |
|
using FluentIcons.FluentAvalonia; |
|
using SpacedGridControl.Avalonia; |
|
using StabilityMatrix.Avalonia.Styles; |
|
|
|
namespace StabilityMatrix.Avalonia.Controls; |
|
|
|
public class StarsRating : TemplatedControl |
|
{ |
|
private SymbolIcon? StarFilledIcon => Resources["StarFilledIcon"] as SymbolIcon; |
|
|
|
private ItemsControl? itemsControl; |
|
|
|
private IEnumerable<SymbolIcon> StarItems => itemsControl!.ItemsSource!.Cast<SymbolIcon>(); |
|
|
|
public static readonly StyledProperty<bool> IsEditableProperty = AvaloniaProperty.Register< |
|
StarsRating, |
|
bool |
|
>("IsEditable"); |
|
|
|
public bool IsEditable |
|
{ |
|
get => GetValue(IsEditableProperty); |
|
set => SetValue(IsEditableProperty, value); |
|
} |
|
|
|
public static readonly StyledProperty<int> MaximumProperty = AvaloniaProperty.Register< |
|
StarsRating, |
|
int |
|
>(nameof(Maximum), 5); |
|
|
|
public int Maximum |
|
{ |
|
get => GetValue(MaximumProperty); |
|
set => SetValue(MaximumProperty, value); |
|
} |
|
|
|
public static readonly StyledProperty<double> ValueProperty = AvaloniaProperty.Register< |
|
StarsRating, |
|
double |
|
>(nameof(Value)); |
|
|
|
public double Value |
|
{ |
|
get => GetValue(ValueProperty); |
|
set => SetValue(ValueProperty, value); |
|
} |
|
|
|
/// <inheritdoc /> |
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|
{ |
|
base.OnApplyTemplate(e); |
|
|
|
itemsControl = e.NameScope.Find<ItemsControl>("PART_StarsItemsControl")!; |
|
|
|
CreateStars(); |
|
} |
|
|
|
/// <inheritdoc /> |
|
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) |
|
{ |
|
base.OnPropertyChanged(change); |
|
|
|
if (!this.IsAttachedToVisualTree()) |
|
{ |
|
return; |
|
} |
|
|
|
if (change.Property == ValueProperty || change.Property == MaximumProperty) |
|
{ |
|
SyncStarState(); |
|
} |
|
} |
|
|
|
private void CreateStars() |
|
{ |
|
if (itemsControl is null) |
|
{ |
|
return; |
|
} |
|
|
|
// Fill stars |
|
var stars = new List<Control>(); |
|
|
|
for (var i = 0; i < Maximum; i++) |
|
{ |
|
var star = new SymbolIcon |
|
{ |
|
FontSize = FontSize, |
|
Margin = new Thickness(0, 0), |
|
Symbol = Symbol.Star, |
|
HorizontalAlignment = HorizontalAlignment.Center, |
|
VerticalAlignment = VerticalAlignment.Center, |
|
Tag = i |
|
}; |
|
|
|
stars.Add(star); |
|
OnStarAdded(star); |
|
} |
|
|
|
itemsControl.ItemsSource = stars; |
|
SyncStarState(); |
|
} |
|
|
|
private void OnStarAdded(SymbolIcon item) |
|
{ |
|
if (IsEditable) |
|
{ |
|
item.Tapped += (sender, args) => |
|
{ |
|
var star = (SymbolIcon)sender!; |
|
Value = (int)star.Tag! + 1; |
|
}; |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Round a number to the nearest 0.5 |
|
/// </summary> |
|
private static double RoundToHalf(double value) |
|
{ |
|
return Math.Round(value * 2, MidpointRounding.AwayFromZero) / 2; |
|
} |
|
|
|
private void SyncStarState() |
|
{ |
|
// Set star to filled when Value is greater than or equal to the star index |
|
foreach (var star in StarItems) |
|
{ |
|
// Add 1 to tag since its index is 0-based |
|
var tag = (int)star.Tag! + 1; |
|
|
|
// Fill if current is equal or lower than floor of Value |
|
if (tag <= Math.Floor(RoundToHalf(Value))) |
|
{ |
|
star.Symbol = Symbol.Star; |
|
star.IsFilled = true; |
|
star.Foreground = Foreground; |
|
} |
|
// If current is between floor and ceil of value, use half-star |
|
else if (tag <= Math.Ceiling(RoundToHalf(Value))) |
|
{ |
|
star.Symbol = Symbol.StarHalf; |
|
star.IsFilled = true; |
|
star.Foreground = Foreground; |
|
} |
|
// Otherwise no fill and gray disabled color |
|
else |
|
{ |
|
star.Symbol = Symbol.Star; |
|
star.IsFilled = false; |
|
star.Foreground = new SolidColorBrush(Colors.DarkSlateGray); |
|
} |
|
} |
|
} |
|
}
|
|
|