diff --git a/samples/BrowserTabTheme/App.axaml.cs b/samples/BrowserTabTheme/App.axaml.cs
index e1c9cca1a..193250d1a 100644
--- a/samples/BrowserTabTheme/App.axaml.cs
+++ b/samples/BrowserTabTheme/App.axaml.cs
@@ -10,6 +10,8 @@ namespace BrowserTabTheme;
public partial class App : Application
{
+ private MainWindowViewModel? _mainWindowViewModel;
+
public override void Initialize()
{
#if DOCK_USE_GENERATED_APP_INITIALIZE_COMPONENT
@@ -66,15 +68,21 @@ public override void OnFrameworkInitializationCompleted()
factory.InitLayout(rootDock);
- var mainWindow = new MainWindow();
- mainWindow.DockControl.Factory = factory;
- mainWindow.DockControl.Layout = rootDock;
- mainWindow.DockControl.InitializeFactory = true;
- mainWindow.DockControl.InitializeLayout = false;
-
+ _mainWindowViewModel = new MainWindowViewModel(factory, rootDock);
+ var mainWindow = new MainWindow
+ {
+ DataContext = _mainWindowViewModel
+ };
desktop.MainWindow = mainWindow;
+ desktop.Exit += (_, _) => DisposeMainWindowViewModel();
}
base.OnFrameworkInitializationCompleted();
}
+
+ private void DisposeMainWindowViewModel()
+ {
+ _mainWindowViewModel?.Dispose();
+ _mainWindowViewModel = null;
+ }
}
diff --git a/samples/BrowserTabTheme/BrowserTabTheme.csproj b/samples/BrowserTabTheme/BrowserTabTheme.csproj
index 34afb3071..7bff107fd 100644
--- a/samples/BrowserTabTheme/BrowserTabTheme.csproj
+++ b/samples/BrowserTabTheme/BrowserTabTheme.csproj
@@ -16,6 +16,7 @@
+
@@ -25,5 +26,11 @@
-
+
+
+ all
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+
+
+
diff --git a/samples/BrowserTabTheme/MainWindow.axaml b/samples/BrowserTabTheme/MainWindow.axaml
index 598998b09..4a487570a 100644
--- a/samples/BrowserTabTheme/MainWindow.axaml
+++ b/samples/BrowserTabTheme/MainWindow.axaml
@@ -1,13 +1,20 @@
-
+
diff --git a/samples/BrowserTabTheme/MainWindowViewModel.cs b/samples/BrowserTabTheme/MainWindowViewModel.cs
new file mode 100644
index 000000000..805bcdf78
--- /dev/null
+++ b/samples/BrowserTabTheme/MainWindowViewModel.cs
@@ -0,0 +1,155 @@
+// Copyright (c) Wiesław Šoltés. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for details.
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.ComponentModel;
+using Dock.Model.Controls;
+using Dock.Model.Core;
+using ReactiveUI;
+using ReactiveUI.SourceGenerators;
+
+namespace BrowserTabTheme;
+
+///
+/// Coordinates the browser sample layout and its main-window chrome state.
+///
+public sealed partial class MainWindowViewModel : ReactiveObject, IDisposable
+{
+ private readonly HashSet _documentDocks = new();
+ private readonly HashSet _dockCollections = new();
+ private bool _isDisposed;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The layout factory.
+ /// The main root layout.
+ public MainWindowViewModel(IFactory factory, IRootDock layout)
+ {
+ Factory = factory ?? throw new ArgumentNullException(nameof(factory));
+ Layout = layout ?? throw new ArgumentNullException(nameof(layout));
+ ExtendClientAreaToDecorationsHint = true;
+ RefreshSubscriptions();
+ }
+
+ ///
+ /// Gets the layout factory used by the dock control.
+ ///
+ public IFactory Factory { get; }
+
+ ///
+ /// Gets the main root layout.
+ ///
+ public IRootDock Layout { get; }
+
+ ///
+ /// Gets whether content should extend into the main window decorations.
+ ///
+ [Reactive]
+ public partial bool ExtendClientAreaToDecorationsHint { get; private set; }
+
+ ///
+ public void Dispose()
+ {
+ if (_isDisposed)
+ {
+ return;
+ }
+
+ _isDisposed = true;
+ ClearSubscriptions();
+ }
+
+ private void RefreshSubscriptions()
+ {
+ ClearSubscriptions();
+
+ var pending = new Stack();
+ var visited = new HashSet();
+ pending.Push(Layout);
+
+ while (pending.Count > 0)
+ {
+ var dockable = pending.Pop();
+ if (!visited.Add(dockable))
+ {
+ continue;
+ }
+
+ if (dockable is IDocumentDock documentDock)
+ {
+ _documentDocks.Add(documentDock);
+ if (documentDock is INotifyPropertyChanged notifyingDocumentDock)
+ {
+ notifyingDocumentDock.PropertyChanged += OnDocumentDockPropertyChanged;
+ }
+ }
+
+ if (dockable is not IDock { VisibleDockables: { } visibleDockables })
+ {
+ continue;
+ }
+
+ if (visibleDockables is INotifyCollectionChanged notifyingCollection
+ && _dockCollections.Add(notifyingCollection))
+ {
+ notifyingCollection.CollectionChanged += OnDockCollectionChanged;
+ }
+
+ for (var index = visibleDockables.Count - 1; index >= 0; index--)
+ {
+ pending.Push(visibleDockables[index]);
+ }
+ }
+
+ UpdateChromeState();
+ }
+
+ private void ClearSubscriptions()
+ {
+ foreach (var documentDock in _documentDocks)
+ {
+ if (documentDock is INotifyPropertyChanged notifyingDocumentDock)
+ {
+ notifyingDocumentDock.PropertyChanged -= OnDocumentDockPropertyChanged;
+ }
+ }
+
+ foreach (var dockCollection in _dockCollections)
+ {
+ dockCollection.CollectionChanged -= OnDockCollectionChanged;
+ }
+
+ _documentDocks.Clear();
+ _dockCollections.Clear();
+ }
+
+ private void OnDockCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
+ {
+ RefreshSubscriptions();
+ }
+
+ private void OnDocumentDockPropertyChanged(object? sender, PropertyChangedEventArgs e)
+ {
+ if (string.IsNullOrEmpty(e.PropertyName) || e.PropertyName == nameof(IDocumentDock.LayoutMode))
+ {
+ UpdateChromeState();
+ }
+ }
+
+ private void UpdateChromeState()
+ {
+ foreach (var documentDock in _documentDocks)
+ {
+ if (documentDock.LayoutMode == DocumentLayoutMode.Mdi)
+ {
+ ExtendClientAreaToDecorationsHint = false;
+ return;
+ }
+ }
+
+ ExtendClientAreaToDecorationsHint = true;
+ }
+}
diff --git a/samples/BrowserTabTheme/README.md b/samples/BrowserTabTheme/README.md
index 337efd0cc..af8fbdd8a 100644
--- a/samples/BrowserTabTheme/README.md
+++ b/samples/BrowserTabTheme/README.md
@@ -6,7 +6,11 @@ This sample backports a browser-tab visual style from StackWich into the origina
- Browser-like document and tool tab visuals.
- Browser-style window chrome driven by Avalonia drawn decorations and Dock theme resources.
-- Dock drag/drop, float, pin, and document creation behavior preserved.
+- Dock drag/drop, including dropping a tab onto a side target to create a new
+ horizontal or vertical document dock.
+- Native main-window chrome while document layout mode is MDI, keeping maximized
+ MDI children clear of the operating-system caption controls.
+- Float, pin, and document creation behavior preserved.
- Theme dictionaries for both Light (`Default`) and Dark variants.
## What is intentionally excluded
diff --git a/src/Dock.Avalonia.Themes.Browser/Styles/BrowserTabAccents.axaml b/src/Dock.Avalonia.Themes.Browser/Styles/BrowserTabAccents.axaml
index 4c0b15f7e..ec2495c34 100644
--- a/src/Dock.Avalonia.Themes.Browser/Styles/BrowserTabAccents.axaml
+++ b/src/Dock.Avalonia.Themes.Browser/Styles/BrowserTabAccents.axaml
@@ -165,7 +165,9 @@
False
True
True
- True
+
+ False
False
6
diff --git a/tests/Dock.Avalonia.HeadlessTests/BrowserTabThemeMainWindowViewModelTests.cs b/tests/Dock.Avalonia.HeadlessTests/BrowserTabThemeMainWindowViewModelTests.cs
new file mode 100644
index 000000000..ff8f646d4
--- /dev/null
+++ b/tests/Dock.Avalonia.HeadlessTests/BrowserTabThemeMainWindowViewModelTests.cs
@@ -0,0 +1,49 @@
+// Copyright (c) Wiesław Šoltés. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for details.
+
+using BrowserTabTheme;
+using Dock.Model.Controls;
+using Dock.Model.Core;
+using Dock.Model.Mvvm;
+using Xunit;
+
+namespace Dock.Avalonia.HeadlessTests;
+
+public class BrowserTabThemeMainWindowViewModelTests
+{
+ [Fact]
+ public void ChromeStateTracksAllCurrentDocumentDocks()
+ {
+ var factory = new Factory();
+ var root = factory.CreateRootDock();
+ root.VisibleDockables = factory.CreateList();
+ var proportionalDock = factory.CreateProportionalDock();
+ proportionalDock.VisibleDockables = factory.CreateList();
+ var firstDocumentDock = factory.CreateDocumentDock();
+ firstDocumentDock.VisibleDockables = factory.CreateList();
+
+ factory.AddDockable(proportionalDock, firstDocumentDock);
+ factory.AddDockable(root, proportionalDock);
+ factory.InitLayout(root);
+
+ using var viewModel = new MainWindowViewModel(factory, root);
+
+ Assert.True(viewModel.ExtendClientAreaToDecorationsHint);
+
+ var secondDocumentDock = factory.CreateDocumentDock();
+ secondDocumentDock.VisibleDockables = factory.CreateList();
+ factory.AddDockable(proportionalDock, secondDocumentDock);
+ secondDocumentDock.LayoutMode = DocumentLayoutMode.Mdi;
+
+ Assert.False(viewModel.ExtendClientAreaToDecorationsHint);
+
+ firstDocumentDock.LayoutMode = DocumentLayoutMode.Mdi;
+ factory.RemoveDockable(secondDocumentDock, false);
+
+ Assert.False(viewModel.ExtendClientAreaToDecorationsHint);
+
+ firstDocumentDock.LayoutMode = DocumentLayoutMode.Tabbed;
+
+ Assert.True(viewModel.ExtendClientAreaToDecorationsHint);
+ }
+}
diff --git a/tests/Dock.Avalonia.HeadlessTests/Dock.Avalonia.HeadlessTests.csproj b/tests/Dock.Avalonia.HeadlessTests/Dock.Avalonia.HeadlessTests.csproj
index 6dbeea9f8..bfc6ad27c 100644
--- a/tests/Dock.Avalonia.HeadlessTests/Dock.Avalonia.HeadlessTests.csproj
+++ b/tests/Dock.Avalonia.HeadlessTests/Dock.Avalonia.HeadlessTests.csproj
@@ -16,6 +16,7 @@
+
diff --git a/tests/Dock.Avalonia.Themes.UnitTests/DocumentTabStripItemAndThemeTests.cs b/tests/Dock.Avalonia.Themes.UnitTests/DocumentTabStripItemAndThemeTests.cs
index a42d8224c..0feabd05b 100644
--- a/tests/Dock.Avalonia.Themes.UnitTests/DocumentTabStripItemAndThemeTests.cs
+++ b/tests/Dock.Avalonia.Themes.UnitTests/DocumentTabStripItemAndThemeTests.cs
@@ -1,6 +1,8 @@
+using Avalonia.Controls;
using Avalonia.Headless.XUnit;
using Avalonia.Styling;
using Dock.Avalonia.Controls;
+using Dock.Avalonia.Themes.Browser;
using Dock.Avalonia.Themes.Fluent;
using Dock.Avalonia.Themes.Simple;
using Xunit;
@@ -36,4 +38,17 @@ public void DockSimpleTheme_Can_Instantiate()
Styles theme = new DockSimpleTheme();
Assert.NotNull(theme);
}
+
+ [AvaloniaFact]
+ public void BrowserTabTheme_Enables_Full_Document_Dock_Selector()
+ {
+ var theme = new BrowserTabTheme();
+ var resourceNode = Assert.IsAssignableFrom(theme);
+
+ Assert.True(resourceNode.TryGetResource(
+ "DockDocumentControlShowDockIndicatorOnly",
+ ThemeVariant.Default,
+ out var resource));
+ Assert.False(Assert.IsType(resource));
+ }
}