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