9ead8098f5
UI: - SettingsOverview cards now wrap subtext to two lines (DrawList wrap- width) and the card height grew from 96 to 110 px. Single-line fitting clipped most of the bilingual subtitles. - HellionStyle pushes ChildBg with alpha 0 when WindowOpacity < 1.0 to keep stacked BeginChild layers from compounding the deckgrade past what the slider suggests. - WindowOpacity slider helpmarker now points to Dalamud's per-window hamburger menu for opacity / blur / pin / click-through overrides. UX defaults (v15 → v16 migration adopts new values only when the user is still on the previous default — bool flips are heuristic, the prior defaults are from the v1.2.0 cycle and rarely toggled): - UseCompactDensity false → true (single-line message style is cleaner) - HideInNewGamePlusMenu false → true (consistent with other hide-flags) - HideSameTimestamps false → true (cleaner log) - MaxLinesToRender 5000 → 2500 (mid-range hardware friendlier) - ChatColours empty → Hellion brand preset (the first-run wizard does not offer a preset choice, so fresh installs get the brand colours out of the box)
280 lines
10 KiB
C#
280 lines
10 KiB
C#
using System.Numerics;
|
|
using Dalamud.Interface.Style;
|
|
using Dalamud.Interface.Utility.Raii;
|
|
using Dalamud.Interface.Windowing;
|
|
using Dalamud.Bindings.ImGui;
|
|
|
|
namespace HellionChat.Ui;
|
|
|
|
internal class Popout : Window
|
|
{
|
|
private readonly ChatLogWindow ChatLogWindow;
|
|
private readonly Tab Tab;
|
|
private readonly int Idx;
|
|
|
|
private long FrameTime; // set every frame
|
|
private long LastActivityTime = Environment.TickCount64;
|
|
|
|
// v0.6.0 — optional input bar inside the pop-out window. Lazy-allocated
|
|
// when the user enables Tab.PopOutInputEnabled and torn down when the
|
|
// toggle is turned off (independent text buffer is intentionally
|
|
// discarded — see v0.6.0 spec edge-case P1).
|
|
public ChatInputBar? InputBar { get; private set; }
|
|
public bool HasFocusedInputBar => InputBar?.IsFocused ?? false;
|
|
|
|
// Hellion Chat — v0.6.1 expose just the tab identifier (not the whole Tab
|
|
// reference) so AutoTellTabsService.DropOldestTempTab can locate the
|
|
// matching pop-out window when an LRU temp tab gets evicted.
|
|
internal Guid TabIdentifier => Tab.Identifier;
|
|
|
|
public Popout(ChatLogWindow chatLogWindow, Tab tab, int idx) : base($"{tab.Name}##popout")
|
|
{
|
|
ChatLogWindow = chatLogWindow;
|
|
Tab = tab;
|
|
Idx = idx;
|
|
|
|
Size = new Vector2(350, 350);
|
|
SizeCondition = ImGuiCond.FirstUseEver;
|
|
|
|
IsOpen = true;
|
|
RespectCloseHotkey = false;
|
|
DisableWindowSounds = true;
|
|
// v1.2.1 — KEIN AllowBackgroundBlur. Pop-Outs werden vom User häufig
|
|
// im Dalamud-Tab-Container mit anderen Plugin-Windows kombiniert; in
|
|
// dem Render-Pfad blurt Dalamud den gesamten Container, nicht nur
|
|
// das Pop-Out — würde die Tab-Bar oben und benachbarte Plugins
|
|
// mitziehen. Wer Blur in Pop-Outs will, kann ihn via Dalamud-
|
|
// Hamburger-Menü pro Window selbst aktivieren.
|
|
}
|
|
|
|
public override void PreOpenCheck()
|
|
{
|
|
if (!Tab.PopOut)
|
|
IsOpen = false;
|
|
}
|
|
|
|
public override bool DrawConditions()
|
|
{
|
|
FrameTime = Environment.TickCount64;
|
|
if (Tab.IndependentHide ? HideStateCheck() : ChatLogWindow.IsHidden)
|
|
return false;
|
|
|
|
if (!Plugin.Config.HideWhenInactive || (!Plugin.Config.InactivityHideActiveDuringBattle && Plugin.InBattle) || !Tab.UnhideOnActivity)
|
|
{
|
|
LastActivityTime = FrameTime;
|
|
return true;
|
|
}
|
|
|
|
// Activity in the tab, this popout window, or the main chat log window.
|
|
var lastActivityTime = Math.Max(Tab.LastActivity, LastActivityTime);
|
|
lastActivityTime = Math.Max(lastActivityTime, ChatLogWindow.LastActivityTime);
|
|
return FrameTime - lastActivityTime <= 1000 * Plugin.Config.InactivityHideTimeout;
|
|
}
|
|
|
|
public override void PreDraw()
|
|
{
|
|
// Hellion Chat v1.1.0+ — Theme-Engine ist Source-of-Truth, kein
|
|
// zusätzlicher Dalamud-StyleModel-Override mehr pro Window. Plugin.Draw
|
|
// pusht das aktive Hellion-Theme global; Pop-Out zeichnet sich damit
|
|
// konsistent zum Haupt-Chat-Window.
|
|
Flags = ImGuiWindowFlags.None;
|
|
if (!Plugin.Config.ShowPopOutTitleBar)
|
|
Flags |= ImGuiWindowFlags.NoTitleBar;
|
|
|
|
if (!Tab.CanMove)
|
|
Flags |= ImGuiWindowFlags.NoMove;
|
|
|
|
if (!Tab.CanResize)
|
|
Flags |= ImGuiWindowFlags.NoResize;
|
|
|
|
// Idx may point past the end if PopOutDocked was resized (e.g., a tab
|
|
// dropped) between the AddPopOutsToDraw() snapshot and this frame.
|
|
// Guard the read so we don't index into stale state.
|
|
if (Idx >= 0 && Idx < ChatLogWindow.PopOutDocked.Count && !ChatLogWindow.PopOutDocked[Idx])
|
|
{
|
|
if (Tab.IndependentOpacity)
|
|
{
|
|
BgAlpha = Tab.Opacity / 100f;
|
|
}
|
|
else
|
|
{
|
|
BgAlpha = Plugin.Config.WindowOpacity;
|
|
}
|
|
}
|
|
}
|
|
|
|
public override void Draw()
|
|
{
|
|
using var id = ImRaii.PushId($"popout-{Tab.Identifier}");
|
|
|
|
if (!Plugin.Config.ShowPopOutTitleBar)
|
|
{
|
|
ImGui.TextUnformatted(Tab.Name);
|
|
ImGui.Separator();
|
|
}
|
|
|
|
// v0.6.0 — one-time hint banner explaining the new pop-out input
|
|
// feature. Shown once per user; "Got it" or "Open settings"
|
|
// dismisses it and persists the flag.
|
|
var hintBannerHeight = DrawHintBannerIfNeeded();
|
|
|
|
// v0.6.0 — pop-out optional input bar. Reserve height first so the
|
|
// message log draws into the right region; only shown when the
|
|
// global master switch is on. Toggle-OFF resets InputBar so the
|
|
// next toggle-ON gives a fresh buffer (no stale text persists).
|
|
var inputEnabled = Plugin.Config.PopOutInputEnabled;
|
|
if (!inputEnabled && InputBar != null)
|
|
{
|
|
InputBar = null;
|
|
}
|
|
if (inputEnabled)
|
|
{
|
|
InputBar ??= new ChatInputBar(ChatLogWindow.Plugin, ChatLogWindow, () => Tab);
|
|
}
|
|
|
|
var inputBarHeight = inputEnabled
|
|
? ImGui.GetFrameHeightWithSpacing() + ImGui.GetStyle().ItemSpacing.Y
|
|
: 0f;
|
|
|
|
var handler = ChatLogWindow.HandlerLender.Borrow();
|
|
var logHeight = ImGui.GetContentRegionAvail().Y - inputBarHeight - hintBannerHeight;
|
|
ChatLogWindow.DrawMessageLog(Tab, handler, logHeight, false);
|
|
|
|
if (inputEnabled && InputBar != null)
|
|
{
|
|
ImGui.Separator();
|
|
InputBar.RenderCompact();
|
|
}
|
|
|
|
if (ImGui.IsWindowHovered(ImGuiHoveredFlags.ChildWindows))
|
|
LastActivityTime = FrameTime;
|
|
}
|
|
|
|
// Returns the vertical space the banner consumed (0 when not shown)
|
|
// so the message log can shrink accordingly.
|
|
private float DrawHintBannerIfNeeded()
|
|
{
|
|
if (Plugin.Config.SeenPopOutInputHint)
|
|
return 0f;
|
|
|
|
var hintText = Resources.HellionStrings.Popout_v060_HintText;
|
|
var ackLabel = Resources.HellionStrings.Popout_v060_HintAck;
|
|
var openLabel = Resources.HellionStrings.Popout_v060_HintOpenSettings;
|
|
|
|
var startY = ImGui.GetCursorPosY();
|
|
|
|
var bg = new System.Numerics.Vector4(0.16f, 0.20f, 0.28f, 1f);
|
|
ImGui.PushStyleColor(ImGuiCol.ChildBg, bg);
|
|
ImGui.PushStyleVar(ImGuiStyleVar.FrameBorderSize, 1f);
|
|
|
|
var dismiss = false;
|
|
var openSettings = false;
|
|
using (var child = ImRaii.Child("##v060-pop-out-hint", new System.Numerics.Vector2(0f, 64f), true))
|
|
{
|
|
if (child)
|
|
{
|
|
ImGui.TextWrapped(hintText);
|
|
if (ImGui.Button(ackLabel))
|
|
dismiss = true;
|
|
ImGui.SameLine();
|
|
if (ImGui.Button(openLabel))
|
|
{
|
|
dismiss = true;
|
|
openSettings = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
ImGui.PopStyleVar();
|
|
ImGui.PopStyleColor();
|
|
ImGui.Spacing();
|
|
|
|
if (dismiss)
|
|
{
|
|
Plugin.Config.SeenPopOutInputHint = true;
|
|
ChatLogWindow.Plugin.SaveConfig();
|
|
Plugin.Log.Debug("Pop-Out input hint dismissed");
|
|
if (openSettings)
|
|
ChatLogWindow.Plugin.SettingsWindow.Toggle();
|
|
}
|
|
|
|
return ImGui.GetCursorPosY() - startY;
|
|
}
|
|
|
|
public override void PostDraw()
|
|
{
|
|
if (Idx >= 0 && Idx < ChatLogWindow.PopOutDocked.Count)
|
|
ChatLogWindow.PopOutDocked[Idx] = ImGui.IsWindowDocked();
|
|
}
|
|
|
|
public override void OnClose()
|
|
{
|
|
ChatLogWindow.PopOutWindows.Remove(Tab.Identifier);
|
|
ChatLogWindow.Plugin.WindowSystem.RemoveWindow(this);
|
|
|
|
Tab.PopOut = false;
|
|
ChatLogWindow.Plugin.SaveConfig();
|
|
}
|
|
|
|
private enum HideState
|
|
{
|
|
None,
|
|
Cutscene,
|
|
CutsceneOverride,
|
|
User,
|
|
Battle
|
|
}
|
|
|
|
private HideState CurrentHideState = HideState.None;
|
|
|
|
private bool HideStateCheck()
|
|
{
|
|
// if the chat has no hide state set, and the player has entered battle, we hide chat if they have configured it
|
|
if (Tab.HideInBattle && CurrentHideState == HideState.None && Plugin.InBattle)
|
|
{
|
|
CurrentHideState = HideState.Battle;
|
|
Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: None → Battle");
|
|
}
|
|
|
|
// If the chat is hidden because of battle, we reset it here
|
|
if (CurrentHideState is HideState.Battle && !Plugin.InBattle)
|
|
{
|
|
CurrentHideState = HideState.None;
|
|
Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: Battle → None");
|
|
}
|
|
|
|
// if the chat has no hide state and in a cutscene, set the hide state to cutscene
|
|
if (Tab.HideDuringCutscenes && CurrentHideState == HideState.None && (Plugin.CutsceneActive || Plugin.GposeActive))
|
|
{
|
|
if (ChatLogWindow.Plugin.Functions.Chat.CheckHideFlags())
|
|
{
|
|
CurrentHideState = HideState.Cutscene;
|
|
Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: None → Cutscene");
|
|
}
|
|
}
|
|
|
|
// if the chat is hidden because of a cutscene and no longer in a cutscene, set the hide state to none
|
|
if (CurrentHideState is HideState.Cutscene or HideState.CutsceneOverride && !Plugin.CutsceneActive && !Plugin.GposeActive)
|
|
{
|
|
Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: {CurrentHideState} → None (cutscene/gpose ended)");
|
|
CurrentHideState = HideState.None;
|
|
}
|
|
|
|
// if the chat is hidden because of a cutscene and the chat has been activated, show chat
|
|
if (CurrentHideState == HideState.Cutscene && ChatLogWindow.Activate)
|
|
{
|
|
CurrentHideState = HideState.CutsceneOverride;
|
|
Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: Cutscene → CutsceneOverride (user activate)");
|
|
}
|
|
|
|
// if the user hid the chat and is now activating chat, reset the hide state
|
|
if (CurrentHideState == HideState.User && ChatLogWindow.Activate)
|
|
{
|
|
CurrentHideState = HideState.None;
|
|
Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: User → None (activate)");
|
|
}
|
|
|
|
return CurrentHideState is HideState.Cutscene or HideState.User or HideState.Battle || (Tab.HideWhenNotLoggedIn && !Plugin.ClientState.IsLoggedIn);
|
|
}
|
|
}
|