Files
HellionChat/HellionChat/Ui/Popout.cs
T
JonKazama-Hellion c4c85cf4b8 docs: unify documentation and streamline code comments
- Translated project documentation (LEARNING-JOURNEY, CONTRIBUTORS, AI_DISCLOSURE) to English for better accessibility.
- Standardized internal code documentation by converting XML-doc blocks to standard comment format.
- Cleaned up inline comments and removed redundant versioning metadata across the codebase.
- Refactored non-functional text elements to improve readability and maintain a consistent style.
2026-05-11 00:52:15 +02:00

269 lines
8.5 KiB
C#

using System.Numerics;
using Dalamud.Bindings.ImGui;
using Dalamud.Interface.Style;
using Dalamud.Interface.Utility.Raii;
using Dalamud.Interface.Windowing;
namespace HellionChat.Ui;
internal class Popout : Window
{
private readonly ChatLogWindow ChatLogWindow;
private readonly Tab Tab;
private readonly int Idx;
private long FrameTime;
private long LastActivityTime = Environment.TickCount64;
// Optional input bar inside the pop-out. Lazy-allocated when enabled,
// torn down on toggle-off (buffer discarded intentionally).
public ChatInputBar? InputBar { get; private set; }
public bool HasFocusedInputBar => InputBar?.IsFocused ?? false;
// Exposed so AutoTellTabsService can locate this window during LRU eviction.
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;
// AllowBackgroundBlur is intentionally off: Dalamud blurs the entire
// tab container, not just this window, which would affect adjacent plugins.
// Users can enable blur per-window via the Dalamud hamburger menu.
}
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;
}
var lastActivityTime = Math.Max(Tab.LastActivity, LastActivityTime);
lastActivityTime = Math.Max(lastActivityTime, ChatLogWindow.LastActivityTime);
return FrameTime - lastActivityTime <= 1000 * Plugin.Config.InactivityHideTimeout;
}
public override void PreDraw()
{
// Theme engine pushes the active theme globally in Plugin.Draw;
// pop-outs draw consistently without per-window overrides.
Flags = ImGuiWindowFlags.None;
if (!Plugin.Config.ShowPopOutTitleBar)
Flags |= ImGuiWindowFlags.NoTitleBar;
if (!Tab.CanMove)
Flags |= ImGuiWindowFlags.NoMove;
if (!Tab.CanResize)
Flags |= ImGuiWindowFlags.NoResize;
// Guard against Idx pointing past the end if PopOutDocked was resized mid-frame.
if (Idx >= 0 && Idx < ChatLogWindow.PopOutDocked.Count && !ChatLogWindow.PopOutDocked[Idx])
{
BgAlpha = Tab.IndependentOpacity ? Tab.Opacity / 100f : 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();
}
var hintBannerHeight = DrawHintBannerIfNeeded();
// Toggle-OFF resets InputBar so the next toggle-ON starts with a fresh buffer.
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 consumed by the banner (0 when not shown).
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 (Tab.HideInBattle && CurrentHideState == HideState.None && Plugin.InBattle)
{
CurrentHideState = HideState.Battle;
Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: None -> Battle");
}
if (CurrentHideState is HideState.Battle && !Plugin.InBattle)
{
CurrentHideState = HideState.None;
Plugin.Log.Verbose($"Popout HideState [{Tab.Name}]: Battle -> None");
}
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 (
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 (CurrentHideState == HideState.Cutscene && ChatLogWindow.Activate)
{
CurrentHideState = HideState.CutsceneOverride;
Plugin.Log.Verbose(
$"Popout HideState [{Tab.Name}]: Cutscene -> CutsceneOverride (user activate)"
);
}
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);
}
}