From 7c710d4b221d993afb09ffd2dd16abfc9d0ee04a Mon Sep 17 00:00:00 2001 From: CHINBOY Date: Sun, 8 Feb 2026 17:25:33 +0800 Subject: [PATCH] Feature(CompactSpace): Add CompactSpaceItem Achieving a layout that children are unaware of --- .../Views/Layout/SpaceShowCase.axaml | 34 +- .../ButtonSpinner/ButtonSpinner.cs | 105 ++++++- .../ButtonSpinnerDecoratedBox.cs | 39 ++- .../ButtonSpinner/ButtonSpinnerHandle.cs | 10 +- .../Themes/ButtonSpinnerHandleTheme.axaml | 36 +-- .../Themes/ButtonSpinnerTheme.axaml | 8 +- src/AtomUI.Desktop.Controls/Buttons/Button.cs | 34 +- .../Dialog/Themes/DialogButtonBoxTheme.axaml | 1 + src/AtomUI.Desktop.Controls/Input/LineEdit.cs | 2 +- src/AtomUI.Desktop.Controls/Input/TextBox.cs | 37 +-- .../Input/Themes/LineEditTheme.axaml | 8 +- .../Themes/SearchEditDecoratedBoxTheme.axaml | 2 - .../Input/Themes/SearchEditTheme.axaml | 9 +- .../NumericUpDown/NumericUpDown.cs | 79 ++++- .../Themes/NumericUpDownTheme.axaml | 5 +- .../AddOnDecoratedBox/AddOnDecoratedBox.cs | 12 +- .../Themes/AddOnDecoratedBoxTheme.axaml | 1 + .../Select/AbstractSelect.cs | 34 +- .../Select/Select.AsyncOptionsLoad.cs | 4 + src/AtomUI.Desktop.Controls/Select/Select.cs | 3 + .../Space/CompactSpace.cs | 291 ++++++++++-------- .../Space/CompactSpaceFiller.cs | 2 + .../Space/CompactSpaceItem.cs | 195 ++++++++++++ .../Space/ICompactSpaceAware.cs | 2 + .../Space/InvalidSpaceFillerUsageException.cs | 8 + .../Space/Themes/CompactSpaceTheme.axaml | 5 +- 26 files changed, 660 insertions(+), 306 deletions(-) create mode 100644 src/AtomUI.Desktop.Controls/Space/CompactSpaceItem.cs create mode 100644 src/AtomUI.Desktop.Controls/Space/InvalidSpaceFillerUsageException.cs diff --git a/controlgallery/AtomUIGallery/ShowCases/Views/Layout/SpaceShowCase.axaml b/controlgallery/AtomUIGallery/ShowCases/Views/Layout/SpaceShowCase.axaml index 3267a5a6b..f1e86358f 100644 --- a/controlgallery/AtomUIGallery/ShowCases/Views/Layout/SpaceShowCase.axaml +++ b/controlgallery/AtomUIGallery/ShowCases/Views/Layout/SpaceShowCase.axaml @@ -205,13 +205,45 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AtomUI.Desktop.Controls/ButtonSpinner/ButtonSpinner.cs b/src/AtomUI.Desktop.Controls/ButtonSpinner/ButtonSpinner.cs index 5a2ebd5ea..80d359ce7 100644 --- a/src/AtomUI.Desktop.Controls/ButtonSpinner/ButtonSpinner.cs +++ b/src/AtomUI.Desktop.Controls/ButtonSpinner/ButtonSpinner.cs @@ -13,6 +13,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Interactivity; +using Avalonia.Layout; using Avalonia.Metadata; using Avalonia.VisualTree; @@ -27,7 +28,8 @@ public enum ButtonSpinnerLocation [PseudoClasses(ButtonSpinnerPseudoClass.Left, ButtonSpinnerPseudoClass.Right)] public class ButtonSpinner : Spinner, IMotionAwareControl, - IControlSharedTokenResourcesHost + IControlSharedTokenResourcesHost, + ICompactSpaceAware { #region 公共属性定义 public static readonly StyledProperty AllowSpinProperty = @@ -185,19 +187,47 @@ public class ButtonSpinner : Spinner, internal static readonly StyledProperty SpinnerHandleWidthProperty = AvaloniaProperty.Register(nameof (SpinnerHandleWidth)); + internal static readonly StyledProperty CompactSpaceItemPositionProperty = + CompactSpaceAwareControlProperty.CompactSpaceItemPositionProperty.AddOwner(); + + internal static readonly StyledProperty CompactSpaceOrientationProperty = + CompactSpaceAwareControlProperty.CompactSpaceOrientationProperty.AddOwner(); + + internal static readonly StyledProperty IsUsedInCompactSpaceProperty = + CompactSpaceAwareControlProperty.IsUsedInCompactSpaceProperty.AddOwner(); + internal double SpinnerHandleWidth { get => GetValue(SpinnerHandleWidthProperty); set => SetValue(SpinnerHandleWidthProperty, value); } + internal SpaceItemPosition? CompactSpaceItemPosition + { + get => GetValue(CompactSpaceItemPositionProperty); + set => SetValue(CompactSpaceItemPositionProperty, value); + } + + internal Orientation CompactSpaceOrientation + { + get => GetValue(CompactSpaceOrientationProperty); + set => SetValue(CompactSpaceOrientationProperty, value); + } + + internal bool IsUsedInCompactSpace + { + get => GetValue(IsUsedInCompactSpaceProperty); + set => SetValue(IsUsedInCompactSpaceProperty, value); + } + Control IControlSharedTokenResourcesHost.HostControl => this; string IControlSharedTokenResourcesHost.TokenId => ButtonSpinnerToken.ID; #endregion - private ButtonSpinnerDecoratedBox? _decoratedBox; + internal ButtonSpinnerDecoratedBox? DecoratedBox; private CompositeDisposable? _addOnBindingDisposables; + private ButtonSpinnerHandle? _spinnerHandle; static ButtonSpinner() { @@ -207,6 +237,7 @@ public class ButtonSpinner : Spinner, public ButtonSpinner() { this.RegisterResources(); + // RenderTransform = new TranslateTransform(-1, 0); } private IconButton? _decreaseButton; @@ -247,7 +278,7 @@ public class ButtonSpinner : Spinner, protected override Size ArrangeOverride(Size finalSize) { - var borderThickness = _decoratedBox?.BorderThickness ?? default; + var borderThickness = DecoratedBox?.BorderThickness ?? default; return base.ArrangeOverride(finalSize).Inflate(borderThickness); } @@ -269,31 +300,42 @@ public class ButtonSpinner : Spinner, { UpdatePseudoClasses(); } + } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { - _decoratedBox = e.NameScope.Find(ButtonSpinnerThemeConstants.DecoratedBoxPart); - base.OnApplyTemplate(e); - if (_decoratedBox?.SpinnerContent is ButtonSpinnerHandle spinnerHandle) + if (_spinnerHandle != null) { - spinnerHandle.ButtonsCreated += (sender, args) => - { - IncreaseButton = spinnerHandle.IncreaseButton; - DecreaseButton = spinnerHandle.DecreaseButton; - }; + _spinnerHandle.ButtonsCreated -= HandleButtonCreated; + } + DecoratedBox = e.NameScope.Find(ButtonSpinnerThemeConstants.DecoratedBoxPart); + base.OnApplyTemplate(e); + if (DecoratedBox?.SpinnerContent is ButtonSpinnerHandle spinnerHandle) + { + _spinnerHandle = spinnerHandle; + _spinnerHandle.ButtonsCreated += HandleButtonCreated; } SetButtonUsage(); ConfigureAddOns(); } + private void HandleButtonCreated(object? sender, EventArgs e) + { + if (_spinnerHandle != null) + { + IncreaseButton = _spinnerHandle.IncreaseButton; + DecreaseButton = _spinnerHandle.DecreaseButton; + } + } + private void ConfigureAddOns() { _addOnBindingDisposables?.Dispose(); _addOnBindingDisposables = new CompositeDisposable(); if (LeftAddOn is PathIcon leftAddOnIcon) { - var iconPresenter = new SizeTypeAwareIconPresenter() + var iconPresenter = new SizeTypeAwareIconPresenter { Icon = leftAddOnIcon }; @@ -302,7 +344,7 @@ public class ButtonSpinner : Spinner, } if (InnerLeftContent is PathIcon innerLeftContent) { - var iconPresenter = new SizeTypeAwareIconPresenter() + var iconPresenter = new SizeTypeAwareIconPresenter { Icon = innerLeftContent }; @@ -311,7 +353,7 @@ public class ButtonSpinner : Spinner, } if (RightAddOn is PathIcon rightAddOnIcon) { - var iconPresenter = new SizeTypeAwareIconPresenter() + var iconPresenter = new SizeTypeAwareIconPresenter { Icon = rightAddOnIcon }; @@ -320,7 +362,7 @@ public class ButtonSpinner : Spinner, } if (InnerRightContent is PathIcon innerRightContent) { - var iconPresenter = new SizeTypeAwareIconPresenter() + var iconPresenter = new SizeTypeAwareIconPresenter { Icon = innerRightContent }; @@ -391,8 +433,8 @@ public class ButtonSpinner : Spinner, case Key.Enter: { //Do not Spin on enter Key when spinners have focus - if (((IncreaseButton != null) && (IncreaseButton.IsFocused)) - || ((DecreaseButton != null) && DecreaseButton.IsFocused)) + if ((IncreaseButton != null && IncreaseButton.IsFocused) + || (DecreaseButton != null && DecreaseButton.IsFocused)) { e.Handled = true; } @@ -457,4 +499,33 @@ public class ButtonSpinner : Spinner, OnSpin(new SpinEventArgs(SpinEvent, direction)); } } + + void ICompactSpaceAware.NotifyPositionChange(SpaceItemPosition? position) + { + IsUsedInCompactSpace = position != null; + CompactSpaceItemPosition = position; + } + + void ICompactSpaceAware.NotifyOrientationChange(Orientation orientation) + { + CompactSpaceOrientation = orientation; + } + + double ICompactSpaceAware.GetBorderThickness() => GetBorderThicknessForCompactSpace(); + + protected virtual double GetBorderThicknessForCompactSpace() + { + if (!IsUsedInCompactSpace) + { + return 0.0; + } + + if (DecoratedBox == null || DecoratedBox.StyleVariant != AddOnDecoratedVariant.Outline) + { + return 0.0; + } + + // 都一样宽 + return DecoratedBox.InnerBoxBorderThickness.Left; + } } \ No newline at end of file diff --git a/src/AtomUI.Desktop.Controls/ButtonSpinner/ButtonSpinnerDecoratedBox.cs b/src/AtomUI.Desktop.Controls/ButtonSpinner/ButtonSpinnerDecoratedBox.cs index 018a2d302..a3a3b5d8c 100644 --- a/src/AtomUI.Desktop.Controls/ButtonSpinner/ButtonSpinnerDecoratedBox.cs +++ b/src/AtomUI.Desktop.Controls/ButtonSpinner/ButtonSpinnerDecoratedBox.cs @@ -223,27 +223,32 @@ internal class ButtonSpinnerDecoratedBox : AddOnDecoratedBox if (args is RawPointerEventArgs pointerEventArgs) { - var pos = this.TranslatePoint(new Point(0, 0), TopLevel.GetTopLevel(this)!); - if (!pos.HasValue) + if (pointerEventArgs.Type == RawPointerEventType.Move || + pointerEventArgs.Type == RawPointerEventType.LeftButtonUp || + pointerEventArgs.Type == RawPointerEventType.RightButtonDown) { - return; - } - - var bounds = new Rect(pos.Value, Bounds.Size); - if (bounds.Contains(pointerEventArgs.Position)) - { - if (IsShowHandle && IsHandleFloatable) + var pos = this.TranslatePoint(new Point(0, 0), TopLevel.GetTopLevel(this)!); + if (!pos.HasValue) { - IsSpinnerContentHover = true; - UpdateHandleVisualState(); + return; } - } - else - { - if (IsShowHandle && IsHandleFloatable) + + var bounds = new Rect(pos.Value, Bounds.Size); + if (bounds.Contains(pointerEventArgs.Position)) { - IsSpinnerContentHover = false; - UpdateHandleVisualState(); + if (IsShowHandle && IsHandleFloatable) + { + IsSpinnerContentHover = true; + UpdateHandleVisualState(); + } + } + else + { + if (IsShowHandle && IsHandleFloatable) + { + IsSpinnerContentHover = false; + UpdateHandleVisualState(); + } } } } diff --git a/src/AtomUI.Desktop.Controls/ButtonSpinner/ButtonSpinnerHandle.cs b/src/AtomUI.Desktop.Controls/ButtonSpinner/ButtonSpinnerHandle.cs index dd81276b0..1c8de5791 100644 --- a/src/AtomUI.Desktop.Controls/ButtonSpinner/ButtonSpinnerHandle.cs +++ b/src/AtomUI.Desktop.Controls/ButtonSpinner/ButtonSpinnerHandle.cs @@ -5,7 +5,6 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Media; -using Avalonia.Interactivity; namespace AtomUI.Desktop.Controls; @@ -98,17 +97,18 @@ internal class ButtonSpinnerHandle : TemplatedControl var handleOffsetY = Bounds.Height / 2; using var optionState = context.PushRenderOptions(new RenderOptions { - EdgeMode = EdgeMode.Aliased + EdgeMode = EdgeMode.Antialias }); + { // 画竖线 - var startPoint = new Point(lineWidth / 2, lineWidth); - var endPoint = new Point(lineWidth / 2, Bounds.Height - lineWidth); + var startPoint = new Point(lineWidth, lineWidth); + var endPoint = new Point(lineWidth, Bounds.Height - lineWidth); context.DrawLine(new Pen(BorderBrush, lineWidth), startPoint, endPoint); } { // 画横线 - var startPoint = new Point(0, handleOffsetY); + var startPoint = new Point(lineWidth, handleOffsetY); var endPoint = new Point(Bounds.Width - lineWidth, handleOffsetY); context.DrawLine(new Pen(BorderBrush, lineWidth), startPoint, endPoint); } diff --git a/src/AtomUI.Desktop.Controls/ButtonSpinner/Themes/ButtonSpinnerHandleTheme.axaml b/src/AtomUI.Desktop.Controls/ButtonSpinner/Themes/ButtonSpinnerHandleTheme.axaml index 69e595cab..cb84dc447 100644 --- a/src/AtomUI.Desktop.Controls/ButtonSpinner/Themes/ButtonSpinnerHandleTheme.axaml +++ b/src/AtomUI.Desktop.Controls/ButtonSpinner/Themes/ButtonSpinnerHandleTheme.axaml @@ -7,25 +7,23 @@ - - - - - - + + + + diff --git a/src/AtomUI.Desktop.Controls/ButtonSpinner/Themes/ButtonSpinnerTheme.axaml b/src/AtomUI.Desktop.Controls/ButtonSpinner/Themes/ButtonSpinnerTheme.axaml index 2a2cc5e7a..b5b7173db 100644 --- a/src/AtomUI.Desktop.Controls/ButtonSpinner/Themes/ButtonSpinnerTheme.axaml +++ b/src/AtomUI.Desktop.Controls/ButtonSpinner/Themes/ButtonSpinnerTheme.axaml @@ -24,14 +24,16 @@ HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Content="{TemplateBinding Content}" - ContentTemplate="{TemplateBinding ContentTemplate}"> + ContentTemplate="{TemplateBinding ContentTemplate}" + CompactSpaceItemPosition="{TemplateBinding CompactSpaceItemPosition}" + CompactSpaceOrientation="{TemplateBinding CompactSpaceOrientation}" + IsUsedInCompactSpace="{TemplateBinding IsUsedInCompactSpace}"> + Width="{Binding (atom:ButtonSpinner.SpinnerHandleWidth), RelativeSource={RelativeSource AncestorType=atom:ButtonSpinner}}"/> diff --git a/src/AtomUI.Desktop.Controls/Buttons/Button.cs b/src/AtomUI.Desktop.Controls/Buttons/Button.cs index d5f7a1d9d..784065f9a 100644 --- a/src/AtomUI.Desktop.Controls/Buttons/Button.cs +++ b/src/AtomUI.Desktop.Controls/Buttons/Button.cs @@ -346,14 +346,6 @@ public class Button : AvaloniaButton, { ConfigureEffectiveCornerRadius(); } - - if (change.Property == CompactSpace.ItemSizeProperty) - { - if (change.NewValue is CompactSpaceSize newSize) - { - CompactSpace.ConfigureItemSize(this, newSize, IsUsedInCompactSpace, CompactSpaceOrientation); - } - } } private void ConfigureWaveSpiritType() @@ -508,7 +500,6 @@ public class Button : AvaloniaButton, private void ConfigureEffectiveCornerRadius() { - CompactSpace.ConfigureItemSize(this, CompactSpace.GetItemSize(this), IsUsedInCompactSpace, CompactSpaceOrientation); if (!IsUsedInCompactSpace) { EffectiveCornerRadius = CornerRadius; @@ -591,27 +582,16 @@ public class Button : AvaloniaButton, { return ButtonType == ButtonType.Primary; } - - protected override void ArrangeCore(Rect finalRect) + + double ICompactSpaceAware.GetBorderThickness() => GetBorderThicknessForCompactSpace(); + + protected virtual double GetBorderThicknessForCompactSpace() { - if (CompactSpaceItemPosition == null || - CompactSpaceItemPosition == SpaceItemPosition.First || - CompactSpaceItemPosition == SpaceItemPosition.FirstAndLast) + if (!IsUsedInCompactSpace) { - base.ArrangeCore(finalRect); - return; + return 0.0; } - var offsetX = finalRect.X; - var offsetY = finalRect.Y; - if (CompactSpaceOrientation == Orientation.Horizontal) - { - offsetX -= BorderThickness.Left; - } - else - { - offsetY -= BorderThickness.Top; - } - base.ArrangeCore(new Rect(offsetX, offsetY, finalRect.Width, finalRect.Height)); + return CompactSpaceOrientation == Orientation.Horizontal ? BorderThickness.Left : BorderThickness.Top; } } \ No newline at end of file diff --git a/src/AtomUI.Desktop.Controls/Dialog/Themes/DialogButtonBoxTheme.axaml b/src/AtomUI.Desktop.Controls/Dialog/Themes/DialogButtonBoxTheme.axaml index 3cc11ee32..72c88ca2c 100644 --- a/src/AtomUI.Desktop.Controls/Dialog/Themes/DialogButtonBoxTheme.axaml +++ b/src/AtomUI.Desktop.Controls/Dialog/Themes/DialogButtonBoxTheme.axaml @@ -30,6 +30,7 @@ + diff --git a/src/AtomUI.Desktop.Controls/Input/LineEdit.cs b/src/AtomUI.Desktop.Controls/Input/LineEdit.cs index 3940ceb98..a2350a1f0 100644 --- a/src/AtomUI.Desktop.Controls/Input/LineEdit.cs +++ b/src/AtomUI.Desktop.Controls/Input/LineEdit.cs @@ -126,7 +126,7 @@ public class LineEdit : TextBox return 0.0; } - if (_addOnDecoratedBox == null || _addOnDecoratedBox.StyleVariant == AddOnDecoratedVariant.Borderless) + if (_addOnDecoratedBox == null || _addOnDecoratedBox.StyleVariant != AddOnDecoratedVariant.Outline) { return 0.0; } diff --git a/src/AtomUI.Desktop.Controls/Input/TextBox.cs b/src/AtomUI.Desktop.Controls/Input/TextBox.cs index 87cdff3b5..b772f5cfa 100644 --- a/src/AtomUI.Desktop.Controls/Input/TextBox.cs +++ b/src/AtomUI.Desktop.Controls/Input/TextBox.cs @@ -211,13 +211,6 @@ public class TextBox : AvaloniaTextBox, ConfigureCornerRadius(); } - if (change.Property == CompactSpace.ItemSizeProperty) - { - if (change.NewValue is CompactSpaceSize newSize) - { - CompactSpace.ConfigureItemSize(this, newSize, IsUsedInCompactSpace, CompactSpaceOrientation); - } - } } private void ConfigureCornerRadius() @@ -228,7 +221,7 @@ public class TextBox : AvaloniaTextBox, } else { - if (SpaceItemPosition.First == CompactSpaceItemPosition) + if ((CompactSpaceItemPosition & SpaceItemPosition.First) != 0) { if (CompactSpaceOrientation == Orientation.Horizontal) { @@ -247,7 +240,7 @@ public class TextBox : AvaloniaTextBox, bottomRight:0); } } - else if (SpaceItemPosition.Middle == CompactSpaceItemPosition) + else if ((CompactSpaceItemPosition & SpaceItemPosition.Middle) != 0) { EffectiveCornerRadius = new CornerRadius( topLeft:0, @@ -255,7 +248,7 @@ public class TextBox : AvaloniaTextBox, topRight:0, bottomRight:0); } - else if (SpaceItemPosition.Last == CompactSpaceItemPosition) + else if ((CompactSpaceItemPosition & SpaceItemPosition.Last) != 0) { if (CompactSpaceOrientation == Orientation.Horizontal) { @@ -341,29 +334,9 @@ public class TextBox : AvaloniaTextBox, CompactSpaceOrientation = orientation; } - protected override void ArrangeCore(Rect finalRect) + double ICompactSpaceAware.GetBorderThickness() { - if (CompactSpaceItemPosition == null || - CompactSpaceItemPosition == SpaceItemPosition.First || - CompactSpaceItemPosition == SpaceItemPosition.FirstAndLast) - { - base.ArrangeCore(finalRect); - return; - } - - var borderThickness = GetBorderThicknessForCompactSpace(); - - var offsetX = finalRect.X; - var offsetY = finalRect.Y; - if (CompactSpaceOrientation == Orientation.Horizontal) - { - offsetX -= borderThickness; - } - else - { - offsetY -= borderThickness; - } - base.ArrangeCore(new Rect(offsetX, offsetY, finalRect.Width, finalRect.Height)); + return GetBorderThicknessForCompactSpace(); } protected virtual double GetBorderThicknessForCompactSpace() diff --git a/src/AtomUI.Desktop.Controls/Input/Themes/LineEditTheme.axaml b/src/AtomUI.Desktop.Controls/Input/Themes/LineEditTheme.axaml index 2a023be75..07a3ad2c5 100644 --- a/src/AtomUI.Desktop.Controls/Input/Themes/LineEditTheme.axaml +++ b/src/AtomUI.Desktop.Controls/Input/Themes/LineEditTheme.axaml @@ -26,7 +26,8 @@ CompactSpaceItemPosition="{TemplateBinding CompactSpaceItemPosition}" CompactSpaceOrientation="{TemplateBinding CompactSpaceOrientation}" IsUsedInCompactSpace="{TemplateBinding IsUsedInCompactSpace}" - Focusable="True"> + Focusable="True" + ClipToBounds="{TemplateBinding ClipToBounds}"> @@ -47,11 +48,11 @@ Focusable="False" Content="{Binding InnerRightContent, RelativeSource={RelativeSource AncestorType=atom:LineEdit}}" IsVisible="{Binding InnerRightContent, RelativeSource={RelativeSource AncestorType=atom:LineEdit}, Converter={x:Static ObjectConverters.IsNotNull}}" /> - + IsVisible="{Binding IsShowCount, RelativeSource={RelativeSource AncestorType=atom:LineEdit}}" /> @@ -99,6 +100,7 @@