Skip to content

Commit 9d40ced

Browse files
ccoulioufrtigclaudeCopilot
authored
Fixes #4850 - Show URL of Link in Tooltip (+ StatusBar in UICatalog) (#4850)
* Show URL on enter link * tooltip used by link * Refactor About dialog and update Link activation logic Refactored the UICatalog About dialog to use a new ShowAboutDialog method with a Dialog, tagline, ASCII art, and a clickable Link to the project URL. Removed GetAboutBoxMessage and made AboutUrl a public constant. Updated Link to trigger Command.Activate on mouse click and moved URL opening logic to OnActivated. OpenUrl now checks for the "DisableRealDriverIO" environment variable. Simplified UseToolTip property and improved tooltip management. Clarified hotkey handling in Link. Removed obsolete About box test from TextFormatterDrawTests.cs. * Upgrade About box to use Link and gradient box-drawing logo Replace MessageBox.Query with a custom Dialog containing 3 subviews: - Tagline label - GradientArtView: Terminal.Gui logo in box-drawing chars with diagonal color gradient - Link view with clickable GitHub URL and tooltip Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * tooltip system * fix UseToolTip case * refactor with provider * Code cleanup & review. Refactored ToolTipExtensions to use a local static extension class for cleaner instance method usage. Updated method signatures, improved XML documentation formatting, and streamlined TooltipManager event registration. Applied minor code style fixes and updated the Link class to use the new tooltip extension pattern. * Update Terminal.Gui/Views/Link.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * RemoveTooltip case * MakeVisible multi event * fix ToolTipExtensions name * fix ToolTipManager name * fix tooltip case * unregister Disposing * empty tooltip removes tooltip * link comment * threadsafe manager * internal fields for tests * tooltip tests * ShowsHidesToolTip with runnable * ApplicationToolTip * Improve link handling and UI consistency in scenarios - Code Cleanup of UI Catalog to fix nullability risks - Replace Label+URL with Link controls in AnimationScenario and FileDialogExamples; add tooltips for links. - Refactor Links scenario to use local variables, add API docs link, and show URL in status bar on hover. - Rename AboutUrl to ABOUT_URL and add tooltip in UICatalogRunnable. - Apply code style improvements: pattern matching, event handler syntax, and field cleanup. * more code cleanup. Update Link text to show 'Terminal.Gui.Views.Link' Changed Link control text from "Docs" to "Terminal.Gui.Views.Link" for clearer destination indication; URL remains unchanged. * Enhance Link design mode with tooltip on initialization Moved and updated IDesignable.EnableForDesign to set Title, Url, and attach a tooltip on Initialized. The tooltip informs users that the Link opens the URL in the default browser. * Fixed double OpenUrl on mouse. Remove default left-click activation from Link Removed MouseBindings.Add for LeftButtonClicked in Link, so clicking a Link no longer triggers Activate by default. --------- Co-authored-by: Tig <tig@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 2c24b9c commit 9d40ced

18 files changed

Lines changed: 1235 additions & 247 deletions

Examples/UICatalog/README.md

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,6 @@ said concepts & features.
1414
The original `demo.cs` sample app for Terminal.Gui is neither good to showcase, nor does it explain different concepts. In addition, because it is built on a single source file, it has proven to cause friction when multiple contributors are simultaneously working on different aspects of Terminal.Gui.
1515
See [Issue #368](https://github.com/giu-cs/Terminal.Gui/issues/368) for more background.
1616

17-
# API Reference
18-
19-
* [UI Catalog API Reference](https://gui-cs.github.io/Terminal.Gui/api/UICatalog/UICatalog.html)
20-
2117
## How To Use
2218

2319
Build and run UI Catalog by typing `dotnet run` from the `UI Catalog` folder or by using the `Terminal.Gui` Visual Studio solution.

Examples/UICatalog/Scenarios/AnimationScenario/AnimationScenario.cs

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
#nullable enable
2-
using System;
32
using System.Diagnostics;
4-
using System.IO;
53
using System.Reflection;
64
using System.Text;
7-
using System.Threading.Tasks;
85
using SixLabors.ImageSharp;
96
using SixLabors.ImageSharp.PixelFormats;
107
using SixLabors.ImageSharp.Processing;
@@ -30,7 +27,7 @@ public override void Main ()
3027
X = 0,
3128
Y = 0,
3229
Width = Dim.Fill (),
33-
Height = Dim.Fill (),
30+
Height = Dim.Fill ()
3431
};
3532

3633
_imageView = new ImageView { Width = Dim.Fill (), Height = Dim.Fill ()! - 2 };
@@ -40,22 +37,21 @@ public override void Main ()
4037
var lbl = new Label { Y = Pos.AnchorEnd (), Text = "Image by Wikiscient" };
4138
win.Add (lbl);
4239

43-
var lbl2 = new Label
40+
Link link = new ()
4441
{
45-
// This ensures the URL that has an underscore is drawn correctly
46-
HotKeySpecifier = new Rune ('\xFFFF'),
47-
X = Pos.AnchorEnd (), Y = Pos.AnchorEnd (),
48-
Text = "https://commons.wikimedia.org/wiki/File:Spinning_globe.gif"
42+
X = Pos.AnchorEnd (),
43+
Y = Pos.AnchorEnd (),
44+
Url = "https://commons.wikimedia.org/wiki/File:Spinning_globe.gif"
4945
};
50-
win.Add (lbl2);
46+
app.ToolTips!.SetToolTip (link, () => link.Url);
47+
win.Add (link);
5148

5249
// Start the animation after the window is initialized
5350
win.Initialized += OnWinOnInitialized;
5451

5552
app.Run (win);
5653
}
5754

58-
5955
private void OnWinOnInitialized (object? sender, EventArgs args)
6056
{
6157
DirectoryInfo dir;
@@ -71,9 +67,7 @@ private void OnWinOnInitialized (object? sender, EventArgs args)
7167
dir = new DirectoryInfo (AppContext.BaseDirectory);
7268
}
7369

74-
var f = new FileInfo (
75-
Path.Combine (dir.FullName, "Scenarios/AnimationScenario", "Spinning_globe_dark_small.gif")
76-
);
70+
var f = new FileInfo (Path.Combine (dir.FullName, "Scenarios/AnimationScenario", "Spinning_globe_dark_small.gif"));
7771

7872
if (!f.Exists)
7973
{
@@ -85,18 +79,16 @@ private void OnWinOnInitialized (object? sender, EventArgs args)
8579

8680
_imageView!.SetImage (Image.Load<Rgba32> (File.ReadAllBytes (f.FullName)));
8781

88-
Task.Run (
89-
() =>
82+
Task.Run (() =>
9083
{
9184
while (_imageView?.App?.Initialized == true)
9285
{
9386
// When updating from a Thread/Task always use Invoke
94-
_imageView?.App?.Invoke (
95-
(_) =>
96-
{
97-
_imageView?.NextFrame ();
98-
_imageView?.SetNeedsDraw ();
99-
});
87+
_imageView?.App?.Invoke (_ =>
88+
{
89+
_imageView?.NextFrame ();
90+
_imageView?.SetNeedsDraw ();
91+
});
10092

10193
Task.Delay (100).Wait ();
10294
}
@@ -171,14 +163,15 @@ private class ImageView : View
171163
private Image<Rgba32> []? _fullResImages;
172164
private Image<Rgba32> []? _matchSizes;
173165
private Rectangle _oldSize = Rectangle.Empty;
174-
public void NextFrame () { _currentFrame = (_currentFrame + 1) % _frameCount; }
166+
public void NextFrame () => _currentFrame = (_currentFrame + 1) % _frameCount;
175167

176168
protected override bool OnDrawingContent (DrawContext? context)
177169
{
178170
if (_frameCount == 0)
179171
{
180172
return false;
181173
}
174+
182175
if (_oldSize != Viewport)
183176
{
184177
// Invalidate cached images now size has changed
@@ -198,18 +191,14 @@ protected override bool OnDrawingContent (DrawContext? context)
198191
int newSize = Math.Min (Viewport.Width, Viewport.Height);
199192

200193
// generate one
201-
if (_matchSizes is not null && imgFull is not null)
194+
if (_matchSizes is { } && imgFull is { })
202195
{
203-
_matchSizes [_currentFrame] = imgScaled = imgFull.Clone (
204-
x => x.Resize (
205-
newSize * BitmapToBraille.CHAR_HEIGHT,
206-
newSize * BitmapToBraille.CHAR_HEIGHT
207-
)
208-
);
196+
_matchSizes [_currentFrame] =
197+
imgScaled = imgFull.Clone (x => x.Resize (newSize * BitmapToBraille.CHAR_HEIGHT, newSize * BitmapToBraille.CHAR_HEIGHT));
209198
}
210199
}
211200

212-
if (braille == null && _brailleCache is not null)
201+
if (braille == null && _brailleCache is { })
213202
{
214203
_brailleCache [_currentFrame] = braille = GetBraille (_matchSizes? [_currentFrame]!);
215204
}
@@ -249,11 +238,7 @@ internal void SetImage (Image<Rgba32> image)
249238

250239
private string GetBraille (Image<Rgba32> img)
251240
{
252-
var braille = new BitmapToBraille (
253-
img.Width,
254-
img.Height,
255-
(x, y) => IsLit (img, x, y)
256-
);
241+
var braille = new BitmapToBraille (img.Width, img.Height, (x, y) => IsLit (img, x, y));
257242

258243
return braille.GenerateImage ();
259244
}

Examples/UICatalog/Scenarios/FileDialogExamples.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,8 +89,11 @@ public override void Main ()
8989
_osIcons.Labels = ["_None", "_Unicode", "Nerd_*"];
9090
win.Add (_osIcons);
9191

92-
win.Add (new Label { Y = Pos.AnchorEnd (2), Text = "* Requires installing Nerd fonts" });
93-
win.Add (new Label { Y = Pos.AnchorEnd (1), Text = " (see: https://github.com/devblackops/Terminal-Icons)" });
92+
Label label = new () { Y = Pos.AnchorEnd (), Text = "* Requires installing Nerd fonts:" };
93+
win.Add (label);
94+
Link link = new () { Y = Pos.Top (label), X = Pos.Right (label) + 1, Url = "https://github.com/devblackops/Terminal-Icons" };
95+
app.ToolTips!.SetToolTip (link, () => link.Url);
96+
win.Add (link);
9497

9598
y = 5;
9699
x = 24;

Examples/UICatalog/Scenarios/Links.cs

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -7,81 +7,91 @@ namespace UICatalog.Scenarios;
77
[ScenarioCategory ("Mouse and Keyboard")]
88
public class Links : Scenario
99
{
10-
private IApplication? _app;
11-
private Window? _appWindow;
12-
private Link? _link;
13-
1410
public override void Main ()
1511
{
1612
ConfigurationManager.Enable (ConfigLocations.All);
1713
using IApplication app = Application.Create ();
1814
app.Init ();
19-
_app = app;
2015

21-
_appWindow = new Window { Title = GetName (), BorderStyle = LineStyle.None };
16+
using Window appWindow = new ();
17+
appWindow.Title = GetName ();
18+
appWindow.BorderStyle = LineStyle.None;
2219

2320
Label titleLabel = new () { Text = "_Title:", X = 1, Y = 1 };
24-
_appWindow.Add (titleLabel);
21+
appWindow.Add (titleLabel);
2522

2623
TextField titleTextField = new () { X = Pos.Right (titleLabel) + 1, Y = Pos.Top (titleLabel), Width = Dim.Fill () };
27-
_appWindow.Add (titleTextField);
24+
appWindow.Add (titleTextField);
2825

29-
Label textLabel = new () { Text = " Te_xt:", X = Pos.Left (titleLabel), Y = Pos.Bottom(titleLabel) };
30-
_appWindow.Add (textLabel);
26+
Label textLabel = new () { Text = " Te_xt:", X = Pos.Left (titleLabel), Y = Pos.Bottom (titleLabel) };
27+
appWindow.Add (textLabel);
3128

32-
TextField textTextField = new () { X = Pos.Right (textLabel) + 1, Y = Pos.Top(textLabel), Width = Dim.Fill () };
33-
_appWindow.Add (textTextField);
29+
TextField textTextField = new () { X = Pos.Right (textLabel) + 1, Y = Pos.Top (textLabel), Width = Dim.Fill () };
30+
appWindow.Add (textTextField);
3431

3532
Label urlLabel = new () { Text = " _Url:", X = 1, Y = Pos.Bottom (titleTextField) + 1 };
36-
_appWindow.Add (urlLabel);
33+
appWindow.Add (urlLabel);
3734

3835
TextField urlTextField = new () { X = Pos.Right (urlLabel) + 1, Y = Pos.Bottom (titleTextField) + 1, Width = Dim.Fill () };
39-
_appWindow.Add (urlTextField);
36+
appWindow.Add (urlTextField);
4037

4138
Label simpleUrlLabel = new () { X = 1, Y = Pos.Bottom (urlTextField) + 2 };
42-
_appWindow.Add (simpleUrlLabel);
39+
appWindow.Add (simpleUrlLabel);
4340

4441
FrameView linkFrame = new ()
4542
{
4643
Title = "_Link Demo",
4744
X = 0,
4845
Y = Pos.Bottom (simpleUrlLabel) + 2,
49-
Width = Dim.Fill(),
46+
Width = Dim.Fill (),
5047
Height = Dim.Auto (),
5148
AssignHotKeys = true,
52-
TabStop = TabBehavior.TabStop
49+
TabStop = TabBehavior.TabStop,
50+
Arrangement = ViewArrangement.Resizable
5351
};
5452

55-
_link = new Link { X = 1, Y = 1, BorderStyle = LineStyle.Dotted };
53+
Link linkWithBorder = new () { BorderStyle = LineStyle.Dotted };
54+
app.ToolTips!.SetToolTip (linkWithBorder, () => linkWithBorder.Url);
5655

57-
_link.TextChanged += (s, e) => simpleUrlLabel.Text = $"This is just a Label with a URL in Text (WT automatically enables URLs) - {_link.Text}";
58-
titleTextField.ValueChanged += (s, e) => _link.Title = e.NewValue ?? string.Empty;
59-
textTextField.ValueChanged += (s, e) => _link.Text = e.NewValue ?? string.Empty;
60-
urlTextField.ValueChanged += (s, e) => _link.Url = e.NewValue ?? Link.DEFAULT_URL;
61-
linkFrame.Add (_link);
56+
linkWithBorder.TextChanged +=
57+
(_, _) => simpleUrlLabel.Text = $"This is just a Label with a URL in Text (WT automatically enables URLs) - {linkWithBorder.Text}";
58+
titleTextField.ValueChanged += (_, e) => linkWithBorder.Title = e.NewValue ?? string.Empty;
59+
textTextField.ValueChanged += (_, e) => linkWithBorder.Text = e.NewValue ?? string.Empty;
60+
urlTextField.ValueChanged += (_, e) => linkWithBorder.Url = e.NewValue ?? string.Empty;
61+
linkFrame.Add (linkWithBorder);
6262

6363
titleTextField.Text = "Title";
6464
textTextField.Text = "GitHub repo";
6565
urlTextField.Text = "https://github.com/gui-cs/Terminal.Gui";
6666

67-
Button copyButton = new () { Title = "_Copy", X = Pos.Center (), Y = Pos.AnchorEnd () };
68-
copyButton.Accepting += (s, e) => _link.Copy ();
67+
Button copyButton = new () { Title = "_Copy", X = Pos.Right (linkWithBorder) + 1, Y = Pos.Top (linkWithBorder) + 1 };
68+
copyButton.Accepting += (_, _) => linkWithBorder.Copy ();
6969

7070
linkFrame.Add (copyButton);
7171

72-
_appWindow.Add (linkFrame);
72+
Label label = new () { Y = Pos.Bottom (linkFrame), Title = "_Link to API Docs:" };
73+
74+
Link link = new ()
75+
{
76+
X = Pos.Right (label) + 1, Y = Pos.Top (label), Text = "Terminal.Gui.Views.Link", Url = "https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui.Views.Link.html"
77+
};
78+
appWindow.Add (label, link);
79+
app.ToolTips!.SetToolTip (link, () => link.Url);
80+
81+
appWindow.Add (linkFrame);
7382

7483
// StatusBar
7584
Shortcut urlIndicator = new (Key.Empty, "", null);
7685

77-
StatusBar statusBar = new ([new Shortcut (Application.GetDefaultKey (Command.Quit), "Quit", Quit), urlIndicator]);
78-
_link.MouseEnter += (s, e) => urlIndicator.Title = _link.Text;
79-
_link.MouseLeave += (s, e) => urlIndicator.Title = "";
80-
_appWindow.Add (statusBar);
86+
StatusBar statusBar = new ([new Shortcut (Application.GetDefaultKey (Command.Quit), "Quit", () => appWindow.RequestStop ()), urlIndicator]);
8187

82-
_app.Run (_appWindow);
83-
_appWindow.Dispose ();
84-
}
88+
// Demonstrate dynamically showing URL in the status bar when hovering over the link.
89+
// Note that we use a Shortcut here to show how they can be used in a StatusBar, but you could use any View.
90+
linkWithBorder.MouseEnter += (_, _) => urlIndicator.Title = linkWithBorder.Url;
91+
linkWithBorder.MouseLeave += (_, _) => urlIndicator.Title = "";
8592

86-
private void Quit () => _appWindow?.RequestStop ();
93+
appWindow.Add (statusBar);
94+
95+
app.Run (appWindow);
96+
}
8797
}

0 commit comments

Comments
 (0)