Skip to main content

siglus_scene_vm/runtime/forms/
editbox.rs

1use anyhow::Result;
2
3use super::prop_access;
4use crate::runtime::constants;
5use crate::runtime::globals::EditBoxListState;
6use crate::runtime::{CommandContext, Value};
7
8fn default_for_ret_form(ret_form: i32) -> Value {
9    if prop_access::ret_form_is_string(ret_form as i64) {
10        Value::Str(String::new())
11    } else {
12        Value::Int(0)
13    }
14}
15
16fn editbox_cnt(ctx: &CommandContext) -> usize {
17    ctx.tables
18        .gameexe
19        .as_ref()
20        .map(|cfg| cfg.indexed_count("EDITBOX"))
21        .unwrap_or(0)
22}
23
24fn is_array_code(elm_array: i32, code: i32) -> bool {
25    if elm_array < 0 {
26        return code != 0;
27    }
28    code == elm_array
29}
30
31fn is_editbox_like_chain(ctx: &CommandContext, form_id: u32, chain: &[i32]) -> bool {
32    if chain.is_empty() || chain[0] as u32 != form_id {
33        return false;
34    }
35    if chain.len() == 2 {
36        return !is_array_code(ctx.ids.elm_array, chain[1]);
37    }
38    chain.len() >= 3 && is_array_code(ctx.ids.elm_array, chain[1])
39}
40
41fn apply_exact_op(
42    form_id: u32,
43    list: &mut EditBoxListState,
44    idx: usize,
45    op: i32,
46    params: &[Value],
47    ret_form: i32,
48    screen_w: i32,
49    screen_h: i32,
50) -> (Option<Value>, Option<Option<(u32, usize)>>) {
51    if idx >= list.boxes.len() {
52        let r = if ret_form != 0 {
53            Some(default_for_ret_form(ret_form))
54        } else {
55            None
56        };
57        return (r, None);
58    }
59
60    let eb = &mut list.boxes[idx];
61    match op {
62        x if x == constants::elm_value::EDITBOX_CREATE => {
63            let x = params.get(0).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
64            let y = params.get(1).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
65            let w = params.get(2).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
66            let h = params.get(3).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
67            let moji_size = params.get(4).and_then(|v| v.as_i64()).unwrap_or(0) as i32;
68            eb.create_like(x, y, w, h, moji_size, screen_w, screen_h);
69            eb.update_rect(screen_w, screen_h);
70            eb.frame(0);
71            (None, Some(Some((form_id, idx))))
72        }
73        x if x == constants::elm_value::EDITBOX_DESTROY => {
74            eb.destroy_like();
75            (None, Some(None))
76        }
77        x if x == constants::elm_value::EDITBOX_SET_TEXT => {
78            eb.set_text_like(
79                params
80                    .iter()
81                    .find_map(|v| v.as_str())
82                    .unwrap_or("")
83                    .to_string(),
84            );
85            (None, None)
86        }
87        x if x == constants::elm_value::EDITBOX_GET_TEXT => {
88            (Some(Value::Str(eb.text.clone())), None)
89        }
90        x if x == constants::elm_value::EDITBOX_SET_FOCUS => {
91            if eb.created {
92                (None, Some(Some((form_id, idx))))
93            } else {
94                (None, None)
95            }
96        }
97        x if x == constants::elm_value::EDITBOX_CLEAR_INPUT => {
98            eb.clear_input();
99            (None, None)
100        }
101        x if x == constants::elm_value::EDITBOX_CHECK_DECIDED => {
102            (Some(Value::Int(if eb.is_decided() { 1 } else { 0 })), None)
103        }
104        x if x == constants::elm_value::EDITBOX_CHECK_CANCELED => {
105            (Some(Value::Int(if eb.is_canceled() { 1 } else { 0 })), None)
106        }
107        _ => {
108            let r = if ret_form != 0 {
109                Some(default_for_ret_form(ret_form))
110            } else {
111                None
112            };
113            (r, None)
114        }
115    }
116}
117
118pub fn dispatch(ctx: &mut CommandContext, form_id: u32, args: &[Value]) -> Result<bool> {
119    let Some((chain_pos, chain)) =
120        prop_access::parse_element_chain_ctx(ctx, form_id, args).map(|(i, ch)| (i, ch.to_vec()))
121    else {
122        return Ok(false);
123    };
124    if !is_editbox_like_chain(ctx, form_id, &chain) {
125        return Ok(false);
126    }
127
128    let (meta_al_id, _meta_ret_form, rhs_meta) =
129        prop_access::infer_assign_and_ret_ctx(ctx, chain_pos, args);
130    if chain.len() >= 4 && is_array_code(ctx.ids.elm_array, chain[1]) && meta_al_id == Some(1) {
131        let rhs = rhs_meta.as_ref();
132        let idx = chain.get(2).copied().unwrap_or(0).max(0) as usize;
133        let op = chain[3];
134        let cnt = editbox_cnt(ctx);
135        let list = ctx
136            .globals
137            .editbox_lists
138            .entry(form_id)
139            .or_insert_with(|| EditBoxListState::new(cnt));
140        list.ensure_size(cnt);
141        let params = match rhs {
142            Some(Value::Str(s)) => vec![Value::Str(s.clone())],
143            Some(v) => vec![v.clone()],
144            None => Vec::new(),
145        };
146        let (_ret, focus_req) = apply_exact_op(
147            form_id,
148            list,
149            idx,
150            op,
151            &params,
152            0,
153            ctx.screen_w as i32,
154            ctx.screen_h as i32,
155        );
156        if let Some(req) = focus_req {
157            match req {
158                Some(tgt) => ctx.globals.focused_editbox = Some(tgt),
159                None => {
160                    if ctx.globals.focused_editbox == Some((form_id, idx)) {
161                        ctx.globals.focused_editbox = None;
162                    }
163                }
164            }
165        }
166        return Ok(true);
167    }
168
169    let params = prop_access::script_args(args, chain_pos);
170    let ret_form = crate::runtime::forms::prop_access::current_vm_meta(ctx)
171        .1
172        .unwrap_or(0) as i32;
173    let elm_array = ctx.ids.elm_array;
174
175    let idx_for_focus = chain.get(2).copied().unwrap_or(0).max(0) as usize;
176    let cnt = editbox_cnt(ctx);
177    let (handled, ret, focus_req): (bool, Option<Value>, Option<Option<(u32, usize)>>) = 'blk: {
178        let list = ctx
179            .globals
180            .editbox_lists
181            .entry(form_id)
182            .or_insert_with(|| EditBoxListState::new(cnt));
183        list.ensure_size(cnt);
184
185        if chain.len() == 2 && !is_array_code(elm_array, chain[1]) {
186            let op = chain[1];
187            if op == constants::elm_value::EDITBOXLIST_CLEAR_INPUT && ret_form == 0 {
188                for eb in list.boxes.iter_mut() {
189                    eb.clear_input();
190                }
191                break 'blk (true, None, None);
192            }
193            if ret_form != 0 {
194                break 'blk (true, Some(Value::Int(list.boxes.len() as i64)), None);
195            }
196            break 'blk (true, None, None);
197        }
198
199        if chain.len() < 4 || !is_array_code(elm_array, chain[1]) {
200            let r = if ret_form != 0 {
201                Some(default_for_ret_form(ret_form))
202            } else {
203                None
204            };
205            break 'blk (true, r, None);
206        }
207
208        let idx = chain.get(2).copied().unwrap_or(0).max(0) as usize;
209        let op = chain[3];
210        let (r, fr) = apply_exact_op(
211            form_id,
212            list,
213            idx,
214            op,
215            params,
216            ret_form,
217            ctx.screen_w as i32,
218            ctx.screen_h as i32,
219        );
220        break 'blk (true, r, fr);
221    };
222
223    if let Some(v) = ret {
224        ctx.push(v);
225    }
226    if let Some(fr) = focus_req {
227        match fr {
228            Some(tgt) => ctx.globals.focused_editbox = Some(tgt),
229            None => {
230                if ctx.globals.focused_editbox == Some((form_id, idx_for_focus)) {
231                    ctx.globals.focused_editbox = None;
232                }
233            }
234        }
235    }
236    Ok(handled)
237}