Skip to content

Commit

Permalink
Merge pull request #329 from BAndysc/host_windows
Browse files Browse the repository at this point in the history
Fixes to floating host windows
  • Loading branch information
wieslawsoltes authored Mar 11, 2024
2 parents ee9921b + 3b62a02 commit 4c42b72
Show file tree
Hide file tree
Showing 15 changed files with 264 additions and 36 deletions.
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

0 comments on commit 4c42b72

Please sign in to comment.