跳转到内容

Tooltips (SusKit)

服务:TooltipService (sus-kit/Runtime/Services/) 扩展:TooltipExtensions.WithTooltip() (sus-kit/Runtime/Extensions/) 模型:SusTooltipCardEntry (sus-kit/Runtime/Models/)


1. 用途

Tooltip 在鼠标悬停在 UI 元素上时出现。它们通过共享传送门 OverlayHost(分类 Tooltip = 30)渲染,不会被父元素裁剪。

两种模式(外加自定义卡片):

  • TextShowAtElement)是一个简单字符串,相对于 目标 VisualElement 定位。
  • RichShowRich)——带标题、描述、图标的卡片 (SusTooltipCardEntry)。
  • CustomShowContent)——任意 VisualElement,通过 Func<VisualElement> 惰性构建,定位方式与 text tooltip 相同(auto-flip + clamp)。 当卡片不是 SusTooltip / SusTooltipCard 时使用(例如游戏特定的 道具装饰)。也可通过 WithTooltip(Func<VisualElement>, placement) 暴露。

显示延迟(500ms)和隐藏延迟(200ms)可通过 ShowDelayMs / HideDelayMs 调整。同一时刻只有一个 tooltip 处于活动状态——再次调用会 取消上一个。


2. 架构

当前实现:TooltipService 对 rich tooltip 直接使用 OverlayHost组件 tooltipSusTooltip.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.usssuskit-base.uss 直接加载到 OverlayHost 本身——CSS 变量和基础样式对 overlay 的每个子元素都可见。

3. TooltipService API

文件: sus-kit/Runtime/Services/TooltipService.cs

初始化

csharp
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。

属性

属性类型默认值说明
InstanceTooltipService (static)WithTooltip 使用的单例
ShowDelayMsProp<float>500显示前的延迟(ms)
HideDelayMsProp<float>200隐藏前的延迟(ms)
OverlayOverlayHost传送门容器
CreateTooltipFunc<VisualElement>SusTooltip(Sharq)的工厂
CreateCardFunc<VisualElement>SusTooltipCard(Sharq)的工厂
IsVisiblebool(只读)当前是否有 tooltip 处于活动状态

4. 定位与 auto-flip

TooltipPlacement

csharp
public enum TooltipPlacement
{
    Auto,    // Tries from bottom → top → right → left
    Top,
    Bottom,
    Left,
    Right
}

Auto 算法

  1. Bottom:若 target.bottom + tooltip.height + 4px ≤ 面板高度 → Bottom
  2. Top:若 target.top - tooltip.height - 4px ≥ 0 → Top
  3. Right:若 target.right + tooltip.width + 4px ≤ 面板宽度 → Right
  4. Left:兜底方案。

边缘限制(Clamping)

计算出位置后,tooltip 会被限制在面板边缘内(4px 边距):

csharp
x = Mathf.Clamp(x, 4f, panelWidth - tooltipWidth - 4f);
y = Mathf.Clamp(y, 4f, panelHeight - tooltipHeight - 4f);

用于测试的纯函数

csharp
// 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:

csharp
// 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);

工作原理:

  • PointerEnterEventservice.ShowAtElement / ShowContent(在 ShowDelayMs 之后)。
  • PointerLeaveEventservice.HideAfterDelay()(在 HideDelayMs 之后)。
  • 元素被分离时(DetachFromPanelEvent)→ service.Hide()

ShowContent(target, contentFactory, placement) 会在显示延迟后构建卡片,并像 text tooltip 一样定位(auto-flip + clamp)。


6. SusTooltipCardEntry

文件: sus-kit/Runtime/Models/SusTooltipCardEntry.cs

csharp
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 一起使用:

csharp
service.ShowRich(new SusTooltipCardEntry
{
    Title = "Sword of justice",
    Description = "Attack: 15-20\nType: slashing",
    IconAlias = "sword",
    FooterText = "One-handed"
});

7. 与 router 集成

路由切换时,tooltip 会通过 beforeEach guard 被清除:

csharp
// 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. 完整接线示例

csharp
// 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.cs9 editmodeResolvePlacementResolvePosition、边缘处的 auto-flip
sus-kit/Runtime/Tests/TooltipServicePlaymodeTests.cs4 playmodeShowAtElement → 可见性、Hide、单一 tooltip 规则、StartsHidden
sus-kit/Runtime/Tests/WithTooltipTests.cs4 playmodeWithTooltip 的 string/rich/null-service/null-element
sus-kit/Editor/Tests/SusTooltipCardEntryTests.cs~3 editmodeSusTooltipCardEntry 的属性

10. OverlayHost 层 + SusOverlayManager 辅助方法

前文已说明:OverlayHost 的分类(Modal = 20Tooltip = 30Dropdown = 40Toast = 45Console = 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 / HideTooltip API 保持不变;宿主可以是 OverlayHost,也可以是 manager 的辅助层。