Browse Source

More mouse support, make sample quit, some mouse drag work

Miguel de Icaza 7 years ago
parent
commit
863343cd3e
6 changed files with 264 additions and 38 deletions
  1. 127 3
      Core.cs
  2. 18 0
      Driver.cs
  3. 5 0
      Event.cs
  4. 51 32
      Views/Button.cs
  5. 53 0
      Views/Menu.cs
  6. 10 3
      demo.cs

+ 127 - 3
Core.cs

@@ -132,6 +132,12 @@ namespace Terminal {
 			}
 			}
 		}
 		}
 
 
+		/// <summary>
+		/// Gets or sets a value indicating whether this <see cref="T:Terminal.View"/> want mouse position reports.
+		/// </summary>
+		/// <value><c>true</c> if want mouse position reports; otherwise, <c>false</c>.</value>
+		public virtual bool WantMousePositionReports { get; set; } = false;
+
 		// The frame for this view
 		// The frame for this view
 		public Rect Frame {
 		public Rect Frame {
 			get => frame;
 			get => frame;
@@ -318,6 +324,22 @@ namespace Terminal {
 			}
 			}
 		}
 		}
 
 
+		/// <summary>
+		/// Converts a point from screen coordinates into the view coordinate space.
+		/// </summary>
+		/// <returns>The mapped point.</returns>
+		/// <param name="x">X screen-coordinate point.</param>
+		/// <param name="y">Y screen-coordinate point.</param>
+		public Point ScreenToView (int x, int y)
+		{
+			if (SuperView == null) {
+				return new Point (x - Frame.X, y - frame.Y);
+			} else {
+				var parent = SuperView.ScreenToView (x, y);
+				return new Point (parent.X - frame.X, parent.Y - frame.Y);
+			}
+		}
+
 		// Converts a rectangle in view coordinates to screen coordinates.
 		// Converts a rectangle in view coordinates to screen coordinates.
 		Rect RectToScreen (Rect rect)
 		Rect RectToScreen (Rect rect)
 		{
 		{
@@ -826,6 +848,59 @@ namespace Terminal {
 			contentView.Redraw (contentView.Bounds);
 			contentView.Redraw (contentView.Bounds);
 			ClearNeedsDisplay ();
 			ClearNeedsDisplay ();
 		}
 		}
+
+#if false
+		// 
+		// It does not look like the event is raised on clicked-drag
+		// need to figure that out.
+		//
+		Point? dragPosition;
+		public override bool MouseEvent(MouseEvent me)
+		{
+			if (me.Flags == MouseFlags.Button1Pressed){
+				if (dragPosition.HasValue) {
+					var dx = me.X - dragPosition.Value.X;
+					var dy = me.Y - dragPosition.Value.Y;
+
+					var nx = Frame.X + dx;
+					var ny = Frame.Y + dy;
+					if (nx < 0)
+						nx = 0;
+					if (ny < 0)
+						ny = 0;
+
+					Demo.ml2.Text = $"{dx},{dy}";
+					dragPosition = new Point (me.X, me.Y);
+
+					// TODO: optimize, only SetNeedsDisplay on the before/after regions.
+					if (SuperView == null)
+						Application.Refresh ();
+					else
+						SuperView.SetNeedsDisplay ();
+					Frame = new Rect (nx, ny, Frame.Width, Frame.Height);
+					SetNeedsDisplay ();
+					return true;
+				} else {
+					dragPosition = new Point (me.X, me.Y);
+					Application.GrabMouse (this);
+
+					Demo.ml2.Text = $"Starting at {dragPosition}";
+					return true;
+				}
+
+
+			}
+
+			if (me.Flags == MouseFlags.Button1Released) {
+				Application.UngrabMouse ();
+				dragPosition = null;
+				//Driver.StopReportingMouseMoves ();
+			}
+
+			Demo.ml.Text = me.ToString ();
+			return false;
+		}
+#endif
 	}
 	}
 
 
 	/// <summary>
 	/// <summary>
@@ -848,10 +923,10 @@ namespace Terminal {
 	public class Application {
 	public class Application {
 		public static ConsoleDriver Driver = new CursesDriver ();
 		public static ConsoleDriver Driver = new CursesDriver ();
 		public static Toplevel Top { get; private set; }
 		public static Toplevel Top { get; private set; }
-		public static View Current { get; private set; }
+		public static Toplevel Current { get; private set; }
 		public static Mono.Terminal.MainLoop MainLoop { get; private set; }
 		public static Mono.Terminal.MainLoop MainLoop { get; private set; }
 
 
-		static Stack<View> toplevels = new Stack<View> ();
+		static Stack<Toplevel> toplevels = new Stack<Toplevel> ();
 
 
 		/// <summary>
 		/// <summary>
 		///   This event is raised on each iteration of the
 		///   This event is raised on each iteration of the
@@ -986,6 +1061,28 @@ namespace Terminal {
 			return start;
 			return start;
 		}
 		}
 
 
+		static View mouseGrabView;
+
+		/// <summary>
+		/// Grabs the mouse, forcing all mouse events to be routed to the specified view until UngrabMouse is called.
+		/// </summary>
+		/// <returns>The grab.</returns>
+		/// <param name="view">View that will receive all mouse events until UngrabMouse is invoked.</param>
+		public static void GrabMouse (View view)
+		{
+			if (view == null)
+				return;
+			mouseGrabView = view;
+		}
+
+		/// <summary>
+		/// Releases the mouse grab, so mouse events will be routed to the view on which the mouse is.
+		/// </summary>
+		public static void UngrabMouse ()
+		{
+			mouseGrabView = null;
+		}
+
 		/// <summary>
 		/// <summary>
 		/// Merely a debugging aid to see the raw mouse events
 		/// Merely a debugging aid to see the raw mouse events
 		/// </summary>
 		/// </summary>
@@ -994,9 +1091,23 @@ namespace Terminal {
 		static void ProcessMouseEvent (MouseEvent me)
 		static void ProcessMouseEvent (MouseEvent me)
 		{
 		{
 			RootMouseEvent?.Invoke (me);
 			RootMouseEvent?.Invoke (me);
+			if (mouseGrabView != null) {
+				var newxy = mouseGrabView.ScreenToView (me.X, me.Y);
+				var nme = new MouseEvent () {
+					X = newxy.X,
+					Y = newxy.Y,
+					Flags = me.Flags
+				};
+				mouseGrabView.MouseEvent (me);
+				return;
+			}
+
 			int rx, ry;
 			int rx, ry;
 			var view = FindDeepestView (Current, me.X, me.Y, out rx, out ry);
 			var view = FindDeepestView (Current, me.X, me.Y, out rx, out ry);
 			if (view != null) {
 			if (view != null) {
+				if (!view.WantMousePositionReports && me.Flags == MouseFlags.ReportMousePosition)
+					return;
+				
 				var nme = new MouseEvent () {
 				var nme = new MouseEvent () {
 					X = rx,
 					X = rx,
 					Y = ry,
 					Y = ry,
@@ -1075,7 +1186,7 @@ namespace Terminal {
 			if (toplevels.Count == 0)
 			if (toplevels.Count == 0)
 				Shutdown ();
 				Shutdown ();
 			else {
 			else {
-				Current = toplevels.Peek ();
+				Current = toplevels.Peek () as Toplevel;
 				Refresh ();
 				Refresh ();
 			}
 			}
 		}
 		}
@@ -1122,6 +1233,9 @@ namespace Terminal {
 					DrawBounds (sub);
 					DrawBounds (sub);
 		}
 		}
 
 
+		/// <summary>
+		/// Runs the application with the built-in toplevel view
+		/// </summary>
 		public static void Run ()
 		public static void Run ()
 		{
 		{
 			Run (Top);
 			Run (Top);
@@ -1148,6 +1262,16 @@ namespace Terminal {
 			End (runToken);
 			End (runToken);
 		}
 		}
 
 
+		/// <summary>
+		/// Stops running the most recent toplevel
+		/// </summary>
+		public static void RequestStop ()
+		{
+			var ct = Current as Toplevel;
+
+			Current.Running = false;
+		}
+
 		static void TerminalResized ()
 		static void TerminalResized ()
 		{
 		{
 			foreach (var t in toplevels) {
 			foreach (var t in toplevels) {

+ 18 - 0
Driver.cs

@@ -102,6 +102,9 @@ namespace Terminal {
 			get => clip;
 			get => clip;
 			set => this.clip = value;
 			set => this.clip = value;
 		}
 		}
+
+		public abstract void StartReportingMouseMoves ();
+		public abstract void StopReportingMouseMoves ();
 	}
 	}
 
 
 	public class CursesDriver : ConsoleDriver {
 	public class CursesDriver : ConsoleDriver {
@@ -340,6 +343,7 @@ namespace Terminal {
 			Curses.Window.Standard.keypad (true);
 			Curses.Window.Standard.keypad (true);
 			reportableMouseEvents = Curses.mousemask (Curses.Event.AllEvents | Curses.Event.ReportMousePosition, out oldMouseEvents);
 			reportableMouseEvents = Curses.mousemask (Curses.Event.AllEvents | Curses.Event.ReportMousePosition, out oldMouseEvents);
 			this.terminalResized = terminalResized;
 			this.terminalResized = terminalResized;
+			StartReportingMouseMoves ();
 
 
 			Colors.Base = new ColorScheme ();
 			Colors.Base = new ColorScheme ();
 			Colors.Dialog = new ColorScheme ();
 			Colors.Dialog = new ColorScheme ();
@@ -396,9 +400,23 @@ namespace Terminal {
 
 
 		public override void Suspend ()
 		public override void Suspend ()
 		{
 		{
+			StopReportingMouseMoves ();
 			Platform.Suspend ();
 			Platform.Suspend ();
 			Curses.Window.Standard.redrawwin ();
 			Curses.Window.Standard.redrawwin ();
 			Curses.refresh ();
 			Curses.refresh ();
+			StartReportingMouseMoves ();
+		}
+
+		public override void StartReportingMouseMoves()
+		{
+			Console.Out.Write ("\x1b[?1003h");
+			Console.Out.Flush ();
+		}
+
+		public override void StopReportingMouseMoves()
+		{
+			Console.Out.Write ("\x1b[?1003l");
+			Console.Out.Flush ();
 		}
 		}
 	}
 	}
 
 

+ 5 - 0
Event.cs

@@ -161,5 +161,10 @@ namespace Terminal {
 		/// Flags indicating the kind of mouse event that is being posted.
 		/// Flags indicating the kind of mouse event that is being posted.
 		/// </summary>
 		/// </summary>
 		public MouseFlags Flags;
 		public MouseFlags Flags;
+
+		public override string ToString()
+		{
+			return $"({X},{Y}:{Flags}";
+		}
 	}
 	}
 }
 }

+ 51 - 32
Views/Button.cs

@@ -25,6 +25,18 @@ namespace Terminal {
 		int hot_pos = -1;
 		int hot_pos = -1;
 		bool is_default;
 		bool is_default;
 
 
+		/// <summary>
+		/// Gets or sets a value indicating whether this <see cref="T:Terminal.Button"/> is the default action to activate on return on a dialog.
+		/// </summary>
+		/// <value><c>true</c> if is default; otherwise, <c>false</c>.</value>
+		public bool IsDefault {
+			get => is_default;
+			set {
+				is_default = value;
+				Update ();
+			}
+		}
+
 		/// <summary>
 		/// <summary>
 		///   Clicked event, raised when the button is clicked.
 		///   Clicked event, raised when the button is clicked.
 		/// </summary>
 		/// </summary>
@@ -33,7 +45,7 @@ namespace Terminal {
 		///   raised when the button is activated either with
 		///   raised when the button is activated either with
 		///   the mouse or the keyboard.
 		///   the mouse or the keyboard.
 		/// </remarks>
 		/// </remarks>
-		public event EventHandler Clicked;
+		public Action Clicked;
 
 
 		/// <summary>
 		/// <summary>
 		///   Public constructor, creates a button based on
 		///   Public constructor, creates a button based on
@@ -76,23 +88,29 @@ namespace Terminal {
 
 
 			set {
 			set {
 				text = value;
 				text = value;
-				if (is_default)
-					shown_text = "[< " + value + " >]";
-				else
-					shown_text = "[ " + value + " ]";
-
-				hot_pos = -1;
-				hot_key = (char)0;
-				int i = 0;
-				foreach (char c in shown_text) {
-					if (Char.IsUpper (c)) {
-						hot_key = c;
-						hot_pos = i;
-						break;
-					}
-					i++;
+				Update ();
+			}
+		}
+
+		internal void Update ()
+		{
+			if (IsDefault)
+				shown_text = "[< " + text + " >]";
+			else
+				shown_text = "[ " + text + " ]";
+
+			hot_pos = -1;
+			hot_key = (char)0;
+			int i = 0;
+			foreach (char c in shown_text) {
+				if (Char.IsUpper (c)) {
+					hot_key = c;
+					hot_pos = i;
+					break;
 				}
 				}
+				i++;
 			}
 			}
+			SetNeedsDisplay ();
 		}
 		}
 
 
 		/// <summary>
 		/// <summary>
@@ -109,7 +127,7 @@ namespace Terminal {
 		{
 		{
 			CanFocus = true;
 			CanFocus = true;
 
 
-			this.is_default = is_default;
+			this.IsDefault = is_default;
 			Text = s;
 			Text = s;
 		}
 		}
 
 
@@ -136,7 +154,7 @@ namespace Terminal {
 			if (Char.ToUpper ((char)key.KeyValue) == hot_key) {
 			if (Char.ToUpper ((char)key.KeyValue) == hot_key) {
 				this.SuperView.SetFocus (this);
 				this.SuperView.SetFocus (this);
 				if (Clicked != null)
 				if (Clicked != null)
-					Clicked (this, EventArgs.Empty);
+					Clicked ();
 				return true;
 				return true;
 			}
 			}
 			return false;
 			return false;
@@ -152,9 +170,9 @@ namespace Terminal {
 
 
 		public override bool ProcessColdKey (KeyEvent kb)
 		public override bool ProcessColdKey (KeyEvent kb)
 		{
 		{
-			if (is_default && kb.KeyValue == '\n') {
+			if (IsDefault && kb.KeyValue == '\n') {
 				if (Clicked != null)
 				if (Clicked != null)
-					Clicked (this, EventArgs.Empty);
+					Clicked ();
 				return true;
 				return true;
 			}
 			}
 			return CheckKey (kb);
 			return CheckKey (kb);
@@ -165,22 +183,23 @@ namespace Terminal {
 			var c = kb.KeyValue;
 			var c = kb.KeyValue;
 			if (c == '\n' || c == ' ' || Char.ToUpper ((char)c) == hot_key) {
 			if (c == '\n' || c == ' ' || Char.ToUpper ((char)c) == hot_key) {
 				if (Clicked != null)
 				if (Clicked != null)
-					Clicked (this, EventArgs.Empty);
+					Clicked ();
 				return true;
 				return true;
 			}
 			}
 			return base.ProcessKey (kb);
 			return base.ProcessKey (kb);
 		}
 		}
 
 
-#if false
-        public override void ProcessMouse (Curses.MouseEvent ev)
-        {
-            if ((ev.ButtonState & Curses.Event.Button1Clicked) != 0) {
-                Container.SetFocus (this);
-                Container.Redraw ();
-                if (Clicked != null)
-                    Clicked (this, EventArgs.Empty);
-            }
-        }
-#endif
+		public override bool MouseEvent(MouseEvent me)
+		{
+			if (me.Flags == MouseFlags.Button1Clicked) {
+				SuperView.SetFocus (this);
+				SetNeedsDisplay ();
+
+				if (Clicked != null)
+					Clicked ();
+				return true;
+			}
+			return false;
+		}
 	}
 	}
 }
 }

+ 53 - 0
Views/Menu.cs

@@ -192,6 +192,30 @@ namespace Terminal {
 			}
 			}
 			return base.ProcessKey (kb);
 			return base.ProcessKey (kb);
 		}
 		}
+
+		public override bool MouseEvent(MouseEvent me)
+		{
+			if (me.Flags == MouseFlags.Button1Clicked || me.Flags == MouseFlags.Button1Released) {
+				if (me.Y < 1)
+					return true;
+				var item = me.Y - 1;
+				if (item >= barItems.Children.Length)
+					return true;
+				host.CloseMenu ();
+				Run (barItems.Children [item].Action);
+				return true;
+			}
+			if (me.Flags == MouseFlags.Button1Pressed) {
+				if (me.Y < 1)
+					return true;
+				if (me.Y - 1 >= barItems.Children.Length)
+					return true;
+				current = me.Y - 1;
+				SetNeedsDisplay ();
+				return true;
+			}
+			return false;
+		}
 	}
 	}
 
 
 	/// <summary>
 	/// <summary>
@@ -275,6 +299,7 @@ namespace Terminal {
 			SuperView.SetFocus (openMenu);
 			SuperView.SetFocus (openMenu);
 		}
 		}
 
 
+		// Starts the menu from a hotkey
 		void StartMenu ()
 		void StartMenu ()
 		{
 		{
 			if (openMenu != null)
 			if (openMenu != null)
@@ -286,6 +311,18 @@ namespace Terminal {
 			OpenMenu (selected);
 			OpenMenu (selected);
 		}
 		}
 
 
+		// Activates the menu, handles either first focus, or activating an entry when it was already active
+		// For mouse events.
+		void Activate (int idx)
+		{
+			selected = idx;
+			if (openMenu == null) 
+				previousFocused = SuperView.Focused;
+			
+			OpenMenu (idx);
+			SetNeedsDisplay ();
+		}
+
 		internal void CloseMenu ()
 		internal void CloseMenu ()
 		{
 		{
 			selected = -1;
 			selected = -1;
@@ -368,6 +405,22 @@ namespace Terminal {
 			SetNeedsDisplay ();
 			SetNeedsDisplay ();
 			return true;
 			return true;
 		}
 		}
+
+		public override bool MouseEvent(MouseEvent me)
+		{
+			if (me.Flags == MouseFlags.Button1Clicked) {
+ 				int pos = 1;
+				int cx = me.X;
+				for (int i = 0; i < Menus.Length; i++) {
+					if (cx > pos && me.X < pos + 1 + Menus [i].TitleLength) {
+						Activate (i);
+						return true;
+					}
+					pos += 2 + Menus [i].TitleLength + 1;
+				}
+			}
+			return false;
+		}
 	}
 	}
 
 
 }
 }

+ 10 - 3
demo.cs

@@ -25,13 +25,20 @@ class Demo {
 		);
 		);
 	}
 	}
 
 
+	public static Label ml2;
+
 	static void NewFile ()
 	static void NewFile ()
 	{
 	{
-		var d = new Dialog ("New File", 50, 20, new Button ("Ok"), new Button ("Cancel"));
+		var d = new Dialog (
+			"New File", 50, 20,
+			new Button ("Ok", is_default: true ) { Clicked = () => { Application.RequestStop (); } },
+			new Button ("Cancel") { Clicked = () => { Application.RequestStop (); } });
+		ml2 = new Label (1, 1, "Mouse Debug Line");
+		d.Add (ml2);
 		Application.Run (d);
 		Application.Run (d);
 	}
 	}
 
 
-	static Label ml;
+	public static Label ml;
 	static void Main ()
 	static void Main ()
 	{
 	{
 		Application.Init ();
 		Application.Init ();
@@ -44,7 +51,7 @@ class Demo {
 				new MenuItem ("_New", "Creates new file", NewFile),
 				new MenuItem ("_New", "Creates new file", NewFile),
 				new MenuItem ("_Open", "", null),
 				new MenuItem ("_Open", "", null),
 				new MenuItem ("_Close", "", null),
 				new MenuItem ("_Close", "", null),
-				new MenuItem ("_Quit", "", null)
+				new MenuItem ("_Quit", "", () => { top.Running = false; })
 			}),
 			}),
 			new MenuBarItem ("_Edit", new MenuItem [] {
 			new MenuBarItem ("_Edit", new MenuItem [] {
 				new MenuItem ("_Copy", "", null),
 				new MenuItem ("_Copy", "", null),