1use std::sync::{Arc, Mutex};
25
26use khora_sdk::editor_ui::*;
27
28use crate::widgets::chrome::paint_panel_header;
29use crate::widgets::inspector::asset_pane::{paint_asset_header, render_asset_pane};
30use crate::widgets::inspector::display::{pick_icon, pick_type_tag};
31use crate::widgets::inspector::header::paint_inspector_header;
32use crate::widgets::inspector::tabs::{DebugTab, InspectorTab, InspectorTabContext, PropertiesTab};
33
34const HEADER_HEIGHT: f32 = 34.0;
35const INSPECTOR_HEADER_HEIGHT: f32 = 64.0;
36const SUBTAB_HEIGHT: f32 = 28.0;
37
38pub struct PropertiesPanel {
39 state: Arc<Mutex<EditorState>>,
40 command_history: Arc<Mutex<CommandHistory>>,
41 theme: UiTheme,
42 tabs: Vec<Box<dyn InspectorTab>>,
43 active_tab: usize,
44}
45
46impl PropertiesPanel {
47 pub fn new(
48 state: Arc<Mutex<EditorState>>,
49 history: Arc<Mutex<CommandHistory>>,
50 theme: UiTheme,
51 ) -> Self {
52 let tabs: Vec<Box<dyn InspectorTab>> = vec![Box::new(PropertiesTab), Box::new(DebugTab)];
53 Self {
54 state,
55 command_history: history,
56 theme,
57 tabs,
58 active_tab: 0,
59 }
60 }
61}
62
63impl EditorPanel for PropertiesPanel {
64 fn id(&self) -> &str {
65 "khora.editor.properties"
66 }
67 fn title(&self) -> &str {
68 "Inspector"
69 }
70
71 fn preferred_size(&self) -> Option<f32> {
72 Some(320.0)
73 }
74
75 fn ui(&mut self, ui: &mut dyn UiBuilder) {
76 let theme = self.theme.clone();
77 let panel_rect = ui.panel_rect();
78 let [px, py, pw, _] = panel_rect;
79
80 paint_panel_header(ui, panel_rect, HEADER_HEIGHT, &theme);
82
83 let (entity_data, asset_path, project_folder) = {
95 let s = match self.state.lock() {
96 Ok(s) => s,
97 Err(_) => return,
98 };
99 let entity_data = s.single_selected().and_then(|e| {
100 s.inspected
101 .clone()
102 .filter(|i| i.entity == e)
103 .map(|i| (e, i))
104 });
105 (
106 entity_data,
107 s.inspected_asset_path.clone(),
108 s.project_folder.clone().unwrap_or_default(),
109 )
110 };
111 let asset_mode = entity_data.is_none() && asset_path.is_some();
112
113 let header_origin = [px, py + HEADER_HEIGHT];
115 let after_header = if asset_mode {
116 paint_asset_header(ui, header_origin, pw, asset_path.as_ref().unwrap(), &theme)
117 } else {
118 match &entity_data {
119 Some((entity, inspected)) => {
120 let icon = pick_icon(inspected);
121 let id_label = format!("id 0x{:04X}", entity.index);
122 let type_tag = pick_type_tag(inspected);
123 paint_inspector_header(
124 ui,
125 header_origin,
126 pw,
127 icon,
128 &inspected.name,
129 type_tag,
130 "Active",
131 theme.success,
132 Some(&id_label),
133 &theme,
134 )
135 }
136 None => {
137 ui.paint_rect_filled(
138 header_origin,
139 [pw, INSPECTOR_HEADER_HEIGHT],
140 theme.surface,
141 0.0,
142 );
143 let w = (pw - 32.0).max(0.0);
146 if w > 0.0 {
147 khora_tool_ui::widgets::empty_state(
148 ui,
149 &theme,
150 [px + 16.0, py + HEADER_HEIGHT + 24.0, w, 120.0],
151 Icon::Crosshair,
152 "No selection",
153 "Select an entity to inspect it.",
154 );
155 }
156 py + HEADER_HEIGHT + INSPECTOR_HEADER_HEIGHT + 96.0
157 }
158 }
159 };
160
161 if asset_mode {
163 if let Some(rel) = asset_path.as_ref() {
164 let body_y = after_header + 12.0;
165 let body_h = (panel_rect[1] + panel_rect[3] - body_y - 6.0).max(0.0);
166 let body_rect = [px + 6.0, body_y, pw - 12.0, body_h];
167 render_asset_pane(ui, body_rect, rel, &project_folder, &theme);
168 }
169 return;
170 }
171
172 let subtab_y = after_header + 8.0;
176 let labels: Vec<&str> = self.tabs.iter().map(|t| t.label()).collect();
177 if let Some(hit) = khora_tool_ui::widgets::segmented_tabs(
178 ui,
179 &theme,
180 [px + 6.0, subtab_y, pw - 12.0, SUBTAB_HEIGHT],
181 "p-sub",
182 &labels,
183 self.active_tab,
184 ) {
185 self.active_tab = hit;
186 }
187
188 let body_y = subtab_y + SUBTAB_HEIGHT + 10.0;
190 let body_h = (panel_rect[1] + panel_rect[3] - body_y - 6.0).max(0.0);
191 let body_rect = [px + 6.0, body_y, pw - 12.0, body_h];
192
193 let (entity, inspected) = match entity_data {
194 Some(e) => e,
195 None => return,
196 };
197
198 let mut ctx = InspectorTabContext {
199 state: &self.state,
200 history: &self.command_history,
201 theme: &theme,
202 entity,
203 inspected: &inspected,
204 };
205 if let Some(tab) = self.tabs.get_mut(self.active_tab) {
206 tab.render(ui, body_rect, &mut ctx);
207 }
208 }
209}