- the world camera's pick regression: its off-screen render target carried `scale_factor: 1.0`, so on a Retina display the camera reported physical pixels as logical and a centred cursor cast its pick ray into the upper-left quadrant — unit and GameObject picks, the occlusion ray, chat bubbles and `world_to_viewport` all broke at once; the target now carries the window's real scale factor, re-stamped when dragging between displays changes it mid-session — and the perf pill's overlap dodge had the same disease in miniature (`GetBottom()` answers in WoW UI units, the pill lays out in window px), so it now answers a fraction of the screen and works at any window height - `boothHalfRate` defaults back to half-rate: a body pane's off-screen booth pass rendered every frame reads +7.6 ms of GPU at 4K (~1.6 ms at 1600×900), on exactly the weak GPUs that felt the last snapshot — the doll's item effects keep full speed, since they key on the pacing regime rather than the camera's active bit, so only the 30 fps animation cadence returns, and full-rate stays one `SetCVar` away - a spell cast by a GameObject arrives with caster guid 0 — vmangos writes the caster slot from a `Unit*` its GameObject constructor never sets — and the whole visual body was dropped at the guid lookup; the decode now reads an empty caster slot as the packet's first guid, which with no cast item IS the caster, so the Lightwell's click plays its impact kit (sound 3155 and the head flash) instead of only the aura glow that hid the miss, and the object stops being mislabelled a cast item - the world map's black hairlines: a `SetTexCoord` crop is not a texture, and a POI icon magnified to 2.08 px per texel sampled half a texel past its crop into the neighbouring atlas cell — an opaque black row bleeding over a cell that is fully transparent (the empty cell is the reference's own zone-level substitution); `UiQuad::uv_clamp` now insets the sampling window half a texel per axis, so a crop renders as if its sub-rect were its own clamped texture, and `WOW_UI_PICK=x,y` names every quad under a pixel — it found this one in a single run Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2227 lines
129 KiB
XML
2227 lines
129 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"
|
||
for i = 1, 2 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 i = 1, 2 do
|
||
local f = getglobal("ChatFrame" .. i)
|
||
if f and (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()
|
||
for i = 1, 2 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
|
||
|
||
-- FCF_SelectDockFrame: show the selected frame, hide the other; selected tab full alpha,
|
||
-- unselected half (both still scaled by the reveal fade below). Stops the selected tab's
|
||
-- whisper flash (ref FCF_SelectDockFrame → UIFrameFlashRemoveFrame). The tab getglobal
|
||
-- stays guarded: both dock tabs exist (1575), but the guard also covers ChatFrame3..7,
|
||
-- which have no tabs at all and which the error above is what actually keeps out.
|
||
function BenillaFCF_SelectDock(id)
|
||
BenillaFCF.selected = id
|
||
for i = 1, 2 do
|
||
local frame = getglobal("ChatFrame" .. i)
|
||
if i == id then frame:Show() else frame:Hide() end
|
||
end
|
||
if BenillaFCF.flashTab == id then
|
||
BenillaFCF.flashTab = 0
|
||
local flash = getglobal("ChatFrame" .. id .. "TabFlash")
|
||
if flash then flash:Hide() end
|
||
end
|
||
end
|
||
|
||
-- FCF_SelectDockFrame(frame) — the reference's own name for the operation above, taking a
|
||
-- FRAME where ours takes a dock id. Both corpus callers pass DEFAULT_CHAT_FRAME:
|
||
--
|
||
-- if not DEFAULT_CHAT_FRAME:IsVisible() then
|
||
-- FCF_SelectDockFrame(DEFAULT_CHAT_FRAME)
|
||
-- end
|
||
-- DEFAULT_CHAT_FRAME:AddMessage(out)
|
||
--
|
||
-- verbatim in both CustomNameplates (`CustomNameplates.lua:69`) and Roid-Macros
|
||
-- (`Utility.lua:40`) — "un-hide the chat window before printing into it". Two addons,
|
||
-- two independent files, not a replicated library.
|
||
--
|
||
-- Ours does the dock half and NOT the rest of ref FCF_SelectDockFrame l.1204-1217: the ref
|
||
-- also assigns `SELECTED_DOCK_FRAME` (a global no corpus addon reads — 0 hits) and calls
|
||
-- `FCF_DockUpdate()` (tab geometry, which FCF_OnUpdate below re-derives every frame).
|
||
--
|
||
-- An UNDOCKED frame RAISES rather than quietly doing nothing. ChatFrame3..7 exist as real
|
||
-- frames but benilla has no undock/dock model at all (0288 §2 — window management is chat
|
||
-- settings, deliberately out), so "select ChatFrame5" has no meaning here; answering it
|
||
-- with silence would leave the caller's next AddMessage landing in a window that is still
|
||
-- hidden, which is the bug the guard was written to prevent.
|
||
function FCF_SelectDockFrame(frame)
|
||
if not frame then
|
||
error("FCF_SelectDockFrame: nil frame", 2)
|
||
end
|
||
local id = frame:GetID()
|
||
if id ~= 1 and id ~= 2 then
|
||
error("FCF_SelectDockFrame: " .. (frame:GetName() or "?")
|
||
.. " is not docked — benilla docks only ChatFrame1/ChatFrame2 (decision 0288 §2)", 2)
|
||
end
|
||
BenillaFCF_SelectDock(id)
|
||
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
|
||
for i = 1, 2 do
|
||
local tab = getglobal("ChatFrame" .. i .. "Tab")
|
||
if tab then
|
||
tab:SetAlpha(((i == s.selected) 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 i = 1, 2 do
|
||
local tab = getglobal("ChatFrame" .. i .. "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 dock: ChatFrame1 (General) + ChatFrame2 (Combat Log) sharing its rect ─────── -->
|
||
<!--
|
||
**One declaration, two windows.** The dock's two windows are the SAME widget seated
|
||
differently, and saying that 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 —
|
||
a part that exists for one docked window now cannot fail to exist for the other.
|
||
|
||
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 of the two 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="BenillaDockedChatFrameTemplate" 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` — the reference's own flag, and it is LOAD-BEARING now rather than an
|
||
-- advertisement. Only this template is inherited, and only ChatFrame1/ChatFrame2
|
||
-- inherit it, so every frame that reaches this line is docked: they share one rect
|
||
-- (window 2's anchors are window 1's corners) and one tab strip.
|
||
--
|
||
-- This file used to withhold the flag on the grounds that it "belongs to a dock
|
||
-- MODEL (dock/undock, FCF_DockUpdate's re-anchoring) that 0288 §2 holds out", i.e.
|
||
-- setting it would advertise verbs we do not have. What changed is that the
|
||
-- reference's move and resize gates READ it — `chatFrame.isDocked and chatFrame ~=
|
||
-- DEFAULT_CHAT_FRAME` is the clause that keeps the Combat Log from being resized
|
||
-- out of the dock it is anchored into — so withholding it now means the gate that
|
||
-- protects window 2 silently never fires. The flag is a fact about our two windows;
|
||
-- the verbs remain out.
|
||
this.isDocked = 1
|
||
</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="BenillaDockedChatFrameTemplate" 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="BenillaDockedChatFrameTemplate" 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.
|
||
|
||
No tabs (`ChatFrame3Tab`..`ChatFrame7Tab`) and no dock regions. The reference builds
|
||
both; here they would be furniture for a dock model we do not have (0288 §2), and
|
||
nothing reads them while these frames stay hidden and undocked. Outfitter.lua:3099 is
|
||
the one addon that would touch a tab — `local vTab = getglobal("ChatFrame"..i.."Tab")`
|
||
then `vTab:GetText()`, unguarded — but only INSIDE `if vChatFrame:IsVisible() or
|
||
vChatFrame.isDocked`, so a hidden, `isDocked`-less window never reaches it.
|
||
|
||
`isDocked` is NOT set on these five, and that is the whole point of the flag now: the two
|
||
dock windows set it in the shared template's `OnLoad` (it is what the reference's move and
|
||
resize gates read), and ChatFrame3..7 are the frames that genuinely are not docked. The
|
||
Outfitter hazard the flag used to carry — `if vChatFrame:IsVisible() or vChatFrame.isDocked`
|
||
then `vTab:GetText()` on a window with no tab — is why it stays off here: these have no
|
||
tabs, and they are hidden, so neither arm reaches the nil.
|
||
|
||
No background/border art: the ref's own defaults put every one of these at window alpha
|
||
0 (COLOR's fourth byte), and BenillaFCF only fades the dock's two. A shown ChatFrame5
|
||
renders its text over the world with no box — which is what the reference gives you. ── -->
|
||
<ScrollingMessageFrame name="ChatFrame3" frameStrata="BACKGROUND" displayDuration="120.0" maxLines="128" hidden="true" id="3">
|
||
<Size><AbsDimension x="430" y="128"/></Size>
|
||
<Anchors><Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="100" y="100"/></Offset></Anchor></Anchors>
|
||
<Scripts>
|
||
<OnMouseWheel>
|
||
if arg1 > 0 then this:ScrollUp() else this:ScrollDown() end
|
||
</OnMouseWheel>
|
||
<OnHyperlinkClick>
|
||
SetItemRef(arg1, arg2, arg3)
|
||
</OnHyperlinkClick>
|
||
</Scripts>
|
||
<FontString inherits="ChatFontNormal" justifyH="LEFT"/>
|
||
</ScrollingMessageFrame>
|
||
<ScrollingMessageFrame name="ChatFrame4" frameStrata="BACKGROUND" displayDuration="120.0" maxLines="128" hidden="true" id="4">
|
||
<Size><AbsDimension x="430" y="128"/></Size>
|
||
<Anchors><Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="100" y="100"/></Offset></Anchor></Anchors>
|
||
<Scripts>
|
||
<OnMouseWheel>
|
||
if arg1 > 0 then this:ScrollUp() else this:ScrollDown() end
|
||
</OnMouseWheel>
|
||
<OnHyperlinkClick>
|
||
SetItemRef(arg1, arg2, arg3)
|
||
</OnHyperlinkClick>
|
||
</Scripts>
|
||
<FontString inherits="ChatFontNormal" justifyH="LEFT"/>
|
||
</ScrollingMessageFrame>
|
||
<ScrollingMessageFrame name="ChatFrame5" frameStrata="BACKGROUND" displayDuration="120.0" maxLines="128" hidden="true" id="5">
|
||
<Size><AbsDimension x="430" y="128"/></Size>
|
||
<Anchors><Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="100" y="100"/></Offset></Anchor></Anchors>
|
||
<Scripts>
|
||
<OnMouseWheel>
|
||
if arg1 > 0 then this:ScrollUp() else this:ScrollDown() end
|
||
</OnMouseWheel>
|
||
<OnHyperlinkClick>
|
||
SetItemRef(arg1, arg2, arg3)
|
||
</OnHyperlinkClick>
|
||
</Scripts>
|
||
<FontString inherits="ChatFontNormal" justifyH="LEFT"/>
|
||
</ScrollingMessageFrame>
|
||
<ScrollingMessageFrame name="ChatFrame6" frameStrata="BACKGROUND" displayDuration="120.0" maxLines="128" hidden="true" id="6">
|
||
<Size><AbsDimension x="430" y="128"/></Size>
|
||
<Anchors><Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="100" y="100"/></Offset></Anchor></Anchors>
|
||
<Scripts>
|
||
<OnMouseWheel>
|
||
if arg1 > 0 then this:ScrollUp() else this:ScrollDown() end
|
||
</OnMouseWheel>
|
||
<OnHyperlinkClick>
|
||
SetItemRef(arg1, arg2, arg3)
|
||
</OnHyperlinkClick>
|
||
</Scripts>
|
||
<FontString inherits="ChatFontNormal" justifyH="LEFT"/>
|
||
</ScrollingMessageFrame>
|
||
<ScrollingMessageFrame name="ChatFrame7" frameStrata="BACKGROUND" displayDuration="120.0" maxLines="128" hidden="true" id="7">
|
||
<Size><AbsDimension x="430" y="128"/></Size>
|
||
<Anchors><Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="100" y="100"/></Offset></Anchor></Anchors>
|
||
<Scripts>
|
||
<OnMouseWheel>
|
||
if arg1 > 0 then this:ScrollUp() else this:ScrollDown() end
|
||
</OnMouseWheel>
|
||
<OnHyperlinkClick>
|
||
SetItemRef(arg1, arg2, arg3)
|
||
</OnHyperlinkClick>
|
||
</Scripts>
|
||
<FontString inherits="ChatFontNormal" justifyH="LEFT"/>
|
||
</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="ChatFrame1Tab" frameStrata="LOW" alpha="0" id="1">
|
||
<Size><AbsDimension x="70" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="BOTTOMLEFT" relativeTo="ChatFrame1Background" relativePoint="TOPLEFT"><Offset><AbsDimension x="2" y="0"/></Offset></Anchor>
|
||
</Anchors>
|
||
<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")
|
||
</OnLoad>
|
||
<OnClick>
|
||
BenillaFCF_TabClick(1, 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" text="GENERAL">
|
||
<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>
|
||
|
||
<!--
|
||
ChatFrame2Tab — the "Combat Log" dock tab. **This is the tab that was missing**: every
|
||
piece of machinery around it already existed and was written to expect it — `BenillaFCF`'s
|
||
fade, resize and flash loops all run `for i = 1, 2` over
|
||
`getglobal("ChatFrame"..i.."Tab")`, `selected = 1` is commented "2 = Combat Log", and
|
||
`FCF_SelectDockFrame` errors on any id that is not 1 or 2 — but the button itself was never
|
||
authored, and each of those loops has an `if tab then` guard that swallowed its absence
|
||
without a word. ChatFrame2 was therefore reachable only from Lua: it is `hidden`, as a
|
||
non-selected dock frame should be, and nothing on screen could select it.
|
||
|
||
Labels are the GlobalString KEYS (`GENERAL`, `COMBAT_LOG`), resolved out of the player's own
|
||
`GlobalStrings.lua` by the loader's ALL-CAPS key rule — the same shape the combat log's own
|
||
format strings use (1571), and the reason neither word is quoted in this file.
|
||
|
||
Anchored LEFT-to-RIGHT off tab 1; `BenillaFCF_TabResize` sizes both to their measured label
|
||
widths, so the abutment survives a locale whose words are longer.
|
||
-->
|
||
<Button name="ChatFrame2Tab" frameStrata="LOW" alpha="0" id="2">
|
||
<Size><AbsDimension x="70" y="32"/></Size>
|
||
<Anchors>
|
||
<Anchor point="LEFT" relativeTo="ChatFrame1Tab" relativePoint="RIGHT"/>
|
||
</Anchors>
|
||
<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")
|
||
</OnLoad>
|
||
<OnClick>
|
||
BenillaFCF_TabClick(2, 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" text="COMBAT_LOG">
|
||
<Anchors>
|
||
<Anchor point="LEFT" relativeTo="$parentLeft" relativePoint="RIGHT"><Offset><AbsDimension x="0" y="-5"/></Offset></Anchor>
|
||
</Anchors>
|
||
</ButtonText>
|
||
<NormalFont inherits="GameFontNormalSmall"/>
|
||
<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>
|
||
|
||
<!-- ── 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": plain arrows fall through to CHARACTER
|
||
MOVEMENT while typing (Ctrl bypasses), because 1.12 lets you keep arrow-steering with
|
||
the box open. benilla has no typing-while-moving arrow scheme, so the attr would leave
|
||
the keys dead; the director's call is the modern law — plain arrows move the caret,
|
||
Up/Down recall history (decision 0301). The engine still honors the attr faithfully
|
||
for any box that sets it. -->
|
||
<!-- 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
|
||
|
||
BenillaFCF_SelectDock(1)
|
||
BenillaFCF_LoadWindowLooks()
|
||
]]></Script>
|
||
</Ui>
|