Tooltips (SusKit)
Сервис:
TooltipService(sus-kit/Runtime/Services/) Расширение:TooltipExtensions.WithTooltip()(sus-kit/Runtime/Extensions/) Модель:SusTooltipCardEntry(sus-kit/Runtime/Models/)
1. Назначение
Тултипы появляются при наведении на UI-элемент. Они рендерятся через общий портал OverlayHost (категория Tooltip = 30) и не обрезаются родителем.
Два режима (плюс кастомные карточки):
- Text (
ShowAtElement) — простая строка, позиционируется относительно целевогоVisualElement. - Rich (
ShowRich) — карточка с заголовком, описанием, иконкой (SusTooltipCardEntry). - Custom (
ShowContent) — любойVisualElement, лениво строящийся черезFunc<VisualElement>, позиционируется как text-тултипы (auto-flip + clamp). Используйте, когда карточка неSusTooltip/SusTooltipCard(например, игровое оформление предмета). Также доступен какWithTooltip(Func<VisualElement>, placement).
Задержка показа (500 мс) и задержка скрытия (200 мс) настраиваются через ShowDelayMs / HideDelayMs. Активен только один тултип одновременно — повторный вызов отменяет предыдущий.
2. Архитектура
Текущая реализация:
TooltipServiceиспользуетOverlayHostнапрямую для rich-тултипов. Компонентные тултипы (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()— копирует.g.ussотSusComponent-предка доRemoveFromHierarchy(). Это гарантирует, что карточка тултипа сохраняет свои стили после телепортации. - Важно:
ShowTooltip()НЕ вызываетRemoveFromHierarchy()передAddToOverlay()— иначе цепочка родителей ломается и стили не копируются. design-tokens.ussиsuskit-base.ussзагружены прямо на самOverlayHost— CSS-переменные и базовые стили видны всем детям оверлея.
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) | Текстовый тултип на элементе. Задержка: ShowDelayMs (по умолчанию 500 мс). Внутри использует ShowContent + CreateTooltip. |
ShowContent | (VisualElement target, Func<VisualElement> contentFactory, TooltipPlacement placement = Auto) | Кастомная карточка на слое OverlayHost Tooltip. Ленивая фабрика после задержки показа; те же placement / auto-flip, что и у текста. |
ShowRich | (SusTooltipCardEntry entry, Vector2? screenPosition = null) | Rich-карточка через CreateCard + Apply(entry). Если позиция не указана — центр экрана. |
Hide | () | Скрыть немедленно. |
HideAfterDelay | () | Скрыть через HideDelayMs (по умолчанию 200 мс). Используется в PointerLeave. |
Свойства
| Свойство | Тип | По умолчанию | Назначение |
|---|---|---|---|
Instance | TooltipService (static) | — | Singleton для WithTooltip |
ShowDelayMs | Prop<float> | 500 | Задержка перед показом (мс) |
HideDelayMs | Prop<float> | 200 | Задержка перед скрытием (мс) |
Overlay | OverlayHost | — | Контейнер портала |
CreateTooltip | Func<VisualElement> | — | Фабрика SusTooltip (Sharq) |
CreateCard | Func<VisualElement> | — | Фабрика SusTooltipCard (Sharq) |
IsVisible | bool (read-only) | — | Активен ли тултип |
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)
После вычисления позиции тултип прижимается к краям панели (отступ 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:
// 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-тултипы (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
Очистка тултипа при смене маршрута — через beforeEach-guard:
// In SusRouter.Init()
ModalService.CloseAll(); // modals
TooltipService.Instance?.Hide(); // active 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). Цвет полосы заголовка берётся из --sk-rarity-* в suskit-tokens.uss:
| Класс | Токен |
|---|---|
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 |
Переопределяйте --sk-rarity-* (или --thm-rarity-*) в теме USS потребителя.
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, один тултип, 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). Тултипы и dropdown'ы остаются выше модалок, чтобы попапы внутри диалога оставались видимыми.
OverlayHost уже является хостом портала для тултипов kit (TooltipService.Init(overlayHost) → OverlayCategory.Tooltip). Кроме того, SusOverlayManager держит два удобных слоя внутри панели для компонентно-локальных dropdown / тултипов:
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, несколько активных, закрытие по hover —SusOverlayManager.ShowTooltip/HideTooltip - Очистка:
DetachFromPanelEventна активаторе,panelDestroyedна панели
Маппинг на категории OverlayHost (когда приложение использует общий портал):
- Popup / select-меню →
OverlayCategory.Dropdown = 40 - Тултипы →
OverlayCategory.Tooltip = 30 - Тосты →
OverlayCategory.Toast = 45 - API
ShowTooltip/HideTooltipостаётся тем же; хост — либо OverlayHost, либо хелпер менеджера