1use crate::docks::visual_code_undo::*;
2use crate::prelude::*;
3use codegridfx::DebugModule;
4use codegridfx::Module;
5use theframework::prelude::*;
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
9pub enum EntityKey {
10 CharacterInstance(Uuid, Uuid), CharacterTemplate(Uuid),
12 ItemInstance(Uuid, Uuid), ItemTemplate(Uuid),
14}
15
16pub struct VisualCodeDock {
17 module: Module,
18 entity_undos: FxHashMap<EntityKey, VisualCodeUndo>,
20 current_entity: Option<EntityKey>,
21 max_undo: usize,
22 prev_module: Option<Module>,
23}
24
25impl Dock for VisualCodeDock {
26 fn new() -> Self
27 where
28 Self: Sized,
29 {
30 Self {
31 module: Module::default(),
32 entity_undos: FxHashMap::default(),
33 current_entity: None,
34 max_undo: 30,
35 prev_module: None,
36 }
37 }
38
39 fn setup(&mut self, ctx: &mut TheContext) -> TheCanvas {
40 self.module.build_canvas(ctx, "DockVisualScripting")
41 }
42
43 fn activate(
44 &mut self,
45 ui: &mut TheUI,
46 ctx: &mut TheContext,
47 project: &Project,
48 server_ctx: &mut ServerContext,
49 ) {
50 if let Some(id) = server_ctx.pc.id() {
51 if let Some(instance_id) = server_ctx.pc.get_region_character_instance_id() {
52 if let Some(region) = project.get_region(&id) {
53 if let Some(character_instance) = region.characters.get(&instance_id) {
54 let mut entity_key = EntityKey::CharacterInstance(id, instance_id);
55 self.module = character_instance.module.clone();
56 self.module.module_type = codegridfx::ModuleType::CharacterInstance;
57
58 if self.module.routines.is_empty() {
59 if let Some(character) =
60 project.characters.get(&character_instance.character_id)
61 {
62 self.module = character.module.clone();
63 self.module.module_type = codegridfx::ModuleType::CharacterTemplate;
64 entity_key =
65 EntityKey::CharacterTemplate(character_instance.character_id);
66 }
67 }
68
69 self.module.view_name = "DockVisualScripting".into();
70 self.module.redraw(ui, ctx);
71 self.switch_to_entity(entity_key, ctx);
72 }
73 }
74 } else if let Some(instance_id) = server_ctx.pc.get_region_item_instance_id() {
75 if let Some(region) = project.get_region(&id) {
76 if let Some(item_instance) = region.items.get(&instance_id) {
77 let mut entity_key = EntityKey::ItemInstance(id, instance_id);
78 self.module = item_instance.module.clone();
79 self.module.module_type = codegridfx::ModuleType::ItemInstance;
80
81 if self.module.routines.is_empty() {
82 if let Some(item) = project.items.get(&item_instance.item_id) {
83 self.module = item.module.clone();
84 self.module.module_type = codegridfx::ModuleType::ItemTemplate;
85 entity_key = EntityKey::ItemTemplate(item_instance.item_id);
86 }
87 }
88
89 self.module.view_name = "DockVisualScripting".into();
90 self.module.redraw(ui, ctx);
91 self.switch_to_entity(entity_key, ctx);
92 }
93 }
94 } else if server_ctx.pc.is_character() {
95 if let Some(character) = project.characters.get(&id) {
96 self.module = character.module.clone();
97 self.module.module_type = codegridfx::ModuleType::CharacterTemplate;
100 self.module.view_name = "DockVisualScripting".into();
101 self.module.redraw(ui, ctx);
102 self.switch_to_entity(EntityKey::CharacterTemplate(id), ctx);
103 }
104 } else if server_ctx.pc.is_item() {
105 if let Some(item) = project.items.get(&id) {
106 self.module = item.module.clone();
107 self.module.module_type = codegridfx::ModuleType::ItemTemplate;
110 self.module.view_name = "DockVisualScripting".into();
111 self.module.redraw(ui, ctx);
112 self.switch_to_entity(EntityKey::ItemTemplate(id), ctx);
113 }
114 }
115 }
116 }
117
118 fn apply_debug_data(
119 &mut self,
120 ui: &mut TheUI,
121 ctx: &mut TheContext,
122 project: &Project,
123 server_ctx: &ServerContext,
124 debug: &DebugModule,
125 ) {
126 if let Some(runtime_id) = self.runtime_debug_id(project, server_ctx) {
127 self.module.redraw_debug(ui, ctx, runtime_id, debug);
128 } else {
129 self.module.redraw(ui, ctx);
130 }
131 }
132
133 fn import(
134 &mut self,
135 content: String,
136 ui: &mut TheUI,
137 ctx: &mut TheContext,
138 project: &mut Project,
139 server_ctx: &mut ServerContext,
140 ) {
141 self.module = Module::from_json(&content);
142 if let Some(prev) = &self.prev_module {
143 self.module.id = prev.id;
144 }
145 self.module.redraw(ui, ctx);
146 self.handle_event(
147 &TheEvent::Custom(TheId::named("ModuleChanged"), TheValue::Empty),
148 ui,
149 ctx,
150 project,
151 server_ctx,
152 );
153 }
154
155 fn export(&self) -> Option<String> {
156 Some(self.module.to_json())
157 }
158
159 fn handle_event(
160 &mut self,
161 event: &TheEvent,
162 ui: &mut TheUI,
163 ctx: &mut TheContext,
164 project: &mut Project,
165 server_ctx: &mut ServerContext,
166 ) -> bool {
167 let redraw = self.module.handle_event(event, ui, ctx, &project.palette);
168
169 match event {
170 TheEvent::Custom(id, _) => {
171 if id.name == "ModuleChanged" {
172 if let Some(prev) = &self.prev_module {
174 if prev.to_json() != self.module.to_json() {
176 let atom =
177 VisualCodeUndoAtom::ModuleEdit(prev.clone(), self.module.clone());
178 self.add_undo(atom, ctx);
179 }
180 }
181
182 self.prev_module = Some(self.module.clone());
184
185 self.update_project_module(project, server_ctx);
186 }
187 }
188 _ => {}
189 }
190 redraw
191 }
192
193 fn supports_undo(&self) -> bool {
194 true
195 }
196
197 fn has_changes(&self) -> bool {
198 self.entity_undos.values().any(|undo| undo.has_changes())
200 }
201
202 fn mark_saved(&mut self) {
203 for undo in self.entity_undos.values_mut() {
204 undo.index = -1;
205 }
206 }
207
208 fn undo(
209 &mut self,
210 ui: &mut TheUI,
211 ctx: &mut TheContext,
212 project: &mut Project,
213 server_ctx: &mut ServerContext,
214 ) {
215 if let Some(entity_key) = self.current_entity {
216 if let Some(undo) = self.entity_undos.get_mut(&entity_key) {
217 undo.undo(&mut self.module, ui, ctx);
218 self.prev_module = Some(self.module.clone());
219 self.set_undo_state_to_ui(ctx);
220
221 self.update_project_module(project, server_ctx);
223 }
224 }
225 }
226
227 fn redo(
228 &mut self,
229 ui: &mut TheUI,
230 ctx: &mut TheContext,
231 project: &mut Project,
232 server_ctx: &mut ServerContext,
233 ) {
234 if let Some(entity_key) = self.current_entity {
235 if let Some(undo) = self.entity_undos.get_mut(&entity_key) {
236 undo.redo(&mut self.module, ui, ctx);
237 self.prev_module = Some(self.module.clone());
238 self.set_undo_state_to_ui(ctx);
239
240 self.update_project_module(project, server_ctx);
242 }
243 }
244 }
245
246 fn set_undo_state_to_ui(&self, ctx: &mut TheContext) {
247 if let Some(entity_key) = self.current_entity {
248 if let Some(undo) = self.entity_undos.get(&entity_key) {
249 if undo.has_undo() {
250 ctx.ui.set_enabled("Undo");
251 } else {
252 ctx.ui.set_disabled("Undo");
253 }
254
255 if undo.has_redo() {
256 ctx.ui.set_enabled("Redo");
257 } else {
258 ctx.ui.set_disabled("Redo");
259 }
260 return;
261 }
262 }
263
264 ctx.ui.set_disabled("Undo");
266 ctx.ui.set_disabled("Redo");
267 }
268}
269
270impl VisualCodeDock {
271 fn switch_to_entity(&mut self, entity_key: EntityKey, ctx: &mut TheContext) {
273 self.current_entity = Some(entity_key);
274 self.prev_module = Some(self.module.clone());
275 self.set_undo_state_to_ui(ctx);
276 }
277
278 fn add_undo(&mut self, atom: VisualCodeUndoAtom, ctx: &mut TheContext) {
280 if let Some(entity_key) = self.current_entity {
281 let undo = self
282 .entity_undos
283 .entry(entity_key)
284 .or_insert_with(VisualCodeUndo::new);
285 undo.add(atom);
286 undo.truncate_to_limit(self.max_undo);
287 self.set_undo_state_to_ui(ctx);
288 }
289 }
290
291 fn update_project_module(&mut self, project: &mut Project, server_ctx: &mut ServerContext) {
293 let code = self.module.build(false);
294 let debug_code = self.module.build(true);
295
296 match self.current_entity {
297 Some(EntityKey::CharacterInstance(region_id, instance_id)) => {
298 if let Some(region) = project.get_region_mut(®ion_id) {
299 if let Some(character_instance) = region.characters.get_mut(&instance_id) {
300 character_instance.module = self.module.clone();
301 character_instance.source = code;
302 character_instance.source_debug = debug_code;
303 }
304 }
305 }
306 Some(EntityKey::CharacterTemplate(id)) => {
307 if let Some(character) = project.characters.get_mut(&id) {
308 character.module = self.module.clone();
309 character.source = code;
310 character.source_debug = debug_code;
311 }
312 }
313 Some(EntityKey::ItemInstance(region_id, instance_id)) => {
314 if let Some(region) = project.get_region_mut(®ion_id) {
315 if let Some(item_instance) = region.items.get_mut(&instance_id) {
316 item_instance.module = self.module.clone();
317 item_instance.source = code;
318 item_instance.source_debug = debug_code;
319 }
320 }
321 }
322 Some(EntityKey::ItemTemplate(id)) => {
323 if let Some(item) = project.items.get_mut(&id) {
324 item.module = self.module.clone();
325 item.source = code;
326 item.source_debug = debug_code;
327 }
328 }
329 None => {
330 let _ = server_ctx;
331 }
332 }
333 }
334
335 fn runtime_debug_id(&self, project: &Project, server_ctx: &ServerContext) -> Option<u32> {
336 let region = project.get_region(&server_ctx.curr_region)?;
337
338 match server_ctx.cc {
339 ContentContext::CharacterInstance(instance_id) => region
340 .map
341 .entities
342 .iter()
343 .find(|entity| entity.creator_id == instance_id)
344 .map(|entity| entity.id),
345 ContentContext::CharacterTemplate(template_id) => region
346 .characters
347 .values()
348 .find(|instance| instance.character_id == template_id)
349 .and_then(|instance| {
350 region
351 .map
352 .entities
353 .iter()
354 .find(|entity| entity.creator_id == instance.id)
355 .map(|entity| entity.id)
356 }),
357 ContentContext::ItemInstance(instance_id) => region
358 .map
359 .items
360 .iter()
361 .find(|item| item.creator_id == instance_id)
362 .map(|item| item.id),
363 ContentContext::ItemTemplate(template_id) => region
364 .items
365 .values()
366 .find(|instance| instance.item_id == template_id)
367 .and_then(|instance| {
368 region
369 .map
370 .items
371 .iter()
372 .find(|item| item.creator_id == instance.id)
373 .map(|item| item.id)
374 }),
375 _ => None,
376 }
377 }
378}