mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2026-08-15 13:04:36 +00:00
Add tooltip service and integrate with MinimapControl
- Introduced TuiTooltipService for managing tooltips across TUI components. - Updated MinimapControl to utilize the new tooltip service for enhanced entity information display. - Refactored tooltip rendering logic to improve visibility and interaction based on mouse position.
This commit is contained in:
parent
0566f2518b
commit
d427a6e160
3 changed files with 194 additions and 73 deletions
|
|
@ -45,6 +45,8 @@ namespace MinecraftClient.Tui
|
|||
private readonly MinimapControl _minimapControl;
|
||||
private volatile bool _minimapVisible;
|
||||
|
||||
private TuiTooltipService? _tooltipService;
|
||||
|
||||
private readonly Border _suggestionBorder;
|
||||
private readonly StackPanel _suggestionPanel;
|
||||
private CommandSuggestion[] _suggestions = Array.Empty<CommandSuggestion>();
|
||||
|
|
@ -57,6 +59,8 @@ namespace MinecraftClient.Tui
|
|||
private int MaxVisibleSuggestions =>
|
||||
Math.Max(1, Settings.Config.Console.CommandSuggestion.Max_Displayed_Suggestions);
|
||||
|
||||
public TuiTooltipService? TooltipService => _tooltipService;
|
||||
|
||||
public MainTuiView()
|
||||
{
|
||||
Background = Brushes.Black;
|
||||
|
|
@ -194,6 +198,10 @@ namespace MinecraftClient.Tui
|
|||
Children = { _mainContent, _minimapBorder, _notificationBorder, _suggestionBorder }
|
||||
};
|
||||
|
||||
_tooltipService = new TuiTooltipService(_rootPanel);
|
||||
_minimapControl.TooltipService = _tooltipService;
|
||||
_minimapControl.Position = mmCfg.Position;
|
||||
|
||||
Content = _rootPanel;
|
||||
|
||||
if (mmCfg.Enabled)
|
||||
|
|
@ -1083,6 +1091,7 @@ namespace MinecraftClient.Tui
|
|||
_minimapBorder.HorizontalAlignment = hAlign;
|
||||
_minimapBorder.VerticalAlignment = vAlign;
|
||||
_minimapBorder.Margin = margin;
|
||||
_minimapControl.Position = pos;
|
||||
Settings.Config.Console.Minimap.Position = pos;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -45,12 +45,11 @@ namespace MinecraftClient.Tui
|
|||
private readonly Grid _mapGrid;
|
||||
private readonly DispatcherTimer _timer;
|
||||
|
||||
private readonly Canvas _tooltipCanvas;
|
||||
private readonly Border _tooltipBorder;
|
||||
private readonly StackPanel _tooltipContent;
|
||||
private SampleResult? _lastResult;
|
||||
private int _hoverCol = -1;
|
||||
private int _hoverRow = -1;
|
||||
private double _hoverGlobalX;
|
||||
private double _hoverGlobalY;
|
||||
|
||||
public int BlocksPerPixel
|
||||
{
|
||||
|
|
@ -60,6 +59,10 @@ namespace MinecraftClient.Tui
|
|||
|
||||
public NameDisplayConfig NameConfig => _nameConfig;
|
||||
|
||||
public TuiTooltipService? TooltipService { get; set; }
|
||||
|
||||
public MinimapPosition Position { get; set; } = MinimapPosition.top_right;
|
||||
|
||||
public int MapPixelWidth => _mapWidth;
|
||||
public int MapPixelHeight => _mapHeight;
|
||||
|
||||
|
|
@ -83,33 +86,10 @@ namespace MinecraftClient.Tui
|
|||
_infoRow = new StackPanel { Orientation = Orientation.Horizontal };
|
||||
_legendPanel = new StackPanel { Orientation = Orientation.Horizontal };
|
||||
|
||||
_tooltipContent = new StackPanel { Orientation = Orientation.Vertical };
|
||||
_tooltipBorder = new Border
|
||||
{
|
||||
Background = new SolidColorBrush(Color.FromArgb(230, 20, 20, 20)),
|
||||
BorderBrush = new SolidColorBrush(Color.FromRgb(120, 120, 120)),
|
||||
BorderThickness = new Thickness(1),
|
||||
Padding = new Thickness(1),
|
||||
Child = _tooltipContent,
|
||||
IsVisible = false,
|
||||
};
|
||||
|
||||
_tooltipCanvas = new Canvas
|
||||
{
|
||||
IsHitTestVisible = false,
|
||||
Children = { _tooltipBorder },
|
||||
};
|
||||
|
||||
var mapLayer = new Panel
|
||||
{
|
||||
ClipToBounds = true,
|
||||
Children = { _mapGrid, _tooltipCanvas },
|
||||
};
|
||||
|
||||
var root = new StackPanel
|
||||
{
|
||||
Orientation = Orientation.Vertical,
|
||||
Children = { mapLayer, _infoRow, _legendPanel },
|
||||
Children = { _mapGrid, _infoRow, _legendPanel },
|
||||
};
|
||||
|
||||
Content = root;
|
||||
|
|
@ -236,6 +216,7 @@ namespace MinecraftClient.Tui
|
|||
{
|
||||
public string Name = "";
|
||||
public MobCategory Category;
|
||||
public double X, Y, Z;
|
||||
public float Health;
|
||||
public float MaxHealth;
|
||||
public int Priority;
|
||||
|
|
@ -352,6 +333,9 @@ namespace MinecraftClient.Tui
|
|||
{
|
||||
Name = eName,
|
||||
Category = cat,
|
||||
X = entity.Location.X,
|
||||
Y = entity.Location.Y,
|
||||
Z = entity.Location.Z,
|
||||
Health = entity.Health,
|
||||
MaxHealth = -1,
|
||||
Priority = priority,
|
||||
|
|
@ -379,6 +363,9 @@ namespace MinecraftClient.Tui
|
|||
{
|
||||
Name = client.GetUsername(),
|
||||
Category = MobCategory.Player,
|
||||
X = playerLoc.X,
|
||||
Y = playerLoc.Y,
|
||||
Z = playerLoc.Z,
|
||||
Health = client.GetHealth(),
|
||||
MaxHealth = 20f,
|
||||
Priority = 5,
|
||||
|
|
@ -700,6 +687,19 @@ namespace MinecraftClient.Tui
|
|||
|
||||
_hoverCol = col;
|
||||
_hoverRow = row;
|
||||
|
||||
if (this.VisualRoot is Visual root
|
||||
&& _mapGrid.TranslatePoint(pos, root) is { } gp)
|
||||
{
|
||||
_hoverGlobalX = gp.X;
|
||||
_hoverGlobalY = gp.Y;
|
||||
}
|
||||
else
|
||||
{
|
||||
_hoverGlobalX = pos.X;
|
||||
_hoverGlobalY = pos.Y;
|
||||
}
|
||||
|
||||
UpdateTooltip(col, row);
|
||||
}
|
||||
|
||||
|
|
@ -712,13 +712,14 @@ namespace MinecraftClient.Tui
|
|||
{
|
||||
_hoverCol = -1;
|
||||
_hoverRow = -1;
|
||||
_tooltipBorder.IsVisible = false;
|
||||
TooltipService?.Hide();
|
||||
}
|
||||
|
||||
private void UpdateTooltip(int col, int row)
|
||||
{
|
||||
var svc = TooltipService;
|
||||
var result = _lastResult;
|
||||
if (result is null) { _tooltipBorder.IsVisible = false; return; }
|
||||
if (svc is null || result is null) { svc?.Hide(); return; }
|
||||
|
||||
int bpp = result.Bpp;
|
||||
int centerX = result.CenterX;
|
||||
|
|
@ -731,7 +732,7 @@ namespace MinecraftClient.Tui
|
|||
int baseZ_top = result.PlayerBlockZ + (topPixelY - centerY) * bpp;
|
||||
int baseZ_bot = result.PlayerBlockZ + (botPixelY - centerY) * bpp;
|
||||
|
||||
_tooltipContent.Children.Clear();
|
||||
var lines = new List<TuiTooltipLine>();
|
||||
|
||||
if (bpp == 1)
|
||||
{
|
||||
|
|
@ -741,7 +742,7 @@ namespace MinecraftClient.Tui
|
|||
string coordLine = baseZ_top == baseZ_bot
|
||||
? $"{baseX}, {surfY_top}, {baseZ_top}"
|
||||
: $"{baseX}, {surfY_top}, {baseZ_top} / {baseX}, {surfY_bot}, {baseZ_bot}";
|
||||
_tooltipContent.Children.Add(MakeTooltipText(coordLine, Brushes.White));
|
||||
lines.Add(new TuiTooltipLine { Text = coordLine, Foreground = Brushes.White });
|
||||
|
||||
if (result.BlockTypes is not null)
|
||||
{
|
||||
|
|
@ -751,7 +752,7 @@ namespace MinecraftClient.Tui
|
|||
string blockLine = mat_top == mat_bot
|
||||
? FormatMaterialName(mat_top)
|
||||
: $"{FormatMaterialName(mat_top)} / {FormatMaterialName(mat_bot)}";
|
||||
_tooltipContent.Children.Add(MakeTooltipText(blockLine, Brushes.LightGray));
|
||||
lines.Add(new TuiTooltipLine { Text = blockLine, Foreground = Brushes.LightGray });
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -759,33 +760,40 @@ namespace MinecraftClient.Tui
|
|||
int endX = baseX + bpp - 1;
|
||||
int endZ_bot = baseZ_bot + bpp - 1;
|
||||
string coordLine = $"X {baseX}~{endX} Z {baseZ_top}~{endZ_bot}";
|
||||
_tooltipContent.Children.Add(MakeTooltipText(coordLine, Brushes.White));
|
||||
lines.Add(new TuiTooltipLine { Text = coordLine, Foreground = Brushes.White });
|
||||
|
||||
AppendBlockSummary(result, col, topPixelY, botPixelY);
|
||||
AppendBlockSummaryLines(result, col, topPixelY, botPixelY, lines);
|
||||
}
|
||||
|
||||
AppendEntityInfo(result, col, topPixelY, botPixelY);
|
||||
AppendEntityInfoLines(result, col, topPixelY, botPixelY, lines);
|
||||
|
||||
if (_tooltipContent.Children.Count == 0)
|
||||
if (lines.Count == 0)
|
||||
{
|
||||
_tooltipBorder.IsVisible = false;
|
||||
svc.Hide();
|
||||
return;
|
||||
}
|
||||
|
||||
int maxTipW = Math.Max(10, _mapWidth / 2 - 2);
|
||||
_tooltipBorder.MaxWidth = maxTipW;
|
||||
_tooltipBorder.MaxHeight = _cellRows;
|
||||
bool preferRight = Position switch
|
||||
{
|
||||
MinimapPosition.top_left or MinimapPosition.bottom_left => true,
|
||||
MinimapPosition.top_right or MinimapPosition.bottom_right => false,
|
||||
_ => true,
|
||||
};
|
||||
|
||||
bool showRight = col < _mapWidth / 2;
|
||||
int tipX = showRight ? col + 2 : Math.Max(0, col - maxTipW - 1);
|
||||
int tipY = Math.Clamp(row, 0, _cellRows - 1);
|
||||
double mx = _hoverGlobalX;
|
||||
double my = _hoverGlobalY;
|
||||
|
||||
Canvas.SetLeft(_tooltipBorder, tipX);
|
||||
Canvas.SetTop(_tooltipBorder, tipY);
|
||||
_tooltipBorder.IsVisible = true;
|
||||
if (Position == MinimapPosition.center
|
||||
&& this.VisualRoot is Visual root)
|
||||
{
|
||||
preferRight = mx < root.Bounds.Width / 2;
|
||||
}
|
||||
|
||||
svc.Show(mx, my, lines, preferRight);
|
||||
}
|
||||
|
||||
private void AppendBlockSummary(SampleResult result, int col, int topPy, int botPy)
|
||||
private void AppendBlockSummaryLines(SampleResult result, int col, int topPy, int botPy,
|
||||
List<TuiTooltipLine> lines)
|
||||
{
|
||||
if (result.BlockSummary is null) return;
|
||||
|
||||
|
|
@ -797,8 +805,6 @@ namespace MinecraftClient.Tui
|
|||
if (merged.Count == 0) return;
|
||||
|
||||
var sorted = merged.OrderByDescending(kv => kv.Value).Take(4);
|
||||
int totalSamples = 0;
|
||||
foreach (var kv in merged) totalSamples += kv.Value;
|
||||
|
||||
var parts = new List<string>();
|
||||
foreach (var kv in sorted)
|
||||
|
|
@ -811,8 +817,11 @@ namespace MinecraftClient.Tui
|
|||
|
||||
if (parts.Count == 0) return;
|
||||
|
||||
string line = string.Join(", ", parts);
|
||||
_tooltipContent.Children.Add(MakeTooltipText(line, Brushes.LightGray));
|
||||
lines.Add(new TuiTooltipLine
|
||||
{
|
||||
Text = string.Join(", ", parts),
|
||||
Foreground = Brushes.LightGray,
|
||||
});
|
||||
}
|
||||
|
||||
private static void MergeBlockCounts(List<(Material Mat, int Count)>?[,] summary,
|
||||
|
|
@ -829,7 +838,8 @@ namespace MinecraftClient.Tui
|
|||
}
|
||||
}
|
||||
|
||||
private void AppendEntityInfo(SampleResult result, int col, int topPy, int botPy)
|
||||
private static void AppendEntityInfoLines(SampleResult result, int col, int topPy, int botPy,
|
||||
List<TuiTooltipLine> lines)
|
||||
{
|
||||
var entityMap = result.EntityMap;
|
||||
if (entityMap is null) return;
|
||||
|
|
@ -851,19 +861,20 @@ namespace MinecraftClient.Tui
|
|||
if (!seen.Add(key)) continue;
|
||||
|
||||
var catColor = MinimapEntityClassifier.GetBaseColor(ent.Category);
|
||||
string hpStr;
|
||||
string coordStr = $"({ent.X:F1}, {ent.Y:F1}, {ent.Z:F1})";
|
||||
string hpStr = "";
|
||||
if (ent.Health > 0)
|
||||
{
|
||||
hpStr = ent.MaxHealth > 0
|
||||
? $" HP:{ent.Health:F0}/{ent.MaxHealth:F0}"
|
||||
: $" HP:{ent.Health:F0}";
|
||||
? $" HP:{ent.Health:F0}/{ent.MaxHealth:F0}"
|
||||
: $" HP:{ent.Health:F0}";
|
||||
}
|
||||
else
|
||||
hpStr = "";
|
||||
|
||||
_tooltipContent.Children.Add(MakeTooltipText(
|
||||
$"{ent.Name}{hpStr}",
|
||||
new SolidColorBrush(catColor)));
|
||||
lines.Add(new TuiTooltipLine
|
||||
{
|
||||
Text = $"{ent.Name} {coordStr}{hpStr}",
|
||||
Foreground = new SolidColorBrush(catColor),
|
||||
});
|
||||
shown++;
|
||||
}
|
||||
}
|
||||
|
|
@ -879,19 +890,6 @@ namespace MinecraftClient.Tui
|
|||
}
|
||||
}
|
||||
|
||||
private static TextBlock MakeTooltipText(string text, IBrush foreground)
|
||||
{
|
||||
return new TextBlock
|
||||
{
|
||||
Text = text,
|
||||
Foreground = foreground,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Padding = new Thickness(0),
|
||||
Margin = new Thickness(0),
|
||||
FontSize = 1,
|
||||
};
|
||||
}
|
||||
|
||||
private static string FormatMaterialName(Material mat)
|
||||
{
|
||||
if (mat == Material.Air) return "Air";
|
||||
|
|
|
|||
114
MinecraftClient/Tui/TuiTooltipService.cs
Normal file
114
MinecraftClient/Tui/TuiTooltipService.cs
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace MinecraftClient.Tui
|
||||
{
|
||||
public sealed class TuiTooltipLine
|
||||
{
|
||||
public string Text { get; init; } = "";
|
||||
public IBrush Foreground { get; init; } = Brushes.White;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Global tooltip that floats above all TUI content.
|
||||
/// Owned by MainTuiView, used by minimap / chat / other components.
|
||||
/// </summary>
|
||||
public sealed class TuiTooltipService
|
||||
{
|
||||
private readonly Panel _rootPanel;
|
||||
private readonly Canvas _canvas;
|
||||
private readonly Border _border;
|
||||
private readonly StackPanel _content;
|
||||
|
||||
internal TuiTooltipService(Panel rootPanel)
|
||||
{
|
||||
_content = new StackPanel { Orientation = Avalonia.Layout.Orientation.Vertical };
|
||||
_border = new Border
|
||||
{
|
||||
Background = new SolidColorBrush(Color.FromArgb(230, 20, 20, 20)),
|
||||
BorderBrush = new SolidColorBrush(Color.FromRgb(120, 120, 120)),
|
||||
BorderThickness = new Thickness(1),
|
||||
Padding = new Thickness(1),
|
||||
Child = _content,
|
||||
IsVisible = false,
|
||||
};
|
||||
|
||||
_canvas = new Canvas
|
||||
{
|
||||
IsHitTestVisible = false,
|
||||
Children = { _border },
|
||||
};
|
||||
|
||||
_rootPanel = rootPanel;
|
||||
rootPanel.Children.Add(_canvas);
|
||||
}
|
||||
|
||||
/// <param name="mouseX">Global X of the mouse cursor.</param>
|
||||
/// <param name="mouseY">Global Y of the mouse cursor.</param>
|
||||
/// <param name="preferRight">
|
||||
/// If true, try placing tooltip to the right of mouseX;
|
||||
/// if false, try placing to the left.
|
||||
/// The service auto-flips when the tooltip would overflow the screen.
|
||||
/// </param>
|
||||
public void Show(double mouseX, double mouseY, IReadOnlyList<TuiTooltipLine> lines,
|
||||
bool preferRight = true)
|
||||
{
|
||||
_content.Children.Clear();
|
||||
|
||||
if (lines.Count == 0)
|
||||
{
|
||||
_border.IsVisible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
int maxChars = 0;
|
||||
foreach (var line in lines)
|
||||
{
|
||||
_content.Children.Add(new TextBlock
|
||||
{
|
||||
Text = line.Text,
|
||||
Foreground = line.Foreground,
|
||||
TextWrapping = TextWrapping.Wrap,
|
||||
Padding = new Thickness(0),
|
||||
Margin = new Thickness(0),
|
||||
FontSize = 1,
|
||||
});
|
||||
if (line.Text.Length > maxChars)
|
||||
maxChars = line.Text.Length;
|
||||
}
|
||||
|
||||
double tipW = maxChars + 4;
|
||||
double screenW = _rootPanel.Bounds.Width;
|
||||
|
||||
const double gap = 1;
|
||||
double gx;
|
||||
if (preferRight)
|
||||
{
|
||||
gx = mouseX + gap;
|
||||
if (gx + tipW > screenW)
|
||||
gx = mouseX - tipW - gap;
|
||||
}
|
||||
else
|
||||
{
|
||||
gx = mouseX - tipW - gap;
|
||||
if (gx < 0)
|
||||
gx = mouseX + gap;
|
||||
}
|
||||
|
||||
Canvas.SetLeft(_border, Math.Max(0, gx));
|
||||
Canvas.SetTop(_border, Math.Max(0, mouseY));
|
||||
_border.IsVisible = true;
|
||||
}
|
||||
|
||||
public void Hide()
|
||||
{
|
||||
_border.IsVisible = false;
|
||||
_content.Children.Clear();
|
||||
}
|
||||
|
||||
public bool IsVisible => _border.IsVisible;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue