From 5df89aa6d8167678278bc17fdc32886c9c73c00a Mon Sep 17 00:00:00 2001 From: chinboy Date: Sat, 1 Nov 2025 09:08:02 +0800 Subject: [PATCH] Add TagClose support for Select --- .../TokenResourceConst.g.cs | 2 - src/AtomUI.Controls/Input/TextBox.cs | 2 +- .../Input/Themes/TextBoxTheme.axaml | 1 + src/AtomUI.Controls/List/ListDefaultView.cs | 1 - .../AddOnDecoratedBox/AddOnDecoratedBox.cs | 22 +++ .../Themes/AddOnDecoratedBoxTheme.axaml | 2 +- src/AtomUI.Controls/Select/Select.cs | 81 +++++--- .../Select/SelectAddOnDecoratedBox.cs | 9 + src/AtomUI.Controls/Select/SelectHandle.cs | 9 + src/AtomUI.Controls/Select/SelectOption.cs | 2 + src/AtomUI.Controls/Select/SelectOptions.cs | 69 ++++++- .../Select/SelectResultOptionsBox.cs | 81 +++++++- src/AtomUI.Controls/Select/SelectTag.cs | 6 + src/AtomUI.Controls/Select/SelectToken.cs | 14 -- src/AtomUI.Controls/Select/SelectWrapPanel.cs | 70 +++++++ .../Themes/SelectAddOnDecoratedBoxTheme.axaml | 185 ++++++++++++++++-- .../Select/Themes/SelectHandleTheme.axaml | 18 +- .../Themes/SelectResultOptionsBoxTheme.axaml | 4 +- .../Select/Themes/SelectTagTheme.axaml | 20 ++ .../Select/Themes/SelectTheme.axaml | 17 +- .../Select/Themes/SelectThemes.axaml | 1 + 21 files changed, 535 insertions(+), 81 deletions(-) create mode 100644 src/AtomUI.Controls/Select/SelectTag.cs create mode 100644 src/AtomUI.Controls/Select/SelectWrapPanel.cs create mode 100644 src/AtomUI.Controls/Select/Themes/SelectTagTheme.axaml diff --git a/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs b/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs index c967d7d5b..aba822709 100644 --- a/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs +++ b/src/AtomUI.Controls/GeneratedFiles/AtomUI.Generator/AtomUI.Generator.TokenResourceKeyGenerator/TokenResourceConst.g.cs @@ -706,8 +706,6 @@ namespace AtomUI.Controls.DesignTokens public static readonly TokenResourceKey MultiModePaddingLG = new TokenResourceKey("Select.MultiModePaddingLG"); public static readonly TokenResourceKey MultiModePaddingSM = new TokenResourceKey("Select.MultiModePaddingSM"); public static readonly TokenResourceKey MultipleItemBg = new TokenResourceKey("Select.MultipleItemBg"); - public static readonly TokenResourceKey MultipleItemBorderColor = new TokenResourceKey("Select.MultipleItemBorderColor"); - public static readonly TokenResourceKey MultipleItemBorderColorDisabled = new TokenResourceKey("Select.MultipleItemBorderColorDisabled"); public static readonly TokenResourceKey MultipleItemColorDisabled = new TokenResourceKey("Select.MultipleItemColorDisabled"); public static readonly TokenResourceKey MultipleItemHeight = new TokenResourceKey("Select.MultipleItemHeight"); public static readonly TokenResourceKey MultipleItemHeightLG = new TokenResourceKey("Select.MultipleItemHeightLG"); diff --git a/src/AtomUI.Controls/Input/TextBox.cs b/src/AtomUI.Controls/Input/TextBox.cs index 9bf2b3abd..55387a00a 100644 --- a/src/AtomUI.Controls/Input/TextBox.cs +++ b/src/AtomUI.Controls/Input/TextBox.cs @@ -4,6 +4,7 @@ using AtomUI.Theme.Utils; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; +using Avalonia.Input; using Avalonia.Media; namespace AtomUI.Controls; @@ -143,5 +144,4 @@ public class TextBox : AvaloniaTextBox, { Clear(); } - } \ No newline at end of file diff --git a/src/AtomUI.Controls/Input/Themes/TextBoxTheme.axaml b/src/AtomUI.Controls/Input/Themes/TextBoxTheme.axaml index c94aa0019..aa6080197 100644 --- a/src/AtomUI.Controls/Input/Themes/TextBoxTheme.axaml +++ b/src/AtomUI.Controls/Input/Themes/TextBoxTheme.axaml @@ -94,6 +94,7 @@ diff --git a/src/AtomUI.Controls/Select/Select.cs b/src/AtomUI.Controls/Select/Select.cs index 76503df26..f40f89d28 100644 --- a/src/AtomUI.Controls/Select/Select.cs +++ b/src/AtomUI.Controls/Select/Select.cs @@ -1,4 +1,3 @@ -using System.Collections; using System.Diagnostics; using System.Reactive.Disposables; using AtomUI.Controls.Data; @@ -12,6 +11,7 @@ using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; +using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.Metadata; using Avalonia.VisualTree; @@ -474,7 +474,8 @@ public class Select : TemplatedControl, private SelectSearchTextBox? _singleSearchInput; private readonly CompositeDisposable _subscriptionsOnOpen = new (); private ListFilterDescription? _filterDescription; - + private string? _filterValue; + static Select() { FocusableProperty.OverrideDefaultValue((x, e) => x.HandleOptionsSourcePropertyChanged(e)); + SelectSearchTextBox.TextChangedEvent.AddClassHandler((x, e) => x.HandleTagCloseRequest(e)); } public Select() @@ -639,6 +642,7 @@ public class Select : TemplatedControl, { _optionsBox.SelectionChanged += HandleOptionsBoxSelectionChanged; } + _popup = e.NameScope.Get(SelectThemeConstants.PopupPart); _popup.Opened += PopupOpened; _popup.Closed += PopupClosed; @@ -669,21 +673,18 @@ public class Select : TemplatedControl, } else { - var selectedOptions = new List(); + var selectedOptions = new HashSet(); if (SelectedOptions != null) { foreach (var option in SelectedOptions) { - if (!selectedOptions.Contains(option)) - { - selectedOptions.Add(option); - } + selectedOptions.Add(option); } } foreach (var item in e.RemovedItems) { - if (item is SelectOption selectOption) + if (item is SelectOption selectOption && !selectOption.IsSelected) { selectedOptions.Remove(selectOption); } @@ -691,17 +692,22 @@ public class Select : TemplatedControl, foreach (var item in e.AddedItems) { - if (item is SelectOption selectOption && !selectedOptions.Contains(selectOption)) + if (item is SelectOption selectOption && !selectedOptions.Contains(selectOption) && selectOption.IsSelected) { selectedOptions.Add(selectOption); } } - SetCurrentValue(SelectedOptionsProperty, selectedOptions); + var oldSelectedOptions = SelectedOptions?.ToHashSet() ?? new HashSet(); + if (!oldSelectedOptions.SetEquals(selectedOptions)) + { + SetCurrentValue(SelectedOptionsProperty, selectedOptions.ToList()); + } } } protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { + base.OnPropertyChanged(change); if (change.Property == SelectedOptionsProperty) { ConfigurePlaceholderVisible(); @@ -735,7 +741,6 @@ public class Select : TemplatedControl, { HandleOptionFilterPropChanged(); } - base.OnPropertyChanged(change); } private void PopupClosed(object? sender, EventArgs e) @@ -750,6 +755,8 @@ public class Select : TemplatedControl, _singleSearchInput.Width = double.NaN; } } + + _filterValue = null; } private void PopupOpened(object? sender, EventArgs e) @@ -761,11 +768,6 @@ public class Select : TemplatedControl, parent.GetObservable(IsVisibleProperty).Subscribe(IsVisibleChanged).DisposeWith(_subscriptionsOnOpen); } - if (_optionsBox != null) - { - _optionsBox.SelectedItem = SelectedOption; - _optionsBox.SelectedItems = (IList?)SelectedOptions; - } DropDownOpened?.Invoke(this, EventArgs.Empty); if (Mode == SelectMode.Single) { @@ -786,8 +788,25 @@ public class Select : TemplatedControl, public void Clear() { + if (SelectedOptions != null) + { + foreach (var option in SelectedOptions) + { + option.IsSelected = false; + } + } + + if (SelectedOption != null) + { + SelectedOption.IsSelected = false; + } SelectedOptions = null; SelectedOption = null; + if (_optionsBox != null) + { + _optionsBox.SelectedItems = null; + _optionsBox.SelectedItem = null; + } } private void ConfigureMaxDropdownHeight() @@ -825,7 +844,7 @@ public class Select : TemplatedControl, private void ConfigurePlaceholderVisible() { SetCurrentValue(IsPlaceholderTextVisibleProperty, (Mode == SelectMode.Single && SelectedOption == null) || - (Mode != SelectMode.Single && SelectedOptions == null || SelectedOptions?.Count == 0)); + (Mode != SelectMode.Single && (SelectedOptions == null || SelectedOptions?.Count == 0) && string.IsNullOrEmpty(_filterValue))); } private void ConfigureSelectionIsEmpty() @@ -841,16 +860,20 @@ public class Select : TemplatedControl, { _singleSearchInput.Width = _singleSearchInput.Bounds.Width; } - _singleSearchInput.TextChanged += HandleSearchInputTextChanged; } } - private void HandleSearchInputTextChanged(object? sender, TextChangedEventArgs e) + private void HandleSearchInputTextChanged(TextChangedEventArgs e) { if (_optionsBox != null && _optionsBox.FilterDescriptions != null) { - var filterValue = _singleSearchInput?.Text; - if (string.IsNullOrEmpty(filterValue)) + if (e.Source is TextBox textBox) + { + _filterValue = textBox.Text; + } + + ConfigurePlaceholderVisible(); + if (string.IsNullOrEmpty(_filterValue)) { _optionsBox.FilterDescriptions.Clear(); } @@ -861,13 +884,13 @@ public class Select : TemplatedControl, var oldFilter = _filterDescription; Debug.Assert(oldFilter.FilterConditions.Count == 1); var oldFilterValue = oldFilter.FilterConditions.First().ToString(); - if (oldFilterValue != filterValue) + if (oldFilterValue != _filterValue) { _filterDescription = new ListFilterDescription() { PropertyPath = _filterDescription.PropertyPath, Filter = _filterDescription.Filter, - FilterConditions = [filterValue] + FilterConditions = [_filterValue] }; _optionsBox.FilterDescriptions.Remove(oldFilter); _optionsBox.FilterDescriptions.Add(_filterDescription); @@ -879,12 +902,22 @@ public class Select : TemplatedControl, { PropertyPath = OptionFilterProp, Filter = FilterFn, - FilterConditions = [filterValue], + FilterConditions = [_filterValue], }; _optionsBox.FilterDescriptions.Add(_filterDescription); } } } + e.Handled = true; + } + + private void HandleTagCloseRequest(RoutedEventArgs e) + { + if (e.Source is SelectTag tag && tag.Option != null) + { + _optionsBox?.DeSelect(tag.Option); + } + e.Handled = true; } private void HandleOptionFilterPropChanged() diff --git a/src/AtomUI.Controls/Select/SelectAddOnDecoratedBox.cs b/src/AtomUI.Controls/Select/SelectAddOnDecoratedBox.cs index 6bbfd398c..44523c321 100644 --- a/src/AtomUI.Controls/Select/SelectAddOnDecoratedBox.cs +++ b/src/AtomUI.Controls/Select/SelectAddOnDecoratedBox.cs @@ -13,6 +13,9 @@ internal class SelectAddOnDecoratedBox : AddOnDecoratedBox o => o.IsSelectionEmpty, (o, v) => o.IsSelectionEmpty = v); + public static readonly StyledProperty IsDropDownOpenProperty = + AvaloniaProperty.Register(nameof(IsDropDownOpen)); + public SelectMode Mode { get => GetValue(ModeProperty); @@ -26,4 +29,10 @@ internal class SelectAddOnDecoratedBox : AddOnDecoratedBox get => _isSelectionEmpty; set => SetAndRaise(IsSelectionEmptyProperty, ref _isSelectionEmpty, value); } + + public bool IsDropDownOpen + { + get => GetValue(IsDropDownOpenProperty); + set => SetValue(IsDropDownOpenProperty, value); + } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Select/SelectHandle.cs b/src/AtomUI.Controls/Select/SelectHandle.cs index eac185ac7..4d841ab39 100644 --- a/src/AtomUI.Controls/Select/SelectHandle.cs +++ b/src/AtomUI.Controls/Select/SelectHandle.cs @@ -11,6 +11,9 @@ internal class SelectHandle : TemplatedControl public static readonly StyledProperty IsInputHoverProperty = AvaloniaProperty.Register(nameof(IsInputHover)); + public static readonly StyledProperty IsInputPressedProperty = + AvaloniaProperty.Register(nameof(IsInputPressed)); + public static readonly StyledProperty IsSearchEnabledProperty = AvaloniaProperty.Register(nameof(IsSearchEnabled)); @@ -35,6 +38,12 @@ internal class SelectHandle : TemplatedControl set => SetValue(IsInputHoverProperty, value); } + public bool IsInputPressed + { + get => GetValue(IsInputPressedProperty); + set => SetValue(IsInputPressedProperty, value); + } + public bool IsSearchEnabled { get => GetValue(IsSearchEnabledProperty); diff --git a/src/AtomUI.Controls/Select/SelectOption.cs b/src/AtomUI.Controls/Select/SelectOption.cs index b5fa8c8be..3c3152b17 100644 --- a/src/AtomUI.Controls/Select/SelectOption.cs +++ b/src/AtomUI.Controls/Select/SelectOption.cs @@ -10,4 +10,6 @@ public record SelectOption : ISelectOption public string Header { get; set; } = string.Empty; public bool IsEnabled { get; set; } = true; public object? Value { get; set; } + + internal bool IsSelected { get; set; } = false; } diff --git a/src/AtomUI.Controls/Select/SelectOptions.cs b/src/AtomUI.Controls/Select/SelectOptions.cs index f045b696c..4b291b2a1 100644 --- a/src/AtomUI.Controls/Select/SelectOptions.cs +++ b/src/AtomUI.Controls/Select/SelectOptions.cs @@ -1,6 +1,5 @@ using System.Reactive.Disposables; using AtomUI.Data; -using AtomUI.Utils; using Avalonia; using Avalonia.Controls; using Avalonia.Input; @@ -18,7 +17,7 @@ internal class SelectOptions : List return new SelectOptionItem(); } - private void DoSelectOption(Control container) + private void LogicalSelectOption(Control container) { if (ListDefaultView != null) { @@ -29,22 +28,69 @@ internal class SelectOptions : List { return; } - - var mode = SelectionMode; + var item = ListDefaultView.ItemFromContainer(container); + var mode = SelectionMode; if (mode == SelectionMode.Single) { ListDefaultView.Selection.Select(index); } else if (mode == SelectionMode.Multiple) { - if (ListDefaultView.Selection.IsSelected(index)) + if (item is SelectOption option) { - ListDefaultView.Selection.Deselect(index); + if (ListDefaultView.Selection.IsSelected(index)) + { + option.IsSelected = false; + ListDefaultView.Selection.Deselect(index); + } + else + { + option.IsSelected = true; + ListDefaultView.Selection.Select(index); + } } - else + } + } + } + } + + public void Select(SelectOption option) + { + if (ListDefaultView != null) + { + var container = ListDefaultView.ContainerFromItem(option); + if (container != null) + { + var index = ListDefaultView.IndexFromContainer(container); + if (index != -1) + { + if (index < 0 || index >= ItemCount) { - ListDefaultView.Selection.Select(index); + return; } + option.IsSelected = true; + ListDefaultView.Selection.Select(index); + } + } + } + } + + public void DeSelect(SelectOption option) + { + if (ListDefaultView != null) + { + var container = ListDefaultView.ContainerFromItem(option); + if (container != null) + { + var index = ListDefaultView.IndexFromContainer(container); + if (index != -1) + { + if (index < 0 || index >= ItemCount) + { + return; + } + option.IsSelected = false; + ListDefaultView.Selection.Deselect(index); } } } @@ -55,7 +101,7 @@ internal class SelectOptions : List var container = GetContainerFromEventSource(source); if (container != null) { - DoSelectOption(container); + LogicalSelectOption(container); return true; } return false; @@ -81,6 +127,11 @@ internal class SelectOptions : List { listItem.SetCurrentValue(IsEnabledProperty, data.IsEnabled); } + if (!listItem.IsSet(SelectOptionItem.IsSelectedProperty)) + { + listItem.SetCurrentValue(SelectOptionItem.IsSelectedProperty, data.IsSelected); + } + } } diff --git a/src/AtomUI.Controls/Select/SelectResultOptionsBox.cs b/src/AtomUI.Controls/Select/SelectResultOptionsBox.cs index 453749574..5f2d6eb90 100644 --- a/src/AtomUI.Controls/Select/SelectResultOptionsBox.cs +++ b/src/AtomUI.Controls/Select/SelectResultOptionsBox.cs @@ -3,6 +3,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Input; +using Avalonia.Layout; namespace AtomUI.Controls; @@ -20,6 +21,9 @@ internal class SelectResultOptionsBox : TemplatedControl public static readonly StyledProperty IsSearchEnabledProperty = Select.IsSearchEnabledProperty.AddOwner(); + public static readonly StyledProperty IsDropDownOpenProperty = + AvaloniaProperty.Register(nameof(IsDropDownOpen)); + private IList? _selectedOptions; public IList? SelectedOptions @@ -39,8 +43,15 @@ internal class SelectResultOptionsBox : TemplatedControl get => GetValue(IsSearchEnabledProperty); set => SetValue(IsSearchEnabledProperty, value); } + + public bool IsDropDownOpen + { + get => GetValue(IsDropDownOpenProperty); + set => SetValue(IsDropDownOpenProperty, value); + } private WrapPanel? _defaultPanel; + private SelectSearchTextBox? _searchTextBox; protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { @@ -49,6 +60,15 @@ internal class SelectResultOptionsBox : TemplatedControl { HandleSelectedOptionsChanged(); } + else if (change.Property == IsSearchEnabledProperty || + change.Property == ModeProperty) + { + ConfigureSearchTextControl(); + } + else if (change.Property == IsDropDownOpenProperty) + { + ConfigureSearchTextReadOnly(); + } } protected override void OnPointerPressed(PointerPressedEventArgs e) @@ -66,25 +86,80 @@ internal class SelectResultOptionsBox : TemplatedControl protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - _defaultPanel = e.NameScope.Find(SelectResultOptionsBoxThemeConstants.DefaultPanelPart); + _defaultPanel = e.NameScope.Find(SelectResultOptionsBoxThemeConstants.DefaultPanelPart); + _searchTextBox = new SelectSearchTextBox() + { + HorizontalAlignment = HorizontalAlignment.Stretch + }; + if (IsSearchEnabled) + { + if (Mode == SelectMode.Multiple) + { + _defaultPanel?.Children.Add(_searchTextBox); + } + } } private void HandleSelectedOptionsChanged() { if (_defaultPanel != null) { + _searchTextBox?.Clear(); _defaultPanel.Children.Clear(); if (_selectedOptions != null) { foreach (var option in _selectedOptions) { - _defaultPanel.Children.Add(new Tag() + _defaultPanel.Children.Add(new SelectTag() { TagText = option.Header, - Bordered = false + Option = option }); } } + + if (_searchTextBox != null) + { + _defaultPanel.Children.Add(_searchTextBox); + _searchTextBox.Focus(); + } + } + } + + private void ConfigureSearchTextControl() + { + if (_searchTextBox != null) + { + if (Mode == SelectMode.Multiple) + { + if (_defaultPanel != null) + { + if (IsSearchEnabled) + { + _defaultPanel.Children.Add(_searchTextBox); + } + else + { + _defaultPanel.Children.Remove(_searchTextBox); + } + } + } + } + } + + private void ConfigureSearchTextReadOnly() + { + if (_searchTextBox != null) + { + if (IsDropDownOpen) + { + _searchTextBox.IsReadOnly = false; + } + else + { + _searchTextBox.Clear(); + _searchTextBox.IsReadOnly = true; + } } } } \ No newline at end of file diff --git a/src/AtomUI.Controls/Select/SelectTag.cs b/src/AtomUI.Controls/Select/SelectTag.cs new file mode 100644 index 000000000..6fed86e1e --- /dev/null +++ b/src/AtomUI.Controls/Select/SelectTag.cs @@ -0,0 +1,6 @@ +namespace AtomUI.Controls; + +internal class SelectTag : Tag +{ + public SelectOption? Option { get; set; } +} \ No newline at end of file diff --git a/src/AtomUI.Controls/Select/SelectToken.cs b/src/AtomUI.Controls/Select/SelectToken.cs index 5a55e411d..b7781caa0 100644 --- a/src/AtomUI.Controls/Select/SelectToken.cs +++ b/src/AtomUI.Controls/Select/SelectToken.cs @@ -15,12 +15,6 @@ internal class SelectToken : AbstractControlDesignToken /// public Color MultipleItemBg { get; set; } - /// - /// 多选标签边框色 - /// Border color of multiple tag - /// - public Color MultipleItemBorderColor { get; set; } - /// /// 多选标签高度 /// Height of multiple tag @@ -51,12 +45,6 @@ internal class SelectToken : AbstractControlDesignToken /// public Color MultipleItemColorDisabled { get; set; } - /// - /// 多选标签禁用边框色 - /// Border color of multiple tag when disabled - /// - public Color MultipleItemBorderColorDisabled { get; set; } - /// /// 选项选中时文本颜色 /// Text color when option is selected @@ -165,13 +153,11 @@ internal class SelectToken : AbstractControlDesignToken OptionFontSize = SharedToken.FontSize; OptionHeight = SharedToken.ControlHeight; MultipleItemBg = SharedToken.ColorFillSecondary; - MultipleItemBorderColor = Colors.Transparent; MultipleItemHeight = multipleItemHeight; MultipleItemHeightSM = multipleItemHeightSM; MultipleItemHeightLG = multipleItemHeightLG; MultipleSelectorBgDisabled = SharedToken.ColorBgContainerDisabled; MultipleItemColorDisabled = SharedToken.ColorTextDisabled; - MultipleItemBorderColorDisabled = Colors.Transparent; SelectAffixPadding = SharedToken.PaddingXXS; PopupBorderRadius = SharedToken.BorderRadiusLG; diff --git a/src/AtomUI.Controls/Select/SelectWrapPanel.cs b/src/AtomUI.Controls/Select/SelectWrapPanel.cs new file mode 100644 index 000000000..73cfa4d7b --- /dev/null +++ b/src/AtomUI.Controls/Select/SelectWrapPanel.cs @@ -0,0 +1,70 @@ +using System.Collections.Specialized; +using Avalonia; +using Avalonia.Controls; + +namespace AtomUI.Controls; + +internal class SelectWrapPanel : WrapPanel +{ + private SelectSearchTextBox? _searchTextBox; + + protected override Size ArrangeOverride(Size finalSize) + { + var size = base.ArrangeOverride(finalSize); + if (_searchTextBox != null) + { + if (Children.Count == 1) + { + _searchTextBox.Arrange(new Rect(0, 0, finalSize.Width, finalSize.Height)); + } + else + { + // 找最后一个 + var indexOf = Children.IndexOf(_searchTextBox); + var lastIndex = indexOf - 1; + var lastTag = Children[lastIndex]; + var lastBounds = lastTag.Bounds; + var offestX = lastBounds.Right + ItemSpacing; + var offsetY = lastBounds.Top; + var width = finalSize.Width - offestX; + + if ((offestX + _searchTextBox.DesiredSize.Width) > finalSize.Width) + { + offestX = 0; + offsetY += lastBounds.Height + LineSpacing; + width = finalSize.Width; + } + var targetSearchBoxBounds = new Rect(offestX, offsetY, width, _searchTextBox.DesiredSize.Height); + _searchTextBox.Arrange(targetSearchBoxBounds); + } + } + + return size; + } + + protected override void ChildrenChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + base.ChildrenChanged(sender, e); + if (e.OldItems != null) + { + foreach (var oldItem in e.OldItems) + { + if (oldItem is SelectSearchTextBox) + { + _searchTextBox = null; + } + } + } + + if (e.NewItems != null) + { + foreach (var newItem in e.NewItems) + { + if (newItem is SelectSearchTextBox searchTextBox) + { + _searchTextBox = searchTextBox; + } + } + } + } +} \ No newline at end of file diff --git a/src/AtomUI.Controls/Select/Themes/SelectAddOnDecoratedBoxTheme.axaml b/src/AtomUI.Controls/Select/Themes/SelectAddOnDecoratedBoxTheme.axaml index d5ba969dd..3e4e46eb2 100644 --- a/src/AtomUI.Controls/Select/Themes/SelectAddOnDecoratedBoxTheme.axaml +++ b/src/AtomUI.Controls/Select/Themes/SelectAddOnDecoratedBoxTheme.axaml @@ -10,24 +10,183 @@ + + + + + - + + + + + + + + + + + + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AtomUI.Controls/Select/Themes/SelectHandleTheme.axaml b/src/AtomUI.Controls/Select/Themes/SelectHandleTheme.axaml index 7c5790c2d..c3414b91a 100644 --- a/src/AtomUI.Controls/Select/Themes/SelectHandleTheme.axaml +++ b/src/AtomUI.Controls/Select/Themes/SelectHandleTheme.axaml @@ -66,14 +66,20 @@ - + + + diff --git a/src/AtomUI.Controls/Select/Themes/SelectResultOptionsBoxTheme.axaml b/src/AtomUI.Controls/Select/Themes/SelectResultOptionsBoxTheme.axaml index 8d4a13092..25623c202 100644 --- a/src/AtomUI.Controls/Select/Themes/SelectResultOptionsBoxTheme.axaml +++ b/src/AtomUI.Controls/Select/Themes/SelectResultOptionsBoxTheme.axaml @@ -7,12 +7,12 @@ - + - diff --git a/src/AtomUI.Controls/Select/Themes/SelectTagTheme.axaml b/src/AtomUI.Controls/Select/Themes/SelectTagTheme.axaml new file mode 100644 index 000000000..ac570e3b1 --- /dev/null +++ b/src/AtomUI.Controls/Select/Themes/SelectTagTheme.axaml @@ -0,0 +1,20 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/AtomUI.Controls/Select/Themes/SelectTheme.axaml b/src/AtomUI.Controls/Select/Themes/SelectTheme.axaml index 2d43026c3..33521f6d9 100644 --- a/src/AtomUI.Controls/Select/Themes/SelectTheme.axaml +++ b/src/AtomUI.Controls/Select/Themes/SelectTheme.axaml @@ -23,7 +23,8 @@ ContentLeftAddOn="{TemplateBinding ContentLeftAddOn}" ContentLeftAddOnTemplate="{TemplateBinding ContentLeftAddOnTemplate}" Mode="{TemplateBinding Mode}" - IsSelectionEmpty="{TemplateBinding IsSelectionEmpty}"> + IsSelectionEmpty="{TemplateBinding IsSelectionEmpty}" + IsDropDownOpen="{TemplateBinding IsDropDownOpen}"> @@ -39,7 +40,8 @@ IsClearable="{Binding IsAllowClear, RelativeSource={RelativeSource AncestorType=atom:Select}}" IsSelectionEmpty="{Binding IsSelectionEmpty, RelativeSource={RelativeSource AncestorType=atom:Select}}" IsDropDownOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource AncestorType=atom:Select}}" - IsInputHover="{Binding IsInnerBoxHover, RelativeSource={RelativeSource AncestorType=atom:AddOnDecoratedBox}}"/> + IsInputHover="{Binding IsInnerBoxHover, RelativeSource={RelativeSource AncestorType=atom:AddOnDecoratedBox}}" + IsInputPressed="{Binding IsInnerBoxPressed, RelativeSource={RelativeSource AncestorType=atom:AddOnDecoratedBox}}"/> @@ -66,11 +68,14 @@ Watermark="{TemplateBinding SelectedOption, Converter={StaticResource OptionHeaderConverter}}"/> + IsDropDownOpen="{TemplateBinding IsDropDownOpen}" + IsVisible="False" + Focusable="True"/> - +