mirror of
https://gitee.com/chinware/atomui.git
synced 2024-11-29 18:38:16 +08:00
Refactor RadioButton
This commit is contained in:
parent
c0db4ba822
commit
d0f71b4cc5
@ -48,8 +48,8 @@ internal class CheckBoxIndicator : Control, IWaveAdornerInfoProvider
|
||||
|
||||
public bool? IsChecked
|
||||
{
|
||||
get => GetValue(ToggleButton.IsCheckedProperty);
|
||||
set => SetValue(ToggleButton.IsCheckedProperty, value);
|
||||
get => GetValue(IsCheckedProperty);
|
||||
set => SetValue(IsCheckedProperty, value);
|
||||
}
|
||||
|
||||
public double Size
|
||||
|
@ -72,11 +72,12 @@ internal class CheckBoxTheme : BaseControlTheme
|
||||
var commonStyle = new Style(selector => selector.Nesting());
|
||||
commonStyle.Add(CheckBox.CursorProperty, new Cursor(StandardCursorType.Hand));
|
||||
commonStyle.Add(CheckBox.HorizontalAlignmentProperty, HorizontalAlignment.Left);
|
||||
Add(commonStyle);
|
||||
|
||||
|
||||
var contentPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ContentPresenterPart));
|
||||
contentPresenterStyle.Add(ContentPresenter.MarginProperty, CheckBoxTokenResourceKey.TextMargin);
|
||||
Add(contentPresenterStyle);
|
||||
commonStyle.Add(contentPresenterStyle);
|
||||
|
||||
Add(commonStyle);
|
||||
|
||||
var disableStyle =
|
||||
new Style(selector => selector.Nesting().PropertyEquals(InputElement.IsEnabledProperty, false));
|
||||
|
@ -517,6 +517,7 @@ namespace AtomUI.Theme.Styling
|
||||
public static readonly TokenResourceKey RadioColor = new TokenResourceKey("RadioButton.RadioColor", "AtomUI.Token");
|
||||
public static readonly TokenResourceKey RadioBgColor = new TokenResourceKey("RadioButton.RadioBgColor", "AtomUI.Token");
|
||||
public static readonly TokenResourceKey DotPadding = new TokenResourceKey("RadioButton.DotPadding", "AtomUI.Token");
|
||||
public static readonly TokenResourceKey TextMargin = new TokenResourceKey("RadioButton.TextMargin", "AtomUI.Token");
|
||||
}
|
||||
|
||||
public static class SegmentedTokenResourceKey
|
||||
|
@ -1,17 +1,4 @@
|
||||
using AtomUI.Controls.Utils;
|
||||
using AtomUI.Media;
|
||||
using AtomUI.Theme.Data;
|
||||
using AtomUI.Theme.Styling;
|
||||
using AtomUI.Theme.Utils;
|
||||
using AtomUI.Utils;
|
||||
using Avalonia;
|
||||
using Avalonia.Animation;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
using Avalonia.Rendering;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
@ -19,273 +6,10 @@ namespace AtomUI.Controls;
|
||||
using AvaloniaRadioButton = Avalonia.Controls.RadioButton;
|
||||
|
||||
public class RadioButton : AvaloniaRadioButton,
|
||||
ICustomHitTest,
|
||||
IWaveAdornerInfoProvider
|
||||
ICustomHitTest
|
||||
{
|
||||
#region 内部属性定义
|
||||
|
||||
internal static readonly StyledProperty<double> RadioSizeProperty =
|
||||
AvaloniaProperty.Register<Button, double>(nameof(RadioSize));
|
||||
|
||||
internal static readonly StyledProperty<double> PaddingInlineProperty =
|
||||
AvaloniaProperty.Register<Button, double>(nameof(PaddingInline));
|
||||
|
||||
internal static readonly StyledProperty<IBrush?> RadioBorderBrushProperty =
|
||||
AvaloniaProperty.Register<Button, IBrush?>(nameof(RadioBorderBrush));
|
||||
|
||||
internal static readonly StyledProperty<IBrush?> RadioInnerBackgroundProperty =
|
||||
AvaloniaProperty.Register<Button, IBrush?>(nameof(RadioInnerBackground));
|
||||
|
||||
internal static readonly StyledProperty<IBrush?> RadioBackgroundProperty =
|
||||
AvaloniaProperty.Register<Button, IBrush?>(nameof(RadioBackground));
|
||||
|
||||
internal static readonly StyledProperty<Thickness> RadioBorderThicknessProperty =
|
||||
AvaloniaProperty.Register<Button, Thickness>(nameof(RadioBorderThickness));
|
||||
|
||||
internal static readonly StyledProperty<double> RadioDotEffectSizeProperty =
|
||||
AvaloniaProperty.Register<Button, double>(nameof(RadioDotEffectSize));
|
||||
|
||||
internal static readonly StyledProperty<double> DotSizeValueProperty =
|
||||
AvaloniaProperty.Register<RadioButton, double>(
|
||||
nameof(DotSizeValue));
|
||||
|
||||
internal static readonly StyledProperty<double> DotPaddingProperty =
|
||||
AvaloniaProperty.Register<RadioButton, double>(
|
||||
nameof(DotPadding));
|
||||
|
||||
internal double RadioSize
|
||||
{
|
||||
get => GetValue(RadioSizeProperty);
|
||||
set => SetValue(RadioSizeProperty, value);
|
||||
}
|
||||
|
||||
internal double PaddingInline
|
||||
{
|
||||
get => GetValue(PaddingInlineProperty);
|
||||
set => SetValue(PaddingInlineProperty, value);
|
||||
}
|
||||
|
||||
internal IBrush? RadioBorderBrush
|
||||
{
|
||||
get => GetValue(RadioBorderBrushProperty);
|
||||
set => SetValue(RadioBorderBrushProperty, value);
|
||||
}
|
||||
|
||||
internal IBrush? RadioInnerBackground
|
||||
{
|
||||
get => GetValue(RadioInnerBackgroundProperty);
|
||||
set => SetValue(RadioInnerBackgroundProperty, value);
|
||||
}
|
||||
|
||||
internal IBrush? RadioBackground
|
||||
{
|
||||
get => GetValue(RadioBackgroundProperty);
|
||||
set => SetValue(RadioBackgroundProperty, value);
|
||||
}
|
||||
|
||||
internal Thickness RadioBorderThickness
|
||||
{
|
||||
get => GetValue(RadioBorderThicknessProperty);
|
||||
set => SetValue(RadioBorderThicknessProperty, value);
|
||||
}
|
||||
|
||||
internal double RadioDotEffectSize
|
||||
{
|
||||
get => GetValue(RadioDotEffectSizeProperty);
|
||||
set => SetValue(RadioDotEffectSizeProperty, value);
|
||||
}
|
||||
|
||||
internal double DotSizeValue
|
||||
{
|
||||
get => GetValue(DotSizeValueProperty);
|
||||
set => SetValue(DotSizeValueProperty, value);
|
||||
}
|
||||
|
||||
internal double DotPadding
|
||||
{
|
||||
get => GetValue(DotPaddingProperty);
|
||||
set => SetValue(DotPaddingProperty, value);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private IPen? _cachedPen;
|
||||
private ControlStyleState _styleState;
|
||||
|
||||
static RadioButton()
|
||||
{
|
||||
AffectsRender<RadioButton>(
|
||||
RadioBorderBrushProperty,
|
||||
RadioInnerBackgroundProperty,
|
||||
RadioBackgroundProperty,
|
||||
RadioBorderThicknessProperty,
|
||||
RadioDotEffectSizeProperty);
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnPropertyChanged(e);
|
||||
HandlePropertyChangedForStyle(e);
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
HandleTemplateApplied(e.NameScope);
|
||||
}
|
||||
|
||||
private void HandleTemplateApplied(INameScope scope)
|
||||
{
|
||||
TokenResourceBinder.CreateGlobalResourceBinding(this, RadioBorderThicknessProperty,
|
||||
GlobalTokenResourceKey.BorderThickness, BindingPriority.Template,
|
||||
new RenderScaleAwareThicknessConfigure(this));
|
||||
Cursor = new Cursor(StandardCursorType.Hand);
|
||||
CollectStyleState();
|
||||
RadioDotEffectSize = CalculateDotSize(IsEnabled, IsChecked.HasValue && IsChecked.Value);
|
||||
SetupTransitions();
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
var size = base.MeasureOverride(availableSize);
|
||||
var targetWidth = size.Width + RadioSize + PaddingInline;
|
||||
var targetHeight = Math.Max(size.Height, RadioSize);
|
||||
return new Size(targetWidth, targetHeight);
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
var arrangeRect = RadioTextRect();
|
||||
|
||||
var visualChildren = VisualChildren;
|
||||
var visualCount = visualChildren.Count;
|
||||
|
||||
for (var i = 0; i < visualCount; i++)
|
||||
{
|
||||
var visual = visualChildren[i];
|
||||
if (visual is Layoutable layoutable)
|
||||
{
|
||||
layoutable.Arrange(arrangeRect);
|
||||
}
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
public sealed override void Render(DrawingContext context)
|
||||
{
|
||||
var radioRect = RadioRect();
|
||||
var penWidth = RadioBorderThickness.Top;
|
||||
PenUtils.TryModifyOrCreate(ref _cachedPen, RadioBorderBrush, RadioBorderThickness.Top);
|
||||
context.DrawEllipse(RadioBackground, _cachedPen, radioRect.Deflate(penWidth / 2));
|
||||
if (IsChecked.HasValue && IsChecked.Value)
|
||||
{
|
||||
var dotDiameter = RadioDotEffectSize / 2;
|
||||
context.DrawEllipse(RadioInnerBackground, null, radioRect.Center, dotDiameter, dotDiameter);
|
||||
}
|
||||
}
|
||||
|
||||
public bool HitTest(Point point)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
private void CollectStyleState()
|
||||
{
|
||||
ControlStateUtils.InitCommonState(this, ref _styleState);
|
||||
if (IsPressed)
|
||||
{
|
||||
_styleState |= ControlStyleState.Sunken;
|
||||
}
|
||||
else
|
||||
{
|
||||
_styleState |= ControlStyleState.Raised;
|
||||
}
|
||||
|
||||
if (IsChecked.HasValue && IsChecked.Value)
|
||||
{
|
||||
_styleState |= ControlStyleState.On;
|
||||
}
|
||||
else
|
||||
{
|
||||
_styleState |= ControlStyleState.Off;
|
||||
}
|
||||
}
|
||||
|
||||
private double CalculateDotSize(bool isEnabled, bool isChecked)
|
||||
{
|
||||
double targetValue;
|
||||
if (isChecked)
|
||||
{
|
||||
if (isEnabled)
|
||||
{
|
||||
targetValue = DotSizeValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetValue = RadioSize - DotPadding * 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
targetValue = DotSizeValue * 0.6;
|
||||
}
|
||||
|
||||
return targetValue;
|
||||
}
|
||||
|
||||
private void SetupTransitions()
|
||||
{
|
||||
Transitions = new Transitions
|
||||
{
|
||||
AnimationUtils.CreateTransition<SolidColorBrushTransition>(RadioBorderBrushProperty),
|
||||
AnimationUtils.CreateTransition<DoubleTransition>(RadioDotEffectSizeProperty),
|
||||
AnimationUtils.CreateTransition<SolidColorBrushTransition>(RadioBackgroundProperty,
|
||||
GlobalTokenResourceKey.MotionDurationFast)
|
||||
};
|
||||
}
|
||||
|
||||
// Measure 之后才有值
|
||||
private Rect RadioRect()
|
||||
{
|
||||
var offsetY = (DesiredSize.Height - Margin.Top - Margin.Bottom - RadioSize) / 2;
|
||||
return new Rect(0d, offsetY, RadioSize, RadioSize);
|
||||
}
|
||||
|
||||
private Rect RadioTextRect()
|
||||
{
|
||||
var offsetX = RadioSize + PaddingInline;
|
||||
return new Rect(offsetX, 0d, DesiredSize.Width - offsetX, DesiredSize.Height);
|
||||
}
|
||||
|
||||
private void HandlePropertyChangedForStyle(AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.Property == IsPointerOverProperty ||
|
||||
e.Property == IsCheckedProperty ||
|
||||
e.Property == IsEnabledProperty)
|
||||
{
|
||||
CollectStyleState();
|
||||
if (VisualRoot is not null)
|
||||
{
|
||||
RadioDotEffectSize = CalculateDotSize(IsEnabled, IsChecked.HasValue && IsChecked.Value);
|
||||
}
|
||||
|
||||
if (e.Property == IsCheckedProperty &&
|
||||
_styleState.HasFlag(ControlStyleState.Enabled) &&
|
||||
_styleState.HasFlag(ControlStyleState.On))
|
||||
{
|
||||
WaveSpiritAdorner.ShowWaveAdorner(this, WaveType.CircleWave);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Rect WaveGeometry()
|
||||
{
|
||||
return RadioRect();
|
||||
}
|
||||
|
||||
public CornerRadius WaveBorderRadius()
|
||||
{
|
||||
return new CornerRadius(RadioSize / 2);
|
||||
}
|
||||
}
|
@ -1,7 +1,13 @@
|
||||
using AtomUI.Theme;
|
||||
using AtomUI.Theme.Styling;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Presenters;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Data.Converters;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Styling;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
@ -9,55 +15,143 @@ namespace AtomUI.Controls;
|
||||
[ControlThemeProvider]
|
||||
internal class RadioButtonTheme : BaseControlTheme
|
||||
{
|
||||
internal const string FramePart = "PART_Frame";
|
||||
internal const string IndicatorPart = "PART_Indicator";
|
||||
internal const string ContentPresenterPart = "PART_ContentPresenter";
|
||||
|
||||
public RadioButtonTheme()
|
||||
: base(typeof(RadioButton))
|
||||
{
|
||||
}
|
||||
|
||||
protected override void BuildStyles()
|
||||
protected override IControlTemplate BuildControlTemplate()
|
||||
{
|
||||
this.Add(RadioButton.RadioSizeProperty, RadioButtonTokenResourceKey.RadioSize);
|
||||
this.Add(RadioButton.PaddingInlineProperty, GlobalTokenResourceKey.PaddingXS);
|
||||
this.Add(RadioButton.RadioBorderBrushProperty, GlobalTokenResourceKey.ColorBorder);
|
||||
this.Add(RadioButton.DotSizeValueProperty, RadioButtonTokenResourceKey.DotSize);
|
||||
this.Add(RadioButton.DotPaddingProperty, RadioButtonTokenResourceKey.DotPadding);
|
||||
BuildEnabledStyle();
|
||||
BuildDisabledStyle();
|
||||
return new FuncControlTemplate<RadioButton>((radioButton, scope) =>
|
||||
{
|
||||
var frame = new Border
|
||||
{
|
||||
Name = FramePart
|
||||
};
|
||||
|
||||
CreateTemplateParentBinding(frame, Border.BackgroundProperty, RadioButton.BackgroundProperty);
|
||||
CreateTemplateParentBinding(frame, Border.BorderBrushProperty, RadioButton.BorderBrushProperty);
|
||||
CreateTemplateParentBinding(frame, Border.BorderThicknessProperty, RadioButton.BorderThicknessProperty);
|
||||
CreateTemplateParentBinding(frame, Border.CornerRadiusProperty, RadioButton.CornerRadiusProperty);
|
||||
|
||||
var layout = new DockPanel
|
||||
{
|
||||
LastChildFill = true
|
||||
};
|
||||
|
||||
var indicator = new RadioIndicator()
|
||||
{
|
||||
Name = IndicatorPart
|
||||
};
|
||||
|
||||
CreateTemplateParentBinding(indicator, CheckBoxIndicator.IsCheckedProperty, RadioButton.IsCheckedProperty);
|
||||
layout.Children.Add(indicator);
|
||||
|
||||
var contentPresenter = new ContentPresenter()
|
||||
{
|
||||
Name = ContentPresenterPart,
|
||||
RecognizesAccessKey = true
|
||||
};
|
||||
|
||||
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentTemplateProperty,
|
||||
RadioButton.ContentTemplateProperty);
|
||||
CreateTemplateParentBinding(contentPresenter, ContentPresenter.ContentProperty,
|
||||
RadioButton.ContentProperty);
|
||||
CreateTemplateParentBinding(contentPresenter, ContentPresenter.FontSizeProperty,
|
||||
RadioButton.FontSizeProperty);
|
||||
CreateTemplateParentBinding(contentPresenter, ContentPresenter.IsVisibleProperty,
|
||||
RadioButton.ContentProperty,
|
||||
BindingMode.Default, ObjectConverters.IsNotNull);
|
||||
|
||||
layout.Children.Add(contentPresenter);
|
||||
frame.Child = layout;
|
||||
return frame;
|
||||
});
|
||||
}
|
||||
|
||||
private void BuildDisabledStyle()
|
||||
protected override void BuildStyles()
|
||||
{
|
||||
var commonStyle = new Style(selector => selector.Nesting());
|
||||
commonStyle.Add(RadioButton.CursorProperty, new Cursor(StandardCursorType.Hand));
|
||||
commonStyle.Add(RadioButton.HorizontalAlignmentProperty, HorizontalAlignment.Left);
|
||||
|
||||
var disableStyle =
|
||||
new Style(selector => selector.Nesting().PropertyEquals(InputElement.IsEnabledProperty, false));
|
||||
disableStyle.Add(TemplatedControl.ForegroundProperty, GlobalTokenResourceKey.ColorTextDisabled);
|
||||
disableStyle.Add(RadioButton.RadioBackgroundProperty, GlobalTokenResourceKey.ColorBgContainerDisabled);
|
||||
disableStyle.Add(RadioButton.RadioInnerBackgroundProperty, RadioButtonTokenResourceKey.DotColorDisabled);
|
||||
Add(disableStyle);
|
||||
commonStyle.Add(disableStyle);
|
||||
|
||||
var contentPresenterStyle = new Style(selector => selector.Nesting().Template().Name(ContentPresenterPart));
|
||||
contentPresenterStyle.Add(ContentPresenter.MarginProperty, CheckBoxTokenResourceKey.TextMargin);
|
||||
commonStyle.Add(contentPresenterStyle);
|
||||
|
||||
Add(commonStyle);
|
||||
|
||||
BuildIndicatorStyle();
|
||||
}
|
||||
|
||||
private void BuildEnabledStyle()
|
||||
private void BuildIndicatorStyle()
|
||||
{
|
||||
{
|
||||
var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(IndicatorPart));
|
||||
indicatorStyle.Add(RadioIndicator.RadioSizeProperty, RadioButtonTokenResourceKey.RadioSize);
|
||||
indicatorStyle.Add(RadioIndicator.WidthProperty, RadioButtonTokenResourceKey.RadioSize);
|
||||
indicatorStyle.Add(RadioIndicator.HeightProperty, RadioButtonTokenResourceKey.RadioSize);
|
||||
indicatorStyle.Add(RadioIndicator.PaddingInlineProperty, GlobalTokenResourceKey.PaddingXS);
|
||||
indicatorStyle.Add(RadioIndicator.DotSizeValueProperty, RadioButtonTokenResourceKey.DotSize);
|
||||
indicatorStyle.Add(RadioIndicator.DotPaddingProperty, RadioButtonTokenResourceKey.DotPadding);
|
||||
Add(indicatorStyle);
|
||||
}
|
||||
var disableStyle =
|
||||
new Style(selector => selector.Nesting().PropertyEquals(InputElement.IsEnabledProperty, false));
|
||||
{
|
||||
var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(IndicatorPart));
|
||||
indicatorStyle.Add(RadioIndicator.RadioBackgroundProperty, GlobalTokenResourceKey.ColorBgContainerDisabled);
|
||||
indicatorStyle.Add(RadioIndicator.RadioBorderBrushProperty, GlobalTokenResourceKey.ColorBorder);
|
||||
indicatorStyle.Add(RadioIndicator.RadioInnerBackgroundProperty,
|
||||
RadioButtonTokenResourceKey.DotColorDisabled);
|
||||
disableStyle.Add(indicatorStyle);
|
||||
}
|
||||
Add(disableStyle);
|
||||
|
||||
var enabledStyle =
|
||||
new Style(selector => selector.Nesting().PropertyEquals(InputElement.IsEnabledProperty, true));
|
||||
enabledStyle.Add(RadioButton.RadioInnerBackgroundProperty, RadioButtonTokenResourceKey.RadioColor);
|
||||
{
|
||||
{
|
||||
var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(IndicatorPart));
|
||||
indicatorStyle.Add(RadioIndicator.RadioInnerBackgroundProperty, RadioButtonTokenResourceKey.RadioColor);
|
||||
indicatorStyle.Add(RadioIndicator.RadioBorderBrushProperty, GlobalTokenResourceKey.ColorBorder);
|
||||
enabledStyle.Add(indicatorStyle);
|
||||
}
|
||||
|
||||
// 选中
|
||||
var checkedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Checked));
|
||||
checkedStyle.Add(RadioButton.RadioBorderBrushProperty, GlobalTokenResourceKey.ColorPrimary);
|
||||
checkedStyle.Add(RadioButton.RadioBackgroundProperty, GlobalTokenResourceKey.ColorPrimary);
|
||||
|
||||
enabledStyle.Add(checkedStyle);
|
||||
|
||||
// 没选中
|
||||
var unCheckedStyle =
|
||||
new Style(selector => selector.Nesting().Not(x => x.Nesting().Class(StdPseudoClass.Checked)));
|
||||
unCheckedStyle.Add(RadioButton.RadioBackgroundProperty, GlobalTokenResourceKey.ColorBgContainer);
|
||||
var unCheckedHoverStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.PointerOver));
|
||||
unCheckedHoverStyle.Add(RadioButton.RadioBorderBrushProperty, GlobalTokenResourceKey.ColorPrimary);
|
||||
unCheckedStyle.Add(unCheckedHoverStyle);
|
||||
|
||||
enabledStyle.Add(unCheckedStyle);
|
||||
var checkedStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.Checked));
|
||||
{
|
||||
var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(IndicatorPart));
|
||||
indicatorStyle.Add(RadioIndicator.RadioBorderBrushProperty, GlobalTokenResourceKey.ColorPrimary);
|
||||
indicatorStyle.Add(RadioIndicator.RadioBackgroundProperty, GlobalTokenResourceKey.ColorPrimary);
|
||||
checkedStyle.Add(indicatorStyle);
|
||||
}
|
||||
enabledStyle.Add(checkedStyle);
|
||||
|
||||
var unCheckedStyle =
|
||||
new Style(selector => selector.Nesting().Not(x => x.Nesting().Class(StdPseudoClass.Checked)));
|
||||
{
|
||||
var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(IndicatorPart));
|
||||
indicatorStyle.Add(RadioIndicator.RadioBackgroundProperty, GlobalTokenResourceKey.ColorBgContainer);
|
||||
unCheckedStyle.Add(indicatorStyle);
|
||||
}
|
||||
var unCheckedHoverStyle = new Style(selector => selector.Nesting().Class(StdPseudoClass.PointerOver));
|
||||
{
|
||||
var indicatorStyle = new Style(selector => selector.Nesting().Template().Name(IndicatorPart));
|
||||
indicatorStyle.Add(RadioIndicator.RadioBorderBrushProperty, GlobalTokenResourceKey.ColorPrimary);
|
||||
unCheckedHoverStyle.Add(indicatorStyle);
|
||||
}
|
||||
unCheckedStyle.Add(unCheckedHoverStyle);
|
||||
enabledStyle.Add(unCheckedStyle);
|
||||
}
|
||||
Add(enabledStyle);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using AtomUI.Theme.TokenSystem;
|
||||
using Avalonia;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
@ -27,7 +28,10 @@ internal class RadioButtonToken : AbstractControlDesignToken
|
||||
public Color RadioColor { get; set; }
|
||||
|
||||
public Color RadioBgColor { get; set; }
|
||||
|
||||
public double DotPadding { get; set; }
|
||||
|
||||
public Thickness TextMargin { get; set; }
|
||||
|
||||
public RadioButtonToken()
|
||||
: base(ID)
|
||||
@ -59,5 +63,6 @@ internal class RadioButtonToken : AbstractControlDesignToken
|
||||
// internal
|
||||
RadioColor = wireFrame ? colorPrimary : colorWhite;
|
||||
RadioBgColor = wireFrame ? colorBgContainer : colorPrimary;
|
||||
TextMargin = new Thickness(_globalToken.MarginXXS, 0, 0, 0);
|
||||
}
|
||||
}
|
221
src/AtomUI.Controls/RadioButton/RadioIndicator.cs
Normal file
221
src/AtomUI.Controls/RadioButton/RadioIndicator.cs
Normal file
@ -0,0 +1,221 @@
|
||||
using AtomUI.Controls.Utils;
|
||||
using AtomUI.Media;
|
||||
using AtomUI.Theme.Data;
|
||||
using AtomUI.Theme.Styling;
|
||||
using AtomUI.Theme.Utils;
|
||||
using AtomUI.Utils;
|
||||
using Avalonia;
|
||||
using Avalonia.Animation;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Data;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
internal class RadioIndicator : Control, IWaveAdornerInfoProvider
|
||||
{
|
||||
public static readonly StyledProperty<bool?> IsCheckedProperty =
|
||||
ToggleButton.IsCheckedProperty.AddOwner<CheckBoxIndicator>();
|
||||
|
||||
public static readonly StyledProperty<double> RadioSizeProperty =
|
||||
AvaloniaProperty.Register<RadioIndicator, double>(nameof(RadioSize));
|
||||
|
||||
public static readonly StyledProperty<double> PaddingInlineProperty =
|
||||
AvaloniaProperty.Register<RadioIndicator, double>(nameof(PaddingInline));
|
||||
|
||||
public static readonly StyledProperty<IBrush?> RadioBorderBrushProperty =
|
||||
AvaloniaProperty.Register<RadioIndicator, IBrush?>(nameof(RadioBorderBrush));
|
||||
|
||||
public static readonly StyledProperty<IBrush?> RadioInnerBackgroundProperty =
|
||||
AvaloniaProperty.Register<RadioIndicator, IBrush?>(nameof(RadioInnerBackground));
|
||||
|
||||
public static readonly StyledProperty<IBrush?> RadioBackgroundProperty =
|
||||
AvaloniaProperty.Register<RadioIndicator, IBrush?>(nameof(RadioBackground));
|
||||
|
||||
public static readonly StyledProperty<Thickness> RadioBorderThicknessProperty =
|
||||
AvaloniaProperty.Register<RadioIndicator, Thickness>(nameof(RadioBorderThickness));
|
||||
|
||||
public static readonly StyledProperty<double> RadioDotEffectSizeProperty =
|
||||
AvaloniaProperty.Register<RadioIndicator, double>(nameof(RadioDotEffectSize));
|
||||
|
||||
public static readonly StyledProperty<double> DotSizeValueProperty =
|
||||
AvaloniaProperty.Register<RadioButton, double>(
|
||||
nameof(DotSizeValue));
|
||||
|
||||
public static readonly StyledProperty<double> DotPaddingProperty =
|
||||
AvaloniaProperty.Register<RadioButton, double>(
|
||||
nameof(DotPadding));
|
||||
|
||||
public bool? IsChecked
|
||||
{
|
||||
get => GetValue(IsCheckedProperty);
|
||||
set => SetValue(IsCheckedProperty, value);
|
||||
}
|
||||
|
||||
public double RadioSize
|
||||
{
|
||||
get => GetValue(RadioSizeProperty);
|
||||
set => SetValue(RadioSizeProperty, value);
|
||||
}
|
||||
|
||||
public double PaddingInline
|
||||
{
|
||||
get => GetValue(PaddingInlineProperty);
|
||||
set => SetValue(PaddingInlineProperty, value);
|
||||
}
|
||||
|
||||
public IBrush? RadioBorderBrush
|
||||
{
|
||||
get => GetValue(RadioBorderBrushProperty);
|
||||
set => SetValue(RadioBorderBrushProperty, value);
|
||||
}
|
||||
|
||||
public IBrush? RadioInnerBackground
|
||||
{
|
||||
get => GetValue(RadioInnerBackgroundProperty);
|
||||
set => SetValue(RadioInnerBackgroundProperty, value);
|
||||
}
|
||||
|
||||
public IBrush? RadioBackground
|
||||
{
|
||||
get => GetValue(RadioBackgroundProperty);
|
||||
set => SetValue(RadioBackgroundProperty, value);
|
||||
}
|
||||
|
||||
public Thickness RadioBorderThickness
|
||||
{
|
||||
get => GetValue(RadioBorderThicknessProperty);
|
||||
set => SetValue(RadioBorderThicknessProperty, value);
|
||||
}
|
||||
|
||||
public double RadioDotEffectSize
|
||||
{
|
||||
get => GetValue(RadioDotEffectSizeProperty);
|
||||
set => SetValue(RadioDotEffectSizeProperty, value);
|
||||
}
|
||||
|
||||
public double DotSizeValue
|
||||
{
|
||||
get => GetValue(DotSizeValueProperty);
|
||||
set => SetValue(DotSizeValueProperty, value);
|
||||
}
|
||||
|
||||
public double DotPadding
|
||||
{
|
||||
get => GetValue(DotPaddingProperty);
|
||||
set => SetValue(DotPaddingProperty, value);
|
||||
}
|
||||
|
||||
private IPen? _cachedPen;
|
||||
private ControlStyleState _styleState;
|
||||
|
||||
static RadioIndicator()
|
||||
{
|
||||
AffectsRender<RadioIndicator>(
|
||||
IsCheckedProperty,
|
||||
RadioBorderBrushProperty,
|
||||
RadioInnerBackgroundProperty,
|
||||
RadioBackgroundProperty,
|
||||
RadioBorderThicknessProperty,
|
||||
RadioDotEffectSizeProperty);
|
||||
}
|
||||
|
||||
public override void ApplyTemplate()
|
||||
{
|
||||
base.ApplyTemplate();
|
||||
CollectStyleState();
|
||||
RadioDotEffectSize = CalculateDotSize(IsEnabled, IsChecked.HasValue && IsChecked.Value);
|
||||
TokenResourceBinder.CreateGlobalResourceBinding(this, RadioBorderThicknessProperty,
|
||||
GlobalTokenResourceKey.BorderThickness, BindingPriority.Template,
|
||||
new RenderScaleAwareThicknessConfigure(this));
|
||||
|
||||
Transitions ??= new Transitions
|
||||
{
|
||||
AnimationUtils.CreateTransition<SolidColorBrushTransition>(RadioBorderBrushProperty),
|
||||
AnimationUtils.CreateTransition<DoubleTransition>(RadioDotEffectSizeProperty),
|
||||
AnimationUtils.CreateTransition<SolidColorBrushTransition>(RadioBackgroundProperty,
|
||||
GlobalTokenResourceKey.MotionDurationFast)
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnPropertyChanged(e);
|
||||
if (e.Property == IsPointerOverProperty ||
|
||||
e.Property == IsCheckedProperty ||
|
||||
e.Property == IsEnabledProperty)
|
||||
{
|
||||
CollectStyleState();
|
||||
if (VisualRoot is not null)
|
||||
{
|
||||
RadioDotEffectSize = CalculateDotSize(IsEnabled, IsChecked.HasValue && IsChecked.Value);
|
||||
}
|
||||
|
||||
if (e.Property == IsCheckedProperty &&
|
||||
_styleState.HasFlag(ControlStyleState.Enabled) &&
|
||||
_styleState.HasFlag(ControlStyleState.On))
|
||||
{
|
||||
WaveSpiritAdorner.ShowWaveAdorner(this, WaveType.CircleWave);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CollectStyleState()
|
||||
{
|
||||
ControlStateUtils.InitCommonState(this, ref _styleState);
|
||||
|
||||
if (IsChecked.HasValue && IsChecked.Value)
|
||||
{
|
||||
_styleState |= ControlStyleState.On;
|
||||
}
|
||||
else
|
||||
{
|
||||
_styleState |= ControlStyleState.Off;
|
||||
}
|
||||
}
|
||||
|
||||
private double CalculateDotSize(bool isEnabled, bool isChecked)
|
||||
{
|
||||
double targetValue;
|
||||
if (isChecked)
|
||||
{
|
||||
if (isEnabled)
|
||||
{
|
||||
targetValue = DotSizeValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetValue = RadioSize - DotPadding * 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
targetValue = DotSizeValue * 0.6;
|
||||
}
|
||||
|
||||
return targetValue;
|
||||
}
|
||||
|
||||
public sealed override void Render(DrawingContext context)
|
||||
{
|
||||
var penWidth = RadioBorderThickness.Top;
|
||||
PenUtils.TryModifyOrCreate(ref _cachedPen, RadioBorderBrush, RadioBorderThickness.Top);
|
||||
context.DrawEllipse(RadioBackground, _cachedPen, Bounds.Deflate(penWidth / 2));
|
||||
if (IsChecked.HasValue && IsChecked.Value)
|
||||
{
|
||||
var dotDiameter = RadioDotEffectSize / 2;
|
||||
context.DrawEllipse(RadioInnerBackground, null, Bounds.Center, dotDiameter, dotDiameter);
|
||||
}
|
||||
}
|
||||
|
||||
public Rect WaveGeometry()
|
||||
{
|
||||
return new Rect(Bounds.Size);
|
||||
}
|
||||
|
||||
public CornerRadius WaveBorderRadius()
|
||||
{
|
||||
return new CornerRadius(RadioSize / 2);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user