Files
benilla-octo/crates/benilla-ui/src/script/editbox/methods.rs
T
SamandClaude Fable 5 2ad0043523 benilla: initial public release
A from-scratch World of Warcraft 1.12.1 client in Rust + Bevy.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-24 14:27:01 +07:00

222 lines
7.7 KiB
Rust

//! The EditBox Lua method surface — `install` builds the method table
//! (`REG_EDITBOX_METHODS`) the dispatcher consults before the shared frame table for EditBox
//! frames: text/cursor/selection accessors, focus, history, the config setters, and the blink
//! dial. Every method routes through the mother module's primitives (`set_text`,
//! `highlight_text`, …), so the byte-verified law lives exactly once.
use mlua::{Lua, Table, Value};
use crate::script::object::frame_handle_of;
use crate::script::Model;
use crate::widget::EditBoxState;
use super::{
clear_focus_handle, highlight_text, insert, set_focus_handle, set_text, set_text_insets,
with_eb, REG_EDITBOX_METHODS,
};
/// Run `f` over a frame's EditBox state for a Lua method call; errors if `this` is not a live EditBox.
fn with_editbox<T>(
lua: &Lua,
this: &Table,
f: impl FnOnce(&mut EditBoxState) -> T,
) -> mlua::Result<T> {
let h = frame_handle_of(lua, this)?;
with_eb(lua, h, f).ok_or_else(|| mlua::Error::runtime("not an EditBox"))
}
pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
let m = lua.create_table()?;
m.set(
"SetText",
lua.create_function(|lua, (this, s): (Table, Option<String>)| {
let h = frame_handle_of(lua, &this)?;
// Programmatic SetText ends a history browse (the engine-internal recall path calls
// `set_text` directly and keeps its browse position).
with_eb(lua, h, EditBoxState::end_history_browse);
set_text(lua, h, &s.unwrap_or_default());
Ok(())
})?,
)?;
m.set(
"GetText",
lua.create_function(|lua, this: Table| with_editbox(lua, &this, |eb| eb.text.clone()))?,
)?;
// GetNumber: atof of the real text (0 on failure), matching `0x798790`.
m.set(
"GetNumber",
lua.create_function(|lua, this: Table| {
let text = with_editbox(lua, &this, |eb| eb.text.clone())?;
Ok(text.trim().parse::<f64>().unwrap_or(0.0))
})?,
)?;
m.set(
"Insert",
lua.create_function(|lua, (this, s): (Table, String)| {
let h = frame_handle_of(lua, &this)?;
insert(lua, h, &s, true);
Ok(())
})?,
)?;
m.set(
"SetFocus",
lua.create_function(|lua, this: Table| {
let h = frame_handle_of(lua, &this)?;
set_focus_handle(lua, h);
Ok(())
})?,
)?;
m.set(
"ClearFocus",
lua.create_function(|lua, this: Table| {
let h = frame_handle_of(lua, &this)?;
clear_focus_handle(lua, h);
Ok(())
})?,
)?;
m.set(
"HasFocus",
lua.create_function(|lua, this: Table| {
let h = frame_handle_of(lua, &this)?;
let model = lua.app_data_ref::<Model>().expect("model app_data");
Ok(model.focused_editbox == Some(h))
})?,
)?;
// HighlightText([start [, end]]) — defaults (0, -1) = select-all.
m.set(
"HighlightText",
lua.create_function(
|lua, (this, start, end): (Table, Option<i64>, Option<i64>)| {
let h = frame_handle_of(lua, &this)?;
highlight_text(lua, h, start.unwrap_or(0), end.unwrap_or(-1));
Ok(())
},
)?,
)?;
m.set(
"SetMaxLetters",
lua.create_function(|lua, (this, n): (Table, i64)| {
with_editbox(lua, &this, |eb| eb.max_letters = n.max(0) as usize)
})?,
)?;
// The submitted-line history (`historyLines`): FrameXML pushes each sent line
// (ChatEdit_AddHistory), UP/DOWN recall it (see `key_input`).
m.set(
"AddHistoryLine",
lua.create_function(|lua, (this, line): (Table, Option<String>)| {
with_editbox(lua, &this, |eb| {
eb.add_history_line(line.as_deref().unwrap_or(""));
})
})?,
)?;
m.set(
"SetHistoryLines",
lua.create_function(|lua, (this, n): (Table, i64)| {
with_editbox(lua, &this, |eb| {
eb.history_max = n.max(0) as usize;
let over = eb.history.len().saturating_sub(eb.history_max);
if over > 0 {
eb.history.drain(..over);
}
})
})?,
)?;
m.set(
"GetHistoryLines",
lua.create_function(|lua, this: Table| {
with_editbox(lua, &this, |eb| eb.history_max as i64)
})?,
)?;
// SetBlinkSpeed/GetBlinkSpeed — the caret half-period (`E+0x370`, XML `blinkSpeed`; ctor 0.5).
m.set(
"SetBlinkSpeed",
lua.create_function(|lua, (this, s): (Table, f32)| {
with_editbox(lua, &this, |eb| eb.blink_period = s)
})?,
)?;
m.set(
"GetBlinkSpeed",
lua.create_function(|lua, this: Table| with_editbox(lua, &this, |eb| eb.blink_period))?,
)?;
m.set(
"SetTextInsets",
lua.create_function(
|lua,
(this, l, r, t, b): (
Table,
Option<f32>,
Option<f32>,
Option<f32>,
Option<f32>,
)| {
let h = frame_handle_of(lua, &this)?;
set_text_insets(
lua,
h,
l.unwrap_or(0.0),
r.unwrap_or(0.0),
t.unwrap_or(0.0),
b.unwrap_or(0.0),
);
Ok(())
},
)?,
)?;
m.set(
"GetTextInsets",
lua.create_function(|lua, this: Table| {
let ins = with_editbox(lua, &this, |eb| eb.text_insets)?;
Ok((ins[0], ins[1], ins[2], ins[3]))
})?,
)?;
// GetNumLetters: the CHARACTER count (not bytes) — `0x7992c0` walks the class array.
m.set(
"GetNumLetters",
lua.create_function(|lua, this: Table| {
with_editbox(lua, &this, |eb| eb.text.chars().count() as i64)
})?,
)?;
// The config flags. SetAutoFocus/SetNumeric/SetPassword/SetMultiLine mirror the live API;
// SetIgnoreArrows has no client method (the flag is XML-only) — a benilla convenience so the
// loader can drive every flag through one method surface.
for (name, set) in flag_setters() {
let refresh_justify = name == "SetMultiLine";
m.set(
name,
lua.create_function(move |lua, (this, v): (Table, Value)| {
let on = !matches!(v, Value::Nil | Value::Boolean(false));
with_editbox(lua, &this, |eb| set(eb, on))?;
if refresh_justify {
// The text-anchoring law reads multiLine (TOP vs MIDDLE), and the loader
// wires the declared `<FontString>` BEFORE the editbox flags (LoadXML order
// 5·b vs 5b) — re-seat an already-wired region so `multiLine="true"` lands.
super::refresh_text_region_justify(lua, &this)?;
}
Ok(())
})?,
)?;
}
lua.set_named_registry_value(REG_EDITBOX_METHODS, m)?;
Ok(())
}
/// A `Set<Flag>` name paired with the EditBoxState mutator it drives (Lua truthiness).
type FlagSetter = (&'static str, fn(&mut EditBoxState, bool));
/// The config-flag setters, in one place so the loader and the Lua surface share the list.
fn flag_setters() -> [FlagSetter; 5] {
[
("SetAutoFocus", |eb, on| eb.auto_focus = on),
("SetNumeric", |eb, on| eb.numeric = on),
("SetPassword", |eb, on| eb.password = on),
("SetMultiLine", |eb, on| eb.multi_line = on),
("SetIgnoreArrows", |eb, on| eb.ignore_arrows = on),
]
}