diff --git a/src/Dock.Avalonia/Controls/DockDockControl.axaml b/src/Dock.Avalonia/Controls/DockDockControl.axaml index ab6e48cdb..d647abd88 100644 --- a/src/Dock.Avalonia/Controls/DockDockControl.axaml +++ b/src/Dock.Avalonia/Controls/DockDockControl.axaml @@ -8,7 +8,8 @@ - + + diff --git a/src/Dock.Avalonia/Controls/DocumentDockControl.axaml b/src/Dock.Avalonia/Controls/DocumentDockControl.axaml index 1cf7924a2..ac76ef257 100644 --- a/src/Dock.Avalonia/Controls/DocumentDockControl.axaml +++ b/src/Dock.Avalonia/Controls/DocumentDockControl.axaml @@ -1,12 +1,14 @@ + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:core="using:Dock.Model.Core"> - + + diff --git a/src/Dock.Avalonia/Controls/HostWindow.axaml b/src/Dock.Avalonia/Controls/HostWindow.axaml index 164ca3b2a..c9ca9cc00 100644 --- a/src/Dock.Avalonia/Controls/HostWindow.axaml +++ b/src/Dock.Avalonia/Controls/HostWindow.axaml @@ -53,9 +53,10 @@ + + + --> diff --git a/src/Dock.Avalonia/Controls/ToolDockControl.axaml.cs b/src/Dock.Avalonia/Controls/ToolDockControl.axaml.cs index 3233b450b..50252c2f1 100644 --- a/src/Dock.Avalonia/Controls/ToolDockControl.axaml.cs +++ b/src/Dock.Avalonia/Controls/ToolDockControl.axaml.cs @@ -1,4 +1,7 @@ +using Avalonia; +using Avalonia.Controls.Presenters; using Avalonia.Controls.Primitives; +using Avalonia.VisualTree; namespace Dock.Avalonia.Controls; @@ -7,4 +10,45 @@ namespace Dock.Avalonia.Controls; /// public class ToolDockControl : TemplatedControl { + private ProportionalStackPanel? GetPanel() + { + if (Parent is ContentPresenter presenter) + { + if (presenter.GetVisualParent() is ProportionalStackPanel panel) + { + return panel; + } + } + else if (this.GetVisualParent() is ProportionalStackPanel psp) + { + return psp; + } + + return null; + } + + /// + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) + { + base.OnPropertyChanged(change); + + if (change.Property == ProportionalStackPanelSplitter.IsEmptyProperty) + { + // TODO: Handle invalidation better. + if (GetPanel() is { } panel) + { + panel.InvalidateMeasure(); + panel.InvalidateArrange(); + panel.UpdateLayout(); + + panel.InvalidateMeasure(); + panel.InvalidateArrange(); + panel.UpdateLayout(); + + panel.InvalidateMeasure(); + panel.InvalidateArrange(); + panel.UpdateLayout(); + } + } + } } diff --git a/src/Dock.Avalonia/Controls/ToolTabStrip.axaml b/src/Dock.Avalonia/Controls/ToolTabStrip.axaml index 7352604fb..71731072f 100644 --- a/src/Dock.Avalonia/Controls/ToolTabStrip.axaml +++ b/src/Dock.Avalonia/Controls/ToolTabStrip.axaml @@ -41,7 +41,7 @@ - diff --git a/src/Dock.Model.Avalonia/Core/DockBase.cs b/src/Dock.Model.Avalonia/Core/DockBase.cs index 174d6185c..9ddedc477 100644 --- a/src/Dock.Model.Avalonia/Core/DockBase.cs +++ b/src/Dock.Model.Avalonia/Core/DockBase.cs @@ -68,6 +68,12 @@ public abstract class DockBase : DockableBase, IDock public static readonly DirectProperty IsActiveProperty = AvaloniaProperty.RegisterDirect(nameof(IsActive), o => o.IsActive, (o, v) => o.IsActive = v); + /// + /// Defines the property. + /// + public static readonly DirectProperty IsEmptyProperty = + AvaloniaProperty.RegisterDirect(nameof(IsEmpty), o => o.IsEmpty, (o, v) => o.IsEmpty = v); + /// /// Defines the property. /// @@ -94,6 +100,7 @@ public abstract class DockBase : DockableBase, IDock private double _proportion = double.NaN; private DockMode _dock = DockMode.Center; private bool _isActive; + private bool _isEmpty; private bool _isCollapsable = true; private bool _canGoBack; private bool _canGoForward; @@ -188,6 +195,15 @@ public bool IsActive set => SetAndRaise(IsActiveProperty, ref _isActive, value); } + /// + [DataMember(IsRequired = false, EmitDefaultValue = true)] + [JsonPropertyName("IsEmpty")] + public bool IsEmpty + { + get => _isEmpty; + set => SetAndRaise(IsEmptyProperty, ref _isEmpty, value); + } + /// [DataMember(IsRequired = false, EmitDefaultValue = true)] [JsonPropertyName("IsCollapsable")] diff --git a/src/Dock.Model.Avalonia/Json/AvaloniaDockSerializer.cs b/src/Dock.Model.Avalonia/Json/AvaloniaDockSerializer.cs index 9a8b0c4d8..823badfef 100644 --- a/src/Dock.Model.Avalonia/Json/AvaloniaDockSerializer.cs +++ b/src/Dock.Model.Avalonia/Json/AvaloniaDockSerializer.cs @@ -113,6 +113,7 @@ public AvaloniaDockSerializer() "Proportion", "Dock", "IsActive", + "IsEmpty", "IsCollapsable", }, [typeof(IDockDock)] = new List @@ -132,6 +133,7 @@ public AvaloniaDockSerializer() "Proportion", "Dock", "IsActive", + "IsEmpty", "IsCollapsable", // IDockDock "LastChildFill", @@ -153,6 +155,7 @@ public AvaloniaDockSerializer() "Proportion", "Dock", "IsActive", + "IsEmpty", "IsCollapsable", // IDocumentDock "CanCreateDocument", @@ -174,6 +177,7 @@ public AvaloniaDockSerializer() "Proportion", "Dock", "IsActive", + "IsEmpty", "IsCollapsable", }, [typeof(IProportionalDock)] = new List @@ -193,6 +197,7 @@ public AvaloniaDockSerializer() "Proportion", "Dock", "IsActive", + "IsEmpty", "IsCollapsable", // IProportionalDock "Orientation", @@ -214,6 +219,7 @@ public AvaloniaDockSerializer() "Proportion", "Dock", "IsActive", + "IsEmpty", "IsCollapsable", }, [typeof(IRootDock)] = new List @@ -233,6 +239,7 @@ public AvaloniaDockSerializer() "Proportion", "Dock", "IsActive", + "IsEmpty", "IsCollapsable", // IRootDock "IsFocusableRoot", @@ -261,6 +268,7 @@ public AvaloniaDockSerializer() "Proportion", "Dock", "IsActive", + "IsEmpty", "IsCollapsable", // IToolDock "Alignment", @@ -295,6 +303,7 @@ public AvaloniaDockSerializer() "Proportion", "Dock", "IsActive", + "IsEmpty", "IsCollapsable", }, [typeof(Document)] = new List @@ -334,6 +343,7 @@ public AvaloniaDockSerializer() "Proportion", "Dock", "IsActive", + "IsEmpty", "IsCollapsable", // IDockDock "LastChildFill", @@ -355,6 +365,7 @@ public AvaloniaDockSerializer() "Proportion", "Dock", "IsActive", + "IsEmpty", "IsCollapsable", // IDocumentDock "CanCreateDocument", @@ -376,6 +387,7 @@ public AvaloniaDockSerializer() "Proportion", "Dock", "IsActive", + "IsEmpty", "IsCollapsable", // IProportionalDock "Orientation", @@ -397,6 +409,7 @@ public AvaloniaDockSerializer() "Proportion", "Dock", "IsActive", + "IsEmpty", "IsCollapsable", }, [typeof(RootDock)] = new List @@ -416,6 +429,7 @@ public AvaloniaDockSerializer() "Proportion", "Dock", "IsActive", + "IsEmpty", "IsCollapsable", // IRootDock "IsFocusableRoot", @@ -444,6 +458,7 @@ public AvaloniaDockSerializer() "Proportion", "Dock", "IsActive", + "IsEmpty", "IsCollapsable", // IToolDock "Alignment", diff --git a/src/Dock.Model.Mvvm/Core/DockBase.cs b/src/Dock.Model.Mvvm/Core/DockBase.cs index aedd5d363..42cdc928d 100644 --- a/src/Dock.Model.Mvvm/Core/DockBase.cs +++ b/src/Dock.Model.Mvvm/Core/DockBase.cs @@ -22,6 +22,7 @@ public abstract class DockBase : DockableBase, IDock private DockMode _dock = DockMode.Center; private bool _isCollapsable = true; private bool _isActive; + private bool _isEmpty; /// /// Initializes new instance of the class. @@ -101,6 +102,14 @@ public bool IsActive set => SetProperty(ref _isActive, value); } + /// + [DataMember(IsRequired = false, EmitDefaultValue = true)] + public bool IsEmpty + { + get => _isEmpty; + set => SetProperty(ref _isEmpty, value); + } + /// [DataMember(IsRequired = false, EmitDefaultValue = true)] public bool IsCollapsable diff --git a/src/Dock.Model.ReactiveUI/Core/DockBase.cs b/src/Dock.Model.ReactiveUI/Core/DockBase.cs index 44d42376e..683895752 100644 --- a/src/Dock.Model.ReactiveUI/Core/DockBase.cs +++ b/src/Dock.Model.ReactiveUI/Core/DockBase.cs @@ -22,6 +22,7 @@ public abstract class DockBase : DockableBase, IDock private DockMode _dock = DockMode.Center; private bool _isCollapsable = true; private bool _isActive; + private bool _isEmpty; /// /// Initializes new instance of the class. @@ -101,6 +102,14 @@ public bool IsActive set => this.RaiseAndSetIfChanged(ref _isActive, value); } + /// + [DataMember(IsRequired = false, EmitDefaultValue = true)] + public bool IsEmpty + { + get => _isEmpty; + set => this.RaiseAndSetIfChanged(ref _isEmpty, value); + } + /// [DataMember(IsRequired = false, EmitDefaultValue = true)] public bool IsCollapsable diff --git a/src/Dock.Model/Core/IDock.cs b/src/Dock.Model/Core/IDock.cs index 8b5b4f415..bfc6553a5 100644 --- a/src/Dock.Model/Core/IDock.cs +++ b/src/Dock.Model/Core/IDock.cs @@ -43,6 +43,11 @@ public interface IDock : IDockable /// bool IsActive { get; set; } + /// + /// Gets if the dockable is empty. + /// + bool IsEmpty { get; set; } + /// /// Gets or sets if the Dock collapses when all its children are removed. /// diff --git a/src/Dock.Model/FactoryBase.Dockable.cs b/src/Dock.Model/FactoryBase.Dockable.cs index de96a78c0..e002a05d0 100644 --- a/src/Dock.Model/FactoryBase.Dockable.cs +++ b/src/Dock.Model/FactoryBase.Dockable.cs @@ -14,6 +14,7 @@ public virtual void AddDockable(IDock dock, IDockable dockable) InitDockable(dockable, dock); dock.VisibleDockables ??= CreateList(); dock.VisibleDockables.Add(dockable); + dock.IsEmpty = dock.VisibleDockables.Count == 0; OnDockableAdded(dockable); } @@ -25,6 +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; OnDockableAdded(dockable); } } @@ -44,6 +46,7 @@ public virtual void RemoveDockable(IDockable dockable, bool collapse) } dock.VisibleDockables.Remove(dockable); + dock.IsEmpty = dock.VisibleDockables.Count == 0; OnDockableRemoved(dockable); var indexActiveDockable = index > 0 ? index - 1 : 0; @@ -100,8 +103,10 @@ 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; OnDockableRemoved(sourceDockable); dock.VisibleDockables.Insert(targetIndex, sourceDockable); + dock.IsEmpty = dock.VisibleDockables.Count == 0; OnDockableAdded(sourceDockable); OnDockableMoved(sourceDockable); dock.ActiveDockable = sourceDockable; @@ -173,8 +178,10 @@ public virtual void MoveDockable(IDock sourceDock, IDock targetDock, IDockable s if (sourceIndex < targetIndex) { targetDock.VisibleDockables.Insert(targetIndex + 1, sourceDockable); + targetDock.IsEmpty = targetDock.VisibleDockables.Count == 0; OnDockableAdded(sourceDockable); targetDock.VisibleDockables.RemoveAt(sourceIndex); + targetDock.IsEmpty = targetDock.VisibleDockables.Count == 0; OnDockableRemoved(sourceDockable); OnDockableMoved(sourceDockable); } @@ -184,8 +191,10 @@ public virtual void MoveDockable(IDock sourceDock, IDock targetDock, IDockable s if (targetDock.VisibleDockables.Count + 1 > removeIndex) { targetDock.VisibleDockables.Insert(targetIndex, sourceDockable); + targetDock.IsEmpty = targetDock.VisibleDockables.Count == 0; OnDockableAdded(sourceDockable); targetDock.VisibleDockables.RemoveAt(removeIndex); + targetDock.IsEmpty = targetDock.VisibleDockables.Count == 0; OnDockableRemoved(sourceDockable); OnDockableMoved(sourceDockable); } @@ -195,6 +204,7 @@ public virtual void MoveDockable(IDock sourceDock, IDock targetDock, IDockable s { RemoveDockable(sourceDockable, true); targetDock.VisibleDockables.Insert(targetIndex, sourceDockable); + targetDock.IsEmpty = targetDock.VisibleDockables.Count == 0; OnDockableAdded(sourceDockable); OnDockableMoved(sourceDockable); InitDockable(sourceDockable, targetDock); @@ -220,9 +230,11 @@ 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); @@ -352,6 +364,7 @@ public virtual void PinDockable(IDockable dockable) if (toolDock.VisibleDockables is not null) { toolDock.VisibleDockables.Remove(dockable); + toolDock.IsEmpty = toolDock.VisibleDockables.Count == 0; OnDockableRemoved(dockable); } @@ -456,6 +469,7 @@ public virtual void PinDockable(IDockable dockable) } toolDock.VisibleDockables.Add(dockable); + toolDock.IsEmpty = toolDock.VisibleDockables.Count == 0; OnDockableAdded(dockable); // TODO: Handle ActiveDockable state. diff --git a/src/Dock.Model/FactoryBase.Init.cs b/src/Dock.Model/FactoryBase.Init.cs index 0d956c57b..634ceb812 100644 --- a/src/Dock.Model/FactoryBase.Init.cs +++ b/src/Dock.Model/FactoryBase.Init.cs @@ -53,6 +53,8 @@ public virtual void InitDockable(IDockable dockable, IDockable? owner) { InitDockable(child, dockable); } + + dock.IsEmpty = dock.VisibleDockables.Count == 0; } } diff --git a/src/Dock.Model/FactoryBase.cs b/src/Dock.Model/FactoryBase.cs index a66051aaf..15fc26aa0 100644 --- a/src/Dock.Model/FactoryBase.cs +++ b/src/Dock.Model/FactoryBase.cs @@ -126,6 +126,7 @@ public virtual IDock CreateSplitLayout(IDock dock, IDockable dockable, DockOpera if (split.VisibleDockables is not null) { split.VisibleDockables.Add(dockable); + split.IsEmpty = split.VisibleDockables.Count == 0; OnDockableAdded(dockable); split.ActiveDockable = dockable; } @@ -166,6 +167,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; OnDockableAdded(split); layout.ActiveDockable = split; } @@ -178,6 +180,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; OnDockableAdded(dock); layout.ActiveDockable = dock; } @@ -187,6 +190,7 @@ public virtual IDock CreateSplitLayout(IDock dock, IDockable dockable, DockOpera } layout.VisibleDockables?.Add(splitter); + layout.IsEmpty = layout.VisibleDockables?.Count == 0; OnDockableAdded(splitter); switch (operation) @@ -197,6 +201,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; OnDockableAdded(dock); layout.ActiveDockable = dock; } @@ -209,6 +214,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; OnDockableAdded(split); layout.ActiveDockable = split; } @@ -237,8 +243,10 @@ public virtual void SplitToDock(IDock dock, IDockable dockable, DockOperation op { var layout = CreateSplitLayout(dock, dockable, operation); ownerDock.VisibleDockables.RemoveAt(index); + ownerDock.IsEmpty = ownerDock.VisibleDockables.Count == 0; OnDockableRemoved(dockable); ownerDock.VisibleDockables.Insert(index, layout); + layout.IsEmpty = layout.VisibleDockables?.Count == 0; OnDockableAdded(dockable); InitDockable(layout, ownerDock); ownerDock.ActiveDockable = layout; @@ -269,6 +277,7 @@ public virtual void SplitToDock(IDock dock, IDockable dockable, DockOperation op if (dock.VisibleDockables is not null) { dock.VisibleDockables.Add(dockable); + dock.IsEmpty = dock.VisibleDockables.Count == 0; OnDockableAdded(dockable); dock.ActiveDockable = dockable; } @@ -301,6 +310,7 @@ public virtual void SplitToDock(IDock dock, IDockable dockable, DockOperation op if (dock.VisibleDockables is not null) { dock.VisibleDockables.Add(dockable); + dock.IsEmpty = dock.VisibleDockables.Count == 0; OnDockableAdded(dockable); dock.ActiveDockable = dockable; } @@ -350,6 +360,7 @@ public virtual void SplitToDock(IDock dock, IDockable dockable, DockOperation op if (root.VisibleDockables is not null && target is not null) { root.VisibleDockables.Add(target); + root.IsEmpty = root.VisibleDockables.Count == 0; OnDockableAdded(target); root.ActiveDockable = target; root.DefaultDockable = target;