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};
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 GRIP_ID: u32 = 0xFFFF_7430; const WMR_MOVE: u32 = 1;
62const WMR_MENU: u32 = 2;
63const WMR_CMD: u32 = 3; const WMR_RESIZE: u32 = 4; #[derive(Default)]
68pub(crate) struct S {
69 cap_w: i32,
71 grip_base: u32,
73 grip_off: u32,
75 pub(crate) wm_req: u32,
77}
78
79pub(crate) fn wm_req_name(r: u32) -> String {
82 let arg = r >> 8;
83 let s = match r & 255 {
84 WMR_MOVE => "move",
85 WMR_MENU => "menu",
86 WMR_CMD => match arg {
87 plat::WCMD_MINIMIZE => "minimize",
88 plat::WCMD_MAXIMIZE => "maximize",
89 plat::WCMD_RESTORE => "restore",
90 plat::WCMD_CLOSE => "close",
91 plat::WCMD_LOWER => "lower",
92 plat::WCMD_FULLSCREEN => "fullscreen",
93 plat::WCMD_WINDOWED => "windowed",
94 _ => "command",
95 },
96 WMR_RESIZE => {
97 let mut e = String::from("resize ");
98 e.push_str(if arg & plat::EDGE_TOP != 0 { "n" } else if arg & plat::EDGE_BOTTOM != 0 { "s" } else { "" });
99 e.push_str(if arg & plat::EDGE_LEFT != 0 { "w" } else if arg & plat::EDGE_RIGHT != 0 { "e" } else { "" });
100 return e;
101 }
102 _ => "none",
103 };
104 String::from(s)
105}
106
107#[derive(Default)]
110pub(crate) struct W {
111 dlg_clip: [i32; 4],
112 dlg_openp: Option<Shared<i32>>,
113 pub(crate) dlg_active: bool,
114 dlg_nest: u32,
115 dlg_id: u32,
116 dlg_phase: u32,
117 dlg_layer: u32,
118 dlg_dis: u32,
119 pub(crate) dlg_fresh: bool,
121 tb_state: u32,
122 tb_overlay: bool,
124 pub(crate) tb_rect: Option<[i32; 4]>,
126 wc_footer: bool,
128 cap_hits: [Hit; 4],
132 pub(crate) cap_n: u8,
133}
134
135fn dlg_part(id: u32, k: u32) -> u32 {
137 id.wrapping_mul(8).wrapping_add(k) | 0x8000_0000
138}
139
140impl Ui {
141 #[cfg_attr(jfgui_dev, track_caller)]
145 pub fn dialog_begin(&mut self, id: u32, title: &str, open: &Shared<i32>) -> bool {
146 let _src = self.src_scope();
147 if self.wp.chrome.dlg_active {
148 self.wp.chrome.dlg_nest += 1; return false;
150 }
151 if open.get() == 0 {
153 let s = self.slot(id);
155 if s.aux != 0 {
156 s.aux = 0;
157 let f = s.scrollx as u32;
158 self.wp.ctx.focus = f;
159 self.repass();
160 }
161 return false;
162 }
163 {
164 let c = &mut self.wp.chrome;
165 c.dlg_active = true;
166 c.dlg_id = id;
167 c.dlg_openp = Some(open.clone());
168 c.dlg_phase = 1;
169 c.dlg_fresh = false;
170 }
171 let focus = self.wp.ctx.focus;
172 let s = self.slot(id);
173 if s.aux == 0 {
174 s.aux = 1;
176 s.scrollx = focus as i32;
177 self.wp.ctx.focus = id;
178 self.wp.ctx.focus_visible = false;
179 self.wp.chrome.dlg_fresh = true;
180 }
181 self.wp.chrome.dlg_layer = self.layer_set(L_DIALOG);
182 self.modal(L_DIALOG);
183 self.wp.chrome.dlg_clip = self.wp.ctx.uiclip;
185 let (w, h) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
186 self.wp.ctx.uiclip = [0, 0, w, h];
187 self.wp.chrome.dlg_dis = self.wp.ctx.disabled_depth;
188 self.wp.ctx.disabled_depth = 0;
189 let st = self.style;
191 let sp = Sp { x: 0, y: 0, w, h, ..Default::default() };
192 st.dialog_smoke(self, &sp);
193 self.wp.ctx.last_rect = [0, 0, w, h];
194 self.hit(dlg_part(id, 1), HB_BLOCK);
195 let (mut dw, nh) = {
197 let s = self.slot(id);
198 (s.measw, s.meash)
199 };
200 let min = self.dp(st.metric(SM_DLG_MIN));
201 if dw < min {
202 dw = min;
203 }
204 let max = self.dp(st.metric(SM_DLG_MAX));
205 if dw > max {
206 dw = max;
207 }
208 let m = self.dp(st.metric(SM_DLG_MARGIN)); if dw > w - m {
210 dw = w - m;
211 }
212 if dw < 1 {
213 dw = 1;
214 }
215 let x = (w - dw) >> 1;
216 let y = ((h - nh) >> 1).max(0);
217 self.next_rect_px(x, y, dw, 0);
218 self.box_begin(id, BOX_V | BOX_BLOCK, 0);
219 let r = self.wp.ctx.last_rect;
220 let fh = self.slot(dlg_part(id, 4)).meash;
222 let mut sp = Sp { value: fh, ..Default::default() };
224 sp.place(r);
225 st.dialog_frame(self, &sp);
226 self.wp.ctx.last_rect = r;
228 self.wp.ctx.last_state = SS_OPEN as u32;
229 self.sem(K_DIALOG, id, Some(title), 0);
230 let gap = st.metric(SM_DLG_CONTENT_GAP);
232 let flags = st.metric(SM_DLG_CONTENT_BOX) as u32;
233 self.box_begin(dlg_part(id, 3), flags, gap);
234 let ts = st.metric(SM_DLG_TITLE_TS) as u32;
235 self.text(ts, title);
236 true
237 }
238
239 #[cfg_attr(jfgui_dev, track_caller)]
243 pub fn dialog_buttons(&mut self, primary: Option<&str>, secondary: Option<&str>, close: Option<&str>) -> u32 {
244 let _src = self.src_scope();
245 if self.wp.chrome.dlg_active {
246 let c = &mut self.wp.chrome;
247 if c.dlg_nest != 0 || c.dlg_phase != 1 {
248 return DLG_NONE;
249 }
250 c.dlg_phase = 2;
251 let id = c.dlg_id;
252 self.box_end(); let r = self.dlg_footer(dlg_part(id, 4), dlg_part(id, 5), primary, secondary, close);
254 if r != 0 {
255 if let Some(o) = &self.wp.chrome.dlg_openp {
256 o.set(0);
257 }
258 self.repass();
259 }
260 return r;
261 }
262 if self.wp.chrome.wc_footer {
264 return DLG_NONE;
265 }
266 let cur = self.uiw_cur;
267 if cur == 0 {
268 return DLG_NONE;
269 }
270 let rec = &self.m.win.recs[(cur as usize) & (WIN_MAX - 1)];
271 if rec.flags & WF_DIALOG == 0 || rec.state & UWI_BUILDING == 0 {
272 return DLG_NONE;
273 }
274 let openp = rec.openp.clone();
275 self.wp.chrome.wc_footer = true;
276 let ids = cur << 4 | WCH_BASE;
277 self.wc_pop_to(ids + WCH_CONTENT);
278 let r = self.dlg_footer(ids + WCH_FOOTER, ids + WCH_BTN, primary, secondary, close);
279 if r != 0 {
280 if let Some(o) = openp {
281 o.set(0);
282 }
283 self.repass();
284 }
285 r
286 }
287
288 fn dlg_footer(&mut self, fid: u32, bid: u32, p: Option<&str>, s: Option<&str>, c: Option<&str>) -> u32 {
291 let st = self.style;
292 let gap = st.metric(SM_DLG_FOOTER_GAP);
293 let flags = st.metric(SM_DLG_FOOTER_BOX) as u32;
294 self.box_begin(fid, flags, gap);
295 let order = st.metric(SM_DLG_BTN_ORDER);
296 let mut r = DLG_NONE;
297 if order == 0 {
298 let n = p.is_some() as u32 + s.is_some() as u32 + c.is_some() as u32;
300 if n == 1 {
301 self.spacer(-1);
302 }
303 if let Some(l) = p {
304 self.next_size(-1, 0);
305 if self.accent_button(bid, l) & EV_ACT != 0 {
306 r = DLG_PRIMARY;
307 }
308 }
309 if let Some(l) = s {
310 self.next_size(-1, 0);
311 if self.button(bid + 1, l) & EV_ACT != 0 {
312 r = DLG_SECONDARY;
313 }
314 }
315 if let Some(l) = c {
316 self.next_size(-1, 0);
317 if self.button(bid + 2, l) & EV_ACT != 0 {
318 r = DLG_CLOSE;
319 }
320 }
321 } else {
322 self.spacer(-1);
325 if order != 2 {
326 if let Some(l) = c {
327 if self.button(bid + 2, l) & EV_ACT != 0 {
328 r = DLG_CLOSE;
329 }
330 }
331 if let Some(l) = s {
332 if self.button(bid + 1, l) & EV_ACT != 0 {
333 r = DLG_SECONDARY;
334 }
335 }
336 if let Some(l) = p {
337 if self.accent_button(bid, l) & EV_ACT != 0 {
338 r = DLG_PRIMARY;
339 }
340 }
341 } else {
342 if let Some(l) = p {
343 if self.accent_button(bid, l) & EV_ACT != 0 {
344 r = DLG_PRIMARY;
345 }
346 }
347 if let Some(l) = s {
348 if self.button(bid + 1, l) & EV_ACT != 0 {
349 r = DLG_SECONDARY;
350 }
351 }
352 if let Some(l) = c {
353 if self.button(bid + 2, l) & EV_ACT != 0 {
354 r = DLG_CLOSE;
355 }
356 }
357 }
358 }
359 self.box_end(); if r != 0 {
362 return r;
363 }
364 if p.is_some() && self.key(VK_RETURN) {
365 return DLG_PRIMARY;
366 }
367 if self.key(VK_ESCAPE) {
368 return DLG_CLOSE;
369 }
370 DLG_NONE
371 }
372
373 #[cfg_attr(jfgui_dev, track_caller)]
375 pub fn dialog_end(&mut self) {
376 let _src = self.src_scope();
377 if self.wp.chrome.dlg_nest != 0 {
378 self.wp.chrome.dlg_nest -= 1;
379 return;
380 }
381 if !self.wp.chrome.dlg_active {
382 return;
383 }
384 if self.wp.chrome.dlg_phase == 1 {
385 if self.key(VK_ESCAPE) {
387 if let Some(o) = &self.wp.chrome.dlg_openp {
388 o.set(0);
389 }
390 self.repass();
391 }
392 self.box_end(); }
394 self.box_end(); let c = &self.wp.chrome;
396 let (clip, dis, layer) = (c.dlg_clip, c.dlg_dis, c.dlg_layer);
397 self.wp.ctx.uiclip = clip;
398 self.wp.ctx.disabled_depth = dis;
399 self.layer_set(layer);
400 self.wp.chrome.dlg_active = false;
401 }
402
403 #[cfg_attr(jfgui_dev, track_caller)]
407 pub fn infobar(&mut self, id: u32, severity: u32, title: &str, msg: &str) -> u32 {
408 let _src = self.src_scope();
409 let sev = severity & 3;
410 let st = self.style;
411 let mut sp = Sp { flags: sev, label: Some(title), label2: Some(msg), ..Default::default() };
413 let (w, h) = st.infobar_size(self, &mut sp);
414 self.place(w, h);
415 let r = self.wp.ctx.last_rect;
416 let fit = if w > r[2] { Some(self.infobar_fit(title, msg, sp.s[0], sp.s[1], w - r[2])) } else { None };
419 if let Some((t, m)) = &fit {
420 sp.label = Some(t);
421 sp.label2 = if m.is_empty() { None } else { Some(m) };
422 st.infobar_size(self, &mut sp);
423 }
424 sp.place(r);
426 st.infobar(self, &mut sp);
427 let (cx, cs) = (sp.s[2], sp.s[3]);
428 self.wp.ctx.last_rect = [cx, ((r[3] - cs) >> 1) + r[1], cs, cs];
429 let ev = self.behave(id.wrapping_add(1), HB_FOCUS);
430 sp.place(self.wp.ctx.last_rect);
432 sp.ev = ev;
433 st.infobar_close(self, &sp);
434 if ev & EV_FOCUS != 0 && self.wp.ctx.focus_visible {
435 let lr = self.wp.ctx.last_rect;
436 st.focus_rect(self, lr[0], lr[1], lr[2], lr[3]);
437 }
438 self.sem(K_BUTTON, id.wrapping_add(1), Some("Close"), 0);
439 self.wp.ctx.last_rect = r;
441 self.wp.ctx.last_state = 0;
442 self.sem(K_BOX, id, Some(title), sev as i32);
443 ev & EV_ACT
444 }
445
446 fn infobar_fit(&mut self, title: &str, msg: &str, tw: i32, mw: i32, over: i32) -> (String, String) {
450 let keep = mw - over;
451 if !msg.is_empty() && keep >= self.dp(32) {
452 self.ts_font(TS_BODY);
453 return (String::from(title), self.ellipsize(msg.as_bytes(), keep).0);
454 }
455 let over = over - mw;
456 if over <= 0 {
457 return (String::from(title), String::new());
458 }
459 self.ts_font(TS_BODY_STRONG);
460 (self.ellipsize(title.as_bytes(), (tw - over).max(1)).0, String::new())
461 }
462
463 #[cfg_attr(jfgui_dev, track_caller)]
467 pub fn statusbar_begin(&mut self) {
468 let _src = self.src_scope();
469 let st = self.style;
470 let h = st.metric(SM_SB_H); self.next_size(0, h);
472 self.box_begin(SB_ID, BOX_H, 0);
473 let r = self.wp.ctx.last_rect;
474 let mut sp = Sp::default();
475 sp.place(r);
476 st.statusbar(self, &sp);
477 let pad = self.dp(st.metric(SM_SB_PAD));
479 let mut w = r[2] - pad - pad;
480 if w <= 0 {
481 w = 1;
482 }
483 self.next_rect_px(r[0] + pad, r[1], w, r[3]);
484 let gap = st.metric(SM_SB_GAP); self.box_begin(SB_INNER_ID, BOX_H | BOX_XCENTER, gap);
486 }
487
488 #[cfg_attr(jfgui_dev, track_caller)]
490 pub fn statusbar_end(&mut self) {
491 let _src = self.src_scope();
492 self.box_end();
493 self.box_end();
494 }
495
496 #[cfg_attr(jfgui_dev, track_caller)]
500 pub fn titlebar_begin(&mut self, title: &str, icon: u32) {
501 let _src = self.src_scope();
502 self.titlebar_begin_opt(Some(title), icon);
503 }
504
505 #[cfg_attr(jfgui_dev, track_caller)]
507 pub fn titlebar_begin_opt(&mut self, title: Option<&str>, icon: u32) {
508 let _src = self.src_scope();
509 self.titlebar_begin_inner(title, icon, false);
510 }
511
512 #[cfg_attr(jfgui_dev, track_caller)]
516 pub fn titlebar_begin_overlay(&mut self, title: Option<&str>, icon: u32) {
517 let _src = self.src_scope();
518 self.titlebar_begin_inner(title, icon, true);
519 }
520
521 fn titlebar_begin_inner(&mut self, title: Option<&str>, icon: u32, overlay: bool) {
522 let st = self.style;
523 self.wp.chrome.tb_overlay = overlay;
524 let f = self.take_flags();
525 let h = st.metric(if f & ST_LARGE != 0 { SM_TB_H_LARGE } else { SM_TB_H });
526 self.next_size(0, h);
527 self.box_begin(TB_ID, BOX_H | BOX_XCENTER, 0);
528 self.wp.chrome.tb_state = if self.headless { plat::WST_ACTIVE } else { plat::window_state() };
530 let tbs = self.wp.chrome.tb_state;
531 let mut sp = Sp { value: (tbs & plat::WST_ACTIVE != 0) as i32, ..Default::default() };
533 sp.place(self.wp.ctx.last_rect);
534 if !overlay { st.window_titlebar(self, &sp); }
535 self.wp.chrome.tb_rect = if overlay { None } else { Some(self.wp.ctx.last_rect) };
536 let ev = self.behave(TB_ID, HB_DRAG);
538 self.tb_clicks(ev, tbs);
539 self.wp.ctx.last_state = 0;
540 self.sem(K_TITLEBAR, TB_ID, title, 0);
541 let left = self.tb_side(false);
543 if left[0] != 0 {
544 if st.metric(SM_CAP_LEFT) != 0 {
545 self.spacer(st.metric(SM_TB_PAD));
546 }
547 self.tb_caps(left);
548 }
549 self.spacer(st.metric(SM_TB_PAD)); if icon != 0 {
552 let s = st.metric(SM_TB_ICON); if s != 0 {
554 self.icon(icon, s);
555 self.spacer(st.metric(SM_TB_PAD));
556 }
557 }
558 if self.agent_enabled() && !overlay {
559 self.next_size(82, 24);
560 if self.button(super::agent::ID_BADGE, if self.agent_connected() { "Agent ●" } else { "Agent ○" }) & EV_ACT != 0 {
561 self.set_agent_enabled(false);
562 }
563 self.tooltip(if self.agent_connected() { "Agent verbunden · Zum Abschalten klicken" } else { "Agentensteuerung aktiv · Zum Abschalten klicken" });
564 self.spacer(8);
565 }
566 let Some(title) = title else { return };
567 let mut ts = st.metric(SM_TB_TITLE_TS) as u32;
568 if tbs & plat::WST_ACTIVE == 0 {
569 ts |= TX_DISABLED;
570 }
571 self.text(ts, title);
572 }
573
574 #[cfg_attr(jfgui_dev, track_caller)]
577 pub fn titlebar_end(&mut self) {
578 let _src = self.src_scope();
579 let st = self.style;
580 if self.wp.chrome.tb_overlay {
581 self.spacer(-1);
582 if self.agent_enabled() {
583 self.next_size(82, 24);
584 if self.button(super::agent::ID_BADGE, if self.agent_connected() { "Agent ●" } else { "Agent ○" }) & EV_ACT != 0 {
585 self.set_agent_enabled(false);
586 }
587 self.tooltip(if self.agent_connected() { "Agent verbunden · Zum Abschalten klicken" } else { "Agentensteuerung aktiv · Zum Abschalten klicken" });
588 self.spacer(8);
589 }
590 }
591 let right = self.tb_side(true);
592 if right[0] != 0 {
593 if !self.wp.chrome.tb_overlay { self.spacer(-1); }
594 self.tb_caps(right);
595 if st.metric(SM_CAP_LEFT) != 0 {
596 self.spacer(st.metric(SM_TB_PAD)); }
598 }
599 self.box_end();
600 self.wp.chrome.tb_rect = None;
601 }
602
603 pub fn titlebar_open(&self) -> Option<([i32; 4], bool)> {
608 let c = &self.wp.chrome;
609 c.tb_rect.map(|r| (r, c.tb_state & plat::WST_ACTIVE != 0))
610 }
611
612 fn tb_clicks(&mut self, ev: u32, tbs: u32) {
618 let tb = self.tb_sys;
619 let c = &self.wp.ctx;
620 let (act, btn) = if ev & EV_DOUBLE != 0 && tb.double != plat::TB_ACT_NONE {
621 (tb.double, BTN_LEFT)
622 } else if c.active == TB_ID && c.pressed & (1 << BTN_LEFT) != 0 {
623 (u8::MAX, BTN_LEFT) } else if c.hot == TB_ID && c.pressed & (1 << BTN_MIDDLE) != 0 && tb.middle != plat::TB_ACT_NONE {
625 (tb.middle, BTN_MIDDLE)
626 } else if c.ractive == TB_ID && c.pressed & (1 << BTN_RIGHT) != 0 && tb.right != plat::TB_ACT_NONE {
627 (tb.right, BTN_RIGHT)
628 } else {
629 return;
630 };
631 let (mx, my) = (c.mx, c.my);
632 match act {
633 u8::MAX => self.wm_move(),
634 plat::TB_ACT_MAXIMIZE => self.tb_cmd(if tbs & plat::WST_MAXIMIZED != 0 { plat::WCMD_RESTORE } else { plat::WCMD_MAXIMIZE }),
635 plat::TB_ACT_MINIMIZE => self.tb_cmd(plat::WCMD_MINIMIZE),
636 plat::TB_ACT_LOWER => self.tb_cmd(plat::WCMD_LOWER),
637 _ => self.wm_menu(mx, my),
638 }
639 if self.headless {
640 return;
641 }
642 let c = &mut self.wp.ctx;
644 match btn {
645 BTN_LEFT => c.active = 0,
646 BTN_RIGHT => c.ractive = 0,
647 _ => {}
648 }
649 c.mbtn &= !(1 << btn);
650 }
651
652 fn wm_move(&mut self) {
655 if self.headless {
656 self.m.chrome.wm_req = WMR_MOVE;
657 } else {
658 plat::begin_move();
659 }
660 }
661
662 fn wm_menu(&mut self, _x: i32, _y: i32) {
663 if self.headless {
664 self.m.chrome.wm_req = WMR_MENU;
665 return;
666 }
667 #[cfg(target_os = "linux")]
668 plat::window_menu(_x, _y);
669 }
670
671 fn wm_resize(&mut self, edge: u32) {
672 if self.headless {
673 self.m.chrome.wm_req = WMR_RESIZE | edge << 8;
674 } else {
675 plat::begin_resize(edge);
676 }
677 }
678
679 fn tb_side(&self, right: bool) -> [u8; 3] {
683 if let Some((l, r)) = self.tb_sys.buttons {
684 return if right { r } else { l };
685 }
686 match (self.style.metric(SM_CAP_LEFT) != 0, right) {
687 (true, false) => [plat::TB_BTN_CLOSE, plat::TB_BTN_MIN, plat::TB_BTN_MAX],
688 (false, true) => [plat::TB_BTN_MIN, plat::TB_BTN_MAX, plat::TB_BTN_CLOSE],
689 _ => [0; 3],
690 }
691 }
692
693 fn tb_caps(&mut self, b: [u8; 3]) {
695 for k in b {
696 match k {
697 plat::TB_BTN_CLOSE => self.tb_cap_close(),
698 plat::TB_BTN_MIN => self.tb_cap_min(),
699 plat::TB_BTN_MAX => self.tb_cap_max(),
700 _ => break,
701 }
702 }
703 }
704
705 fn tb_cap_min(&mut self) {
708 if self.cap_button(TB_MIN_ID, IC_CHROME_MIN, HB_MIN, "Minimize") & EV_ACT != 0 {
709 self.tb_cmd(plat::WCMD_MINIMIZE);
710 }
711 }
712
713 fn tb_cap_max(&mut self) {
714 let max = self.wp.chrome.tb_state & plat::WST_MAXIMIZED != 0;
715 let (ic, l) = if max { (IC_CHROME_RESTORE, "Restore") } else { (IC_CHROME_MAX, "Maximize") };
716 if self.cap_button(TB_MAX_ID, ic, HB_MAX, l) & EV_ACT != 0 {
717 let max = self.wp.chrome.tb_state & plat::WST_MAXIMIZED != 0;
718 self.tb_cmd(if max { plat::WCMD_RESTORE } else { plat::WCMD_MAXIMIZE });
719 }
720 }
721
722 fn tb_cap_close(&mut self) {
723 if self.cap_button(TB_CLOSE_ID, IC_CHROME_CLOSE, HB_CLOSE, "Close") & EV_ACT != 0 {
724 self.tb_cmd(plat::WCMD_CLOSE);
725 }
726 }
727
728 fn tb_cmd(&mut self, cmd: u32) {
730 if self.headless {
731 self.m.chrome.wm_req = WMR_CMD | cmd << 8;
732 } else {
733 plat::window_cmd(cmd);
734 }
735 }
736
737 fn cap_button(&mut self, id: u32, icon: u32, hb: u32, label: &str) -> u32 {
740 let st = self.style;
741 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) };
743 let w = self.dp(w);
744 self.place(w, bh);
745 let n = self.wp.ctx.hits[self.wp.ctx.hit_cur].len();
746 let ev = self.behave(id, hb);
747 if let Some(h) = self.wp.ctx.hits[self.wp.ctx.hit_cur].get(n).copied() {
749 let c = &mut self.wp.chrome;
750 if let Some(slot) = c.cap_hits.get_mut(c.cap_n as usize) {
751 *slot = h;
752 c.cap_n += 1;
753 }
754 }
755 let active = (self.wp.chrome.tb_state & plat::WST_ACTIVE != 0) as i32;
757 let mut sp = Sp { ev, flags: hb, icon, value: active, ..Default::default() };
758 sp.place(self.wp.ctx.last_rect);
759 st.caption_button(self, &sp);
760 self.sem(K_BUTTON, id, Some(label), 0);
761 ev
762 }
763
764 pub(crate) fn win_chrome_begin(&mut self, i: u32) {
770 let st = self.style;
771 let iu = (i as usize) & (WIN_MAX - 1);
772 let (flags, tbh) = (self.m.win.recs[iu].flags, self.m.win.recs[iu].tbh);
773 let ids = i << 4 | WCH_BASE; self.wp.chrome.wc_footer = false;
775 let mut tbs = 0;
777 if self.uiw_app_active() {
778 tbs = plat::WST_ACTIVE;
779 if flags & WF_TOOL == 0 && i != self.m.win.active {
780 tbs = 0;
781 }
782 }
783 self.wp.chrome.tb_state = tbs;
784 let b = self.uiw_border();
785 if b != 0 {
786 let (w, h) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
788 let sp = Sp { x: 0, y: 0, w, h, value: b, ..Default::default() };
789 st.window_border(self, &sp);
790 }
791 if tbh != 0 {
792 self.next_size(0, tbh);
793 self.box_begin(ids, BOX_H | BOX_XCENTER, 0);
794 let mut sp = Sp { value: (tbs & plat::WST_ACTIVE != 0) as i32, ..Default::default() };
796 sp.place(self.wp.ctx.last_rect);
797 st.window_titlebar(self, &sp);
798 self.behave(ids, HB_DRAG);
801 if self.m.win.recs[(i as usize) & (WIN_MAX - 1)].flags & WF_TOUCH_HOLD != 0 {
802 self.touch_grab(super::touch::AX_ALL | (TF_HOLD | TF_HOLD_LONG) as u8);
803 self.touch_hold_show(ids);
804 }
805 if !self.headless && self.wp.ctx.active == ids && self.wp.ctx.pressed & (1 << BTN_LEFT) != 0 {
806 plat::begin_move(); self.wp.ctx.active = 0; self.wp.ctx.mbtn &= !(1 << BTN_LEFT);
809 } else if self.headless && self.mptype == PTR_TOUCH && self.wp.ctx.active == ids && self.wp.ctx.mbtn & (1 << BTN_LEFT) != 0 {
810 let pressed = self.wp.ctx.pressed & (1 << BTN_LEFT) != 0;
811 self.touch_window_drag(i, pressed); }
813 let title = alloc::string::String::from(self.uiw_title(i));
814 self.wp.ctx.last_state = 0;
815 self.sem(K_TITLEBAR, ids, Some(&title), 0);
816 self.spacer(st.metric(SM_WC_TITLE_PAD)); let mut capw = 0; let left = match self.tb_sys.buttons {
822 Some((l, r)) if l.contains(&plat::TB_BTN_CLOSE) || r.contains(&plat::TB_BTN_CLOSE) => l.contains(&plat::TB_BTN_CLOSE) as i32,
823 _ => st.metric(SM_CAP_LEFT),
824 };
825 if left != 0 {
826 capw = self.wc_close_btn(ids, i);
827 if capw != 0 {
828 self.spacer(st.metric(SM_WC_TITLE_PAD));
829 }
830 }
831 self.next_size(SIZE_FILL, 0);
834 let mut ts = st.metric(SM_TB_TITLE_TS) as u32 | TX_ELLIPSIS;
835 if tbs & plat::WST_ACTIVE == 0 {
836 ts |= TX_DISABLED;
837 }
838 self.text(ts, &title);
839 self.spacer(st.metric(SM_WC_TITLE_GAP)); if left == 0 {
841 self.wc_close_btn(ids, i);
842 } else if capw != 0 {
843 self.spacer(capw);
845 self.spacer(st.metric(SM_WC_TITLE_PAD));
846 }
847 self.box_end();
848 }
849 self.next_size(0, -1);
851 if flags & WF_DIALOG != 0 {
852 let gap = st.metric(SM_DLG_CONTENT_GAP);
853 let bf = st.metric(SM_DLG_CONTENT_BOX) as u32 | BOX_FIXED;
854 self.box_begin(ids + WCH_CONTENT, bf, gap);
855 let mut sp = Sp::default();
857 sp.place(self.wp.ctx.last_rect);
858 st.window_dialog_fill(self, &sp);
859 } else {
860 self.box_begin(ids + WCH_CONTENT, BOX_V | BOX_FIXED, 0);
861 }
862 }
863
864 fn wc_close_btn(&mut self, ids: u32, i: u32) -> i32 {
867 let (flags, id) = {
868 let r = &self.m.win.recs[(i as usize) & (WIN_MAX - 1)];
869 (r.flags, r.id)
870 };
871 if flags & WF_NOCLOSE != 0 {
872 return 0;
873 }
874 let st = self.style;
875 let w = st.metric(if flags & WF_TOOL != 0 { SM_CAP_W_TOOL } else { SM_CAP_W });
876 let wide = super::layout::px2dip_at(self.wp.rt.fb.w, self.wp.rt.dpi) >= super::touch::CAPTION_MIN_W;
878 let w = if self.m.touch.on && wide { w.max(super::touch::CAPTION_DIP) } else { w };
879 self.m.chrome.cap_w = w;
880 let ev = self.cap_button(ids + WCH_CLOSE, IC_CHROME_CLOSE, HB_CLOSE, "Close");
881 self.m.chrome.cap_w = 0;
882 if ev & EV_ACT != 0 {
883 self.cmd_run(UIC_WINCLOSE, id);
884 }
885 w
886 }
887
888 pub(crate) fn win_chrome_end(&mut self, i: u32) -> i32 {
891 let ids = i << 4 | WCH_BASE;
892 let footer = self.wp.chrome.wc_footer;
893 if !footer {
894 self.wc_pop_to(ids + WCH_CONTENT);
895 }
896 let mut h = self.slot(ids + WCH_CONTENT).meash;
897 if footer {
898 h += self.slot(ids + WCH_FOOTER).meash;
899 }
900 self.px2dip(h)
901 }
902
903 fn wc_pop_to(&mut self, id: u32) {
906 for _ in 0..BOX_STACK_MAX {
907 let b = self.box_id();
908 if b == id {
909 self.box_end();
910 return;
911 }
912 if b == self.wp.lay.root_id {
913 return;
914 }
915 self.warn("ui_box_begin without ui_box_end in a window");
916 self.box_end();
917 }
918 }
919
920 pub(crate) fn edge_grips(&mut self) {
935 let on = if self.headless { plat::getenv("jfgui_CSD").is_some_and(|v| v.starts_with('1')) } else { !cfg!(windows) || self.m.touch.on };
937 if !on || (self.m.touch.on && self.uiw_cur == 0) {
939 return;
940 }
941 let i = self.uiw_cur & (WIN_MAX as u32 - 1);
943 self.m.chrome.grip_base = if i == 0 { GRIP_ID } else { (i << 4) + 8 | WCH_BASE };
944 let state = if !self.headless {
945 plat::window_state()
946 } else if self.m.win.recs[i as usize].fs {
947 plat::WST_FULLSCREEN
948 } else {
949 0
950 };
951 if state & (plat::WST_MAXIMIZED | plat::WST_FULLSCREEN) != 0 {
952 return;
953 }
954 self.m.chrome.grip_off = if state & plat::WST_TILED != 0 { state >> plat::WST_TILED_SHIFT & 15 } else { 0 };
957 let st = self.style;
958 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) };
962 let (w, h) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
963 use plat::{EDGE_BOTTOM, EDGE_TOP};
964 let (left, right) = if self.rtl { (plat::EDGE_RIGHT, plat::EDGE_LEFT) } else { (plat::EDGE_LEFT, plat::EDGE_RIGHT) };
966 let (nwse, nesw) = if self.rtl { (CUR_SIZE_NESW, CUR_SIZE_NWSE) } else { (CUR_SIZE_NWSE, CUR_SIZE_NESW) };
967 let content = self.wp.ctx.hits[self.wp.ctx.hit_cur].len();
968 self.grip(0, EDGE_TOP, CUR_SIZE_NS, c, 0, w - c - c, t);
970 self.grip(1, EDGE_BOTTOM, CUR_SIZE_NS, cf, h - tf, w - cf - cf, tf);
971 self.grip(2, left, CUR_SIZE_WE, 0, c, tf, h - c - cf);
972 self.grip(3, right, CUR_SIZE_WE, w - tf, c, tf, h - c - cf);
973 self.grip(4, EDGE_TOP | left, nwse, 0, 0, c, c);
975 self.grip(5, EDGE_TOP | right, nesw, w - c, 0, c, c);
976 self.grip(6, EDGE_BOTTOM | left, nesw, 0, h - cf, cf, cf);
977 self.grip(7, EDGE_BOTTOM | right, nwse, w - cf, h - cf, cf, cf);
978 let layer = self.wp.ctx.cmd_layer_ui as u8;
979 if self.m.touch.on {
982 let small = self.dp(super::touch::TARGET_DIP);
983 let c = &mut self.wp.ctx;
984 let list = &mut c.hits[c.hit_cur];
985 for k in 0..content {
986 let mut b = list[k];
987 let (bw, bh) = (b.x1 - b.x0, b.y1 - b.y0);
988 let widget = b.id != 0 && b.flags as u32 & (HB_BLOCK | HB_DRAG) == 0 && bw > 0 && bh > 0 && bw < small && bh < small;
989 let edge = b.x0 < tf || b.x1 > w - tf || b.y1 > h - tf || (b.y1 > h - cf && (b.x0 < cf || b.x1 > w - cf));
990 if widget && edge && list.len() < super::ctx::HITS_MAX {
991 b.continuation = true;
992 b.layer = b.layer.max(layer);
993 list.push(b);
994 }
995 }
996 }
997 for k in 0..self.wp.chrome.cap_n as usize {
999 let mut h = self.wp.chrome.cap_hits[k];
1000 h.continuation = true;
1001 h.layer = h.layer.max(layer);
1002 let c = &mut self.wp.ctx;
1003 if c.hits[c.hit_cur].len() < super::ctx::HITS_MAX {
1004 c.hits[c.hit_cur].push(h);
1005 }
1006 }
1007 }
1008
1009 #[allow(clippy::too_many_arguments)]
1011 fn grip(&mut self, k: u32, edge: u32, cur: u32, x: i32, y: i32, w: i32, h: i32) {
1012 if edge & self.m.chrome.grip_off != 0 {
1013 return; }
1015 let id = k.wrapping_add(self.m.chrome.grip_base);
1016 self.hit_rect(id, x, y, w, h, (cur & 0xFF) << HB_CURSOR_SHIFT);
1017 if self.wp.ctx.active == id && self.wp.ctx.pressed & (1 << BTN_LEFT) != 0 {
1018 self.wm_resize(edge);
1019 if !self.headless {
1020 self.wp.ctx.active = 0;
1022 self.wp.ctx.mbtn &= !(1 << BTN_LEFT);
1023 }
1024 }
1025 }
1026}