Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docfx/articles/dock-adapters.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Dock exposes a small set of helper classes that adapt the core interfaces to dif

## HostAdapter

`HostAdapter` implements `IHostAdapter` and bridges a dock window (`IDockWindow`) with a platform-specific host window (`IHostWindow`). It resolves the host window via `IFactory.GetHostWindow` when needed, pushes layout, title, size, and position into the host when presenting, and reads the current size and position back when saving.
`HostAdapter` implements `IHostAdapter` and bridges a dock window (`IDockWindow`) with a platform-specific host window (`IHostWindow`). It resolves the host window via `IFactory.GetHostWindow` when needed, pushes layout, title, size, position, and `WindowState` into the host when presenting, and reads the current size, position, and state back when saving.

Typical usage looks like the following:

Expand All @@ -13,7 +13,7 @@ var hostAdapter = new HostAdapter(dockWindow);
hostAdapter.Present(isDialog: false);
```

The adapter fetches the host window instance from `IFactory.GetHostWindow` if it has not already been assigned. Calling `Save` stores the last known position and dimensions so they can be restored later. `Exit` closes the host and clears the reference, and `SetActive` forwards the activation request to the host window.
The adapter fetches the host window instance from `IFactory.GetHostWindow` if it has not already been assigned. Calling `Save` stores the last known position, dimensions, and window state so they can be restored later. `Exit` closes the host and clears the reference, and `SetActive` forwards the activation request to the host window.

## NavigateAdapter

Expand Down
11 changes: 11 additions & 0 deletions docfx/articles/dock-enums.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,17 @@ Indicates the current state of an MDI document window.
| `Minimized` | Window is minimized. |
| `Maximized` | Window fills the document area. |

## DockWindowState

Indicates the current state of a floating dock window (`IDockWindow.WindowState`).

| Value | Description |
| ----- | ----------- |
| `Normal` | Window is in its normal size and position. |
| `Minimized` | Window is minimized. |
| `Maximized` | Window is maximized. |
| `FullScreen` | Window uses full-screen presentation when supported. |

## GripMode

Determines how the grip element in tool chrome behaves (used by tool docks).
Expand Down
2 changes: 1 addition & 1 deletion docfx/articles/dock-serialization.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ DI-based construction, use the `Dock.Serializer.DockSerializer` overload that ac
2. Load the layout and call `_dockState.Restore` to restore content/templates if needed.
3. Handle the case where the layout file does not exist or fails to deserialize.

Following this pattern keeps the window arrangement consistent across sessions.
Following this pattern keeps the window arrangement consistent across sessions. Floating window bounds, title, and `WindowState` are serialized as part of `IDockWindow`.

## Dockable identifiers

Expand Down
6 changes: 4 additions & 2 deletions docfx/articles/dock-windows.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public override IDockWindow? CreateWindowFrom(IDockable dockable)
}
```

Calling `FloatDockable` on the factory opens a dockable in a new window. The new `IDockWindow` is tracked by the root dock and stores its bounds and title so it can be serialized together with the layout.
Calling `FloatDockable` on the factory opens a dockable in a new window. The new `IDockWindow` is tracked by the root dock and stores its bounds, title, and `WindowState` so it can be serialized together with the layout.

To control parent/owner relationships and modality at creation time, use the `DockWindowOptions` overloads:

Expand Down Expand Up @@ -58,6 +58,7 @@ For setup details see the [Managed windows guide](dock-managed-windows-guide.md)
| `Id` | `string` | Window identifier. |
| `X`, `Y` | `double` | Window position. |
| `Width`, `Height` | `double` | Window size. |
| `WindowState` | `DockWindowState` | Window state (`Normal`, `Minimized`, `Maximized`, `FullScreen`). |
| `Topmost` | `bool` | Keeps the window on top when `true`. |
| `Title` | `string` | Window title. |
| `OwnerMode` | `DockWindowOwnerMode` | Owner resolution mode for the host window. |
Expand All @@ -72,7 +73,7 @@ For setup details see the [Managed windows guide](dock-managed-windows-guide.md)
| `OnMoveDragBegin()` | `bool` | Drag begin callback (return `false` to cancel). |
| `OnMoveDrag()` | `void` | Drag in progress callback. |
| `OnMoveDragEnd()` | `void` | Drag end callback. |
| `Save()` | `void` | Persist size/position into the model. |
| `Save()` | `void` | Persist size/position/window state into the model. |
| `Present(bool)` | `void` | Show the window. |
| `Exit()` | `void` | Close the window. |
| `SetActive()` | `void` | Activate the window. |
Expand All @@ -90,6 +91,7 @@ For setup details see the [Managed windows guide](dock-managed-windows-guide.md)
| `Exit()` | `void` | Close the host window. |
| `SetPosition/GetPosition` | `void` | Read/write the host position. |
| `SetSize/GetSize` | `void` | Read/write the host size. |
| `SetWindowState/GetWindowState` | `void` / `DockWindowState` | Read/write the host window state. |
| `SetTitle` | `void` | Update the host title. |
| `SetLayout` | `void` | Assign the hosted layout. |
| `SetActive` | `void` | Activate the host window. |
Expand Down
73 changes: 73 additions & 0 deletions src/Dock.Avalonia/Controls/HostWindow.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,10 @@ public class HostWindow : Window, IHostWindow
private List<Control> _chromeGrips = new();
private HostWindowTitleBar? _hostWindowTitleBar;
private bool _mouseDown, _draggingWindow;
private double _normalX = double.NaN;
private double _normalY = double.NaN;
private double _normalWidth = double.NaN;
private double _normalHeight = double.NaN;

/// <summary>
/// Define <see cref="IsToolWindow"/> property.
Expand Down Expand Up @@ -214,6 +218,7 @@ private void HostWindow_PositionChanged(object? sender, PixelPointEventArgs e)
{
if (Window is { } && IsTracked)
{
CaptureNormalBounds();
Window.Save();

if (_mouseDown)
Expand Down Expand Up @@ -267,6 +272,7 @@ private void HostWindow_LayoutUpdated(object? sender, EventArgs e)
{
if (Window is { } && IsTracked)
{
CaptureNormalBounds();
Window.Save();
}
}
Expand Down Expand Up @@ -375,6 +381,10 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
{
UpdatePseudoClasses(IsToolWindow, ToolChromeControlsWholeWindow, change.GetNewValue<bool>());
}
else if (change.Property == WindowStateProperty && Window is { } && IsTracked)
{
Window.Save();
}
}

private void UpdatePseudoClasses(bool isToolWindow, bool toolChromeControlsWholeWindow, bool documentChromeControlsWholeWindow)
Expand Down Expand Up @@ -659,12 +669,21 @@ public void SetPosition(double x, double y)
if (!double.IsNaN(x) && !double.IsNaN(y))
{
Position = new PixelPoint((int)x, (int)y);
_normalX = x;
_normalY = y;
}
}

/// <inheritdoc/>
public void GetPosition(out double x, out double y)
{
if (WindowState != WindowState.Normal && TryGetNormalBounds(out var normalX, out var normalY, out _, out _))
{
x = normalX;
y = normalY;
return;
}

x = Position.X;
y = Position.Y;
}
Expand All @@ -675,21 +694,42 @@ public void SetSize(double width, double height)
if (!double.IsNaN(width))
{
Width = width;
_normalWidth = width;
}

if (!double.IsNaN(height))
{
Height = height;
_normalHeight = height;
}
}

/// <inheritdoc/>
public void GetSize(out double width, out double height)
{
if (WindowState != WindowState.Normal && TryGetNormalBounds(out _, out _, out var normalWidth, out var normalHeight))
{
width = normalWidth;
height = normalHeight;
return;
}

width = Width;
height = Height;
}

/// <inheritdoc/>
public void SetWindowState(DockWindowState windowState)
{
WindowState = DockWindowStateHelper.ToAvaloniaWindowState(windowState);
}

/// <inheritdoc/>
public DockWindowState GetWindowState()
{
return DockWindowStateHelper.ToDockWindowState(WindowState);
}

/// <inheritdoc/>
public void SetTitle(string? title)
{
Expand All @@ -707,6 +747,39 @@ public void SetLayout(IDock layout)
DataContext = layout;
}

private void CaptureNormalBounds()
{
if (WindowState != WindowState.Normal)
{
return;
}

if (double.IsNaN(Width) || double.IsNaN(Height))
{
return;
}

_normalX = Position.X;
_normalY = Position.Y;
_normalWidth = Width;
_normalHeight = Height;
}

private bool TryGetNormalBounds(out double x, out double y, out double width, out double height)
{
x = _normalX;
y = _normalY;
width = _normalWidth;
height = _normalHeight;

return !double.IsNaN(x)
&& !double.IsNaN(y)
&& !double.IsNaN(width)
&& !double.IsNaN(height)
&& width > 0
&& height > 0;
}

void IHostWindow.SetActive()
{
if(WindowState == WindowState.Minimized)
Expand Down
16 changes: 16 additions & 0 deletions src/Dock.Avalonia/Controls/ManagedDockWindowDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
_window = window;
Id = window.Id;
Title = window.Title;
_mdiState = DockWindowStateHelper.ToMdiWindowState(window.WindowState);
Context = window;
AttachWindow(window);
AttachLayout(window.Layout);
Expand Down Expand Up @@ -171,6 +172,14 @@
{
_window.Id = Id;
}
else if (propertyName == nameof(MdiState))
{
var mappedState = DockWindowStateHelper.ToDockWindowState(_mdiState);
if (!(_window.WindowState == DockWindowState.FullScreen && mappedState == DockWindowState.Normal))
{
_window.WindowState = mappedState;
}
}
}

private void AttachWindow(IDockWindow? window)
Expand Down Expand Up @@ -212,6 +221,13 @@
{
UpdateTitleFromLayout();
}

return;
}

if (e.PropertyName == nameof(IDockWindow.WindowState) && _window is { } window)
{
MdiState = DockWindowStateHelper.ToMdiWindowState(window.WindowState);
}
}

Expand Down Expand Up @@ -255,7 +271,7 @@
var focusedTitle = focusedDockable?.Title;
if (!string.IsNullOrEmpty(focusedTitle))
{
Title = focusedTitle;

Check warning on line 274 in src/Dock.Avalonia/Controls/ManagedDockWindowDocument.cs

View workflow job for this annotation

GitHub Actions / Test macos-latest

Possible null reference assignment.

Check warning on line 274 in src/Dock.Avalonia/Controls/ManagedDockWindowDocument.cs

View workflow job for this annotation

GitHub Actions / pack

Possible null reference assignment.
return;
}

Expand Down
41 changes: 41 additions & 0 deletions src/Dock.Avalonia/Controls/ManagedHostWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ public sealed class ManagedHostWindow : IHostWindow
private double _y;
private double _width = 400;
private double _height = 300;
private DockWindowState _windowState = DockWindowState.Normal;
private bool _closed;
private bool _lastCloseCanceled;

Expand Down Expand Up @@ -81,11 +82,16 @@ public void Present(bool isDialog)
_document = new ManagedDockWindowDocument(Window);
_document.Title = _title ?? _document.Title;
_document.MdiBounds = new DockRect(_x, _y, _width, _height);
_document.MdiState = DockWindowStateHelper.ToMdiWindowState(_windowState);
if (_layout is { } root)
{
_document.Content = ManagedDockWindowDocumentContent.Create(root);
}
}
else
{
_document.MdiState = DockWindowStateHelper.ToMdiWindowState(_windowState);
}

_dock.AddWindow(_document);
Window.Host = this;
Expand Down Expand Up @@ -211,6 +217,36 @@ public void GetSize(out double width, out double height)
height = _height;
}

/// <inheritdoc />
public void SetWindowState(DockWindowState windowState)
{
_windowState = windowState;
if (Window is { } window)
{
window.WindowState = windowState;
}

if (_document is not null)
{
_document.MdiState = DockWindowStateHelper.ToMdiWindowState(windowState);
}
}

/// <inheritdoc />
public DockWindowState GetWindowState()
{
if (_document is not null)
{
var mappedState = DockWindowStateHelper.ToDockWindowState(_document.MdiState);
if (!(_windowState == DockWindowState.FullScreen && mappedState == DockWindowState.Normal))
{
_windowState = mappedState;
}
}

return _windowState;
}

/// <inheritdoc />
public void SetTitle(string? title)
{
Expand All @@ -236,6 +272,11 @@ public void SetActive()
{
if (_dock is { } && _document is { })
{
if (_document.MdiState == MdiWindowState.Minimized)
{
_document.MdiState = MdiWindowState.Normal;
}

_dock.ActiveDockable = _document;
}
}
Expand Down
51 changes: 51 additions & 0 deletions src/Dock.Avalonia/Internal/DockWindowStateHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (c) Wiesław Šoltés. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
using Avalonia.Controls;
using Dock.Model.Core;

namespace Dock.Avalonia.Internal;

internal static class DockWindowStateHelper
{
public static DockWindowState ToDockWindowState(WindowState windowState)
{
return windowState switch
{
WindowState.Minimized => DockWindowState.Minimized,
WindowState.Maximized => DockWindowState.Maximized,
WindowState.FullScreen => DockWindowState.FullScreen,
_ => DockWindowState.Normal
};
}

public static WindowState ToAvaloniaWindowState(DockWindowState windowState)
{
return windowState switch
{
DockWindowState.Minimized => WindowState.Minimized,
DockWindowState.Maximized => WindowState.Maximized,
DockWindowState.FullScreen => WindowState.FullScreen,
_ => WindowState.Normal
};
}

public static DockWindowState ToDockWindowState(MdiWindowState mdiWindowState)
{
return mdiWindowState switch
{
MdiWindowState.Minimized => DockWindowState.Minimized,
MdiWindowState.Maximized => DockWindowState.Maximized,
_ => DockWindowState.Normal
};
}

public static MdiWindowState ToMdiWindowState(DockWindowState windowState)
{
return windowState switch
{
DockWindowState.Minimized => MdiWindowState.Minimized,
DockWindowState.Maximized => MdiWindowState.Maximized,
_ => MdiWindowState.Normal
};
}
}
Loading
Loading