Browse Source

Merge branch 'feature/customFonts' of https://github.com/Gillibald/Avalonia into feature/customFonts

Benedikt Schroeder 7 years ago
parent
commit
35bb01880f

+ 4 - 3
src/Avalonia.Controls/ContextMenu.cs

@@ -19,7 +19,7 @@ namespace Avalonia.Controls
         {
             ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
 
-            MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);            
+            MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);
         }
 
         /// <summary>
@@ -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;

+ 31 - 8
src/Avalonia.Controls/Primitives/Popup.cs

@@ -40,6 +40,12 @@ namespace Avalonia.Controls.Primitives
         public static readonly StyledProperty<PlacementMode> PlacementModeProperty =
             AvaloniaProperty.Register<Popup, PlacementMode>(nameof(PlacementMode), defaultValue: PlacementMode.Bottom);
 
+        /// <summary>
+        /// Defines the <see cref="ObeyScreenEdges"/> property.
+        /// </summary>
+        public static readonly StyledProperty<bool> ObeyScreenEdgesProperty =
+            AvaloniaProperty.Register<Popup, bool>(nameof(ObeyScreenEdges));
+
         /// <summary>
         /// Defines the <see cref="HorizontalOffset"/> property.
         /// </summary>
@@ -136,6 +142,16 @@ namespace Avalonia.Controls.Primitives
             set { SetValue(PlacementModeProperty, value); }
         }
 
+        /// <summary>
+        /// 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.
+        /// </summary>
+        public bool ObeyScreenEdges
+        {
+            get => GetValue(ObeyScreenEdgesProperty);
+            set => SetValue(ObeyScreenEdgesProperty, value);
+        }
+
         /// <summary>
         /// Gets or sets the Horizontal offset of the popup in relation to the <see cref="PlacementTarget"/>
         /// </summary>
@@ -216,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;
                     }
@@ -234,13 +250,18 @@ namespace Avalonia.Controls.Primitives
 
             _popupRoot.Show();
 
+            if (ObeyScreenEdges)
+            {
+                _popupRoot.SnapInsideScreenEdges();
+            }
+
             _ignoreIsOpenChanged = true;
             IsOpen = true;
             _ignoreIsOpenChanged = false;
 
             Opened?.Invoke(this, EventArgs.Empty);
         }
-        
+
         /// <summary>
         /// Closes the popup.
         /// </summary>
@@ -346,8 +367,10 @@ namespace Avalonia.Controls.Primitives
         /// <returns>The popup's position in screen coordinates.</returns>
         protected virtual Point GetPosition()
         {
-            return GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot, 
+            var result = GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot,
                 HorizontalOffset, VerticalOffset);
+
+            return result;
         }
 
         internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)
@@ -399,8 +422,8 @@ namespace Avalonia.Controls.Primitives
         {
             if (!StaysOpen)
             {
-                if(!IsChildOrThis((IVisual)e.Source))
-                {                     
+                if (!IsChildOrThis((IVisual)e.Source))
+                {
                     Close();
                     e.Handled = true;
                 }
@@ -412,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)

+ 26 - 0
src/Avalonia.Controls/Primitives/PopupRoot.cs

@@ -2,10 +2,12 @@
 // 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;
 using Avalonia.Layout;
+using Avalonia.LogicalTree;
 using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.Styling;
@@ -75,6 +77,30 @@ namespace Avalonia.Controls.Primitives
         /// <inheritdoc/>
         public void Dispose() => PlatformImpl?.Dispose();
 
+        /// <summary>
+        /// Moves the Popups position so that it doesnt overlap screen edges.
+        /// This method can be called immediately after Show has been called.
+        /// </summary>
+        public void SnapInsideScreenEdges()
+        {
+            var window = this.GetSelfAndLogicalAncestors().OfType<Window>().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;
+
+            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));
+            }
+        }
+
         /// <inheritdoc/>
         protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
         {

+ 4 - 2
src/Avalonia.Themes.Default/MenuItem.xaml

@@ -45,7 +45,8 @@
             <Popup Name="PART_Popup"
                    PlacementMode="Right"
                    StaysOpen="True"
-                   IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}">
+                   IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}"
+                   ObeyScreenEdges="True">
               <Border Background="{TemplateBinding Background}"
                       BorderBrush="{DynamicResource ThemeBorderMidBrush}"
                       BorderThickness="1">
@@ -92,7 +93,8 @@
             </ContentPresenter>
             <Popup Name="PART_Popup"
                    IsOpen="{TemplateBinding Path=IsSubMenuOpen, Mode=TwoWay}"
-                   StaysOpen="True">
+                   StaysOpen="True" 
+                   ObeyScreenEdges="True">
               <Border Background="{TemplateBinding Background}"
                       BorderBrush="{DynamicResource ThemeBorderMidBrush}"
                       BorderThickness="1">

+ 2 - 2
src/Avalonia.Visuals/Rendering/DefaultRenderLoop.cs

@@ -41,12 +41,12 @@ namespace Avalonia.Rendering
         {
             add
             {
+                _tick += value;
+
                 if (_subscriberCount++ == 0)
                 {
                     Start();
                 }
-
-                _tick += value;
             }
 
             remove

+ 0 - 7
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

+ 7 - 11
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
 {

+ 9 - 9
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)