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
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 019b6da66..c987ef63b 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,18 @@ 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 +548,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 +565,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 +722,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;
}