1use std::any::Any;
2use std::collections::VecDeque;
3use std::fmt;
4use std::time::Duration;
5
6use densa_core::{Point, Rect, Size};
7use densa_draw::Canvas;
8use densa_paragraph::TextSystem;
9use taffy::style::AvailableSpace;
10use taffy::{TaffyResult, TaffyTree};
11
12use crate::{
13 Action, Available, Constraints, Cursor, EventCx, LayoutCx, Overflow, PaintCx, PointerButton,
14 Style, Theme, Widget, WidgetId,
15};
16
17const ACTION_CAPACITY: usize = 64;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq)]
22#[non_exhaustive]
23pub enum UiError {
24 NoSuchWidget(WidgetId),
26 TooManyWidgets,
28}
29
30impl fmt::Display for UiError {
31 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
32 match self {
33 Self::NoSuchWidget(id) => write!(f, "there is no widget {id:?}"),
34 Self::TooManyWidgets => f.write_str("more than 2^32 widgets"),
35 }
36 }
37}
38
39impl std::error::Error for UiError {}
40
41#[derive(Debug)]
43pub(crate) struct Node {
44 pub(crate) generation: u32,
45 pub(crate) access: u64,
47 pub(crate) live: bool,
48 pub(crate) parent: Option<WidgetId>,
49 pub(crate) children: Vec<WidgetId>,
50 pub(crate) taffy: taffy::NodeId,
51 pub(crate) style: Style,
52 pub(crate) rect: Rect,
54 pub(crate) content: Rect,
56 pub(crate) content_size: Size,
58 pub(crate) scroll: Point,
60 pub(crate) hovered: bool,
62 pub(crate) cursor: Option<Cursor>,
63 pub(crate) extent: Rect,
65 pub(crate) dirty: bool,
67 pub(crate) notified: Option<Size>,
69}
70
71#[derive(Debug, Clone, Copy)]
73pub(crate) struct Press {
74 pub(crate) time: Duration,
75 pub(crate) position: Point,
76 pub(crate) button: PointerButton,
77 pub(crate) clicks: u8,
78}
79
80#[derive(Debug)]
82pub(crate) struct State {
83 pub(crate) nodes: Vec<Node>,
84 free: Vec<u32>,
85 pub(crate) next_access: u64,
87 taffy: TaffyTree<WidgetId>,
88 pub(crate) root: WidgetId,
89 pub(crate) theme: Theme,
90 pub(crate) scale: f32,
91 viewport: Size,
92 pub(crate) text: TextSystem,
93 pub(crate) focus: Option<WidgetId>,
94 pub(crate) hover: Option<WidgetId>,
95 pub(crate) capture: Option<WidgetId>,
96 pub(crate) requested_focus: Option<Option<WidgetId>>,
98 pub(crate) actions: VecDeque<Action>,
99 pub(crate) needs_layout: bool,
100 pub(crate) needs_paint: bool,
101 pub(crate) needs_place: bool,
103 pub(crate) ime_area: Option<(WidgetId, Rect)>,
104 pub(crate) last_press: Option<Press>,
105 pub(crate) pointer: Option<Point>,
107}
108
109fn sync<T>(result: TaffyResult<T>) -> T {
112 match result {
113 Ok(value) => value,
114 Err(error) => unreachable!("the layout tree is out of step with the widgets: {error}"),
115 }
116}
117
118impl State {
119 pub(crate) fn is_live(&self, id: WidgetId) -> bool {
120 self.nodes
121 .get(id.slot())
122 .is_some_and(|node| node.live && node.generation == id.generation)
123 }
124
125 pub(crate) fn node(&self, id: WidgetId) -> Option<&Node> {
126 self.is_live(id).then(|| &self.nodes[id.slot()])
127 }
128
129 fn taffy_style(&self, id: WidgetId) -> taffy::Style {
131 let mut style = self.nodes[id.slot()].style.to_taffy(self.scale);
132 if id == self.root {
133 style.size = taffy::Size {
134 width: taffy::style::Dimension::length(self.viewport.width),
135 height: taffy::style::Dimension::length(self.viewport.height),
136 };
137 }
138 style
139 }
140
141 fn renew_layout_node(&mut self, id: WidgetId) {
147 let old = self.nodes[id.slot()].taffy;
148 let fresh = sync(self.taffy.new_leaf_with_context(self.taffy_style(id), id));
149 if let Some(parent) = self.taffy.parent(old) {
150 let index = sync(self.taffy.children(parent))
151 .iter()
152 .position(|child| *child == old);
153 if let Some(index) = index {
154 sync(self.taffy.replace_child_at_index(parent, index, fresh));
155 }
156 }
157 sync(self.taffy.remove(old));
158 self.nodes[id.slot()].taffy = fresh;
159 self.nodes[id.slot()].dirty = true;
160 }
161
162 pub(crate) fn set_style(&mut self, id: WidgetId, style: Style) -> bool {
163 if !self.is_live(id) {
164 return false;
165 }
166 self.nodes[id.slot()].style = style;
167 self.nodes[id.slot()].dirty = true;
168 let taffy_style = self.taffy_style(id);
169 sync(
170 self.taffy
171 .set_style(self.nodes[id.slot()].taffy, taffy_style),
172 );
173 self.needs_layout = true;
174 self.needs_paint = true;
175 true
176 }
177
178 pub(crate) fn is_shown(&self, id: WidgetId) -> bool {
180 let mut at = Some(id);
181 while let Some(current) = at.filter(|w| self.is_live(*w)) {
182 let node = &self.nodes[current.slot()];
183 if node.style.hidden {
184 return false;
185 }
186 at = node.parent;
187 }
188 self.is_live(id)
189 }
190
191 pub(crate) fn is_within(&self, id: WidgetId, ancestor: WidgetId) -> bool {
193 let mut at = Some(id);
194 while let Some(current) = at.filter(|w| self.is_live(*w)) {
195 if current == ancestor {
196 return true;
197 }
198 at = self.nodes[current.slot()].parent;
199 }
200 false
201 }
202
203 pub(crate) fn mark_dirty(&mut self, id: WidgetId) {
204 if self.is_live(id) {
205 sync(self.taffy.mark_dirty(self.nodes[id.slot()].taffy));
206 self.nodes[id.slot()].dirty = true;
207 self.needs_layout = true;
208 self.needs_paint = true;
209 }
210 }
211
212 pub(crate) fn scroll_to(&mut self, id: WidgetId, offset: Point) -> Point {
213 let node = &mut self.nodes[id.slot()];
214 let room = |content: f32, shown: f32| (content - shown).max(0.0);
215 let max_x = room(node.content_size.width, node.rect.width());
216 let max_y = room(node.content_size.height, node.rect.height());
217 let clamped = Point::new(offset.x.clamp(0.0, max_x), offset.y.clamp(0.0, max_y));
218 if clamped != node.scroll {
219 node.scroll = clamped;
220 self.needs_place = true;
221 self.needs_paint = true;
222 }
223 clamped
224 }
225}
226
227pub struct Ui {
236 pub(crate) widgets: Vec<Option<Box<dyn Widget>>>,
237 pub(crate) state: State,
238}
239
240impl fmt::Debug for Ui {
241 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242 f.debug_struct("Ui")
243 .field("widgets", &self.widgets.len())
244 .field("viewport", &self.state.viewport)
245 .field("focus", &self.state.focus)
246 .finish_non_exhaustive()
247 }
248}
249
250struct Root;
252
253impl Widget for Root {
254 fn paint(&mut self, cx: &PaintCx<'_>, canvas: &mut Canvas<'_>) {
255 canvas.fill_rect(cx.rect(), cx.theme().background);
256 }
257}
258
259impl Ui {
260 pub fn new(text: TextSystem) -> Self {
263 let root = WidgetId {
264 index: 0,
265 generation: 0,
266 };
267 let style = Style::column();
268 let mut taffy = TaffyTree::new();
269 let node = sync(taffy.new_leaf_with_context(style.to_taffy(1.0), root));
270 let mut state = State {
271 nodes: vec![Node {
272 generation: 0,
273 access: 0,
274 live: true,
275 parent: None,
276 children: Vec::new(),
277 taffy: node,
278 style,
279 rect: Rect::default(),
280 content: Rect::default(),
281 content_size: Size::ZERO,
282 scroll: Point::ORIGIN,
283 hovered: false,
284 cursor: None,
285 extent: Rect::default(),
286 dirty: true,
287 notified: None,
288 }],
289 free: Vec::new(),
290 next_access: 0,
291 taffy,
292 root,
293 theme: Theme::default(),
294 scale: 1.0,
295 viewport: Size::ZERO,
296 text,
297 focus: None,
298 hover: None,
299 capture: None,
300 requested_focus: None,
301 actions: VecDeque::with_capacity(ACTION_CAPACITY),
302 needs_layout: true,
303 needs_paint: true,
304 needs_place: false,
305 ime_area: None,
306 last_press: None,
307 pointer: None,
308 };
309 let style = state.taffy_style(root);
310 sync(state.taffy.set_style(node, style));
311 Self {
312 widgets: vec![Some(Box::new(Root))],
313 state,
314 }
315 }
316
317 pub fn root(&self) -> WidgetId {
319 self.state.root
320 }
321
322 pub fn add(
327 &mut self,
328 parent: WidgetId,
329 widget: impl Widget,
330 style: Style,
331 ) -> Result<WidgetId, UiError> {
332 let index = self.children(parent).len();
333 self.insert(parent, index, widget, style)
334 }
335
336 pub fn insert(
342 &mut self,
343 parent: WidgetId,
344 index: usize,
345 widget: impl Widget,
346 style: Style,
347 ) -> Result<WidgetId, UiError> {
348 if !self.state.is_live(parent) {
349 return Err(UiError::NoSuchWidget(parent));
350 }
351 let position = index.min(self.state.nodes[parent.slot()].children.len());
352 let index = match self.state.free.pop() {
353 Some(index) => index,
354 None => u32::try_from(self.state.nodes.len()).map_err(|_| UiError::TooManyWidgets)?,
355 };
356 let slot = index as usize;
357 let generation = self.state.nodes.get(slot).map_or(0, |node| node.generation);
358 let id = WidgetId { index, generation };
359 let taffy = sync(
360 self.state
361 .taffy
362 .new_leaf_with_context(style.to_taffy(self.state.scale), id),
363 );
364 sync(self.state.taffy.insert_child_at_index(
365 self.state.nodes[parent.slot()].taffy,
366 position,
367 taffy,
368 ));
369 self.state.next_access += 1;
370 let node = Node {
371 generation,
372 access: self.state.next_access,
373 live: true,
374 parent: Some(parent),
375 children: Vec::new(),
376 taffy,
377 style,
378 rect: Rect::default(),
379 content: Rect::default(),
380 content_size: Size::ZERO,
381 scroll: Point::ORIGIN,
382 hovered: false,
383 cursor: None,
384 extent: Rect::default(),
385 dirty: true,
386 notified: None,
387 };
388 if slot == self.state.nodes.len() {
389 self.state.nodes.push(node);
390 self.widgets.push(Some(Box::new(widget)));
391 } else {
392 self.state.nodes[slot] = node;
393 self.widgets[slot] = Some(Box::new(widget));
394 }
395 self.state.nodes[parent.slot()]
396 .children
397 .insert(position, id);
398 self.state.needs_layout = true;
399 self.state.needs_paint = true;
400 Ok(id)
401 }
402
403 pub fn remove(&mut self, id: WidgetId) -> bool {
406 if !self.state.is_live(id) || id == self.state.root {
407 return false;
408 }
409 if let Some(hover) = self.state.hover.filter(|h| self.state.is_within(*h, id)) {
410 self.mark_hovered(Some(hover), false);
411 self.state.hover = None;
412 }
413 let parent = self.state.nodes[id.slot()].parent;
414 if let Some(parent) = parent {
415 self.state.nodes[parent.slot()]
416 .children
417 .retain(|child| *child != id);
418 }
419 let mut doomed = vec![id];
420 while let Some(gone) = doomed.pop() {
421 let node = &mut self.state.nodes[gone.slot()];
422 doomed.append(&mut node.children);
423 node.live = false;
424 node.generation = node.generation.wrapping_add(1);
425 node.cursor = None;
426 let taffy = node.taffy;
427 sync(self.state.taffy.remove(taffy));
428 self.widgets[gone.slot()] = None;
429 self.state.free.push(gone.index);
430 }
431 if let Some(parent) = parent.filter(|p| self.state.nodes[p.slot()].children.is_empty()) {
432 self.state.renew_layout_node(parent);
433 }
434 let state = &mut self.state;
435 let live = |slot: &mut Option<WidgetId>, nodes: &[Node]| {
436 if slot.is_some_and(|w| {
437 !(nodes[w.slot()].live && nodes[w.slot()].generation == w.generation)
438 }) {
439 *slot = None;
440 }
441 };
442 live(&mut state.focus, &state.nodes);
443 live(&mut state.hover, &state.nodes);
444 live(&mut state.capture, &state.nodes);
445 if state.ime_area.is_some_and(|(w, _)| !state.is_live(w)) {
446 state.ime_area = None;
447 }
448 state.needs_layout = true;
449 state.needs_paint = true;
450 true
451 }
452
453 pub fn set_style(&mut self, id: WidgetId, style: Style) -> bool {
455 self.state.set_style(id, style)
456 }
457
458 pub fn style(&self, id: WidgetId) -> Option<&Style> {
460 self.state.node(id).map(|node| &node.style)
461 }
462
463 pub fn parent(&self, id: WidgetId) -> Option<WidgetId> {
465 self.state.node(id).and_then(|node| node.parent)
466 }
467
468 pub fn children(&self, id: WidgetId) -> &[WidgetId] {
470 self.state.node(id).map_or(&[], |node| &node.children)
471 }
472
473 pub fn rect(&self, id: WidgetId) -> Option<Rect> {
476 self.state.node(id).map(|node| node.rect)
477 }
478
479 pub fn widget<T: Widget>(&self, id: WidgetId) -> Option<&T> {
481 if !self.state.is_live(id) {
482 return None;
483 }
484 let widget: &dyn Widget = self.widgets[id.slot()].as_deref()?;
485 (widget as &dyn Any).downcast_ref::<T>()
486 }
487
488 pub fn update<T: Widget, R>(
491 &mut self,
492 id: WidgetId,
493 update: impl FnOnce(&mut T, &mut EventCx<'_>) -> R,
494 ) -> Option<R> {
495 if !self.state.is_live(id) {
496 return None;
497 }
498 let widget: &mut dyn Widget = self.widgets[id.slot()].as_deref_mut()?;
499 let widget = (widget as &mut dyn Any).downcast_mut::<T>()?;
500 let mut cx = EventCx {
501 state: &mut self.state,
502 id,
503 };
504 let result = update(widget, &mut cx);
505 self.settle();
506 Some(result)
507 }
508
509 pub fn theme(&self) -> &Theme {
511 &self.state.theme
512 }
513
514 pub fn set_theme(&mut self, theme: Theme) {
516 self.state.theme = theme;
517 self.restyle();
518 }
519
520 pub fn scale(&self) -> f32 {
522 self.state.scale
523 }
524
525 pub fn set_scale(&mut self, scale: f32) {
527 if scale.is_finite() && scale > 0.0 && scale != self.state.scale {
528 self.state.scale = scale;
529 self.restyle();
530 }
531 }
532
533 pub fn viewport(&self) -> Size {
535 self.state.viewport
536 }
537
538 pub fn resize(&mut self, viewport: Size) {
540 if viewport != self.state.viewport {
541 self.state.viewport = viewport;
542 let root = self.state.root;
543 let style = self.state.taffy_style(root);
544 sync(
545 self.state
546 .taffy
547 .set_style(self.state.nodes[root.slot()].taffy, style),
548 );
549 self.state.needs_layout = true;
550 self.state.needs_paint = true;
551 }
552 }
553
554 pub fn text_system(&mut self) -> &mut TextSystem {
556 &mut self.state.text
557 }
558
559 pub fn needs_paint(&self) -> bool {
561 self.state.needs_paint || self.state.needs_layout || self.state.needs_place
562 }
563
564 pub fn cursor(&self) -> Cursor {
567 let mut at = self.state.capture.or(self.state.hover);
568 while let Some(id) = at.filter(|id| self.state.is_live(*id)) {
569 let node = &self.state.nodes[id.slot()];
570 if let Some(cursor) = node.cursor {
571 return cursor;
572 }
573 at = node.parent;
574 }
575 Cursor::Default
576 }
577
578 pub fn ime_area(&self) -> Option<Rect> {
580 self.state
581 .ime_area
582 .filter(|(id, _)| self.state.focus == Some(*id))
583 .map(|(_, area)| area)
584 }
585
586 pub fn actions(&mut self) -> impl Iterator<Item = Action> + '_ {
588 self.state.actions.drain(..)
589 }
590
591 pub fn next_action(&mut self) -> Option<Action> {
594 self.state.actions.pop_front()
595 }
596
597 pub fn layout(&mut self) {
600 if self.state.needs_layout {
601 let Self { widgets, state } = self;
602 let root = state.nodes[state.root.slot()].taffy;
603 let available = taffy::Size {
604 width: AvailableSpace::Definite(state.viewport.width),
605 height: AvailableSpace::Definite(state.viewport.height),
606 };
607 let scale = state.scale;
608 let (text, theme) = (&mut state.text, &state.theme);
609 let computed = state.taffy.compute_layout_with_measure(
610 root,
611 available,
612 |inputs, _, context, style| {
613 taffy::compute_leaf_layout(
614 inputs,
615 style,
616 |_, _| 0.0,
617 |known, room| {
618 let widget =
619 context.and_then(|id| widgets.get_mut(id.slot())?.as_mut());
620 let Some(widget) = widget else {
621 return taffy::Size::ZERO;
622 };
623 let mut cx = LayoutCx {
624 text: &mut *text,
625 theme,
626 scale,
627 };
628 let size = widget.measure(&mut cx, constraints(known, room));
629 taffy::Size {
630 width: known.width.unwrap_or(size.width),
631 height: known.height.unwrap_or(size.height),
632 }
633 },
634 )
635 },
636 );
637 sync(computed);
638 self.state.needs_layout = false;
639 self.place(self.state.root, Point::ORIGIN, true, true);
640 self.state.needs_place = false;
641 self.after_moves();
642 }
643 if self.state.needs_place {
644 self.place(self.state.root, Point::ORIGIN, false, true);
645 self.state.needs_place = false;
646 self.after_moves();
647 }
648 }
649
650 fn after_moves(&mut self) {
653 if self.state.focus.is_some_and(|f| !self.state.is_shown(f)) {
654 self.set_focus(None);
655 }
656 if let (Some(at), None) = (self.state.pointer, self.state.capture) {
657 let hit = self.hit(self.state.root, at);
658 self.set_hover(hit, at);
659 }
660 }
661
662 fn place(&mut self, id: WidgetId, origin: Point, notify: bool, shown: bool) {
665 let state = &mut self.state;
666 let node = &mut state.nodes[id.slot()];
667 let layout = *sync(state.taffy.layout(node.taffy));
668 let rect = Rect::new(
669 origin.x + layout.location.x,
670 origin.y + layout.location.y,
671 layout.size.width,
672 layout.size.height,
673 );
674 node.rect = rect;
675 node.content = Rect::from_edges(
676 rect.left() + layout.border.left + layout.padding.left,
677 rect.top() + layout.border.top + layout.padding.top,
678 rect.right() - layout.border.right - layout.padding.right,
679 rect.bottom() - layout.border.bottom - layout.padding.bottom,
680 );
681 let overflow = layout.scrollable_overflow_rect;
683 node.content_size = Size::new(overflow.right, overflow.bottom);
684 let shown = shown && !node.style.hidden;
688 let room = |content: f32, view: f32| (content - view).max(0.0);
689 let clamped = if !shown {
690 node.scroll
691 } else {
692 Point::new(
693 node.scroll.x.clamp(0.0, room(overflow.right, rect.width())),
694 node.scroll
695 .y
696 .clamp(0.0, room(overflow.bottom, rect.height())),
697 )
698 };
699 if clamped != node.scroll {
700 node.scroll = clamped;
701 state.needs_paint = true;
702 }
703 node.extent = if node.style.overflow == Overflow::Visible {
704 Rect::from_edges(
705 rect.left() + overflow.left.min(0.0),
706 rect.top() + overflow.top.min(0.0),
707 rect.right().max(rect.left() + overflow.right),
708 rect.bottom().max(rect.top() + overflow.bottom),
709 )
710 } else {
711 rect
712 };
713 let inner = Point::new(rect.left() - node.scroll.x, rect.top() - node.scroll.y);
714 let changed = node.dirty || node.notified != Some(rect.size());
715 if notify && changed {
716 node.dirty = false;
717 node.notified = Some(rect.size());
718 if let Some(widget) = self.widgets[id.slot()].as_mut() {
719 let mut cx = LayoutCx {
720 text: &mut state.text,
721 theme: &state.theme,
722 scale: state.scale,
723 };
724 widget.layout(&mut cx, rect.size());
725 }
726 }
727 for index in 0..self.state.nodes[id.slot()].children.len() {
728 let child = self.state.nodes[id.slot()].children[index];
729 self.place(child, inner, notify, shown);
730 }
731 }
732
733 pub fn paint(&mut self, canvas: &mut Canvas<'_>) {
735 self.layout();
736 self.paint_node(self.state.root, canvas);
737 self.state.needs_paint = false;
738 }
739
740 fn paint_node(&mut self, id: WidgetId, canvas: &mut Canvas<'_>) {
741 let node = &self.state.nodes[id.slot()];
742 if node.style.hidden {
743 return;
744 }
745 let rect = node.rect;
746 let clips = node.style.overflow != Overflow::Visible;
747 if canvas.clip_rect().intersect(node.extent).is_empty() {
749 return;
750 }
751 let saved = canvas.state();
752 if let Some(widget) = self.widgets[id.slot()].as_mut() {
753 widget.paint(
754 &PaintCx {
755 state: &self.state,
756 id,
757 },
758 canvas,
759 );
760 }
761 if clips {
762 canvas.clip(rect);
763 }
764 for index in 0..self.state.nodes[id.slot()].children.len() {
765 let child = self.state.nodes[id.slot()].children[index];
766 self.paint_node(child, canvas);
767 }
768 canvas.restore(saved);
769 if let Some(widget) = self.widgets[id.slot()].as_mut() {
770 widget.paint_over(
771 &PaintCx {
772 state: &self.state,
773 id,
774 },
775 canvas,
776 );
777 }
778 }
779
780 fn restyle(&mut self) {
782 for slot in 0..self.state.nodes.len() {
783 let node = &self.state.nodes[slot];
784 if node.live {
785 let id = WidgetId {
786 index: slot as u32,
787 generation: node.generation,
788 };
789 let style = self.state.taffy_style(id);
790 sync(self.state.taffy.set_style(node.taffy, style));
791 self.state.nodes[slot].dirty = true;
792 }
793 }
794 self.state.needs_layout = true;
795 self.state.needs_paint = true;
796 }
797
798 pub(crate) fn settle(&mut self) {
800 for _ in 0..4 {
802 match self.state.requested_focus.take() {
803 Some(focus) => self.give_focus(focus),
804 None => break,
805 }
806 }
807 self.state.requested_focus = None;
808 }
809}
810
811fn constraints(known: taffy::Size<Option<f32>>, room: taffy::Size<AvailableSpace>) -> Constraints {
813 let available = |space: AvailableSpace| match space {
814 AvailableSpace::Definite(room) => Available::Definite(room),
815 AvailableSpace::MinContent => Available::MinContent,
816 AvailableSpace::MaxContent => Available::MaxContent,
817 };
818 Constraints {
819 width: known.width,
820 height: known.height,
821 available_width: available(room.width),
822 available_height: available(room.height),
823 }
824}