1use alloc::string::String;
42
43use super::ctx::Hit;
44
45use super::consts::*;
46use super::keys::*;
47use super::layout::BOX_STACK_MAX;
48use super::win::{Shared, UWI_BUILDING, WCH_BASE, WCH_BTN, WCH_CLOSE, WCH_CONTENT, WCH_FOOTER, WCH_ROLL};
49use super::{Sp, Ui};
50
51const TB_ID: u32 = 0xFFFF_7400;
52const TB_MIN_ID: u32 = 0xFFFF_7401;
53const TB_MAX_ID: u32 = 0xFFFF_7402;
54const TB_CLOSE_ID: u32 = 0xFFFF_7403;
55const SB_ID: u32 = 0xFFFF_7410;
56const SB_INNER_ID: u32 = 0xFFFF_7411;
57const SB_SCROLL_ID: u32 = 0xFFFF_7412; const GRIP_ID: u32 = 0xFFFF_7430; const WMR_MOVE: u32 = 1;
63const WMR_MENU: u32 = 2;
64const WMR_CMD: u32 = 3; const WMR_RESIZE: u32 = 4; #[derive(Default)]
69pub(crate) struct S {
70 cap_w: i32,
72 grip_base: u32,
74 grip_off: u32,
76 pub(crate) wm_req: u32,
78}
79
80pub(crate) fn wm_req_name(r: u32) -> String {
83 let arg = r >> 8;
84 let s = match r & 255 {
85 WMR_MOVE => "move",
86 WMR_MENU => "menu",
87 WMR_CMD => match arg {
88 plat::WCMD_MINIMIZE => "minimize",
89 plat::WCMD_MAXIMIZE => "maximize",
90 plat::WCMD_RESTORE => "restore",
91 plat::WCMD_CLOSE => "close",
92 plat::WCMD_LOWER => "lower",
93 plat::WCMD_FULLSCREEN => "fullscreen",
94 plat::WCMD_WINDOWED => "windowed",
95 _ => "command",
96 },
97 WMR_RESIZE => {
98 let mut e = String::from("resize ");
99 e.push_str(if arg & plat::EDGE_TOP != 0 { "n" } else if arg & plat::EDGE_BOTTOM != 0 { "s" } else { "" });
100 e.push_str(if arg & plat::EDGE_LEFT != 0 { "w" } else if arg & plat::EDGE_RIGHT != 0 { "e" } else { "" });
101 return e;
102 }
103 _ => "none",
104 };
105 String::from(s)
106}
107
108#[derive(Default)]
111pub(crate) struct W {
112 sb_scroll: bool,
114 dlg_clip: [i32; 4],
115 dlg_openp: Option<Shared<i32>>,
116 pub(crate) dlg_active: bool,
117 dlg_nest: u32,
118 dlg_id: u32,
119 dlg_phase: u32,
120 dlg_layer: u32,
121 dlg_dis: u32,
122 pub(crate) dlg_fresh: bool,
124 tb_state: u32,
125 tb_overlay: bool,
127 pub(crate) tb_rect: Option<[i32; 4]>,
129 dlg_roll: bool,
133 wc_roll: bool,
134 wc_footer: bool,
135 cap_hits: [Hit; 4],
139 pub(crate) cap_n: u8,
140}
141
142fn dlg_part(id: u32, k: u32) -> u32 {
144 id.wrapping_mul(8).wrapping_add(k) | 0x8000_0000
145}
146
147impl Ui {
148 #[cfg_attr(jfgui_dev, track_caller)]
152 pub fn dialog_begin(&mut self, id: u32, title: &str, open: &Shared<i32>) -> bool {
153 let _src = self.src_scope();
154 if self.wp.chrome.dlg_active {
155 self.wp.chrome.dlg_nest += 1; return false;
157 }
158 if open.get() == 0 {
160 let s = self.slot(id);
162 if s.aux != 0 {
163 s.aux = 0;
164 let f = s.scrollx as u32;
165 self.wp.ctx.focus = f;
166 self.repass();
167 }
168 return false;
169 }
170 {
171 let c = &mut self.wp.chrome;
172 c.dlg_active = true;
173 c.dlg_id = id;
174 c.dlg_openp = Some(open.clone());
175 c.dlg_phase = 1;
176 c.dlg_fresh = false;
177 }
178 let focus = self.wp.ctx.focus;
179 let s = self.slot(id);
180 if s.aux == 0 {
181 s.aux = 1;
183 s.scrollx = focus as i32;
184 self.wp.ctx.focus = id;
185 self.wp.ctx.focus_visible = false;
186 self.wp.chrome.dlg_fresh = true;
187 }
188 self.wp.chrome.dlg_layer = self.layer_set(L_DIALOG);
189 self.modal(L_DIALOG);
190 self.wp.chrome.dlg_clip = self.wp.ctx.uiclip;
192 let (w, h) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
193 self.wp.ctx.uiclip = [0, 0, w, h];
194 self.wp.chrome.dlg_dis = self.wp.ctx.disabled_depth;
195 self.wp.ctx.disabled_depth = 0;
196 let st = self.style;
198 let sp = Sp { x: 0, y: 0, w, h, ..Default::default() };
199 st.dialog_smoke(self, &sp);
200 self.wp.ctx.last_rect = [0, 0, w, h];
201 self.hit(dlg_part(id, 1), HB_BLOCK);
202 let (mut dw, nh) = {
204 let s = self.slot(id);
205 (s.measw, s.meash)
206 };
207 let min = self.dp(st.metric(SM_DLG_MIN));
208 if dw < min {
209 dw = min;
210 }
211 let max = self.dp(st.metric(SM_DLG_MAX));
212 if dw > max {
213 dw = max;
214 }
215 let m = self.dp(st.metric(SM_DLG_MARGIN)); if dw > w - m {
217 dw = w - m;
218 }
219 if dw < 1 {
220 dw = 1;
221 }
222 let x = (w - dw) >> 1;
223 let roll = self.mobile() && nh > h - m;
226 self.wp.chrome.dlg_roll = roll;
227 let (y, dh) = if roll { (m >> 1, h - m) } else { (((h - nh) >> 1).max(0), 0) };
228 self.next_rect_px(x, y, dw, dh);
229 self.box_begin(id, BOX_V | BOX_BLOCK, 0);
230 let r = self.wp.ctx.last_rect;
231 let fh = self.slot(dlg_part(id, 4)).meash;
233 let mut sp = Sp { value: fh, ..Default::default() };
235 sp.place(r);
236 st.dialog_frame(self, &sp);
237 self.wp.ctx.last_rect = r;
239 self.wp.ctx.last_state = SS_OPEN as u32;
240 self.sem(K_DIALOG, id, Some(title), 0);
241 let gap = st.metric(SM_DLG_CONTENT_GAP);
243 let flags = st.metric(SM_DLG_CONTENT_BOX) as u32;
244 if roll {
245 self.next_size(0, -1);
246 self.scroll_begin(dlg_part(id, 2), SC_V); self.next_size(-1, 0);
248 }
249 self.box_begin(dlg_part(id, 3), flags, gap);
250 let ts = st.metric(SM_DLG_TITLE_TS) as u32;
251 self.text(ts, title);
252 true
253 }
254
255 #[cfg_attr(jfgui_dev, track_caller)]
259 pub fn dialog_buttons(&mut self, primary: Option<&str>, secondary: Option<&str>, close: Option<&str>) -> u32 {
260 let _src = self.src_scope();
261 if self.wp.chrome.dlg_active {
262 let c = &mut self.wp.chrome;
263 if c.dlg_nest != 0 || c.dlg_phase != 1 {
264 return DLG_NONE;
265 }
266 c.dlg_phase = 2;
267 let id = c.dlg_id;
268 self.box_end(); if self.wp.chrome.dlg_roll {
270 self.scroll_end();
271 }
272 let r = self.dlg_footer(dlg_part(id, 4), dlg_part(id, 5), primary, secondary, close);
273 if r != 0 {
274 if let Some(o) = &self.wp.chrome.dlg_openp {
275 o.set(0);
276 }
277 self.repass();
278 }
279 return r;
280 }
281 if self.wp.chrome.wc_footer {
283 return DLG_NONE;
284 }
285 let cur = self.uiw_cur;
286 if cur == 0 {
287 return DLG_NONE;
288 }
289 let rec = &self.m.win.recs[(cur as usize) & (WIN_MAX - 1)];
290 if rec.flags & WF_DIALOG == 0 || rec.state & UWI_BUILDING == 0 {
291 return DLG_NONE;
292 }
293 let openp = rec.openp.clone();
294 self.wp.chrome.wc_footer = true;
295 let ids = cur << 4 | WCH_BASE;
296 self.wc_pop_to(ids + WCH_CONTENT);
297 if core::mem::take(&mut self.wp.chrome.wc_roll) {
298 self.scroll_end();
299 }
300 let r = self.dlg_footer(ids + WCH_FOOTER, ids + WCH_BTN, primary, secondary, close);
301 if r != 0 {
302 if let Some(o) = openp {
303 o.set(0);
304 }
305 self.repass();
306 }
307 r
308 }
309
310 fn dlg_footer(&mut self, fid: u32, bid: u32, p: Option<&str>, s: Option<&str>, c: Option<&str>) -> u32 {
313 let st = self.style;
314 let gap = st.metric(SM_DLG_FOOTER_GAP);
315 let flags = st.metric(SM_DLG_FOOTER_BOX) as u32;
316 self.box_begin(fid, flags, gap);
317 let order = st.metric(SM_DLG_BTN_ORDER);
318 let mut r = DLG_NONE;
319 if order == 0 {
320 let n = p.is_some() as u32 + s.is_some() as u32 + c.is_some() as u32;
322 if n == 1 {
323 self.spacer(-1);
324 }
325 if let Some(l) = p {
326 self.next_size(-1, 0);
327 if self.accent_button(bid, l) & EV_ACT != 0 {
328 r = DLG_PRIMARY;
329 }
330 }
331 if let Some(l) = s {
332 self.next_size(-1, 0);
333 if self.button(bid + 1, l) & EV_ACT != 0 {
334 r = DLG_SECONDARY;
335 }
336 }
337 if let Some(l) = c {
338 self.next_size(-1, 0);
339 if self.button(bid + 2, l) & EV_ACT != 0 {
340 r = DLG_CLOSE;
341 }
342 }
343 } else {
344 self.spacer(-1);
347 if order != 2 {
348 if let Some(l) = c {
349 if self.button(bid + 2, l) & EV_ACT != 0 {
350 r = DLG_CLOSE;
351 }
352 }
353 if let Some(l) = s {
354 if self.button(bid + 1, l) & EV_ACT != 0 {
355 r = DLG_SECONDARY;
356 }
357 }
358 if let Some(l) = p {
359 if self.accent_button(bid, l) & EV_ACT != 0 {
360 r = DLG_PRIMARY;
361 }
362 }
363 } else {
364 if let Some(l) = p {
365 if self.accent_button(bid, l) & EV_ACT != 0 {
366 r = DLG_PRIMARY;
367 }
368 }
369 if let Some(l) = s {
370 if self.button(bid + 1, l) & EV_ACT != 0 {
371 r = DLG_SECONDARY;
372 }
373 }
374 if let Some(l) = c {
375 if self.button(bid + 2, l) & EV_ACT != 0 {
376 r = DLG_CLOSE;
377 }
378 }
379 }
380 }
381 self.box_end(); if r != 0 {
384 return r;
385 }
386 if p.is_some() && self.key(VK_RETURN) {
387 return DLG_PRIMARY;
388 }
389 if self.key(VK_ESCAPE) {
390 return DLG_CLOSE;
391 }
392 DLG_NONE
393 }
394
395 #[cfg_attr(jfgui_dev, track_caller)]
397 pub fn dialog_end(&mut self) {
398 let _src = self.src_scope();
399 if self.wp.chrome.dlg_nest != 0 {
400 self.wp.chrome.dlg_nest -= 1;
401 return;
402 }
403 if !self.wp.chrome.dlg_active {
404 return;
405 }
406 if self.wp.chrome.dlg_phase == 1 {
407 if self.key(VK_ESCAPE) {
409 if let Some(o) = &self.wp.chrome.dlg_openp {
410 o.set(0);
411 }
412 self.repass();
413 }
414 self.box_end(); if self.wp.chrome.dlg_roll {
416 self.scroll_end();
417 }
418 }
419 self.wp.chrome.dlg_roll = false;
420 self.box_end(); let c = &self.wp.chrome;
422 let (clip, dis, layer) = (c.dlg_clip, c.dlg_dis, c.dlg_layer);
423 self.wp.ctx.uiclip = clip;
424 self.wp.ctx.disabled_depth = dis;
425 self.layer_set(layer);
426 self.wp.chrome.dlg_active = false;
427 }
428
429 #[cfg_attr(jfgui_dev, track_caller)]
433 pub fn infobar(&mut self, id: u32, severity: u32, title: &str, msg: &str) -> u32 {
434 let _src = self.src_scope();
435 let sev = severity & 3;
436 let st = self.style;
437 let mut sp = Sp { flags: sev, label: Some(title), label2: Some(msg), ..Default::default() };
439 let (w, h) = st.infobar_size(self, &mut sp);
440 self.place(w, h);
441 let r = self.wp.ctx.last_rect;
442 let fit = if w > r[2] { Some(self.infobar_fit(title, msg, sp.s[0], sp.s[1], w - r[2])) } else { None };
445 if let Some((t, m)) = &fit {
446 sp.label = Some(t);
447 sp.label2 = if m.is_empty() { None } else { Some(m) };
448 st.infobar_size(self, &mut sp);
449 }
450 sp.place(r);
452 st.infobar(self, &mut sp);
453 let (cx, cs) = (sp.s[2], sp.s[3]);
454 self.wp.ctx.last_rect = [cx, ((r[3] - cs) >> 1) + r[1], cs, cs];
455 let ev = self.behave(id.wrapping_add(1), HB_FOCUS);
456 sp.place(self.wp.ctx.last_rect);
458 sp.ev = ev;
459 st.infobar_close(self, &sp);
460 if ev & EV_FOCUS != 0 && self.wp.ctx.focus_visible {
461 let lr = self.wp.ctx.last_rect;
462 st.focus_rect(self, lr[0], lr[1], lr[2], lr[3]);
463 }
464 self.sem(K_BUTTON, id.wrapping_add(1), Some("Close"), 0);
465 self.wp.ctx.last_rect = r;
467 self.wp.ctx.last_state = 0;
468 self.sem(K_BOX, id, Some(title), sev as i32);
469 ev & EV_ACT
470 }
471
472 fn infobar_fit(&mut self, title: &str, msg: &str, tw: i32, mw: i32, over: i32) -> (String, String) {
476 let keep = mw - over;
477 if !msg.is_empty() && keep >= self.dp(32) {
478 self.ts_font(TS_BODY);
479 return (String::from(title), self.ellipsize(msg.as_bytes(), keep).0);
480 }
481 let over = over - mw;
482 if over <= 0 {
483 return (String::from(title), String::new());
484 }
485 self.ts_font(TS_BODY_STRONG);
486 (self.ellipsize(title.as_bytes(), (tw - over).max(1)).0, String::new())
487 }
488
489 #[cfg_attr(jfgui_dev, track_caller)]
493 pub fn statusbar_begin(&mut self) {
494 let _src = self.src_scope();
495 let st = self.style;
496 let h = st.metric(SM_SB_H); self.next_size(0, h);
498 self.box_begin(SB_ID, BOX_H, 0);
499 let r = self.wp.ctx.last_rect;
500 let mut sp = Sp::default();
501 sp.place(r);
502 st.statusbar(self, &sp);
503 let pad = self.dp(st.metric(SM_SB_PAD));
505 let mut w = r[2] - pad - pad;
506 if w <= 0 {
507 w = 1;
508 }
509 self.next_rect_px(r[0] + pad, r[1], w, r[3]);
510 let gap = st.metric(SM_SB_GAP); let roll = self.mobile() && self.slot(SB_INNER_ID).fixed > w;
514 self.wp.chrome.sb_scroll = roll;
515 if roll {
516 self.scroll_begin(SB_SCROLL_ID, SC_H | SC_NOBAR | BOX_XCENTER);
517 }
518 self.box_begin(SB_INNER_ID, BOX_H | BOX_XCENTER, gap);
519 }
520
521 #[cfg_attr(jfgui_dev, track_caller)]
523 pub fn statusbar_end(&mut self) {
524 let _src = self.src_scope();
525 self.box_end();
526 if core::mem::take(&mut self.wp.chrome.sb_scroll) {
527 self.scroll_end();
528 }
529 self.box_end();
530 }
531
532 #[cfg_attr(jfgui_dev, track_caller)]
536 pub fn titlebar_begin(&mut self, title: &str, icon: u32) {
537 let _src = self.src_scope();
538 self.titlebar_begin_opt(Some(title), icon);
539 }
540
541 #[cfg_attr(jfgui_dev, track_caller)]
543 pub fn titlebar_begin_opt(&mut self, title: Option<&str>, icon: u32) {
544 let _src = self.src_scope();
545 self.titlebar_begin_inner(title, icon, false);
546 }
547
548 #[cfg_attr(jfgui_dev, track_caller)]
552 pub fn titlebar_begin_overlay(&mut self, title: Option<&str>, icon: u32) {
553 let _src = self.src_scope();
554 self.titlebar_begin_inner(title, icon, true);
555 }
556
557 fn titlebar_begin_inner(&mut self, title: Option<&str>, icon: u32, overlay: bool) {
558 let st = self.style;
559 self.wp.chrome.tb_overlay = overlay;
560 let f = self.take_flags();
561 let h = st.metric(if f & ST_LARGE != 0 { SM_TB_H_LARGE } else { SM_TB_H });
562 self.next_size(0, h);
563 self.box_begin(TB_ID, BOX_H | BOX_XCENTER, 0);
564 self.wp.chrome.tb_state = if self.headless { plat::WST_ACTIVE } else { plat::window_state() };
566 let tbs = self.wp.chrome.tb_state;
567 let mut sp = Sp { value: (tbs & plat::WST_ACTIVE != 0) as i32, ..Default::default() };
569 sp.place(self.wp.ctx.last_rect);
570 if !overlay { st.window_titlebar(self, &sp); }
571 self.wp.chrome.tb_rect = if overlay { None } else { Some(self.wp.ctx.last_rect) };
572 let ev = self.behave(TB_ID, HB_DRAG);
574 self.tb_clicks(ev, tbs);
575 self.wp.ctx.last_state = 0;
576 self.sem(K_TITLEBAR, TB_ID, title, 0);
577 let left = self.tb_side(false);
579 if left[0] != 0 {
580 if st.metric(SM_CAP_LEFT) != 0 {
581 self.spacer(st.metric(SM_TB_PAD));
582 }
583 self.tb_caps(left);
584 }
585 self.spacer(st.metric(SM_TB_PAD)); if icon != 0 {
588 let s = st.metric(SM_TB_ICON); if s != 0 {
590 self.icon(icon, s);
591 self.spacer(st.metric(SM_TB_PAD));
592 }
593 }
594 if self.agent_enabled() && !overlay {
595 self.next_size(82, 24);
596 if self.button(super::agent::ID_BADGE, if self.agent_connected() { "Agent ●" } else { "Agent ○" }) & EV_ACT != 0 {
597 self.set_agent_enabled(false);
598 }
599 self.tooltip(if self.agent_connected() { "Agent verbunden · Zum Abschalten klicken" } else { "Agentensteuerung aktiv · Zum Abschalten klicken" });
600 self.spacer(8);
601 }
602 let Some(title) = title else { return };
603 let mut ts = st.metric(SM_TB_TITLE_TS) as u32;
604 if tbs & plat::WST_ACTIVE == 0 {
605 ts |= TX_DISABLED;
606 }
607 self.text(ts, title);
608 }
609
610 #[cfg_attr(jfgui_dev, track_caller)]
613 pub fn titlebar_end(&mut self) {
614 let _src = self.src_scope();
615 let st = self.style;
616 if self.wp.chrome.tb_overlay {
617 self.spacer(-1);
618 if self.agent_enabled() {
619 self.next_size(82, 24);
620 if self.button(super::agent::ID_BADGE, if self.agent_connected() { "Agent ●" } else { "Agent ○" }) & EV_ACT != 0 {
621 self.set_agent_enabled(false);
622 }
623 self.tooltip(if self.agent_connected() { "Agent verbunden · Zum Abschalten klicken" } else { "Agentensteuerung aktiv · Zum Abschalten klicken" });
624 self.spacer(8);
625 }
626 }
627 let right = self.tb_side(true);
628 if right[0] != 0 {
629 if !self.wp.chrome.tb_overlay { self.spacer(-1); }
630 self.tb_caps(right);
631 if st.metric(SM_CAP_LEFT) != 0 {
632 self.spacer(st.metric(SM_TB_PAD)); }
634 }
635 self.box_end();
636 self.wp.chrome.tb_rect = None;
637 }
638
639 pub fn titlebar_open(&self) -> Option<([i32; 4], bool)> {
644 let c = &self.wp.chrome;
645 c.tb_rect.map(|r| (r, c.tb_state & plat::WST_ACTIVE != 0))
646 }
647
648 fn tb_clicks(&mut self, ev: u32, tbs: u32) {
654 let tb = self.tb_sys;
655 let c = &self.wp.ctx;
656 let (act, btn) = if ev & EV_DOUBLE != 0 && tb.double != plat::TB_ACT_NONE {
657 (tb.double, BTN_LEFT)
658 } else if c.active == TB_ID && c.pressed & (1 << BTN_LEFT) != 0 {
659 (u8::MAX, BTN_LEFT) } else if c.hot == TB_ID && c.pressed & (1 << BTN_MIDDLE) != 0 && tb.middle != plat::TB_ACT_NONE {
661 (tb.middle, BTN_MIDDLE)
662 } else if c.ractive == TB_ID && c.pressed & (1 << BTN_RIGHT) != 0 && tb.right != plat::TB_ACT_NONE {
663 (tb.right, BTN_RIGHT)
664 } else {
665 return;
666 };
667 let (mx, my) = (c.mx, c.my);
668 match act {
669 u8::MAX => self.wm_move(),
670 plat::TB_ACT_MAXIMIZE => self.tb_cmd(if tbs & plat::WST_MAXIMIZED != 0 { plat::WCMD_RESTORE } else { plat::WCMD_MAXIMIZE }),
671 plat::TB_ACT_MINIMIZE => self.tb_cmd(plat::WCMD_MINIMIZE),
672 plat::TB_ACT_LOWER => self.tb_cmd(plat::WCMD_LOWER),
673 _ => self.wm_menu(mx, my),
674 }
675 if self.headless {
676 return;
677 }
678 let c = &mut self.wp.ctx;
680 match btn {
681 BTN_LEFT => c.active = 0,
682 BTN_RIGHT => c.ractive = 0,
683 _ => {}
684 }
685 c.mbtn &= !(1 << btn);
686 }
687
688 fn wm_move(&mut self) {
691 if self.headless {
692 self.m.chrome.wm_req = WMR_MOVE;
693 } else {
694 plat::begin_move();
695 }
696 }
697
698 fn wm_menu(&mut self, _x: i32, _y: i32) {
699 if self.headless {
700 self.m.chrome.wm_req = WMR_MENU;
701 return;
702 }
703 #[cfg(target_os = "linux")]
704 plat::window_menu(_x, _y);
705 }
706
707 fn wm_resize(&mut self, edge: u32) {
708 if self.headless {
709 self.m.chrome.wm_req = WMR_RESIZE | edge << 8;
710 } else {
711 plat::begin_resize(edge);
712 }
713 }
714
715 fn tb_side(&self, right: bool) -> [u8; 3] {
719 if self.mobile() && self.uiw_cur == 0 {
720 return [0; 3]; }
722 if let Some((l, r)) = self.tb_sys.buttons {
723 return if right { r } else { l };
724 }
725 match (self.style.metric(SM_CAP_LEFT) != 0, right) {
726 (true, false) => [plat::TB_BTN_CLOSE, plat::TB_BTN_MIN, plat::TB_BTN_MAX],
727 (false, true) => [plat::TB_BTN_MIN, plat::TB_BTN_MAX, plat::TB_BTN_CLOSE],
728 _ => [0; 3],
729 }
730 }
731
732 fn tb_caps(&mut self, b: [u8; 3]) {
734 for k in b {
735 match k {
736 plat::TB_BTN_CLOSE => self.tb_cap_close(),
737 plat::TB_BTN_MIN => self.tb_cap_min(),
738 plat::TB_BTN_MAX => self.tb_cap_max(),
739 _ => break,
740 }
741 }
742 }
743
744 fn tb_cap_min(&mut self) {
747 if self.cap_button(TB_MIN_ID, IC_CHROME_MIN, HB_MIN, "Minimize") & EV_ACT != 0 {
748 self.tb_cmd(plat::WCMD_MINIMIZE);
749 }
750 }
751
752 fn tb_cap_max(&mut self) {
753 let max = self.wp.chrome.tb_state & plat::WST_MAXIMIZED != 0;
754 let (ic, l) = if max { (IC_CHROME_RESTORE, "Restore") } else { (IC_CHROME_MAX, "Maximize") };
755 if self.cap_button(TB_MAX_ID, ic, HB_MAX, l) & EV_ACT != 0 {
756 let max = self.wp.chrome.tb_state & plat::WST_MAXIMIZED != 0;
757 self.tb_cmd(if max { plat::WCMD_RESTORE } else { plat::WCMD_MAXIMIZE });
758 }
759 }
760
761 fn tb_cap_close(&mut self) {
762 if self.cap_button(TB_CLOSE_ID, IC_CHROME_CLOSE, HB_CLOSE, "Close") & EV_ACT != 0 {
763 self.tb_cmd(plat::WCMD_CLOSE);
764 }
765 }
766
767 fn tb_cmd(&mut self, cmd: u32) {
769 if self.headless {
770 self.m.chrome.wm_req = WMR_CMD | cmd << 8;
771 } else {
772 plat::window_cmd(cmd);
773 }
774 }
775
776 fn cap_button(&mut self, id: u32, icon: u32, hb: u32, label: &str) -> u32 {
779 let st = self.style;
780 let bh = self.box_rect()[3]; let w = if self.m.chrome.cap_w != 0 { self.m.chrome.cap_w } else { st.metric(SM_CAP_W) };
782 let w = self.dp(w);
783 self.place(w, bh);
784 let n = self.wp.ctx.hits[self.wp.ctx.hit_cur].len();
785 let ev = self.behave(id, hb);
786 if let Some(h) = self.wp.ctx.hits[self.wp.ctx.hit_cur].get(n).copied() {
788 let c = &mut self.wp.chrome;
789 if let Some(slot) = c.cap_hits.get_mut(c.cap_n as usize) {
790 *slot = h;
791 c.cap_n += 1;
792 }
793 }
794 let active = (self.wp.chrome.tb_state & plat::WST_ACTIVE != 0) as i32;
796 let mut sp = Sp { ev, flags: hb, icon, value: active, ..Default::default() };
797 sp.place(self.wp.ctx.last_rect);
798 st.caption_button(self, &sp);
799 self.sem(K_BUTTON, id, Some(label), 0);
800 ev
801 }
802
803 pub(crate) fn win_chrome_begin(&mut self, i: u32) {
809 let st = self.style;
810 let iu = (i as usize) & (WIN_MAX - 1);
811 let (flags, tbh) = (self.m.win.recs[iu].flags, self.m.win.recs[iu].tbh);
812 let ids = i << 4 | WCH_BASE; self.wp.chrome.wc_footer = false;
814 let mut tbs = 0;
816 if self.uiw_app_active() {
817 tbs = plat::WST_ACTIVE;
818 if flags & WF_TOOL == 0 && i != self.m.win.active {
819 tbs = 0;
820 }
821 }
822 self.wp.chrome.tb_state = tbs;
823 let b = self.uiw_border();
824 if b != 0 {
825 let (w, h) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
827 let sp = Sp { x: 0, y: 0, w, h, value: b, ..Default::default() };
828 st.window_border(self, &sp);
829 }
830 if tbh != 0 {
831 self.next_size(0, tbh);
832 self.box_begin(ids, BOX_H | BOX_XCENTER, 0);
833 let mut sp = Sp { value: (tbs & plat::WST_ACTIVE != 0) as i32, ..Default::default() };
835 sp.place(self.wp.ctx.last_rect);
836 st.window_titlebar(self, &sp);
837 self.behave(ids, HB_DRAG);
840 if self.m.win.recs[(i as usize) & (WIN_MAX - 1)].flags & WF_TOUCH_HOLD != 0 {
841 self.touch_grab(super::touch::AX_ALL | (TF_HOLD | TF_HOLD_LONG) as u8);
842 self.touch_hold_show(ids);
843 }
844 if !self.headless && self.wp.ctx.active == ids && self.wp.ctx.pressed & (1 << BTN_LEFT) != 0 {
845 plat::begin_move(); self.wp.ctx.active = 0; self.wp.ctx.mbtn &= !(1 << BTN_LEFT);
848 } else if self.headless && self.mptype == PTR_TOUCH && self.wp.ctx.active == ids && self.wp.ctx.mbtn & (1 << BTN_LEFT) != 0 {
849 let pressed = self.wp.ctx.pressed & (1 << BTN_LEFT) != 0;
850 self.touch_window_drag(i, pressed); }
852 let title = alloc::string::String::from(self.uiw_title(i));
853 self.wp.ctx.last_state = 0;
854 self.sem(K_TITLEBAR, ids, Some(&title), 0);
855 self.spacer(st.metric(SM_WC_TITLE_PAD)); let mut capw = 0; let left = match self.tb_sys.buttons {
861 Some((l, r)) if l.contains(&plat::TB_BTN_CLOSE) || r.contains(&plat::TB_BTN_CLOSE) => l.contains(&plat::TB_BTN_CLOSE) as i32,
862 _ => st.metric(SM_CAP_LEFT),
863 };
864 if left != 0 {
865 capw = self.wc_close_btn(ids, i);
866 if capw != 0 {
867 self.spacer(st.metric(SM_WC_TITLE_PAD));
868 }
869 }
870 self.next_size(SIZE_FILL, 0);
873 let mut ts = st.metric(SM_TB_TITLE_TS) as u32 | TX_ELLIPSIS;
874 if tbs & plat::WST_ACTIVE == 0 {
875 ts |= TX_DISABLED;
876 }
877 self.text(ts, &title);
878 self.spacer(st.metric(SM_WC_TITLE_GAP)); if left == 0 {
880 self.wc_close_btn(ids, i);
881 } else if capw != 0 {
882 self.spacer(capw);
884 self.spacer(st.metric(SM_WC_TITLE_PAD));
885 }
886 self.box_end();
887 }
888 self.next_size(0, -1);
890 let roll = self.mobile() && {
893 let mut natural = self.slot(ids + WCH_CONTENT).meash;
894 if flags & WF_DIALOG != 0 {
895 natural += self.slot(ids + WCH_FOOTER).meash;
896 }
897 natural > self.wp.rt.fb.h - b * 2 - self.dp(tbh)
898 };
899 self.wp.chrome.wc_roll = roll;
900 let fixed = if roll { 0 } else { BOX_FIXED };
901 if roll {
902 self.scroll_begin(ids + WCH_ROLL, SC_V);
903 self.next_size(-1, 0);
904 }
905 if flags & WF_DIALOG != 0 {
906 let gap = st.metric(SM_DLG_CONTENT_GAP);
907 let bf = st.metric(SM_DLG_CONTENT_BOX) as u32 | fixed;
908 self.box_begin(ids + WCH_CONTENT, bf, gap);
909 let mut sp = Sp::default();
911 sp.place(self.wp.ctx.last_rect);
912 st.window_dialog_fill(self, &sp);
913 } else {
914 self.box_begin(ids + WCH_CONTENT, BOX_V | fixed, 0);
915 }
916 }
917
918 fn wc_close_btn(&mut self, ids: u32, i: u32) -> i32 {
921 let (flags, id) = {
922 let r = &self.m.win.recs[(i as usize) & (WIN_MAX - 1)];
923 (r.flags, r.id)
924 };
925 if flags & WF_NOCLOSE != 0 {
926 return 0;
927 }
928 let st = self.style;
929 let w = st.metric(if flags & WF_TOOL != 0 { SM_CAP_W_TOOL } else { SM_CAP_W });
930 let wide = super::layout::px2dip_at(self.wp.rt.fb.w, self.wp.rt.dpi) >= super::touch::CAPTION_MIN_W;
932 let w = if self.m.touch.on && wide { w.max(super::touch::CAPTION_DIP) } else { w };
933 self.m.chrome.cap_w = w;
934 let ev = self.cap_button(ids + WCH_CLOSE, IC_CHROME_CLOSE, HB_CLOSE, "Close");
935 self.m.chrome.cap_w = 0;
936 if ev & EV_ACT != 0 {
937 self.cmd_run(UIC_WINCLOSE, id);
938 }
939 w
940 }
941
942 pub(crate) fn win_chrome_end(&mut self, i: u32) -> i32 {
945 let ids = i << 4 | WCH_BASE;
946 let footer = self.wp.chrome.wc_footer;
947 if !footer {
948 self.wc_pop_to(ids + WCH_CONTENT);
949 if core::mem::take(&mut self.wp.chrome.wc_roll) {
950 self.scroll_end();
951 }
952 }
953 let mut h = self.slot(ids + WCH_CONTENT).meash;
954 if footer {
955 h += self.slot(ids + WCH_FOOTER).meash;
956 }
957 self.px2dip(h)
958 }
959
960 fn wc_pop_to(&mut self, id: u32) {
963 for _ in 0..BOX_STACK_MAX {
964 let b = self.box_id();
965 if b == id {
966 self.box_end();
967 return;
968 }
969 if b == self.wp.lay.root_id {
970 return;
971 }
972 self.warn("ui_box_begin without ui_box_end in a window");
973 self.box_end();
974 }
975 }
976
977 pub(crate) fn edge_grips(&mut self) {
992 let on = if self.headless { plat::getenv("jfgui_CSD").is_some_and(|v| v.starts_with('1')) } else { !cfg!(windows) || self.m.touch.on };
994 if !on || (self.m.touch.on && self.uiw_cur == 0) {
996 return;
997 }
998 let i = self.uiw_cur & (WIN_MAX as u32 - 1);
1000 self.m.chrome.grip_base = if i == 0 { GRIP_ID } else { (i << 4) + 8 | WCH_BASE };
1001 let state = if !self.headless {
1002 plat::window_state()
1003 } else if self.m.win.recs[i as usize].fs {
1004 plat::WST_FULLSCREEN
1005 } else {
1006 0
1007 };
1008 if state & (plat::WST_MAXIMIZED | plat::WST_FULLSCREEN) != 0 {
1009 return;
1010 }
1011 self.m.chrome.grip_off = if state & plat::WST_TILED != 0 { state >> plat::WST_TILED_SHIFT & 15 } else { 0 };
1014 let st = self.style;
1015 let t = self.dp(st.metric(SM_GRIP)); let c = self.dp(st.metric(SM_GRIP_CORNER)); let (tf, cf) = if self.m.touch.on { (t.max(self.dp(14)), c.max(self.dp(32))) } else { (t, c) };
1019 let (w, h) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
1020 use plat::{EDGE_BOTTOM, EDGE_TOP};
1021 let (left, right) = if self.rtl { (plat::EDGE_RIGHT, plat::EDGE_LEFT) } else { (plat::EDGE_LEFT, plat::EDGE_RIGHT) };
1023 let (nwse, nesw) = if self.rtl { (CUR_SIZE_NESW, CUR_SIZE_NWSE) } else { (CUR_SIZE_NWSE, CUR_SIZE_NESW) };
1024 let content = self.wp.ctx.hits[self.wp.ctx.hit_cur].len();
1025 self.grip(0, EDGE_TOP, CUR_SIZE_NS, c, 0, w - c - c, t);
1027 self.grip(1, EDGE_BOTTOM, CUR_SIZE_NS, cf, h - tf, w - cf - cf, tf);
1028 self.grip(2, left, CUR_SIZE_WE, 0, c, tf, h - c - cf);
1029 self.grip(3, right, CUR_SIZE_WE, w - tf, c, tf, h - c - cf);
1030 self.grip(4, EDGE_TOP | left, nwse, 0, 0, c, c);
1032 self.grip(5, EDGE_TOP | right, nesw, w - c, 0, c, c);
1033 self.grip(6, EDGE_BOTTOM | left, nesw, 0, h - cf, cf, cf);
1034 self.grip(7, EDGE_BOTTOM | right, nwse, w - cf, h - cf, cf, cf);
1035 let layer = self.wp.ctx.cmd_layer_ui as u8;
1036 if self.m.touch.on {
1039 let small = self.dp(super::touch::TARGET_DIP);
1040 let c = &mut self.wp.ctx;
1041 let list = &mut c.hits[c.hit_cur];
1042 for k in 0..content {
1043 let mut b = list[k];
1044 let (bw, bh) = (b.x1 - b.x0, b.y1 - b.y0);
1045 let widget = b.id != 0 && b.flags as u32 & (HB_BLOCK | HB_DRAG) == 0 && bw > 0 && bh > 0 && bw < small && bh < small;
1046 let edge = b.x0 < tf || b.x1 > w - tf || b.y1 > h - tf || (b.y1 > h - cf && (b.x0 < cf || b.x1 > w - cf));
1047 if widget && edge && list.len() < super::ctx::HITS_MAX {
1048 b.continuation = true;
1049 b.layer = b.layer.max(layer);
1050 list.push(b);
1051 }
1052 }
1053 }
1054 for k in 0..self.wp.chrome.cap_n as usize {
1056 let mut h = self.wp.chrome.cap_hits[k];
1057 h.continuation = true;
1058 h.layer = h.layer.max(layer);
1059 let c = &mut self.wp.ctx;
1060 if c.hits[c.hit_cur].len() < super::ctx::HITS_MAX {
1061 c.hits[c.hit_cur].push(h);
1062 }
1063 }
1064 }
1065
1066 #[allow(clippy::too_many_arguments)]
1068 fn grip(&mut self, k: u32, edge: u32, cur: u32, x: i32, y: i32, w: i32, h: i32) {
1069 if edge & self.m.chrome.grip_off != 0 {
1070 return; }
1072 let id = k.wrapping_add(self.m.chrome.grip_base);
1073 self.hit_rect(id, x, y, w, h, (cur & 0xFF) << HB_CURSOR_SHIFT);
1074 if self.wp.ctx.active == id && self.wp.ctx.pressed & (1 << BTN_LEFT) != 0 {
1075 self.wm_resize(edge);
1076 if !self.headless {
1077 self.wp.ctx.active = 0;
1079 self.wp.ctx.mbtn &= !(1 << BTN_LEFT);
1080 }
1081 }
1082 }
1083}