Tooltip adjustments

This commit is contained in:
DazedAnon 2026-07-14 19:18:57 -05:00
parent 7b9c0d5c8a
commit 7b4ff19b01
4 changed files with 127 additions and 6 deletions

View file

@ -21,6 +21,17 @@ public static class DossierAvatar
public static bool UsingVanilla => _usingVanilla;
/// <summary>True if <paramref name="screenPoint"/> is inside the live portrait rect.</summary>
public static bool ContainsScreenPoint(Vector2 screenPoint)
{
if (_host == null || !_host.gameObject.activeInHierarchy)
{
return false;
}
return RectTransformUtility.RectangleContainsScreenPoint(_host, screenPoint, null);
}
public static void ResetHost()
{
_template = null;

View file

@ -0,0 +1,77 @@
using HarmonyLib;
namespace IdleSpectator;
/// <summary>
/// Idle pins <see cref="UnitSelectionEffect.last_actor"/> so relationship arrows draw.
/// That same pin would always show the focus unit tip via
/// <see cref="CursorTooltipHelper.updateGameplayTooltip"/>.
/// Allow tips for a real cursor unit or the dossier live sprite; suppress the pin tip on empty world / dossier chrome.
/// </summary>
[HarmonyPatch(typeof(CursorTooltipHelper), nameof(CursorTooltipHelper.updateGameplayTooltip))]
internal static class FocusTooltipPatches
{
[HarmonyPrefix]
private static bool Prefix(ref bool __result, ref Actor __state)
{
__state = null;
if (!SpectatorMode.Active)
{
return true;
}
// Live sprite: tip for the focused unit (last_actor is already pinned).
if (WatchCaption.IsPointerOverAvatar())
{
return true;
}
// Dossier chrome (not the sprite): no tip.
if (WatchCaption.IsPointerOverPanel())
{
Tooltip.hideTooltip();
__result = false;
return false;
}
Actor near = GetActorNearCursorSafe();
if (near != null && near.isAlive())
{
// Real world hover: briefly use that unit for the tip, then restore the pin.
__state = UnitSelectionEffect.last_actor;
UnitSelectionEffect.setLastActor(near);
return true;
}
// Empty world (grass, water, etc.): do not leak the idle focus tip.
Tooltip.hideTooltip();
__result = false;
return false;
}
[HarmonyPostfix]
private static void Postfix(Actor __state)
{
if (__state != null)
{
UnitSelectionEffect.setLastActor(__state);
}
}
private static Actor GetActorNearCursorSafe()
{
try
{
if (World.world == null || ControllableUnit.isControllingUnit())
{
return null;
}
return World.world.getActorNearCursor();
}
catch
{
return null;
}
}
}

View file

@ -19,6 +19,8 @@ public static class WatchCaption
private const float HistoryIcon = 10f;
private const float HistoryColMinW = 118f;
private const float PadX = 4f;
/// <summary>Inset from the canvas top-right corner (grows left via pivot).</summary>
private const float ScreenInset = 12f;
private const float PadTop = 4f;
private const float HeaderH = 18f;
private const float BodyH = 44f;
@ -133,6 +135,23 @@ public static class WatchCaption
return pixelRect.width > 8f && pixelRect.height > 8f;
}
/// <summary>True if the mouse is over the dossier panel (including the live sprite).</summary>
public static bool IsPointerOverPanel()
{
if (_rootRt == null || _root == null || !_root.activeInHierarchy)
{
return false;
}
return RectTransformUtility.RectangleContainsScreenPoint(_rootRt, Input.mousePosition, null);
}
/// <summary>True if the mouse is over the dossier live sprite only.</summary>
public static bool IsPointerOverAvatar()
{
return DossierAvatar.ContainsScreenPoint(Input.mousePosition);
}
public static void Clear()
{
_current = null;
@ -770,6 +789,7 @@ public static class WatchCaption
float height = Mathf.Max(HeaderH + PadTop * 2f, y + PadTop - Gap);
if (_rootRt != null)
{
ApplyRootPlacement();
_rootRt.sizeDelta = new Vector2(_panelWidth, height);
LastPanelSize = _rootRt.sizeDelta;
}
@ -1159,11 +1179,8 @@ public static class WatchCaption
_root.transform.SetParent(canvas.transform, false);
_rootRt = _root.GetComponent<RectTransform>();
_rootRt.anchorMin = new Vector2(0f, 1f);
_rootRt.anchorMax = new Vector2(0f, 1f);
_rootRt.pivot = new Vector2(0f, 1f);
ApplyRootPlacement();
_rootRt.sizeDelta = new Vector2(LiveMax + PadX * 2f, HeaderH + PadTop * 2f);
_rootRt.anchoredPosition = new Vector2(12f, -12f);
Image bg = _root.GetComponent<Image>();
bg.raycastTarget = false;
@ -1250,6 +1267,22 @@ public static class WatchCaption
LogService.LogInfo("[IdleSpectator] Dossier HUD ready (nametag chips + mini history)");
}
/// <summary>
/// Pin the card to the canvas top-right. Pivot (1,1) so width growth expands left and stays on-screen.
/// </summary>
private static void ApplyRootPlacement()
{
if (_rootRt == null)
{
return;
}
_rootRt.anchorMin = new Vector2(1f, 1f);
_rootRt.anchorMax = new Vector2(1f, 1f);
_rootRt.pivot = new Vector2(1f, 1f);
_rootRt.anchoredPosition = new Vector2(-ScreenInset, -ScreenInset);
}
private static void PlaceLine(RectTransform lineRt, float yFromTop, float height, float left)
{
if (lineRt == null)

View file

@ -1,7 +1,7 @@
{
"name": "IdleSpectator",
"author": "dazed",
"version": "0.9.28",
"description": "AFK Idle Spectator (I) + Chronicle (F9). Dossier grows wide before history wraps.",
"version": "0.9.31",
"description": "AFK Idle Spectator (I) + Chronicle (F9). Dossier top-right; tip on world hover or live sprite.",
"GUID": "com.dazed.idlespectator"
}