From 84245d888fc497c0ef28376b3aa180df221ddeef Mon Sep 17 00:00:00 2001 From: Matt Parker <61717342+MattParkerDev@users.noreply.github.com> Date: Wed, 26 Nov 2025 18:13:37 +1000 Subject: [PATCH] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Improve=20Problem=20diagno?= =?UTF-8?q?stics=20display?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Features/Problems/ProblemsPanel.cs | 97 +++++++++++++++++-- .../Features/Problems/ProblemsPanel.tscn | 13 ++- 2 files changed, 98 insertions(+), 12 deletions(-) diff --git a/src/SharpIDE.Godot/Features/Problems/ProblemsPanel.cs b/src/SharpIDE.Godot/Features/Problems/ProblemsPanel.cs index 716937a..9cc8e25 100644 --- a/src/SharpIDE.Godot/Features/Problems/ProblemsPanel.cs +++ b/src/SharpIDE.Godot/Features/Problems/ProblemsPanel.cs @@ -28,6 +28,7 @@ public partial class ProblemsPanel : Control public override void _Ready() { + _diagnosticCustomDrawCallable = new Callable(this, MethodName.DiagnosticCustomDraw); _tree = GetNode("%Tree"); _tree.ItemActivated += TreeOnItemActivated; _rootItem = _tree.CreateItem(); @@ -78,19 +79,101 @@ public partial class ProblemsPanel : Control }); } + private Callable? _diagnosticCustomDrawCallable; + private TextLine _diagnosticTextLine = new TextLine(); // Reusing this is based on the assumption that it is called by godot in a single-threaded fashion + private void DiagnosticCustomDraw(TreeItem treeItem, Rect2 rect) + { + var hovered = _tree.GetItemAtPosition(_tree.GetLocalMousePosition()) == treeItem; + var isSelected = treeItem.IsSelected(0); + + var diagnosticContainer = treeItem.GetMetadata(0).As?>(); + if (diagnosticContainer is null) return; + + var diagnostic = diagnosticContainer.Item; + var message = diagnostic.Diagnostic.GetMessage(); + var severity = diagnostic.Diagnostic.Severity; + var linePosition = diagnostic.Span.Start; + + // Get icon based on severity + var icon = severity switch + { + DiagnosticSeverity.Error => ErrorIcon, + DiagnosticSeverity.Warning => WarningIcon, + _ => null + }; + + // Define padding and spacing + const float padding = 4.0f; + const float iconSize = 22.0f; + const float spacing = 6.0f; + + var currentX = rect.Position.X + padding; + var currentY = rect.Position.Y; + + // Draw icon + if (icon is not null) + { + var iconRect = new Rect2(currentX, currentY + (rect.Size.Y - iconSize) / 2, iconSize, iconSize); + _tree.DrawTextureRect(icon, iconRect, false); + currentX += iconSize + spacing; + } + + // Get font and prepare text + var font = _tree.GetThemeFont("font"); + var fontSize = _tree.GetThemeFontSize("font_size"); + var textColor = (isSelected, hovered) switch + { + (true, true) => _tree.GetThemeColor("font_hovered_selected_color"), + (true, false) => _tree.GetThemeColor("font_selected_color"), + (false, true) => _tree.GetThemeColor("font_hovered_color"), + (false, false) => _tree.GetThemeColor("font_color") + }; + var textYPos = currentY + (rect.Size.Y + fontSize) / 2 - 2; + + // Calculate right-hand text widths first + var fileName = Path.GetFileName(diagnostic.FilePath); + var fileNameWidth = font.GetStringSize(fileName, HorizontalAlignment.Left, -1, fontSize).X; + var locationText = $"({linePosition.Line + 1}:{linePosition.Character + 1})"; + var locationWidth = font.GetStringSize(locationText, HorizontalAlignment.Left, -1, fontSize).X; + var rightSideWidth = locationWidth + spacing + fileNameWidth + padding; + + var textLine = _diagnosticTextLine; + textLine.TextOverrunBehavior = TextServer.OverrunBehavior.TrimEllipsis; + textLine.SetHorizontalAlignment(HorizontalAlignment.Left); + textLine.AddString(message, font, fontSize); + + // Draw message with width constraint to avoid overlap + var maxMessageWidth = rect.Size.X - currentX - rightSideWidth - spacing; + if (maxMessageWidth > 0) + { + textLine.Width = maxMessageWidth; + textLine.Draw(_tree.GetCanvasItem(), new Vector2(currentX, textYPos - textLine.GetLineAscent()), textColor); + textLine.Clear(); + //_tree.DrawString(font, new Vector2(currentX, textYPos), message, HorizontalAlignment.Left, maxMessageWidth, fontSize, textColor); + } + + // Draw location info (line:column) on the right side + var locationX = rect.Position.X + rect.Size.X - rightSideWidth; + var locationColor = textColor with { A = 0.5f }; + _tree.DrawString(font, new Vector2(locationX, textYPos), locationText, HorizontalAlignment.Left, -1, fontSize, locationColor); + + // Draw file name on the right side, after the location + var fileNameX = locationX + locationWidth + spacing; + var fileNameColor = textColor with { A = 0.7f }; + _tree.DrawString(font, new Vector2(fileNameX, textYPos), fileName, HorizontalAlignment.Left, -1, fontSize, fileNameColor); + } + private async Task CreateDiagnosticTreeItem(Tree tree, TreeItem parent, ViewChangedEvent e) { await this.InvokeAsync(() => { var diagItem = tree.CreateItem(parent); - diagItem.SetText(0, e.NewItem.Value.Diagnostic.GetMessage()); + diagItem.SetCellMode(0, TreeItem.TreeCellMode.Custom); + diagItem.SetCustomAsButton(0, true); + diagItem.SetTooltipText(0, e.NewItem.Value.Diagnostic.GetMessage()); diagItem.SetMetadata(0, new RefCountedContainer(e.NewItem.Value)); - diagItem.SetIcon(0, e.NewItem.Value.Diagnostic.Severity switch - { - DiagnosticSeverity.Error => ErrorIcon, - DiagnosticSeverity.Warning => WarningIcon, - _ => null - }); + // Avoid allocation via Callable.From((TreeItem s, Rect2 x) => CustomDraw(s, x)) + diagItem.SetCustomDrawCallback(0, _diagnosticCustomDrawCallable!.Value); e.NewItem.View.Value = diagItem; }); } diff --git a/src/SharpIDE.Godot/Features/Problems/ProblemsPanel.tscn b/src/SharpIDE.Godot/Features/Problems/ProblemsPanel.tscn index 0785710..2c3d199 100644 --- a/src/SharpIDE.Godot/Features/Problems/ProblemsPanel.tscn +++ b/src/SharpIDE.Godot/Features/Problems/ProblemsPanel.tscn @@ -6,6 +6,10 @@ [ext_resource type="Texture2D" uid="uid://cqt30ma6xgder" path="res://Features/SolutionExplorer/Resources/Csproj.svg" id="4_wqdox"] [sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_wqdox"] +content_margin_left = 5.0 +content_margin_top = 5.0 +content_margin_right = 5.0 +content_margin_bottom = 5.0 [node name="ProblemsPanel" type="Control"] layout_mode = 3 @@ -42,15 +46,14 @@ text = "Problems" [node name="HSeparator" type="HSeparator" parent="VBoxContainer"] layout_mode = 2 -[node name="ScrollContainer" type="ScrollContainer" parent="VBoxContainer"] -layout_mode = 2 -size_flags_vertical = 3 - -[node name="Tree" type="Tree" parent="VBoxContainer/ScrollContainer"] +[node name="Tree" type="Tree" parent="VBoxContainer"] unique_name_in_owner = true layout_mode = 2 size_flags_horizontal = 3 size_flags_vertical = 3 +theme_override_constants/inner_item_margin_left = 5 +theme_override_constants/inner_item_margin_right = 5 theme_override_constants/draw_guides = 0 theme_override_styles/panel = SubResource("StyleBoxEmpty_wqdox") hide_root = true +scroll_horizontal_enabled = false