Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes to floating host windows #329

Merged
merged 5 commits into from
Mar 11, 2024
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
11 changes: 11 additions & 0 deletions samples/DockMvvmSample/ViewModels/DockFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,17 @@ public override IRootDock CreateLayout()
return rootDock;
}

public override IDockWindow? CreateWindowFrom(IDockable dockable)
{
var window = base.CreateWindowFrom(dockable);

if (window != null)
{
window.Title = "Dock Avalonia Demo";
}
return window;
}

public override void InitLayout(IDockable layout)
{
ContextLocator = new Dictionary<string, Func<object?>>
Expand Down
12 changes: 9 additions & 3 deletions src/Dock.Avalonia/Controls/HostWindow.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
<HostWindow IsToolWindow="False" Width="300" Height="400" />
</Design.PreviewWith>

<IntLessThanConverter x:Key="LessThan2" TrueIfLessThan="2" />

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

<Setter Property="Background" Value="{DynamicResource DockThemeBackgroundBrush}" />
Expand All @@ -17,7 +19,8 @@
<Setter Property="Title" Value="{Binding ActiveDockable.Title}" />
<Setter Property="Topmost" Value="{Binding Window.Topmost}" x:DataType="controls:IRootDock" />
<Setter Property="SystemDecorations" Value="Full" />
<Setter Property="ExtendClientAreaToDecorationsHint" Value="True" />
<Setter Property="ToolChromeControlsWholeWindow" Value="{CompiledBinding OpenedDockablesCount, Converter={StaticResource LessThan2}}" x:DataType="controls:IRootDock" />
<Setter Property="ExtendClientAreaToDecorationsHint" Value="False" />
<Setter Property="ExtendClientAreaChromeHints" Value="PreferSystemChrome" />

<Setter Property="Template">
Expand Down Expand Up @@ -56,10 +59,13 @@
<Style Selector="^:toolwindow">

<Setter Property="SystemDecorations" Value="Full" />
<Setter Property="ExtendClientAreaToDecorationsHint" Value="True" />
<Setter Property="ExtendClientAreaChromeHints" Value="NoChrome" />
<Setter Property="ExtendClientAreaTitleBarHeightHint" Value="0" />

<Style Selector="^:toolchromecontrolswindow">
<Setter Property="ExtendClientAreaToDecorationsHint" Value="True" />
<Setter Property="ExtendClientAreaChromeHints" Value="NoChrome" />
</Style>

<Setter Property="Template">
<ControlTemplate>
<Panel>
Expand Down
81 changes: 74 additions & 7 deletions src/Dock.Avalonia/Controls/HostWindow.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Styling;
using Avalonia.VisualTree;
using Dock.Avalonia.Internal;
using Dock.Model;
using Dock.Model.Controls;
using Dock.Model.Core;

namespace Dock.Avalonia.Controls;
Expand All @@ -22,7 +25,7 @@ public class HostWindow : Window, IHostWindow
{
private readonly DockManager _dockManager;
private readonly HostWindowState _hostWindowState;
private Control? _chromeGrip;
private List<Control> _chromeGrips = new();
private HostWindowTitleBar? _hostWindowTitleBar;
private bool _mouseDown, _draggingWindow;

Expand All @@ -32,6 +35,12 @@ public class HostWindow : Window, IHostWindow
public static readonly StyledProperty<bool> IsToolWindowProperty =
AvaloniaProperty.Register<HostWindow, bool>(nameof(IsToolWindow));

/// <summary>
/// Define <see cref="ToolChromeControlsWholeWindow"/> property.
/// </summary>
public static readonly StyledProperty<bool> ToolChromeControlsWholeWindowProperty =
AvaloniaProperty.Register<HostWindow, bool>(nameof(ToolChromeControlsWholeWindow));

/// <inheritdoc/>
protected override Type StyleKeyOverride => typeof(HostWindow);

Expand All @@ -44,6 +53,15 @@ public bool IsToolWindow
set => SetValue(IsToolWindowProperty, value);
}

/// <summary>
/// Gets or sets if the tool chrome controls the whole window.
/// </summary>
public bool ToolChromeControlsWholeWindow
{
get => GetValue(ToolChromeControlsWholeWindowProperty);
set => SetValue(ToolChromeControlsWholeWindowProperty, value);
}

/// <inheritdoc/>
public IDockManager DockManager => _dockManager;

Expand All @@ -66,7 +84,7 @@ public HostWindow()

_dockManager = new DockManager();
_hostWindowState = new HostWindowState(_dockManager, this);
UpdatePseudoClasses(IsToolWindow);
UpdatePseudoClasses(IsToolWindow, ToolChromeControlsWholeWindow);
}

/// <inheritdoc/>
Expand Down Expand Up @@ -99,6 +117,9 @@ private PixelPoint ClientPointToScreenRelativeToWindow(Point clientPoint)

private void MoveDrag(PointerPressedEventArgs e)
{
if (!ToolChromeControlsWholeWindow)
return;

if (Window?.Factory?.OnWindowMoveDragBegin(Window) != true)
{
return;
Expand Down Expand Up @@ -127,7 +148,7 @@ protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);

if (_chromeGrip is { } && _chromeGrip.IsPointerOver)
if (_chromeGrips.Any(grip => grip.IsPointerOver))
{
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
Expand Down Expand Up @@ -177,28 +198,72 @@ public void AttachGrip(ToolChromeControl chromeControl)
{
if (chromeControl.CloseButton is not null)
{
chromeControl.CloseButton.Click += (_, _) => Exit();
chromeControl.CloseButton.Click += ChromeCloseClick;
}

if (chromeControl.Grip is { } grip)
{
_chromeGrips.Add(grip);
}

_chromeGrip = chromeControl.Grip;
((IPseudoClasses)chromeControl.Classes).Add(":floating");
IsToolWindow = true;
}

/// <summary>
/// Detaches grip to chrome.
/// </summary>
/// <param name="chromeControl">The chrome control.</param>
public void DetachGrip(ToolChromeControl chromeControl)
{
if (chromeControl.Grip is { } grip)
{
_chromeGrips.Remove(grip);
}

if (chromeControl.CloseButton is not null)
{
chromeControl.CloseButton.Click -= ChromeCloseClick;
}
}

/// <inheritdoc/>
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
base.OnPropertyChanged(change);

if (change.Property == IsToolWindowProperty)
{
UpdatePseudoClasses(change.GetNewValue<bool>());
UpdatePseudoClasses(change.GetNewValue<bool>(), ToolChromeControlsWholeWindow);
}
else if (change.Property == ToolChromeControlsWholeWindowProperty)
{
UpdatePseudoClasses(IsToolWindow, change.GetNewValue<bool>());
}
}

private void UpdatePseudoClasses(bool isToolWindow)
private void UpdatePseudoClasses(bool isToolWindow, bool toolChromeControlsWholeWindow)
{
PseudoClasses.Set(":toolwindow", isToolWindow);
PseudoClasses.Set(":toolchromecontrolswindow", toolChromeControlsWholeWindow);
}

private int CountVisibleToolsAndDocuments(IDockable? dockable)
{
switch (dockable)
{
case ITool: return 1;
case IDocument: return 1;
case IDock dock:
return dock.VisibleDockables?.Sum(CountVisibleToolsAndDocuments) ?? 0;
default: return 0;
}
}

private void ChromeCloseClick(object? sender, RoutedEventArgs e)
{
if (CountVisibleToolsAndDocuments(DataContext as IRootDock) <= 1)
Exit();
}

/// <inheritdoc/>
Expand Down Expand Up @@ -282,6 +347,8 @@ public void Present(bool isDialog)
var ownerDockControl = Window?.Layout?.Factory?.DockControls.FirstOrDefault();
if (ownerDockControl is Control control && control.GetVisualRoot() is Window parentWindow)
{
Title = parentWindow.Title;
Icon = parentWindow.Icon;
Show(parentWindow);
}
else
Expand Down
2 changes: 1 addition & 1 deletion src/Dock.Avalonia/Controls/ToolChromeControl.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@
</Style>

<Style Selector="^:floating /template/ Grid#PART_Grip">
<Setter Property="(DockProperties.IsDragArea)" Value="False" />
<Setter Property="(DockProperties.IsDragArea)" Value="{Binding $parent[HostWindow].ToolChromeControlsWholeWindow, Mode=OneWay, Converter={x:Static BoolConverters.Not}}" />
</Style>

<Style Selector="^:active /template/ Grid#PART_Grip">
Expand Down
33 changes: 27 additions & 6 deletions src/Dock.Avalonia/Controls/ToolChromeControl.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ namespace Dock.Avalonia.Controls;
[PseudoClasses(":floating", ":active", ":pinned", ":maximized")]
public class ToolChromeControl : ContentControl
{
private HostWindow? _attachedWindow;

/// <summary>
/// Define <see cref="Title"/> property.
/// </summary>
Expand Down Expand Up @@ -106,9 +108,20 @@ public bool IsActive
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
AddHandler(PointerPressedEvent, PressedHandler, RoutingStrategies.Tunnel);
AttachToWindow();
}

/// <inheritdoc/>
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
if (_attachedWindow != null)
{
_attachedWindow.DetachGrip(this);
_attachedWindow = null;
}
}

private void PressedHandler(object? sender, PointerPressedEventArgs e)
{
if (DataContext is IDock {Factory: { } factory} dock && dock.ActiveDockable is { })
Expand All @@ -124,15 +137,23 @@ private void PressedHandler(object? sender, PointerPressedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
Grip = e.NameScope.Find<Control>("PART_Grip");
CloseButton = e.NameScope.Find<Button>("PART_CloseButton");
AddHandler(PointerPressedEvent, PressedHandler, RoutingStrategies.Tunnel);
AttachToWindow();
}

private void AttachToWindow()
{
if (Grip == null)
return;

//On linux we dont attach to the HostWindow because of inconsistent drag behaviour
if (VisualRoot is HostWindow window
if (VisualRoot is HostWindow window
&& (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) || RuntimeInformation.IsOSPlatform(OSPlatform.OSX)))
{
Grip = e.NameScope.Find<Control>("PART_Grip");
CloseButton = e.NameScope.Find<Button>("PART_CloseButton");

window.AttachGrip(this);
_attachedWindow = window;

SetCurrentValue(IsFloatingProperty, true);
}
Expand Down
24 changes: 24 additions & 0 deletions src/Dock.Avalonia/Converters/IntLessThanConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System;
using System.Globalization;
using Avalonia.Data.Converters;

namespace Dock.Avalonia.Converters;

internal class IntLessThanConverter : IValueConverter
{
public int TrueIfLessThan { get; set; }

public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture)
{
if (value is int intValue)
{
return intValue < TrueIfLessThan;
}
return false;
}

public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
8 changes: 1 addition & 7 deletions src/Dock.Avalonia/Internal/DockControlState.cs
Original file line number Diff line number Diff line change
Expand Up @@ -247,14 +247,8 @@ public void Process(Point point, Vector delta, EventType eventType, DragAction d
Visual? targetDockControl = null;
Control? dropControl = null;

foreach (var dockControl in dockControls)
foreach (var inputDockControl in dockControls.GetZOrderedDockControls())
{
if (dockControl is not Visual inputDockControl ||
inputDockControl == inputActiveDockControl)
{
continue;
}

if (inputActiveDockControl.GetVisualRoot() is null)
{
continue;
Expand Down
32 changes: 32 additions & 0 deletions src/Dock.Avalonia/Internal/Extensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.VisualTree;
using Dock.Avalonia.Controls;
using Dock.Model.Core;

namespace Dock.Avalonia.Internal;

internal static class Extensions
{
public static IEnumerable<DockControl> GetZOrderedDockControls(this IList<IDockControl> dockControls)
{
// Note: we should traverse the dock controls in their windows' z-order.
// However there is no way to get the z-order of a window in Avalonia.
// Uncomment once this PR is merged and a new Avalonia version is released
// https://github.com/AvaloniaUI/Avalonia/pull/14909
// return dockControls
// .OfType<DockControl>()
// .Select(dock => (dock, order: (dock.GetVisualRoot() as Window)?.WindowZOrder ?? IntPtr.Zero))
// .OrderByDescending(x => x.order)
// .Select(pair => pair.dock);

// For now, as a workaround, iterating in the reverse order of the dock controls is better then the regular order,
// because the main window dock control is always at index 0 and all the other windows are always
// on top of the main window.
return dockControls
.OfType<DockControl>()
.Reverse();
}
}
Loading
Loading