1use alloc::collections::BTreeMap;
58use alloc::string::String;
59use alloc::vec::Vec;
60
61use crate::consts::*;
62use crate::keys::*;
63use crate::{Sp, Ui};
64use plat::{PA_ABOVE, PA_BELOW, PA_FREE, PA_POINT, PA_SIDE};
65
66const POPUP_MAX: usize = 6;
69pub(crate) const PPF_FOCUS: u32 = 1; const PPF_AUTO: u32 = 2; const PPF_POINT: u32 = 4; const PPF_QUIET: u32 = 8; const PPF_TWOCOL: u32 = 16; const PPF_SCROLLED: u32 = 32; const FLN_MAX: usize = 8; const CST_LIMIT: usize = 1536; const MENU_MAX: usize = 250;
81const MB_MAX: usize = 16; const LAB_MAX: usize = 256;
83pub(crate) const CATCH_ID: u32 = 0x7FFE_0C0C; const CTX_SALT: u32 = 0x00C0_FFEE;
85const BOXID_XOR: u32 = 0x6C00_0000; const FLS_XOR: u32 = 0x0100_0000; const TIP_DELAY: u32 = 500;
88const TIP_QUICK: u32 = 300;
89const BTN_MASK: u32 = (1 << BTN_LEFT) | (1 << BTN_RIGHT) | (1 << BTN_MIDDLE);
90const MOD3: u32 = MOD_SHIFT | MOD_CTRL | MOD_ALT;
91
92#[derive(Clone, Copy, Debug)]
94pub struct Command {
95 pub id: u32,
97 pub icon: u32,
99 pub label: &'static str,
101 pub key: u16,
103 pub mods: u8,
105 pub flags: u8,
107}
108
109impl Command {
110 pub const fn new(id: u32, label: &'static str, icon: u32, mods: u32, key: u32, flags: u32) -> Command {
112 Command { id, icon, label, key: key as u16, mods: mods as u8, flags: flags as u8 }
113 }
114}
115
116#[derive(Clone, Copy, Debug)]
118#[non_exhaustive]
119pub enum Mi {
120 Cmd(u32),
122 Sep,
124 Sub(&'static str, &'static [Mi]),
126 Menu(&'static str, &'static [Mi]),
128 Item(u32, &'static str, u32),
130 List(u32, u16, &'static str),
134}
135
136impl Mi {
137 fn label(&self) -> Option<&'static str> {
139 match *self {
140 Mi::Sub(l, _) | Mi::Menu(l, _) | Mi::Item(_, l, _) | Mi::List(_, _, l) => Some(l),
141 Mi::Cmd(_) | Mi::Sep => None,
142 }
143 }
144 fn arg(&self) -> u32 {
146 match *self {
147 Mi::Cmd(a) | Mi::Item(a, _, _) | Mi::List(a, _, _) => a,
148 _ => 0,
149 }
150 }
151}
152
153#[derive(Clone, Copy, Default)]
155struct Pp {
156 id: u32,
157 flags: u32,
159 anchor: [i32; 4],
161 rect: [i32; 4],
163 menu: Option<&'static [Mi]>,
165 ret: u32,
167 kind: u32,
171 key: [i32; 2],
172 nat: [i32; 2],
173 seen: u32,
175 born: u32,
176 hit: u32,
178 hitend: u32,
179 focusid: u32,
181}
182
183#[derive(Clone, Copy, Default)]
185struct Fln {
186 open: bool,
187 idx: u32,
188 layer: u32,
189 dis: u32,
190 level: u32,
191 scroll: bool,
193 clip: [i32; 4],
194}
195
196#[derive(Default)]
198pub(crate) struct S {
199 cst: BTreeMap<u32, u32>,
201 cst_warned: bool,
202 accel_order: u32,
204 pub(crate) lists: Vec<(u32, Vec<String>)>,
206 pub(crate) list_empty: Vec<(u32, String)>,
208}
209
210#[derive(Default)]
212pub(crate) struct W {
213 pp: [Pp; POPUP_MAX],
214 pp_n: u32,
215 fln: [Fln; FLN_MAX],
216 fl_sp: u32,
217 fl_over: u32,
218 fl_level: u32,
219 fl_offy: i32,
220 catch_frame: u32,
221 mb_mode: bool,
222 mb_prev: u32,
223 alt_armed: bool,
224 ctx_target: u32,
225 tip_id: u32,
226 tip_t0: u32,
227 tip_block: u32,
228 tip_shown: u32,
229 tip_hid_t: u32,
230 tip_hid_ok: bool,
231 tip_suppress: bool,
232 tip_part: u64,
234 hot_prev: u32,
235 tip_rect: [i32; 4],
237 tip_seen: u32,
238 tip_anchor: [i32; 4],
240 tip_nat: [i32; 2],
241}
242
243fn strip_amp(src: &str) -> (String, u32, i32) {
248 let s = src.as_bytes();
249 let mut out: Vec<u8> = Vec::with_capacity(s.len().min(LAB_MAX));
250 let mut m = 0u32;
251 let mut pos = -1i32;
252 let mut i = 0;
253 while out.len() < LAB_MAX - 1 && i < s.len() {
254 let mut c = s[i];
255 if c == 0 {
256 break;
257 }
258 i += 1;
259 if c == b'&' {
260 if i >= s.len() || s[i] == 0 {
261 break;
262 }
263 c = s[i];
264 i += 1;
265 if c != b'&' && m == 0 {
266 pos = out.len() as i32;
267 m = c as u32;
268 if c.is_ascii_lowercase() {
269 m -= 32;
270 }
271 }
272 }
273 out.push(c);
274 }
275 (utf8_string(out), m, pos)
276}
277
278fn utf8_string(mut b: Vec<u8>) -> String {
280 match core::str::from_utf8(&b) {
281 Ok(_) => {}
282 Err(e) => b.truncate(e.valid_up_to()),
283 }
284 String::from_utf8(b).unwrap_or_default()
285}
286
287fn cat(dst: &mut String, src: &str, cap: usize) {
290 if dst.len() >= cap {
291 return;
292 }
293 let mut n = (cap - dst.len() - 1).min(src.len());
294 while !src.is_char_boundary(n) {
295 n -= 1;
296 }
297 dst.push_str(&src[..n]);
298}
299
300fn menu_pid(menu: &'static [Mi], salt: u32) -> u32 {
302 let a = (menu.as_ptr() as usize as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15);
303 let e = ((a >> 32) as u32) ^ salt.wrapping_mul(0x045D_9F3B);
304 (e & 0x0FFF_FF00) | 0xB000_0000
305}
306
307fn mnem_find(mn: &[u8], vk: u32) -> i32 {
309 mn.iter().position(|&m| m != 0 && m as u32 == vk).map_or(-1, |i| i as i32)
310}
311
312fn is_alnum_vk(vk: u32) -> bool {
314 vk.wrapping_sub(0x30) <= 9 || vk.wrapping_sub(0x41) <= 25
315}
316
317fn strlist_n(list: &[&str]) -> usize {
319 list.iter().position(|s| s.is_empty()).unwrap_or(list.len())
320}
321
322fn strlist_at<'a>(list: &[&'a str], i: i32) -> Option<&'a str> {
324 if i < 0 || i as usize >= strlist_n(list) { None } else { Some(list[i as usize]) }
325}
326
327static KEY_TABLE: [(u32, &str); 26] = [
329 (VK_DELETE, "Entf"),
330 (VK_INSERT, "Einfg"),
331 (VK_RETURN, "Eingabe"),
332 (VK_ESCAPE, "Esc"),
333 (VK_BACK, "Rücktaste"),
334 (VK_TAB, "Tab"),
335 (VK_SPACE, "Leertaste"),
336 (VK_HOME, "Pos1"),
337 (VK_END, "Ende"),
338 (VK_PRIOR, "Bild auf"),
339 (VK_NEXT, "Bild ab"),
340 (VK_LEFT, "Nach-links"),
341 (VK_RIGHT, "Nach-rechts"),
342 (VK_UP, "Nach-oben"),
343 (VK_DOWN, "Nach-unten"),
344 (VK_OEM_PLUS, "Plus"),
345 (VK_OEM_MINUS, "Minus"),
346 (VK_ADD, "Num +"),
347 (VK_SUBTRACT, "Num -"),
348 (VK_OEM_COMMA, ","),
349 (VK_OEM_PERIOD, "."),
350 (VK_OEM_4, "["), (VK_OEM_6, "]"),
352 (VK_MULTIPLY, "Num *"),
353 (VK_DIVIDE, "Num /"),
354 (VK_DECIMAL, "Num ,"),
355];
356
357fn key_name(vk: u32) -> String {
359 if is_alnum_vk(vk) {
360 let mut s = String::new();
361 s.push(vk as u8 as char);
362 return s;
363 }
364 if vk.wrapping_sub(VK_F1) <= 23 {
365 return alloc::format!("F{}", vk - VK_F1 + 1);
366 }
367 if vk.wrapping_sub(VK_NUMPAD0) <= 9 {
368 return alloc::format!("Num {}", vk - VK_NUMPAD0); }
370 let name = KEY_TABLE.iter().find(|k| k.0 == vk).map_or("?", |k| crate::i18n::tr(k.1));
371 String::from(name)
372}
373
374impl Ui {
375 fn pp_find(&self, id: u32) -> i32 {
379 let w = &self.wp.popup;
380 (0..w.pp_n as usize).rev().find(|&i| w.pp[i].id == id).map_or(-1, |i| i as i32)
381 }
382
383 pub(crate) fn pp_flag(&mut self, id: u32, f: u32) {
385 let i = self.pp_find(id);
386 if i >= 0 {
387 self.wp.popup.pp[i as usize].flags |= f;
388 }
389 }
390
391 fn pp_push(&mut self, id: u32, level: u32) -> usize {
393 let mut l = level.min(self.wp.popup.pp_n);
394 if l > POPUP_MAX as u32 - 1 {
395 l = POPUP_MAX as u32 - 1;
396 self.limit_hit(super::ctx::LIM_POPUPS);
397 }
398 let w = &mut self.wp.popup;
399 w.pp_n = l + 1;
400 w.pp[l as usize] = Pp {
401 id,
402 ret: self.wp.ctx.focus,
403 born: self.frame,
404 anchor: self.wp.ctx.last_rect,
405 ..Pp::default()
406 };
407 self.repass = true;
408 l as usize
409 }
410
411 fn pp_close_to(&mut self, level: u32) {
414 let w = &mut self.wp.popup;
415 if level >= w.pp_n {
416 return;
417 }
418 w.pp_n = level;
419 self.wp.ctx.focus = w.pp[level as usize].ret;
420 self.repass = true;
421 }
422
423 fn pp_dismiss_all(&mut self) {
425 self.pp_close_to(0);
426 if self.wp.popup.mb_mode {
427 self.wp.popup.mb_mode = false;
428 self.wp.ctx.focus = self.wp.popup.mb_prev;
429 self.repass = true;
430 }
431 }
432
433 pub(crate) fn open_key(&mut self) -> bool {
436 let k = self.key_first();
437 let vk = k & 0xFFFF;
438 let m = (k >> 16) & MOD3;
439 let yes = if vk == VK_F4 && m == 0 {
440 self.accel_find(VK_F4, 0).is_none() } else {
442 vk == VK_DOWN && m == MOD_ALT
443 };
444 if yes {
445 self.key(vk);
446 }
447 yes
448 }
449
450 pub(crate) fn accel_find(&self, vk: u32, mods: u32) -> Option<&'static Command> {
452 let t: &'static [Command] = self.desc.cmds;
453 t.iter().take_while(|c| c.id != 0).find(|c| c.key as u32 == vk && c.mods as u32 & MOD3 == mods)
454 }
455
456 fn last_widget(&self) -> (u32, [i32; 4]) {
459 self.sems.iter().rev().find(|r| r.id != 0).map_or((0, [0; 4]), |r| (r.id, self.phys_rect(r.rect)))
460 }
461
462 pub(crate) fn focus_is_text(&self) -> bool {
464 let c = &self.wp.ctx;
465 if c.focus == 0 {
466 return false;
467 }
468 c.hits[c.hit_cur].iter().rev().find(|h| h.id == c.focus).is_some_and(|h| h.flags as u32 & HB_TEXT != 0)
469 }
470
471 pub(crate) fn focus_was_text(&self) -> bool {
474 let c = &self.wp.ctx;
475 c.focus != 0 && c.hits[c.hit_cur ^ 1].iter().rev().find(|h| h.id == c.focus).is_some_and(|h| h.flags as u32 & HB_TEXT != 0)
476 }
477
478 fn pp_popup_area(&mut self) -> Option<([i32; 4], bool)> {
481 self.uiw_popup_area()
482 }
483
484 fn pp_noaccel(&self) -> bool {
486 self.m.win.noaccel
487 }
488
489 fn pp_bounds(&mut self, w: i32, h: i32) -> [i32; 4] {
495 let win = [0, 0, self.wp.rt.fb.w, self.wp.rt.fb.h];
496 match self.pp_popup_area() {
497 Some((r, true)) => r,
498 Some((r, false)) if w > win[2] || h > win[3] => r,
499 _ => win,
500 }
501 }
502
503 fn pp_nudge(&self, item: u32, r: [i32; 4]) -> (i32, i32) {
507 let p = self.phys_rect(r);
508 let (dx, dy) = self.uiw_popup_nudge(item, [p[0], p[1]]);
509 (if self.rtl { -dx } else { dx }, dy)
510 }
511
512 pub fn popup_is_open(&self, id: u32) -> bool {
516 self.pp_find(id) >= 0
517 }
518
519 pub fn popup_count(&self) -> u32 {
521 self.wp.popup.pp_n
522 }
523
524 pub fn popup_open(&mut self, id: u32) {
527 let i = self.pp_find(id);
528 if i >= 0 {
529 let n = i as u32 + 1;
530 if n < self.wp.popup.pp_n {
531 self.wp.popup.pp_n = n;
532 self.repass = true;
533 }
534 return;
535 }
536 let l = self.wp.popup.fl_level;
537 self.pp_push(id, l);
538 }
539
540 pub fn popup_close(&mut self) {
542 self.pp_dismiss_all();
543 }
544
545 fn popup_catcher(&mut self) -> bool {
549 if self.wp.popup.pp_n == 0 || self.wp.popup.catch_frame == self.frame {
550 return false;
551 }
552 self.wp.popup.catch_frame = self.frame;
553 let c = &mut self.wp.ctx;
554 if c.hot == CATCH_ID && c.pressed & BTN_MASK != 0 {
555 c.pressed &= !BTN_MASK;
556 self.pp_dismiss_all();
557 return true;
558 }
559 let (lr, clip) = (c.last_rect, c.uiclip);
560 let (w, h) = (self.wp.rt.fb.w, self.wp.rt.fb.h);
561 self.wp.ctx.last_rect = [0, 0, w, h];
562 self.wp.ctx.uiclip = [0, 0, w, h];
563 let prev = self.layer_set(L_POPUP);
564 self.hit(CATCH_ID, 0);
565 self.layer_set(prev);
566 self.wp.ctx.last_rect = lr;
567 self.wp.ctx.uiclip = clip;
568 false
569 }
570
571 #[cfg_attr(jfgui_dev, track_caller)]
578 pub fn flyout_begin(&mut self, id: u32, flags: u32) -> bool {
579 let _src = self.src_scope();
580 let sp_i = self.wp.popup.fl_sp as usize;
581 if sp_i >= FLN_MAX {
582 self.wp.popup.fl_over += 1;
583 self.limit_hit(super::ctx::LIM_FLYOUTS);
584 return false;
585 }
586 self.wp.popup.fl_sp += 1;
587 self.wp.popup.fln[sp_i].open = false;
588 let i = self.pp_find(id);
589 if i < 0 {
590 return false;
591 }
592 let idx = i as usize;
593 {
595 let (mx, my, lr) = (self.wp.ctx.mx, self.wp.ctx.my, self.wp.ctx.last_rect);
596 let e = &mut self.wp.popup.pp[idx];
597 if e.flags & PPF_POINT == 0 {
598 if flags & FL_POINT != 0 {
599 e.flags |= PPF_POINT;
600 e.anchor = [mx, my, 0, 0];
601 } else {
602 e.anchor = lr;
603 }
604 }
605 }
606 if self.popup_catcher() {
607 return false;
608 }
609 if self.wp.popup.pp_n.wrapping_sub(1) == idx as u32 && self.key(VK_ESCAPE) {
611 self.pp_close_to(idx as u32);
612 self.wp.ctx.focus_visible = true;
613 return false;
614 }
615 let boxid = id ^ BOXID_XOR;
617 let (mut fw, mut fh) = {
618 let s = self.slot(boxid);
619 (s.measw, s.meash)
620 };
621 let anchor = self.wp.popup.pp[idx].anchor;
622 let mut forced = 0; if flags & FL_MINW != 0 && fw < anchor[2] {
624 fw = anchor[2];
625 forced = fw;
626 }
627 let st = self.style;
628 let gap = st.flyout_pad(self);
629 let area = self.pp_bounds(fw, fh);
632 let mut room = area[3] - area[1];
636 if fh > self.wp.rt.fb.h {
637 if let Some(h) = self.uiw_popup_screen_h() {
638 room = room.min(h);
639 }
640 }
641 let mut scroll = false;
642 if fh > room {
643 fh = room;
644 scroll = true;
645 }
646 let mut x = anchor[0];
648 let mut y = anchor[1];
649 let point = self.wp.popup.pp[idx].flags & PPF_POINT != 0;
650 if point {
651 if x + fw > area[2] {
652 x -= fw;
653 }
654 if y + fh > area[3] {
655 y -= fh;
656 }
657 } else if flags & FL_RIGHT != 0 {
658 x += anchor[2];
660 y -= gap;
661 if x + fw > area[2] {
662 x = anchor[0] - fw;
663 }
664 } else if flags & FL_OVER != 0 {
665 if !scroll {
666 y -= self.wp.popup.fl_offy; }
668 } else {
669 if flags & FL_ALIGN_END != 0 {
670 x += anchor[2] - fw;
671 }
672 if flags & FL_ABOVE != 0 {
673 y -= gap + fh;
674 if y < area[1] {
675 y = anchor[1] + anchor[3] + gap;
676 }
677 } else {
678 y += anchor[3] + gap;
680 if y + fh > area[3] {
681 let a = anchor[1] - gap - fh;
682 if a >= area[1] {
683 y = a;
684 }
685 }
686 }
687 }
688 if x > area[2] - fw {
690 x = area[2] - fw;
691 }
692 if x < area[0] {
693 x = area[0];
694 }
695 if y > area[3] - fh {
696 y = area[3] - fh;
697 }
698 if y < area[1] {
699 y = area[1];
700 }
701 let kind = if point {
703 PA_POINT
704 } else if flags & FL_RIGHT != 0 {
705 PA_SIDE
706 } else if flags & FL_OVER != 0 {
707 PA_FREE
708 } else if y < anchor[1] {
709 PA_ABOVE
710 } else {
711 PA_BELOW
712 };
713 let key = [x, y];
714 let (dx, dy) = self.pp_nudge(idx as u32, [x, y, fw, fh]);
715 x += dx;
716 y += dy;
717 let e = &mut self.wp.popup.pp[idx];
718 e.kind = kind;
719 e.key = key;
720 let br = self.box_rect();
722 let xd = self.px2dip(x - br[0]);
723 let yd = self.px2dip(y - br[1]);
724 let wd = if forced != 0 { self.px2dip(forced) } else { 0 };
725 let hd = if scroll { self.px2dip(fh) } else { 0 };
726 self.next_rect(xd, yd, wd, hd);
727 let layer = self.layer_set(L_POPUP);
729 let w = &mut self.wp.popup;
730 let f = &mut w.fln[sp_i];
731 f.layer = layer;
732 f.clip = self.wp.ctx.uiclip;
733 f.dis = self.wp.ctx.disabled_depth;
734 self.wp.ctx.disabled_depth = 0;
735 self.wp.ctx.uiclip = area;
736 f.level = w.fl_level;
737 f.idx = idx as u32;
738 w.fl_level = idx as u32 + 1;
739 f.open = true;
740 f.scroll = scroll;
741 let frame = self.frame;
742 if !scroll {
743 let bf = st.flyout_box(self);
745 self.box_begin(boxid, bf, 0);
746 let e = &mut self.wp.popup.pp[idx];
747 e.rect = self.wp.ctx.last_rect;
748 e.nat = [e.rect[0] - dx, e.rect[1] - dy];
749 e.seen = frame;
750 self.fl_surface(idx, boxid);
751 return true;
752 }
753 let e = &mut self.wp.popup.pp[idx];
756 e.rect = [x, y, fw, fh];
757 e.nat = key;
758 e.seen = frame;
759 self.fl_surface(idx, boxid);
760 self.fl_scroll_begin(boxid ^ FLS_XOR);
761 let e = &mut self.wp.popup.pp[idx];
763 if e.flags & PPF_SCROLLED == 0 {
764 e.flags |= PPF_SCROLLED;
765 if flags & FL_OVER != 0 {
766 let oy = self.px2dip(self.wp.popup.fl_offy);
767 self.fl_scroll_to(boxid ^ FLS_XOR, oy);
768 }
769 }
770 let bf = st.flyout_box(self);
771 self.box_begin(boxid, bf, 0);
772 true
773 }
774
775 fn fl_scroll_begin(&mut self, id: u32) {
777 self.scroll_begin(id, SC_V);
778 }
779 fn fl_scroll_to(&mut self, id: u32, y_dip: i32) {
780 self.scroll_to(id, -1, y_dip);
781 }
782 fn fl_scroll_end(&mut self) {
783 self.scroll_end();
784 }
785
786 pub fn popup_ext(&self, i: u32) -> Option<[i32; 4]> {
790 self.popup_item(i, false)
791 }
792
793 pub fn popup_rect(&self, i: u32) -> Option<[i32; 4]> {
797 self.popup_item(i, true)
798 }
799
800 pub(crate) fn popup_place(&self, i: u32) -> Option<([i32; 4], u32, [i32; 2], [i32; 2])> {
804 let w = &self.wp.popup;
805 let (anchor, kind, nat, key, r) = if i == POPUP_MAX as u32 {
806 (w.tip_anchor, PA_BELOW, w.tip_nat, w.tip_nat, w.tip_rect)
807 } else {
808 let e = w.pp.get(i as usize).filter(|_| i < w.pp_n)?;
809 (e.anchor, e.kind, e.nat, e.key, e.rect)
810 };
811 let n = self.phys_rect([nat[0], nat[1], r[2], r[3]]);
812 let k = self.phys_rect([key[0], key[1], r[2], r[3]]);
813 Some((self.phys_rect(anchor), kind, [n[0], n[1]], [k[0], k[1]]))
814 }
815
816 fn popup_item(&self, i: u32, anywhere: bool) -> Option<[i32; 4]> {
817 let w = &self.wp.popup;
818 let r = if i == POPUP_MAX as u32 {
819 if w.tip_shown == 0 || w.tip_seen != self.frame {
820 return None;
821 }
822 w.tip_rect
823 } else {
824 if i > POPUP_MAX as u32 || i >= w.pp_n {
825 return None;
826 }
827 let e = &w.pp[i as usize];
828 if e.seen != self.frame {
829 return None;
830 }
831 e.rect
832 };
833 if r[2] <= 0 || r[3] <= 0 {
834 return None;
835 }
836 let r = self.phys_rect(r); if anywhere {
838 return Some(r);
839 }
840 let fb = &self.wp.rt.fb;
842 if r[0] < 0 || r[1] < 0 || r[2] + r[0] > fb.w || r[1] + r[3] > fb.h { Some(r) } else { None }
843 }
844
845 fn fl_surface(&mut self, idx: usize, boxid: u32) {
848 let n = self.wp.ctx.hits[self.wp.ctx.hit_cur].len() as u32;
849 let e = &mut self.wp.popup.pp[idx];
850 e.hit = n;
851 e.hitend = n;
852 let r = e.rect;
853 if r[2] <= 0 || r[3] <= 0 {
854 return;
855 }
856 let st = self.style;
857 let mut sp = Sp::default();
858 sp.place(r);
859 st.flyout_frame(self, &sp);
860 self.hit(boxid, HB_BLOCK);
861 }
862
863 #[cfg_attr(jfgui_dev, track_caller)]
865 pub fn flyout_end(&mut self) {
866 let _src = self.src_scope();
867 let w = &mut self.wp.popup;
868 if w.fl_over != 0 {
869 w.fl_over -= 1;
870 return;
871 }
872 if w.fl_sp == 0 {
873 return;
874 }
875 w.fl_sp -= 1;
876 let f = w.fln[w.fl_sp as usize];
877 if !f.open {
878 return;
879 }
880 self.box_end();
881 if f.scroll {
882 self.fl_scroll_end(); }
884 self.layer_set(f.layer);
885 self.wp.ctx.uiclip = f.clip;
886 self.wp.ctx.disabled_depth = f.dis;
887 self.wp.popup.fl_level = f.level;
888 let i = f.idx;
890 if i >= self.wp.popup.pp_n {
891 return;
892 }
893 let idx = i as usize;
894 self.wp.popup.pp[idx].hitend = self.wp.ctx.hits[self.wp.ctx.hit_cur].len() as u32;
895 let fid = self.wp.popup.pp[idx].focusid;
897 if fid != 0 {
898 let e = &mut self.wp.popup.pp[idx];
899 e.focusid = 0;
900 if e.flags & PPF_QUIET != 0 {
901 e.flags &= !PPF_QUIET;
902 self.wp.ctx.focus = fid;
903 self.repass = true;
904 } else {
905 self.focus(fid);
906 }
907 } else if self.wp.popup.pp[idx].flags & PPF_FOCUS != 0 {
908 self.wp.popup.pp[idx].flags &= !PPF_FOCUS;
909 let t = self.pp_nav(idx, 0);
910 if t != 0 {
911 self.focus(t);
912 }
913 }
914 if self.wp.popup.pp_n.wrapping_sub(1) == i {
916 let nav = if self.key_peek(VK_DOWN) {
917 Some((1, VK_DOWN))
918 } else if self.key_peek(VK_UP) {
919 Some((-1, VK_UP))
920 } else {
921 None
922 };
923 if let Some((dir, vk)) = nav {
924 let t = self.pp_nav(idx, dir);
925 if t != 0 {
926 self.key(vk);
927 self.focus(t);
928 }
929 }
930 }
931 self.wp.ctx.last_rect = self.wp.popup.pp[idx].rect;
932 }
933
934 fn pp_nav(&self, idx: usize, dir: i32) -> u32 {
937 let e = &self.wp.popup.pp[idx];
938 let c = &self.wp.ctx;
939 let list = &c.hits[c.hit_cur];
940 let (a, b) = ((e.hit as usize).min(list.len()), (e.hitend as usize).min(list.len()));
941 let (mut first, mut last, mut before, mut after, mut found, mut prev) = (0, 0, 0, 0, false, 0);
942 for h in &list[a..b.max(a)] {
943 if h.flags as u32 & (HB_FOCUS | HB_DISABLED) != HB_FOCUS || h.layer as u32 != L_POPUP {
944 continue;
945 }
946 let id = h.id;
947 if first == 0 {
948 first = id;
949 }
950 last = id;
951 if found {
952 if after == 0 {
953 after = id;
954 }
955 } else if id == c.focus {
956 found = true;
957 before = prev;
958 }
959 prev = id;
960 }
961 if dir == 0 {
962 return first;
963 }
964 if !found {
965 return if dir > 0 { first } else { last };
966 }
967 if dir > 0 {
968 if after != 0 { after } else { first }
969 } else if before != 0 {
970 before
971 } else {
972 last
973 }
974 }
975
976 pub fn cmd_find(&self, id: u32) -> Option<&'static Command> {
980 let t: &'static [Command] = self.desc.cmds;
981 t.iter().take_while(|c| c.id != 0).find(|c| c.id == id)
982 }
983
984 pub fn cmd_state(&mut self, id: u32, flags: u32) {
988 if id == 0 {
989 return;
990 }
991 let s = &mut self.m.popup;
992 let cur = match s.cst.get(&id) {
993 Some(&f) => f,
994 None if s.cst.len() >= CST_LIMIT => {
995 if !s.cst_warned {
996 s.cst_warned = true;
997 let m = alloc::format!("ui_cmd_state: more than {} command states (id {} ignored)", CST_LIMIT, id);
998 self.warn(&m);
999 }
1000 return;
1001 }
1002 None => u32::MAX, };
1004 self.m.popup.cst.insert(id, flags);
1005 if cur != flags {
1006 self.repass = true;
1007 }
1008 }
1009
1010 pub fn cmd_flags(&self, id: u32) -> u32 {
1012 if id != 0 {
1013 if let Some(&f) = self.m.popup.cst.get(&id) {
1014 return f;
1015 }
1016 }
1017 self.cmd_find(id).map_or(0, |c| c.flags as u32)
1018 }
1019
1020 pub fn cmd_label(&self, id: u32) -> String {
1022 strip_amp(self.cmd_find(id).map_or("", |c| c.label)).0
1023 }
1024
1025 pub fn accel_text(&self, id: u32) -> String {
1027 match self.cmd_find(id) {
1028 Some(c) => self.accel_fmt(c, 64),
1029 None => String::new(),
1030 }
1031 }
1032
1033 pub fn accel_order_set(&mut self, order: u32) {
1035 self.m.popup.accel_order = order;
1036 }
1037
1038 fn accel_fmt(&self, c: &Command, cap: usize) -> String {
1041 let mut s = String::new();
1042 if c.key == 0 {
1043 return s;
1044 }
1045 let modifier = |s: &mut String, name: &'static str| {
1046 cat(s, crate::i18n::tr(name), cap);
1047 cat(s, "+", cap);
1048 };
1049 let mut m = c.mods as u32;
1050 if m & MOD_CTRL != 0 {
1051 modifier(&mut s, "Strg");
1052 }
1053 if self.m.popup.accel_order != 0 && m & MOD_ALT != 0 {
1054 modifier(&mut s, "Alt"); m &= !MOD_ALT;
1056 }
1057 if m & MOD_SHIFT != 0 {
1058 modifier(&mut s, "Umschalt");
1059 }
1060 if m & MOD_ALT != 0 {
1061 modifier(&mut s, "Alt");
1062 }
1063 cat(&mut s, &key_name(c.key as u32), cap);
1064 s
1065 }
1066
1067 #[cfg_attr(jfgui_dev, track_caller)]
1071 pub fn cmd_button(&mut self, id: u32, flags: u32) -> u32 {
1072 let _src = self.src_scope();
1073 let Some(c) = self.cmd_find(id) else { return 0 };
1074 let cf = self.cmd_flags(id);
1075 if cf & CF_HIDDEN != 0 {
1076 return 0;
1077 }
1078 let mut f = flags;
1079 if cf & CF_DISABLED != 0 {
1080 f |= ST_DISABLED;
1081 }
1082 if cf & CF_CHECKED != 0 {
1083 f |= ST_CHECKED;
1084 }
1085 self.next_flags(f);
1086 if c.icon != 0 {
1087 self.next_icon(c.icon);
1088 }
1089 let lab = strip_amp(c.label).0;
1090 self.wp.popup.tip_suppress = true;
1091 let ev = self.button(id, &lab);
1092 self.wp.popup.tip_suppress = false;
1093 if ev & EV_ACT != 0 {
1094 self.cmd_run(id, 0);
1095 }
1096 let mut tip = String::new();
1098 cat(&mut tip, &lab, LAB_MAX);
1099 let acc = self.accel_fmt(c, 64);
1100 if !acc.is_empty() {
1101 cat(&mut tip, " (", LAB_MAX);
1102 cat(&mut tip, &acc, LAB_MAX);
1103 cat(&mut tip, ")", LAB_MAX);
1104 }
1105 self.tip_core(&tip);
1106 ev
1107 }
1108
1109 fn mitem(&mut self, id: u32, label: &str, icon: u32, accel: Option<&str>, mflags: u32) -> u32 {
1114 let mut mf = mflags;
1115 let lv = self.wp.popup.fl_level.wrapping_sub(1);
1118 if (lv as i32) >= 0 && mf & MIF_LIST == 0 && lv < self.wp.popup.pp_n {
1119 let e = &mut self.wp.popup.pp[lv as usize];
1120 if mf & MIF_CHECKED != 0 && icon != 0 && e.flags & PPF_TWOCOL == 0 {
1121 e.flags |= PPF_TWOCOL;
1122 self.repass = true;
1123 }
1124 if e.flags & PPF_TWOCOL != 0 {
1125 mf |= MIF_TWOCOL;
1126 }
1127 }
1128 let lab = strip_amp(label).0;
1129 let st = self.style;
1130 let mut sp = Sp { label: Some(&lab), label2: accel, flags: mf, ..Sp::default() };
1131 let (w, h) = st.menu_item_size(self, &mut sp);
1132 let r = self.place(w, h);
1133 let mut hb = HB_FOCUS;
1134 if mf & MIF_DISABLED != 0 {
1135 hb |= HB_DISABLED;
1136 }
1137 let ev = self.behave(id, hb);
1138 sp.place(r);
1139 sp.ev = ev;
1140 sp.flags = mf;
1141 sp.icon = icon;
1142 st.menu_item(self, &sp);
1143 let mut s = self.wp.ctx.last_state;
1144 if mf & MIF_CHECKED != 0 {
1145 s |= SS_CHECKED as u32;
1146 }
1147 if mf & MIF_OPEN != 0 {
1148 s |= SS_OPEN as u32;
1149 }
1150 if mf & MIF_SELECTED != 0 {
1151 s |= SS_SELECTED as u32;
1152 }
1153 self.wp.ctx.last_state = s;
1154 let kind = if mf & MIF_LIST != 0 { K_LISTITEM } else { K_MENUITEM };
1155 self.sem(kind, id, Some(&lab), 0);
1156 ev
1157 }
1158
1159 fn mcmd(&mut self, cmd: u32, id: u32) -> u32 {
1162 let c = self.cmd_find(cmd);
1163 let cf = self.cmd_flags(cmd);
1164 if cf & CF_HIDDEN != 0 {
1165 return 0;
1166 }
1167 let acc = match c {
1168 Some(c) => self.accel_fmt(c, 64),
1169 None => String::new(),
1170 };
1171 let mut mf = 0;
1172 if cf & CF_DISABLED != 0 {
1173 mf |= MIF_DISABLED;
1174 }
1175 if cf & CF_CHECKED != 0 {
1176 mf |= MIF_CHECKED;
1177 }
1178 let (label, icon) = match c {
1179 Some(c) => (c.label, c.icon),
1180 None => ("?", 0),
1181 };
1182 let ev = self.mitem(id, label, icon, Some(&acc), mf);
1183 if ev & EV_ACT != 0 {
1184 self.cmd_run(cmd, 0);
1185 self.pp_dismiss_all();
1186 }
1187 ev
1188 }
1189
1190 #[cfg_attr(jfgui_dev, track_caller)]
1193 pub fn menu_item(&mut self, id: u32, label: &str) -> u32 {
1194 let _src = self.src_scope();
1195 let f = self.take_flags();
1196 let mut mf = 0;
1197 if f & ST_DISABLED != 0 {
1198 mf |= MIF_DISABLED;
1199 }
1200 if f & ST_CHECKED != 0 {
1201 mf |= MIF_CHECKED;
1202 }
1203 let icon = self.take_icon();
1204 let ev = self.mitem(id, label, icon, None, mf);
1205 if ev & EV_ACT != 0 {
1206 self.pp_dismiss_all();
1207 }
1208 ev
1209 }
1210
1211 #[cfg_attr(jfgui_dev, track_caller)]
1214 pub fn menu_item_keys(&mut self, id: u32, label: &str, keys: &str) -> u32 {
1215 let _src = self.src_scope();
1216 let f = self.take_flags();
1217 let mut mf = 0;
1218 if f & ST_DISABLED != 0 {
1219 mf |= MIF_DISABLED;
1220 }
1221 if f & ST_CHECKED != 0 {
1222 mf |= MIF_CHECKED;
1223 }
1224 let icon = self.take_icon();
1225 let ev = self.mitem(id, label, icon, if keys.is_empty() { None } else { Some(keys) }, mf);
1226 if ev & EV_ACT != 0 {
1227 self.pp_dismiss_all();
1228 }
1229 ev
1230 }
1231
1232 pub fn key_text(&self, vk: u32, mods: u32) -> String {
1235 self.accel_fmt(&Command::new(0, "", 0, mods, vk, 0), 64)
1236 }
1237
1238 #[cfg_attr(jfgui_dev, track_caller)]
1240 pub fn menu_cmd(&mut self, cmd: u32) -> u32 {
1241 let _src = self.src_scope();
1242 self.mcmd(cmd, cmd)
1243 }
1244
1245 #[cfg_attr(jfgui_dev, track_caller)]
1247 pub fn menu_sep(&mut self) -> u32 {
1248 let _src = self.src_scope();
1249 let st = self.style;
1250 let h = st.menu_sep_size(self);
1251 let r = self.place(0, h);
1252 let mut sp = Sp::default();
1253 sp.place(r);
1254 st.menu_sep(self, &sp);
1255 0
1256 }
1257
1258 #[cfg_attr(jfgui_dev, track_caller)]
1261 pub fn menu_list_empty(&mut self, base: u32, text: &str) {
1262 let _src = self.src_scope();
1263 let l = &mut self.m.popup.list_empty;
1264 match l.iter_mut().find(|e| e.0 == base) {
1265 Some(e) => {
1266 if e.1 != text {
1267 e.1 = String::from(text);
1268 }
1269 }
1270 None => l.push((base, String::from(text))),
1271 }
1272 }
1273
1274 #[cfg_attr(jfgui_dev, track_caller)]
1277 pub fn menu_list<T: AsRef<str>>(&mut self, base: u32, rows: &[T]) {
1278 let _src = self.src_scope();
1279 let v: Vec<String> = rows.iter().map(|s| String::from(s.as_ref())).collect();
1280 let l = &mut self.m.popup.lists;
1281 match l.iter_mut().find(|e| e.0 == base) {
1282 Some(e) => e.1 = v,
1283 None => l.push((base, v)),
1284 }
1285 }
1286
1287 fn row_mnemonic(&self, rec: &Mi) -> u32 {
1289 let label = match rec {
1290 Mi::Cmd(c) => self.cmd_find(*c).map(|c| c.label),
1291 r => r.label(),
1292 };
1293 strip_amp(label.unwrap_or("")).1
1294 }
1295
1296 fn menu_flyout(&mut self, menu: &'static [Mi], pid: u32, flags: u32) -> bool {
1299 let mut rows = 0usize;
1300 let mut ids = [0u32; MENU_MAX];
1301 let mut recs = [0u8; MENU_MAX];
1302 let mut activated = false;
1303 let mut open_sub: Option<(&'static [Mi], u32, [i32; 4])> = None;
1306 let open = self.flyout_begin(pid, flags);
1307 if !open {
1308 self.flyout_end();
1309 return false;
1310 }
1311 let level = self.pp_find(pid) as u32;
1312 let remember = |rows: &mut usize, ids: &mut [u32; MENU_MAX], recs: &mut [u8; MENU_MAX], id: u32, ri: usize| {
1314 if *rows < MENU_MAX {
1315 ids[*rows] = id;
1316 recs[*rows] = ri as u8;
1317 *rows += 1;
1318 }
1319 };
1320 for (ri, rec) in menu.iter().enumerate() {
1321 if ri >= MENU_MAX {
1322 self.limit_hit(super::ctx::LIM_MENU_ROWS);
1323 break;
1324 }
1325 let rid = pid.wrapping_add(ri as u32 + 1); let ev = match *rec {
1327 Mi::Sep => {
1328 self.menu_sep();
1329 continue;
1330 }
1331 Mi::Cmd(c) => {
1332 if self.cmd_flags(c) & CF_HIDDEN != 0 {
1333 continue;
1334 }
1335 self.mcmd(c, rid)
1336 }
1337 Mi::Item(id, label, icon) => {
1338 let ev = self.mitem(rid, label, icon, None, 0);
1339 if ev & EV_ACT != 0 {
1340 self.cmd_run(id, 0);
1341 self.pp_dismiss_all();
1342 }
1343 ev
1344 }
1345 Mi::Sub(label, sub) => {
1346 let spid = menu_pid(sub, rid); let sopen = self.pp_find(spid) >= 0;
1348 let mf = if sopen { MIF_SUB | MIF_OPEN } else { MIF_SUB };
1349 let ev = self.mitem(rid, label, 0, None, mf);
1350 remember(&mut rows, &mut ids, &mut recs, rid, ri);
1351 if !sopen {
1352 let entered = ev & EV_HOVER != 0 && self.wp.ctx.hot != self.wp.popup.hot_prev;
1355 if ev & EV_ACT == 0 && !entered {
1356 continue;
1357 }
1358 self.popup_open(spid);
1359 if ev & EV_ACT != 0 && self.wp.ctx.focus_visible {
1360 self.pp_flag(spid, PPF_FOCUS);
1361 }
1362 }
1363 open_sub = Some((sub, spid, self.wp.ctx.last_rect));
1364 continue;
1365 }
1366 Mi::List(base, max, empty) => {
1367 let items: Vec<String> =
1370 self.m.popup.lists.iter().find(|e| e.0 == base).map_or(Vec::new(), |e| e.1.clone());
1371 let mut k = 0u32;
1372 for s in items.iter() {
1373 if s.is_empty() || k >= max as u32 {
1374 break;
1375 }
1376 let id = base.wrapping_add(k);
1377 let ev = self.mitem(id, s, 0, None, 0);
1378 if ev & EV_ACT != 0 {
1379 activated = true;
1380 self.cmd_run(id, 0);
1381 self.pp_dismiss_all();
1382 } else if ev & EV_HOVER != 0
1383 && self.wp.ctx.hot != self.wp.popup.hot_prev
1384 && level + 1 < self.wp.popup.pp_n
1385 {
1386 self.pp_close_to(level + 1);
1388 }
1389 k += 1;
1390 }
1391 let reason = self.m.popup.list_empty.iter().find(|e| e.0 == base && !e.1.is_empty()).map(|e| e.1.clone());
1392 let empty = reason.as_deref().unwrap_or(empty);
1393 if k == 0 && !empty.is_empty() {
1394 self.mitem(rid, empty, 0, None, MIF_DISABLED); }
1396 continue;
1397 }
1398 Mi::Menu(..) => continue,
1399 };
1400 if ev & EV_ACT != 0 {
1402 activated = true;
1403 }
1404 if ev & EV_HOVER != 0 && self.wp.ctx.hot != self.wp.popup.hot_prev && level + 1 < self.wp.popup.pp_n {
1406 self.pp_close_to(level + 1);
1407 }
1408 if self.wp.ctx.last_state & SS_DISABLED as u32 == 0 {
1409 remember(&mut rows, &mut ids, &mut recs, rid, ri);
1410 }
1411 }
1412 'keys: {
1414 if activated || self.wp.popup.pp_n.wrapping_sub(1) != level {
1415 break 'keys;
1416 }
1417 let focus = self.wp.ctx.focus;
1419 let sel = (0..rows).rev().find(|&j| ids[j] == focus).map(|j| (ids[j], recs[j] as usize));
1420 if let Some((rid, ri)) = sel {
1422 if let Mi::Sub(_, sub) = menu[ri] {
1423 if self.key(VK_RIGHT) {
1424 self.mf_open_sub(sub, rid);
1425 break 'keys;
1426 }
1427 }
1428 }
1429 if level != 0 && self.key(VK_LEFT) {
1431 self.pp_close_to(level);
1432 self.wp.ctx.focus_visible = true;
1433 break 'keys;
1434 }
1435 let k = self.key_first();
1437 if k == 0 || k & (MOD_CTRL << 16) != 0 {
1438 break 'keys;
1439 }
1440 let vk = k & 0xFFFF;
1441 if !is_alnum_vk(vk) {
1442 break 'keys;
1443 }
1444 for j in 0..rows {
1445 let rec = menu[recs[j] as usize];
1446 if self.row_mnemonic(&rec) != vk {
1447 continue;
1448 }
1449 self.key(vk);
1450 let rid = ids[j];
1451 if let Mi::Sub(_, sub) = rec {
1452 self.focus(rid);
1453 self.mf_open_sub(sub, rid);
1454 } else {
1455 self.cmd_run(rec.arg(), 0);
1456 self.pp_dismiss_all();
1457 }
1458 break;
1459 }
1460 }
1461 self.flyout_end();
1462 if let Some((sub, spid, row)) = open_sub {
1463 let rect = self.wp.ctx.last_rect;
1464 self.wp.ctx.last_rect = row; self.menu_flyout(sub, spid, FL_RIGHT);
1466 self.wp.ctx.last_rect = rect;
1467 }
1468 true
1469 }
1470
1471 fn mf_open_sub(&mut self, sub: &'static [Mi], rid: u32) {
1473 let pid = menu_pid(sub, rid);
1474 self.popup_open(pid);
1475 self.pp_flag(pid, PPF_FOCUS);
1476 }
1477
1478 fn mb_menu(mb: &'static [Mi], i: usize) -> &'static [Mi] {
1482 match mb[i] {
1483 Mi::Menu(_, m) | Mi::Sub(_, m) => m,
1484 _ => &[],
1485 }
1486 }
1487
1488 fn mb_open_index(&self, mb: &'static [Mi], base: u32, n: usize) -> i32 {
1490 if self.wp.popup.pp_n == 0 {
1491 return -1;
1492 }
1493 let top = self.wp.popup.pp[0].id;
1494 (0..n).find(|&i| menu_pid(Self::mb_menu(mb, i), base.wrapping_add(i as u32)) == top).map_or(-1, |i| i as i32)
1495 }
1496
1497 fn mb_open(&mut self, mb: &'static [Mi], base: u32, i: usize, focus: bool) {
1499 let pid = menu_pid(Self::mb_menu(mb, i), base.wrapping_add(i as u32));
1500 let w = &mut self.wp.popup;
1502 let mut ret = self.wp.ctx.focus;
1503 if w.pp_n != 0 {
1504 ret = w.pp[0].ret;
1505 }
1506 if w.mb_mode {
1507 ret = base.wrapping_add(i as u32);
1508 }
1509 w.pp_n = 0;
1510 let e = self.pp_push(pid, 0);
1511 let e = &mut self.wp.popup.pp[e];
1512 e.ret = ret;
1513 if focus {
1514 e.flags |= PPF_FOCUS;
1515 }
1516 }
1517
1518 #[cfg_attr(jfgui_dev, track_caller)]
1523 pub fn menubar(&mut self, menubar: Option<&'static [Mi]>) -> u32 {
1524 let _src = self.src_scope();
1525 let mb: &'static [Mi] = match menubar {
1526 Some(m) => m,
1527 None => self.desc.menubar,
1528 };
1529 if mb.is_empty() {
1530 return 0;
1531 }
1532 let a = (mb.as_ptr() as usize as u64).wrapping_mul(0x9E37_79B9_7F4A_7C15);
1533 let base = ((a >> 36) as u32 & 0x0FFF_FF00) | 0xC000_0000;
1534 let mut mn = [0u8; MB_MAX];
1536 let mut mpos = [0u8; MB_MAX];
1537 let mut n = 0usize;
1538 while n < mb.len() && n < MB_MAX {
1539 let Mi::Menu(label, _) = mb[n] else { break };
1540 let (_, m, pos) = strip_amp(label);
1541 mn[n] = m as u8;
1542 mpos[n] = pos as u8;
1543 n += 1;
1544 }
1545 if n == MB_MAX && matches!(mb.get(n), Some(Mi::Menu(..))) {
1546 self.limit_hit(super::ctx::LIM_MENUBAR);
1547 }
1548 if n == 0 {
1549 return 0;
1550 }
1551 let mut open = self.mb_open_index(mb, base, n);
1552 let fi0 = self.wp.ctx.focus.wrapping_sub(base);
1553 let mut fi: i32 = if (fi0 as usize) < n { fi0 as i32 } else { -1 };
1554 if self.wp.popup.mb_mode && fi < 0 && self.wp.popup.pp_n == 0 {
1556 self.wp.popup.mb_mode = false;
1557 }
1558 'keys: {
1559 if self.modal_active() != 0 {
1560 break 'keys; }
1562 let mut toggle = false;
1563 let k = self.key_first();
1564 let vk = k & 0xFFFF;
1565 let allm = k >> 16; let m = allm & MOD3;
1567 if vk == VK_MENU {
1569 self.wp.popup.alt_armed = allm & (MOD_CTRL | MOD_SHIFT | MOD_ALTGR) == 0;
1570 } else if vk != 0 || self.wp.ctx.pressed & BTN_MASK != 0 {
1571 self.wp.popup.alt_armed = false;
1572 } else if self.wp.popup.alt_armed && !self.key_down(VK_MENU) {
1573 self.wp.popup.alt_armed = false;
1574 if self.headless || plat::window_state() & plat::WST_ACTIVE != 0 {
1575 toggle = true;
1576 }
1577 }
1578 if vk == VK_F10 && m == 0 {
1579 self.key(VK_F10);
1580 toggle = true;
1581 }
1582 if toggle {
1583 if !self.wp.popup.mb_mode && open < 0 {
1584 let w = &mut self.wp.popup;
1586 let mut f = self.wp.ctx.focus;
1587 if w.pp_n != 0 {
1588 f = w.pp[0].ret;
1589 w.pp_n = 0;
1590 }
1591 w.mb_prev = f;
1592 w.mb_mode = true;
1593 self.focus(base);
1594 fi = 0;
1595 open = -1;
1596 } else {
1597 self.pp_dismiss_all();
1598 open = -1;
1599 fi = -1;
1600 }
1601 break 'keys;
1602 }
1603 if m & MOD_ALT != 0 {
1605 if m & MOD_CTRL != 0 {
1606 break 'keys;
1607 }
1608 let i = mnem_find(&mn[..n], vk);
1609 if i < 0 {
1610 break 'keys;
1611 }
1612 self.key(vk);
1613 if !self.wp.popup.mb_mode {
1614 let w = &mut self.wp.popup;
1615 let mut f = self.wp.ctx.focus;
1616 if w.pp_n != 0 {
1617 f = w.pp[0].ret;
1618 }
1619 w.mb_prev = f;
1620 w.mb_mode = true;
1621 }
1622 self.mb_open(mb, base, i as usize, true);
1623 open = i;
1624 break 'keys;
1625 }
1626 if fi < 0 || open >= 0 || vk == 0 || m & (MOD_CTRL | MOD_ALT) != 0 {
1628 break 'keys;
1629 }
1630 let mut i = fi;
1631 match vk {
1632 VK_LEFT | VK_RIGHT => {
1633 self.key(vk);
1634 if vk == VK_LEFT {
1635 i = if fi - 1 < 0 { n as i32 - 1 } else { fi - 1 };
1636 } else {
1637 i = if fi + 1 < n as i32 { fi + 1 } else { 0 };
1638 }
1639 fi = i;
1640 self.focus(base.wrapping_add(i as u32));
1641 break 'keys;
1642 }
1643 VK_ESCAPE => {
1644 self.key(vk);
1645 self.wp.popup.mb_mode = false;
1646 self.wp.ctx.focus = self.wp.popup.mb_prev;
1647 self.repass();
1648 fi = -1;
1649 break 'keys;
1650 }
1651 VK_DOWN | VK_UP | VK_RETURN | VK_SPACE => {}
1652 _ => {
1653 i = mnem_find(&mn[..n], vk);
1654 if i < 0 {
1655 break 'keys;
1656 }
1657 }
1658 }
1659 self.key(vk);
1660 self.mb_open(mb, base, i as usize, true);
1661 open = i;
1662 }
1663 if open >= 0 && self.popup_catcher() {
1665 open = -1;
1666 fi = -1;
1667 }
1668 self.box_begin(base | 0xFF, BOX_H | BOX_XCENTER | PAD_4, 4);
1669 let st = self.style;
1670 let mut rects = [[0i32; 4]; MB_MAX];
1671 for i in 0..n {
1672 let Mi::Menu(label, _) = mb[i] else { break };
1673 let lab = strip_amp(label).0;
1674 let mut sp = Sp { label: Some(&lab), ..Sp::default() };
1675 let (w, h) = st.menubar_item_size(self, &mut sp);
1676 let r = self.place(w, h);
1677 rects[i] = r;
1678 let id = base.wrapping_add(i as u32); let ev = self.behave(id, 0);
1680 let state = self.wp.ctx.last_state;
1681 if open >= 0 {
1683 let prev = self.layer_set(L_POPUP);
1684 self.hit(id, 0);
1685 self.layer_set(prev);
1686 }
1687 let c = &self.wp.ctx;
1689 if ev & EV_HELD != 0 && c.pressed & (1 << BTN_LEFT) != 0 && c.hot == id {
1690 if open == i as i32 {
1691 self.pp_dismiss_all();
1692 open = -1;
1693 } else {
1694 self.mb_open(mb, base, i, false);
1695 open = i as i32;
1696 }
1697 } else if ev & EV_HOVER != 0 && open >= 0 && open != i as i32 && c.hot != self.wp.popup.hot_prev {
1698 self.mb_open(mb, base, i, false);
1700 open = i as i32;
1701 }
1702 sp.place(self.wp.ctx.last_rect);
1704 sp.ev = ev;
1705 let mut f = 0;
1706 if open == i as i32 {
1707 f |= 1; }
1709 if fi == i as i32 {
1710 f |= 2; }
1712 sp.flags = f;
1713 sp.value = -1; if self.wp.popup.mb_mode && mpos[i] != 0xFF {
1715 sp.value = mpos[i] as i32;
1716 }
1717 st.menubar_item(self, &sp);
1718 if fi == i as i32 && open < 0 {
1719 self.focus_ring_draw(ev);
1720 }
1721 let mut s = state;
1722 if open == i as i32 {
1723 s |= SS_OPEN as u32;
1724 }
1725 self.wp.ctx.last_state = s;
1726 self.sem(K_MENU, id, Some(&lab), 0);
1727 }
1728 self.box_end();
1729 if open >= 0 {
1731 let oi = open as usize;
1732 self.wp.ctx.last_rect = rects[oi];
1733 let menu = Self::mb_menu(mb, oi);
1734 let pid = menu_pid(menu, base.wrapping_add(oi as u32));
1735 self.menu_flyout(menu, pid, FL_BELOW);
1736 if self.wp.popup.pp_n == 1 && self.wp.popup.pp[0].id == pid {
1738 let d = if self.key(VK_LEFT) {
1739 -1
1740 } else if self.key(VK_RIGHT) {
1741 1
1742 } else {
1743 0
1744 };
1745 if d != 0 {
1746 let mut j = open + d;
1747 if j < 0 {
1748 j = n as i32 - 1;
1749 }
1750 if j >= n as i32 {
1751 j = 0;
1752 }
1753 open = j;
1754 self.mb_open(mb, base, j as usize, true);
1755 }
1756 }
1757 }
1758 if open >= 0 { EV_OPEN } else { 0 }
1759 }
1760
1761 fn ctx_open(&mut self, menu: &'static [Mi], x: i32, y: i32, focus: bool) {
1765 let pid = menu_pid(menu, CTX_SALT);
1766 let w = &mut self.wp.popup;
1767 let mut ret = self.wp.ctx.focus;
1768 if w.pp_n != 0 {
1769 ret = w.pp[0].ret;
1770 w.pp_n = 0;
1771 }
1772 let i = self.pp_push(pid, 0);
1773 let e = &mut self.wp.popup.pp[i];
1774 e.ret = ret;
1775 e.menu = Some(menu);
1776 e.flags |= PPF_AUTO | PPF_POINT | if focus { PPF_FOCUS } else { 0 };
1777 e.anchor = [x, y, 0, 0];
1778 }
1779
1780 #[cfg_attr(jfgui_dev, track_caller)]
1784 pub fn menu_popup(&mut self, menu: &'static [Mi]) {
1785 let _src = self.src_scope();
1786 self.wp.popup.ctx_target = 0;
1787 if let Some(r) = self.kb_ctx_rect() {
1788 self.ctx_open(menu, r[0], r[1] + r[3], true);
1789 return;
1790 }
1791 let (x, y) = (self.wp.ctx.mx, self.wp.ctx.my);
1792 self.ctx_open(menu, x, y, false);
1793 }
1794
1795 #[cfg_attr(jfgui_dev, track_caller)]
1800 pub fn context_menu(&mut self, menu: &'static [Mi]) -> u32 {
1801 let _src = self.src_scope();
1802 let (id, r) = self.last_widget();
1803 if id == 0 {
1804 return 0;
1805 }
1806 let c = &self.wp.ctx;
1807 if c.hot == id && c.released & (1 << BTN_RIGHT) != 0 {
1808 let (x, y) = (c.mx, c.my);
1809 self.ctx_open(menu, x, y, false);
1810 self.wp.popup.ctx_target = id;
1811 } else if self.wp.ctx.focus == id || self.list_row_focused(id) {
1812 let mut open = self.key(VK_APPS);
1813 if !open {
1814 let k = self.key_first();
1815 if k & 0xFFFF == VK_F10 && (k >> 16) & MOD3 == MOD_SHIFT {
1816 self.key(VK_F10);
1817 open = true;
1818 }
1819 }
1820 if open {
1821 self.ctx_open(menu, r[0], r[1] + r[3], true);
1822 self.wp.popup.ctx_target = id;
1823 }
1824 }
1825 if self.pp_find(menu_pid(menu, CTX_SALT)) < 0 || self.wp.popup.ctx_target != id {
1826 return 0;
1827 }
1828 EV_OPEN
1829 }
1830
1831 pub fn context_target(&self) -> u32 {
1833 self.wp.popup.ctx_target
1834 }
1835
1836 #[cfg_attr(jfgui_dev, track_caller)]
1843 pub fn dropdown_button(&mut self, id: u32, label: &str) -> u32 {
1844 let _src = self.src_scope();
1845 let flags = self.take_flags();
1846 let icon = self.take_icon();
1847 let st = self.style;
1848 let mut sp = Sp { flags, icon, label: Some(label), ..Sp::default() };
1849 let (w, h) = st.dropdown_size(self, &mut sp);
1850 let (w, h) = self.ctrl_size(w, h, flags, false); self.place(w, h);
1852 let mut hb = if flags & ST_NOFOCUS != 0 { 0 } else { HB_FOCUS };
1853 if flags & ST_DISABLED != 0 {
1854 hb |= HB_DISABLED;
1855 }
1856 let ev = self.behave(id, hb);
1857 let state = self.wp.ctx.last_state;
1858 let mut open = self.popup_is_open(id);
1859 if !open {
1860 let mut go = ev & EV_ACT != 0;
1861 if !go && ev & EV_FOCUS != 0 && self.open_key() {
1862 self.wp.ctx.focus_visible = true;
1863 go = true;
1864 }
1865 if go {
1866 self.popup_open(id);
1867 open = true;
1868 if self.wp.ctx.focus_visible {
1869 self.pp_flag(id, PPF_FOCUS);
1870 }
1871 }
1872 }
1873 sp.place(self.wp.ctx.last_rect);
1874 sp.ev = ev;
1875 sp.value = open as i32;
1876 sp.value2 = (state == SS_DISABLED as u32) as i32;
1877 st.dropdown(self, &sp);
1878 self.focus_ring_draw(ev);
1879 self.wp.ctx.last_state = state | if open { SS_OPEN as u32 } else { 0 };
1880 self.sem(K_BUTTON, id, Some(label), 0);
1881 if flags & ST_ICON_ONLY != 0 && !open {
1882 self.tooltip(label);
1883 }
1884 if open { ev | EV_OPEN } else { ev }
1885 }
1886
1887 #[cfg_attr(jfgui_dev, track_caller)]
1891 pub fn split_button(&mut self, id: u32, label: &str) -> u32 {
1892 let _src = self.src_scope();
1893 let flags = self.take_flags();
1894 let icon = self.take_icon();
1895 let st = self.style;
1896 let mut sp = Sp { icon, label: Some(label), ..Sp::default() };
1897 let (w, h) = st.split_size(self, &mut sp);
1898 let (w, h) = self.ctrl_size(w, h, flags, false); let (mw, cw) = (sp.s[0], sp.s[1]);
1900 let whole = self.place(w, h);
1901 let dis = if flags & ST_DISABLED != 0 { HB_DISABLED } else { 0 };
1902 self.wp.ctx.last_rect[0] += mw;
1904 self.wp.ctx.last_rect[2] = cw;
1905 let evc = self.behave(id | 0x8000_0000, dis);
1906 let stc = self.wp.ctx.last_state;
1907 self.wp.ctx.last_rect[0] = whole[0];
1909 self.wp.ctx.last_rect[2] = mw;
1910 let evm = self.behave(id, dis | HB_FOCUS);
1911 let stm = self.wp.ctx.last_state;
1912 let mut open = self.popup_is_open(id);
1913 if !open {
1914 if evc & EV_ACT != 0 {
1915 self.popup_open(id);
1916 open = true;
1917 } else if evm & EV_FOCUS != 0 && self.open_key() {
1918 self.wp.ctx.focus_visible = true;
1919 self.popup_open(id);
1920 self.pp_flag(id, PPF_FOCUS);
1921 open = true;
1922 }
1923 }
1924 sp.place(whole);
1925 sp.ev = evm; sp.s[2] = evc as i32; sp.value = open as i32;
1928 sp.value2 = (stm == SS_DISABLED as u32) as i32 | ((stc == SS_DISABLED as u32) as i32) << 1;
1929 sp.s[0] = mw;
1930 sp.s[1] = cw;
1931 st.split(self, &sp);
1932 let os = if open { SS_OPEN as u32 } else { 0 };
1933 self.wp.ctx.last_rect[0] = whole[0] + mw;
1935 self.wp.ctx.last_rect[2] = cw;
1936 self.wp.ctx.last_state = stc | os;
1937 self.sem(K_BUTTON, id | 0x8000_0000, None, 0);
1938 self.wp.ctx.last_rect = whole;
1940 self.focus_ring_draw(evm);
1941 self.wp.ctx.last_rect[2] = mw;
1942 self.wp.ctx.last_state = stm | os;
1943 self.sem(K_BUTTON, id, Some(label), 0);
1944 self.wp.ctx.last_rect = whole;
1945 if open { evm | EV_OPEN } else { evm }
1946 }
1947
1948 #[cfg_attr(jfgui_dev, track_caller)]
1954 pub fn combobox(&mut self, id: u32, sel: &mut i32, list: &[&str]) -> u32 {
1955 let _src = self.src_scope();
1956 let flags = self.take_flags();
1957 let mut changed = false;
1958 self.ts_font(TS_BODY);
1960 let n = strlist_n(list);
1961 let mut widest = 0;
1962 for s in &list[..n] {
1963 let w = self.text.width(s);
1964 if w > widest {
1965 widest = w;
1966 }
1967 }
1968 let st = self.style;
1969 let mut sp = Sp { flags, value: widest, ..Sp::default() };
1970 let (w, h) = st.combo_size(self, &mut sp);
1971 let (w, h) = self.ctrl_size(w, h, flags, false); let rect = self.place(w, h);
1973 let mut hb = HB_FOCUS;
1974 if flags & ST_DISABLED != 0 {
1975 hb |= HB_DISABLED;
1976 }
1977 let ev = self.behave(id, hb);
1978 let state = self.wp.ctx.last_state;
1979 let mut open = self.popup_is_open(id);
1980 let rowbase = (id.wrapping_mul(0x9E37_79B1) & 0x0FFF_FF00) | 0xD000_0000;
1981 if !open {
1982 let mut go = ev & EV_ACT != 0;
1983 if !go && ev & EV_FOCUS != 0 {
1984 if self.open_key() {
1985 self.wp.ctx.focus_visible = true;
1986 go = true;
1987 } else if self.key(VK_DOWN) {
1988 let v = sel.wrapping_add(1);
1989 if v < n as i32 {
1990 *sel = v;
1991 changed = true;
1992 self.wp.ctx.focus_visible = true;
1993 }
1994 } else if self.key(VK_UP) {
1995 let v = sel.wrapping_sub(1);
1996 if v >= 0 {
1997 *sel = v;
1998 changed = true;
1999 self.wp.ctx.focus_visible = true;
2000 }
2001 }
2002 }
2003 if go {
2004 self.popup_open(id);
2005 open = true;
2006 let i = self.pp_find(id);
2008 if i >= 0 {
2009 let c = *sel;
2010 if c >= 0 && c < n as i32 {
2011 let fv = self.wp.ctx.focus_visible;
2012 let e = &mut self.wp.popup.pp[i as usize];
2013 e.focusid = rowbase.wrapping_add(c as u32 + 1);
2014 if !fv {
2015 e.flags |= PPF_QUIET;
2016 }
2017 }
2018 }
2019 }
2020 }
2021 let mut sp = Sp { flags, label: strlist_at(list, *sel), ev, value2: (state == SS_DISABLED as u32) as i32, ..Sp::default() };
2023 sp.place(rect);
2024 st.combo(self, &sp);
2025 self.focus_ring_draw(ev);
2026 if open {
2028 let r = if *sel < 0 || *sel >= n as i32 { 0 } else { *sel };
2029 let ih = st.menu_item_h(self);
2030 let pad = st.flyout_pad(self);
2031 self.wp.popup.fl_offy = r.wrapping_mul(ih) + pad;
2032 self.wp.ctx.last_rect = [rect[0] - pad, rect[1], rect[2] + pad * 2, rect[3]];
2034 if self.flyout_begin(id, FL_OVER | FL_MINW) {
2035 for (k, s) in list[..n].iter().enumerate() {
2036 let k = k as i32;
2037 let mut mf = MIF_LIST;
2038 if k == *sel {
2039 mf |= MIF_SELECTED;
2040 }
2041 let e = self.mitem(rowbase.wrapping_add(k as u32 + 1), s, 0, None, mf);
2042 if e & EV_ACT != 0 {
2043 if k != *sel {
2044 *sel = k;
2045 changed = true;
2046 }
2047 let i = self.pp_find(id);
2048 if i >= 0 {
2049 self.pp_close_to(i as u32);
2050 }
2051 }
2052 }
2053 }
2054 self.flyout_end();
2055 self.wp.ctx.last_rect = rect;
2056 }
2057 let lab = strlist_at(list, *sel);
2058 let open = self.popup_is_open(id);
2059 self.wp.ctx.last_state = state | if open { SS_OPEN as u32 } else { 0 };
2060 let v = *sel;
2061 self.sem(K_COMBO, id, lab, v).max = n as i32;
2062 let mut r = ev & !EV_ACT;
2063 if changed {
2064 r |= EV_ACT;
2065 self.repass();
2066 }
2067 if open {
2068 r |= EV_OPEN;
2069 }
2070 r
2071 }
2072
2073 #[cfg_attr(jfgui_dev, track_caller)]
2079 pub fn tooltip(&mut self, s: &str) {
2080 let _src = self.src_scope();
2081 if self.wp.popup.tip_suppress {
2082 self.wp.popup.tip_suppress = false;
2083 return;
2084 }
2085 self.tip_core(s);
2086 }
2087
2088 #[cfg_attr(jfgui_dev, track_caller)]
2093 pub fn tooltip_part(&mut self, s: &str, part: u64) {
2094 let _src = self.src_scope();
2095 let (id, _) = self.last_widget();
2096 if id == 0 {
2097 return;
2098 }
2099 if self.wp.ctx.hot != id || part == 0 {
2100 if self.wp.popup.tip_id == id {
2101 self.tip_reset();
2102 }
2103 self.wp.popup.tip_part = 0;
2104 return;
2105 }
2106 if self.wp.popup.tip_part != part {
2107 let w = &mut self.wp.popup;
2109 w.tip_part = part;
2110 if w.tip_block == id {
2111 w.tip_block = 0;
2112 }
2113 if w.tip_id == id {
2114 self.tip_reset();
2115 }
2116 }
2117 self.tip_core(s);
2118 }
2119
2120 fn tip_core(&mut self, s: &str) {
2122 let (id, _) = self.last_widget();
2123 if id == 0 {
2124 return;
2125 }
2126 let w = &mut self.wp.popup;
2127 if self.wp.ctx.hot != id {
2128 if w.tip_block == id {
2129 w.tip_block = 0;
2130 }
2131 if w.tip_id == id {
2132 self.tip_reset();
2133 }
2134 return;
2135 }
2136 if w.tip_block == id {
2137 return;
2138 }
2139 let press = if self.m.touch.on { 1 << BTN_LEFT } else { BTN_MASK };
2141 if self.wp.ctx.pressed & press != 0 || self.key_first() != 0 {
2142 let w = &mut self.wp.popup;
2143 w.tip_block = id;
2144 w.tip_id = 0;
2145 w.tip_shown = 0;
2146 return;
2147 }
2148 let now = self.now_ms;
2149 let w = &mut self.wp.popup;
2150 if w.tip_id != id {
2151 w.tip_id = id;
2152 w.tip_t0 = now;
2153 if w.tip_shown != 0 || (w.tip_hid_ok && now.wrapping_sub(w.tip_hid_t) < TIP_QUICK) {
2154 w.tip_t0 = now.wrapping_sub(TIP_DELAY); }
2156 }
2157 let el = now.wrapping_sub(w.tip_t0);
2158 if el < TIP_DELAY {
2159 self.timer(UIT_TOOLTIP, TIP_DELAY - el);
2160 return;
2161 }
2162 w.tip_shown = id;
2163 w.tip_hid_ok = false;
2164 self.tip_draw(s);
2165 }
2166
2167 pub(crate) fn tip_showing(&self) -> bool {
2169 self.wp.popup.tip_shown != 0
2170 }
2171
2172 fn tip_reset(&mut self) {
2174 let now = self.now_ms;
2175 let w = &mut self.wp.popup;
2176 w.tip_id = 0;
2177 if w.tip_shown != 0 {
2178 w.tip_shown = 0;
2179 w.tip_hid_t = now;
2180 w.tip_hid_ok = true;
2181 }
2182 }
2183
2184 fn tip_draw(&mut self, s: &str) {
2187 let (lr, ls) = (self.wp.ctx.last_rect, self.wp.ctx.last_state);
2188 let prev = self.layer_set(L_TIP);
2189 let st = self.style;
2190 let mut sp = Sp { label: Some(s), ..Sp::default() };
2191 let (w, h) = st.tooltip_size(self, &mut sp);
2192 let area = self.pp_bounds(w, h);
2195 let (mx, my) = (self.wp.ctx.mx, self.wp.ctx.my);
2196 let mut x = mx.wrapping_sub(((w as u32) >> 1) as i32);
2197 let mut y = my + sp.s[1]; if y + h > area[3] {
2199 y = my - sp.s[2] - h; }
2201 if x > area[2] - w {
2202 x = area[2] - w;
2203 }
2204 if x < area[0] {
2205 x = area[0];
2206 }
2207 if y > area[3] - h {
2208 y = area[3] - h;
2209 }
2210 if y < area[1] {
2211 y = area[1];
2212 }
2213 self.wp.popup.tip_anchor = [mx, my, 1, 1];
2215 self.wp.popup.tip_nat = [x, y];
2216 let (dx, dy) = self.pp_nudge(POPUP_MAX as u32, [x, y, w, h]);
2217 let (x, y) = (x + dx, y + dy);
2218 self.wp.popup.tip_rect = [x, y, w, h]; self.wp.popup.tip_seen = self.frame;
2220 sp.place([x, y, w, h]);
2221 st.tooltip(self, &sp);
2222 self.wp.ctx.last_rect = [x, y, w, h];
2224 self.wp.ctx.last_state = 0;
2225 self.sem(K_TEXT, 0, Some(s), 0);
2226 self.layer_set(prev);
2227 self.wp.ctx.last_rect = lr;
2228 self.wp.ctx.last_state = ls;
2229 }
2230
2231 pub(crate) fn accel_dispatch(&mut self) {
2238 let frame = self.frame;
2240 let w = &self.wp.popup;
2241 if w.pp_n != 0 && w.pp[0].flags & PPF_AUTO != 0 && w.pp[0].seen != frame {
2242 let (menu, id) = (w.pp[0].menu.unwrap_or(&[]), w.pp[0].id);
2243 self.menu_flyout(menu, id, FL_POINT);
2244 }
2245 for i in 0..self.wp.popup.pp_n {
2247 let e = &self.wp.popup.pp[i as usize];
2248 if e.seen != frame && e.born != frame {
2249 self.pp_close_to(i);
2250 break;
2251 }
2252 }
2253 self.wp.popup.hot_prev = self.wp.ctx.hot;
2254 let t = self.wp.popup.tip_id;
2255 if t != 0 && t != self.wp.ctx.hot {
2256 self.tip_reset();
2257 }
2258 if self.pp_noaccel() {
2260 return;
2261 }
2262 let k = self.key_first();
2263 if k == 0 {
2264 return;
2265 }
2266 let vk = k & 0xFFFF;
2267 let mods = (k >> 16) & MOD3;
2268 if vk == VK_SHIFT || vk == VK_CONTROL || vk == VK_MENU {
2269 return;
2270 }
2271 if mods & (MOD_CTRL | MOD_ALT) == 0 && vk.wrapping_sub(VK_F1) > 23 && self.focus_is_text() {
2273 return;
2274 }
2275 let Some(c) = self.accel_find(vk, mods) else { return };
2276 self.key(vk);
2277 if self.cmd_flags(c.id) & (CF_DISABLED | CF_HIDDEN) != 0 {
2278 return;
2279 }
2280 self.pp_dismiss_all();
2281 self.cmd_run(c.id, 0);
2282 }
2283}