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..9caceef
--- /dev/null
+++ b/demo/Ursa.Demo/DataTemplates/ToolBarItemTemplateSelector.cs
@@ -0,0 +1,48 @@
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
+using Avalonia.Data;
+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 ToolBarButtonItemViewModel vm)
+ {
+ return new Button()
+ {
+ [!ContentControl.ContentProperty] = new Binding() { Path = "Content" },
+ [!Button.CommandProperty] = new Binding() { Path = "Command" },
+ };
+ }
+ if (param is ToolBarCheckBoxItemViweModel cb)
+ {
+ return new CheckBox()
+ {
+ [!ContentControl.ContentProperty] = new Binding() { Path = "Content" },
+ [!ToggleButton.IsCheckedProperty] = new Binding() { Path = "IsChecked" },
+ [!Button.CommandProperty] = new Binding() { Path = "Command" },
+ };
+ }
+ 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" },
+ };
+ }
+ 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..61af50f
--- /dev/null
+++ b/demo/Ursa.Demo/Pages/ToolBarDemo.axaml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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..ad510d9
--- /dev/null
+++ b/demo/Ursa.Demo/ViewModels/ToolBarDemoViewModel.cs
@@ -0,0 +1,72 @@
+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" },
+ new ToolBarButtonItemViewModel() { Content = "Open" },
+ new ToolBarButtonItemViewModel() { Content = "Save" },
+ new ToolBarCheckBoxItemViweModel() { Content = "Bold" },
+ new ToolBarCheckBoxItemViweModel() { Content = "Italic" },
+ new ToolBarComboBoxItemViewModel() { Content = "Font Size", Items = new (){ "10", "12", "14" } }
+ };
+ }
+}
+
+public abstract class ToolBarItemViewModel: ObservableObject
+{
+
+}
+
+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);
+ }
+ }
+
+
+
+}
\ No newline at end of file
diff --git a/src/Ursa.Themes.Semi/Controls/ToolBar.axaml b/src/Ursa.Themes.Semi/Controls/ToolBar.axaml
new file mode 100644
index 0000000..7f1d026
--- /dev/null
+++ b/src/Ursa.Themes.Semi/Controls/ToolBar.axaml
@@ -0,0 +1,37 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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/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..0582c65
--- /dev/null
+++ b/src/Ursa/Controls/ToolBar/ToolBar.cs
@@ -0,0 +1,61 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Controls.Metadata;
+using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Primitives;
+using Avalonia.Controls.Templates;
+using Avalonia.Layout;
+
+namespace Ursa.Controls;
+
+[TemplatePart(PART_OverflowPanel, typeof(Panel))]
+public class ToolBar: HeaderedItemsControl
+{
+ public const string PART_OverflowPanel = "PART_OverflowPanel";
+
+ 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 BandProperty = AvaloniaProperty.Register(
+ nameof(Band));
+
+ public int Band
+ {
+ get => GetValue(BandProperty);
+ set => SetValue(BandProperty, value);
+ }
+
+ static ToolBar()
+ {
+ IsTabStopProperty.OverrideDefaultValue(false);
+ ItemsPanelProperty.OverrideDefaultValue(DefaultTemplate);
+ OrientationProperty.OverrideDefaultValue(Orientation.Horizontal);
+ }
+
+ 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)
+ {
+ 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/ToolBarOverflowPanel.cs b/src/Ursa/Controls/ToolBar/ToolBarOverflowPanel.cs
new file mode 100644
index 0000000..be52346
--- /dev/null
+++ b/src/Ursa/Controls/ToolBar/ToolBarOverflowPanel.cs
@@ -0,0 +1,8 @@
+using Avalonia.Controls;
+
+namespace Ursa.Controls;
+
+public class ToolBarOverflowPanel: StackPanel
+{
+
+}
\ 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..288d0e4
--- /dev/null
+++ b/src/Ursa/Controls/ToolBar/ToolBarPanel.cs
@@ -0,0 +1,34 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Layout;
+using Avalonia.LogicalTree;
+
+namespace Ursa.Controls;
+
+public class ToolBarPanel: StackPanel
+{
+ private ToolBar? _parent;
+ private Panel? _overflowPanel;
+
+ internal 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)
+ {
+ return base.MeasureOverride(availableSize);
+
+ }
+}
\ 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 @@
+
+
+
+