diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs
index 561d6a8c4..990c02e71 100644
--- a/Terminal.Gui/Core/Application.cs
+++ b/Terminal.Gui/Core/Application.cs
@@ -162,7 +162,7 @@ namespace Terminal.Gui {
if (alternateForwardKey != value) {
var oldKey = alternateForwardKey;
alternateForwardKey = value;
- OnAlternateForwardKeyChanged (new KeyChangedEventArgs(oldKey,value));
+ OnAlternateForwardKeyChanged (new KeyChangedEventArgs (oldKey, value));
}
}
}
@@ -186,7 +186,7 @@ namespace Terminal.Gui {
if (alternateBackwardKey != value) {
var oldKey = alternateBackwardKey;
alternateBackwardKey = value;
- OnAlternateBackwardKeyChanged (new KeyChangedEventArgs(oldKey,value));
+ OnAlternateBackwardKeyChanged (new KeyChangedEventArgs (oldKey, value));
}
}
}
@@ -210,7 +210,7 @@ namespace Terminal.Gui {
if (quitKey != value) {
var oldKey = quitKey;
quitKey = value;
- OnQuitKeyChanged (new KeyChangedEventArgs(oldKey,value));
+ OnQuitKeyChanged (new KeyChangedEventArgs (oldKey, value));
}
}
}
@@ -720,6 +720,16 @@ namespace Terminal.Gui {
///
public static View MouseGrabView => mouseGrabView;
+ ///
+ /// Event to be invoked when a view want grab the mouse which can be canceled.
+ ///
+ public static event EventHandler GrabbingMouse;
+
+ ///
+ /// Event to be invoked when a view want ungrab the mouse which can be canceled.
+ ///
+ public static event EventHandler UnGrabbingMouse;
+
///
/// Event to be invoked when a view grab the mouse.
///
@@ -739,9 +749,11 @@ namespace Terminal.Gui {
{
if (view == null)
return;
- OnGrabbedMouse (view);
- mouseGrabView = view;
- Driver.UncookMouse ();
+ if (!OnGrabbingMouse (view)) {
+ OnGrabbedMouse (view);
+ mouseGrabView = view;
+ Driver.UncookMouse ();
+ }
}
///
@@ -751,16 +763,36 @@ namespace Terminal.Gui {
{
if (mouseGrabView == null)
return;
- OnUnGrabbedMouse (mouseGrabView);
- mouseGrabView = null;
- Driver.CookMouse ();
+ if (!OnUnGrabbingMouse (mouseGrabView)) {
+ OnUnGrabbedMouse (mouseGrabView);
+ mouseGrabView = null;
+ Driver.CookMouse ();
+ }
+ }
+
+ static bool OnGrabbingMouse (View view)
+ {
+ if (view == null)
+ return false;
+ var evArgs = new GrabMouseEventArgs (view);
+ GrabbingMouse?.Invoke (view, evArgs);
+ return evArgs.Cancel;
+ }
+
+ static bool OnUnGrabbingMouse (View view)
+ {
+ if (view == null)
+ return false;
+ var evArgs = new GrabMouseEventArgs (view);
+ UnGrabbingMouse?.Invoke (view, evArgs);
+ return evArgs.Cancel;
}
static void OnGrabbedMouse (View view)
{
if (view == null)
return;
- GrabbedMouse?.Invoke (view, new ViewEventArgs(view));
+ GrabbedMouse?.Invoke (view, new ViewEventArgs (view));
}
static void OnUnGrabbedMouse (View view)
@@ -1029,7 +1061,7 @@ namespace Terminal.Gui {
Driver.Refresh ();
}
- NotifyNewRunState?.Invoke (toplevel, new RunStateEventArgs(rs));
+ NotifyNewRunState?.Invoke (toplevel, new RunStateEventArgs (rs));
return rs;
}
@@ -1497,7 +1529,7 @@ namespace Terminal.Gui {
static void OnNotifyStopRunState (Toplevel top)
{
if (ExitRunLoopAfterFirstIteration) {
- NotifyStopRunState?.Invoke (top, new ToplevelEventArgs(top));
+ NotifyStopRunState?.Invoke (top, new ToplevelEventArgs (top));
}
}
@@ -1516,7 +1548,7 @@ namespace Terminal.Gui {
t.SetRelativeLayout (full);
t.LayoutSubviews ();
t.PositionToplevels ();
- t.OnResized (new SizeChangedEventArgs(full.Size));
+ t.OnResized (new SizeChangedEventArgs (full.Size));
}
Refresh ();
}
diff --git a/Terminal.Gui/Core/EventArgs/GrabMouseEventArgs.cs b/Terminal.Gui/Core/EventArgs/GrabMouseEventArgs.cs
new file mode 100644
index 000000000..952dc5ac6
--- /dev/null
+++ b/Terminal.Gui/Core/EventArgs/GrabMouseEventArgs.cs
@@ -0,0 +1,29 @@
+using System;
+
+namespace Terminal.Gui {
+ ///
+ /// Args for events that relate to specific
+ ///
+ public class GrabMouseEventArgs : EventArgs{
+
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// The view that the event is about.
+ public GrabMouseEventArgs (View view)
+ {
+ View = view;
+ }
+
+ ///
+ /// The view that the event is about.
+ ///
+ public View View { get; }
+
+ ///
+ /// Flag that allows the cancellation of the event. If set to in the
+ /// event handler, the event will be canceled.
+ ///
+ public bool Cancel { get; set; }
+ }
+}
diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs
index 02727aa39..7995337ba 100644
--- a/Terminal.Gui/Core/Toplevel.cs
+++ b/Terminal.Gui/Core/Toplevel.cs
@@ -120,7 +120,7 @@ namespace Terminal.Gui {
internal virtual void OnChildUnloaded (Toplevel top)
{
- ChildUnloaded?.Invoke (this, new ToplevelEventArgs(top));
+ ChildUnloaded?.Invoke (this, new ToplevelEventArgs (top));
}
internal virtual void OnChildLoaded (Toplevel top)
@@ -159,7 +159,7 @@ namespace Terminal.Gui {
internal virtual void OnActivate (Toplevel deactivated)
{
- Activate?.Invoke (this, new ToplevelEventArgs(deactivated));
+ Activate?.Invoke (this, new ToplevelEventArgs (deactivated));
}
///
@@ -221,6 +221,9 @@ namespace Terminal.Gui {
{
ColorScheme = Colors.TopLevel;
+ Application.GrabbingMouse += Application_GrabbingMouse;
+ Application.UnGrabbingMouse += Application_UnGrabbingMouse;
+
// Things this view knows how to do
AddCommand (Command.QuitToplevel, () => { QuitToplevel (); return true; });
AddCommand (Command.Suspend, () => { Driver.Suspend (); ; return true; });
@@ -256,6 +259,20 @@ namespace Terminal.Gui {
AddKeyBinding (Key.L | Key.CtrlMask, Command.Refresh);
}
+ private void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e)
+ {
+ if (Application.MouseGrabView == this && dragPosition.HasValue) {
+ e.Cancel = true;
+ }
+ }
+
+ private void Application_GrabbingMouse (object sender, GrabMouseEventArgs e)
+ {
+ if (Application.MouseGrabView == this && dragPosition.HasValue) {
+ e.Cancel = true;
+ }
+ }
+
///
/// Invoked when the is changed.
///
@@ -824,8 +841,8 @@ namespace Terminal.Gui {
}
if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released) && dragPosition.HasValue) {
- Application.UngrabMouse ();
dragPosition = null;
+ Application.UngrabMouse ();
}
//System.Diagnostics.Debug.WriteLine ($"dragPosition after: {dragPosition.HasValue}");
diff --git a/UICatalog/Scenarios/Clipping.cs b/UICatalog/Scenarios/Clipping.cs
index 606902bd4..58f89b7a3 100644
--- a/UICatalog/Scenarios/Clipping.cs
+++ b/UICatalog/Scenarios/Clipping.cs
@@ -37,7 +37,8 @@ namespace UICatalog.Scenarios {
Y = 3,
Width = Dim.Fill (3),
Height = Dim.Fill (3),
- ColorScheme = Colors.Dialog
+ ColorScheme = Colors.Dialog,
+ Id = "1"
};
var embedded2 = new Window ("2") {
@@ -45,7 +46,8 @@ namespace UICatalog.Scenarios {
Y = 3,
Width = Dim.Fill (3),
Height = Dim.Fill (3),
- ColorScheme = Colors.Error
+ ColorScheme = Colors.Error,
+ Id = "2"
};
embedded1.Add (embedded2);
@@ -54,7 +56,8 @@ namespace UICatalog.Scenarios {
Y = 3,
Width = Dim.Fill (3),
Height = Dim.Fill (3),
- ColorScheme = Colors.TopLevel
+ ColorScheme = Colors.TopLevel,
+ Id = "3"
};
var testButton = new Button (2, 2, "click me");
diff --git a/UnitTests/TopLevels/ToplevelTests.cs b/UnitTests/TopLevels/ToplevelTests.cs
index 1da5891e5..12763a163 100644
--- a/UnitTests/TopLevels/ToplevelTests.cs
+++ b/UnitTests/TopLevels/ToplevelTests.cs
@@ -154,28 +154,28 @@ namespace Terminal.Gui.TopLevelTests {
var eventInvoked = "";
- top.ChildUnloaded += (s,e) => eventInvoked = "ChildUnloaded";
+ top.ChildUnloaded += (s, e) => eventInvoked = "ChildUnloaded";
top.OnChildUnloaded (top);
Assert.Equal ("ChildUnloaded", eventInvoked);
- top.ChildLoaded += (s,e) => eventInvoked = "ChildLoaded";
+ top.ChildLoaded += (s, e) => eventInvoked = "ChildLoaded";
top.OnChildLoaded (top);
Assert.Equal ("ChildLoaded", eventInvoked);
top.Closed += (s, e) => eventInvoked = "Closed";
top.OnClosed (top);
Assert.Equal ("Closed", eventInvoked);
- top.Closing += (s,e) => eventInvoked = "Closing";
+ top.Closing += (s, e) => eventInvoked = "Closing";
top.OnClosing (new ToplevelClosingEventArgs (top));
Assert.Equal ("Closing", eventInvoked);
top.AllChildClosed += (s, e) => eventInvoked = "AllChildClosed";
top.OnAllChildClosed ();
Assert.Equal ("AllChildClosed", eventInvoked);
- top.ChildClosed += (s,e) => eventInvoked = "ChildClosed";
+ top.ChildClosed += (s, e) => eventInvoked = "ChildClosed";
top.OnChildClosed (top);
Assert.Equal ("ChildClosed", eventInvoked);
- top.Deactivate += (s,e) => eventInvoked = "Deactivate";
+ top.Deactivate += (s, e) => eventInvoked = "Deactivate";
top.OnDeactivate (top);
Assert.Equal ("Deactivate", eventInvoked);
- top.Activate += (s,e) => eventInvoked = "Activate";
+ top.Activate += (s, e) => eventInvoked = "Activate";
top.OnActivate (top);
Assert.Equal ("Activate", eventInvoked);
top.Loaded += (s, e) => eventInvoked = "Loaded";
@@ -356,7 +356,7 @@ namespace Terminal.Gui.TopLevelTests {
var top = Application.Top;
top.Add (win1, win2);
top.Loaded += (s, e) => isRunning = true;
- top.Closing += (s,e) => isRunning = false;
+ top.Closing += (s, e) => isRunning = false;
Application.Begin (top);
top.Running = true;
@@ -589,9 +589,9 @@ namespace Terminal.Gui.TopLevelTests {
void View_Added (object sender, SuperViewChangedEventArgs e)
{
- Assert.Throws (() => Application.Top.AlternateForwardKeyChanged += (s,e) => alternateForwardKey = e.OldKey);
- Assert.Throws (() => Application.Top.AlternateBackwardKeyChanged += (s,e) => alternateBackwardKey = e.OldKey);
- Assert.Throws (() => Application.Top.QuitKeyChanged += (s,e) => quitKey = e.OldKey);
+ Assert.Throws (() => Application.Top.AlternateForwardKeyChanged += (s, e) => alternateForwardKey = e.OldKey);
+ Assert.Throws (() => Application.Top.AlternateBackwardKeyChanged += (s, e) => alternateBackwardKey = e.OldKey);
+ Assert.Throws (() => Application.Top.QuitKeyChanged += (s, e) => quitKey = e.OldKey);
Assert.False (wasAdded);
wasAdded = true;
view.Added -= View_Added;
@@ -621,7 +621,7 @@ namespace Terminal.Gui.TopLevelTests {
void View_Initialized (object sender, EventArgs e)
{
- Application.Top.AlternateForwardKeyChanged += (s,e) => alternateForwardKey = e.OldKey;
+ Application.Top.AlternateForwardKeyChanged += (s, e) => alternateForwardKey = e.OldKey;
Application.Top.AlternateBackwardKeyChanged += (s, e) => alternateBackwardKey = e.OldKey;
Application.Top.QuitKeyChanged += (s, e) => quitKey = e.OldKey;
}
@@ -1081,5 +1081,137 @@ namespace Terminal.Gui.TopLevelTests {
Assert.Equal (new Rect (1, 3, 10, 5), view.Frame);
Assert.Equal (new Rect (0, 0, 10, 5), view.NeedDisplay);
}
+
+ [Fact, AutoInitShutdown]
+ public void Toplevel_Inside_ScrollView_MouseGrabView ()
+ {
+ var scrollView = new ScrollView () {
+ X = 3,
+ Y = 3,
+ Width = 40,
+ Height = 16,
+ ContentSize = new Size (200, 100)
+ };
+ var win = new Window ("Window") { X = 3, Y = 3, Width = Dim.Fill (3), Height = Dim.Fill (3) };
+ scrollView.Add (win);
+ var top = Application.Top;
+ top.Add (scrollView);
+ Application.Begin (top);
+
+ Assert.Equal (new Rect (0, 0, 80, 25), top.Frame);
+ Assert.Equal (new Rect (3, 3, 40, 16), scrollView.Frame);
+ Assert.Equal (new Rect (0, 0, 200, 100), scrollView.Subviews [0].Frame);
+ Assert.Equal (new Rect (3, 3, 194, 94), win.Frame);
+ TestHelpers.AssertDriverContentsWithFrameAre (@"
+ ▲
+ ┬
+ │
+ ┌ Window ───────────────────────────┴
+ │ ░
+ │ ░
+ │ ░
+ │ ░
+ │ ░
+ │ ░
+ │ ░
+ │ ░
+ │ ░
+ │ ░
+ │ ▼
+ ◄├──────┤░░░░░░░░░░░░░░░░░░░░░░░░░░░░░► ", output);
+
+ ReflectionTools.InvokePrivate (
+ typeof (Application),
+ "ProcessMouseEvent",
+ new MouseEvent () {
+ X = 6,
+ Y = 6,
+ Flags = MouseFlags.Button1Pressed
+ });
+ Assert.Equal (win, Application.MouseGrabView);
+ Assert.Equal (new Rect (3, 3, 194, 94), win.Frame);
+
+ ReflectionTools.InvokePrivate (
+ typeof (Application),
+ "ProcessMouseEvent",
+ new MouseEvent () {
+ X = 9,
+ Y = 9,
+ Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition
+ });
+ Assert.Equal (win, Application.MouseGrabView);
+ top.SetNeedsLayout ();
+ top.LayoutSubviews ();
+ Assert.Equal (new Rect (6, 6, 191, 91), win.Frame);
+ Application.Refresh ();
+ TestHelpers.AssertDriverContentsWithFrameAre (@"
+ ▲
+ ┬
+ │
+ ┴
+ ░
+ ░
+ ┌ Window ────────────────────────░
+ │ ░
+ │ ░
+ │ ░
+ │ ░
+ │ ░
+ │ ░
+ │ ░
+ │ ▼
+ ◄├──────┤░░░░░░░░░░░░░░░░░░░░░░░░░░░░░► ", output);
+
+ ReflectionTools.InvokePrivate (
+ typeof (Application),
+ "ProcessMouseEvent",
+ new MouseEvent () {
+ X = 5,
+ Y = 5,
+ Flags = MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition
+ });
+ Assert.Equal (win, Application.MouseGrabView);
+ top.SetNeedsLayout ();
+ top.LayoutSubviews ();
+ Assert.Equal (new Rect (2, 2, 195, 95), win.Frame);
+ Application.Refresh ();
+ TestHelpers.AssertDriverContentsWithFrameAre (@"
+ ▲
+ ┬
+ ┌ Window ────────────────────────────│
+ │ ┴
+ │ ░
+ │ ░
+ │ ░
+ │ ░
+ │ ░
+ │ ░
+ │ ░
+ │ ░
+ │ ░
+ │ ░
+ │ ▼
+ ◄├──────┤░░░░░░░░░░░░░░░░░░░░░░░░░░░░░► ", output);
+
+ ReflectionTools.InvokePrivate (
+ typeof (Application),
+ "ProcessMouseEvent",
+ new MouseEvent () {
+ X = 5,
+ Y = 5,
+ Flags = MouseFlags.Button1Released
+ });
+ Assert.Null (Application.MouseGrabView);
+
+ ReflectionTools.InvokePrivate (
+ typeof (Application),
+ "ProcessMouseEvent",
+ new MouseEvent () {
+ X = 4,
+ Y = 4,
+ Flags = MouseFlags.ReportMousePosition
+ });
+ Assert.Equal (scrollView, Application.MouseGrabView);
+ }
}
}
\ No newline at end of file