diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs index 042cd6403..2c188e6a4 100644 --- a/Terminal.Gui/Core.cs +++ b/Terminal.Gui/Core.cs @@ -228,7 +228,15 @@ namespace Terminal.Gui { View container = null; View focused = null; Direction focusDirection; + + /// + /// Event fired when the view get focus. + /// public event EventHandler OnEnter; + + /// + /// Event fired when the view lost focus. + /// public event EventHandler OnLeave; internal Direction FocusDirection { @@ -441,7 +449,7 @@ namespace Terminal.Gui { layoutNeeded = true; if (SuperView == null) return; - SuperView.layoutNeeded = true; + SuperView.SetNeedsLayout (); } /// @@ -657,58 +665,13 @@ namespace Terminal.Gui { { var h = r.Height; var w = r.Width; - for (int line = 0; line < h; line++) { - Move (0, line); + for (int line = r.Y; line < r.Y + h; line++) { + Driver.Move (r.X, line); for (int col = 0; col < w; col++) Driver.AddRune (' '); } } - /// - /// Clears the specified rectangular region with the container color - /// - public void ClearContainer (Rect r) - { - if (this.container != null) { - var current = ColorScheme; - switch (this.container.ColorScheme.Caller) { - case "Base": - Driver.SetAttribute (Colors.Base.Normal); - break; - case "Dialog": - Driver.SetAttribute (Colors.Dialog.Normal); - break; - case "Error": - Driver.SetAttribute (Colors.Error.Normal); - break; - case "Menu": - Driver.SetAttribute (Colors.Menu.Normal); - break; - } - var h = r.Height; - var w = r.Width; - for (int line = r.Y; line < r.Y + h; line++) { - Driver.Move (r.X, line); - for (int col = 0; col < w; col++) - Driver.AddRune (' '); - } - ColorScheme = current; - } - } - - public void GrabToView (MouseEvent mouseEvent, int startX, Point? dragPos, Rect frame, out int nx, out int ny) - { - var dx = mouseEvent.X - dragPos.Value.X - startX + frame.Left; - var dy = mouseEvent.Y - dragPos.Value.Y; - - nx = dx; - ny = frame.Y + dy; - if (nx < 0) - nx = 0; - if (ny < 1) - ny = frame.Y; - } - /// /// Converts the (col,row) position from the view into a screen (col,row). The values are clamped to (0..ScreenDim-1) /// @@ -971,7 +934,9 @@ namespace Terminal.Gui { if (!view.NeedDisplay.IsEmpty || view.childNeedsDisplay) { if (view.Frame.IntersectsWith (clipRect) && view.Frame.IntersectsWith (region)) { - // TODO: optimize this by computing the intersection of region and view.Bounds + // FIXED: optimize this by computing the intersection of region and view.Bounds + if (view.layoutNeeded) + view.LayoutSubviews (); view.Redraw (view.Bounds); } view.NeedDisplay = Rect.Empty; @@ -1362,7 +1327,7 @@ namespace Terminal.Gui { /// Frame. public Toplevel (Rect frame) : base (frame) { - ColorScheme = Colors.Base; + Initialize (); } /// @@ -1370,11 +1335,16 @@ namespace Terminal.Gui { /// public Toplevel () : base () { - ColorScheme = Colors.Base; + Initialize (); Width = Dim.Fill (); Height = Dim.Fill (); } + void Initialize () + { + ColorScheme = Colors.Base; + } + /// /// Convenience factory method that creates a new toplevel with the current terminal dimensions. /// @@ -1395,6 +1365,16 @@ namespace Terminal.Gui { /// public bool Modal { get; set; } + /// + /// Check id current toplevel has menu bar + /// + public bool HasMenuBar { get; set; } + + /// + /// Check id current toplevel has status bar + /// + public bool HasStatusBar { get; set; } + public override bool ProcessKey (KeyEvent keyEvent) { if (base.ProcessKey (keyEvent)) @@ -1444,6 +1424,100 @@ namespace Terminal.Gui { return false; } + public override void Add (View view) + { + if (this == Application.Top) { + if (view is MenuBar) + HasMenuBar = true; + if (view is StatusBar) + HasStatusBar = true; + } + base.Add (view); + } + + public override void Remove (View view) + { + if (this == Application.Top) { + if (view is MenuBar) + HasMenuBar = true; + if (view is StatusBar) + HasStatusBar = true; + } + base.Remove (view); + } + + public override void RemoveAll () + { + if (this == Application.Top) { + HasMenuBar = false; + HasStatusBar = false; + } + base.RemoveAll (); + } + + internal void EnsureVisibleBounds (Toplevel top, int x, int y, out int nx, out int ny) + { + nx = Math.Max (x, 0); + nx = nx + top.Frame.Width > Driver.Cols ? Math.Max(Driver.Cols - top.Frame.Width, 0) : nx; + bool m, s; + if (SuperView == null) + m = Application.Top.HasMenuBar; + else + m = ((Toplevel)SuperView).HasMenuBar; + int l = m ? 1 : 0; + ny = Math.Max (y, l); + if (SuperView == null) + s = Application.Top.HasStatusBar; + else + s = ((Toplevel)SuperView).HasStatusBar; + l = s ? Driver.Rows - 1 : Driver.Rows; + ny = Math.Min (ny, l); + ny = ny + top.Frame.Height > l ? Math.Max(l - top.Frame.Height, m ? 1 : 0) : ny; + } + + //Dictionary subViews; + internal void PositionToplevels () + { + if (this != Application.Top) { + EnsureVisibleBounds (this, Frame.X, Frame.Y, out int nx, out int ny); + if (nx != Frame.X || ny != Frame.Y) { + X = nx; + Y = ny; + } + } else { + foreach (var top in Subviews) { + if (top is Toplevel) { + EnsureVisibleBounds ((Toplevel)top, top.Frame.X, top.Frame.Y, out int nx, out int ny); + if (nx != top.Frame.X || ny != top.Frame.Y) { + top.X = nx; + top.Y = ny; + } + } + } + } + } + + public override void Redraw (Rect region) + { + if (this == Application.Top) { + if (!NeedDisplay.IsEmpty) { + Driver.SetAttribute (Colors.TopLevel.Normal); + Clear (region); + Driver.SetAttribute (Colors.Base.Normal); + } + foreach (var view in Subviews) { + if (view.Frame.IntersectsWith (region)) { + //view.SetNeedsLayout (); + view.SetNeedsDisplay (view.Bounds); + } + } + + ClearNeedsDisplay (); + } + + base.Redraw (base.Bounds); + } + /// /// This method is invoked by Application.Begin as part of the Application.Run after /// the views have been laid out, and before the views are drawn for the first time. @@ -1628,73 +1702,58 @@ namespace Terminal.Gui { // FIXED:It does not look like the event is raised on clicked-drag // need to figure that out. // - Point? dragPosition; - int startX; - public override bool MouseEvent(MouseEvent mouseEvent) + internal static Point? dragPosition; + Point start; + public override bool MouseEvent (MouseEvent mouseEvent) { // FIXED:The code is currently disabled, because the // Driver.UncookMouse does not seem to have an effect if there is // a pending mouse event activated. - if ((mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) || + int nx, ny; + if ((mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) || mouseEvent.Flags == MouseFlags.Button4Pressed)) { - if (dragPosition.HasValue) { - int nx, ny; - GrabToView (mouseEvent, startX, dragPosition, Frame, out nx, out ny); - dragPosition = new Point (nx, ny); + if (SuperView == null) { + Application.Top.SetNeedsDisplay (Frame); + Application.Top.Redraw (Frame); + } else { + SuperView.SetNeedsDisplay (Frame); + } + EnsureVisibleBounds (this, mouseEvent.X + mouseEvent.OfX - start.X, + mouseEvent.Y + mouseEvent.OfY, out nx, out ny); - //System.Diagnostics.Debug.WriteLine ($"dragging: {mouseEvent}"); - //System.Diagnostics.Debug.WriteLine ($"dx: {dx}, dy: {dy}"); - //System.Diagnostics.Debug.WriteLine ($"nx: {nx}, ny: {ny}"); + dragPosition = new Point (nx, ny); + Frame = new Rect (nx, ny, Frame.Width, Frame.Height); + X = nx; + Y = ny; //Demo.ml2.Text = $"{dx},{dy}"; - // 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); - Rect top = new Rect () { - X = 0, - Y = 0, - Width = Driver.Cols, - Height = ny - }; - Rect left = new Rect () { - X = 0, - Y = ny, - Width = nx, - Height = Driver.Rows - }; - ClearContainer (top); - ClearContainer (left); + // FIXED: optimize, only SetNeedsDisplay on the before/after regions. SetNeedsDisplay (); return true; } else { // Only start grabbing if the user clicks on the title bar. if (mouseEvent.Y == 0) { - startX = mouseEvent.X; - dragPosition = new Point (mouseEvent.X, mouseEvent.Y); + start = new Point (mouseEvent.X, mouseEvent.Y); + dragPosition = new Point (); + nx = mouseEvent.X - mouseEvent.OfX; + ny = mouseEvent.Y - mouseEvent.OfY; + dragPosition = new Point (nx, ny); Application.GrabMouse (this); } - //System.Diagnostics.Debug.WriteLine ($"Starting at {dragPosition}"); //Demo.ml2.Text = $"Starting at {dragPosition}"; return true; } } - if (mouseEvent.Flags == MouseFlags.Button1Released) { + if (mouseEvent.Flags == MouseFlags.Button1Released && dragPosition.HasValue) { Application.UngrabMouse (); Driver.UncookMouse (); - dragPosition = null; - //Driver.StopReportingMouseMoves (); } - //System.Diagnostics.Debug.WriteLine ($"mouseEvent {mouseEvent.ToString ()}"); //Demo.ml.Text = me.ToString (); return false; } @@ -1784,9 +1843,9 @@ namespace Terminal.Gui { { mainLoop.AddIdle (() => { d (state); - mainLoop.Driver.Wakeup (); return false; }); + mainLoop.Driver.Wakeup (); } public override void Send (SendOrPostCallback d, object state) @@ -1902,7 +1961,7 @@ namespace Terminal.Gui { } } - internal static View FindDeepestView (View start, int x, int y, out int resx, out int resy) + static View FindDeepestView (View start, int x, int y, out int resx, out int resy) { var startFrame = start.Frame; @@ -1933,7 +1992,7 @@ namespace Terminal.Gui { return start; } - static View mouseGrabView; + internal static View mouseGrabView; /// /// Grabs the mouse, forcing all mouse events to be routed to the specified view until UngrabMouse is called. @@ -1964,20 +2023,22 @@ namespace Terminal.Gui { static void ProcessMouseEvent (MouseEvent me) { + var view = FindDeepestView (Current, me.X, me.Y, out int rx, out int ry); 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 + Flags = me.Flags, + OfX = me.X - newxy.X, + OfY = me.Y - newxy.Y, + View = view }; - mouseGrabView.MouseEvent (me); + mouseGrabView.MouseEvent (nme); return; } - int rx, ry; - var view = FindDeepestView (Current, me.X, me.Y, out rx, out ry); if (view != null) { if (!view.WantMousePositionReports && me.Flags == MouseFlags.ReportMousePosition) return; @@ -1985,7 +2046,10 @@ namespace Terminal.Gui { var nme = new MouseEvent () { X = rx, Y = ry, - Flags = me.Flags + Flags = me.Flags, + OfX = rx, + OfY = ry, + View = view }; // Should we bubbled up the event, if it is not handled? view.MouseEvent (nme); @@ -2035,7 +2099,7 @@ namespace Terminal.Gui { } /// - /// Building block API: completes the exection of a Toplevel that was started with Begin. + /// Building block API: completes the execution of a Toplevel that was started with Begin. /// /// The runstate returned by the method. static public void End (RunState runState) @@ -2046,6 +2110,9 @@ namespace Terminal.Gui { runState.Dispose (); } + /// + /// Finalize the driver. + /// public static void Shutdown () { Driver.End (); @@ -2122,40 +2189,11 @@ namespace Terminal.Gui { DrawBounds (state.Toplevel); state.Toplevel.PositionCursor (); Driver.Refresh (); - } else if (CheckLayoutNeeded (state.Toplevel)) { - TerminalResized (); - layoutNeeded = false; - toplevel = null; } else Driver.UpdateCursor (); } } - static bool layoutNeeded; - static View toplevel; - static bool CheckLayoutNeeded (View view) - { - if (view is Toplevel && ((Toplevel)view).Modal) - return false; - if (view is Toplevel) - toplevel = view; - - return CheckTopLevelLayoutNeeded (view); - } - - private static bool CheckTopLevelLayoutNeeded (View view) - { - if (!(view is Toplevel) && view == toplevel && view.layoutNeeded) - return layoutNeeded = view.layoutNeeded; - - for (int i = 0; view.Subviews.Count > i; i++) { - CheckLayoutNeeded (view.Subviews [i]); - if (layoutNeeded) - return layoutNeeded; - } - return layoutNeeded; - } - internal static bool DebugDrawBounds; // Need to look into why this does not work properly. @@ -2229,6 +2267,7 @@ namespace Terminal.Gui { var full = new Rect (0, 0, Driver.Cols, Driver.Rows); Driver.Clip = full; foreach (var t in toplevels) { + t.PositionToplevels (); t.RelativeLayout (full); t.LayoutSubviews (); } diff --git a/Terminal.Gui/Drivers/ConsoleDriver.cs b/Terminal.Gui/Drivers/ConsoleDriver.cs index 99e839667..58b3ee50c 100644 --- a/Terminal.Gui/Drivers/ConsoleDriver.cs +++ b/Terminal.Gui/Drivers/ConsoleDriver.cs @@ -110,6 +110,11 @@ namespace Terminal.Gui { this.background = background; } + /// + /// Initializes a new instance of the struct. + /// + /// Foreground + /// Background public Attribute (Color foreground = new Color (), Color background = new Color ()) { this.value = value = ((int)foreground | (int)background << 4); @@ -156,6 +161,7 @@ namespace Terminal.Gui { private Attribute _hotNormal; private Attribute _hotFocus; private Attribute _disabled; + internal string caller = ""; /// /// The default color for text, when the view is not focused. @@ -182,8 +188,6 @@ namespace Terminal.Gui { /// public Attribute Disabled { get { return _disabled; } set { _disabled = SetAttribute (value); } } - public string Caller = ""; - private bool preparingScheme = false; private Attribute SetAttribute (Attribute attribute, [CallerMemberName]string callerMemberName = null) @@ -195,7 +199,26 @@ namespace Terminal.Gui { return attribute; preparingScheme = true; - switch (Caller) { + switch (caller) { + case "TopLevel": + switch (callerMemberName) { + case "Normal": + HotNormal = Application.Driver.MakeAttribute (HotNormal.foreground, attribute.background); + break; + case "Focus": + HotFocus = Application.Driver.MakeAttribute (HotFocus.foreground, attribute.background); + break; + case "HotNormal": + HotFocus = Application.Driver.MakeAttribute (attribute.foreground, HotFocus.background); + break; + case "HotFocus": + HotNormal = Application.Driver.MakeAttribute (attribute.foreground, HotNormal.background); + if (Focus.foreground != attribute.background) + Focus = Application.Driver.MakeAttribute (Focus.foreground, attribute.background); + break; + } + break; + case "Base": switch (callerMemberName) { case "Normal": @@ -298,11 +321,17 @@ namespace Terminal.Gui { /// The default ColorSchemes for the application. /// public static class Colors { + private static ColorScheme _toplevel; private static ColorScheme _base; private static ColorScheme _dialog; private static ColorScheme _menu; private static ColorScheme _error; + /// + /// The application toplevel color scheme, for the default toplevel views. + /// + public static ColorScheme TopLevel { get { return _toplevel; } set { _toplevel = SetColorScheme (value); } } + /// /// The base color scheme, for the default toplevel views. /// @@ -325,7 +354,7 @@ namespace Terminal.Gui { private static ColorScheme SetColorScheme (ColorScheme colorScheme, [CallerMemberName]string callerMemberName = null) { - colorScheme.Caller = callerMemberName; + colorScheme.caller = callerMemberName; return colorScheme; } } @@ -400,6 +429,9 @@ namespace Terminal.Gui { /// ConsoleDriver is an abstract class that defines the requirements for a console driver. One implementation if the CursesDriver, and another one uses the .NET Console one. /// public abstract class ConsoleDriver { + /// + /// The handler fired when the terminal is resized. + /// protected Action TerminalResized; /// @@ -431,6 +463,12 @@ namespace Terminal.Gui { /// /// String. public abstract void AddStr (ustring str); + /// + /// Prepare the driver and set the key and mouse events handlers. + /// + /// + /// + /// public abstract void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action mouseHandler); /// @@ -459,7 +497,11 @@ namespace Terminal.Gui { /// C. public abstract void SetAttribute (Attribute c); - // Set Colors from limit sets of colors + /// + /// Set Colors from limit sets of colors. + /// + /// Foreground. + /// Background. public abstract void SetColors (ConsoleColor foreground, ConsoleColor background); // Advanced uses - set colors to any pre-set pairs, you would need to init_color @@ -472,6 +514,10 @@ namespace Terminal.Gui { /// Background color identifier. public abstract void SetColors (short foregroundColorId, short backgroundColorId); + /// + /// Set the handler when the terminal is resized. + /// + /// public void SetTerminalResized(Action terminalResized) { TerminalResized = terminalResized; @@ -555,7 +601,14 @@ namespace Terminal.Gui { set => this.clip = value; } + /// + /// Start of mouse moves. + /// public abstract void StartReportingMouseMoves (); + + /// + /// Stop reporting mouses moves. + /// public abstract void StopReportingMouseMoves (); /// @@ -628,6 +681,12 @@ namespace Terminal.Gui { /// public Rune BottomTee; + /// + /// Make the attribute for the foreground and background colors. + /// + /// Foreground. + /// Background. + /// public abstract Attribute MakeAttribute (Color fore, Color back); } } diff --git a/Terminal.Gui/Drivers/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver.cs index 17cf60ad0..6077f5f7d 100644 --- a/Terminal.Gui/Drivers/WindowsDriver.cs +++ b/Terminal.Gui/Drivers/WindowsDriver.cs @@ -437,10 +437,17 @@ namespace Terminal.Gui { public WindowsDriver () { + Colors.TopLevel = new ColorScheme (); + + Colors.TopLevel.Normal = MakeColor (ConsoleColor.Green, ConsoleColor.Black); + Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan); + Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black); + Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.DarkCyan); + winConsole = new WindowsConsole (); cols = Console.WindowWidth; - rows = Console.WindowHeight - 1; + rows = Console.WindowHeight; WindowsConsole.SmallRect.MakeEmpty (ref damageRegion); ResizeScreen (); @@ -520,7 +527,6 @@ namespace Terminal.Gui { } finally { eventReady.Reset(); } - //Debug.WriteLine("Events ready"); if (!tokenSource.IsCancellationRequested) return result != null; @@ -574,16 +580,17 @@ namespace Terminal.Gui { private WindowsConsole.ButtonState? LastMouseButtonPressed = null; private bool IsButtonReleased = false; private bool IsButtonDoubleClicked = false; - private object token; private MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent) { MouseFlags mouseFlag = MouseFlags.AllEvents; - if (token != null) - Application.MainLoop.RemoveTimeout (token); - if (IsButtonDoubleClicked) - token = Application.MainLoop.AddTimeout (TimeSpan.FromMilliseconds (300), timer); + if (IsButtonDoubleClicked) { + Task.Run (async () => { + await Task.Delay (300); + _ = new Action (() => IsButtonDoubleClicked = false); + }); + } // The ButtonState member of the MouseEvent structure has bit corresponding to each mouse button. // This will tell when a mouse button is pressed. When the button is released this event will @@ -595,7 +602,6 @@ namespace Terminal.Gui { IsButtonReleased = false; } - //Debug.WriteLine ($"MouseEventRecord: {mouseEvent}"); if ((mouseEvent.EventFlags == 0 && LastMouseButtonPressed == null && !IsButtonDoubleClicked) || (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved && mouseEvent.ButtonState != 0 && !IsButtonDoubleClicked)) { @@ -696,7 +702,6 @@ namespace Terminal.Gui { mouseFlag = SetControlKeyStates (mouseEvent, mouseFlag); - Debug.WriteLine ($"MouseFlags: {mouseFlag}"); return new MouseEvent () { X = mouseEvent.MousePosition.X, Y = mouseEvent.MousePosition.Y, @@ -719,12 +724,6 @@ namespace Terminal.Gui { return mouseFlag; } - bool timer (MainLoop caller) - { - IsButtonDoubleClicked = false; - return false; - } - public ConsoleKeyInfoEx ToConsoleKeyInfoEx (WindowsConsole.KeyEventRecord keyEvent) { var state = keyEvent.dwControlKeyState; @@ -818,9 +817,12 @@ namespace Terminal.Gui { return (Key)((uint)Key.ControlA + delta); if (keyInfo.Modifiers == ConsoleModifiers.Alt) return (Key)(((uint)Key.AltMask) | ((uint)'A' + delta)); - // TODO: Only apply after pull requests at NStack are merged. - //if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) - // return (Key)((uint)keyInfo.KeyChar); + if ((keyInfo.Modifiers & (ConsoleModifiers.Alt | ConsoleModifiers.Control)) != 0) { + if (keyInfo.KeyChar == 0) + return (Key)(((uint)Key.AltMask) + ((uint)Key.ControlA + delta)); + else + return (Key)((uint)keyInfo.KeyChar); + } return (Key)((uint)alphaBase + delta); } if (key >= ConsoleKey.D0 && key <= ConsoleKey.D9) { @@ -900,7 +902,7 @@ namespace Terminal.Gui { for (int row = 0; row < rows; row++) for (int col = 0; col < cols; col++) { int position = row * cols + col; - OutputBuffer [position].Attributes = (ushort)MakeColor (ConsoleColor.White, ConsoleColor.Blue); + OutputBuffer [position].Attributes = (ushort)Colors.TopLevel.Normal; OutputBuffer [position].Char.UnicodeChar = ' '; } } diff --git a/Terminal.Gui/Event.cs b/Terminal.Gui/Event.cs index a59284d16..aea8d9780 100644 --- a/Terminal.Gui/Event.cs +++ b/Terminal.Gui/Event.cs @@ -448,6 +448,21 @@ namespace Terminal.Gui { /// public MouseFlags Flags; + /// + /// The offset X (column) location for the mouse event. + /// + public int OfX; + + /// + /// The offset Y (column) location for the mouse event. + /// + public int OfY; + + /// + /// The current view at the location for the mouse event. + /// + public View View; + /// /// Returns a that represents the current . ///