From 47872188a2afbebda62cf04677f937cc1b5f3a34 Mon Sep 17 00:00:00 2001 From: Curtis Wensley Date: Fri, 21 Jan 2022 14:33:44 -0800 Subject: [PATCH] Add Window.FromPoint API --- src/Eto.Gtk/Forms/WindowHandler.cs | 49 ++++++++++++++++++++ src/Eto.Gtk/Platform.cs | 1 + src/Eto.Mac/Forms/WindowHandler.cs | 37 +++++++++++++++ src/Eto.Mac/Platform.cs | 1 + src/Eto.WinForms/Forms/WindowHandler.cs | 20 +++++++- src/Eto.WinForms/Platform.cs | 1 + src/Eto.WinForms/Win32.cs | 3 ++ src/Eto.Wpf/Forms/WindowHandler.cs | 30 ++++++++++++ src/Eto.Wpf/Platform.cs | 1 + src/Eto/Forms/Window.cs | 31 ++++++++++++- test/Eto.Test/UnitTests/Forms/WindowTests.cs | 22 +++++++++ 11 files changed, 194 insertions(+), 2 deletions(-) create mode 100644 src/Eto.Gtk/Forms/WindowHandler.cs create mode 100644 src/Eto.Mac/Forms/WindowHandler.cs create mode 100755 src/Eto.Wpf/Forms/WindowHandler.cs diff --git a/src/Eto.Gtk/Forms/WindowHandler.cs b/src/Eto.Gtk/Forms/WindowHandler.cs new file mode 100644 index 0000000000..5aa5aeae2b --- /dev/null +++ b/src/Eto.Gtk/Forms/WindowHandler.cs @@ -0,0 +1,49 @@ +using Eto.Drawing; +using Eto.Forms; +using System.Linq; + +namespace Eto.GtkSharp.Forms +{ + public class WindowHandler : Window.IWindowHandler + { + public Window FromPoint(PointF point) + { + var screenWindowStackWorks = false; + var windowStack = Gdk.Screen.Default.WindowStack; + // doesn't work on Mac.. can't figure out if it works on windows 🤔 + if (windowStack != null) + { + foreach (var gdkWindow in windowStack.Reverse()) + { + screenWindowStackWorks = true; + if (!gdkWindow.FrameExtents.Contains((int)point.X, (int)point.Y)) + continue; + + foreach (var window in Application.Instance.Windows) + { + if (window.Handler is IGtkWindow handler && handler.Control.Window?.Handle == gdkWindow.Handle) + { + return window; + } + } + } + } + + if (!screenWindowStackWorks) + { + // fallback to looking at all windows in no particular order. + // TODO: this needs a proper implementation for Mac and possibly also for Windows, + // depending on whether Screen.WindowStack actually works there. + var pt = Point.Round(point); + foreach (var window in Application.Instance.Windows) + { + if (window.Bounds.Contains(pt)) + { + return window; + } + } + } + return null; + } + } +} \ No newline at end of file diff --git a/src/Eto.Gtk/Platform.cs b/src/Eto.Gtk/Platform.cs index 08edc515be..9cf7774c46 100644 --- a/src/Eto.Gtk/Platform.cs +++ b/src/Eto.Gtk/Platform.cs @@ -222,6 +222,7 @@ public static void AddTo(Eto.Platform p) p.Add(() => new FixedMaskedTextProviderHandler()); p.Add(() => new DataObjectHandler()); p.Add(() => new DataFormatsHandler()); + p.Add(() => new WindowHandler()); if (EtoEnvironment.Platform.IsLinux) { p.Add(() => new LinuxTrayIndicatorHandler()); diff --git a/src/Eto.Mac/Forms/WindowHandler.cs b/src/Eto.Mac/Forms/WindowHandler.cs new file mode 100644 index 0000000000..a3afe023cf --- /dev/null +++ b/src/Eto.Mac/Forms/WindowHandler.cs @@ -0,0 +1,37 @@ +using Eto.Drawing; +using Eto.Forms; +using System.Linq; +#if XAMMAC2 +using AppKit; +using Foundation; +using CoreGraphics; +using ObjCRuntime; +using CoreAnimation; +#else +using MonoMac.AppKit; +using MonoMac.Foundation; +using MonoMac.CoreGraphics; +using MonoMac.ObjCRuntime; +using MonoMac.CoreAnimation; +#endif + +namespace Eto.Mac.Forms +{ + public class WindowHandler : Window.IWindowHandler + { + public Window FromPoint(PointF point) + { + var mainFrame = NSScreen.Screens[0].Frame; + var nspoint = new CGPoint(point.X, mainFrame.Height - point.Y); + var windowNumber = NSWindow.WindowNumberAtPoint(nspoint, 0); + foreach (var window in Application.Instance.Windows) + { + if (window.Handler is IMacWindow handler && handler.Control.WindowNumber == windowNumber) + { + return window; + } + } + return null; + } + } +} \ No newline at end of file diff --git a/src/Eto.Mac/Platform.cs b/src/Eto.Mac/Platform.cs index ca3803c157..6d54024ed1 100644 --- a/src/Eto.Mac/Platform.cs +++ b/src/Eto.Mac/Platform.cs @@ -287,6 +287,7 @@ public static void AddTo(Eto.Platform p) p.Add(() => new TrayIndicatorHandler()); p.Add(() => new DataFormatsHandler()); p.Add(() => new TaskbarHandler()); + p.Add(() => new WindowHandler()); // IO p.Add(() => new SystemIconsHandler()); diff --git a/src/Eto.WinForms/Forms/WindowHandler.cs b/src/Eto.WinForms/Forms/WindowHandler.cs index 2c25585c95..50ea3c5732 100755 --- a/src/Eto.WinForms/Forms/WindowHandler.cs +++ b/src/Eto.WinForms/Forms/WindowHandler.cs @@ -16,9 +16,27 @@ public interface IWindowHandler swf.IWin32Window Win32Window { get; } } - static class WindowHandler + public class WindowHandler : Window.IWindowHandler { internal static readonly object MovableByWindowBackground_Key = new object(); + + public Window FromPoint(PointF point) + { + var screenPoint = point.LogicalToScreen(); + var windowHandle = Win32.WindowFromPoint(new Win32.POINT(screenPoint.X, screenPoint.Y)); + if (windowHandle == IntPtr.Zero) + return null; + windowHandle = Win32.GetAncestor(windowHandle, Win32.GA.GA_ROOT); + + foreach (var window in Application.Instance.Windows) + { + if (window.NativeHandle == windowHandle) + { + return window; + } + } + return null; + } } public abstract class WindowHandler : WindowsPanel, Window.IHandler, IWindowHandler diff --git a/src/Eto.WinForms/Platform.cs b/src/Eto.WinForms/Platform.cs index 49ee5111e9..90b6adb045 100644 --- a/src/Eto.WinForms/Platform.cs +++ b/src/Eto.WinForms/Platform.cs @@ -173,6 +173,7 @@ public static void AddTo(Eto.Platform p) p.Add(() => new DataObjectHandler()); p.Add(() => new DataFormatsHandler()); p.Add(() => new TaskbarHandler()); + p.Add(() => new WindowHandler()); // IO p.Add(() => new SystemIconsHandler()); diff --git a/src/Eto.WinForms/Win32.cs b/src/Eto.WinForms/Win32.cs index 074d91b30d..9207f8a52f 100644 --- a/src/Eto.WinForms/Win32.cs +++ b/src/Eto.WinForms/Win32.cs @@ -492,5 +492,8 @@ public static IntPtr GetThreadFocusWindow(uint? threadId = null) [DllImport("gdi32.dll")] public static extern bool OffsetWindowOrgEx(IntPtr hdc, int nXOffset, int nYOffset, ref POINT lpPoint); + + [DllImport("user32.dll")] + public static extern IntPtr WindowFromPoint(POINT lpPoint); } } diff --git a/src/Eto.Wpf/Forms/WindowHandler.cs b/src/Eto.Wpf/Forms/WindowHandler.cs new file mode 100755 index 0000000000..08c5e926df --- /dev/null +++ b/src/Eto.Wpf/Forms/WindowHandler.cs @@ -0,0 +1,30 @@ +using System; +using Eto.Drawing; +using Eto.Forms; + +namespace Eto.Wpf.Forms +{ + + public class WindowHandler : Window.IWindowHandler + { + internal static readonly object MovableByWindowBackground_Key = new object(); + + public Window FromPoint(PointF point) + { + var screenPoint = point.LogicalToScreen(); + var windowHandle = Win32.WindowFromPoint(new Win32.POINT(screenPoint.X, screenPoint.Y)); + if (windowHandle == IntPtr.Zero) + return null; + windowHandle = Win32.GetAncestor(windowHandle, Win32.GA.GA_ROOT); + + foreach (var window in Application.Instance.Windows) + { + if (window.NativeHandle == windowHandle) + { + return window; + } + } + return null; + } + } +} \ No newline at end of file diff --git a/src/Eto.Wpf/Platform.cs b/src/Eto.Wpf/Platform.cs index da8c6c7eec..bd4f968c3b 100755 --- a/src/Eto.Wpf/Platform.cs +++ b/src/Eto.Wpf/Platform.cs @@ -192,6 +192,7 @@ public static void AddTo(Eto.Platform p) p.Add(() => new DataObjectHandler()); p.Add(() => new DataFormatsHandler()); p.Add(() => new TaskbarHandler()); + p.Add(() => new WindowHandler()); // IO p.Add(() => new SystemIconsHandler()); diff --git a/src/Eto/Forms/Window.cs b/src/Eto/Forms/Window.cs index a358544a3e..9c77e67392 100644 --- a/src/Eto/Forms/Window.cs +++ b/src/Eto/Forms/Window.cs @@ -51,7 +51,7 @@ public enum WindowStyle /// public abstract class Window : Panel { - new IHandler Handler { get { return (IHandler)base.Handler; } } + new IHandler Handler => (IHandler)base.Handler; #region Events @@ -204,6 +204,19 @@ static Window() EventLookup.Register(c => c.OnWindowStateChanged(null), WindowStateChangedEvent); EventLookup.Register(c => c.OnLogicalPixelSizeChanged(null), LogicalPixelSizeChangedEvent); } + + /// + /// Gets the window at the specified logical screen point. + /// + /// + /// This should get the first Eto window directly underneath the specified point. + /// + /// Point to find the window at + /// Instance of a Window (Form or Dialog) underneath the specified point, or null if none found + public static Window FromPoint(PointF point) + { + return Platform.Instance.CreateShared().FromPoint(point); + } /// /// Initializes a new instance of the class. @@ -846,6 +859,22 @@ public void OnLogicalPixelSizeChanged(Window widget, EventArgs e) /// true to auto size the window when its content changes, false to only auto size when first created bool AutoSize { get; set; } } + + /// + /// Handler interface for static methods of + /// + public interface IWindowHandler + { + /// + /// Gets the window at the specified logical screen point. + /// + /// + /// This should get the first Eto window directly underneath the specified point. + /// + /// Point to find the window at + /// Instance of a Window (Form or Dialog) underneath the specified point, or null if none found + Window FromPoint(PointF point); + } #endregion } diff --git a/test/Eto.Test/UnitTests/Forms/WindowTests.cs b/test/Eto.Test/UnitTests/Forms/WindowTests.cs index 0e541a4a48..906d7000f5 100644 --- a/test/Eto.Test/UnitTests/Forms/WindowTests.cs +++ b/test/Eto.Test/UnitTests/Forms/WindowTests.cs @@ -7,6 +7,7 @@ using System.Collections.Specialized; using Eto.Drawing; using System.Threading; +using System.Threading.Tasks; namespace Eto.Test.UnitTests.Forms { @@ -251,6 +252,27 @@ Label CreateLabel() return layout; }); } + + [Test, ManualTest] + public void WindowFromPointShouldReturnWindowUnderPoint() + { + ManualForm("Move your mouse, it should show the title of the window under the mouse pointer", + form => { + var content = new Panel { MinimumSize = new Size(100, 100) }; + var timer = new UITimer { Interval = 0.5 }; + timer.Elapsed += (sender, e) => { + var window = Window.FromPoint(Mouse.Position); + content.Content = $"Window: {window?.Title}"; + }; + timer.Start(); + form.Closed += (sender, e) => { + timer.Stop(); + }; + form.Title = "Test Form"; + return content; + } + ); + } } }