using System.Collections.Generic;
using NeoModLoader.services;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.UI;
namespace IdleSpectator;
///
/// Bottom-right Lore panel (L): World Memory + Living + Fallen tabs.
/// Sized in canvas units (same scaler as the dossier) to stay clear of top-right.
///
public static class ChronicleHud
{
public enum LoreTab
{
World = 0,
Living = 1,
Fallen = 2
}
public enum DetailPane
{
Life = 0,
Activity = 1
}
private const float PanelWidth = 268f;
private const float PanelHeight = 188f;
private const float ScreenInset = 12f;
private const float BottomClearance = 64f;
private const int MaxVisibleRows = 24;
private const float RowHeightMin = 20f;
private const float ChapterHeight = 15f;
private const float KindIconSize = 11f;
private const float TitleIconSize = 13f;
private const float TitleBandH = 22f;
private const float TabH = 15f;
private const float CharToolsH = 17f;
private const float ScrollSensitivity = 55f;
private const string ChromeMarker = "LoreV6";
private static GameObject _root;
private static RectTransform _rootRt;
private static RectTransform _content;
private static RectTransform _scrollRt;
private static ScrollRect _scroll;
private static Text _titleText;
private static Text _hotkeyText;
private static Image _titleIcon;
private static readonly List Rows = new List();
private static bool _visible;
private static int _lastCount = -1;
private static string _lastAgeId = "";
private static LoreTab _tab = LoreTab.World;
private static GameObject _tabsBar;
private static GameObject _worldTabBtn;
private static GameObject _livingTabBtn;
private static GameObject _fallenTabBtn;
private static GameObject _charTools;
private static InputField _searchField;
private static Button _favFilterBtn;
private static Image _favFilterIcon;
private static Button _refreshBtn;
private static Image _refreshBg;
private static Text _refreshLabel;
private static bool _favoritesOnly;
private static string _searchText = "";
private static int _lastCharFingerprint = int.MinValue;
private static int _builtAtRevision = int.MinValue;
private static long _detailUnitId;
private static bool _followFocus;
private static GameObject _backBtn;
private static Text _backBtnLabel;
private static GameObject _detailPaneBar;
private static GameObject _detailActivityBtn;
private static GameObject _detailLifeBtn;
private static DetailPane _detailPane = DetailPane.Life;
/// Rows actually built in the last character detail rebuild (harness).
public static int LastDetailHistoryRows { get; private set; }
/// Activity rows in the last Activity-pane rebuild (harness).
public static int LastDetailActivityRows { get; private set; }
/// Active detail sub-pane when a unit history is open.
public static DetailPane ActiveDetailPane => _detailPane;
/// Title of the first row in the last Living/Fallen list rebuild (harness).
public static string LastListTopTitle { get; private set; } = "";
/// True when Living/Fallen list is older than new chronicle events (needs Refresh).
public static bool CastListStale =>
Visible && IsCastTab && _detailUnitId == 0 && Chronicle.HistoryRevision != _builtAtRevision;
public static bool Visible => _visible && _root != null && _root.activeSelf;
public static bool IsReady => _root != null;
public static LoreTab ActiveTab => _tab;
public static bool FavoritesOnly => _favoritesOnly;
public static string SearchText => _searchText ?? "";
public static long DetailUnitId => _detailUnitId;
private static bool IsCastTab => _tab == LoreTab.Living || _tab == LoreTab.Fallen;
/// True when Lore detail was opened via L/books and should track idle focus.
public static bool FollowFocus => _followFocus;
public static bool IsTypingSearch =>
_detailUnitId == 0
&& _searchField != null
&& _searchField.isFocused
&& _root != null
&& _root.activeInHierarchy;
public static void EnsureBuilt()
{
DestroyOrphanHuds();
if (_root != null
&& (_root.transform.Find("Tabs") == null || _root.transform.Find(ChromeMarker) == null))
{
try
{
Object.Destroy(_root);
}
catch
{
// ignore
}
_root = null;
_rootRt = null;
_content = null;
_scrollRt = null;
_scroll = null;
_tabsBar = null;
_searchField = null;
_backBtn = null;
_backBtnLabel = null;
_detailPaneBar = null;
_detailActivityBtn = null;
_detailLifeBtn = null;
_detailUnitId = 0;
_followFocus = false;
_detailPane = DetailPane.Life;
}
if (_root != null)
{
ApplyRootPlacement();
return;
}
if (!Config.game_loaded && CanvasMain.instance == null)
{
return;
}
Canvas canvas = HudCanvas.Resolve();
if (canvas == null)
{
return;
}
_root = new GameObject("IdleSpectatorWorldMemoryHud", typeof(RectTransform), typeof(Image), typeof(CanvasGroup));
_root.transform.SetParent(canvas.transform, false);
new GameObject(ChromeMarker, typeof(RectTransform)).transform.SetParent(_root.transform, false);
_rootRt = _root.GetComponent();
ApplyRootPlacement();
Image bg = _root.GetComponent();
bg.raycastTarget = true;
HudCanvas.StylePanel(bg, _root.transform, new Color(0.07f, 0.08f, 0.1f, 0.92f));
CanvasGroup group = _root.GetComponent();
group.blocksRaycasts = true;
group.interactable = true;
_hotkeyText = HudCanvas.MakeText(_root.transform, "Hotkey", "L", 8);
RectTransform hotRt = _hotkeyText.GetComponent();
hotRt.anchorMin = new Vector2(1f, 1f);
hotRt.anchorMax = new Vector2(1f, 1f);
hotRt.pivot = new Vector2(1f, 1f);
hotRt.sizeDelta = new Vector2(16f, 12f);
hotRt.anchoredPosition = new Vector2(-6f, -5f);
_hotkeyText.alignment = TextAnchor.MiddleRight;
_hotkeyText.color = new Color(0.5f, 0.53f, 0.58f, 0.95f);
_hotkeyText.raycastTarget = false;
_titleIcon = HudCanvas.MakeIcon(_root.transform, "TitleIcon", TitleIconSize);
RectTransform titleIconRt = _titleIcon.GetComponent();
titleIconRt.anchorMin = new Vector2(0f, 1f);
titleIconRt.anchorMax = new Vector2(0f, 1f);
titleIconRt.pivot = new Vector2(0.5f, 0.5f);
titleIconRt.anchoredPosition = new Vector2(13f, -TitleBandH * 0.5f - 1f);
HudIcons.Apply(_titleIcon, HudIcons.FromUiIcon("iconBooks") ?? HudIcons.FromUiIcon("iconWar"));
_titleText = HudCanvas.MakeText(_root.transform, "Title", "Lore", 9);
RectTransform titleRt = _titleText.GetComponent();
titleRt.anchorMin = new Vector2(0f, 1f);
titleRt.anchorMax = new Vector2(1f, 1f);
titleRt.pivot = new Vector2(0.5f, 1f);
titleRt.offsetMin = new Vector2(24f, -TitleBandH);
titleRt.offsetMax = new Vector2(-22f, -2f);
_titleText.alignment = TextAnchor.MiddleLeft;
_titleText.color = new Color(0.9f, 0.91f, 0.93f, 1f);
_titleText.fontStyle = FontStyle.Bold;
_titleText.raycastTarget = false;
_titleText.horizontalOverflow = HorizontalWrapMode.Overflow;
BuildTabs();
BuildCharTools();
BuildScroll();
_root.SetActive(false);
_visible = false;
ApplyTabChrome();
LogService.LogInfo("[IdleSpectator] Lore HUD ready (L, World + Living + Fallen)");
}
private static void BuildTabs()
{
_tabsBar = new GameObject("Tabs", typeof(RectTransform));
_tabsBar.transform.SetParent(_root.transform, false);
RectTransform tabsRt = _tabsBar.GetComponent();
tabsRt.anchorMin = new Vector2(0f, 1f);
tabsRt.anchorMax = new Vector2(1f, 1f);
tabsRt.pivot = new Vector2(0.5f, 1f);
tabsRt.anchoredPosition = new Vector2(0f, -TitleBandH);
tabsRt.sizeDelta = new Vector2(-12f, TabH);
_worldTabBtn = BuildTabButton(_tabsBar.transform, "WorldTab", "World", () => SetTab(LoreTab.World));
_livingTabBtn = BuildTabButton(_tabsBar.transform, "LivingTab", "Living", () => SetTab(LoreTab.Living));
_fallenTabBtn = BuildTabButton(_tabsBar.transform, "FallenTab", "Fallen", () => SetTab(LoreTab.Fallen));
PlaceTab(_worldTabBtn, 0f, 3f);
PlaceTab(_livingTabBtn, 1f, 3f);
PlaceTab(_fallenTabBtn, 2f, 3f);
}
private static GameObject BuildTabButton(Transform parent, string name, string label, UnityAction onClick)
{
GameObject go = new GameObject(name, typeof(RectTransform), typeof(Image), typeof(Button));
go.transform.SetParent(parent, false);
Image bg = go.GetComponent();
bg.color = new Color(0.1f, 0.11f, 0.13f, 0.35f);
bg.raycastTarget = true;
Button button = go.GetComponent