diff --git a/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml b/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml
new file mode 100644
index 0000000..e204bff
--- /dev/null
+++ b/demo/Ursa.Demo/Pages/TreeComboBoxDemo.axaml
@@ -0,0 +1,62 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 1ac7c29..80b9f6a 100644
--- a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
+++ b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
@@ -56,6 +56,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 c58784d..a1fcdb7 100644
--- a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
+++ b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
@@ -44,6 +44,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 = "Time Box", Key = MenuKeys.MenuKeyTimeBox, Status = "New" },
@@ -88,6 +89,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";
public const string MenuKeyTimeBox = "TimeBox";
diff --git a/demo/Ursa.Demo/ViewModels/TreeComboBoxDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/TreeComboBoxDemoViewModel.cs
new file mode 100644
index 0000000..998bb41
--- /dev/null
+++ b/demo/Ursa.Demo/ViewModels/TreeComboBoxDemoViewModel.cs
@@ -0,0 +1,68 @@
+using System.Collections.Generic;
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace Ursa.Demo.ViewModels;
+
+public partial class TreeComboBoxDemoViewModel: ObservableObject
+{
+ [ObservableProperty] private TreeComboBoxItemViewModel? _selectedItem;
+ 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.Themes.Semi/Controls/TreeComboBox.axaml b/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml
new file mode 100644
index 0000000..23d2641
--- /dev/null
+++ b/src/Ursa.Themes.Semi/Controls/TreeComboBox.axaml
@@ -0,0 +1,298 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml
index 0e6694b..6e25af2 100644
--- a/src/Ursa.Themes.Semi/Controls/_index.axaml
+++ b/src/Ursa.Themes.Semi/Controls/_index.axaml
@@ -33,6 +33,7 @@
+
diff --git a/src/Ursa/Controls/ComboBox/TreeComboBox.cs b/src/Ursa/Controls/ComboBox/TreeComboBox.cs
new file mode 100644
index 0000000..e88d55e
--- /dev/null
+++ b/src/Ursa/Controls/ComboBox/TreeComboBox.cs
@@ -0,0 +1,339 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
+using Avalonia.Controls.Mixins;
+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 Irihi.Avalonia.Shared.Helpers;
+using Size = Avalonia.Size;
+
+
+namespace Ursa.Controls;
+
+[TemplatePart(PartNames.PART_Popup, typeof(Popup))]
+[PseudoClasses(PC_DropdownOpen)]
+public class TreeComboBox: ItemsControl, IClearControl, IInnerContentControl, IPopupInnerContent
+{
+ public const string PC_DropdownOpen = ":dropdownopen";
+
+ private Popup? _popup;
+
+ 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));
+
+ [InheritDataTypeFromItems(nameof(ItemsSource))]
+ 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);
+ }
+
+ private object? _selectedItem;
+
+ public static readonly DirectProperty SelectedItemProperty =
+ AvaloniaProperty.RegisterDirect(
+ nameof(SelectedItem), o => o.SelectedItem, (o, v) => o.SelectedItem = v,
+ defaultBindingMode: BindingMode.TwoWay);
+
+ public object? SelectedItem
+ {
+ get => _selectedItem;
+ set => SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
+ }
+
+ public static readonly StyledProperty