Skip to content

Commit

Permalink
Add Control.IsMouseCaptured, CaptureMouse, and ReleaseMouseCapture APIs
Browse files Browse the repository at this point in the history
  • Loading branch information
cwensley committed Jan 18, 2024
1 parent 51e1798 commit f6c6aeb
Show file tree
Hide file tree
Showing 13 changed files with 594 additions and 66 deletions.
114 changes: 110 additions & 4 deletions src/Eto.Gtk/Forms/GtkControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ namespace Eto.GtkSharp.Forms
{
public interface IGtkControl
{
Control Widget { get; }
Point CurrentLocation { get; set; }

Size UserPreferredSize { get; }
Expand All @@ -17,6 +18,9 @@ public interface IGtkControl
#if GTK2
void TriggerEnabled(bool oldEnabled, bool newEnabled, bool force = false);
#endif

void TriggerMouseEnterIfNeeded();
void TriggerMouseLeaveIfNeeded();
}

public static class GtkControlExtensions
Expand Down Expand Up @@ -58,7 +62,13 @@ static class GtkControl
public static readonly object Cursor_Key = new object();
public static readonly object AllowDrop_Key = new object();
public static readonly object DeferMouseLeave_Key = new object();
public static readonly object IsMouseCaptured_Key = new object();
public static readonly object LastEnteredControls_Key = new object();
public static readonly object ShouldTranslatePoints_Key = new object();
public static uint? DefaultBorderWidth;

public static HashSet<IGtkControl> EnteredControls = new HashSet<IGtkControl>();
public static bool ShouldCaptureMouse;
}

public abstract class GtkControl<TControl, TWidget, TCallback> : WidgetHandler<TControl, TWidget, TCallback>, Control.IHandler, IGtkControl
Expand Down Expand Up @@ -535,7 +545,7 @@ public void HandleControlLeaveNotifyEvent(object o, Gtk.LeaveNotifyEventArgs arg
return;

// ignore child events
if (args.Event.Detail == Gdk.NotifyType.Inferior)
if (args.Event.Detail == Gdk.NotifyType.Inferior || !_mouseEntered || args.Event.Window != handler.EventControl.GetWindow())
return;
var p = new PointF((float)args.Event.X, (float)args.Event.Y);
Keys modifiers = args.Event.State.ToEtoKey();
Expand All @@ -546,26 +556,43 @@ public void HandleControlLeaveNotifyEvent(object o, Gtk.LeaveNotifyEventArgs arg
Application.Instance.AsyncInvoke(() =>
{
if (!handler.Widget.IsDisposed)
{
GtkControl.EnteredControls.Remove(handler);
handler.Callback.OnMouseLeave(handler.Widget, new MouseEventArgs(buttons, modifiers, p));
}
});
}
else
{
GtkControl.EnteredControls.Remove(handler);
handler.Callback.OnMouseLeave(handler.Widget, new MouseEventArgs(buttons, modifiers, p));
}
}

public void TriggerMouseLeaveIfNeeded()
{
var handler = Handler;
if (handler == null || !_mouseEntered)
return;
_mouseEntered = false;
GtkControl.EnteredControls.Remove(handler);
var p = handler.Widget.PointFromScreen(Mouse.Position);
Keys modifiers = Keyboard.Modifiers;
MouseButtons buttons = MouseButtons.None;
handler.Callback.OnMouseLeave(handler.Widget, new MouseEventArgs(buttons, modifiers, p));
}
public void TriggerMouseEnterIfNeeded()
{
var handler = Handler;
if (handler == null || _mouseEntered)
return;
_mouseEntered = true;
GtkControl.EnteredControls.Add(handler);
var p = handler.Widget.PointFromScreen(Mouse.Position);
Keys modifiers = Keyboard.Modifiers;
MouseButtons buttons = MouseButtons.None;
handler.Callback.OnMouseEnter(handler.Widget, new MouseEventArgs(buttons, modifiers, p));
}

[GLib.ConnectBefore]
public void HandleControlEnterNotifyEvent(object o, Gtk.EnterNotifyEventArgs args)
Expand All @@ -575,12 +602,13 @@ public void HandleControlEnterNotifyEvent(object o, Gtk.EnterNotifyEventArgs arg
return;

// ignore child events
if (args.Event.Detail == Gdk.NotifyType.Inferior)
if (args.Event.Detail == Gdk.NotifyType.Inferior || _mouseEntered || args.Event.Window != handler.EventControl.GetWindow())
return;
var p = new PointF((float)args.Event.X, (float)args.Event.Y);
Keys modifiers = args.Event.State.ToEtoKey();
MouseButtons buttons = MouseButtons.None;
_mouseEntered = true;
GtkControl.EnteredControls.Add(handler);
handler.Callback.OnMouseEnter(handler.Widget, new MouseEventArgs(buttons, modifiers, p));
}

Expand All @@ -607,6 +635,7 @@ public void HandleButtonReleaseEvent(object o, Gtk.ButtonReleaseEventArgs args)
if (handler == null)
return;

handler.IsMouseCaptured = false;
var p = new PointF((float)args.Event.X, (float)args.Event.Y);
p = handler.TranslatePoint(args.Event.Window, p);
Keys modifiers = args.Event.State.ToEtoKey();
Expand All @@ -624,6 +653,8 @@ public void HandleButtonPressEvent(object sender, Gtk.ButtonPressEventArgs args)
if (handler == null)
return;

handler.IsMouseCaptured = false;
GtkControl.ShouldCaptureMouse = true;
var p = new PointF((float)args.Event.X, (float)args.Event.Y);
p = handler.TranslatePoint(args.Event.Window, p);
Keys modifiers = args.Event.State.ToEtoKey();
Expand All @@ -641,6 +672,8 @@ public void HandleButtonPressEvent(object sender, Gtk.ButtonPressEventArgs args)
handler.EventControl.GrabFocus();
if (args.RetVal != null && (bool)args.RetVal == true)
return;
if (mouseArgs.Handled && GtkControl.ShouldCaptureMouse)
handler.IsMouseCaptured = true;
args.RetVal = mouseArgs.Handled;
}

Expand Down Expand Up @@ -936,8 +969,13 @@ public virtual void HandleStateFlagsChangedForEnabled(object o, Gtk.StateFlagsCh
}
#endif
}


public virtual bool ShouldTranslatePoints => false;
public virtual bool ShouldTranslatePoints
{
get => Widget.Properties.Get<bool>(GtkControl.ShouldTranslatePoints_Key);
private set => Widget.Properties.Set(GtkControl.ShouldTranslatePoints_Key, value);
}

public virtual PointF TranslatePoint(Gdk.Window window, PointF p)
{
Expand Down Expand Up @@ -1187,5 +1225,73 @@ public virtual void UpdateLayout()
// is this the best way to force a layout pass? I can't find anything else..
ContainerControl.Toplevel?.SizeAllocate(ContainerControl.Toplevel.Allocation);
}

public bool IsMouseCaptured
{
get => Widget.Properties.Get(GtkControl.IsMouseCaptured_Key, false) || EventControl.HasGrab;
private set => Widget.Properties.Set(GtkControl.IsMouseCaptured_Key, value);
}

Control IGtkControl.Widget => Widget;

public void TriggerMouseEnterIfNeeded() => Connector.TriggerMouseEnterIfNeeded();
public void TriggerMouseLeaveIfNeeded() => Connector.TriggerMouseLeaveIfNeeded();

public bool CaptureMouse()
{
NativeMethods.gtk_grab_add(EventControl.Handle);
var ret = EventControl.HasGrab;

// var status = Gdk.Display.Default.DefaultSeat.Grab(EventControl.GetWindow(), Gdk.SeatCapabilities.Pointer, false, null, null, null);
// var ret = status == Gdk.GrabStatus.Success || status == Gdk.GrabStatus.AlreadyGrabbed;
IsMouseCaptured = ret;
if (ret)
{
GtkControl.ShouldCaptureMouse = false;
ShouldTranslatePoints = true;
var lastEntered = Widget.Properties.Create<List<IGtkControl>>(GtkControl.LastEnteredControls_Key);
lastEntered.Clear();
var parents = Widget.Parents.Select(r => r.Handler).OfType<IGtkControl>().ToList();
foreach (var entered in GtkControl.EnteredControls)
{
if (parents.Contains(entered))
continue;
entered.TriggerMouseLeaveIfNeeded();
lastEntered.Add(entered);
}
foreach (var parent in parents)
parent.TriggerMouseEnterIfNeeded();
TriggerMouseEnterIfNeeded();
}

return ret;
}

public void ReleaseMouseCapture()
{
if (IsMouseCaptured)
{
ShouldTranslatePoints = false;
NativeMethods.gtk_grab_remove(EventControl.Handle);
// Gdk.Display.Default.DefaultSeat.Ungrab(); // doesn't work?!
IsMouseCaptured = false;
var lastEntered = Widget.Properties.Create<List<IGtkControl>>(GtkControl.LastEnteredControls_Key);
var mouseLocation = Mouse.Position;
if (!Widget.RectangleToScreen(new Rectangle(Widget.Size)).Contains(mouseLocation))
TriggerMouseLeaveIfNeeded();
var parents = Widget.Parents.Select(r => r.Handler).OfType<IGtkControl>().ToList();
foreach (var parent in parents)
{
if (!parent.Widget.RectangleToScreen(new Rectangle(parent.Widget.Size)).Contains(mouseLocation))
parent.TriggerMouseLeaveIfNeeded();
}

foreach (var last in lastEntered)
{
if (last.Widget.RectangleToScreen(new Rectangle(last.Widget.Size)).Contains(mouseLocation))
last.TriggerMouseEnterIfNeeded();
}
}
}
}
}
38 changes: 38 additions & 0 deletions src/Eto.Gtk/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,12 @@ static NMWindows()
[DllImport(libgtk, CallingConvention = CallingConvention.Cdecl)]
public extern static uint gtk_get_micro_version();

[DllImport(libgtk, CallingConvention = CallingConvention.Cdecl)]
public extern static void gtk_grab_add(IntPtr widget);

[DllImport(libgtk, CallingConvention = CallingConvention.Cdecl)]
public extern static void gtk_grab_remove(IntPtr widget);

[DllImport(libgdk, CallingConvention = CallingConvention.Cdecl)]
public extern static bool gdk_cairo_get_clip_rectangle(IntPtr context, IntPtr rect);

Expand Down Expand Up @@ -439,6 +445,12 @@ static NMLinux()
[DllImport(libgtk, CallingConvention = CallingConvention.Cdecl)]
public extern static uint gtk_get_micro_version();

[DllImport(libgtk, CallingConvention = CallingConvention.Cdecl)]
public extern static void gtk_grab_add(IntPtr widget);

[DllImport(libgtk, CallingConvention = CallingConvention.Cdecl)]
public extern static void gtk_grab_remove(IntPtr widget);

[DllImport(libgdk, CallingConvention = CallingConvention.Cdecl)]
public extern static bool gdk_cairo_get_clip_rectangle(IntPtr context, IntPtr rect);

Expand Down Expand Up @@ -658,6 +670,12 @@ static NMMac()
[DllImport(libgtk, CallingConvention = CallingConvention.Cdecl)]
public extern static uint gtk_get_micro_version();

[DllImport(libgtk, CallingConvention = CallingConvention.Cdecl)]
public extern static void gtk_grab_add(IntPtr widget);

[DllImport(libgtk, CallingConvention = CallingConvention.Cdecl)]
public extern static void gtk_grab_remove(IntPtr widget);

[DllImport(libgdk, CallingConvention = CallingConvention.Cdecl)]
public extern static bool gdk_cairo_get_clip_rectangle(IntPtr context, IntPtr rect);

Expand Down Expand Up @@ -988,6 +1006,26 @@ public static uint gtk_get_micro_version()
return NMWindows.gtk_get_micro_version();
}

public static void gtk_grab_add(IntPtr widget)
{
if (EtoEnvironment.Platform.IsLinux)
NMLinux.gtk_grab_add(widget);
else if (EtoEnvironment.Platform.IsMac)
NMMac.gtk_grab_add(widget);
else
NMWindows.gtk_grab_add(widget);
}

public static void gtk_grab_remove(IntPtr widget)
{
if (EtoEnvironment.Platform.IsLinux)
NMLinux.gtk_grab_remove(widget);
else if (EtoEnvironment.Platform.IsMac)
NMMac.gtk_grab_remove(widget);
else
NMWindows.gtk_grab_remove(widget);
}

public static IntPtr webkit_web_view_new()
{
if (EtoEnvironment.Platform.IsLinux)
Expand Down
4 changes: 3 additions & 1 deletion src/Eto.Gtk/NativeMethods.tt
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,9 @@ var gtkmethods = new[]
"IntPtr gtk_button_get_event_window(IntPtr button)",
"uint gtk_get_major_version()",
"uint gtk_get_minor_version()",
"uint gtk_get_micro_version()"
"uint gtk_get_micro_version()",
"void gtk_grab_add(IntPtr widget)",
"void gtk_grab_remove(IntPtr widget)",
};

var gdkmethods = new[]
Expand Down
Loading

0 comments on commit f6c6aeb

Please sign in to comment.