khora_editor/widgets/inspector/
tabs.rs1use std::sync::{Arc, Mutex};
17
18use khora_sdk::editor_ui::{EditorState, InspectedEntity, PropertyEdit, UiBuilder, UiTheme};
19use khora_sdk::prelude::ecs::EntityId;
20use khora_sdk::CommandHistory;
21
22use super::add_component::{is_author_facing, render_add_component};
23use super::card::render_card;
24use super::display::icon_for_domain_tag;
25use super::tag_chips::render_tag_chips;
26use super::walker::render_value;
27
28pub struct InspectorTabContext<'a> {
31 pub state: &'a Arc<Mutex<EditorState>>,
32 pub history: &'a Arc<Mutex<CommandHistory>>,
33 pub theme: &'a UiTheme,
34 pub entity: EntityId,
35 pub inspected: &'a InspectedEntity,
36}
37
38pub trait InspectorTab: Send + Sync {
41 fn label(&self) -> &str;
42 fn render(
43 &mut self,
44 ui: &mut dyn UiBuilder,
45 body_rect: [f32; 4],
46 ctx: &mut InspectorTabContext<'_>,
47 );
48}
49
50pub struct PropertiesTab;
52
53impl InspectorTab for PropertiesTab {
54 fn label(&self) -> &str {
55 "Properties"
56 }
57
58 fn render(
59 &mut self,
60 ui: &mut dyn UiBuilder,
61 body_rect: [f32; 4],
62 ctx: &mut InspectorTabContext<'_>,
63 ) {
64 let theme = ctx.theme.clone();
65 let entity = ctx.entity;
66 let inspected = ctx.inspected.clone();
67 let state_arc = ctx.state.clone();
68
69 ui.region_at("inspector-properties", body_rect, &mut |ui_region| {
74 ui_region.scroll_area("inspector-cards", &mut |ui_inner| {
75 let mut state_guard = match state_arc.lock() {
76 Ok(s) => s,
77 Err(_) => return,
78 };
79 let panel_rect_inner = ui_inner.panel_rect();
80 let card_x = panel_rect_inner[0];
81 let card_w = panel_rect_inner[2];
82
83 let mut edits: Vec<PropertyEdit> = Vec::new();
84
85 for cj in inspected.components_json.iter() {
86 if !is_author_facing(&cj.type_name) {
87 continue;
88 }
89 let title = cj.type_name.clone();
90 let mut value = cj.value.clone();
91 let icon = icon_for_domain_tag(cj.domain);
92 let mut changed = false;
93 render_card(
94 ui_inner,
95 entity,
96 &title,
97 icon,
98 None,
99 true, card_x,
101 card_w,
102 &theme,
103 &mut state_guard,
104 &mut |ui_b| {
105 if cj.type_name == "Tag" {
108 changed = render_tag_chips(ui_b, entity, &mut value);
109 } else {
110 changed = render_value(ui_b, &mut value, &theme);
111 }
112 },
113 );
114 if changed {
115 edits.push(PropertyEdit::SetComponentJson {
116 entity,
117 type_name: cj.type_name.clone(),
118 value,
119 });
120 }
121 }
122
123 ui_inner.spacing(8.0);
124 render_add_component(ui_inner, entity, &inspected, &mut state_guard);
125
126 for e in edits.drain(..) {
127 state_guard.push_edit(e);
128 }
129 });
130 });
131 }
132}
133
134pub struct DebugTab;
136
137impl InspectorTab for DebugTab {
138 fn label(&self) -> &str {
139 "Debug"
140 }
141
142 fn render(
143 &mut self,
144 ui: &mut dyn UiBuilder,
145 body_rect: [f32; 4],
146 ctx: &mut InspectorTabContext<'_>,
147 ) {
148 let theme = ctx.theme.clone();
149 let history = ctx.history.clone();
150 let undo_desc = history
151 .lock()
152 .ok()
153 .and_then(|h| h.undo_description().map(|s| s.to_owned()))
154 .unwrap_or_else(|| "(none)".to_owned());
155 let redo_desc = history
156 .lock()
157 .ok()
158 .and_then(|h| h.redo_description().map(|s| s.to_owned()))
159 .unwrap_or_else(|| "(none)".to_owned());
160 ui.region_at("inspector-debug", body_rect, &mut |ui_inner| {
161 ui_inner.colored_label(theme.text_dim, "Command history:");
162 ui_inner.colored_label(
163 theme.text_muted,
164 &format!("Undo: {} | Redo: {}", undo_desc, redo_desc),
165 );
166 });
167 }
168}