From 73ca7334ee880714926fea387f2e32c1814c806a Mon Sep 17 00:00:00 2001 From: Benedikt Schroeder Date: Thu, 26 Apr 2018 18:39:28 +0200 Subject: [PATCH 1/8] Initial --- .../Data/BindingExpressionTests.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs index f5c0c6ec15..f42e0daf2a 100644 --- a/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs +++ b/tests/Avalonia.Markup.UnitTests/Data/BindingExpressionTests.cs @@ -58,7 +58,7 @@ namespace Avalonia.Markup.UnitTests.Data [Fact] public async Task Should_Convert_Get_String_To_Double() { - var data = new Class1 { StringValue = "5.6" }; + var data = new Class1 { StringValue = $"{5.6}" }; var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double)); var result = await target.Take(1); @@ -94,12 +94,12 @@ namespace Avalonia.Markup.UnitTests.Data [Fact] public void Should_Convert_Set_String_To_Double() { - var data = new Class1 { StringValue = (5.6).ToString() }; + var data = new Class1 { StringValue = $"{5.6}" }; var target = new BindingExpression(new ExpressionObserver(data, "StringValue"), typeof(double)); target.OnNext(6.7); - Assert.Equal((6.7).ToString(), data.StringValue); + Assert.Equal($"{6.7}", data.StringValue); GC.KeepAlive(data); } @@ -111,7 +111,7 @@ namespace Avalonia.Markup.UnitTests.Data var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string)); var result = await target.Take(1); - Assert.Equal((5.6).ToString(), result); + Assert.Equal($"{5.6}", result); GC.KeepAlive(data); } @@ -122,7 +122,7 @@ namespace Avalonia.Markup.UnitTests.Data var data = new Class1 { DoubleValue = 5.6 }; var target = new BindingExpression(new ExpressionObserver(data, "DoubleValue"), typeof(string)); - target.OnNext("6.7"); + target.OnNext($"{6.7}"); Assert.Equal(6.7, data.DoubleValue); @@ -318,15 +318,15 @@ namespace Avalonia.Markup.UnitTests.Data target.Subscribe(x => result.Add(x)); target.OnNext(1.2); - target.OnNext("3.4"); + target.OnNext($"{3.4}"); target.OnNext("bar"); Assert.Equal( new[] { - new BindingNotification("5.6"), - new BindingNotification("1.2"), - new BindingNotification("3.4"), + new BindingNotification($"{5.6}"), + new BindingNotification($"{1.2}"), + new BindingNotification($"{3.4}"), new BindingNotification( new InvalidCastException("'bar' is not a valid number."), BindingErrorType.Error) From 3bc303c4e8fc3a1906616e12782ef5c420eed0f0 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Fri, 27 Apr 2018 13:12:28 +0100 Subject: [PATCH 2/8] 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 3/8] 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 4/8] 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 35b4913c0bfe712045a6ef340daebea721f8aa14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Sat, 28 Apr 2018 18:13:44 +0100 Subject: [PATCH 5/8] Removed AvaloniaWin32Exception usings. --- src/Windows/Avalonia.Win32/ScreenImpl.cs | 7 ------- src/Windows/Avalonia.Win32/Win32Platform.cs | 18 +++++++----------- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs index 113b2811dc..e1df24151d 100644 --- a/src/Windows/Avalonia.Win32/ScreenImpl.cs +++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs @@ -2,16 +2,9 @@ // 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; using Avalonia.Platform; -using Avalonia.Utilities; using static Avalonia.Win32.Interop.UnmanagedMethods; -#if NETSTANDARD -using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception; -#endif - namespace Avalonia.Win32 { public class ScreenImpl : IScreenImpl diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 902abaf65b..95077f82a1 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -1,27 +1,23 @@ // Copyright (c) The Avalonia Project. All rights reserved. // Licensed under the MIT license. See licence.md file in the project root for full license information. -using Avalonia.Input.Platform; using System; using System.Collections.Generic; -using System.Reactive.Disposables; +using System.ComponentModel; using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Reactive.Disposables; using System.Runtime.InteropServices; using System.Threading; +using Avalonia.Controls; using Avalonia.Controls.Platform; using Avalonia.Input; +using Avalonia.Input.Platform; using Avalonia.Platform; -using Avalonia.Win32.Input; -using Avalonia.Win32.Interop; -using Avalonia.Controls; using Avalonia.Rendering; using Avalonia.Threading; -using System.IO; -#if NETSTANDARD -using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception; -#else -using System.ComponentModel; -#endif +using Avalonia.Win32.Input; +using Avalonia.Win32.Interop; namespace Avalonia { From c1d6fe66864e797f486e7f4b1609438b270b0330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Pedro?= Date: Sat, 28 Apr 2018 22:45:11 +0100 Subject: [PATCH 6/8] Fixed possible NullReferenceException in DefaultRenderLoop. --- src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs b/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs index 5dff3715b3..9cf849f59b 100644 --- a/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs +++ b/src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs @@ -41,12 +41,12 @@ namespace Avalonia.Rendering { add { + _tick += value; + if (_subscriberCount++ == 0) { Start(); } - - _tick += value; } remove From 6bb25bea93a20657c441ed6b2b0e3a53a01115c7 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Mon, 30 Apr 2018 10:22:43 +0100 Subject: [PATCH 7/8] 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 8/8] 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;