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>
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
//! 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),
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user