123
demo/Ursa.Demo/Pages/RatingDemo.axaml
Normal file
123
demo/Ursa.Demo/Pages/RatingDemo.axaml
Normal file
@@ -0,0 +1,123 @@
|
||||
<UserControl x:Class="Ursa.Demo.Pages.RatingDemo"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:vm="clr-namespace:Ursa.Demo.ViewModels"
|
||||
xmlns:u="https://irihi.tech/ursa"
|
||||
d:DesignHeight="450"
|
||||
d:DesignWidth="800"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="vm:RatingDemoViewModel"
|
||||
mc:Ignorable="d">
|
||||
<UserControl.Resources>
|
||||
<StreamGeometry x:Key="LoveHeartGeometry">
|
||||
M12 5.99999C10.5 2.12432 5.75193 2.0557 3.40383 4.4038C0.865423 6.94221 0.999999 10.5 3.50001 14C5.52139 16.8299 9.83088 20.3136 11.4069 21.5438C11.7573 21.8172 12.2427 21.8172 12.5931 21.5438C14.1691 20.3136 18.4786 16.8299 20.5 14C23 10.5 23.1346 6.94221 20.5962 4.4038C18.2481 2.0557 14.5 2.12432 12 5.99999Z
|
||||
</StreamGeometry>
|
||||
</UserControl.Resources>
|
||||
<StackPanel Spacing="20">
|
||||
<Grid ColumnDefinitions="*, 300">
|
||||
<Grid Grid.Column="0">
|
||||
<StackPanel HorizontalAlignment="Center">
|
||||
<u:Rating
|
||||
AllowClear="{Binding AllowClear}"
|
||||
AllowHalf="{Binding AllowHalf}"
|
||||
IsEnabled="{Binding IsEnabled}"
|
||||
Value="{Binding Value}"
|
||||
Count="{Binding Count}"
|
||||
DefaultValue="{Binding DefaultValue}" />
|
||||
<u:Rating
|
||||
Classes="Small"
|
||||
AllowClear="{Binding AllowClear}"
|
||||
AllowHalf="{Binding AllowHalf}"
|
||||
IsEnabled="{Binding IsEnabled}"
|
||||
Value="{Binding Value}"
|
||||
Count="{Binding Count}"
|
||||
DefaultValue="{Binding DefaultValue}" />
|
||||
<u:Rating
|
||||
Size="48"
|
||||
AllowClear="{Binding AllowClear}"
|
||||
AllowHalf="{Binding AllowHalf}"
|
||||
IsEnabled="{Binding IsEnabled}"
|
||||
Value="{Binding Value}"
|
||||
Count="{Binding Count}"
|
||||
DefaultValue="{Binding DefaultValue}"
|
||||
Foreground="{StaticResource SemiRed5}"
|
||||
Character="{StaticResource LoveHeartGeometry}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Border Grid.Column="1" VerticalAlignment="Top">
|
||||
<Grid ColumnDefinitions="*, Auto" RowDefinitions="*,*,*,*,*,*,*,*">
|
||||
<Label
|
||||
Grid.Row="0"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Content="AllowClear" />
|
||||
<ToggleSwitch
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
MinWidth="200"
|
||||
IsChecked="{Binding AllowClear}" />
|
||||
<Label
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Content="AllowHalf" />
|
||||
<ToggleSwitch
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding AllowHalf}" />
|
||||
<Label
|
||||
Grid.Row="3"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Content="IsEnabled" />
|
||||
<ToggleSwitch
|
||||
Grid.Row="3"
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
IsChecked="{Binding IsEnabled}" />
|
||||
<Label
|
||||
Grid.Row="4"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Content="DefaultValue" />
|
||||
<Label
|
||||
Grid.Row="4"
|
||||
Grid.Column="1"
|
||||
Theme="{StaticResource TagLabel}"
|
||||
Classes="Large"
|
||||
VerticalAlignment="Center"
|
||||
Content="{Binding DefaultValue}" />
|
||||
<Label
|
||||
Grid.Row="5"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Content="Value" />
|
||||
<NumericUpDown
|
||||
Grid.Row="5"
|
||||
Grid.Column="1"
|
||||
Maximum="100"
|
||||
Minimum="-10"
|
||||
Increment="0.1"
|
||||
VerticalAlignment="Center"
|
||||
Value="{Binding Value}" />
|
||||
<Label
|
||||
Grid.Row="6"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Content="Count" />
|
||||
<NumericUpDown
|
||||
Grid.Row="6"
|
||||
Grid.Column="1"
|
||||
Maximum="100"
|
||||
Minimum="-10"
|
||||
Increment="1"
|
||||
VerticalAlignment="Center"
|
||||
Value="{Binding Count}" />
|
||||
</Grid>
|
||||
</Border>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
13
demo/Ursa.Demo/Pages/RatingDemo.axaml.cs
Normal file
13
demo/Ursa.Demo/Pages/RatingDemo.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Avalonia.Controls;
|
||||
using Ursa.Demo.ViewModels;
|
||||
|
||||
namespace Ursa.Demo.Pages;
|
||||
|
||||
public partial class RatingDemo : UserControl
|
||||
{
|
||||
public RatingDemo()
|
||||
{
|
||||
InitializeComponent();
|
||||
this.DataContext = new RatingDemoViewModel();
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,7 @@ public class MainViewViewModel : ViewModelBase
|
||||
MenuKeys.MenuKeyNumPad => new NumPadDemoViewModel(),
|
||||
MenuKeys.MenuKeyPagination => new PaginationDemoViewModel(),
|
||||
MenuKeys.MenuKeyRangeSlider => new RangeSliderDemoViewModel(),
|
||||
MenuKeys.MenuKeyRating => new RatingDemoViewModel(),
|
||||
MenuKeys.MenuKeyScrollToButton => new ScrollToButtonDemoViewModel(),
|
||||
MenuKeys.MenuKeySelectionList => new SelectionListDemoViewModel(),
|
||||
MenuKeys.MenuKeySkeleton => new SkeletonDemoViewModel(),
|
||||
|
||||
@@ -38,6 +38,7 @@ public class MenuViewModel: ViewModelBase
|
||||
new() { MenuHeader = "NumPad", Key = MenuKeys.MenuKeyNumPad },
|
||||
new() { MenuHeader = "Pagination", Key = MenuKeys.MenuKeyPagination },
|
||||
new() { MenuHeader = "RangeSlider", Key = MenuKeys.MenuKeyRangeSlider },
|
||||
new() { MenuHeader = "Rating", Key = MenuKeys.MenuKeyRating, Status = "New"},
|
||||
new() { MenuHeader = "Scroll To", Key = MenuKeys.MenuKeyScrollToButton },
|
||||
new() { MenuHeader = "Selection List", Key = MenuKeys.MenuKeySelectionList },
|
||||
new() { MenuHeader = "Skeleton", Key = MenuKeys.MenuKeySkeleton },
|
||||
@@ -83,6 +84,7 @@ public static class MenuKeys
|
||||
public const string MenuKeyNumPad = "NumPad";
|
||||
public const string MenuKeyPagination = "Pagination";
|
||||
public const string MenuKeyRangeSlider = "RangeSlider";
|
||||
public const string MenuKeyRating = "Rating";
|
||||
public const string MenuKeyScrollToButton = "ScrollToButton";
|
||||
public const string MenuKeySelectionList = "SelectionList";
|
||||
public const string MenuKeyTagInput = "TagInput";
|
||||
|
||||
13
demo/Ursa.Demo/ViewModels/RatingDemoViewModel.cs
Normal file
13
demo/Ursa.Demo/ViewModels/RatingDemoViewModel.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Ursa.Demo.ViewModels;
|
||||
|
||||
public partial class RatingDemoViewModel : ViewModelBase
|
||||
{
|
||||
[ObservableProperty] private bool _allowClear = true;
|
||||
[ObservableProperty] private bool _allowHalf = true;
|
||||
[ObservableProperty] private bool _isEnabled = true;
|
||||
[ObservableProperty] private double _value;
|
||||
[ObservableProperty] private double _defaultValue = 2.3;
|
||||
[ObservableProperty] private int _count = 5;
|
||||
}
|
||||
78
src/Ursa.Themes.Semi/Controls/Rating.axaml
Normal file
78
src/Ursa.Themes.Semi/Controls/Rating.axaml
Normal file
@@ -0,0 +1,78 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="https://irihi.tech/ursa">
|
||||
<ControlTheme x:Key="{x:Type u:RatingCharacter}" TargetType="u:RatingCharacter">
|
||||
<Setter Property="Character" Value="{DynamicResource RatingStarIconGlyph}" />
|
||||
<Setter Property="Background" Value="{DynamicResource RatingCharacterBackground}" />
|
||||
<Setter Property="Cursor" Value="Hand" />
|
||||
<Setter Property="Margin" Value="{DynamicResource RatingCharacterMargin}" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate TargetType="u:RatingCharacter">
|
||||
<Canvas Name="PART_Root"
|
||||
Width="{TemplateBinding Size}"
|
||||
Height="{TemplateBinding Size}">
|
||||
<Path Width="{Binding #PART_Root.Width}"
|
||||
Height="{Binding #PART_Root.Height}"
|
||||
Stretch="Uniform"
|
||||
Data="{TemplateBinding Character}"
|
||||
Fill="{TemplateBinding Background}" />
|
||||
<Border Name="{x:Static u:RatingCharacter.PART_IconBorder}"
|
||||
IsVisible="False"
|
||||
ClipToBounds="True">
|
||||
<Path Width="{Binding #PART_Root.Width}"
|
||||
Height="{Binding #PART_Root.Height}"
|
||||
HorizontalAlignment="Left"
|
||||
Stretch="Uniform"
|
||||
Data="{TemplateBinding Character}"
|
||||
Fill="{TemplateBinding Foreground}" />
|
||||
</Border>
|
||||
</Canvas>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
<Style Selector="^:pointerover">
|
||||
<Setter Property="RenderTransform" Value="scale(1.1)" />
|
||||
</Style>
|
||||
<Style Selector="^:selected /template/ Border#PART_IconBorder">
|
||||
<Setter Property="IsVisible" Value="True" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
|
||||
<ControlTheme x:Key="{x:Type u:Rating}" TargetType="u:Rating">
|
||||
<Setter Property="Foreground" Value="{DynamicResource RatingCharacterForeground}" />
|
||||
<Setter Property="Character" Value="{DynamicResource RatingStarIconGlyph}" />
|
||||
<Setter Property="Size" Value="{DynamicResource RatingDefaultSize}" />
|
||||
<Setter Property="ItemTemplate">
|
||||
<DataTemplate>
|
||||
<u:RatingCharacter />
|
||||
</DataTemplate>
|
||||
</Setter>
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate TargetType="u:Rating">
|
||||
<Border Name="PART_RootBorder"
|
||||
VerticalAlignment="Stretch"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Panel HorizontalAlignment="Stretch">
|
||||
<ItemsControl Name="{x:Static u:Rating.PART_ItemsControl}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
ItemTemplate="{TemplateBinding ItemTemplate}"
|
||||
ItemsSource="{TemplateBinding Items}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
</ItemsControl>
|
||||
</Panel>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
<Style Selector="^.Small">
|
||||
<Setter Property="Size" Value="{DynamicResource RatingSmallSize}" />
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
</ResourceDictionary>
|
||||
@@ -28,6 +28,7 @@
|
||||
<ResourceInclude Source="NumberDisplayer.axaml" />
|
||||
<ResourceInclude Source="Pagination.axaml" />
|
||||
<ResourceInclude Source="RangeSlider.axaml" />
|
||||
<ResourceInclude Source="Rating.axaml" />
|
||||
<ResourceInclude Source="ScrollToButton.axaml" />
|
||||
<ResourceInclude Source="SelectionList.axaml" />
|
||||
<ResourceInclude Source="TagInput.axaml" />
|
||||
|
||||
4
src/Ursa.Themes.Semi/Themes/Dark/Rating.axaml
Normal file
4
src/Ursa.Themes.Semi/Themes/Dark/Rating.axaml
Normal file
@@ -0,0 +1,4 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="RatingCharacterForeground" Color="#FDDE43" />
|
||||
<SolidColorBrush x:Key="RatingCharacterBackground" Opacity="0.12" Color="White" />
|
||||
</ResourceDictionary>
|
||||
@@ -12,6 +12,7 @@
|
||||
<MergeResourceInclude Source="Loading.axaml" />
|
||||
<MergeResourceInclude Source="NavigationMenu.axaml" />
|
||||
<MergeResourceInclude Source="Pagination.axaml" />
|
||||
<MergeResourceInclude Source="Rating.axaml" />
|
||||
<MergeResourceInclude Source="TagInput.axaml" />
|
||||
<MergeResourceInclude Source="Timeline.axaml" />
|
||||
<MergeResourceInclude Source="Skeleton.axaml" />
|
||||
|
||||
4
src/Ursa.Themes.Semi/Themes/Light/Rating.axaml
Normal file
4
src/Ursa.Themes.Semi/Themes/Light/Rating.axaml
Normal file
@@ -0,0 +1,4 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<SolidColorBrush x:Key="RatingCharacterForeground" Color="#FAC800" />
|
||||
<SolidColorBrush x:Key="RatingCharacterBackground" Opacity="0.05" Color="#2E3238" />
|
||||
</ResourceDictionary>
|
||||
@@ -12,6 +12,7 @@
|
||||
<MergeResourceInclude Source="Loading.axaml" />
|
||||
<MergeResourceInclude Source="NavigationMenu.axaml" />
|
||||
<MergeResourceInclude Source="Pagination.axaml" />
|
||||
<MergeResourceInclude Source="Rating.axaml" />
|
||||
<MergeResourceInclude Source="TagInput.axaml" />
|
||||
<MergeResourceInclude Source="Timeline.axaml" />
|
||||
<MergeResourceInclude Source="Skeleton.axaml" />
|
||||
|
||||
6
src/Ursa.Themes.Semi/Themes/Shared/Rating.axaml
Normal file
6
src/Ursa.Themes.Semi/Themes/Shared/Rating.axaml
Normal file
@@ -0,0 +1,6 @@
|
||||
<ResourceDictionary xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
<x:Double x:Key="RatingDefaultSize">24</x:Double>
|
||||
<x:Double x:Key="RatingSmallSize">16</x:Double>
|
||||
<Thickness x:Key="RatingCharacterMargin">3 4</Thickness>
|
||||
<StreamGeometry x:Key="RatingStarIconGlyph">M10.7525 1.90411C11.1451 0.698628 12.8549 0.698631 13.2475 1.90411L15.2395 8.01946H21.6858C22.9565 8.01946 23.4848 9.64143 22.4568 10.3865L17.2417 14.1659L19.2337 20.2813C19.6263 21.4868 18.2431 22.4892 17.2151 21.7442L12 17.9647L6.78489 21.7442C5.75687 22.4892 4.37368 21.4868 4.76635 20.2813L6.75834 14.1659L1.54323 10.3865C0.515206 9.64142 1.04354 8.01946 2.31425 8.01946H8.76048L10.7525 1.90411Z</StreamGeometry>
|
||||
</ResourceDictionary>
|
||||
@@ -13,6 +13,7 @@
|
||||
<MergeResourceInclude Source="MessageBox.axaml" />
|
||||
<MergeResourceInclude Source="NavigationMenu.axaml" />
|
||||
<MergeResourceInclude Source="Pagination.axaml" />
|
||||
<MergeResourceInclude Source="Rating.axaml" />
|
||||
<MergeResourceInclude Source="ScrollToButton.axaml" />
|
||||
<MergeResourceInclude Source="TagInput.axaml" />
|
||||
<MergeResourceInclude Source="Skeleton.axaml" />
|
||||
|
||||
257
src/Ursa/Controls/Rating/Rating.cs
Normal file
257
src/Ursa/Controls/Rating/Rating.cs
Normal file
@@ -0,0 +1,257 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Collections;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Interactivity;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
[PseudoClasses(PC_Selected)]
|
||||
[TemplatePart(PART_ItemsControl, typeof(ItemsControl))]
|
||||
public class Rating : TemplatedControl
|
||||
{
|
||||
public const string PART_ItemsControl = "PART_ItemsControl";
|
||||
protected const string PC_Selected = ":selected";
|
||||
|
||||
public static readonly StyledProperty<double> ValueProperty =
|
||||
AvaloniaProperty.Register<Rating, double>(nameof(Value), defaultBindingMode: BindingMode.TwoWay);
|
||||
|
||||
public static readonly StyledProperty<bool> AllowClearProperty =
|
||||
AvaloniaProperty.Register<Rating, bool>(nameof(AllowClear), true);
|
||||
|
||||
public static readonly StyledProperty<bool> AllowHalfProperty =
|
||||
AvaloniaProperty.Register<Rating, bool>(nameof(AllowHalf));
|
||||
|
||||
public static readonly StyledProperty<object> CharacterProperty =
|
||||
AvaloniaProperty.Register<Rating, object>(nameof(Character));
|
||||
|
||||
public static readonly StyledProperty<int> CountProperty =
|
||||
AvaloniaProperty.Register<Rating, int>(nameof(Count), 5);
|
||||
|
||||
public static readonly StyledProperty<double> DefaultValueProperty =
|
||||
AvaloniaProperty.Register<Rating, double>(nameof(DefaultValue));
|
||||
|
||||
public static readonly StyledProperty<double> SizeProperty =
|
||||
AvaloniaProperty.Register<RatingCharacter, double>(nameof(Size));
|
||||
|
||||
public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
|
||||
AvaloniaProperty.Register<Rating, IDataTemplate?>(nameof(ItemTemplate));
|
||||
|
||||
public double Value
|
||||
{
|
||||
get => GetValue(ValueProperty);
|
||||
set => SetValue(ValueProperty, value);
|
||||
}
|
||||
|
||||
public bool AllowClear
|
||||
{
|
||||
get => GetValue(AllowClearProperty);
|
||||
set => SetValue(AllowClearProperty, value);
|
||||
}
|
||||
|
||||
public bool AllowHalf
|
||||
{
|
||||
get => GetValue(AllowHalfProperty);
|
||||
set => SetValue(AllowHalfProperty, value);
|
||||
}
|
||||
|
||||
public object Character
|
||||
{
|
||||
get => GetValue(CharacterProperty);
|
||||
set => SetValue(CharacterProperty, value);
|
||||
}
|
||||
|
||||
public int Count
|
||||
{
|
||||
get => GetValue(CountProperty);
|
||||
set => SetValue(CountProperty, value);
|
||||
}
|
||||
|
||||
public double DefaultValue
|
||||
{
|
||||
get => GetValue(DefaultValueProperty);
|
||||
set => SetValue(DefaultValueProperty, value);
|
||||
}
|
||||
|
||||
public double Size
|
||||
{
|
||||
get => GetValue(SizeProperty);
|
||||
set => SetValue(SizeProperty, value);
|
||||
}
|
||||
|
||||
public IDataTemplate? ItemTemplate
|
||||
{
|
||||
get => GetValue(ItemTemplateProperty);
|
||||
set => SetValue(ItemTemplateProperty, value);
|
||||
}
|
||||
|
||||
public static readonly DirectProperty<Rating, AvaloniaList<RatingCharacter>> ItemsProperty =
|
||||
AvaloniaProperty.RegisterDirect<Rating, AvaloniaList<RatingCharacter>>(
|
||||
nameof(Items), o => o.Items);
|
||||
|
||||
private AvaloniaList<RatingCharacter> _items = null!;
|
||||
|
||||
public AvaloniaList<RatingCharacter> Items
|
||||
{
|
||||
get => _items;
|
||||
private set => SetAndRaise(ItemsProperty, ref _items, value);
|
||||
}
|
||||
|
||||
public Rating()
|
||||
{
|
||||
Items = [];
|
||||
}
|
||||
|
||||
static Rating()
|
||||
{
|
||||
ValueProperty.Changed.AddClassHandler<Rating>((s, e) => s.OnValueChanged(e));
|
||||
CountProperty.Changed.AddClassHandler<Rating>((s, e) => s.OnCountChanged(e));
|
||||
AllowHalfProperty.Changed.AddClassHandler<Rating>((s, e) => s.OnAllowHalfChanged(e));
|
||||
}
|
||||
|
||||
private void OnValueChanged(AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (!IsLoaded) return;
|
||||
if (e.NewValue is double newValue)
|
||||
{
|
||||
UpdateItemsByValue(newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnCountChanged(AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (!IsLoaded) return;
|
||||
var currentCount = Items.Count;
|
||||
var newCount = e.GetNewValue<int>();
|
||||
|
||||
if (currentCount < newCount)
|
||||
{
|
||||
var itemsToAdd = newCount - currentCount;
|
||||
for (var i = 0; i < itemsToAdd; i++)
|
||||
{
|
||||
Items.Add(new RatingCharacter
|
||||
{
|
||||
Character = Character,
|
||||
AllowHalf = AllowHalf,
|
||||
Size = Size,
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (currentCount > newCount)
|
||||
{
|
||||
var itemsToRemove = currentCount - newCount;
|
||||
for (var i = 0; i < itemsToRemove && currentCount > i; i++)
|
||||
{
|
||||
Items.RemoveAt(currentCount - i - 1);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateItemsByValue(Value);
|
||||
}
|
||||
|
||||
private void OnAllowHalfChanged(AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (!IsLoaded) return;
|
||||
if (e.NewValue is not bool newValue) return;
|
||||
foreach (var item in Items)
|
||||
{
|
||||
item.AllowHalf = newValue;
|
||||
}
|
||||
|
||||
UpdateItemsByValue(Value);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
for (var i = 0; i < Count; i++)
|
||||
{
|
||||
Items.Add(new RatingCharacter
|
||||
{
|
||||
Character = Character,
|
||||
AllowHalf = AllowHalf,
|
||||
Size = Size,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
SetCurrentValue(ValueProperty, DefaultValue);
|
||||
}
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
base.OnLoaded(e);
|
||||
UpdateItemsByValue(DefaultValue);
|
||||
}
|
||||
|
||||
internal void PointerEnteredHandler(RatingCharacter o)
|
||||
{
|
||||
var item = Items.FirstOrDefault(item => item.IsLast);
|
||||
if (item is not null)
|
||||
{
|
||||
item.IsHalf = false;
|
||||
}
|
||||
|
||||
var index = Items.IndexOf(o);
|
||||
UpdateItemsByIndex(index);
|
||||
}
|
||||
|
||||
internal void PointerReleasedHandler(RatingCharacter o)
|
||||
{
|
||||
var index = Items.IndexOf(o);
|
||||
double newValue = index + 1;
|
||||
if (AllowHalf && o.IsHalf)
|
||||
{
|
||||
newValue = index + 0.5;
|
||||
}
|
||||
|
||||
if (AllowClear && Math.Abs(Value - newValue) < double.Epsilon)
|
||||
{
|
||||
SetCurrentValue(ValueProperty, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetCurrentValue(ValueProperty, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateItemsByValue(double newValue)
|
||||
{
|
||||
RestorePreviousLastItem();
|
||||
var index = (int)Math.Ceiling(newValue - 1);
|
||||
UpdateItemsByIndex(index);
|
||||
UpdateChosenItem(newValue);
|
||||
}
|
||||
|
||||
private void RestorePreviousLastItem()
|
||||
{
|
||||
if (!AllowHalf) return;
|
||||
var item = Items.FirstOrDefault(item => item.IsLast);
|
||||
if (item is null) return;
|
||||
item.Ratio = 1;
|
||||
item.ApplyRatio();
|
||||
}
|
||||
|
||||
private void UpdateItemsByIndex(int index)
|
||||
{
|
||||
for (var i = 0; i < Items.Count; i++)
|
||||
{
|
||||
Items[i].SetSelectedState(i <= index);
|
||||
Items[i].IsLast = i == index;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateChosenItem(double newValue)
|
||||
{
|
||||
var item = Items.FirstOrDefault(item => item.IsLast);
|
||||
if (item is null) return;
|
||||
var ratio = newValue - Math.Floor(newValue);
|
||||
var isFraction = ratio >= double.Epsilon;
|
||||
item.SetSelectedState(AllowHalf || !isFraction);
|
||||
item.Ratio = AllowHalf && isFraction ? ratio : 1;
|
||||
item.ApplyRatio();
|
||||
}
|
||||
}
|
||||
114
src/Ursa/Controls/Rating/RatingCharacter.cs
Normal file
114
src/Ursa/Controls/Rating/RatingCharacter.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.LogicalTree;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
[PseudoClasses(PC_Selected)]
|
||||
[TemplatePart(PART_IconBorder, typeof(Border))]
|
||||
public class RatingCharacter : TemplatedControl
|
||||
{
|
||||
public const string PART_IconBorder = "PART_IconBorder";
|
||||
protected const string PC_Selected = ":selected";
|
||||
|
||||
private Border? _icon;
|
||||
|
||||
public static readonly StyledProperty<bool> AllowHalfProperty =
|
||||
Rating.AllowHalfProperty.AddOwner<RatingCharacter>();
|
||||
|
||||
public static readonly StyledProperty<object> CharacterProperty =
|
||||
Rating.CharacterProperty.AddOwner<RatingCharacter>();
|
||||
|
||||
public static readonly StyledProperty<double> SizeProperty =
|
||||
Rating.SizeProperty.AddOwner<RatingCharacter>();
|
||||
|
||||
public bool AllowHalf
|
||||
{
|
||||
get => GetValue(AllowHalfProperty);
|
||||
set => SetValue(AllowHalfProperty, value);
|
||||
}
|
||||
|
||||
public object Character
|
||||
{
|
||||
get => GetValue(CharacterProperty);
|
||||
set => SetValue(CharacterProperty, value);
|
||||
}
|
||||
|
||||
public double Size
|
||||
{
|
||||
get => GetValue(SizeProperty);
|
||||
set => SetValue(SizeProperty, value);
|
||||
}
|
||||
|
||||
internal bool IsLast { get; set; }
|
||||
|
||||
private bool _isHalf;
|
||||
|
||||
internal bool IsHalf
|
||||
{
|
||||
get => _isHalf;
|
||||
set
|
||||
{
|
||||
if (!AllowHalf) return;
|
||||
_isHalf = value;
|
||||
if (_icon is null) return;
|
||||
_icon.Width = value ? Bounds.Width * 0.5 : Bounds.Width;
|
||||
}
|
||||
}
|
||||
|
||||
internal double Ratio { get; set; } = 1;
|
||||
|
||||
protected override void OnLoaded(RoutedEventArgs e)
|
||||
{
|
||||
base.OnLoaded(e);
|
||||
ApplyRatio();
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
_icon = e.NameScope.Find<Border>(PART_IconBorder);
|
||||
}
|
||||
|
||||
protected override void OnPointerEntered(PointerEventArgs e)
|
||||
{
|
||||
var parent = this.GetLogicalAncestors().OfType<Rating>().FirstOrDefault();
|
||||
parent?.PointerEnteredHandler(this);
|
||||
}
|
||||
|
||||
protected override void OnPointerMoved(PointerEventArgs e)
|
||||
{
|
||||
if (!AllowHalf) return;
|
||||
var p = e.GetPosition(this);
|
||||
IsHalf = p.X < Bounds.Width * 0.5;
|
||||
}
|
||||
|
||||
protected override void OnPointerExited(PointerEventArgs e)
|
||||
{
|
||||
var parent = this.GetLogicalAncestors().OfType<Rating>().FirstOrDefault();
|
||||
parent?.UpdateItemsByValue(parent.Value);
|
||||
}
|
||||
|
||||
protected override void OnPointerReleased(PointerReleasedEventArgs e)
|
||||
{
|
||||
var parent = this.GetLogicalAncestors().OfType<Rating>().FirstOrDefault();
|
||||
parent?.PointerReleasedHandler(this);
|
||||
}
|
||||
|
||||
internal void SetSelectedState(bool value)
|
||||
{
|
||||
PseudoClasses.Set(PC_Selected, value);
|
||||
}
|
||||
|
||||
internal void ApplyRatio()
|
||||
{
|
||||
if (_icon is not null)
|
||||
{
|
||||
_icon.Width = Bounds.Width * Ratio;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user