a slider snaps to its step, a pushed button releases, and warnings show
- a slider with a value step snaps to it: a stepped drag fires once per row instead of once per pixel, a scrollbar cannot rest between two rows, and one wheel notch on the trainer list reaches the bottom (2133) - setting a step on a live slider moves its value onto the new lattice and runs its handler, which an addon on the real client can count on (2143) - a button pushed from script releases on the next mouse-up, so Tablet-2.0's rows stop staying pressed forever; a release off the button's rect un-presses it, and a locked state holds (2134) - the mail body and the GM ticket box scroll as you type past their bottom, and the Use UI Scale checkbox reads a registered setting (2141) - an engine warning, a call that was accepted and did nothing, reaches the /errors window in grey instead of one terminal line; a text row trimmed mid-character no longer kills the window's repaint (2135, 2138) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -30,7 +30,8 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
|
||||
m.set(
|
||||
"SetText",
|
||||
lua.create_function(|lua, (this, s): (Table, Option<String>)| {
|
||||
lua.create_function(|lua, (this, s): (Table, Option<mlua::Value>)| {
|
||||
let s = crate::script::binding_abi::text_arg(lua, s)?;
|
||||
let h = frame_handle_of(lua, &this)?;
|
||||
// Programmatic SetText KEEPS a history browse in progress: the chat live parse
|
||||
// rewrites the box on every recalled slash line ("/s hi" → Say + "hi"), and ending
|
||||
@@ -100,7 +101,8 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
// `Option<String>` coercion follows it.
|
||||
m.set(
|
||||
"Insert",
|
||||
lua.create_function(|lua, (this, s): (Table, Option<String>)| {
|
||||
lua.create_function(|lua, (this, s): (Table, Option<mlua::Value>)| {
|
||||
let s = crate::script::binding_abi::text_arg(lua, s)?;
|
||||
let h = frame_handle_of(lua, &this)?;
|
||||
if let Some(s) = s {
|
||||
insert(lua, h, &s, true);
|
||||
|
||||
@@ -1585,3 +1585,87 @@ fn set_max_bytes_caps_the_buffer_in_bytes_with_minus_one_unlimited() {
|
||||
assert!(s.run("E:SetMaxBytes()").is_err(), "too few raises");
|
||||
assert!(s.run("E:SetMaxBytes(1, 2)").is_err(), "too many raises");
|
||||
}
|
||||
|
||||
/// **`OnCursorChanged(x, y, w, h)` fires when the caret moves** — the edge the shipped
|
||||
/// `MailFrame.xml` and `HelpFrame.xml` wire `ScrollingEdit_OnCursorChanged` to, and the reason a
|
||||
/// multiline box follows its caret as you type past the bottom (decision 2141).
|
||||
///
|
||||
/// The four args are the reference's (wow-re's RF-0085 caret law, VERIFIED): `x` the caret's
|
||||
/// advance along its line, `y` **negative-downward** by row (which is what
|
||||
/// `ScrollingEdit_OnCursorChanged`'s `cursorOffset = y` then `-this.cursorOffset` reads back as a
|
||||
/// positive distance), `w` the constant `4.0`, `h` the line height.
|
||||
#[test]
|
||||
fn the_caret_flush_fires_on_cursor_changed_with_the_references_four_args() {
|
||||
let mut s = script();
|
||||
s.set_screen_size(800.0, 600.0);
|
||||
s.run(
|
||||
r#"
|
||||
fires = {}
|
||||
E = CreateFrame("EditBox", "E")
|
||||
E:SetWidth(200); E:SetHeight(64)
|
||||
E:SetPoint("BOTTOMLEFT", 100, 50)
|
||||
E:SetMultiLine(true)
|
||||
E:SetScript("OnCursorChanged", function()
|
||||
table.insert(fires, { x = arg1, y = arg2, w = arg3, h = arg4 })
|
||||
end)
|
||||
E:SetFocus()
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
s.resolve();
|
||||
// Three bytes on row 0, then a wrap onto row 1 — the host answers the rows and the pitch.
|
||||
for ch in "abcdef".chars() {
|
||||
s.char_input(&ch.to_string());
|
||||
}
|
||||
s.resolve();
|
||||
if let Some(req) = s.editbox_advances_request() {
|
||||
// A plain monotonic 7 px/byte table (the rig's own), wrapped into two rows at byte 3 —
|
||||
// `caret_row_x` subtracts the row start's cumulative advance, which is the whole point.
|
||||
let cum: Vec<f32> = (0..=req.text.len()).map(|i| i as f32 * 7.0).collect();
|
||||
s.set_editbox_advances(req.id, req.key, cum, vec![0, 3], 12.0);
|
||||
}
|
||||
s.tick(0.016);
|
||||
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
||||
|
||||
let n: i64 = s.eval("return table.getn(fires)").unwrap();
|
||||
assert!(n >= 1, "the flush fired at least once");
|
||||
let (x, y, w, h): (f64, f64, f64, f64) = s
|
||||
.eval("local f = fires[table.getn(fires)] return f.x, f.y, f.w, f.h")
|
||||
.unwrap();
|
||||
// The caret sits after 6 bytes: row 1 (rows start at 0 and 3), 3 bytes along it.
|
||||
assert_eq!(
|
||||
x, 21.0,
|
||||
"x is the advance from the ROW's start, not the text's"
|
||||
);
|
||||
assert_eq!(
|
||||
y, -12.0,
|
||||
"y is minus the row index times the pitch — downward is negative"
|
||||
);
|
||||
assert_eq!(w, 4.0, "w is the reference's constant, not a measurement");
|
||||
assert_eq!(h, 12.0, "h is the line height");
|
||||
|
||||
// **The edge is a CHANGE.** A tick that moves nothing fires nothing — which is what makes
|
||||
// `ScrollingEdit_OnUpdate`'s `if (this.cursorOffset)` loop terminate instead of re-scrolling
|
||||
// every frame.
|
||||
let before: i64 = s.eval("return table.getn(fires)").unwrap();
|
||||
s.tick(0.016);
|
||||
s.tick(0.016);
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return table.getn(fires)").unwrap(),
|
||||
before,
|
||||
"two quiet ticks fire nothing"
|
||||
);
|
||||
|
||||
// …and a caret move fires again.
|
||||
s.editbox_action(EditAction::Move {
|
||||
unit: EditUnit::Char,
|
||||
back: true,
|
||||
extend: false,
|
||||
});
|
||||
s.tick(0.016);
|
||||
assert!(
|
||||
s.eval::<i64>("return table.getn(fires)").unwrap() > before,
|
||||
"moving the caret one char fires the flush"
|
||||
);
|
||||
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user