Skip to content

Commit

Permalink
Add Window.FromPoint API
Browse files Browse the repository at this point in the history
  • Loading branch information
cwensley committed Jan 21, 2022
1 parent fd3b7a5 commit 4787218
Show file tree
Hide file tree
Showing 11 changed files with 194 additions and 2 deletions.
49 changes: 49 additions & 0 deletions src/Eto.Gtk/Forms/WindowHandler.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
1 change: 1 addition & 0 deletions src/Eto.Gtk/Platform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ public static void AddTo(Eto.Platform p)
p.Add<FixedMaskedTextProvider.IHandler>(() => new FixedMaskedTextProviderHandler());
p.Add<DataObject.IHandler>(() => new DataObjectHandler());
p.Add<DataFormats.IHandler>(() => new DataFormatsHandler());
p.Add<Window.IWindowHandler>(() => new WindowHandler());
if (EtoEnvironment.Platform.IsLinux)
{
p.Add<TrayIndicator.IHandler>(() => new LinuxTrayIndicatorHandler());
Expand Down
37 changes: 37 additions & 0 deletions src/Eto.Mac/Forms/WindowHandler.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
1 change: 1 addition & 0 deletions src/Eto.Mac/Platform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,7 @@ public static void AddTo(Eto.Platform p)
p.Add<TrayIndicator.IHandler>(() => new TrayIndicatorHandler());
p.Add<DataFormats.IHandler>(() => new DataFormatsHandler());
p.Add<Taskbar.IHandler>(() => new TaskbarHandler());
p.Add<Window.IWindowHandler>(() => new WindowHandler());

// IO
p.Add<SystemIcons.IHandler>(() => new SystemIconsHandler());
Expand Down
20 changes: 19 additions & 1 deletion src/Eto.WinForms/Forms/WindowHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<TControl, TWidget, TCallback> : WindowsPanel<TControl, TWidget, TCallback>, Window.IHandler, IWindowHandler
Expand Down
1 change: 1 addition & 0 deletions src/Eto.WinForms/Platform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@ public static void AddTo(Eto.Platform p)
p.Add<DataObject.IHandler>(() => new DataObjectHandler());
p.Add<DataFormats.IHandler>(() => new DataFormatsHandler());
p.Add<Taskbar.IHandler>(() => new TaskbarHandler());
p.Add<Window.IWindowHandler>(() => new WindowHandler());

// IO
p.Add<SystemIcons.IHandler>(() => new SystemIconsHandler());
Expand Down
3 changes: 3 additions & 0 deletions src/Eto.WinForms/Win32.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
30 changes: 30 additions & 0 deletions src/Eto.Wpf/Forms/WindowHandler.cs
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
1 change: 1 addition & 0 deletions src/Eto.Wpf/Platform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@ public static void AddTo(Eto.Platform p)
p.Add<DataObject.IHandler>(() => new DataObjectHandler());
p.Add<DataFormats.IHandler>(() => new DataFormatsHandler());
p.Add<Taskbar.IHandler>(() => new TaskbarHandler());
p.Add<Window.IWindowHandler>(() => new WindowHandler());

// IO
p.Add<SystemIcons.IHandler>(() => new SystemIconsHandler());
Expand Down
31 changes: 30 additions & 1 deletion src/Eto/Forms/Window.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public enum WindowStyle
/// </summary>
public abstract class Window : Panel
{
new IHandler Handler { get { return (IHandler)base.Handler; } }
new IHandler Handler => (IHandler)base.Handler;

#region Events

Expand Down Expand Up @@ -204,6 +204,19 @@ static Window()
EventLookup.Register<Window>(c => c.OnWindowStateChanged(null), WindowStateChangedEvent);
EventLookup.Register<Window>(c => c.OnLogicalPixelSizeChanged(null), LogicalPixelSizeChangedEvent);
}

/// <summary>
/// Gets the window at the specified logical screen point.
/// </summary>
/// <remarks>
/// This should get the first Eto window directly underneath the specified point.
/// </remarks>
/// <param name="point">Point to find the window at</param>
/// <returns>Instance of a Window (Form or Dialog) underneath the specified point, or null if none found</returns>
public static Window FromPoint(PointF point)
{
return Platform.Instance.CreateShared<IWindowHandler>().FromPoint(point);
}

/// <summary>
/// Initializes a new instance of the <see cref="Eto.Forms.Window"/> class.
Expand Down Expand Up @@ -846,6 +859,22 @@ public void OnLogicalPixelSizeChanged(Window widget, EventArgs e)
/// <value><c>true</c> to auto size the window when its content changes, <c>false</c> to only auto size when first created</value>
bool AutoSize { get; set; }
}

/// <summary>
/// Handler interface for static methods of <see cref="Window"/>
/// </summary>
public interface IWindowHandler
{
/// <summary>
/// Gets the window at the specified logical screen point.
/// </summary>
/// <remarks>
/// This should get the first Eto window directly underneath the specified point.
/// </remarks>
/// <param name="point">Point to find the window at</param>
/// <returns>Instance of a Window (Form or Dialog) underneath the specified point, or null if none found</returns>
Window FromPoint(PointF point);
}

#endregion
}
Expand Down
22 changes: 22 additions & 0 deletions test/Eto.Test/UnitTests/Forms/WindowTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Collections.Specialized;
using Eto.Drawing;
using System.Threading;
using System.Threading.Tasks;

namespace Eto.Test.UnitTests.Forms
{
Expand Down Expand Up @@ -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;
}
);
}
}
}

0 comments on commit 4787218

Please sign in to comment.