瀏覽代碼

implement menus and submenus obey screen extremities.

Dan Walmsley 7 年之前
父節點
當前提交
3bc303c4e8

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

@@ -19,7 +19,7 @@ namespace Avalonia.Controls
         {
         {
             ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
             ContextMenuProperty.Changed.Subscribe(ContextMenuChanged);
 
 
-            MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);            
+            MenuItem.ClickEvent.AddClassHandler<ContextMenu>(x => x.OnContextMenuClick, handledEventsToo: true);
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -75,13 +75,14 @@ namespace Avalonia.Controls
         {
         {
             if (control != null)
             if (control != null)
             {
             {
-                if(_popup == null)
+                if (_popup == null)
                 {
                 {
                     _popup = new Popup()
                     _popup = new Popup()
                     {
                     {
                         PlacementMode = PlacementMode.Pointer,
                         PlacementMode = PlacementMode.Pointer,
                         PlacementTarget = control,
                         PlacementTarget = control,
-                        StaysOpen = false                                         
+                        StaysOpen = false,
+                        ObeyScreenEdges = true
                     };
                     };
 
 
                     _popup.Closed += PopupClosed;
                     _popup.Closed += PopupClosed;

+ 17 - 1
src/Avalonia.Controls/Primitives/Popup.cs

@@ -40,6 +40,9 @@ namespace Avalonia.Controls.Primitives
         public static readonly StyledProperty<PlacementMode> PlacementModeProperty =
         public static readonly StyledProperty<PlacementMode> PlacementModeProperty =
             AvaloniaProperty.Register<Popup, PlacementMode>(nameof(PlacementMode), defaultValue: PlacementMode.Bottom);
             AvaloniaProperty.Register<Popup, PlacementMode>(nameof(PlacementMode), defaultValue: PlacementMode.Bottom);
 
 
+        public static readonly StyledProperty<bool> ObeyScreenEdgesProperty =
+            AvaloniaProperty.Register<Popup, bool>(nameof(ObeyScreenEdges));
+
         /// <summary>
         /// <summary>
         /// Defines the <see cref="HorizontalOffset"/> property.
         /// Defines the <see cref="HorizontalOffset"/> property.
         /// </summary>
         /// </summary>
@@ -136,6 +139,12 @@ namespace Avalonia.Controls.Primitives
             set { SetValue(PlacementModeProperty, value); }
             set { SetValue(PlacementModeProperty, value); }
         }
         }
 
 
+        public bool ObeyScreenEdges
+        {
+            get => GetValue(ObeyScreenEdgesProperty);
+            set => SetValue(ObeyScreenEdgesProperty, value);
+        }
+
         /// <summary>
         /// <summary>
         /// Gets or sets the Horizontal offset of the popup in relation to the <see cref="PlacementTarget"/>
         /// Gets or sets the Horizontal offset of the popup in relation to the <see cref="PlacementTarget"/>
         /// </summary>
         /// </summary>
@@ -234,6 +243,11 @@ namespace Avalonia.Controls.Primitives
 
 
             _popupRoot.Show();
             _popupRoot.Show();
 
 
+            if (ObeyScreenEdges)
+            {
+                _popupRoot.SnapInsideScreenEdges();
+            }
+
             _ignoreIsOpenChanged = true;
             _ignoreIsOpenChanged = true;
             IsOpen = true;
             IsOpen = true;
             _ignoreIsOpenChanged = false;
             _ignoreIsOpenChanged = false;
@@ -346,8 +360,10 @@ namespace Avalonia.Controls.Primitives
         /// <returns>The popup's position in screen coordinates.</returns>
         /// <returns>The popup's position in screen coordinates.</returns>
         protected virtual Point GetPosition()
         protected virtual Point GetPosition()
         {
         {
-            return GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot, 
+            var result = GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot, 
                 HorizontalOffset, VerticalOffset);
                 HorizontalOffset, VerticalOffset);
+
+            return result;
         }
         }
 
 
         internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)
         internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)

+ 24 - 0
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.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
 using System;
 using System;
+using System.Linq;
 using Avalonia.Controls.Platform;
 using Avalonia.Controls.Platform;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Presenters;
 using Avalonia.Interactivity;
 using Avalonia.Interactivity;
@@ -75,6 +76,29 @@ namespace Avalonia.Controls.Primitives
         /// <inheritdoc/>
         /// <inheritdoc/>
         public void Dispose() => PlatformImpl?.Dispose();
         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 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));
+            }
+        }
+
         /// <inheritdoc/>
         /// <inheritdoc/>
         protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
         protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
         {
         {

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

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