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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ A docking layout system.
**Key Features:**
- **ItemsSource Support**: Bind document collections directly to DocumentDock for automatic document management
- **Flexible Content Templates**: Use DocumentTemplate for customizable document content rendering
- **Optional Document Content Caching**: Keep document views alive across tab switches via theme option (`CacheDocumentTabContent`)
- **Multiple MVVM Frameworks**: Support for ReactiveUI, Prism, ReactiveProperty, and standard MVVM patterns
- **Comprehensive Serialization**: Save and restore layouts with multiple format options (JSON, XML, YAML, Protobuf)
- **Rich Theming**: Fluent and Simple themes with full customization support
Expand Down
10 changes: 9 additions & 1 deletion docfx/articles/dock-theme-browser.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Reference the browser theme project/package and use it in `App.axaml`:
<Application.Styles>
<FluentTheme />
<DockFluentTheme />
<browserTheme:BrowserTabTheme />
<browserTheme:BrowserTabTheme CacheDocumentTabContent="True" />
</Application.Styles>
</Application>
```
Expand All @@ -38,6 +38,14 @@ Compact density dictionary:

- `avares://Dock.Avalonia.Themes.Browser/Styles/DensityStyles/Compact.axaml`

## Document tab content caching

`BrowserTabTheme` exposes `CacheDocumentTabContent` to keep document views alive while switching tabs:

```xaml
<browserTheme:BrowserTabTheme CacheDocumentTabContent="True" />
```

## Theme resources

Primary dictionaries:
Expand Down
10 changes: 10 additions & 0 deletions docfx/articles/dock-theme-fluent.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ Compact density resource dictionary:

Compact mode reduces tab/button/icon metrics by overriding density tokens (for example `DockTabItemMinHeight`, `DockCloseButtonSize`, `DockChromeButtonWidth`).

## Document tab content caching

`DockFluentTheme` can keep document tab content alive instead of recreating it on each tab switch:

```xaml
<dockFluent:DockFluentTheme CacheDocumentTabContent="True" />
```

When enabled, document views remain instantiated while hidden tabs are inactive, which can improve tab-switch latency for heavy views.

## Fluent token customization

Customize Dock Fluent visuals by overriding semantic tokens after `DockFluentTheme`.
Expand Down
10 changes: 10 additions & 0 deletions docfx/articles/dock-theme-simple.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,16 @@ Compact density resource dictionary:

- `avares://Dock.Avalonia.Themes.Simple/DensityStyles/Compact.axaml`

## Document tab content caching

`DockSimpleTheme` can keep document tab content alive instead of recreating it on each tab switch:

```xaml
<dockSimple:DockSimpleTheme CacheDocumentTabContent="True" />
```

This uses the shared document control templates and keeps hidden tab views instantiated.

## Simple token customization

Override semantic tokens after `DockSimpleTheme`:
Expand Down
61 changes: 61 additions & 0 deletions src/Dock.Avalonia.Themes.Browser/Styles/BrowserTabTheme.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@
using System.Collections.Generic;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
using Dock.Avalonia.Controls;
using Dock.Avalonia.Themes;

namespace Dock.Avalonia.Themes.Browser;
Expand All @@ -15,6 +18,8 @@ public class BrowserTabTheme : Styles, IResourceNode
{
private readonly ResourceDictionary _compactStyles;
private DockDensityStyle _densityStyle;
private bool _cacheDocumentTabContent;
private Style? _cachedDocumentTemplateOverrideStyle;

/// <summary>
/// Initializes a new instance of the <see cref="BrowserTabTheme"/> class.
Expand All @@ -24,6 +29,7 @@ public BrowserTabTheme(IServiceProvider? serviceProvider = null)
{
AvaloniaXamlLoader.Load(serviceProvider, this);
_compactStyles = (ResourceDictionary)GetAndRemove("CompactStyles");
UpdateDocumentTemplateOverride();

object GetAndRemove(string key)
{
Expand All @@ -42,6 +48,15 @@ object GetAndRemove(string key)
o => o.DensityStyle,
(o, v) => o.DensityStyle = v);

/// <summary>
/// Backing direct property for <see cref="CacheDocumentTabContent"/>.
/// </summary>
public static readonly DirectProperty<BrowserTabTheme, bool> CacheDocumentTabContentProperty =
AvaloniaProperty.RegisterDirect<BrowserTabTheme, bool>(
nameof(CacheDocumentTabContent),
o => o.CacheDocumentTabContent,
(o, v) => o.CacheDocumentTabContent = v);

/// <summary>
/// Gets or sets the density mode used by the browser theme resources.
/// </summary>
Expand All @@ -51,6 +66,15 @@ public DockDensityStyle DensityStyle
set => SetAndRaise(DensityStyleProperty, ref _densityStyle, value);
}

/// <summary>
/// Gets or sets whether document tab content should be cached in an items host instead of recreated.
/// </summary>
public bool CacheDocumentTabContent
{
get => _cacheDocumentTabContent;
set => SetAndRaise(CacheDocumentTabContentProperty, ref _cacheDocumentTabContent, value);
}

/// <summary>
/// Notifies hosted resources when density changes so consumers can refresh theme resources.
/// </summary>
Expand All @@ -63,6 +87,12 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
{
Owner?.NotifyHostedResourcesChanged(new ResourcesChangedEventArgs());
}

if (change.Property == CacheDocumentTabContentProperty)
{
UpdateDocumentTemplateOverride();
Owner?.NotifyHostedResourcesChanged(new ResourcesChangedEventArgs());
}
}

bool IResourceNode.TryGetResource(object key, ThemeVariant? theme, out object? value)
Expand All @@ -74,4 +104,35 @@ bool IResourceNode.TryGetResource(object key, ThemeVariant? theme, out object? v

return base.TryGetResource(key, theme, out value);
}

private void UpdateDocumentTemplateOverride()
{
if (!_cacheDocumentTabContent)
{
if (_cachedDocumentTemplateOverrideStyle is not null)
{
Remove(_cachedDocumentTemplateOverrideStyle);
_cachedDocumentTemplateOverrideStyle = null;
}

return;
}

if (!base.TryGetResource("DockDocumentControlCachedContentTemplate", null, out var templateObject)
|| templateObject is not IControlTemplate template)
{
return;
}

if (_cachedDocumentTemplateOverrideStyle is null)
{
_cachedDocumentTemplateOverrideStyle = new Style(x => x.OfType<DocumentControl>());
_cachedDocumentTemplateOverrideStyle.Setters.Add(new Setter(TemplatedControl.TemplateProperty, template));
Add(_cachedDocumentTemplateOverrideStyle);
return;
}

_cachedDocumentTemplateOverrideStyle.Setters.Clear();
_cachedDocumentTemplateOverrideStyle.Setters.Add(new Setter(TemplatedControl.TemplateProperty, template));
}
}
177 changes: 122 additions & 55 deletions src/Dock.Avalonia.Themes.Fluent/Controls/DocumentControl.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,127 @@
</Style>
</ControlTheme>

<ControlTemplate x:Key="DockDocumentControlSingleContentTemplate" TargetType="DocumentControl">
<DockPanel x:Name="PART_DockPanel"
DockProperties.IsDropArea="True"
DockProperties.IsDockTarget="True"
DockProperties.ShowDockIndicatorOnly="{DynamicResource DockDocumentControlShowDockIndicatorOnly}"
DockProperties.IndicatorDockOperation="{DynamicResource DockDocumentControlIndicatorDockOperation}"
VerticalSpacing="{DynamicResource DockDocumentControlVerticalSpacing}"
Background="Transparent"
ZIndex="1">
<DocumentTabStrip x:Name="PART_TabStrip"
ItemsSource="{Binding VisibleDockables}"
SelectedItem="{Binding ActiveDockable, Mode=TwoWay}"
CanCreateItem="{Binding CanCreateDocument}"
IsActive="{TemplateBinding IsActive}"
Orientation="{Binding TabsLayout, Converter={x:Static DocumentTabOrientationConverter.Instance}}"
DockPanel.Dock="{Binding TabsLayout, Converter={x:Static DocumentTabDockConverter.Instance}}"
DockProperties.IsDropArea="True"
DockProperties.DockAdornerHost="{Binding #PART_DockPanel}">
<DocumentTabStrip.Styles>
<Style Selector="DocumentTabStripItem">
<Setter Property="IsActive" Value="{Binding $parent[DocumentTabStrip].IsActive}" />
</Style>
</DocumentTabStrip.Styles>
</DocumentTabStrip>
<Panel x:Name="PART_DocumentSeperatorHost" IsVisible="{DynamicResource DockDocumentTabStripSeparatorVisible}">
<Panel x:Name="PART_DocumentSeperator"
IsVisible="{Binding #PART_TabStrip.IsVisible}" />
</Panel>
<Border x:Name="PART_Border">
<Grid>
<DockableControl DataContext="{Binding ActiveDockable}"
TrackingMode="Visible"
IsVisible="{Binding $parent[DocumentControl].HasVisibleDockables}">
<ContentControl x:Name="PART_ContentPresenter"
Content="{Binding}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<ContentControl.ContentTemplate>
<ControlRecyclingDataTemplate Parent="{Binding #PART_ContentPresenter}" />
</ContentControl.ContentTemplate>
</ContentControl>
</DockableControl>

<ContentControl x:Name="PART_EmptyContentHost"
Content="{Binding EmptyContent}"
ContentTemplate="{Binding $parent[DocumentControl].EmptyContentTemplate}"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
IsVisible="{Binding !$parent[DocumentControl].HasVisibleDockables}" />
</Grid>
</Border>
</DockPanel>
</ControlTemplate>

<ControlTemplate x:Key="DockDocumentControlCachedContentTemplate" TargetType="DocumentControl">
<DockPanel x:Name="PART_DockPanel"
DockProperties.IsDropArea="True"
DockProperties.IsDockTarget="True"
DockProperties.ShowDockIndicatorOnly="{DynamicResource DockDocumentControlShowDockIndicatorOnly}"
DockProperties.IndicatorDockOperation="{DynamicResource DockDocumentControlIndicatorDockOperation}"
VerticalSpacing="{DynamicResource DockDocumentControlVerticalSpacing}"
Background="Transparent"
ZIndex="1">
<DocumentTabStrip x:Name="PART_TabStrip"
ItemsSource="{Binding VisibleDockables}"
SelectedItem="{Binding ActiveDockable, Mode=TwoWay}"
CanCreateItem="{Binding CanCreateDocument}"
IsActive="{TemplateBinding IsActive}"
Orientation="{Binding TabsLayout, Converter={x:Static DocumentTabOrientationConverter.Instance}}"
DockPanel.Dock="{Binding TabsLayout, Converter={x:Static DocumentTabDockConverter.Instance}}"
DockProperties.IsDropArea="True"
DockProperties.DockAdornerHost="{Binding #PART_DockPanel}">
<DocumentTabStrip.Styles>
<Style Selector="DocumentTabStripItem">
<Setter Property="IsActive" Value="{Binding $parent[DocumentTabStrip].IsActive}" />
</Style>
</DocumentTabStrip.Styles>
</DocumentTabStrip>
<Panel x:Name="PART_DocumentSeperatorHost" IsVisible="{DynamicResource DockDocumentTabStripSeparatorVisible}">
<Panel x:Name="PART_DocumentSeperator"
IsVisible="{Binding #PART_TabStrip.IsVisible}" />
</Panel>
<Border x:Name="PART_Border">
<Grid>
<ItemsControl x:Name="PART_CachedContentHost"
ItemsSource="{Binding VisibleDockables}"
IsVisible="{Binding $parent[DocumentControl].HasVisibleDockables}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Grid />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="core:IDockable">
<DockableControl TrackingMode="Visible">
<DockableControl.IsVisible>
<MultiBinding Converter="{x:Static converters:ReferenceEqualsMultiConverter.Instance}">
<Binding />
<Binding Path="((core:IDock)Owner).ActiveDockable" />
</MultiBinding>
</DockableControl.IsVisible>
<ContentControl x:Name="PART_CachedContentPresenter"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Content="{Binding}" />
</DockableControl>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>

<ContentControl x:Name="PART_EmptyContentHost"
Content="{Binding EmptyContent}"
ContentTemplate="{Binding $parent[DocumentControl].EmptyContentTemplate}"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
IsVisible="{Binding !$parent[DocumentControl].HasVisibleDockables}" />
</Grid>
</Border>
</DockPanel>
</ControlTemplate>

<ControlTheme x:Key="{x:Type DocumentControl}" TargetType="DocumentControl">

<Setter Property="(DockProperties.IsDragEnabled)">
Expand Down Expand Up @@ -108,61 +229,7 @@
</DataTemplate>
</Setter>

<Setter Property="Template">
<ControlTemplate>
<DockPanel x:Name="PART_DockPanel"
DockProperties.IsDropArea="True"
DockProperties.IsDockTarget="True"
DockProperties.ShowDockIndicatorOnly="{DynamicResource DockDocumentControlShowDockIndicatorOnly}"
DockProperties.IndicatorDockOperation="{DynamicResource DockDocumentControlIndicatorDockOperation}"
VerticalSpacing="{DynamicResource DockDocumentControlVerticalSpacing}"
Background="Transparent"
ZIndex="1">
<DocumentTabStrip x:Name="PART_TabStrip"
ItemsSource="{Binding VisibleDockables}"
SelectedItem="{Binding ActiveDockable, Mode=TwoWay}"
CanCreateItem="{Binding CanCreateDocument}"
IsActive="{TemplateBinding IsActive}"
Orientation="{Binding TabsLayout, Converter={x:Static DocumentTabOrientationConverter.Instance}}"
DockPanel.Dock="{Binding TabsLayout, Converter={x:Static DocumentTabDockConverter.Instance}}"
DockProperties.IsDropArea="True"
DockProperties.DockAdornerHost="{Binding #PART_DockPanel}">
<DocumentTabStrip.Styles>
<Style Selector="DocumentTabStripItem">
<Setter Property="IsActive" Value="{Binding $parent[DocumentTabStrip].IsActive}" />
</Style>
</DocumentTabStrip.Styles>
</DocumentTabStrip>
<Panel x:Name="PART_DocumentSeperatorHost" IsVisible="{DynamicResource DockDocumentTabStripSeparatorVisible}">
<Panel x:Name="PART_DocumentSeperator"
IsVisible="{Binding #PART_TabStrip.IsVisible}" />
</Panel>
<Border x:Name="PART_Border">
<Grid>
<DockableControl DataContext="{Binding ActiveDockable}"
TrackingMode="Visible"
IsVisible="{Binding $parent[DocumentControl].HasVisibleDockables}">
<ContentControl x:Name="PART_ContentPresenter"
Content="{Binding}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch">
<ContentControl.ContentTemplate>
<ControlRecyclingDataTemplate Parent="{Binding #PART_ContentPresenter}" />
</ContentControl.ContentTemplate>
</ContentControl>
</DockableControl>

<ContentControl x:Name="PART_EmptyContentHost"
Content="{Binding EmptyContent}"
ContentTemplate="{Binding $parent[DocumentControl].EmptyContentTemplate}"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
IsVisible="{Binding !$parent[DocumentControl].HasVisibleDockables}" />
</Grid>
</Border>
</DockPanel>
</ControlTemplate>
</Setter>
<Setter Property="Template" Value="{StaticResource DockDocumentControlSingleContentTemplate}" />

<Style Selector="^[TabsLayout=Top]/template/ Panel#PART_DocumentSeperatorHost">
<Setter Property="DockPanel.Dock" Value="Top" />
Expand Down
Loading
Loading