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_UP: u32 = 3;
84    pub const KEY_RIGHT: u32 = 4;
85    pub const KEY_DOWN: u32 = 5;
86    pub const KEY_LEFT: u32 = 6;
87    pub const KEY_SPACE: u32 = 7;
88    pub const KEY_TAB: u32 = 8;
89}
90
91// --- FFI exports for the Xcode static library build ---
92// Build with: cargo build -p eldiron-creator --lib --release --no-default-features --features staticlib
93
94#[cfg(feature = "staticlib")]
95mod ffi {
96    use super::editor::Editor;
97    use super::prelude::*;
98
99    use std::ffi::{CStr, CString};
100    use std::os::raw::c_char;
101    use std::ptr;
102
103    use lazy_static::lazy_static;
104    use std::sync::Mutex;
105
106    lazy_static! {
107        static ref APP: Mutex<Editor> = Mutex::new(Editor::new());
108        static ref CTX: Mutex<TheContext> = Mutex::new(TheContext::new(800, 600, 1.0));
109        static ref UI: Mutex<TheUI> = Mutex::new(TheUI::new());
110    }
111
112    #[unsafe(no_mangle)]
113    pub extern "C" fn rust_init() {
114        UI.lock().unwrap().init(&mut CTX.lock().unwrap());
115        APP.lock().unwrap().init(&mut CTX.lock().unwrap());
116        APP.lock()
117            .unwrap()
118            .init_ui(&mut UI.lock().unwrap(), &mut CTX.lock().unwrap());
119
120        // Keep startup behavior aligned with winit path.
121        APP.lock().unwrap().set_cmd_line_args(
122            vec!["eldiron-creator".to_string()],
123            &mut CTX.lock().unwrap(),
124        );
125    }
126
127    #[unsafe(no_mangle)]
128    pub extern "C" fn rust_resize(width: u32, height: u32, scale_factor: f32) {
129        let mut ctx = CTX.lock().unwrap();
130        if ctx.width != width as usize
131            || ctx.height != height as usize
132            || (ctx.scale_factor - scale_factor).abs() > f32::EPSILON
133        {
134            ctx.width = width as usize;
135            ctx.height = height as usize;
136            ctx.scale_factor = scale_factor;
137            ctx.ui.relayout = true;
138            ctx.ui.redraw_all = true;
139        }
140    }
141
142    /// # Safety
143    #[unsafe(no_mangle)]
144    pub unsafe extern "C" fn rust_draw(pixels: *mut u8, width: u32, height: u32) {
145        let length = width as usize * height as usize * 4;
146        let slice = unsafe { std::slice::from_raw_parts_mut(pixels, length) };
147
148        CTX.lock().unwrap().width = width as usize;
149        CTX.lock().unwrap().height = height as usize;
150
151        UI.lock().unwrap().draw(slice, &mut CTX.lock().unwrap());
152    }
153
154    #[unsafe(no_mangle)]
155    pub extern "C" fn rust_update() -> bool {
156        UI.lock().unwrap().update(&mut CTX.lock().unwrap());
157        APP.lock()
158            .unwrap()
159            .update_ui(&mut UI.lock().unwrap(), &mut CTX.lock().unwrap());
160        APP.lock().unwrap().update(&mut CTX.lock().unwrap())
161    }
162
163    #[unsafe(no_mangle)]
164    pub extern "C" fn rust_target_fps() -> u32 {
165        APP.lock().unwrap().target_fps().clamp(1.0, 120.0) as u32
166    }
167
168    #[unsafe(no_mangle)]
169    pub extern "C" fn rust_hover(x: f32, y: f32) -> bool {
170        UI.lock().unwrap().hover(x, y, &mut CTX.lock().unwrap())
171    }
172
173    #[unsafe(no_mangle)]
174    pub extern "C" fn rust_touch_down(x: f32, y: f32) -> bool {
175        UI.lock()
176            .unwrap()
177            .touch_down(x, y, &mut CTX.lock().unwrap())
178    }
179
180    #[unsafe(no_mangle)]
181    pub extern "C" fn rust_touch_dragged(x: f32, y: f32) -> bool {
182        UI.lock()
183            .unwrap()
184            .touch_dragged(x, y, &mut CTX.lock().unwrap())
185    }
186
187    #[unsafe(no_mangle)]
188    pub extern "C" fn rust_touch_up(x: f32, y: f32) -> bool {
189        UI.lock().unwrap().touch_up(x, y, &mut CTX.lock().unwrap())
190    }
191
192    #[unsafe(no_mangle)]
193    pub extern "C" fn rust_touch_wheel(x: f32, y: f32) -> bool {
194        UI.lock()
195            .unwrap()
196            .mouse_wheel((x as i32, y as i32), &mut CTX.lock().unwrap())
197    }
198
199    /// # Safety
200    #[unsafe(no_mangle)]
201    pub unsafe extern "C" fn rust_key_down(p: *const c_char) -> bool {
202        let c_str = unsafe { CStr::from_ptr(p) };
203        if let Ok(key) = c_str.to_str() {
204            if let Some(ch) = key.chars().next() {
205                return UI
206                    .lock()
207                    .unwrap()
208                    .key_down(Some(ch), None, &mut CTX.lock().unwrap());
209            }
210        }
211        false
212    }
213
214    /// # Safety
215    #[unsafe(no_mangle)]
216    pub unsafe extern "C" fn rust_key_up(p: *const c_char) -> bool {
217        let c_str = unsafe { CStr::from_ptr(p) };
218        if let Ok(key) = c_str.to_str() {
219            if let Some(ch) = key.chars().next() {
220                return UI
221                    .lock()
222                    .unwrap()
223                    .key_up(Some(ch), None, &mut CTX.lock().unwrap());
224            }
225        }
226        false
227    }
228
229    #[unsafe(no_mangle)]
230    pub extern "C" fn rust_special_key_down(key: u32) -> bool {
231        if key == KEY_ESCAPE {
232            UI.lock()
233                .unwrap()
234                .key_down(None, Some(TheKeyCode::Escape), &mut CTX.lock().unwrap())
235        } else if key == KEY_RETURN {
236            UI.lock()
237                .unwrap()
238                .key_down(None, Some(TheKeyCode::Return), &mut CTX.lock().unwrap())
239        } else if key == KEY_DELETE {
240            UI.lock()
241                .unwrap()
242                .key_down(None, Some(TheKeyCode::Delete), &mut CTX.lock().unwrap())
243        } else if key == KEY_UP {
244            UI.lock()
245                .unwrap()
246                .key_down(None, Some(TheKeyCode::Up), &mut CTX.lock().unwrap())
247        } else if key == KEY_RIGHT {
248            UI.lock()
249                .unwrap()
250                .key_down(None, Some(TheKeyCode::Right), &mut CTX.lock().unwrap())
251        } else if key == KEY_DOWN {
252            UI.lock()
253                .unwrap()
254                .key_down(None, Some(TheKeyCode::Down), &mut CTX.lock().unwrap())
255        } else if key == KEY_LEFT {
256            UI.lock()
257                .unwrap()
258                .key_down(None, Some(TheKeyCode::Left), &mut CTX.lock().unwrap())
259        } else if key == KEY_SPACE {
260            UI.lock()
261                .unwrap()
262                .key_down(None, Some(TheKeyCode::Space), &mut CTX.lock().unwrap())
263        } else {
264            UI.lock()
265                .unwrap()
266                .key_down(None, Some(TheKeyCode::Tab), &mut CTX.lock().unwrap())
267        }
268    }
269
270    #[unsafe(no_mangle)]
271    pub extern "C" fn rust_key_modifier_changed(
272        shift: bool,
273        ctrl: bool,
274        alt: bool,
275        logo: bool,
276    ) -> bool {
277        UI.lock()
278            .unwrap()
279            .modifier_changed(shift, ctrl, alt, logo, &mut CTX.lock().unwrap());
280        APP.lock().unwrap().modifier_changed(shift, ctrl, alt, logo)
281    }
282
283    /// # Safety
284    #[unsafe(no_mangle)]
285    pub unsafe extern "C" fn rust_dropped_file(p: *const c_char) {
286        let path_str = unsafe { CStr::from_ptr(p) };
287        if let Ok(path) = path_str.to_str() {
288            APP.lock().unwrap().dropped_file(path.to_string());
289        }
290    }
291
292    #[unsafe(no_mangle)]
293    pub extern "C" fn rust_new() {
294        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
295            TheId::named("New"),
296            TheWidgetState::Clicked,
297        ));
298    }
299
300    #[unsafe(no_mangle)]
301    pub extern "C" fn rust_play() {
302        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
303            TheId::named("Play"),
304            TheWidgetState::Clicked,
305        ));
306    }
307
308    #[unsafe(no_mangle)]
309    pub extern "C" fn rust_pause() {
310        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
311            TheId::named("Pause"),
312            TheWidgetState::Clicked,
313        ));
314    }
315
316    #[unsafe(no_mangle)]
317    pub extern "C" fn rust_stop() {
318        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
319            TheId::named("Stop"),
320            TheWidgetState::Clicked,
321        ));
322    }
323
324    #[unsafe(no_mangle)]
325    pub extern "C" fn rust_open() {
326        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
327            TheId::named("Open"),
328            TheWidgetState::Clicked,
329        ));
330    }
331
332    #[unsafe(no_mangle)]
333    pub extern "C" fn rust_close() {
334        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
335            TheId::named("Close"),
336            TheWidgetState::Clicked,
337        ));
338    }
339
340    #[unsafe(no_mangle)]
341    pub extern "C" fn rust_save() {
342        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
343            TheId::named("Save"),
344            TheWidgetState::Clicked,
345        ));
346    }
347
348    #[unsafe(no_mangle)]
349    pub extern "C" fn rust_save_as() {
350        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
351            TheId::named("Save As"),
352            TheWidgetState::Clicked,
353        ));
354    }
355
356    #[unsafe(no_mangle)]
357    pub extern "C" fn rust_show_settings() {
358        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
359            TheId::named("Show Settings"),
360            TheWidgetState::Clicked,
361        ));
362    }
363
364    #[unsafe(no_mangle)]
365    pub extern "C" fn rust_show_rules() {
366        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
367            TheId::named("Show Rules"),
368            TheWidgetState::Clicked,
369        ));
370    }
371
372    #[unsafe(no_mangle)]
373    pub extern "C" fn rust_show_locales() {
374        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
375            TheId::named("Show Locales"),
376            TheWidgetState::Clicked,
377        ));
378    }
379
380    #[unsafe(no_mangle)]
381    pub extern "C" fn rust_show_audio_fx() {
382        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
383            TheId::named("Show Audio FX"),
384            TheWidgetState::Clicked,
385        ));
386    }
387
388    #[unsafe(no_mangle)]
389    pub extern "C" fn rust_show_authoring() {
390        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
391            TheId::named("Show Authoring"),
392            TheWidgetState::Clicked,
393        ));
394    }
395
396    #[unsafe(no_mangle)]
397    pub extern "C" fn rust_show_debug_log() {
398        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
399            TheId::named("Show Debug Log"),
400            TheWidgetState::Clicked,
401        ));
402    }
403
404    #[unsafe(no_mangle)]
405    pub extern "C" fn rust_show_console() {
406        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
407            TheId::named("Show Console"),
408            TheWidgetState::Clicked,
409        ));
410    }
411
412    #[unsafe(no_mangle)]
413    pub extern "C" fn rust_cut() -> *mut c_char {
414        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
415            TheId::named("Cut"),
416            TheWidgetState::Clicked,
417        ));
418        APP.lock()
419            .unwrap()
420            .update_ui(&mut UI.lock().unwrap(), &mut CTX.lock().unwrap());
421
422        if let Some(TheValue::Text(text)) = &CTX.lock().unwrap().ui.clipboard {
423            return CString::new(text.clone()).unwrap().into_raw();
424        }
425        ptr::null_mut()
426    }
427
428    #[unsafe(no_mangle)]
429    pub extern "C" fn rust_copy() -> *mut c_char {
430        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
431            TheId::named("Copy"),
432            TheWidgetState::Clicked,
433        ));
434        APP.lock()
435            .unwrap()
436            .update_ui(&mut UI.lock().unwrap(), &mut CTX.lock().unwrap());
437
438        if let Some(TheValue::Text(text)) = &CTX.lock().unwrap().ui.clipboard {
439            return CString::new(text.clone()).unwrap().into_raw();
440        }
441        ptr::null_mut()
442    }
443
444    /// # Safety
445    #[unsafe(no_mangle)]
446    pub unsafe extern "C" fn rust_paste(p: *const c_char) {
447        let text_str = unsafe { CStr::from_ptr(p) };
448        if let Ok(text) = text_str.to_str() {
449            {
450                let mut ctx = CTX.lock().unwrap();
451                ctx.ui.clipboard = Some(TheValue::Text(text.to_string()));
452                ctx.ui.clipboard_app_type = Some("text/plain".to_string());
453            }
454
455            CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
456                TheId::named("Paste"),
457                TheWidgetState::Clicked,
458            ));
459
460            APP.lock()
461                .unwrap()
462                .update_ui(&mut UI.lock().unwrap(), &mut CTX.lock().unwrap());
463        }
464    }
465
466    #[unsafe(no_mangle)]
467    pub extern "C" fn rust_undo() {
468        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
469            TheId::named("Undo"),
470            TheWidgetState::Clicked,
471        ));
472    }
473
474    #[unsafe(no_mangle)]
475    pub extern "C" fn rust_redo() {
476        CTX.lock().unwrap().ui.send(TheEvent::StateChanged(
477            TheId::named("Redo"),
478            TheWidgetState::Clicked,
479        ));
480    }
481
482    #[unsafe(no_mangle)]
483    pub extern "C" fn rust_has_changes() -> bool {
484        APP.lock().unwrap().has_changes()
485    }
486}