Tooltips (SusKit)
服务:
TooltipService(sus-kit/Runtime/Services/) 扩展:TooltipExtensions.WithTooltip()(sus-kit/Runtime/Extensions/) 模型:SusTooltipCardEntry(sus-kit/Runtime/Models/)
1. 用途
Tooltip 在鼠标悬停在 UI 元素上时出现。它们通过共享传送门 OverlayHost(分类 Tooltip = 30)渲染,不会被父元素裁剪。
两种模式(外加自定义卡片):
- Text(
ShowAtElement)是一个简单字符串,相对于 目标VisualElement定位。 - Rich(
ShowRich)——带标题、描述、图标的卡片 (SusTooltipCardEntry)。 - Custom(
ShowContent)——任意VisualElement,通过Func<VisualElement>惰性构建,定位方式与 text tooltip 相同(auto-flip + clamp)。 当卡片不是SusTooltip/SusTooltipCard时使用(例如游戏特定的 道具装饰)。也可通过WithTooltip(Func<VisualElement>, placement)暴露。
显示延迟(500ms)和隐藏延迟(200ms)可通过 ShowDelayMs / HideDelayMs 调整。同一时刻只有一个 tooltip 处于活动状态——再次调用会 取消上一个。
2. 架构
当前实现:
TooltipService对 rich tooltip 直接使用OverlayHost。 组件 tooltip(SusTooltip.sharq)使用SusOverlayManager.ShowTooltip()——一个集中式包装层,位于 OverlayHost 之上, 会复制样式表(CopyAncestorStyleSheets)。
OverlayHost (portal, auto-BringToFront via GeometryChangedEvent)
├── [World = 0] healthbar / nameplate
├── [Transition = 10] curtain
├── [Modal = 20] dialogues
├── [Tooltip = 30] ← TOOLTIPS HERE (SusOverlayManager.ShowTooltip)
├── [Dropdown = 40] ← Select/Dropdown (SusOverlayManager.Show)
├── [Toast = 45] snackbars
└── [Console = 50] dev-console- 样式复制:
OverlayHost.AddToOverlay()调用CopyAncestorStyleSheets(), 该方法在元素被RemoveFromHierarchy()分离之前,从最近的SusComponent祖先复制.g.uss样式表。这保证了 tooltip 卡片在被传送到 overlay 之后 仍保留其样式。 - 重要:
ShowTooltip()在AddToOverlay()之前不会调用RemoveFromHierarchy()——否则会在样式复制之前破坏祖先链。 design-tokens.uss和suskit-base.uss直接加载到OverlayHost本身——CSS 变量和基础样式对 overlay 的每个子元素都可见。
3. TooltipService API
文件: sus-kit/Runtime/Services/TooltipService.cs
初始化
var tooltipService = new TooltipService();
tooltipService.Init(overlayHost); // OverlayHost for portal rendering
TooltipService.Instance = tooltipService; // Singleton for WithTooltip
// Factories (Sharq components live in the CompiledUI assembly and are not directly accessible)
tooltipService.CreateTooltip = () => new SusTooltip();
tooltipService.CreateCard = () => new SusTooltipCard();方法
| 方法 | 签名 | 说明 |
|---|---|---|
ShowAtElement | (VisualElement target, string text, TooltipPlacement placement = Auto) | 元素上的文本 tooltip。延迟:ShowDelayMs(默认 500ms)。内部使用 ShowContent + CreateTooltip。 |
ShowContent | (VisualElement target, Func<VisualElement> contentFactory, TooltipPlacement placement = Auto) | OverlayHost Tooltip 层上的自定义卡片。显示延迟后惰性调用工厂;placement / auto-flip 规则与 text 相同。 |
ShowRich | (SusTooltipCardEntry entry, Vector2? screenPosition = null) | 通过 CreateCard + Apply(entry) 生成的 rich 卡片。若未指定位置——居中显示在屏幕上。 |
Hide | () | 立即隐藏。 |
HideAfterDelay | () | 经过 HideDelayMs(默认 200ms)后隐藏。用于 PointerLeave。 |
属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
Instance | TooltipService (static) | — | 供 WithTooltip 使用的单例 |
ShowDelayMs | Prop<float> | 500 | 显示前的延迟(ms) |
HideDelayMs | Prop<float> | 200 | 隐藏前的延迟(ms) |
Overlay | OverlayHost | — | 传送门容器 |
CreateTooltip | Func<VisualElement> | — | SusTooltip(Sharq)的工厂 |
CreateCard | Func<VisualElement> | — | SusTooltipCard(Sharq)的工厂 |
IsVisible | bool(只读) | — | 当前是否有 tooltip 处于活动状态 |
4. 定位与 auto-flip
TooltipPlacement
public enum TooltipPlacement
{
Auto, // Tries from bottom → top → right → left
Top,
Bottom,
Left,
Right
}Auto 算法
- Bottom:若
target.bottom + tooltip.height + 4px≤ 面板高度 →Bottom。 - Top:若
target.top - tooltip.height - 4px≥ 0 →Top。 - Right:若
target.right + tooltip.width + 4px≤ 面板宽度 →Right。 - Left:兜底方案。
边缘限制(Clamping)
计算出位置后,tooltip 会被限制在面板边缘内(4px 边距):
x = Mathf.Clamp(x, 4f, panelWidth - tooltipWidth - 4f);
y = Mathf.Clamp(y, 4f, panelHeight - tooltipHeight - 4f);用于测试的纯函数
// Resolve placement (no panel)
TooltipPlacement resolved = TooltipService.ResolvePlacement(
targetRect, tooltipSize, panelSize, TooltipPlacement.Auto);
// Resolve position (in panel coordinates)
Vector2 pos = TooltipService.ResolvePosition(
targetRect, tooltipSize, panelSize, TooltipPlacement.Bottom);5. Fluent 扩展 WithTooltip
文件: sus-kit/Runtime/Extensions/TooltipExtensions.cs
一种便捷方式,可以给任意 VisualElement 挂上 tooltip:
// Simple text
myButton.WithTooltip("Save game");
// With explicit placement
iconButton.WithTooltip("Settings", TooltipPlacement.Right);
// Rich card
var entry = new SusTooltipCardEntry
{
Title = "Fireball",
Description = "Damage: 50-70\nRange: 3 cells\nCooldown: 2 turns",
IconAlias = "fire",
};
spellIcon.WithTooltip(entry);
// Custom VisualElement (lazy factory) — any card on the OverlayHost Tooltip layer
slot.WithTooltip(() => BuildItemTooltip(item), TooltipPlacement.Right);工作原理:
PointerEnterEvent→service.ShowAtElement/ShowContent(在ShowDelayMs之后)。PointerLeaveEvent→service.HideAfterDelay()(在HideDelayMs之后)。- 元素被分离时(
DetachFromPanelEvent)→service.Hide()。
ShowContent(target, contentFactory, placement) 会在显示延迟后构建卡片,并像 text tooltip 一样定位(auto-flip + clamp)。
6. SusTooltipCardEntry
文件: sus-kit/Runtime/Models/SusTooltipCardEntry.cs
public class SusTooltipCardEntry
{
public string Title; // Heading
public string Description; // Description (multiline)
public string IconAlias; // Alias of an icon from SusIconRegistry, e.g. "info-circle"
public string FooterText; // Footer text
}与 ShowRich 一起使用:
service.ShowRich(new SusTooltipCardEntry
{
Title = "Sword of justice",
Description = "Attack: 15-20\nType: slashing",
IconAlias = "sword",
FooterText = "One-handed"
});7. 与 router 集成
路由切换时,tooltip 会通过 beforeEach guard 被清除:
// In SusRouter.Init()
ModalService.CloseAll(); // modals
TooltipService.Instance?.Hide(); // active tooltip当目标元素被分离时,tooltip 会自动隐藏 (WithTooltip 监听 DetachFromPanelEvent)。
主题化 (--sus-tooltip-*)
定义于 SusTooltip.sharq 的 <style> 中(在项目 USS 里通过 .sus-tooltip__card 覆盖):
| 令牌 | 默认作用 |
|---|---|
--sus-tooltip-bg | 卡片背景 |
--sus-tooltip-radius | 圆角半径 |
--sus-tooltip-border / --sus-tooltip-border-hover | 描边 |
--sus-tooltip-pad | 卡片内边距 |
--sus-tooltip-header-bg | 头部条(也受稀有度影响) |
tip + card 上的稀有度修饰符:sus-tooltip--rarity-common|uncommon|rare|epic|legendary|exotic (由库存 / 道具卡片在 hover 时设置)。头部条颜色来自 suskit-tokens.uss 中的 --sk-rarity-*:
| 类 | 令牌 |
|---|---|
sus-tooltip--rarity-common | --sk-rarity-common |
sus-tooltip--rarity-uncommon | --sk-rarity-uncommon |
sus-tooltip--rarity-rare | --sk-rarity-rare |
sus-tooltip--rarity-epic | --sk-rarity-epic |
sus-tooltip--rarity-legendary | --sk-rarity-legendary |
sus-tooltip--rarity-exotic | --sk-rarity-exotic |
在消费方主题 USS 中覆盖 --sk-rarity-*(或 --thm-rarity-*)。
8. 完整接线示例
// 1. Bootstrap
var overlay = SusBootstrap.GetOrCreateOverlay(uiDocument.rootVisualElement);
var tooltip = new TooltipService();
tooltip.Init(overlay);
tooltip.CreateTooltip = () => new SusTooltip();
tooltip.CreateCard = () => new SusTooltipCard();
TooltipService.Instance = tooltip;
// 2. The router picks up the same OverlayHost
router.Init(overlay);
// 3. Components use WithTooltip
settingsButton.WithTooltip("Game Settings", TooltipPlacement.Left);
attackIcon.WithTooltip(new SusTooltipCardEntry { Title = "Attack", Description = "Basic attack" });
slot.WithTooltip(() => BuildCustomCard(item), TooltipPlacement.Right);9. 测试
| 文件 | 测试 | 覆盖内容 |
|---|---|---|
sus-kit/Editor/Tests/TooltipPlacementTests.cs | 9 editmode | ResolvePlacement、ResolvePosition、边缘处的 auto-flip |
sus-kit/Runtime/Tests/TooltipServicePlaymodeTests.cs | 4 playmode | ShowAtElement → 可见性、Hide、单一 tooltip 规则、StartsHidden |
sus-kit/Runtime/Tests/WithTooltipTests.cs | 4 playmode | WithTooltip 的 string/rich/null-service/null-element |
sus-kit/Editor/Tests/SusTooltipCardEntryTests.cs | ~3 editmode | SusTooltipCardEntry 的属性 |
10. OverlayHost 层 + SusOverlayManager 辅助方法
前文已说明:OverlayHost 的分类(Modal = 20、Tooltip = 30、Dropdown = 40、 Toast = 45、Console = 50)。Tooltip 和 dropdown 始终保持在模态框之上,这样对话框内部 弹出的内容依然可见。
OverlayHost 已经是 kit tooltip 的传送门宿主(TooltipService.Init(overlayHost) → OverlayCategory.Tooltip)。此外,SusOverlayManager 还在面板内维护两个便捷层, 用于组件本地的 dropdown / tooltip:
panel.visualTree
├── ...app content...
├── sus-overlay-popup ← Dropdown/Select (Show/Hide)
└── sus-overlay-tooltip ← SusTooltip (ShowTooltip/HideTooltip)
↑ always above the popup → the tooltip is visible on top of dropdowns- Popup 层: 同一时刻一个活动实例、click-outside、滚动跟踪 -
SusOverlayManager.Show/Hide - Tooltip 层:
pickingMode = Ignore,可同时有多个处于活动状态,鼠标移出即关闭 -SusOverlayManager.ShowTooltip/HideTooltip - 清理: 激活元素上的
DetachFromPanelEvent,面板上的panelDestroyed
映射到 OverlayHost 分类(当应用使用共享传送门时):
- Popup / select 菜单 →
OverlayCategory.Dropdown = 40 - Tooltip →
OverlayCategory.Tooltip = 30 - Toast →
OverlayCategory.Toast = 45 ShowTooltip/HideTooltipAPI 保持不变;宿主可以是 OverlayHost,也可以是 manager 的辅助层。