Merge pull request #114 from irihitech/toolbar

Toolbar
This commit is contained in:
Dong Bin
2024-02-23 20:36:53 +08:00
committed by GitHub
22 changed files with 770 additions and 1 deletions

View File

@@ -5,6 +5,6 @@
xmlns:u-semi="https://irihi.tech/ursa/themes/semi">
<Application.Styles>
<StyleInclude Source="avares://Semi.Avalonia/Themes/Index.axaml" />
<u-semi:SemiTheme Locale="zh-CN"/>
<u-semi:SemiTheme Locale="zh-CN" />
</Application.Styles>
</Application>

View File

@@ -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;
}
}

View File

@@ -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";
}

View File

@@ -0,0 +1,136 @@
<UserControl
x:Class="Ursa.Demo.Pages.ToolBarDemo"
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:template="using:Ursa.Demo.Converters"
xmlns:u="https://irihi.tech/ursa"
xmlns:vm="using:Ursa.Demo.ViewModels"
d:DesignHeight="450"
d:DesignWidth="800"
x:CompileBindings="True"
x:DataType="vm:ToolBarDemoViewModel"
mc:Ignorable="d">
<UserControl.Resources>
<ResourceDictionary>
<StreamGeometry x:Key="BoldGlyph">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</StreamGeometry>
<StreamGeometry x:Key="ItalicGlyph">M10,4V7H12.21L8.79,15H6V18H14V15H11.79L15.21,7H18V4H10Z</StreamGeometry>
</ResourceDictionary>
</UserControl.Resources>
<Grid ColumnDefinitions="Auto, Auto, *" RowDefinitions="Auto, Auto, *">
<u:EnumSelector Name="Orientation" EnumType="Orientation" />
<u:ToolBar
DockPanel.Dock="Top"
Header="Hello World"
Orientation="{Binding #Orientation.Value}">
<Button u:ToolBar.OverflowMode="Never" Content="Button 1" />
<u:ToolBarSeparator />
<Button u:ToolBar.OverflowMode="AsNeeded" Content="Button 2" />
<Button u:ToolBar.OverflowMode="AsNeeded" Content="Button 3" />
<ToggleButton Content="Toggle" />
<u:ToolBar.Styles>
<Style Selector="u|ToolBar[Orientation=Vertical]">
<Setter Property="Grid.Row" Value="1" />
<Setter Property="Grid.Column" Value="1" />
<Setter Property="Grid.RowSpan" Value="2" />
<Setter Property="Grid.ColumnSpan" Value="1" />
</Style>
<Style Selector="u|ToolBar[Orientation=Horizontal]">
<Setter Property="Grid.Row" Value="1" />
<Setter Property="Grid.Column" Value="1" />
<Setter Property="Grid.RowSpan" Value="1" />
<Setter Property="Grid.ColumnSpan" Value="3" />
</Style>
</u:ToolBar.Styles>
</u:ToolBar>
<u:ToolBar
DockPanel.Dock="Top"
ItemsSource="{Binding Items}"
Orientation="{Binding #Orientation.Value}">
<u:ToolBar.ItemTemplate>
<template:ToolBarItemTemplateSelector />
</u:ToolBar.ItemTemplate>
<u:ToolBar.Styles>
<Style Selector="u|ToolBar[Orientation=Vertical]">
<Setter Property="Grid.Row" Value="1" />
<Setter Property="Grid.Column" Value="0" />
<Setter Property="Grid.RowSpan" Value="2" />
<Setter Property="Grid.ColumnSpan" Value="1" />
</Style>
<Style Selector="u|ToolBar[Orientation=Horizontal]">
<Setter Property="Grid.Row" Value="0" />
<Setter Property="Grid.Column" Value="1" />
<Setter Property="Grid.RowSpan" Value="1" />
<Setter Property="Grid.ColumnSpan" Value="3" />
</Style>
</u:ToolBar.Styles>
</u:ToolBar>
<Grid
Grid.Row="2"
Grid.Column="2"
RowDefinitions="Auto, *">
<u:ToolBar Margin="16">
<ToggleButton Name="bold">
<PathIcon
Width="16"
Height="16"
Data="{DynamicResource BoldGlyph}" />
</ToggleButton>
<ToggleButton Name="italic">
<PathIcon
Width="16"
Height="16"
Data="{DynamicResource ItalicGlyph}" />
</ToggleButton>
<u:ToolBarSeparator />
<StackPanel Orientation="Horizontal">
<TextBlock
Margin="8,0"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="Font Size" />
<ComboBox
Name="size"
Width="90"
SelectedIndex="0">
<x:Double>8</x:Double>
<x:Double>16</x:Double>
<x:Double>32</x:Double>
</ComboBox>
</StackPanel>
<u:ToolBar.Styles>
<Style Selector="u|ToolBar[Orientation=Horizontal]">
<Setter Property="Grid.Row" Value="2" />
<Setter Property="Grid.Column" Value="1" />
<Setter Property="Grid.RowSpan" Value="1" />
<Setter Property="Grid.ColumnSpan" Value="3" />
</Style>
</u:ToolBar.Styles>
</u:ToolBar>
<TextBlock
Grid.Row="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Classes.Bold="{Binding #bold.IsChecked}"
Classes.Italic="{Binding #italic.IsChecked}"
FontSize="{Binding #size.SelectedItem}"
Text="Hello Ursa Avalonia">
<TextBlock.Styles>
<Style Selector="TextBlock">
<Setter Property="FontWeight" Value="Regular" />
<Setter Property="FontSize" Value="8" />
<Setter Property="FontStyle" Value="Normal" />
</Style>
<Style Selector="TextBlock.Bold">
<Setter Property="FontWeight" Value="Bold" />
</Style>
<Style Selector="TextBlock.Italic">
<Setter Property="FontStyle" Value="Italic" />
</Style>
</TextBlock.Styles>
</TextBlock>
</Grid>
</Grid>
</UserControl>

View File

@@ -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();
}
}

View File

@@ -53,6 +53,7 @@ public class MainViewViewModel : ViewModelBase
MenuKeys.MenuKeyTimeline => new TimelineDemoViewModel(),
MenuKeys.MenuKeyTwoTonePathIcon => new TwoTonePathIconDemoViewModel(),
MenuKeys.MenuKeyThemeToggler => new ThemeTogglerDemoViewModel(),
MenuKeys.MenuKeyToolBar => new ToolBarDemoViewModel(),
};
}
}

View File

@@ -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" }
};
}
}

View File

@@ -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<ToolBarItemViewModel> 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<string> Items { get; set; }
private string _selectedItem;
public string SelectedItem
{
get => _selectedItem;
set
{
SetProperty(ref _selectedItem, value);
MessageBox.ShowOverlayAsync(value);
}
}
}
public class ToolBarSeparatorViewModel: ToolBarItemViewModel
{
}

View File

@@ -1,4 +1,5 @@
using Avalonia.Controls;
using Avalonia.Interactivity;
namespace Ursa.Demo.Views;

View File

@@ -0,0 +1,137 @@
<ResourceDictionary
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="https://irihi.tech/ursa">
<!-- Add Resources Here -->
<ControlTheme x:Key="ToolBarExpandToggleButton" TargetType="ToggleButton">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Template">
<ControlTemplate>
<ContentPresenter
x:Name="PART_ContentPresenter"
Padding="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Content="{TemplateBinding Content}"
ContentTemplate="{TemplateBinding ContentTemplate}"
CornerRadius="{TemplateBinding CornerRadius}"
RecognizesAccessKey="True"
TextElement.FontSize="{TemplateBinding FontSize}"
TextElement.FontWeight="{TemplateBinding FontWeight}"
UseLayoutRounding="False" />
</ControlTemplate>
</Setter>
</ControlTheme>
<ControlTheme x:Key="{x:Type u:ToolBar}" TargetType="u:ToolBar">
<Setter Property="HorizontalAlignment" Value="Left"/>
<Setter Property="VerticalAlignment" Value="Top"/>
<Setter Property="ItemsPanel">
<ItemsPanelTemplate>
<u:ToolBarPanel />
</ItemsPanelTemplate>
</Setter>
<Setter Property="Template">
<ControlTemplate TargetType="u:ToolBar">
<Border
Padding="2"
Margin="0"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
VerticalAlignment="{TemplateBinding VerticalAlignment}"
CornerRadius="4"
Theme="{DynamicResource CardBorder}">
<DockPanel LastChildFill="True">
<ContentPresenter
Name="PART_Header"
Margin="8,0"
HorizontalAlignment="Left"
VerticalAlignment="Center"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
DockPanel.Dock="Left"
Foreground="{DynamicResource SemiColorText2}"
IsVisible="{TemplateBinding Header,
Converter={x:Static ObjectConverters.IsNotNull}}" />
<Panel Name="PART_PopupButtonPanel" DockPanel.Dock="Right">
<ToggleButton
Name="button"
Padding="8,0"
VerticalAlignment="Stretch"
IsVisible="False"
Theme="{DynamicResource ToolBarExpandToggleButton}">
<PathIcon
Name="PART_Icon"
Height="12"
Foreground="{DynamicResource SemiColorText2}"
Data="{DynamicResource ToolBarHorizontalMoreGlyph}" />
</ToggleButton>
<Popup
IsLightDismissEnabled="True"
IsOpen="{Binding #button.IsChecked, Mode=TwoWay}"
Placement="{TemplateBinding PopupPlacement}"
PlacementTarget="{Binding #button}">
<Border Padding="2" Theme="{DynamicResource CardBorder}">
<StackPanel Name="{x:Static u:ToolBar.PART_OverflowPanel}" />
</Border>
</Popup>
</Panel>
<ItemsPresenter
HorizontalAlignment="Left"
VerticalAlignment="Center"
ItemsPanel="{TemplateBinding ItemsPanel}" />
</DockPanel>
</Border>
</ControlTemplate>
</Setter>
<Style Selector="^[Orientation=Horizontal]">
<Setter Property="PopupPlacement" Value="BottomEdgeAlignedLeft" />
</Style>
<Style Selector="^[Orientation=Vertical]">
<Setter Property="PopupPlacement" Value="RightEdgeAlignedTop" />
<Style Selector="^ /template/ ContentPresenter#PART_Header">
<Setter Property="DockPanel.Dock" Value="Top" />
<Setter Property="HorizontalAlignment" Value="Center" />
<Setter Property="VerticalAlignment" Value="Top" />
<Setter Property="Margin" Value="0 8" />
</Style>
<Style Selector="^ /template/ Panel#PART_PopupButtonPanel">
<Setter Property="DockPanel.Dock" Value="Bottom" />
</Style>
<Style Selector="^ /template/ ToggleButton#button">
<Setter Property="Padding" Value="0 8" />
</Style>
<Style Selector="^ /template/ PathIcon#PART_Icon">
<Setter Property="Data" Value="{DynamicResource ToolBarVerticalMoreGlyph}" />
<Setter Property="Width" Value="12" />
<Setter Property="Height" Value="{x:Static x:Double.NaN}" />
</Style>
</Style>
<Style Selector="^:overflow /template/ ToggleButton#button">
<Setter Property="IsVisible" Value="True" />
</Style>
</ControlTheme>
<ControlTheme x:Key="{x:Type u:ToolBarSeparator}" TargetType="u:ToolBarSeparator">
<Setter Property="Template">
<ControlTemplate>
<Rectangle
Name="PART_Rect"
Margin="4"
Fill="{DynamicResource SemiColorBorder}" />
</ControlTemplate>
</Setter>
<Style Selector="^ /template/ Rectangle#PART_Rect">
<Setter Property="Width" Value="1" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="HorizontalAlignment" Value="Center" />
</Style>
<Style Selector="^:vertical /template/ Rectangle#PART_Rect">
<Setter Property="Height" Value="1" />
<Setter Property="Width" Value="{x:Static x:Double.NaN}" />
<Setter Property="HorizontalAlignment" Value="Stretch" />
<Setter Property="VerticalAlignment" Value="Center" />
</Style>
</ControlTheme>
</ResourceDictionary>

View File

@@ -30,5 +30,6 @@
<ResourceInclude Source="ThemeSelector.axaml" />
<ResourceInclude Source="Timeline.axaml" />
<ResourceInclude Source="TwoTonePathIcon.axaml" />
<ResourceInclude Source="ToolBar.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

View File

@@ -0,0 +1,26 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="https://irihi.tech/ursa">
<Design.PreviewWith>
<Border Padding="20">
<!-- Add Controls for Previewer Here -->
</Border>
</Design.PreviewWith>
<Style Selector="u|ToolBar Button">
<Setter Property="Theme" Value="{DynamicResource BorderlessButton}"></Setter>
<Setter Property="FontWeight" Value="Regular" />
<Setter Property="Foreground" Value="{DynamicResource SemiColorText0}"></Setter>
</Style>
<Style Selector="u|ToolBar CheckBox">
<Setter Property="Margin" Value="8 0" />
</Style>
<Style Selector="u|ToolBar ToggleButton">
<Setter Property="FontWeight" Value="Regular" />
<Setter Property="Background" Value="Transparent"/>
<Setter Property="Foreground" Value="{DynamicResource SemiColorText0}"/>
</Style>
<Style Selector="u|ToolBar ComboBox">
<Setter Property="HorizontalAlignment" Value="Stretch"/>
</Style>
<!-- Add Styles Here -->
</Styles>

View File

@@ -5,5 +5,6 @@
</Border>
</Design.PreviewWith>
<StyleInclude Source="ButtonGroup.axaml" />
<StyleInclude Source="ToolBar.axaml"/>
<!-- Add Styles Here -->
</Styles>

View File

@@ -0,0 +1,6 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- Add Resources Here -->
<StreamGeometry x:Key="ToolBarHorizontalMoreGlyph">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</StreamGeometry>
<StreamGeometry x:Key="ToolBarVerticalMoreGlyph">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</StreamGeometry>
</ResourceDictionary>

View File

@@ -15,5 +15,6 @@
<MergeResourceInclude Source="Pagination.axaml" />
<MergeResourceInclude Source="TagInput.axaml" />
<MergeResourceInclude Source="ThemeSelector.axaml" />
<MergeResourceInclude Source="ToolBar.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>

View File

@@ -0,0 +1,8 @@
namespace Ursa.Controls;
public enum OverflowMode
{
AsNeeded,
Always,
Never
}

View File

@@ -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<Panel?> DefaultTemplate =
new FuncTemplate<Panel?>(() => new ToolBarPanel() { Orientation = Orientation.Horizontal });
public static readonly StyledProperty<Orientation> OrientationProperty =
StackPanel.OrientationProperty.AddOwner<ToolBar>();
public Orientation Orientation
{
get => GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
public static readonly StyledProperty<PlacementMode> PopupPlacementProperty =
Popup.PlacementProperty.AddOwner<ToolBar>();
public PlacementMode PopupPlacement
{
get => GetValue(PopupPlacementProperty);
set => SetValue(PopupPlacementProperty, value);
}
public static readonly AttachedProperty<OverflowMode> OverflowModeProperty =
AvaloniaProperty.RegisterAttached<ToolBar, Control, OverflowMode>("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<bool> IsOverflowItemProperty =
AvaloniaProperty.RegisterAttached<ToolBar, Control, bool>("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<ToolBar>(false);
ItemsPanelProperty.OverrideDefaultValue<ToolBar>(DefaultTemplate);
OrientationProperty.OverrideDefaultValue<ToolBar>(Orientation.Horizontal);
// TODO: use helper method after merged and upgrade helper dependency.
IsOverflowItemProperty.Changed.AddClassHandler<Control, bool>((o, e) =>
{
PseudolassesExtensions.Set(o.Classes, PC_Overflow, e.NewValue.Value);
});
}
protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
{
return NeedsContainer<Control>(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<Panel>(PART_OverflowPanel);
}
}

View File

@@ -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<ToolBarPanel>(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<Control>().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<Control>().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);
}
}

View File

@@ -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<Orientation> OrientationProperty =
ToolBar.OrientationProperty.AddOwner<ToolBarSeparator>();
public Orientation Orientation
{
get => GetValue(OrientationProperty);
set => SetValue(OrientationProperty, value);
}
static ToolBarSeparator()
{
OrientationProperty.OverrideDefaultValue<ToolBarSeparator>(Orientation.Horizontal);
OrientationProperty.Changed.AddClassHandler<ToolBarSeparator, Orientation>((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<ToolBar>().FirstOrDefault();
if (ancestor is null) return;
this[!OrientationProperty] = ancestor[!ToolBar.OrientationProperty];
}
}

View File

@@ -17,4 +17,8 @@
<PackageReference Include="Irihi.Avalonia.Shared" Version="0.1.2" />
</ItemGroup>
<ItemGroup>
<Folder Include="Controls\Panels\" />
</ItemGroup>
</Project>