Refactor infopicker

This commit is contained in:
chinboy
2025-10-28 10:06:44 +08:00
parent d0d3c1d46b
commit aed59e9712
11 changed files with 141 additions and 104 deletions
+3 -1
View File
@@ -285,7 +285,7 @@ public class DatePicker : InfoPickerInput,
}
else
{
var text = FormatDateTime(DateTime.Today);
var text = FormatDateTime(DateTime.Today);
var preferredInputWidth = TextUtils.CalculateTextSize(text, FontSize, FontFamily, FontStyle, FontWeight).Width;
if (Watermark != null)
{
@@ -293,6 +293,8 @@ public class DatePicker : InfoPickerInput,
TextUtils.CalculateTextSize(Watermark, FontSize, FontFamily, FontStyle, FontWeight).Width);
}
preferredInputWidth *= 1.1;
if (!double.IsNaN(MinWidth))
{
preferredInputWidth = Math.Max(MinWidth, preferredInputWidth);
@@ -458,12 +458,12 @@ public class RangeDatePicker : RangeInfoPickerInput,
if (PickerInnerBox is not null)
{
var preferredWidth = 0d;
// if (PickerInnerBox.RightAddOnContent is Control rightAddOnContent)
// {
// preferredWidth += PreferredWidth + rightAddOnContent.DesiredSize.Width +
// PickerInnerBox.EffectiveInnerBoxPadding.Left +
// PickerInnerBox.EffectiveInnerBoxPadding.Right;
// }
if (DecoratedBox?.ContentRightAddOn is Control rightAddOnContent)
{
preferredWidth += PreferredWidth + rightAddOnContent.DesiredSize.Width +
PickerInnerBox.Padding.Left +
PickerInnerBox.Padding.Right;
}
if (RangePickerArrow is not null)
{
@@ -234,12 +234,12 @@ internal class AddOnDecoratedBox : ContentControl,
Control IMotionAwareControl.PropertyBindTarget => this;
#endregion
private Border? _contentFrame;
private IDisposable? _borderThicknessDisposable;
private IDisposable? _borderThicknessDisposable;
private protected Control? _leftAddOn;
private protected Control? _rightAddOn;
internal Border? ContentFrame;
static AddOnDecoratedBox()
{
@@ -362,16 +362,16 @@ internal class AddOnDecoratedBox : ContentControl,
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
_contentFrame = e.NameScope.Find<Border>(AddOnDecoratedBoxThemeConstants.ContentFramePart);
ContentFrame = e.NameScope.Find<Border>(AddOnDecoratedBoxThemeConstants.ContentFramePart);
_leftAddOn = e.NameScope.Find<Control>(AddOnDecoratedBoxThemeConstants.LeftAddOnPart);
_rightAddOn = e.NameScope.Find<Control>(AddOnDecoratedBoxThemeConstants.RightAddOnPart);
if (_contentFrame != null)
if (ContentFrame != null)
{
_contentFrame.PointerEntered += (sender, args) =>
ContentFrame.PointerEntered += (sender, args) =>
{
IsInnerBoxHover = true;
};
_contentFrame.PointerExited += (sender, args) =>
ContentFrame.PointerExited += (sender, args) =>
{
IsInnerBoxHover = false;
};
@@ -424,4 +424,5 @@ internal class AddOnDecoratedBox : ContentControl,
bottomLeft: bottomLeftRadius,
bottomRight: bottomRightRadius);
}
}
@@ -87,7 +87,7 @@
<Style
Selector="^ /template/ DockPanel#ContentLayout">
<Setter Property="HorizontalSpacing"
Value="{DynamicResource {x:Static atom:SharedTokenKey.SpacingXS}}" />
Value="{DynamicResource {x:Static atom:SharedTokenKey.SpacingXXS}}" />
</Style>
<Style Selector="^[SizeType=Large]">
@@ -1,5 +1,4 @@
using System.Diagnostics;
using System.Reactive.Disposables;
using System.Reactive.Disposables;
using AtomUI.Controls.Primitives.Themes;
using AtomUI.Data;
using AtomUI.IconPkg;
@@ -11,10 +10,10 @@ using Avalonia.Controls.Diagnostics;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.VisualTree;
namespace AtomUI.Controls.Primitives;
@@ -235,6 +234,11 @@ public abstract class InfoPickerInput : TemplatedControl,
o => o.PreferredInputWidth,
(o, v) => o.PreferredInputWidth = v);
internal static readonly DirectProperty<InfoPickerInput, bool> IsClearButtonVisibleProperty =
AvaloniaProperty.RegisterDirect<InfoPickerInput, bool>(nameof(IsClearButtonVisible),
o => o.IsClearButtonVisible,
(o, v) => o.IsClearButtonVisible = v);
protected string? Text
{
get => GetValue(TextProperty);
@@ -249,6 +253,14 @@ public abstract class InfoPickerInput : TemplatedControl,
set => SetAndRaise(PreferredInputWidthProperty, ref _preferredInputWidth, value);
}
private bool _isClearButtonVisible;
internal bool IsClearButtonVisible
{
get => _isClearButtonVisible;
set => SetAndRaise(IsClearButtonVisibleProperty, ref _isClearButtonVisible, value);
}
Control IControlSharedTokenResourcesHost.HostControl => this;
string IControlSharedTokenResourcesHost.TokenId => InfoPickerInputToken.ID;
Control IMotionAwareControl.PropertyBindTarget => this;
@@ -260,9 +272,8 @@ public abstract class InfoPickerInput : TemplatedControl,
private protected readonly FlyoutStateHelper FlyoutStateHelper;
private protected Flyout? PickerFlyout;
protected bool CurrentValidSelected;
private IDisposable? _clearUpButtonDetectDisposable;
private protected Border? PickerInnerBox;
protected Avalonia.Controls.TextBox? InfoInputBox;
protected TextBox? InfoInputBox;
protected Border? PickerInnerBox;
private protected bool IsFlyoutOpen;
private protected bool IsChoosing;
@@ -340,7 +351,7 @@ public abstract class InfoPickerInput : TemplatedControl,
{
if (hostProvider.PopupHost != args.Root)
{
if (!IsPointerInInfoInputBox(args.Position))
if (!IsPointerInInfoInputBox(args.Position) || ClickInClearUpButtonWithClearMode(args.Position))
{
return true;
}
@@ -355,8 +366,42 @@ public abstract class InfoPickerInput : TemplatedControl,
{
return false;
}
return IsPointerInInfoInputBox(position) && !ClickInClearUpButtonWithClearMode(position);
}
return IsPointerInInfoInputBox(position);
protected bool ClickInClearUpButtonWithNormalMode(Point position)
{
if (PickerClearUpButton != null)
{
var pos = PickerClearUpButton.TranslatePoint(new Point(0, 0), TopLevel.GetTopLevel(this)!);
if (pos.HasValue)
{
var clearUpButtonBounds = new Rect(pos.Value, PickerClearUpButton.Bounds.Size);
if (clearUpButtonBounds.Contains(position) && !PickerClearUpButton.IsInClearMode)
{
return true;
}
}
}
return false;
}
protected bool ClickInClearUpButtonWithClearMode(Point position)
{
if (PickerClearUpButton != null)
{
var pos = PickerClearUpButton.TranslatePoint(new Point(0, 0), TopLevel.GetTopLevel(this)!);
if (pos.HasValue)
{
var clearUpButtonBounds = new Rect(pos.Value, PickerClearUpButton.Bounds.Size);
if (clearUpButtonBounds.Contains(position) && PickerClearUpButton.IsInClearMode)
{
return true;
}
}
}
return false;
}
private bool IsPointerInInfoInputBox(Point position)
@@ -368,7 +413,7 @@ public abstract class InfoPickerInput : TemplatedControl,
{
return false;
}
var targetWidth = PickerInnerBox.Bounds.Width;
var targetHeight = PickerInnerBox.Bounds.Height;
var startOffsetX = pos.Value.X;
@@ -382,16 +427,7 @@ public abstract class InfoPickerInput : TemplatedControl,
startOffsetX = leftContentPos.Value.X + leftContent.Bounds.Width;
}
}
if (PickerClearUpButton is Control rightContent)
{
var rightContentPos = rightContent.TranslatePoint(new Point(0, 0), TopLevel.GetTopLevel(this)!);
if (rightContentPos.HasValue)
{
endOffsetX = rightContentPos.Value.X;
}
}
targetWidth = endOffsetX - startOffsetX;
var bounds = new Rect(new Point(startOffsetX, offsetY), new Size(targetWidth, targetHeight));
if (bounds.Contains(position))
@@ -399,7 +435,7 @@ public abstract class InfoPickerInput : TemplatedControl,
return true;
}
}
return false;
}
@@ -423,23 +459,50 @@ public abstract class InfoPickerInput : TemplatedControl,
FlyoutStateHelper.Flyout = PickerFlyout;
}
DecoratedBox = e.NameScope.Get<AddOnDecoratedBox>(InfoPickerInputThemeConstants.DecoratedBoxPart);
PickerInnerBox = e.NameScope.Get<Border>(InfoPickerInputThemeConstants.PickerInnerPart);
InfoInputBox = e.NameScope.Get<Avalonia.Controls.TextBox>(InfoPickerInputThemeConstants.InfoInputBoxPart);
PickerClearUpButton = e.NameScope.Get<PickerClearUpButton>(InfoPickerInputThemeConstants.ClearUpButtonPart);
DecoratedBox = e.NameScope.Get<AddOnDecoratedBox>(InfoPickerInputThemeConstants.DecoratedBoxPart);
InfoInputBox = e.NameScope.Get<TextBox>(InfoPickerInputThemeConstants.InfoInputBoxPart);
if (DecoratedBox != null)
{
if (DecoratedBox.ContentRightAddOn is Control rightContent)
{
PickerClearUpButton = rightContent.FindDescendantOfType<PickerClearUpButton>();
}
DecoratedBox.TemplateApplied += (sender, args) =>
{
PickerInnerBox = DecoratedBox.ContentFrame;
FlyoutStateHelper.AnchorTarget = PickerInnerBox;
};
DecoratedBox.PropertyChanged += (sender, args) =>
{
if (args.Property == AddOnDecoratedBox.IsInnerBoxHoverProperty)
{
ConfigureIsClearButtonVisible();
}
};
}
if (PickerClearUpButton is not null)
{
PickerClearUpButton.ClearRequest += (sender, args) => { NotifyClearButtonClicked(); };
}
FlyoutStateHelper.AnchorTarget = DecoratedBox;
SetupFlyoutProperties();
}
protected virtual void ConfigureIsClearButtonVisible()
{
if (DecoratedBox is not null)
{
SetCurrentValue(IsClearButtonVisibleProperty, DecoratedBox.IsInnerBoxHover && InfoInputBox?.IsReadOnly == false && InfoInputBox.Text?.Length > 0);
}
}
protected virtual void NotifyClearButtonClicked()
{
Clear();
SetCurrentValue(IsClearButtonVisibleProperty, false);
}
protected virtual void NotifyFlyoutPresenterCreated(Control flyoutPresenter)
@@ -489,55 +552,12 @@ public abstract class InfoPickerInput : TemplatedControl,
{
base.OnAttachedToVisualTree(e);
FlyoutStateHelper.NotifyAttachedToVisualTree();
if (_clearUpButtonDetectDisposable is null)
{
var inputManager = AvaloniaLocator.Current.GetService<IInputManager>()!;
_clearUpButtonDetectDisposable = inputManager.Process.Subscribe(DetectClearUpButtonState);
}
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
FlyoutStateHelper.NotifyDetachedFromVisualTree();
_clearUpButtonDetectDisposable?.Dispose();
_clearUpButtonDetectDisposable = null;
}
private void DetectClearUpButtonState(RawInputEventArgs args)
{
if (IsEnabled)
{
if (args is RawPointerEventArgs pointerEventArgs)
{
if (PickerInnerBox is not null && PickerClearUpButton != null)
{
var topLevel = TopLevel.GetTopLevel(this);
Debug.Assert(topLevel is not null);
if (topLevel != pointerEventArgs.Root)
{
PickerClearUpButton!.IsInClearMode = false;
}
else
{
var pos = PickerInnerBox.TranslatePoint(new Point(0, 0), topLevel);
if (!pos.HasValue)
{
return;
}
var bounds = new Rect(pos.Value, PickerInnerBox.Bounds.Size);
if (bounds.Contains(pointerEventArgs.Position))
{
PickerClearUpButton.IsInClearMode = ShowClearButtonPredicate();
}
else
{
PickerClearUpButton.IsInClearMode = false;
}
}
}
}
}
}
protected virtual bool ShowClearButtonPredicate()
@@ -1,6 +1,5 @@
using AtomUI.Controls.Primitives.Themes;
using AtomUI.IconPkg;
using AtomUI.Reflection;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
@@ -77,7 +77,7 @@ public abstract class RangeInfoPickerInput : InfoPickerInput
private protected Rectangle? RangePickerIndicator;
private protected Icon? RangePickerArrow;
private protected Avalonia.Controls.TextBox? SecondaryInfoInputBox;
private protected TextBox? SecondaryInfoInputBox;
private TopLevel? _topLevel;
public override void Clear()
@@ -89,7 +89,7 @@ public abstract class RangeInfoPickerInput : InfoPickerInput
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
SecondaryInfoInputBox = e.NameScope.Get<Avalonia.Controls.TextBox>(RangeInfoPickerInputThemeConstants.SecondaryInfoInputBoxPart);
SecondaryInfoInputBox = e.NameScope.Get<TextBox>(RangeInfoPickerInputThemeConstants.SecondaryInfoInputBoxPart);
RangePickerIndicator = e.NameScope.Get<Rectangle>(RangeInfoPickerInputThemeConstants.RangePickerIndicatorPart);
RangePickerArrow = e.NameScope.Get<Icon>(RangeInfoPickerInputThemeConstants.RangePickerArrowPart);
if (RangePickerIndicator != null)
@@ -179,12 +179,12 @@ public abstract class RangeInfoPickerInput : InfoPickerInput
return true;
}
if (IsPointerInSecondaryTextBox(position))
if (IsPointerInSecondaryTextBox(position) || ClickInClearUpButtonWithNormalMode(position))
{
RangeActivatedPart = RangeActivatedPart.End;
return true;
}
return false;
}
@@ -279,7 +279,7 @@ public abstract class RangeInfoPickerInput : InfoPickerInput
RangeActivatedPart = RangeActivatedPart.End;
}
if (!inRangeStart && !inRangeEnd)
if ((!inRangeStart && !inRangeEnd) || ClickInClearUpButtonWithClearMode(args.Position))
{
return true;
}
@@ -387,6 +387,18 @@ public abstract class RangeInfoPickerInput : InfoPickerInput
PickerIndicatorOffsetX = offset.X;
}
}
protected override void ConfigureIsClearButtonVisible()
{
if (DecoratedBox is not null)
{
SetCurrentValue(IsClearButtonVisibleProperty, DecoratedBox.IsInnerBoxHover &&
InfoInputBox?.IsReadOnly == false &&
InfoInputBox.Text?.Length > 0 &&
SecondaryInfoInputBox?.IsReadOnly == false &&
SecondaryInfoInputBox?.Text?.Length > 0);
}
}
}
internal enum RangeActivatedPart
@@ -23,10 +23,12 @@
<atom:AddOnDecoratedBox.ContentRightAddOn>
<StackPanel Orientation="Horizontal" Spacing="{DynamicResource {x:Static atom:SharedTokenKey.SpacingXS}}">
<atom:PickerClearUpButton
Name="{x:Static atom:InfoPickerInputThemeConstants.ClearUpButtonPart}"
Icon="{Binding (atom:InfoPickerInput.InfoIcon), RelativeSource={RelativeSource AncestorType=atom:InfoPickerInput}}" />
<ContentPresenter Content="{Binding (atom:InfoPickerInput.ContentLeftAddOn), RelativeSource={RelativeSource AncestorType=atom:InfoPickerInput}}"
ContentTemplate="{Binding (atom:InfoPickerInput.ContentLeftAddOnTemplate), RelativeSource={RelativeSource AncestorType=atom:InfoPickerInput}}"/>
IsInClearMode="{Binding IsClearButtonVisible, RelativeSource={RelativeSource AncestorType=atom:InfoPickerInput}}"
Icon="{Binding InfoIcon, RelativeSource={RelativeSource AncestorType=atom:InfoPickerInput}}"
IsVisible="{Binding InfoIcon, RelativeSource={RelativeSource AncestorType=atom:RangeInfoPickerInput}, Converter={x:Static ObjectConverters.IsNotNull}}"/>
<ContentPresenter Content="{Binding ContentRightAddOn, RelativeSource={RelativeSource AncestorType=atom:InfoPickerInput}}"
ContentTemplate="{Binding ContentRightAddOnTemplate, RelativeSource={RelativeSource AncestorType=atom:InfoPickerInput}}"
IsVisible="{Binding ContentRightAddOn, RelativeSource={RelativeSource AncestorType=atom:InfoPickerInput}, Converter={x:Static ObjectConverters.IsNotNull}}"/>
</StackPanel>
</atom:AddOnDecoratedBox.ContentRightAddOn>
<atom:TextBox Name="{x:Static atom:InfoPickerInputThemeConstants.InfoInputBoxPart}"
@@ -3,7 +3,6 @@ namespace AtomUI.Controls.Primitives.Themes;
internal static class InfoPickerInputThemeConstants
{
public const string DecoratedBoxPart = "PART_DecoratedBox";
public const string PickerInnerPart = "PART_PickerInner";
public const string InfoInputBoxPart = "PART_InfoInputBox";
public const string ClearUpButtonPart = "PART_ClearUpButton";
}
@@ -28,11 +28,14 @@
<atom:AddOnDecoratedBox.ContentRightAddOn>
<StackPanel Orientation="Horizontal" Spacing="{DynamicResource {x:Static atom:SharedTokenKey.SpacingXS}}">
<atom:PickerClearUpButton
IsInClearMode="{Binding IsClearButtonVisible, RelativeSource={RelativeSource AncestorType=atom:InfoPickerInput}}"
Name="{x:Static atom:InfoPickerInputThemeConstants.ClearUpButtonPart}"
Icon="{Binding InfoIcon, RelativeSource={RelativeSource AncestorType=atom:RangeInfoPickerInput}}" />
Icon="{Binding InfoIcon, RelativeSource={RelativeSource AncestorType=atom:RangeInfoPickerInput}}"
IsVisible="{Binding InfoIcon, RelativeSource={RelativeSource AncestorType=atom:RangeInfoPickerInput}, Converter={x:Static ObjectConverters.IsNotNull}}"/>
<ContentPresenter
Content="{Binding ContentRightAddOn, RelativeSource={RelativeSource AncestorType=atom:RangeInfoPickerInput}}"
ContentTemplate="{Binding ContentRightAddOnTemplate, RelativeSource={RelativeSource AncestorType=atom:RangeInfoPickerInput}}"/>
ContentTemplate="{Binding ContentRightAddOnTemplate, RelativeSource={RelativeSource AncestorType=atom:RangeInfoPickerInput}}"
IsVisible="{Binding ContentRightAddOn, RelativeSource={RelativeSource AncestorType=atom:RangeInfoPickerInput}, Converter={x:Static ObjectConverters.IsNotNull}}"/>
</StackPanel>
</atom:AddOnDecoratedBox.ContentRightAddOn>
<Grid ColumnDefinitions="*, Auto, *">
@@ -76,7 +79,6 @@
IsReadOnly="{TemplateBinding IsReadOnly}"
Text="{TemplateBinding SecondaryText}"
Watermark="{TemplateBinding SecondaryWatermark}" />
</Grid>
</atom:AddOnDecoratedBox>
@@ -461,12 +461,12 @@ public class RangeTimePicker : RangeInfoPickerInput,
if (PickerInnerBox is not null)
{
var preferredWidth = 0d;
// if (PickerInnerBox.RightAddOnContent is Control rightAddOnContent)
// {
// preferredWidth += PreferredWidth + rightAddOnContent.DesiredSize.Width +
// PickerInnerBox.EffectiveInnerBoxPadding.Left +
// PickerInnerBox.EffectiveInnerBoxPadding.Right;
// }
if (DecoratedBox?.ContentRightAddOn is Control rightAddOnContent)
{
preferredWidth += PreferredWidth + rightAddOnContent.DesiredSize.Width +
PickerInnerBox.Padding.Left +
PickerInnerBox.Padding.Right;
}
if (RangePickerArrow is not null)
{