From 3bc303c4e8fc3a1906616e12782ef5c420eed0f0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Apr 2018 13:12:28 +0100 Subject: [PATCH 1/5] implement menus and submenus obey screen extremities. --- src/Avalonia.Controls/ContextMenu.cs | 7 +++--- src/Avalonia.Controls/Primitives/Popup.cs | 18 +++++++++++++- src/Avalonia.Controls/Primitives/PopupRoot.cs | 24 +++++++++++++++++++ src/Avalonia.Themes.Default/MenuItem.xaml | 6 +++-- 4 files changed, 49 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index fdb04f4ade..78dc994df7 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -19,7 +19,7 @@ namespace Avalonia.Controls { ContextMenuProperty.Changed.Subscribe(ContextMenuChanged); - MenuItem.ClickEvent.AddClassHandler(x => x.OnContextMenuClick, handledEventsToo: true); + MenuItem.ClickEvent.AddClassHandler(x => x.OnContextMenuClick, handledEventsToo: true); } /// @@ -75,13 +75,14 @@ namespace Avalonia.Controls { if (control != null) { - if(_popup == null) + if (_popup == null) { _popup = new Popup() { PlacementMode = PlacementMode.Pointer, PlacementTarget = control, - StaysOpen = false + StaysOpen = false, + ObeyScreenEdges = true }; _popup.Closed += PopupClosed; diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index b8817d28f3..d34e467133 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -40,6 +40,9 @@ namespace Avalonia.Controls.Primitives public static readonly StyledProperty PlacementModeProperty = AvaloniaProperty.Register(nameof(PlacementMode), defaultValue: PlacementMode.Bottom); + public static readonly StyledProperty ObeyScreenEdgesProperty = + AvaloniaProperty.Register(nameof(ObeyScreenEdges)); + /// /// Defines the property. /// @@ -136,6 +139,12 @@ namespace Avalonia.Controls.Primitives set { SetValue(PlacementModeProperty, value); } } + public bool ObeyScreenEdges + { + get => GetValue(ObeyScreenEdgesProperty); + set => SetValue(ObeyScreenEdgesProperty, value); + } + /// /// Gets or sets the Horizontal offset of the popup in relation to the /// @@ -234,6 +243,11 @@ namespace Avalonia.Controls.Primitives _popupRoot.Show(); + if (ObeyScreenEdges) + { + _popupRoot.SnapInsideScreenEdges(); + } + _ignoreIsOpenChanged = true; IsOpen = true; _ignoreIsOpenChanged = false; @@ -346,8 +360,10 @@ namespace Avalonia.Controls.Primitives /// The popup's position in screen coordinates. protected virtual Point GetPosition() { - return GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot, + var result = GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot, HorizontalOffset, VerticalOffset); + + return result; } internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset) diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index 507a085fed..d0279200b6 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. See licence.md file in the project root for full license information. using System; +using System.Linq; using Avalonia.Controls.Platform; using Avalonia.Controls.Presenters; using Avalonia.Interactivity; @@ -75,6 +76,29 @@ namespace Avalonia.Controls.Primitives /// public void Dispose() => PlatformImpl?.Dispose(); + /// + /// Moves the Popups position so that it doesnt overlap screen edges. + /// This method can be called immediately after Show has been called. + /// + public void SnapInsideScreenEdges() + { + var mainWindow = Window.OpenWindows.First(); + var screen = mainWindow.Screens.ScreenFromPoint(Position); + + var screenX = Position.X + Bounds.Width - screen.Bounds.X; + var screenY = Position.Y + Bounds.Height - screen.Bounds.Y; + + if (screenX > screen.Bounds.Width) + { + Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width)); + } + + if (screenY > screen.Bounds.Height) + { + Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height)); + } + } + /// protected override void OnTemplateApplied(TemplateAppliedEventArgs e) { diff --git a/src/Avalonia.Themes.Default/MenuItem.xaml b/src/Avalonia.Themes.Default/MenuItem.xaml index 66f226d2f6..efb31175fa 100644 --- a/src/Avalonia.Themes.Default/MenuItem.xaml +++ b/src/Avalonia.Themes.Default/MenuItem.xaml @@ -45,7 +45,8 @@ + IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}" + ObeyScreenEdges="True"> @@ -92,7 +93,8 @@ + StaysOpen="True" + ObeyScreenEdges="True"> From 89767c8b78d122056d9a6827a6384be795a4e1ea Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 28 Apr 2018 09:43:03 +0100 Subject: [PATCH 2/5] add comments to new properties. --- src/Avalonia.Controls/Primitives/Popup.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index d34e467133..5483815522 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -39,7 +39,10 @@ namespace Avalonia.Controls.Primitives /// public static readonly StyledProperty PlacementModeProperty = AvaloniaProperty.Register(nameof(PlacementMode), defaultValue: PlacementMode.Bottom); - + + /// + /// Degines the property. + /// public static readonly StyledProperty ObeyScreenEdgesProperty = AvaloniaProperty.Register(nameof(ObeyScreenEdges)); @@ -139,6 +142,10 @@ namespace Avalonia.Controls.Primitives set { SetValue(PlacementModeProperty, value); } } + /// + /// Gets or sets a value indicating whether the popup positions itself within the nearest screen boundary + /// when its opened at a position where it would otherwise overlap the screen edge. + /// public bool ObeyScreenEdges { get => GetValue(ObeyScreenEdgesProperty); From 240bb40be4075aee7efa68a829bcd8852517c573 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Sat, 28 Apr 2018 09:45:08 +0100 Subject: [PATCH 3/5] whitespace fixes. --- src/Avalonia.Controls/Primitives/Popup.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 5483815522..56e09b7db6 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -39,7 +39,7 @@ namespace Avalonia.Controls.Primitives /// public static readonly StyledProperty PlacementModeProperty = AvaloniaProperty.Register(nameof(PlacementMode), defaultValue: PlacementMode.Bottom); - + /// /// Degines the property. /// @@ -232,12 +232,12 @@ namespace Avalonia.Controls.Primitives var window = _topLevel as Window; if (window != null) { - window.Deactivated += WindowDeactivated; + window.Deactivated += WindowDeactivated; } else { var parentPopuproot = _topLevel as PopupRoot; - if(parentPopuproot != null && parentPopuproot.Parent!=null) + if (parentPopuproot != null && parentPopuproot.Parent != null) { ((Popup)(parentPopuproot.Parent)).Closed += ParentClosed; } @@ -261,7 +261,7 @@ namespace Avalonia.Controls.Primitives Opened?.Invoke(this, EventArgs.Empty); } - + /// /// Closes the popup. /// @@ -367,7 +367,7 @@ namespace Avalonia.Controls.Primitives /// The popup's position in screen coordinates. protected virtual Point GetPosition() { - var result = GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot, + var result = GetPosition(PlacementTarget ?? this.GetVisualParent(), PlacementMode, PopupRoot, HorizontalOffset, VerticalOffset); return result; @@ -422,8 +422,8 @@ namespace Avalonia.Controls.Primitives { if (!StaysOpen) { - if(!IsChildOrThis((IVisual)e.Source)) - { + if (!IsChildOrThis((IVisual)e.Source)) + { Close(); e.Handled = true; } @@ -435,12 +435,12 @@ namespace Avalonia.Controls.Primitives IVisual root = child.GetVisualRoot(); while (root is PopupRoot) { - if (root == PopupRoot) return true; + if (root == PopupRoot) return true; root = ((PopupRoot)root).Parent.GetVisualRoot(); } return false; } - + private void WindowDeactivated(object sender, EventArgs e) { if (!StaysOpen) From 6bb25bea93a20657c441ed6b2b0e3a53a01115c7 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 30 Apr 2018 10:22:43 +0100 Subject: [PATCH 4/5] fix typo. --- src/Avalonia.Controls/Primitives/Popup.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs index 56e09b7db6..656f3890cd 100644 --- a/src/Avalonia.Controls/Primitives/Popup.cs +++ b/src/Avalonia.Controls/Primitives/Popup.cs @@ -41,7 +41,7 @@ namespace Avalonia.Controls.Primitives AvaloniaProperty.Register(nameof(PlacementMode), defaultValue: PlacementMode.Bottom); /// - /// Degines the property. + /// Defines the property. /// public static readonly StyledProperty ObeyScreenEdgesProperty = AvaloniaProperty.Register(nameof(ObeyScreenEdges)); From fd8e1ecba7e52abfe72cfa37af8d2921d72763c4 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 30 Apr 2018 20:26:10 +0100 Subject: [PATCH 5/5] walk tree to find window. --- src/Avalonia.Controls/Primitives/PopupRoot.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs index d0279200b6..457a7bd4b4 100644 --- a/src/Avalonia.Controls/Primitives/PopupRoot.cs +++ b/src/Avalonia.Controls/Primitives/PopupRoot.cs @@ -7,6 +7,7 @@ using Avalonia.Controls.Platform; using Avalonia.Controls.Presenters; using Avalonia.Interactivity; using Avalonia.Layout; +using Avalonia.LogicalTree; using Avalonia.Media; using Avalonia.Platform; using Avalonia.Styling; @@ -82,8 +83,9 @@ namespace Avalonia.Controls.Primitives /// public void SnapInsideScreenEdges() { - var mainWindow = Window.OpenWindows.First(); - var screen = mainWindow.Screens.ScreenFromPoint(Position); + var window = this.GetSelfAndLogicalAncestors().OfType().First(); + + var screen = window.Screens.ScreenFromPoint(Position); var screenX = Position.X + Bounds.Width - screen.Bounds.X; var screenY = Position.Y + Bounds.Height - screen.Bounds.Y;