From 40c1d96f2e8a249e5c47a938ea5a09eb0fbd0441 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Thu, 11 Apr 2024 23:00:38 +0800 Subject: [PATCH 01/13] feat: add demo. update overrides with internal implementations. --- demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml | 23 ++++ .../Ursa.Demo/Pages/TreeComboBoxDemo.axaml.cs | 13 ++ .../Ursa.Demo/ViewModels/MainViewViewModel.cs | 1 + demo/Ursa.Demo/ViewModels/MenuViewModel.cs | 2 + .../ViewModels/TreeComboBoxDemoViewModel.cs | 8 ++ .../Controls/TreeComboBox.axaml | 41 ++++++ src/Ursa.Themes.Semi/Controls/_index.axaml | 1 + src/Ursa/Controls/ComboBox/TreeComboBox.cs | 128 ++++++++++++++++++ .../Controls/ComboBox/TreeComboBoxItem.cs | 110 +++++++++++++++ src/Ursa/Ursa.csproj | 2 +- 10 files changed, 328 insertions(+), 1 deletion(-) create mode 100644 demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml create mode 100644 demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml.cs create mode 100644 demo/Ursa.Demo/ViewModels/TreeComboBoxDemoViewModel.cs create mode 100644 src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml create mode 100644 src/Ursa/Controls/ComboBox/TreeComboBox.cs create mode 100644 src/Ursa/Controls/ComboBox/TreeComboBoxItem.cs diff --git a/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml b/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml new file mode 100644 index 0000000..2624e7d --- /dev/null +++ b/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml.cs b/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml.cs new file mode 100644 index 0000000..6338466 --- /dev/null +++ b/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Markup.Xaml; + +namespace Ursa.Demo.Pages; + +public partial class TreeComboBoxDemo : UserControl +{ + public TreeComboBoxDemo() + { + InitializeComponent(); + } +} \ No newline at end of file diff --git a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs index 15af99e..a86e947 100644 --- a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs @@ -55,6 +55,7 @@ public class MainViewViewModel : ViewModelBase MenuKeys.MenuKeySkeleton => new SkeletonDemoViewModel(), MenuKeys.MenuKeyTagInput => new TagInputDemoViewModel(), MenuKeys.MenuKeyTimeline => new TimelineDemoViewModel(), + MenuKeys.MenuKeyTreeComboBox => new TreeComboBoxDemoViewModel(), MenuKeys.MenuKeyTwoTonePathIcon => new TwoTonePathIconDemoViewModel(), MenuKeys.MenuKeyThemeToggler => new ThemeTogglerDemoViewModel(), MenuKeys.MenuKeyToolBar => new ToolBarDemoViewModel(), diff --git a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs index 5a345ef..006bcd6 100644 --- a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs @@ -43,6 +43,7 @@ public class MenuViewModel: ViewModelBase new() { MenuHeader = "TagInput", Key = MenuKeys.MenuKeyTagInput }, new() { MenuHeader = "Theme Toggler", Key = MenuKeys.MenuKeyThemeToggler }, new() { MenuHeader = "Timeline", Key = MenuKeys.MenuKeyTimeline }, + new() { MenuHeader = "TreeComboBox", Key = MenuKeys.MenuKeyTreeComboBox }, new() { MenuHeader = "TwoTonePathIcon", Key = MenuKeys.MenuKeyTwoTonePathIcon}, new() { MenuHeader = "ToolBar", Key = MenuKeys.MenuKeyToolBar }, new() { MenuHeader = "Verification Code", Key = MenuKeys.MenuKeyVerificationCode, Status = "New" }, @@ -85,6 +86,7 @@ public static class MenuKeys public const string MenuKeyTimeline = "Timeline"; public const string MenuKeyTwoTonePathIcon = "TwoTonePathIcon"; public const string MenuKeyThemeToggler = "ThemeToggler"; + public const string MenuKeyTreeComboBox = "TreeComboBox"; public const string MenuKeyToolBar = "ToolBar"; public const string MenuKeyVerificationCode = "VerificationCode"; diff --git a/demo/Ursa.Demo/ViewModels/TreeComboBoxDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/TreeComboBoxDemoViewModel.cs new file mode 100644 index 0000000..33a9252 --- /dev/null +++ b/demo/Ursa.Demo/ViewModels/TreeComboBoxDemoViewModel.cs @@ -0,0 +1,8 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace Ursa.Demo.ViewModels; + +public class TreeComboBoxDemoViewModel: ObservableObject +{ + +} \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml b/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml new file mode 100644 index 0000000..db57123 --- /dev/null +++ b/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml index 9a138a9..4b325cd 100644 --- a/src/Ursa.Themes.Semi/Controls/_index.axaml +++ b/src/Ursa.Themes.Semi/Controls/_index.axaml @@ -32,6 +32,7 @@ + diff --git a/src/Ursa/Controls/ComboBox/TreeComboBox.cs b/src/Ursa/Controls/ComboBox/TreeComboBox.cs new file mode 100644 index 0000000..060dfee --- /dev/null +++ b/src/Ursa/Controls/ComboBox/TreeComboBox.cs @@ -0,0 +1,128 @@ +using System.Data; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Primitives; +using Avalonia.Controls.Templates; +using Avalonia.Input; +using Avalonia.Layout; +using Avalonia.OpenGL.Controls; +using Irihi.Avalonia.Shared.Common; + + +namespace Ursa.Controls; + +[TemplatePart(PartNames.PART_Popup, typeof(Popup))] +public class TreeComboBox: SelectingItemsControl +{ + private static readonly FuncTemplate DefaultPanel = + new FuncTemplate(() => new VirtualizingStackPanel()); + + public static readonly StyledProperty MaxDropDownHeightProperty = + ComboBox.MaxDropDownHeightProperty.AddOwner(); + + public double MaxDropDownHeight + { + get => GetValue(MaxDropDownHeightProperty); + set => SetValue(MaxDropDownHeightProperty, value); + } + + public static readonly StyledProperty WatermarkProperty = + TextBox.WatermarkProperty.AddOwner(); + + public string? Watermark + { + get => GetValue(WatermarkProperty); + set => SetValue(WatermarkProperty, value); + } + + public static readonly StyledProperty IsDropDownOpenProperty = + ComboBox.IsDropDownOpenProperty.AddOwner(); + + public bool IsDropDownOpen + { + get => GetValue(IsDropDownOpenProperty); + set => SetValue(IsDropDownOpenProperty, value); + } + + public static readonly StyledProperty HorizontalContentAlignmentProperty = + ContentControl.HorizontalContentAlignmentProperty.AddOwner(); + + public HorizontalAlignment HorizontalContentAlignment + { + get => GetValue(HorizontalContentAlignmentProperty); + set => SetValue(HorizontalContentAlignmentProperty, value); + } + + public static readonly StyledProperty VerticalContentAlignmentProperty = + ContentControl.VerticalContentAlignmentProperty.AddOwner(); + + public VerticalAlignment VerticalContentAlignment + { + get => GetValue(VerticalContentAlignmentProperty); + set => SetValue(VerticalContentAlignmentProperty, value); + } + + public static readonly StyledProperty SelectedItemTemplateProperty = + AvaloniaProperty.Register(nameof(SelectedItemTemplate)); + + public IDataTemplate? SelectedItemTemplate + { + get => GetValue(SelectedItemTemplateProperty); + set => SetValue(SelectedItemTemplateProperty, value); + } + + public static readonly DirectProperty SelectionBoxItemProperty = AvaloniaProperty.RegisterDirect( + nameof(SelectionBoxItem), o => o.SelectionBoxItem); + private object? _selectionBoxItem; + public object? SelectionBoxItem + { + get => _selectionBoxItem; + protected set => SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); + } + + static TreeComboBox() + { + ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); + FocusableProperty.OverrideDefaultValue(true); + } + + protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey) + { + return NeedsContainer(item, out recycleKey); + } + + internal bool NeedsContainerInternal(object? item, int index, out object? recycleKey) + { + return NeedsContainerOverride(item, index, out recycleKey); + } + + protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) + { + return new TreeComboBoxItem(); + } + + internal Control CreateContainerForItemInternal(object? item, int index, object? recycleKey) + { + return CreateContainerForItemOverride(item, index, recycleKey); + } + + protected override void ContainerForItemPreparedOverride(Control container, object? item, int index) + { + base.ContainerForItemPreparedOverride(container, item, index); + } + + internal void ContainerForItemPreparedInternal(Control container, object? item, int index) + { + ContainerForItemPreparedOverride(container, item, index); + } + + protected override void OnPointerReleased(PointerReleasedEventArgs e) + { + base.OnPointerReleased(e); + if (e.InitialPressMouseButton == MouseButton.Left) + { + IsDropDownOpen = !IsDropDownOpen; + } + } +} \ No newline at end of file diff --git a/src/Ursa/Controls/ComboBox/TreeComboBoxItem.cs b/src/Ursa/Controls/ComboBox/TreeComboBoxItem.cs new file mode 100644 index 0000000..3908c00 --- /dev/null +++ b/src/Ursa/Controls/ComboBox/TreeComboBoxItem.cs @@ -0,0 +1,110 @@ +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Primitives; +using Avalonia.Input; +using Avalonia.LogicalTree; +using Avalonia.Media; +using Avalonia.Media.TextFormatting; +using Irihi.Avalonia.Shared.Common; +using Irihi.Avalonia.Shared.Helpers; + +namespace Ursa.Controls; + +[TemplatePart(PartNames.PART_Header, typeof(Control))] +public class TreeComboBoxItem: HeaderedItemsControl, ISelectable +{ + private Control? _header; + private TreeComboBox? _treeComboBox; + + public static readonly StyledProperty IsSelectedProperty = TreeViewItem.IsSelectedProperty.AddOwner(); + + public bool IsSelected + { + get => GetValue(IsSelectedProperty); + set => SetValue(IsSelectedProperty, value); + } + + public static readonly StyledProperty IsExpandedProperty = TreeViewItem.IsExpandedProperty.AddOwner(); + + public bool IsExpanded + { + get => GetValue(IsExpandedProperty); + set => SetValue(IsExpandedProperty, value); + } + + + + public static readonly DirectProperty LevelProperty = AvaloniaProperty.RegisterDirect( + nameof(Level), o => o.Level, (o, v) => o.Level = v); + private int _level; + public int Level + { + get => _level; + protected set => SetAndRaise(LevelProperty, ref _level, value); + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + DoubleTappedEvent.RemoveHandler(OnDoubleTapped, _header); + _header = e.NameScope.Find(PartNames.PART_Header); + DoubleTappedEvent.AddHandler(OnDoubleTapped, _header); + } + + protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) + { + base.OnAttachedToLogicalTree(e); + _treeComboBox = this.FindLogicalAncestorOfType(); + Level = CalculateDistanceFromLogicalParent(this); + if (this.ItemTemplate is null && this._treeComboBox?.ItemTemplate is not null) + { + SetCurrentValue(ItemTemplateProperty, this._treeComboBox.ItemTemplate); + } + + + + } + + private void OnDoubleTapped(object sender, TappedEventArgs e) + { + if (this.ItemCount <= 0) return; + this.SetCurrentValue(IsExpandedProperty, !IsExpanded); + e.Handled = true; + } + + protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey) + { + return EnsureParent().NeedsContainerInternal(item, index, out recycleKey); + } + + protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) + { + return EnsureParent().CreateContainerForItemInternal(item, index, recycleKey); + } + + protected override void ContainerForItemPreparedOverride(Control container, object? item, int index) + { + EnsureParent().ContainerForItemPreparedInternal(container, item, index); + } + + // TODO replace with helper method from shared library. + private static int CalculateDistanceFromLogicalParent(ILogical? logical, int @default = -1) where T: ILogical + { + int distance = 0; + ILogical? parent = logical; + while (parent is not null) + { + if (parent is T) return distance; + parent = parent.LogicalParent; + distance++; + } + return @default; + } + + private TreeComboBox EnsureParent() + { + return this._treeComboBox ?? + throw new InvalidOperationException("TreeComboBoxItem must be a part of TreeComboBox"); + } +} \ No newline at end of file diff --git a/src/Ursa/Ursa.csproj b/src/Ursa/Ursa.csproj index 8c86bf6..853e121 100644 --- a/src/Ursa/Ursa.csproj +++ b/src/Ursa/Ursa.csproj @@ -17,7 +17,7 @@ - + From edf0e387ccfae82380069f0d4a6d1ffc5caafef4 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Thu, 11 Apr 2024 23:47:36 +0800 Subject: [PATCH 02/13] feat: update click behavior. --- demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml | 6 +++++- .../Controls/TreeComboBox.axaml | 21 ++++++++++++------- src/Ursa/Controls/ComboBox/TreeComboBox.cs | 19 ++++++++++++++++- .../Controls/ComboBox/TreeComboBoxItem.cs | 5 +++-- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml b/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml index 2624e7d..171cfe8 100644 --- a/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml +++ b/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml @@ -9,7 +9,11 @@ - + + + + + diff --git a/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml b/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml index db57123..01642e4 100644 --- a/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml +++ b/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml @@ -1,17 +1,19 @@ + xmlns:iri="https://irihi.tech/shared" + xmlns:converters="clr-namespace:Avalonia.Controls.Converters;assembly=Avalonia.Controls"> + + - - + + - - + @@ -28,12 +30,15 @@ - - + + - + diff --git a/src/Ursa/Controls/ComboBox/TreeComboBox.cs b/src/Ursa/Controls/ComboBox/TreeComboBox.cs index 060dfee..a20632a 100644 --- a/src/Ursa/Controls/ComboBox/TreeComboBox.cs +++ b/src/Ursa/Controls/ComboBox/TreeComboBox.cs @@ -15,6 +15,8 @@ namespace Ursa.Controls; [TemplatePart(PartNames.PART_Popup, typeof(Popup))] public class TreeComboBox: SelectingItemsControl { + private Popup? _popup; + private static readonly FuncTemplate DefaultPanel = new FuncTemplate(() => new VirtualizingStackPanel()); @@ -87,6 +89,12 @@ public class TreeComboBox: SelectingItemsControl FocusableProperty.OverrideDefaultValue(true); } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + _popup = e.NameScope.Find(PartNames.PART_Popup); + } + protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey) { return NeedsContainer(item, out recycleKey); @@ -122,7 +130,16 @@ public class TreeComboBox: SelectingItemsControl base.OnPointerReleased(e); if (e.InitialPressMouseButton == MouseButton.Left) { - IsDropDownOpen = !IsDropDownOpen; + if (_popup is not null && _popup.IsOpen && e.Source is TextBlock v && _popup.IsInsidePopup(v)) + { + SelectionBoxItem = v.Text; + SetCurrentValue(IsDropDownOpenProperty, false); + } + else + { + IsDropDownOpen = !IsDropDownOpen; + } + } } } \ No newline at end of file diff --git a/src/Ursa/Controls/ComboBox/TreeComboBoxItem.cs b/src/Ursa/Controls/ComboBox/TreeComboBoxItem.cs index 3908c00..887d20a 100644 --- a/src/Ursa/Controls/ComboBox/TreeComboBoxItem.cs +++ b/src/Ursa/Controls/ComboBox/TreeComboBoxItem.cs @@ -3,6 +3,7 @@ using Avalonia.Controls; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Input; +using Avalonia.Interactivity; using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Media.TextFormatting; @@ -47,9 +48,9 @@ public class TreeComboBoxItem: HeaderedItemsControl, ISelectable protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); - DoubleTappedEvent.RemoveHandler(OnDoubleTapped, _header); + DoubleTappedEvent.RemoveHandler(OnDoubleTapped, this); _header = e.NameScope.Find(PartNames.PART_Header); - DoubleTappedEvent.AddHandler(OnDoubleTapped, _header); + DoubleTappedEvent.AddHandler(OnDoubleTapped, RoutingStrategies.Tunnel, true, this); } protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e) From 1521b0778545f951aaa78abee37958d9c62ca93d Mon Sep 17 00:00:00 2001 From: rabbitism Date: Mon, 15 Apr 2024 03:35:27 +0800 Subject: [PATCH 03/13] feat: improve styling. --- .../Controls/TreeComboBox.axaml | 148 +++++++++++++++++- src/Ursa/Controls/ComboBox/TreeComboBox.cs | 24 ++- .../Controls/ComboBox/TreeComboBoxItem.cs | 19 ++- .../SelectionBoxTemplateConverter.cs | 16 ++ src/Ursa/Ursa.csproj | 2 +- 5 files changed, 188 insertions(+), 21 deletions(-) create mode 100644 src/Ursa/Converters/SelectionBoxTemplateConverter.cs diff --git a/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml b/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml index 01642e4..97f3fbc 100644 --- a/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml +++ b/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml @@ -8,13 +8,85 @@ + + + + + + + - - - - - + + + + + + + + + @@ -26,21 +98,81 @@ + + + + - + - - + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ursa/Controls/ComboBox/TreeComboBox.cs b/src/Ursa/Controls/ComboBox/TreeComboBox.cs index a20632a..8de5173 100644 --- a/src/Ursa/Controls/ComboBox/TreeComboBox.cs +++ b/src/Ursa/Controls/ComboBox/TreeComboBox.cs @@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Layout; +using Avalonia.LogicalTree; using Avalonia.OpenGL.Controls; using Irihi.Avalonia.Shared.Common; @@ -115,24 +116,31 @@ public class TreeComboBox: SelectingItemsControl return CreateContainerForItemOverride(item, index, recycleKey); } - protected override void ContainerForItemPreparedOverride(Control container, object? item, int index) - { - base.ContainerForItemPreparedOverride(container, item, index); - } - internal void ContainerForItemPreparedInternal(Control container, object? item, int index) { ContainerForItemPreparedOverride(container, item, index); } - + protected override void OnPointerReleased(PointerReleasedEventArgs e) { base.OnPointerReleased(e); if (e.InitialPressMouseButton == MouseButton.Left) { - if (_popup is not null && _popup.IsOpen && e.Source is TextBlock v && _popup.IsInsidePopup(v)) + if (_popup is not null && _popup.IsOpen && e.Source is Visual v && _popup.IsInsidePopup(v)) { - SelectionBoxItem = v.Text; + var container = v.FindLogicalAncestorOfType(); + container?.SetValue(IsSelectedProperty, true); + if (container?.Header is Control control) + { + SelectionBoxItem = control.DataContext ?? control.ToString(); + // UpdateSelectionFromEventSource(e.Source); + + } + else + { + SelectionBoxItem = container?.Header; + // UpdateSelectionFromEventSource(e.Source); + } SetCurrentValue(IsDropDownOpenProperty, false); } else diff --git a/src/Ursa/Controls/ComboBox/TreeComboBoxItem.cs b/src/Ursa/Controls/ComboBox/TreeComboBoxItem.cs index 887d20a..f2d3b1c 100644 --- a/src/Ursa/Controls/ComboBox/TreeComboBoxItem.cs +++ b/src/Ursa/Controls/ComboBox/TreeComboBoxItem.cs @@ -1,6 +1,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; +using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Interactivity; @@ -45,6 +46,13 @@ public class TreeComboBoxItem: HeaderedItemsControl, ISelectable protected set => SetAndRaise(LevelProperty, ref _level, value); } + static TreeComboBoxItem() + { + IsSelectedProperty.AffectsPseudoClass(PseudoClassName.PC_Selected, + SelectingItemsControl.IsSelectedChangedEvent); + PressedMixin.Attach(); + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); @@ -57,14 +65,15 @@ public class TreeComboBoxItem: HeaderedItemsControl, ISelectable { base.OnAttachedToLogicalTree(e); _treeComboBox = this.FindLogicalAncestorOfType(); - Level = CalculateDistanceFromLogicalParent(this); + Level = CalculateDistanceFromLogicalParent(this) - 1; if (this.ItemTemplate is null && this._treeComboBox?.ItemTemplate is not null) { SetCurrentValue(ItemTemplateProperty, this._treeComboBox.ItemTemplate); } - - - + if(this.ItemContainerTheme is null && this._treeComboBox?.ItemContainerTheme is not null) + { + SetCurrentValue(ItemContainerThemeProperty, this._treeComboBox.ItemContainerTheme); + } } private void OnDoubleTapped(object sender, TappedEventArgs e) @@ -76,6 +85,8 @@ public class TreeComboBoxItem: HeaderedItemsControl, ISelectable protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey) { + TreeViewItem t = new TreeViewItem(); + ComboBox c = new ComboBox(); return EnsureParent().NeedsContainerInternal(item, index, out recycleKey); } diff --git a/src/Ursa/Converters/SelectionBoxTemplateConverter.cs b/src/Ursa/Converters/SelectionBoxTemplateConverter.cs new file mode 100644 index 0000000..3f735b7 --- /dev/null +++ b/src/Ursa/Converters/SelectionBoxTemplateConverter.cs @@ -0,0 +1,16 @@ +using System.Globalization; +using Avalonia.Controls.Templates; +using Avalonia.Data.Converters; + +namespace Ursa.Converters; + +public class SelectionBoxTemplateConverter: IMultiValueConverter +{ + public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) + { + var selectedItemTemplate = values.Count > 0 ? values[0] as IDataTemplate : null; + if (selectedItemTemplate is not null) return selectedItemTemplate; + var itemTemplate = values.Count > 1 ? values[1] as IDataTemplate : null; + return itemTemplate; + } +} \ No newline at end of file diff --git a/src/Ursa/Ursa.csproj b/src/Ursa/Ursa.csproj index 853e121..6b5f3e0 100644 --- a/src/Ursa/Ursa.csproj +++ b/src/Ursa/Ursa.csproj @@ -17,7 +17,7 @@ - + From 4b58825ac486b5ab01dfaf0ef555ed6c5f3eda64 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Tue, 16 Apr 2024 23:35:24 +0800 Subject: [PATCH 04/13] feat: treeview is not inheriting selecting items control since selected item is not always within item view collection. --- src/Ursa/Controls/ComboBox/TreeComboBox.cs | 40 ++++++++++++++-------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/src/Ursa/Controls/ComboBox/TreeComboBox.cs b/src/Ursa/Controls/ComboBox/TreeComboBox.cs index 8de5173..921535f 100644 --- a/src/Ursa/Controls/ComboBox/TreeComboBox.cs +++ b/src/Ursa/Controls/ComboBox/TreeComboBox.cs @@ -1,8 +1,11 @@ +using System.Collections; +using System.Collections.Specialized; using System.Data; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; +using Avalonia.Controls.Selection; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Layout; @@ -14,7 +17,7 @@ using Irihi.Avalonia.Shared.Common; namespace Ursa.Controls; [TemplatePart(PartNames.PART_Popup, typeof(Popup))] -public class TreeComboBox: SelectingItemsControl +public class TreeComboBox: ItemsControl { private Popup? _popup; @@ -89,6 +92,27 @@ public class TreeComboBox: SelectingItemsControl ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); FocusableProperty.OverrideDefaultValue(true); } + + private Control? TreeContainerFromItem(object item) + { + return TreeContainerFromItemInternal(this, item); + + static Control? TreeContainerFromItemInternal(ItemsControl itemsControl, object item) + { + Control? control = itemsControl.ContainerFromItem(item); + if(control is not null) return control; + foreach (var child in itemsControl.GetRealizedContainers()) + { + if (child is ItemsControl childItemsControl) + { + control = TreeContainerFromItemInternal(childItemsControl, item); + if (control is not null) return control; + } + } + return null; + } + } + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { @@ -129,19 +153,7 @@ public class TreeComboBox: SelectingItemsControl if (_popup is not null && _popup.IsOpen && e.Source is Visual v && _popup.IsInsidePopup(v)) { var container = v.FindLogicalAncestorOfType(); - container?.SetValue(IsSelectedProperty, true); - if (container?.Header is Control control) - { - SelectionBoxItem = control.DataContext ?? control.ToString(); - // UpdateSelectionFromEventSource(e.Source); - - } - else - { - SelectionBoxItem = container?.Header; - // UpdateSelectionFromEventSource(e.Source); - } - SetCurrentValue(IsDropDownOpenProperty, false); + } else { From 887242d4a6188e94d9b6a7767fdb0e617258822b Mon Sep 17 00:00:00 2001 From: rabbitism Date: Wed, 17 Apr 2024 01:02:57 +0800 Subject: [PATCH 05/13] feat: implement tree single selection. --- src/Ursa/Controls/ComboBox/TreeComboBox.cs | 172 +++++++++++++++--- .../Controls/ComboBox/TreeComboBoxItem.cs | 1 + 2 files changed, 146 insertions(+), 27 deletions(-) diff --git a/src/Ursa/Controls/ComboBox/TreeComboBox.cs b/src/Ursa/Controls/ComboBox/TreeComboBox.cs index 921535f..430105b 100644 --- a/src/Ursa/Controls/ComboBox/TreeComboBox.cs +++ b/src/Ursa/Controls/ComboBox/TreeComboBox.cs @@ -1,17 +1,16 @@ -using System.Collections; -using System.Collections.Specialized; -using System.Data; using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; -using Avalonia.Controls.Selection; +using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Layout; using Avalonia.LogicalTree; -using Avalonia.OpenGL.Controls; +using Avalonia.Media; +using Avalonia.VisualTree; using Irihi.Avalonia.Shared.Common; +using Size = Avalonia.Size; namespace Ursa.Controls; @@ -86,34 +85,40 @@ public class TreeComboBox: ItemsControl get => _selectionBoxItem; protected set => SetAndRaise(SelectionBoxItemProperty, ref _selectionBoxItem, value); } + + private object? _selectedItem; + + public static readonly DirectProperty SelectedItemProperty = AvaloniaProperty.RegisterDirect( + nameof(SelectedItem), o => o.SelectedItem, (o, v) => o.SelectedItem = v); + + public object? SelectedItem + { + get + { + return _selectedItem; + } + set + { + SetAndRaise(SelectedItemProperty, ref _selectedItem, value); + + } + } static TreeComboBox() { ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); FocusableProperty.OverrideDefaultValue(true); + SelectedItemProperty.Changed.AddClassHandler((box, args) => box.OnSelectedItemChanged(args)); } - - private Control? TreeContainerFromItem(object item) - { - return TreeContainerFromItemInternal(this, item); - static Control? TreeContainerFromItemInternal(ItemsControl itemsControl, object item) + private void OnSelectedItemChanged(AvaloniaPropertyChangedEventArgs args) + { + if (args.NewValue.Value is not null) { - Control? control = itemsControl.ContainerFromItem(item); - if(control is not null) return control; - foreach (var child in itemsControl.GetRealizedContainers()) - { - if (child is ItemsControl childItemsControl) - { - control = TreeContainerFromItemInternal(childItemsControl, item); - if (control is not null) return control; - } - } - return null; + UpdateSelectionBoxItem(args.NewValue.Value); } } - - + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); @@ -148,12 +153,25 @@ public class TreeComboBox: ItemsControl protected override void OnPointerReleased(PointerReleasedEventArgs e) { base.OnPointerReleased(e); - if (e.InitialPressMouseButton == MouseButton.Left) + if (e is { InitialPressMouseButton: MouseButton.Left, Source: Visual source }) { - if (_popup is not null && _popup.IsOpen && e.Source is Visual v && _popup.IsInsidePopup(v)) + if (_popup is not null && _popup.IsOpen && _popup.IsInsidePopup(source)) { - var container = v.FindLogicalAncestorOfType(); - + var container = GetContainerFromEventSource(source); + if (container is null) return; + var item = TreeItemFromContainer(container); + if (item is null) return; + if (SelectedItem is not null) + { + var selectedContainer = TreeContainerFromItem(SelectedItem); + if(selectedContainer is TreeComboBoxItem selectedTreeComboBoxItem) + { + selectedTreeComboBoxItem.IsSelected = false; + } + } + this.SelectedItem = item; + container.IsSelected = true; + IsDropDownOpen = false; } else { @@ -162,4 +180,104 @@ public class TreeComboBox: ItemsControl } } + + private void UpdateSelectionBoxItem(object? item) + { + if (item is ContentControl contentControl) + { + item = contentControl.Content; + } + else if(item is HeaderedItemsControl headeredItemsControl) + { + item = headeredItemsControl.Header; + } + + if (item is Control control) + { + if (VisualRoot == null) return; + control.Measure(Size.Infinity); + SelectionBoxItem = new Rectangle + { + Width = control.DesiredSize.Width, + Height = control.DesiredSize.Height, + Fill = new VisualBrush + { + Visual = control, + Stretch = Stretch.None, + AlignmentX = AlignmentX.Left, + } + }; + // TODO: Implement flow direction udpate + } + else + { + if (ItemTemplate is null && DisplayMemberBinding is { } binding) + { + var template = new FuncDataTemplate((a,_) => new TextBlock + { + [DataContextProperty] = a, + [!TextBlock.TextProperty] = binding, + }); + var textBlock = template.Build(item); + SelectionBoxItem = textBlock; + } + else + { + SelectionBoxItem = item; + } + } + } + + private TreeComboBoxItem? GetContainerFromEventSource(object eventSource) + { + if (eventSource is Visual visual) + { + var item = visual.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); + return item?.Owner == this ? item : null!; + } + + return null; + } + + private object? TreeItemFromContainer(Control container) + { + return TreeItemFromContainer(this, container); + } + + private Control? TreeContainerFromItem(object item) + { + return TreeContainerFromItem(this, item); + } + + private static Control? TreeContainerFromItem(ItemsControl itemsControl, object item) + { + if (itemsControl.ContainerFromItem(item) is { } container) + { + return container; + } + foreach (var child in itemsControl.GetRealizedContainers()) + { + if(child is ItemsControl childItemsControl && TreeContainerFromItem(childItemsControl, item) is { } childContainer) + { + return childContainer; + } + } + return null; + } + + private static object? TreeItemFromContainer(ItemsControl itemsControl, Control container) + { + if (itemsControl.ItemFromContainer(container) is { } item) + { + return item; + } + foreach (var child in itemsControl.GetRealizedContainers()) + { + if(child is ItemsControl childItemsControl && TreeItemFromContainer(childItemsControl, container) is { } childItem) + { + return childItem; + } + } + return null; + } } \ No newline at end of file diff --git a/src/Ursa/Controls/ComboBox/TreeComboBoxItem.cs b/src/Ursa/Controls/ComboBox/TreeComboBoxItem.cs index f2d3b1c..cab3e1e 100644 --- a/src/Ursa/Controls/ComboBox/TreeComboBoxItem.cs +++ b/src/Ursa/Controls/ComboBox/TreeComboBoxItem.cs @@ -18,6 +18,7 @@ public class TreeComboBoxItem: HeaderedItemsControl, ISelectable { private Control? _header; private TreeComboBox? _treeComboBox; + public TreeComboBox? Owner => _treeComboBox; public static readonly StyledProperty IsSelectedProperty = TreeViewItem.IsSelectedProperty.AddOwner(); From 13a9806e4b57a8fbdb830a6b8cd207b0500e0c7c Mon Sep 17 00:00:00 2001 From: rabbitism Date: Wed, 17 Apr 2024 01:11:25 +0800 Subject: [PATCH 06/13] feat: mark container from external selection change. clean up code --- src/Ursa/Controls/ComboBox/TreeComboBox.cs | 29 ++++++++++++---------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/Ursa/Controls/ComboBox/TreeComboBox.cs b/src/Ursa/Controls/ComboBox/TreeComboBox.cs index 430105b..83999fd 100644 --- a/src/Ursa/Controls/ComboBox/TreeComboBox.cs +++ b/src/Ursa/Controls/ComboBox/TreeComboBox.cs @@ -93,15 +93,8 @@ public class TreeComboBox: ItemsControl public object? SelectedItem { - get - { - return _selectedItem; - } - set - { - SetAndRaise(SelectedItemProperty, ref _selectedItem, value); - - } + get => _selectedItem; + set => SetAndRaise(SelectedItemProperty, ref _selectedItem, value); } static TreeComboBox() @@ -113,10 +106,9 @@ public class TreeComboBox: ItemsControl private void OnSelectedItemChanged(AvaloniaPropertyChangedEventArgs args) { - if (args.NewValue.Value is not null) - { - UpdateSelectionBoxItem(args.NewValue.Value); - } + MarkContainerSelection(args.OldValue.Value, false); + MarkContainerSelection(args.NewValue.Value, true); + UpdateSelectionBoxItem(args.NewValue.Value); } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) @@ -183,6 +175,7 @@ public class TreeComboBox: ItemsControl private void UpdateSelectionBoxItem(object? item) { + if(item is null) SelectionBoxItem = null; if (item is ContentControl contentControl) { item = contentControl.Content; @@ -280,4 +273,14 @@ public class TreeComboBox: ItemsControl } return null; } + + private void MarkContainerSelection(object? item, bool selected) + { + if (item is null) return; + var container = TreeContainerFromItem(item); + if (container is TreeComboBoxItem treeComboBoxItem) + { + treeComboBoxItem.IsSelected = selected; + } + } } \ No newline at end of file From 2c471aa3f6a4e98feb73376b79343a7a5211f3dd Mon Sep 17 00:00:00 2001 From: rabbitism Date: Wed, 17 Apr 2024 20:10:32 +0800 Subject: [PATCH 07/13] feat: add mvvm sample. --- demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml | 54 ++++++++++------ .../ViewModels/TreeComboBoxDemoViewModel.cs | 61 ++++++++++++++++++- src/Ursa/Controls/ComboBox/TreeComboBox.cs | 3 +- 3 files changed, 99 insertions(+), 19 deletions(-) diff --git a/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml b/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml index 171cfe8..82d1737 100644 --- a/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml +++ b/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml @@ -1,27 +1,47 @@ - + - + - - - + + + - - + + - - - + + + + + + + + + + + + + + + diff --git a/demo/Ursa.Demo/ViewModels/TreeComboBoxDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/TreeComboBoxDemoViewModel.cs index 33a9252..0261bd0 100644 --- a/demo/Ursa.Demo/ViewModels/TreeComboBoxDemoViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/TreeComboBoxDemoViewModel.cs @@ -1,8 +1,67 @@ +using System.Collections.Generic; using CommunityToolkit.Mvvm.ComponentModel; namespace Ursa.Demo.ViewModels; public class TreeComboBoxDemoViewModel: ObservableObject { - + public List Items { get; set; } + + public TreeComboBoxDemoViewModel() + { + Items = new List() + { + new TreeComboBoxItemViewModel() + { + ItemName = "Item 1", + Children = new List() + { + new TreeComboBoxItemViewModel() + { + ItemName = "Item 1-1", + Children = new List() + { + new TreeComboBoxItemViewModel() + { + ItemName = "Item 1-1-1" + }, + new TreeComboBoxItemViewModel() + { + ItemName = "Item 1-1-2" + } + } + }, + new TreeComboBoxItemViewModel() + { + ItemName = "Item 1-2" + } + } + }, + new TreeComboBoxItemViewModel() + { + ItemName = "Item 2", + Children = new List() + { + new TreeComboBoxItemViewModel() + { + ItemName = "Item 2-1" + }, + new TreeComboBoxItemViewModel() + { + ItemName = "Item 2-2" + } + } + }, + new TreeComboBoxItemViewModel() + { + ItemName = "Item 3" + }, + }; + } +} + +public partial class TreeComboBoxItemViewModel : ObservableObject +{ + [ObservableProperty] private string? _itemName; + public List Children { get; set; } = new (); } \ No newline at end of file diff --git a/src/Ursa/Controls/ComboBox/TreeComboBox.cs b/src/Ursa/Controls/ComboBox/TreeComboBox.cs index 83999fd..f39f262 100644 --- a/src/Ursa/Controls/ComboBox/TreeComboBox.cs +++ b/src/Ursa/Controls/ComboBox/TreeComboBox.cs @@ -6,8 +6,8 @@ using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; using Avalonia.Input; using Avalonia.Layout; -using Avalonia.LogicalTree; using Avalonia.Media; +using Avalonia.Metadata; using Avalonia.VisualTree; using Irihi.Avalonia.Shared.Common; using Size = Avalonia.Size; @@ -71,6 +71,7 @@ public class TreeComboBox: ItemsControl public static readonly StyledProperty SelectedItemTemplateProperty = AvaloniaProperty.Register(nameof(SelectedItemTemplate)); + [InheritDataTypeFromItems(nameof(ItemsSource))] public IDataTemplate? SelectedItemTemplate { get => GetValue(SelectedItemTemplateProperty); From 50be089b4ec830d3a60edbb5ff6bfbedad8de9af Mon Sep 17 00:00:00 2001 From: rabbitism Date: Wed, 17 Apr 2024 20:35:35 +0800 Subject: [PATCH 08/13] feat: selected item is now two-way binding by default. --- demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml | 15 ++++--- .../ViewModels/TreeComboBoxDemoViewModel.cs | 3 +- .../Controls/TreeComboBox.axaml | 43 +++++++++++-------- .../SelectedItemTemplateConverter.cs | 24 +++++++++++ src/Ursa/Controls/ComboBox/TreeComboBox.cs | 15 +++++-- 5 files changed, 73 insertions(+), 27 deletions(-) create mode 100644 src/Ursa.Themes.Semi/Converters/SelectedItemTemplateConverter.cs diff --git a/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml b/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml index 82d1737..5816d12 100644 --- a/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml +++ b/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml @@ -31,17 +31,22 @@ - - - - - + + + + + + + + + diff --git a/demo/Ursa.Demo/ViewModels/TreeComboBoxDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/TreeComboBoxDemoViewModel.cs index 0261bd0..998bb41 100644 --- a/demo/Ursa.Demo/ViewModels/TreeComboBoxDemoViewModel.cs +++ b/demo/Ursa.Demo/ViewModels/TreeComboBoxDemoViewModel.cs @@ -3,8 +3,9 @@ using CommunityToolkit.Mvvm.ComponentModel; namespace Ursa.Demo.ViewModels; -public class TreeComboBoxDemoViewModel: ObservableObject +public partial class TreeComboBoxDemoViewModel: ObservableObject { + [ObservableProperty] private TreeComboBoxItemViewModel? _selectedItem; public List Items { get; set; } public TreeComboBoxDemoViewModel() diff --git a/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml b/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml index 97f3fbc..42dbb78 100644 --- a/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml +++ b/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml @@ -2,10 +2,12 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:u="https://irihi.tech/ursa" xmlns:iri="https://irihi.tech/shared" - xmlns:converters="clr-namespace:Avalonia.Controls.Converters;assembly=Avalonia.Controls"> + xmlns:converters="clr-namespace:Avalonia.Controls.Converters;assembly=Avalonia.Controls" + xmlns:converters1="clr-namespace:Ursa.Themes.Semi.Converters"> + @@ -17,7 +19,7 @@ - + + Content="{TemplateBinding SelectionBoxItem}"> + + + + + + + - - + + + + IsOpen="{Binding IsDropDownOpen, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}"> values, Type targetType, object? parameter, CultureInfo culture) + { + if(values.Count>0 && values[0] is IDataTemplate template1) + { + return template1; + } + if(values.Count>1 && values[1] is IDataTemplate template2) + { + return template2; + } + return AvaloniaProperty.UnsetValue; + } +} \ No newline at end of file diff --git a/src/Ursa/Controls/ComboBox/TreeComboBox.cs b/src/Ursa/Controls/ComboBox/TreeComboBox.cs index f39f262..2424ea4 100644 --- a/src/Ursa/Controls/ComboBox/TreeComboBox.cs +++ b/src/Ursa/Controls/ComboBox/TreeComboBox.cs @@ -4,19 +4,21 @@ using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; using Avalonia.Controls.Shapes; using Avalonia.Controls.Templates; +using Avalonia.Data; using Avalonia.Input; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Metadata; using Avalonia.VisualTree; using Irihi.Avalonia.Shared.Common; +using Irihi.Avalonia.Shared.Contracts; using Size = Avalonia.Size; namespace Ursa.Controls; [TemplatePart(PartNames.PART_Popup, typeof(Popup))] -public class TreeComboBox: ItemsControl +public class TreeComboBox: ItemsControl, IClearControl { private Popup? _popup; @@ -89,8 +91,10 @@ public class TreeComboBox: ItemsControl private object? _selectedItem; - public static readonly DirectProperty SelectedItemProperty = AvaloniaProperty.RegisterDirect( - nameof(SelectedItem), o => o.SelectedItem, (o, v) => o.SelectedItem = v); + public static readonly DirectProperty SelectedItemProperty = + AvaloniaProperty.RegisterDirect( + nameof(SelectedItem), o => o.SelectedItem, (o, v) => o.SelectedItem = v, + defaultBindingMode: BindingMode.TwoWay); public object? SelectedItem { @@ -284,4 +288,9 @@ public class TreeComboBox: ItemsControl treeComboBoxItem.IsSelected = selected; } } + + public void Clear() + { + SelectedItem = null; + } } \ No newline at end of file From 0dbe12fef1ffd6a582b6a6196aea111b35ff60ac Mon Sep 17 00:00:00 2001 From: rabbitism Date: Thu, 18 Apr 2024 22:11:36 +0800 Subject: [PATCH 09/13] feat: WIP: add inner content. --- demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml | 1 + src/Ursa/Controls/ComboBox/TreeComboBox.cs | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml b/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml index 5816d12..06d7241 100644 --- a/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml +++ b/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml @@ -30,6 +30,7 @@ diff --git a/src/Ursa/Controls/ComboBox/TreeComboBox.cs b/src/Ursa/Controls/ComboBox/TreeComboBox.cs index 2424ea4..689ba99 100644 --- a/src/Ursa/Controls/ComboBox/TreeComboBox.cs +++ b/src/Ursa/Controls/ComboBox/TreeComboBox.cs @@ -18,7 +18,7 @@ using Size = Avalonia.Size; namespace Ursa.Controls; [TemplatePart(PartNames.PART_Popup, typeof(Popup))] -public class TreeComboBox: ItemsControl, IClearControl +public class TreeComboBox: ItemsControl, IClearControl, IInnerContentControl, IPopupInnerContent { private Popup? _popup; @@ -293,4 +293,9 @@ public class TreeComboBox: ItemsControl, IClearControl { SelectedItem = null; } + + public object? InnerLeftContent { get; set; } + public object? InnerRightContent { get; set; } + public object? PopupInnerTopContent { get; set; } + public object? PopupInnerBottomContent { get; set; } } \ No newline at end of file From f7a316340b57b6e29025f220575a9763095921bf Mon Sep 17 00:00:00 2001 From: rabbitism Date: Sat, 20 Apr 2024 01:20:04 +0800 Subject: [PATCH 10/13] feat: add inner contents. --- demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml | 25 +- .../Controls/TreeComboBox.axaml | 276 ++++++++++++------ .../SelectedItemTemplateConverter.cs | 24 -- src/Ursa/Controls/ComboBox/TreeComboBox.cs | 46 ++- .../SelectionBoxTemplateConverter.cs | 11 +- 5 files changed, 257 insertions(+), 125 deletions(-) delete mode 100644 src/Ursa.Themes.Semi/Converters/SelectedItemTemplateConverter.cs diff --git a/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml b/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml index 06d7241..e204bff 100644 --- a/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml +++ b/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml @@ -41,13 +41,22 @@ - - - - - - - - + + + + + + + diff --git a/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml b/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml index 42dbb78..3f12f48 100644 --- a/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml +++ b/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml @@ -1,68 +1,92 @@ - - - - - - + + + + + - - - - - - - + + + + + + + + - - - + + + - + - - - + + + - + + - + IsHitTestVisible="True"> - - + - - - + CornerRadius="6"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + - - - - + + + + - - + - + + Content="{TemplateBinding Header}" + ContentTemplate="{TemplateBinding HeaderTemplate}" + Focusable="False" + Foreground="{TemplateBinding Foreground}" /> - + - +