Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
5 changes: 4 additions & 1 deletion samples/DockReactiveUICanonicalSample/App.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,10 @@ private void RegisterDockableTemplate()
{
if (existing is ReactiveUI.Avalonia.ViewModelViewHost existingHost)
{
existingHost.ViewModel = item;
if (!ReferenceEquals(existingHost.ViewModel, item))
{
existingHost.ViewModel = item;
}
return existingHost;
}

Expand Down
53 changes: 47 additions & 6 deletions src/Dock.Avalonia/Controls/DockControl.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Recycling;
using Avalonia.Controls.Recycling.Model;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
Expand Down Expand Up @@ -36,6 +38,7 @@
[TemplatePart("PART_ManagedWindowLayer", typeof(ManagedWindowLayer))]
public class DockControl : TemplatedControl, IDockControl, IDockSelectorService
{
private static readonly ConditionalWeakTable<IFactory, IControlRecycling> s_controlRecycling = new();
private readonly DockManagerOptions _dockManagerOptions;
private readonly DockManager _dockManager;
private readonly DockControlState _dockControlState;
Expand Down Expand Up @@ -269,16 +272,53 @@

private void InitializeControlRecycling()
{
var recycling = ControlRecyclingDataTemplate.GetControlRecycling(this);
if (recycling is ControlRecycling shared)
if (Layout?.Factory is not { } factory)
{
var local = new ControlRecycling
return;
}

var controlRecycling = ControlRecyclingDataTemplate.GetControlRecycling(this);
if (controlRecycling is null)
{
return;
}

if (s_controlRecycling.TryGetValue(factory, out var shared))
{
if (ReferenceEquals(shared, controlRecycling))
{
TryToUseIdAsKey = shared.TryToUseIdAsKey
};
return;
}

if (shared is ControlRecycling sharedRecycling && controlRecycling is ControlRecycling localRecycling)
{
if (sharedRecycling.TryToUseIdAsKey != localRecycling.TryToUseIdAsKey)
{
sharedRecycling.TryToUseIdAsKey = localRecycling.TryToUseIdAsKey;
}

ControlRecyclingDataTemplate.SetControlRecycling(this, local);
ControlRecyclingDataTemplate.SetControlRecycling(this, sharedRecycling);
return;
}

if (controlRecycling is ControlRecycling)
{
ControlRecyclingDataTemplate.SetControlRecycling(this, shared);
}

return;
}

if (controlRecycling is ControlRecycling defaultRecycling)
{
controlRecycling = new ControlRecycling
{
TryToUseIdAsKey = defaultRecycling.TryToUseIdAsKey
};
ControlRecyclingDataTemplate.SetControlRecycling(this, controlRecycling);
}

s_controlRecycling.Add(factory, controlRecycling);
}

private void InitializeDefaultDataTemplates()
Expand Down Expand Up @@ -346,6 +386,7 @@

layout.Factory.DockControls.Add(this);

InitializeControlRecycling();
UpdateManagedWindowLayer(layout);

if (InitializeFactory)
Expand All @@ -367,7 +408,7 @@
{
if (HostWindowFactory is { } factory)
{
return factory();

Check warning on line 411 in src/Dock.Avalonia/Controls/DockControl.axaml.cs

View workflow job for this annotation

GitHub Actions / Build ubuntu-latest

Possible null reference return.

Check warning on line 411 in src/Dock.Avalonia/Controls/DockControl.axaml.cs

View workflow job for this annotation

GitHub Actions / Build windows-latest

Possible null reference return.

Check warning on line 411 in src/Dock.Avalonia/Controls/DockControl.axaml.cs

View workflow job for this annotation

GitHub Actions / Build macos-latest

Possible null reference return.

Check warning on line 411 in src/Dock.Avalonia/Controls/DockControl.axaml.cs

View workflow job for this annotation

GitHub Actions / Test ubuntu-latest

Possible null reference return.

Check warning on line 411 in src/Dock.Avalonia/Controls/DockControl.axaml.cs

View workflow job for this annotation

GitHub Actions / Test windows-latest

Possible null reference return.

Check warning on line 411 in src/Dock.Avalonia/Controls/DockControl.axaml.cs

View workflow job for this annotation

GitHub Actions / Test macos-latest

Possible null reference return.

Check warning on line 411 in src/Dock.Avalonia/Controls/DockControl.axaml.cs

View workflow job for this annotation

GitHub Actions / pack

Possible null reference return.
}

return DockSettings.UseManagedWindows
Expand Down
83 changes: 78 additions & 5 deletions src/Dock.Avalonia/Controls/ManagedDockWindowDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@
// Licensed under the MIT license. See LICENSE file in the project root for details.
using System;
using System.ComponentModel;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Markup.Xaml.Templates;
using Avalonia.Media;
using Avalonia.VisualTree;
using Dock.Avalonia.Internal;
using Dock.Model.Controls;
using Dock.Model.Core;
Expand Down Expand Up @@ -130,7 +133,7 @@

public Control? Build(object? data, Control? existing)
{
return BuildContent(Content, this);
return BuildContent(Content, this, existing);
}

public void Dispose()
Expand Down Expand Up @@ -243,7 +246,7 @@
var focusedTitle = focusedDockable?.Title;
if (!string.IsNullOrEmpty(focusedTitle))
{
Title = focusedTitle;

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

View workflow job for this annotation

GitHub Actions / pack

Possible null reference assignment.
return;
}

Expand Down Expand Up @@ -316,7 +319,7 @@
}
}

private static Control? BuildContent(object? content, IDockable dockable)
private static Control? BuildContent(object? content, IDockable dockable, Control? existing)
{
if (DragPreviewContext.IsPreviewing(dockable))
{
Expand All @@ -330,15 +333,85 @@

if (content is Control directControl)
{
return directControl;
return DetachOrFallback(directControl, existing, null);
}

if (content is Func<IServiceProvider, object> direct)
{
return direct(null!) as Control;
return DetachOrFallback(direct(null!) as Control, existing, () => direct(null!) as Control);
}

return TemplateContent.Load(content)?.Result;
return DetachOrFallback(TemplateContent.Load(content)?.Result, existing, () => TemplateContent.Load(content)?.Result);
}

private static Control? DetachOrFallback(Control? control, Control? existing, Func<Control?>? fallbackFactory)
{
if (control is null || ReferenceEquals(control, existing))
{
return control;
}

if (TryDetachFromParent(control))
{
return control;
}

if (fallbackFactory is null)
{
return existing;
}

var fallback = fallbackFactory();
if (fallback is null)
{
return existing;
}

if (ReferenceEquals(fallback, existing))
{
return fallback;
}

return TryDetachFromParent(fallback) ? fallback : existing;
}

private static bool TryDetachFromParent(Control control)
{
var parent = control.Parent ?? control.GetVisualParent();

if (parent is null)
{
return true;
}

switch (parent)
{
case Panel panel:
return panel.Children.Remove(control);
case ContentPresenter presenter:
return TryDetachFromContentPresenter(presenter, control);
case ContentControl contentControl when ReferenceEquals(contentControl.Content, control):
contentControl.SetCurrentValue(ContentControl.ContentProperty, null);
return true;
case Decorator decorator when ReferenceEquals(decorator.Child, control):
decorator.Child = null;
return true;
default:
return false;
}
}

private static bool TryDetachFromContentPresenter(ContentPresenter presenter, Control control)
{
if (!ReferenceEquals(presenter.Child, control))
{
return false;
}

presenter.SetCurrentValue(ContentPresenter.ContentProperty, null);
presenter.UpdateChild();

return control.GetVisualParent() is null;
}

private static Control BuildPreviewContent(object? content)
Expand Down
30 changes: 23 additions & 7 deletions src/Dock.Avalonia/Controls/Overlays/OverlayHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using Avalonia.Controls.Templates;
using Avalonia.Metadata;
using Avalonia.Styling;
using Avalonia.VisualTree;

namespace Dock.Avalonia.Controls.Overlays;

Expand Down Expand Up @@ -250,9 +251,10 @@ private void OnOverlayLayerPropertyChanged(object? sender, AvaloniaPropertyChang

private void RebuildPipeline()
{
if (Content is Control hostedContent && hostedContent.Parent is not null)
if (Content is Control hostedContent)
{
if (!TryDetachControl(hostedContent))
var parent = hostedContent.Parent ?? hostedContent.GetVisualParent();
if (parent is not null && !TryDetachControl(hostedContent))
{
return;
}
Expand Down Expand Up @@ -325,22 +327,36 @@ private void RebuildPipeline()

private static bool TryDetachControl(Control control)
{
if (control.Parent is null)
var parent = control.Parent ?? control.GetVisualParent();
if (parent is null)
{
return true;
}

switch (control.Parent)
switch (parent)
{
case Panel panel:
panel.Children.Remove(control);
return true;
case Decorator decorator when ReferenceEquals(decorator.Child, control):
decorator.Child = null;
return true;
case ContentPresenter presenter when ReferenceEquals(presenter.Content, control):
presenter.Content = null;
return true;
case ContentPresenter presenter:
if (ReferenceEquals(presenter.Child, control))
{
presenter.SetCurrentValue(ContentPresenter.ContentProperty, null);
presenter.UpdateChild();
return control.GetVisualParent() is null;
}

if (ReferenceEquals(presenter.Content, control))
{
presenter.SetCurrentValue(ContentPresenter.ContentProperty, null);
presenter.UpdateChild();
return true;
}

return false;
case ContentControl contentControl when ReferenceEquals(contentControl.Content, control):
contentControl.Content = null;
return true;
Expand Down
Loading
Loading