1use crate::ui::{DrawCommand, RectDraw, TextDraw, Ui};
4use zenthra_core::{Color, Id, Rect, Response};
5use zenthra_render::RectInstance;
6
7pub struct MenuBarBuilder<'u, 'a> {
8 ui: &'u mut Ui<'a>,
9 bg: Option<Color>,
10}
11
12impl<'u, 'a> MenuBarBuilder<'u, 'a> {
13 pub fn new(ui: &'u mut Ui<'a>) -> Self {
14 Self { ui, bg: None }
15 }
16
17 pub fn bg(mut self, color: Color) -> Self {
18 self.bg = Some(color);
19 self
20 }
21
22 pub fn show<F>(self, f: F)
23 where F: FnOnce(&mut Ui) {
24 let active_menu_key = Id::from_u64(999999900);
25 let active_submenu_key = Id::from_u64(999999901);
26 let hover_flag_key = Id::from_u64(999999902);
27
28 self.ui.interaction_state.insert(hover_flag_key, 0.0);
30
31 let menu_bar_bg = self.bg.unwrap_or_else(|| {
33 let is_light_theme = self.ui.interaction_state.get(&zenthra_core::Id::from_u64(999999999)).copied().unwrap_or(0.0) > 0.5;
34 if is_light_theme {
35 Color::rgb(0.90, 0.90, 0.92)
36 } else {
37 Color::rgb(2.0 / 255.0, 2.0 / 255.0, 2.0 / 255.0) }
39 });
40
41 self.ui.container()
42 .full_width()
43 .height(30.0)
44 .bg(menu_bar_bg)
45 .row()
46 .padding_left(8.0)
47 .padding_right(8.0)
48 .show(|ui| {
49 f(ui);
50 });
51
52 let clicked = self.ui.clicked;
54 let active_menu_id = self.ui.interaction_state.get(&active_menu_key).copied().map(|v| v as u64).unwrap_or(0);
55 let hover_flag = self.ui.interaction_state.get(&hover_flag_key).copied().unwrap_or(0.0) > 0.5;
56
57 if active_menu_id != 0 && clicked && !hover_flag {
58 self.ui.interaction_state.insert(active_menu_key, 0.0);
59 self.ui.interaction_state.insert(active_submenu_key, 0.0);
60 self.ui.needs_redraw = true;
61 }
62 }
63}
64
65pub struct MenuBuilder<'u, 'a> {
66 ui: &'u mut Ui<'a>,
67 label: String,
68 id: Id,
69}
70
71impl<'u, 'a> MenuBuilder<'u, 'a> {
72 pub fn new(ui: &'u mut Ui<'a>, label: &str) -> Self {
73 let id = ui.id();
74 Self {
75 ui,
76 label: label.to_string(),
77 id,
78 }
79 }
80
81 pub fn show<F>(self, f: F)
82 where F: FnOnce(&mut Ui) {
83 let active_menu_key = Id::from_u64(999999900);
84 let active_submenu_key = Id::from_u64(999999901);
85 let hover_flag_key = Id::from_u64(999999902);
86
87 let active_menu_id = self.ui.interaction_state.get(&active_menu_key).copied().map(|v| v as u64).unwrap_or(0);
88 let is_currently_active = active_menu_id == self.id.raw();
89
90 let (x, y) = (self.ui.cursor_x, self.ui.cursor_y);
91 let mut text_w = self.label.len() as f32 * 7.0;
92 if let Some(fs) = self.ui.font_system.as_ref() {
93 let mut adapter = zenthra_text::prelude::CosmicFontProvider::new_with_system(fs.clone());
94 let options = zenthra_text::prelude::TextOptions::new().font_size(13.0);
95 adapter.set_layout_size(1000.0, 100.0);
96 let buffer = adapter.shape(&self.label, &options);
97 text_w = buffer.size().0;
98 }
99 let padding_x = 10.0;
100 let w = text_w + padding_x * 2.0;
101 let h = 26.0;
102
103 let (actual_ox, actual_oy, actual_w, actual_h) = if let Some((rect, _)) = self.ui.get_recorded_layout(self.id) {
104 (
105 rect.origin.x + self.ui.offset_x,
106 rect.origin.y + self.ui.offset_y,
107 rect.size.width.max(w),
108 rect.size.height.max(h)
109 )
110 } else {
111 (x + self.ui.offset_x, y + self.ui.offset_y, w, h)
112 };
113
114 let is_hovered = self.ui.is_hovered(self.id, actual_ox, actual_oy, actual_w, actual_h);
115
116 if is_hovered {
117 self.ui.interaction_state.insert(hover_flag_key, 1.0);
118 }
119
120 if is_hovered && active_menu_id != 0 && !is_currently_active {
122 self.ui.interaction_state.insert(active_menu_key, self.id.raw() as f32);
123 self.ui.interaction_state.remove(&active_submenu_key);
124 self.ui.needs_redraw = true;
125 }
126
127 if self.ui.clicked && is_hovered {
129 if is_currently_active {
130 self.ui.interaction_state.insert(active_menu_key, 0.0);
131 } else {
132 self.ui.interaction_state.insert(active_menu_key, self.id.raw() as f32);
133 }
134 self.ui.interaction_state.remove(&active_submenu_key);
135 self.ui.needs_redraw = true;
136 }
137
138 let is_light_theme = self.ui.interaction_state.get(&Id::from_u64(999999999)).copied().unwrap_or(0.0) > 0.5;
139
140 let bg_color = if is_light_theme {
141 if is_currently_active {
142 Color::rgb(0.80, 0.80, 0.85)
143 } else if is_hovered {
144 Color::rgb(0.88, 0.88, 0.90)
145 } else {
146 Color::TRANSPARENT
147 }
148 } else {
149 if is_currently_active || is_hovered {
150 Color::rgb(3.0 / 255.0, 3.0 / 255.0, 3.0 / 255.0) } else {
152 Color::TRANSPARENT
153 }
154 };
155
156 let text_color = if is_light_theme {
157 Color::rgb(0.1, 0.1, 0.15)
158 } else {
159 if is_currently_active || is_hovered {
160 Color::rgb(255.0 / 255.0, 214.0 / 255.0, 0.0 / 255.0) } else {
162 Color::rgb(224.0 / 255.0, 224.0 / 255.0, 224.0 / 255.0) }
164 };
165
166 let start_draw = self.ui.draws.len();
167 self.ui.draws.push(DrawCommand::Rect(RectDraw {
168 instance: RectInstance {
169 pos: [x, y + 2.0],
170 size: [w, h],
171 color: bg_color.to_array(),
172 radius: [3.0; 4],
173 ..Default::default()
174 }
175 }));
176
177 self.ui.draws.push(DrawCommand::Text(TextDraw {
178 text: self.label.clone(),
179 pos: [x + (w - text_w) / 2.0, y + 6.0],
180 options: zenthra_text::prelude::TextOptions::new().font_size(13.0).color(text_color),
181 clip: [x, y, w, h + 4.0],
182 }));
183
184 self.ui.record_layout(self.id, Rect::new(x, y, w, h + 4.0));
185 self.ui.advance(w, h + 4.0, start_draw);
186
187 if is_currently_active {
188 let popup_id = Id::from_u64((self.id.raw() << 8) | 10);
189
190 if let Some(rect) = self.ui.screen_layout_cache.get(&popup_id) {
192 if self.ui.mouse_in_rect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height) {
193 self.ui.interaction_state.insert(hover_flag_key, 1.0);
194 }
195 }
196
197 let prev_child_ranges = std::mem::take(&mut self.ui.child_draw_ranges);
199 let prev_id_ranges = std::mem::take(&mut self.ui.id_ranges);
200 let prev_id_log = std::mem::take(&mut self.ui.id_log);
201 let prev_child_sizes = std::mem::take(&mut self.ui.child_sizes);
202 let prev_child_origins = std::mem::take(&mut self.ui.child_origins);
203
204 let popup_bg = if is_light_theme {
205 Color::WHITE
206 } else {
207 Color::rgb(1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0) };
209
210 let popup_border = if is_light_theme {
211 Color::rgb(0.8, 0.8, 0.85)
212 } else {
213 Color::rgb(3.0 / 255.0, 3.0 / 255.0, 3.0 / 255.0) };
215
216 let popup_x = if let Some((rect, _)) = self.ui.get_recorded_layout(self.id) {
217 rect.origin.x
218 } else {
219 x
220 };
221 let popup_y = if let Some((rect, _)) = self.ui.get_recorded_layout(self.id) {
222 rect.origin.y
223 } else {
224 y
225 };
226
227 self.ui.container()
228 .id(popup_id)
229 .absolute(popup_x, popup_y + h + 2.0)
230 .overlay()
231 .width(270.0)
232 .bg(popup_bg)
233 .border(popup_border, 1.0)
234 .radius_all(4.0)
235 .padding(4.0, 4.0, 4.0, 4.0)
236 .column()
237 .show(|ui| {
238 f(ui);
239 });
240
241 let _ = std::mem::take(&mut self.ui.child_draw_ranges);
243 let _ = std::mem::take(&mut self.ui.id_ranges);
244 let _ = std::mem::take(&mut self.ui.id_log);
245 let _ = std::mem::take(&mut self.ui.child_sizes);
246 let _ = std::mem::take(&mut self.ui.child_origins);
247
248 self.ui.child_draw_ranges = prev_child_ranges;
249 self.ui.id_ranges = prev_id_ranges;
250 self.ui.id_log = prev_id_log;
251 self.ui.child_sizes = prev_child_sizes;
252 self.ui.child_origins = prev_child_origins;
253 }
254 }
255}
256
257pub struct SubMenuBuilder<'u, 'a> {
258 ui: &'u mut Ui<'a>,
259 label: String,
260 id: Id,
261}
262
263impl<'u, 'a> SubMenuBuilder<'u, 'a> {
264 pub fn new(ui: &'u mut Ui<'a>, label: &str) -> Self {
265 let id = ui.id();
266 Self {
267 ui,
268 label: label.to_string(),
269 id,
270 }
271 }
272
273 pub fn show<F>(self, f: F)
274 where F: FnOnce(&mut Ui) {
275 let active_submenu_key = Id::from_u64(999999901);
276 let hover_flag_key = Id::from_u64(999999902);
277
278 let active_submenu_id = self.ui.interaction_state.get(&active_submenu_key).copied().map(|v| v as u64).unwrap_or(0);
279 let is_currently_active = active_submenu_id == self.id.raw();
280
281 let (x, y) = (self.ui.cursor_x, self.ui.cursor_y);
282 let w = 262.0;
283 let h = 26.0;
284
285 let (actual_ox, actual_oy, actual_w, actual_h) = if let Some((rect, _)) = self.ui.get_recorded_layout(self.id) {
286 (
287 rect.origin.x + self.ui.offset_x,
288 rect.origin.y + self.ui.offset_y,
289 rect.size.width.max(w),
290 rect.size.height.max(h)
291 )
292 } else {
293 (x + self.ui.offset_x, y + self.ui.offset_y, w, h)
294 };
295
296 let is_hovered = self.ui.is_hovered(self.id, actual_ox, actual_oy, actual_w, actual_h);
297
298 if is_hovered {
299 self.ui.interaction_state.insert(hover_flag_key, 1.0);
300 }
301
302 if is_hovered && !is_currently_active {
304 self.ui.interaction_state.insert(active_submenu_key, self.id.raw() as f32);
305 self.ui.needs_redraw = true;
306 }
307
308 let is_light_theme = self.ui.interaction_state.get(&Id::from_u64(999999999)).copied().unwrap_or(0.0) > 0.5;
309
310 let bg_color = if is_light_theme {
311 if is_currently_active || is_hovered {
312 Color::rgb(0.90, 0.90, 0.95)
313 } else {
314 Color::TRANSPARENT
315 }
316 } else {
317 if is_currently_active || is_hovered {
318 Color::rgb(3.0 / 255.0, 3.0 / 255.0, 3.0 / 255.0) } else {
320 Color::TRANSPARENT
321 }
322 };
323
324 let text_color = if is_light_theme {
325 Color::rgb(0.1, 0.1, 0.15)
326 } else {
327 if is_currently_active || is_hovered {
328 Color::rgb(255.0 / 255.0, 214.0 / 255.0, 0.0 / 255.0) } else {
330 Color::rgb(224.0 / 255.0, 224.0 / 255.0, 224.0 / 255.0) }
332 };
333
334 let chevron_color = if is_light_theme {
335 Color::rgb(0.5, 0.5, 0.5)
336 } else {
337 if is_currently_active || is_hovered {
338 Color::rgb(255.0 / 255.0, 214.0 / 255.0, 0.0 / 255.0) } else {
340 Color::rgb(102.0 / 255.0, 102.0 / 255.0, 102.0 / 255.0) }
342 };
343
344 let start_draw = self.ui.draws.len();
345
346 self.ui.draws.push(DrawCommand::Rect(RectDraw {
347 instance: RectInstance {
348 pos: [x, y],
349 size: [w, h],
350 color: bg_color.to_array(),
351 radius: [3.0; 4],
352 ..Default::default()
353 }
354 }));
355
356 self.ui.draws.push(DrawCommand::Text(TextDraw {
357 text: self.label.clone(),
358 pos: [x + 8.0, y + 6.0],
359 options: zenthra_text::prelude::TextOptions::new().font_size(13.0).color(text_color),
360 clip: [x, y, w, h],
361 }));
362
363 self.ui.draws.push(DrawCommand::Text(TextDraw {
365 text: crate::icons::NF_FA_CHEVRON_RIGHT.to_string(),
366 pos: [x + w - 15.0, y + 6.0],
367 options: zenthra_text::prelude::TextOptions::new().font_size(12.0).color(chevron_color),
368 clip: [x, y, w, h],
369 }));
370
371 self.ui.record_layout(self.id, Rect::new(x, y, w, h));
372 self.ui.advance(w, h, start_draw);
373
374 if is_currently_active {
375 let sub_popup_id = Id::from_u64((self.id.raw() << 8) | 11);
376
377 if let Some(rect) = self.ui.screen_layout_cache.get(&sub_popup_id) {
379 if self.ui.mouse_in_rect(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height) {
380 self.ui.interaction_state.insert(hover_flag_key, 1.0);
381 }
382 }
383
384 let prev_child_ranges = std::mem::take(&mut self.ui.child_draw_ranges);
386 let prev_id_ranges = std::mem::take(&mut self.ui.id_ranges);
387 let prev_id_log = std::mem::take(&mut self.ui.id_log);
388 let prev_child_sizes = std::mem::take(&mut self.ui.child_sizes);
389 let prev_child_origins = std::mem::take(&mut self.ui.child_origins);
390
391 let popup_bg = if is_light_theme {
392 Color::WHITE
393 } else {
394 Color::rgb(1.0 / 255.0, 1.0 / 255.0, 1.0 / 255.0) };
396
397 let popup_border = if is_light_theme {
398 Color::rgb(0.8, 0.8, 0.85)
399 } else {
400 Color::rgb(3.0 / 255.0, 3.0 / 255.0, 3.0 / 255.0) };
402
403 self.ui.container()
404 .id(sub_popup_id)
405 .absolute(x + w + 4.0, y)
406 .overlay()
407 .width(270.0)
408 .bg(popup_bg)
409 .border(popup_border, 1.0)
410 .radius_all(4.0)
411 .padding(4.0, 4.0, 4.0, 4.0)
412 .column()
413 .show(|ui| {
414 f(ui);
415 });
416
417 let _ = std::mem::take(&mut self.ui.child_draw_ranges);
419 let _ = std::mem::take(&mut self.ui.id_ranges);
420 let _ = std::mem::take(&mut self.ui.id_log);
421 let _ = std::mem::take(&mut self.ui.child_sizes);
422 let _ = std::mem::take(&mut self.ui.child_origins);
423
424 self.ui.child_draw_ranges = prev_child_ranges;
425 self.ui.id_ranges = prev_id_ranges;
426 self.ui.id_log = prev_id_log;
427 self.ui.child_sizes = prev_child_sizes;
428 self.ui.child_origins = prev_child_origins;
429 }
430 }
431}
432
433pub struct MenuItemBuilder<'u, 'a> {
434 ui: &'u mut Ui<'a>,
435 label: String,
436 shortcut: Option<String>,
437 id: Id,
438}
439
440impl<'u, 'a> MenuItemBuilder<'u, 'a> {
441 pub fn new(ui: &'u mut Ui<'a>, label: &str) -> Self {
442 let id = ui.id();
443 Self {
444 ui,
445 label: label.to_string(),
446 shortcut: None,
447 id,
448 }
449 }
450
451 pub fn shortcut(mut self, shortcut: &str) -> Self {
452 self.shortcut = Some(shortcut.to_string());
453 self
454 }
455
456 pub fn show(self) -> Response {
457 let hover_flag_key = Id::from_u64(999999902);
458
459 let (x, y) = (self.ui.cursor_x, self.ui.cursor_y);
460 let w = 262.0;
461 let h = 26.0;
462
463 let (actual_ox, actual_oy, actual_w, actual_h) = if let Some((rect, _)) = self.ui.get_recorded_layout(self.id) {
464 (
465 rect.origin.x + self.ui.offset_x,
466 rect.origin.y + self.ui.offset_y,
467 rect.size.width.max(w),
468 rect.size.height.max(h)
469 )
470 } else {
471 (x + self.ui.offset_x, y + self.ui.offset_y, w, h)
472 };
473
474 let is_hovered = self.ui.is_hovered(self.id, actual_ox, actual_oy, actual_w, actual_h);
475
476 if is_hovered {
477 self.ui.interaction_state.insert(hover_flag_key, 1.0);
478 }
479
480 let is_light_theme = self.ui.interaction_state.get(&Id::from_u64(999999999)).copied().unwrap_or(0.0) > 0.5;
481
482 let bg_color = if is_light_theme {
483 if is_hovered {
484 Color::rgb(0.9, 0.9, 0.95)
485 } else {
486 Color::TRANSPARENT
487 }
488 } else {
489 if is_hovered {
490 Color::rgb(3.0 / 255.0, 3.0 / 255.0, 3.0 / 255.0) } else {
492 Color::TRANSPARENT
493 }
494 };
495
496 let text_color = if is_light_theme {
497 Color::rgb(0.1, 0.1, 0.15)
498 } else {
499 if is_hovered {
500 Color::rgb(255.0 / 255.0, 214.0 / 255.0, 0.0 / 255.0) } else {
502 Color::rgb(224.0 / 255.0, 224.0 / 255.0, 224.0 / 255.0) }
504 };
505
506 let shortcut_color = if is_light_theme {
507 Color::rgb(0.5, 0.5, 0.5)
508 } else {
509 if is_hovered {
510 Color::rgb(255.0 / 255.0, 214.0 / 255.0, 0.0 / 255.0) } else {
512 Color::rgb(102.0 / 255.0, 102.0 / 255.0, 102.0 / 255.0) }
514 };
515
516 let start_draw = self.ui.draws.len();
517
518 self.ui.draws.push(DrawCommand::Rect(RectDraw {
519 instance: RectInstance {
520 pos: [x, y],
521 size: [w, h],
522 color: bg_color.to_array(),
523 radius: [3.0; 4],
524 ..Default::default()
525 }
526 }));
527
528 self.ui.draws.push(DrawCommand::Text(TextDraw {
529 text: self.label.clone(),
530 pos: [x + 8.0, y + 6.0],
531 options: zenthra_text::prelude::TextOptions::new().font_size(13.0).color(text_color),
532 clip: [x, y, w, h],
533 }));
534
535 if let Some(sh) = self.shortcut {
536 self.ui.draws.push(DrawCommand::Text(TextDraw {
537 text: sh,
538 pos: [x + w - 80.0, y + 6.0],
539 options: zenthra_text::prelude::TextOptions::new().font_size(12.0).color(shortcut_color),
540 clip: [x, y, w, h],
541 }));
542 }
543
544 self.ui.record_layout(self.id, Rect::new(x, y, w, h));
545 self.ui.advance(w, h, start_draw);
546
547 let clicked = self.ui.clicked && is_hovered;
548 if clicked {
549 let active_menu_key = Id::from_u64(999999900);
551 let active_submenu_key = Id::from_u64(999999901);
552 self.ui.interaction_state.insert(active_menu_key, 0.0);
553 self.ui.interaction_state.insert(active_submenu_key, 0.0);
554 self.ui.needs_redraw = true;
555 }
556
557 Response {
558 clicked,
559 hovered: is_hovered,
560 pressed: is_hovered && self.ui.mouse_down,
561 }
562 }
563}