Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
d40e3bb
Show URL on enter link
ccoulioufr Mar 24, 2026
5dbab39
tooltip used by link
ccoulioufr Mar 24, 2026
96a40a3
Refactor About dialog and update Link activation logic
tig Mar 25, 2026
37b4fd3
Upgrade About box to use Link and gradient box-drawing logo
tig Mar 25, 2026
bfb24ad
tooltip system
ccoulioufr Mar 25, 2026
0d7424c
merge tig fixes
ccoulioufr Mar 25, 2026
488f315
fix UseToolTip case
ccoulioufr Mar 25, 2026
bf17542
refactor with provider
ccoulioufr Mar 26, 2026
bd8c3c8
Merge branch 'v2_develop' into fix/show-link-url-on-enter
tig Mar 27, 2026
20fc680
Code cleanup & review.
tig Mar 27, 2026
6d9d6ec
Update Terminal.Gui/Views/Link.cs
tig Mar 27, 2026
e4e842a
Merge branch 'v2_develop' into fix/show-link-url-on-enter
tig Mar 28, 2026
c4a7117
Merge branch 'v2_develop' into fix/show-link-url-on-enter
tig Mar 30, 2026
7814520
Merge branch 'v2_develop' into fix/show-link-url-on-enter
tig Mar 30, 2026
5862ce8
RemoveTooltip case
ccoulioufr Mar 30, 2026
4963bf1
MakeVisible multi event
ccoulioufr Mar 30, 2026
5b133f4
fix ToolTipExtensions name
ccoulioufr Mar 30, 2026
5de6af6
fix ToolTipManager name
ccoulioufr Mar 30, 2026
72c81ad
fix tooltip case
ccoulioufr Mar 30, 2026
4563bd1
unregister Disposing
ccoulioufr Mar 30, 2026
9c80674
empty tooltip removes tooltip
ccoulioufr Mar 30, 2026
ede92d2
link comment
ccoulioufr Mar 30, 2026
87a04cb
threadsafe manager
ccoulioufr Mar 30, 2026
76d0f80
internal fields for tests
ccoulioufr Mar 30, 2026
47dff31
Merge branch 'fix/show-link-url-on-enter' of https://github.com/ccoul…
tig Mar 30, 2026
b2a066c
tooltip tests
ccoulioufr Mar 30, 2026
3229bd9
ShowsHidesToolTip with runnable
ccoulioufr Mar 31, 2026
e333fbe
ApplicationToolTip
ccoulioufr Apr 4, 2026
108975a
Merge branch 'v2_develop' into fix/show-link-url-on-enter
tig Apr 6, 2026
3beee2d
Merge branch 'fix/show-link-url-on-enter' of https://github.com/ccoul…
tig Apr 6, 2026
069f001
Improve link handling and UI consistency in scenarios
tig Apr 6, 2026
448f707
more code cleanup.
tig Apr 6, 2026
bc6c94c
Enhance Link design mode with tooltip on initialization
tig Apr 6, 2026
0d8b7c5
Fixed double OpenUrl on mouse.
tig Apr 6, 2026
56cb119
Merge branch 'v2_develop' into fix/show-link-url-on-enter
tig Apr 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Examples/UICatalog/Scenarios/Links.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public override void Main ()
_link.TextChanged += (s, e) => simpleUrlLabel.Text = $"This is just a Label with a URL in Text (WT automatically enables URLs) - {_link.Text}";
titleTextField.ValueChanged += (s, e) => _link.Title = e.NewValue ?? string.Empty;
textTextField.ValueChanged += (s, e) => _link.Text = e.NewValue ?? string.Empty;
urlTextField.ValueChanged += (s, e) => _link.Url = e.NewValue ?? Link.DEFAULT_URL;
urlTextField.ValueChanged += (s, e) => _link.Url = e.NewValue ?? string.Empty;
linkFrame.Add (_link);

titleTextField.Text = "Title";
Expand All @@ -75,7 +75,7 @@ public override void Main ()
Shortcut urlIndicator = new (Key.Empty, "", null);

StatusBar statusBar = new ([new Shortcut (Application.GetDefaultKey (Command.Quit), "Quit", Quit), urlIndicator]);
_link.MouseEnter += (s, e) => urlIndicator.Title = _link.Text;
_link.MouseEnter += (s, e) => urlIndicator.Title = _link.Url;
_link.MouseLeave += (s, e) => urlIndicator.Title = "";
_appWindow.Add (statusBar);

Expand Down
156 changes: 120 additions & 36 deletions Examples/UICatalog/UICatalogRunnable.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#nullable enable
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Text;
using System.Text.Json.Serialization;
using Microsoft.Extensions.Logging;
using Terminal.Gui;
Expand Down Expand Up @@ -154,14 +152,7 @@ private MenuBar CreateMenuBar ()
"Project readme",
() => Link.OpenUrl ("https://github.com/gui-cs/Terminal.Gui"),
Key.F2),
new MenuItem ("_About...",
"About UI Catalog",
() => MessageBox.Query (App!,
"",
GetAboutBoxMessage (),
wrapMessage: false,
buttons: Strings.btnOk),
Key.A.WithCtrl)
new MenuItem ("_About...", "About UI Catalog", ShowAboutDialog, Key.A.WithCtrl)
])
]) { Title = "menuBar", Id = "menuBar" };

Expand Down Expand Up @@ -421,9 +412,7 @@ View [] CreateLoggingMenuItems ()

CheckBox drawTraceCheckBox = new ()
{
Text = "_Draw",
Value = Trace.EnabledCategories.HasFlag (TraceCategory.Draw) ? CheckState.Checked : CheckState.UnChecked,
CanFocus = false
Text = "_Draw", Value = Trace.EnabledCategories.HasFlag (TraceCategory.Draw) ? CheckState.Checked : CheckState.UnChecked, CanFocus = false
};

drawTraceCheckBox.ValueChanging += (_, e) =>
Expand Down Expand Up @@ -762,31 +751,126 @@ private void ConfigApplied ()
#endregion Configuration Manager

/// <summary>
/// Gets the message displayed in the About Box. `public` so it can be used from Unit tests.
/// The URL displayed in the About Box.
/// </summary>
/// <returns></returns>
public static string GetAboutBoxMessage ()
public const string AboutUrl = "https://github.com/gui-cs/Terminal.Gui";

private void ShowAboutDialog ()
{
// NOTE: Do not use multiline verbatim strings here.
// WSL gets all confused.
StringBuilder msg = new ();
msg.AppendLine ("UI Catalog: A comprehensive sample library and test app for");
msg.AppendLine ();

msg.AppendLine ("""
_______ _ _ _____ _
|__ __| (_) | | / ____| (_)
| | ___ _ __ _ __ ___ _ _ __ __ _| || | __ _ _ _
| |/ _ \ '__| '_ ` _ \| | '_ \ / _` | || | |_ | | | | |
| | __/ | | | | | | | | | | | (_| | || |__| | |_| | |
|_|\___|_| |_| |_| |_|_|_| |_|\__,_|_(_)_____|\__,_|_|
""");
msg.AppendLine ();
msg.AppendLine ("v2 - Beta");
msg.AppendLine ();
msg.Append ("https://github.com/gui-cs/Terminal.Gui");

return msg.ToString ();
Dialog dialog = new () { Title = "", Buttons = [new Button { Title = Strings.btnOk, IsDefault = true }] };

Label tagline = new ()
{
Text = "UI Catalog: A comprehensive sample library and test app for",
TextAlignment = Alignment.Center,
X = Pos.Center (),
Width = Dim.Auto (DimAutoStyle.Text),
Height = Dim.Auto (DimAutoStyle.Text)
};

GradientArtView asciiArt = new () { X = Pos.Center (), Y = Pos.Bottom (tagline) + 1 };

Link link = new ()
{
Text = AboutUrl,
Url = AboutUrl,
X = Pos.Center (),
Y = Pos.Bottom (asciiArt) + 1
};

dialog.Add (tagline, asciiArt, link);
dialog.Buttons.ElementAt (0).SetFocus ();
App!.Run (dialog);
dialog.Dispose ();
}

/// <summary>
/// Renders the Terminal.Gui logo in box-drawing characters with a diagonal gradient.
/// </summary>
private sealed class GradientArtView : View
{
// @formatter:off
private const string ART = """
╺┳╸┏━╸┏━┓┏┳┓╻┏┓╻┏━┓╻ ┏━╸╻ ╻╻
┃ ┣╸ ┣┳┛┃┃┃┃┃┗┫┣━┫┃ ┃╺┓┃ ┃┃
╹ ┗━╸╹┗╸╹ ╹╹╹ ╹╹ ╹┗━╸╹┗━┛┗━┛╹

v2 - Beta
""";

// @formatter:on

private static readonly string [] _artLines = ART.ReplaceLineEndings ("\n").Split ('\n');

public GradientArtView ()
{
int artWidth = _artLines.Select (line => line.Length).Prepend (0).Max ();

Width = artWidth;
Height = _artLines.Length;
}

/// <inheritdoc/>
protected override bool OnDrawingContent (DrawContext? context)
{
List<Color> stops =
[
new (0, 128, 255), // Bright Blue
new (0, 255, 128), // Bright Green
new (255, 255), // Bright Yellow
new (255, 128) // Bright Orange
];

List<int> steps = [10];

Gradient gradient = new (stops, steps);

var artHeight = 3; // Only the box-drawing lines get the gradient
int artWidth = _artLines [0].Length;

Dictionary<Point, Color> colorMap = gradient.BuildCoordinateColorMapping (artHeight, artWidth, GradientDirection.Diagonal);

Attribute normalAttr = GetAttributeForRole (VisualRole.Normal);

for (var row = 0; row < _artLines.Length; row++)
{
string line = _artLines [row];

for (var col = 0; col < line.Length; col++)
{
char ch = line [col];

if (ch == ' ')
{
continue;
}

// Gradient only on the 3 art lines; version text uses normal color
if (row < 3)
{
Point coord = new (col, row);

if (colorMap.TryGetValue (coord, out Color color))
{
SetAttribute (new Attribute (color, normalAttr.Background));
}
else
{
SetAttribute (normalAttr);
}
}
else
{
SetAttribute (normalAttr);
}

Move (col, row);
AddStr (ch.ToString ());
}
}

return true;
}
}

/// <summary>
Expand Down
78 changes: 78 additions & 0 deletions Terminal.Gui/App/Popovers/TooTipExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
namespace Terminal.Gui.App;

/// <summary>
/// Provides extension methods for associating tooltips with views in a Terminal.Gui application.
/// </summary>
/// <remarks>
/// These extension methods enable developers to attach or remove tooltips from any View instance.
/// Tooltips can be specified as static text, dynamically generated text, or as a custom View for advanced scenarios.
/// Tooltips enhance user experience by providing contextual information when users interact with UI elements.
/// </remarks>
public static class ToolTipExtensions
{
/// <param name="view">The view to which the tooltip will be attached. Cannot be null.</param>
extension (View view)
{
/// <summary>
/// Associates a tooltip with the specified view, displaying the provided text when the user hovers over or focuses
/// on the view.
/// </summary>
/// <remarks>
/// If a tooltip is already set for the view, this method replaces it with the new text. Tooltips
/// provide additional context or guidance to users interacting with the UI element.
/// </remarks>
/// <param name="text">The text to display in the tooltip. If null or empty, no tooltip will be shown.</param>
public void SetToolTip (string text)
{
ArgumentNullException.ThrowIfNull (view);
TooltipManager.Instance.SetToolTip (view, new ToolTipProvider (text));
}

/// <summary>
/// Associates a dynamic tooltip with the specified view using a factory function to generate the tooltip text.
/// </summary>
/// <remarks>
/// Use this method to provide tooltips that can change dynamically based on application state or
/// user interaction. The tooltip text is evaluated at the time the tooltip is displayed, allowing for
/// context-sensitive information.
/// </remarks>
/// <param name="textFactory">
/// A function that returns the tooltip text to display. This function is invoked each time the tooltip is shown.
/// Cannot be null.
/// </param>
public void SetToolTip (Func<string> textFactory)
{
ArgumentNullException.ThrowIfNull (view);
ArgumentNullException.ThrowIfNull (textFactory);
TooltipManager.Instance.SetToolTip (view, new ToolTipProvider (textFactory));
}

/// <summary>
/// Associates a dynamic tooltip with the specified view using a factory function to generate the tooltip content.
/// </summary>
/// <remarks>
/// The tooltip content is generated each time the tooltip is shown by invoking the provided
/// factory function. This allows the tooltip to reflect dynamic or context-sensitive information.
/// </remarks>
/// <param name="contentFactory">A function that returns the content view to display as the tooltip. Cannot be null.</param>
public void SetToolTip (Func<View> contentFactory)
{
ArgumentNullException.ThrowIfNull (view);
ArgumentNullException.ThrowIfNull (contentFactory);
TooltipManager.Instance.SetToolTip (view, new ToolTipProvider (contentFactory));
}

/// <summary>
/// Removes the tooltip associated with the specified view, if one exists.
/// </summary>
/// <remarks>
/// If the specified view does not have an associated tooltip, this method has no
/// effect.
/// </remarks>
public void RemoveToolTip ()
{
ArgumentNullException.ThrowIfNull (view);
TooltipManager.Instance.RemoveTooltip (view);
}
}
}
Loading
Loading