1use yog_api::ui::event::keys;
7use yog_api::ui::widget::WidgetKind;
8use yog_api::ui::{
9 layout, surface, widget, Action, FlexDir, LayoutNode, Pointer, Rect, StateFlags, TextBoxEvent,
10 UiEvent, UiRoot, Units, Widget,
11};
12
13use crate::screen::{self, Request};
14use crate::{Anchor, Cx, Parts, ScreenHandle, View, ViewEvent, ViewModel};
15
16const WHEEL_STEP: f32 = 16.0;
18
19#[derive(Debug, Clone, Copy, Default, PartialEq)]
21pub struct Viewport {
22 pub offset: f32,
24 pub height: f32,
26}
27
28#[derive(Debug, Default)]
30pub struct Update {
31 pub(crate) relayout: bool,
32 pub(crate) repaint: bool,
33 pub(crate) viewports: Vec<Viewport>,
34 pub(crate) text_sets: Vec<(u16, String)>,
35 pub(crate) scroll_resets: Vec<u16>,
36 pub(crate) lists: Vec<(u16, u32, Option<u32>)>,
37}
38
39impl Update {
40 pub fn new() -> Self {
42 Self::default()
43 }
44 pub fn relayout(&mut self) {
46 self.relayout = true;
47 }
48 pub fn repaint(&mut self) {
50 self.repaint = true;
51 }
52 pub fn needs_relayout(&self) -> bool {
53 self.relayout
54 }
55 pub fn needs_repaint(&self) -> bool {
56 self.repaint || self.relayout
57 }
58 pub fn viewport(&self, scroller: u16) -> Viewport {
60 self.viewports
61 .get(scroller as usize)
62 .copied()
63 .unwrap_or_default()
64 }
65 pub fn set_text_box(&mut self, slot: u16, text: &str) {
68 self.text_sets.push((slot, text.to_owned()));
69 }
70 pub fn reset_scroll(&mut self, scroller: u16) {
72 self.scroll_resets.push(scroller);
73 self.relayout();
74 }
75 pub fn list_info(&mut self, owner: u16, len: u32, selected: Option<u32>) {
77 self.lists.push((owner, len, selected));
78 }
79}
80
81pub(crate) fn find_path(w: &Widget, id: &str) -> Option<Vec<u16>> {
83 if w.id.as_deref() == Some(id) {
84 return Some(Vec::new());
85 }
86 w.children.iter().enumerate().find_map(|(i, c)| {
87 find_path(c, id).map(|mut p| {
88 p.insert(0, i as u16);
89 p
90 })
91 })
92}
93
94fn with_root(path: &[u16]) -> Vec<u16> {
95 let mut p = Vec::with_capacity(path.len() + 1);
96 p.push(0);
97 p.extend_from_slice(path);
98 p
99}
100
101pub(crate) struct Host<W: View> {
102 pub(crate) vm: W::Vm,
103 view: W,
104 pub(crate) ui: UiRoot,
106 pub(crate) parts: Parts,
107 pub(crate) size: (f32, f32),
108 pub(crate) dirty: bool,
109 hover: Option<Vec<u16>>,
111 pressed: Option<Vec<u16>>,
112 focus: Option<Vec<u16>>,
113 focus_visible: bool,
115 outlined: Option<Vec<u16>>,
116 mouse: Option<(f32, f32)>,
117 captured: Option<Vec<u16>>,
119 hovered_surface: Option<Vec<u16>>,
121 opened: bool,
122 reveal: Option<(u16, u32)>,
123 seen_viewports: Vec<Viewport>,
125 pub(crate) handle: ScreenHandle<W::Vm>,
126 pub(crate) log: Vec<String>,
128 pub(crate) live: bool,
130}
131
132impl<W: View> Host<W> {
133 pub(crate) fn new(vm: W::Vm, handle: ScreenHandle<W::Vm>, live: bool) -> Self {
134 let (view, content, parts) = W::build();
135 let screen = widget::panel(FlexDir::Column).child(content);
136 Self {
137 vm,
138 view,
139 ui: UiRoot::new(W::META.id, screen),
140 parts,
141 size: (W::META.w, W::META.h),
142 dirty: true,
143 hover: None,
144 pressed: None,
145 focus: None,
146 focus_visible: false,
147 outlined: None,
148 mouse: None,
149 captured: None,
150 hovered_surface: None,
151 opened: false,
152 reveal: None,
153 seen_viewports: Vec::new(),
154 handle,
155 log: Vec::new(),
156 live,
157 }
158 }
159
160 pub(crate) fn content(&self) -> &Widget {
161 &self.ui.root.children[0]
162 }
163
164 fn node(&self, path: &[u16]) -> Option<&LayoutNode> {
165 layout::node_at(&self.ui.layout_root, &with_root(path))
166 }
167
168 pub(crate) fn content_node(&self, path: &[u16]) -> Option<&LayoutNode> {
169 self.node(path)
170 }
171
172 fn hit(&self, x: f32, y: f32) -> Option<Vec<u16>> {
174 let p = layout::hit_path(&self.ui.layout_root, x, y)?;
175 (p.first() == Some(&0)).then(|| p[1..].to_vec())
176 }
177
178 fn with_cx<R>(&mut self, f: impl FnOnce(&mut W::Vm, &mut W, &mut Cx<'_, W::Vm>) -> R) -> R {
181 let mut reqs = Vec::new();
182 let r = {
183 let mut cx = Cx::new(&mut reqs, &self.handle);
184 f(&mut self.vm, &mut self.view, &mut cx)
185 };
186 self.dirty = true;
187 for req in reqs {
188 self.request(req);
189 }
190 r
191 }
192
193 fn request(&mut self, req: Request) {
194 let id = W::META.id;
195 match req {
196 Request::Close => {
197 self.log.push("close".into());
198 if self.live {
199 yog_api::close_ui(id);
200 }
201 }
202 Request::Navigate(to) => {
203 self.log.push(format!("navigate:{to}"));
204 if self.live {
205 screen::navigate(id, &to);
206 }
207 }
208 Request::Back => {
209 self.log.push("back".into());
210 if !self.live || !screen::go_back() {
211 self.request(Request::Close);
212 }
213 }
214 Request::Focus(wid) => {
215 if let Some(p) = find_path(self.content(), &wid) {
216 self.set_focus(Some(p), true);
217 }
218 }
219 Request::Invalidate => self.dirty = true,
220 }
221 }
222
223 pub(crate) fn dispatch(&mut self, ev: ViewEvent) {
224 self.with_cx(|vm, view, cx| view.dispatch(vm, cx, ev));
225 }
226
227 fn open_vm(&mut self) {
228 self.opened = true;
229 self.reset_input();
230 self.with_cx(|vm, _, cx| vm.on_open(cx));
231 }
232
233 fn close_vm(&mut self) {
234 self.opened = false;
235 self.reset_input();
236 self.with_cx(|vm, _, cx| vm.on_close(cx));
237 }
238
239 fn reset_input(&mut self) {
240 self.hover = None;
241 self.pressed = None;
242 self.focus = None;
243 self.mouse = None;
244 for t in &mut self.parts.text_boxes {
245 t.state.blur();
246 }
247 }
248
249 pub(crate) fn drain_queue(&mut self) {
250 for job in self.handle.take_queue() {
251 job(&mut self.vm);
252 self.dirty = true;
253 }
254 }
255
256 pub(crate) fn frame(&mut self, sw: f32, sh: f32) {
260 self.size = (sw, sh);
261 self.drain_queue();
262 for _ in 0..2 {
265 if self.dirty {
266 self.run_update();
267 }
268 self.apply_parts();
269 self.apply_states();
270 self.layout();
271 if !self.do_reveal() && !self.viewports_changed() {
272 break;
273 }
274 }
275 }
276
277 fn viewports(&self) -> Vec<Viewport> {
278 self.parts
279 .scrollers
280 .iter()
281 .map(|s| Viewport {
282 offset: s.state.offset,
283 height: self.node(&s.path).map_or(0.0, |n| n.rect.h),
284 })
285 .collect()
286 }
287
288 fn viewports_changed(&mut self) -> bool {
291 let now = self.viewports();
292 let changed = self.parts.lists.iter().any(|l| {
293 let (Some(_), Some(s)) = (l.item_height, l.scroller) else {
294 return false;
295 };
296 let (a, b) = (self.seen_viewports.get(s as usize), now.get(s as usize));
297 a.map(|v| v.height) != b.map(|v| v.height)
298 });
299 if changed {
300 self.dirty = true;
301 }
302 changed
303 }
304
305 fn run_update(&mut self) {
306 let viewports = self.viewports();
307 self.seen_viewports.clone_from(&viewports);
308 let mut u = Update {
309 viewports,
310 ..Update::default()
311 };
312 let content = &mut self.ui.root.children[0];
313 self.view.update(content, &self.vm, &mut u);
314 self.vm.customize(content);
315 for (slot, text) in u.text_sets {
316 if let Some(t) = self.parts.text_boxes.get_mut(slot as usize) {
317 if t.state.text() != text {
318 t.state.set_text(text);
319 }
320 }
321 }
322 for s in u.scroll_resets {
323 if let Some(s) = self.parts.scrollers.get_mut(s as usize) {
324 s.state.reset();
325 }
326 }
327 for (owner, len, selected) in u.lists {
328 if let Some(l) = self.parts.lists.iter_mut().find(|l| l.owner == owner) {
329 l.len = len;
330 l.selected = selected;
331 }
332 }
333 self.dirty = false;
334 }
335
336 fn apply_parts(&mut self) {
337 let content = &mut self.ui.root.children[0];
338 for s in &self.parts.scrollers {
339 if let Some(w) = content.get_path_mut(&s.path) {
340 w.style.scroll_y = s.state.offset;
341 }
342 }
343 for t in &mut self.parts.text_boxes {
344 if let Some(w) = content.get_path_mut(&t.path) {
345 t.state.apply(w);
346 }
347 }
348 }
349
350 fn apply_states(&mut self) {
351 let content = &mut self.ui.root.children[0];
352 content.clear_state_deep(StateFlags::HOVER | StateFlags::PRESSED | StateFlags::FOCUSED);
353 if let Some(p) = self.outlined.take() {
354 if let Some(w) = content.get_path_mut(&p) {
355 w.focused = false;
356 }
357 }
358 for (path, flag) in [
359 (&self.hover, StateFlags::HOVER),
360 (&self.pressed, StateFlags::PRESSED),
361 (&self.focus, StateFlags::FOCUSED),
362 ] {
363 if let Some(w) = path.as_deref().and_then(|p| content.get_path_mut(p)) {
364 w.set_state_inherited(flag, true);
365 }
366 }
367 if self.focus_visible {
368 if let Some(p) = &self.focus {
369 if let Some(w) = content.get_path_mut(p) {
370 if w.is_interactive() {
371 w.focused = true;
372 self.outlined = Some(p.clone());
373 }
374 }
375 }
376 }
377 }
378
379 fn layout(&mut self) {
380 let (sw, sh) = self.size;
381 let m = W::META;
382 let root = &mut self.ui.root;
383 root.style.w = Units::Px(sw);
384 root.style.h = Units::Px(sh);
385 let c = &mut root.children[0];
386 if m.anchor == Anchor::Fill {
387 c.style.w = Units::Percent(100.0);
388 c.style.h = Units::Percent(100.0);
389 c.style.pos = Some((0.0, 0.0));
390 } else {
391 c.style.w = Units::Px(m.w);
392 c.style.h = Units::Px(m.h);
393 c.style.pos = Some(m.anchor.place(sw, sh, m.w, m.h));
394 }
395 self.ui.layout(sw, sh);
396 }
397
398 fn do_reveal(&mut self) -> bool {
401 let Some((owner, index)) = self.reveal.take() else {
402 return false;
403 };
404 let Some(list) = self.parts.lists.iter().find(|l| l.owner == owner) else {
405 return false;
406 };
407 let Some(si) = list.scroller else {
408 return false;
409 };
410 let Some(scroller) = self.parts.scrollers.get(si as usize) else {
411 return false;
412 };
413 let (Some(view), Some(items)) = (self.node(&scroller.path), self.node(&list.path)) else {
414 return false;
415 };
416 let found = items.children.iter().find(|c| {
417 c.action
418 .is_some_and(|a| a.owner == owner && a.index == index)
419 });
420 let (top, h) = match (found, list.item_height) {
421 (Some(n), _) => (n.rect.y - view.rect.y + view.scroll_y, n.rect.h),
422 (None, Some(ih)) => {
423 let w = self.content().get_path(&list.path);
424 let (gap, pad) = w.map_or((0.0, 0.0), |w| (w.style.gap, w.style.pad[0]));
425 (pad + index as f32 * (ih + gap), ih)
426 }
427 (None, None) => return false,
428 };
429 let (view_h, max) = (view.rect.h, view.max_scroll());
430 let state = &mut self.parts.scrollers[si as usize].state;
431 let before = state.offset;
432 state.scroll_into_view(top, h, view_h);
433 state.offset = state.offset.clamp(0.0, max.max(0.0));
434 if (state.offset - before).abs() > f32::EPSILON {
435 self.dirty = true;
436 true
437 } else {
438 false
439 }
440 }
441
442 pub(crate) fn on_event(&mut self, raw: &str) -> bool {
446 self.drain_queue();
447 let ev = UiEvent::parse(raw);
448 match ev {
449 UiEvent::Open { w, h } => {
450 self.size = (w, h);
451 if self.opened {
452 self.close_vm();
453 }
454 self.open_vm();
455 false
456 }
457 UiEvent::Close => {
458 self.captured = None;
459 self.leave_surface();
460 if self.opened {
461 self.close_vm();
462 }
463 if self.live {
464 screen::screen_closed();
465 }
466 false
467 }
468 UiEvent::Move { x, y } => {
469 self.mouse = Some((x, y));
470 self.hover = self.hit(x, y);
471 let under = self.surface_at(x, y);
472 if under != self.hovered_surface {
473 self.leave_surface();
474 self.hovered_surface = under.clone();
475 }
476 under.is_some_and(|p| self.to_surface(&p, x, y, |x, y| Pointer::Move { x, y }))
477 }
478 UiEvent::Scroll { .. } => self.scroll(&ev),
479 UiEvent::Click { x, y } => self.click(x, y, &ev),
480 UiEvent::Drag { x, y } | UiEvent::Release { x, y } if self.captured.is_some() => {
481 let release = matches!(ev, UiEvent::Release { .. });
482 let path = if release {
483 self.pressed = None;
484 self.captured.take()
485 } else {
486 self.captured.clone()
487 };
488 if let Some(path) = path {
489 self.to_surface(&path, x, y, |x, y| {
490 if release {
491 Pointer::Up { x, y }
492 } else {
493 Pointer::Drag { x, y }
494 }
495 });
496 }
497 true
498 }
499 UiEvent::Drag { .. } | UiEvent::Release { .. } => {
500 if matches!(ev, UiEvent::Release { .. }) {
501 self.pressed = None;
502 }
503 let mut used = false;
504 for i in 0..self.parts.scrollers.len() {
505 let Some(node) = self.node(&self.parts.scrollers[i].path).cloned() else {
506 continue;
507 };
508 if self.parts.scrollers[i]
509 .state
510 .handle_event(&ev, &node, WHEEL_STEP)
511 {
512 used = true;
513 self.dirty = true;
514 }
515 }
516 for i in 0..self.parts.text_boxes.len() {
517 let Some(node) = self.node(&self.parts.text_boxes[i].path).cloned() else {
518 continue;
519 };
520 used |= self.parts.text_boxes[i]
521 .state
522 .handle_event(&ev, &node)
523 .consumed();
524 }
525 used
526 }
527 UiEvent::Key { key, mods } => self.key(key, mods, &ev),
528 UiEvent::Char(_) => self.text_input(&ev),
529 UiEvent::Other(_) => false,
530 }
531 }
532
533 fn scroll(&mut self, ev: &UiEvent<'_>) -> bool {
534 if let (Some((x, y)), UiEvent::Scroll { dy }) = (self.mouse, ev) {
536 if let Some((s, r)) = self.surface_at(x, y).and_then(|p| self.surface_box(&p)) {
537 if s.scroll(*dy, (r.w, r.h)) {
538 return true;
539 }
540 }
541 }
542 let under = self
543 .mouse
544 .and_then(|(x, y)| layout::deepest_path(&self.ui.layout_root, x, y))
545 .filter(|p| p.first() == Some(&0))
546 .map(|p| p[1..].to_vec());
547 let target = under
548 .and_then(|p| {
549 self.parts
550 .scrollers
551 .iter()
552 .enumerate()
553 .filter(|(_, s)| p.starts_with(&s.path))
554 .max_by_key(|(_, s)| s.path.len())
555 .map(|(i, _)| i)
556 })
557 .or_else(|| {
558 self.focused_list()
559 .and_then(|l| l.scroller.map(usize::from))
560 })
561 .or_else(|| (!self.parts.scrollers.is_empty()).then_some(0));
562 let Some(i) = target else {
563 return false;
564 };
565 let Some(node) = self.node(&self.parts.scrollers[i].path).cloned() else {
566 return false;
567 };
568 self.parts.scrollers[i]
569 .state
570 .handle_event(ev, &node, WHEEL_STEP);
571 self.dirty = true;
572 true
573 }
574
575 fn click(&mut self, x: f32, y: f32, ev: &UiEvent<'_>) -> bool {
576 self.mouse = Some((x, y));
577 self.hover = self.hit(x, y);
578 for i in 0..self.parts.scrollers.len() {
579 let Some(node) = self.node(&self.parts.scrollers[i].path).cloned() else {
580 continue;
581 };
582 if self.parts.scrollers[i]
583 .state
584 .handle_event(ev, &node, WHEEL_STEP)
585 {
586 self.dirty = true;
587 return true;
588 }
589 }
590 let mut clicked_box = None;
591 for i in 0..self.parts.text_boxes.len() {
592 let Some(node) = self.node(&self.parts.text_boxes[i].path).cloned() else {
593 continue;
594 };
595 if self.parts.text_boxes[i]
596 .state
597 .handle_event(ev, &node)
598 .consumed()
599 {
600 clicked_box = Some(i);
601 }
602 }
603 if let Some(i) = clicked_box {
604 self.focus = Some(self.parts.text_boxes[i].path.clone());
605 self.focus_visible = false;
606 return true;
607 }
608 if let Some(path) = self.surface_at(x, y) {
609 if self.to_surface(&path, x, y, |x, y| Pointer::Down { x, y }) {
610 self.captured = Some(path);
611 self.focus = None;
612 return true;
613 }
614 }
615 let Some(path) = self.hit(x, y) else {
616 self.focus = None;
617 return false;
618 };
619 self.pressed = Some(path.clone());
620 self.focus = self
621 .parts
622 .lists
623 .iter()
624 .filter(|l| l.scroller.is_some() && path.starts_with(&l.path))
625 .max_by_key(|l| l.path.len())
626 .map(|l| l.path.clone());
627 self.focus_visible = false;
628 self.activate(&path);
629 true
630 }
631
632 fn surface_at(&self, x: f32, y: f32) -> Option<Vec<u16>> {
637 let p = layout::deepest_path(&self.ui.layout_root, x, y)?;
638 if p.first() != Some(&0) {
639 return None;
640 }
641 let mut p = p[1..].to_vec();
642 loop {
643 if let Some(w) = self.content().get_path(&p) {
644 if matches!(w.kind, WidgetKind::Surface(_)) && w.enabled && w.visible {
645 return Some(p);
646 }
647 }
648 p.pop()?;
649 }
650 }
651
652 fn surface_box(
654 &self,
655 path: &[u16],
656 ) -> Option<(std::sync::Arc<dyn yog_api::ui::Surface>, Rect)> {
657 let w = self.content().get_path(path)?;
658 let WidgetKind::Surface(id) = &w.kind else {
659 return None;
660 };
661 let r = self.node(path)?.rect;
662 let pad = w.effective_style().pad;
663 let rect = Rect {
664 x: r.x + pad[3],
665 y: r.y + pad[0],
666 w: (r.w - pad[1] - pad[3]).max(0.0),
667 h: (r.h - pad[0] - pad[2]).max(0.0),
668 };
669 Some((surface::get(id)?, rect))
670 }
671
672 fn to_surface(
674 &self,
675 path: &[u16],
676 x: f32,
677 y: f32,
678 ev: impl FnOnce(f32, f32) -> Pointer,
679 ) -> bool {
680 match self.surface_box(path) {
681 Some((s, r)) => s.pointer(ev(x - r.x, y - r.y), (r.w, r.h)),
682 None => false,
683 }
684 }
685
686 fn leave_surface(&mut self) {
688 if let Some(path) = self.hovered_surface.take() {
689 if let Some((s, r)) = self.surface_box(&path) {
690 s.pointer(Pointer::Leave, (r.w, r.h));
691 }
692 }
693 }
694
695 fn activate(&mut self, path: &[u16]) {
697 let Some(w) = self.content().get_path(path) else {
698 return;
699 };
700 if !w.enabled || !w.visible {
701 return;
702 }
703 if let Some(a) = w.action {
704 if a.slot == Action::SELECT {
705 self.reveal = Some((a.owner, a.index));
706 }
707 self.dispatch(ViewEvent::Action(a));
708 }
709 }
710
711 fn focused_box(&self) -> Option<usize> {
712 self.parts
713 .text_boxes
714 .iter()
715 .position(|t| t.state.is_focused())
716 }
717
718 fn focused_list(&self) -> Option<&crate::ListPart> {
719 let f = self.focus.as_ref()?;
720 self.parts.lists.iter().find(|l| &l.path == f)
721 }
722
723 fn text_input(&mut self, ev: &UiEvent<'_>) -> bool {
725 let Some(i) = self.focused_box() else {
726 return false;
727 };
728 let Some(node) = self.node(&self.parts.text_boxes[i].path).cloned() else {
729 return false;
730 };
731 let r = self.parts.text_boxes[i].state.handle_event(ev, &node);
732 let text = || self.parts.text_boxes[i].state.text().to_owned();
733 match r {
734 TextBoxEvent::Changed => {
735 let text = text();
736 self.dispatch(ViewEvent::TextChanged {
737 slot: i as u16,
738 text,
739 });
740 }
741 TextBoxEvent::Submitted => {
742 let text = text();
743 self.dispatch(ViewEvent::TextSubmitted {
744 slot: i as u16,
745 text,
746 });
747 }
748 _ => {}
749 }
750 if !self.parts.text_boxes[i].state.is_focused() {
751 self.focus = None;
752 }
753 r.consumed()
754 }
755
756 fn key(&mut self, key: i32, mods: i32, ev: &UiEvent<'_>) -> bool {
757 if key == keys::TAB {
758 self.cycle_focus(mods & keys::MOD_SHIFT != 0);
759 return true;
760 }
761 if self.focused_box().is_some() {
762 return self.text_input(ev);
763 }
764 match key {
765 keys::ESCAPE => {
766 if self.with_cx(|vm, _, cx| vm.on_back(cx)) {
767 return true;
768 }
769 if self.live && screen::go_back() {
770 self.log.push("back".into());
771 return true;
772 }
773 false
774 }
775 keys::ENTER | keys::KP_ENTER | keys::SPACE => {
776 let Some(p) = self.focus.clone() else {
777 return false;
778 };
779 if self
780 .content()
781 .get_path(&p)
782 .is_some_and(|w| w.is_interactive())
783 {
784 self.activate(&p);
785 return true;
786 }
787 false
788 }
789 keys::UP | keys::DOWN | keys::PAGE_UP | keys::PAGE_DOWN | keys::HOME | keys::END => {
790 self.list_key(key)
791 }
792 _ => false,
793 }
794 }
795
796 fn list_key(&mut self, key: i32) -> bool {
798 let list = self
799 .focused_list()
800 .or_else(|| self.parts.lists.iter().find(|l| l.scroller.is_some()));
801 let Some(list) = list.cloned() else {
802 return false;
803 };
804 if list.len == 0 {
805 return true;
806 }
807 let last = list.len - 1;
808 let page = match (list.item_height, list.scroller) {
809 (Some(ih), Some(s)) => {
810 let view_h = self
811 .parts
812 .scrollers
813 .get(s as usize)
814 .and_then(|s| self.node(&s.path))
815 .map_or(0.0, |n| n.rect.h);
816 ((view_h / ih.max(1.0)) as u32).max(1)
817 }
818 _ => 5,
819 };
820 let new = match (key, list.selected) {
821 (keys::HOME, _) => 0,
822 (keys::END, _) => last,
823 (_, None) => 0,
824 (keys::UP, Some(i)) => i.saturating_sub(1),
825 (keys::DOWN, Some(i)) => (i + 1).min(last),
826 (keys::PAGE_UP, Some(i)) => i.saturating_sub(page),
827 (_, Some(i)) => (i + page).min(last),
828 };
829 if list.selected != Some(new) {
830 self.dispatch(ViewEvent::Select {
831 list: list.owner,
832 index: new,
833 });
834 }
835 self.reveal = Some((list.owner, new));
836 if self.focus.is_none() {
837 self.focus = Some(list.path.clone());
838 }
839 true
840 }
841
842 fn set_focus(&mut self, path: Option<Vec<u16>>, visible: bool) {
843 for t in &mut self.parts.text_boxes {
844 t.state.blur();
845 }
846 if let Some(p) = &path {
847 if let Some(t) = self.parts.text_boxes.iter_mut().find(|t| &t.path == p) {
848 t.state.focus();
849 }
850 }
851 self.focus = path;
852 self.focus_visible = visible;
853 }
854
855 fn cycle_focus(&mut self, back: bool) {
857 let n = self.parts.focusables.len();
858 if n == 0 {
859 return;
860 }
861 let cur = self
862 .focus
863 .as_ref()
864 .and_then(|f| self.parts.focusables.iter().position(|p| p == f));
865 for step in 1..=n {
866 let i = match (cur, back) {
867 (None, false) => step - 1,
868 (None, true) => n - step,
869 (Some(c), false) => (c + step) % n,
870 (Some(c), true) => (c + n - step % n) % n,
871 };
872 let p = &self.parts.focusables[i];
873 if self
874 .node(p)
875 .is_some_and(|n| n.visible && n.enabled && n.rect.w > 0.0)
876 {
877 let p = p.clone();
878 self.set_focus(Some(p), true);
879 return;
880 }
881 }
882 }
883}