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