From dea96493dc4639de2c556da2a0cfed18cbc6aa23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Korczy=C5=84ski?= Date: Fri, 8 Mar 2024 02:13:30 +0000 Subject: [PATCH 1/2] Corrected the logic for updating IsEmpty --- src/Dock.Model/FactoryBase.Dockable.cs | 107 ++++++++++++++++++------- src/Dock.Model/FactoryBase.Init.cs | 2 +- src/Dock.Model/FactoryBase.cs | 33 +++----- 3 files changed, 92 insertions(+), 50 deletions(-) diff --git a/src/Dock.Model/FactoryBase.Dockable.cs b/src/Dock.Model/FactoryBase.Dockable.cs index e002a05d0..6b4629972 100644 --- a/src/Dock.Model/FactoryBase.Dockable.cs +++ b/src/Dock.Model/FactoryBase.Dockable.cs @@ -1,4 +1,5 @@ -using Dock.Model.Controls; +using System.Linq; +using Dock.Model.Controls; using Dock.Model.Core; namespace Dock.Model; @@ -13,8 +14,7 @@ public virtual void AddDockable(IDock dock, IDockable dockable) { InitDockable(dockable, dock); dock.VisibleDockables ??= CreateList(); - dock.VisibleDockables.Add(dockable); - dock.IsEmpty = dock.VisibleDockables.Count == 0; + AddVisibleDockable(dock, dockable); OnDockableAdded(dockable); } @@ -25,8 +25,7 @@ public virtual void InsertDockable(IDock dock, IDockable dockable, int index) { InitDockable(dockable, dock); dock.VisibleDockables ??= CreateList(); - dock.VisibleDockables.Insert(index, dockable); - dock.IsEmpty = dock.VisibleDockables.Count == 0; + InsertVisibleDockable(dock, index, dockable); OnDockableAdded(dockable); } } @@ -45,8 +44,7 @@ public virtual void RemoveDockable(IDockable dockable, bool collapse) return; } - dock.VisibleDockables.Remove(dockable); - dock.IsEmpty = dock.VisibleDockables.Count == 0; + RemoveVisibleDockable(dock, dockable); OnDockableRemoved(dockable); var indexActiveDockable = index > 0 ? index - 1 : 0; @@ -102,11 +100,9 @@ public virtual void MoveDockable(IDock dock, IDockable sourceDockable, IDockable if (sourceIndex >= 0 && targetIndex >= 0 && sourceIndex != targetIndex) { - dock.VisibleDockables.RemoveAt(sourceIndex); - dock.IsEmpty = dock.VisibleDockables.Count == 0; + RemoveVisibleDockableAt(dock, sourceIndex); OnDockableRemoved(sourceDockable); - dock.VisibleDockables.Insert(targetIndex, sourceDockable); - dock.IsEmpty = dock.VisibleDockables.Count == 0; + InsertVisibleDockable(dock, targetIndex, sourceDockable); OnDockableAdded(sourceDockable); OnDockableMoved(sourceDockable); dock.ActiveDockable = sourceDockable; @@ -177,11 +173,9 @@ public virtual void MoveDockable(IDock sourceDock, IDock targetDock, IDockable s var sourceIndex = sourceDock.VisibleDockables.IndexOf(sourceDockable); if (sourceIndex < targetIndex) { - targetDock.VisibleDockables.Insert(targetIndex + 1, sourceDockable); - targetDock.IsEmpty = targetDock.VisibleDockables.Count == 0; + InsertVisibleDockable(targetDock, targetIndex + 1, sourceDockable); OnDockableAdded(sourceDockable); - targetDock.VisibleDockables.RemoveAt(sourceIndex); - targetDock.IsEmpty = targetDock.VisibleDockables.Count == 0; + RemoveVisibleDockableAt(targetDock, sourceIndex); OnDockableRemoved(sourceDockable); OnDockableMoved(sourceDockable); } @@ -190,11 +184,9 @@ public virtual void MoveDockable(IDock sourceDock, IDock targetDock, IDockable s var removeIndex = sourceIndex + 1; if (targetDock.VisibleDockables.Count + 1 > removeIndex) { - targetDock.VisibleDockables.Insert(targetIndex, sourceDockable); - targetDock.IsEmpty = targetDock.VisibleDockables.Count == 0; + InsertVisibleDockable(targetDock, targetIndex, sourceDockable); OnDockableAdded(sourceDockable); - targetDock.VisibleDockables.RemoveAt(removeIndex); - targetDock.IsEmpty = targetDock.VisibleDockables.Count == 0; + RemoveVisibleDockableAt(targetDock, removeIndex); OnDockableRemoved(sourceDockable); OnDockableMoved(sourceDockable); } @@ -203,8 +195,7 @@ public virtual void MoveDockable(IDock sourceDock, IDock targetDock, IDockable s else { RemoveDockable(sourceDockable, true); - targetDock.VisibleDockables.Insert(targetIndex, sourceDockable); - targetDock.IsEmpty = targetDock.VisibleDockables.Count == 0; + InsertVisibleDockable(targetDock, targetIndex, sourceDockable); OnDockableAdded(sourceDockable); OnDockableMoved(sourceDockable); InitDockable(sourceDockable, targetDock); @@ -230,11 +221,9 @@ public virtual void SwapDockable(IDock dock, IDockable sourceDockable, IDockable var originalTargetDockable = dock.VisibleDockables[targetIndex]; dock.VisibleDockables[targetIndex] = originalSourceDockable; - dock.IsEmpty = dock.VisibleDockables.Count == 0; OnDockableRemoved(originalTargetDockable); OnDockableAdded(originalSourceDockable); dock.VisibleDockables[sourceIndex] = originalTargetDockable; - dock.IsEmpty = dock.VisibleDockables.Count == 0; OnDockableAdded(originalTargetDockable); OnDockableSwapped(originalSourceDockable); OnDockableSwapped(originalTargetDockable); @@ -363,8 +352,7 @@ public virtual void PinDockable(IDockable dockable) if (toolDock.VisibleDockables is not null) { - toolDock.VisibleDockables.Remove(dockable); - toolDock.IsEmpty = toolDock.VisibleDockables.Count == 0; + RemoveVisibleDockable(toolDock, dockable); OnDockableRemoved(dockable); } @@ -468,8 +456,7 @@ public virtual void PinDockable(IDockable dockable) } } - toolDock.VisibleDockables.Add(dockable); - toolDock.IsEmpty = toolDock.VisibleDockables.Count == 0; + AddVisibleDockable(toolDock, dockable); OnDockableAdded(dockable); // TODO: Handle ActiveDockable state. @@ -610,4 +597,70 @@ public virtual void CloseRightDockables(IDockable dockable) CloseDockablesRange(dock, indexOf + 1, dock.VisibleDockables.Count - 1); } + + /// + /// Adds the dockable to the visible dockables list of the dock. + /// + protected void AddVisibleDockable(IDock dock, IDockable dockable) + { + if (dock.VisibleDockables == null) + { + dock.VisibleDockables = CreateList(); + } + dock.VisibleDockables.Add(dockable); + UpdateIsEmpty(dock); + } + + /// + /// Inserts the dockable to the visible dockables list of the dock at the specified index. + /// + protected void InsertVisibleDockable(IDock dock, int index, IDockable dockable) + { + if (dock.VisibleDockables == null) + { + dock.VisibleDockables = CreateList(); + } + dock.VisibleDockables.Insert(index, dockable); + UpdateIsEmpty(dock); + } + + /// + /// Removes the dockable from the visible dockables list of the dock. + /// + protected void RemoveVisibleDockable(IDock dock, IDockable dockable) + { + if (dock.VisibleDockables != null) + { + dock.VisibleDockables.Remove(dockable); + UpdateIsEmpty(dock); + } + } + + /// + /// Removes the dockable at the specified index from the visible dockables list of the dock. + /// + protected void RemoveVisibleDockableAt(IDock dock, int index) + { + if (dock.VisibleDockables != null) + { + dock.VisibleDockables.RemoveAt(index); + UpdateIsEmpty(dock); + } + } + + private void UpdateIsEmpty(IDock dock) + { + bool oldIsEmpty = dock.IsEmpty; + + var newIsEmpty = dock.VisibleDockables == null + || dock.VisibleDockables?.Count == 0 + || dock.VisibleDockables!.All(x => x is IDock { IsEmpty: true } or IProportionalDockSplitter); + + if (oldIsEmpty != newIsEmpty) + { + dock.IsEmpty = newIsEmpty; + if (dock.Owner is IDock parent) + UpdateIsEmpty(parent); + } + } } diff --git a/src/Dock.Model/FactoryBase.Init.cs b/src/Dock.Model/FactoryBase.Init.cs index 634ceb812..9b0ec5096 100644 --- a/src/Dock.Model/FactoryBase.Init.cs +++ b/src/Dock.Model/FactoryBase.Init.cs @@ -54,7 +54,7 @@ public virtual void InitDockable(IDockable dockable, IDockable? owner) InitDockable(child, dockable); } - dock.IsEmpty = dock.VisibleDockables.Count == 0; + UpdateIsEmpty(dock); } } diff --git a/src/Dock.Model/FactoryBase.cs b/src/Dock.Model/FactoryBase.cs index 15fc26aa0..442495695 100644 --- a/src/Dock.Model/FactoryBase.cs +++ b/src/Dock.Model/FactoryBase.cs @@ -125,8 +125,7 @@ public virtual IDock CreateSplitLayout(IDock dock, IDockable dockable, DockOpera split.VisibleDockables = CreateList(); if (split.VisibleDockables is not null) { - split.VisibleDockables.Add(dockable); - split.IsEmpty = split.VisibleDockables.Count == 0; + AddVisibleDockable(split, dockable); OnDockableAdded(dockable); split.ActiveDockable = dockable; } @@ -166,8 +165,7 @@ public virtual IDock CreateSplitLayout(IDock dock, IDockable dockable, DockOpera { if (layout.VisibleDockables is not null) { - layout.VisibleDockables.Add(split); - layout.IsEmpty = layout.VisibleDockables.Count == 0; + AddVisibleDockable(layout, split); OnDockableAdded(split); layout.ActiveDockable = split; } @@ -179,8 +177,7 @@ public virtual IDock CreateSplitLayout(IDock dock, IDockable dockable, DockOpera { if (layout.VisibleDockables is not null) { - layout.VisibleDockables.Add(dock); - layout.IsEmpty = layout.VisibleDockables.Count == 0; + AddVisibleDockable(layout, dock); OnDockableAdded(dock); layout.ActiveDockable = dock; } @@ -189,8 +186,7 @@ public virtual IDock CreateSplitLayout(IDock dock, IDockable dockable, DockOpera } } - layout.VisibleDockables?.Add(splitter); - layout.IsEmpty = layout.VisibleDockables?.Count == 0; + AddVisibleDockable(layout, splitter); OnDockableAdded(splitter); switch (operation) @@ -200,8 +196,7 @@ public virtual IDock CreateSplitLayout(IDock dock, IDockable dockable, DockOpera { if (layout.VisibleDockables is not null) { - layout.VisibleDockables.Add(dock); - layout.IsEmpty = layout.VisibleDockables.Count == 0; + AddVisibleDockable(layout, dock); OnDockableAdded(dock); layout.ActiveDockable = dock; } @@ -213,8 +208,7 @@ public virtual IDock CreateSplitLayout(IDock dock, IDockable dockable, DockOpera { if (layout.VisibleDockables is not null) { - layout.VisibleDockables.Add(split); - layout.IsEmpty = layout.VisibleDockables.Count == 0; + AddVisibleDockable(layout, split); OnDockableAdded(split); layout.ActiveDockable = split; } @@ -242,11 +236,9 @@ public virtual void SplitToDock(IDock dock, IDockable dockable, DockOperation op if (index >= 0) { var layout = CreateSplitLayout(dock, dockable, operation); - ownerDock.VisibleDockables.RemoveAt(index); - ownerDock.IsEmpty = ownerDock.VisibleDockables.Count == 0; + RemoveVisibleDockableAt(ownerDock, index); OnDockableRemoved(dockable); - ownerDock.VisibleDockables.Insert(index, layout); - layout.IsEmpty = layout.VisibleDockables?.Count == 0; + InsertVisibleDockable(ownerDock, index, layout); OnDockableAdded(dockable); InitDockable(layout, ownerDock); ownerDock.ActiveDockable = layout; @@ -276,8 +268,7 @@ public virtual void SplitToDock(IDock dock, IDockable dockable, DockOperation op dock.VisibleDockables = CreateList(); if (dock.VisibleDockables is not null) { - dock.VisibleDockables.Add(dockable); - dock.IsEmpty = dock.VisibleDockables.Count == 0; + AddVisibleDockable(dock, dockable); OnDockableAdded(dockable); dock.ActiveDockable = dockable; } @@ -309,8 +300,7 @@ public virtual void SplitToDock(IDock dock, IDockable dockable, DockOperation op } if (dock.VisibleDockables is not null) { - dock.VisibleDockables.Add(dockable); - dock.IsEmpty = dock.VisibleDockables.Count == 0; + AddVisibleDockable(dock, dockable); OnDockableAdded(dockable); dock.ActiveDockable = dockable; } @@ -359,8 +349,7 @@ public virtual void SplitToDock(IDock dock, IDockable dockable, DockOperation op root.VisibleDockables = CreateList(); if (root.VisibleDockables is not null && target is not null) { - root.VisibleDockables.Add(target); - root.IsEmpty = root.VisibleDockables.Count == 0; + AddVisibleDockable(root, target); OnDockableAdded(target); root.ActiveDockable = target; root.DefaultDockable = target; From dc2890757bdee05c35e74af07fc64eccb64ab388 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20Korczy=C5=84ski?= Date: Sun, 10 Mar 2024 15:52:35 +0000 Subject: [PATCH 2/2] Auto hide pinned tool docks --- samples/DockMvvmSample/Views/MainWindow.axaml | 34 +++-- samples/DockXamlSample/MainWindow.axaml | 34 +++-- .../Controls/DockControl.axaml.cs | 15 ++ .../Controls/PinnedDockControl.axaml | 40 +++++ .../Controls/PinnedDockControl.axaml.cs | 91 ++++++++++++ .../Controls/RootDockControl.axaml | 5 +- .../Controls/RootDockControl.axaml.cs | 16 ++ .../Controls/ToolChromeControl.axaml | 139 ++++++++++++------ .../Controls/ToolChromeControl.axaml.cs | 79 +++++++++- .../Controls/ToolDockControl.axaml | 1 + .../Controls/ToolPinItemControl.axaml | 4 +- .../Controls/ToolTabStripItem.axaml | 16 +- .../Converters/EitherNotNullConverter.cs | 29 ++++ .../Converters/IsMaximizedConverter.cs | 34 +++++ .../Internal/DockControlState.cs | 10 +- .../Themes/DockFluentTheme.axaml | 1 + .../Themes/DockSimpleTheme.axaml | 1 + src/Dock.Model.Avalonia/Controls/RootDock.cs | 17 +++ src/Dock.Model.Avalonia/Core/DockableBase.cs | 17 +++ src/Dock.Model.Mvvm/Controls/RootDock.cs | 9 ++ src/Dock.Model.Mvvm/Core/DockableBase.cs | 9 ++ .../Controls/RootDock.cs | 9 ++ .../Core/DockableBase.cs | 9 ++ src/Dock.Model/Controls/IRootDock.cs | 5 + .../Core/DockOperationExtensions.cs | 16 ++ src/Dock.Model/Core/IDockable.cs | 5 + src/Dock.Model/Core/IFactory.cs | 31 +++- src/Dock.Model/DockManager.cs | 1 + src/Dock.Model/FactoryBase.Dockable.cs | 110 +++++++++++++- src/Dock.Model/FactoryBase.Locator.cs | 4 +- 30 files changed, 701 insertions(+), 90 deletions(-) create mode 100644 src/Dock.Avalonia/Controls/PinnedDockControl.axaml create mode 100644 src/Dock.Avalonia/Controls/PinnedDockControl.axaml.cs create mode 100644 src/Dock.Avalonia/Converters/EitherNotNullConverter.cs create mode 100644 src/Dock.Avalonia/Converters/IsMaximizedConverter.cs create mode 100644 src/Dock.Model/Core/DockOperationExtensions.cs diff --git a/samples/DockMvvmSample/Views/MainWindow.axaml b/samples/DockMvvmSample/Views/MainWindow.axaml index be7c99312..b358b2328 100644 --- a/samples/DockMvvmSample/Views/MainWindow.axaml +++ b/samples/DockMvvmSample/Views/MainWindow.axaml @@ -20,22 +20,36 @@ ExtendClientAreaToDecorationsHint="True" ExtendClientAreaChromeHints="PreferSystemChrome" ids:DockProperties.IsDragEnabled="True" ids:DockProperties.IsDropEnabled="True"> + + + - - - - - + - + + + + + diff --git a/samples/DockXamlSample/MainWindow.axaml b/samples/DockXamlSample/MainWindow.axaml index 43e33d2f4..6e1d1f45e 100644 --- a/samples/DockXamlSample/MainWindow.axaml +++ b/samples/DockXamlSample/MainWindow.axaml @@ -16,19 +16,33 @@ Title="Dock Avalonia Demo" Width="800" Height="600" ExtendClientAreaToDecorationsHint="True" ExtendClientAreaChromeHints="PreferSystemChrome"> + + + - - - - - + - + + + + + diff --git a/src/Dock.Avalonia/Controls/DockControl.axaml.cs b/src/Dock.Avalonia/Controls/DockControl.axaml.cs index 07f6b8275..4d45cb25d 100644 --- a/src/Dock.Avalonia/Controls/DockControl.axaml.cs +++ b/src/Dock.Avalonia/Controls/DockControl.axaml.cs @@ -52,6 +52,12 @@ public class DockControl : TemplatedControl, IDockControl public static readonly StyledProperty FactoryProperty = AvaloniaProperty.Register(nameof(Factory)); + /// + /// Defines the property. + /// + public static readonly StyledProperty IsDraggingDockProperty = + AvaloniaProperty.Register(nameof(IsDraggingDock)); + /// public IDockManager DockManager => _dockManager; @@ -94,6 +100,15 @@ public IFactory? Factory set => SetValue(FactoryProperty, value); } + /// + /// Gets or sets whether any dock is being dragged. + /// + public bool IsDraggingDock + { + get => GetValue(IsDraggingDockProperty); + set => SetValue(IsDraggingDockProperty, value); + } + /// /// Initialize the new instance of the . /// diff --git a/src/Dock.Avalonia/Controls/PinnedDockControl.axaml b/src/Dock.Avalonia/Controls/PinnedDockControl.axaml new file mode 100644 index 000000000..de2a1b6db --- /dev/null +++ b/src/Dock.Avalonia/Controls/PinnedDockControl.axaml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Dock.Avalonia/Controls/PinnedDockControl.axaml.cs b/src/Dock.Avalonia/Controls/PinnedDockControl.axaml.cs new file mode 100644 index 000000000..cc90832b1 --- /dev/null +++ b/src/Dock.Avalonia/Controls/PinnedDockControl.axaml.cs @@ -0,0 +1,91 @@ +using System; +using Avalonia; +using Avalonia.Controls; +using Avalonia.Controls.Metadata; +using Avalonia.Controls.Primitives; +using Dock.Model.Core; + +namespace Dock.Avalonia.Controls; + +/// +/// Interaction logic for xaml. +/// +[TemplatePart("PART_PinnedDock", typeof(ContentControl)/*, IsRequired = true*/)] +[TemplatePart("PART_PinnedDockGrid", typeof(Grid)/*, IsRequired = true*/)] +public class PinnedDockControl : TemplatedControl +{ + /// + /// Define the property. + /// + public static readonly StyledProperty PinnedDockAlignmentProperty = AvaloniaProperty.Register(nameof(PinnedDockAlignment)); + + /// + /// Gets or sets pinned dock alignment + /// + public Alignment PinnedDockAlignment + { + get => GetValue(PinnedDockAlignmentProperty); + set => SetValue(PinnedDockAlignmentProperty, value); + } + + private Grid? _pinnedDockGrid; + private ContentControl? _pinnedDock; + + static PinnedDockControl() + { + PinnedDockAlignmentProperty.Changed.AddClassHandler((control, e) => control.UpdateGrid()); + } + + private void UpdateGrid() + { + if (_pinnedDockGrid == null || _pinnedDock == null) + return; + + _pinnedDockGrid.RowDefinitions.Clear(); + _pinnedDockGrid.ColumnDefinitions.Clear(); + switch (PinnedDockAlignment) + { + case Alignment.Unset: + case Alignment.Left: + _pinnedDockGrid.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Auto) { MinWidth = 50 }); + _pinnedDockGrid.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Auto)); + _pinnedDockGrid.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Star) { MinWidth = 50 }); + Grid.SetColumn(_pinnedDock, 0); + Grid.SetRow(_pinnedDock, 0); + break; + case Alignment.Bottom: + _pinnedDockGrid.RowDefinitions.Add(new RowDefinition(GridLength.Star) { MinHeight = 50 }); + _pinnedDockGrid.RowDefinitions.Add(new RowDefinition(GridLength.Auto)); + _pinnedDockGrid.RowDefinitions.Add(new RowDefinition(GridLength.Auto) { MinHeight = 50 }); + Grid.SetColumn(_pinnedDock, 0); + Grid.SetRow(_pinnedDock, 2); + break; + case Alignment.Right: + _pinnedDockGrid.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Star) { MinWidth = 50 }); + _pinnedDockGrid.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Auto)); + _pinnedDockGrid.ColumnDefinitions.Add(new ColumnDefinition(GridLength.Auto) { MinWidth = 50 }); + Grid.SetColumn(_pinnedDock, 2); + Grid.SetRow(_pinnedDock, 0); + break; + case Alignment.Top: + _pinnedDockGrid.RowDefinitions.Add(new RowDefinition(GridLength.Auto) { MinHeight = 50 }); + _pinnedDockGrid.RowDefinitions.Add(new RowDefinition(GridLength.Auto)); + _pinnedDockGrid.RowDefinitions.Add(new RowDefinition(GridLength.Star) { MinHeight = 50 }); + Grid.SetColumn(_pinnedDock, 1); + Grid.SetRow(_pinnedDock, 0); + break; + default: + throw new ArgumentOutOfRangeException(); + } + } + + /// + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + _pinnedDockGrid = e.NameScope.Get("PART_PinnedDockGrid"); + _pinnedDock = e.NameScope.Get("PART_PinnedDock"); + UpdateGrid(); + } +} + diff --git a/src/Dock.Avalonia/Controls/RootDockControl.axaml b/src/Dock.Avalonia/Controls/RootDockControl.axaml index a3908a24e..5a5605cc0 100644 --- a/src/Dock.Avalonia/Controls/RootDockControl.axaml +++ b/src/Dock.Avalonia/Controls/RootDockControl.axaml @@ -30,7 +30,10 @@ Orientation="Horizontal" Items="{Binding BottomPinnedDockables}" IsVisible="{Binding !!BottomPinnedDockables.Count}" /> - + + + + diff --git a/src/Dock.Avalonia/Controls/RootDockControl.axaml.cs b/src/Dock.Avalonia/Controls/RootDockControl.axaml.cs index 71558223f..159cc76ca 100644 --- a/src/Dock.Avalonia/Controls/RootDockControl.axaml.cs +++ b/src/Dock.Avalonia/Controls/RootDockControl.axaml.cs @@ -1,10 +1,26 @@ +using Avalonia.Controls; +using Avalonia.Controls.Metadata; using Avalonia.Controls.Primitives; +using Avalonia.Interactivity; +using Dock.Model.Controls; namespace Dock.Avalonia.Controls; /// /// Interaction logic for xaml. /// +[TemplatePart("PART_MainContent", typeof(ContentControl)/*, IsRequired = true*/)] public class RootDockControl : TemplatedControl { + /// + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + var mainContent = e.NameScope.Get("PART_MainContent"); + mainContent.AddHandler(PointerPressedEvent, (_, _) => + { + if (DataContext is IRootDock rootDock) + rootDock.Factory?.HidePreviewingDockables(rootDock); + }, RoutingStrategies.Tunnel); + } } diff --git a/src/Dock.Avalonia/Controls/ToolChromeControl.axaml b/src/Dock.Avalonia/Controls/ToolChromeControl.axaml index c65511a89..f871bceb5 100644 --- a/src/Dock.Avalonia/Controls/ToolChromeControl.axaml +++ b/src/Dock.Avalonia/Controls/ToolChromeControl.axaml @@ -6,29 +6,51 @@ - + - + + + + + + + + - - + + + + + + + + + + + + + @@ -43,7 +65,7 @@ @@ -51,24 +73,45 @@ - - + + + + + + @@ -162,37 +205,39 @@ - - - + + + + + + + + + diff --git a/src/Dock.Avalonia/Controls/ToolChromeControl.axaml.cs b/src/Dock.Avalonia/Controls/ToolChromeControl.axaml.cs index 8431d4389..1b86f3534 100644 --- a/src/Dock.Avalonia/Controls/ToolChromeControl.axaml.cs +++ b/src/Dock.Avalonia/Controls/ToolChromeControl.axaml.cs @@ -12,7 +12,7 @@ namespace Dock.Avalonia.Controls; /// /// Dock tool chrome content control. /// -[PseudoClasses(":floating", ":active")] +[PseudoClasses(":floating", ":active", ":pinned", ":maximized")] public class ToolChromeControl : ContentControl { /// @@ -27,12 +27,57 @@ public class ToolChromeControl : ContentControl public static readonly StyledProperty IsActiveProperty = AvaloniaProperty.Register(nameof(IsActive)); + /// + /// Define the property. + /// + public static readonly StyledProperty IsPinnedProperty = + AvaloniaProperty.Register(nameof(IsPinned)); + + /// + /// Define the property. + /// + public static readonly StyledProperty IsFloatingProperty = + AvaloniaProperty.Register(nameof(IsFloating)); + + /// + /// Define the property. + /// + public static readonly StyledProperty IsMaximizedProperty = + AvaloniaProperty.Register(nameof(IsMaximized)); + + /// + /// Gets or sets is pinned + /// + public bool IsPinned + { + get => GetValue(IsPinnedProperty); + set => SetValue(IsPinnedProperty, value); + } + + /// + /// Gets or sets is floating + /// + public bool IsFloating + { + get => GetValue(IsFloatingProperty); + set => SetValue(IsFloatingProperty, value); + } + + /// + /// Gets or sets is maximized + /// + public bool IsMaximized + { + get => GetValue(IsMaximizedProperty); + set => SetValue(IsMaximizedProperty, value); + } + /// /// Initialize the new instance of the . /// public ToolChromeControl() { - UpdatePseudoClasses(IsActive); + UpdatePseudoClasses(); } /// @@ -89,7 +134,21 @@ protected override void OnApplyTemplate(TemplateAppliedEventArgs e) window.AttachGrip(this); - PseudoClasses.Set(":floating", true); + SetCurrentValue(IsFloatingProperty, true); + } + + var maximizeRestoreButton = e.NameScope.Get +public class EitherNotNullConverter : IMultiValueConverter +{ + /// + /// Gets instance. + /// + public static readonly EitherNotNullConverter Instance = new EitherNotNullConverter(); + + /// + public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) + { + foreach (var value in values) + { + if (value != null) + return value; + } + + return values; + } +} diff --git a/src/Dock.Avalonia/Converters/IsMaximizedConverter.cs b/src/Dock.Avalonia/Converters/IsMaximizedConverter.cs new file mode 100644 index 000000000..99c17fd2b --- /dev/null +++ b/src/Dock.Avalonia/Converters/IsMaximizedConverter.cs @@ -0,0 +1,34 @@ +using System; +using System.Globalization; +using Avalonia.Controls; +using Avalonia.Data.Converters; + +namespace Dock.Avalonia.Converters; + +/// +/// Converts WindowState to bool indicating if the window is maximized. +/// +public class IsMaximizedConverter : IValueConverter +{ + /// + /// Gets instance. + /// + public static IsMaximizedConverter Instance { get; } = new(); + + /// + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + if (value is WindowState windowState) + { + return windowState == WindowState.Maximized; + } + + return false; + } + + /// + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } +} diff --git a/src/Dock.Avalonia/Internal/DockControlState.cs b/src/Dock.Avalonia/Internal/DockControlState.cs index b2e3abec0..cc0ccfad8 100644 --- a/src/Dock.Avalonia/Internal/DockControlState.cs +++ b/src/Dock.Avalonia/Internal/DockControlState.cs @@ -139,6 +139,11 @@ private void Execute(Point point, DockOperation operation, DragAction dragAction if (_state.DragControl.DataContext is IDockable sourceDockable && _state.DropControl.DataContext is IDockable targetDockable) { + if (sourceDockable is IDock dock) + sourceDockable = dock.ActiveDockable; + if (sourceDockable == null) + return; + DockManager.Position = DockHelpers.ToDockPoint(point); if (relativeTo.GetVisualRoot() is null) @@ -167,7 +172,7 @@ private static bool IsMinimumDragDistance(Vector diff) /// The input drag action. /// The active dock control. /// The dock controls. - public void Process(Point point, Vector delta, EventType eventType, DragAction dragAction, Visual activeDockControl, IList dockControls) + public void Process(Point point, Vector delta, EventType eventType, DragAction dragAction, DockControl activeDockControl, IList dockControls) { if (activeDockControl is not { } inputActiveDockControl) { @@ -187,6 +192,7 @@ public void Process(Point point, Vector delta, EventType eventType, DragAction d break; } _state.Start(dragControl, point); + activeDockControl.IsDraggingDock = true; } break; } @@ -211,6 +217,7 @@ public void Process(Point point, Vector delta, EventType eventType, DragAction d } Leave(); _state.End(); + activeDockControl.IsDraggingDock = false; break; } case EventType.Moved: @@ -343,6 +350,7 @@ public void Process(Point point, Vector delta, EventType eventType, DragAction d { Leave(); _state.End(); + activeDockControl.IsDraggingDock = false; break; } case EventType.WheelChanged: diff --git a/src/Dock.Avalonia/Themes/DockFluentTheme.axaml b/src/Dock.Avalonia/Themes/DockFluentTheme.axaml index 22a220cc6..e8417caf5 100644 --- a/src/Dock.Avalonia/Themes/DockFluentTheme.axaml +++ b/src/Dock.Avalonia/Themes/DockFluentTheme.axaml @@ -18,6 +18,7 @@ + diff --git a/src/Dock.Avalonia/Themes/DockSimpleTheme.axaml b/src/Dock.Avalonia/Themes/DockSimpleTheme.axaml index 12c463ea0..ae9a5058a 100644 --- a/src/Dock.Avalonia/Themes/DockSimpleTheme.axaml +++ b/src/Dock.Avalonia/Themes/DockSimpleTheme.axaml @@ -18,6 +18,7 @@ + diff --git a/src/Dock.Model.Avalonia/Controls/RootDock.cs b/src/Dock.Model.Avalonia/Controls/RootDock.cs index bb287c761..a81af8c9f 100644 --- a/src/Dock.Model.Avalonia/Controls/RootDock.cs +++ b/src/Dock.Model.Avalonia/Controls/RootDock.cs @@ -45,6 +45,14 @@ public class RootDock : DockBase, IRootDock nameof(LeftPinnedDockables), o => o.LeftPinnedDockables, (o, v) => o.LeftPinnedDockables = v); + /// + /// Defines the property. + /// + public static readonly DirectProperty PinnedDockProperty = + AvaloniaProperty.RegisterDirect( + nameof(PinnedDock), o => o.PinnedDock, + (o, v) => o.PinnedDock = v); + /// /// Defines the property. /// @@ -93,6 +101,7 @@ public class RootDock : DockBase, IRootDock private IList? _rightPinnedDockables; private IList? _topPinnedDockables; private IList? _bottomPinnedDockables; + private IToolDock? _pinnedDock; private IDockWindow? _window; private IList? _windows; @@ -139,6 +148,14 @@ public IList? LeftPinnedDockables set => SetAndRaise(LeftPinnedDockablesProperty, ref _leftPinnedDockables, value); } + /// + [JsonIgnore] + public IToolDock? PinnedDock + { + get => _pinnedDock; + set => SetAndRaise(PinnedDockProperty, ref _pinnedDock, value); + } + /// [DataMember(IsRequired = false, EmitDefaultValue = true)] [JsonPropertyName("RightPinnedDockables")] diff --git a/src/Dock.Model.Avalonia/Core/DockableBase.cs b/src/Dock.Model.Avalonia/Core/DockableBase.cs index 10f1f0c61..d04cf992b 100644 --- a/src/Dock.Model.Avalonia/Core/DockableBase.cs +++ b/src/Dock.Model.Avalonia/Core/DockableBase.cs @@ -48,6 +48,12 @@ public abstract class DockableBase : StyledElement, IDockable public static readonly DirectProperty OwnerProperty = AvaloniaProperty.RegisterDirect(nameof(Owner), o => o.Owner, (o, v) => o.Owner = v); + /// + /// Defines the property. + /// + public static readonly DirectProperty OriginalOwnerProperty = + AvaloniaProperty.RegisterDirect(nameof(OriginalOwner), o => o.OriginalOwner, (o, v) => o.OriginalOwner = v); + /// /// Defines the property. /// @@ -77,6 +83,7 @@ public abstract class DockableBase : StyledElement, IDockable private string _title = string.Empty; private object? _context; private IDockable? _owner; + private IDockable? _originalOwner; private IFactory? _factory; private bool _canClose = true; private bool _canPin = true; @@ -127,6 +134,16 @@ public IDockable? Owner set => SetAndRaise(OwnerProperty, ref _owner, value); } + /// + [ResolveByName] + [IgnoreDataMember] + [JsonIgnore] + public IDockable? OriginalOwner + { + get => _originalOwner; + set => SetAndRaise(OriginalOwnerProperty, ref _originalOwner, value); + } + /// [IgnoreDataMember] [JsonIgnore] diff --git a/src/Dock.Model.Mvvm/Controls/RootDock.cs b/src/Dock.Model.Mvvm/Controls/RootDock.cs index 74d2ec9d9..474ff9f88 100644 --- a/src/Dock.Model.Mvvm/Controls/RootDock.cs +++ b/src/Dock.Model.Mvvm/Controls/RootDock.cs @@ -22,6 +22,7 @@ public class RootDock : DockBase, IRootDock private IList? _bottomPinnedDockables; private IDockWindow? _window; private IList? _windows; + private IToolDock? _pinnedDock; /// /// Initializes new instance of the class. @@ -32,6 +33,14 @@ public RootDock() ExitWindows = new RelayCommand(() => _navigateAdapter.ExitWindows()); } + /// + [DataMember(IsRequired = false, EmitDefaultValue = true)] + public IToolDock? PinnedDock + { + get => _pinnedDock; + set => SetProperty(ref _pinnedDock, value); + } + /// [DataMember(IsRequired = false, EmitDefaultValue = true)] public bool IsFocusableRoot diff --git a/src/Dock.Model.Mvvm/Core/DockableBase.cs b/src/Dock.Model.Mvvm/Core/DockableBase.cs index 6289bd04d..f67e4ef02 100644 --- a/src/Dock.Model.Mvvm/Core/DockableBase.cs +++ b/src/Dock.Model.Mvvm/Core/DockableBase.cs @@ -16,6 +16,7 @@ public abstract class DockableBase : ObservableObject, IDockable private string _title = string.Empty; private object? _context; private IDockable? _owner; + private IDockable? _originalOwner; private IFactory? _factory; private bool _canClose = true; private bool _canPin = true; @@ -61,6 +62,14 @@ public IDockable? Owner set => SetProperty(ref _owner, value); } + /// + [IgnoreDataMember] + public IDockable? OriginalOwner + { + get => _originalOwner; + set => SetProperty(ref _originalOwner, value); + } + /// [IgnoreDataMember] public IFactory? Factory diff --git a/src/Dock.Model.ReactiveUI/Controls/RootDock.cs b/src/Dock.Model.ReactiveUI/Controls/RootDock.cs index 32c2b92cd..add74922e 100644 --- a/src/Dock.Model.ReactiveUI/Controls/RootDock.cs +++ b/src/Dock.Model.ReactiveUI/Controls/RootDock.cs @@ -20,6 +20,7 @@ public class RootDock : DockBase, IRootDock private IList? _rightPinnedDockables; private IList? _topPinnedDockables; private IList? _bottomPinnedDockables; + private IToolDock? _pinnedDock; private IDockWindow? _window; private IList? _windows; @@ -80,6 +81,14 @@ public IList? BottomPinnedDockables set => this.RaiseAndSetIfChanged(ref _bottomPinnedDockables, value); } + /// + [DataMember(IsRequired = false, EmitDefaultValue = true)] + public IToolDock? PinnedDock + { + get => _pinnedDock; + set => this.RaiseAndSetIfChanged(ref _pinnedDock, value); + } + /// [DataMember(IsRequired = false, EmitDefaultValue = true)] public IDockWindow? Window diff --git a/src/Dock.Model.ReactiveUI/Core/DockableBase.cs b/src/Dock.Model.ReactiveUI/Core/DockableBase.cs index 1b487660f..966145fe3 100644 --- a/src/Dock.Model.ReactiveUI/Core/DockableBase.cs +++ b/src/Dock.Model.ReactiveUI/Core/DockableBase.cs @@ -16,6 +16,7 @@ public abstract class DockableBase : ReactiveObject, IDockable private string _title = string.Empty; private object? _context; private IDockable? _owner; + private IDockable? _originalOwner; private IFactory? _factory; private bool _canClose = true; private bool _canPin = true; @@ -61,6 +62,14 @@ public IDockable? Owner set => this.RaiseAndSetIfChanged(ref _owner, value); } + /// + [IgnoreDataMember] + public IDockable? OriginalOwner + { + get => _originalOwner; + set => this.RaiseAndSetIfChanged(ref _originalOwner, value); + } + /// [IgnoreDataMember] public IFactory? Factory diff --git a/src/Dock.Model/Controls/IRootDock.cs b/src/Dock.Model/Controls/IRootDock.cs index 0042484f8..76df78a3f 100644 --- a/src/Dock.Model/Controls/IRootDock.cs +++ b/src/Dock.Model/Controls/IRootDock.cs @@ -40,6 +40,11 @@ public interface IRootDock : IDock /// IList? BottomPinnedDockables { get; set; } + /// + /// Gets or sets pinned tool dock. + /// + IToolDock? PinnedDock { get; set; } + /// /// Gets or sets owner window. /// diff --git a/src/Dock.Model/Core/DockOperationExtensions.cs b/src/Dock.Model/Core/DockOperationExtensions.cs new file mode 100644 index 000000000..99ccc900c --- /dev/null +++ b/src/Dock.Model/Core/DockOperationExtensions.cs @@ -0,0 +1,16 @@ +namespace Dock.Model.Core; + +internal static class DockOperationExtensions +{ + public static Alignment ToAlignment(this DockOperation operation) + { + return operation switch + { + DockOperation.Left => Alignment.Left, + DockOperation.Bottom => Alignment.Bottom, + DockOperation.Right => Alignment.Right, + DockOperation.Top => Alignment.Top, + _ => Alignment.Unset + }; + } +} diff --git a/src/Dock.Model/Core/IDockable.cs b/src/Dock.Model/Core/IDockable.cs index 3894bda62..e6e30841f 100644 --- a/src/Dock.Model/Core/IDockable.cs +++ b/src/Dock.Model/Core/IDockable.cs @@ -26,6 +26,11 @@ public interface IDockable /// IDockable? Owner { get; set; } + /// + /// Gets or sets dockable original owner. + /// + IDockable? OriginalOwner { get; set; } + /// /// Gets or sets dockable factory. /// diff --git a/src/Dock.Model/Core/IFactory.cs b/src/Dock.Model/Core/IFactory.cs index 94cc856da..f9d319b98 100644 --- a/src/Dock.Model/Core/IFactory.cs +++ b/src/Dock.Model/Core/IFactory.cs @@ -184,7 +184,7 @@ public partial interface IFactory /// The dockable to find root for. /// The predicate to filter root docks. /// The root dockable instance or null if root dockable was not found. - IRootDock? FindRoot(IDockable dockable, Func predicate); + IRootDock? FindRoot(IDockable dockable, Func? predicate = null); /// /// Searches dock for dockable. @@ -266,11 +266,36 @@ public partial interface IFactory void SwapDockable(IDock sourceDock, IDock targetDock, IDockable sourceDockable, IDockable targetDockable); /// - /// Pins dockable. + /// Pins or unpins a dockable. /// - /// The dockable to pin. + /// The dockable to pin/unpin. void PinDockable(IDockable dockable); + /// + /// Unpins a dockable. + /// + /// The dockable to unpin. + void UnpinDockable(IDockable dockable); + + /// + /// Temporarily shows a pinned dockable. + /// + /// The dockable to show. + void PreviewPinnedDockable(IDockable dockable); + + /// + /// Hides all temporarily shown pinned dockables. + /// + /// The owner of the pinned dockables + void HidePreviewingDockables(IRootDock rootDock); + + /// + /// Returns true if dockable is pinned. + /// + /// The dockable to check. + /// The root dock. If null, the root will be automatically found. + bool IsDockablePinned(IDockable dockable, IRootDock? rootDock = null); + /// /// Floats dockable. /// diff --git a/src/Dock.Model/DockManager.cs b/src/Dock.Model/DockManager.cs index dc45141d9..c830f060e 100644 --- a/src/Dock.Model/DockManager.cs +++ b/src/Dock.Model/DockManager.cs @@ -80,6 +80,7 @@ private void SplitToolDockable(IDockable sourceDockable, IDock sourceDockableOwn var targetToolDock = factory.CreateToolDock(); targetToolDock.Title = nameof(IToolDock); + targetToolDock.Alignment = operation.ToAlignment(); targetToolDock.VisibleDockables = factory.CreateList(); factory.MoveDockable(sourceDockableOwner, targetToolDock, sourceDockable, null); factory.SplitToDock(targetDock, targetToolDock, operation); diff --git a/src/Dock.Model/FactoryBase.Dockable.cs b/src/Dock.Model/FactoryBase.Dockable.cs index 6b4629972..375db2165 100644 --- a/src/Dock.Model/FactoryBase.Dockable.cs +++ b/src/Dock.Model/FactoryBase.Dockable.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System.Diagnostics; +using System.Linq; using Dock.Model.Controls; using Dock.Model.Core; @@ -33,6 +34,9 @@ public virtual void InsertDockable(IDock dock, IDockable dockable, int index) /// public virtual void RemoveDockable(IDockable dockable, bool collapse) { + // to correctly remove a pinned dockable, it needs to be unpinned + UnpinDockable(dockable); + if (dockable.Owner is not IDock dock || dock.VisibleDockables is null) { return; @@ -112,6 +116,8 @@ public virtual void MoveDockable(IDock dock, IDockable sourceDockable, IDockable /// public virtual void MoveDockable(IDock sourceDock, IDock targetDock, IDockable sourceDockable, IDockable? targetDockable) { + UnpinDockable(sourceDockable); + if (targetDock.VisibleDockables is null) { targetDock.VisibleDockables = CreateList(); @@ -260,8 +266,19 @@ public virtual void SwapDockable(IDock sourceDock, IDock targetDock, IDockable s } } - private bool IsDockablePinned(IDockable dockable, IRootDock rootDock) + /// + public bool IsDockablePinned(IDockable dockable, IRootDock? rootDock = null) { + if (rootDock == null) + { + rootDock = FindRoot(dockable); + + if (rootDock == null) + { + return false; + } + } + if (rootDock.LeftPinnedDockables is not null) { if (rootDock.LeftPinnedDockables.Contains(dockable)) @@ -297,6 +314,51 @@ private bool IsDockablePinned(IDockable dockable, IRootDock rootDock) return false; } + /// + public void HidePreviewingDockables(IRootDock rootDock) + { + if (rootDock.PinnedDock == null) + return; + + if (rootDock.PinnedDock.VisibleDockables != null) + { + foreach (var dockable in rootDock.PinnedDock.VisibleDockables) + { + dockable.Owner = dockable.OriginalOwner; + dockable.OriginalOwner = null; + } + RemoveAllVisibleDockables(rootDock.PinnedDock); + } + } + + /// + public void PreviewPinnedDockable(IDockable dockable) + { + var rootDock = FindRoot(dockable, _ => true); + if (rootDock is null) + { + return; + } + + HidePreviewingDockables(rootDock); + + var alignment = (dockable.Owner as IToolDock)?.Alignment ?? Alignment.Unset; + + if (rootDock.PinnedDock == null) + { + rootDock.PinnedDock = CreateToolDock(); + InitDockable(rootDock.PinnedDock, rootDock); + } + rootDock.PinnedDock.Alignment = alignment; + + Debug.Assert(rootDock.PinnedDock != null); + + RemoveAllVisibleDockables(rootDock.PinnedDock); + + dockable.OriginalOwner = dockable.Owner; + AddVisibleDockable(rootDock.PinnedDock, dockable); + } + /// public virtual void PinDockable(IDockable dockable) { @@ -319,7 +381,9 @@ public virtual void PinDockable(IDockable dockable) var isPinned = IsDockablePinned(dockable, rootDock); - var alignment = toolDock.Alignment; + var originalToolDock = dockable.OriginalOwner as IToolDock; + + var alignment = originalToolDock?.Alignment ?? toolDock.Alignment; if (isVisible && !isPinned) { @@ -405,7 +469,7 @@ public virtual void PinDockable(IDockable dockable) // TODO: Handle IsExpanded property of IToolDock. // TODO: Handle AutoHide property of IToolDock. } - else if (!isVisible && isPinned) + else if (isPinned) { // Unpin dockable. @@ -456,7 +520,17 @@ public virtual void PinDockable(IDockable dockable) } } - AddVisibleDockable(toolDock, dockable); + if (!isVisible) + { + AddVisibleDockable(toolDock, dockable); + } + else + { + Debug.Assert(dockable.OriginalOwner is IDock); + var originalOwner = (IDock)dockable.OriginalOwner; + HidePreviewingDockables(rootDock); + AddVisibleDockable(originalOwner, dockable); + } OnDockableAdded(dockable); // TODO: Handle ActiveDockable state. @@ -473,6 +547,15 @@ public virtual void PinDockable(IDockable dockable) } } + /// + public void UnpinDockable(IDockable dockable) + { + if (IsDockablePinned(dockable)) + { + PinDockable(dockable); + } + } + /// public virtual void FloatDockable(IDockable dockable) { @@ -481,6 +564,8 @@ public virtual void FloatDockable(IDockable dockable) return; } + UnpinDockable(dockable); + dock.GetPointerScreenPosition(out var dockPointerScreenX, out var dockPointerScreenY); dockable.GetPointerScreenPosition(out var dockablePointerScreenX, out var dockablePointerScreenY); @@ -636,6 +721,21 @@ protected void RemoveVisibleDockable(IDock dock, IDockable dockable) } } + /// + /// Removes all visible dockable of the dock. + /// + protected void RemoveAllVisibleDockables(IDock dock) + { + if (dock.VisibleDockables != null) + { + if (dock.VisibleDockables.Count > 0) + { + dock.VisibleDockables.Clear(); + UpdateIsEmpty(dock); + } + } + } + /// /// Removes the dockable at the specified index from the visible dockables list of the dock. /// diff --git a/src/Dock.Model/FactoryBase.Locator.cs b/src/Dock.Model/FactoryBase.Locator.cs index 3e0893370..b3169a070 100644 --- a/src/Dock.Model/FactoryBase.Locator.cs +++ b/src/Dock.Model/FactoryBase.Locator.cs @@ -74,13 +74,13 @@ public abstract partial class FactoryBase } /// - public virtual IRootDock? FindRoot(IDockable dockable, Func predicate) + public virtual IRootDock? FindRoot(IDockable dockable, Func? predicate = null) { if (dockable.Owner is null) { return null; } - if (dockable.Owner is IRootDock rootDock && predicate(rootDock)) + if (dockable.Owner is IRootDock rootDock && (predicate?.Invoke(rootDock) ?? true)) { return rootDock; }