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/DockMvvmSample/Views/ProportionalStackPanelView.axaml b/samples/DockMvvmSample/Views/ProportionalStackPanelView.axaml index 9e07a74fb..f1b182926 100644 --- a/samples/DockMvvmSample/Views/ProportionalStackPanelView.axaml +++ b/samples/DockMvvmSample/Views/ProportionalStackPanelView.axaml @@ -14,8 +14,8 @@ - - + + @@ -35,7 +35,7 @@ - + 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/DockDockControl.axaml b/src/Dock.Avalonia/Controls/DockDockControl.axaml index d647abd88..30acd1bec 100644 --- a/src/Dock.Avalonia/Controls/DockDockControl.axaml +++ b/src/Dock.Avalonia/Controls/DockDockControl.axaml @@ -8,9 +8,6 @@ - - - diff --git a/src/Dock.Avalonia/Controls/DocumentDockControl.axaml b/src/Dock.Avalonia/Controls/DocumentDockControl.axaml index ac76ef257..505791a5e 100644 --- a/src/Dock.Avalonia/Controls/DocumentDockControl.axaml +++ b/src/Dock.Avalonia/Controls/DocumentDockControl.axaml @@ -7,13 +7,9 @@ - - - - + diff --git a/src/Dock.Avalonia/Controls/HostWindow.axaml b/src/Dock.Avalonia/Controls/HostWindow.axaml index c9ca9cc00..d64af0d17 100644 --- a/src/Dock.Avalonia/Controls/HostWindow.axaml +++ b/src/Dock.Avalonia/Controls/HostWindow.axaml @@ -1,6 +1,7 @@  + xmlns:core="using:Dock.Model.Core" + xmlns:controls="clr-namespace:Dock.Model.Controls;assembly=Dock.Model"> @@ -14,6 +15,7 @@ + diff --git a/src/Dock.Avalonia/Controls/HostWindow.axaml.cs b/src/Dock.Avalonia/Controls/HostWindow.axaml.cs index c49308947..336387290 100644 --- a/src/Dock.Avalonia/Controls/HostWindow.axaml.cs +++ b/src/Dock.Avalonia/Controls/HostWindow.axaml.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using System.Runtime.InteropServices; using Avalonia; using Avalonia.Controls; @@ -6,6 +7,7 @@ using Avalonia.Controls.Primitives; using Avalonia.Input; using Avalonia.Styling; +using Avalonia.VisualTree; using Dock.Avalonia.Internal; using Dock.Model; using Dock.Model.Core; @@ -269,7 +271,15 @@ public void Present(bool isDialog) Window.Factory?.OnWindowOpened(Window); } - Show(); + var ownerDockControl = Window?.Layout?.Factory?.DockControls.FirstOrDefault(); + if (ownerDockControl is Control control && control.GetVisualRoot() is Window parentWindow) + { + Show(parentWindow); + } + else + { + Show(); + } } } } @@ -327,12 +337,6 @@ public void GetSize(out double width, out double height) height = Height; } - /// - public void SetTopmost(bool topmost) - { - Topmost = topmost; - } - /// public void SetTitle(string title) { 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/ProportionalDockControl.axaml b/src/Dock.Avalonia/Controls/ProportionalDockControl.axaml index 20f3e25a4..32976b692 100644 --- a/src/Dock.Avalonia/Controls/ProportionalDockControl.axaml +++ b/src/Dock.Avalonia/Controls/ProportionalDockControl.axaml @@ -8,17 +8,21 @@ - - - - + - diff --git a/src/Dock.Avalonia/Controls/ProportionalStackPanel.cs b/src/Dock.Avalonia/Controls/ProportionalStackPanel.cs index 85a1bc139..f937bcb27 100644 --- a/src/Dock.Avalonia/Controls/ProportionalStackPanel.cs +++ b/src/Dock.Avalonia/Controls/ProportionalStackPanel.cs @@ -4,6 +4,7 @@ using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Presenters; +using Avalonia.Data; using Avalonia.Layout; namespace Dock.Avalonia.Controls; @@ -28,6 +29,58 @@ public Orientation Orientation set => SetValue(OrientationProperty, value); } + /// + /// Defines the Proportion attached property. + /// + public static readonly AttachedProperty ProportionProperty = + AvaloniaProperty.RegisterAttached("Proportion", double.NaN, false, BindingMode.TwoWay); + + /// + /// Gets the value of the Proportion attached property on the specified control. + /// + /// The control. + /// The Proportion attached property. + public static double GetProportion(AvaloniaObject control) + { + return control.GetValue(ProportionProperty); + } + + /// + /// Sets the value of the Proportion attached property on the specified control. + /// + /// The control. + /// The value of the Proportion property. + public static void SetProportion(AvaloniaObject control, double value) + { + control.SetValue(ProportionProperty, value); + } + + /// + /// Defines the IsCollapsed attached property. + /// + public static readonly AttachedProperty IsCollapsedProperty = + AvaloniaProperty.RegisterAttached("IsCollapsed", false, false, BindingMode.TwoWay); + + /// + /// Gets the value of the IsCollapsed attached property on the specified control. + /// + /// The control. + /// The IsCollapsed attached property. + public static bool GetIsCollapsed(AvaloniaObject control) + { + return control.GetValue(IsCollapsedProperty); + } + + /// + /// Sets the value of the IsCollapsed attached property on the specified control. + /// + /// The control. + /// The value of the IsCollapsed property. + public static void SetIsCollapsed(AvaloniaObject control, bool value) + { + control.SetValue(IsCollapsedProperty, value); + } + private void AssignProportions(global::Avalonia.Controls.Controls children) { var assignedProportion = 0.0; @@ -36,14 +89,14 @@ private void AssignProportions(global::Avalonia.Controls.Controls children) for (var i = 0; i < children.Count; i++) { var control = children[i]; - var isEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(control); + var isCollapsed = GetIsCollapsed(control); var isSplitter = ProportionalStackPanelSplitter.IsSplitter(control, out _); if (!isSplitter) { - var proportion = ProportionalStackPanelSplitter.GetControlProportion(control); + var proportion = GetProportion(control); - if (isEmpty) + if (isCollapsed) { proportion = 0.0; } @@ -64,14 +117,14 @@ private void AssignProportions(global::Avalonia.Controls.Controls children) var toAssign = assignedProportion; foreach (var control in children.Where(c => { - var isEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(c); - return !isEmpty && double.IsNaN(ProportionalStackPanelSplitter.GetControlProportion(c)); + var isCollapsed = GetIsCollapsed(c); + return !isCollapsed && double.IsNaN(GetProportion(c)); })) { if (!ProportionalStackPanelSplitter.IsSplitter(control, out _)) { var proportion = (1.0 - toAssign) / unassignedProportions; - ProportionalStackPanelSplitter.SetControlProportion(control, proportion); + SetProportion(control, proportion); assignedProportion += (1.0 - toAssign) / unassignedProportions; } } @@ -85,12 +138,12 @@ private void AssignProportions(global::Avalonia.Controls.Controls children) foreach (var child in children.Where(c => { - var isEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(c); - return !isEmpty && !ProportionalStackPanelSplitter.IsSplitter(c, out _); + var isCollapsed = GetIsCollapsed(c); + return !isCollapsed && !ProportionalStackPanelSplitter.IsSplitter(c, out _); })) { - var proportion = ProportionalStackPanelSplitter.GetControlProportion(child) + toAdd; - ProportionalStackPanelSplitter.SetControlProportion(child, proportion); + var proportion = GetProportion(child) + toAdd; + SetProportion(child, proportion); } } else if (assignedProportion > 1) @@ -101,19 +154,19 @@ private void AssignProportions(global::Avalonia.Controls.Controls children) foreach (var child in children.Where(c => { - var isEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(c); - return !isEmpty && !ProportionalStackPanelSplitter.IsSplitter(c, out _); + var isCollapsed = GetIsCollapsed(c); + return !isCollapsed && !ProportionalStackPanelSplitter.IsSplitter(c, out _); })) { - var proportion = ProportionalStackPanelSplitter.GetControlProportion(child) - toRemove; - ProportionalStackPanelSplitter.SetControlProportion(child, proportion); + var proportion = GetProportion(child) - toRemove; + SetProportion(child, proportion); } } } private double GetTotalSplitterThickness(global::Avalonia.Controls.Controls children) { - var previousIsEmpty = false; + var previousisCollapsed = false; var totalThickness = 0.0; for (var i = 0; i < children.Count; i++) @@ -123,17 +176,17 @@ private double GetTotalSplitterThickness(global::Avalonia.Controls.Controls chil if (isSplitter && proportionalStackPanelSplitter is not null) { - if (previousIsEmpty) + if (previousisCollapsed) { - previousIsEmpty = false; + previousisCollapsed = false; continue; } if (i + 1 < Children.Count) { var nextControl = Children[i + 1]; - var nextIsEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(nextControl); - if (nextIsEmpty) + var nextisCollapsed = GetIsCollapsed(nextControl); + if (nextisCollapsed) { continue; } @@ -144,7 +197,7 @@ private double GetTotalSplitterThickness(global::Avalonia.Controls.Controls chil } else { - previousIsEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(c); + previousisCollapsed = GetIsCollapsed(c); } } @@ -171,7 +224,7 @@ protected override Size MeasureOverride(Size constraint) AssignProportions(Children); - var previousIsEmpty = false; + var previousisCollapsed = false; // Measure each of the Children for (var i = 0; i < Children.Count; i++) @@ -184,13 +237,13 @@ protected override Size MeasureOverride(Size constraint) Math.Max(0.0, constraint.Width - usedWidth - splitterThickness), Math.Max(0.0, constraint.Height - usedHeight - splitterThickness)); - var proportion = ProportionalStackPanelSplitter.GetControlProportion(control); + var proportion = GetProportion(control); - var isEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(control); - if (isEmpty) + var isCollapsed = !isSplitter && GetIsCollapsed(control); + if (isCollapsed) { // TODO: Also handle next is empty. - previousIsEmpty = true; + previousisCollapsed = true; var size = new Size(); control.Measure(size); continue; @@ -220,25 +273,25 @@ protected override Size MeasureOverride(Size constraint) } else { - var nextIsEmpty = false; + var nextisCollapsed = false; if (i + 1 < Children.Count) { var nextControl = Children[i + 1]; - nextIsEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(nextControl); + nextisCollapsed = !ProportionalStackPanelSplitter.IsSplitter(nextControl, out _ ) && GetIsCollapsed(nextControl); } - if (previousIsEmpty || nextIsEmpty) + if (previousisCollapsed || nextisCollapsed) { var size = new Size(); control.Measure(size); - previousIsEmpty = true; + previousisCollapsed = true; continue; } control.Measure(remainingSize); } - previousIsEmpty = false; + previousisCollapsed = false; var desiredSize = control.DesiredSize; @@ -298,33 +351,33 @@ protected override Size ArrangeOverride(Size arrangeSize) AssignProportions(Children); - var previousIsEmpty = false; + var previousisCollapsed = false; for (var i = 0; i < Children.Count; i++) { var control = Children[i]; - var isEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(control); - if (isEmpty) + var isSplitter = ProportionalStackPanelSplitter.IsSplitter(control, out _); + + var isCollapsed = !isSplitter && GetIsCollapsed(control); + if (isCollapsed) { // TODO: Also handle next is empty. - previousIsEmpty = true; + previousisCollapsed = true; var rect = new Rect(); control.Arrange(rect); index++; continue; } - var isSplitter = ProportionalStackPanelSplitter.IsSplitter(control, out _); - - var nextIsEmpty = false; + var nextisCollapsed = false; if (i + 1 < Children.Count) { var nextControl = Children[i + 1]; - nextIsEmpty = ProportionalStackPanelSplitter.GetControlIsEmpty(nextControl); + nextisCollapsed = !ProportionalStackPanelSplitter.IsSplitter(nextControl, out _) && GetIsCollapsed(nextControl); } - if (isSplitter && (previousIsEmpty || nextIsEmpty)) + if (isSplitter && (previousisCollapsed || nextisCollapsed)) { var rect = new Rect(); control.Arrange(rect); @@ -332,7 +385,7 @@ protected override Size ArrangeOverride(Size arrangeSize) continue; } - previousIsEmpty = false; + previousisCollapsed = false; // Determine the remaining space left to arrange the element var remainingRect = new Rect( @@ -347,7 +400,7 @@ protected override Size ArrangeOverride(Size arrangeSize) if (index < Children.Count) { var desiredSize = control.DesiredSize; - var proportion = ProportionalStackPanelSplitter.GetControlProportion(control); + var proportion = GetProportion(control); switch (Orientation) { @@ -405,4 +458,12 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang InvalidateMeasure(); } } + + static ProportionalStackPanel() + { + AffectsParentMeasure(IsCollapsedProperty); + AffectsParentArrange(IsCollapsedProperty); + AffectsParentMeasure(ProportionProperty); + AffectsParentArrange(ProportionProperty); + } } diff --git a/src/Dock.Avalonia/Controls/ProportionalStackPanelSplitter.axaml.cs b/src/Dock.Avalonia/Controls/ProportionalStackPanelSplitter.axaml.cs index a318b46a0..19f4e4a69 100644 --- a/src/Dock.Avalonia/Controls/ProportionalStackPanelSplitter.axaml.cs +++ b/src/Dock.Avalonia/Controls/ProportionalStackPanelSplitter.axaml.cs @@ -1,4 +1,5 @@ -using Avalonia; +using System; +using Avalonia; using Avalonia.Controls; using Avalonia.Controls.Metadata; using Avalonia.Controls.Presenters; @@ -16,58 +17,6 @@ namespace Dock.Avalonia.Controls; [PseudoClasses(":horizontal", ":vertical")] public class ProportionalStackPanelSplitter : Thumb { - /// - /// Defines the Proportion attached property. - /// - public static readonly AttachedProperty ProportionProperty = - AvaloniaProperty.RegisterAttached("Proportion", double.NaN, false, BindingMode.TwoWay); - - /// - /// Gets the value of the Proportion attached property on the specified control. - /// - /// The control. - /// The Proportion attached property. - public static double GetProportion(AvaloniaObject control) - { - return control.GetValue(ProportionProperty); - } - - /// - /// Sets the value of the Proportion attached property on the specified control. - /// - /// The control. - /// The value of the Proportion property. - public static void SetProportion(AvaloniaObject control, double value) - { - control.SetValue(ProportionProperty, value); - } - - /// - /// Defines the IsEmpty attached property. - /// - public static readonly AttachedProperty IsEmptyProperty = - AvaloniaProperty.RegisterAttached("IsEmpty", false, false, BindingMode.TwoWay); - - /// - /// Gets the value of the IsEmpty attached property on the specified control. - /// - /// The control. - /// The IsEmpty attached property. - public static bool GetIsEmpty(AvaloniaObject control) - { - return control.GetValue(IsEmptyProperty); - } - - /// - /// Sets the value of the IsEmpty attached property on the specified control. - /// - /// The control. - /// The value of the IsEmpty property. - public static void SetIsEmpty(AvaloniaObject control, bool value) - { - control.SetValue(IsEmptyProperty, value); - } - /// /// Defines the property. /// @@ -139,79 +88,6 @@ internal static bool IsSplitter(Control? control, out ProportionalStackPanelSpli return false; } - internal static void SetControlProportion(Control? control, double proportion) - { - if (control is ContentPresenter contentPresenter) - { - if (contentPresenter.Child is null) - { - contentPresenter.UpdateChild(); - } - - if (contentPresenter.Child is not null) - { - SetProportion(contentPresenter.Child, proportion); - } - } - else - { - if (control is not null) - { - SetProportion(control, proportion); - } - } - } - - internal static double GetControlProportion(Control? control) - { - if (control is ContentPresenter contentPresenter) - { - if (contentPresenter.Child is null) - { - contentPresenter.UpdateChild(); - } - - if (contentPresenter.Child is not null) - { - return GetProportion(contentPresenter.Child); - } - - return double.NaN; - } - - if (control is not null) - { - return GetProportion(control); - } - - return double.NaN; - } - - internal static bool GetControlIsEmpty(Control? control) - { - if (control is ContentPresenter contentPresenter) - { - if (contentPresenter.Child is null) - { - contentPresenter.UpdateChild(); - } - - if (contentPresenter.Child is not null) - { - return GetIsEmpty(contentPresenter.Child); - } - - return false; - } - - if (control is not null) - { - return GetIsEmpty(control); - } - - return false; - } - /// protected override void OnPointerPressed(PointerPressedEventArgs e) { @@ -304,18 +180,7 @@ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) nextIndex = children.IndexOf(this) + 1; } - var child = children[nextIndex]; - if (child is ContentPresenter contentPresenter) - { - if (contentPresenter.Child is null) - { - contentPresenter.UpdateChild(); - } - - return contentPresenter.Child; - } - - return child; + return children[nextIndex]; } private void SetTargetProportion(double dragDelta) @@ -329,8 +194,8 @@ private void SetTargetProportion(double dragDelta) var child = FindNextChild(panel); - var targetElementProportion = GetControlProportion(target); - var neighbourProportion = child is not null ? GetControlProportion(child) : double.NaN; + var targetElementProportion = ProportionalStackPanel.GetProportion(target); + var neighbourProportion = child is not null ? ProportionalStackPanel.GetProportion(child) : double.NaN; var dProportion = dragDelta / (panel.Orientation == Orientation.Vertical ? panel.Bounds.Height : panel.Bounds.Width); @@ -363,15 +228,12 @@ private void SetTargetProportion(double dragDelta) targetElementProportion += dProportion; } - SetProportion(target, targetElementProportion); + ProportionalStackPanel.SetProportion(target, targetElementProportion); if (child is not null) { - SetProportion(child, neighbourProportion); + ProportionalStackPanel.SetProportion(child, neighbourProportion); } - - panel.InvalidateMeasure(); - panel.InvalidateArrange(); } private void UpdateHeightOrWidth() @@ -422,17 +284,10 @@ private void UpdateHeightOrWidth() } var parent = Parent as Control; - var index = parent is null ? -1 :panel.Children.IndexOf(parent); + var index = parent is null ? -1 : panel.Children.IndexOf(parent); if (index > 0 && panel.Children.Count > 0) { - if (panel.Children[index - 1] is ContentPresenter contentPresenter) - { - return contentPresenter.Child; - } - else - { - return null; - } + return panel.Children[index - 1]; } } else 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/ProportionalDockSplitter.cs b/src/Dock.Model.Avalonia/Controls/ProportionalDockSplitter.cs index 5182849b9..84259b640 100644 --- a/src/Dock.Model.Avalonia/Controls/ProportionalDockSplitter.cs +++ b/src/Dock.Model.Avalonia/Controls/ProportionalDockSplitter.cs @@ -1,4 +1,6 @@ -using System.Runtime.Serialization; +using System; +using System.Runtime.Serialization; +using Avalonia; using Dock.Model.Avalonia.Core; using Dock.Model.Controls; 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/Adapters/HostAdapter.cs b/src/Dock.Model/Adapters/HostAdapter.cs index 7cdee8287..3baf45227 100644 --- a/src/Dock.Model/Adapters/HostAdapter.cs +++ b/src/Dock.Model/Adapters/HostAdapter.cs @@ -56,7 +56,6 @@ public void Present(bool isDialog) _window.Host.Present(isDialog); _window.Host.SetPosition(_window.X, _window.Y); _window.Host.SetSize(_window.Width, _window.Height); - _window.Host.SetTopmost(_window.Topmost); _window.Host.SetTitle(_window.Title); _window.Host.SetLayout(_window.Layout); _window.Host.IsTracked = true; 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/Core/IHostWindow.cs b/src/Dock.Model/Core/IHostWindow.cs index ffaff405f..eef221c7d 100644 --- a/src/Dock.Model/Core/IHostWindow.cs +++ b/src/Dock.Model/Core/IHostWindow.cs @@ -64,12 +64,6 @@ public interface IHostWindow /// The host height. void GetSize(out double width, out double height); - /// - /// Sets host topmost. - /// - /// The host topmost. - void SetTopmost(bool topmost); - /// /// Sets host title. /// 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 e002a05d0..c987ef63b 100644 --- a/src/Dock.Model/FactoryBase.Dockable.cs +++ b/src/Dock.Model/FactoryBase.Dockable.cs @@ -1,4 +1,6 @@ -using Dock.Model.Controls; +using System.Diagnostics; +using System.Linq; +using Dock.Model.Controls; using Dock.Model.Core; namespace Dock.Model; @@ -13,8 +15,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 +26,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); } } @@ -34,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; @@ -45,8 +48,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 +104,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; @@ -116,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(); @@ -177,11 +179,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 +190,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 +201,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 +227,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); @@ -259,7 +254,7 @@ public virtual void SwapDockable(IDock sourceDock, IDock targetDock, IDockable s var originalTargetDockable = targetDock.VisibleDockables[targetIndex]; sourceDock.VisibleDockables[sourceIndex] = originalTargetDockable; targetDock.VisibleDockables[targetIndex] = originalSourceDockable; - + InitDockable(originalSourceDockable, targetDock); InitDockable(originalTargetDockable, sourceDock); @@ -271,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)) @@ -308,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) { @@ -320,7 +371,7 @@ public virtual void PinDockable(IDockable dockable) { return; } - + var isVisible = false; if (toolDock.VisibleDockables is not null) @@ -330,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) { @@ -360,11 +413,10 @@ public virtual void PinDockable(IDockable dockable) break; } } - + if (toolDock.VisibleDockables is not null) { - toolDock.VisibleDockables.Remove(dockable); - toolDock.IsEmpty = toolDock.VisibleDockables.Count == 0; + RemoveVisibleDockable(toolDock, dockable); OnDockableRemoved(dockable); } @@ -417,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. @@ -468,10 +520,20 @@ public virtual void PinDockable(IDockable dockable) } } - toolDock.VisibleDockables.Add(dockable); - toolDock.IsEmpty = toolDock.VisibleDockables.Count == 0; + 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. // TODO: Handle IsExpanded property of IToolDock. // TODO: Handle AutoHide property of IToolDock. @@ -486,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) { @@ -494,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); @@ -532,7 +605,7 @@ public virtual void FloatDockable(IDockable dockable) /// public virtual void CloseDockable(IDockable dockable) { - if (dockable.OnClose()) + if (dockable.CanClose && dockable.OnClose()) { RemoveDockable(dockable, true); OnDockableClosed(dockable); @@ -590,7 +663,7 @@ public virtual void CloseLeftDockables(IDockable dockable) { return; } - + CloseDockablesRange(dock, 0, indexOf - 1); } @@ -607,7 +680,88 @@ public virtual void CloseRightDockables(IDockable dockable) { return; } - + 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 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. + /// + 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.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; } diff --git a/src/Dock.Model/FactoryBase.cs b/src/Dock.Model/FactoryBase.cs index 15fc26aa0..9b21cfd16 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; @@ -263,7 +255,6 @@ public virtual void SplitToDock(IDock dock, IDockable dockable, DockOperation op public virtual IDockWindow? CreateWindowFrom(IDockable dockable) { IDockable? target; - bool topmost; switch (dockable) { @@ -276,13 +267,11 @@ 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; } } - topmost = true; break; } case IDocument: @@ -309,43 +298,36 @@ 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; } } - topmost = false; break; } case IToolDock: { target = dockable; - topmost = true; break; } case IDocumentDock: { target = dockable; - topmost = false; break; } case IProportionalDock proportionalDock: { target = proportionalDock; - topmost = false; break; } case IDockDock dockDock: { target = dockDock; - topmost = false; break; } case IRootDock rootDock: { target = rootDock.ActiveDockable; - topmost = false; break; } default: @@ -359,8 +341,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; @@ -372,7 +353,6 @@ public virtual void SplitToDock(IDock dock, IDockable dockable, DockOperation op window.Title = ""; window.Width = double.NaN; window.Height = double.NaN; - window.Topmost = topmost; window.Layout = root; root.Window = window; diff --git a/tests/Dock.Avalonia.UnitTests/Controls/ProportionalStackPanelTests.cs b/tests/Dock.Avalonia.UnitTests/Controls/ProportionalStackPanelTests.cs index 807c08ae9..263f5f880 100644 --- a/tests/Dock.Avalonia.UnitTests/Controls/ProportionalStackPanelTests.cs +++ b/tests/Dock.Avalonia.UnitTests/Controls/ProportionalStackPanelTests.cs @@ -85,7 +85,7 @@ public void Lays_Out_Children_Default() new Border() { Background = Brushes.Red, - [ProportionalStackPanelSplitter.ProportionProperty] = 0.5 + [ProportionalStackPanel.ProportionProperty] = 0.5 }, new ProportionalStackPanelSplitter(), new Border() @@ -138,7 +138,7 @@ public void Lays_Out_Children_Default() new Border() { Background=Brushes.Red, - [ProportionalStackPanelSplitter.ProportionProperty] = 0.5 + [ProportionalStackPanel.ProportionProperty] = 0.5 } } },