diff --git a/src/Avalonia.Controls/Chrome/CaptionButtons.cs b/src/Avalonia.Controls/Chrome/CaptionButtons.cs new file mode 100644 index 0000000000..20a42aadf6 --- /dev/null +++ b/src/Avalonia.Controls/Chrome/CaptionButtons.cs @@ -0,0 +1,89 @@ +using System; +using System.Collections.Generic; +using System.Reactive.Disposables; +using System.Text; +using Avalonia.Controls.Primitives; +using Avalonia.VisualTree; + +namespace Avalonia.Controls.Chrome +{ + public class CaptionButtons : TemplatedControl + { + private CompositeDisposable _disposables; + private Window _hostWindow; + + public CaptionButtons(Window hostWindow) + { + _hostWindow = hostWindow; + } + + public void Attach() + { + if (_disposables == null) + { + var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); + + layer.Children.Add(this); + + _disposables = new CompositeDisposable + { + _hostWindow.GetObservable(Window.WindowDecorationMarginsProperty) + .Subscribe(x => + { + Height = x.Top; + }), + + + _hostWindow.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty) + .Subscribe(x => InvalidateSize()), + + _hostWindow.GetObservable(Window.OffScreenMarginProperty) + .Subscribe(x => InvalidateSize()), + + _hostWindow.GetObservable(Window.WindowStateProperty) + .Subscribe(x => + { + PseudoClasses.Set(":minimized", x == WindowState.Minimized); + PseudoClasses.Set(":normal", x == WindowState.Normal); + PseudoClasses.Set(":maximized", x == WindowState.Maximized); + PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); + }) + }; + } + } + + void InvalidateSize () + { + Margin = new Thickness(1, _hostWindow.OffScreenMargin.Top, 1, 1); + Height = _hostWindow.WindowDecorationMargins.Top - _hostWindow.OffScreenMargin.Top; + } + + public void Detach() + { + if (_disposables != null) + { + var layer = ChromeOverlayLayer.GetOverlayLayer(_hostWindow); + + layer.Children.Remove(this); + + _disposables.Dispose(); + _disposables = null; + } + } + + protected override void OnApplyTemplate(TemplateAppliedEventArgs e) + { + base.OnApplyTemplate(e); + + var closeButton = e.NameScope.Find("PART_CloseButton"); + var restoreButton = e.NameScope.Find("PART_RestoreButton"); + var minimiseButton = e.NameScope.Find("PART_MinimiseButton"); + var fullScreenButton = e.NameScope.Find("PART_FullScreenButton"); + + closeButton.PointerPressed += (sender, e) => _hostWindow.Close(); + restoreButton.PointerPressed += (sender, e) => _hostWindow.WindowState = _hostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; + minimiseButton.PointerPressed += (sender, e) => _hostWindow.WindowState = WindowState.Minimized; + fullScreenButton.PointerPressed += (sender, e) => _hostWindow.WindowState = _hostWindow.WindowState == WindowState.FullScreen ? WindowState.Normal : WindowState.FullScreen; + } + } +} diff --git a/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs new file mode 100644 index 0000000000..5deb9ac408 --- /dev/null +++ b/src/Avalonia.Controls/Primitives/ChromeOverlayLayer.cs @@ -0,0 +1,37 @@ +using System.Linq; +using Avalonia.Rendering; +using Avalonia.VisualTree; + +namespace Avalonia.Controls.Primitives +{ + public class ChromeOverlayLayer : Canvas, ICustomSimpleHitTest + { + public Size AvailableSize { get; private set; } + + public static ChromeOverlayLayer GetOverlayLayer(IVisual visual) + { + foreach (var v in visual.GetVisualAncestors()) + if (v is VisualLayerManager vlm) + if (vlm.OverlayLayer != null) + return vlm.ChromeOverlayLayer; + + if (visual is TopLevel tl) + { + var layers = tl.GetVisualDescendants().OfType().FirstOrDefault(); + return layers?.ChromeOverlayLayer; + } + + return null; + } + + public bool HitTest(Point point) => Children.HitTestCustom(point); + + protected override Size ArrangeOverride(Size finalSize) + { + // We are saving it here since child controls might need to know the entire size of the overlay + // and Bounds won't be updated in time + AvailableSize = finalSize; + return base.ArrangeOverride(finalSize); + } + } +} diff --git a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs index 86aeb5c62a..3084d7fa72 100644 --- a/src/Avalonia.Controls/Primitives/VisualLayerManager.cs +++ b/src/Avalonia.Controls/Primitives/VisualLayerManager.cs @@ -6,7 +6,9 @@ namespace Avalonia.Controls.Primitives public class VisualLayerManager : Decorator { private const int AdornerZIndex = int.MaxValue - 100; - private const int OverlayZIndex = int.MaxValue - 99; + private const int ChromeZIndex = int.MaxValue - 99; + private const int OverlayZIndex = int.MaxValue - 98; + private ILogicalRoot _logicalRoot; private readonly List _layers = new List(); @@ -24,6 +26,17 @@ namespace Avalonia.Controls.Primitives } } + public ChromeOverlayLayer ChromeOverlayLayer + { + get + { + var rv = FindLayer(); + if (rv == null) + AddLayer(rv = new ChromeOverlayLayer(), ChromeZIndex); + return rv; + } + } + public OverlayLayer OverlayLayer { get diff --git a/src/Avalonia.Controls/Properties/AssemblyInfo.cs b/src/Avalonia.Controls/Properties/AssemblyInfo.cs index 060db46212..672fbe294e 100644 --- a/src/Avalonia.Controls/Properties/AssemblyInfo.cs +++ b/src/Avalonia.Controls/Properties/AssemblyInfo.cs @@ -13,3 +13,4 @@ using Avalonia.Metadata; [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Shapes")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Templates")] [assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Notifications")] +[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Controls.Chrome")] diff --git a/src/Avalonia.Themes.Default/CaptionButtons.xaml b/src/Avalonia.Themes.Default/CaptionButtons.xaml new file mode 100644 index 0000000000..0cc0d6b9cd --- /dev/null +++ b/src/Avalonia.Themes.Default/CaptionButtons.xaml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml index 94d26e798b..97cff5d94b 100644 --- a/src/Avalonia.Themes.Default/DefaultTheme.xaml +++ b/src/Avalonia.Themes.Default/DefaultTheme.xaml @@ -9,6 +9,7 @@ +