feat: almost complete.

This commit is contained in:
Zhang Dian
2024-06-06 14:54:15 +08:00
parent 0a033e0328
commit 8d1ff56e85
3 changed files with 108 additions and 121 deletions

View File

@@ -21,8 +21,7 @@
IsEnabled="{Binding IsEnabled}"
Value="{Binding Value}"
Count="{Binding Count}"
DefaultValue="{Binding DefaultValue }"
Tooltips="{Binding Tooltips }" />
DefaultValue="{Binding DefaultValue }" />
</StackPanel>
</Grid>
<Border Grid.Column="1" VerticalAlignment="Top">
@@ -71,41 +70,40 @@
Grid.Row="4"
Grid.Column="0"
VerticalAlignment="Center"
Content="Value" />
<NumericUpDown
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="-1"
Minimum="-10"
Increment="0.1"
VerticalAlignment="Center"
Value="{Binding Value}" />
<Label
Grid.Row="5"
Grid.Row="6"
Grid.Column="0"
VerticalAlignment="Center"
Content="Count" />
<NumericUpDown
Grid.Row="5"
Grid.Row="6"
Grid.Column="1"
Maximum="100"
Minimum="-1"
Minimum="-10"
Increment="1"
VerticalAlignment="Center"
Value="{Binding Count}" />
<Label
Grid.Row="6"
Grid.Column="0"
VerticalAlignment="Center"
Content="DefaultValue" />
<NumericUpDown
Grid.Row="6"
Grid.Column="1"
Maximum="100"
Minimum="-1"
Increment="0.1"
VerticalAlignment="Center"
Value="{Binding DefaultValue}" />
</Grid>
</Border>
</Grid>

View File

@@ -1,13 +1,10 @@
using System.Collections;
using System.Collections.ObjectModel;
using Avalonia;
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.Input;
using Avalonia.Interactivity;
namespace Ursa.Controls;
@@ -20,7 +17,7 @@ public class Rating : TemplatedControl
protected const string PC_Selected = ":selected";
private ItemsControl? _itemsControl;
private const double Tolerance = 0.0001;
private const double Tolerance = 0.00000001;
public static readonly StyledProperty<double> ValueProperty =
AvaloniaProperty.Register<Rating, double>(nameof(Value), defaultBindingMode: BindingMode.TwoWay);
@@ -43,18 +40,6 @@ public class Rating : TemplatedControl
public static readonly StyledProperty<double> DefaultValueProperty =
AvaloniaProperty.Register<Rating, double>(nameof(DefaultValue));
public static readonly StyledProperty<IList<string>> TooltipsProperty =
AvaloniaProperty.Register<Rating, IList<string>>(nameof(Tooltips));
public static readonly StyledProperty<string> SelectedTooltipProperty =
AvaloniaProperty.Register<Rating, string>(nameof(SelectedTooltip));
public string SelectedTooltip
{
get => GetValue(SelectedTooltipProperty);
set => SetValue(SelectedTooltipProperty, value);
}
public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
AvaloniaProperty.Register<Rating, IDataTemplate?>(nameof(ItemTemplate));
@@ -100,25 +85,19 @@ public class Rating : TemplatedControl
set => SetValue(DefaultValueProperty, value);
}
public IList<string> Tooltips
{
get => GetValue(TooltipsProperty);
set => SetValue(TooltipsProperty, value);
}
public IDataTemplate? ItemTemplate
{
get => GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
public static readonly DirectProperty<Rating, IList> ItemsProperty =
AvaloniaProperty.RegisterDirect<Rating, IList>(
public static readonly DirectProperty<Rating, AvaloniaList<RatingCharacter>> ItemsProperty =
AvaloniaProperty.RegisterDirect<Rating, AvaloniaList<RatingCharacter>>(
nameof(Items), o => o.Items);
private IList _items;
private AvaloniaList<RatingCharacter> _items;
public IList Items
public AvaloniaList<RatingCharacter> Items
{
get => _items;
private set => SetAndRaise(ItemsProperty, ref _items, value);
@@ -126,14 +105,14 @@ public class Rating : TemplatedControl
public Rating()
{
Items = new AvaloniaList<object>();
Tooltips = new ObservableCollection<string>();
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)
@@ -169,6 +148,23 @@ public class Rating : TemplatedControl
}
}
UpdateItemsByValue(Value);
foreach (var item in Items)
{
item.AllowHalf = AllowHalf;
}
AdjustWidth(Value);
}
private void OnAllowHalfChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.NewValue is not bool newValue) return;
foreach (var item in Items)
{
item.AllowHalf = newValue;
}
UpdateItemsByValue(Value);
AdjustWidth(Value);
}
@@ -183,113 +179,77 @@ public class Rating : TemplatedControl
Items.Add(new RatingCharacter());
}
if (DefaultValue > Count)
{
SetCurrentValue(ValueProperty, Math.Max(Count, 0));
}
else
{
SetCurrentValue(ValueProperty, DefaultValue);
}
SetCurrentValue(ValueProperty, DefaultValue);
}
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
UpdateItemsByValue(Value);
AdjustWidth(Value);
}
public void Preview(RatingCharacter o)
{
var index = Items.IndexOf(o);
var tooltipsCount = Tooltips.Count;
if (tooltipsCount > 0)
{
if (index < tooltipsCount)
{
SetCurrentValue(SelectedTooltipProperty, Tooltips[index]);
}
else
{
SetCurrentValue(SelectedTooltipProperty, string.Empty);
}
}
UpdateItemsByIndex(index);
}
protected override void OnPointerExited(PointerEventArgs e)
{
UpdateItemsByValue(Value);
}
public void PointerReleasedHandler(RatingCharacter o)
internal void PointerReleasedHandler(RatingCharacter o)
{
var index = Items.IndexOf(o);
double newValue = index + 1;
if (o.IsHalf)
if (AllowHalf && o.IsHalf)
{
newValue = index + 0.5;
}
if (AllowClear && Math.Abs(Value - newValue) < Tolerance)
{
UpdateItemsByValue(-1);
SetCurrentValue(ValueProperty, 0);
}
else
{
UpdateItemsByValue(newValue);
SetCurrentValue(ValueProperty, newValue);
}
}
private void UpdateItemsByIndex(int index)
{
var isInt = Math.Abs(Value - Math.Floor(Value)) < Tolerance;
for (var i = 0; i <= index && i < Items.Count; i++)
{
if (Items[i] is RatingCharacter item)
{
item.Select(true);
item.IsHalf = !isInt && i == index;
}
}
for (var i = index + 1; i >= 0 && i < Items.Count; i++)
{
if (Items[i] is RatingCharacter item)
{
item.Select(false);
item.IsHalf = false;
}
}
}
private void UpdateItemsByValue(double newValue)
internal void UpdateItemsByValue(double newValue)
{
var index = (int)Math.Ceiling(newValue - 1);
UpdateItemsByIndex(index);
}
private void AdjustWidth(double newValue)
private void UpdateItemsByIndex(int index)
{
for (var i = 0; i < Items.Count; i++)
{
if (i == index) continue;
Items[i].Select(i < index);
Items[i].IsLast = false;
Items[i].IsHalf = false;
Items[i].Ratio = 1;
}
if (index >= Items.Count || index < 0) return;
var ratio = Math.Abs(Value - Math.Floor(Value));
var isInt = ratio < Tolerance;
Items[index].Select(true);
Items[index].IsLast = true;
Items[index].IsHalf = AllowHalf && !isInt;
Items[index].Ratio = AllowHalf ? ratio : 1;
}
internal void AdjustWidth(double newValue)
{
var ratio = Math.Abs(newValue - Math.Floor(newValue));
foreach (var character in Items)
var isInt = ratio < Tolerance;
ratio = AllowHalf && !isInt ? ratio : 1;
foreach (var item in Items)
{
if (character is RatingCharacter item)
{
if (item.IsHalf)
{
item.Ratio = ratio;
}
else
{
item.Ratio = 1;
}
item.AdjustWidth();
}
item.Ratio = item.IsLast ? ratio : 1;
item.AdjustWidth();
}
}
}

View File

@@ -1,7 +1,9 @@
using Avalonia.Controls;
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;
@@ -15,6 +17,18 @@ public class RatingCharacter : TemplatedControl
private Control? _icon;
public static readonly StyledProperty<bool> AllowHalfProperty = AvaloniaProperty.Register<RatingCharacter, bool>(
nameof(AllowHalf));
public bool AllowHalf
{
get => GetValue(AllowHalfProperty);
set => SetValue(AllowHalfProperty, value);
}
internal bool IsLast { get; set; }
private bool _isHalf;
internal bool IsHalf
@@ -22,6 +36,7 @@ public class RatingCharacter : TemplatedControl
get => _isHalf;
set
{
if (!AllowHalf) return;
_isHalf = value;
if (_icon is null) return;
_icon.Width = value ? Bounds.Width * 0.5 : Bounds.Width;
@@ -30,6 +45,12 @@ public class RatingCharacter : TemplatedControl
internal double Ratio { get; set; }
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
AdjustWidth();
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
@@ -44,22 +65,30 @@ public class RatingCharacter : TemplatedControl
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);
parent?.AdjustWidth(parent.Value);
}
protected override void OnPointerReleased(PointerReleasedEventArgs e)
{
var parent = this.GetLogicalAncestors().OfType<Rating>().FirstOrDefault();
parent?.PointerReleasedHandler(this);
}
public void Select(bool value)
internal void Select(bool value)
{
PseudoClasses.Set(PC_Selected, value);
}
public void AdjustWidth()
internal void AdjustWidth()
{
if (_icon is not null)
{