1use floem_reactive::{ReadSignal, RwSignal, SignalGet};
47use peniko::kurbo::{Circle, Insets, Line, Point, Rect, RoundedRect, Size};
48use std::any::Any;
49use taffy::tree::NodeId;
50
51use crate::{
52 app_state::AppState,
53 context::{ComputeLayoutCx, EventCx, LayoutCx, PaintCx, StyleCx, UpdateCx},
54 event::{Event, EventPropagation},
55 id::ViewId,
56 style::{LayoutProps, Style, StyleClassRef},
57 view_state::ViewStyleProps,
58 views::{dyn_view, DynamicView},
59 Renderer,
60};
61
62pub type AnyView = Box<dyn View>;
98
99pub trait IntoView: Sized {
114 type V: View + 'static;
115
116 fn into_view(self) -> Self::V;
118
119 fn into_any(self) -> AnyView {
121 Box::new(self.into_view())
122 }
123}
124
125impl<IV: IntoView + 'static> IntoView for Box<dyn Fn() -> IV> {
126 type V = DynamicView;
127
128 fn into_view(self) -> Self::V {
129 dyn_view(self)
130 }
131}
132
133impl<T: IntoView + Clone + 'static> IntoView for RwSignal<T> {
134 type V = DynamicView;
135
136 fn into_view(self) -> Self::V {
137 dyn_view(move || self.get())
138 }
139}
140
141impl<T: IntoView + Clone + 'static> IntoView for ReadSignal<T> {
142 type V = DynamicView;
143
144 fn into_view(self) -> Self::V {
145 dyn_view(move || self.get())
146 }
147}
148
149impl<VW: View + 'static> IntoView for VW {
150 type V = VW;
151
152 fn into_view(self) -> Self::V {
153 self
154 }
155}
156
157impl IntoView for i32 {
158 type V = crate::views::Label;
159
160 fn into_view(self) -> Self::V {
161 crate::views::text(self)
162 }
163}
164
165impl IntoView for usize {
166 type V = crate::views::Label;
167
168 fn into_view(self) -> Self::V {
169 crate::views::text(self)
170 }
171}
172
173impl IntoView for &str {
174 type V = crate::views::Label;
175
176 fn into_view(self) -> Self::V {
177 crate::views::text(self)
178 }
179}
180
181impl IntoView for String {
182 type V = crate::views::Label;
183
184 fn into_view(self) -> Self::V {
185 crate::views::text(self)
186 }
187}
188
189impl<IV: IntoView + 'static> IntoView for Vec<IV> {
190 type V = crate::views::Stack;
191
192 fn into_view(self) -> Self::V {
193 crate::views::stack_from_iter(self)
194 }
195}
196
197pub fn recursively_layout_view(id: ViewId, cx: &mut LayoutCx) -> NodeId {
201 cx.layout_node(id, true, |cx| {
202 let mut nodes = Vec::new();
203 for child in id.children() {
204 let view = child.view();
205 let mut view = view.borrow_mut();
206 nodes.push(view.layout(cx));
207 }
208 nodes
209 })
210}
211
212pub trait View {
257 fn id(&self) -> ViewId;
258
259 fn view_style(&self) -> Option<Style> {
260 None
261 }
262
263 fn view_class(&self) -> Option<StyleClassRef> {
264 None
265 }
266
267 fn debug_name(&self) -> std::borrow::Cow<'static, str> {
268 core::any::type_name::<Self>().into()
269 }
270
271 fn update(&mut self, cx: &mut UpdateCx, state: Box<dyn Any>) {
283 let _ = cx;
285 let _ = state;
286 }
287
288 fn style_pass(&mut self, cx: &mut StyleCx<'_>) {
293 for child in self.id().children() {
294 cx.style_view(child);
295 }
296 }
297
298 fn layout(&mut self, cx: &mut LayoutCx) -> NodeId {
304 recursively_layout_view(self.id(), cx)
305 }
306
307 fn compute_layout(&mut self, cx: &mut ComputeLayoutCx) -> Option<Rect> {
312 default_compute_layout(self.id(), cx)
313 }
314
315 fn event_before_children(&mut self, cx: &mut EventCx, event: &Event) -> EventPropagation {
316 let _ = cx;
318 let _ = event;
319
320 EventPropagation::Continue
321 }
322
323 fn event_after_children(&mut self, cx: &mut EventCx, event: &Event) -> EventPropagation {
324 let _ = cx;
326 let _ = event;
327
328 EventPropagation::Continue
329 }
330
331 fn paint(&mut self, cx: &mut PaintCx) {
335 cx.paint_children(self.id());
336 }
337
338 fn scroll_to(&mut self, cx: &mut AppState, target: ViewId, rect: Option<Rect>) -> bool {
341 if self.id() == target {
342 return true;
343 }
344 let mut found = false;
345
346 for child in self.id().children() {
347 found |= child.view().borrow_mut().scroll_to(cx, target, rect);
348 }
349 found
350 }
351}
352
353impl View for Box<dyn View> {
354 fn id(&self) -> ViewId {
355 (**self).id()
356 }
357
358 fn view_style(&self) -> Option<Style> {
359 (**self).view_style()
360 }
361
362 fn view_class(&self) -> Option<StyleClassRef> {
363 (**self).view_class()
364 }
365
366 fn debug_name(&self) -> std::borrow::Cow<'static, str> {
367 (**self).debug_name()
368 }
369
370 fn update(&mut self, cx: &mut UpdateCx, state: Box<dyn Any>) {
371 (**self).update(cx, state)
372 }
373
374 fn style_pass(&mut self, cx: &mut StyleCx) {
375 (**self).style_pass(cx)
376 }
377
378 fn layout(&mut self, cx: &mut LayoutCx) -> NodeId {
379 (**self).layout(cx)
380 }
381
382 fn event_before_children(&mut self, cx: &mut EventCx, event: &Event) -> EventPropagation {
383 (**self).event_before_children(cx, event)
384 }
385
386 fn event_after_children(&mut self, cx: &mut EventCx, event: &Event) -> EventPropagation {
387 (**self).event_after_children(cx, event)
388 }
389
390 fn compute_layout(&mut self, cx: &mut ComputeLayoutCx) -> Option<Rect> {
391 (**self).compute_layout(cx)
392 }
393
394 fn paint(&mut self, cx: &mut PaintCx) {
395 (**self).paint(cx)
396 }
397
398 fn scroll_to(&mut self, cx: &mut AppState, target: ViewId, rect: Option<Rect>) -> bool {
399 (**self).scroll_to(cx, target, rect)
400 }
401}
402
403pub fn default_compute_layout(id: ViewId, cx: &mut ComputeLayoutCx) -> Option<Rect> {
405 let mut layout_rect: Option<Rect> = None;
406 for child in id.children() {
407 let child_layout = cx.compute_view_layout(child);
408 if let Some(child_layout) = child_layout {
409 if let Some(rect) = layout_rect {
410 layout_rect = Some(rect.union(child_layout));
411 } else {
412 layout_rect = Some(child_layout);
413 }
414 }
415 }
416 layout_rect
417}
418
419pub(crate) fn paint_bg(cx: &mut PaintCx, style: &ViewStyleProps, size: Size) {
420 let radius = match style.border_radius() {
421 crate::unit::PxPct::Px(px) => px,
422 crate::unit::PxPct::Pct(pct) => size.min_side() * (pct / 100.),
423 };
424 if radius > 0.0 {
425 let rect = size.to_rect();
426 let width = rect.width();
427 let height = rect.height();
428 if width > 0.0 && height > 0.0 && radius > width.max(height) / 2.0 {
429 let radius = width.max(height) / 2.0;
430 let circle = Circle::new(rect.center(), radius);
431 let bg = match style.background() {
432 Some(color) => color,
433 None => return,
434 };
435 cx.fill(&circle, &bg, 0.0);
436 } else {
437 paint_box_shadow(cx, style, rect, Some(radius));
438 let bg = match style.background() {
439 Some(color) => color,
440 None => return,
441 };
442 let rounded_rect = rect.to_rounded_rect(radius);
443 cx.fill(&rounded_rect, &bg, 0.0);
444 }
445 } else {
446 paint_box_shadow(cx, style, size.to_rect(), None);
447 let bg = match style.background() {
448 Some(color) => color,
449 None => return,
450 };
451 cx.fill(&size.to_rect(), &bg, 0.0);
452 }
453}
454
455fn paint_box_shadow(
456 cx: &mut PaintCx,
457 style: &ViewStyleProps,
458 rect: Rect,
459 rect_radius: Option<f64>,
460) {
461 if let Some(shadow) = &style.shadow() {
462 let min = rect.size().min_side();
463 let h_offset = match shadow.h_offset {
464 crate::unit::PxPct::Px(px) => px,
465 crate::unit::PxPct::Pct(pct) => min * (pct / 100.),
466 };
467 let v_offset = match shadow.v_offset {
468 crate::unit::PxPct::Px(px) => px,
469 crate::unit::PxPct::Pct(pct) => min * (pct / 100.),
470 };
471 let spread = match shadow.spread {
472 crate::unit::PxPct::Px(px) => px,
473 crate::unit::PxPct::Pct(pct) => min * (pct / 100.),
474 };
475 let blur_radius = match shadow.blur_radius {
476 crate::unit::PxPct::Px(px) => px,
477 crate::unit::PxPct::Pct(pct) => min * (pct / 100.),
478 };
479 let inset = Insets::new(
480 -h_offset / 2.0,
481 -v_offset / 2.0,
482 h_offset / 2.0,
483 v_offset / 2.0,
484 );
485 let rect = rect.inflate(spread, spread).inset(inset);
486 if let Some(radii) = rect_radius {
487 let rounded_rect = RoundedRect::from_rect(rect, radii + spread);
488 cx.fill(&rounded_rect, shadow.color, blur_radius);
489 } else {
490 cx.fill(&rect, shadow.color, blur_radius);
491 }
492 }
493}
494
495pub(crate) fn paint_outline(cx: &mut PaintCx, style: &ViewStyleProps, size: Size) {
496 let outline = &style.outline().0;
497 if outline.width == 0. {
498 return;
500 }
501 let half = outline.width / 2.0;
502 let rect = size.to_rect().inflate(half, half);
503 let border_radius = match style.border_radius() {
504 crate::unit::PxPct::Px(px) => px,
505 crate::unit::PxPct::Pct(pct) => size.min_side() * (pct / 100.),
506 };
507 cx.stroke(
508 &rect.to_rounded_rect(border_radius + half),
509 &style.outline_color(),
510 outline,
511 );
512}
513
514pub(crate) fn paint_border(
515 cx: &mut PaintCx,
516 layout_style: &LayoutProps,
517 style: &ViewStyleProps,
518 size: Size,
519) {
520 let left = layout_style.border_left().0;
521 let top = layout_style.border_top().0;
522 let right = layout_style.border_right().0;
523 let bottom = layout_style.border_bottom().0;
524
525 let border_color = style.border_color();
526 if left.width == top.width
527 && top.width == right.width
528 && right.width == bottom.width
529 && bottom.width == left.width
530 && left.width > 0.0
531 {
532 let half = left.width / 2.0;
533 let rect = size.to_rect().inflate(-half, -half);
534 let radius = match style.border_radius() {
535 crate::unit::PxPct::Px(px) => px,
536 crate::unit::PxPct::Pct(pct) => size.min_side() * (pct / 100.),
537 };
538 if radius > 0.0 {
539 let radius = (radius - half).max(0.0);
540 cx.stroke(&rect.to_rounded_rect(radius), &border_color, &left);
541 } else {
542 cx.stroke(&rect, &border_color, &left);
543 }
544 } else {
545 if left.width > 0.0 {
547 let half = left.width / 2.0;
548 cx.stroke(
549 &Line::new(Point::new(half, 0.0), Point::new(half, size.height)),
550 &border_color,
551 &left,
552 );
553 }
554 if right.width > 0.0 {
555 let half = right.width / 2.0;
556 cx.stroke(
557 &Line::new(
558 Point::new(size.width - half, 0.0),
559 Point::new(size.width - half, size.height),
560 ),
561 &border_color,
562 &right,
563 );
564 }
565 if top.width > 0.0 {
566 let half = top.width / 2.0;
567 cx.stroke(
568 &Line::new(Point::new(0.0, half), Point::new(size.width, half)),
569 &border_color,
570 &top,
571 );
572 }
573 if bottom.width > 0.0 {
574 let half = bottom.width / 2.0;
575 cx.stroke(
576 &Line::new(
577 Point::new(0.0, size.height - half),
578 Point::new(size.width, size.height - half),
579 ),
580 &border_color,
581 &bottom,
582 );
583 }
584 }
585}
586
587#[allow(dead_code)]
589pub(crate) fn view_tab_navigation(root_view: ViewId, app_state: &mut AppState, backwards: bool) {
590 let start = app_state
591 .focus
592 .unwrap_or(app_state.prev_focus.unwrap_or(root_view));
593
594 let tree_iter = |id: ViewId| {
595 if backwards {
596 view_tree_previous(root_view, id).unwrap_or_else(|| view_nested_last_child(root_view))
597 } else {
598 view_tree_next(id).unwrap_or(root_view)
599 }
600 };
601
602 let mut new_focus = tree_iter(start);
603 while new_focus != start && !app_state.can_focus(new_focus) {
604 new_focus = tree_iter(new_focus);
605 }
606
607 app_state.clear_focus();
608 app_state.update_focus(new_focus, true);
609}
610
611fn view_tree_next(id: ViewId) -> Option<ViewId> {
613 if let Some(child) = id.children().into_iter().next() {
614 return Some(child);
615 }
616
617 let mut ancestor = id;
618 loop {
619 if let Some(next_sibling) = view_next_sibling(ancestor) {
620 return Some(next_sibling);
621 }
622 ancestor = ancestor.parent()?;
623 }
624}
625
626fn view_next_sibling(id: ViewId) -> Option<ViewId> {
628 let parent = id.parent();
629
630 let Some(parent) = parent else {
631 return None;
633 };
634
635 let children = parent.children();
636 let pos = children.iter().position(|v| v == &id)?;
638
639 if pos + 1 < children.len() {
640 Some(children[pos + 1])
641 } else {
642 None
643 }
644}
645
646fn view_tree_previous(root_view: ViewId, id: ViewId) -> Option<ViewId> {
648 view_previous_sibling(id)
649 .map(view_nested_last_child)
650 .or_else(|| {
651 (root_view != id).then_some(
652 id.parent()
653 .unwrap_or_else(|| view_nested_last_child(root_view)),
654 )
655 })
656}
657
658fn view_previous_sibling(id: ViewId) -> Option<ViewId> {
660 let parent = id.parent();
661
662 let Some(parent) = parent else {
663 return None;
665 };
666
667 let children = parent.children();
668 let pos = children.iter().position(|v| v == &id).unwrap();
669
670 if pos > 0 {
671 Some(children[pos - 1])
672 } else {
673 None
674 }
675}
676
677fn view_nested_last_child(view: ViewId) -> ViewId {
678 let mut last_child = view;
679 while let Some(new_last_child) = last_child.children().pop() {
680 last_child = new_last_child;
681 }
682 last_child
683}
684
685#[allow(dead_code)]
687pub(crate) fn view_debug_tree(root_view: ViewId) {
688 let mut views = vec![(root_view, Vec::new())];
689 while let Some((current_view, active_lines)) = views.pop() {
690 if let Some((leaf, root)) = active_lines.split_last() {
692 for line in root {
693 print!("{}", if *line { "│ " } else { " " });
694 }
695 print!("{}", if *leaf { "├── " } else { "└── " });
696 }
697 println!(
698 "{:?} {}",
699 current_view,
700 current_view.view().borrow().debug_name()
701 );
702
703 let mut children = current_view.children();
704 if let Some(last_child) = children.pop() {
705 views.push((last_child, [active_lines.as_slice(), &[false]].concat()));
706 }
707
708 views.extend(
709 children
710 .into_iter()
711 .rev()
712 .map(|child| (child, [active_lines.as_slice(), &[true]].concat())),
713 );
714 }
715}