diff --git a/Terminal.Gui/Core/Range.cs b/Terminal.Gui/Core/Range.cs new file mode 100644 index 000000000..0cf10a1cd --- /dev/null +++ b/Terminal.Gui/Core/Range.cs @@ -0,0 +1,4 @@ +namespace Terminal.Gui { + internal class Range { + } +} \ No newline at end of file diff --git a/Terminal.Gui/Core/TextFormatter.cs b/Terminal.Gui/Core/TextFormatter.cs index 36989bef1..e2a4b7221 100644 --- a/Terminal.Gui/Core/TextFormatter.cs +++ b/Terminal.Gui/Core/TextFormatter.cs @@ -5,6 +5,28 @@ using System.Linq; using NStack; namespace Terminal.Gui { + /// + /// Text alignment enumeration, controls how text is displayed. + /// + public enum TextAlignment { + /// + /// Aligns the text to the left of the frame. + /// + Left, + /// + /// Aligns the text to the right side of the frame. + /// + Right, + /// + /// Centers the text in the frame. + /// + Centered, + /// + /// Shows the text as justified text in the frame. + /// + Justified + } + /// /// Provides text formatting capabilites for console apps. Supports, hotkeys, horizontal alignment, multille lines, and word-based line wrap. /// @@ -177,21 +199,20 @@ namespace Terminal.Gui { return lines; } - var runes = StripCRLF (text).ToRunes (); + var runes = StripCRLF (text).ToRuneList(); - while ((end = start + width) < runes.Length) { + while ((end = start + width) < runes.Count) { while (runes [end] != ' ' && end > start) end -= 1; if (end == start) end = start + width; - - - lines.Add (ustring.Make (runes [start..end]).TrimSpace ()); + lines.Add (ustring.Make (runes.GetRange (start, end - start)).TrimSpace()); start = end; } - if (start < text.RuneCount) - lines.Add (ustring.Make (runes [start..]).TrimSpace ()); + if (start < text.RuneCount) { + lines.Add (ustring.Make (runes.GetRange (start, runes.Count - start)).TrimSpace ()); + } return lines; } @@ -212,10 +233,10 @@ namespace Terminal.Gui { return text; } - var runes = text.ToRunes (); - int slen = runes.Length; + var runes = text.ToRuneList (); + int slen = runes.Count; if (slen > width) { - return ustring.Make (runes [0..width]); // text [0, width]; + return ustring.Make (runes.GetRange(0, width)); } else { if (talign == TextAlignment.Justified) { return Justify (text, width); @@ -302,13 +323,13 @@ namespace Terminal.Gui { return lineResult; } - var runes = text.ToRunes (); - int runeCount = runes.Length; + var runes = text.ToRuneList (); + int runeCount = runes.Count; int lp = 0; for (int i = 0; i < runeCount; i++) { Rune c = text [i]; if (c == '\n') { - var wrappedLines = WordWrap (ustring.Make (runes [lp..i]), width); + var wrappedLines = WordWrap (ustring.Make (runes.GetRange(lp, i - lp)), width); foreach (var line in wrappedLines) { lineResult.Add (ClipAndJustify (line, width, talign)); } @@ -318,7 +339,7 @@ namespace Terminal.Gui { lp = i + 1; } } - foreach (var line in WordWrap (ustring.Make (runes [lp..runeCount]), width)) { + foreach (var line in WordWrap (ustring.Make (runes.GetRange(lp, runeCount - lp)), width)) { lineResult.Add (ClipAndJustify (line, width, talign)); } @@ -516,7 +537,7 @@ namespace Terminal.Gui { // Use "Lines" to ensure a Format (don't use "lines")) for (int line = 0; line < Lines.Count; line++) { - if (line < (bounds.Height - bounds.Top) || line >= bounds.Height) + if (line > bounds.Height) continue; var runes = lines [line].ToRunes (); int x; @@ -537,7 +558,7 @@ namespace Terminal.Gui { throw new ArgumentOutOfRangeException (); } for (var col = bounds.Left; col < bounds.Left + bounds.Width; col++) { - Application.Driver.Move (col, bounds.Y + line); + Application.Driver.Move (col, bounds.Top + line); var rune = (Rune)' '; if (col >= x && col < (x + runes.Length)) { rune = runes [col - x]; @@ -552,5 +573,6 @@ namespace Terminal.Gui { } } } + } } diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index 3d5424626..36b02cd06 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -18,28 +18,6 @@ using System.Linq; using NStack; namespace Terminal.Gui { - /// - /// Text alignment enumeration, controls how text is displayed. - /// - public enum TextAlignment { - /// - /// Aligns the text to the left of the frame. - /// - Left, - /// - /// Aligns the text to the right side of the frame. - /// - Right, - /// - /// Centers the text in the frame. - /// - Centered, - /// - /// Shows the text as justified text in the frame. - /// - Justified - } - /// /// Determines the LayoutStyle for a view, if Absolute, during LayoutSubviews, the /// value from the Frame will be used, if the value is Computed, then the Frame @@ -172,12 +150,12 @@ namespace Terminal.Gui { public Action MouseClick; /// - /// 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. + /// 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. /// public Key HotKey { get => viewText.HotKey; set => viewText.HotKey = value; } /// - /// + /// Gets or sets the specifier character for the hotkey (e.g. '_'). Set to '\xffff' to disable hotkey support for this View instance. The default is '\xffff'. /// public Rune HotKeySpecifier { get => viewText.HotKeySpecifier; set => viewText.HotKeySpecifier = value; } @@ -860,21 +838,24 @@ namespace Terminal.Gui { /// /// Utility function to draw strings that contain a hotkey. /// - /// String to display, the underscoore before a letter flags the next letter as the hotkey. + /// String to display, the hotkey specifier before a letter flags the next letter as the hotkey. /// Hot color. /// Normal color. /// - /// The hotkey is any character following an underscore ('_') character. + /// The hotkey is any character following the hotkey specifier, which is the underscore ('_') character by default. + /// The hotkey specifier can be changed via + /// public void DrawHotString (ustring text, Attribute hotColor, Attribute normalColor) { - Driver.SetAttribute (normalColor); + var hotkeySpec = HotKeySpecifier == (Rune)0xffff ? (Rune)'_' : HotKeySpecifier; + Application.Driver.SetAttribute (normalColor); foreach (var rune in text) { - if (rune == '_') { - Driver.SetAttribute (hotColor); + if (rune == hotkeySpec) { + Application.Driver.SetAttribute (hotColor); continue; } - Driver.AddRune (rune); - Driver.SetAttribute (normalColor); + Application.Driver.AddRune (rune); + Application.Driver.SetAttribute (normalColor); } } @@ -1078,9 +1059,8 @@ namespace Terminal.Gui { if (!ustring.IsNullOrEmpty (Text)) { Clear (); // Draw any Text - // TODO: Figure out if this should go here or after OnDrawContent viewText?.SetNeedsFormat (); - viewText?.Draw (ViewToScreen (Bounds), ColorScheme.Normal, ColorScheme.HotNormal); + viewText?.Draw (ViewToScreen (Bounds), HasFocus ? ColorScheme.Focus : ColorScheme.Normal, HasFocus ? ColorScheme.HotFocus : ColorScheme.HotNormal); } // Invoke DrawContentEvent @@ -1594,7 +1574,19 @@ namespace Terminal.Gui { /// The text displayed by the . /// /// - /// The text will only be displayed if the View has no subviews. + /// + /// If provided, the text will be drawn before any subviews are drawn. + /// + /// + /// The text will be drawn starting at the view origin (0, 0) and will be formatted according + /// to the property. If the view's height is greater than 1, the + /// text will word-wrap to additional lines if it does not fit horizontally. If the view's height + /// is 1, the text will be clipped. + /// + /// + /// Set the to enable hotkey support. To disable hotkey support set to + /// (Rune)0xffff. + /// /// public virtual ustring Text { get => viewText.Text; @@ -1605,7 +1597,7 @@ namespace Terminal.Gui { } /// - /// Controls the text-alignment property of the View. Changing this property will redisplay the . + /// Gets or sets how the View's is aligned horizontally when drawn. Changing this property will redisplay the . /// /// The text alignment. public virtual TextAlignment TextAlignment { diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj index 144731b3d..907228f62 100644 --- a/Terminal.Gui/Terminal.Gui.csproj +++ b/Terminal.Gui/Terminal.Gui.csproj @@ -1,12 +1,11 @@ - netstandard2.1 + net472;netstandard2.0 Terminal.Gui Terminal.Gui bin\Release\Terminal.Gui.xml true 0.90.0.0 - 8.0 true @@ -75,7 +74,7 @@ * Added a OpenSelectedItem event to the ListView #429. (Thanks @bdisp!) * Fixes the return value of the position cursor in the TextField. (Thanks @bdisp!) * Updates screen on Unix window resizing. (Thanks @bdisp!) - * Fixes the functions of the Edit->Copy-Cut-Paste menu for the TextField that was not working well. (Thanks @bdisp!) + * Fixes the functions of the Edit-Copy-Cut-Paste menu for the TextField that was not working well. (Thanks @bdisp!) * More robust error handing in Pos/Dim. Fixes #355 stack overflow with Pos based on the size of windows at startup. Added a OnResized action to set the Pos after the terminal are resized. (Thanks @bdisp!) * Fixes #389 Window layouting breaks when resizing. (Thanks @bdisp!) * Fixes #557 MessageBox needs to take ustrings (BREAKING CHANGE). (Thanks @tig!) @@ -101,6 +100,15 @@ * ConsoleDriver and Drivers have new standard glyph definitions for things like right arrow. (Thanks @tig!) * ScrollView updated to use pretty glyphs. (Thanks @tig!) * Menubar now uses pretty arrow glyph for sub-menus. (Thanks @tig!) + * The project now has a growing set of unit tests (over 100 tests). (Thanks @tig!) + * View now has a Text property, implemented via the new TextFormatting class. (Thanks @tig!) + * TextAlignment is implemented once across all Views that support it. + * Unicode support is now much more robust and complete; dozens of bugs fixed. + * Any view dervied from View now has a Text property with multi-line text formatting, including word-wrap and hotkey support. + * Any view derived from View now gets mouse click (Clicked event) support for free. + * Label is now just an alias for View. + * Button is now a very thin class derived from View (no API changes). + * Dozens of unit tests for TextAlignment are provided reducing the chance of regressions. 0.81: * Fix ncurses engine for macOS/Linux, it works again diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index 877d1be76..b5fa6fec4 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -15,8 +15,14 @@ namespace Terminal.Gui { /// /// /// Provides a button showing text invokes an when clicked on with a mouse - /// or when the user presses SPACE, ENTER, or hotkey. The hotkey is specified by the first uppercase - /// letter in the button. + /// or when the user presses SPACE, ENTER, or hotkey. The hotkey is the first letter or digit following the first underscore ('_') + /// in the button text. + /// + /// + /// Use to change the hotkey specifier from the default of ('_'). + /// + /// + /// If no hotkey specifier is found, the first uppercase letter encountered will be used as the hotkey. /// /// /// When the button is configured as the default () and the user presses @@ -103,19 +109,8 @@ namespace Terminal.Gui { CanFocus = true; this.IsDefault = is_default; Text = text ?? string.Empty; - //int w = SetWidthHeight (text, is_default); - //Frame = new Rect (Frame.Location, new Size (w, 1)); } - //int SetWidthHeight (ustring text, bool is_default) - //{ - // int w = text.RuneCount;// + 4 + (is_default ? 2 : 0); - // Width = w; - // Height = 1; - // Frame = new Rect (Frame.Location, new Size (w, 1)); - // return w; - //} - /// /// The text displayed by this . /// @@ -153,7 +148,6 @@ namespace Terminal.Gui { Width = w; Height = 1; Frame = new Rect (Frame.Location, new Size (w, 1)); - SetNeedsDisplay (); } @@ -196,6 +190,5 @@ namespace Terminal.Gui { } return base.ProcessKey (kb); } - } } diff --git a/UICatalog/Scenarios/AllViewsTester.cs b/UICatalog/Scenarios/AllViewsTester.cs index ff689f3b2..a30a7eb42 100644 --- a/UICatalog/Scenarios/AllViewsTester.cs +++ b/UICatalog/Scenarios/AllViewsTester.cs @@ -76,9 +76,9 @@ namespace UICatalog { _leftPane = new Window ("Classes") { X = 0, - Y = 0, // for menu + Y = 0, Width = 15, - Height = Dim.Fill (), + Height = Dim.Fill (1), // for status bar CanFocus = false, ColorScheme = Colors.TopLevel, }; @@ -87,7 +87,7 @@ namespace UICatalog { X = 0, Y = 0, Width = Dim.Fill (0), - Height = Dim.Fill (), // for status bar + Height = Dim.Fill (0), AllowsMarking = false, ColorScheme = Colors.TopLevel, }; diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs index 1b1b483fe..975ca9ee4 100644 --- a/UICatalog/Scenarios/Buttons.cs +++ b/UICatalog/Scenarios/Buttons.cs @@ -70,9 +70,6 @@ namespace UICatalog { //prev = colorButton; x += colorButton.Frame.Width + 2; } - // BUGBUG: For some reason these buttons don't move to correct locations initially. - // This was the only way I find to resolves this with the View prev variable. - //Top.Ready += () => Top.Redraw (Top.Bounds); Button button; Win.Add (button = new Button ("A super long _Button that will probably expose a bug in clipping or wrapping of text. Will it?") { @@ -187,23 +184,26 @@ namespace UICatalog { ustring MoveHotkey (ustring txt) { // Remove the '_' - var i = txt.IndexOf ('_'); + var runes = txt.ToRuneList (); + + var i = runes.IndexOf ('_'); ustring start = ""; - if (i > -1) - start = txt [0, i]; - txt = start + txt [i + 1, txt.RuneCount]; + if (i > -1) { + start = ustring.Make (runes.GetRange (0, i)); + } + txt = start + ustring.Make (runes.GetRange (i + 1, runes.Count - (i + 1))); + + runes = txt.ToRuneList (); // Move over one or go to start i++; - if (i >= txt.RuneCount) { + if (i >= runes.Count) { i = 0; } // Slip in the '_' - start = txt [0, i]; - txt = start + ustring.Make ('_') + txt [i, txt.RuneCount]; - - return txt; + start = ustring.Make (runes.GetRange (0, i)); + return start + ustring.Make ('_') + ustring.Make (runes.GetRange (i, runes.Count - i)); } var mhkb = "Click to Change th_is Button's Hotkey"; @@ -218,7 +218,7 @@ namespace UICatalog { }; Win.Add (moveHotKeyBtn); - var muhkb = ustring.Make(" ~  s  gui.cs   master ↑10 = Сохранить"); + var muhkb = ustring.Make (" ~  s  gui.cs   master ↑10 = Сохранить"); var moveUnicodeHotKeyBtn = new Button (muhkb) { X = Pos.Left (absoluteFrame) + 1, Y = Pos.Bottom (radioGroup) + 1, diff --git a/UICatalog/Scenarios/Clipping.cs b/UICatalog/Scenarios/Clipping.cs index 2817ebac7..d2b9e9dd7 100644 --- a/UICatalog/Scenarios/Clipping.cs +++ b/UICatalog/Scenarios/Clipping.cs @@ -34,7 +34,7 @@ namespace UICatalog { //Win.Height = Dim.Fill () - 2; var label = new Label ("ScrollView (new Rect (5, 5, 100, 60)) with a 200, 100 ContentSize...") { X = 0, Y = 0, - ColorScheme = Colors.Dialog + //ColorScheme = Colors.Dialog }; Top.Add (label); diff --git a/UICatalog/Scenarios/LabelsAsButtons.cs b/UICatalog/Scenarios/LabelsAsButtons.cs index 9ab09f861..243735f5b 100644 --- a/UICatalog/Scenarios/LabelsAsButtons.cs +++ b/UICatalog/Scenarios/LabelsAsButtons.cs @@ -6,10 +6,10 @@ using System.Reflection; using Terminal.Gui; namespace UICatalog { - [ScenarioMetadata (Name: "LabelsAsButtons", Description: "POC to see how making Label more a base class would work")] + [ScenarioMetadata (Name: "Labels As Buttons", Description: "Illustrates that Button is really just a Label++")] [ScenarioCategory ("Controls")] [ScenarioCategory ("POC")] - class LabelsAsButtons : Scenario { + class LabelsAsLabels : Scenario { public override void Setup () { // Add a label & text field so we can demo IsDefault @@ -22,107 +22,120 @@ namespace UICatalog { var edit = new TextField (31, 0, 15, ""); Win.Add (edit); - // This is the default button (IsDefault = true); if user presses ENTER in the TextField + // This is the default Label (IsDefault = true); if user presses ENTER in the TextField // the scenario will quit - var defaultButton = new Label ("_Quit") { + var defaultLabel = new Label ("_Quit") { X = Pos.Center (), //TODO: Change to use Pos.AnchorEnd() Y = Pos.Bottom (Win) - 3, //IsDefault = true, Clicked = () => Application.RequestStop (), + HotKeySpecifier = (System.Rune)'_', + CanFocus = true, }; - Win.Add (defaultButton); + Win.Add (defaultLabel); - var swapButton = new Label (50, 0, "Swap Default (Absolute Layout)"); - swapButton.Clicked = () => { - //defaultButton.IsDefault = !defaultButton.IsDefault; - //swapButton.IsDefault = !swapButton.IsDefault; + var swapLabel = new Label (50, 0, "S_wap Default (Absolute Layout)") { + HotKeySpecifier = (System.Rune)'_', + CanFocus = true, }; - Win.Add (swapButton); + swapLabel.Clicked = () => { + //defaultLabel.IsDefault = !defaultLabel.IsDefault; + //swapLabel.IsDefault = !swapLabel.IsDefault; + }; + Win.Add (swapLabel); - static void DoMessage (Label button, ustring txt) + static void DoMessage (Label Label, ustring txt) { - button.Clicked = () => { - var btnText = button.Text.ToString (); + Label.Clicked = () => { + var btnText = Label.Text.ToString (); MessageBox.Query ("Message", $"Did you click {txt}?", "Yes", "No"); }; } - var colorButtonsLabel = new Label ("Color Buttons:") { + var colorLabelsLabel = new Label ("Color Labels:") { X = 0, Y = Pos.Bottom (editLabel) + 1, }; - Win.Add (colorButtonsLabel); - - //View prev = colorButtonsLabel; + Win.Add (colorLabelsLabel); //With this method there is no need to call Top.Ready += () => Top.Redraw (Top.Bounds); - var x = Pos.Right (colorButtonsLabel) + 2; + var x = Pos.Right (colorLabelsLabel) + 2; foreach (var colorScheme in Colors.ColorSchemes) { - var colorButton = new Label ($"{colorScheme.Key}") { + var colorLabel = new Label ($"{colorScheme.Key}") { ColorScheme = colorScheme.Value, - //X = Pos.Right (prev) + 2, X = x, - Y = Pos.Y (colorButtonsLabel), + Y = Pos.Y (colorLabelsLabel), + HotKeySpecifier = (System.Rune)'_', + CanFocus = true, }; - DoMessage (colorButton, colorButton.Text); - Win.Add (colorButton); - //prev = colorButton; - x += colorButton.Frame.Width + 2; + DoMessage (colorLabel, colorLabel.Text); + Win.Add (colorLabel); + x += colorLabel.Text.Length + 2; } - // BUGBUG: For some reason these buttons don't move to correct locations initially. - // This was the only way I find to resolves this with the View prev variable. - //Top.Ready += () => Top.Redraw (Top.Bounds); + Top.Ready += () => Top.Redraw (Top.Bounds); - Label button; - Win.Add (button = new Label ("A super long _Button that will probably expose a bug in clipping or wrapping of text. Will it?") { + Label Label; + Win.Add (Label = new Label ("A super long _Label that will probably expose a bug in clipping or wrapping of text. Will it?") { X = 2, - Y = Pos.Bottom (colorButtonsLabel) + 1, + Y = Pos.Bottom (colorLabelsLabel) + 1, + HotKeySpecifier = (System.Rune)'_', + CanFocus = true, }); - DoMessage (button, button.Text); + DoMessage (Label, Label.Text); // Note the 'N' in 'Newline' will be the hotkey - Win.Add (button = new Label ("a Newline\nin the button") { + Win.Add (Label = new Label ("a Newline\nin the Label") { X = 2, - Y = Pos.Bottom (button) + 1, - Clicked = () => MessageBox.Query ("Message", "Question?", "Yes", "No") + Y = Pos.Bottom (Label) + 1, + Clicked = () => MessageBox.Query ("Message", "Question?", "Yes", "No"), + HotKeySpecifier = (System.Rune)'_', + CanFocus = true, }); var textChanger = new Label ("Te_xt Changer") { X = 2, - Y = Pos.Bottom (button) + 1, + Y = Pos.Bottom (Label) + 1, + HotKeySpecifier = (System.Rune)'_', + CanFocus = true, }; Win.Add (textChanger); textChanger.Clicked = () => textChanger.Text += "!"; - Win.Add (button = new Label ("Lets see if this will move as \"Text Changer\" grows") { + Win.Add (Label = new Label ("Lets see if this will move as \"Text Changer\" grows") { X = Pos.Right (textChanger) + 2, Y = Pos.Y (textChanger), + HotKeySpecifier = (System.Rune)'_', + CanFocus = true, }); - var removeButton = new Label ("Remove this button") { + var removeLabel = new Label ("Remove this Label") { X = 2, - Y = Pos.Bottom (button) + 1, - ColorScheme = Colors.Error + Y = Pos.Bottom (Label) + 1, + ColorScheme = Colors.Error, + HotKeySpecifier = (System.Rune)'_', + CanFocus = true, }; - Win.Add (removeButton); + Win.Add (removeLabel); // This in intresting test case because `moveBtn` and below are laid out relative to this one! - removeButton.Clicked = () => Win.Remove (removeButton); + removeLabel.Clicked = () => Win.Remove (removeLabel); var computedFrame = new FrameView ("Computed Layout") { X = 0, - Y = Pos.Bottom (removeButton) + 1, + Y = Pos.Bottom (removeLabel) + 1, Width = Dim.Percent (50), Height = 5 }; Win.Add (computedFrame); // Demonstrates how changing the View.Frame property can move Views - var moveBtn = new Label ("Move This \u263b Button _via Pos") { + var moveBtn = new Label ("Move This \u263b Label _via Pos") { X = 0, Y = Pos.Center () - 1, Width = 30, ColorScheme = Colors.Error, + HotKeySpecifier = (System.Rune)'_', + CanFocus = true, }; moveBtn.Clicked = () => { moveBtn.X = moveBtn.Frame.X + 5; @@ -132,12 +145,14 @@ namespace UICatalog { computedFrame.Add (moveBtn); // Demonstrates how changing the View.Frame property can SIZE Views (#583) - var sizeBtn = new Label ("Size This \u263a Button _via Pos") { - //var sizeBtn = new Label ("Size This x Button _via Pos") { + var sizeBtn = new Label ("Size This \u263a Label _via Pos") { + //var sizeBtn = new Label ("Size This x Label _via Pos") { X = 0, Y = Pos.Center () + 1, Width = 30, ColorScheme = Colors.Error, + HotKeySpecifier = (System.Rune)'_', + CanFocus = true, }; sizeBtn.Clicked = () => { sizeBtn.Width = sizeBtn.Frame.Width + 5; @@ -147,15 +162,17 @@ namespace UICatalog { var absoluteFrame = new FrameView ("Absolute Layout") { X = Pos.Right (computedFrame), - Y = Pos.Bottom (removeButton) + 1, + Y = Pos.Bottom (removeLabel) + 1, Width = Dim.Fill (), Height = 5 }; Win.Add (absoluteFrame); // Demonstrates how changing the View.Frame property can move Views - var moveBtnA = new Label (0, 0, "Move This Button via Frame") { + var moveBtnA = new Label (0, 0, "Move This Label via Frame") { ColorScheme = Colors.Error, + HotKeySpecifier = (System.Rune)'_', + CanFocus = true, }; moveBtnA.Clicked = () => { moveBtnA.Frame = new Rect (moveBtnA.Frame.X + 5, moveBtnA.Frame.Y, moveBtnA.Frame.Width, moveBtnA.Frame.Height); @@ -165,15 +182,19 @@ namespace UICatalog { // Demonstrates how changing the View.Frame property can SIZE Views (#583) var sizeBtnA = new Label (0, 2, " ~  s  gui.cs   master ↑10 = Со_хранить") { ColorScheme = Colors.Error, + HotKeySpecifier = (System.Rune)'_', + CanFocus = true, }; sizeBtnA.Clicked = () => { sizeBtnA.Frame = new Rect (sizeBtnA.Frame.X, sizeBtnA.Frame.Y, sizeBtnA.Frame.Width + 5, sizeBtnA.Frame.Height); }; absoluteFrame.Add (sizeBtnA); - var label = new Label ("Text Alignment (changes the four buttons above): ") { + var label = new Label ("Text Alignment (changes the four Labels above): ") { X = 2, Y = Pos.Bottom (computedFrame) + 1, + HotKeySpecifier = (System.Rune)'_', + CanFocus = true, }; Win.Add (label); @@ -188,31 +209,36 @@ namespace UICatalog { ustring MoveHotkey (ustring txt) { // Remove the '_' - var i = txt.IndexOf ('_'); + var runes = txt.ToRuneList (); + + var i = runes.IndexOf ('_'); ustring start = ""; - if (i > -1) - start = txt [0, i]; - txt = start + txt [i + 1, txt.RuneCount]; + if (i > -1) { + start = ustring.Make (runes.GetRange (0, i)); + } + txt = start + ustring.Make (runes.GetRange (i + 1, runes.Count - (i + 1))); + + runes = txt.ToRuneList (); // Move over one or go to start i++; - if (i >= txt.RuneCount) { + if (i >= runes.Count) { i = 0; } // Slip in the '_' - start = txt [0, i]; - txt = start + ustring.Make ('_') + txt [i, txt.RuneCount]; - - return txt; + start = ustring.Make (runes.GetRange (0, i)); + return start + ustring.Make ('_') + ustring.Make (runes.GetRange (i, runes.Count - i)); } - var mhkb = "Click to Change th_is Button's Hotkey"; + var mhkb = "Click to Change th_is Label's Hotkey"; var moveHotKeyBtn = new Label (mhkb) { X = 2, Y = Pos.Bottom (radioGroup) + 1, Width = mhkb.Length + 10, ColorScheme = Colors.TopLevel, + HotKeySpecifier = (System.Rune)'_', + CanFocus = true, }; moveHotKeyBtn.Clicked = () => { moveHotKeyBtn.Text = MoveHotkey (moveHotKeyBtn.Text); @@ -225,6 +251,8 @@ namespace UICatalog { Y = Pos.Bottom (radioGroup) + 1, Width = muhkb.Length + 30, ColorScheme = Colors.TopLevel, + HotKeySpecifier = (System.Rune)'_', + CanFocus = true, }; moveUnicodeHotKeyBtn.Clicked = () => { moveUnicodeHotKeyBtn.Text = MoveHotkey (moveUnicodeHotKeyBtn.Text); diff --git a/UICatalog/Scenarios/TextAlignments.cs b/UICatalog/Scenarios/TextAlignments.cs index ca8a4459c..9b0c6406d 100644 --- a/UICatalog/Scenarios/TextAlignments.cs +++ b/UICatalog/Scenarios/TextAlignments.cs @@ -10,7 +10,7 @@ namespace UICatalog { public override void Setup () { Win.X = 10; - Win.Width = Dim.Fill (20); + Win.Width = Dim.Fill (10); string txt = "Hello world, how are you today? Pretty neat!"; string unicodeSampleText = "A Unicode sentence (пÑРвеÑ) has words."; @@ -22,8 +22,8 @@ namespace UICatalog { var multiLineHeight = 5; foreach (var alignment in alignments) { - singleLines[(int)alignment] = new Label (txt) { TextAlignment = alignment, Width = Dim.Fill (), Height = 1, ColorScheme = Colors.Dialog }; - multipleLines [(int)alignment] = new Label (txt) { TextAlignment = alignment, Width = Dim.Fill (), Height = multiLineHeight, ColorScheme = Colors.Dialog }; + singleLines [(int)alignment] = new Label (txt) { TextAlignment = alignment, X = 1, Width = Dim.Fill (1), Height = 1, ColorScheme = Colors.Dialog }; + multipleLines [(int)alignment] = new Label (txt) { TextAlignment = alignment, X = 1, Width = Dim.Fill (1), Height = multiLineHeight, ColorScheme = Colors.Dialog }; } // Add a label & text field so we can demo IsDefault @@ -35,7 +35,7 @@ namespace UICatalog { var edit = new TextView () { X = Pos.Right (editLabel) + 1, Y = Pos.Y (editLabel), - Width = Dim.Fill("Text:".Length + " Unicode Sample".Length + 2), + Width = Dim.Fill ("Text:".Length + " Unicode Sample".Length + 2), Height = 4, ColorScheme = Colors.TopLevel, Text = txt, @@ -57,7 +57,7 @@ namespace UICatalog { }; Win.Add (unicodeSample); - var update = new Button ("_Update", is_default: true) { + var update = new Button ("_Update") { X = Pos.Right (edit) + 1, Y = Pos.Bottom (edit) - 1, Clicked = () => { @@ -69,7 +69,14 @@ namespace UICatalog { }; Win.Add (update); - var label = new Label ($"Demonstrating single-line (should clip):") { Y = Pos.Bottom (edit) + 1 }; + var enableHotKeyCheckBox = new CheckBox ("Enable Hotkey (_)", false) { + X = 0, + Y = Pos.Bottom (edit), + }; + + Win.Add (enableHotKeyCheckBox); + + var label = new Label ($"Demonstrating single-line (should clip):") { Y = Pos.Bottom (enableHotKeyCheckBox) + 1 }; Win.Add (label); foreach (var alignment in alignments) { label = new Label ($"{alignment}:") { Y = Pos.Bottom (label) }; @@ -80,7 +87,7 @@ namespace UICatalog { } txt += "\nSecond line\n\nFourth Line."; - label = new Label ($"Demonstrating multi-line and word wrap:") { Y = Pos.Bottom (label) + 1 }; + label = new Label ($"Demonstrating multi-line and word wrap:") { Y = Pos.Bottom (label) }; Win.Add (label); foreach (var alignment in alignments) { label = new Label ($"{alignment}:") { Y = Pos.Bottom (label) }; @@ -89,6 +96,15 @@ namespace UICatalog { Win.Add (multipleLines [(int)alignment]); label = multipleLines [(int)alignment]; } + + enableHotKeyCheckBox.Toggled += (previous) => { + foreach (var alignment in alignments) { + singleLines [(int)alignment].HotKeySpecifier = previous ? (Rune)0xffff : (Rune)'_'; + multipleLines [(int)alignment].HotKeySpecifier = previous ? (Rune)0xffff : (Rune)'_'; + } + Win.SetNeedsDisplay (); + Win.LayoutSubviews (); + }; } } } \ No newline at end of file diff --git a/UICatalog/Scenarios/TextFormatterDemo.cs b/UICatalog/Scenarios/TextFormatterDemo.cs new file mode 100644 index 000000000..05f11f10e --- /dev/null +++ b/UICatalog/Scenarios/TextFormatterDemo.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Terminal.Gui; + +namespace UICatalog { + [ScenarioMetadata (Name: "TextFormatter Demo", Description: "Demos and tests the TextFormatter class.")] + [ScenarioCategory ("Text")] + [ScenarioCategory ("POC")] + class TextFormatterDemo : Scenario { + public override void Init (Toplevel top, ColorScheme colorScheme) + { + Application.Init (); + + Top = top; + if (Top == null) { + Top = Application.Top; + } + Win = null; + } + + public override void Setup () + { + Top.Text = "Press CTRL-Q to Quit. This is the Text for the TopLevel View. TextAlignment.Centered was specified. It is intentionally very long to illustrate word wrap.\n" + + "<-- There is a new line here to show a hard line break. You should see this text bleed underneath the subviews, which start at Y = 3."; + Top.TextAlignment = TextAlignment.Centered; + Top.ColorScheme = Colors.Base; + + string text = "Hello world, how are you today? Pretty neat!\nSecond line\n\nFourth Line."; + string unicode = "Τὴ γλῶσσα μοῦ ἔδωσαν ἑλληνικὴ\nτὸ σπίτι φτωχικὸ στὶς ἀμμουδιὲς τοῦ Ὁμήρου.\nΜονάχη ἔγνοια ἡ γλῶσσα μου στὶς ἀμμουδιὲς τοῦ Ὁμήρου."; + + var unicodeCheckBox = new CheckBox ("Unicode", Top.HotKeySpecifier == (Rune)' ') { + X = 0, + Y = 3, + }; + + Top.Add (unicodeCheckBox); + + var alignments = Enum.GetValues (typeof (Terminal.Gui.TextAlignment)).Cast ().ToList (); + var singleLines = new Label [alignments.Count]; + var multipleLines = new Label [alignments.Count]; + + var multiLineHeight = 5; + + foreach (var alignment in alignments) { + singleLines [(int)alignment] = new Label (text) { TextAlignment = alignment, X = 0, Width = Dim.Fill (), Height = 1, ColorScheme = Colors.Dialog }; + multipleLines [(int)alignment] = new Label (text) { TextAlignment = alignment, X = 0, Width = Dim.Fill (), Height = multiLineHeight, ColorScheme = Colors.Dialog }; + } + + var label = new Label ($"Demonstrating single-line (should clip):") { Y = Pos.Bottom (unicodeCheckBox) + 1 }; + Top.Add (label); + foreach (var alignment in alignments) { + label = new Label ($"{alignment}:") { Y = Pos.Bottom (label) }; + Top.Add (label); + singleLines [(int)alignment].Y = Pos.Bottom (label); + Top.Add (singleLines [(int)alignment]); + label = singleLines [(int)alignment]; + } + + label = new Label ($"Demonstrating multi-line and word wrap:") { Y = Pos.Bottom (label) }; + Top.Add (label); + foreach (var alignment in alignments) { + label = new Label ($"{alignment}:") { Y = Pos.Bottom (label) }; + Top.Add (label); + multipleLines [(int)alignment].Y = Pos.Bottom (label); + Top.Add (multipleLines [(int)alignment]); + label = multipleLines [(int)alignment]; + } + + unicodeCheckBox.Toggled += (previous) => { + foreach (var alignment in alignments) { + singleLines [(int)alignment].Text = previous ? text : unicode; + multipleLines [(int)alignment].Text = previous ? text : unicode; + } + }; + } + } +} \ No newline at end of file diff --git a/UICatalog/Scenarios/Unicode.cs b/UICatalog/Scenarios/Unicode.cs index 694a6b4da..0f764f5bb 100644 --- a/UICatalog/Scenarios/Unicode.cs +++ b/UICatalog/Scenarios/Unicode.cs @@ -10,6 +10,9 @@ namespace UICatalog { class UnicodeInMenu : Scenario { public override void Setup () { + //string text = "Hello world, how are you today? Pretty neat!\nSecond line\n\nFourth Line."; + string unicode = "Τὴ γλῶσσα μοῦ ἔδωσαν ἑλληνικὴ\nτὸ σπίτι φτωχικὸ στὶς ἀμμουδιὲς τοῦ Ὁμήρου.\nΜονάχη ἔγνοια ἡ γλῶσσα μου στὶς ἀμμουδιὲς τοῦ Ὁμήρου."; + var menu = new MenuBar (new MenuBarItem [] { new MenuBarItem ("_Файл", new MenuItem [] { new MenuItem ("_Создать", "Creates new file", null), @@ -25,23 +28,32 @@ namespace UICatalog { }); Top.Add (menu); - var label = new Label ("Button:") { X = 0, Y = 1 }; + var label = new Label ("Label:") { X = 0, Y = 1 }; Win.Add (label); - var button2 = new Button ("Со_хранить") { X = 15, Y = Pos.Y (label), Width = Dim.Percent (50), }; - Win.Add (button2); + var testlabel = new Label ("Стоял _он, дум великих полн") { X = 20, Y = Pos.Y (label), Width = Dim.Percent (50), }; + Win.Add (testlabel); + + label = new Label ("Label (CanFocus):") { X = Pos.X (label), Y = Pos.Bottom (label) + 1 }; + Win.Add (label); + testlabel = new Label ("Стоял &он, дум великих полн") { X = 20, Y = Pos.Y (label), Width = Dim.Percent (50), CanFocus = true, HotKeySpecifier = new System.Rune('&') }; + Win.Add (testlabel); + + label = new Label ("Button:") { X = Pos.X (label), Y = Pos.Bottom (label) + 1 }; + Win.Add (label); + var button = new Button ("A123456789♥♦♣♠JQK") { X = 20, Y = Pos.Y (label) }; + Win.Add (button); label = new Label ("CheckBox:") { X = Pos.X (label), Y = Pos.Bottom (label) + 1 }; Win.Add (label); - var checkBox = new CheckBox (" ~  s  gui.cs   master ↑10") { X = 15, Y = Pos.Y (label), Width = Dim.Percent (50) }; + var checkBox = new CheckBox (" ~  s  gui.cs   master ↑10") { X = 20, Y = Pos.Y (label), Width = Dim.Percent (50) }; Win.Add (checkBox); label = new Label ("ComboBox:") { X = Pos.X (label), Y = Pos.Bottom (label) + 1 }; Win.Add (label); var comboBox = new ComboBox () { - X = 15, + X = 20, Y = Pos.Y (label), Width = Dim.Percent (50), - ColorScheme = Colors.Error }; comboBox.SetSource (new List () { "item #1", " ~  s  gui.cs   master ↑10", "Со_хранить" }); @@ -51,7 +63,7 @@ namespace UICatalog { label = new Label ("HexView:") { X = Pos.X (label), Y = Pos.Bottom (label) + 2 }; Win.Add (label); var hexView = new HexView (new System.IO.MemoryStream (Encoding.ASCII.GetBytes (" ~  s  gui.cs   master ↑10 Со_хранить"))) { - X = 15, + X = 20, Y = Pos.Y (label), Width = Dim.Percent (60), Height = 5 @@ -60,56 +72,39 @@ namespace UICatalog { label = new Label ("ListView:") { X = Pos.X (label), Y = Pos.Bottom (hexView) + 1 }; Win.Add (label); - var listView = new ListView (new List () { "item #1", " ~  s  gui.cs   master ↑10", "Со_хранить" }) { - X = 15, + var listView = new ListView (new List () { "item #1", " ~  s  gui.cs   master ↑10", "Со_хранить", unicode }) { + X = 20, Y = Pos.Y (label), Width = Dim.Percent (60), Height = 3, - ColorScheme = Colors.Menu }; Win.Add (listView); label = new Label ("RadioGroup:") { X = Pos.X (label), Y = Pos.Bottom (listView) + 1 }; Win.Add (label); var radioGroup = new RadioGroup (new ustring [] { "item #1", " ~  s  gui.cs   master ↑10", "Со_хранить" }, selected: 0) { - X = 15, + X = 20, Y = Pos.Y (label), Width = Dim.Percent (60), - ColorScheme = Colors.Menu }; Win.Add (radioGroup); label = new Label ("TextField:") { X = Pos.X (label), Y = Pos.Bottom (radioGroup) + 1 }; Win.Add (label); - var textField = new TextField (" ~  s  gui.cs   master ↑10 = Со_хранить") { X = 15, Y = Pos.Y (label), Width = Dim.Percent (60) }; + var textField = new TextField (" ~  s  gui.cs   master ↑10 = Со_хранить") { X = 20, Y = Pos.Y (label), Width = Dim.Percent (60) }; Win.Add (textField); label = new Label ("TextView:") { X = Pos.X (label), Y = Pos.Bottom (textField) + 1 }; Win.Add (label); var textView = new TextView () { - X = 15, + X = 20, Y = Pos.Y (label), Width = Dim.Percent (60), - Height = 3, - ColorScheme = Colors.Menu, - Text = " ~  s  gui.cs   master ↑10\nСо_хранить", + Height = 5, + Text = unicode, }; Win.Add (textView); - //label = new Label ("Charset:") { - // X = Pos.Percent(75) + 1, - // Y = 0, - //}; - //Win.Add (label); - //var charset = new Label ("") { - // X = Pos.Percent(75) + 1, - // Y = Pos.Y (label) + 1, - // Width = Dim.Fill (1), - // Height = Dim.Fill (), - // ColorScheme = Colors.Dialog - //}; - //Win.Add (charset); - // Move Win down to row 1, below menu Win.Y = 1; Top.LayoutSubviews (); diff --git a/UICatalog/Scenarios/ViewWithText.cs b/UICatalog/Scenarios/ViewWithText.cs deleted file mode 100644 index b9f78e904..000000000 --- a/UICatalog/Scenarios/ViewWithText.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Terminal.Gui; - -namespace UICatalog { - [ScenarioMetadata (Name: "View Text", Description: "Demos and tests View's Text capabilities.")] - [ScenarioCategory ("Text")] - [ScenarioCategory ("POC")] - class ViewWithText : Scenario { - public override void Setup () - { - Win.Text = "This is the Te_xt for the host Win object. TextAlignment.Centered was specified. It is intentionally very long to illustrate word wrap.\n" + - "<-- There is a new line here to show a hard line break. You should see this text bleed underneath the subviews, which start at Y = 3."; - Win.TextAlignment = TextAlignment.Centered; -#if true - string txt = "Hello world, how are you today? Pretty neat!"; -#else - string txt = "Hello world, how are you today? Unicode:  ~  gui.cs  . Neat?"; -#endif - var alignments = Enum.GetValues (typeof (Terminal.Gui.TextAlignment)).Cast ().ToList (); - var label = new View ($"Demonstrating single-line (should clip!):") { Y = 3 }; - Win.Add (label); - foreach (var alignment in alignments) { - label = new Label ($"{alignment}:") { Y = Pos.Bottom (label) }; - Win.Add (label); - label = new Label (txt) { - TextAlignment = alignment, - Y = Pos.Bottom (label), - Width = Dim.Fill (), - Height = 1, - ColorScheme = Colors.Dialog, - }; - Win.Add (label); - } - - txt += "\nSecond line\n\nFourth Line."; - label = new View ($"Demonstrating multi-line and word wrap:") { Y = Pos.Bottom (label) + 1 }; - Win.Add (label); - foreach (var alignment in alignments) { - label = new View ($"{alignment}:") { Y = Pos.Bottom (label) }; - Win.Add (label); - label = new View (txt) { TextAlignment = alignment, Width = Dim.Fill (), Height = 6, ColorScheme = Colors.Dialog, Y = Pos.Bottom (label) }; - Win.Add (label); - } - } - } -} \ No newline at end of file diff --git a/UnitTests/TextFormatterTests.cs b/UnitTests/TextFormatterTests.cs index 073d8519a..9bb8b0c57 100644 --- a/UnitTests/TextFormatterTests.cs +++ b/UnitTests/TextFormatterTests.cs @@ -1613,6 +1613,10 @@ namespace Terminal.Gui { Assert.Equal ("se", wrappedLines [1].ToString ()); Assert.Equal ("nte", wrappedLines [2].ToString ()); Assert.Equal ("nce", wrappedLines [3].ToString ()); + Assert.Equal ("ha", wrappedLines [4].ToString ()); + Assert.Equal ("s", wrappedLines [5].ToString ()); + Assert.Equal ("wo", wrappedLines [6].ToString ()); + Assert.Equal ("rds", wrappedLines [7].ToString ()); Assert.Equal (".", wrappedLines [^1].ToString ()); maxWidth = 2;