khora_editor/chrome/
title_bar.rs1use std::sync::{Arc, Mutex};
28
29use khora_sdk::editor_ui::*;
30
31use crate::widgets::{
32 brand::paint_brand_pill,
33 chrome::paint_search_pill,
34 paint::{paint_vertical_gradient, with_alpha},
35};
36
37const TITLE_BAR_HEIGHT: f32 = 44.0;
38const SEARCH_MIN_W: f32 = 180.0;
39const SEARCH_PREFERRED_W: f32 = 320.0;
40const SEARCH_HEIGHT: f32 = 28.0;
41const SEARCH_COLLAPSE_THRESHOLD: f32 = 120.0;
43const MENU_REGION_WIDTH: f32 = 240.0;
44
45pub struct TitleBarPanel {
47 state: Arc<Mutex<EditorState>>,
48 theme: UiTheme,
49}
50
51impl TitleBarPanel {
52 pub fn new(state: Arc<Mutex<EditorState>>, theme: UiTheme) -> Self {
53 Self { state, theme }
54 }
55
56 fn open_command_palette(&self) {
57 if let Ok(mut s) = self.state.lock() {
58 s.command_palette_open = true;
59 }
60 }
61}
62
63impl EditorPanel for TitleBarPanel {
64 fn id(&self) -> &str {
65 "khora.editor.title_bar"
66 }
67
68 fn title(&self) -> &str {
69 "Title Bar"
70 }
71
72 fn preferred_size(&self) -> Option<f32> {
73 Some(TITLE_BAR_HEIGHT)
74 }
75
76 fn ui(&mut self, ui: &mut dyn UiBuilder) {
77 let theme = self.theme.clone();
78 let project_name = self
79 .state
80 .lock()
81 .ok()
82 .and_then(|s| s.project_name.clone())
83 .unwrap_or_else(|| "untitled".to_owned());
84
85 let rect = ui.panel_rect();
86 let [x, y, w, h] = rect;
87
88 paint_vertical_gradient(ui, rect, theme.surface_elevated, theme.surface, 6);
90 ui.paint_line(
91 [x, y + h],
92 [x + w, y + h],
93 with_alpha(theme.separator, 0.55),
94 1.0,
95 );
96
97 let brand_x = x + 14.0;
99 let pill_right =
100 paint_brand_pill(ui, [brand_x, y], h, "KhoraEngine", &project_name, &theme);
101
102 let menus_x = pill_right + 12.0;
104 let menus_y = y + (h - 24.0) * 0.5;
105 let menu_region = [menus_x, menus_y, MENU_REGION_WIDTH, 24.0];
106
107 let state_for_menus = self.state.clone();
111 let dispatch = move |action: &str| {
112 if let Ok(mut s) = state_for_menus.lock() {
113 s.pending_menu_action = Some(action.to_owned());
114 }
115 };
116
117 ui.region_at("titlebar-menus", menu_region, &mut |ui_inner| {
118 ui_inner.horizontal(&mut |ui_inner| {
119 ui_inner.menu_button("File", &mut |m| {
120 if m.button("New Scene") {
121 dispatch("new_scene");
122 m.close_menu();
123 }
124 if m.button("Open…") {
125 dispatch("open");
126 m.close_menu();
127 }
128 m.separator();
129 if m.button("Save") {
130 dispatch("save");
131 m.close_menu();
132 }
133 if m.button("Save As…") {
134 dispatch("save_as");
135 m.close_menu();
136 }
137 m.separator();
138 if m.button("Quit") {
139 dispatch("quit");
140 m.close_menu();
141 }
142 });
143 ui_inner.menu_button("Edit", &mut |m| {
148 if m.button("Delete Del") {
149 dispatch("delete");
150 m.close_menu();
151 }
152 });
153 ui_inner.menu_button("Build", &mut |m| {
154 if m.button("Build Game…") {
155 dispatch("build_game");
156 m.close_menu();
157 }
158 });
159 ui_inner.menu_button("Help", &mut |m| {
160 if m.button("Documentation") {
161 dispatch("documentation");
162 m.close_menu();
163 }
164 if m.button("About Khora Engine") {
165 dispatch("about");
166 m.close_menu();
167 }
168 });
169 });
170 });
171
172 let right_pad = 14.0;
174 let search_right = x + w - right_pad;
175 let search_left_min = menus_x + MENU_REGION_WIDTH + 12.0;
176 let available = (search_right - search_left_min).max(0.0);
177
178 if available >= SEARCH_MIN_W {
179 let search_w = SEARCH_PREFERRED_W.min(available);
181 let search_x = search_right - search_w;
182 let search_y = y + (h - SEARCH_HEIGHT) * 0.5;
183 let (search_int, _) = paint_search_pill(
184 ui,
185 [search_x, search_y],
186 search_w,
187 SEARCH_HEIGHT,
188 "Search commands, assets, entities…",
189 &theme,
190 );
191 if search_int.clicked {
192 self.open_command_palette();
193 }
194 } else if available >= SEARCH_COLLAPSE_THRESHOLD {
195 let icon_x = search_right - 28.0;
197 let icon_y = y + (h - 28.0) * 0.5;
198 let int = ui.interact_rect("titlebar-search-collapsed", [icon_x, icon_y, 28.0, 28.0]);
199 if int.hovered {
200 ui.paint_rect_filled([icon_x, icon_y], [28.0, 28.0], theme.surface_active, 6.0);
201 }
202 crate::widgets::paint::paint_icon(
203 ui,
204 [icon_x + 7.0, icon_y + 7.0],
205 Icon::Search,
206 14.0,
207 theme.text_dim,
208 );
209 if int.clicked {
210 self.open_command_palette();
211 }
212 }
213 }
215}