Comments in benilla-ui: one line of fact each, no history, no record numbers (#143)
Part of #41: the comments of `benilla-ui` (the Lua UI engine, its FrameXML loader and its widgets) are rewritten to the rule in `docs/METHOD.md`, which is what the code does and the reference fact behind it, in a line. 43,140 comment lines become 15,161. - **The mechanical strip.** The citations that were only a private record number go first, and each edit is shown to be a pure removal. - **Wrong facts corrected.** The rewrite corrects the comments that stated wrong facts. Among them: - `0x64a480` is the case-sensitive compare (a byte `repz cmpsb`), and `0x64a4c0` the case-insensitive one. - A frame's id sits at `+0xb0`, not `+0xb4`. `EnableKeyboard`/`IsKeyboardEnabled` are `0x776ec0`/`0x776f90`. - A region's unpinned edge has no fallback to its owner's rect: the owner supplies only the scale. - `$parent` expands to the nearest named ancestor, or "Top" when none is named (`0x76c5b0`), not simply to the parent. - A StatusBar fills from its internal normalised fraction, not from `GetValue()`. - The addresses of `StartDuel` and `StartDuelUnit` were swapped. - Many "untraced" notes now give the traced reference fact: the drag threshold, the per-grip sizing edges, the empty-`autoFocus` skip, and `SetTexCoordModifiesRect`'s default. - Stock FrameXML line citations are checked against the stock 1.12 files and corrected. `ref-` file names become the stock file names. - **Deviation labels.** "Deviation:" now marks only a choice with a reason. A surface that exists only for the retired Era addon target says so plainly: it is not 1.12. - **Verification.** Every file strips to identical code, and `cargo fmt --check` is clean. Nine second readers compared every file old against new, restored the facts the rewrite had dropped, and checked each new citation against the code, the stock files, vmangos and the reference. Behaviour differences the rewrite surfaced are filed as their own issues (#131–#142), and more are being validated. The comments here state what the code does. String literals are untouched; they are #92. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
8505187668
commit
fd386e75ae
@@ -1,54 +1,11 @@
|
||||
//! The **layout cache** — the consumer `Frame::user_placed` never had.
|
||||
//! The layout cache: the reference writes a per-character `layout-cache.txt` at logout and seats
|
||||
//! the user-placed frames from it at load (userPlaced is `frame+0xb4 & 0x1000`). This is the
|
||||
//! engine half, a snapshot out, a restore in and a dirty bit; the app owns the file
|
||||
//! (`benilla_app::ui_layout`).
|
||||
//!
|
||||
//! `SetUserPlaced` / the drag entries set the client's userPlaced bit (`frame+0xb4 & 0x1000`,
|
||||
//! [`super::object::movable`]), and together with the frame's own `movable`/`resizable` flags it
|
||||
//! means *"the player put this window here; put it back next time"*. The real client honours that
|
||||
//! by writing a per-character `layout-cache.txt` at logout and seating the frames from it at load;
|
||||
//! benilla had the bit and nothing that read it, so a dragged or resized window went back to its
|
||||
//! authored anchors on the next login.
|
||||
//!
|
||||
//! **The bit is not the whole gate**, at either end — see [`super::UiScript::user_placed_layouts`]
|
||||
//! and [`super::UiScript::restore_user_placed_layouts`] for the conjuncts and their byte sites.
|
||||
//! Reading it as the whole gate is what let one addon's stamp seat a stock frame forever
|
||||
//! (decision 2193).
|
||||
//!
|
||||
//! This module is the **engine half** of that: a snapshot out, a restore in, and a dirty bit for
|
||||
//! the host to debounce on. Where the file lives, when it is written and which character owns it
|
||||
//! are the app's ([`benilla_app::ui_layout`] — `benilla-config/layout/<realm>-<character>.txt`,
|
||||
//! through `local_state`, like every other resident of that folder).
|
||||
//!
|
||||
//! ## Why a host seam at all, when `GetPoint`/`SetPoint` exist
|
||||
//!
|
||||
//! Everything *about one frame* is already Lua-reachable — `GetNumPoints`, `GetPoint`, `GetWidth`,
|
||||
//! `GetHeight`, `IsUserPlaced`, `SetPoint`, `SetWidth`, `SetHeight` — and if the saver knew which
|
||||
//! frames to ask about, it would need nothing here. **Enumeration is the part Lua cannot do**:
|
||||
//! there is no `EnumerateFrames` in 1.12 and inventing one would be a far wider surface than the
|
||||
//! three functions below. The alternative — a hand-maintained list of names in FrameXML — is a
|
||||
//! list that goes stale the first time a window becomes movable and nobody remembers to add it,
|
||||
//! which is the failure mode the *bit* exists to prevent.
|
||||
//!
|
||||
//! So the seam is deliberately three calls wide and mirrors the chat-look seam next door
|
||||
//! (`chat_window_looks` / `set_chat_window_looks` / `take_chat_window_changes`, decision 1589):
|
||||
//! [`super::UiScript::user_placed_layouts`], [`super::UiScript::restore_user_placed_layouts`] and
|
||||
//! [`super::UiScript::take_user_placed_change`].
|
||||
//!
|
||||
//! ## What a saved frame carries
|
||||
//!
|
||||
//! Its **name**, its **width/height**, and its **whole anchor list** — point, target, relative
|
||||
//! point, offsets — because that is what `GetPoint` answers and what `SetPoint` takes. A frame is
|
||||
//! addressed by name in the file for the obvious reason: handles are minted per session and mean
|
||||
//! nothing across a relog.
|
||||
//!
|
||||
//! Two anchor targets survive the round trip and everything else drops the frame's row rather than
|
||||
//! guessing:
|
||||
//!
|
||||
//! - the **screen root** ([`super::SCREEN`]) — written as no target, which is exactly what
|
||||
//! `GetPoint` answers there (`nil`, decision 0068's stated `UIParent` divergence);
|
||||
//! - a **named frame** — written by name.
|
||||
//!
|
||||
//! An anchor onto an unnamed frame or onto a *region* cannot be re-addressed at load, so a frame
|
||||
//! carrying one is skipped **whole**. Restoring half an anchor set would seat a window somewhere
|
||||
//! nobody put it, which is worse than leaving it on its authored anchors.
|
||||
//! A saved frame carries its name, size and whole anchor list. An anchor's target is the screen
|
||||
//! root (saved as no target, the `nil` `GetPoint` answers there) or a named frame; a frame anchored
|
||||
//! to anything else is skipped whole, never half-restored.
|
||||
|
||||
use crate::layout::Anchor;
|
||||
use crate::widget::FrameHandle;
|
||||
@@ -61,24 +18,22 @@ use super::{Model, SCREEN};
|
||||
pub struct LayoutPoint {
|
||||
/// The point on the saved frame (`"BOTTOMLEFT"`, …).
|
||||
pub point: String,
|
||||
/// The frame this anchor is relative to, by name; `None` is the screen root, which is what
|
||||
/// `GetPoint` reports as `nil`.
|
||||
/// The target frame's name; `None` is the screen root, which `GetPoint` reports as `nil`.
|
||||
pub relative_to: Option<String>,
|
||||
/// The point on the target.
|
||||
pub relative_point: String,
|
||||
/// The anchor's x offset, in the frame's own (pre-scale) units — `CAnchor+0x4`.
|
||||
/// `CAnchor+0x4`, in the frame's own pre-scale units.
|
||||
pub x: f32,
|
||||
/// The anchor's y offset — `CAnchor+0x8`.
|
||||
/// `CAnchor+0x8`.
|
||||
pub y: f32,
|
||||
}
|
||||
|
||||
/// One user-placed frame's persisted geometry: what a layout cache has to hold to put a window
|
||||
/// back exactly where the player left it.
|
||||
/// One user-placed frame's persisted geometry.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct FrameLayout {
|
||||
/// The frame's global name — how the row is addressed across sessions.
|
||||
/// The frame's global name, which addresses the row across sessions.
|
||||
pub name: String,
|
||||
/// The frame's authored width (`LayoutInput::width`), which is what a resize drag writes.
|
||||
/// The frame's authored width (`LayoutInput::width`), which a resize drag writes.
|
||||
pub width: f32,
|
||||
/// The frame's authored height.
|
||||
pub height: f32,
|
||||
@@ -87,24 +42,11 @@ pub struct FrameLayout {
|
||||
}
|
||||
|
||||
impl super::UiScript {
|
||||
/// Snapshot every frame the reference's writer would write — the save path.
|
||||
///
|
||||
/// Sorted by name so the file is stable: a rewrite that reorders its own lines makes every
|
||||
/// diff useless and every "did this change?" check a false positive. Frames whose anchors
|
||||
/// cannot be re-addressed by name are dropped (see the module doc), as are unnamed ones —
|
||||
/// there is nothing to write them under.
|
||||
///
|
||||
/// **The filter is `userPlaced AND (movable OR resizable)`, not the bit alone.** The
|
||||
/// reference's per-frame writer `0x490e60` tests four conjuncts in a row: a non-NULL name
|
||||
/// (`0x490e79`), a non-empty one (`0x490e82`), `userPlaced` (`0x490e8e test ah,0x10` =
|
||||
/// `0x1000`) and **`movable|resizable`** (`0x490e97 test ah,0x3` = `0x100|0x200`). The bit
|
||||
/// alone was benilla's whole filter until decision 2193, and the missing fourth conjunct is
|
||||
/// what made this cache a one-way ratchet: **every** drag entry stamps `userPlaced`
|
||||
/// (`0x7652b0` @`0x7652e5`, with no cursor-movement dependency), so an addon that windows a
|
||||
/// stock frame and stamps it in passing — `Cartographer/Modules/LookNFeel.lua`'s
|
||||
/// `StartMoving(); StopMovingOrSizing()` over `WorldMapFrame` — wrote a row that outlived
|
||||
/// the addon on every character, forever. The reference drops that row at the first logout
|
||||
/// the addon does not load, because the frame's own `movable`/`resizable` flags went with it.
|
||||
/// Snapshot, sorted by name so the file is stable, every frame the reference's writer
|
||||
/// (`0x490e60`) writes: a non-empty name (`0x490e79`, `0x490e82`), `userPlaced` (`0x490e8e`,
|
||||
/// `0x1000`) and `movable` or `resizable` (`0x490e97`, `0x100|0x200`), with every anchor
|
||||
/// nameable. Every drag stamps `userPlaced` (`0x7652b0` at `0x7652e5`), so the last test is
|
||||
/// what drops the row of a stock frame an addon once moved, once that addon is gone.
|
||||
pub fn user_placed_layouts(&self) -> Vec<FrameLayout> {
|
||||
let model = self.model_ref();
|
||||
let mut out: Vec<FrameLayout> = model
|
||||
@@ -131,40 +73,22 @@ impl super::UiScript {
|
||||
out
|
||||
}
|
||||
|
||||
/// Seat saved geometry back onto the frames that still exist — the load path.
|
||||
///
|
||||
/// Each row is applied whole or not at all: a name this build has no frame for, a point name
|
||||
/// that does not parse, or a target frame that is not loaded leaves the frame on its authored
|
||||
/// anchors rather than half-seated. Restoring **sets the userPlaced bit**, which is what keeps
|
||||
/// the position: `UIParent_ManageFramePositions` skips a user-placed frame, so the managed
|
||||
/// bottom-stack pass does not re-seat the window the player moved.
|
||||
///
|
||||
/// **The apply is gated per ARM, not per row** (`0x4905e0`, decision 2193): the position arm
|
||||
/// is behind the frame's own `movable` bit (`0x490600 test ah,0x1`) and the size arm behind
|
||||
/// `resizable` (`0x490689 test ah,0x2`) — a frame carrying neither is left entirely alone,
|
||||
/// however old and however stamped its row is. The userPlaced stamp itself lives **inside**
|
||||
/// each arm (`0x49067e` in the position arm, `0x490706` in the size one), after that arm's
|
||||
/// own "the row carries no position" bail
|
||||
/// (`0x490613`, the reference's `X == 0 && Y == 0`; the size arm's twin is `0x49069a`'s
|
||||
/// `W == 0 && H == 0`). Both bails are spelled in this payload's own terms rather than the
|
||||
/// reference's integer sentinels: a row that carries no anchors, and a row that carries no
|
||||
/// size, are the same two states in a format that stores an anchor list instead of an X/Y
|
||||
/// pair.
|
||||
///
|
||||
/// It marks nothing dirty — the values came *from* the file, and an echo would re-dirty what
|
||||
/// was just read (`set_chat_window_looks`' reasoning, one store over).
|
||||
/// Seat saved geometry back onto the frames that exist, each row whole or not at all. The
|
||||
/// apply is per arm (`0x4905e0`): the position needs `movable` (`0x490600`) and anchors (the
|
||||
/// no-position bail, `0x490613`), the size needs `resizable` (`0x490689`) and a non-zero size
|
||||
/// (`0x49069a`), and each arm that applies stamps `userPlaced` (`0x49067e`, `0x490706`), which
|
||||
/// keeps `UIParent_ManageFramePositions` off the frame. Marks nothing dirty: the values came
|
||||
/// from the file.
|
||||
pub fn restore_user_placed_layouts(&mut self, layouts: impl IntoIterator<Item = FrameLayout>) {
|
||||
let mut model = self.model_mut();
|
||||
for l in layouts {
|
||||
let Some(h) = model.arena.lookup(&l.name) else {
|
||||
continue; // a window this build does not have — the file outlives a rename
|
||||
continue; // a window this build does not have
|
||||
};
|
||||
let mut anchors: Vec<Anchor> = Vec::with_capacity(l.points.len());
|
||||
let mut usable = true;
|
||||
for p in &l.points {
|
||||
// `frame_id`, not a `frame_to_id` read: ids are minted lazily, so a target frame
|
||||
// that nothing has anchored to yet has none — and refusing the row for that would
|
||||
// make the restore depend on load order.
|
||||
// `frame_id` mints the id lazily, so a target nothing has anchored to yet resolves.
|
||||
let rel = match &p.relative_to {
|
||||
None => Some(SCREEN),
|
||||
Some(n) => model.arena.lookup(n).map(|t| model.frame_id(t)),
|
||||
@@ -210,32 +134,26 @@ impl super::UiScript {
|
||||
input.width = l.width;
|
||||
input.height = l.height;
|
||||
}
|
||||
// The stamp is EACH arm's, not the row's: the position arm's is `0x49067e` and the
|
||||
// size arm's `0x490706`, both inside their own gate. A frame that took neither is
|
||||
// left un-stamped, which is what lets its row fall out of the file.
|
||||
// Only an arm that applied stamps, so a row neither arm took falls out of the file.
|
||||
if seat_position || seat_size {
|
||||
if let Some(f) = model.arena.frame_mut(h) {
|
||||
f.user_placed = true;
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
// The whole graph, not the frame alone: a restore REPOINTS anchors (a saved row
|
||||
// may name a different target than the authored one), and decision 1388's
|
||||
// frame-scoped touch is only valid while every anchor keeps its target.
|
||||
// The whole graph, not the frame: a restored row may repoint an anchor.
|
||||
model.touch_layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Has a user-placed frame moved or resized since the last call? The host's cue to persist —
|
||||
/// a single bit rather than a set, because the file is written whole either way.
|
||||
/// Whether a user-placed frame moved or resized since the last call: the host's cue to persist.
|
||||
pub fn take_user_placed_change(&mut self) -> bool {
|
||||
std::mem::take(&mut self.model_mut().user_placed_changed)
|
||||
}
|
||||
}
|
||||
|
||||
/// One live anchor → its saved form, or `None` when the target cannot be named across a session
|
||||
/// (an unnamed frame, or a region — see the module doc).
|
||||
/// An anchor's saved form, or `None` when its target is an unnamed frame or a region.
|
||||
fn saved_point(model: &Model, a: &Anchor) -> Option<LayoutPoint> {
|
||||
let relative_to = if a.relative_to == SCREEN {
|
||||
None
|
||||
|
||||
Reference in New Issue
Block a user