К содержимому

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

Инициализация

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)Текстовый тултип на элементе. Задержка: 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.

Свойства

СвойствоТипПо умолчаниюНазначение
InstanceTooltipService (static)Singleton для WithTooltip
ShowDelayMsProp<float>500Задержка перед показом (мс)
HideDelayMsProp<float>200Задержка перед скрытием (мс)
OverlayOverlayHostКонтейнер портала
CreateTooltipFunc<VisualElement>Фабрика SusTooltip (Sharq)
CreateCardFunc<VisualElement>Фабрика SusTooltipCard (Sharq)
IsVisiblebool (read-only)Активен ли тултип

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)

После вычисления позиции тултип прижимается к краям панели (отступ 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:

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-тултипы (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

Очистка тултипа при смене маршрута — через beforeEach-guard:

csharp
// 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. Полный пример подключения

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 editmodeResolvePlacement, ResolvePosition, auto-flip на границах
sus-kit/Runtime/Tests/TooltipServicePlaymodeTests.cs4 playmodeShowAtElement → видимость, Hide, один тултип, StartsHidden
sus-kit/Runtime/Tests/WithTooltipTests.cs4 playmodeWithTooltip 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, либо хелпер менеджера