1#![cfg(any(target_os = "macos", target_os = "windows", target_os = "linux"))]
7
8use std::ffi::{c_char, c_void};
9use std::path::PathBuf;
10use std::time::Duration;
11
12use winit::dpi::{LogicalPosition, LogicalSize};
13use winit::event::{ElementState, Event, Ime, KeyEvent, MouseButton, MouseScrollDelta, WindowEvent};
14use winit::application::ApplicationHandler;
15use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
16use winit::keyboard::{KeyCode, PhysicalKey};
17use winit::platform::pump_events::{EventLoopExtPumpEvents, PumpStatus};
18use winit::window::{Window, WindowAttributes, WindowId};
19
20use crate::host::{cstr_opt, cstr_required, parse_bool_exit, SiglusHost, SiglusHostConfig, SiglusNativeMessageBoxCallback};
21use crate::render::Renderer;
22use crate::runtime::game_display_info::resolve_game_name_from_project_dir;
23use crate::runtime::input::{VmKey, VmMouseButton};
24
25pub struct SiglusPumpHandle {
26 event_loop: EventLoop<()>,
27 app: PumpApp,
28}
29
30struct PumpApp {
31 config: SiglusHostConfig,
32 window: Option<&'static Window>,
33 window_id: Option<WindowId>,
34 host: Option<SiglusHost>,
35 init_error: Option<String>,
36 exit_requested: bool,
37 native_messagebox_callback: Option<SiglusNativeMessageBoxCallback>,
38 native_messagebox_user_data: *mut c_void,
39}
40
41impl PumpApp {
42 fn new(config: SiglusHostConfig) -> Self {
43 Self {
44 config,
45 window: None,
46 window_id: None,
47 host: None,
48 init_error: None,
49 exit_requested: false,
50 native_messagebox_callback: None,
51 native_messagebox_user_data: std::ptr::null_mut(),
52 }
53 }
54
55 fn ensure_created(&mut self, elwt: &ActiveEventLoop) {
56 if self.window.is_some() || self.init_error.is_some() {
57 return;
58 }
59 let width = self.config.width.unwrap_or(1280).max(1);
60 let height = self.config.height.unwrap_or(720).max(1);
61 let title = resolve_game_name_from_project_dir(&self.config.project_dir);
62 let window = match elwt.create_window(
63 WindowAttributes::default()
64 .with_title(title)
65 .with_inner_size(LogicalSize::new(width as f64, height as f64)),
66 ) {
67 Ok(w) => w,
68 Err(e) => {
69 self.init_error = Some(format!("create window: {e:?}"));
70 elwt.exit();
71 return;
72 }
73 };
74 let window: &'static Window = Box::leak(Box::new(window));
75 let renderer = match pollster::block_on(Renderer::new(window)) {
76 Ok(r) => r,
77 Err(e) => {
78 self.init_error = Some(format!("renderer init: {e:?}"));
79 elwt.exit();
80 return;
81 }
82 };
83 let mut host = match pollster::block_on(SiglusHost::new_with_renderer(self.config.clone(), renderer)) {
84 Ok(h) => h,
85 Err(e) => {
86 self.init_error = Some(format!("host init: {e:?}"));
87 elwt.exit();
88 return;
89 }
90 };
91 host.set_native_messagebox_callback(self.native_messagebox_callback, self.native_messagebox_user_data);
92 self.window_id = Some(window.id());
93 self.window = Some(window);
94 self.host = Some(host);
95 window.request_redraw();
96 }
97
98 fn handle_event(&mut self, event: Event<()>, elwt: &ActiveEventLoop) {
99 match event {
100 Event::Resumed => self.ensure_created(elwt),
101 Event::WindowEvent { window_id, event } if self.window_id == Some(window_id) => {
102 self.handle_window_event(event, elwt);
103 }
104 Event::AboutToWait => {
105 if self.exit_requested {
106 elwt.exit();
107 return;
108 }
109 if let Some(w) = self.window.as_ref() {
110 w.request_redraw();
111 }
112 }
113 _ => {}
114 }
115 }
116
117 fn handle_window_event(&mut self, event: WindowEvent, elwt: &ActiveEventLoop) {
118 let Some(host) = self.host.as_mut() else {
119 return;
120 };
121 match event {
122 WindowEvent::CloseRequested => {
123 self.exit_requested = true;
124 elwt.exit();
125 }
126 WindowEvent::Resized(size) => {
127 let sf = self.window.as_ref().map(|w| w.scale_factor() as f32).unwrap_or(1.0);
128 host.resize(size.width.max(1), size.height.max(1), sf);
129 }
130 WindowEvent::KeyboardInput {
131 event: KeyEvent { state: ElementState::Pressed, physical_key: PhysicalKey::Code(code), .. },
132 ..
133 } => {
134 if let Some(k) = map_keycode(code) {
135 host.key_down(k);
136 }
137 }
138 WindowEvent::KeyboardInput {
139 event: KeyEvent { state: ElementState::Released, physical_key: PhysicalKey::Code(code), .. },
140 ..
141 } => {
142 if let Some(k) = map_keycode(code) {
143 host.key_up(k);
144 }
145 }
146 WindowEvent::Ime(Ime::Commit(text)) => host.text_input(&text),
147 WindowEvent::CursorMoved { position, .. } => {
148 let (x, y) = if let Some(w) = self.window.as_ref() {
149 let p = position.to_logical::<f64>(w.scale_factor());
150 (p.x, p.y)
151 } else {
152 (position.x, position.y)
153 };
154 host.mouse_move(x, y);
155 }
156 WindowEvent::MouseInput { state, button, .. } => {
157 if let Some(b) = map_mouse_button(button) {
158 match state {
159 ElementState::Pressed => {
160 if matches!(b, VmMouseButton::Left) {
161 let (x, y) = current_mouse_pos(host);
162 host.touch(0, x, y);
163 } else {
164 host.mouse_down(b);
165 }
166 }
167 ElementState::Released => {
168 if matches!(b, VmMouseButton::Left) {
169 let (x, y) = current_mouse_pos(host);
170 host.touch(2, x, y);
171 } else {
172 host.mouse_up(b);
173 }
174 }
175 }
176 }
177 }
178 WindowEvent::MouseWheel { delta, .. } => {
179 let dy = match delta {
180 MouseScrollDelta::LineDelta(_, y) => (y * 120.0) as i32,
181 MouseScrollDelta::PixelDelta(p) => p.y.round() as i32,
182 };
183 host.mouse_wheel(dy);
184 }
185 WindowEvent::RedrawRequested => {
186 if let Some(window) = self.window.as_ref() {
187 apply_ime_window_state(window, host);
188 }
189 let status = parse_bool_exit(host.step(16), "siglus_pump_step/redraw");
190 if status != 0 {
191 self.exit_requested = true;
192 elwt.exit();
193 }
194 }
195 _ => {}
196 }
197 }
198}
199
200
201impl ApplicationHandler for PumpApp {
202 fn resumed(&mut self, elwt: &ActiveEventLoop) {
203 self.ensure_created(elwt);
204 }
205
206 fn window_event(&mut self, elwt: &ActiveEventLoop, window_id: WindowId, event: WindowEvent) {
207 if self.window_id == Some(window_id) {
208 self.handle_window_event(event, elwt);
209 }
210 }
211
212 fn about_to_wait(&mut self, elwt: &ActiveEventLoop) {
213 if self.exit_requested {
214 elwt.exit();
215 return;
216 }
217 if let Some(w) = self.window.as_ref() {
218 w.request_redraw();
219 }
220 elwt.set_control_flow(ControlFlow::WaitUntil(std::time::Instant::now() + Duration::from_millis(16)));
221 }
222}
223
224fn apply_ime_window_state(window: &Window, host: &mut SiglusHost) {
225 if let Some((x, y, width, height)) = host.vm_mut().ctx.focused_editbox_ime_area() {
226 window.set_ime_allowed(true);
227 window.set_ime_cursor_area(
228 LogicalPosition::new(x.max(0) as f64, y.max(0) as f64),
229 LogicalSize::new(width.max(1) as f64, height.max(1) as f64),
230 );
231 } else {
232 window.set_ime_allowed(false);
233 }
234}
235
236fn current_mouse_pos(host: &mut SiglusHost) -> (f64, f64) {
237 let input = &host.vm_mut().ctx.input;
238 (input.mouse_x as f64, input.mouse_y as f64)
239}
240
241fn map_mouse_button(b: MouseButton) -> Option<VmMouseButton> {
242 match b {
243 MouseButton::Left => Some(VmMouseButton::Left),
244 MouseButton::Right => Some(VmMouseButton::Right),
245 MouseButton::Middle => Some(VmMouseButton::Middle),
246 _ => None,
247 }
248}
249
250fn map_keycode(k: KeyCode) -> Option<VmKey> {
251 use KeyCode::*;
252 match k {
253 Escape => Some(VmKey::Escape),
254 Enter => Some(VmKey::Enter),
255 Space => Some(VmKey::Space),
256 Backspace => Some(VmKey::Backspace),
257 Tab => Some(VmKey::Tab),
258 ShiftLeft | ShiftRight => Some(VmKey::Shift),
259 AltLeft | AltRight => Some(VmKey::Alt),
260 ArrowLeft => Some(VmKey::ArrowLeft),
261 ArrowUp => Some(VmKey::ArrowUp),
262 ArrowRight => Some(VmKey::ArrowRight),
263 ArrowDown => Some(VmKey::ArrowDown),
264 KeyA => Some(VmKey::Letter('A')),
265 KeyB => Some(VmKey::Letter('B')),
266 KeyC => Some(VmKey::Letter('C')),
267 KeyD => Some(VmKey::Letter('D')),
268 KeyE => Some(VmKey::Letter('E')),
269 KeyF => Some(VmKey::Letter('F')),
270 KeyG => Some(VmKey::Letter('G')),
271 KeyH => Some(VmKey::Letter('H')),
272 KeyI => Some(VmKey::Letter('I')),
273 KeyJ => Some(VmKey::Letter('J')),
274 KeyK => Some(VmKey::Letter('K')),
275 KeyL => Some(VmKey::Letter('L')),
276 KeyM => Some(VmKey::Letter('M')),
277 KeyN => Some(VmKey::Letter('N')),
278 KeyO => Some(VmKey::Letter('O')),
279 KeyP => Some(VmKey::Letter('P')),
280 KeyQ => Some(VmKey::Letter('Q')),
281 KeyR => Some(VmKey::Letter('R')),
282 KeyS => Some(VmKey::Letter('S')),
283 KeyT => Some(VmKey::Letter('T')),
284 KeyU => Some(VmKey::Letter('U')),
285 KeyV => Some(VmKey::Letter('V')),
286 KeyW => Some(VmKey::Letter('W')),
287 KeyX => Some(VmKey::Letter('X')),
288 KeyY => Some(VmKey::Letter('Y')),
289 KeyZ => Some(VmKey::Letter('Z')),
290 Digit0 => Some(VmKey::Digit(0)),
291 Digit1 => Some(VmKey::Digit(1)),
292 Digit2 => Some(VmKey::Digit(2)),
293 Digit3 => Some(VmKey::Digit(3)),
294 Digit4 => Some(VmKey::Digit(4)),
295 Digit5 => Some(VmKey::Digit(5)),
296 Digit6 => Some(VmKey::Digit(6)),
297 Digit7 => Some(VmKey::Digit(7)),
298 Digit8 => Some(VmKey::Digit(8)),
299 Digit9 => Some(VmKey::Digit(9)),
300 F1 => Some(VmKey::F(1)),
301 F2 => Some(VmKey::F(2)),
302 F3 => Some(VmKey::F(3)),
303 F4 => Some(VmKey::F(4)),
304 F5 => Some(VmKey::F(5)),
305 F6 => Some(VmKey::F(6)),
306 F7 => Some(VmKey::F(7)),
307 F8 => Some(VmKey::F(8)),
308 F9 => Some(VmKey::F(9)),
309 F10 => Some(VmKey::F(10)),
310 F11 => Some(VmKey::F(11)),
311 F12 => Some(VmKey::F(12)),
312 _ => None,
313 }
314}
315
316#[no_mangle]
317pub unsafe extern "C" fn siglus_pump_create(
318 game_root_utf8: *const c_char,
319) -> *mut SiglusPumpHandle {
320 let game_root = match cstr_required(game_root_utf8, "game_root_utf8") {
321 Ok(s) => s,
322 Err(e) => {
323 log::error!("siglus_pump_create: {e:?}");
324 return std::ptr::null_mut();
325 }
326 };
327 let config = SiglusHostConfig::new(PathBuf::from(game_root));
328 let event_loop = match EventLoop::new() {
329 Ok(el) => el,
330 Err(e) => {
331 log::error!("siglus_pump_create: EventLoop::new: {e:?}");
332 return std::ptr::null_mut();
333 }
334 };
335 let mut handle = Box::new(SiglusPumpHandle { event_loop, app: PumpApp::new(config) });
336 {
337 let event_loop = &mut handle.event_loop;
338 let app = &mut handle.app;
339 let _ = event_loop.pump_events(Some(Duration::from_millis(0)), |event, elwt| {
340 app.handle_event(event, elwt);
341 });
342 }
343 Box::into_raw(handle)
344}
345
346#[no_mangle]
347pub unsafe extern "C" fn siglus_pump_set_native_messagebox_callback(
348 handle: *mut SiglusPumpHandle,
349 callback: Option<SiglusNativeMessageBoxCallback>,
350 user_data: *mut c_void,
351) {
352 if handle.is_null() {
353 return;
354 }
355 let h = &mut *handle;
356 h.app.native_messagebox_callback = callback;
357 h.app.native_messagebox_user_data = user_data;
358 if let Some(host) = h.app.host.as_mut() {
359 host.set_native_messagebox_callback(callback, user_data);
360 }
361}
362
363#[no_mangle]
364pub unsafe extern "C" fn siglus_pump_submit_messagebox_result(
365 handle: *mut SiglusPumpHandle,
366 request_id: u64,
367 value: i64,
368) {
369 if handle.is_null() {
370 return;
371 }
372 let h = &mut *handle;
373 if let Some(host) = h.app.host.as_mut() {
374 host.submit_native_messagebox_result(request_id, value);
375 }
376}
377
378#[no_mangle]
379pub unsafe extern "C" fn siglus_pump_text_input(handle: *mut SiglusPumpHandle, text_utf8: *const c_char) {
380 let Some(handle) = handle.as_mut() else {
381 return;
382 };
383 let Some(host) = handle.app.host.as_mut() else {
384 return;
385 };
386 if let Some(text) = cstr_opt(text_utf8) {
387 host.text_input(&text);
388 }
389}
390
391#[no_mangle]
392pub unsafe extern "C" fn siglus_pump_key_down(handle: *mut SiglusPumpHandle, key_code: i32) {
393 let Some(handle) = handle.as_mut() else {
394 return;
395 };
396 let Some(host) = handle.app.host.as_mut() else {
397 return;
398 };
399 host.key_down_code(key_code);
400}
401
402#[no_mangle]
403pub unsafe extern "C" fn siglus_pump_key_up(handle: *mut SiglusPumpHandle, key_code: i32) {
404 let Some(handle) = handle.as_mut() else {
405 return;
406 };
407 let Some(host) = handle.app.host.as_mut() else {
408 return;
409 };
410 host.key_up_code(key_code);
411}
412
413#[no_mangle]
414pub unsafe extern "C" fn siglus_pump_step(handle: *mut SiglusPumpHandle, timeout_ms: u32) -> i32 {
415 if handle.is_null() {
416 return 2;
417 }
418 let h = &mut *handle;
419 if let Some(w) = h.app.window.as_ref() {
420 w.request_redraw();
421 }
422 let status = {
423 let event_loop = &mut h.event_loop;
424 let app = &mut h.app;
425 event_loop.pump_events(Some(Duration::from_millis(timeout_ms.max(1) as u64)), |event, elwt| {
426 app.handle_event(event, elwt);
427 })
428 };
429 match status {
430 PumpStatus::Continue => 0,
431 _ => 1,
432 }
433}
434
435#[no_mangle]
436pub unsafe extern "C" fn siglus_pump_destroy(handle: *mut SiglusPumpHandle) {
437 if handle.is_null() {
438 return;
439 }
440 drop(Box::from_raw(handle));
441}
442
443#[no_mangle]
444pub unsafe extern "C" fn siglus_run_entry(game_root_utf8: *const c_char) -> i32 {
445 let game_root = match cstr_required(game_root_utf8, "game_root_utf8") {
446 Ok(s) => s,
447 Err(e) => {
448 log::error!("siglus_run_entry: {e:?}");
449 return 1;
450 }
451 };
452 let event_loop = match EventLoop::new() {
453 Ok(el) => el,
454 Err(e) => {
455 log::error!("siglus_run_entry: EventLoop::new: {e:?}");
456 return 1;
457 }
458 };
459 event_loop.set_control_flow(ControlFlow::Poll);
460 let config = SiglusHostConfig::new(PathBuf::from(game_root));
461 let mut app = PumpApp::new(config);
462 match event_loop.run_app(&mut app) {
463 Ok(()) => {
464 if let Some(e) = app.init_error {
465 log::error!("siglus_run_entry: {e}");
466 1
467 } else {
468 0
469 }
470 }
471 Err(e) => {
472 log::error!("siglus_run_entry: run_app: {e:?}");
473 1
474 }
475 }
476}