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

View File

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

View File

@@ -1,7 +1,9 @@
using Avalonia.Controls; using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata; using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
namespace Ursa.Controls; namespace Ursa.Controls;
@@ -15,6 +17,18 @@ public class RatingCharacter : TemplatedControl
private Control? _icon; 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; private bool _isHalf;
internal bool IsHalf internal bool IsHalf
@@ -22,6 +36,7 @@ public class RatingCharacter : TemplatedControl
get => _isHalf; get => _isHalf;
set set
{ {
if (!AllowHalf) return;
_isHalf = value; _isHalf = value;
if (_icon is null) return; if (_icon is null) return;
_icon.Width = value ? Bounds.Width * 0.5 : Bounds.Width; _icon.Width = value ? Bounds.Width * 0.5 : Bounds.Width;
@@ -30,6 +45,12 @@ public class RatingCharacter : TemplatedControl
internal double Ratio { get; set; } internal double Ratio { get; set; }
protected override void OnLoaded(RoutedEventArgs e)
{
base.OnLoaded(e);
AdjustWidth();
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
base.OnApplyTemplate(e); base.OnApplyTemplate(e);
@@ -44,22 +65,30 @@ public class RatingCharacter : TemplatedControl
protected override void OnPointerMoved(PointerEventArgs e) protected override void OnPointerMoved(PointerEventArgs e)
{ {
if (!AllowHalf) return;
var p = e.GetPosition(this); var p = e.GetPosition(this);
IsHalf = p.X < Bounds.Width * 0.5; 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) protected override void OnPointerReleased(PointerReleasedEventArgs e)
{ {
var parent = this.GetLogicalAncestors().OfType<Rating>().FirstOrDefault(); var parent = this.GetLogicalAncestors().OfType<Rating>().FirstOrDefault();
parent?.PointerReleasedHandler(this); parent?.PointerReleasedHandler(this);
} }
public void Select(bool value) internal void Select(bool value)
{ {
PseudoClasses.Set(PC_Selected, value); PseudoClasses.Set(PC_Selected, value);
} }
public void AdjustWidth() internal void AdjustWidth()
{ {
if (_icon is not null) if (_icon is not null)
{ {