Skip to main content

rustapi/
dockmanager.rs

1use crate::editor::TOOLLIST;
2use crate::prelude::*;
3use codegridfx::DebugModule;
4
5#[derive(Clone, Copy, PartialEq)]
6pub enum DockManagerState {
7    Minimized,
8    Maximized,
9    Editor,
10}
11
12pub struct DockManager {
13    pub state: DockManagerState,
14
15    pub docks: IndexMap<String, Box<dyn Dock>>,
16
17    pub editor_canvases: IndexMap<String, usize>,
18    pub editor_docks: IndexMap<String, Box<dyn Dock>>,
19
20    pub dock: String,
21    pub index: usize,
22    pub editor_index: Option<usize>,
23
24    pub supports_undo: bool,
25}
26
27impl Default for DockManager {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33impl DockManager {
34    pub fn new() -> Self {
35        let mut docks = IndexMap::default();
36
37        let dock: Box<dyn Dock> = Box::new(crate::docks::tiles::TilesDock::new());
38        docks.insert("Tiles".into(), dock);
39
40        let dock: Box<dyn Dock> = Box::new(crate::docks::visual_code::VisualCodeDock::new());
41        docks.insert("Visual Code".into(), dock);
42
43        let dock: Box<dyn Dock> = Box::new(crate::docks::code::CodeDock::new());
44        docks.insert("Code".into(), dock);
45
46        let dock: Box<dyn Dock> = Box::new(crate::docks::data::DataDock::new());
47        docks.insert("Data".into(), dock);
48
49        let dock: Box<dyn Dock> = Box::new(crate::docks::log::LogDock::new());
50        docks.insert("Log".into(), dock);
51
52        let dock: Box<dyn Dock> = Box::new(crate::docks::console::ConsoleDock::new());
53        docks.insert("Console".into(), dock);
54
55        let dock: Box<dyn Dock> = Box::new(crate::docks::tilemap::TilemapDock::new());
56        docks.insert("Tilemap".into(), dock);
57
58        Self {
59            state: DockManagerState::Minimized,
60            docks,
61            editor_canvases: IndexMap::default(),
62            editor_docks: IndexMap::default(),
63            dock: "".into(),
64            index: 0,
65            editor_index: None,
66            supports_undo: false,
67        }
68    }
69
70    pub fn init(&mut self, ctx: &mut TheContext) -> TheCanvas {
71        let mut canvas: TheCanvas = TheCanvas::new();
72
73        let mut shared_layout = TheSharedHLayout::new(TheId::named("Dock Shared Layout"));
74        shared_layout.set_shared_ratio(1.0 - 0.27);
75        shared_layout.set_mode(TheSharedHLayoutMode::Shared);
76
77        // Main Stack
78
79        let mut dock_canvas = TheCanvas::new();
80        let mut dock_stack = TheStackLayout::new(TheId::named("Dock Stack"));
81
82        for dock in &mut self.docks.values_mut() {
83            let canvas = dock.setup(ctx);
84            dock_stack.add_canvas(canvas);
85        }
86
87        dock_canvas.set_layout(dock_stack);
88        shared_layout.add_canvas(dock_canvas);
89
90        // Action Canvas
91        let mut action_canvas: TheCanvas = TheCanvas::new();
92
93        let mut toolbar_canvas = TheCanvas::default();
94        let traybar_widget = TheTraybar::new(TheId::empty());
95        toolbar_canvas.set_widget(traybar_widget);
96        let mut toolbar_hlayout = TheHLayout::new(TheId::empty());
97        toolbar_hlayout.set_background_color(None);
98
99        let mut text = TheText::new(TheId::named("Action Text"));
100        text.set_text(fl!("dock_auto"));
101        text.set_text_size(12.0);
102
103        let mut action_auto_button = TheCheckButton::new(TheId::named("Action Auto"));
104        action_auto_button.set_status_text(&fl!("status_dock_action_auto"));
105        action_auto_button.set_value(TheValue::Bool(true));
106
107        let mut action_apply_button = TheTraybarButton::new(TheId::named("Action Apply"));
108        action_apply_button.set_text(fl!("apply"));
109        action_apply_button.set_status_text(&fl!("status_dock_action_apply"));
110
111        toolbar_hlayout.set_margin(Vec4::new(10, 1, 5, 1));
112        toolbar_hlayout.set_padding(3);
113        toolbar_hlayout.add_widget(Box::new(text));
114        toolbar_hlayout.add_widget(Box::new(action_auto_button));
115        toolbar_hlayout.add_widget(Box::new(action_apply_button));
116        toolbar_hlayout.set_reverse_index(Some(1));
117        toolbar_canvas.set_layout(toolbar_hlayout);
118
119        let action_list_layout = TheListLayout::new(TheId::named("Action List"));
120        action_canvas.set_layout(action_list_layout);
121        action_canvas.set_top(toolbar_canvas);
122
123        // ---
124
125        shared_layout.add_canvas(action_canvas);
126
127        canvas.set_layout(shared_layout);
128
129        canvas
130    }
131
132    pub fn set_dock(
133        &mut self,
134        dock: String,
135        ui: &mut TheUI,
136        ctx: &mut TheContext,
137        project: &Project,
138        server_ctx: &mut ServerContext,
139    ) {
140        if dock != self.dock {
141            self.minimize(ui, ctx);
142
143            if let Some(index) = self.docks.get_index_of(&dock) {
144                self.index = index;
145                self.dock = dock;
146
147                if let Some(stack) = ui.get_stack_layout("Dock Stack") {
148                    stack.set_index(index);
149                }
150
151                self.editor_index = self.editor_canvases.get(&self.dock).copied();
152            } else {
153                eprint!("Dock \"{}\" not found!", self.dock);
154                return;
155            }
156
157            // Turn actions off / on
158            if let Some(layout) = ui.get_sharedhlayout("Dock Shared Layout") {
159                if self.docks[self.index].supports_actions() {
160                    layout.set_mode(TheSharedHLayoutMode::Shared);
161                } else {
162                    layout.set_mode(TheSharedHLayoutMode::Left);
163                }
164            }
165
166            if let Some(layout) = ui.get_sharedvlayout("Shared VLayout") {
167                let state = self.docks[self.index].default_state();
168                if state == DockDefaultState::Minimized {
169                    self.state = DockManagerState::Minimized;
170                    layout.set_mode(TheSharedVLayoutMode::Shared);
171                } else {
172                    self.state = DockManagerState::Maximized;
173                    layout.set_mode(TheSharedVLayoutMode::Bottom);
174                }
175            }
176        }
177        self.docks[self.index].activate(ui, ctx, project, server_ctx);
178        self.set_supports_undo(self.docks[self.index].supports_undo(), ctx);
179        if self.supports_undo {
180            self.docks[self.index].set_undo_state_to_ui(ctx);
181        }
182    }
183
184    pub fn import(
185        &mut self,
186        content: String,
187        ui: &mut TheUI,
188        ctx: &mut TheContext,
189        project: &mut Project,
190        server_ctx: &mut ServerContext,
191    ) {
192        if let Some((_, dock)) = self.docks.get_index_mut(self.index) {
193            dock.import(content.clone(), ui, ctx, project, server_ctx);
194
195            if let Some(editor_dock) = self.editor_docks.get_mut(&self.dock) {
196                editor_dock.import(content, ui, ctx, project, server_ctx);
197            }
198        }
199    }
200
201    pub fn export(&self) -> Option<String> {
202        if let Some((_, dock)) = self.docks.get_index(self.index) {
203            dock.export()
204        } else {
205            None
206        }
207    }
208
209    pub fn handle_event(
210        &mut self,
211        event: &TheEvent,
212        ui: &mut TheUI,
213        ctx: &mut TheContext,
214        project: &mut Project,
215        server_ctx: &mut ServerContext,
216    ) -> bool {
217        let mut redraw = false;
218
219        if let Some((_, dock)) = self.docks.get_index_mut(self.index) {
220            redraw = dock.handle_event(event, ui, ctx, project, server_ctx);
221
222            if let Some(editor_dock) = self.editor_docks.get_mut(&self.dock) {
223                if editor_dock.handle_event(event, ui, ctx, project, server_ctx) {
224                    redraw = true;
225                }
226            }
227        }
228        redraw
229    }
230
231    /// Returns the state of the dock manager.
232    pub fn get_state(&self) -> DockManagerState {
233        self.state
234    }
235
236    /// Add the dock editors to the stack and maps.
237    pub fn add_editors_to_stack(&mut self, stack: &mut TheStackLayout, ctx: &mut TheContext) {
238        let mut tiles_editor: Box<dyn Dock> =
239            Box::new(crate::docks::tiles_editor::TilesEditorDock::new());
240        let tiles_editor_canvas = tiles_editor.setup(ctx);
241        let index = stack.add_canvas(tiles_editor_canvas);
242        self.editor_canvases.insert("Tiles".to_string(), index);
243        self.editor_docks.insert("Tiles".to_string(), tiles_editor);
244
245        let mut data_editor: Box<dyn Dock> =
246            Box::new(crate::docks::data_editor::DataEditorDock::new());
247        let data_editor_canvas = data_editor.setup(ctx);
248        let index = stack.add_canvas(data_editor_canvas);
249        self.editor_canvases.insert("Data".to_string(), index);
250        self.editor_docks.insert("Data".to_string(), data_editor);
251    }
252
253    /// Shows the editor of the current dock if available, otherwise maximizes the dock.
254    pub fn edit_maximize(
255        &mut self,
256        ui: &mut TheUI,
257        ctx: &mut TheContext,
258        project: &mut Project,
259        server_ctx: &mut ServerContext,
260    ) {
261        let use_editor_canvas = if self.dock == "Data" {
262            matches!(server_ctx.pc, ProjectContext::CharacterPreviewRigging(_))
263        } else {
264            self.editor_index.is_some()
265        };
266
267        if use_editor_canvas {
268            let Some(editor_index) = self.editor_index else {
269                return;
270            };
271            if let Some(stack) = ui.get_stack_layout("Editor Stack") {
272                stack.set_index(editor_index);
273                self.state = DockManagerState::Editor;
274
275                let mut supports_undo = None;
276                if let Some(editor_dock) = self.editor_docks.get_mut(&self.dock) {
277                    editor_dock.activate(ui, ctx, project, server_ctx);
278                    supports_undo = Some(editor_dock.supports_undo());
279                    if let Some(supports_undo) = supports_undo
280                        && supports_undo
281                    {
282                        editor_dock.set_undo_state_to_ui(ctx);
283                    }
284
285                    // Switch to editor tools if the dock provides them
286                    if let Some(tools) = editor_dock.editor_tools() {
287                        TOOLLIST.write().unwrap().set_editor_tools(tools, ui, ctx);
288                    }
289                }
290
291                if let Some(supports_undo) = supports_undo {
292                    self.set_supports_undo(supports_undo, ctx);
293                }
294            }
295        } else if let Some(layout) = ui.get_sharedvlayout("Shared VLayout") {
296            layout.set_mode(TheSharedVLayoutMode::Bottom);
297            self.state = DockManagerState::Maximized;
298        }
299    }
300
301    /// Shows the editor of the current dock if available, otherwise maximizes the dock.
302    pub fn minimize(&mut self, ui: &mut TheUI, ctx: &mut TheContext) {
303        if self.state != DockManagerState::Minimized {
304            // Switch back to game tools when minimizing from editor mode
305            if self.state == DockManagerState::Editor {
306                if let Some(editor_dock) = self.editor_docks.get_mut(&self.dock) {
307                    editor_dock.minimized(ui, ctx);
308                }
309                TOOLLIST.write().unwrap().set_game_tools(ui, ctx);
310                if let Some(stack) = ui.get_stack_layout("Editor Stack") {
311                    stack.set_index(0);
312                }
313                self.state = DockManagerState::Minimized;
314            } else if let Some(layout) = ui.get_sharedvlayout("Shared VLayout") {
315                layout.set_mode(TheSharedVLayoutMode::Shared);
316                self.state = DockManagerState::Minimized;
317            }
318
319            self.set_supports_undo(self.docks[self.index].supports_undo(), ctx);
320        }
321    }
322
323    /// Returns true if the current dock (either the editor dock or the normal dock) supports undo.
324    pub fn current_dock_supports_undo(&self) -> bool {
325        self.supports_undo
326    }
327
328    /// Sets the undo support.
329    fn set_supports_undo(&mut self, supports_undo: bool, ctx: &mut TheContext) {
330        if !supports_undo {
331            ctx.ui.send(TheEvent::Custom(
332                TheId::named("Set Project Undo State"),
333                TheValue::Empty,
334            ));
335        }
336        self.supports_undo = supports_undo;
337    }
338
339    pub fn undo(
340        &mut self,
341        ui: &mut TheUI,
342        ctx: &mut TheContext,
343        project: &mut Project,
344        server_ctx: &mut ServerContext,
345    ) {
346        if self.state == DockManagerState::Editor {
347            if let Some(editor_dock) = self.editor_docks.get_mut(&self.dock) {
348                editor_dock.undo(ui, ctx, project, server_ctx);
349            }
350        } else {
351            self.docks[self.index].undo(ui, ctx, project, server_ctx);
352        }
353    }
354
355    pub fn redo(
356        &mut self,
357        ui: &mut TheUI,
358        ctx: &mut TheContext,
359        project: &mut Project,
360        server_ctx: &mut ServerContext,
361    ) {
362        if self.state == DockManagerState::Editor {
363            if let Some(editor_dock) = self.editor_docks.get_mut(&self.dock) {
364                editor_dock.redo(ui, ctx, project, server_ctx);
365            }
366        } else {
367            self.docks[self.index].redo(ui, ctx, project, server_ctx);
368        }
369    }
370
371    /// Returns true if the current (visible) dock needs animated minimap updates.
372    pub fn current_dock_supports_minimap_animation(&self) -> bool {
373        match self.state {
374            DockManagerState::Editor => self
375                .editor_docks
376                .get(&self.dock)
377                .map(|d| d.supports_minimap_animation())
378                .unwrap_or(false),
379            _ => self
380                .docks
381                .get_index(self.index)
382                .map(|(_, d)| d.supports_minimap_animation())
383                .unwrap_or(false),
384        }
385    }
386
387    /// Get the currently active dock (editor dock if in editor mode, otherwise the current dock)
388    pub fn get_active_dock(&self) -> Option<&dyn Dock> {
389        if self.state == DockManagerState::Editor {
390            self.editor_docks.get(&self.dock).map(|d| d.as_ref())
391        } else {
392            Some(self.docks[self.index].as_ref())
393        }
394    }
395
396    /// Check if any dock has unsaved changes in its undo stack
397    pub fn has_dock_changes(&self) -> bool {
398        // Check all regular docks
399        for dock in self.docks.values() {
400            if dock.has_changes() {
401                return true;
402            }
403        }
404
405        // Check all editor docks
406        for dock in self.editor_docks.values() {
407            if dock.has_changes() {
408                return true;
409            }
410        }
411
412        false
413    }
414
415    /// Mark all dock-local undo states as saved.
416    pub fn mark_saved(&mut self) {
417        for dock in self.docks.values_mut() {
418            dock.mark_saved();
419        }
420        for dock in self.editor_docks.values_mut() {
421            dock.mark_saved();
422        }
423    }
424
425    pub fn apply_debug_data(
426        &mut self,
427        ui: &mut TheUI,
428        ctx: &mut TheContext,
429        project: &Project,
430        server_ctx: &ServerContext,
431        debug: &DebugModule,
432    ) {
433        if self.state == DockManagerState::Editor {
434            if let Some(editor_dock) = self.editor_docks.get_mut(&self.dock) {
435                editor_dock.apply_debug_data(ui, ctx, project, server_ctx, debug);
436            }
437        } else if let Some((_, dock)) = self.docks.get_index_mut(self.index) {
438            dock.apply_debug_data(ui, ctx, project, server_ctx, debug);
439        }
440    }
441}