diff --git a/demo/Ursa.Demo/App.axaml b/demo/Ursa.Demo/App.axaml
index 342fcd6..abf9048 100644
--- a/demo/Ursa.Demo/App.axaml
+++ b/demo/Ursa.Demo/App.axaml
@@ -5,6 +5,6 @@
xmlns:u-semi="https://irihi.tech/ursa/themes/semi">
-
+
\ No newline at end of file
diff --git a/demo/Ursa.Demo/Converters/MenuDataTemplateSelector.cs b/demo/Ursa.Demo/DataTemplates/MenuDataTemplateSelector.cs
similarity index 100%
rename from demo/Ursa.Demo/Converters/MenuDataTemplateSelector.cs
rename to demo/Ursa.Demo/DataTemplates/MenuDataTemplateSelector.cs
diff --git a/demo/Ursa.Demo/DataTemplates/ToolBarItemTemplateSelector.cs b/demo/Ursa.Demo/DataTemplates/ToolBarItemTemplateSelector.cs
new file mode 100644
index 0000000..bfd9c76
--- /dev/null
+++ b/demo/Ursa.Demo/DataTemplates/ToolBarItemTemplateSelector.cs
@@ -0,0 +1,56 @@
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
+using Avalonia.Data;
+using Ursa.Controls;
+using Ursa.Demo.ViewModels;
+
+namespace Ursa.Demo.Converters;
+
+public class ToolBarItemTemplateSelector: IDataTemplate
+{
+
+ public static ToolBarItemTemplateSelector Instance { get; } = new();
+ public Control? Build(object? param)
+ {
+ if (param is null) return null;
+ if (param is ToolBarSeparatorViewModel sep)
+ {
+ return new ToolBarSeparator();
+ }
+ if (param is ToolBarButtonItemViewModel vm)
+ {
+ return new Button()
+ {
+ [!ContentControl.ContentProperty] = new Binding() { Path = "Content" },
+ [!Button.CommandProperty] = new Binding() { Path = "Command" },
+ [!ToolBar.OverflowModeProperty] = new Binding(){Path = nameof(ToolBarItemViewModel.OverflowMode)},
+ };
+ }
+ if (param is ToolBarCheckBoxItemViweModel cb)
+ {
+ return new CheckBox()
+ {
+ [!ContentControl.ContentProperty] = new Binding() { Path = "Content" },
+ [!ToggleButton.IsCheckedProperty] = new Binding() { Path = "IsChecked" },
+ [!ToolBar.OverflowModeProperty] = new Binding(){Path = nameof(ToolBarItemViewModel.OverflowMode)},
+ };
+ }
+ if (param is ToolBarComboBoxItemViewModel combo)
+ {
+ return new ComboBox()
+ {
+ [!ContentControl.ContentProperty] = new Binding() { Path = "Content" },
+ [!SelectingItemsControl.SelectedItemProperty] = new Binding() { Path = "SelectedItem" },
+ [!ItemsControl.ItemsSourceProperty] = new Binding() { Path = "Items" },
+ [!ToolBar.OverflowModeProperty] = new Binding(){Path = nameof(ToolBarItemViewModel.OverflowMode)},
+ };
+ }
+ return new Button() { Content = "Undefined Item" };
+ }
+
+ public bool Match(object? data)
+ {
+ return data is ToolBarItemViewModel;
+ }
+}
\ No newline at end of file
diff --git a/demo/Ursa.Demo/Converters/ViewLocator.cs b/demo/Ursa.Demo/DataTemplates/ViewLocator.cs
similarity index 100%
rename from demo/Ursa.Demo/Converters/ViewLocator.cs
rename to demo/Ursa.Demo/DataTemplates/ViewLocator.cs
diff --git a/demo/Ursa.Demo/Models/MenuKeys.cs b/demo/Ursa.Demo/Models/MenuKeys.cs
index 4c707a6..1351630 100644
--- a/demo/Ursa.Demo/Models/MenuKeys.cs
+++ b/demo/Ursa.Demo/Models/MenuKeys.cs
@@ -31,5 +31,6 @@ public static class MenuKeys
public const string MenuKeyTimeline = "Timeline";
public const string MenuKeyTwoTonePathIcon = "TwoTonePathIcon";
public const string MenuKeyThemeToggler = "ThemeToggler";
+ public const string MenuKeyToolBar = "ToolBar";
}
\ No newline at end of file
diff --git a/demo/Ursa.Demo/Pages/ToolBarDemo.axaml b/demo/Ursa.Demo/Pages/ToolBarDemo.axaml
new file mode 100644
index 0000000..b00fc45
--- /dev/null
+++ b/demo/Ursa.Demo/Pages/ToolBarDemo.axaml
@@ -0,0 +1,136 @@
+
+
+
+ M13.5,15.5H10V12.5H13.5A1.5,1.5 0 0,1 15,14A1.5,1.5 0 0,1 13.5,15.5M10,6.5H13A1.5,1.5 0 0,1 14.5,8A1.5,1.5 0 0,1 13,9.5H10M15.6,10.79C16.57,10.11 17.25,9 17.25,8C17.25,5.74 15.5,4 13.25,4H7V18H14.04C16.14,18 17.75,16.3 17.75,14.21C17.75,12.69 16.89,11.39 15.6,10.79Z
+ M10,4V7H12.21L8.79,15H6V18H14V15H11.79L15.21,7H18V4H10Z
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 8
+ 16
+ 32
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/Ursa.Demo/Pages/ToolBarDemo.axaml.cs b/demo/Ursa.Demo/Pages/ToolBarDemo.axaml.cs
new file mode 100644
index 0000000..f9f0f8f
--- /dev/null
+++ b/demo/Ursa.Demo/Pages/ToolBarDemo.axaml.cs
@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Ursa.Demo.Pages;
+
+public partial class ToolBarDemo : UserControl
+{
+ public ToolBarDemo()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
index e5a996d..d072e7f 100644
--- a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
+++ b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
@@ -53,6 +53,7 @@ public class MainViewViewModel : ViewModelBase
MenuKeys.MenuKeyTimeline => new TimelineDemoViewModel(),
MenuKeys.MenuKeyTwoTonePathIcon => new TwoTonePathIconDemoViewModel(),
MenuKeys.MenuKeyThemeToggler => new ThemeTogglerDemoViewModel(),
+ MenuKeys.MenuKeyToolBar => new ToolBarDemoViewModel(),
};
}
}
\ No newline at end of file
diff --git a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
index 54e6f8b..130d16a 100644
--- a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
+++ b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
@@ -40,6 +40,7 @@ public class MenuViewModel: ViewModelBase
new() { MenuHeader = "Theme Toggler", Key = MenuKeys.MenuKeyThemeToggler },
new() { MenuHeader = "Timeline", Key = MenuKeys.MenuKeyTimeline, Status = "Updated" },
new() { MenuHeader = "TwoTonePathIcon", Key = MenuKeys.MenuKeyTwoTonePathIcon, Status = "New"},
+ new() { MenuHeader = "ToolBar", Key = MenuKeys.MenuKeyToolBar, Status = "New" }
};
}
}
\ No newline at end of file
diff --git a/demo/Ursa.Demo/ViewModels/ToolBarDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/ToolBarDemoViewModel.cs
new file mode 100644
index 0000000..d17c4db
--- /dev/null
+++ b/demo/Ursa.Demo/ViewModels/ToolBarDemoViewModel.cs
@@ -0,0 +1,83 @@
+using System.Collections.ObjectModel;
+using System.Windows.Input;
+using CommunityToolkit.Mvvm.ComponentModel;
+using CommunityToolkit.Mvvm.Input;
+using Ursa.Controls;
+
+namespace Ursa.Demo.ViewModels;
+
+public partial class ToolBarDemoViewModel: ObservableObject
+{
+ public ObservableCollection Items { get; set; }
+ public ToolBarDemoViewModel()
+ {
+ Items = new()
+ {
+ new ToolBarButtonItemViewModel() { Content = "New", OverflowMode = OverflowMode.AsNeeded},
+ new ToolBarButtonItemViewModel() { Content = "Open" },
+ new ToolBarButtonItemViewModel() { Content = "Save1" },
+ new ToolBarButtonItemViewModel() { Content = "Save2" },
+ new ToolBarSeparatorViewModel(),
+ new ToolBarButtonItemViewModel() { Content = "Save3" },
+ new ToolBarButtonItemViewModel() { Content = "Save4" },
+ new ToolBarButtonItemViewModel() { Content = "Save5" },
+ new ToolBarButtonItemViewModel() { Content = "Save6" },
+ new ToolBarButtonItemViewModel() { Content = "Save7" },
+ new ToolBarSeparatorViewModel(),
+ new ToolBarButtonItemViewModel() { Content = "Save8" },
+ new ToolBarCheckBoxItemViweModel() { Content = "Bold" },
+ new ToolBarCheckBoxItemViweModel() { Content = "Italic", OverflowMode = OverflowMode.Never},
+ new ToolBarComboBoxItemViewModel() { Content = "Font Size", Items = new (){ "10", "12", "14" } }
+ };
+ }
+}
+
+public abstract class ToolBarItemViewModel: ObservableObject
+{
+ public OverflowMode OverflowMode { get; set; }
+}
+
+public class ToolBarButtonItemViewModel: ToolBarItemViewModel
+{
+ public string Content { get; set; }
+ public ICommand Command { get; set; }
+
+ public ToolBarButtonItemViewModel()
+ {
+ Command = new AsyncRelayCommand(async () => { await MessageBox.ShowOverlayAsync(Content); });
+ }
+}
+
+public class ToolBarCheckBoxItemViweModel: ToolBarItemViewModel
+{
+ public string Content { get; set; }
+ public bool IsChecked { get; set; }
+ public ICommand Command { get; set; }
+
+ public ToolBarCheckBoxItemViweModel()
+ {
+ Command = new AsyncRelayCommand(async () => { await MessageBox.ShowOverlayAsync(Content); });
+ }
+}
+
+public class ToolBarComboBoxItemViewModel: ToolBarItemViewModel
+{
+ public string Content { get; set; }
+ public ObservableCollection Items { get; set; }
+
+ private string _selectedItem;
+ public string SelectedItem
+ {
+ get => _selectedItem;
+ set
+ {
+ SetProperty(ref _selectedItem, value);
+ MessageBox.ShowOverlayAsync(value);
+ }
+ }
+}
+
+public class ToolBarSeparatorViewModel: ToolBarItemViewModel
+{
+
+}
\ No newline at end of file
diff --git a/demo/Ursa.Demo/Views/MainWindow.axaml.cs b/demo/Ursa.Demo/Views/MainWindow.axaml.cs
index 45aa4b8..73e4f8a 100644
--- a/demo/Ursa.Demo/Views/MainWindow.axaml.cs
+++ b/demo/Ursa.Demo/Views/MainWindow.axaml.cs
@@ -1,4 +1,5 @@
using Avalonia.Controls;
+using Avalonia.Interactivity;
namespace Ursa.Demo.Views;
diff --git a/src/Ursa.Themes.Semi/Controls/ToolBar.axaml b/src/Ursa.Themes.Semi/Controls/ToolBar.axaml
new file mode 100644
index 0000000..9fe7cfb
--- /dev/null
+++ b/src/Ursa.Themes.Semi/Controls/ToolBar.axaml
@@ -0,0 +1,137 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml
index 848b1b9..fdbbda4 100644
--- a/src/Ursa.Themes.Semi/Controls/_index.axaml
+++ b/src/Ursa.Themes.Semi/Controls/_index.axaml
@@ -30,5 +30,6 @@
+
diff --git a/src/Ursa.Themes.Semi/Styles/ToolBar.axaml b/src/Ursa.Themes.Semi/Styles/ToolBar.axaml
new file mode 100644
index 0000000..1e040a8
--- /dev/null
+++ b/src/Ursa.Themes.Semi/Styles/ToolBar.axaml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ursa.Themes.Semi/Styles/_index.axaml b/src/Ursa.Themes.Semi/Styles/_index.axaml
index 8423f00..5f40b0f 100644
--- a/src/Ursa.Themes.Semi/Styles/_index.axaml
+++ b/src/Ursa.Themes.Semi/Styles/_index.axaml
@@ -5,5 +5,6 @@
+
diff --git a/src/Ursa.Themes.Semi/Themes/Shared/ToolBar.axaml b/src/Ursa.Themes.Semi/Themes/Shared/ToolBar.axaml
new file mode 100644
index 0000000..508a5dc
--- /dev/null
+++ b/src/Ursa.Themes.Semi/Themes/Shared/ToolBar.axaml
@@ -0,0 +1,6 @@
+
+
+ M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z
+ M16,12A2,2 0 0,1 18,10A2,2 0 0,1 20,12A2,2 0 0,1 18,14A2,2 0 0,1 16,12M10,12A2,2 0 0,1 12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12M4,12A2,2 0 0,1 6,10A2,2 0 0,1 8,12A2,2 0 0,1 6,14A2,2 0 0,1 4,12Z
+
diff --git a/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml b/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml
index d06590f..481acde 100644
--- a/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml
+++ b/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml
@@ -15,5 +15,6 @@
+
diff --git a/src/Ursa/Controls/ToolBar/OverflowMode.cs b/src/Ursa/Controls/ToolBar/OverflowMode.cs
new file mode 100644
index 0000000..975c7e5
--- /dev/null
+++ b/src/Ursa/Controls/ToolBar/OverflowMode.cs
@@ -0,0 +1,8 @@
+namespace Ursa.Controls;
+
+public enum OverflowMode
+{
+ AsNeeded,
+ Always,
+ Never
+}
\ No newline at end of file
diff --git a/src/Ursa/Controls/ToolBar/ToolBar.cs b/src/Ursa/Controls/ToolBar/ToolBar.cs
new file mode 100644
index 0000000..5259247
--- /dev/null
+++ b/src/Ursa/Controls/ToolBar/ToolBar.cs
@@ -0,0 +1,101 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
+using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
+using Avalonia.Layout;
+using Avalonia.Markup.Xaml.Templates;
+using Irihi.Avalonia.Shared.Helpers;
+
+namespace Ursa.Controls;
+
+[PseudoClasses(PC_Overflow)]
+[TemplatePart(PART_OverflowPanel, typeof(Panel))]
+public class ToolBar: HeaderedItemsControl
+{
+ public const string PART_OverflowPanel = "PART_OverflowPanel";
+ public const string PC_Overflow = ":overflow";
+
+ internal Panel? OverflowPanel { get; private set; }
+
+ private static readonly ITemplate DefaultTemplate =
+ new FuncTemplate(() => new ToolBarPanel() { Orientation = Orientation.Horizontal });
+
+ public static readonly StyledProperty OrientationProperty =
+ StackPanel.OrientationProperty.AddOwner();
+
+ public Orientation Orientation
+ {
+ get => GetValue(OrientationProperty);
+ set => SetValue(OrientationProperty, value);
+ }
+
+ public static readonly StyledProperty PopupPlacementProperty =
+ Popup.PlacementProperty.AddOwner();
+
+ public PlacementMode PopupPlacement
+ {
+ get => GetValue(PopupPlacementProperty);
+ set => SetValue(PopupPlacementProperty, value);
+ }
+
+ public static readonly AttachedProperty OverflowModeProperty =
+ AvaloniaProperty.RegisterAttached("OverflowMode");
+
+ public static void SetOverflowMode(Control obj, OverflowMode value) => obj.SetValue(OverflowModeProperty, value);
+ public static OverflowMode GetOverflowMode(Control obj) => obj.GetValue(OverflowModeProperty);
+
+ internal static readonly AttachedProperty IsOverflowItemProperty =
+ AvaloniaProperty.RegisterAttached("IsOverflowItem");
+
+ internal static void SetIsOverflowItem(Control obj, bool value) => obj.SetValue(IsOverflowItemProperty, value);
+ internal static bool GetIsOverflowItem(Control obj) => obj.GetValue(IsOverflowItemProperty);
+
+ private bool _hasOverflowItems;
+ internal bool HasOverflowItems
+ {
+ get => _hasOverflowItems;
+ set
+ {
+ _hasOverflowItems = value;
+ PseudoClasses.Set(PC_Overflow, value);
+ }
+ }
+
+ static ToolBar()
+ {
+ IsTabStopProperty.OverrideDefaultValue(false);
+ ItemsPanelProperty.OverrideDefaultValue(DefaultTemplate);
+ OrientationProperty.OverrideDefaultValue(Orientation.Horizontal);
+ // TODO: use helper method after merged and upgrade helper dependency.
+ IsOverflowItemProperty.Changed.AddClassHandler((o, e) =>
+ {
+ PseudolassesExtensions.Set(o.Classes, PC_Overflow, e.NewValue.Value);
+ });
+ }
+
+ protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
+ {
+ return NeedsContainer(item, out recycleKey);
+ }
+
+ protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
+ {
+ if(item is Control c)
+ {
+ return c;
+ }
+ if(ItemTemplate is not null && ItemTemplate.Match(item))
+ {
+ return ItemTemplate.Build(item)?? new ContentPresenter();
+ }
+ return new ContentPresenter();
+ }
+
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+ OverflowPanel = e.NameScope.Find(PART_OverflowPanel);
+ }
+}
\ No newline at end of file
diff --git a/src/Ursa/Controls/ToolBar/ToolBarPanel.cs b/src/Ursa/Controls/ToolBar/ToolBarPanel.cs
new file mode 100644
index 0000000..cea2ae4
--- /dev/null
+++ b/src/Ursa/Controls/ToolBar/ToolBarPanel.cs
@@ -0,0 +1,151 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Layout;
+using Avalonia.LogicalTree;
+
+namespace Ursa.Controls;
+
+public class ToolBarPanel: StackPanel
+{
+ private ToolBar? _parent;
+ private Panel? _overflowPanel;
+
+ private Panel? OverflowPanel => _overflowPanel ??= _parent?.OverflowPanel;
+ internal ToolBar? ParentToolBar => _parent ??= this.TemplatedParent as ToolBar;
+
+ static ToolBarPanel()
+ {
+ OrientationProperty.OverrideDefaultValue(Orientation.Horizontal);
+ }
+
+ protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToLogicalTree(e);
+ _parent = this.TemplatedParent as ToolBar;
+ if (_parent is null) return;
+ this[!OrientationProperty] = _parent[!OrientationProperty];
+ }
+
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ var logicalChildren = _parent?.GetLogicalChildren().OfType().ToList();
+ Size size = new Size();
+ double spacing = 0;
+ Size measureSize = availableSize;
+ bool horizontal = Orientation == Orientation.Horizontal;
+ bool hasVisibleChildren = false;
+ if (logicalChildren is null) return size;
+ for (int i = 0; i < logicalChildren.Count; i++)
+ {
+ Control control = logicalChildren[i];
+ var mode = ToolBar.GetOverflowMode(control);
+ control.Measure(measureSize);
+ if (mode == OverflowMode.Always)
+ {
+ ToolBar.SetIsOverflowItem(control, true);
+ }
+ else if (mode == OverflowMode.Never)
+ {
+ if (control.IsVisible)
+ {
+ hasVisibleChildren = true;
+ size = horizontal
+ ? size.WithWidth(size.Width + control.DesiredSize.Width + spacing)
+ .WithHeight(Math.Max(size.Height, control.DesiredSize.Height))
+ : size.WithHeight(size.Height + control.DesiredSize.Height + spacing)
+ .WithWidth(Math.Max(size.Width, control.DesiredSize.Width));
+ ToolBar.SetIsOverflowItem(control, false);
+ }
+ }
+ }
+ bool isOverflow = false;
+ for(int i = 0; i < logicalChildren.Count; i++)
+ {
+ Control control = logicalChildren[i];
+ var mode = ToolBar.GetOverflowMode(control);
+ if (mode != OverflowMode.AsNeeded) continue;
+ //Always keeps the order of display. It's very un reasonable to display the second but short control
+ //and push the first control to the overflow panel. So once a control is marked as overflow, the following
+ //controls will be marked as overflow too.
+ if (isOverflow)
+ {
+ ToolBar.SetIsOverflowItem(control, isOverflow);
+ continue;
+ }
+ bool isFit = horizontal
+ ? (size.Width + control.DesiredSize.Width <= availableSize.Width)
+ : (size.Height + control.DesiredSize.Height <= availableSize.Height);
+ if (isFit)
+ {
+ ToolBar.SetIsOverflowItem(control, false);
+ size = horizontal
+ ? size.WithWidth(size.Width + control.DesiredSize.Width + spacing)
+ .WithHeight(Math.Max(size.Height, control.DesiredSize.Height))
+ : size.WithHeight(size.Height + control.DesiredSize.Height + spacing)
+ .WithWidth(Math.Max(size.Width, control.DesiredSize.Width));
+ }
+ else
+ {
+ isOverflow = true;
+ ToolBar.SetIsOverflowItem(control, isOverflow);
+ }
+ }
+ if (hasVisibleChildren)
+ {
+ size = horizontal ? size.WithWidth(size.Width - spacing) : size.WithHeight(size.Height - spacing);
+ }
+
+ return size;
+ }
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ Children.Clear();
+ OverflowPanel?.Children.Clear();
+ var logicalChildren = _parent?.GetLogicalChildren().OfType().ToList();
+ if(logicalChildren is null) return finalSize;
+ bool overflow = false;
+ foreach (var child in logicalChildren)
+ {
+ if (ToolBar.GetIsOverflowItem(child))
+ {
+ OverflowPanel?.Children.Add(child);
+ overflow = true;
+ }
+ else
+ {
+ Children.Add(child);
+ }
+
+ if (child is ToolBarSeparator s)
+ {
+ s.IsVisible = true;
+ }
+ }
+
+ var thisLast = this.Children.LastOrDefault();
+ if (thisLast is ToolBarSeparator s2)
+ {
+ s2.IsVisible = false;
+ }
+
+ var thatFirst = OverflowPanel?.Children.FirstOrDefault();
+ if (thatFirst is ToolBarSeparator s3)
+ {
+ s3.IsVisible = false;
+ }
+ if (_parent != null) _parent.HasOverflowItems = overflow;
+ return base.ArrangeOverride(finalSize);
+ }
+
+ protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ var list = OverflowPanel?.Children.ToList();
+ if (list is not null)
+ {
+ OverflowPanel?.Children.Clear();
+ Children.AddRange(list);
+ }
+ base.OnDetachedFromVisualTree(e);
+ }
+}
\ No newline at end of file
diff --git a/src/Ursa/Controls/ToolBar/ToolBarSeparator.cs b/src/Ursa/Controls/ToolBar/ToolBarSeparator.cs
new file mode 100644
index 0000000..24a9648
--- /dev/null
+++ b/src/Ursa/Controls/ToolBar/ToolBarSeparator.cs
@@ -0,0 +1,41 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
+using Avalonia.Controls.Primitives;
+using Avalonia.Layout;
+using Avalonia.LogicalTree;
+using Irihi.Avalonia.Shared.Helpers;
+
+namespace Ursa.Controls;
+
+[PseudoClasses(PC_Vertical)]
+public class ToolBarSeparator: TemplatedControl
+{
+ public const string PC_Vertical = ":vertical";
+
+ public static readonly StyledProperty OrientationProperty =
+ ToolBar.OrientationProperty.AddOwner();
+
+ public Orientation Orientation
+ {
+ get => GetValue(OrientationProperty);
+ set => SetValue(OrientationProperty, value);
+ }
+
+ static ToolBarSeparator()
+ {
+ OrientationProperty.OverrideDefaultValue(Orientation.Horizontal);
+ OrientationProperty.Changed.AddClassHandler((separator, args) =>
+ {
+ separator.PseudoClasses.Set(PC_Vertical, args.NewValue.Value == Orientation.Vertical);
+ });
+ }
+
+ protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToLogicalTree(e);
+ var ancestor = this.GetLogicalAncestors().OfType().FirstOrDefault();
+ if (ancestor is null) return;
+ this[!OrientationProperty] = ancestor[!ToolBar.OrientationProperty];
+ }
+}
\ No newline at end of file
diff --git a/src/Ursa/Ursa.csproj b/src/Ursa/Ursa.csproj
index ec98fe6..2bd6329 100644
--- a/src/Ursa/Ursa.csproj
+++ b/src/Ursa/Ursa.csproj
@@ -17,4 +17,8 @@
+
+
+
+