Skip to main content

rustapi/docks/
code.rs

1use crate::docks::code_undo::*;
2use crate::prelude::*;
3use theframework::prelude::*;
4use theframework::theui::thewidget::thetextedit::TheTextEditState;
5
6/// Unique identifier for entities being edited
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
8pub enum EntityKey {
9    Character(Uuid),
10    Item(Uuid),
11}
12
13pub struct CodeDock {
14    // Per-entity undo stacks
15    entity_undos: FxHashMap<EntityKey, CodeUndo>,
16    current_entity: Option<EntityKey>,
17    max_undo: usize,
18    prev_state: Option<TheTextEditState>,
19}
20
21impl Dock for CodeDock {
22    fn new() -> Self
23    where
24        Self: Sized,
25    {
26        Self {
27            entity_undos: FxHashMap::default(),
28            current_entity: None,
29            max_undo: 30,
30            prev_state: None,
31        }
32    }
33
34    fn setup(&mut self, _ctx: &mut TheContext) -> TheCanvas {
35        let mut center = TheCanvas::new();
36
37        let mut textedit = TheTextAreaEdit::new(TheId::named("DockCodeEditor"));
38
39        if let Some(bytes) = crate::Embedded::get("parser/eldrin.sublime-syntax") {
40            if let Ok(source) = std::str::from_utf8(bytes.data.as_ref()) {
41                textedit.add_syntax_from_string(source);
42                textedit.set_code_type("Eldrin Script");
43            }
44        }
45
46        if let Some(bytes) = crate::Embedded::get("parser/gruvbox-dark.tmTheme") {
47            if let Ok(source) = std::str::from_utf8(bytes.data.as_ref()) {
48                textedit.add_theme_from_string(source);
49                textedit.set_code_theme("Gruvbox Dark");
50            }
51        }
52
53        textedit.set_continuous(true);
54        textedit.display_line_number(true);
55        // textedit.set_code_theme("base16-eighties.dark");
56        textedit.use_global_statusbar(true);
57        textedit.set_font_size(14.0);
58        // Tell the widget we handle undo/redo manually here
59        textedit.set_supports_undo(false);
60        center.set_widget(textedit);
61
62        center
63    }
64
65    fn activate(
66        &mut self,
67        ui: &mut TheUI,
68        ctx: &mut TheContext,
69        project: &Project,
70        server_ctx: &mut ServerContext,
71    ) {
72        if let Some(id) = server_ctx.pc.id() {
73            if server_ctx.pc.is_character() {
74                if let Some(character) = project.characters.get(&id) {
75                    ui.set_widget_value(
76                        "DockCodeEditor",
77                        ctx,
78                        TheValue::Text(character.source.clone()),
79                    );
80                    // Switch to this entity's undo stack
81                    self.switch_to_entity(EntityKey::Character(id), ctx);
82                }
83            } else if server_ctx.pc.is_item() {
84                if let Some(item) = project.items.get(&id) {
85                    ui.set_widget_value("DockCodeEditor", ctx, TheValue::Text(item.source.clone()));
86                    // Switch to this entity's undo stack
87                    self.switch_to_entity(EntityKey::Item(id), ctx);
88                }
89            }
90        }
91
92        // Store initial state for undo
93        if let Some(edit) = ui.get_text_area_edit("DockCodeEditor") {
94            self.prev_state = Some(edit.get_state());
95        }
96    }
97
98    fn handle_event(
99        &mut self,
100        event: &TheEvent,
101        ui: &mut TheUI,
102        ctx: &mut TheContext,
103        project: &mut Project,
104        server_ctx: &mut ServerContext,
105    ) -> bool {
106        let mut redraw = false;
107
108        match event {
109            TheEvent::ValueChanged(id, value) => {
110                if id.name == "DockCodeEditor" {
111                    if let Some(edit) = ui.get_text_area_edit("DockCodeEditor") {
112                        // Add undo atom before applying the change
113                        if let Some(prev) = &self.prev_state {
114                            let current_state = edit.get_state();
115                            let atom = CodeUndoAtom::TextEdit(prev.clone(), current_state.clone());
116                            self.add_undo(atom, ctx);
117                            self.prev_state = Some(current_state);
118                        }
119                    }
120
121                    if let Some(id) = server_ctx.pc.id() {
122                        if server_ctx.pc.is_character() {
123                            if let Some(code) = value.to_string() {
124                                if let Some(character) = project.characters.get_mut(&id) {
125                                    character.source = code.clone();
126                                    character.source_debug = code;
127                                    redraw = true;
128                                }
129                            }
130                        } else if server_ctx.pc.is_item() {
131                            if let Some(code) = value.to_string() {
132                                if let Some(item) = project.items.get_mut(&id) {
133                                    item.source = code.clone();
134                                    item.source_debug = code;
135                                    redraw = true;
136                                }
137                            }
138                        }
139                    }
140                }
141            }
142            _ => {}
143        }
144        redraw
145    }
146
147    fn supports_undo(&self) -> bool {
148        true
149    }
150
151    fn has_changes(&self) -> bool {
152        // Check if any entity has changes (index >= 0, meaning not fully undone)
153        self.entity_undos.values().any(|undo| undo.has_changes())
154    }
155
156    fn mark_saved(&mut self) {
157        for undo in self.entity_undos.values_mut() {
158            undo.index = -1;
159        }
160    }
161
162    fn undo(
163        &mut self,
164        ui: &mut TheUI,
165        ctx: &mut TheContext,
166        project: &mut Project,
167        server_ctx: &mut ServerContext,
168    ) {
169        if let Some(entity_key) = self.current_entity {
170            if let Some(undo) = self.entity_undos.get_mut(&entity_key) {
171                if let Some(edit) = ui.get_text_area_edit("DockCodeEditor") {
172                    undo.undo(edit);
173                    self.prev_state = Some(edit.get_state());
174                    self.set_undo_state_to_ui(ctx);
175
176                    // Update the project with the undone text
177                    self.update_project_code(ui, project, server_ctx);
178                }
179            }
180        }
181    }
182
183    fn redo(
184        &mut self,
185        ui: &mut TheUI,
186        ctx: &mut TheContext,
187        project: &mut Project,
188        server_ctx: &mut ServerContext,
189    ) {
190        if let Some(entity_key) = self.current_entity {
191            if let Some(undo) = self.entity_undos.get_mut(&entity_key) {
192                if let Some(edit) = ui.get_text_area_edit("DockCodeEditor") {
193                    undo.redo(edit);
194                    self.prev_state = Some(edit.get_state());
195                    self.set_undo_state_to_ui(ctx);
196
197                    // Update the project with the redone text
198                    self.update_project_code(ui, project, server_ctx);
199                }
200            }
201        }
202    }
203
204    fn set_undo_state_to_ui(&self, ctx: &mut TheContext) {
205        if let Some(entity_key) = self.current_entity {
206            if let Some(undo) = self.entity_undos.get(&entity_key) {
207                if undo.has_undo() {
208                    ctx.ui.set_enabled("Undo");
209                } else {
210                    ctx.ui.set_disabled("Undo");
211                }
212
213                if undo.has_redo() {
214                    ctx.ui.set_enabled("Redo");
215                } else {
216                    ctx.ui.set_disabled("Redo");
217                }
218                return;
219            }
220        }
221
222        // No entity selected or no undo stack
223        ctx.ui.set_disabled("Undo");
224        ctx.ui.set_disabled("Redo");
225    }
226}
227
228impl CodeDock {
229    /// Switch to a different entity and update undo button states
230    fn switch_to_entity(&mut self, entity_key: EntityKey, ctx: &mut TheContext) {
231        self.current_entity = Some(entity_key);
232        self.set_undo_state_to_ui(ctx);
233    }
234
235    /// Add an undo atom to the current entity's undo stack
236    fn add_undo(&mut self, atom: CodeUndoAtom, ctx: &mut TheContext) {
237        if let Some(entity_key) = self.current_entity {
238            let undo = self
239                .entity_undos
240                .entry(entity_key)
241                .or_insert_with(CodeUndo::new);
242            undo.add(atom);
243            undo.truncate_to_limit(self.max_undo);
244            self.set_undo_state_to_ui(ctx);
245        }
246    }
247
248    /// Update the project with the current text state
249    fn update_project_code(
250        &mut self,
251        ui: &mut TheUI,
252        project: &mut Project,
253        server_ctx: &mut ServerContext,
254    ) {
255        if let Some(id) = server_ctx.pc.id() {
256            if let Some(edit) = ui.get_text_area_edit("DockCodeEditor") {
257                let state = edit.get_state();
258                let text = state.rows.join("\n");
259
260                if server_ctx.pc.is_character() {
261                    if let Some(character) = project.characters.get_mut(&id) {
262                        character.source = text.clone();
263                        character.source_debug = text;
264                    }
265                } else if server_ctx.pc.is_item() {
266                    if let Some(item) = project.items.get_mut(&id) {
267                        item.source = text.clone();
268                        item.source_debug = text;
269                    }
270                }
271            }
272        }
273    }
274}