diff --git a/Terminal.Gui/Core/View.cs b/Terminal.Gui/Core/View.cs index ff11a4fa3..c612d16b8 100644 --- a/Terminal.Gui/Core/View.cs +++ b/Terminal.Gui/Core/View.cs @@ -17,6 +17,27 @@ 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 @@ -299,6 +320,7 @@ namespace Terminal.Gui { set { x = value; SetNeedsLayout (); + SetNeedsDisplay (frame); } } @@ -314,6 +336,7 @@ namespace Terminal.Gui { set { y = value; SetNeedsLayout (); + SetNeedsDisplay (frame); } } @@ -331,6 +354,7 @@ namespace Terminal.Gui { set { width = value; SetNeedsLayout (); + SetNeedsDisplay (frame); } } @@ -344,6 +368,7 @@ namespace Terminal.Gui { set { height = value; SetNeedsLayout (); + SetNeedsDisplay (frame); } } @@ -1450,6 +1475,153 @@ namespace Terminal.Gui { OnLayoutComplete (new LayoutEventArgs () { OldBounds = oldBounds }); } + /// + /// A generic virtual method at the level of View to manipulate any hot-keys. + /// + /// The text to manipulate. + /// The hot-key to look for. + /// The returning hot-key position. + /// The character immediately to the right relative to the hot-key position + /// It aims to facilitate the preparation for procedures. + public virtual ustring GetTextFromHotKey(ustring text, Rune hotKey, out int hotPos, out Rune showHotKey) + { + Rune hot_key = (Rune)0; + int hot_pos = -1; + ustring shown_text = text; + + // Use first hot_key char passed into 'hotKey'. + int i = 0; + foreach (Rune c in shown_text) { + if ((char)c != 0xFFFD) { + if (c == hotKey) { + hot_pos = i; + } else if (hot_pos > -1) { + hot_key = c; + break; + } + } + i++; + } + + if (hot_pos == -1) { + // Use first upper-case char if there are no hot-key in the text. + i = 0; + foreach (Rune c in shown_text) { + if ((char)c != 0xFFFD) { + if (Rune.IsUpper (c)) { + hot_key = c; + hot_pos = i; + break; + } + } + i++; + } + } else { + // Use char after 'hotKey' + ustring start = ""; + i = 0; + foreach (Rune c in shown_text) { + start += ustring.Make (c); + i++; + if (i == hot_pos) + break; + } + var st = shown_text; + shown_text = start; + i = 0; + foreach (Rune c in st) { + i++; + if (i > hot_pos + 1) { + shown_text += ustring.Make (c); + } + } + } + hotPos = hot_pos; + showHotKey = hot_key; + return shown_text; + } + + /// + /// A generic virtual method at the level of View to manipulate any hot-keys with process. + /// + /// The text to manipulate to align. + /// The passed in hot-key position. + /// The returning hot-key position. + /// The to align to. + /// It performs the process to the caller. + public virtual ustring GetTextAlignment (ustring shown_text, int hot_pos, out int c_hot_pos, TextAlignment textAlignment) + { + int start; + var caption = shown_text; + c_hot_pos = hot_pos; + + if (Frame.Width > shown_text.Length + 1) { + switch (textAlignment) { + case TextAlignment.Left: + caption += new string (' ', Frame.Width - caption.RuneCount); + break; + case TextAlignment.Right: + start = Frame.Width - caption.RuneCount; + caption = $"{new string (' ', Frame.Width - caption.RuneCount)}{caption}"; + if (c_hot_pos > -1) { + c_hot_pos += start; + } + break; + case TextAlignment.Centered: + start = Frame.Width / 2 - caption.RuneCount / 2; + caption = $"{new string (' ', start)}{caption}{new string (' ', Frame.Width - caption.RuneCount - start)}"; + if (c_hot_pos > -1) { + c_hot_pos += start; + } + break; + case TextAlignment.Justified: + var words = caption.Split (" "); + var wLen = GetWordsLength (words, c_hot_pos, out int runeCount, out int w_hot_pos); + var space = (Frame.Width - runeCount) / (caption.Length - wLen); + caption = ""; + for (int i = 0; i < words.Length; i++) { + if (i == words.Length - 1) { + caption += new string (' ', Frame.Width - caption.RuneCount - 1); + caption += words [i]; + } else { + caption += words [i]; + } + if (i < words.Length - 1) { + caption += new string (' ', space); + } + } + if (c_hot_pos > -1) { + if (wLen - runeCount == 0) { + c_hot_pos += (wLen - runeCount == 0 ? w_hot_pos * (space) - space - w_hot_pos + 1 : space + wLen - runeCount); + } else { + c_hot_pos += space + wLen - runeCount; + } + } + break; + } + } + + return caption; + } + + int GetWordsLength (ustring [] words, int hotPos, out int runeCount, out int wordHotPos) + { + int length = 0; + int rCount = 0; + int wHotPos = -1; + for (int i = 0; i < words.Length; i++) { + if (wHotPos == -1 && rCount + words [i].RuneCount >= hotPos) + wHotPos = i; + length += words [i].Length; + rCount += words [i].RuneCount; + } + if (wHotPos == -1 && hotPos > -1) + wHotPos = words.Length; + runeCount = rCount; + wordHotPos = wHotPos; + return length; + } + /// /// Pretty prints the View /// @@ -1505,7 +1677,6 @@ namespace Terminal.Gui { return false; } - /// /// Method invoked when a mouse event is generated /// diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index db47043d4..b9e475338 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -137,9 +137,7 @@ namespace Terminal.Gui { } set { - if (text?.Length != value?.Length) { - SetWidthHeight (value, is_default); - } + SetWidthHeight (value, is_default); text = value; Update (); } @@ -152,14 +150,14 @@ namespace Terminal.Gui { get => textAlignment; set { textAlignment = value; - SetNeedsDisplay (); + Update (); } } - Rune _leftBracket = new Rune ('['); - Rune _rightBracket = new Rune (']'); - Rune _leftDefault = new Rune ('<'); - Rune _rightDefault = new Rune ('>'); + Rune _leftBracket = new Rune (Driver.LeftBracket); + Rune _rightBracket = new Rune (Driver.RightBracket); + Rune _leftDefault = new Rune (Driver.LeftDefaultIndicator); + Rune _rightDefault = new Rune (Driver.RightDefaultIndicator); internal void Update () { @@ -168,26 +166,7 @@ namespace Terminal.Gui { else shown_text = ustring.Make (_leftBracket) + " " + text + " " + ustring.Make (_rightBracket); - hot_key = (Rune)0; - hot_pos = shown_text.IndexOf ('_'); - - if (hot_pos == -1) { - // Use first upper-case char - int i = 0; - foreach (Rune c in shown_text) { - if (Rune.IsUpper (c)) { - hot_key = c; - hot_pos = i; - break; - } - i++; - } - } else { - // Use char after '_' - var start = shown_text [0, hot_pos]; - shown_text = start + shown_text [hot_pos + 1, shown_text.Length]; - hot_key = Char.ToUpper((char)shown_text [hot_pos]); - } + shown_text = GetTextFromHotKey (shown_text, '_', out hot_pos, out hot_key); SetNeedsDisplay (); } @@ -200,51 +179,8 @@ namespace Terminal.Gui { Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal); Move (0, 0); - var caption = shown_text; - c_hot_pos = hot_pos; - int start; - - if (Frame.Width > shown_text.Length + 1) { - switch (TextAlignment) { - case TextAlignment.Left: - caption += new string (' ', Frame.Width - caption.Length); - break; - case TextAlignment.Right: - start = Frame.Width - caption.Length; - caption = $"{new string (' ', Frame.Width - caption.Length)}{caption}"; - if (c_hot_pos > -1) { - c_hot_pos += start; - } - break; - case TextAlignment.Centered: - start = Frame.Width / 2 - caption.Length / 2; - caption = $"{new string (' ', start)}{caption}{new string (' ', Frame.Width - caption.Length - start)}"; - if (c_hot_pos > -1) { - c_hot_pos += start; - } - break; - case TextAlignment.Justified: - var words = caption.ToString ().Split (new string [] { " " }, StringSplitOptions.RemoveEmptyEntries); - var wLen = GetWordsLength (words); - var space = (Frame.Width - wLen) / (caption.Length - wLen); - caption = ""; - for (int i = 0; i < words.Length; i++) { - if (i == words.Length - 1) { - caption += new string (' ', Frame.Width - caption.Length - 1); - caption += words [i]; - } else { - caption += words [i]; - } - if (i < words.Length - 1) { - caption += new string (' ', space); - } - } - if (c_hot_pos > -1) { - c_hot_pos += space - 1; - } - break; - } - } + var caption = GetTextAlignment (shown_text, hot_pos, out int s_hot_pos, TextAlignment); + c_hot_pos = s_hot_pos; Driver.AddStr (caption); @@ -255,17 +191,6 @@ namespace Terminal.Gui { } } - int GetWordsLength (string[] words) - { - int length = 0; - - for (int i = 0; i < words.Length; i++) { - length += words [i].Length; - } - - return length; - } - /// public override void PositionCursor () { @@ -274,7 +199,7 @@ namespace Terminal.Gui { bool CheckKey (KeyEvent key) { - if (Char.ToUpper ((char)key.KeyValue) == hot_key) { + if ((char)key.KeyValue == hot_key) { this.SuperView.SetFocus (this); Clicked?.Invoke (); return true; diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs index 88d81bf6d..c387441b3 100644 --- a/Terminal.Gui/Views/Label.cs +++ b/Terminal.Gui/Views/Label.cs @@ -11,28 +11,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 - } - /// /// The Label displays a string at a given position and supports multiple lines separted by newline characters. /// diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs index be51b1d79..af56b2982 100644 --- a/UICatalog/Scenarios/Buttons.cs +++ b/UICatalog/Scenarios/Buttons.cs @@ -89,7 +89,7 @@ namespace UICatalog { textChanger.Clicked = () => textChanger.Text += "!"; Win.Add (button = new Button ("Lets see if this will move as \"Text Changer\" grows") { - X = Pos.Right(textChanger) + 2, + X = Pos.Right (textChanger) + 2, Y = Pos.Y (textChanger), }); @@ -105,7 +105,7 @@ namespace UICatalog { var computedFrame = new FrameView ("Computed Layout") { X = 0, Y = Pos.Bottom (removeButton) + 1, - Width = Dim.Percent(50), + Width = Dim.Percent (50), Height = 5 }; Win.Add (computedFrame); @@ -113,7 +113,7 @@ namespace UICatalog { // Demonstrates how changing the View.Frame property can move Views var moveBtn = new Button ("Move This \u263b Button _via Pos") { X = 0, - Y = Pos.Center() - 1, + Y = Pos.Center () - 1, Width = 30, ColorScheme = Colors.Error, }; @@ -132,14 +132,14 @@ namespace UICatalog { }; sizeBtn.Clicked = () => { sizeBtn.Width = sizeBtn.Frame.Width + 5; - computedFrame.LayoutSubviews (); // BUGBUG: This call should not be needed. View.X is not causing relayout correctly + //computedFrame.LayoutSubviews (); // FIXED: This call should not be needed. View.X is not causing relayout correctly }; computedFrame.Add (sizeBtn); var absoluteFrame = new FrameView ("Absolute Layout") { - X = Pos.Right(computedFrame), + X = Pos.Right (computedFrame), Y = Pos.Bottom (removeButton) + 1, - Width = Dim.Fill(), + Width = Dim.Fill (), Height = 5 }; Win.Add (absoluteFrame); @@ -154,7 +154,7 @@ namespace UICatalog { absoluteFrame.Add (moveBtnA); // Demonstrates how changing the View.Frame property can SIZE Views (#583) - var sizeBtnA = new Button (0, 2, "Size This Button via Frame") { + var sizeBtnA = new Button (0, 2, " ~  s  gui.cs   master ↑10 = Со_хранить") { ColorScheme = Colors.Error, }; sizeBtnA.Clicked = () => { @@ -172,34 +172,6 @@ namespace UICatalog { X = 4, Y = Pos.Bottom (label) + 1, SelectedItem = 2, - SelectedItemChanged = (args) => { - switch (args.SelectedItem) { - case 0: - moveBtn.TextAlignment = TextAlignment.Left; - sizeBtn.TextAlignment = TextAlignment.Left; - moveBtnA.TextAlignment = TextAlignment.Left; - sizeBtnA.TextAlignment = TextAlignment.Left; - break; - case 1: - moveBtn.TextAlignment = TextAlignment.Right; - sizeBtn.TextAlignment = TextAlignment.Right; - moveBtnA.TextAlignment = TextAlignment.Right; - sizeBtnA.TextAlignment = TextAlignment.Right; - break; - case 2: - moveBtn.TextAlignment = TextAlignment.Centered; - sizeBtn.TextAlignment = TextAlignment.Centered; - moveBtnA.TextAlignment = TextAlignment.Centered; - sizeBtnA.TextAlignment = TextAlignment.Centered; - break; - case 3: - moveBtn.TextAlignment = TextAlignment.Justified; - sizeBtn.TextAlignment = TextAlignment.Justified; - moveBtnA.TextAlignment = TextAlignment.Justified; - sizeBtnA.TextAlignment = TextAlignment.Justified; - break; - } - } }; Win.Add (radioGroup); @@ -208,7 +180,9 @@ namespace UICatalog { { // Remove the '_' var i = txt.IndexOf ('_'); - var start = txt [0, i]; + ustring start = ""; + if (i > -1) + start = txt [0, i]; txt = start + txt [i + 1, txt.Length]; // Move over one or go to start @@ -224,15 +198,66 @@ namespace UICatalog { return txt; } - var moveHotKeyBtn = new Button ("Click to Change th_is Button's Hotkey") { + var mhkb = "Click to Change th_is Button's Hotkey"; + var moveHotKeyBtn = new Button (mhkb) { X = 2, Y = Pos.Bottom (radioGroup) + 1, + Width = mhkb.Length + 10, ColorScheme = Colors.TopLevel, }; moveHotKeyBtn.Clicked = () => { moveHotKeyBtn.Text = MoveHotkey (moveHotKeyBtn.Text); }; Win.Add (moveHotKeyBtn); + + var muhkb = " ~  s  gui.cs   master ↑10 = Сохранить"; + var moveUnicodeHotKeyBtn = new Button (muhkb) { + X = Pos.Right (moveHotKeyBtn) + 6, + Y = Pos.Bottom (radioGroup) + 1, + Width = muhkb.Length + 30, + ColorScheme = Colors.TopLevel, + }; + moveUnicodeHotKeyBtn.Clicked = () => { + moveUnicodeHotKeyBtn.Text = MoveHotkey (moveUnicodeHotKeyBtn.Text); + }; + Win.Add (moveUnicodeHotKeyBtn); + + radioGroup.SelectedItemChanged += (args) => { + switch (args.SelectedItem) { + case 0: + moveBtn.TextAlignment = TextAlignment.Left; + sizeBtn.TextAlignment = TextAlignment.Left; + moveBtnA.TextAlignment = TextAlignment.Left; + sizeBtnA.TextAlignment = TextAlignment.Left; + moveHotKeyBtn.TextAlignment = TextAlignment.Left; + moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Left; + break; + case 1: + moveBtn.TextAlignment = TextAlignment.Right; + sizeBtn.TextAlignment = TextAlignment.Right; + moveBtnA.TextAlignment = TextAlignment.Right; + sizeBtnA.TextAlignment = TextAlignment.Right; + moveHotKeyBtn.TextAlignment = TextAlignment.Right; + moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Right; + break; + case 2: + moveBtn.TextAlignment = TextAlignment.Centered; + sizeBtn.TextAlignment = TextAlignment.Centered; + moveBtnA.TextAlignment = TextAlignment.Centered; + sizeBtnA.TextAlignment = TextAlignment.Centered; + moveHotKeyBtn.TextAlignment = TextAlignment.Centered; + moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Centered; + break; + case 3: + moveBtn.TextAlignment = TextAlignment.Justified; + sizeBtn.TextAlignment = TextAlignment.Justified; + moveBtnA.TextAlignment = TextAlignment.Justified; + sizeBtnA.TextAlignment = TextAlignment.Justified; + moveHotKeyBtn.TextAlignment = TextAlignment.Justified; + moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Justified; + break; + } + }; } } -} +} \ No newline at end of file