From 57f109ced89b7d6068e0b93ca39f32184c3cad5b Mon Sep 17 00:00:00 2001 From: Shman4ik Date: Thu, 1 Oct 2026 09:14:43 +0200 Subject: [PATCH] fix(ui): a cut status line shows its whole text on hover MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A failed safe-mode commit read "Commit failed…" in a narrow window with no way to see why. The main status text had a ToolTip.Tip bound to the full message since the bar was built, but a TextBlock with no Background is not hit-testable in Avalonia: every point over the text reached the status bar behind it, so the tooltip never opened. The segment tooltips beside it (read-only reason, In transaction, staged changes) opened only over an icon, and the Activity, Database Overview and Security windows' status lines had no tooltip and no ellipsis at all. A server error's DETAIL arrives after a newline, so the bar also grew a second line, each cut on its own. TextBlock.statusMessage is the one mechanism: a single line (MaxLines=1) cut with an ellipsis, a transparent background, and the whole text as its tooltip only while it is cut (Converters/CutTextTip). Status-bar segments get a transparent background too. No new control. Co-Authored-By: Claude Opus 5.5 --- .claude/CLAUDE.md | 18 ++ src/PgNimbus.App/Converters/CutTextTip.cs | 31 +++ src/PgNimbus.App/Styles/Theme.axaml | 35 +++- src/PgNimbus.App/Views/ActivityWindow.axaml | 2 +- .../Views/DatabaseOverviewWindow.axaml | 2 +- src/PgNimbus.App/Views/MainWindow.axaml | 16 +- .../Views/Security/SecurityWindow.axaml | 2 +- tests/PgNimbus.App.Tests/StatusLineTests.cs | 179 ++++++++++++++++++ 8 files changed, 272 insertions(+), 13 deletions(-) create mode 100644 src/PgNimbus.App/Converters/CutTextTip.cs create mode 100644 tests/PgNimbus.App.Tests/StatusLineTests.cs diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index b0ce666c..0d47c31d 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -1425,6 +1425,24 @@ Three rules about it: vocabulary is app-wide across the secondary windows and dialogs; the main window's command bar deliberately keeps its flat minimalist `toolbar` buttons (rule 1) and is the one surface exempt. + **A status line is `TextBlock.statusMessage`** (`Styles/Theme.axaml`, + 2026-10, 1.0.1 release pass): one line (`MaxLines=1`: Npgsql puts a server + error's `DETAIL` after a newline, which grew the bar a line), cut with an + ellipsis, and its whole text in a tooltip only while it is cut + (`Converters/CutTextTip`, which reads the block's `TextLayout` for a + collapsed line or lines left out). The main window's message and + cap warning, and the Activity, Database Overview and Security windows' status + lines use it; Slow Queries keeps its wrapping line, a paragraph of caveats + meant to be read. Found because a failed safe-mode commit read "Commit + failed…" in a narrow window with no way to see why: the main message had + carried a `ToolTip.Tip` since the bar was built, and it never once opened. + **Landmine: a `TextBlock` or panel with no `Background` is not hit-testable** + (not even over its glyphs: 0 of 4,536 points over the status text reached + it), so a tooltip on it is dead and the pointer lands on whatever has a + background behind it. The style sets `Background="Transparent"`, and so does + `Border.statusBar StackPanel`, whose segment tooltips (read-only reason, In + transaction, staged changes) had opened only over an icon's ink. + `StatusLineTests` hovers each with real pointer input. **Surfaces and dialogs have a vocabulary too** (2026-09, DESIGN.md rules 15 and 16). Every secondary window sits on the shell tone — a `Window` style in `Styles/Theme.axaml` sets it, and the Inter font, so no window sets either — diff --git a/src/PgNimbus.App/Converters/CutTextTip.cs b/src/PgNimbus.App/Converters/CutTextTip.cs new file mode 100644 index 00000000..edfb27f7 --- /dev/null +++ b/src/PgNimbus.App/Converters/CutTextTip.cs @@ -0,0 +1,31 @@ +using System.Globalization; +using Avalonia.Controls; +using Avalonia.Data.Converters; + +namespace PgNimbus.App.Converters; + +/// +/// The tooltip of a TextBlock.statusMessage: its whole text while the +/// line is cut with an ellipsis, and nothing while it fits (a tooltip repeating +/// what is already on screen is noise). Bound as a multi-binding over the text +/// block itself, its text and its width, so it is asked again whenever either +/// changes; by then the block's layout reflects the new width. +/// +public sealed class CutTextTip : IMultiValueConverter +{ + public static readonly CutTextTip Instance = new(); + + public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) => + values is [TextBlock { Text: { Length: > 0 } text } block, ..] && IsCut(block) ? text : null; + + /// + /// Whether is showing less than its text: a line + /// ended in an ellipsis, or lines left out by MaxLines. + /// + public static bool IsCut(TextBlock block) + { + var lines = block.TextLayout.TextLines; + return lines.Any(line => line.HasCollapsed) + || lines.Sum(line => line.Length) < (block.Text?.Length ?? 0); + } +} diff --git a/src/PgNimbus.App/Styles/Theme.axaml b/src/PgNimbus.App/Styles/Theme.axaml index e64941cd..5c5292c7 100644 --- a/src/PgNimbus.App/Styles/Theme.axaml +++ b/src/PgNimbus.App/Styles/Theme.axaml @@ -3,7 +3,8 @@ xmlns:cc="clr-namespace:AvaloniaEdit.CodeCompletion;assembly=AvaloniaEdit" xmlns:search="clr-namespace:AvaloniaEdit.Search;assembly=AvaloniaEdit" xmlns:completion="using:PgNimbus.App.Completion" - xmlns:cmd="using:PgNimbus.App"> + xmlns:cmd="using:PgNimbus.App" + xmlns:conv="using:PgNimbus.App.Converters"> + + + + + diff --git a/src/PgNimbus.App/Views/ActivityWindow.axaml b/src/PgNimbus.App/Views/ActivityWindow.axaml index 523be1b3..7748aa19 100644 --- a/src/PgNimbus.App/Views/ActivityWindow.axaml +++ b/src/PgNimbus.App/Views/ActivityWindow.axaml @@ -166,7 +166,7 @@ - + diff --git a/src/PgNimbus.App/Views/DatabaseOverviewWindow.axaml b/src/PgNimbus.App/Views/DatabaseOverviewWindow.axaml index 1dfd2323..a958cad5 100644 --- a/src/PgNimbus.App/Views/DatabaseOverviewWindow.axaml +++ b/src/PgNimbus.App/Views/DatabaseOverviewWindow.axaml @@ -109,7 +109,7 @@ - + diff --git a/src/PgNimbus.App/Views/MainWindow.axaml b/src/PgNimbus.App/Views/MainWindow.axaml index 75baed20..016c0622 100644 --- a/src/PgNimbus.App/Views/MainWindow.axaml +++ b/src/PgNimbus.App/Views/MainWindow.axaml @@ -522,15 +522,14 @@ + (statusMessage: full text on hover while it is cut), while every + other segment is compact and keeps its width. Long cap text + truncates the same way. --> + Classes="statusText statusMessage" Classes.error="{Binding ActiveTab.HasError}" + VerticalAlignment="Center" HorizontalAlignment="Left" /> - +