- the chat dock is a list, not a hardcoded pair: DOCKED_CHAT_FRAMES, SELECTED_DOCK_FRAME and the FCF verbs — DockFrame, UnDockFrame, DockUpdate, SelectDockFrame, SaveDock, SetTabPosition, Close and the button-side trio — are transcribed from FloatingChatFrame.lua, and every "for i = 1, 2" moved to what it meant, the dock or every window; the dock seeds at load from the window record's own docked byte handed to FCF_DockFrame, whose first line IS the reference's early return — transcribing the function that answers the way the reference answers, where an empty stub "because we have no dock" would have been the bandaid; its unconditional re-lock cannot clobber an unlock you saved, because the stored look is applied last; pfUI's RefreshChat now runs end to end - ignoreArrows is alt-arrow mode, and the arrow is DECLINED, not swallowed: the reference's edit-box handler returns unconsumed on an arrow key, so the walk carries it past the box and it runs TURNLEFT — the whole feature is steering while the chat box holds focus, where the old reading left the arrows dead; holding Alt resumes the caret; SetAltArrowKeyMode and its getter land with the reference's own quirk that a missing argument enables while nil disables (the getter answers 1 or nil, never a boolean), SetIgnoreArrows turns out not to exist in 1.12 and is unpublished, and the binding pass gets a named four-key exemption rather than a hole, because the handler consumes every other path - pfUI's square minimap is actually square: Minimap:SetMaskTexture makes the disc's mask state instead of a load-time constant — write-only from Lua, as shipped, no invented getter — where an empty path restores the round default and a failed load keeps the current mask and warns, because either mistake would otherwise hand a mistyped path a silently square map that looks like a feature; and MiniMapBattlefieldFrame grows the Border and Shine regions beside its icon, on the reference's own layers — icon on BACKGROUND, ring on ARTWORK above it, the only order a ring around an icon can draw in — which pfUI hides unguarded to sit the icon flush Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2401 lines
140 KiB
XML
2401 lines
140 KiB
XML
<Ui>
|
||
<!--
|
||
The chat window, rebuilt on the reference's own structure (decision 0288 P3): the docked
|
||
General + Combat Log pair sharing ChatFrame1's rect, the hover-revealed tabs and
|
||
background (the classic transparent chat — chat-cache.txt ships window alpha 0, hover
|
||
fades to DEFAULT_CHATFRAME_ALPHA 0.25), the outside-left scroll column with the
|
||
blinking bottom button, and the reference edit box (three-piece border art + header)
|
||
below the frame. Geometry, art paths, timing constants, and behaviors are QUOTED from the
|
||
extracted patch-chain FloatingChatFrame.xml/.lua + ChatFrame.xml/.lua (the pin in 0288);
|
||
window management (drag/undock/resize/rename, the tab right-click options dropdown) is
|
||
chat SETTINGS — deliberately out (0288 §2).
|
||
|
||
The inbound feed (ui_chat::feed) AddMessages into ChatFrame1; the composer/router phase
|
||
(0288 P4) fans CHAT_MSG groups across both windows per the chat-cache defaults. The edit
|
||
box submits through SubmitChatInput → ui_chat::input's grammar; its live header machine
|
||
(ChatEdit_UpdateHeader) is 0288 P5 — until then the header shows the sticky default
|
||
"Say:" with static insets.
|
||
-->
|
||
<Script><![CDATA[
|
||
-- ── NUM_CHAT_WINDOWS — how many chat windows the UI declares ─────────────────────────
|
||
-- `NUM_CHAT_WINDOWS = 7` is ChatFrame.lua line 5, verbatim. It is a FrameXML constant, not
|
||
-- an engine one, and the number is NOT the engine's: the chat-cache and the per-window
|
||
-- struct array `0xb4fe50` (wow-re `chat-color-table.md`, stride 0x98) both hold **10**
|
||
-- windows, and FloatingChatFrame.xml duly declares ChatFrame1TabDockRegion..10 — but only
|
||
-- seven ChatFrames. 7 is the count an addon iterates.
|
||
--
|
||
-- Measured demand: **5 corpus addons, all genuinely distinct** (no replicated library —
|
||
-- the one clean count in this area). Two idioms, and they need different things from us:
|
||
-- getglobal("ChatFrame"..i) Outfitter, _LazyPig — _LazyPig indexes the result
|
||
-- UNGUARDED (`ChatFrame:IsVisible()`), so it dies at i=3
|
||
-- unless ChatFrame3..7 are real frames. They are, below.
|
||
-- GetChatWindowInfo(i) EnhTooltip, MikScrollingBattleText, Enchantrix — the
|
||
-- engine getter, `benilla_ui::script::chat_window`.
|
||
NUM_CHAT_WINDOWS = 7
|
||
|
||
-- ── SlashCmdList — the addon slash-command registry (decision 1195) ────────────────────
|
||
-- Declared HERE, not in Rust, because the 1.12 client declares it in ChatFrame.lua and the
|
||
-- captured `_G` attributes it `table framexml`. An addon's registration is three lines at
|
||
-- FILE SCOPE:
|
||
-- SLASH_MYADDON1 = "/myaddon"
|
||
-- SlashCmdList["MYADDON"] = function(msg) ... end
|
||
-- so a missing table is not a missing feature, it is `attempt to index global
|
||
-- 'SlashCmdList'` on the addon's very first chunk — 26 of a 218-addon corpus died there.
|
||
-- The dispatch walk is `benilla_ui::script::slash`, transcribing ChatEdit_ParseText's.
|
||
--
|
||
-- It starts EMPTY: benilla's own commands are dispatched host-side (decision 0881's table)
|
||
-- where the reference registers them here. That divergence is recorded in `slash.rs`.
|
||
SlashCmdList = {}
|
||
|
||
-- ── ChatTypeGroup / OtherMenuChatTypeGroups — declared, and deliberately EMPTY ────────
|
||
-- The reference builds `ChatTypeGroup` at ChatFrame.lua l.115-348: 68 entries mapping a
|
||
-- chat TYPE to the list of `CHAT_MSG_*` events that belong to it, which its
|
||
-- `ChatFrame_RegisterForMessages` walks to register events on a chat window.
|
||
--
|
||
-- Declared here for the same reason `SlashCmdList` above is: an addon REGISTERS ITS OWN
|
||
-- chat type at file scope, and a missing table is not a missing feature — it is
|
||
-- `attempt to index global 'ChatTypeGroup'` on the addon's very first chunk. CT_RaidAssist
|
||
-- does exactly that three lines apart (`CT_RaidAssist.lua:59` assigns
|
||
-- `ChatTypeGroup["CTRAID"]`, `:63` tinserts into `OtherMenuChatTypeGroups`), and it dies
|
||
-- there — taking `CT_RA_OnEvent` with it, which is why FOUR MORE corpus addons
|
||
-- (CT_RABossMods, FixCTGroups, FuBar_CTRaid, SmartRes) die on that function being nil.
|
||
-- One missing table, five addons.
|
||
--
|
||
-- **EMPTY, not populated, and that is the honest shape rather than a shortcut.** Two
|
||
-- reasons, in order of weight:
|
||
-- · The 68 entries are 233 lines of the reference's own Lua. We RUN the reference's
|
||
-- files where we can (1234) and write our own where we cannot; what we do not do is
|
||
-- paste its source into a repo that mirrors publicly.
|
||
-- · Nothing would read them. Measured across the 218-addon corpus with `grep -a`:
|
||
-- CT_RaidAssist is the ONLY addon that touches either table, and it only WRITES. Not
|
||
-- one addon reads a group out.
|
||
--
|
||
-- The divergence that leaves, stated rather than discovered: our chat frames do not
|
||
-- register through this table at all — `ui_chat` routes host-side, the same split
|
||
-- `SlashCmdList` records above. So an addon that adds a group gets no routing from it.
|
||
-- That is the `ChatFrame_OnEvent` architecture question, not this table's.
|
||
ChatTypeGroup = {}
|
||
OtherMenuChatTypeGroups = {}
|
||
|
||
-- ── ChatTypeInfo — the per-chat-type table addons read colors out of ──────────────────
|
||
-- Declared HERE because the 1.12 client declares it at the top of ChatFrame.lua, and it
|
||
-- is an ADDON-FACING table before it is ours: `local info = ChatTypeInfo["SAY"]` then
|
||
-- `frame:AddMessage(text, info.r, info.g, info.b, info.id)` is the standard way an addon
|
||
-- prints a colored line.
|
||
--
|
||
-- Measured, and the raw count is misleading enough to be worth writing down: 234 corpus
|
||
-- sites, but **192 of them are one line** — AceEvent-2.0's
|
||
-- `ChatTypeInfo and ChatTypeInfo.WHISPER and ChatTypeInfo.WHISPER.r`, a GUARDED readiness
|
||
-- probe that copy-pastes into 64 addons and cannot raise. The reads that actually index
|
||
-- the table are 42 sites in 6 addons (ChatLog, oRA2/Participant's RaidWarn, Glory-2.0's
|
||
-- two FuBar hosts, FuBar_HeyFu) — those are `attempt to index global 'ChatTypeInfo'`
|
||
-- without it, and they are the reason this exists.
|
||
--
|
||
-- Three columns, three sources:
|
||
-- sticky the reference's own literals (ChatFrame.lua l.10-114) — the five sticky types
|
||
-- are SAY/PARTY/RAID/GUILD/BATTLEGROUND, which `ui_chat::edit` already quotes.
|
||
-- id GetChatTypeIndex's 1-based index into the engine's 94-entry color registry
|
||
-- (wow-re `chat-color-table.md`, static table 0x804710) — the ref sets it in
|
||
-- ChatFrame_OnLoad, and AddMessage's 5th argument is what consumes it.
|
||
-- r,g,b that registry's byte defaults, /255 — the engine hands Lua 0..1 floats
|
||
-- (it re-normalizes with the f32 1/255 at 0x8026c8).
|
||
--
|
||
-- ONE DIVERGENCE, and it is a shortcut through a two-step: the reference's OnLoad writes
|
||
-- 1.0/1.0/1.0 into every entry and waits for the engine's UPDATE_CHAT_COLOR storm to fill
|
||
-- the real values in (ChatFrame.lua l.1349 is the only writer in the whole default UI).
|
||
-- benilla has no chat-color settings surface and nothing that fires that event, so the
|
||
-- values are seeded at their END STATE directly. `ui_chat::event::default_color` is the
|
||
-- same wow-re table rendered host-side for our own feed, and
|
||
-- `chat_type_info_matches_the_host_color_table` fails if the two ever drift.
|
||
--
|
||
-- REPLY and COMBAT_ERROR are the two keys FrameXML declares that the engine's registry
|
||
-- does NOT contain, so GetChatTypeIndex answers 0 for both and no UPDATE_CHAT_COLOR is
|
||
-- ever fired for either name. They do NOT share a fate, and the first version of this
|
||
-- block got that wrong by generalising:
|
||
-- COMBAT_ERROR nothing writes it, ever — id 0, white, on the real client too.
|
||
-- REPLY the UPDATE_CHAT_COLOR handler MIRRORS WHISPER INTO IT by hand
|
||
-- (ChatFrame.lua l.1357-1365: `if strupper(arg1) == "WHISPER" then
|
||
-- info = ChatTypeInfo["REPLY"] ... end`). So its end state is WHISPER's
|
||
-- FF80FF, which is what the /r header shows, and seeding it white was a
|
||
-- bug the comment above it was busy justifying.
|
||
-- CHANNEL1..CHANNEL10 are the boot-seeded extras (indices 95-104), each colored from the
|
||
-- live CHANNEL entry — FFC0C0.
|
||
ChatTypeInfo = {}
|
||
local function chatType(name, sticky, id, r, g, b)
|
||
ChatTypeInfo[name] = { sticky = sticky, id = id, r = r / 255, g = g / 255, b = b / 255 }
|
||
end
|
||
chatType("SAY", 1, 1, 255, 255, 255)
|
||
chatType("PARTY", 1, 2, 170, 170, 255)
|
||
chatType("RAID", 1, 3, 255, 127, 0)
|
||
chatType("GUILD", 1, 4, 64, 255, 64)
|
||
chatType("OFFICER", 0, 5, 64, 192, 64)
|
||
chatType("YELL", 0, 6, 255, 64, 64)
|
||
chatType("WHISPER", 0, 7, 255, 128, 255)
|
||
chatType("WHISPER_INFORM", 0, 8, 255, 128, 255)
|
||
chatType("REPLY", 0, 0, 255, 128, 255)
|
||
chatType("EMOTE", 0, 9, 255, 128, 64)
|
||
chatType("TEXT_EMOTE", 0, 10, 255, 128, 64)
|
||
chatType("SYSTEM", 0, 11, 255, 255, 0)
|
||
chatType("MONSTER_WHISPER", 0, 27, 179, 179, 179)
|
||
chatType("MONSTER_SAY", 0, 12, 255, 255, 159)
|
||
chatType("MONSTER_YELL", 0, 13, 255, 64, 64)
|
||
chatType("MONSTER_EMOTE", 0, 14, 255, 128, 64)
|
||
chatType("CHANNEL", 0, 15, 255, 192, 192)
|
||
chatType("CHANNEL_JOIN", 0, 16, 192, 128, 128)
|
||
chatType("CHANNEL_LEAVE", 0, 17, 192, 128, 128)
|
||
chatType("CHANNEL_LIST", 0, 18, 192, 128, 128)
|
||
chatType("CHANNEL_NOTICE", 0, 19, 192, 192, 192)
|
||
chatType("CHANNEL_NOTICE_USER", 0, 20, 192, 192, 192)
|
||
chatType("AFK", 0, 21, 255, 128, 255)
|
||
chatType("DND", 0, 22, 255, 128, 255)
|
||
chatType("IGNORED", 0, 23, 255, 0, 0)
|
||
chatType("SKILL", 0, 24, 85, 85, 255)
|
||
chatType("LOOT", 0, 25, 0, 170, 0)
|
||
chatType("COMBAT_ERROR", 0, 0, 255, 255, 255)
|
||
chatType("COMBAT_MISC_INFO", 0, 26, 128, 128, 255)
|
||
chatType("CHANNEL1", 0, 95, 255, 192, 192)
|
||
chatType("CHANNEL2", 0, 96, 255, 192, 192)
|
||
chatType("CHANNEL3", 0, 97, 255, 192, 192)
|
||
chatType("CHANNEL4", 0, 98, 255, 192, 192)
|
||
chatType("CHANNEL5", 0, 99, 255, 192, 192)
|
||
chatType("CHANNEL6", 0, 100, 255, 192, 192)
|
||
chatType("CHANNEL7", 0, 101, 255, 192, 192)
|
||
chatType("CHANNEL8", 0, 102, 255, 192, 192)
|
||
chatType("CHANNEL9", 0, 103, 255, 192, 192)
|
||
chatType("CHANNEL10", 0, 104, 255, 192, 192)
|
||
chatType("COMBAT_SELF_HITS", 0, 28, 255, 255, 255)
|
||
chatType("COMBAT_SELF_MISSES", 0, 29, 255, 255, 255)
|
||
chatType("COMBAT_PET_HITS", 0, 30, 255, 255, 255)
|
||
chatType("COMBAT_PET_MISSES", 0, 31, 255, 255, 255)
|
||
chatType("COMBAT_PARTY_HITS", 0, 32, 255, 255, 255)
|
||
chatType("COMBAT_PARTY_MISSES", 0, 33, 255, 255, 255)
|
||
chatType("COMBAT_FRIENDLYPLAYER_HITS", 0, 34, 255, 255, 255)
|
||
chatType("COMBAT_FRIENDLYPLAYER_MISSES", 0, 35, 255, 255, 255)
|
||
chatType("COMBAT_HOSTILEPLAYER_HITS", 0, 36, 255, 255, 255)
|
||
chatType("COMBAT_HOSTILEPLAYER_MISSES", 0, 37, 255, 255, 255)
|
||
chatType("COMBAT_CREATURE_VS_SELF_HITS", 0, 38, 255, 47, 47)
|
||
chatType("COMBAT_CREATURE_VS_SELF_MISSES", 0, 39, 255, 47, 47)
|
||
chatType("COMBAT_CREATURE_VS_PARTY_HITS", 0, 40, 255, 255, 255)
|
||
chatType("COMBAT_CREATURE_VS_PARTY_MISSES", 0, 41, 255, 255, 255)
|
||
chatType("COMBAT_CREATURE_VS_CREATURE_HITS", 0, 42, 255, 255, 255)
|
||
chatType("COMBAT_CREATURE_VS_CREATURE_MISSES", 0, 43, 255, 255, 255)
|
||
chatType("COMBAT_FRIENDLY_DEATH", 0, 44, 255, 255, 255)
|
||
chatType("COMBAT_HOSTILE_DEATH", 0, 45, 255, 255, 255)
|
||
chatType("COMBAT_XP_GAIN", 0, 46, 111, 111, 255)
|
||
chatType("COMBAT_HONOR_GAIN", 0, 82, 224, 202, 10)
|
||
chatType("SPELL_SELF_DAMAGE", 0, 47, 255, 255, 0)
|
||
chatType("SPELL_SELF_BUFF", 0, 48, 255, 255, 0)
|
||
chatType("SPELL_PET_DAMAGE", 0, 49, 255, 255, 255)
|
||
chatType("SPELL_PET_BUFF", 0, 50, 255, 255, 255)
|
||
chatType("SPELL_PARTY_DAMAGE", 0, 51, 255, 255, 255)
|
||
chatType("SPELL_PARTY_BUFF", 0, 52, 255, 255, 255)
|
||
chatType("SPELL_FRIENDLYPLAYER_DAMAGE", 0, 53, 255, 255, 255)
|
||
chatType("SPELL_FRIENDLYPLAYER_BUFF", 0, 54, 255, 255, 255)
|
||
chatType("SPELL_HOSTILEPLAYER_DAMAGE", 0, 55, 255, 255, 255)
|
||
chatType("SPELL_HOSTILEPLAYER_BUFF", 0, 56, 255, 255, 255)
|
||
chatType("SPELL_CREATURE_VS_SELF_DAMAGE", 0, 57, 202, 76, 217)
|
||
chatType("SPELL_CREATURE_VS_SELF_BUFF", 0, 58, 255, 255, 255)
|
||
chatType("SPELL_CREATURE_VS_PARTY_DAMAGE", 0, 59, 255, 255, 255)
|
||
chatType("SPELL_CREATURE_VS_PARTY_BUFF", 0, 60, 255, 255, 255)
|
||
chatType("SPELL_CREATURE_VS_CREATURE_DAMAGE", 0, 61, 255, 255, 255)
|
||
chatType("SPELL_CREATURE_VS_CREATURE_BUFF", 0, 62, 255, 255, 255)
|
||
chatType("SPELL_TRADESKILLS", 0, 63, 255, 255, 255)
|
||
chatType("SPELL_DAMAGESHIELDS_ON_SELF", 0, 64, 255, 255, 255)
|
||
chatType("SPELL_DAMAGESHIELDS_ON_OTHERS", 0, 65, 255, 255, 255)
|
||
chatType("SPELL_AURA_GONE_SELF", 0, 66, 255, 255, 255)
|
||
chatType("SPELL_AURA_GONE_PARTY", 0, 67, 255, 255, 255)
|
||
chatType("SPELL_AURA_GONE_OTHER", 0, 68, 255, 255, 255)
|
||
chatType("SPELL_ITEM_ENCHANTMENTS", 0, 69, 255, 255, 255)
|
||
chatType("SPELL_BREAK_AURA", 0, 70, 255, 255, 255)
|
||
chatType("SPELL_PERIODIC_SELF_DAMAGE", 0, 71, 255, 255, 255)
|
||
chatType("SPELL_PERIODIC_SELF_BUFFS", 0, 72, 255, 255, 255)
|
||
chatType("SPELL_PERIODIC_PARTY_DAMAGE", 0, 73, 255, 255, 255)
|
||
chatType("SPELL_PERIODIC_PARTY_BUFFS", 0, 74, 255, 255, 255)
|
||
chatType("SPELL_PERIODIC_FRIENDLYPLAYER_DAMAGE", 0, 75, 255, 255, 255)
|
||
chatType("SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS", 0, 76, 255, 255, 255)
|
||
chatType("SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE", 0, 77, 255, 255, 255)
|
||
chatType("SPELL_PERIODIC_HOSTILEPLAYER_BUFFS", 0, 78, 255, 255, 255)
|
||
chatType("SPELL_PERIODIC_CREATURE_DAMAGE", 0, 79, 255, 255, 255)
|
||
chatType("SPELL_PERIODIC_CREATURE_BUFFS", 0, 80, 255, 255, 255)
|
||
chatType("SPELL_FAILED_LOCALPLAYER", 0, 81, 255, 255, 255)
|
||
chatType("BG_SYSTEM_NEUTRAL", 0, 83, 255, 120, 10)
|
||
chatType("BG_SYSTEM_ALLIANCE", 0, 84, 0, 174, 239)
|
||
chatType("BG_SYSTEM_HORDE", 0, 85, 255, 0, 0)
|
||
chatType("COMBAT_FACTION_CHANGE", 0, 86, 128, 128, 255)
|
||
chatType("MONEY", 0, 87, 255, 255, 0)
|
||
chatType("RAID_LEADER", 0, 88, 255, 219, 183)
|
||
chatType("RAID_WARNING", 0, 89, 255, 219, 183)
|
||
chatType("RAID_BOSS_EMOTE", 0, 91, 255, 219, 183)
|
||
chatType("FILTERED", 0, 92, 255, 0, 0)
|
||
chatType("BATTLEGROUND", 1, 93, 255, 127, 0)
|
||
chatType("BATTLEGROUND_LEADER", 0, 94, 255, 219, 183)
|
||
|
||
-- ── The FloatingChatFrame constants, as the GLOBALS the reference declares ───────────
|
||
-- These were comments here until decision 1589 (B246): the Chat options page's *Remove
|
||
-- Chat Hover Delay* row writes the first two through `SetChatMouseOverDelay`, so they
|
||
-- have to be values something can move rather than numbers inlined in the fade. Names,
|
||
-- spellings and values are the reference's own (FloatingChatFrame.lua l.8-12), and two of
|
||
-- them — `CHAT_FRAME_FADE_TIME` and `CHAT_FRAME_TEXTURES` — are in the captured 1.12 `_G`
|
||
-- as `framexml` globals, so an addon can read them.
|
||
CHAT_TAB_SHOW_DELAY = 0.2
|
||
CHAT_FRAME_FADE_TIME = 0.15
|
||
DEFAULT_CHATFRAME_ALPHA = 0.25
|
||
DEFAULT_CHATFRAME_COLOR = { r = 0, g = 0, b = 0 }
|
||
-- Still quoted rather than declared (nothing writes them): CHAT_BUTTON_FLASH_TIME=0.5,
|
||
-- MESSAGE_SCROLLBUTTON_INITIAL_DELAY=0, MESSAGE_SCROLLBUTTON_SCROLL_DELAY=0.05.
|
||
|
||
-- REMOVE_CHAT_DELAY — the Chat options page's *Remove Chat Hover Delay* row (decision
|
||
-- 1589), a saved variable exactly as the reference has it (`UIOptionsFrameCheckButtons`
|
||
-- CheckButton18, `uvar = "REMOVE_CHAT_DELAY"`, `default = "0"`). Declared HERE because
|
||
-- this is the file that owns what it changes — a row whose defining file was not loaded
|
||
-- gets a nil `applyFunc` and is skipped, which is what keeps a partial harness honest
|
||
-- (OptionsFrame.xml's `OptionsFrame_ApplySavedSettings`). The `RegisterForSave` sits on
|
||
-- the line under the assignment, per 1128's load order.
|
||
REMOVE_CHAT_DELAY = "0"
|
||
RegisterForSave("REMOVE_CHAT_DELAY")
|
||
|
||
-- CHAT_LOCKED — the master "no chat window may be moved or resized" switch, the reference's
|
||
-- own saved variable. The reference declares it in `UIOptionsFrame.lua` l.96-97 (`"0"` plus
|
||
-- `RegisterForSave`) because that is where its *Lock Chat Settings* checkbox lives; ours is
|
||
-- declared here for the reason the row above is — the file that owns what it changes owns
|
||
-- the variable, and a row whose defining file was not loaded gets a nil `applyFunc` and is
|
||
-- skipped rather than half-applied.
|
||
--
|
||
-- **`"0"` — off.** Note the reference's own two-value disagreement: its checkbox table says
|
||
-- `default = "1"` (`UIOptionsFrame.lua:44`) while the variable is initialised `"0"` two
|
||
-- lines later, and the initialiser is what a fresh client actually runs. `"0"` here too.
|
||
-- The per-window lock (`FCF_SetLocked`, the tab menu's row) is the one that ships ON; this
|
||
-- one is the blanket override on top of it.
|
||
CHAT_LOCKED = "0"
|
||
RegisterForSave("CHAT_LOCKED")
|
||
|
||
-- The tab menu's lock row labels (GlobalStrings l.2590 / l.4448). Live text comes off the
|
||
-- player's own `GlobalStrings.lua`; these are the headless-test fallback this file already
|
||
-- uses everywhere else.
|
||
LOCK_WINDOW = LOCK_WINDOW or "Lock Window"
|
||
UNLOCK_WINDOW = UNLOCK_WINDOW or "Unlock Window"
|
||
|
||
-- The row's applyFunc. The reference has no such function because it calls
|
||
-- `SetChatMouseOverDelay(REMOVE_CHAT_DELAY)` inline from two places (its options load,
|
||
-- l.216, and the checkbox's own commit); ours needs a zero-argument name to hand the row,
|
||
-- and this is it — one line, in the file that owns both halves.
|
||
function ChatFrame_ApplyMouseOverDelay()
|
||
SetChatMouseOverDelay(REMOVE_CHAT_DELAY)
|
||
end
|
||
|
||
-- SetChatMouseOverDelay(noDelay) — ref FloatingChatFrame.lua l.1456-1464, verbatim in
|
||
-- effect: "1" collapses BOTH the stationary-hover delay and the fade to zero, so the box
|
||
-- appears the instant the cursor crosses it. The options row's `applyFunc` is the only
|
||
-- caller, and `UIOptionsFrame_Load` calls it at login for the same reason ours does.
|
||
function SetChatMouseOverDelay(noDelay)
|
||
if noDelay == "1" then
|
||
CHAT_TAB_SHOW_DELAY = 0
|
||
CHAT_FRAME_FADE_TIME = 0
|
||
else
|
||
CHAT_TAB_SHOW_DELAY = 0.2
|
||
CHAT_FRAME_FADE_TIME = 0.15
|
||
end
|
||
end
|
||
|
||
-- ── BenillaFCF: the FloatingChatFrame behaviors, re-expressed (ref FloatingChatFrame.lua)
|
||
BenillaFCF = {
|
||
selected = 1, -- SELECTED_DOCK_FRAME (1 = General, 2 = Combat Log)
|
||
hover = false, -- cursor inside the dock's padded rect
|
||
hoverTime = 0, -- stationary-cursor accumulator (ref: moving resets it)
|
||
oldX = 0, oldY = 0,
|
||
reveal = 0, -- 0..1 master fade the tab/texture alphas scale by
|
||
revealTarget = 0,
|
||
-- What the apply block below last WROTE, so an unchanged frame writes nothing
|
||
-- (decision 1396). -1 is unreachable for both, so the first pass always applies.
|
||
appliedReveal = -1,
|
||
appliedSelected = -1,
|
||
tabsSettled = false, -- both tabs' host-measured label widths have landed
|
||
flashTab = 0, -- tab id whose whisper flash runs (0 = none), 0288 P4 wires it
|
||
flashLeft = 0, -- remaining flash seconds (ref: 60s, blink 0.25/0.25)
|
||
}
|
||
|
||
-- CHAT_FRAME_TEXTURES — the reference's own list (FloatingChatFrame.lua l.14-24), verbatim
|
||
-- in content and in spelling: the background plus the eight RESIZE GRIPS' textures. The
|
||
-- window's border art IS the grips, which is why one list drives both the hover fade
|
||
-- (`FCF_OnUpdate`) and the tint/alpha setters (`FCF_SetWindowColor`/`FCF_SetWindowAlpha`) —
|
||
-- the ring appears with the box and is invisible with it.
|
||
--
|
||
-- benilla used to author these as plain `$parentBorder*` textures ("the hover LOOK without
|
||
-- the resize mechanics"); the mechanics landed with the move/resize arc, so the suffixes
|
||
-- are the reference's again and the pieces are its buttons' children.
|
||
CHAT_FRAME_TEXTURES = {
|
||
"Background",
|
||
"ResizeTopLeftTexture", "ResizeTopRightTexture",
|
||
"ResizeBottomLeftTexture", "ResizeBottomRightTexture",
|
||
"ResizeTopTexture", "ResizeBottomTexture",
|
||
"ResizeLeftTexture", "ResizeRightTexture",
|
||
}
|
||
|
||
-- The live texture objects of one window's set. Global: the load-time tint below needs it.
|
||
function BenillaFCF_Textures(id)
|
||
local n = "ChatFrame" .. id
|
||
local list = {}
|
||
for _, suffix in ipairs(CHAT_FRAME_TEXTURES) do
|
||
local t = getglobal(n .. suffix)
|
||
if t then table.insert(list, t) end
|
||
end
|
||
return list
|
||
end
|
||
|
||
-- ── The per-window LOOK: the tint and the base alpha (decision 1589, B246) ───────────
|
||
-- `frame.oldAlpha` and the CHAT_FRAME_TEXTURES vertex colour in the reference; a pair of
|
||
-- constants here (alpha 0, black) until B246 asked for a way to change them. The store is
|
||
-- the engine's — `GetChatWindowInfo`/`SetChatWindowColor`/`SetChatWindowAlpha`, which is
|
||
-- what an addon reads too — and this table is the per-frame cache the fade reads each
|
||
-- frame instead of re-crossing that seam.
|
||
BenillaFCF_Look = {}
|
||
|
||
local function lookFor(id)
|
||
local l = BenillaFCF_Look[id]
|
||
if not l then
|
||
l = { r = 0, g = 0, b = 0, a = 0 }
|
||
BenillaFCF_Look[id] = l
|
||
end
|
||
return l
|
||
end
|
||
|
||
-- FCF_SetWindowColor(frame, r, g, b, doNotSave) — ref FloatingChatFrame.lua l.696-704.
|
||
-- The tint is applied here and now (a vertex colour is not part of the fade); the save is
|
||
-- what makes it outlive the session, and `doNotSave` is the reference's own flag for
|
||
-- "this value CAME from the store" (its `FloatingChatFrame_Update` passes it).
|
||
function FCF_SetWindowColor(frame, r, g, b, doNotSave)
|
||
local id = frame:GetID()
|
||
local l = lookFor(id)
|
||
l.r, l.g, l.b = r, g, b
|
||
for _, t in ipairs(BenillaFCF_Textures(id)) do
|
||
t:SetVertexColor(r, g, b)
|
||
end
|
||
if not doNotSave then
|
||
SetChatWindowColor(id, r, g, b)
|
||
end
|
||
end
|
||
|
||
-- FCF_SetWindowAlpha(frame, alpha, doNotSave) — ref l.706-716.
|
||
--
|
||
-- The reference writes the nine textures' alpha straight here; ours cannot, because the
|
||
-- hover fade below is the single writer of those alphas (decision 1396's latch is what
|
||
-- keeps an idle dock from rewriting them every frame). So this moves the BASE the fade
|
||
-- interpolates from and invalidates that latch, which makes the next frame's apply the
|
||
-- one write — the reference's own `frame.oldAlpha` bookkeeping, minus the double write.
|
||
function FCF_SetWindowAlpha(frame, alpha, doNotSave)
|
||
local id = frame:GetID()
|
||
lookFor(id).a = alpha
|
||
frame.oldAlpha = alpha -- ref: "Remember the alpha"
|
||
BenillaFCF.appliedReveal = -1
|
||
if not doNotSave then
|
||
SetChatWindowAlpha(id, alpha)
|
||
end
|
||
end
|
||
|
||
-- The alpha a window's chrome actually wears at a given point in the hover fade.
|
||
--
|
||
-- **The reference only fades a window that is DIMMER than the hover value**: both the fade
|
||
-- in and the fade out are guarded by `chatFrame.oldAlpha < DEFAULT_CHATFRAME_ALPHA`
|
||
-- (FloatingChatFrame.lua l.871 / l.913). So a player who has made their chat box solid
|
||
-- keeps it solid when the cursor leaves — the box does not dim itself back down — while
|
||
-- the shipped default (alpha 0) rides the full 0 → 0.25 ramp exactly as before.
|
||
local function windowAlpha(id, reveal)
|
||
local base = lookFor(id).a
|
||
if base >= DEFAULT_CHATFRAME_ALPHA then
|
||
return base
|
||
end
|
||
return base + (DEFAULT_CHATFRAME_ALPHA - base) * reveal
|
||
end
|
||
|
||
-- ── The lock, the move and the resize (ref FloatingChatFrame.lua) ────────────────────
|
||
--
|
||
-- Three gates decide whether a window may be dragged or resized, and the reference applies
|
||
-- all three at both verbs (`FCF_Resize` l.583-601, `FCF_Tab_OnClick`'s move arm l.1247-1256):
|
||
-- 1 · `FCF_Get_ChatLocked()` — the blanket `CHAT_LOCKED` saved variable;
|
||
-- 2 · `chatFrame.isLocked` — this window's own lock, the tab menu's row;
|
||
-- 3 · docked-and-not-the-default — a docked window's rect belongs to the DOCK, so only
|
||
-- `DEFAULT_CHAT_FRAME` may move it. ChatFrame2's anchors ARE ChatFrame1's corners:
|
||
-- resizing it directly would stretch it off the window it follows.
|
||
|
||
-- The eight grips' name suffixes, the reference's own (see `CHAT_FRAME_TEXTURES`).
|
||
local RESIZE_GRIPS = {
|
||
"TopLeft", "TopRight", "BottomLeft", "BottomRight",
|
||
"Top", "Bottom", "Left", "Right",
|
||
}
|
||
|
||
-- May this window be moved/resized at all? The three gates above, in one place because
|
||
-- both verbs and the lock's own plumbing ask the same question.
|
||
function FCF_CanMove(chatFrame)
|
||
if FCF_Get_ChatLocked() then return nil end
|
||
if chatFrame.isLocked then return nil end
|
||
if chatFrame.isDocked and chatFrame ~= DEFAULT_CHAT_FRAME then return nil end
|
||
return 1
|
||
end
|
||
|
||
-- Hand the window's EDGES and its BODY to the mouse, or take them back. benilla's own —
|
||
-- the reference needs nothing like it because its chat window is `enableMouse="false"` and
|
||
-- so competes for no click. Ours does (`OnClick`, `OnHyperlinkClick`), so a locked window
|
||
-- has to be exactly as click-through as it was before this arc: eight live grip buttons
|
||
-- would shadow the outer 16px of the window, and the bottom strip is where the newest chat
|
||
-- line — the one with the item link in it — is drawn.
|
||
function BenillaFCF_ApplyLock(chatFrame)
|
||
local free = FCF_CanMove(chatFrame)
|
||
local name = chatFrame:GetName()
|
||
for _, grip in ipairs(RESIZE_GRIPS) do
|
||
local b = getglobal(name .. "Resize" .. grip)
|
||
if b then b:EnableMouse(free and true or false) end
|
||
end
|
||
local tab = getglobal(name .. "Tab")
|
||
if free then
|
||
chatFrame:RegisterForDrag("LeftButton")
|
||
if tab then tab:RegisterForDrag("LeftButton") end
|
||
else
|
||
chatFrame:RegisterForDrag()
|
||
if tab then tab:RegisterForDrag() end
|
||
end
|
||
end
|
||
|
||
-- ref l.802-805, verbatim: the frame field and the engine store, together.
|
||
-- `BenillaFCF_ApplyLock` is ours, and it is what makes the flag do something structurally
|
||
-- rather than only in the two early returns below.
|
||
function FCF_SetLocked(chatFrame, isLocked)
|
||
chatFrame.isLocked = isLocked
|
||
SetChatWindowLocked(chatFrame:GetID(), isLocked)
|
||
BenillaFCF_ApplyLock(chatFrame)
|
||
end
|
||
|
||
-- ref l.785-800. **Its undock branch is deliberately absent**: the reference, on unlocking
|
||
-- a docked frame that is not the default one, calls `FCF_UnDockFrame`, re-centres it on the
|
||
-- screen and re-seats its tab. benilla has no undock (0288 §2), so that arm has nothing to
|
||
-- run — named here rather than stubbed (1134 §4), and reachable only from the Combat Log's
|
||
-- tab, whose own gate above already refuses to move it.
|
||
function FCF_ToggleLock()
|
||
local chatFrame = FCF_GetCurrentChatFrame()
|
||
if chatFrame.isLocked then
|
||
FCF_SetLocked(chatFrame, nil)
|
||
else
|
||
FCF_SetLocked(chatFrame, 1)
|
||
end
|
||
end
|
||
|
||
-- ref l.1428-1441, verbatim. `CHAT_LOCKED` is the blanket switch over every window; the
|
||
-- re-apply is ours, for the reason `FCF_SetLocked`'s is.
|
||
function FCF_Set_ChatLocked(isLocked)
|
||
-- The saved variable is a STRING, not a boolean — the uvar contract every options row
|
||
-- in this client shares, and what `RegisterForSave` writes out.
|
||
CHAT_LOCKED = isLocked and "1" or "0"
|
||
-- EVERY window, which is what "the blanket switch" means and what the reference's own
|
||
-- l.1428-1441 loop does — not the dock's two. It read `1, 2` while those were the only
|
||
-- windows with grips and tabs to re-arm (1714).
|
||
for i = 1, NUM_CHAT_WINDOWS do
|
||
local frame = getglobal("ChatFrame" .. i)
|
||
if frame then BenillaFCF_ApplyLock(frame) end
|
||
end
|
||
end
|
||
|
||
function FCF_Get_ChatLocked()
|
||
-- `1`/nil rather than a boolean, because every caller here is an `if` and the
|
||
-- reference's own callers pass the result around as one.
|
||
if CHAT_LOCKED == "1" then return 1 end
|
||
return nil
|
||
end
|
||
|
||
-- ref l.583-601. `this` is the grip button; the window is its parent. The commented-out
|
||
-- `DEFAULT_CHAT_FRAME.resizing = 1` block in the reference is left out with it.
|
||
function FCF_Resize(anchorPoint)
|
||
local chatFrame = this:GetParent()
|
||
if not FCF_CanMove(chatFrame) then return end
|
||
chatFrame.resizing = 1
|
||
chatFrame:StartSizing(anchorPoint)
|
||
end
|
||
|
||
-- ref l.603-609. The reference also calls `FCF_DockUpdate()` when the parent is the default
|
||
-- frame; ours has no such function and needs none — the tab is anchored to the window's own
|
||
-- background, so it follows a resize by anchor rather than by a re-layout pass.
|
||
function FCF_StopResize()
|
||
local chatFrame = this:GetParent()
|
||
chatFrame:StopMovingOrSizing()
|
||
chatFrame.resizing = nil
|
||
end
|
||
|
||
-- The move half. The reference reaches `StartMoving`/`StopMovingOrSizing` from
|
||
-- `FCF_Tab_OnClick`'s press/release arms; ours reaches them from the drag trio, for the
|
||
-- reasons written at the template's `OnDragStart`. Same two engine verbs, same three gates.
|
||
--
|
||
-- `MOVING_CHATFRAME` (the reference's global) is not set: its only readers there are
|
||
-- `FCF_IsValidChatFrame` and the dock-region hit test, neither of which exists here, and a
|
||
-- global nothing reads is state that starts lying the first time a path forgets to clear it.
|
||
function FCF_StartMove(chatFrame)
|
||
if not FCF_CanMove(chatFrame) then return end
|
||
chatFrame.moving = 1
|
||
chatFrame:StartMoving()
|
||
end
|
||
|
||
function FCF_StopMove(chatFrame)
|
||
chatFrame:StopMovingOrSizing()
|
||
chatFrame.moving = nil
|
||
end
|
||
|
||
-- Is either dock window mid-drag? The hover fade holds the chrome visible while it is —
|
||
-- ref FCF_OnUpdate's own `(MouseIsOver(...) or chatFrame.resizing)`. Without it the box
|
||
-- fades out under a moving cursor and takes the grip out of the hand holding it.
|
||
function BenillaFCF_Dragging()
|
||
for _, f in ipairs(DOCKED_CHAT_FRAMES) do
|
||
if f.resizing or f.moving then return 1 end
|
||
end
|
||
return nil
|
||
end
|
||
|
||
-- FloatingChatFrame_Update's look half (ref l.49-57): pull every window's stored tint and
|
||
-- alpha out of the engine and seat them, WITHOUT saving them back (they came from there).
|
||
-- Runs at load and on `UPDATE_CHAT_WINDOWS`, which is the event the host fires after it
|
||
-- restores the player's file.
|
||
function BenillaFCF_LoadWindowLooks()
|
||
-- All seven, because the reference's `FloatingChatFrame_Update` is per window and
|
||
-- every one of them now has the chrome these setters paint (1712). It read `1, 2`
|
||
-- while ChatFrame3..7 had no textures to tint.
|
||
for i = 1, NUM_CHAT_WINDOWS do
|
||
local frame = getglobal("ChatFrame" .. i)
|
||
if frame then
|
||
local _, fontSize, r, g, b, a, _, locked = GetChatWindowInfo(i)
|
||
FCF_SetWindowColor(frame, r, g, b, 1)
|
||
FCF_SetWindowAlpha(frame, a, 1)
|
||
-- ref l.56, in the same block and for the same reason as the two above: the
|
||
-- window's stored LOCKED is seated onto the frame. It ships `1` (the stock
|
||
-- chat-cache row), so both dock windows start locked exactly as a fresh 1.12
|
||
-- client's do — and the write back through `SetChatWindowLocked` is idempotent,
|
||
-- so seating what was just read dirties nothing.
|
||
FCF_SetLocked(frame, locked)
|
||
-- SIZE 0 is the stock "use the font's own height" and applies nothing.
|
||
if fontSize and fontSize > 0 then
|
||
FCF_SetChatWindowFontSize(frame, fontSize, 1)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
|
||
-- ── The DOCK (decision 1714) ────────────────────────────────────────────────────────
|
||
-- `DOCKED_CHAT_FRAMES` is the reference's own list — the docked windows in tab order —
|
||
-- and `SELECTED_DOCK_FRAME` the one of them that is shown. Everything below is
|
||
-- FloatingChatFrame.lua l.1049-1302, and the loops that used to run `for i = 1, 2` run
|
||
-- over the list instead: "the dock is windows 1 and 2" was a fact about our seeding, and
|
||
-- writing it as a literal is what made `FCF_DockFrame` impossible to answer honestly.
|
||
--
|
||
-- **The verbs exist; the GESTURE still does not.** 0288 §3 excluded drag/undock/resize as
|
||
-- chat *settings*, on the director's word, and nothing here adds a way for a player to
|
||
-- dock or undock a window — no dock regions, no tab-drag docking, no Remove row in the
|
||
-- tab menu. What changed is that an ADDON can now call the functions, which is a
|
||
-- different thing from us shipping the UI: pfUI's `RefreshChat` calls `FCF_DockFrame` on
|
||
-- every docked window and `FCF_UnDockFrame` on every undocked one, unguarded
|
||
-- (`chat.lua:374`/`:384`), and it is the top-installed addon of the expansion with
|
||
-- pfQuest, pfQuest-turtle and ShaguDPS embedding the same module. On our seeding both of
|
||
-- its calls hit the reference's own early returns and do nothing at all — which is the
|
||
-- point: a faithful no-op is what the reference gives it, and a missing global is not.
|
||
DOCKED_CHAT_FRAMES = {}
|
||
SELECTED_DOCK_FRAME = nil
|
||
|
||
-- ref l.1261-1264, verbatim. Every tab's seat, docked or not: `x` is the running dock
|
||
-- width for a docked tab and `0` for a loose one.
|
||
function FCF_SetTabPosition(chatFrame, x)
|
||
local chatTab = getglobal(chatFrame:GetName() .. "Tab")
|
||
chatTab:SetPoint("BOTTOMLEFT", chatFrame:GetName() .. "Background", "TOPLEFT", x + 2, 0)
|
||
end
|
||
|
||
-- ref l.1276-1285, verbatim: compact the list and write each window's POSITION back to the
|
||
-- engine record, which is what `GetChatWindowInfo`'s 9th return hands the next load.
|
||
function FCF_SaveDock()
|
||
local count = 1
|
||
local tempDock = DOCKED_CHAT_FRAMES
|
||
DOCKED_CHAT_FRAMES = {}
|
||
for _, value in ipairs(tempDock) do
|
||
DOCKED_CHAT_FRAMES[count] = value
|
||
SetChatWindowDocked(value:GetID(), count)
|
||
count = count + 1
|
||
end
|
||
end
|
||
|
||
-- ref l.1049-1140. Three of its four jobs are here; the fourth is stated, not stubbed.
|
||
--
|
||
-- · the non-first docked windows take DEFAULT_CHAT_FRAME's rect (which is what being
|
||
-- docked MEANS, and what ChatFrame2's XML anchors already say statically — this is
|
||
-- the same three points, re-applied whenever the list changes);
|
||
-- · show the selected window, hide the rest;
|
||
-- · lay the tabs out left to right by running dock width.
|
||
--
|
||
-- NOT here: the dock REGIONS (`ChatFrame<n>TabDockRegion`) the reference hides and
|
||
-- re-anchors in the same loop. They exist only to hit-test a tab being DRAGGED onto the
|
||
-- dock, which is the gesture 0288 §3 leaves out; there is nothing to anchor and nothing
|
||
-- to hide. Also not here: the overflow pass that shrinks every tab to the average width
|
||
-- when the strip outgrows the window (l.1101-1139) — it needs
|
||
-- `PanelTemplates_TabResize(0, tab, width)`'s explicit-width form, and two tabs at their
|
||
-- measured widths cannot outgrow a 430 px window.
|
||
--
|
||
-- **The tab ALPHAS are deliberately not written here**, though the reference writes them
|
||
-- in this loop. `FCF_OnUpdate` owns them behind a latch keyed on `(reveal, selected)`
|
||
-- (1395): writing them from here would leave the latch believing it had already applied a
|
||
-- value it had not, and the next hover would fade from the wrong place. Setting
|
||
-- `BenillaFCF.selected` below is what makes the latch fire, one frame later, with the
|
||
-- whole fade state consistent.
|
||
function FCF_DockUpdate()
|
||
local dockWidth = 0
|
||
local previousDockedFrame
|
||
for index, value in ipairs(DOCKED_CHAT_FRAMES) do
|
||
if index ~= 1 then
|
||
value:ClearAllPoints()
|
||
value:SetPoint("TOPLEFT", DEFAULT_CHAT_FRAME, "TOPLEFT", 0, 0)
|
||
value:SetPoint("BOTTOMLEFT", DEFAULT_CHAT_FRAME, "BOTTOMLEFT", 0, 0)
|
||
value:SetPoint("BOTTOMRIGHT", DEFAULT_CHAT_FRAME, "BOTTOMRIGHT", 0, 0)
|
||
end
|
||
if value == SELECTED_DOCK_FRAME then value:Show() else value:Hide() end
|
||
local chatTab = getglobal(value:GetName() .. "Tab")
|
||
if chatTab then
|
||
BenillaFCF_TabResize(chatTab)
|
||
if previousDockedFrame then
|
||
chatTab:ClearAllPoints()
|
||
FCF_SetTabPosition(value, dockWidth)
|
||
end
|
||
dockWidth = dockWidth + chatTab:GetWidth()
|
||
end
|
||
previousDockedFrame = value
|
||
end
|
||
end
|
||
|
||
-- ref l.1204-1217. Takes a FRAME, where `BenillaFCF_SelectDock` below takes a dock id.
|
||
--
|
||
-- It no longer RAISES on an undocked frame. It used to, because "select ChatFrame5" had
|
||
-- no meaning in a client with no dock model, and answering with silence would have left
|
||
-- the caller's next `AddMessage` landing in a still-hidden window. There is a dock model
|
||
-- now, and the reference's own answer is the assignment below: `FCF_DockUpdate` then
|
||
-- hides every docked window (none of them is the selection) and leaves the undocked one
|
||
-- exactly as it was. That is not a nice answer, but it is the client's, and the two corpus
|
||
-- callers — CustomNameplates `CustomNameplates.lua:69` and Roid-Macros `Utility.lua:40`,
|
||
-- both `FCF_SelectDockFrame(DEFAULT_CHAT_FRAME)` before printing — never reach it.
|
||
function FCF_SelectDockFrame(frame)
|
||
if not frame then
|
||
error("FCF_SelectDockFrame: nil frame", 2)
|
||
end
|
||
SELECTED_DOCK_FRAME = frame
|
||
-- `SELECTED_CHAT_FRAME` tracks the SELECTION, which is what makes it different from
|
||
-- `DEFAULT_CHAT_FRAME` (the print target, seeded once and left alone). The reference
|
||
-- writes it from the tab's own OnClick before it even reaches here
|
||
-- (`FloatingChatFrame.lua:1232`) and again on a drag (`FloatingChatFrame.xml:158`);
|
||
-- ours has one selection verb every one of those paths goes through, so it is written
|
||
-- here rather than replicated at three call sites.
|
||
SELECTED_CHAT_FRAME = frame
|
||
BenillaFCF.selected = frame:GetID()
|
||
-- Stop the selected tab's whisper flash (ref UIFrameFlashRemoveFrame + Hide).
|
||
if BenillaFCF.flashTab == frame:GetID() then
|
||
BenillaFCF.flashTab = 0
|
||
local flash = getglobal(frame:GetName() .. "TabFlash")
|
||
if flash then flash:Hide() end
|
||
end
|
||
FCF_DockUpdate()
|
||
end
|
||
|
||
-- The id-taking entry point, which is what every call site in this file uses.
|
||
function BenillaFCF_SelectDock(id)
|
||
FCF_SelectDockFrame(getglobal("ChatFrame" .. id))
|
||
end
|
||
|
||
-- ── The scroll column's SIDE (ref l.1015-1047, verbatim) ────────────────────────────
|
||
-- The column hangs off the window's left edge until the window is close enough to the
|
||
-- screen's left that it would hang off-screen, at which point it flips to the right. Only
|
||
-- `BottomButton` is re-anchored: `DownButton` sits on top of it and `UpButton` on top of
|
||
-- that, so the one point moves all three (the same chain the template declares).
|
||
--
|
||
-- The template's authored anchor IS the "left" case at the reference's own numbers
|
||
-- (`BOTTOMLEFT` of the frame's `BOTTOMLEFT`, −32/−4), so the first `FCF_SetButtonSide`
|
||
-- re-applies the point it already has and only the `buttonSide` field actually changes.
|
||
--
|
||
-- pfUI calls `FCF_UpdateButtonSide` directly on every member of `DOCKED_CHAT_FRAMES`
|
||
-- (`chat.lua:449`) after re-parenting the windows into its own panels, which is exactly
|
||
-- what this is for: it has just moved them, so the side has to be re-derived.
|
||
function FCF_UpdateButtonSide(chatFrame)
|
||
local leftDist = chatFrame:GetLeft()
|
||
local rightDist = GetScreenWidth() - chatFrame:GetRight()
|
||
local changed = nil
|
||
if (leftDist > 0 and leftDist <= rightDist) or rightDist < 0 then
|
||
if chatFrame.buttonSide ~= "left" then
|
||
FCF_SetButtonSide(chatFrame, "left")
|
||
changed = 1
|
||
end
|
||
else
|
||
if chatFrame.buttonSide ~= "right" or leftDist < 0 then
|
||
FCF_SetButtonSide(chatFrame, "right")
|
||
changed = 1
|
||
end
|
||
end
|
||
return changed
|
||
end
|
||
|
||
function FCF_SetButtonSide(chatFrame, buttonSide)
|
||
if chatFrame.buttonSide == buttonSide then return end
|
||
local bottom = getglobal(chatFrame:GetName() .. "BottomButton")
|
||
if buttonSide == "left" then
|
||
bottom:SetPoint("BOTTOMLEFT", chatFrame, "BOTTOMLEFT", -32, -4)
|
||
elseif buttonSide == "right" then
|
||
bottom:SetPoint("BOTTOMLEFT", chatFrame, "BOTTOMRIGHT", 0, -4)
|
||
end
|
||
chatFrame.buttonSide = buttonSide
|
||
end
|
||
|
||
function FCF_GetButtonSide(chatFrame)
|
||
return chatFrame.buttonSide
|
||
end
|
||
|
||
-- ref l.1148-1175, complete. The early return is the whole of what pfUI reaches: it calls
|
||
-- this on windows whose `isDocked` is already set.
|
||
function FCF_DockFrame(frame, index)
|
||
if frame.isDocked then return end
|
||
frame.isDocked = 1
|
||
if not index then
|
||
index = table.getn(DOCKED_CHAT_FRAMES) + 1
|
||
end
|
||
table.insert(DOCKED_CHAT_FRAMES, index, frame)
|
||
FCF_SaveDock()
|
||
FCF_SelectDockFrame(frame)
|
||
-- ref l.1167-1171: the dock's base window derives its own side; every other member
|
||
-- copies it, because they share its rect and a per-window derivation would race the
|
||
-- re-anchor above.
|
||
if frame == DEFAULT_CHAT_FRAME then
|
||
FCF_UpdateButtonSide(frame)
|
||
else
|
||
FCF_SetButtonSide(frame, FCF_GetButtonSide(DEFAULT_CHAT_FRAME))
|
||
end
|
||
FCF_SetLocked(frame, 1)
|
||
end
|
||
|
||
-- ref l.1177-1202. Minus the dock-region reset (l.1195-1197), for the reason FCF_DockUpdate
|
||
-- gives. The tab going to full alpha is the reference's own line and is kept: a loose tab
|
||
-- is not part of the dock's fade, so it does not follow the reveal.
|
||
function FCF_UnDockFrame(frame)
|
||
if frame == DEFAULT_CHAT_FRAME or not frame.isDocked then return end
|
||
SetChatWindowDocked(frame:GetID(), nil)
|
||
for index, value in ipairs(DOCKED_CHAT_FRAMES) do
|
||
if value == frame then
|
||
table.remove(DOCKED_CHAT_FRAMES, index)
|
||
break
|
||
end
|
||
end
|
||
frame.isDocked = nil
|
||
local chatTab = getglobal(frame:GetName() .. "Tab")
|
||
if chatTab then chatTab:SetAlpha(1.0) end
|
||
FCF_SelectDockFrame(DOCKED_CHAT_FRAMES[1])
|
||
end
|
||
|
||
-- ref l.1287-1302, verbatim. `HideUIPanel` is the reference's own call and is right here:
|
||
-- a chat window holds no panel slot, and ours falls through to `frame:Hide()` for exactly
|
||
-- that case (UiPanels.xml, "seated nowhere: a plain toplevel, or an addon's frame we never
|
||
-- placed").
|
||
function FCF_Close(frame)
|
||
if not frame then
|
||
frame = FCF_GetCurrentChatFrame()
|
||
end
|
||
if not frame:IsShown() then
|
||
FCF_DockUpdate()
|
||
return
|
||
end
|
||
if frame == DEFAULT_CHAT_FRAME then return end
|
||
HideUIPanel(frame)
|
||
local chatTab = getglobal(frame:GetName() .. "Tab")
|
||
if chatTab then chatTab:Hide() end
|
||
FCF_UnDockFrame(frame)
|
||
end
|
||
|
||
function BenillaFCF_TabClick(id, button)
|
||
-- ref FCF_Tab_OnClick l.1219-1226: a RIGHT click opens the window's options menu and
|
||
-- returns — it does not select the tab, and it plays no click sound (the ref's sound
|
||
-- is on the OnClick *after* the call, which the early return skips). The ref also
|
||
-- calls `chatFrame:StopMovingOrSizing()` first; benilla has no drag, so there is
|
||
-- nothing in flight to stop.
|
||
if button == "RightButton" then
|
||
ToggleDropDownMenu(1, nil, getglobal("ChatFrame" .. id .. "TabDropDown"),
|
||
"ChatFrame" .. id .. "Tab", 0, 0)
|
||
return
|
||
end
|
||
-- ref FCF_Tab_OnClick (docked, left click): close any open menu, then select.
|
||
CloseDropDownMenus()
|
||
BenillaFCF_SelectDock(id)
|
||
PlaySound("UChatScrollButton")
|
||
end
|
||
|
||
-- ── The chat tab's options menu (decision 1589, B246) ────────────────────────────────
|
||
-- ref `FCFOptionsDropDown_Initialize` (FloatingChatFrame.lua l.87-393). The reference's
|
||
-- level-1 menu has four blocks: the window verbs (Lock/Unlock · Rename · Create New ·
|
||
-- Remove), a **DISPLAY** header over Font Size and Background, a **FILTERS** header over
|
||
-- seven message-group submenus, and Join New Channel.
|
||
--
|
||
-- benilla ships the Lock/Unlock row (1594 built the move, the resize and both lock
|
||
-- scopes under it) and the DISPLAY block, and that is the honest tree rather than a first
|
||
-- pass: the OTHER three window verbs still need the window management 0288 §2 left out
|
||
-- (there is no rename, no undock, no create/close), and the FILTERS block needs a runtime
|
||
-- message-group editor over `ui_chat::frames`' registration, which is its own feature and
|
||
-- not the one B246 reports missing. Each block lands with the machinery under it.
|
||
--
|
||
-- The menu is per tab (`ChatFrame<n>TabDropDown`), because `FCF_GetCurrentChatFrameID`
|
||
-- reads the open menu's PARENT id — that is the reference's contract and the reason the
|
||
-- capsule is declared inside the tab rather than shared.
|
||
-- ref l.81-85 is `UIDropDownMenu_Initialize(this, …, "MENU")` plus two width setters.
|
||
-- Ours assigns the two fields that call would have set, and skips the widths — both
|
||
-- departures are about OUR kit, not about the reference:
|
||
-- · `UIDropDownMenu_Initialize` clears every row of both shared lists, which is work at
|
||
-- LOAD time for a menu nobody has opened — and it would make this file's load order
|
||
-- depend on the dropdown kit's, and through it on GameTooltip's
|
||
-- `TOOLTIP_DEFAULT_COLOR`. `ToggleDropDownMenu` runs that init on every open anyway,
|
||
-- which is where the reference's own re-init happens too.
|
||
-- · `UIDropDownMenu_SetWidth(50)` means something DIFFERENT here: our kit reads a
|
||
-- declared width as `noResize` + a fixed list width (UIDropDownMenu.xml l.672-679),
|
||
-- so transcribing it would pin the menu at 50 px and cut "Background" in half. In the
|
||
-- reference, MENU mode never draws the capsule at all, so its width is inert there.
|
||
-- `BenillaFriendsDropDown` (FriendsFrame.xml) is already this same shape.
|
||
function FCFOptionsDropDown_OnLoad()
|
||
this.initialize = FCFOptionsDropDown_Initialize
|
||
this.displayMode = "MENU"
|
||
end
|
||
|
||
function FCFOptionsDropDown_Initialize()
|
||
local _, _, r, g, b, a = GetChatWindowInfo(FCF_GetCurrentChatFrameID())
|
||
local info
|
||
|
||
-- Level 2: the font-height list.
|
||
if UIDROPDOWNMENU_MENU_LEVEL == 2 then
|
||
if UIDROPDOWNMENU_MENU_VALUE == FONT_SIZE then
|
||
local _, fontHeight = FCF_GetCurrentChatFrame():GetFont()
|
||
for _, value in ipairs(CHAT_FONT_HEIGHTS) do
|
||
info = {}
|
||
info.text = format(FONT_SIZE_TEMPLATE, value)
|
||
info.value = value
|
||
info.func = FCF_SetChatWindowFontSize
|
||
-- ref: `value == floor(fontHeight+0.5)` — the tick follows the font the
|
||
-- frame is actually wearing, not the stored SIZE (which is 0 until the
|
||
-- player picks one).
|
||
if fontHeight and value == floor(fontHeight + 0.5) then
|
||
info.checked = 1
|
||
end
|
||
UIDropDownMenu_AddButton(info, UIDROPDOWNMENU_MENU_LEVEL)
|
||
end
|
||
end
|
||
return
|
||
end
|
||
|
||
-- Window options — the reference's FIRST row (ref l.236-246), and the only one of its
|
||
-- four window verbs benilla can honour. Rename, Create New and Remove each need the
|
||
-- window management 0288 §2 holds out (there is no rename, no undock, no create/close),
|
||
-- so they are named here rather than stubbed: a row that looks like a setting and
|
||
-- changes nothing cannot be told from a working one by looking (1134 §4).
|
||
local current = FCF_GetCurrentChatFrame()
|
||
info = {}
|
||
info.text = (current and current.isLocked) and UNLOCK_WINDOW or LOCK_WINDOW
|
||
info.func = FCF_ToggleLock
|
||
info.notCheckable = 1
|
||
UIDropDownMenu_AddButton(info)
|
||
|
||
-- Display header (ref l.273-279).
|
||
info = {}
|
||
info.text = DISPLAY
|
||
info.notClickable = 1
|
||
info.isTitle = 1
|
||
info.notCheckable = 1
|
||
UIDropDownMenu_AddButton(info)
|
||
|
||
-- Font size (ref l.281-288).
|
||
info = {}
|
||
info.text = FONT_SIZE
|
||
info.hasArrow = 1
|
||
info.func = nil
|
||
info.notCheckable = 1
|
||
UIDropDownMenu_AddButton(info)
|
||
|
||
-- Background — the colour swatch plus the opacity slider (ref l.290-307). This row is
|
||
-- B246's actual fix: `hasOpacity` is what puts `OpacitySliderFrame` beside the colour
|
||
-- wheel, and `opacityFunc` is what makes the chat box follow the thumb live.
|
||
--
|
||
-- **`info.opacity = 1 - a` is the reference's own line**, comment included ("Done
|
||
-- because the slider is reversed"): the opacity slider reads 0 at the TOP, so the
|
||
-- value it is seeded with and the value read back off it are both complements. The
|
||
-- read-back half is `FCF_SetChatWindowOpacity` below.
|
||
info = {}
|
||
info.text = BACKGROUND
|
||
info.hasColorSwatch = 1
|
||
info.r, info.g, info.b = r, g, b
|
||
if a then
|
||
a = 1 - a
|
||
end
|
||
info.opacity = a
|
||
info.swatchFunc = FCF_SetChatWindowBackGroundColor
|
||
info.func = UIDropDownMenuButton_OpenColorPicker
|
||
info.hasOpacity = 1
|
||
info.opacityFunc = FCF_SetChatWindowOpacity
|
||
info.cancelFunc = FCF_CancelWindowColorSettings
|
||
UIDropDownMenu_AddButton(info)
|
||
end
|
||
|
||
-- ref l.723-733: the id/frame the open menu belongs to. The menu's capsule is a child of
|
||
-- the tab, and the tab's id is its window's — which is why the tabs carry `id=` at all.
|
||
function FCF_GetCurrentChatFrameID()
|
||
return UIDropDownMenu_GetCurrentDropDown():GetParent():GetID()
|
||
end
|
||
|
||
-- ref l.729-735, fallback included: `UIDropDownMenu_GetCurrentDropDown` answers `this`
|
||
-- when no menu has ever been opened, so the lookup can miss — and then the caller IS a
|
||
-- row of the menu, whose parent is the capsule.
|
||
function FCF_GetCurrentChatFrame()
|
||
local current = getglobal("ChatFrame"
|
||
.. UIDropDownMenu_GetCurrentDropDown():GetParent():GetID())
|
||
if not current then
|
||
current = getglobal("ChatFrame" .. this:GetParent():GetID())
|
||
end
|
||
return current
|
||
end
|
||
|
||
-- ref l.740-744. The picker drives this on every drag of the colour wheel.
|
||
function FCF_SetChatWindowBackGroundColor()
|
||
local r, g, b = ColorPickerFrame:GetColorRGB()
|
||
FCF_SetWindowColor(FCF_GetCurrentChatFrame(), r, g, b)
|
||
end
|
||
|
||
-- ref l.746-749, the reversed slider's other half (see the Background row above).
|
||
function FCF_SetChatWindowOpacity()
|
||
FCF_SetWindowAlpha(FCF_GetCurrentChatFrame(), 1.0 - OpacitySliderFrame:GetValue())
|
||
end
|
||
|
||
-- Cancel restores what the row was opened with. The reference names this
|
||
-- `FCF_CancelWindowColorSettings` and hands it the pre-open r/g/b/opacity as arguments;
|
||
-- ours reads them off the button the picker was opened from, which is where
|
||
-- `UIDropDownMenuButton_OpenColorPicker` already stashes them (UIDropDownMenu.xml) — the
|
||
-- same values, one hop closer, and no argument contract to get wrong.
|
||
function FCF_CancelWindowColorSettings(prev)
|
||
local frame = FCF_GetCurrentChatFrame()
|
||
if prev and prev.r then
|
||
FCF_SetWindowColor(frame, prev.r, prev.g, prev.b)
|
||
end
|
||
if prev and prev.opacity then
|
||
FCF_SetWindowAlpha(frame, 1.0 - prev.opacity)
|
||
end
|
||
end
|
||
|
||
-- ref l.751-763: the visible half is the frame's own font, the stored half is SIZE.
|
||
-- `doNotSave` is ours, for the load path (the value already came from the store).
|
||
function FCF_SetChatWindowFontSize(chatFrame, fontSize, doNotSave)
|
||
if not chatFrame then
|
||
chatFrame = FCF_GetCurrentChatFrame()
|
||
end
|
||
if not fontSize then
|
||
fontSize = this.value
|
||
end
|
||
local fontFile, unused, fontFlags = chatFrame:GetFont()
|
||
chatFrame:SetFont(fontFile, fontSize, fontFlags)
|
||
if not doNotSave then
|
||
SetChatWindowSize(chatFrame:GetID(), fontSize)
|
||
end
|
||
end
|
||
|
||
-- The whisper tab flash (ref FCF_FlashTab: never the selected tab; 0.25s on/off for 60s,
|
||
-- and a flashing tab holds the dock tabs visible). Fired by the app on a received
|
||
-- whisper landing in an unselected window (0288 P4). No-tab windows can't flash.
|
||
function BenillaFCF_FlashTab(id)
|
||
if id == BenillaFCF.selected then return end
|
||
if not getglobal("ChatFrame" .. id .. "TabFlash") then return end
|
||
BenillaFCF.flashTab = id
|
||
BenillaFCF.flashLeft = 60
|
||
end
|
||
|
||
-- PanelTemplates_TabResize(5, tab), re-expressed: the ref sizes every docked tab to its
|
||
-- label (FCF_DockUpdate) — middle slice = text width + 5, tab = middle + the two 16px
|
||
-- caps; the flash/highlight bands follow the Left/Right pieces by anchor. The label
|
||
-- width is host-measured (GetStringWidth), so early frames may see 0. It RETURNS whether
|
||
-- the width is known yet — which is what lets FCF_OnUpdate's apply gate keep polling
|
||
-- until the measurement lands and then stop for good (decision 1396). Without that
|
||
-- answer the gate would have to choose between polling forever and shipping tabs stuck
|
||
-- at their authored width on whichever session measured late.
|
||
function BenillaFCF_TabResize(tab)
|
||
local w = getglobal(tab:GetName() .. "Text"):GetStringWidth()
|
||
if w == 0 then return false end
|
||
if tab.benillaTabTextW == w then return true end
|
||
tab.benillaTabTextW = w
|
||
getglobal(tab:GetName() .. "Middle"):SetWidth(w + 5)
|
||
tab:SetWidth(w + 5 + 32)
|
||
return true
|
||
end
|
||
|
||
-- The hover rect: the dock's rect expanded by the ref's MouseIsOver pads
|
||
-- (FCF_OnUpdate: MouseIsOver(chatFrame, 45, -10, -5, 5) — 45 up into the tab zone,
|
||
-- 10 down past the edit box seam, 5 out each side).
|
||
local function overDock(x, y)
|
||
-- **The SELECTED frame, not ChatFrame1.** The two dock windows are mutually exclusive
|
||
-- — selecting one hides the other — so reading a fixed frame here meant that the
|
||
-- instant the Combat Log was selected the hover test measured a hidden window, went
|
||
-- false, and drove the reveal fade to 0: the tabs and the chat background disappeared
|
||
-- together (director-reported). The dock's rect is whichever window is up.
|
||
local f = getglobal("ChatFrame" .. BenillaFCF.selected) or ChatFrame1
|
||
local left, right = f:GetLeft(), f:GetRight()
|
||
local top, bottom = f:GetTop(), f:GetBottom()
|
||
if not left then return false end
|
||
return x >= left - 5 and x <= right + 5 and y >= bottom - 10 and y <= top + 45
|
||
end
|
||
|
||
-- FCF_OnUpdate transcription (the parts that survive with window management out):
|
||
-- reveal after a 0.2s STATIONARY hover (a moving cursor keeps resetting the delay),
|
||
-- fade over 0.15s, conceal on leave; a flashing tab forces the tabs visible; the
|
||
-- selected window's textures ride the same fade toward alpha 0.25 (the chat-cache
|
||
-- default window alpha is 0 — the box only exists on hover).
|
||
function FCF_OnUpdate(elapsed)
|
||
local s = BenillaFCF
|
||
local x, y = GetCursorPosition()
|
||
if overDock(x, y) then
|
||
if not s.hover then
|
||
s.hover = true
|
||
s.hoverTime = 0
|
||
s.oldX, s.oldY = x, y
|
||
elseif s.oldX == x and s.oldY == y then
|
||
s.hoverTime = s.hoverTime + elapsed
|
||
else
|
||
s.hoverTime = 0
|
||
s.oldX, s.oldY = x, y
|
||
end
|
||
if s.hoverTime > CHAT_TAB_SHOW_DELAY then s.revealTarget = 1 end
|
||
else
|
||
s.hover = false
|
||
s.hoverTime = 0
|
||
s.revealTarget = 0
|
||
end
|
||
if s.flashTab ~= 0 then s.revealTarget = 1 end
|
||
-- ref FCF_OnUpdate's `(MouseIsOver(...) or chatFrame.resizing)`: a drag in flight pins
|
||
-- the chrome open. The cursor is moving during a resize, which would otherwise keep
|
||
-- resetting the stationary-hover delay and fade the grips out from under it.
|
||
if BenillaFCF_Dragging() then s.revealTarget = 1 end
|
||
|
||
-- The linear fade toward the target (ref UIFrameFadeIn/Out over CHAT_FRAME_FADE_TIME).
|
||
-- **Zero is a real setting, not a guard against nothing**: the Chat page's *Remove
|
||
-- Chat Hover Delay* row sets both this and the delay to 0 (ref SetChatMouseOverDelay),
|
||
-- and the division would be 0/0 on the frame `elapsed` is also zero. The reference
|
||
-- lands in the same place by a different road — `UIFrameFadeIn` with timeToFade 0
|
||
-- finishes on its first tick.
|
||
local fade = CHAT_FRAME_FADE_TIME
|
||
if fade <= 0 then
|
||
s.reveal = s.revealTarget
|
||
elseif s.reveal < s.revealTarget then
|
||
s.reveal = math.min(s.revealTarget, s.reveal + elapsed / fade)
|
||
elseif s.reveal > s.revealTarget then
|
||
s.reveal = math.max(s.revealTarget, s.reveal - elapsed / fade)
|
||
end
|
||
|
||
-- Apply: tabs at 1.0 (selected) / 0.5 (unselected) × reveal; each window's
|
||
-- background+borders at its own `windowAlpha` (ref: fade oldAlpha↔DEFAULT_CHATFRAME_
|
||
-- ALPHA). Until 1589 that second term was the constant `0.25 * reveal`, which is what
|
||
-- `windowAlpha` still computes for the shipped base of 0 — the default look is
|
||
-- unchanged, it is now just one case of a rule instead of the only case.
|
||
--
|
||
-- GATED on everything the writes below depend on — the reveal, the selected tab, and
|
||
-- whether the label measurements have landed (decision 1396). **The reference needs
|
||
-- no gate because it does not do this here at all**: ref FCF_OnUpdate hands the fade
|
||
-- to UIFrameFadeIn/Out, and UIFrameFade_OnUpdate walks only the frames still fading,
|
||
-- so a settled dock costs nothing. This transcription inlined the ramp and lost that
|
||
-- property with it, and ~24 getglobals, two table builds and ~20 SetAlpha calls then
|
||
-- ran every frame of every session to write the values that were already there —
|
||
-- 36 us/frame, the largest single handler in the client (1395 measured it). The
|
||
-- guard restores the reference's own behaviour rather than inventing one.
|
||
if s.reveal ~= s.appliedReveal or s.selected ~= s.appliedSelected then
|
||
-- Over the DOCK, not over `1, 2`: the fade belongs to the windows sharing one
|
||
-- rect and one tab strip, and which windows those are is `DOCKED_CHAT_FRAMES`'s
|
||
-- answer now that `FCF_DockFrame` can change it (1714).
|
||
for _, frame in ipairs(DOCKED_CHAT_FRAMES) do
|
||
local i = frame:GetID()
|
||
local tab = getglobal("ChatFrame" .. i .. "Tab")
|
||
if tab then
|
||
tab:SetAlpha(((frame == SELECTED_DOCK_FRAME) and 1.0 or 0.5) * s.reveal)
|
||
end
|
||
local a = windowAlpha(i, s.reveal)
|
||
for _, t in ipairs(BenillaFCF_Textures(i)) do
|
||
t:SetAlpha(a)
|
||
end
|
||
end
|
||
s.appliedReveal = s.reveal
|
||
s.appliedSelected = s.selected
|
||
end
|
||
|
||
-- The tab widths are a SEPARATE trigger and get their own latch, deliberately: they
|
||
-- depend on a host measurement rather than on the fade, and folding them into the
|
||
-- gate above would tie the alphas to a width that can legitimately never arrive (an
|
||
-- empty label measures 0 forever) — the apply block would then run every frame again
|
||
-- for a reason having nothing to do with what it writes. Polls until both land, then
|
||
-- stops for good.
|
||
if not s.tabsSettled then
|
||
local settled = true
|
||
for _, frame in ipairs(DOCKED_CHAT_FRAMES) do
|
||
local tab = getglobal(frame:GetName() .. "Tab")
|
||
if tab and not BenillaFCF_TabResize(tab) then settled = false end
|
||
end
|
||
s.tabsSettled = settled
|
||
end
|
||
|
||
-- The whisper flash blink (0.25 on / 0.25 off, 60s — ref UIFrameFlash args).
|
||
if s.flashTab ~= 0 then
|
||
s.flashLeft = s.flashLeft - elapsed
|
||
local flash = getglobal("ChatFrame" .. s.flashTab .. "TabFlash")
|
||
if s.flashLeft <= 0 then
|
||
s.flashTab = 0
|
||
flash:Hide()
|
||
else
|
||
local phase = mod(s.flashLeft, 0.5)
|
||
if phase > 0.25 then flash:Show() else flash:Hide() end
|
||
end
|
||
end
|
||
end
|
||
|
||
-- ChatFrame_OnUpdate transcription: the bottom button's blink while scrolled up
|
||
-- (CHAT_BUTTON_FLASH_TIME = 0.5s toggle; hidden the moment the view is AtBottom).
|
||
function ChatFrame_OnUpdate(elapsed)
|
||
local frame = this
|
||
local flash = getglobal(frame:GetName() .. "BottomButtonFlash")
|
||
if not flash then return end
|
||
if frame:AtBottom() then
|
||
-- The reference's own gate (ChatFrame.lua): a settled view writes nothing. Our
|
||
-- flashTimer reset rides inside it — so a return-to-bottom during a hidden blink
|
||
-- phase keeps the residual phase (a divergence that restarts the blink mid-phase
|
||
-- next scroll-up, invisible at rest and costing zero writes there).
|
||
if flash:IsVisible() then
|
||
flash:Hide()
|
||
frame.flashTimer = 0
|
||
end
|
||
return
|
||
end
|
||
frame.flashTimer = (frame.flashTimer or 0) + elapsed
|
||
while frame.flashTimer >= 0.5 do
|
||
frame.flashTimer = frame.flashTimer - 0.5
|
||
if flash:IsVisible() then flash:Hide() else flash:Show() end
|
||
end
|
||
end
|
||
|
||
-- A completed left click on the chat body dismisses a held SPELL/ACTION payload
|
||
-- (decision 0843, the director's call — no ref counterpart: the reference keeps a
|
||
-- non-item payload on any frame click). Items are deliberately excluded: an item
|
||
-- dismissal is a destroy, which only the world-drop popup flow may offer.
|
||
function BenillaChatFrame_DismissCursor(button)
|
||
if button ~= "LeftButton" then return end
|
||
local kind = GetCursorInfo()
|
||
if kind == "spell" or kind == "action" then
|
||
ClearCursor()
|
||
end
|
||
end
|
||
|
||
-- MessageFrameScrollButton_OnLoad/_OnUpdate + the ref OnClick pair: a press arms the
|
||
-- hold-repeat (initial delay 0), each 0.05s while held scrolls WITHOUT the sound; the
|
||
-- release click scrolls once WITH the sound (ref FloatingChatFrame.xml's DownButton/
|
||
-- UpButton scripts — sound only on the non-pushed branch).
|
||
function BenillaChatScrollButton_OnClick(btn, down, dir, sound)
|
||
if down then
|
||
btn.clickDelay = 0
|
||
else
|
||
PlaySound(sound)
|
||
local frame = getglobal("ChatFrame" .. BenillaFCF.selected)
|
||
if dir == "up" then frame:ScrollUp() else frame:ScrollDown() end
|
||
end
|
||
end
|
||
|
||
function BenillaChatScrollButton_OnUpdate(btn, elapsed, dir)
|
||
if btn:GetButtonState() == "PUSHED" then
|
||
btn.clickDelay = (btn.clickDelay or 0) - elapsed
|
||
if btn.clickDelay < 0 then
|
||
local frame = getglobal("ChatFrame" .. BenillaFCF.selected)
|
||
if dir == "up" then frame:ScrollUp() else frame:ScrollDown() end
|
||
btn.clickDelay = 0.05
|
||
end
|
||
end
|
||
end
|
||
|
||
-- ── BenillaUIMenu: the 1.12 UIMenu kit, re-expressed (ref UIMenu.lua — the chat menu's
|
||
-- row engine: fixed 16px rows in a tooltip-backdrop box, a 2 s no-hover timeout that
|
||
-- walks up parentMenu chains, nested submenus opened on row hover). Constants quoted.
|
||
-- Scoped here because the chat menu is its only consumer; a second consumer graduates
|
||
-- it to its own file.
|
||
BENILLA_UIMENU_BUTTON_HEIGHT = 16
|
||
BENILLA_UIMENU_BUTTON_WIDTH = 104
|
||
BENILLA_UIMENU_BORDER_HEIGHT = 12
|
||
BENILLA_UIMENU_BORDER_WIDTH = 12
|
||
BENILLA_UIMENU_NUMBUTTONS = 32
|
||
BENILLA_UIMENU_TIMEOUT = 2.0
|
||
|
||
function UIMenu_Initialize()
|
||
local menu = this
|
||
menu.numButtons = 0
|
||
menu.subMenu = ""
|
||
local name = menu:GetName()
|
||
for i = 1, BENILLA_UIMENU_NUMBUTTONS do
|
||
local button = getglobal(name .. "Button" .. i)
|
||
button:SetWidth(BENILLA_UIMENU_BUTTON_WIDTH)
|
||
button:SetHeight(BENILLA_UIMENU_BUTTON_HEIGHT)
|
||
button:Hide()
|
||
getglobal(button:GetName() .. "ShortcutText"):Hide()
|
||
end
|
||
menu:SetWidth(BENILLA_UIMENU_BUTTON_WIDTH + BENILLA_UIMENU_BORDER_WIDTH * 2)
|
||
menu:SetHeight(BENILLA_UIMENU_BORDER_HEIGHT * 2)
|
||
end
|
||
|
||
-- Rows past the button pool are dropped (the ref message()s; nothing we author overflows).
|
||
function UIMenu_AddButton(text, shortcut, func, nested)
|
||
local menu = this
|
||
local id = menu.numButtons + 1
|
||
if id > BENILLA_UIMENU_NUMBUTTONS then return end
|
||
menu.numButtons = id
|
||
local button = getglobal(menu:GetName() .. "Button" .. id)
|
||
if text then button:SetText(text) end
|
||
button.func = func
|
||
button.nested = nested
|
||
button:Show()
|
||
if shortcut then
|
||
local s = getglobal(button:GetName() .. "ShortcutText")
|
||
s:SetText(shortcut)
|
||
s:Show()
|
||
end
|
||
menu:SetHeight(id * BENILLA_UIMENU_BUTTON_HEIGHT + BENILLA_UIMENU_BORDER_HEIGHT * 2)
|
||
end
|
||
|
||
function UIMenu_OnShow()
|
||
this.timeleft = BENILLA_UIMENU_TIMEOUT
|
||
this.counting = 1
|
||
end
|
||
|
||
function UIMenu_OnUpdate(elapsed)
|
||
if this.counting == 1 then
|
||
local timeleft = (this.timeleft or BENILLA_UIMENU_TIMEOUT) - elapsed
|
||
if timeleft <= 0 then
|
||
this:Hide()
|
||
return
|
||
end
|
||
this.timeleft = timeleft
|
||
end
|
||
end
|
||
|
||
function UIMenu_StartCounting(menu)
|
||
menu.counting = 1
|
||
if menu.parentMenu then UIMenu_StartCounting(getglobal(menu.parentMenu)) end
|
||
end
|
||
|
||
function UIMenu_StopCounting(menu)
|
||
menu.counting = 0
|
||
menu.timeleft = BENILLA_UIMENU_TIMEOUT
|
||
if menu.parentMenu then UIMenu_StopCounting(getglobal(menu.parentMenu)) end
|
||
end
|
||
|
||
function UIMenuButton_OnLoad()
|
||
this:SetPoint("TOP", this:GetParent():GetName(), "TOP", 0,
|
||
-((this:GetID() - 1) * BENILLA_UIMENU_BUTTON_HEIGHT) - BENILLA_UIMENU_BORDER_HEIGHT)
|
||
end
|
||
|
||
function UIMenuButton_OnClick()
|
||
if this.func then this.func() end
|
||
this:GetParent():Hide()
|
||
PlaySound("UChatScrollButton")
|
||
end
|
||
|
||
function UIMenuButton_OnEnter()
|
||
local nested = this.nested
|
||
if nested then
|
||
local menu = getglobal(nested)
|
||
if not menu:IsVisible() then
|
||
local oldMenu = getglobal(this:GetParent().subMenu or "")
|
||
if oldMenu then oldMenu:Hide() end
|
||
this:GetParent().subMenu = nested
|
||
menu:SetPoint("BOTTOMLEFT", this:GetName(), "BOTTOMRIGHT", 10, -12)
|
||
menu:Show()
|
||
end
|
||
UIMenu_StopCounting(menu)
|
||
else
|
||
UIMenu_StopCounting(this:GetParent())
|
||
end
|
||
end
|
||
|
||
function UIMenuButton_OnLeave()
|
||
UIMenu_StartCounting(this:GetParent())
|
||
if this.nested then UIMenu_StartCounting(getglobal(this.nested)) end
|
||
end
|
||
|
||
-- ── The chat menu rows (ref ChatMenu_* in ChatFrame.lua, adapted): every type row
|
||
-- prefixes the box with the type's slash and lets the LIVE PARSE convert — the ref's
|
||
-- own Whisper row does literally `SetText("/w "..GetText())`, and routing all rows the
|
||
-- same way means the menu can never drift from the slash grammar the edit machine
|
||
-- owns. Labels/shortcuts quoted from GlobalStrings. Language (benilla has no language
|
||
-- model yet) and Macro (no macro frame) are deliberately out; this OnLoad is their
|
||
-- return path.
|
||
function BenillaChatMenu_Type(prefix)
|
||
local eb = ChatFrameEditBox
|
||
eb:Show()
|
||
eb:SetFocus()
|
||
eb:SetText(prefix .. (eb:GetText() or ""))
|
||
end
|
||
|
||
function ChatMenu_OnLoad()
|
||
UIMenu_Initialize()
|
||
UIMenu_AddButton("Say", "/s", function() BenillaChatMenu_Type("/s ") end)
|
||
UIMenu_AddButton("Party Chat", "/p", function() BenillaChatMenu_Type("/p ") end)
|
||
UIMenu_AddButton("Guild Chat", "/g", function() BenillaChatMenu_Type("/g ") end)
|
||
UIMenu_AddButton("Yell", "/y", function() BenillaChatMenu_Type("/y ") end)
|
||
UIMenu_AddButton("Whisper", "/w", function() BenillaChatMenu_Type("/w ") end)
|
||
UIMenu_AddButton("Emote", "/e", function() BenillaChatMenu_Type("/e ") end, "EmoteMenu")
|
||
UIMenu_AddButton("Reply", "/r", function() BenillaChatMenu_Type("/r ") end)
|
||
UIMenu_AddButton("Voice Emote", nil, nil, "VoiceMacroMenu")
|
||
end
|
||
|
||
-- An emote row submits its /command through the REAL chat pipeline (SubmitChatInput →
|
||
-- the router's EmotesText resolve + the send-side posture gate) — the ref's DoEmote by
|
||
-- token, reached through our one grammar. The lists are the ref's EmoteList /
|
||
-- TextEmoteSpeechList tokens as OUR canonical /commands (the EmotesText names the typed
|
||
-- grammar resolves), CMD1-sorted like the ref menu. Three ref labels differ
|
||
-- (/attacktarget /followme /silly are GlobalStrings CMD aliases the typed grammar
|
||
-- doesn't carry yet) — each row shows the command it actually runs.
|
||
BenillaEmoteMenuList = {
|
||
"/applaud", "/beg", "/bow", "/chicken", "/cry", "/dance", "/eat", "/flex",
|
||
"/kiss", "/kneel", "/laugh", "/point", "/roar", "/rude", "/salute", "/shy",
|
||
"/sit", "/sleep", "/stand", "/talk", "/wave",
|
||
}
|
||
BenillaVoiceMacroList = {
|
||
"/attackmytarget", "/bye", "/charge", "/cheer", "/congratulate", "/flee",
|
||
"/flirt", "/follow", "/healme", "/hello", "/helpme", "/incoming", "/joke",
|
||
"/no", "/nod", "/oom", "/openfire", "/rasp", "/thank", "/train", "/wait",
|
||
"/welcome",
|
||
}
|
||
|
||
function BenillaEmoteMenu_Click(cmd)
|
||
SubmitChatInput(cmd)
|
||
ChatMenu:Hide()
|
||
end
|
||
|
||
function BenillaOnEmoteMenuLoad(list)
|
||
UIMenu_Initialize()
|
||
this.parentMenu = "ChatMenu"
|
||
for _, cmd in ipairs(list) do
|
||
local c = cmd
|
||
UIMenu_AddButton(cmd, nil, function() BenillaEmoteMenu_Click(c) end)
|
||
end
|
||
end
|
||
]]></Script>
|
||
|
||
<!-- ── The seven chat windows: ChatFrame1 (General) + ChatFrame2 (Combat Log) docked, 3..7
|
||
hidden and undocked ──────────────────────────────────────────────────────────────── -->
|
||
<!--
|
||
**One declaration, seven windows.** A chat window is the SAME widget seated differently, and
|
||
saying it twice is how three straight rounds of bugs got in: 1575 found the Combat Log with
|
||
no tab, 1579 found it with no rect and no border art, and 1588 found it with no `OnUpdate`
|
||
and no scroll column — each time because window 2 was a HAND-MIRRORED partial copy of
|
||
window 1 and the mirroring missed a part. The reference does not mirror: it declares
|
||
`FloatingChatFrameTemplate` `virtual="true"` and every `ChatFrameN` inherits it
|
||
(FloatingChatFrame.xml l.211, l.685-832). So do we, and the class of bug goes with it.
|
||
|
||
**All seven, not two — and that changed for a measured reason (1712).** ChatFrame3..7 were
|
||
declared bare: a `ScrollingMessageFrame` and nothing else, on the argument that nothing
|
||
reads their furniture while they stay hidden and undocked. The top-20 corpus falsifies it.
|
||
pfUI's `chat.lua:588` is `for i = 1, NUM_CHAT_WINDOWS do _G["ChatFrame"..i.."UpButton"]:Hide()`
|
||
— the scroll column, the tab, and (in `RefreshChat`) `$parentBackground`, every
|
||
`CHAT_FRAME_TEXTURES` slice and `$parentResizeBottom`, all seven, none guarded. The
|
||
reference has them because the template is shared; the visibility of the window it hangs
|
||
off has nothing to do with whether the global resolves.
|
||
|
||
Per-instance is only what genuinely differs: the seat (ChatFrame1 is the managed-position
|
||
base — `UIPARENT_MANAGED_FRAME_POSITIONS["ChatFrame1"]`, baseY 85 + the bottom-stack
|
||
offsets, decision 0272 — and ChatFrame2 takes its rect from ChatFrame1's, which is what
|
||
being docked MEANS and why it must never carry a managed row of its own, 1579), the id,
|
||
and which ships shown.
|
||
|
||
The chrome the template carries: displayDuration 120 / maxLines 128 (ref ChatFrameTemplate);
|
||
a background stretched -2/+2 x and +3/-6 y past the frame (ref $parentBackground anchors)
|
||
and the 8 resize grips ringing it, which are the border art AND the handles (ref
|
||
CHAT_FRAME_TEXTURES — see the block below); the scroll column; and the per-frame scripts.
|
||
Everything starts alpha 0; BenillaFCF fades it.
|
||
|
||
`movable`/`resizable`/`<ResizeBounds>` are the reference's own attributes at the reference's
|
||
own numbers (FloatingChatFrame.xml l.211, l.222-230): min 296×75, max 608×400. The
|
||
`<TitleRegion setAllPoints="true"/>` that sits beside them THERE is deliberately absent
|
||
here — see the drag scripts at the foot of this template for why, and for what replaces it.
|
||
|
||
**`EXECUTE_CHAT_LINE` is gated to window 1 on purpose** — see the OnLoad below. It is the
|
||
one script here that is genuinely ChatFrame1's rather than the dock's, so the template
|
||
carries the gate rather than the registration.
|
||
-->
|
||
<ScrollingMessageFrame name="BenillaChatFrameTemplate" virtual="true"
|
||
frameStrata="BACKGROUND" displayDuration="120.0" maxLines="128"
|
||
movable="true" resizable="true">
|
||
<!-- ref FloatingChatFrame.xml l.222-230, verbatim. The only `<ResizeBounds>` in the whole
|
||
of 1.12.1's FrameXML, and the numbers are the window's whole size domain: nothing else
|
||
clamps a chat window. -->
|
||
<ResizeBounds>
|
||
<minResize><AbsDimension x="296" y="75"/></minResize>
|
||
<maxResize><AbsDimension x="608" y="400"/></maxResize>
|
||
</ResizeBounds>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<Texture name="$parentBackground" file="Interface\ChatFrame\ChatFrameBackground" alpha="0">
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="-2" y="3"/></Offset></Anchor>
|
||
<Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="2" y="-6"/></Offset></Anchor>
|
||
</Anchors>
|
||
</Texture>
|
||
</Layer>
|
||
</Layers>
|
||
<Frames>
|
||
<!-- ── The eight RESIZE GRIPS (ref FloatingChatFrame.xml l.260-540) ─────────────────
|
||
The window's border art and its resize handles are the same eight objects: four
|
||
16×16 corners and four strips stretched between them, each a Button whose
|
||
`$parentTexture` is one slice of `ChatFrameBorder`. `CHAT_FRAME_TEXTURES` names
|
||
them, so they ride the hover fade and the tab menu's tint/opacity exactly as the
|
||
background does — they appear when the box appears and are invisible otherwise.
|
||
|
||
They were plain `$parentBorder*` textures until this arc (the look without the
|
||
mechanics); the anchors and TexCoords below are unchanged from those, so the ring
|
||
draws pixel-for-pixel where it did.
|
||
|
||
**Two of the reference's clauses are deliberately NOT transcribed, and both are
|
||
downstream of ONE divergence: its chat window is `enableMouse="false"` and ours is
|
||
not** (a `<ScrollingMessageFrame>` takes the mouse by construction here, and our
|
||
window uses it — `OnClick` dismisses a held spell, 0843, and `OnHyperlinkClick`
|
||
opens an item link, 0288 P2). In the reference the window competes for nothing, so:
|
||
|
||
· **No `this:SetFrameLevel(this:GetFrameLevel()-1)` OnLoad.** There it only sinks
|
||
the border art under the background wash. Here it would sink the grips under a
|
||
mouse-enabled parent, and every grip STRADDLES the window's edge (the top-left
|
||
corner overlaps its interior by 12×11 px, the edge strips by most of their
|
||
width) — so the overlapping half would stop hitting the grip and the handle
|
||
would be the outer sliver only. Level unchanged also keeps the ring drawing
|
||
over the wash, which is where benilla's border textures already were (they sat
|
||
after `$parentBackground` in the same layer).
|
||
· **The grips take the mouse only while the window is UNLOCKED**
|
||
(`BenillaFCF_ApplyLock`). Eight always-live buttons would shadow the outer
|
||
16px of the window — including the bottom strip, where the newest chat line
|
||
is — so an item link on the last line would stop being clickable. Locked is
|
||
the shipped state, so out of the box the window behaves exactly as it did;
|
||
unlocking it hands the edges over, which is what unlocking means. This is
|
||
benilla's stand-in for the click-through the reference gets for free.
|
||
|
||
`FCF_Resize`/`FCF_StopResize` still carry the reference's own three gates on top —
|
||
they are what `CHAT_LOCKED` and the docked-but-not-default case need. ── -->
|
||
<Button name="$parentResizeTopLeft">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativeTo="$parentBackground"><Offset><AbsDimension x="-2" y="2"/></Offset></Anchor>
|
||
</Anchors>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<Texture name="$parentTexture" file="Interface\ChatFrame\ChatFrameBorder" alpha="0">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT"/></Anchors>
|
||
<TexCoords left="0" right="0.25" top="0" bottom="0.125"/>
|
||
</Texture>
|
||
</Layer>
|
||
</Layers>
|
||
<Scripts>
|
||
<OnMouseDown>FCF_Resize("TOPLEFT")</OnMouseDown>
|
||
<OnMouseUp>FCF_StopResize()</OnMouseUp>
|
||
</Scripts>
|
||
</Button>
|
||
<Button name="$parentResizeTopRight">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPRIGHT" relativeTo="$parentBackground"><Offset><AbsDimension x="2" y="2"/></Offset></Anchor>
|
||
</Anchors>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<Texture name="$parentTexture" file="Interface\ChatFrame\ChatFrameBorder" alpha="0">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT"/></Anchors>
|
||
<TexCoords left="0.75" right="1.0" top="0" bottom="0.125"/>
|
||
</Texture>
|
||
</Layer>
|
||
</Layers>
|
||
<Scripts>
|
||
<OnMouseDown>FCF_Resize("TOPRIGHT")</OnMouseDown>
|
||
<OnMouseUp>FCF_StopResize()</OnMouseUp>
|
||
</Scripts>
|
||
</Button>
|
||
<Button name="$parentResizeBottomLeft">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors>
|
||
<Anchor point="BOTTOMLEFT" relativeTo="$parentBackground"><Offset><AbsDimension x="-2" y="-3"/></Offset></Anchor>
|
||
</Anchors>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<Texture name="$parentTexture" file="Interface\ChatFrame\ChatFrameBorder" alpha="0">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT"/></Anchors>
|
||
<TexCoords left="0" right="0.25" top="0.7265625" bottom="0.8515625"/>
|
||
</Texture>
|
||
</Layer>
|
||
</Layers>
|
||
<Scripts>
|
||
<OnMouseDown>FCF_Resize("BOTTOMLEFT")</OnMouseDown>
|
||
<OnMouseUp>FCF_StopResize()</OnMouseUp>
|
||
</Scripts>
|
||
</Button>
|
||
<Button name="$parentResizeBottomRight">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors>
|
||
<Anchor point="BOTTOMRIGHT" relativeTo="$parentBackground"><Offset><AbsDimension x="2" y="-3"/></Offset></Anchor>
|
||
</Anchors>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<Texture name="$parentTexture" file="Interface\ChatFrame\ChatFrameBorder" alpha="0">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT"/></Anchors>
|
||
<TexCoords left="0.75" right="1.0" top="0.7265625" bottom="0.8515625"/>
|
||
</Texture>
|
||
</Layer>
|
||
</Layers>
|
||
<Scripts>
|
||
<OnMouseDown>FCF_Resize("BOTTOMRIGHT")</OnMouseDown>
|
||
<OnMouseUp>FCF_StopResize()</OnMouseUp>
|
||
</Scripts>
|
||
</Button>
|
||
<Button name="$parentResizeTop">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors>
|
||
<Anchor point="LEFT" relativeTo="$parentResizeTopLeft" relativePoint="RIGHT"/>
|
||
<Anchor point="RIGHT" relativeTo="$parentResizeTopRight" relativePoint="LEFT"/>
|
||
</Anchors>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<Texture name="$parentTexture" file="Interface\ChatFrame\ChatFrameBorder" alpha="0">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors>
|
||
<Anchor point="LEFT"/>
|
||
<Anchor point="RIGHT"/>
|
||
</Anchors>
|
||
<TexCoords left="0.25" right="0.75" top="0" bottom="0.125"/>
|
||
</Texture>
|
||
</Layer>
|
||
</Layers>
|
||
<Scripts>
|
||
<OnMouseDown>FCF_Resize("TOP")</OnMouseDown>
|
||
<OnMouseUp>FCF_StopResize()</OnMouseUp>
|
||
</Scripts>
|
||
</Button>
|
||
<Button name="$parentResizeBottom">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors>
|
||
<Anchor point="LEFT" relativeTo="$parentResizeBottomLeft" relativePoint="RIGHT"/>
|
||
<Anchor point="RIGHT" relativeTo="$parentResizeBottomRight" relativePoint="LEFT"/>
|
||
</Anchors>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<Texture name="$parentTexture" file="Interface\ChatFrame\ChatFrameBorder" alpha="0">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors>
|
||
<Anchor point="LEFT"/>
|
||
<Anchor point="RIGHT"/>
|
||
</Anchors>
|
||
<TexCoords left="0.25" right="0.75" top="0.7265625" bottom="0.8515625"/>
|
||
</Texture>
|
||
</Layer>
|
||
</Layers>
|
||
<Scripts>
|
||
<OnMouseDown>FCF_Resize("BOTTOM")</OnMouseDown>
|
||
<OnMouseUp>FCF_StopResize()</OnMouseUp>
|
||
</Scripts>
|
||
</Button>
|
||
<Button name="$parentResizeLeft">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOP" relativeTo="$parentResizeTopLeft" relativePoint="BOTTOM"/>
|
||
<Anchor point="BOTTOM" relativeTo="$parentResizeBottomLeft" relativePoint="TOP"/>
|
||
</Anchors>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<Texture name="$parentTexture" file="Interface\ChatFrame\ChatFrameBorder" alpha="0">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOP"/>
|
||
<Anchor point="BOTTOM"/>
|
||
</Anchors>
|
||
<TexCoords left="0" right="0.25" top="0.125" bottom="0.7265625"/>
|
||
</Texture>
|
||
</Layer>
|
||
</Layers>
|
||
<Scripts>
|
||
<OnMouseDown>FCF_Resize("LEFT")</OnMouseDown>
|
||
<OnMouseUp>FCF_StopResize()</OnMouseUp>
|
||
</Scripts>
|
||
</Button>
|
||
<Button name="$parentResizeRight">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOP" relativeTo="$parentResizeTopRight" relativePoint="BOTTOM"/>
|
||
<Anchor point="BOTTOM" relativeTo="$parentResizeBottomRight" relativePoint="TOP"/>
|
||
</Anchors>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<Texture name="$parentTexture" file="Interface\ChatFrame\ChatFrameBorder" alpha="0">
|
||
<Size><AbsDimension x="16" y="16"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOP"/>
|
||
<Anchor point="BOTTOM"/>
|
||
</Anchors>
|
||
<TexCoords left="0.75" right="1.0" top="0.125" bottom="0.7265625"/>
|
||
</Texture>
|
||
</Layer>
|
||
</Layers>
|
||
<Scripts>
|
||
<OnMouseDown>FCF_Resize("RIGHT")</OnMouseDown>
|
||
<OnMouseUp>FCF_StopResize()</OnMouseUp>
|
||
</Scripts>
|
||
</Button>
|
||
<!-- The scroll column, outside bottom-left (ref: BottomButton at (-32,-4), Down and
|
||
Up stacked above it, 2px overlaps). Sounds + hold-repeat per the ref scripts. -->
|
||
<Button name="$parentBottomButton">
|
||
<Size><AbsDimension x="32" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-32" y="-4"/></Offset></Anchor>
|
||
</Anchors>
|
||
<Layers>
|
||
<Layer level="OVERLAY">
|
||
<Texture name="$parentFlash" file="Interface\ChatFrame\UI-ChatIcon-BlinkHilight" hidden="true" setAllPoints="true"/>
|
||
</Layer>
|
||
</Layers>
|
||
<Scripts>
|
||
<OnClick>
|
||
PlaySound("igChatBottom")
|
||
getglobal("ChatFrame" .. BenillaFCF.selected):ScrollToBottom()
|
||
</OnClick>
|
||
</Scripts>
|
||
<NormalTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollEnd-Up"/>
|
||
<PushedTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollEnd-Down"/>
|
||
<DisabledTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollEnd-Disabled"/>
|
||
<HighlightTexture file="Interface\Buttons\UI-Common-MouseHilight" alphaMode="ADD"/>
|
||
</Button>
|
||
<Button name="$parentDownButton">
|
||
<Size><AbsDimension x="32" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="BOTTOM" relativeTo="$parentBottomButton" relativePoint="TOP"><Offset><AbsDimension x="0" y="-2"/></Offset></Anchor>
|
||
</Anchors>
|
||
<Scripts>
|
||
<OnLoad>
|
||
<!-- All four transitions, ref-verbatim (MessageFrameScrollButton_OnLoad,
|
||
ChatFrame.lua:1583): the OnClick body reads no button, so a right-click
|
||
scrolls exactly like a left one, and the DOWN halves are what arm the
|
||
hold-repeat. -->
|
||
this:RegisterForClicks("LeftButtonDown", "LeftButtonUp", "RightButtonUp", "RightButtonDown")
|
||
</OnLoad>
|
||
<OnClick>
|
||
BenillaChatScrollButton_OnClick(this, arg2, "down", "igChatScrollDown")
|
||
</OnClick>
|
||
<OnUpdate>
|
||
BenillaChatScrollButton_OnUpdate(this, arg1, "down")
|
||
</OnUpdate>
|
||
</Scripts>
|
||
<NormalTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollDown-Up"/>
|
||
<PushedTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollDown-Down"/>
|
||
<DisabledTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollDown-Disabled"/>
|
||
<HighlightTexture file="Interface\Buttons\UI-Common-MouseHilight" alphaMode="ADD"/>
|
||
</Button>
|
||
<Button name="$parentUpButton">
|
||
<Size><AbsDimension x="32" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="BOTTOM" relativeTo="$parentDownButton" relativePoint="TOP"/>
|
||
</Anchors>
|
||
<Scripts>
|
||
<OnLoad>
|
||
<!-- All four, ref-verbatim — see the down button's note. -->
|
||
this:RegisterForClicks("LeftButtonDown", "LeftButtonUp", "RightButtonUp", "RightButtonDown")
|
||
</OnLoad>
|
||
<OnClick>
|
||
BenillaChatScrollButton_OnClick(this, arg2, "up", "igChatScrollUp")
|
||
</OnClick>
|
||
<OnUpdate>
|
||
BenillaChatScrollButton_OnUpdate(this, arg1, "up")
|
||
</OnUpdate>
|
||
</Scripts>
|
||
<NormalTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollUp-Up"/>
|
||
<PushedTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollUp-Down"/>
|
||
<DisabledTexture file="Interface\ChatFrame\UI-ChatIcon-ScrollUp-Disabled"/>
|
||
<HighlightTexture file="Interface\Buttons\UI-Common-MouseHilight" alphaMode="ADD"/>
|
||
</Button>
|
||
</Frames>
|
||
<Scripts>
|
||
<!-- EXECUTE_CHAT_LINE — how a MACRO line reaches the chat grammar (0996, wow-re
|
||
`macro-execution-law.md` §4, VERIFIED). The engine's macro runner (`0x4f14e0`)
|
||
names no Lua function and walks no command table: per body line it fires
|
||
`FrameScript_SignalEvent(0x188, "%s", line)`, and `0x188` is EXECUTE_CHAT_LINE
|
||
(the registry slot `0xbe17b8` is written once, at `0x51b4ff`, with `0x852470`).
|
||
ChatFrame.lua:1343 is the handler — `editBox:SetText(arg1)` → `ChatEdit_SendText`
|
||
→ `ChatEdit_OnEscapePressed` — and ChatFrame1's OnLoad
|
||
(FloatingChatFrame.xml:700) is its ONLY registrant in the whole default UI, so
|
||
every macro line runs through THIS frame's box whichever one has focus. benilla's
|
||
SubmitChatInput stands in for ChatEdit_SendText: the grammar behind it is Rust
|
||
(0881), which is the only part of that chain we replace. -->
|
||
<OnLoad>
|
||
<!-- UPDATE_CHAT_WINDOWS — the reference's own event for "a window's stored
|
||
settings moved", and the one FloatingChatFrame_OnEvent listens for
|
||
(FloatingChatFrame.lua l.34-38) to re-read GetChatWindowInfo. Ours is fired
|
||
by the host once it has restored the player's per-character file
|
||
(ui_chat::settings), which lands after this file has already run. It rides
|
||
window 1's registration for the same reason EXECUTE_CHAT_LINE does: the
|
||
template is shared by all seven, the handler re-reads EVERY window, and seven
|
||
registrations would run it seven times per restore. -->
|
||
if this:GetID() == 1 then
|
||
this:RegisterEvent("EXECUTE_CHAT_LINE")
|
||
this:RegisterEvent("UPDATE_CHAT_WINDOWS")
|
||
end
|
||
-- `isDocked` is NOT set here, and that is the reference's own shape: the flag is
|
||
-- `FCF_DockFrame`'s to write (FloatingChatFrame.lua:1153), and the chat-cache
|
||
-- load is what calls it — this file's trailing seeding loop, which reads
|
||
-- `GetChatWindowInfo`'s `docked` position exactly as `FloatingChatFrame_Update`
|
||
-- (l.34-70) does.
|
||
--
|
||
-- It briefly lived here (1712), gated to ids 1 and 2, while there was no dock
|
||
-- model to own it. There is one now (1714), and leaving the assignment here would
|
||
-- break it outright: `FCF_DockFrame` early-returns on an already-docked frame, so
|
||
-- a pre-set flag would keep both windows out of `DOCKED_CHAT_FRAMES` — docked
|
||
-- according to the flag, absent from the list every dock verb walks.
|
||
</OnLoad>
|
||
<OnEvent>
|
||
if event == "EXECUTE_CHAT_LINE" then
|
||
SubmitChatInput(arg1)
|
||
elseif event == "UPDATE_CHAT_WINDOWS" then
|
||
BenillaFCF_LoadWindowLooks()
|
||
end
|
||
</OnEvent>
|
||
<OnUpdate>
|
||
ChatFrame_OnUpdate(arg1)
|
||
</OnUpdate>
|
||
<OnMouseWheel>
|
||
-- WoW wheel convention: +1 per notch up, -1 down. Up = older messages.
|
||
if arg1 > 0 then this:ScrollUp() else this:ScrollDown() end
|
||
</OnMouseWheel>
|
||
<OnClick>
|
||
BenillaChatFrame_DismissCursor(arg1)
|
||
</OnClick>
|
||
<OnHyperlinkClick>
|
||
SetItemRef(arg1, arg2, arg3)
|
||
</OnHyperlinkClick>
|
||
<!-- ── Dragging the window ──────────────────────────────────────────────────────────
|
||
**What the reference does, and why we cannot copy the declaration.** Its template
|
||
carries `<TitleRegion setAllPoints="true"/>` — the engine drag where a mouse-down
|
||
anywhere in the frame moves it — and that region is INERT there, because the same
|
||
line declares `enableMouse="false"`. So 1.12 never drags a chat window by its body;
|
||
it drags it by the TAB (`FCF_Tab_OnClick`'s `chatFrame:StartMoving()`, and the
|
||
tab's own newbie tip says so in as many words: *"Left-click and drag to move the
|
||
window"*).
|
||
|
||
Ours is mouse-enabled (a `<ScrollingMessageFrame>` takes the mouse by construction,
|
||
and this window uses it for `OnClick` and `OnHyperlinkClick`), so transcribing the
|
||
title region would have three consequences the reference never pays: a press on the
|
||
chat body would be SWALLOWED before `OnClick` (0843's held-spell dismissal dies), a
|
||
LOCKED window would drag anyway (the engine title path has no lock gate — wow-re Q6
|
||
records that the movable bit is not read on it at all), and there would be no way to
|
||
turn it off again (an anchorless region falls back to its owner's rect, 1349, so
|
||
`ClearAllPoints` widens it rather than removing it). It is also inert for a fourth,
|
||
duller reason: our loader has no `<TitleRegion>` element — the region is
|
||
Lua-created only.
|
||
|
||
So the gesture is the drag TRIO instead, which is the reference's own layering
|
||
(`RegisterForDrag` + `OnDragStart`/`OnDragStop` → `StartMoving`/
|
||
`StopMovingOrSizing`) and buys exactly what the title region costs: a CLICK still
|
||
reaches `OnClick` because an un-started gesture leaves the click path alone, the
|
||
lock is a Lua gate like every other one here, and `OnDragStop` fires on the SOURCE
|
||
frame, so a drag released anywhere at all still ends the move.
|
||
|
||
The registration itself is switched by `BenillaFCF_ApplyLock` — a locked window is
|
||
not drag-registered, so it behaves exactly as it did before this arc. Both the body
|
||
(benilla's addition, the affordance the title region would have given) and the tab
|
||
(the reference's own, on ChatFrame<n>Tab) reach the same two verbs. ── -->
|
||
<OnDragStart>
|
||
FCF_StartMove(this)
|
||
</OnDragStart>
|
||
<OnDragStop>
|
||
FCF_StopMove(this)
|
||
</OnDragStop>
|
||
</Scripts>
|
||
<FontString inherits="ChatFontNormal" justifyH="LEFT"/>
|
||
</ScrollingMessageFrame>
|
||
|
||
<ScrollingMessageFrame name="ChatFrame1" inherits="BenillaChatFrameTemplate" id="1">
|
||
<Size><AbsDimension x="430" y="120"/></Size>
|
||
<Anchors>
|
||
<Anchor point="BOTTOMLEFT" relativePoint="BOTTOMLEFT">
|
||
<Offset><AbsDimension x="32" y="85"/></Offset>
|
||
</Anchor>
|
||
</Anchors>
|
||
</ScrollingMessageFrame>
|
||
|
||
<!-- ChatFrame2 "Combat Log": docked — it takes ChatFrame1's rect from these two anchors and
|
||
nothing else, ships hidden, and its tab (ChatFrame2Tab, below) selects it. Its message
|
||
groups are the chat-cache combat set, registered app-side (`ui_chat::frames`, 1571). -->
|
||
<ScrollingMessageFrame name="ChatFrame2" inherits="BenillaChatFrameTemplate" hidden="true" id="2">
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativeTo="ChatFrame1" relativePoint="TOPLEFT"/>
|
||
<Anchor point="BOTTOMRIGHT" relativeTo="ChatFrame1" relativePoint="BOTTOMRIGHT"/>
|
||
</Anchors>
|
||
</ScrollingMessageFrame>
|
||
|
||
<!-- The dock's DRIVER. `FCF_OnUpdate` is dock-GLOBAL — one `reveal`, one selected tab, both
|
||
windows' tabs and textures — and it used to ride ChatFrame1's own OnUpdate. Selecting the
|
||
Combat Log hides ChatFrame1, a hidden frame gets no OnUpdate, and so the whole dock froze
|
||
the instant it was used: the tab alphas stuck showing General as selected (what the
|
||
director saw), the hover fade stuck at whatever it was, the tab-width settle stopped, and
|
||
the whisper flash stopped blinking. A driver must not be one of the things it drives.
|
||
|
||
So it rides a frame the dock cannot hide. Mounting it on BOTH windows instead would work
|
||
only while exactly one of them is ever visible — an invariant the dock happens to hold
|
||
today and that a second visible window would turn into a double-speed fade.
|
||
|
||
**A frame the dock cannot hide is the reference's own answer**, and this comment used to
|
||
claim the opposite: that 1.12.1 ships `FCF_OnUpdate` defined and never referenced, so
|
||
vanilla's tabs never hover-fade and the reveal was benilla's own (0288). That is wrong.
|
||
`UIParent.xml` l.19 calls `FCF_OnUpdate(arg1)` from **UIParent's** `<OnUpdate>`, in the
|
||
same list as `UIFrameFadeUpdate`/`UIFrameFlashUpdate` — never from a chat frame, for the
|
||
reason above. A 1x1 frame parented to UIParent is that mounting point re-expressed; the
|
||
reasoning was right and only its premise was invented. The hover reveal, the 0.2 s
|
||
stationary delay and the 1.0/0.5 selected/unselected tab alphas are all the reference's.
|
||
|
||
`ChatFrame_OnUpdate` stays on the windows, where it belongs: it reads `this` and blinks
|
||
that frame's own bottom button. -->
|
||
<Frame name="BenillaChatDockDriver" parent="UIParent">
|
||
<Size><AbsDimension x="1" y="1"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT"/></Anchors>
|
||
<Scripts>
|
||
<OnUpdate>
|
||
FCF_OnUpdate(arg1)
|
||
</OnUpdate>
|
||
</Scripts>
|
||
</Frame>
|
||
|
||
<!-- ── ChatFrame3..ChatFrame7: the five undocked windows ────────────────────────────────
|
||
The reference declares seven ScrollingMessageFrames (FloatingChatFrame.xml l.822-832,
|
||
`inherits="FloatingChatFrameTemplate"`), of which 3..7 ship HIDDEN and UNDOCKED — the
|
||
shipped chat-cache says so in as many words, and it is the state that matters here:
|
||
|
||
WINDOW 3 … 10: SIZE 0 / COLOR 0 0 0 0 / LOCKED 1 / DOCKED 0 / SHOWN 0
|
||
MESSAGES <empty> CHANNELS <empty> ZONECHANNELS 0
|
||
|
||
(`wow-5875-re/WoW/WTF/.../chat-cache.txt`, a real 1.12 character). So these are not
|
||
placeholders standing in for something richer — hidden, empty, unsubscribed IS what
|
||
ChatFrame3 is on a stock client, and an `AddMessage` into one lands in a ring nobody is
|
||
looking at, exactly as it does there.
|
||
|
||
**They are load-bearing for two different reasons, both measured:**
|
||
|
||
1. `_LazyPig/LazyPig.lua:1992` walks `for i = 1, NUM_CHAT_WINDOWS` and calls
|
||
`getglobal("ChatFrame"..i):IsVisible()` with NO nil guard — it dies at i=3 the moment
|
||
NUM_CHAT_WINDOWS exists without the frames behind it. Declaring the constant alone
|
||
would have *created* that crash.
|
||
2. `ChatFrame3` and `ChatFrame5` are named directly as debug sinks by five distinct
|
||
addons: IgniteStatus (7 unguarded `ChatFrame3:AddMessage` sites), TipBuddy, Radar
|
||
(the one guarded read), and FuBar_CorkFu + FuBar_oRA2CooldownFu, which store the frame
|
||
as AceDebug's `debugFrame`. A sixth "consumer", RosterLib-2.0's
|
||
`local function print(text) ChatFrame3:AddMessage(text) end`, is dead code replicated
|
||
into four folders — the count that looks like 12 files is 7 real sites.
|
||
|
||
**They now carry the full template, and that is 1712's correction.** These five used to
|
||
be declared bare — no tab, no background, no border art, no scroll column — on the
|
||
argument that the furniture is "for a dock model we do not have (0288 §2), and nothing
|
||
reads it while these frames stay hidden and undocked". The reading of Outfitter behind it
|
||
was right (`Outfitter.lua:3099` gates its `vTab:GetText()` behind `IsVisible() or
|
||
isDocked`) and the conclusion was still wrong, because one addon is not the corpus.
|
||
pfUI reads every piece of it, for all seven, unguarded:
|
||
|
||
chat.lua:588 `_G["ChatFrame"..i.."UpButton"]:Hide()` — and Down/Bottom, and
|
||
`_G["ChatFrame"..i.."Tab"].SetAlpha`, at module load
|
||
chat.lua:322 `local tab = _G["ChatFrame"..i.."Tab"]` → `tab:SetParent(…)`,
|
||
`tab:GetRegions()`
|
||
chat.lua:404 every `CHAT_FRAME_TEXTURES` slice, `texture:SetTexture()`
|
||
chat.lua:419 `_G["ChatFrame"..i.."ResizeBottom"]:Hide()`
|
||
|
||
Visibility has nothing to do with whether a global resolves, and pfQuest, pfQuest-turtle
|
||
and ShaguDPS embed the same library. So all seven inherit the same template, exactly as
|
||
the reference does (FloatingChatFrame.xml l.822-832 are five one-liners), and the tabs
|
||
below are declared the same way.
|
||
|
||
`isDocked` is still NOT set on these five, and that stays the whole point of the flag: the
|
||
template's `OnLoad` gates it to the two windows the chat-cache marks `DOCKED 1`, and these
|
||
are the frames that genuinely are not docked — which is what keeps Outfitter's own gate
|
||
doing what it does on a stock client.
|
||
|
||
They keep the reference's own look: window alpha 0 (the chat-cache `COLOR`'s fourth byte)
|
||
means a shown ChatFrame5 renders its text over the world with no box, and the template's
|
||
art is all alpha-0 until `BenillaFCF` fades it — which it does for the dock's two. ── -->
|
||
<ScrollingMessageFrame name="ChatFrame3" inherits="BenillaChatFrameTemplate" hidden="true" id="3">
|
||
<Size><AbsDimension x="430" y="128"/></Size>
|
||
<Anchors><Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="100" y="100"/></Offset></Anchor></Anchors>
|
||
</ScrollingMessageFrame>
|
||
<ScrollingMessageFrame name="ChatFrame4" inherits="BenillaChatFrameTemplate" hidden="true" id="4">
|
||
<Size><AbsDimension x="430" y="128"/></Size>
|
||
<Anchors><Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="100" y="100"/></Offset></Anchor></Anchors>
|
||
</ScrollingMessageFrame>
|
||
<ScrollingMessageFrame name="ChatFrame5" inherits="BenillaChatFrameTemplate" hidden="true" id="5">
|
||
<Size><AbsDimension x="430" y="128"/></Size>
|
||
<Anchors><Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="100" y="100"/></Offset></Anchor></Anchors>
|
||
</ScrollingMessageFrame>
|
||
<ScrollingMessageFrame name="ChatFrame6" inherits="BenillaChatFrameTemplate" hidden="true" id="6">
|
||
<Size><AbsDimension x="430" y="128"/></Size>
|
||
<Anchors><Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="100" y="100"/></Offset></Anchor></Anchors>
|
||
</ScrollingMessageFrame>
|
||
<ScrollingMessageFrame name="ChatFrame7" inherits="BenillaChatFrameTemplate" hidden="true" id="7">
|
||
<Size><AbsDimension x="430" y="128"/></Size>
|
||
<Anchors><Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="100" y="100"/></Offset></Anchor></Anchors>
|
||
</ScrollingMessageFrame>
|
||
|
||
<!-- ── The tabs (ref ChatTabTemplate: 64×32, ChatFrameTab 3-slice quarters 16/44/16, label
|
||
at the left slice's right edge y −5, GameFontNormalSmall; the flash band at y −7; the
|
||
ref's ButtonText also carries a vestigial 50×8 Size that TabResize immediately
|
||
shrink-wraps — omitted). The middle slice hugs the label live (BenillaFCF_TabResize —
|
||
the ref's PanelTemplates_TabResize(5, tab) from FCF_DockUpdate). Hidden by alpha
|
||
(hover reveals).
|
||
|
||
ChatFrame2Tab ("Combat Log") is deliberately NOT built: the combat log renders nothing
|
||
real yet (window 2 gets its chat-cache groups from the router, but the combat-event →
|
||
line pipeline doesn't exist), so the tab just selected an empty window. ChatFrame2
|
||
itself stays above (the router keeps feeding it) so the tab can return whole with the
|
||
combat-log arc — director's call, 2026-07-11. The FCF loops guard the missing tab. ── -->
|
||
<Button name="BenillaChatTabTemplate" virtual="true" frameStrata="LOW" alpha="0">
|
||
<Size><AbsDimension x="70" y="32"/></Size>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<Texture name="$parentLeft" file="Interface\ChatFrame\ChatFrameTab">
|
||
<Size><AbsDimension x="16" y="32"/></Size>
|
||
<Anchors><Anchor point="TOPLEFT"/></Anchors>
|
||
<TexCoords left="0.0" right="0.25" top="0.0" bottom="1.0"/>
|
||
</Texture>
|
||
<Texture name="$parentMiddle" file="Interface\ChatFrame\ChatFrameTab">
|
||
<Size><AbsDimension x="38" y="32"/></Size>
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentLeft" relativePoint="RIGHT"/></Anchors>
|
||
<TexCoords left="0.25" right="0.75" top="0.0" bottom="1.0"/>
|
||
</Texture>
|
||
<Texture name="$parentRight" file="Interface\ChatFrame\ChatFrameTab">
|
||
<Size><AbsDimension x="16" y="32"/></Size>
|
||
<Anchors><Anchor point="LEFT" relativeTo="$parentMiddle" relativePoint="RIGHT"/></Anchors>
|
||
<TexCoords left="0.75" right="1.0" top="0.0" bottom="1.0"/>
|
||
</Texture>
|
||
</Layer>
|
||
</Layers>
|
||
<Frames>
|
||
<Frame name="$parentFlash" hidden="true">
|
||
<Size><AbsDimension x="5" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="LEFT" relativeTo="$parentLeft"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor>
|
||
<Anchor point="RIGHT" relativeTo="$parentRight"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor>
|
||
</Anchors>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<Texture file="Interface\PaperDollInfoFrame\UI-Character-Tab-Highlight" alphaMode="ADD" setAllPoints="true"/>
|
||
</Layer>
|
||
</Layers>
|
||
</Frame>
|
||
<!-- The window's options menu (decision 1589). A child of the TAB, because
|
||
FCF_GetCurrentChatFrameID reads the open menu's parent id — the reference's own
|
||
contract, and why the tab carries `id` above. -->
|
||
<Frame name="$parentDropDown" inherits="UIDropDownMenuTemplate" hidden="true">
|
||
<Scripts>
|
||
<OnLoad>FCFOptionsDropDown_OnLoad()</OnLoad>
|
||
</Scripts>
|
||
</Frame>
|
||
</Frames>
|
||
<Scripts>
|
||
<!-- ref FloatingChatFrame.xml l.121: the tab takes the right button too, which is
|
||
what opens the options menu. A Button's default set is LeftButtonUp only, so
|
||
without this the right-click never reaches OnClick at all. -->
|
||
<OnLoad>
|
||
this:RegisterForClicks("LeftButtonUp", "RightButtonUp")
|
||
<!-- The tab's LABEL is the window's name. The reference reads it from
|
||
`GetChatWindowInfo` (`FCF_SetWindowName`), and the shipped chat-cache holds a
|
||
NAME for windows 1 and 2 and leaves it EMPTY for 3..10 — so an undocked
|
||
window's tab has no text there either, and `GetText()` on it is nil. Ours
|
||
seats the same two from their GlobalStrings, which is where the loader's
|
||
ALL-CAPS key rule used to reach for them when the text was authored here. -->
|
||
local id = this:GetID()
|
||
if id == 1 then
|
||
this:SetText(GENERAL)
|
||
elseif id == 2 then
|
||
this:SetText(COMBAT_LOG)
|
||
end
|
||
</OnLoad>
|
||
<OnClick>
|
||
BenillaFCF_TabClick(this:GetID(), arg1)
|
||
</OnClick>
|
||
<!-- The reference's own move handle (ref FCF_Tab_OnClick's press/release arms, and the
|
||
tip below, which says "Left-click and drag to move the window" in as many words).
|
||
It reaches the same two verbs as the window body does; the registration that arms
|
||
it is `BenillaFCF_ApplyLock`'s, so a locked window's tab still only selects. -->
|
||
<OnDragStart>
|
||
FCF_StartMove(getglobal("ChatFrame" .. this:GetID()))
|
||
</OnDragStart>
|
||
<OnDragStop>
|
||
FCF_StopMove(getglobal("ChatFrame" .. this:GetID()))
|
||
</OnDragStop>
|
||
<!-- ref l.129-134. The tip is how the menu is DISCOVERABLE: nothing on screen says a
|
||
chat tab has a right-click menu, which is half of why B246 read as "no chat
|
||
options at all". `noNormalText` is the reference's own 6th argument. -->
|
||
<OnEnter>
|
||
BenillaTooltip_AddNewbieTipFor(this, CHAT_OPTIONS_LABEL, 1.0, 1.0, 1.0,
|
||
NEWBIE_TOOLTIP_CHATOPTIONS, 1)
|
||
</OnEnter>
|
||
<OnLeave>
|
||
GameTooltip:Hide()
|
||
</OnLeave>
|
||
</Scripts>
|
||
<ButtonText name="$parentText">
|
||
<Anchors>
|
||
<Anchor point="LEFT" relativeTo="$parentLeft" relativePoint="RIGHT"><Offset><AbsDimension x="0" y="-5"/></Offset></Anchor>
|
||
</Anchors>
|
||
</ButtonText>
|
||
<NormalFont inherits="GameFontNormalSmall"/>
|
||
<!-- The ref leaves this texture unsized and leans on the BLP's NATURAL height (32) to
|
||
seat the glow at the anchors' y −7 — centered on the tab's visible body, which sits
|
||
in the BOTTOM 22 rows of the ChatFrameTab canvas. benilla's v1 region model has no
|
||
natural-size law (an unpinned axis inherits the owner's edges, decision 0068), which
|
||
parked the glow 7 units high; the explicit height states what the ref reads from the
|
||
file. -->
|
||
<HighlightTexture file="Interface\PaperDollInfoFrame\UI-Character-Tab-Highlight" alphaMode="ADD">
|
||
<Size><AbsDimension x="0" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="LEFT" relativeTo="$parentLeft"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor>
|
||
<Anchor point="RIGHT" relativeTo="$parentRight"><Offset><AbsDimension x="0" y="-7"/></Offset></Anchor>
|
||
</Anchors>
|
||
</HighlightTexture>
|
||
</Button>
|
||
|
||
<!-- ChatFrame1Tab — "General". Only the seat is per-instance, exactly as the reference's own
|
||
instance is (FloatingChatFrame.xml l.712-720: an `<Anchors>` block and nothing else). -->
|
||
<Button name="ChatFrame1Tab" inherits="BenillaChatTabTemplate" id="1">
|
||
<Anchors>
|
||
<Anchor point="BOTTOMLEFT" relativeTo="ChatFrame1Background" relativePoint="TOPLEFT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
|
||
</Anchors>
|
||
</Button>
|
||
|
||
<!-- ChatFrame2Tab — "Combat Log". Its seat is the dock's tab strip: abutted to tab 1's right
|
||
edge, which is our standing-in for `FCF_DockUpdate`'s cumulative `FCF_SetTabPosition` x.
|
||
`BenillaFCF_TabResize` sizes both to their measured label widths, so the abutment survives
|
||
a locale whose words are longer. -->
|
||
<Button name="ChatFrame2Tab" inherits="BenillaChatTabTemplate" id="2">
|
||
<Anchors>
|
||
<Anchor point="LEFT" relativeTo="ChatFrame1Tab" relativePoint="RIGHT"/>
|
||
</Anchors>
|
||
</Button>
|
||
|
||
<!-- ChatFrame3Tab..ChatFrame7Tab — the five undocked windows' tabs. `hidden`, because that is
|
||
the reference's `ChatTabTemplate` default and only `FCF_DockFrame`/`FCF_OpenNewWindow`
|
||
un-hide one; and seated on their own window's background at the reference's own offset —
|
||
`FCF_SetTabPosition(chatFrame, 0)` is `SetPoint("BOTTOMLEFT", <frame>Background, "TOPLEFT",
|
||
x+2, 0)` (FloatingChatFrame.lua:1263), which is the same anchor tab 1 carries above.
|
||
|
||
They exist because addons read them, not because anything here shows them: pfUI's
|
||
`chat.lua:595` is `_G["ChatFrame"..i.."Tab"].SetAlpha` for all seven at module load, and
|
||
`chat.lua:322` hands the same tab to `SetParent`/`GetRegions` (1712). -->
|
||
<Button name="ChatFrame3Tab" inherits="BenillaChatTabTemplate" hidden="true" id="3">
|
||
<Anchors>
|
||
<Anchor point="BOTTOMLEFT" relativeTo="ChatFrame3Background" relativePoint="TOPLEFT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
|
||
</Anchors>
|
||
</Button>
|
||
|
||
<Button name="ChatFrame4Tab" inherits="BenillaChatTabTemplate" hidden="true" id="4">
|
||
<Anchors>
|
||
<Anchor point="BOTTOMLEFT" relativeTo="ChatFrame4Background" relativePoint="TOPLEFT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
|
||
</Anchors>
|
||
</Button>
|
||
|
||
<Button name="ChatFrame5Tab" inherits="BenillaChatTabTemplate" hidden="true" id="5">
|
||
<Anchors>
|
||
<Anchor point="BOTTOMLEFT" relativeTo="ChatFrame5Background" relativePoint="TOPLEFT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
|
||
</Anchors>
|
||
</Button>
|
||
|
||
<Button name="ChatFrame6Tab" inherits="BenillaChatTabTemplate" hidden="true" id="6">
|
||
<Anchors>
|
||
<Anchor point="BOTTOMLEFT" relativeTo="ChatFrame6Background" relativePoint="TOPLEFT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
|
||
</Anchors>
|
||
</Button>
|
||
|
||
<Button name="ChatFrame7Tab" inherits="BenillaChatTabTemplate" hidden="true" id="7">
|
||
<Anchors>
|
||
<Anchor point="BOTTOMLEFT" relativeTo="ChatFrame7Background" relativePoint="TOPLEFT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
|
||
</Anchors>
|
||
</Button>
|
||
|
||
|
||
<!-- ── The UIMenu kit (ref UIMenu.xml, names kept for the addon surface): a row button
|
||
with a right-aligned shortcut string, and the tooltip-backdrop menu holding the ref's
|
||
fixed pool of 32 rows (hidden until AddButton). The label is authored LEFT-anchored:
|
||
the ref leans on the 1.12 ButtonText default rect spanning the button, which our lazy
|
||
label slot doesn't reproduce. ─────────────────────────────────────────────────── -->
|
||
<Button name="UIMenuButtonTemplate" virtual="true">
|
||
<Size><AbsDimension x="128" y="16"/></Size>
|
||
<Scripts>
|
||
<OnLoad>
|
||
UIMenuButton_OnLoad()
|
||
</OnLoad>
|
||
<OnClick>
|
||
UIMenuButton_OnClick()
|
||
</OnClick>
|
||
<OnEnter>
|
||
UIMenuButton_OnEnter()
|
||
</OnEnter>
|
||
<OnLeave>
|
||
UIMenuButton_OnLeave()
|
||
</OnLeave>
|
||
</Scripts>
|
||
<Layers>
|
||
<Layer level="ARTWORK">
|
||
<FontString name="$parentShortcutText" inherits="GameFontHighlight" hidden="true">
|
||
<Anchors><Anchor point="RIGHT"/></Anchors>
|
||
</FontString>
|
||
</Layer>
|
||
</Layers>
|
||
<ButtonText name="$parentText">
|
||
<Anchors><Anchor point="LEFT"/></Anchors>
|
||
</ButtonText>
|
||
<NormalFont inherits="GameFontNormal" justifyH="LEFT"/>
|
||
<HighlightFont inherits="GameFontHighlight" justifyH="LEFT"/>
|
||
<DisabledFont inherits="GameFontDisable" justifyH="LEFT"/>
|
||
</Button>
|
||
<Frame name="UIMenuTemplate" frameStrata="DIALOG" virtual="true">
|
||
<Backdrop bgFile="Interface\Tooltips\UI-Tooltip-Background" edgeFile="Interface\Tooltips\UI-Tooltip-Border" tile="true">
|
||
<BackgroundInsets><AbsInset left="4" right="4" top="4" bottom="4"/></BackgroundInsets>
|
||
<TileSize><AbsValue val="32"/></TileSize>
|
||
<EdgeSize><AbsValue val="13"/></EdgeSize>
|
||
</Backdrop>
|
||
<Frames>
|
||
<Button name="$parentButton1" inherits="UIMenuButtonTemplate" id="1"/>
|
||
<Button name="$parentButton2" inherits="UIMenuButtonTemplate" id="2"/>
|
||
<Button name="$parentButton3" inherits="UIMenuButtonTemplate" id="3"/>
|
||
<Button name="$parentButton4" inherits="UIMenuButtonTemplate" id="4"/>
|
||
<Button name="$parentButton5" inherits="UIMenuButtonTemplate" id="5"/>
|
||
<Button name="$parentButton6" inherits="UIMenuButtonTemplate" id="6"/>
|
||
<Button name="$parentButton7" inherits="UIMenuButtonTemplate" id="7"/>
|
||
<Button name="$parentButton8" inherits="UIMenuButtonTemplate" id="8"/>
|
||
<Button name="$parentButton9" inherits="UIMenuButtonTemplate" id="9"/>
|
||
<Button name="$parentButton10" inherits="UIMenuButtonTemplate" id="10"/>
|
||
<Button name="$parentButton11" inherits="UIMenuButtonTemplate" id="11"/>
|
||
<Button name="$parentButton12" inherits="UIMenuButtonTemplate" id="12"/>
|
||
<Button name="$parentButton13" inherits="UIMenuButtonTemplate" id="13"/>
|
||
<Button name="$parentButton14" inherits="UIMenuButtonTemplate" id="14"/>
|
||
<Button name="$parentButton15" inherits="UIMenuButtonTemplate" id="15"/>
|
||
<Button name="$parentButton16" inherits="UIMenuButtonTemplate" id="16"/>
|
||
<Button name="$parentButton17" inherits="UIMenuButtonTemplate" id="17"/>
|
||
<Button name="$parentButton18" inherits="UIMenuButtonTemplate" id="18"/>
|
||
<Button name="$parentButton19" inherits="UIMenuButtonTemplate" id="19"/>
|
||
<Button name="$parentButton20" inherits="UIMenuButtonTemplate" id="20"/>
|
||
<Button name="$parentButton21" inherits="UIMenuButtonTemplate" id="21"/>
|
||
<Button name="$parentButton22" inherits="UIMenuButtonTemplate" id="22"/>
|
||
<Button name="$parentButton23" inherits="UIMenuButtonTemplate" id="23"/>
|
||
<Button name="$parentButton24" inherits="UIMenuButtonTemplate" id="24"/>
|
||
<Button name="$parentButton25" inherits="UIMenuButtonTemplate" id="25"/>
|
||
<Button name="$parentButton26" inherits="UIMenuButtonTemplate" id="26"/>
|
||
<Button name="$parentButton27" inherits="UIMenuButtonTemplate" id="27"/>
|
||
<Button name="$parentButton28" inherits="UIMenuButtonTemplate" id="28"/>
|
||
<Button name="$parentButton29" inherits="UIMenuButtonTemplate" id="29"/>
|
||
<Button name="$parentButton30" inherits="UIMenuButtonTemplate" id="30"/>
|
||
<Button name="$parentButton31" inherits="UIMenuButtonTemplate" id="31"/>
|
||
<Button name="$parentButton32" inherits="UIMenuButtonTemplate" id="32"/>
|
||
</Frames>
|
||
<Scripts>
|
||
<OnShow>
|
||
UIMenu_OnShow()
|
||
PlaySound("UChatScrollButton")
|
||
this:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b)
|
||
this:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
|
||
</OnShow>
|
||
<OnUpdate>
|
||
UIMenu_OnUpdate(arg1)
|
||
</OnUpdate>
|
||
</Scripts>
|
||
</Frame>
|
||
|
||
<!-- ── The chat menu button (ref ChatFrameMenuButton): the bubble icon atop the scroll
|
||
column; click opens ChatMenu (ref ChatFrame_OpenMenu). ──────────────────────────── -->
|
||
<Button name="ChatFrameMenuButton" frameStrata="LOW">
|
||
<Size><AbsDimension x="32" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="BOTTOM" relativeTo="ChatFrame1UpButton" relativePoint="TOP"/>
|
||
</Anchors>
|
||
<Scripts>
|
||
<OnClick>
|
||
PlaySound("igChatEmoteButton")
|
||
ChatMenu:Show()
|
||
</OnClick>
|
||
</Scripts>
|
||
<NormalTexture file="Interface\ChatFrame\UI-ChatIcon-Chat-Up"/>
|
||
<PushedTexture file="Interface\ChatFrame\UI-ChatIcon-Chat-Down"/>
|
||
<DisabledTexture file="Interface\ChatFrame\UI-ChatIcon-Chat-Disabled"/>
|
||
<HighlightTexture file="Interface\Buttons\UI-Common-MouseHilight" alphaMode="ADD"/>
|
||
</Button>
|
||
|
||
<!-- ChatMenu + its two nested menus (ref FloatingChatFrame.xml; the ref's own OnShow
|
||
hides only EmoteMenu — hiding both submenus is the same intent without the ref's
|
||
Voice-menu-left-open corner). The submenus are authored as children (the ref's
|
||
parent="ChatMenu" attr — this engine attaches by nesting, CharacterFrame.xml's note),
|
||
so hiding ChatMenu hides them with it. -->
|
||
<Frame name="ChatMenu" inherits="UIMenuTemplate" hidden="true">
|
||
<Anchors>
|
||
<Anchor point="BOTTOMLEFT" relativeTo="ChatFrameMenuButton" relativePoint="TOPRIGHT"/>
|
||
</Anchors>
|
||
<Frames>
|
||
<Frame name="EmoteMenu" inherits="UIMenuTemplate" hidden="true">
|
||
<Scripts>
|
||
<OnLoad>
|
||
BenillaOnEmoteMenuLoad(BenillaEmoteMenuList)
|
||
</OnLoad>
|
||
</Scripts>
|
||
</Frame>
|
||
<Frame name="VoiceMacroMenu" inherits="UIMenuTemplate" hidden="true">
|
||
<Scripts>
|
||
<OnLoad>
|
||
BenillaOnEmoteMenuLoad(BenillaVoiceMacroList)
|
||
</OnLoad>
|
||
</Scripts>
|
||
</Frame>
|
||
</Frames>
|
||
<Scripts>
|
||
<OnLoad>
|
||
ChatMenu_OnLoad()
|
||
</OnLoad>
|
||
<OnShow>
|
||
UIMenu_OnShow()
|
||
EmoteMenu:Hide()
|
||
VoiceMacroMenu:Hide()
|
||
this:SetBackdropBorderColor(TOOLTIP_DEFAULT_COLOR.r, TOOLTIP_DEFAULT_COLOR.g, TOOLTIP_DEFAULT_COLOR.b)
|
||
this:SetBackdropColor(TOOLTIP_DEFAULT_BACKGROUND_COLOR.r, TOOLTIP_DEFAULT_BACKGROUND_COLOR.g, TOOLTIP_DEFAULT_BACKGROUND_COLOR.b)
|
||
</OnShow>
|
||
</Scripts>
|
||
</Frame>
|
||
|
||
<!-- ── The edit box (ref ChatFrameEditBox): below ChatFrame1, -5/+5 wide, 32 tall, DIALOG
|
||
strata, hidden until ENTER opens it (ui_chat::input). The three-piece
|
||
UI-ChatInputBorder art; the header ("Say:" until the P5 edit machine drives it) at
|
||
LEFT+13; typed text starts past it (static insets, P5 makes them live). ─────────── -->
|
||
<!-- The ref template ships ignoreArrows="true": the four arrows are NOT consumed by the box
|
||
unless **ALT** is held, so they fall through to their bindings and you keep arrow-steering
|
||
with the box open. (This comment said "Ctrl bypasses" — a wow-re note had `0x41f8f0(2)`
|
||
labelled Ctrl; it is Alt, and the §5 `ignorearrows-alt-arrow-gate.md` corrected the note.
|
||
The binding on that very bit is named `SetAltArrowKeyMode`.)
|
||
|
||
benilla deliberately does NOT set the attribute here — the director's call is the modern
|
||
law: plain arrows move the caret, Up/Down recall history (decision 0301). What changed is
|
||
only that the engine now honours the attribute FAITHFULLY for a box that does set it: the
|
||
key is declined rather than swallowed-and-ignored, which is the difference between
|
||
arrow-steering working and the keys being dead. -->
|
||
<!-- historyLines: the ref template ships 32; 64 at the director's call ("at least 50",
|
||
2026-07-26) — the recall walk is the modern law already (decision 0301). -->
|
||
<EditBox name="ChatFrameEditBox" frameStrata="DIALOG" hidden="true" historyLines="64" letters="255">
|
||
<Size><AbsDimension x="0" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="TOPLEFT" relativeTo="ChatFrame1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="-5" y="-2"/></Offset></Anchor>
|
||
<Anchor point="TOPRIGHT" relativeTo="ChatFrame1" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="5" y="-2"/></Offset></Anchor>
|
||
</Anchors>
|
||
<TextInsets><AbsInset left="47" right="13" top="0" bottom="0"/></TextInsets>
|
||
<Layers>
|
||
<Layer level="BACKGROUND">
|
||
<Texture name="$parentLeft" file="Interface\ChatFrame\UI-ChatInputBorder-Left">
|
||
<Size><AbsDimension x="256" y="32"/></Size>
|
||
<Anchors><Anchor point="LEFT"/></Anchors>
|
||
</Texture>
|
||
<Texture name="$parentRight" file="Interface\ChatFrame\UI-ChatInputBorder-Right">
|
||
<Size><AbsDimension x="16" y="32"/></Size>
|
||
<Anchors><Anchor point="RIGHT"/></Anchors>
|
||
<TexCoords left="0.9375" right="1.0" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
<Texture name="$parentMid" file="Interface\ChatFrame\UI-ChatInputBorder-Right">
|
||
<Size><AbsDimension x="1" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="LEFT" relativeTo="$parentLeft" relativePoint="RIGHT"/>
|
||
<Anchor point="RIGHT" relativeTo="$parentRight" relativePoint="LEFT"/>
|
||
</Anchors>
|
||
<TexCoords left="0" right="0.9375" top="0" bottom="1.0"/>
|
||
</Texture>
|
||
</Layer>
|
||
<Layer level="ARTWORK">
|
||
<FontString name="$parentHeader" inherits="ChatFontNormal" text="Say:">
|
||
<Anchors>
|
||
<Anchor point="LEFT"><Offset><AbsDimension x="13" y="0"/></Offset></Anchor>
|
||
</Anchors>
|
||
</FontString>
|
||
</Layer>
|
||
</Layers>
|
||
<Scripts>
|
||
<OnEnterPressed>
|
||
SubmitChatInput(this:GetText())
|
||
this:SetText("")
|
||
this:ClearFocus()
|
||
this:Hide()
|
||
</OnEnterPressed>
|
||
<OnEscapePressed>
|
||
this:SetText("")
|
||
this:ClearFocus()
|
||
this:Hide()
|
||
</OnEscapePressed>
|
||
<OnTabPressed>
|
||
BenillaChatTabPressed()
|
||
</OnTabPressed>
|
||
</Scripts>
|
||
<FontString inherits="ChatFontNormal" justifyH="LEFT"/>
|
||
</EditBox>
|
||
|
||
<!-- The load-order hack, like the ref's own trailing FCF_DockFrame(ChatFrame1, 1): select
|
||
the General tab once everything above exists — and seat every window's stored LOOK
|
||
(FCF_SetWindowColor/Alpha: the ref vertex-colors every CHAT_FRAME_TEXTURES piece with the
|
||
chat-cache window COLOR, which ships 0 0 0; the ChatFrameBackground art itself is
|
||
white, so untinted it reads as a white wash over the world). Since 1589 that colour is
|
||
read out of the engine rather than written as a literal, because the player can now
|
||
change it — a fresh store still answers 0 0 0, so a first run looks exactly as before. -->
|
||
<Script><![CDATA[
|
||
-- `DEFAULT_CHAT_FRAME = ChatFrame1` — the reference's own line (ChatFrame.lua), and the
|
||
-- name half of FrameXML actually prints through. It sits here rather than in the header
|
||
-- script because a <Script> block runs where it stands: at the top of this file
|
||
-- ChatFrame1 does not exist yet. Measured demand: 3 of the 218 vanilla-corpus addons
|
||
-- die on `attempt to index global 'DEFAULT_CHAT_FRAME'` without it, and many more reach
|
||
-- it on a later path.
|
||
DEFAULT_CHAT_FRAME = ChatFrame1
|
||
-- ...and `SELECTED_CHAT_FRAME`, which the reference seats on the SAME line of the same
|
||
-- OnLoad (`FloatingChatFrame.xml:739-740`). The two are not synonyms and the difference
|
||
-- is why both exist: DEFAULT is where FrameXML PRINTS, SELECTED is which window the
|
||
-- player last picked, and it moves with the tabs (BenillaFCF_SelectDock, above). Seeded
|
||
-- here because `BenillaFCF_SelectDock(1)` on the next line is what first writes it —
|
||
-- pfUI reads it from a chat-tab SetAlpha hook that can fire before any tab is clicked,
|
||
-- and a nil there stopped it (and, through its embedded copies, pfQuest, pfQuest-turtle
|
||
-- and ShaguDPS) dead.
|
||
SELECTED_CHAT_FRAME = ChatFrame1
|
||
|
||
-- ── The four per-window fields `ChatFrame_OnLoad` seats on every chat frame ───────────
|
||
-- `this.tellTimer`, `this.channelList`, `this.zoneChannelList`, `this.messageTypeList`
|
||
-- (ChatFrame.lua l.1155-1158). Written here in one loop rather than in an OnLoad, because
|
||
-- ChatFrame3..7 deliberately have no OnLoad at all (see their block above) and the
|
||
-- reference's semantics are per-FRAME, not per-template — a loop over the seven windows
|
||
-- is the same set of writes with nothing left uncovered.
|
||
--
|
||
-- **The three tables are EMPTY, which is the same honest shape `ChatTypeGroup` above
|
||
-- carries and for the same reason**: our chat routes host-side (`ui_chat`), so a window
|
||
-- does not accumulate a message-group subscription list to put in `messageTypeList`, and
|
||
-- channel membership is the engine's, not a per-window Lua list. The divergence, stated:
|
||
-- an addon that reads these gets a truthful "this window subscribes to nothing in Lua"
|
||
-- rather than a nil. That is the `ChatFrame_OnEvent` architecture question again, not
|
||
-- these fields'.
|
||
--
|
||
-- Measured demand: pfUI's `RefreshChat` walks `_G["ChatFrame"..i].messageTypeList` with
|
||
-- `pairs` and NO nil guard (`modules/chat.lua:325`), counting how many of a window's
|
||
-- types look like a combat log. A nil there is `bad argument #1 to 'pairs'` — and pfUI is
|
||
-- embedded in pfQuest, pfQuest-turtle and ShaguDPS, so it is four top-20 addons on one
|
||
-- unset field. With an empty list the count is 0 and no window is classed as a combat
|
||
-- log, which is exactly right for a UI that has no combat-log window.
|
||
for i = 1, NUM_CHAT_WINDOWS do
|
||
local frame = getglobal("ChatFrame" .. i)
|
||
if frame then
|
||
frame.tellTimer = GetTime()
|
||
frame.channelList = {}
|
||
frame.zoneChannelList = {}
|
||
frame.messageTypeList = {}
|
||
end
|
||
end
|
||
|
||
-- ── Seed the dock (ref `FloatingChatFrame_Update` l.34-70, the `docked` arm) ─────────
|
||
-- `GetChatWindowInfo`'s 9th return is the window's stored dock POSITION, and the
|
||
-- reference hands it straight to `FCF_DockFrame` as the insertion index. Windows 1 and 2
|
||
-- answer 1 and 2 out of the shipped chat-cache; 3..7 answer nil and stay loose. This runs
|
||
-- before the selection below because `FCF_SelectDockFrame` shows the selected member of
|
||
-- `DOCKED_CHAT_FRAMES` and hides the rest — with an empty list it would hide nothing and
|
||
-- show nothing.
|
||
for i = 1, NUM_CHAT_WINDOWS do
|
||
local _, _, _, _, _, _, _, _, docked = GetChatWindowInfo(i)
|
||
if docked then
|
||
FCF_DockFrame(getglobal("ChatFrame" .. i), docked)
|
||
end
|
||
end
|
||
BenillaFCF_SelectDock(1)
|
||
BenillaFCF_LoadWindowLooks()
|
||
]]></Script>
|
||
</Ui>
|