feat: almost complete.
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user