Skip to main content

rustapi/undo/
project_helper.rs

1use crate::editor::{DOCKMANAGER, RUSTERIX};
2use crate::prelude::*;
3use theframework::prelude::*;
4
5fn data_attr_bool(data: &str, key: &str) -> bool {
6    let Ok(value) = data.parse::<toml::Value>() else {
7        return false;
8    };
9
10    let attrs = value
11        .get("attributes")
12        .and_then(|v| v.as_table())
13        .or_else(|| value.as_table());
14
15    let Some(attrs) = attrs else {
16        return false;
17    };
18
19    match attrs.get(key) {
20        Some(toml::Value::Boolean(b)) => *b,
21        Some(toml::Value::Integer(i)) => *i != 0,
22        Some(toml::Value::String(s)) => {
23            let s = s.trim().to_ascii_lowercase();
24            s == "true" || s == "1" || s == "yes"
25        }
26        _ => false,
27    }
28}
29
30/// Generate a tree node for the given region
31pub fn gen_region_tree_node(region: &Region) -> TheTreeNode {
32    let mut node: TheTreeNode = TheTreeNode::new(TheId::named_with_id(&region.name, region.id));
33    node.set_root_mode(false);
34
35    gen_region_tree_items(&mut node, region);
36
37    node
38}
39
40/// Generate the items for the region node
41pub fn gen_region_tree_items(node: &mut TheTreeNode, region: &Region) {
42    node.widgets = vec![];
43
44    // Name
45    let mut item = TheTreeItem::new(TheId::named_with_reference("Region Item", region.id));
46    item.set_text(fl!("name"));
47
48    let name = format!("Region Item Name Edit: {}", region.name);
49    let mut edit = TheTextLineEdit::new(TheId::named_with_id(&name, region.id));
50    edit.set_text(region.name.clone());
51    item.add_widget_column(200, Box::new(edit));
52    node.add_widget(Box::new(item));
53
54    // Settings
55    let mut item = TheTreeItem::new(TheId::named_with_reference(
56        "Region Settings Item",
57        region.id,
58    ));
59    item.set_text(fl!("settings"));
60    node.add_widget(Box::new(item));
61
62    for (id, character) in &region.characters {
63        let mut item = TheTreeItem::new(TheId::named_with_id("Region Content List Item", *id));
64        item.add_value_column(200, TheValue::Text(fl!("character_instance")));
65        item.set_background_color(TheColor::from(ActionRole::Dock.to_color()));
66        item.set_text(character.name.clone());
67        node.add_widget(Box::new(item));
68    }
69
70    for (id, item_) in &region.items {
71        let mut item = TheTreeItem::new(TheId::named_with_id("Region Content List Item", *id));
72        item.add_value_column(200, TheValue::Text(fl!("item_instance")));
73        item.set_background_color(TheColor::from(ActionRole::Editor.to_color()));
74        item.set_text(item_.name.clone());
75        node.add_widget(Box::new(item));
76    }
77}
78
79/// Returns a TheTreeNode for the character.
80pub fn gen_character_tree_node(character: &Character) -> TheTreeNode {
81    let mut node: TheTreeNode =
82        TheTreeNode::new(TheId::named_with_id(&character.name, character.id));
83    node.set_root_mode(false);
84    if data_attr_bool(&character.data, "player") {
85        node.set_background_color(ActionRole::Camera.to_color());
86    }
87
88    let mut item = TheTreeItem::new(TheId::named_with_reference("Character Item", character.id));
89    item.set_text(fl!("name"));
90
91    let mut edit = TheTextLineEdit::new(TheId::named_with_id(
92        "Character Item Name Edit",
93        character.id,
94    ));
95    edit.set_text(character.name.clone());
96    item.add_widget_column(200, Box::new(edit));
97
98    node.add_widget(Box::new(item));
99
100    let mut item = TheTreeItem::new(TheId::named_with_reference(
101        "Character Item Visual Code Edit",
102        character.id,
103    ));
104    item.set_background_color(TheColor::from(ActionRole::Dock.to_color()));
105    item.set_text(fl!("visual_script"));
106    node.add_widget(Box::new(item));
107
108    let mut item = TheTreeItem::new(TheId::named_with_reference(
109        "Character Item Code Edit",
110        character.id,
111    ));
112    item.set_background_color(TheColor::from(ActionRole::Dock.to_color()));
113    item.set_text(fl!("eldrin_scripting"));
114    node.add_widget(Box::new(item));
115
116    let mut item = TheTreeItem::new(TheId::named_with_reference(
117        "Character Item Data Edit",
118        character.id,
119    ));
120    item.set_background_color(TheColor::from(ActionRole::Dock.to_color()));
121    item.set_text(fl!("attributes"));
122    node.add_widget(Box::new(item));
123
124    let mut item = TheTreeItem::new(TheId::named_with_reference(
125        "Character Item Preview Rigging Edit",
126        character.id,
127    ));
128    item.set_background_color(TheColor::from(ActionRole::Dock.to_color()));
129    item.set_text(fl!("preview_rigging"));
130    node.add_widget(Box::new(item));
131
132    node
133}
134
135/// Returns a TheTreeNode for the item.
136pub fn gen_item_tree_node(item_: &Item) -> TheTreeNode {
137    let mut node: TheTreeNode = TheTreeNode::new(TheId::named_with_id(&item_.name, item_.id));
138    node.set_root_mode(false);
139    if data_attr_bool(&item_.data, "is_spell") {
140        node.set_background_color(ActionRole::Editor.to_color());
141    }
142
143    let mut item = TheTreeItem::new(TheId::named_with_reference("Item Item", item_.id));
144    item.set_text(fl!("name"));
145
146    let mut edit = TheTextLineEdit::new(TheId::named_with_id("Item Item Name Edit", item_.id));
147    edit.set_text(item_.name.clone());
148    item.add_widget_column(200, Box::new(edit));
149
150    node.add_widget(Box::new(item));
151
152    let mut item = TheTreeItem::new(TheId::named_with_reference(
153        "Item Item Visual Code Edit",
154        item_.id,
155    ));
156    item.set_background_color(TheColor::from(ActionRole::Dock.to_color()));
157    item.set_text(fl!("visual_script"));
158    node.add_widget(Box::new(item));
159
160    let mut item = TheTreeItem::new(TheId::named_with_reference("Item Item Code Edit", item_.id));
161    item.set_background_color(TheColor::from(ActionRole::Dock.to_color()));
162    item.set_text(fl!("eldrin_scripting"));
163    node.add_widget(Box::new(item));
164
165    let mut item = TheTreeItem::new(TheId::named_with_reference("Item Item Data Edit", item_.id));
166    item.set_background_color(TheColor::from(ActionRole::Dock.to_color()));
167    item.set_text(fl!("attributes"));
168    node.add_widget(Box::new(item));
169
170    node
171}
172
173/// Rebuilds the tree node for an avatar in the Project Tree.
174pub fn rebuild_avatar_tree_node(
175    avatar_id: &Uuid,
176    project: &Project,
177    ui: &mut TheUI,
178    server_ctx: &ServerContext,
179) {
180    if let Some(tree_layout) = ui.get_tree_layout("Project Tree") {
181        if let Some(avatars_node) = tree_layout.get_node_by_id_mut(&server_ctx.tree_avatars_id) {
182            let index = avatars_node
183                .childs
184                .iter()
185                .position(|child| child.id.uuid == *avatar_id);
186            avatars_node.remove_child_by_uuid(avatar_id);
187
188            if let Some(avatar) = project.avatars.get(avatar_id) {
189                let mut node = gen_avatar_tree_node(avatar);
190                node.set_open(true);
191                if let Some(idx) = index {
192                    avatars_node.add_child_at(idx, node);
193                } else {
194                    avatars_node.add_child(node);
195                }
196            }
197        }
198    }
199}
200
201/// Rebuilds the tree node for an animation within its avatar node.
202pub fn rebuild_animation_tree_node(
203    avatar_id: &Uuid,
204    anim_id: &Uuid,
205    project: &Project,
206    ui: &mut TheUI,
207    server_ctx: &ServerContext,
208) {
209    if let Some(tree_layout) = ui.get_tree_layout("Project Tree") {
210        if let Some(avatars_node) = tree_layout.get_node_by_id_mut(&server_ctx.tree_avatars_id) {
211            if let Some(avatar_node) = avatars_node
212                .childs
213                .iter_mut()
214                .find(|c| c.id.uuid == *avatar_id)
215            {
216                let index = avatar_node
217                    .childs
218                    .iter()
219                    .position(|child| child.id.uuid == *anim_id);
220                avatar_node.remove_child_by_uuid(anim_id);
221
222                if let Some(avatar) = project.avatars.get(avatar_id) {
223                    if let Some(anim) = avatar.animations.iter().find(|a| a.id == *anim_id) {
224                        let mut anim_node = gen_avatar_animation_node(anim);
225                        anim_node.set_open(true);
226                        if let Some(idx) = index {
227                            avatar_node.add_child_at(idx, anim_node);
228                        } else {
229                            avatar_node.add_child(anim_node);
230                        }
231                    }
232                }
233            }
234        }
235    }
236}
237
238/// Returns a TheTreeNode for the avatar item.
239pub fn gen_avatar_tree_node(avatar: &Avatar) -> TheTreeNode {
240    let mut node: TheTreeNode = TheTreeNode::new(TheId::named_with_id(&avatar.name, avatar.id));
241    node.set_root_mode(false);
242
243    let mut item = TheTreeItem::new(TheId::named_with_reference("Avatar Item", avatar.id));
244    item.set_text(fl!("name"));
245
246    let mut edit = TheTextLineEdit::new(TheId::named_with_id("Avatar Item Name Edit", avatar.id));
247    edit.set_text(avatar.name.clone());
248    item.add_widget_column(200, Box::new(edit));
249
250    node.add_widget(Box::new(item));
251
252    let mut item = TheTreeItem::new(TheId::named_with_reference(
253        "Avatar Item Resolution",
254        avatar.id,
255    ));
256    item.set_text("Resolution".to_string());
257
258    let mut edit = TheTextLineEdit::new(TheId::named_with_reference(
259        "Avatar Item Resolution Edit",
260        avatar.id,
261    ));
262    edit.set_value(TheValue::Int(avatar.resolution as i32));
263    item.add_widget_column(200, Box::new(edit));
264
265    node.add_widget(Box::new(item));
266
267    let mut item = TheTreeItem::new(TheId::named_with_reference(
268        "Avatar Perspectives",
269        avatar.id,
270    ));
271    item.set_text("Perspectives".to_string());
272
273    let mut drop_down = TheDropdownMenu::new(TheId::named_with_reference(
274        "Avatar Perspective Count",
275        avatar.id,
276    ));
277    drop_down.add_option("1".to_string());
278    drop_down.add_option("4".to_string());
279    drop_down.set_selected_index(match avatar.perspective_count {
280        AvatarPerspectiveCount::One => 0,
281        AvatarPerspectiveCount::Four => 1,
282    });
283    item.add_widget_column(200, Box::new(drop_down));
284
285    node.add_widget(Box::new(item));
286
287    let mut item = TheTreeItem::new(TheId::named_with_reference("Avatar Animations", avatar.id));
288    item.set_text("Animations".to_string());
289
290    let mut add_button = TheTraybarButton::new(TheId::named_with_reference(
291        "Avatar Add Animation",
292        avatar.id,
293    ));
294    add_button.set_text("Add".to_string());
295    item.add_widget_column(200, Box::new(add_button));
296
297    node.add_widget(Box::new(item));
298
299    // Add existing animations as child nodes
300    for animation in &avatar.animations {
301        let anim_node = gen_avatar_animation_node(animation);
302        node.add_child(anim_node);
303    }
304
305    node
306}
307
308/// Returns a TheTreeNode for an avatar animation.
309pub fn gen_avatar_animation_node(animation: &AvatarAnimation) -> TheTreeNode {
310    let label = format!("{} - Animation", animation.name);
311    let mut node = TheTreeNode::new(TheId::named_with_id(&label, animation.id));
312    node.set_root_mode(false);
313
314    // Name
315    let mut item = TheTreeItem::new(TheId::named_with_reference(
316        "Avatar Animation Item",
317        animation.id,
318    ));
319    item.set_text(fl!("name"));
320
321    let mut edit = TheTextLineEdit::new(TheId::named_with_reference(
322        "Avatar Animation Name Edit",
323        animation.id,
324    ));
325    edit.set_text(animation.name.clone());
326    item.add_widget_column(200, Box::new(edit));
327    node.add_widget(Box::new(item));
328
329    // Frame count
330    let mut item = TheTreeItem::new(TheId::named_with_reference(
331        "Avatar Animation Frames",
332        animation.id,
333    ));
334    item.set_text("Frames".to_string());
335
336    let frame_count = if let Some(p) = animation.perspectives.first() {
337        p.frames.len() as i32
338    } else {
339        0
340    };
341    let mut edit = TheTextLineEdit::new(TheId::named_with_reference(
342        "Avatar Animation Frame Count Edit",
343        animation.id,
344    ));
345    edit.set_value(TheValue::Int(frame_count));
346    item.add_widget_column(200, Box::new(edit));
347    node.add_widget(Box::new(item));
348
349    // Speed
350    let mut item = TheTreeItem::new(TheId::named_with_reference(
351        "Avatar Animation Speed",
352        animation.id,
353    ));
354    item.set_text("Speed".to_string());
355
356    let mut edit = TheTextLineEdit::new(TheId::named_with_reference(
357        "Avatar Animation Speed Edit",
358        animation.id,
359    ));
360    edit.set_value(TheValue::Float(animation.speed));
361    item.add_widget_column(200, Box::new(edit));
362    node.add_widget(Box::new(item));
363
364    // Perspective child nodes
365    for (persp_index, perspective) in animation.perspectives.iter().enumerate() {
366        let dir_name = match perspective.direction {
367            AvatarDirection::Front => "Front",
368            AvatarDirection::Back => "Back",
369            AvatarDirection::Left => "Left",
370            AvatarDirection::Right => "Right",
371        };
372
373        let mut persp_node = TheTreeNode::new(TheId::named(dir_name));
374        persp_node.set_root_mode(false);
375        persp_node.set_open(true);
376
377        let mut icons = TheTreeIcons::new(TheId::named_with_reference(
378            &format!("Avatar Perspective Icons {}", persp_index),
379            animation.id,
380        ));
381        icons.set_icon_size(24);
382        icons.set_icons_per_row(10);
383        icons.set_icon_count(perspective.frames.len());
384        for (i, frame) in perspective.frames.iter().enumerate() {
385            icons.set_icon(i, frame.texture.to_rgba());
386        }
387        icons.set_selected_index(None);
388        persp_node.add_widget(Box::new(icons));
389
390        node.add_child(persp_node);
391    }
392
393    node
394}
395
396/// Returns a TheTreeNode for the tilemap item.
397pub fn gen_tilemap_tree_node(tilemap: &Tilemap) -> TheTreeNode {
398    let mut node: TheTreeNode = TheTreeNode::new(TheId::named_with_id(&tilemap.name, tilemap.id));
399    node.set_root_mode(false);
400
401    let mut item = TheTreeItem::new(TheId::named_with_reference("Tileset Item", tilemap.id));
402    item.set_text(fl!("name"));
403
404    let mut edit = TheTextLineEdit::new(TheId::named_with_id("Tilemap Item Name Edit", tilemap.id));
405    edit.set_text(tilemap.name.clone());
406    item.add_widget_column(200, Box::new(edit));
407
408    node.add_widget(Box::new(item));
409
410    let mut item = TheTreeItem::new(TheId::named_with_reference(
411        "Tilemap Item Code Edit",
412        tilemap.id,
413    ));
414    item.set_text(fl!("grid_size"));
415
416    let mut edit = TheTextLineEdit::new(TheId::named_with_reference(
417        "Tilemap Item Grid Edit",
418        tilemap.id,
419    ));
420    edit.set_value(TheValue::Int(tilemap.grid_size));
421    item.add_widget_column(200, Box::new(edit));
422
423    node.add_widget(Box::new(item));
424
425    node
426}
427
428/// Returns a TheTreeNode for the screen.
429pub fn gen_screen_tree_node(screen: &Screen) -> TheTreeNode {
430    let mut node: TheTreeNode = TheTreeNode::new(TheId::named_with_id(&screen.name, screen.id));
431    node.set_root_mode(false);
432
433    gen_screen_tree_items(&mut node, screen);
434
435    node
436}
437
438/// Generate the items for the screen node
439pub fn gen_screen_tree_items(node: &mut TheTreeNode, screen: &Screen) {
440    node.widgets = vec![];
441
442    let mut item = TheTreeItem::new(TheId::named_with_reference("Screen Item", screen.id));
443    item.set_text(fl!("name"));
444
445    let mut edit = TheTextLineEdit::new(TheId::named_with_id("Screen Item Name Edit", screen.id));
446    edit.set_text(screen.name.clone());
447    item.add_widget_column(200, Box::new(edit));
448
449    node.add_widget(Box::new(item));
450
451    for sector in &screen.map.sectors {
452        if !sector.name.is_empty() {
453            let mut item = TheTreeItem::new(TheId::named_with_id_and_reference(
454                "Screen Content List Item",
455                sector.creator_id,
456                screen.id,
457            ));
458            item.add_value_column(200, TheValue::Text("Widget".to_string()));
459            item.set_background_color(TheColor::from(ActionRole::Dock.to_color()));
460            item.set_text(sector.name.clone());
461            node.add_widget(Box::new(item));
462        }
463    }
464}
465
466/// Returns a TheTreeNode for the screen.
467pub fn gen_asset_tree_node(asset: &Asset) -> TheTreeNode {
468    let mut node: TheTreeNode = TheTreeNode::new(TheId::named_with_id(&asset.name, asset.id));
469    node.set_root_mode(false);
470
471    let mut item = TheTreeItem::new(TheId::named_with_reference("Asset Item", asset.id));
472    item.set_text(fl!("name"));
473
474    let mut edit = TheTextLineEdit::new(TheId::named_with_id("Asset Item Name Edit", asset.id));
475    edit.set_text(asset.name.clone());
476    item.add_widget_column(200, Box::new(edit));
477
478    node.add_widget(Box::new(item));
479
480    node
481}
482
483/// Rerender the current region.
484pub fn update_region(ctx: &mut TheContext) {
485    ctx.ui.send(TheEvent::Custom(
486        TheId::named("Update Minimap"),
487        TheValue::Empty,
488    ));
489
490    RUSTERIX.write().unwrap().set_dirty();
491
492    ctx.ui.send(TheEvent::Custom(
493        TheId::named("Render SceneManager Map"),
494        TheValue::Empty,
495    ));
496}
497
498/// Apply the current palette to the tree.
499pub fn apply_palette(
500    ui: &mut TheUI,
501    _ctx: &mut TheContext,
502    server_ctx: &mut ServerContext,
503    project: &mut Project,
504) {
505    if let Some(tree_layout) = ui.get_tree_layout("Project Tree") {
506        if let Some(palette_node) = tree_layout.get_node_by_id_mut(&server_ctx.tree_palette_id) {
507            palette_node.widgets.clear();
508            palette_node.childs.clear();
509
510            let mut item = TheTreeIcons::new(TheId::named("Palette Item"));
511            item.set_icon_count(256);
512            item.set_icons_per_row(17);
513            item.set_palette(&project.palette);
514            item.set_selected_index(Some(project.palette.current_index as usize));
515
516            palette_node.add_widget(Box::new(item));
517        }
518    }
519}
520
521/// Set the project context and the current docker.
522pub fn set_project_context(
523    ctx: &mut TheContext,
524    ui: &mut TheUI,
525    project: &Project,
526    server_ctx: &mut ServerContext,
527    pc: ProjectContext,
528) {
529    // println!("set_project_context {:?}", pc);
530    let mut old_project_id = None;
531    if let Some(old_id) = server_ctx.pc.id() {
532        if let Some(tree_layout) = ui.get_tree_layout("Project Tree") {
533            if let Some(node) = tree_layout.get_node_by_id_mut(&old_id) {
534                if let Some(snapper) = node.widget.as_any().downcast_mut::<TheSnapperbar>() {
535                    snapper.set_selected(false);
536                }
537            }
538        }
539        old_project_id = Some(old_id);
540    }
541
542    server_ctx.pc = pc;
543
544    let duplicate_allowed = matches!(
545        pc,
546        ProjectContext::Region(_)
547            | ProjectContext::RegionSettings(_)
548            | ProjectContext::RegionCharacterInstance(_, _)
549            | ProjectContext::RegionItemInstance(_, _)
550            | ProjectContext::Character(_)
551            | ProjectContext::CharacterVisualCode(_)
552            | ProjectContext::CharacterCode(_)
553            | ProjectContext::CharacterData(_)
554            | ProjectContext::CharacterPreviewRigging(_)
555            | ProjectContext::Item(_)
556            | ProjectContext::ItemVisualCode(_)
557            | ProjectContext::ItemCode(_)
558            | ProjectContext::ItemData(_)
559            | ProjectContext::Screen(_)
560            | ProjectContext::ScreenWidget(_, _)
561            | ProjectContext::Avatar(_)
562            | ProjectContext::AvatarAnimation(_, _, _)
563    );
564    ui.set_widget_disabled_state("Project Duplicate", ctx, !duplicate_allowed);
565
566    match pc {
567        ProjectContext::Region(id) => {
568            if let Some(region) = project.get_region(&id) {
569                server_ctx.curr_region = id;
570                ui.set_widget_value(
571                    "Project Context",
572                    ctx,
573                    TheValue::Text(format!("{}: {}", fl!("region"), region.name)),
574                );
575            }
576            DOCKMANAGER
577                .write()
578                .unwrap()
579                .set_dock("Tiles".into(), ui, ctx, project, server_ctx);
580        }
581        ProjectContext::RegionSettings(id) => {
582            if let Some(region) = project.get_region(&id) {
583                server_ctx.curr_region = id;
584                ui.set_widget_value(
585                    "Project Context",
586                    ctx,
587                    TheValue::Text(format!("Region Settings: {}", region.name)),
588                );
589            }
590            DOCKMANAGER
591                .write()
592                .unwrap()
593                .set_dock("Data".into(), ui, ctx, project, server_ctx);
594        }
595        ProjectContext::RegionCharacterInstance(id, _) => {
596            if let Some(region) = project.get_region(&id) {
597                server_ctx.curr_region = id;
598                ui.set_widget_value(
599                    "Project Context",
600                    ctx,
601                    TheValue::Text(format!("Region ({}) Character", region.name)),
602                );
603            }
604            DOCKMANAGER.write().unwrap().set_dock(
605                "Visual Code".into(),
606                ui,
607                ctx,
608                project,
609                server_ctx,
610            );
611        }
612        ProjectContext::RegionItemInstance(id, _) => {
613            if let Some(region) = project.get_region(&id) {
614                server_ctx.curr_region = id;
615                ui.set_widget_value(
616                    "Project Context",
617                    ctx,
618                    TheValue::Text(format!("Region ({}) Item", region.name)),
619                );
620            }
621            DOCKMANAGER
622                .write()
623                .unwrap()
624                .set_dock("Tiles".into(), ui, ctx, project, server_ctx);
625        }
626        ProjectContext::Character(id) => {
627            if let Some(region) = project.characters.get(&id) {
628                ui.set_widget_value(
629                    "Project Context",
630                    ctx,
631                    TheValue::Text(format!("Character: {}", region.name)),
632                );
633            }
634            DOCKMANAGER
635                .write()
636                .unwrap()
637                .set_dock("Tiles".into(), ui, ctx, project, server_ctx);
638        }
639        ProjectContext::CharacterVisualCode(id) => {
640            if let Some(region) = project.characters.get(&id) {
641                ui.set_widget_value(
642                    "Project Context",
643                    ctx,
644                    TheValue::Text(format!("Character: {}", region.name)),
645                );
646            }
647            DOCKMANAGER.write().unwrap().set_dock(
648                "Visual Code".into(),
649                ui,
650                ctx,
651                project,
652                server_ctx,
653            );
654        }
655        ProjectContext::CharacterCode(id) => {
656            if let Some(region) = project.characters.get(&id) {
657                ui.set_widget_value(
658                    "Project Context",
659                    ctx,
660                    TheValue::Text(format!("Character: {}", region.name)),
661                );
662            }
663            DOCKMANAGER
664                .write()
665                .unwrap()
666                .set_dock("Code".into(), ui, ctx, project, server_ctx);
667        }
668        ProjectContext::CharacterData(id) => {
669            if let Some(region) = project.characters.get(&id) {
670                ui.set_widget_value(
671                    "Project Context",
672                    ctx,
673                    TheValue::Text(format!("Character: {}", region.name)),
674                );
675            }
676            DOCKMANAGER
677                .write()
678                .unwrap()
679                .set_dock("Data".into(), ui, ctx, project, server_ctx);
680        }
681        ProjectContext::CharacterPreviewRigging(id) => {
682            if let Some(region) = project.characters.get(&id) {
683                ui.set_widget_value(
684                    "Project Context",
685                    ctx,
686                    TheValue::Text(format!("Character Preview Rigging: {}", region.name)),
687                );
688            }
689            DOCKMANAGER
690                .write()
691                .unwrap()
692                .set_dock("Data".into(), ui, ctx, project, server_ctx);
693        }
694        ProjectContext::Item(id) => {
695            if let Some(item) = project.items.get(&id) {
696                ui.set_widget_value(
697                    "Project Context",
698                    ctx,
699                    TheValue::Text(format!("Item: {}", item.name)),
700                );
701            }
702            DOCKMANAGER
703                .write()
704                .unwrap()
705                .set_dock("Tiles".into(), ui, ctx, project, server_ctx);
706        }
707        ProjectContext::ItemVisualCode(id) => {
708            if let Some(item) = project.items.get(&id) {
709                ui.set_widget_value(
710                    "Project Context",
711                    ctx,
712                    TheValue::Text(format!("Item: {}", item.name)),
713                );
714            }
715            DOCKMANAGER.write().unwrap().set_dock(
716                "Visual Code".into(),
717                ui,
718                ctx,
719                project,
720                server_ctx,
721            );
722        }
723        ProjectContext::ItemCode(id) => {
724            if let Some(item) = project.items.get(&id) {
725                ui.set_widget_value(
726                    "Project Context",
727                    ctx,
728                    TheValue::Text(format!("Item: {}", item.name)),
729                );
730            }
731            DOCKMANAGER
732                .write()
733                .unwrap()
734                .set_dock("Code".into(), ui, ctx, project, server_ctx);
735        }
736        ProjectContext::ItemData(id) => {
737            if let Some(item) = project.items.get(&id) {
738                ui.set_widget_value(
739                    "Project Context",
740                    ctx,
741                    TheValue::Text(format!("Item: {}", item.name)),
742                );
743            }
744            DOCKMANAGER
745                .write()
746                .unwrap()
747                .set_dock("Data".into(), ui, ctx, project, server_ctx);
748        }
749        ProjectContext::Tilemap(id) => {
750            if let Some(tilemap) = project.get_tilemap(id) {
751                ui.set_widget_value(
752                    "Project Context",
753                    ctx,
754                    TheValue::Text(format!("Tilemap: {}", tilemap.name)),
755                );
756            }
757            DOCKMANAGER
758                .write()
759                .unwrap()
760                .set_dock("Tilemap".into(), ui, ctx, project, server_ctx);
761        }
762        ProjectContext::Screen(id) => {
763            // Screens are always edited in 2D preview mode.
764            server_ctx.editor_view_mode = EditorViewMode::D2;
765            ui.set_widget_value("Editor View Switch", ctx, TheValue::Int(0));
766            if let Some(screen) = project.screens.get(&id) {
767                ui.set_widget_value(
768                    "Project Context",
769                    ctx,
770                    TheValue::Text(format!("Screen: {}", screen.name)),
771                );
772            }
773            DOCKMANAGER
774                .write()
775                .unwrap()
776                .set_dock("Tiles".into(), ui, ctx, project, server_ctx);
777        }
778        ProjectContext::ScreenWidget(id, _widget_id) => {
779            // Screens are always edited in 2D preview mode.
780            server_ctx.editor_view_mode = EditorViewMode::D2;
781            ui.set_widget_value("Editor View Switch", ctx, TheValue::Int(0));
782            if let Some(screen) = project.screens.get(&id) {
783                ui.set_widget_value(
784                    "Project Context",
785                    ctx,
786                    TheValue::Text(format!("Screen ({}) Widget", screen.name,)),
787                );
788            }
789            DOCKMANAGER
790                .write()
791                .unwrap()
792                .set_dock("Data".into(), ui, ctx, project, server_ctx);
793        }
794        ProjectContext::Asset(id) => {
795            if let Some(asset) = project.assets.get(&id) {
796                ui.set_widget_value(
797                    "Project Context",
798                    ctx,
799                    TheValue::Text(format!("Asset: {}", asset.name)),
800                );
801            }
802            DOCKMANAGER
803                .write()
804                .unwrap()
805                .set_dock("Tiles".into(), ui, ctx, project, server_ctx);
806        }
807        ProjectContext::Avatar(id) => {
808            if let Some(avatar) = project.avatars.get(&id) {
809                ui.set_widget_value(
810                    "Project Context",
811                    ctx,
812                    TheValue::Text(format!("Avatar: {}", avatar.name)),
813                );
814            }
815            DOCKMANAGER
816                .write()
817                .unwrap()
818                .set_dock("Tiles".into(), ui, ctx, project, server_ctx);
819        }
820        ProjectContext::AvatarAnimation(avatar_id, anim_id, frame) => {
821            if let Some(avatar) = project.avatars.get(&avatar_id) {
822                if let Some(anim) = avatar.animations.iter().find(|a| a.id == anim_id) {
823                    ui.set_widget_value(
824                        "Project Context",
825                        ctx,
826                        TheValue::Text(format!(
827                            "{} - {} (Frame {})",
828                            avatar.name, anim.name, frame
829                        )),
830                    );
831                }
832            }
833            DOCKMANAGER
834                .write()
835                .unwrap()
836                .set_dock("Tiles".into(), ui, ctx, project, server_ctx);
837        }
838        ProjectContext::ProjectSettings => {
839            ui.set_widget_value(
840                "Project Context",
841                ctx,
842                TheValue::Text("Project Settings".into()),
843            );
844            DOCKMANAGER
845                .write()
846                .unwrap()
847                .set_dock("Data".into(), ui, ctx, project, server_ctx);
848        }
849        ProjectContext::GameRules => {
850            ui.set_widget_value("Project Context", ctx, TheValue::Text("Game Rules".into()));
851            DOCKMANAGER
852                .write()
853                .unwrap()
854                .set_dock("Data".into(), ui, ctx, project, server_ctx);
855        }
856        ProjectContext::GameLocales => {
857            ui.set_widget_value(
858                "Project Context",
859                ctx,
860                TheValue::Text("Game Locales".into()),
861            );
862            DOCKMANAGER
863                .write()
864                .unwrap()
865                .set_dock("Data".into(), ui, ctx, project, server_ctx);
866        }
867        ProjectContext::GameAudioFx => {
868            ui.set_widget_value(
869                "Project Context",
870                ctx,
871                TheValue::Text("Game Audio FX".into()),
872            );
873            DOCKMANAGER
874                .write()
875                .unwrap()
876                .set_dock("Data".into(), ui, ctx, project, server_ctx);
877        }
878        ProjectContext::DebugLog => {
879            // ui.set_widget_value("LogEdit", ctx, TheValue::Text(log));
880            DOCKMANAGER
881                .write()
882                .unwrap()
883                .set_dock("Log".into(), ui, ctx, project, server_ctx);
884        }
885        ProjectContext::Console => {
886            ui.set_widget_value("Project Context", ctx, TheValue::Text("Console".into()));
887            DOCKMANAGER
888                .write()
889                .unwrap()
890                .set_dock("Console".into(), ui, ctx, project, server_ctx);
891        }
892        _ => {}
893    }
894
895    // If the region changed, update it
896    if pc.is_region() {
897        if old_project_id != pc.id() {
898            ctx.ui.send(TheEvent::Custom(
899                TheId::named("Update Minimap"),
900                TheValue::Empty,
901            ));
902
903            RUSTERIX.write().unwrap().set_dirty();
904
905            ctx.ui.send(TheEvent::Custom(
906                TheId::named("Render SceneManager Map"),
907                TheValue::Empty,
908            ));
909        }
910    }
911
912    if let Some(tree_layout) = ui.get_tree_layout("Project Tree") {
913        if let Some(new_id) = pc.id() {
914            if let Some(node) = tree_layout.get_node_by_id_mut(&new_id)
915                && let Some(snapper) = node.widget.as_any().downcast_mut::<TheSnapperbar>()
916            {
917                snapper.set_selected(true);
918            }
919        } else {
920            match pc {
921                ProjectContext::ProjectSettings => {
922                    let target_id = {
923                        let mut found: Option<TheId> = None;
924                        for node in &mut tree_layout.get_root().childs {
925                            if node.id.name == fl!("game") {
926                                node.set_open(true);
927                                for widget in &node.widgets {
928                                    if widget.id().name == "Project Settings" {
929                                        found = Some(widget.id().clone());
930                                        break;
931                                    }
932                                }
933                                break;
934                            }
935                        }
936                        found
937                    };
938                    if let Some(id) = target_id {
939                        tree_layout.new_item_selected(id);
940                    }
941                }
942                ProjectContext::GameRules => {
943                    let target_id = {
944                        let mut found: Option<TheId> = None;
945                        for node in &mut tree_layout.get_root().childs {
946                            if node.id.name == fl!("game") {
947                                node.set_open(true);
948                                for widget in &node.widgets {
949                                    if widget.id().name == "Game Rules" {
950                                        found = Some(widget.id().clone());
951                                        break;
952                                    }
953                                }
954                                break;
955                            }
956                        }
957                        found
958                    };
959                    if let Some(id) = target_id {
960                        tree_layout.new_item_selected(id);
961                    }
962                }
963                ProjectContext::GameLocales => {
964                    let target_id = {
965                        let mut found: Option<TheId> = None;
966                        for node in &mut tree_layout.get_root().childs {
967                            if node.id.name == fl!("game") {
968                                node.set_open(true);
969                                for widget in &node.widgets {
970                                    if widget.id().name == "Game Locales" {
971                                        found = Some(widget.id().clone());
972                                        break;
973                                    }
974                                }
975                                break;
976                            }
977                        }
978                        found
979                    };
980                    if let Some(id) = target_id {
981                        tree_layout.new_item_selected(id);
982                    }
983                }
984                ProjectContext::GameAudioFx => {
985                    let target_id = {
986                        let mut found: Option<TheId> = None;
987                        for node in &mut tree_layout.get_root().childs {
988                            if node.id.name == fl!("game") {
989                                node.set_open(true);
990                                for widget in &node.widgets {
991                                    if widget.id().name == "Game Audio FX" {
992                                        found = Some(widget.id().clone());
993                                        break;
994                                    }
995                                }
996                                break;
997                            }
998                        }
999                        found
1000                    };
1001                    if let Some(id) = target_id {
1002                        tree_layout.new_item_selected(id);
1003                    }
1004                }
1005                ProjectContext::DebugLog => {
1006                    let target_id = {
1007                        let mut found: Option<TheId> = None;
1008                        for node in &mut tree_layout.get_root().childs {
1009                            if node.id.name == fl!("game") {
1010                                node.set_open(true);
1011                                for widget in &node.widgets {
1012                                    if widget.id().name == "Debug Log" {
1013                                        found = Some(widget.id().clone());
1014                                        break;
1015                                    }
1016                                }
1017                                break;
1018                            }
1019                        }
1020                        found
1021                    };
1022                    if let Some(id) = target_id {
1023                        tree_layout.new_item_selected(id);
1024                    }
1025                }
1026                ProjectContext::Console => {
1027                    let target_id = {
1028                        let mut found: Option<TheId> = None;
1029                        for node in &mut tree_layout.get_root().childs {
1030                            if node.id.name == fl!("game") {
1031                                node.set_open(true);
1032                                for widget in &node.widgets {
1033                                    if widget.id().name == "Console" {
1034                                        found = Some(widget.id().clone());
1035                                        break;
1036                                    }
1037                                }
1038                                break;
1039                            }
1040                        }
1041                        found
1042                    };
1043                    if let Some(id) = target_id {
1044                        tree_layout.new_item_selected(id);
1045                    }
1046                }
1047                _ => {}
1048            }
1049        }
1050    }
1051
1052    ctx.ui.send(TheEvent::Custom(
1053        TheId::named("Update Action List"),
1054        TheValue::Empty,
1055    ));
1056
1057    ctx.ui.relayout = true;
1058}