Skip to main content

eldiron_creator/
lib.rs

1#[cfg(not(target_arch = "wasm32"))]
2#[macro_use]
3mod macros;
4
5pub mod actionlist;
6pub mod actions;
7pub mod configeditor;
8pub mod dockmanager;
9pub mod docks;
10pub mod editcamera;
11pub mod editor;
12pub mod editor_tools;
13pub mod hud;
14#[cfg(not(target_arch = "wasm32"))]
15pub mod i18n;
16pub mod mapeditor;
17pub mod minimap;
18pub mod misc;
19#[cfg(all(not(target_arch = "wasm32"), feature = "self-update"))]
20pub mod self_update;
21pub mod sidebar;
22pub mod textplay;
23pub mod toollist;
24pub mod tools;
25pub mod treasury;
26pub mod undo;
27pub mod utils;
28
29use rust_embed::RustEmbed;
30#[derive(RustEmbed)]
31#[folder = "embedded/"]
32#[exclude = "*.DS_Store"]
33pub struct Embedded;
34
35pub const DEFAULT_VLAYOUT_RATIO: f32 = 0.62;
36
37#[allow(ambiguous_glob_reexports)]
38pub mod prelude {
39    pub use ::serde::{Deserialize, Serialize};
40
41    pub use codegridfx::prelude::*;
42    pub use shared::prelude::*;
43    pub use std::sync::{LazyLock, RwLock};
44    pub use theframework::prelude::*;
45
46    pub use crate::mapeditor::*;
47    pub use crate::misc::*;
48    // pub use crate::previewview::*;
49    pub use crate::actionlist::*;
50    pub use crate::sidebar::*;
51    pub use crate::textplay::*;
52    pub use crate::toollist::*;
53    pub use crate::treasury::*;
54    pub use crate::undo::project_atoms::*;
55    pub use crate::undo::project_helper::*;
56    pub use crate::undo::project_undo::*;
57    pub use crate::undo::*;
58    pub use crate::utils::*;
59
60    pub use crate::tools::game::GameTool;
61    pub use crate::tools::linedef::LinedefTool;
62    pub use crate::tools::sector::SectorTool;
63    // pub use crate::tools::tileset::TilesetTool;
64    pub use crate::tools::vertex::VertexTool;
65
66    pub use crate::docks::tiles::*;
67
68    pub use crate::actions::*;
69    pub use crate::docks::*;
70    pub use crate::editor_tools::*;
71    pub use crate::tools::*;
72
73    pub use crate::configeditor::ConfigEditor;
74    pub use crate::editcamera::{CustomMoveAction, EditCamera};
75
76    pub use crate::dockmanager::{DockManager, DockManagerState};
77
78    pub use toml::Table;
79
80    pub const KEY_ESCAPE: u32 = 0;
81    pub const KEY_RETURN: u32 = 1;
82    pub const KEY_DELETE: u32 = 2;
83    pub const KEY_HOME: u32 = 3;
84    pub const KEY_END: u32 = 4;
85    pub const KEY_UP: u32 = 5;
86    pub const KEY_RIGHT: u32 = 6;
87    pub const KEY_DOWN: u32 = 7;
88    pub const KEY_LEFT: u32 = 8;
89    pub const KEY_SPACE: u32 = 9;
90    pub const KEY_TAB: u32 = 10;
91}
92
93// --- FFI exports for the Xcode static library build ---
94// Build with: cargo build -p eldiron-creator --lib --release --no-default-features --features staticlib
95
96#[cfg(feature = "staticlib")]
97mod ffi {
98    use super::editor::Editor;
99    use super::prelude::*;
100
101    use std::ffi::{CStr, CString};
102    use std::os::raw::c_char;
103    use std::ptr;
104
105    use lazy_static::lazy_static;
106    use std::sync::Mutex;
107
108    lazy_static! {
109        static ref APP: Mutex<Editor> = Mutex::new(Editor::new());
110        static ref CTX: Mutex<TheContext> = Mutex::new(TheContext::new(800, 600, 1.0));
111        static ref UI: Mutex<TheUI> = Mutex::new(TheUI::new());
112    }
113
114    #[unsafe(no_mangle)]
115    pub extern "C" fn rust_init() {
116        UI.lock().unwrap().init(&mut CTX.lock().unwrap());
117        APP.lock().unwrap().init(&mut CTX.lock().unwrap());
118        APP.lock()
119            .unwrap()
120            .init_ui(&mut UI.lock().unwrap(), &mut CTX.lock().unwrap());
121
122        // Keep startup behavior aligned with winit path.
123        APP.lock().unwrap().set_cmd_line_args(
124            vec!["eldiron-creator".to_string()],
125            &mut CTX.lock().unwrap(),
126        );
127    }
128
129    #[unsafe(no_mangle)]
130    pub extern "C" fn rust_resize(width: u32, height: u32, scale_factor: f32) {
131        let mut ctx = CTX.lock().unwrap();
132        if ctx.width != width as usize
133            || ctx.height != height as usize
134            || (ctx.scale_factor - scale_factor).abs() > f32::EPSILON
135        {
136            ctx.width = width as usize;
137            ctx.height = height as usize;
138            ctx.scale_factor = scale_factor;
139            ctx.ui.relayout = true;
140            ctx.ui.redraw_all = true;
141        }
142    }
143
144    /// # Safety
145    #[unsafe(no_mangle)]
146    pub unsafe extern "C" fn rust_draw(pixels: *mut u8, width: u32, height: u32) {
147        let length = width as usize * height as usize * 4;
148        let slice = unsafe { std::slice::from_raw_parts_mut(pixels, length) };
149
150        CTX.lock().unwrap().width = width as usize;
151        CTX.lock().unwrap().height = height as usize;
152
153        UI.lock().unwrap().draw(slice, &mut CTX.lock().unwrap());
154    }
155
156    #[unsafe(no_mangle)]
157    pub extern "C" fn rust_update() -> bool {
158        UI.lock().unwrap().update(&mut CTX.lock().unwrap());
159        APP.lock()
160            .unwrap()
161            .update_ui(&mut UI.lock().unwrap(), &mut CTX.lock().unwrap());
162        APP.lock().unwrap().update(&mut CTX.lock().unwrap())
163    }
164
165    #[unsafe(no_mangle)]
166    pub extern "C" fn rust_target_fps() -> u32 {
167        APP.lock().unwrap().target_fps().clamp(1.0, 120.0) as u32
168    }
169
170    #[unsafe(no_mangle)]
171    pub extern "C" fn rust_hover(x: f32, y: f32) -> bool {
172        UI.lock().unwrap().hover(x, y, &mut CTX.lock().unwrap())
173    }
174
175    #[unsafe(no_mangle)]
176    pub extern "C" fn rust_touch_down(x: f32, y: f32) -> bool {
177        UI.lock()
178            .unwrap()
179            .touch_down(x, y, &mut CTX.lock().unwrap())
180    }
181
182    #[unsafe(no_mangle)]
183    pub extern "C" fn rust_touch_dragged(x: f32, y: f32) -> bool {
184        UI.lock()
185            .unwrap()
186            .touch_dragged(x, y, &mut CTX.lock().unwrap())
187    }
188
189    #[unsafe(no_mangle)]
190    pub extern "C" fn rust_touch_up(x: f32, y: f32) -> bool {
191        UI.lock().unwrap().touch_up(x, y, &mut CTX.lock().unwrap())
192    }
193
194    #[unsafe(no_mangle)]
195    pub extern "C" fn rust_touch_wheel(x: f32, y: f32) -> bool {
196        UI.lock()
197            .unwrap()
198            .mouse_wheel((x as i32, y as i32), &mut CTX.lock().unwrap())
199    }
200
201    /// # Safety
202    #[unsafe(no_mangle)]
203    pub unsafe extern "C" fn rust_key_down(p: *const c_char) -> bool {
204        let c_str = unsafe { CStr::from_ptr(p) };
205        if let Ok(key) = c_str.to_str() {
206            if let Some(ch) = key.chars().next() {
207                return UI
208                    .lock()
209                    .unwrap()
210                    .key_down(Some(ch), None, &mut CTX.lock().unwrap());
211            }
212        }
213        false
214    }
215
216    /// # Safety
217    #[unsafe(no_mangle)]
218    pub unsafe extern "C" fn rust_key_up(p: *const c_char) -> bool {
219        let c_str = unsafe { CStr::from_ptr(p) };
220        if let Ok(key) = c_str.to_str() {
221            if let Some(ch) = key.chars().next() {
222                return UI
223                    .lock()
224                    .unwrap()
225                    .key_up(Some(ch), None, &mut CTX.lock().unwrap());
226            }
227        }
228        false
229    }
230
231    #[unsafe(no_mangle)]
232    pub extern "C" fn rust_special_key_down(key: u32) -> bool {
233        if key == KEY_ESCAPE {
234            UI.lock()
235                .unwrap()
236                .key_down(None, Some(TheKeyCode::Escape), &mut CTX.lock().unwrap())
237        } else if key == KEY_RETURN {
238            UI.lock()
239                .unwrap()
240                .key_down(None, Some(TheKeyCode::Return), &mut CTX.lock().unwrap())
241        } else if key == KEY_DELETE {
242            UI.lock()
243                .unwrap()
244                .key_down(None, Some(TheKeyCode::Delete), &mut CTX.lock().unwrap())
245        } else if key == KEY_HOME {
246            UI.lock()
247                .unwrap()
248                .key_down(None, Some(TheKeyCode::Home), &mut CTX.lock().unwrap())
249        } else if key == KEY_END {
250            UI.lock()
251                .unwrap()
252                .key_down(None, Some(TheKeyCode::End), &mut CTX.lock().unwrap())
253        } else if key == KEY_UP {
254            UI.lock()
255                .unwrap()
256                .key_down(None, Some(TheKeyCode::Up), &mut CTX.lock().unwrap())
257        } else if key == KEY_RIGHT {
258            UI.lock()
259                .unwrap()
260                .key_down(None, Some(TheKeyCode::Right), &mut CTX.lock().unwrap())
261        } else if key == KEY_DOWN {
262            UI.lock()
263                .unwrap()
264                .key_down(None, Some(TheKeyCode::Down), &mut CTX.lock().unwrap())
265        } else if key == KEY_LEFT {
266            UI.lock()
267                .unwrap()
268                .key_down(None, Some(TheKeyCode::Left), &mut CTX.lock().unwrap())
269        } else if key == KEY_SPACE {
270            UI.lock()
271                .unwrap()
272                .key_down(None, Some(TheKeyCode::Space), &mut CTX.lock().unwrap())
273        } else {
274            UI.lock()
275                .unwrap()
276                .key_down(None, Some(TheKeyCode::Tab), &mut CTX.lock().unwrap())
277        }
278    }
279
280    #[unsafe(no_mangle)]
281    pub extern "C" fn rust_key_modifier_changed(
282        shift: bool,
283        ctrl: bool,
284        alt: bool,
285        logo: bool,
286    ) -> bool {
287        UI.lock()
288            .unwrap()
289            .modifier_changed(shift, ctrl, alt, logo, &mut CTX.lock().unwrap());
290        APP.lock().unwrap().modifier_changed(shift, ctrl, alt, logo)
291    }
292
293    /// # Safety
294    #[unsafe(no_mangle)]
295    pub unsafe extern "C" fn rust_dropped_file(p: *const c_char) {
296        let path_str = unsafe { CStr::from_ptr(p) };
297        if let Ok(path) = path_str.to_str() {
298            APP.lock().unwrap().dropped_file(path.to_string());
299        }
300    }
301
302    #[unsafe(no_mangle)]
303    pub extern "C" fn rust_new() {
304        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
305            TheId::named("New"),
306            TheWidgetState::Clicked,
307        ));
308    }
309
310    #[unsafe(no_mangle)]
311    pub extern "C" fn rust_play() {
312        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
313            TheId::named("Play"),
314            TheWidgetState::Clicked,
315        ));
316    }
317
318    #[unsafe(no_mangle)]
319    pub extern "C" fn rust_pause() {
320        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
321            TheId::named("Pause"),
322            TheWidgetState::Clicked,
323        ));
324    }
325
326    #[unsafe(no_mangle)]
327    pub extern "C" fn rust_stop() {
328        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
329            TheId::named("Stop"),
330            TheWidgetState::Clicked,
331        ));
332    }
333
334    #[unsafe(no_mangle)]
335    pub extern "C" fn rust_open() {
336        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
337            TheId::named("Open"),
338            TheWidgetState::Clicked,
339        ));
340    }
341
342    #[unsafe(no_mangle)]
343    pub extern "C" fn rust_close() {
344        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
345            TheId::named("Close"),
346            TheWidgetState::Clicked,
347        ));
348    }
349
350    #[unsafe(no_mangle)]
351    pub extern "C" fn rust_save() {
352        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
353            TheId::named("Save"),
354            TheWidgetState::Clicked,
355        ));
356    }
357
358    #[unsafe(no_mangle)]
359    pub extern "C" fn rust_save_as() {
360        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
361            TheId::named("Save As"),
362            TheWidgetState::Clicked,
363        ));
364    }
365
366    #[unsafe(no_mangle)]
367    pub extern "C" fn rust_show_settings() {
368        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
369            TheId::named("Show Settings"),
370            TheWidgetState::Clicked,
371        ));
372    }
373
374    #[unsafe(no_mangle)]
375    pub extern "C" fn rust_show_rules() {
376        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
377            TheId::named("Show Rules"),
378            TheWidgetState::Clicked,
379        ));
380    }
381
382    #[unsafe(no_mangle)]
383    pub extern "C" fn rust_show_locales() {
384        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
385            TheId::named("Show Locales"),
386            TheWidgetState::Clicked,
387        ));
388    }
389
390    #[unsafe(no_mangle)]
391    pub extern "C" fn rust_show_audio_fx() {
392        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
393            TheId::named("Show Audio FX"),
394            TheWidgetState::Clicked,
395        ));
396    }
397
398    #[unsafe(no_mangle)]
399    pub extern "C" fn rust_show_authoring() {
400        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
401            TheId::named("Show Authoring"),
402            TheWidgetState::Clicked,
403        ));
404    }
405
406    #[unsafe(no_mangle)]
407    pub extern "C" fn rust_show_debug_log() {
408        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
409            TheId::named("Show Debug Log"),
410            TheWidgetState::Clicked,
411        ));
412    }
413
414    #[unsafe(no_mangle)]
415    pub extern "C" fn rust_show_console() {
416        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
417            TheId::named("Show Console"),
418            TheWidgetState::Clicked,
419        ));
420    }
421
422    #[unsafe(no_mangle)]
423    pub extern "C" fn rust_cut() -> *mut c_char {
424        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
425            TheId::named("Cut"),
426            TheWidgetState::Clicked,
427        ));
428        APP.lock()
429            .unwrap()
430            .update_ui(&mut UI.lock().unwrap(), &mut CTX.lock().unwrap());
431
432        if let Some(TheValue::Text(text)) = &CTX.lock().unwrap().ui.clipboard {
433            return CString::new(text.clone()).unwrap().into_raw();
434        }
435        ptr::null_mut()
436    }
437
438    #[unsafe(no_mangle)]
439    pub extern "C" fn rust_copy() -> *mut c_char {
440        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
441            TheId::named("Copy"),
442            TheWidgetState::Clicked,
443        ));
444        APP.lock()
445            .unwrap()
446            .update_ui(&mut UI.lock().unwrap(), &mut CTX.lock().unwrap());
447
448        if let Some(TheValue::Text(text)) = &CTX.lock().unwrap().ui.clipboard {
449            return CString::new(text.clone()).unwrap().into_raw();
450        }
451        ptr::null_mut()
452    }
453
454    /// # Safety
455    #[unsafe(no_mangle)]
456    pub unsafe extern "C" fn rust_paste(p: *const c_char) {
457        let text_str = unsafe { CStr::from_ptr(p) };
458        if let Ok(text) = text_str.to_str() {
459            {
460                let mut ctx = CTX.lock().unwrap();
461                ctx.ui.clipboard = Some(TheValue::Text(text.to_string()));
462                ctx.ui.clipboard_app_type = Some("text/plain".to_string());
463            }
464
465            CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
466                TheId::named("Paste"),
467                TheWidgetState::Clicked,
468            ));
469
470            APP.lock()
471                .unwrap()
472                .update_ui(&mut UI.lock().unwrap(), &mut CTX.lock().unwrap());
473        }
474    }
475
476    #[unsafe(no_mangle)]
477    pub extern "C" fn rust_undo() {
478        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
479            TheId::named("Undo"),
480            TheWidgetState::Clicked,
481        ));
482    }
483
484    #[unsafe(no_mangle)]
485    pub extern "C" fn rust_redo() {
486        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
487            TheId::named("Redo"),
488            TheWidgetState::Clicked,
489        ));
490    }
491
492    #[unsafe(no_mangle)]
493    pub extern "C" fn rust_has_changes() -> bool {
494        APP.lock().unwrap().has_changes()
495    }
496}