diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 93a4db09d..aae666622 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -143,6 +143,11 @@ namespace Terminal.Gui { background: MapCursesColor (background)); } + static Attribute MakeColor (Color fore, Color back) + { + return MakeColor ((short)MapColor (fore), (short)MapColor (back)); + } + int [,] colorPairs = new int [16, 16]; public override void SetColors (ConsoleColor foreground, ConsoleColor background) @@ -253,6 +258,7 @@ namespace Terminal.Gui { bool cancelButtonClicked; bool isReportMousePosition; Point point; + int buttonPressedCount; MouseEvent ToDriverMouse (Curses.MouseEvent cev) { @@ -263,10 +269,59 @@ namespace Terminal.Gui { isButtonPressed = false; } + if (cev.ButtonState == Curses.Event.Button1Pressed + || cev.ButtonState == Curses.Event.Button2Pressed + || cev.ButtonState == Curses.Event.Button3Pressed) { - if ((cev.ButtonState == Curses.Event.Button1Clicked || cev.ButtonState == Curses.Event.Button2Clicked || - cev.ButtonState == Curses.Event.Button3Clicked) && - lastMouseButtonPressed == null) { + isButtonPressed = true; + buttonPressedCount++; + } else { + buttonPressedCount = 0; + } + //System.Diagnostics.Debug.WriteLine ($"buttonPressedCount: {buttonPressedCount}"); + + if (buttonPressedCount == 2 + && (cev.ButtonState == Curses.Event.Button1Pressed + || cev.ButtonState == Curses.Event.Button2Pressed + || cev.ButtonState == Curses.Event.Button3Pressed)) { + + switch (cev.ButtonState) { + case Curses.Event.Button1Pressed: + mouseFlag = MouseFlags.Button1DoubleClicked; + break; + + case Curses.Event.Button2Pressed: + mouseFlag = MouseFlags.Button2DoubleClicked; + break; + + case Curses.Event.Button3Pressed: + mouseFlag = MouseFlags.Button3DoubleClicked; + break; + } + + } else if (buttonPressedCount == 3 + && (cev.ButtonState == Curses.Event.Button1Pressed + || cev.ButtonState == Curses.Event.Button2Pressed + || cev.ButtonState == Curses.Event.Button3Pressed)) { + + switch (cev.ButtonState) { + case Curses.Event.Button1Pressed: + mouseFlag = MouseFlags.Button1TripleClicked; + break; + + case Curses.Event.Button2Pressed: + mouseFlag = MouseFlags.Button2TripleClicked; + break; + + case Curses.Event.Button3Pressed: + mouseFlag = MouseFlags.Button3TripleClicked; + break; + } + buttonPressedCount = 0; + + } else if ((cev.ButtonState == Curses.Event.Button1Clicked || cev.ButtonState == Curses.Event.Button2Clicked || + cev.ButtonState == Curses.Event.Button3Clicked) && + lastMouseButtonPressed == null) { isButtonPressed = false; mouseFlag = ProcessButtonClickedEvent (cev); @@ -797,57 +852,66 @@ namespace Terminal.Gui { Curses.StartColor (); Curses.UseDefaultColors (); - Colors.TopLevel.Normal = MakeColor (Curses.COLOR_GREEN, Curses.COLOR_BLACK); - Colors.TopLevel.Focus = MakeColor (Curses.COLOR_WHITE, Curses.COLOR_CYAN); - Colors.TopLevel.HotNormal = MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_BLACK); - Colors.TopLevel.HotFocus = MakeColor (Curses.COLOR_BLUE, Curses.COLOR_CYAN); + Colors.TopLevel.Normal = MakeColor (Color.Green, Color.Black); + Colors.TopLevel.Focus = MakeColor (Color.White, Color.Cyan); + Colors.TopLevel.HotNormal = MakeColor (Color.Brown, Color.Black); + Colors.TopLevel.HotFocus = MakeColor (Color.Blue, Color.Cyan); + Colors.TopLevel.Disabled = MakeColor (Color.DarkGray, Color.Black); - Colors.Base.Normal = MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLUE); - Colors.Base.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE); - Colors.Base.HotNormal = MakeColor (Curses.COLOR_CYAN, Curses.COLOR_BLUE); - Colors.Base.HotFocus = Curses.A_BOLD | MakeColor (Curses.COLOR_BLUE, Curses.COLOR_GRAY); + Colors.Base.Normal = MakeColor (Color.White, Color.Blue); + Colors.Base.Focus = MakeColor (Color.Black, Color.Gray); + Colors.Base.HotNormal = MakeColor (Color.Cyan, Color.Blue); + Colors.Base.HotFocus = MakeColor (Color.Blue, Color.Gray); + Colors.Base.Disabled = MakeColor (Color.DarkGray, Color.Blue); // Focused, // Selected, Hot: Yellow on Black // Selected, text: white on black // Unselected, hot: yellow on cyan // unselected, text: same as unfocused - Colors.Menu.Normal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_GRAY); - Colors.Menu.Focus = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLACK); - Colors.Menu.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_GRAY); - Colors.Menu.HotFocus = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_BLACK); - Colors.Menu.Disabled = MakeColor (Curses.COLOR_WHITE, Curses.COLOR_GRAY); + Colors.Menu.Normal = MakeColor (Color.White, Color.DarkGray); + Colors.Menu.Focus = MakeColor (Color.White, Color.Black); + Colors.Menu.HotNormal = MakeColor (Color.BrightYellow, Color.DarkGray); + Colors.Menu.HotFocus = MakeColor (Color.BrightYellow, Color.Black); + Colors.Menu.Disabled = MakeColor (Color.Gray, Color.DarkGray); - Colors.Dialog.Normal = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE); - Colors.Dialog.Focus = MakeColor (Curses.COLOR_WHITE, Curses.COLOR_GRAY); - Colors.Dialog.HotNormal = MakeColor (Curses.COLOR_BLUE, Curses.COLOR_WHITE); - Colors.Dialog.HotFocus = MakeColor (Curses.COLOR_BLUE, Curses.COLOR_GRAY); + Colors.Dialog.Normal = MakeColor (Color.Black, Color.Gray); + Colors.Dialog.Focus = MakeColor (Color.White, Color.DarkGray); + Colors.Dialog.HotNormal = MakeColor (Color.Blue, Color.Gray); + Colors.Dialog.HotFocus = MakeColor (Color.Blue, Color.DarkGray); + Colors.Dialog.Disabled = MakeColor (Color.DarkGray, Color.Gray); - Colors.Error.Normal = MakeColor (Curses.COLOR_RED, Curses.COLOR_WHITE); - Colors.Error.Focus = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_RED); - Colors.Error.HotNormal = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE); - Colors.Error.HotFocus = Curses.A_BOLD | MakeColor (Curses.COLOR_BLACK, Curses.COLOR_RED); + Colors.Error.Normal = MakeColor (Color.Red, Color.White); + Colors.Error.Focus = MakeColor (Color.White, Color.Red); + Colors.Error.HotNormal = MakeColor (Color.Black, Color.White); + Colors.Error.HotFocus = MakeColor (Color.Black, Color.Red); + Colors.Error.Disabled = MakeColor (Color.DarkGray, Color.White); } else { Colors.TopLevel.Normal = Curses.COLOR_GREEN; Colors.TopLevel.Focus = Curses.COLOR_WHITE; Colors.TopLevel.HotNormal = Curses.COLOR_YELLOW; Colors.TopLevel.HotFocus = Curses.COLOR_YELLOW; + Colors.TopLevel.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY; Colors.Base.Normal = Curses.A_NORMAL; Colors.Base.Focus = Curses.A_REVERSE; Colors.Base.HotNormal = Curses.A_BOLD; Colors.Base.HotFocus = Curses.A_BOLD | Curses.A_REVERSE; + Colors.Base.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY; Colors.Menu.Normal = Curses.A_REVERSE; Colors.Menu.Focus = Curses.A_NORMAL; Colors.Menu.HotNormal = Curses.A_BOLD; Colors.Menu.HotFocus = Curses.A_NORMAL; + Colors.Menu.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY; Colors.Dialog.Normal = Curses.A_REVERSE; Colors.Dialog.Focus = Curses.A_NORMAL; Colors.Dialog.HotNormal = Curses.A_BOLD; Colors.Dialog.HotFocus = Curses.A_NORMAL; + Colors.Dialog.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY; Colors.Error.Normal = Curses.A_BOLD; Colors.Error.Focus = Curses.A_BOLD | Curses.A_REVERSE; Colors.Error.HotNormal = Curses.A_BOLD | Curses.A_REVERSE; Colors.Error.HotFocus = Curses.A_REVERSE; + Colors.Error.Disabled = Curses.A_BOLD | Curses.COLOR_GRAY; } } diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs index 0aa48539f..ba68cf3ef 100644 --- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs @@ -145,7 +145,9 @@ namespace Terminal.Gui { cols = FakeConsole.WindowWidth = FakeConsole.BufferWidth = FakeConsole.WIDTH; rows = FakeConsole.WindowHeight = FakeConsole.BufferHeight = FakeConsole.HEIGHT; - UpdateOffscreen (); + FakeConsole.Clear (); + ResizeScreen (); + UpdateOffScreen (); Colors.TopLevel = new ColorScheme (); Colors.Base = new ColorScheme (); @@ -158,11 +160,13 @@ namespace Terminal.Gui { Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan); Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black); Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkCyan); + Colors.TopLevel.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Black); Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Blue); Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan); Colors.Base.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Blue); Colors.Base.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan); + Colors.Base.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.DarkBlue); // Focused, // Selected, Hot: Yellow on Black @@ -179,11 +183,13 @@ namespace Terminal.Gui { Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan); Colors.Dialog.HotNormal = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray); Colors.Dialog.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Cyan); + Colors.Dialog.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Gray); Colors.Error.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Red); Colors.Error.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray); Colors.Error.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Red); Colors.Error.HotFocus = Colors.Error.HotNormal; + Colors.Error.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.White); //MockConsole.Clear (); } @@ -442,7 +448,11 @@ namespace Terminal.Gui { /// public override bool GetCursorVisibility (out CursorVisibility visibility) { - visibility = CursorVisibility.Default; + if (FakeConsole.CursorVisible) { + visibility = CursorVisibility.Default; + } else { + visibility = CursorVisibility.Invisible; + } return false; } @@ -450,6 +460,12 @@ namespace Terminal.Gui { /// public override bool SetCursorVisibility (CursorVisibility visibility) { + if (visibility == CursorVisibility.Invisible) { + FakeConsole.CursorVisible = false; + } else { + FakeConsole.CursorVisible = true; + } + return false; } diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index df97fe92c..7aceb48bd 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -506,7 +506,7 @@ namespace Terminal.Gui { bool isButtonDoubleClicked; bool isButtonTripleClicked; bool isProcContBtnPressedRuning; - Point point = new Point (); + int buttonPressedCount; //bool isButtonReleased; void GetMouseEvent (ConsoleKeyInfo [] cki) @@ -701,16 +701,52 @@ namespace Terminal.Gui { if ((buttonState & MouseButtonState.Button1Pressed) != 0 || (buttonState & MouseButtonState.Button2Pressed) != 0 || (buttonState & MouseButtonState.Button3Pressed) != 0) { + + if ((buttonState & MouseButtonState.ReportMousePosition) == 0) { + buttonPressedCount++; + } else { + buttonPressedCount = 0; + } + //System.Diagnostics.Debug.WriteLine ($"buttonPressedCount: {buttonPressedCount}"); isButtonPressed = true; } else { isButtonPressed = false; + buttonPressedCount = 0; } + if (buttonPressedCount == 2 && !isButtonDoubleClicked + && (lastMouseEvent.ButtonState == MouseButtonState.Button1Pressed + || lastMouseEvent.ButtonState == MouseButtonState.Button2Pressed + || lastMouseEvent.ButtonState == MouseButtonState.Button3Pressed)) { + + isButtonDoubleClicked = true; + ProcessButtonDoubleClicked (mouseEvent); + inputReady.Set (); + return; + } else if (buttonPressedCount == 3 && isButtonDoubleClicked + && (lastMouseEvent.ButtonState == MouseButtonState.Button1Pressed + || lastMouseEvent.ButtonState == MouseButtonState.Button2Pressed + || lastMouseEvent.ButtonState == MouseButtonState.Button3Pressed)) { + + isButtonDoubleClicked = false; + isButtonTripleClicked = true; + buttonPressedCount = 0; + ProcessButtonTripleClicked (mouseEvent); + lastMouseEvent = mouseEvent; + inputReady.Set (); + return; + } + + //System.Diagnostics.Debug.WriteLine ($"isButtonClicked: {isButtonClicked} isButtonDoubleClicked: {isButtonDoubleClicked} isButtonTripleClicked: {isButtonTripleClicked}"); if ((isButtonClicked || isButtonDoubleClicked || isButtonTripleClicked) && ((buttonState & MouseButtonState.Button1Released) != 0 || (buttonState & MouseButtonState.Button2Released) != 0 || (buttonState & MouseButtonState.Button3Released) != 0)) { + + //isButtonClicked = false; + //isButtonDoubleClicked = false; isButtonTripleClicked = false; + buttonPressedCount = 0; return; } @@ -721,11 +757,13 @@ namespace Terminal.Gui { || (buttonState & MouseButtonState.Button1Released) != 0 || (buttonState & MouseButtonState.Button2Released) != 0 || (buttonState & MouseButtonState.Button3Released) != 0)) { + + isButtonClicked = false; isButtonDoubleClicked = true; ProcessButtonDoubleClicked (mouseEvent); Application.MainLoop.AddIdle (() => { Task.Run (async () => { - await Task.Delay (300); + await Task.Delay (600); isButtonDoubleClicked = false; }); return false; @@ -740,6 +778,8 @@ namespace Terminal.Gui { || (buttonState & MouseButtonState.Button1Released) != 0 || (buttonState & MouseButtonState.Button2Released) != 0 || (buttonState & MouseButtonState.Button3Released) != 0)) { + + isButtonDoubleClicked = false; isButtonTripleClicked = true; ProcessButtonTripleClicked (mouseEvent); inputReady.Set (); @@ -792,7 +832,7 @@ namespace Terminal.Gui { } if ((buttonState & MouseButtonState.ReportMousePosition) == 0) { Application.MainLoop.AddIdle (() => { - Task.Run (async () => await ProcessContinuousButtonPressedAsync (mouseEvent)); + Task.Run (async () => await ProcessContinuousButtonPressedAsync ()); return false; }); } @@ -873,24 +913,20 @@ namespace Terminal.Gui { }); } - async Task ProcessContinuousButtonPressedAsync (MouseEvent mouseEvent) + async Task ProcessContinuousButtonPressedAsync () { isProcContBtnPressedRuning = true; await Task.Delay (200); while (isButtonPressed) { await Task.Delay (100); - var me = new MouseEvent () { - Position = new Point (mouseEvent.Position.X, mouseEvent.Position.Y), - ButtonState=mouseEvent.ButtonState - }; var view = Application.wantContinuousButtonPressedView; if (view == null) { break; } - if (isButtonPressed && (mouseEvent.ButtonState & MouseButtonState.ReportMousePosition) == 0) { + if (isButtonPressed && (lastMouseEvent.ButtonState & MouseButtonState.ReportMousePosition) == 0) { inputResultQueue.Enqueue (new InputResult () { EventType = EventType.Mouse, - MouseEvent = me + MouseEvent = lastMouseEvent }); inputReady.Set (); } @@ -1244,11 +1280,13 @@ namespace Terminal.Gui { Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan); Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black); Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkCyan); + Colors.TopLevel.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Black); Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkBlue); Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray); Colors.Base.HotNormal = MakeColor (ConsoleColor.DarkCyan, ConsoleColor.DarkBlue); Colors.Base.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray); + Colors.Base.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.DarkBlue); // Focused, // Selected, Hot: Yellow on Black @@ -1265,11 +1303,13 @@ namespace Terminal.Gui { Colors.Dialog.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkGray); Colors.Dialog.HotNormal = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.Gray); Colors.Dialog.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkGray); + Colors.Dialog.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Gray); Colors.Error.Normal = MakeColor (ConsoleColor.DarkRed, ConsoleColor.White); Colors.Error.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkRed); Colors.Error.HotNormal = MakeColor (ConsoleColor.Black, ConsoleColor.White); Colors.Error.HotFocus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkRed); + Colors.Error.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.White); } void ResizeScreen () diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index c77bd7c0e..340deba13 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -791,11 +791,15 @@ namespace Terminal.Gui { bool isButtonReleased = false; bool isButtonDoubleClicked = false; Point point; + int buttonPressedCount; + bool isOneFingerDoubleClicked = false; MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent) { MouseFlags mouseFlag = MouseFlags.AllEvents; + //System.Diagnostics.Debug.WriteLine ($"ButtonState: {mouseEvent.ButtonState};EventFlags: {mouseEvent.EventFlags}"); + if (isButtonDoubleClicked) { Application.MainLoop.AddIdle (() => { Task.Run (async () => await ProcessButtonDoubleClickedAsync ()); @@ -810,7 +814,7 @@ namespace Terminal.Gui { // map to the correct clicked event. if ((lastMouseButtonPressed != null || isButtonReleased) && mouseEvent.ButtonState != 0) { lastMouseButtonPressed = null; - isButtonPressed = false; + //isButtonPressed = false; isButtonReleased = false; } @@ -819,9 +823,70 @@ namespace Terminal.Gui { Y = mouseEvent.MousePosition.Y }; - if ((mouseEvent.ButtonState != 0 && mouseEvent.EventFlags == 0 && lastMouseButtonPressed == null && !isButtonDoubleClicked) || - (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved && - mouseEvent.ButtonState != 0 && !isButtonReleased && !isButtonDoubleClicked)) { + if (!isButtonPressed && buttonPressedCount < 2 + && mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved + && (mouseEvent.ButtonState == WindowsConsole.ButtonState.Button1Pressed + || mouseEvent.ButtonState == WindowsConsole.ButtonState.Button2Pressed + || mouseEvent.ButtonState == WindowsConsole.ButtonState.Button3Pressed)) { + + lastMouseButtonPressed = mouseEvent.ButtonState; + buttonPressedCount++; + } else if (!isButtonPressed && buttonPressedCount > 0 && mouseEvent.ButtonState == 0 + && mouseEvent.EventFlags == 0) { + + buttonPressedCount++; + } else { + buttonPressedCount = 0; + isOneFingerDoubleClicked = false; + } + //System.Diagnostics.Debug.WriteLine ($"isButtonPressed: {isButtonPressed};buttonPressedCount: {buttonPressedCount};lastMouseButtonPressed: {lastMouseButtonPressed}"); + + if (buttonPressedCount == 3 && lastMouseButtonPressed != null + && lastMouseButtonPressed == WindowsConsole.ButtonState.Button1Pressed + || lastMouseButtonPressed == WindowsConsole.ButtonState.Button2Pressed + || lastMouseButtonPressed == WindowsConsole.ButtonState.Button3Pressed) { + + switch (lastMouseButtonPressed) { + case WindowsConsole.ButtonState.Button1Pressed: + mouseFlag = MouseFlags.Button1DoubleClicked; + break; + + case WindowsConsole.ButtonState.Button2Pressed: + mouseFlag = MouseFlags.Button2DoubleClicked; + break; + + case WindowsConsole.ButtonState.Button3Pressed: + mouseFlag = MouseFlags.Button3DoubleClicked; + break; + } + isOneFingerDoubleClicked = true; + + } else if (buttonPressedCount == 5 && lastMouseButtonPressed != null && isOneFingerDoubleClicked + && lastMouseButtonPressed == WindowsConsole.ButtonState.Button1Pressed + || lastMouseButtonPressed == WindowsConsole.ButtonState.Button2Pressed + || lastMouseButtonPressed == WindowsConsole.ButtonState.Button3Pressed) { + + switch (lastMouseButtonPressed) { + case WindowsConsole.ButtonState.Button1Pressed: + mouseFlag = MouseFlags.Button1TripleClicked; + break; + + case WindowsConsole.ButtonState.Button2Pressed: + mouseFlag = MouseFlags.Button2TripleClicked; + break; + + case WindowsConsole.ButtonState.Button3Pressed: + mouseFlag = MouseFlags.Button3TripleClicked; + break; + } + buttonPressedCount = 0; + lastMouseButtonPressed = null; + isOneFingerDoubleClicked = false; + isButtonReleased = false; + + } else if ((mouseEvent.ButtonState != 0 && mouseEvent.EventFlags == 0 && lastMouseButtonPressed == null && !isButtonDoubleClicked) || + (lastMouseButtonPressed == null && mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved && + mouseEvent.ButtonState != 0 && !isButtonReleased && !isButtonDoubleClicked)) { switch (mouseEvent.ButtonState) { case WindowsConsole.ButtonState.Button1Pressed: mouseFlag = MouseFlags.Button1Pressed; @@ -856,7 +921,6 @@ namespace Terminal.Gui { }); } - } else if ((mouseEvent.EventFlags == 0 || mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) && lastMouseButtonPressed != null && !isButtonReleased && !isButtonDoubleClicked) { switch (lastMouseButtonPressed) { @@ -874,8 +938,8 @@ namespace Terminal.Gui { } isButtonPressed = false; isButtonReleased = true; - } else if ((mouseEvent.EventFlags == 0 || mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) && - isButtonReleased && p == point) { + } else if ((mouseEvent.EventFlags == 0 || mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) + && !isOneFingerDoubleClicked && isButtonReleased && p == point) { switch (lastMouseButtonPressed) { case WindowsConsole.ButtonState.Button1Pressed: mouseFlag = MouseFlags.Button1Clicked; @@ -1214,11 +1278,13 @@ namespace Terminal.Gui { Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan); Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black); Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkCyan); + Colors.TopLevel.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Black); Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkBlue); Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray); Colors.Base.HotNormal = MakeColor (ConsoleColor.DarkCyan, ConsoleColor.DarkBlue); Colors.Base.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray); + Colors.Base.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.DarkBlue); Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkGray); Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black); @@ -1230,11 +1296,13 @@ namespace Terminal.Gui { Colors.Dialog.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkGray); Colors.Dialog.HotNormal = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.Gray); Colors.Dialog.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkGray); + Colors.Dialog.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Gray); Colors.Error.Normal = MakeColor (ConsoleColor.DarkRed, ConsoleColor.White); Colors.Error.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkRed); Colors.Error.HotNormal = MakeColor (ConsoleColor.Black, ConsoleColor.White); Colors.Error.HotFocus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkRed); + Colors.Error.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.White); } void ResizeScreen () diff --git a/Terminal.Gui/Core/Application.cs b/Terminal.Gui/Core/Application.cs index a880e6f2c..c12af219a 100644 --- a/Terminal.Gui/Core/Application.cs +++ b/Terminal.Gui/Core/Application.cs @@ -728,8 +728,8 @@ namespace Terminal.Gui { Driver.PrepareToRun (MainLoop, ProcessKeyEvent, ProcessKeyDownEvent, ProcessKeyUpEvent, ProcessMouseEvent); if (toplevel.LayoutStyle == LayoutStyle.Computed) toplevel.SetRelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows)); - toplevel.PositionToplevels (); toplevel.LayoutSubviews (); + toplevel.PositionToplevels (); toplevel.WillPresent (); if (refreshDriver) { if (MdiTop != null) { @@ -794,6 +794,7 @@ namespace Terminal.Gui { RootMouseEvent = null; Resized = null; _initialized = false; + mouseGrabView = null; // Reset synchronization context to allow the user to run async/await, // as the main loop has been ended, the synchronization context from @@ -1141,8 +1142,8 @@ namespace Terminal.Gui { Driver.Clip = full; foreach (var t in toplevels) { t.SetRelativeLayout (full); - t.PositionToplevels (); t.LayoutSubviews (); + t.PositionToplevels (); } Refresh (); } @@ -1167,6 +1168,9 @@ namespace Terminal.Gui { static bool SetCurrentAsTop () { if (MdiTop == null && Current != Top && Current?.SuperView == null && Current?.Modal == false) { + if (Current.Frame != new Rect (0, 0, Driver.Cols, Driver.Rows)) { + Current.Frame = new Rect (0, 0, Driver.Cols, Driver.Rows); + } Top = Current; return true; } diff --git a/Terminal.Gui/Core/Responder.cs b/Terminal.Gui/Core/Responder.cs index 1fe1e1231..37de82145 100644 --- a/Terminal.Gui/Core/Responder.cs +++ b/Terminal.Gui/Core/Responder.cs @@ -58,6 +58,16 @@ namespace Terminal.Gui { /// true if has focus; otherwise, false. public virtual bool HasFocus { get; } + /// + /// Gets or sets a value indicating whether this can respond to user interaction. + /// + public virtual bool Enabled { get; set; } = true; + + /// + /// Gets or sets a value indicating whether this and all its child controls are displayed. + /// + public virtual bool Visible { get; set; } = true; + // Key handling /// /// This method can be overwritten by view that @@ -211,6 +221,21 @@ namespace Terminal.Gui { return false; } + /// + /// Method invoked when the property from a view is changed. + /// + public virtual void OnCanFocusChanged () { } + + /// + /// Method invoked when the property from a view is changed. + /// + public virtual void OnEnabledChanged () { } + + /// + /// Method invoked when the property from a view is changed. + /// + public virtual void OnVisibleChanged () { } + /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. /// diff --git a/Terminal.Gui/Core/ShortcutHelper.cs b/Terminal.Gui/Core/ShortcutHelper.cs index 9314afdd0..437a6e4a4 100644 --- a/Terminal.Gui/Core/ShortcutHelper.cs +++ b/Terminal.Gui/Core/ShortcutHelper.cs @@ -59,15 +59,18 @@ namespace Terminal.Gui { /// Get the key as string. /// /// The shortcut key. + /// The delimiter string. /// - public static ustring GetShortcutTag (Key shortcut) + public static ustring GetShortcutTag (Key shortcut, ustring delimiter = null) { if (shortcut == Key.Null) { return ""; } var k = shortcut; - var delimiter = MenuBar.ShortcutDelimiter; + if (delimiter == null) { + delimiter = MenuBar.ShortcutDelimiter; + } ustring tag = ustring.Empty; var sCut = GetKeyToString (k, out Key knm).ToString (); if (knm == Key.Unknown) { @@ -146,7 +149,8 @@ namespace Terminal.Gui { /// Allows to retrieve a from a /// /// The key as string. - public static Key GetShortcutFromTag (ustring tag) + /// The delimiter string. + public static Key GetShortcutFromTag (ustring tag, ustring delimiter = null) { var sCut = tag; if (sCut.IsEmpty) { @@ -155,7 +159,9 @@ namespace Terminal.Gui { Key key = Key.Null; //var hasCtrl = false; - var delimiter = MenuBar.ShortcutDelimiter; + if (delimiter == null) { + delimiter = MenuBar.ShortcutDelimiter; + } ustring [] keys = sCut.Split (delimiter); for (int i = 0; i < keys.Length; i++) { diff --git a/Terminal.Gui/Core/Toplevel.cs b/Terminal.Gui/Core/Toplevel.cs index 95453b709..c8e433036 100644 --- a/Terminal.Gui/Core/Toplevel.cs +++ b/Terminal.Gui/Core/Toplevel.cs @@ -478,15 +478,19 @@ namespace Terminal.Gui { } } - internal void EnsureVisibleBounds (Toplevel top, int x, int y, out int nx, out int ny) + internal View EnsureVisibleBounds (Toplevel top, int x, int y, + out int nx, out int ny, out View mb, out View sb) { - nx = Math.Max (x, 0); int l; - if (SuperView == null || SuperView is Toplevel) { + View superView; + if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { l = Driver.Cols; + superView = Application.Top; } else { - l = SuperView.Frame.Width; + l = top.SuperView.Frame.Width; + superView = top.SuperView; } + nx = Math.Max (x, 0); nx = nx + top.Frame.Width > l ? Math.Max (l - top.Frame.Width, 0) : nx; SetWidth (top.Frame.Width, out int rWidth); if (rWidth < 0 && nx >= top.Frame.X) { @@ -494,34 +498,48 @@ namespace Terminal.Gui { } //System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}"); bool m, s; - if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) { - m = Application.Top.MenuBar != null; + if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { + m = Application.Top.MenuBar?.Visible == true; + mb = Application.Top.MenuBar; } else { - m = ((Toplevel)SuperView).MenuBar != null; + var t = top.SuperView; + while (!(t is Toplevel)) { + t = t.SuperView; + } + m = ((Toplevel)t).MenuBar?.Visible == true; + mb = ((Toplevel)t).MenuBar; } - if (SuperView == null || SuperView is Toplevel) { + if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { l = m ? 1 : 0; } else { l = 0; } ny = Math.Max (y, l); - if (SuperView == null || SuperView.GetType () != typeof (Toplevel)) { - s = Application.Top.StatusBar != null && Application.Top.StatusBar.Visible; + if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { + s = Application.Top.StatusBar?.Visible == true; + sb = Application.Top.StatusBar; } else { - s = ((Toplevel)SuperView).StatusBar != null && ((Toplevel)SuperView).StatusBar.Visible; + var t = top.SuperView; + while (!(t is Toplevel)) { + t = t.SuperView; + } + s = ((Toplevel)t).StatusBar?.Visible == true; + sb = ((Toplevel)t).StatusBar; } - if (SuperView == null || SuperView is Toplevel) { + if (top?.SuperView == null || top == Application.Top || top?.SuperView == Application.Top) { l = s ? Driver.Rows - 1 : Driver.Rows; } else { - l = s ? SuperView.Frame.Height - 1 : SuperView.Frame.Height; + l = s ? top.SuperView.Frame.Height - 1 : top.SuperView.Frame.Height; } ny = Math.Min (ny, l); - ny = ny + top.Frame.Height > l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny; + ny = ny + top.Frame.Height >= l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny; SetHeight (top.Frame.Height, out int rHeight); if (rHeight < 0 && ny >= top.Frame.Y) { ny = Math.Max (top.Frame.Bottom - 2, 0); } //System.Diagnostics.Debug.WriteLine ($"ny:{ny}, rHeight:{rHeight}"); + + return superView; } internal void PositionToplevels () @@ -540,45 +558,32 @@ namespace Terminal.Gui { /// The toplevel. public virtual void PositionToplevel (Toplevel top) { - EnsureVisibleBounds (top, top.Frame.X, top.Frame.Y, out int nx, out int ny); - if ((top?.SuperView != null || top != Application.Top) + var superView = EnsureVisibleBounds (top, top.Frame.X, top.Frame.Y, + out int nx, out int ny, out _, out View sb); + bool layoutSubviews = false; + if ((top?.SuperView != null || (top != Application.Top && top.Modal) + || (top?.SuperView == null && top.IsMdiChild)) && (nx > top.Frame.X || ny > top.Frame.Y) && top.LayoutStyle == LayoutStyle.Computed) { + if ((top.X == null || top.X is Pos.PosAbsolute) && top.Bounds.X != nx) { top.X = nx; + layoutSubviews = true; } if ((top.Y == null || top.Y is Pos.PosAbsolute) && top.Bounds.Y != ny) { top.Y = ny; + layoutSubviews = true; } } - View superView = null; - StatusBar statusBar = null; + if (sb != null && ny + top.Frame.Height != superView.Frame.Height - (sb.Visible ? 1 : 0) + && top.Height is Dim.DimFill) { - if (top != Application.Top && Application.Top.StatusBar != null) { - superView = Application.Top; - statusBar = Application.Top.StatusBar; - } else if (top?.SuperView != null && top.SuperView is Toplevel toplevel) { - superView = top.SuperView; - statusBar = toplevel.StatusBar; + top.Height = Dim.Fill (sb.Visible ? 1 : 0); + layoutSubviews = true; } - if (statusBar != null) { - if (ny + top.Frame.Height >= superView.Frame.Height - (statusBar.Visible ? 1 : 0)) { - if (top.Height is Dim.DimFill) { - top.Height = Dim.Fill (statusBar.Visible ? 1 : 0); - } - } - if (superView == Application.Top) { - top.SetRelativeLayout (superView.Frame); - } else { - superView.LayoutSubviews (); - } - } - if (top.StatusBar != null) { - if (top.StatusBar.Frame.Y != top.Frame.Height - (top.StatusBar.Visible ? 1 : 0)) { - top.StatusBar.Y = top.Frame.Height - (top.StatusBar.Visible ? 1 : 0); - top.LayoutSubviews (); - } - top.BringSubviewToFront (top.StatusBar); + + if (layoutSubviews) { + superView.LayoutSubviews (); } } @@ -590,18 +595,15 @@ namespace Terminal.Gui { } if (!NeedDisplay.IsEmpty || ChildNeedsDisplay || LayoutNeeded) { - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); // This is the Application.Top. Clear just the region we're being asked to redraw // (the bounds passed to us). - // Must be the screen-relative region to clear, not the bounds. - Clear (Frame); - Driver.SetAttribute (Colors.Base.Normal); + Clear (); + Driver.SetAttribute (Enabled ? Colors.Base.Normal : Colors.Base.Disabled); - if (LayoutStyle == LayoutStyle.Computed) - SetRelativeLayout (Bounds); - PositionToplevels (); LayoutSubviews (); + PositionToplevels (); if (this == Application.MdiTop) { foreach (var top in Application.MdiChildes.AsEnumerable ().Reverse ()) { @@ -680,7 +682,8 @@ namespace Terminal.Gui { SuperView.SetNeedsDisplay (); } EnsureVisibleBounds (this, mouseEvent.X + (SuperView == null ? mouseEvent.OfX - start.X : Frame.X - start.X), - mouseEvent.Y + (SuperView == null ? mouseEvent.OfY : Frame.Y), out nx, out ny); + mouseEvent.Y + (SuperView == null ? mouseEvent.OfY : Frame.Y), + out nx, out ny, out _, out _); dragPosition = new Point (nx, ny); LayoutSubviews (); diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 21a714082..9944559cc 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -163,6 +163,21 @@ namespace Terminal.Gui { /// public event Action MouseClick; + /// + /// Event fired when the value is being changed. + /// + public event Action CanFocusChanged; + + /// + /// Event fired when the value is being changed. + /// + public event Action EnabledChanged; + + /// + /// Event fired when the value is being changed. + /// + public event Action VisibleChanged; + /// /// Gets or sets the HotKey defined for this view. A user pressing HotKey on the keyboard while this view has focus will cause the Clicked event to fire. /// @@ -329,6 +344,11 @@ namespace Terminal.Gui { TabIndex = SuperView != null ? SuperView.tabIndexes.IndexOf (this) : -1; } TabStop = value; + if (!value && HasFocus) { + SetHasFocus (false, this); + } + OnCanFocusChanged (); + SetNeedsDisplay (); } if (subviews != null && IsInitialized) { foreach (var view in subviews) { @@ -1114,7 +1134,7 @@ namespace Terminal.Gui { if (focused) DrawHotString (text, scheme.HotFocus, scheme.Focus); else - DrawHotString (text, scheme.HotNormal, scheme.Normal); + DrawHotString (text, Enabled ? scheme.HotNormal : scheme.Disabled, Enabled ? scheme.Normal : scheme.Disabled); } /// @@ -1143,11 +1163,11 @@ namespace Terminal.Gui { /// in a visually sensible place. public virtual void PositionCursor () { - if (!CanBeVisible (this)) { + if (!CanBeVisible (this) || !Enabled) { return; } - if (focused?.Frame.Width > 0 && focused.Frame.Height > 0) { + if (focused?.Visible == true && focused?.Enabled == true && focused?.Frame.Width > 0 && focused.Frame.Height > 0) { focused.PositionCursor (); } else { if (CanFocus && HasFocus && Visible && Frame.Width > 0 && Frame.Height > 0) { @@ -1271,6 +1291,8 @@ namespace Terminal.Gui { } } + ColorScheme colorScheme; + /// /// The color scheme for this view, if it is not defined, it returns the 's /// color scheme. @@ -1289,8 +1311,6 @@ namespace Terminal.Gui { } } - ColorScheme colorScheme; - /// /// Displays the specified character in the specified column and row of the View. /// @@ -1351,7 +1371,8 @@ namespace Terminal.Gui { if (textFormatter != null) { textFormatter.NeedsFormat = true; } - textFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : ColorScheme.Normal, HasFocus ? ColorScheme.HotFocus : ColorScheme.HotNormal); + textFormatter?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : GetNormalColor (), + HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled); } // Invoke DrawContentEvent @@ -1414,7 +1435,7 @@ namespace Terminal.Gui { if (view == null) return; //Console.WriteLine ($"Request to focus {view}"); - if (!view.CanFocus || !view.Visible) + if (!view.CanFocus || !view.Visible || !view.Enabled) return; if (focused?.hasFocus == true && focused == view) return; @@ -1444,7 +1465,10 @@ namespace Terminal.Gui { /// public void SetFocus () { - if (!CanBeVisible (this)) { + if (!CanBeVisible (this) || !Enabled) { + if (HasFocus) { + SetHasFocus (false, this); + } return; } @@ -1479,14 +1503,20 @@ namespace Terminal.Gui { /// public override bool ProcessKey (KeyEvent keyEvent) { + if (!Enabled) { + return false; + } + KeyEventEventArgs args = new KeyEventEventArgs (keyEvent); KeyPress?.Invoke (args); if (args.Handled) return true; - Focused?.KeyPress?.Invoke (args); - if (args.Handled) - return true; - if (Focused?.ProcessKey (keyEvent) == true) + if (Focused?.Enabled == true) { + Focused?.KeyPress?.Invoke (args); + if (args.Handled) + return true; + } + if (Focused?.Enabled == true && Focused?.ProcessKey (keyEvent) == true) return true; return false; @@ -1495,19 +1525,25 @@ namespace Terminal.Gui { /// public override bool ProcessHotKey (KeyEvent keyEvent) { + if (!Enabled) { + return false; + } + KeyEventEventArgs args = new KeyEventEventArgs (keyEvent); KeyPress?.Invoke (args); if (args.Handled) return true; - Focused?.KeyPress?.Invoke (args); - if (args.Handled) - return true; - if (Focused?.ProcessKey (keyEvent) == true) + if (Focused?.Enabled == true) { + Focused?.KeyPress?.Invoke (args); + if (args.Handled) + return true; + } + if (Focused?.Enabled == true && Focused?.ProcessKey (keyEvent) == true) return true; if (subviews == null || subviews.Count == 0) return false; foreach (var view in subviews) - if (view.ProcessHotKey (keyEvent)) + if (view.Enabled && view.ProcessHotKey (keyEvent)) return true; return false; } @@ -1515,19 +1551,25 @@ namespace Terminal.Gui { /// public override bool ProcessColdKey (KeyEvent keyEvent) { + if (!Enabled) { + return false; + } + KeyEventEventArgs args = new KeyEventEventArgs (keyEvent); KeyPress?.Invoke (args); if (args.Handled) return true; - Focused?.KeyPress?.Invoke (args); - if (args.Handled) - return true; - if (Focused?.ProcessKey (keyEvent) == true) + if (Focused?.Enabled == true) { + Focused?.KeyPress?.Invoke (args); + if (args.Handled) + return true; + } + if (Focused?.Enabled == true && Focused?.ProcessKey (keyEvent) == true) return true; if (subviews == null || subviews.Count == 0) return false; foreach (var view in subviews) - if (view.ProcessColdKey (keyEvent)) + if (view.Enabled && view.ProcessColdKey (keyEvent)) return true; return false; } @@ -1540,12 +1582,16 @@ namespace Terminal.Gui { /// Contains the details about the key that produced the event. public override bool OnKeyDown (KeyEvent keyEvent) { + if (!Enabled) { + return false; + } + KeyEventEventArgs args = new KeyEventEventArgs (keyEvent); KeyDown?.Invoke (args); if (args.Handled) { return true; } - if (Focused?.OnKeyDown (keyEvent) == true) { + if (Focused?.Enabled == true && Focused?.OnKeyDown (keyEvent) == true) { return true; } @@ -1560,12 +1606,16 @@ namespace Terminal.Gui { /// Contains the details about the key that produced the event. public override bool OnKeyUp (KeyEvent keyEvent) { + if (!Enabled) { + return false; + } + KeyEventEventArgs args = new KeyEventEventArgs (keyEvent); KeyUp?.Invoke (args); if (args.Handled) { return true; } - if (Focused?.OnKeyUp (keyEvent) == true) { + if (Focused?.Enabled == true && Focused?.OnKeyUp (keyEvent) == true) { return true; } @@ -1601,7 +1651,7 @@ namespace Terminal.Gui { } foreach (var view in tabIndexes) { - if (view.CanFocus && view.tabStop && view.Visible) { + if (view.CanFocus && view.tabStop && view.Visible && view.Enabled) { SetFocus (view); return; } @@ -1626,7 +1676,7 @@ namespace Terminal.Gui { i--; View v = tabIndexes [i]; - if (v.CanFocus && v.tabStop && v.Visible) { + if (v.CanFocus && v.tabStop && v.Visible && v.Enabled) { SetFocus (v); return; } @@ -1662,10 +1712,10 @@ namespace Terminal.Gui { focused_idx = i; continue; } - if (w.CanFocus && focused_idx != -1 && w.tabStop && w.Visible) { + if (w.CanFocus && focused_idx != -1 && w.tabStop && w.Visible && w.Enabled) { focused.SetHasFocus (false, w); - if (w != null && w.CanFocus && w.tabStop && w.Visible) + if (w != null && w.CanFocus && w.tabStop && w.Visible && w.Enabled) w.FocusLast (); SetFocus (w); @@ -1708,10 +1758,10 @@ namespace Terminal.Gui { focused_idx = i; continue; } - if (w.CanFocus && focused_idx != -1 && w.tabStop && w.Visible) { + if (w.CanFocus && focused_idx != -1 && w.tabStop && w.Visible && w.Enabled) { focused.SetHasFocus (false, w); - if (w != null && w.CanFocus && w.tabStop && w.Visible) + if (w != null && w.CanFocus && w.tabStop && w.Visible && w.Enabled) w.FocusFirst (); SetFocus (w); @@ -2072,7 +2122,50 @@ namespace Terminal.Gui { /// Get or sets if the was already initialized. /// This derived from to allow notify all the views that are being initialized. /// - public bool IsInitialized { get; set; } + public virtual bool IsInitialized { get; set; } + + bool oldEnabled; + + /// + public override bool Enabled { + get => base.Enabled; + set { + if (base.Enabled != value) { + base.Enabled = value; + if (!value && HasFocus) { + SetHasFocus (false, this); + } + OnEnabledChanged (); + SetNeedsDisplay (); + } + if (subviews != null) { + foreach (var view in subviews) { + if (!value) { + view.oldEnabled = view.Enabled; + view.Enabled = value; + } else { + view.Enabled = view.oldEnabled; + view.addingView = false; + } + } + } + } + } + + /// > + public override bool Visible { + get => base.Visible; + set { + if (base.Visible != value) { + base.Visible = value; + if (!value && HasFocus) { + SetHasFocus (false, this); + } + OnVisibleChanged (); + SetNeedsDisplay (); + } + } + } /// /// Pretty prints the View @@ -2148,6 +2241,10 @@ namespace Terminal.Gui { /// public override bool OnMouseEnter (MouseEvent mouseEvent) { + if (!Enabled) { + return true; + } + if (!CanBeVisible (this)) { return false; } @@ -2165,6 +2262,10 @@ namespace Terminal.Gui { /// public override bool OnMouseLeave (MouseEvent mouseEvent) { + if (!Enabled) { + return true; + } + if (!CanBeVisible (this)) { return false; } @@ -2186,13 +2287,16 @@ namespace Terminal.Gui { /// true, if the event was handled, false otherwise. public virtual bool OnMouseEvent (MouseEvent mouseEvent) { + if (!Enabled) { + return true; + } + if (!CanBeVisible (this)) { return false; } MouseEventArgs args = new MouseEventArgs (mouseEvent); - OnMouseClick (args); - if (args.Handled) + if (OnMouseClick (args)) return true; if (MouseEvent (mouseEvent)) return true; @@ -2211,7 +2315,24 @@ namespace Terminal.Gui { /// /// Invokes the MouseClick event. /// - protected void OnMouseClick (MouseEventArgs args) => MouseClick?.Invoke (args); + protected bool OnMouseClick (MouseEventArgs args) + { + if (!Enabled) { + return true; + } + + MouseClick?.Invoke (args); + return args.Handled; + } + + /// + public override void OnCanFocusChanged () => CanFocusChanged?.Invoke (); + + /// + public override void OnEnabledChanged () => EnabledChanged?.Invoke (); + + /// + public override void OnVisibleChanged () => VisibleChanged?.Invoke (); /// protected override void Dispose (bool disposing) @@ -2258,11 +2379,6 @@ namespace Terminal.Gui { Initialized?.Invoke (this, EventArgs.Empty); } - /// - /// Gets or sets the view visibility. - /// - public bool Visible { get; set; } = true; - bool CanBeVisible (View view) { if (!view.Visible) { @@ -2372,5 +2488,15 @@ namespace Terminal.Gui { return CanSetHeight (0, out _); } + + /// + /// Determines the current based on the value. + /// + /// if is + /// or if is + protected Attribute GetNormalColor () + { + return Enabled ? ColorScheme.Normal : ColorScheme.Disabled; + } } } diff --git a/Terminal.Gui/Core/Window.cs b/Terminal.Gui/Core/Window.cs index 99734a657..26a389275 100644 --- a/Terminal.Gui/Core/Window.cs +++ b/Terminal.Gui/Core/Window.cs @@ -181,7 +181,7 @@ namespace Terminal.Gui { // BUGBUG: Why do we draw the frame twice? This call is here to clear the content area, I think. Why not just clear that area? if (!NeedDisplay.IsEmpty) { - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: true); } @@ -194,13 +194,13 @@ namespace Terminal.Gui { ClearLayoutNeeded (); ClearNeedsDisplay (); - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: false); if (HasFocus) Driver.SetAttribute (ColorScheme.HotNormal); Driver.DrawWindowTitle (scrRect, Title, padding, padding, padding, padding); - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); // Checks if there are any SuperView view which intersect with this window. if (SuperView != null) { diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index 094d297b2..4cd600f3f 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -184,6 +184,10 @@ namespace Terminal.Gui { /// public override bool ProcessHotKey (KeyEvent kb) { + if (!Enabled) { + return false; + } + if (kb.IsAlt) return CheckKey (kb); @@ -193,6 +197,10 @@ namespace Terminal.Gui { /// public override bool ProcessColdKey (KeyEvent kb) { + if (!Enabled) { + return false; + } + if (IsDefault && kb.KeyValue == '\n') { Clicked?.Invoke (); return true; @@ -203,6 +211,10 @@ namespace Terminal.Gui { /// public override bool ProcessKey (KeyEvent kb) { + if (!Enabled) { + return false; + } + var c = kb.KeyValue; if (c == '\n' || c == ' ' || kb.Key == HotKey) { Clicked?.Invoke (); @@ -228,7 +240,7 @@ namespace Terminal.Gui { { if (me.Flags == MouseFlags.Button1Clicked || me.Flags == MouseFlags.Button1DoubleClicked || me.Flags == MouseFlags.Button1TripleClicked) { - if (CanFocus) { + if (CanFocus && Enabled) { if (!HasFocus) { SetFocus (); SetNeedsDisplay (); diff --git a/Terminal.Gui/Views/Checkbox.cs b/Terminal.Gui/Views/Checkbox.cs index 72fc8304d..97bf09e7d 100644 --- a/Terminal.Gui/Views/Checkbox.cs +++ b/Terminal.Gui/Views/Checkbox.cs @@ -116,7 +116,7 @@ namespace Terminal.Gui { /// public override void Redraw (Rect bounds) { - Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal); + Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ()); Move (0, 0); Driver.AddRune (Checked ? Driver.Checked : Driver.UnChecked); Driver.AddRune (' '); @@ -124,7 +124,7 @@ namespace Terminal.Gui { Driver.AddStr (Text); if (hot_pos != -1) { Move (2 + hot_pos, 0); - Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : ColorScheme.HotNormal); + Driver.SetAttribute (HasFocus ? ColorScheme.HotFocus : Enabled ? ColorScheme.HotNormal : ColorScheme.Disabled); Driver.AddRune (hot_key); } } diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index 122eeaa62..3eea30eb9 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -30,7 +30,7 @@ namespace Terminal.Gui { source = value; // Only need to refresh list if its been added to a container view - if(SuperView != null && SuperView.Subviews.Contains(this)) { + if (SuperView != null && SuperView.Subviews.Contains (this)) { Search_Changed (""); SetNeedsDisplay (); } @@ -90,7 +90,7 @@ namespace Terminal.Gui { { search = new TextField (""); listview = new ListView () { LayoutStyle = LayoutStyle.Computed, CanFocus = true, TabStop = false }; - + Initialize (); Text = text; } @@ -124,8 +124,8 @@ namespace Terminal.Gui { // On resize LayoutComplete += (LayoutEventArgs a) => { - if (!autoHide && search.Frame.Width != Bounds.Width || - autoHide && search.Frame.Width != Bounds.Width - 1) { + if ((!autoHide && Bounds.Width > 0 && search.Frame.Width != Bounds.Width) || + (autoHide && Bounds.Width > 0 && search.Frame.Width != Bounds.Width - 1)) { search.Width = listview.Width = autoHide ? Bounds.Width - 1 : Bounds.Width; listview.Height = CalculatetHeight (); search.SetRelativeLayout (Bounds); @@ -144,7 +144,7 @@ namespace Terminal.Gui { // Determine if this view is hosted inside a dialog and is the only control for (View view = this.SuperView; view != null; view = view.SuperView) { - if (view is Dialog && SuperView != null && SuperView.Subviews.Count == 1 && SuperView.Subviews[0] == this) { + if (view is Dialog && SuperView != null && SuperView.Subviews.Count == 1 && SuperView.Subviews [0] == this) { autoHide = false; break; } @@ -176,6 +176,21 @@ namespace Terminal.Gui { } } + /// + ///If set to true its not allow any changes in the text. + /// + public bool ReadOnly { + get => search.ReadOnly; + set { + search.ReadOnly = value; + if (search.ReadOnly) { + if (search.ColorScheme != null) { + search.ColorScheme.Normal = search.ColorScheme.Focus; + } + } + } + } + /// public override bool MouseEvent (MouseEvent me) { @@ -247,7 +262,7 @@ namespace Terminal.Gui { { // Note: Cannot rely on "listview.SelectedItem != lastSelectedItem" because the list is dynamic. // So we cannot optimize. Ie: Don't call if not changed - SelectedItemChanged?.Invoke (new ListViewItemEventArgs(SelectedItem, search.Text)); + SelectedItemChanged?.Invoke (new ListViewItemEventArgs (SelectedItem, search.Text)); return true; } @@ -321,7 +336,7 @@ namespace Terminal.Gui { return true; } - if(e.Key == Key.PageDown) { + if (e.Key == Key.PageDown) { if (listview.SelectedItem != -1) { listview.MovePageDown (); } @@ -342,8 +357,8 @@ namespace Terminal.Gui { return true; } - if(e.Key == Key.End) { - if(listview.SelectedItem != -1) { + if (e.Key == Key.End) { + if (listview.SelectedItem != -1) { listview.MoveEnd (); } return true; @@ -380,7 +395,7 @@ namespace Terminal.Gui { private void SetValue (object text) { search.TextChanged -= Search_Changed; - this.text = search.Text = text.ToString(); + this.text = search.Text = text.ToString (); search.CursorPosition = 0; search.TextChanged += Search_Changed; SelectedItem = GetSelectedItemFromSource (this.text); @@ -472,7 +487,7 @@ namespace Terminal.Gui { ResetSearchSet (noCopy: true); foreach (var item in source.ToList ()) { // Iterate to preserver object type and force deep copy - if (item.ToString().StartsWith (search.Text.ToString(), StringComparison.CurrentCultureIgnoreCase)) { + if (item.ToString ().StartsWith (search.Text.ToString (), StringComparison.CurrentCultureIgnoreCase)) { searchset.Add (item); } } @@ -501,9 +516,11 @@ namespace Terminal.Gui { /// Consider making public private void HideList () { + var rect = listview.ViewToScreen (listview.Bounds); Reset (SelectedItem > -1); - listview.Clear (); + listview.Clear (rect); listview.TabStop = false; + SuperView?.SetNeedsDisplay (rect); } /// @@ -515,7 +532,7 @@ namespace Terminal.Gui { if (Bounds.Height == 0) return 0; - return Math.Min (Math.Max(Bounds.Height - 1, minimumHeight - 1), searchset?.Count > 0 ? searchset.Count : isShow ? Math.Max (Bounds.Height - 1, minimumHeight - 1) : 0); + return Math.Min (Math.Max (Bounds.Height - 1, minimumHeight - 1), searchset?.Count > 0 ? searchset.Count : isShow ? Math.Max (Bounds.Height - 1, minimumHeight - 1) : 0); } } } diff --git a/Terminal.Gui/Views/FrameView.cs b/Terminal.Gui/Views/FrameView.cs index 8f880d613..b19411f25 100644 --- a/Terminal.Gui/Views/FrameView.cs +++ b/Terminal.Gui/Views/FrameView.cs @@ -60,7 +60,7 @@ namespace Terminal.Gui { /// /// Frame. /// Title. - /// /// Views. + /// Views. public FrameView (Rect frame, ustring title, View [] views) : this (frame, title) { Initialize (title, frame, views); @@ -157,7 +157,7 @@ namespace Terminal.Gui { var scrRect = ViewToScreen (new Rect (0, 0, Frame.Width, Frame.Height)); if (!NeedDisplay.IsEmpty) { - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: true); } @@ -166,13 +166,13 @@ namespace Terminal.Gui { Driver.Clip = savedClip; ClearNeedsDisplay (); - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); Driver.DrawWindowFrame (scrRect, padding + 1, padding + 1, padding + 1, padding + 1, border: true, fill: false); if (HasFocus) Driver.SetAttribute (ColorScheme.HotNormal); Driver.DrawWindowTitle (scrRect, Title, padding, padding, padding, padding); - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); } /// diff --git a/Terminal.Gui/Views/GraphView.cs b/Terminal.Gui/Views/GraphView.cs index f012f7cd3..8a38c0e5f 100644 --- a/Terminal.Gui/Views/GraphView.cs +++ b/Terminal.Gui/Views/GraphView.cs @@ -99,7 +99,7 @@ namespace Terminal.Gui { throw new Exception ($"{nameof(CellSize)} cannot be 0"); } - SetDriverColorToGraphColor (); + SetDriverColorToGraphColor (); Move (0, 0); @@ -164,7 +164,7 @@ namespace Terminal.Gui { /// public void SetDriverColorToGraphColor () { - Driver.SetAttribute (GraphColor ?? ColorScheme.Normal); + Driver.SetAttribute (GraphColor ?? (GetNormalColor ())); } /// diff --git a/Terminal.Gui/Views/HexView.cs b/Terminal.Gui/Views/HexView.cs index 85ea8d848..631fe10cd 100644 --- a/Terminal.Gui/Views/HexView.cs +++ b/Terminal.Gui/Views/HexView.cs @@ -162,7 +162,7 @@ namespace Terminal.Gui { Driver.AddStr (string.Format ("{0:x8} ", displayStart + line * nblocks * 4)); currentAttribute = ColorScheme.HotNormal; - SetAttribute (ColorScheme.Normal); + SetAttribute (GetNormalColor ()); for (int block = 0; block < nblocks; block++) { for (int b = 0; b < 4; b++) { @@ -172,10 +172,10 @@ namespace Terminal.Gui { if (offset + displayStart == position || edited) SetAttribute (leftSide ? activeColor : trackingColor); else - SetAttribute (ColorScheme.Normal); + SetAttribute (GetNormalColor ()); Driver.AddStr (offset >= n ? " " : string.Format ("{0:x2}", value)); - SetAttribute (ColorScheme.Normal); + SetAttribute (GetNormalColor ()); Driver.AddRune (' '); } Driver.AddStr (block + 1 == nblocks ? " " : "| "); @@ -201,7 +201,7 @@ namespace Terminal.Gui { if (offset + displayStart == position || edited) SetAttribute (leftSide ? trackingColor : activeColor); else - SetAttribute (ColorScheme.Normal); + SetAttribute (GetNormalColor ()); Driver.AddRune (c); } @@ -401,12 +401,11 @@ namespace Terminal.Gui { /// /// Get / Set the wished cursor when the field is focused /// - public CursorVisibility DesiredCursorVisibility - { - get => desiredCursorVisibility; + public CursorVisibility DesiredCursorVisibility { + get => desiredCursorVisibility; set { if (desiredCursorVisibility != value && HasFocus) { - Application.Driver.SetCursorVisibility (value); + Application.Driver.SetCursorVisibility (value); } desiredCursorVisibility = value; diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs index 2c438bddd..56aaeca7d 100644 --- a/Terminal.Gui/Views/Label.cs +++ b/Terminal.Gui/Views/Label.cs @@ -94,8 +94,7 @@ namespace Terminal.Gui { public override bool OnMouseEvent (MouseEvent mouseEvent) { MouseEventArgs args = new MouseEventArgs (mouseEvent); - OnMouseClick (args); - if (args.Handled) + if (OnMouseClick (args)) return true; if (MouseEvent (mouseEvent)) return true; diff --git a/Terminal.Gui/Views/LineView.cs b/Terminal.Gui/Views/LineView.cs index a0290281b..e65b9f91c 100644 --- a/Terminal.Gui/Views/LineView.cs +++ b/Terminal.Gui/Views/LineView.cs @@ -73,7 +73,7 @@ namespace Terminal.Gui.Views { base.Redraw (bounds); Move (0, 0); - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); var hLineWidth = Math.Max (1, Rune.ColumnWidth (Driver.HLine)); diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 85274cd31..fad92f4e2 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -334,7 +334,9 @@ namespace Terminal.Gui { for (int row = 0; row < f.Height; row++, item++) { bool isSelected = item == selected; - var newcolor = focused ? (isSelected ? ColorScheme.Focus : ColorScheme.Normal) : (isSelected ? ColorScheme.HotNormal : ColorScheme.Normal); + var newcolor = focused ? (isSelected ? ColorScheme.Focus : GetNormalColor ()) + : (isSelected ? ColorScheme.HotNormal : GetNormalColor ()); + if (newcolor != current) { Driver.SetAttribute (newcolor); current = newcolor; diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 3a4d5099e..2c77abcfb 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -426,17 +426,18 @@ namespace Terminal.Gui { if (index == current) return ColorScheme.Focus; if (!item.IsEnabled ()) return ColorScheme.Disabled; } - return ColorScheme.Normal; + return GetNormalColor (); } public override void Redraw (Rect bounds) { - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); DrawFrame (bounds, padding: 0, fill: true); for (int i = 0; i < barItems.Children.Length; i++) { var item = barItems.Children [i]; - Driver.SetAttribute (item == null ? ColorScheme.Normal : i == current ? ColorScheme.Focus : ColorScheme.Normal); + Driver.SetAttribute (item == null ? GetNormalColor () + : i == current ? ColorScheme.Focus : GetNormalColor ()); if (item == null) { Move (0, i + 1); Driver.AddRune (Driver.LeftTee); @@ -482,7 +483,7 @@ namespace Terminal.Gui { else DrawHotString (textToDraw, i == current ? ColorScheme.HotFocus : ColorScheme.HotNormal, - i == current ? ColorScheme.Focus : ColorScheme.Normal); + i == current ? ColorScheme.Focus : GetNormalColor ()); // The help string var l = item.ShortcutTag.RuneCount == 0 ? item.Help.RuneCount : item.Help.RuneCount + item.ShortcutTag.RuneCount + 2; @@ -905,7 +906,7 @@ namespace Terminal.Gui { public override void Redraw (Rect bounds) { Move (0, 0); - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); for (int i = 0; i < Frame.Width; i++) Driver.AddRune (' '); @@ -918,13 +919,14 @@ namespace Terminal.Gui { Attribute hotColor, normalColor; if (i == selected && IsMenuOpen) { hotColor = i == selected ? ColorScheme.HotFocus : ColorScheme.HotNormal; - normalColor = i == selected ? ColorScheme.Focus : ColorScheme.Normal; + normalColor = i == selected ? ColorScheme.Focus : + GetNormalColor (); } else if (openedByAltKey) { hotColor = ColorScheme.HotNormal; - normalColor = ColorScheme.Normal; + normalColor = GetNormalColor (); } else { - hotColor = ColorScheme.Normal; - normalColor = ColorScheme.Normal; + hotColor = GetNormalColor (); + normalColor = GetNormalColor (); } DrawHotString (menu.Help.IsEmpty ? $" {menu.Title} " : $" {menu.Title} {menu.Help} ", hotColor, normalColor); pos += 1 + menu.TitleLength + (menu.Help.Length > 0 ? menu.Help.Length + 2 : 0) + 2; @@ -989,7 +991,7 @@ namespace Terminal.Gui { /// Virtual method that will invoke the event if it's defined. /// /// The current menu to be replaced. - /// /// Returns the + /// Returns the public virtual MenuOpeningEventArgs OnMenuOpening (MenuBarItem currentMenu) { var ev = new MenuOpeningEventArgs (currentMenu); @@ -1175,7 +1177,7 @@ namespace Terminal.Gui { } LastFocused = lastFocused; lastFocused = null; - if (LastFocused != null) { + if (LastFocused != null && LastFocused.CanFocus) { if (!reopen) { selected = -1; } diff --git a/Terminal.Gui/Views/ProgressBar.cs b/Terminal.Gui/Views/ProgressBar.cs index 7a1abb4d4..c1df8900e 100644 --- a/Terminal.Gui/Views/ProgressBar.cs +++ b/Terminal.Gui/Views/ProgressBar.cs @@ -279,7 +279,7 @@ namespace Terminal.Gui { { DrawFrame (); - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); int fWidth = GetFrameWidth (); if (isActivity) { diff --git a/Terminal.Gui/Views/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs index a9c6cb05a..2e4a2fe66 100644 --- a/Terminal.Gui/Views/RadioGroup.cs +++ b/Terminal.Gui/Views/RadioGroup.cs @@ -17,7 +17,7 @@ namespace Terminal.Gui { void Init (Rect rect, ustring [] radioLabels, int selected) { if (radioLabels == null) { - this.radioLabels = new List(); + this.radioLabels = new List (); } else { this.radioLabels = radioLabels.ToList (); } @@ -63,7 +63,8 @@ namespace Terminal.Gui { /// The radio labels; an array of strings that can contain hotkeys using an underscore before the letter. /// The item to be selected, the value is clamped to the number of items. public RadioGroup (int x, int y, ustring [] radioLabels, int selected = 0) : - this (MakeRect (x, y, radioLabels != null ? radioLabels.ToList() : null), radioLabels, selected) { } + this (MakeRect (x, y, radioLabels != null ? radioLabels.ToList () : null), radioLabels, selected) + { } /// /// Gets or sets the for this . @@ -141,7 +142,7 @@ namespace Terminal.Gui { /// /// The radio labels. public ustring [] RadioLabels { - get => radioLabels.ToArray(); + get => radioLabels.ToArray (); set { var prevCount = radioLabels.Count; radioLabels = value.ToList (); @@ -184,7 +185,7 @@ namespace Terminal.Gui { /// public override void Redraw (Rect bounds) { - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); Clear (); for (int i = 0; i < radioLabels.Count; i++) { switch (DisplayMode) { @@ -195,8 +196,8 @@ namespace Terminal.Gui { Move (horizontal [i].pos, 0); break; } - Driver.SetAttribute (ColorScheme.Normal); - Driver.AddStr (ustring.Make(new Rune[] { (i == selected ? Driver.Selected : Driver.UnSelected), ' '})); + Driver.SetAttribute (GetNormalColor ()); + Driver.AddStr (ustring.Make (new Rune [] { (i == selected ? Driver.Selected : Driver.UnSelected), ' ' })); DrawHotString (radioLabels [i], HasFocus && i == cursor, ColorScheme); } } @@ -222,7 +223,7 @@ namespace Terminal.Gui { /// /// Gets the index of the item that was previously selected. -1 if there was no previous selection. /// - public int PreviousSelectedItem { get; } + public int PreviousSelectedItem { get; } /// /// Gets the index of the item that is now selected. -1 if there is no selection. @@ -234,7 +235,7 @@ namespace Terminal.Gui { /// /// /// - public SelectedItemChangedArgs(int selectedItem, int previousSelectedItem) + public SelectedItemChangedArgs (int selectedItem, int previousSelectedItem) { PreviousSelectedItem = previousSelectedItem; SelectedItem = selectedItem; diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index ed08c607a..7760245eb 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -85,13 +85,22 @@ namespace Terminal.Gui { X = isVertical ? Pos.Right (host) - 1 : Pos.Left (host); Y = isVertical ? Pos.Top (host) : Pos.Bottom (host) - 1; Host = host; + CanFocus = host.CanFocus; + Enabled = host.Enabled; + Visible = host.Visible; + Host.CanFocusChanged += Host_CanFocusChanged; + Host.EnabledChanged += Host_EnabledChanged; + Host.VisibleChanged += Host_VisibleChanged; Host.SuperView.Add (this); AutoHideScrollBars = true; if (showBothScrollIndicator) { OtherScrollBarView = new ScrollBarView (0, 0, !isVertical) { ColorScheme = host.ColorScheme, Host = host, - OtherScrollBarView = this, + CanFocus = host.CanFocus, + Enabled = host.Enabled, + Visible = host.Visible, + OtherScrollBarView = this }; OtherScrollBarView.hosted = true; OtherScrollBarView.X = OtherScrollBarView.IsVertical ? Pos.Right (host) - 1 : Pos.Left (host); @@ -100,7 +109,7 @@ namespace Terminal.Gui { OtherScrollBarView.showScrollIndicator = true; } ShowScrollIndicator = true; - contentBottomRightCorner = new View (" "); + contentBottomRightCorner = new View (" ") { Visible = host.Visible }; Host.SuperView.Add (contentBottomRightCorner); contentBottomRightCorner.X = Pos.Right (host) - 1; contentBottomRightCorner.Y = Pos.Bottom (host) - 1; @@ -109,6 +118,36 @@ namespace Terminal.Gui { contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick; } + private void Host_VisibleChanged () + { + if (!Host.Visible) { + Visible = Host.Visible; + if (otherScrollBarView != null) { + otherScrollBarView.Visible = Visible; + } + contentBottomRightCorner.Visible = Visible; + } else { + ShowHideScrollBars (); + } + } + + private void Host_EnabledChanged () + { + Enabled = Host.Enabled; + if (otherScrollBarView != null) { + otherScrollBarView.Enabled = Enabled; + } + contentBottomRightCorner.Enabled = Enabled; + } + + private void Host_CanFocusChanged () + { + CanFocus = Host.CanFocus; + if (otherScrollBarView != null) { + otherScrollBarView.CanFocus = CanFocus; + } + } + void ContentBottomRightCorner_MouseClick (MouseEventArgs me) { if (me.MouseEvent.Flags == MouseFlags.WheeledDown || me.MouseEvent.Flags == MouseFlags.WheeledUp @@ -322,6 +361,13 @@ namespace Terminal.Gui { } else { contentBottomRightCorner.Visible = false; } + if (Host?.Visible == true && showScrollIndicator && !Visible) { + Visible = true; + } + if (Host?.Visible == true && otherScrollBarView != null && otherScrollBarView.showScrollIndicator + && !otherScrollBarView.Visible) { + otherScrollBarView.Visible = true; + } if (showScrollIndicator) { Redraw (Bounds); } @@ -390,7 +436,7 @@ namespace Terminal.Gui { return; } - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); if ((vertical && Bounds.Height == 0) || (!vertical && Bounds.Width == 0)) { return; @@ -549,7 +595,10 @@ namespace Terminal.Gui { return false; } - if (Host != null && !Host.HasFocus) { + if (!Host.CanFocus) { + return true; + } + if (Host?.HasFocus == false) { Host.SetFocus (); } diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index 1842de883..943d2dbcb 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -250,7 +250,7 @@ namespace Terminal.Gui { } /// - /// /// Gets or sets the visibility for the vertical scroll indicator. + /// Gets or sets the visibility for the vertical scroll indicator. /// /// true if show vertical scroll indicator; otherwise, false. public bool ShowVerticalScrollIndicator { @@ -281,7 +281,7 @@ namespace Terminal.Gui { /// public override void Redraw (Rect region) { - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); SetViewsNeedsDisplay (); Clear (); @@ -308,7 +308,7 @@ namespace Terminal.Gui { if (ShowVerticalScrollIndicator && ShowHorizontalScrollIndicator) { AddRune (Bounds.Width - 1, Bounds.Height - 1, ' '); } - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); } void ShowHideScrollBars () diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs index 0e3c3b60d..f8db8920c 100644 --- a/Terminal.Gui/Views/StatusBar.cs +++ b/Terminal.Gui/Views/StatusBar.cs @@ -7,6 +7,7 @@ // TODO: // Add mouse support using System; +using System.Collections.Generic; using NStack; namespace Terminal.Gui { @@ -82,8 +83,6 @@ namespace Terminal.Gui { /// A list of statusbar items. public StatusBar (StatusItem [] items) : base () { - Width = Dim.Fill (); - Height = 1; Items = items; CanFocus = false; ColorScheme = Colors.Menu; @@ -97,7 +96,17 @@ namespace Terminal.Gui { private void StatusBar_Initialized (object sender, EventArgs e) { - Y = SuperView.Frame.Height - 1; + if (SuperView.Frame == Rect.Empty) { + ((Toplevel)SuperView).Loaded += StatusBar_Loaded; + } else { + Y = Math.Max (SuperView.Frame.Height - (Visible ? 1 : 0), 0); + } + } + + private void StatusBar_Loaded () + { + Y = Math.Max (SuperView.Frame.Height - (Visible ? 1 : 0), 0); + ((Toplevel)SuperView).Loaded -= StatusBar_Loaded; } private Action Application_Resized () @@ -106,13 +115,26 @@ namespace Terminal.Gui { X = 0; Height = 1; if (SuperView != null || SuperView is Toplevel) { - Y = SuperView.Frame.Height - (Visible ? 1 : 0); - } else { - //Y = Pos.Bottom (SuperView); + if (Frame.Y != SuperView.Frame.Height - (Visible ? 1 : 0)) { + Y = SuperView.Frame.Height - (Visible ? 1 : 0); + } } }; } + static ustring shortcutDelimiter = "-"; + /// + /// Used for change the shortcut delimiter separator. + /// + public static ustring ShortcutDelimiter { + get => shortcutDelimiter; + set { + if (shortcutDelimiter != value) { + shortcutDelimiter = value == ustring.Empty ? " " : value; + } + } + } + Attribute ToggleScheme (Attribute scheme) { var result = scheme == ColorScheme.Normal ? ColorScheme.HotNormal : ColorScheme.Normal; @@ -130,12 +152,12 @@ namespace Terminal.Gui { //} Move (0, 0); - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); for (int i = 0; i < Frame.Width; i++) Driver.AddRune (' '); Move (1, 0); - var scheme = ColorScheme.Normal; + var scheme = GetNormalColor (); Driver.SetAttribute (scheme); for (int i = 0; i < Items.Length; i++) { var title = Items [i].Title.ToString (); @@ -159,7 +181,7 @@ namespace Terminal.Gui { { foreach (var item in Items) { if (kb.Key == item.Shortcut) { - item.Action?.Invoke (); + Run (item.Action); return true; } } @@ -176,6 +198,7 @@ namespace Terminal.Gui { for (int i = 0; i < Items.Length; i++) { if (me.X >= pos && me.X < pos + GetItemTitleLength (Items [i].Title)) { Run (Items [i].Action); + break; } pos += GetItemTitleLength (Items [i].Title) + 3; } @@ -223,5 +246,34 @@ namespace Terminal.Gui { return base.OnEnter (view); } + + /// + /// Inserts a in the specified index of . + /// + /// The zero-based index at which item should be inserted. + /// The item to insert. + public void AddItemAt (int index, StatusItem item) + { + var itemsList = new List (Items); + itemsList.Insert (index, item); + Items = itemsList.ToArray (); + SetNeedsDisplay (); + } + + /// + /// Removes a at specified index of . + /// + /// The zero-based index of the item to remove. + /// The removed. + public StatusItem RemoveItem (int index) + { + var itemsList = new List (Items); + var item = itemsList [index]; + itemsList.RemoveAt (index); + Items = itemsList.ToArray (); + SetNeedsDisplay (); + + return item; + } } } \ No newline at end of file diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs index 77cd20411..193751e4c 100644 --- a/Terminal.Gui/Views/TabView.cs +++ b/Terminal.Gui/Views/TabView.cs @@ -165,7 +165,7 @@ namespace Terminal.Gui { public override void Redraw (Rect bounds) { Move (0, 0); - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); if (Style.ShowBorder) { @@ -486,7 +486,7 @@ namespace Terminal.Gui { var tabLocations = host.CalculateViewport (bounds).ToArray (); var width = bounds.Width; - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); if (host.Style.ShowTopLine) { RenderOverline (tabLocations, width); @@ -495,7 +495,7 @@ namespace Terminal.Gui { RenderTabLine (tabLocations, width); RenderUnderline (tabLocations, width); - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); } @@ -589,7 +589,7 @@ namespace Terminal.Gui { Driver.AddStr (toRender.TextToRender); - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); if (toRender.IsSelected) { Driver.AddRune (Driver.VLine); diff --git a/Terminal.Gui/Views/TableView.cs b/Terminal.Gui/Views/TableView.cs index 4f8deab56..0a11ff10f 100644 --- a/Terminal.Gui/Views/TableView.cs +++ b/Terminal.Gui/Views/TableView.cs @@ -199,7 +199,7 @@ namespace Terminal.Gui { // What columns to render at what X offset in viewport var columnsToRender = CalculateViewport (bounds).ToArray (); - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); //invalidate current row (prevents scrolling around leaving old characters in the frame Driver.AddStr (new string (' ', bounds.Width)); @@ -253,7 +253,7 @@ namespace Terminal.Gui { private void ClearLine (int row, int width) { Move (0, row); - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); Driver.AddStr (new string (' ', width)); } @@ -391,7 +391,8 @@ namespace Terminal.Gui { //start by clearing the entire line Move (0, row); - Driver.SetAttribute (FullRowSelect && IsSelected (0, rowToRender) ? rowScheme.HotFocus : rowScheme.Normal); + Driver.SetAttribute (FullRowSelect && IsSelected (0, rowToRender) ? rowScheme.HotFocus + : Enabled ? rowScheme.Normal : rowScheme.Disabled); Driver.AddStr (new string (' ', Bounds.Width)); // Render cells for each visible header for the current row @@ -431,7 +432,7 @@ namespace Terminal.Gui { scheme = rowScheme; } - var cellColor = isSelectedCell ? scheme.HotFocus : scheme.Normal; + var cellColor = isSelectedCell ? scheme.HotFocus : Enabled ? scheme.Normal : scheme.Disabled; var render = TruncateOrPad (val, representation, current.Width, colStyle); @@ -442,12 +443,13 @@ namespace Terminal.Gui { // Reset color scheme to normal for drawing separators if we drew text with custom scheme if (scheme != rowScheme) { - Driver.SetAttribute (isSelectedCell ? rowScheme.HotFocus : rowScheme.Normal); + Driver.SetAttribute (isSelectedCell ? rowScheme.HotFocus + : Enabled ? rowScheme.Normal : rowScheme.Disabled); } // If not in full row select mode always, reset color scheme to normal and render the vertical line (or space) at the end of the cell if (!FullRowSelect) - Driver.SetAttribute (rowScheme.Normal); + Driver.SetAttribute (Enabled ? rowScheme.Normal : rowScheme.Disabled); RenderSeparator (current.X - 1, row, false); diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index fb6d5a7ab..61c2ab8b4 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -83,7 +83,7 @@ namespace Terminal.Gui { void Initialize (ustring text, int w) { - Initialize (); + Height = 1; if (text == null) text = ""; @@ -96,11 +96,6 @@ namespace Terminal.Gui { WantMousePositionReports = true; } - void Initialize () - { - Height = 1; - } - /// public override bool OnLeave (View view) { @@ -221,7 +216,7 @@ namespace Terminal.Gui { int col = 0; int width = Frame.Width + OffSetBackground (); var tcount = text.Count; - var roc = Colors.Menu.Disabled; + var roc = GetReadOnlyColor (); for (int idx = p; idx < tcount; idx++) { var rune = text [idx]; var cols = Rune.ColumnWidth (rune); @@ -229,8 +224,10 @@ namespace Terminal.Gui { Driver.SetAttribute (selColor); } else if (ReadOnly) { Driver.SetAttribute (idx >= start && length > 0 && idx < start + length ? selColor : roc); - } else if (!HasFocus) { + } else if (!HasFocus && Enabled) { Driver.SetAttribute (ColorScheme.Focus); + } else if (!Enabled) { + Driver.SetAttribute (roc); } else { Driver.SetAttribute (idx >= start && length > 0 && idx < start + length ? selColor : ColorScheme.Focus); } @@ -253,6 +250,14 @@ namespace Terminal.Gui { PositionCursor (); } + Attribute GetReadOnlyColor () + { + if (ColorScheme.Disabled.Foreground == ColorScheme.Focus.Background) { + return new Attribute (ColorScheme.Focus.Foreground, ColorScheme.Focus.Background); + } + return new Attribute (ColorScheme.Disabled.Foreground, ColorScheme.Focus.Background); + } + void Adjust () { int offB = OffSetBackground (); diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index b6084e4b9..f4da8c33b 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -303,10 +303,7 @@ namespace Terminal.Gui { if (rune == '\t') { size += tabWidth + 1; } - if (size > width) { - if (end == t.Count) { - col++; - } + if (size >= width) { break; } else if (end < t.Count && col > 0 && start < end && col == start) { break; @@ -898,6 +895,7 @@ namespace Terminal.Gui { bool wordWrap; WordWrapManager wrapManager; bool continuousFind; + int bottomOffset, rightOffset; int tabWidth = 4; bool allowsTab = true; bool allowsReturn = true; @@ -1128,13 +1126,31 @@ namespace Terminal.Gui { /// The bottom offset needed to use a horizontal scrollbar or for another reason. /// This is only needed with the keyboard navigation. /// - public int BottomOffset { get; set; } + public int BottomOffset { + get => bottomOffset; + set { + if (currentRow == Lines - 1 && bottomOffset > 0 && value == 0) { + topRow = Math.Max (topRow - bottomOffset, 0); + } + bottomOffset = value; + Adjust (); + } + } /// /// The right offset needed to use a vertical scrollbar or for another reason. /// This is only needed with the keyboard navigation. /// - public int RightOffset { get; set; } + public int RightOffset { + get => rightOffset; + set { + if (currentColumn == GetCurrentLine ().Count && rightOffset > 0 && value == 0) { + leftColumn = Math.Max (leftColumn - rightOffset, 0); + } + rightOffset = value; + Adjust (); + } + } /// /// Gets or sets a value indicating whether pressing ENTER in a @@ -1236,22 +1252,27 @@ namespace Terminal.Gui { return SelectedText.Length; } - CursorVisibility savedCursorVisibility = CursorVisibility.Default; + CursorVisibility savedCursorVisibility; void SaveCursorVisibility () { if (desiredCursorVisibility != CursorVisibility.Invisible) { - savedCursorVisibility = desiredCursorVisibility; + if (savedCursorVisibility == 0) { + savedCursorVisibility = desiredCursorVisibility; + } DesiredCursorVisibility = CursorVisibility.Invisible; } } void ResetCursorVisibility () { - if (savedCursorVisibility != desiredCursorVisibility) { + if (savedCursorVisibility == 0) { + savedCursorVisibility = desiredCursorVisibility; + } + if (savedCursorVisibility != desiredCursorVisibility && !HasFocus) { DesiredCursorVisibility = savedCursorVisibility; savedCursorVisibility = CursorVisibility.Default; - } else { + } else if (desiredCursorVisibility != CursorVisibility.Underline) { DesiredCursorVisibility = CursorVisibility.Underline; } } @@ -1309,6 +1330,10 @@ namespace Terminal.Gui { /// public override void PositionCursor () { + if (!CanFocus || !Enabled) { + return; + } + if (selecting) { var minRow = Math.Min (Math.Max (Math.Min (selectionStartRow, currentRow) - topRow, 0), Frame.Height); var maxRow = Math.Min (Math.Max (Math.Max (selectionStartRow, currentRow) - topRow, 0), Frame.Height); @@ -1325,10 +1350,13 @@ namespace Terminal.Gui { if (line [idx] == '\t') { cols += TabWidth + 1; } - TextModel.SetCol (ref col, Frame.Width, cols); + if (!TextModel.SetCol (ref col, Frame.Width, cols)) { + col = currentColumn; + break; + } } } - if ((col >= leftColumn || col < Frame.Width) + if (col >= leftColumn && currentColumn - leftColumn + RightOffset < Frame.Width && topRow <= currentRow && currentRow - topRow + BottomOffset < Frame.Height) { ResetCursorVisibility (); Move (col, currentRow - topRow); @@ -1351,7 +1379,7 @@ namespace Terminal.Gui { /// protected virtual void ColorNormal () { - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); } /// @@ -1363,7 +1391,7 @@ namespace Terminal.Gui { /// protected virtual void ColorNormal (List line, int idx) { - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); } /// @@ -1400,6 +1428,7 @@ namespace Terminal.Gui { get => isReadOnly; set { isReadOnly = value; + SetNeedsDisplay (); } } @@ -1416,6 +1445,7 @@ namespace Terminal.Gui { } desiredCursorVisibility = value; + SetNeedsDisplay (); } } @@ -1970,6 +2000,10 @@ namespace Terminal.Gui { /// public override bool ProcessKey (KeyEvent kb) { + if (!CanFocus) { + return true; + } + int restCount; List rest; diff --git a/Terminal.Gui/Views/TreeView.cs b/Terminal.Gui/Views/TreeView.cs index ecf67c478..139178da6 100644 --- a/Terminal.Gui/Views/TreeView.cs +++ b/Terminal.Gui/Views/TreeView.cs @@ -377,7 +377,7 @@ namespace Terminal.Gui { // Else clear the line to prevent stale symbols due to scrolling etc Move (0, line); - Driver.SetAttribute (ColorScheme.Normal); + Driver.SetAttribute (GetNormalColor ()); Driver.AddStr (new string (' ', bounds.Width)); } diff --git a/Terminal.Gui/Windows/FileDialog.cs b/Terminal.Gui/Windows/FileDialog.cs index 941f6e94f..35bb56937 100644 --- a/Terminal.Gui/Windows/FileDialog.cs +++ b/Terminal.Gui/Windows/FileDialog.cs @@ -19,6 +19,7 @@ namespace Terminal.Gui { internal class DirListView : View { int top, selected; DirectoryInfo dirInfo; + FileSystemWatcher watcher; List<(string, bool, bool)> infos; internal bool canChooseFiles = true; internal bool canChooseDirectories = false; @@ -39,7 +40,7 @@ namespace Terminal.Gui { if (allowedFileTypes == null) return true; foreach (var ft in allowedFileTypes) - if (fsi.Name.EndsWith (ft)) + if (fsi.Name.EndsWith (ft) || ft == ".*") return true; return false; } @@ -49,6 +50,21 @@ namespace Terminal.Gui { bool valid = false; try { dirInfo = new DirectoryInfo (value == null ? directory.ToString () : value.ToString ()); + watcher = new FileSystemWatcher (dirInfo.FullName); + watcher.NotifyFilter = NotifyFilters.Attributes + | NotifyFilters.CreationTime + | NotifyFilters.DirectoryName + | NotifyFilters.FileName + | NotifyFilters.LastAccess + | NotifyFilters.LastWrite + | NotifyFilters.Security + | NotifyFilters.Size; + watcher.Changed += Watcher_Changed; + watcher.Created += Watcher_Changed; + watcher.Deleted += Watcher_Changed; + watcher.Renamed += Watcher_Changed; + watcher.Error += Watcher_Error; + watcher.EnableRaisingEvents = true; infos = (from x in dirInfo.GetFileSystemInfos () where IsAllowed (x) && (!canChooseFiles ? x.Attributes.HasFlag (FileAttributes.Directory) : true) orderby (!x.Attributes.HasFlag (FileAttributes.Directory)) + x.Name @@ -62,6 +78,7 @@ namespace Terminal.Gui { case DirectoryNotFoundException _: case ArgumentException _: dirInfo = null; + watcher = null; infos.Clear (); valid = true; break; @@ -77,6 +94,16 @@ namespace Terminal.Gui { return valid; } + void Watcher_Error (object sender, ErrorEventArgs e) + { + Application.MainLoop.Invoke (() => Reload ()); + } + + void Watcher_Changed (object sender, FileSystemEventArgs e) + { + Application.MainLoop.Invoke (() => Reload ()); + } + ustring directory; public ustring Directory { get => directory; @@ -168,13 +195,11 @@ namespace Terminal.Gui { return true; } - private void UnMarkAll () + void UnMarkAll () { - if (allowsMultipleSelection && infos.Count > 0) { - for (int i = 0; i < infos.Count; i++) { - if (infos [i].Item3) { - infos [i] = (infos [i].Item1, infos [i].Item2, false); - } + for (int i = 0; i < infos.Count; i++) { + if (infos [i].Item3) { + infos [i] = (infos [i].Item1, infos [i].Item2, false); } } } @@ -221,7 +246,8 @@ namespace Terminal.Gui { for (int row = 0; row < f.Height; row++, item++) { bool isSelected = item == selected; Move (0, row); - var newcolor = focused ? (isSelected ? ColorScheme.HotNormal : ColorScheme.Focus) : ColorScheme.Focus; + var newcolor = focused ? (isSelected ? ColorScheme.HotNormal : ColorScheme.Focus) + : Enabled ? ColorScheme.Focus : ColorScheme.Disabled; if (newcolor != current) { Driver.SetAttribute (newcolor); current = newcolor; @@ -250,11 +276,13 @@ namespace Terminal.Gui { public Action DirectoryChanged { get; set; } public Action FileChanged { get; set; } + string splitString = ","; + void OnSelectionChanged () { if (allowsMultipleSelection) { if (FilePaths.Count > 0) { - FileChanged?.Invoke (string.Join (", ", GetFilesName (FilePaths))); + FileChanged?.Invoke (string.Join (splitString, GetFilesName (FilePaths))); } else { FileChanged?.Invoke (infos [selected].Item2 && !canChooseDirectories ? "" : Path.GetFileName (infos [selected].Item1)); } @@ -275,6 +303,46 @@ namespace Terminal.Gui { return filesName; } + public bool GetValidFilesName (string files, out string result) + { + result = string.Empty; + if (infos?.Count == 0) { + return false; + } + + var valid = true; + IReadOnlyList filesList = new List (files.Split (splitString.ToArray (), StringSplitOptions.None)); + var filesName = new List (); + UnMarkAll (); + + foreach (var file in filesList) { + if (!allowsMultipleSelection && filesName.Count > 0) { + break; + } + var idx = infos.IndexOf (x => x.Item1.IndexOf (file, StringComparison.OrdinalIgnoreCase) >= 0); + if (idx > -1 && string.Equals (infos [idx].Item1, file, StringComparison.OrdinalIgnoreCase)) { + if (canChooseDirectories && !canChooseFiles && !infos [idx].Item2) { + valid = false; + } + if (allowsMultipleSelection && !infos [idx].Item3) { + infos [idx] = (infos [idx].Item1, infos [idx].Item2, true); + } + if (!allowsMultipleSelection) { + selected = idx; + } + filesName.Add (Path.GetFileName (infos [idx].Item1)); + } else if (idx > -1) { + valid = false; + filesName.Add (Path.GetFileName (file)); + } + } + result = string.Join (splitString, filesName); + if (string.IsNullOrEmpty (result)) { + valid = false; + } + return valid; + } + public override bool ProcessKey (KeyEvent keyEvent) { switch (keyEvent.Key) { @@ -378,7 +446,7 @@ namespace Terminal.Gui { } } - internal bool ExecuteSelection () + internal bool ExecuteSelection (bool navigateFolder = true) { if (infos.Count == 0) { return false; @@ -388,8 +456,11 @@ namespace Terminal.Gui { if (isDir) { Directory = Path.GetFullPath (Path.Combine (Path.GetFullPath (Directory.ToString ()), infos [selected].Item1)); DirectoryChanged?.Invoke (Directory); + if (canChooseDirectories && !navigateFolder) { + return true; + } } else { - FileChanged?.Invoke (infos [selected].Item1); + OnSelectionChanged (); if (canChooseFiles) { // Ensures that at least one file is selected. if (FilePaths.Count == 0) @@ -483,11 +554,14 @@ namespace Terminal.Gui { Label nameFieldLabel, message, nameDirLabel; TextField dirEntry, nameEntry; internal DirListView dirListView; + ComboBox cmbAllowedTypes; /// /// Initializes a new . /// - public FileDialog () : this (title: string.Empty, prompt: string.Empty, nameFieldLabel: string.Empty, message: string.Empty) { } + public FileDialog () : this (title: string.Empty, prompt: string.Empty, + nameFieldLabel: string.Empty, message: string.Empty) + { } /// /// Initializes a new instance of @@ -496,8 +570,9 @@ namespace Terminal.Gui { /// The prompt. /// The name of the file field label.. /// The message. - public FileDialog (ustring title, ustring prompt, ustring nameFieldLabel, ustring message) - : this (title, prompt, ustring.Empty, nameFieldLabel, message) { } + /// The allowed types. + public FileDialog (ustring title, ustring prompt, ustring nameFieldLabel, ustring message, List allowedTypes = null) + : this (title, prompt, ustring.Empty, nameFieldLabel, message, allowedTypes) { } /// /// Initializes a new instance of @@ -505,8 +580,9 @@ namespace Terminal.Gui { /// The title. /// The prompt. /// The message. - - public FileDialog (ustring title, ustring prompt, ustring message) : this (title, prompt, ustring.Empty, message) { } + /// The allowed types. + public FileDialog (ustring title, ustring prompt, ustring message, List allowedTypes) + : this (title, prompt, ustring.Empty, message, allowedTypes) { } /// /// Initializes a new instance of @@ -516,7 +592,9 @@ namespace Terminal.Gui { /// The name of the directory field label. /// The name of the file field label.. /// The message. - public FileDialog (ustring title, ustring prompt, ustring nameDirLabel, ustring nameFieldLabel, ustring message) : base (title)//, Driver.Cols - 20, Driver.Rows - 5, null) + /// The allowed types. + public FileDialog (ustring title, ustring prompt, ustring nameDirLabel, ustring nameFieldLabel, ustring message, + List allowedTypes = null) : base (title)//, Driver.Cols - 20, Driver.Rows - 5, null) { this.message = new Label (message) { X = 1, @@ -550,10 +628,22 @@ namespace Terminal.Gui { nameEntry = new TextField ("") { X = Pos.Left (dirEntry), Y = 3 + msgLines, - Width = Dim.Fill () - 1 + Width = Dim.Percent (70, true) }; Add (this.nameFieldLabel, nameEntry); + cmbAllowedTypes = new ComboBox () { + X = Pos.Right (nameEntry) + 2, + Y = Pos.Top (nameEntry), + Width = Dim.Fill (1), + Height = allowedTypes != null ? allowedTypes.Count + 1 : 1, + Text = allowedTypes?.Count > 0 ? allowedTypes [0] : string.Empty, + ReadOnly = true + }; + cmbAllowedTypes.SetSource (allowedTypes ?? new List ()); + cmbAllowedTypes.OpenSelectedItem += (e) => AllowedFileTypes = cmbAllowedTypes.Text.ToString ().Split (';'); + Add (cmbAllowedTypes); + dirListView = new DirListView (this) { X = 1, Y = 3 + msgLines + 2, @@ -562,21 +652,40 @@ namespace Terminal.Gui { }; DirectoryPath = Path.GetFullPath (Environment.CurrentDirectory); Add (dirListView); + + AllowedFileTypes = cmbAllowedTypes.Text.ToString ().Split (';'); dirListView.DirectoryChanged = (dir) => { nameEntry.Text = ustring.Empty; dirEntry.Text = dir; }; dirListView.FileChanged = (file) => nameEntry.Text = file == ".." ? "" : file; dirListView.SelectedChanged = (file) => nameEntry.Text = file.Item1 == ".." ? "" : file.Item1; this.cancel = new Button ("Cancel"); this.cancel.Clicked += () => { - canceled = true; - Application.RequestStop (); + Cancel (); }; AddButton (cancel); this.prompt = new Button (prompt.IsEmpty ? "Ok" : prompt) { IsDefault = true, - CanFocus = nameEntry.Text.IsEmpty ? false : true + Enabled = nameEntry.Text.IsEmpty ? false : true }; this.prompt.Clicked += () => { + if (this is OpenDialog) { + if (!dirListView.GetValidFilesName (nameEntry.Text.ToString (), out string res)) { + nameEntry.Text = res; + dirListView.SetNeedsDisplay (); + return; + } + if (!dirListView.canChooseDirectories && !dirListView.ExecuteSelection (false)) { + return; + } + } else if (this is SaveDialog) { + var name = nameEntry.Text.ToString (); + if (FilePath.IsEmpty || name.Split (',').Length > 1) { + return; + } + var ext = name.EndsWith (cmbAllowedTypes.Text.ToString ()) + ? "" : cmbAllowedTypes.Text.ToString (); + FilePath = Path.Combine (FilePath.ToString (), $"{name}{ext}"); + } canceled = false; Application.RequestStop (); }; @@ -584,9 +693,9 @@ namespace Terminal.Gui { nameEntry.TextChanged += (e) => { if (nameEntry.Text.IsEmpty) { - this.prompt.CanFocus = false; + this.prompt.Enabled = false; } else { - this.prompt.CanFocus = true; + this.prompt.Enabled = true; } }; @@ -595,6 +704,18 @@ namespace Terminal.Gui { // On success, we will set this to false. canceled = true; + + KeyPress += (e) => { + if (e.KeyEvent.Key == Key.Esc) { + Cancel (); + e.Handled = true; + } + }; + void Cancel () + { + canceled = true; + Application.RequestStop (); + } } internal bool canceled; @@ -603,7 +724,7 @@ namespace Terminal.Gui { public override void WillPresent () { base.WillPresent (); - //SetFocus (nameEntry); + dirListView.SetFocus (); } //protected override void Dispose (bool disposing) @@ -689,7 +810,6 @@ namespace Terminal.Gui { set => dirListView.AllowedFileTypes = value; } - /// /// Gets or sets a value indicating whether this allows the file to be saved with a different extension /// @@ -736,7 +856,9 @@ namespace Terminal.Gui { /// /// The title. /// The message. - public SaveDialog (ustring title, ustring message) : base (title, prompt: "Save", nameFieldLabel: "Save as:", message: message) { } + /// The allowed types. + public SaveDialog (ustring title, ustring message, List allowedTypes = null) + : base (title, prompt: "Save", nameFieldLabel: "Save as:", message: message, allowedTypes) { } /// /// Gets the name of the file the user selected for saving, or null @@ -764,13 +886,33 @@ namespace Terminal.Gui { /// /// To use, create an instance of , and pass it to /// . This will run the dialog modally, - /// and when this returns, the list of filds will be available on the property. + /// and when this returns, the list of files will be available on the property. /// /// /// To select more than one file, users can use the spacebar, or control-t. /// /// public class OpenDialog : FileDialog { + OpenMode openMode; + + /// + /// Determine which type to open. + /// + public enum OpenMode { + /// + /// Opens only file or files. + /// + File, + /// + /// Opens only directory or directories. + /// + Directory, + /// + /// Opens files and directories. + /// + Mixed + } + /// /// Initializes a new . /// @@ -779,10 +921,30 @@ namespace Terminal.Gui { /// /// Initializes a new . /// - /// - /// - public OpenDialog (ustring title, ustring message) : base (title, prompt: "Open", nameFieldLabel: "Open", message: message) + /// The title. + /// The message. + /// The allowed types. + /// The open mode. + public OpenDialog (ustring title, ustring message, List allowedTypes = null, OpenMode openMode = OpenMode.File) : base (title, + prompt: openMode == OpenMode.File ? "Open" : openMode == OpenMode.Directory ? "Select folder" : "Select Mixed", + nameFieldLabel: "Open", message: message, allowedTypes) { + this.openMode = openMode; + switch (openMode) { + case OpenMode.File: + CanChooseFiles = true; + CanChooseDirectories = false; + break; + case OpenMode.Directory: + CanChooseFiles = false; + CanChooseDirectories = true; + break; + case OpenMode.Mixed: + CanChooseFiles = true; + CanChooseDirectories = true; + AllowsMultipleSelection = true; + break; + } } /// @@ -816,6 +978,9 @@ namespace Terminal.Gui { public bool AllowsMultipleSelection { get => dirListView.allowsMultipleSelection; set { + if (!value && openMode == OpenMode.Mixed) { + return; + } dirListView.allowsMultipleSelection = value; dirListView.Reload (); } diff --git a/UICatalog/Scenarios/DynamicMenuBar.cs b/UICatalog/Scenarios/DynamicMenuBar.cs index 54f86f96e..13918064d 100644 --- a/UICatalog/Scenarios/DynamicMenuBar.cs +++ b/UICatalog/Scenarios/DynamicMenuBar.cs @@ -515,10 +515,10 @@ namespace UICatalog { _currentEditMenuBarItem = menuItem; _frmMenuDetails.EditMenuBarItem (menuItem); - var f = _btnOk.CanFocus == _frmMenuDetails.CanFocus; + var f = _btnOk.Enabled == _frmMenuDetails.Enabled; if (!f) { - _btnOk.CanFocus = _frmMenuDetails.CanFocus; - _btnCancel.CanFocus = _frmMenuDetails.CanFocus; + _btnOk.Enabled = _frmMenuDetails.Enabled; + _btnCancel.Enabled = _frmMenuDetails.Enabled; } } @@ -624,7 +624,7 @@ namespace UICatalog { } - _frmMenuDetails.Initialized += (s, e) => _frmMenuDetails.CanFocus = false; + //_frmMenuDetails.Initialized += (s, e) => _frmMenuDetails.Enabled = false; } } @@ -790,19 +790,19 @@ namespace UICatalog { if (_ckbIsTopLevel.Checked) { _ckbSubMenu.Checked = false; _ckbSubMenu.SetNeedsDisplay (); - _txtHelp.CanFocus = true; - _txtAction.CanFocus = true; - _txtShortcut.CanFocus = !_ckbIsTopLevel.Checked && !_ckbSubMenu.Checked; + _txtHelp.Enabled = true; + _txtAction.Enabled = true; + _txtShortcut.Enabled = !_ckbIsTopLevel.Checked && !_ckbSubMenu.Checked; } else { if (_menuItem == null && !hasParent || _menuItem.Parent == null) { _ckbSubMenu.Checked = true; _ckbSubMenu.SetNeedsDisplay (); - _txtShortcut.CanFocus = false; + _txtShortcut.Enabled = false; } _txtHelp.Text = ""; - _txtHelp.CanFocus = false; + _txtHelp.Enabled = false; _txtAction.Text = ""; - _txtAction.CanFocus = false; + _txtAction.Enabled = false; } }; _ckbSubMenu.Toggled += (e) => { @@ -810,20 +810,20 @@ namespace UICatalog { _ckbIsTopLevel.Checked = false; _ckbIsTopLevel.SetNeedsDisplay (); _txtHelp.Text = ""; - _txtHelp.CanFocus = false; + _txtHelp.Enabled = false; _txtAction.Text = ""; - _txtAction.CanFocus = false; + _txtAction.Enabled = false; _txtShortcut.Text = ""; - _txtShortcut.CanFocus = false; + _txtShortcut.Enabled = false; } else { if (!hasParent) { _ckbIsTopLevel.Checked = true; _ckbIsTopLevel.SetNeedsDisplay (); - _txtShortcut.CanFocus = false; + _txtShortcut.Enabled = false; } - _txtHelp.CanFocus = true; - _txtAction.CanFocus = true; - _txtShortcut.CanFocus = !_ckbIsTopLevel.Checked && !_ckbSubMenu.Checked; + _txtHelp.Enabled = true; + _txtAction.Enabled = true; + _txtShortcut.Enabled = !_ckbIsTopLevel.Checked && !_ckbSubMenu.Checked; } }; @@ -843,9 +843,9 @@ namespace UICatalog { _txtAction.Text = m.action; _ckbIsTopLevel.Checked = false; _ckbSubMenu.Checked = !hasParent; - _txtHelp.CanFocus = hasParent; - _txtAction.CanFocus = hasParent; - _txtShortcut.CanFocus = hasParent; + _txtHelp.Enabled = hasParent; + _txtAction.Enabled = hasParent; + _txtShortcut.Enabled = hasParent; } else { EditMenuBarItem (_menuItem); } @@ -891,12 +891,12 @@ namespace UICatalog { { if (menuItem == null) { hasParent = false; - CanFocus = false; + Enabled = false; CleanEditMenuBarItem (); return; } else { hasParent = menuItem.Parent != null; - CanFocus = true; + Enabled = true; } _menuItem = menuItem; _txtTitle.Text = menuItem?.Title ?? ""; @@ -904,11 +904,11 @@ namespace UICatalog { _txtAction.Text = menuItem != null && menuItem.Action != null ? GetTargetAction (menuItem.Action) : ustring.Empty; _ckbIsTopLevel.Checked = IsTopLevel (menuItem); _ckbSubMenu.Checked = HasSubMenus (menuItem); - _txtHelp.CanFocus = !_ckbSubMenu.Checked; - _txtAction.CanFocus = !_ckbSubMenu.Checked; + _txtHelp.Enabled = !_ckbSubMenu.Checked; + _txtAction.Enabled = !_ckbSubMenu.Checked; _rbChkStyle.SelectedItem = (int)(menuItem?.CheckType ?? MenuItemCheckStyle.NoCheck); _txtShortcut.Text = menuItem?.ShortcutTag ?? ""; - _txtShortcut.CanFocus = !_ckbIsTopLevel.Checked && !_ckbSubMenu.Checked; + _txtShortcut.Enabled = !_ckbIsTopLevel.Checked && !_ckbSubMenu.Checked; } void CleanEditMenuBarItem () diff --git a/UICatalog/Scenarios/DynamicStatusBar.cs b/UICatalog/Scenarios/DynamicStatusBar.cs new file mode 100644 index 000000000..be4f960af --- /dev/null +++ b/UICatalog/Scenarios/DynamicStatusBar.cs @@ -0,0 +1,594 @@ +using NStack; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using Terminal.Gui; + +namespace UICatalog { + [ScenarioMetadata (Name: "Dynamic StatusBar", Description: "Demonstrates how to add and remove a StatusBar and change items dynamically.")] + [ScenarioCategory ("Dynamic")] + class DynamicStatusBar : Scenario { + public override void Run () + { + Top.Add (new DynamicStatusBarSample (Win.Title)); + base.Run (); + } + } + + public class DynamicStatusItemList { + public ustring Title { get; set; } + public StatusItem StatusItem { get; set; } + + public DynamicStatusItemList () { } + + public DynamicStatusItemList (ustring title, StatusItem statusItem) + { + Title = title; + StatusItem = statusItem; + } + + public override string ToString () => $"{Title}, {StatusItem}"; + } + + public class DynamicStatusItem { + public ustring title = "New"; + public ustring action = ""; + public ustring shortcut; + + public DynamicStatusItem () { } + + public DynamicStatusItem (ustring title) + { + this.title = title; + } + + public DynamicStatusItem (ustring title, ustring action, ustring shortcut = null) + { + this.title = title; + this.action = action; + this.shortcut = shortcut; + } + } + + public class DynamicStatusBarSample : Window { + StatusBar _statusBar; + StatusItem _currentStatusItem; + int _currentSelectedStatusBar = -1; + StatusItem _currentEditStatusItem; + ListView _lstItems; + + public DynamicStatusItemModel DataContext { get; set; } + + public DynamicStatusBarSample (ustring title) : base (title) + { + DataContext = new DynamicStatusItemModel (); + + var _frmDelimiter = new FrameView ("Shortcut Delimiter:") { + X = Pos.Center (), + Y = 0, + Width = 25, + Height = 4 + }; + + var _txtDelimiter = new TextField (StatusBar.ShortcutDelimiter.ToString ()) { + X = Pos.Center (), + Width = 2, + }; + _txtDelimiter.TextChanged += (_) => StatusBar.ShortcutDelimiter = _txtDelimiter.Text; + _frmDelimiter.Add (_txtDelimiter); + + Add (_frmDelimiter); + + var _frmStatusBar = new FrameView ("Items:") { + Y = 5, + Width = Dim.Percent (50), + Height = Dim.Fill (2) + }; + + var _btnAddStatusBar = new Button ("Add a StatusBar") { + Y = 1, + }; + _frmStatusBar.Add (_btnAddStatusBar); + + var _btnRemoveStatusBar = new Button ("Remove a StatusBar") { + Y = 1 + }; + _btnRemoveStatusBar.X = Pos.AnchorEnd () - (Pos.Right (_btnRemoveStatusBar) - Pos.Left (_btnRemoveStatusBar)); + _frmStatusBar.Add (_btnRemoveStatusBar); + + var _btnAdd = new Button (" Add ") { + Y = Pos.Top (_btnRemoveStatusBar) + 2, + }; + _btnAdd.X = Pos.AnchorEnd () - (Pos.Right (_btnAdd) - Pos.Left (_btnAdd)); + _frmStatusBar.Add (_btnAdd); + + _lstItems = new ListView (new List ()) { + ColorScheme = Colors.Dialog, + Y = Pos.Top (_btnAddStatusBar) + 2, + Width = Dim.Fill () - Dim.Width (_btnAdd) - 1, + Height = Dim.Fill (), + }; + _frmStatusBar.Add (_lstItems); + + var _btnRemove = new Button ("Remove") { + X = Pos.Left (_btnAdd), + Y = Pos.Top (_btnAdd) + 1 + }; + _frmStatusBar.Add (_btnRemove); + + var _btnUp = new Button ("^") { + X = Pos.Right (_lstItems) + 2, + Y = Pos.Top (_btnRemove) + 2 + }; + _frmStatusBar.Add (_btnUp); + + var _btnDown = new Button ("v") { + X = Pos.Right (_lstItems) + 2, + Y = Pos.Top (_btnUp) + 1 + }; + _frmStatusBar.Add (_btnDown); + + Add (_frmStatusBar); + + + var _frmStatusBarDetails = new DynamicStatusBarDetails ("StatusBar Item Details:") { + X = Pos.Right (_frmStatusBar), + Y = Pos.Top (_frmStatusBar), + Width = Dim.Fill (), + Height = Dim.Fill (4) + }; + Add (_frmStatusBarDetails); + + _btnUp.Clicked += () => { + var i = _lstItems.SelectedItem; + var statusItem = DataContext.Items.Count > 0 ? DataContext.Items [i].StatusItem : null; + if (statusItem != null) { + var items = _statusBar.Items; + if (i > 0) { + items [i] = items [i - 1]; + items [i - 1] = statusItem; + DataContext.Items [i] = DataContext.Items [i - 1]; + DataContext.Items [i - 1] = new DynamicStatusItemList (statusItem.Title, statusItem); + _lstItems.SelectedItem = i - 1; + _statusBar.SetNeedsDisplay (); + } + } + }; + + _btnDown.Clicked += () => { + var i = _lstItems.SelectedItem; + var statusItem = DataContext.Items.Count > 0 ? DataContext.Items [i].StatusItem : null; + if (statusItem != null) { + var items = _statusBar.Items; + if (i < items.Length - 1) { + items [i] = items [i + 1]; + items [i + 1] = statusItem; + DataContext.Items [i] = DataContext.Items [i + 1]; + DataContext.Items [i + 1] = new DynamicStatusItemList (statusItem.Title, statusItem); + _lstItems.SelectedItem = i + 1; + _statusBar.SetNeedsDisplay (); + } + } + }; + + var _btnOk = new Button ("Ok") { + X = Pos.Right (_frmStatusBar) + 20, + Y = Pos.Bottom (_frmStatusBarDetails), + }; + Add (_btnOk); + + var _btnCancel = new Button ("Cancel") { + X = Pos.Right (_btnOk) + 3, + Y = Pos.Top (_btnOk), + }; + _btnCancel.Clicked += () => { + SetFrameDetails (_currentEditStatusItem); + }; + Add (_btnCancel); + + _lstItems.SelectedItemChanged += (e) => { + SetFrameDetails (); + }; + + _btnOk.Clicked += () => { + if (ustring.IsNullOrEmpty (_frmStatusBarDetails._txtTitle.Text) && _currentEditStatusItem != null) { + MessageBox.ErrorQuery ("Invalid title", "Must enter a valid title!.", "Ok"); + } else if (_currentEditStatusItem != null) { + _frmStatusBarDetails._txtTitle.Text = SetTitleText ( + _frmStatusBarDetails._txtTitle.Text, _frmStatusBarDetails._txtShortcut.Text); + var statusItem = new DynamicStatusItem (_frmStatusBarDetails._txtTitle.Text, + _frmStatusBarDetails._txtAction.Text, + _frmStatusBarDetails._txtShortcut.Text); + UpdateStatusItem (_currentEditStatusItem, statusItem, _lstItems.SelectedItem); + } + }; + + _btnAdd.Clicked += () => { + if (StatusBar == null) { + MessageBox.ErrorQuery ("StatusBar Bar Error", "Must add a StatusBar first!", "Ok"); + _btnAddStatusBar.SetFocus (); + return; + } + + var frameDetails = new DynamicStatusBarDetails (); + var item = frameDetails.EnterStatusItem (); + if (item == null) { + return; + } + + StatusItem newStatusItem = CreateNewStatusBar (item); + _currentSelectedStatusBar++; + _statusBar.AddItemAt (_currentSelectedStatusBar, newStatusItem); + DataContext.Items.Add (new DynamicStatusItemList (newStatusItem.Title, newStatusItem)); + _lstItems.MoveDown (); + }; + + _btnRemove.Clicked += () => { + var statusItem = DataContext.Items.Count > 0 ? DataContext.Items [_lstItems.SelectedItem].StatusItem : null; + if (statusItem != null) { + _statusBar.RemoveItem (_currentSelectedStatusBar); + DataContext.Items.RemoveAt (_lstItems.SelectedItem); + if (_lstItems.Source.Count > 0 && _lstItems.SelectedItem > _lstItems.Source.Count - 1) { + _lstItems.SelectedItem = _lstItems.Source.Count - 1; + } + _lstItems.SetNeedsDisplay (); + SetFrameDetails (); + } + }; + + _lstItems.Enter += (_) => { + var statusItem = DataContext.Items.Count > 0 ? DataContext.Items [_lstItems.SelectedItem].StatusItem : null; + SetFrameDetails (statusItem); + }; + + _btnAddStatusBar.Clicked += () => { + if (_statusBar != null) { + return; + } + + _statusBar = new StatusBar (); + Add (_statusBar); + }; + + _btnRemoveStatusBar.Clicked += () => { + if (_statusBar == null) { + return; + } + + Remove (_statusBar); + _statusBar = null; + DataContext.Items = new List (); + _currentStatusItem = null; + _currentSelectedStatusBar = -1; + SetListViewSource (_currentStatusItem, true); + SetFrameDetails (null); + }; + + + SetFrameDetails (); + + + var ustringConverter = new UStringValueConverter (); + var listWrapperConverter = new ListWrapperConverter (); + + var lstItems = new Binding (this, "Items", _lstItems, "Source", listWrapperConverter); + + + void SetFrameDetails (StatusItem statusItem = null) + { + StatusItem newStatusItem; + + if (statusItem == null) { + newStatusItem = DataContext.Items.Count > 0 ? DataContext.Items [_lstItems.SelectedItem].StatusItem : null; + } else { + newStatusItem = statusItem; + } + + _currentEditStatusItem = newStatusItem; + _frmStatusBarDetails.EditStatusItem (newStatusItem); + var f = _btnOk.Enabled == _frmStatusBarDetails.Enabled; + if (!f) { + _btnOk.Enabled = _frmStatusBarDetails.Enabled; + _btnCancel.Enabled = _frmStatusBarDetails.Enabled; + } + } + + void SetListViewSource (StatusItem _currentStatusItem, bool fill = false) + { + DataContext.Items = new List (); + var statusItem = _currentStatusItem; + if (!fill) { + return; + } + if (statusItem != null) { + foreach (var si in _statusBar.Items) { + DataContext.Items.Add (new DynamicStatusItemList (si.Title, si)); + } + } + } + + StatusItem CreateNewStatusBar (DynamicStatusItem item) + { + StatusItem newStatusItem; + newStatusItem = new StatusItem (ShortcutHelper.GetShortcutFromTag ( + item.shortcut, StatusBar.ShortcutDelimiter), + item.title, _frmStatusBarDetails.CreateAction (item)); + + return newStatusItem; + } + + void UpdateStatusItem (StatusItem _currentEditStatusItem, DynamicStatusItem statusItem, int index) + { + _currentEditStatusItem = CreateNewStatusBar (statusItem); + _statusBar.Items [index] = _currentEditStatusItem; + if (DataContext.Items.Count == 0) { + DataContext.Items.Add (new DynamicStatusItemList (_currentEditStatusItem.Title, _currentEditStatusItem)); + } + DataContext.Items [index] = new DynamicStatusItemList (_currentEditStatusItem.Title, _currentEditStatusItem); + SetFrameDetails (_currentEditStatusItem); + } + + + //_frmStatusBarDetails.Initialized += (s, e) => _frmStatusBarDetails.Enabled = false; + } + + public static ustring SetTitleText (ustring title, ustring shortcut) + { + var txt = title; + var split = title.ToString ().Split ('~'); + if (split.Length > 1) { + txt = split [2].Trim (); ; + } + if (string.IsNullOrEmpty (shortcut.ToString ())) { + return txt; + } + + return $"~{shortcut}~ {txt}"; + } + } + + public class DynamicStatusBarDetails : FrameView { + public StatusItem _statusItem; + public TextField _txtTitle; + public TextView _txtAction; + public TextField _txtShortcut; + + public DynamicStatusBarDetails (StatusItem statusItem = null) : this (statusItem == null ? "Adding New StatusBar Item." : "Editing StatusBar Item.") + { + _statusItem = statusItem; + } + + public DynamicStatusBarDetails (ustring title) : base (title) + { + var _lblTitle = new Label ("Title:") { + Y = 1 + }; + Add (_lblTitle); + + _txtTitle = new TextField () { + X = Pos.Right (_lblTitle) + 4, + Y = Pos.Top (_lblTitle), + Width = Dim.Fill () + }; + Add (_txtTitle); + + var _lblAction = new Label ("Action:") { + X = Pos.Left (_lblTitle), + Y = Pos.Bottom (_lblTitle) + 1 + }; + Add (_lblAction); + + _txtAction = new TextView () { + ColorScheme = Colors.Dialog, + X = Pos.Left (_txtTitle), + Y = Pos.Top (_lblAction), + Width = Dim.Fill (), + Height = 5 + }; + Add (_txtAction); + + var _lblShortcut = new Label ("Shortcut:") { + X = Pos.Left (_lblTitle), + Y = Pos.Bottom (_txtAction) + 1 + }; + Add (_lblShortcut); + + _txtShortcut = new TextField () { + X = Pos.X (_txtAction), + Y = Pos.Y (_lblShortcut), + Width = Dim.Fill (), + ReadOnly = true + }; + _txtShortcut.KeyDown += (e) => { + if (!ProcessKey (e.KeyEvent)) { + return; + } + + var k = ShortcutHelper.GetModifiersKey (e.KeyEvent); + if (CheckShortcut (k, true)) { + e.Handled = true; + } + }; + + bool ProcessKey (KeyEvent ev) + { + switch (ev.Key) { + case Key.CursorUp: + case Key.CursorDown: + case Key.Tab: + case Key.BackTab: + return false; + } + + return true; + } + + bool CheckShortcut (Key k, bool pre) + { + var m = _statusItem != null ? _statusItem : new StatusItem (k, "", null); + if (pre && !ShortcutHelper.PreShortcutValidation (k)) { + _txtShortcut.Text = ""; + return false; + } + if (!pre) { + if (!ShortcutHelper.PostShortcutValidation (ShortcutHelper.GetShortcutFromTag ( + _txtShortcut.Text, StatusBar.ShortcutDelimiter))) { + _txtShortcut.Text = ""; + return false; + } + return true; + } + _txtShortcut.Text = ShortcutHelper.GetShortcutTag (k, StatusBar.ShortcutDelimiter); + + return true; + } + + _txtShortcut.KeyUp += (e) => { + var k = ShortcutHelper.GetModifiersKey (e.KeyEvent); + if (CheckShortcut (k, false)) { + e.Handled = true; + } + }; + Add (_txtShortcut); + + var _btnShortcut = new Button ("Clear Shortcut") { + X = Pos.X (_lblShortcut), + Y = Pos.Bottom (_txtShortcut) + 1 + }; + _btnShortcut.Clicked += () => { + _txtShortcut.Text = ""; + }; + Add (_btnShortcut); + } + + + public DynamicStatusItem EnterStatusItem () + { + var valid = false; + + if (_statusItem == null) { + var m = new DynamicStatusItem (); + _txtTitle.Text = m.title; + _txtAction.Text = m.action; + } else { + EditStatusItem (_statusItem); + } + + var _btnOk = new Button ("Ok") { + IsDefault = true, + }; + _btnOk.Clicked += () => { + if (ustring.IsNullOrEmpty (_txtTitle.Text)) { + MessageBox.ErrorQuery ("Invalid title", "Must enter a valid title!.", "Ok"); + } else { + if (!ustring.IsNullOrEmpty (_txtShortcut.Text)) { + _txtTitle.Text = DynamicStatusBarSample.SetTitleText ( + _txtTitle.Text, _txtShortcut.Text); + } + valid = true; + Application.RequestStop (); + } + }; + var _btnCancel = new Button ("Cancel"); + _btnCancel.Clicked += () => { + _txtTitle.Text = ustring.Empty; + Application.RequestStop (); + }; + var _dialog = new Dialog ("Please enter the item details.", _btnOk, _btnCancel); + + Width = Dim.Fill (); + Height = Dim.Fill () - 1; + _dialog.Add (this); + _txtTitle.SetFocus (); + _txtTitle.CursorPosition = _txtTitle.Text.Length; + Application.Run (_dialog); + + if (valid) { + return new DynamicStatusItem (_txtTitle.Text, _txtAction.Text, _txtShortcut.Text); + } else { + return null; + } + } + + public void EditStatusItem (StatusItem statusItem) + { + if (statusItem == null) { + Enabled = false; + CleanEditStatusItem (); + return; + } else { + Enabled = true; + } + _statusItem = statusItem; + _txtTitle.Text = statusItem?.Title ?? ""; + _txtAction.Text = statusItem != null && statusItem.Action != null ? GetTargetAction (statusItem.Action) : ustring.Empty; + _txtShortcut.Text = ShortcutHelper.GetShortcutTag (statusItem.Shortcut, StatusBar.ShortcutDelimiter) ?? ""; + } + + void CleanEditStatusItem () + { + _txtTitle.Text = ""; + _txtAction.Text = ""; + _txtShortcut.Text = ""; + } + + ustring GetTargetAction (Action action) + { + var me = action.Target; + + if (me == null) { + throw new ArgumentException (); + } + object v = new object (); + foreach (var field in me.GetType ().GetFields ()) { + if (field.Name == "item") { + v = field.GetValue (me); + } + } + return v == null || !(v is DynamicStatusItem item) ? ustring.Empty : item.action; + } + + public Action CreateAction (DynamicStatusItem item) + { + return new Action (() => MessageBox.ErrorQuery (item.title, item.action, "Ok")); + } + } + + public class DynamicStatusItemModel : INotifyPropertyChanged { + public event PropertyChangedEventHandler PropertyChanged; + + private ustring statusBar; + private List items; + + public ustring StatusBar { + get => statusBar; + set { + if (value != statusBar) { + statusBar = value; + PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (GetPropertyName ())); + } + } + } + + public List Items { + get => items; + set { + if (value != items) { + items = value; + PropertyChanged?.Invoke (this, new PropertyChangedEventArgs (GetPropertyName ())); + } + } + } + + public DynamicStatusItemModel () + { + Items = new List (); + } + + public string GetPropertyName ([CallerMemberName] string propertyName = null) + { + return propertyName; + } + } +} diff --git a/UICatalog/Scenarios/Editor.cs b/UICatalog/Scenarios/Editor.cs index 045a478bc..3cb550c51 100644 --- a/UICatalog/Scenarios/Editor.cs +++ b/UICatalog/Scenarios/Editor.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Text; using Terminal.Gui; @@ -19,7 +20,7 @@ namespace UICatalog { private string _textToReplace; private bool _matchCase; private bool _matchWholeWord; - Window winDialog; + private Window winDialog; public override void Init (Toplevel top, ColorScheme colorScheme) { @@ -29,6 +30,30 @@ namespace UICatalog { Top = Application.Top; } + Win = new Window (_fileName ?? "Untitled") { + X = 0, + Y = 1, + Width = Dim.Fill (), + Height = Dim.Fill (), + ColorScheme = colorScheme, + }; + Top.Add (Win); + + _textView = new TextView () { + X = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill (), + BottomOffset = 1, + RightOffset = 1 + }; + + CreateDemoFile (_fileName); + + LoadFile (); + + Win.Add (_textView); + var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_File", new MenuItem [] { new MenuItem ("_New", "", () => New()), @@ -71,42 +96,24 @@ namespace UICatalog { new MenuBarItem ("Forma_t", new MenuItem [] { CreateWrapChecked (), CreateAllowsTabChecked () - }) + }), + new MenuBarItem ("_Responder", new MenuItem [] { + CreateCanFocusChecked (), + CreateEnabledChecked (), + CreateVisibleChecked () + }), }); Top.Add (menu); var statusBar = new StatusBar (new StatusItem [] { new StatusItem(Key.F2, "~F2~ Open", () => Open()), new StatusItem(Key.F3, "~F3~ Save", () => Save()), + new StatusItem(Key.F4, "~F4~ Save As", () => SaveAs()), new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Quit()), new StatusItem(Key.Null, $"OS Clipboard IsSupported : {Clipboard.IsSupported}", null) }); Top.Add (statusBar); - CreateDemoFile (_fileName); - - Win = new Window (_fileName ?? "Untitled") { - X = 0, - Y = 1, - Width = Dim.Fill (), - Height = Dim.Fill (), - ColorScheme = colorScheme, - }; - Top.Add (Win); - - _textView = new TextView () { - X = 0, - Y = 0, - Width = Dim.Fill (), - Height = Dim.Fill (), - BottomOffset = 1, - RightOffset = 1 - }; - - LoadFile (); - - Win.Add (_textView); - _scrollBar = new ScrollBarView (_textView, true); _scrollBar.ChangedPosition += () => { @@ -125,6 +132,22 @@ namespace UICatalog { _textView.SetNeedsDisplay (); }; + _scrollBar.VisibleChanged += () => { + if (_scrollBar.Visible && _textView.RightOffset == 0) { + _textView.RightOffset = 1; + } else if (!_scrollBar.Visible && _textView.RightOffset == 1) { + _textView.RightOffset = 0; + } + }; + + _scrollBar.OtherScrollBarView.VisibleChanged += () => { + if (_scrollBar.OtherScrollBarView.Visible && _textView.BottomOffset == 0) { + _textView.BottomOffset = 1; + } else if (!_scrollBar.OtherScrollBarView.Visible && _textView.BottomOffset == 1) { + _textView.BottomOffset = 0; + } + }; + _textView.DrawContent += (e) => { _scrollBar.Size = _textView.Lines; _scrollBar.Position = _textView.TopRow; @@ -315,8 +338,8 @@ namespace UICatalog { if (!CanCloseFile ()) { return; } - - var d = new OpenDialog ("Open", "Open a file") { AllowsMultipleSelection = false }; + var aTypes = new List () { ".txt;.bin;.xml;.json", ".txt", ".bin", ".xml", ".*" }; + var d = new OpenDialog ("Open", "Choose the path where to open the file.", aTypes) { AllowsMultipleSelection = false }; Application.Run (d); if (!d.Canceled && d.FilePaths.Count > 0) { @@ -338,7 +361,8 @@ namespace UICatalog { private bool SaveAs () { - var sd = new SaveDialog ("Save file", "Choose the path where to save the file."); + var aTypes = new List () { ".txt", ".bin", ".xml", ".*" }; + var sd = new SaveDialog ("Save file", "Choose the path where to save the file.", aTypes); sd.FilePath = System.IO.Path.Combine (sd.FilePath.ToString (), Win.Title.ToString ()); Application.Run (sd); @@ -433,7 +457,7 @@ namespace UICatalog { Title = "Word Wrap" }; item.CheckType |= MenuItemCheckStyle.Checked; - item.Checked = false; + item.Checked = _textView.WordWrap; item.Action += () => { _textView.WordWrap = item.Checked = !item.Checked; if (_textView.WordWrap) { @@ -455,7 +479,7 @@ namespace UICatalog { Title = "Allows Tab" }; item.CheckType |= MenuItemCheckStyle.Checked; - item.Checked = true; + item.Checked = _textView.AllowsTab; item.Action += () => { _textView.AllowsTab = item.Checked = !item.Checked; }; @@ -463,6 +487,57 @@ namespace UICatalog { return item; } + private MenuItem CreateCanFocusChecked () + { + var item = new MenuItem { + Title = "CanFocus" + }; + item.CheckType |= MenuItemCheckStyle.Checked; + item.Checked = _textView.CanFocus; + item.Action += () => { + _textView.CanFocus = item.Checked = !item.Checked; + if (_textView.CanFocus) { + _textView.SetFocus (); + } + }; + + return item; + } + + private MenuItem CreateEnabledChecked () + { + var item = new MenuItem { + Title = "Enabled" + }; + item.CheckType |= MenuItemCheckStyle.Checked; + item.Checked = _textView.Enabled; + item.Action += () => { + _textView.Enabled = item.Checked = !item.Checked; + if (_textView.Enabled) { + _textView.SetFocus (); + } + }; + + return item; + } + + private MenuItem CreateVisibleChecked () + { + var item = new MenuItem { + Title = "Visible" + }; + item.CheckType |= MenuItemCheckStyle.Checked; + item.Checked = _textView.Visible; + item.Action += () => { + _textView.Visible = item.Checked = !item.Checked; + if (_textView.Visible) { + _textView.SetFocus (); + } + }; + + return item; + } + private void CreateFindReplace (bool isFind = true) { winDialog = new Window (isFind ? "Find" : "Replace") { @@ -538,7 +613,7 @@ namespace UICatalog { X = Pos.Right (txtToFind) + 1, Y = Pos.Top (label), Width = 20, - CanFocus = !txtToFind.Text.IsEmpty, + Enabled = !txtToFind.Text.IsEmpty, TextAlignment = TextAlignment.Centered, IsDefault = true }; @@ -549,7 +624,7 @@ namespace UICatalog { X = Pos.Right (txtToFind) + 1, Y = Pos.Top (btnFindNext) + 1, Width = 20, - CanFocus = !txtToFind.Text.IsEmpty, + Enabled = !txtToFind.Text.IsEmpty, TextAlignment = TextAlignment.Centered }; btnFindPrevious.Clicked += () => FindPrevious (); @@ -558,8 +633,8 @@ namespace UICatalog { txtToFind.TextChanged += (e) => { _textToFind = txtToFind.Text.ToString (); _textView.FindTextChanged (); - btnFindNext.CanFocus = !txtToFind.Text.IsEmpty; - btnFindPrevious.CanFocus = !txtToFind.Text.IsEmpty; + btnFindNext.Enabled = !txtToFind.Text.IsEmpty; + btnFindPrevious.Enabled = !txtToFind.Text.IsEmpty; }; var btnCancel = new Button ("Cancel") { @@ -631,7 +706,7 @@ namespace UICatalog { X = Pos.Right (txtToFind) + 1, Y = Pos.Top (label), Width = 20, - CanFocus = !txtToFind.Text.IsEmpty, + Enabled = !txtToFind.Text.IsEmpty, TextAlignment = TextAlignment.Centered, IsDefault = true }; @@ -659,7 +734,7 @@ namespace UICatalog { X = Pos.Right (txtToFind) + 1, Y = Pos.Top (btnFindNext) + 1, Width = 20, - CanFocus = !txtToFind.Text.IsEmpty, + Enabled = !txtToFind.Text.IsEmpty, TextAlignment = TextAlignment.Centered }; btnFindPrevious.Clicked += () => ReplacePrevious (); @@ -669,7 +744,7 @@ namespace UICatalog { X = Pos.Right (txtToFind) + 1, Y = Pos.Top (btnFindPrevious) + 1, Width = 20, - CanFocus = !txtToFind.Text.IsEmpty, + Enabled = !txtToFind.Text.IsEmpty, TextAlignment = TextAlignment.Centered }; btnReplaceAll.Clicked += () => ReplaceAll (); @@ -678,9 +753,9 @@ namespace UICatalog { txtToFind.TextChanged += (e) => { _textToFind = txtToFind.Text.ToString (); _textView.FindTextChanged (); - btnFindNext.CanFocus = !txtToFind.Text.IsEmpty; - btnFindPrevious.CanFocus = !txtToFind.Text.IsEmpty; - btnReplaceAll.CanFocus = !txtToFind.Text.IsEmpty; + btnFindNext.Enabled = !txtToFind.Text.IsEmpty; + btnFindPrevious.Enabled = !txtToFind.Text.IsEmpty; + btnReplaceAll.Enabled = !txtToFind.Text.IsEmpty; }; var btnCancel = new Button ("Cancel") { diff --git a/UICatalog/Scenarios/ProgressBarStyles.cs b/UICatalog/Scenarios/ProgressBarStyles.cs index 4eaf5aa82..91da481ac 100644 --- a/UICatalog/Scenarios/ProgressBarStyles.cs +++ b/UICatalog/Scenarios/ProgressBarStyles.cs @@ -64,7 +64,7 @@ namespace UICatalog { }; button.Clicked += () => { if (_fractionTimer == null) { - button.CanFocus = false; + button.Enabled = false; blocksPB.Fraction = 0; continuousPB.Fraction = 0; float fractionSum = 0; @@ -75,7 +75,7 @@ namespace UICatalog { if (fractionSum > 1) { _fractionTimer.Dispose (); _fractionTimer = null; - button.CanFocus = true; + button.Enabled = true; } Application.MainLoop.Driver.Wakeup (); }, null, 0, _timerTick); diff --git a/UICatalog/Scenarios/SingleBackgroundWorker.cs b/UICatalog/Scenarios/SingleBackgroundWorker.cs index 2cf11e6ef..0cb7c262b 100644 --- a/UICatalog/Scenarios/SingleBackgroundWorker.cs +++ b/UICatalog/Scenarios/SingleBackgroundWorker.cs @@ -60,11 +60,6 @@ namespace UICatalog { Add (top); } - public void Load () - { - Application.Run (this); - } - private void RunWorker () { worker = new BackgroundWorker () { WorkerSupportsCancellation = true }; @@ -87,6 +82,10 @@ namespace UICatalog { listLog.SetNeedsDisplay (); var md = new Dialog ($"Running Worker started at {startStaging}.{startStaging:fff}", cancel); + md.Add (new Label ("Wait for worker to finish...") { + X = Pos.Center (), + Y = Pos.Center () + }); worker.DoWork += (s, e) => { var stageResult = new List (); @@ -163,9 +162,6 @@ namespace UICatalog { top.Add (statusBar); Title = $"Worker started at {start}.{start:fff}"; - Y = 1; - Height = Dim.Fill (1); - ColorScheme = Colors.Base; Add (new ListView (list) { diff --git a/UnitTests/LineViewTests.cs b/UnitTests/LineViewTests.cs index 555d2ee5e..99b581be6 100644 --- a/UnitTests/LineViewTests.cs +++ b/UnitTests/LineViewTests.cs @@ -1,9 +1,7 @@ -using Terminal.Gui; -using Terminal.Gui.Graphs; -using Terminal.Gui.Views; +using Terminal.Gui.Graphs; using Xunit; -namespace UnitTests { +namespace Terminal.Gui.Views { public class LineViewTests { [Fact] diff --git a/UnitTests/ResponderTests.cs b/UnitTests/ResponderTests.cs index 3f5ed7e5f..7f2d927f8 100644 --- a/UnitTests/ResponderTests.cs +++ b/UnitTests/ResponderTests.cs @@ -4,7 +4,7 @@ using System.Linq; using Terminal.Gui; using Xunit; -// Alais Console to MockConsole so we don't accidentally use Console +// Alias Console to MockConsole so we don't accidentally use Console using Console = Terminal.Gui.FakeConsole; namespace Terminal.Gui.Core { @@ -17,6 +17,8 @@ namespace Terminal.Gui.Core { Assert.Equal ("Terminal.Gui.Responder", r.ToString ()); Assert.False (r.CanFocus); Assert.False (r.HasFocus); + Assert.True (r.Enabled); + Assert.True (r.Visible); } [Fact] diff --git a/UnitTests/StatusBarTests.cs b/UnitTests/StatusBarTests.cs new file mode 100644 index 000000000..c9168e23a --- /dev/null +++ b/UnitTests/StatusBarTests.cs @@ -0,0 +1,160 @@ +using System; +using Xunit; +using Xunit.Abstractions; + +namespace Terminal.Gui.Views { + public class StatusBarTests { + readonly ITestOutputHelper output; + + public StatusBarTests (ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void StatusItem_Constructor () + { + var si = new StatusItem (Key.CtrlMask | Key.Q, "~^Q~ Quit", null); + Assert.Equal (Key.CtrlMask | Key.Q, si.Shortcut); + Assert.Equal ("~^Q~ Quit", si.Title); + Assert.Null (si.Action); + si = new StatusItem (Key.CtrlMask | Key.Q, "~^Q~ Quit", () => { }); + Assert.NotNull (si.Action); + } + + [Fact] + public void StatusBar_Contructor_Default () + { + var sb = new StatusBar (); + + Assert.Empty (sb.Items); + Assert.False (sb.CanFocus); + Assert.Equal (Colors.Menu, sb.ColorScheme); + Assert.Equal (0, sb.X); + Assert.Equal (Dim.Fill (), sb.Width); + Assert.Equal (1, sb.Height); + + Assert.Equal (0, sb.Y); + + var driver = new FakeDriver (); + Application.Init (driver, new FakeMainLoop (() => FakeConsole.ReadKey (true))); + + sb = new StatusBar (); + + driver.SetCursorVisibility (CursorVisibility.Default); + driver.GetCursorVisibility (out CursorVisibility cv); + Assert.Equal (CursorVisibility.Default, cv); + Assert.True (FakeConsole.CursorVisible); + + Application.Iteration += () => { + Assert.Equal (24, sb.Y); + + driver.SetWindowSize (driver.Cols, 15); + + Assert.Equal (14, sb.Y); + + sb.OnEnter (null); + driver.GetCursorVisibility (out cv); + Assert.Equal (CursorVisibility.Invisible, cv); + Assert.False (FakeConsole.CursorVisible); + + Application.RequestStop (); + }; + + Application.Top.Add (sb); + + Application.Run (); + + Application.Shutdown (); + } + + [Fact] + [AutoInitShutdown] + public void Run_Action_With_Key_And_Mouse () + { + var msg = ""; + var sb = new StatusBar (new StatusItem [] { new StatusItem (Key.CtrlMask | Key.Q, "~^Q~ Quit", () => msg = "Quiting...") }); + Application.Top.Add (sb); + + var iteration = 0; + + Application.Iteration += () => { + if (iteration == 0) { + Assert.Equal ("", msg); + sb.ProcessHotKey (new KeyEvent (Key.CtrlMask | Key.Q, null)); + } else if (iteration == 1) { + Assert.Equal ("Quiting...", msg); + msg = ""; + sb.MouseEvent (new MouseEvent () { X = 1, Y = 24, Flags = MouseFlags.Button1Clicked }); + } else { + Assert.Equal ("Quiting...", msg); + + Application.RequestStop (); + } + iteration++; + }; + + Application.Run (); + } + + [Fact] + [AutoInitShutdown] + public void Redraw_Output () + { + var sb = new StatusBar (new StatusItem [] { + new StatusItem (Key.CtrlMask | Key.Q, "~^O~ Open", null), + new StatusItem (Key.CtrlMask | Key.Q, "~^Q~ Quit", null) + }); + Application.Top.Add (sb); + + sb.Redraw (sb.Bounds); + + string expected = @$" +^O Open {Application.Driver.VLine} ^Q Quit +"; + + GraphViewTests.AssertDriverContentsAre (expected, output); + + sb = new StatusBar (new StatusItem [] { + new StatusItem (Key.CtrlMask | Key.Q, "~CTRL-O~ Open", null), + new StatusItem (Key.CtrlMask | Key.Q, "~CTRL-Q~ Quit", null) + }); + sb.Redraw (sb.Bounds); + + expected = @$" +CTRL-O Open {Application.Driver.VLine} CTRL-Q Quit +"; + + GraphViewTests.AssertDriverContentsAre (expected, output); + } + + [Fact] + public void AddItemAt_RemoveItem_Replacing () + { + var sb = new StatusBar (new StatusItem [] { + new StatusItem (Key.CtrlMask | Key.Q, "~^O~ Open", null), + new StatusItem (Key.CtrlMask | Key.Q, "~^S~ Save", null), + new StatusItem (Key.CtrlMask | Key.Q, "~^Q~ Quit", null) + }); + + sb.AddItemAt (2, new StatusItem (Key.CtrlMask | Key.Q, "~^C~ Close", null)); + + Assert.Equal ("~^O~ Open", sb.Items [0].Title); + Assert.Equal ("~^S~ Save", sb.Items [1].Title); + Assert.Equal ("~^C~ Close", sb.Items [2].Title); + Assert.Equal ("~^Q~ Quit", sb.Items [^1].Title); + + Assert.Equal ("~^S~ Save", sb.RemoveItem (1).Title); + + Assert.Equal ("~^O~ Open", sb.Items [0].Title); + Assert.Equal ("~^C~ Close", sb.Items [1].Title); + Assert.Equal ("~^Q~ Quit", sb.Items [^1].Title); + + sb.Items [1] = new StatusItem (Key.CtrlMask | Key.A, "~^A~ Save As", null); + + Assert.Equal ("~^O~ Open", sb.Items [0].Title); + Assert.Equal ("~^A~ Save As", sb.Items [1].Title); + Assert.Equal ("~^Q~ Quit", sb.Items [^1].Title); + } + } +} diff --git a/UnitTests/TextViewTests.cs b/UnitTests/TextViewTests.cs index 359338791..ab06df48a 100644 --- a/UnitTests/TextViewTests.cs +++ b/UnitTests/TextViewTests.cs @@ -1801,10 +1801,7 @@ namespace Terminal.Gui.Views { if (r == '\t') { sumLength += tabWidth + 1; } - if (sumLength > width) { - if (cCol == line.Length) { - col++; - } + if (sumLength >= width) { break; } else if (cCol < line.Length && col > 0 && start < cCol && col == start) { break; @@ -1968,6 +1965,7 @@ line. int col = 0; Assert.True (TextModel.SetCol (ref col, 80, 79)); Assert.False (TextModel.SetCol (ref col, 80, 80)); + Assert.Equal (79, col); var start = 0; var x = 8; @@ -1981,9 +1979,9 @@ line. Assert.Equal ((15, 15), TextModel.DisplaySize (txtRunes)); Assert.Equal ((6, 6), TextModel.DisplaySize (txtRunes, 1, 7)); - Assert.Equal (0, TextModel.CalculateLeftColumn (txtRunes, 0, 7, 8)); - Assert.Equal (1, TextModel.CalculateLeftColumn (txtRunes, 0, 8, 8)); - Assert.Equal (2, TextModel.CalculateLeftColumn (txtRunes, 0, 9, 8)); + Assert.Equal (1, TextModel.CalculateLeftColumn (txtRunes, 0, 7, 8)); + Assert.Equal (2, TextModel.CalculateLeftColumn (txtRunes, 0, 8, 8)); + Assert.Equal (3, TextModel.CalculateLeftColumn (txtRunes, 0, 9, 8)); var tm = new TextModel (); tm.AddLine (0, TextModel.ToRunes ("This is first line.")); @@ -2020,5 +2018,65 @@ line. Assert.Equal (TextModel.ToRunes ("This really first line."), tm.GetLine (0)); Assert.Equal (TextModel.ToRunes ("This really last line."), tm.GetLine (1)); } + + [Fact] + [InitShutdown] + public void BottomOffset_Sets_To_Zero_Adjust_TopRow () + { + string text = ""; + + for (int i = 0; i < 12; i++) { + text += $"This is the line {i}\n"; + } + var tv = new TextView () { Width = 10, Height = 10, BottomOffset = 1 }; + tv.Text = text; + + tv.ProcessKey (new KeyEvent (Key.CtrlMask | Key.End, null)); + + Assert.Equal (4, tv.TopRow); + Assert.Equal (1, tv.BottomOffset); + + tv.BottomOffset = 0; + Assert.Equal (3, tv.TopRow); + Assert.Equal (0, tv.BottomOffset); + + tv.BottomOffset = 2; + Assert.Equal (5, tv.TopRow); + Assert.Equal (2, tv.BottomOffset); + + tv.BottomOffset = 0; + Assert.Equal (3, tv.TopRow); + Assert.Equal (0, tv.BottomOffset); + } + + [Fact] + [InitShutdown] + public void RightOffset_Sets_To_Zero_Adjust_leftColumn () + { + string text = ""; + + for (int i = 0; i < 12; i++) { + text += $"{i.ToString () [^1]}"; + } + var tv = new TextView () { Width = 10, Height = 10, RightOffset = 1 }; + tv.Text = text; + + tv.ProcessKey (new KeyEvent (Key.End, null)); + + Assert.Equal (4, tv.LeftColumn); + Assert.Equal (1, tv.RightOffset); + + tv.RightOffset = 0; + Assert.Equal (3, tv.LeftColumn); + Assert.Equal (0, tv.RightOffset); + + tv.RightOffset = 2; + Assert.Equal (5, tv.LeftColumn); + Assert.Equal (2, tv.RightOffset); + + tv.RightOffset = 0; + Assert.Equal (3, tv.LeftColumn); + Assert.Equal (0, tv.RightOffset); + } } } diff --git a/UnitTests/ToplevelTests.cs b/UnitTests/ToplevelTests.cs new file mode 100644 index 000000000..d8eaf0ba8 --- /dev/null +++ b/UnitTests/ToplevelTests.cs @@ -0,0 +1,315 @@ +using System; +using Xunit; + +namespace Terminal.Gui.Core { + public class ToplevelTests { + [Fact] + [AutoInitShutdown] + public void Constructor_Default () + { + var top = new Toplevel (); + + Assert.Equal (Colors.TopLevel, top.ColorScheme); + Assert.Equal ("Dim.Fill(margin=0)", top.Width.ToString ()); + Assert.Equal ("Dim.Fill(margin=0)", top.Height.ToString ()); + Assert.False (top.Running); + Assert.False (top.Modal); + Assert.Null (top.MenuBar); + Assert.Null (top.StatusBar); + Assert.False (top.IsMdiContainer); + Assert.False (top.IsMdiChild); + } + + [Fact] + [AutoInitShutdown] + public void Create_Toplevel () + { + var top = Toplevel.Create (); + Assert.Equal (new Rect (0, 0, Application.Driver.Cols, Application.Driver.Rows), top.Bounds); + } + + + [Fact] + [AutoInitShutdown] + public void Application_Top_EnsureVisibleBounds_To_Driver_Rows_And_Cols () + { + var iterations = 0; + + Application.Iteration += () => { + if (iterations == 0) { + Assert.Equal ("Top1", Application.Top.Text); + Assert.Equal (0, Application.Top.Frame.X); + Assert.Equal (0, Application.Top.Frame.Y); + Assert.Equal (Application.Driver.Cols, Application.Top.Frame.Width); + Assert.Equal (Application.Driver.Rows, Application.Top.Frame.Height); + + Application.Top.ProcessHotKey (new KeyEvent (Key.CtrlMask | Key.R, new KeyModifiers ())); + } else if (iterations == 1) { + Assert.Equal ("Top2", Application.Top.Text); + Assert.Equal (0, Application.Top.Frame.X); + Assert.Equal (0, Application.Top.Frame.Y); + Assert.Equal (Application.Driver.Cols, Application.Top.Frame.Width); + Assert.Equal (Application.Driver.Rows, Application.Top.Frame.Height); + + Application.Top.ProcessHotKey (new KeyEvent (Key.CtrlMask | Key.C, new KeyModifiers ())); + } else if (iterations == 3) { + Assert.Equal ("Top1", Application.Top.Text); + Assert.Equal (0, Application.Top.Frame.X); + Assert.Equal (0, Application.Top.Frame.Y); + Assert.Equal (Application.Driver.Cols, Application.Top.Frame.Width); + Assert.Equal (Application.Driver.Rows, Application.Top.Frame.Height); + + Application.Top.ProcessHotKey (new KeyEvent (Key.CtrlMask | Key.R, new KeyModifiers ())); + } else if (iterations == 4) { + Assert.Equal ("Top2", Application.Top.Text); + Assert.Equal (0, Application.Top.Frame.X); + Assert.Equal (0, Application.Top.Frame.Y); + Assert.Equal (Application.Driver.Cols, Application.Top.Frame.Width); + Assert.Equal (Application.Driver.Rows, Application.Top.Frame.Height); + + Application.Top.ProcessHotKey (new KeyEvent (Key.CtrlMask | Key.C, new KeyModifiers ())); + } else if (iterations == 6) { + Assert.Equal ("Top1", Application.Top.Text); + Assert.Equal (0, Application.Top.Frame.X); + Assert.Equal (0, Application.Top.Frame.Y); + Assert.Equal (Application.Driver.Cols, Application.Top.Frame.Width); + Assert.Equal (Application.Driver.Rows, Application.Top.Frame.Height); + + Application.Top.ProcessHotKey (new KeyEvent (Key.CtrlMask | Key.Q, new KeyModifiers ())); + } + iterations++; + }; + + Application.Run (Top1 ()); + + Toplevel Top1 () + { + var top = Application.Top; + top.Text = "Top1"; + var menu = new MenuBar (new MenuBarItem [] { + new MenuBarItem ("_Options", new MenuItem [] { + new MenuItem ("_Run Top2", "", () => Application.Run (Top2 ()), null, null, Key.CtrlMask | Key.R), + new MenuItem ("_Quit", "", () => Application.RequestStop(), null, null, Key.CtrlMask | Key.Q) + }) + }); + top.Add (menu); + + var statusBar = new StatusBar (new [] { + new StatusItem(Key.CtrlMask | Key.R, "~^R~ Run Top2", () => Application.Run (Top2 ())), + new StatusItem(Key.CtrlMask | Key.Q, "~^Q~ Quit", () => Application.RequestStop()) + }); + top.Add (statusBar); + + var t1 = new Toplevel (); + top.Add (t1); + + return top; + } + + Toplevel Top2 () + { + var top = new Toplevel (Application.Top.Frame); + top.Text = "Top2"; + var win = new Window () { Width = Dim.Fill (), Height = Dim.Fill () }; + var menu = new MenuBar (new MenuBarItem [] { + new MenuBarItem ("_Stage", new MenuItem [] { + new MenuItem ("_Close", "", () => Application.RequestStop(), null, null, Key.CtrlMask | Key.C) + }) + }); + top.Add (menu); + + var statusBar = new StatusBar (new [] { + new StatusItem(Key.CtrlMask | Key.C, "~^C~ Close", () => Application.RequestStop()), + }); + top.Add (statusBar); + + win.Add (new ListView () { + X = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill () + }); + top.Add (win); + + return top; + } + } + + [Fact] + [AutoInitShutdown] + public void Internal_Tests () + { + var top = new Toplevel (); + var eventInvoked = ""; + + top.ChildUnloaded += (e) => eventInvoked = "ChildUnloaded"; + top.OnChildUnloaded (top); + Assert.Equal ("ChildUnloaded", eventInvoked); + top.ChildLoaded += (e) => eventInvoked = "ChildLoaded"; + top.OnChildLoaded (top); + Assert.Equal ("ChildLoaded", eventInvoked); + top.Closed += (e) => eventInvoked = "Closed"; + top.OnClosed (top); + Assert.Equal ("Closed", eventInvoked); + top.Closing += (e) => eventInvoked = "Closing"; + top.OnClosing (new ToplevelClosingEventArgs (top)); + Assert.Equal ("Closing", eventInvoked); + top.AllChildClosed += () => eventInvoked = "AllChildClosed"; + top.OnAllChildClosed (); + Assert.Equal ("AllChildClosed", eventInvoked); + top.ChildClosed += (e) => eventInvoked = "ChildClosed"; + top.OnChildClosed (top); + Assert.Equal ("ChildClosed", eventInvoked); + top.Deactivate += (e) => eventInvoked = "Deactivate"; + top.OnDeactivate (top); + Assert.Equal ("Deactivate", eventInvoked); + top.Activate += (e) => eventInvoked = "Activate"; + top.OnActivate (top); + Assert.Equal ("Activate", eventInvoked); + top.Loaded += () => eventInvoked = "Loaded"; + top.OnLoaded (); + Assert.Equal ("Loaded", eventInvoked); + top.Ready += () => eventInvoked = "Ready"; + top.OnReady (); + Assert.Equal ("Ready", eventInvoked); + top.Unloaded += () => eventInvoked = "Unloaded"; + top.OnUnloaded (); + Assert.Equal ("Unloaded", eventInvoked); + + top.AddMenuStatusBar (new MenuBar ()); + Assert.NotNull (top.MenuBar); + top.AddMenuStatusBar (new StatusBar ()); + Assert.NotNull (top.StatusBar); + top.RemoveMenuStatusBar (top.MenuBar); + Assert.Null (top.MenuBar); + top.RemoveMenuStatusBar (top.StatusBar); + Assert.Null (top.StatusBar); + + Application.Begin (top); + Assert.Equal (top, Application.Top); + + // top is Application.Top without menu and status bar. + var supView = top.EnsureVisibleBounds (top, 2, 2, out int nx, out int ny, out View mb, out View sb); + Assert.Equal (Application.Top, supView); + Assert.Equal (0, nx); + Assert.Equal (0, ny); + Assert.Null (mb); + Assert.Null (sb); + + top.AddMenuStatusBar (new MenuBar ()); + Assert.NotNull (top.MenuBar); + + // top is Application.Top with a menu and without status bar. + top.EnsureVisibleBounds (top, 2, 2, out nx, out ny, out mb, out sb); + Assert.Equal (0, nx); + Assert.Equal (1, ny); + Assert.NotNull (mb); + Assert.Null (sb); + + top.AddMenuStatusBar (new StatusBar ()); + Assert.NotNull (top.StatusBar); + + // top is Application.Top with a menu and status bar. + top.EnsureVisibleBounds (top, 2, 2, out nx, out ny, out mb, out sb); + Assert.Equal (0, nx); + Assert.Equal (1, ny); + Assert.NotNull (mb); + Assert.NotNull (sb); + + top.RemoveMenuStatusBar (top.MenuBar); + Assert.Null (top.MenuBar); + + // top is Application.Top without a menu and with a status bar. + top.EnsureVisibleBounds (top, 2, 2, out nx, out ny, out mb, out sb); + Assert.Equal (0, nx); + Assert.Equal (0, ny); + Assert.Null (mb); + Assert.NotNull (sb); + + top.RemoveMenuStatusBar (top.StatusBar); + Assert.Null (top.StatusBar); + Assert.Null (top.MenuBar); + + var win = new Window () { Width = Dim.Fill (), Height = Dim.Fill () }; + top.Add (win); + top.LayoutSubviews (); + + // The SuperView is always the same regardless of the caller. + supView = top.EnsureVisibleBounds (win, 0, 0, out nx, out ny, out mb, out sb); + Assert.Equal (Application.Top, supView); + supView = win.EnsureVisibleBounds (win, 0, 0, out nx, out ny, out mb, out sb); + Assert.Equal (Application.Top, supView); + + // top is Application.Top without menu and status bar. + top.EnsureVisibleBounds (win, 0, 0, out nx, out ny, out mb, out sb); + Assert.Equal (0, nx); + Assert.Equal (0, ny); + Assert.Null (mb); + Assert.Null (sb); + + top.AddMenuStatusBar (new MenuBar ()); + Assert.NotNull (top.MenuBar); + + // top is Application.Top with a menu and without status bar. + top.EnsureVisibleBounds (win, 2, 2, out nx, out ny, out mb, out sb); + Assert.Equal (0, nx); + Assert.Equal (1, ny); + Assert.NotNull (mb); + Assert.Null (sb); + + top.AddMenuStatusBar (new StatusBar ()); + Assert.NotNull (top.StatusBar); + + // top is Application.Top with a menu and status bar. + top.EnsureVisibleBounds (win, 30, 20, out nx, out ny, out mb, out sb); + Assert.Equal (0, nx); + Assert.Equal (1, ny); + Assert.NotNull (mb); + Assert.NotNull (sb); + + top.RemoveMenuStatusBar (top.MenuBar); + top.RemoveMenuStatusBar (top.StatusBar); + Assert.Null (top.StatusBar); + Assert.Null (top.MenuBar); + + top.Remove (win); + + win = new Window () { Width = 60, Height = 15 }; + top.Add (win); + + // top is Application.Top without menu and status bar. + top.EnsureVisibleBounds (win, 0, 0, out nx, out ny, out mb, out sb); + Assert.Equal (0, nx); + Assert.Equal (0, ny); + Assert.Null (mb); + Assert.Null (sb); + + top.AddMenuStatusBar (new MenuBar ()); + Assert.NotNull (top.MenuBar); + + // top is Application.Top with a menu and without status bar. + top.EnsureVisibleBounds (win, 2, 2, out nx, out ny, out mb, out sb); + Assert.Equal (2, nx); + Assert.Equal (2, ny); + Assert.NotNull (mb); + Assert.Null (sb); + + top.AddMenuStatusBar (new StatusBar ()); + Assert.NotNull (top.StatusBar); + + // top is Application.Top with a menu and status bar. + top.EnsureVisibleBounds (win, 30, 20, out nx, out ny, out mb, out sb); + Assert.Equal (20, nx); // 20+60=80 + Assert.Equal (9, ny); // 9+15+1(mb)=25 + Assert.NotNull (mb); + Assert.NotNull (sb); + + top.PositionToplevels (); + Assert.Equal (new Rect (0, 1, 60, 15), win.Frame); + + Assert.Null (Toplevel.dragPosition); + win.MouseEvent (new MouseEvent () { X = 6, Y = 0, Flags = MouseFlags.Button1Pressed }); + Assert.Equal (new Point (6, 0), Toplevel.dragPosition); + } + } +} diff --git a/UnitTests/ViewTests.cs b/UnitTests/ViewTests.cs index 7b73705f8..fb34db691 100644 --- a/UnitTests/ViewTests.cs +++ b/UnitTests/ViewTests.cs @@ -1412,5 +1412,161 @@ namespace Terminal.Gui.Views { view.OnLayoutComplete (null); Assert.False (layoutStarted); } + + [Fact] + [AutoInitShutdown] + public void Enabled_False_Sets_HasFocus_To_False () + { + var wasClicked = false; + var view = new Button ("Click Me"); + view.Clicked += () => wasClicked = !wasClicked; + Application.Top.Add (view); + + view.ProcessKey (new KeyEvent (Key.Enter, null)); + Assert.True (wasClicked); + view.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Clicked }); + Assert.False (wasClicked); + Assert.True (view.Enabled); + Assert.True (view.CanFocus); + Assert.True (view.HasFocus); + + view.Enabled = false; + view.ProcessKey (new KeyEvent (Key.Enter, null)); + Assert.False (wasClicked); + view.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Clicked }); + Assert.False (wasClicked); + Assert.False (view.Enabled); + Assert.True (view.CanFocus); + Assert.False (view.HasFocus); + view.SetFocus (); + Assert.False (view.HasFocus); + } + + [Fact] + [AutoInitShutdown] + public void Enabled_Sets_Also_Sets_Subviews () + { + var wasClicked = false; + var button = new Button ("Click Me"); + button.Clicked += () => wasClicked = !wasClicked; + var win = new Window () { Width = Dim.Fill (), Height = Dim.Fill () }; + win.Add (button); + Application.Top.Add (win); + + var iterations = 0; + + Application.Iteration += () => { + iterations++; + + button.ProcessKey (new KeyEvent (Key.Enter, null)); + Assert.True (wasClicked); + button.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Clicked }); + Assert.False (wasClicked); + Assert.True (button.Enabled); + Assert.True (button.CanFocus); + Assert.True (button.HasFocus); + Assert.True (win.Enabled); + Assert.True (win.CanFocus); + Assert.True (win.HasFocus); + + win.Enabled = false; + button.ProcessKey (new KeyEvent (Key.Enter, null)); + Assert.False (wasClicked); + button.MouseEvent (new MouseEvent () { Flags = MouseFlags.Button1Clicked }); + Assert.False (wasClicked); + Assert.False (button.Enabled); + Assert.True (button.CanFocus); + Assert.False (button.HasFocus); + Assert.False (win.Enabled); + Assert.True (win.CanFocus); + Assert.False (win.HasFocus); + button.SetFocus (); + Assert.False (button.HasFocus); + Assert.False (win.HasFocus); + win.SetFocus (); + Assert.False (button.HasFocus); + Assert.False (win.HasFocus); + + win.Enabled = true; + win.FocusFirst (); + Assert.True (button.HasFocus); + Assert.True (win.HasFocus); + + Application.RequestStop (); + }; + + Application.Run (); + + Assert.Equal (1, iterations); + } + + [Fact] + [AutoInitShutdown] + public void Visible_Sets_Also_Sets_Subviews () + { + var button = new Button ("Click Me"); + var win = new Window () { Width = Dim.Fill (), Height = Dim.Fill () }; + win.Add (button); + var top = Application.Top; + top.Add (win); + + var iterations = 0; + + Application.Iteration += () => { + iterations++; + + Assert.True (button.Visible); + Assert.True (button.CanFocus); + Assert.True (button.HasFocus); + Assert.True (win.Visible); + Assert.True (win.CanFocus); + Assert.True (win.HasFocus); + Assert.True (RunesCount () > 0); + + win.Visible = false; + Assert.True (button.Visible); + Assert.True (button.CanFocus); + Assert.False (button.HasFocus); + Assert.False (win.Visible); + Assert.True (win.CanFocus); + Assert.False (win.HasFocus); + button.SetFocus (); + Assert.False (button.HasFocus); + Assert.False (win.HasFocus); + win.SetFocus (); + Assert.False (button.HasFocus); + Assert.False (win.HasFocus); + top.Redraw (top.Bounds); + Assert.True (RunesCount () == 0); + + win.Visible = true; + win.FocusFirst (); + Assert.True (button.HasFocus); + Assert.True (win.HasFocus); + top.Redraw (top.Bounds); + Assert.True (RunesCount () > 0); + + Application.RequestStop (); + }; + + Application.Run (); + + Assert.Equal (1, iterations); + + int RunesCount () + { + var contents = ((FakeDriver)Application.Driver).Contents; + var runesCount = 0; + + for (int i = 0; i < Application.Driver.Rows; i++) { + for (int j = 0; j < Application.Driver.Cols; j++) { + if (contents [i, j, 0] != ' ') { + runesCount++; + } + } + } + return runesCount; + } + } } }