浏览代码

Merge branch 'events' of https://github.com/tznind/gui.cs into events

tznind 2 年之前
父节点
当前提交
fe2ed669c5

+ 45 - 13
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 {
 		/// </summary>
 		public static View MouseGrabView => mouseGrabView;
 
+		/// <summary>
+		/// Event to be invoked when a view want grab the mouse which can be canceled.
+		/// </summary>
+		public static event EventHandler<GrabMouseEventArgs> GrabbingMouse;
+
+		/// <summary>
+		/// Event to be invoked when a view want ungrab the mouse which can be canceled.
+		/// </summary>
+		public static event EventHandler<GrabMouseEventArgs> UnGrabbingMouse;
+
 		/// <summary>
 		/// Event to be invoked when a view grab the mouse.
 		/// </summary>
@@ -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 ();
+			}
 		}
 
 		/// <summary>
@@ -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 ();
 		}

+ 29 - 0
Terminal.Gui/Core/EventArgs/GrabMouseEventArgs.cs

@@ -0,0 +1,29 @@
+using System;
+
+namespace Terminal.Gui {
+	/// <summary>
+	/// Args for events that relate to specific <see cref="Application.MouseGrabView"/>
+	/// </summary>
+	public class GrabMouseEventArgs : EventArgs{
+
+		/// <summary>
+		/// Creates a new instance of the <see cref="GrabMouseEventArgs"/> class.
+		/// </summary>
+		/// <param name="view">The view that the event is about.</param>
+		public GrabMouseEventArgs (View view)
+		{
+			View = view;
+		}
+
+		/// <summary>
+		/// The view that the event is about.
+		/// </summary>
+		public View View { get; }
+
+		/// <summary>
+		/// Flag that allows the cancellation of the event. If set to <see langword="true"/> in the
+		/// event handler, the event will be canceled.
+		/// </summary>
+		public bool Cancel { get; set; }
+	}
+}

+ 20 - 3
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));
 		}
 
 		/// <summary>
@@ -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;
+			}
+		}
+
 		/// <summary>
 		/// Invoked when the <see cref="Application.AlternateForwardKey"/> is changed.
 		/// </summary>
@@ -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}");

+ 6 - 3
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");

+ 143 - 11
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<NullReferenceException> (() => Application.Top.AlternateForwardKeyChanged += (s,e) => alternateForwardKey = e.OldKey);
-				Assert.Throws<NullReferenceException> (() => Application.Top.AlternateBackwardKeyChanged += (s,e) => alternateBackwardKey = e.OldKey);
-				Assert.Throws<NullReferenceException> (() => Application.Top.QuitKeyChanged += (s,e) => quitKey = e.OldKey);
+				Assert.Throws<NullReferenceException> (() => Application.Top.AlternateForwardKeyChanged += (s, e) => alternateForwardKey = e.OldKey);
+				Assert.Throws<NullReferenceException> (() => Application.Top.AlternateBackwardKeyChanged += (s, e) => alternateBackwardKey = e.OldKey);
+				Assert.Throws<NullReferenceException> (() => 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);
+		}
 	}
 }