diff --git a/Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs b/Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs
index 26eb08072..e4a73a928 100644
--- a/Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs
+++ b/Examples/UICatalog/Scenarios/CharacterMap/CharacterMap.cs
@@ -85,7 +85,7 @@ public class CharacterMap : Scenario
X = Pos.Right (jumpLabel) + 1,
Y = Pos.Y (_charMap),
Width = 17,
- Caption = "e.g. 01BE3 or ✈"
+ Title = "e.g. 01BE3 or ✈"
//SchemeName = "Dialog"
};
diff --git a/Examples/UICatalog/Scenarios/TextInputControls.cs b/Examples/UICatalog/Scenarios/TextInputControls.cs
index 797d83f2f..19e71a427 100644
--- a/Examples/UICatalog/Scenarios/TextInputControls.cs
+++ b/Examples/UICatalog/Scenarios/TextInputControls.cs
@@ -68,7 +68,7 @@ public class TextInputControls : Scenario
X = Pos.Right (label) + 1,
Y = Pos.Bottom (textField),
Width = Dim.Percent (50) - 1,
- Caption = "TextField with caption"
+ Title = "TextField with caption"
};
win.Add (textField);
diff --git a/Terminal.Gui/Resources/Strings.Designer.cs b/Terminal.Gui/Resources/Strings.Designer.cs
index ac857b4e6..5d4d4b1cb 100644
--- a/Terminal.Gui/Resources/Strings.Designer.cs
+++ b/Terminal.Gui/Resources/Strings.Designer.cs
@@ -628,7 +628,7 @@ namespace Terminal.Gui.Resources {
}
///
- /// Looks up a localized string similar to Enter Path.
+ /// Looks up a localized string similar to _Enter Path.
///
internal static string fdPathCaption {
get {
@@ -682,7 +682,7 @@ namespace Terminal.Gui.Resources {
}
///
- /// Looks up a localized string similar to Find.
+ /// Looks up a localized string similar to _Find.
///
internal static string fdSearchCaption {
get {
diff --git a/Terminal.Gui/Resources/Strings.resx b/Terminal.Gui/Resources/Strings.resx
index a05bc2eb5..f7e3edc49 100644
--- a/Terminal.Gui/Resources/Strings.resx
+++ b/Terminal.Gui/Resources/Strings.resx
@@ -192,10 +192,7 @@
Modified
- Enter Path
-
-
- Find
+ _Enter Path
Size
@@ -359,4 +356,7 @@
_Tree
Show/Hide Tree View
+
+ _Find
+
\ No newline at end of file
diff --git a/Terminal.Gui/ViewBase/View.Command.cs b/Terminal.Gui/ViewBase/View.Command.cs
index 10c3cd99f..0c3a741ac 100644
--- a/Terminal.Gui/ViewBase/View.Command.cs
+++ b/Terminal.Gui/ViewBase/View.Command.cs
@@ -22,9 +22,9 @@ public partial class View // Command APIs
// HotKey - SetFocus and raise HandlingHotKey
AddCommand (
Command.HotKey,
- () =>
+ (ctx) =>
{
- if (RaiseHandlingHotKey () is true)
+ if (RaiseHandlingHotKey (ctx) is true)
{
return true;
}
@@ -257,15 +257,16 @@ public partial class View // Command APIs
/// which can be cancelled; if not cancelled raises .
/// event. The default handler calls this method.
///
+ /// The context to pass with the command.
///
/// if no event was raised; input processing should continue.
/// if the event was raised and was not handled (or cancelled); input processing should
/// continue.
/// if the event was raised and handled (or cancelled); input processing should stop.
///
- protected bool? RaiseHandlingHotKey ()
+ protected bool? RaiseHandlingHotKey (ICommandContext? ctx)
{
- CommandEventArgs args = new () { Context = new CommandContext { Command = Command.HotKey } };
+ CommandEventArgs args = new () { Context = ctx };
//Logging.Debug ($"{Title} ({args.Context?.Source?.Title})");
// Best practice is to invoke the virtual method first.
diff --git a/Terminal.Gui/Views/CheckBox.cs b/Terminal.Gui/Views/CheckBox.cs
index b6165186b..195428988 100644
--- a/Terminal.Gui/Views/CheckBox.cs
+++ b/Terminal.Gui/Views/CheckBox.cs
@@ -32,7 +32,7 @@ public class CheckBox : View
// Hotkey - Advance state and raise Select event - DO NOT raise Accept
AddCommand (Command.HotKey, ctx =>
{
- if (RaiseHandlingHotKey () is true)
+ if (RaiseHandlingHotKey (ctx) is true)
{
return true;
}
diff --git a/Terminal.Gui/Views/FileDialogs/FileDialog.cs b/Terminal.Gui/Views/FileDialogs/FileDialog.cs
index 7fd813fb8..90982e96f 100644
--- a/Terminal.Gui/Views/FileDialogs/FileDialog.cs
+++ b/Terminal.Gui/Views/FileDialogs/FileDialog.cs
@@ -148,7 +148,7 @@ public class FileDialog : Dialog, IDesignable
e.Handled = true;
};
- _tbPath = new () { Width = Dim.Fill (),/* CaptionColor = new (Color.Black)*/ };
+ _tbPath = new () { Width = Dim.Fill () };
_tbPath.KeyDown += (s, k) =>
{
@@ -248,7 +248,6 @@ public class FileDialog : Dialog, IDesignable
X = 0,
Width = Dim.Fill (),
Y = Pos.AnchorEnd (),
- HotKey = Key.F.WithAlt,
Id = "_tbFind",
};
@@ -456,8 +455,8 @@ public class FileDialog : Dialog, IDesignable
_btnBack.Text = GetBackButtonText ();
_btnForward.Text = GetForwardButtonText ();
- _tbPath.Caption = Style.PathCaption;
- _tbFind.Caption = Style.SearchCaption;
+ _tbPath.Title = Style.PathCaption;
+ _tbFind.Title = Style.SearchCaption;
_tbPath.Autocomplete.Scheme = new (_tbPath.GetScheme ())
{
diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs
index d315fe502..5ea22dba9 100644
--- a/Terminal.Gui/Views/Label.cs
+++ b/Terminal.Gui/Views/Label.cs
@@ -60,7 +60,7 @@ public class Label : View, IDesignable
private bool? InvokeHotKeyOnNextPeer (ICommandContext commandContext)
{
- if (RaiseHandlingHotKey () == true)
+ if (RaiseHandlingHotKey (commandContext) == true)
{
return true;
}
diff --git a/Terminal.Gui/Views/Menu/MenuBarv2.cs b/Terminal.Gui/Views/Menu/MenuBarv2.cs
index 1385aa24e..fe2942a96 100644
--- a/Terminal.Gui/Views/Menu/MenuBarv2.cs
+++ b/Terminal.Gui/Views/Menu/MenuBarv2.cs
@@ -31,11 +31,11 @@ public class MenuBarv2 : Menuv2, IDesignable
AddCommand (
Command.HotKey,
- () =>
+ (ctx) =>
{
// Logging.Debug ($"{Title} - Command.HotKey");
- if (RaiseHandlingHotKey () is true)
+ if (RaiseHandlingHotKey (ctx) is true)
{
return true;
}
diff --git a/Terminal.Gui/Views/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs
index 1040c6e83..55d52bb51 100644
--- a/Terminal.Gui/Views/RadioGroup.cs
+++ b/Terminal.Gui/Views/RadioGroup.cs
@@ -94,7 +94,7 @@ public class RadioGroup : View, IDesignable, IOrientation
return false;
}
- if (RaiseHandlingHotKey () == true)
+ if (RaiseHandlingHotKey (ctx) == true)
{
return true;
}
diff --git a/Terminal.Gui/Views/TextInput/TextField.cs b/Terminal.Gui/Views/TextInput/TextField.cs
index 70d7e007e..bc2390fb6 100644
--- a/Terminal.Gui/Views/TextInput/TextField.cs
+++ b/Terminal.Gui/Views/TextInput/TextField.cs
@@ -28,9 +28,6 @@ public class TextField : View, IDesignable
_selectedStart = -1;
_text = new ();
- // TODO: Determine if this is a good choice. Previously this was hard coded to
- // TODO: DarkGray which was NOT a good choice.
- CaptionColor = GetAttributeForRole (VisualRole.Normal).Foreground.GetBrighterColor();
ReadOnly = false;
Autocomplete = new TextFieldAutocomplete ();
Height = Dim.Auto (DimAutoStyle.Text, 1);
@@ -40,9 +37,6 @@ public class TextField : View, IDesignable
Used = true;
WantMousePositionReports = true;
- // By default, disable hotkeys (in case someome sets Title)
- HotKeySpecifier = new ('\xffff');
-
_historyText.ChangeText += HistoryText_ChangeText;
Initialized += TextField_Initialized;
@@ -324,6 +318,30 @@ public class TextField : View, IDesignable
}
);
+ AddCommand (
+ Command.HotKey,
+ ctx =>
+ {
+ if (RaiseHandlingHotKey (ctx) is true)
+ {
+ return true;
+ }
+
+ // If we have focus, then ignore the hotkey because the user
+ // means to enter it
+ if (HasFocus)
+ {
+ return false;
+ }
+
+ // This is what the default HotKey handler does:
+ SetFocus ();
+
+ // Always return true on hotkey, even if SetFocus fails because
+ // hotkeys are always handled by the View (unless RaiseHandlingHotKey cancels).
+ return true;
+ });
+
// Default keybindings for this view
// We follow this as closely as possible: https://en.wikipedia.org/wiki/Table_of_keyboard_shortcuts
KeyBindings.Add (Key.Delete, Command.DeleteCharRight);
@@ -411,15 +429,6 @@ public class TextField : View, IDesignable
///
public IAutocomplete Autocomplete { get; set; }
- ///
- /// Gets or sets the text to render in control when no value has been entered yet and the does
- /// not yet have input focus.
- ///
- public string Caption { get; set; }
-
- /// Gets or sets the foreground to use when rendering .
- public Color CaptionColor { get; set; }
-
/// Get the Context Menu for this view.
[CanBeNull]
public PopoverMenu ContextMenu { get; private set; }
@@ -920,7 +929,7 @@ public class TextField : View, IDesignable
_isDrawing = true;
// Cache attributes as GetAttributeForRole might raise events
- Attribute selectedAttribute = new Attribute (GetAttributeForRole (VisualRole.Active));
+ var selectedAttribute = new Attribute (GetAttributeForRole (VisualRole.Active));
Attribute readonlyAttribute = GetAttributeForRole (VisualRole.ReadOnly);
Attribute normalAttribute = GetAttributeForRole (VisualRole.Editable);
@@ -943,7 +952,7 @@ public class TextField : View, IDesignable
{
// Disabled
SetAttributeForRole (VisualRole.Disabled);
- }
+ }
else if (idx == _cursorPosition && HasFocus && !Used && SelectedLength == 0 && !ReadOnly)
{
// Selected text
@@ -1157,7 +1166,6 @@ public class TextField : View, IDesignable
/////
//public event EventHandler> TextChanged;
-
/// Undoes the latest changes.
public void Undo ()
{
@@ -1699,25 +1707,33 @@ public class TextField : View, IDesignable
private void RenderCaption ()
{
if (HasFocus
- || Caption == null
- || Caption.Length == 0
+ || string.IsNullOrEmpty (Title)
|| Text?.Length > 0)
{
return;
}
- var color = new Attribute (CaptionColor, GetAttributeForRole (VisualRole.Editable).Background, GetAttributeForRole (VisualRole.Editable).Style);
- SetAttribute (color);
-
- Move (0, 0);
- string render = Caption;
-
- if (render.GetColumns () > Viewport.Width)
+ // Ensure TitleTextFormatter has the current Title text
+ // (should already be set by the Title property setter, but being defensive)
+ if (TitleTextFormatter.Text != Title)
{
- render = render [..Viewport.Width];
+ TitleTextFormatter.Text = Title;
}
- AddStr (render);
+ var captionAttribute = new Attribute (
+ GetAttributeForRole (VisualRole.Editable).Foreground.GetDimColor (),
+ GetAttributeForRole (VisualRole.Editable).Background);
+
+ var hotKeyAttribute = new Attribute (
+ GetAttributeForRole (VisualRole.Editable).Foreground.GetDimColor (),
+ GetAttributeForRole (VisualRole.Editable).Background,
+ GetAttributeForRole (VisualRole.Editable).Style | TextStyle.Underline);
+
+ // Use TitleTextFormatter to render the caption with hotkey support
+ TitleTextFormatter.Draw (
+ ViewportToScreen (new Rectangle (0, 0, Viewport.Width, 1)),
+ captionAttribute,
+ hotKeyAttribute);
}
private void SetClipboard (IEnumerable text)
@@ -1814,11 +1830,11 @@ public class TextField : View, IDesignable
}
}
- ///
+ ///
public bool EnableForDesign ()
{
Text = "This is a test.";
- Caption = "Caption";
+ Title = "Caption";
return true;
}
diff --git a/Tests/UnitTests/FileServices/FileDialogTests.cs b/Tests/UnitTests/FileServices/FileDialogTests.cs
index 6a7840096..9488884eb 100644
--- a/Tests/UnitTests/FileServices/FileDialogTests.cs
+++ b/Tests/UnitTests/FileServices/FileDialogTests.cs
@@ -107,7 +107,7 @@ public class FileDialogTests ()
Assert.IsType (dlg.MostFocused);
Assert.Same (tf, dlg.MostFocused);
- Assert.Equal ("Find", tf.Caption);
+ Assert.Equal ("_Find", tf.Title);
// Dialog has not yet been confirmed with a choice
Assert.True (dlg.Canceled);
diff --git a/Tests/UnitTests/Views/TextFieldTests.cs b/Tests/UnitTests/Views/TextFieldTests.cs
index 40fc6a300..123201f49 100644
--- a/Tests/UnitTests/Views/TextFieldTests.cs
+++ b/Tests/UnitTests/Views/TextFieldTests.cs
@@ -145,7 +145,7 @@ public class TextFieldTests (ITestOutputHelper output)
TextField tf = GetTextFieldsInView ();
// Caption has no effect when focused
- tf.Caption = caption;
+ tf.Title = caption;
Application.RaiseKeyDownEvent ('\t');
Assert.False (tf.HasFocus);
@@ -165,7 +165,7 @@ public class TextFieldTests (ITestOutputHelper output)
TextField tf = GetTextFieldsInView ();
- tf.Caption = caption;
+ tf.Title = caption;
Application.RaiseKeyDownEvent ('\t');
Assert.False (tf.HasFocus);
@@ -185,7 +185,7 @@ public class TextFieldTests (ITestOutputHelper output)
tf.Draw ();
DriverAssert.AssertDriverContentsAre ("", output);
- tf.Caption = "Enter txt";
+ tf.Title = "Enter txt";
Application.RaiseKeyDownEvent ('\t');
// Caption should appear when not focused and no text
@@ -212,7 +212,7 @@ public class TextFieldTests (ITestOutputHelper output)
DriverAssert.AssertDriverContentsAre ("", output);
// Caption has no effect when focused
- tf.Caption = "Enter txt";
+ tf.Title = "Enter txt";
Assert.True (tf.HasFocus);
View.SetClipToScreen ();
tf.Draw ();
@@ -227,6 +227,104 @@ public class TextFieldTests (ITestOutputHelper output)
Application.Top.Dispose ();
}
+ [Fact]
+ [AutoInitShutdown]
+ public void Title_RendersAsCaption_WithCorrectAttributes ()
+ {
+ TextField tf = GetTextFieldsInView ();
+
+ // Set a title (caption)
+ tf.Title = "Enter text";
+
+ // Remove focus so caption appears
+ Application.RaiseKeyDownEvent ('\t');
+ Assert.False (tf.HasFocus);
+
+ View.SetClipToScreen ();
+ tf.Draw ();
+
+ // Verify the caption text is rendered
+ DriverAssert.AssertDriverContentsAre ("Enter text", output);
+
+ // Verify the caption uses dimmed color attribute
+ Attribute captionAttr = new Attribute (
+ tf.GetAttributeForRole (VisualRole.Editable).Foreground.GetDimColor (),
+ tf.GetAttributeForRole (VisualRole.Editable).Background);
+
+ // All characters in "Enter text" should have the caption attribute
+ DriverAssert.AssertDriverAttributesAre ("0000000000", output, Application.Driver, captionAttr);
+
+ Application.Top.Dispose ();
+ }
+
+ [Fact]
+ [AutoInitShutdown]
+ public void Title_WithHotkey_RendersUnderlined ()
+ {
+ TextField tf = GetTextFieldsInView ();
+
+ // Title with hotkey should be rendered with the hotkey underlined when not focused
+ tf.Title = "_Find";
+
+ // Remove focus so caption appears
+ Application.RaiseKeyDownEvent ('\t');
+ Assert.False (tf.HasFocus);
+
+ View.SetClipToScreen ();
+ tf.Draw ();
+
+ // The hotkey character 'F' should be rendered (without the underscore in the actual text)
+ DriverAssert.AssertDriverContentsAre ("Find", output);
+
+ // Verify the hotkey character 'F' has underline style
+ Attribute captionAttr = new Attribute (
+ tf.GetAttributeForRole (VisualRole.Editable).Foreground.GetDimColor (),
+ tf.GetAttributeForRole (VisualRole.Editable).Background);
+ Attribute hotkeyAttr = new Attribute (
+ tf.GetAttributeForRole (VisualRole.Editable).Foreground.GetDimColor (),
+ tf.GetAttributeForRole (VisualRole.Editable).Background,
+ tf.GetAttributeForRole (VisualRole.Editable).Style | TextStyle.Underline);
+
+ // F is underlined (index 1), remaining characters use normal caption attribute (index 0)
+ DriverAssert.AssertDriverAttributesAre ("1000", output, Application.Driver, captionAttr, hotkeyAttr);
+
+ Application.Top.Dispose ();
+ }
+
+ [Fact]
+ [AutoInitShutdown]
+ public void Title_WithHotkey_MiddleCharacter_RendersUnderlined ()
+ {
+ TextField tf = GetTextFieldsInView ();
+
+ // Title with hotkey in middle of text
+ tf.Title = "Enter _Text";
+
+ // Remove focus so caption appears
+ Application.RaiseKeyDownEvent ('\t');
+ Assert.False (tf.HasFocus);
+
+ View.SetClipToScreen ();
+ tf.Draw ();
+
+ // The underscore should not be rendered, 'T' should be underlined
+ DriverAssert.AssertDriverContentsAre ("Enter Text", output);
+
+ // Verify the hotkey character 'T' has underline style
+ Attribute captionAttr = new Attribute (
+ tf.GetAttributeForRole (VisualRole.Editable).Foreground.GetDimColor (),
+ tf.GetAttributeForRole (VisualRole.Editable).Background);
+ Attribute hotkeyAttr = new Attribute (
+ tf.GetAttributeForRole (VisualRole.Editable).Foreground.GetDimColor (),
+ tf.GetAttributeForRole (VisualRole.Editable).Background,
+ tf.GetAttributeForRole (VisualRole.Editable).Style | TextStyle.Underline);
+
+ // "Enter " (6 chars) + "T" (underlined) + "ext" (3 chars)
+ DriverAssert.AssertDriverAttributesAre ("0000001000", output, Application.Driver, captionAttr, hotkeyAttr);
+
+ Application.Top.Dispose ();
+ }
+
[Fact]
[TextFieldTestsAutoInitShutdown]
public void Changing_SelectedStart_Or_CursorPosition_Update_SelectedLength_And_SelectedText ()
diff --git a/local_packages/Terminal.Gui.2.0.0.nupkg b/local_packages/Terminal.Gui.2.0.0.nupkg
index c8a1980a4..f784c5fef 100644
Binary files a/local_packages/Terminal.Gui.2.0.0.nupkg and b/local_packages/Terminal.Gui.2.0.0.nupkg differ
diff --git a/local_packages/Terminal.Gui.2.0.0.snupkg b/local_packages/Terminal.Gui.2.0.0.snupkg
index aa76f8d2a..5b2fcc136 100644
Binary files a/local_packages/Terminal.Gui.2.0.0.snupkg and b/local_packages/Terminal.Gui.2.0.0.snupkg differ