use floem_reactive::{ReadSignal, RwSignal, SignalGet};
use peniko::kurbo::{Circle, Insets, Line, Point, Rect, RoundedRect, Size};
use std::any::Any;
use taffy::tree::NodeId;
use crate::{
app_state::AppState,
context::{ComputeLayoutCx, EventCx, LayoutCx, PaintCx, StyleCx, UpdateCx},
event::{Event, EventPropagation},
id::ViewId,
style::{LayoutProps, Style, StyleClassRef},
view_state::ViewStyleProps,
views::{dyn_view, DynamicView},
Renderer,
};
pub type AnyView = Box<dyn View>;
pub trait IntoView: Sized {
type V: View + 'static;
fn into_view(self) -> Self::V;
fn into_any(self) -> AnyView {
Box::new(self.into_view())
}
}
impl<IV: IntoView + 'static> IntoView for Box<dyn Fn() -> IV> {
type V = DynamicView;
fn into_view(self) -> Self::V {
dyn_view(self)
}
}
impl<T: IntoView + Clone + 'static> IntoView for RwSignal<T> {
type V = DynamicView;
fn into_view(self) -> Self::V {
dyn_view(move || self.get())
}
}
impl<T: IntoView + Clone + 'static> IntoView for ReadSignal<T> {
type V = DynamicView;
fn into_view(self) -> Self::V {
dyn_view(move || self.get())
}
}
impl<VW: View + 'static> IntoView for VW {
type V = VW;
fn into_view(self) -> Self::V {
self
}
}
impl IntoView for i32 {
type V = crate::views::Label;
fn into_view(self) -> Self::V {
crate::views::text(self)
}
}
impl IntoView for usize {
type V = crate::views::Label;
fn into_view(self) -> Self::V {
crate::views::text(self)
}
}
impl IntoView for &str {
type V = crate::views::Label;
fn into_view(self) -> Self::V {
crate::views::text(self)
}
}
impl IntoView for String {
type V = crate::views::Label;
fn into_view(self) -> Self::V {
crate::views::text(self)
}
}
impl<IV: IntoView + 'static> IntoView for Vec<IV> {
type V = crate::views::Stack;
fn into_view(self) -> Self::V {
crate::views::stack_from_iter(self)
}
}
pub fn recursively_layout_view(id: ViewId, cx: &mut LayoutCx) -> NodeId {
cx.layout_node(id, true, |cx| {
let mut nodes = Vec::new();
for child in id.children() {
let view = child.view();
let mut view = view.borrow_mut();
nodes.push(view.layout(cx));
}
nodes
})
}
pub trait View {
fn id(&self) -> ViewId;
fn view_style(&self) -> Option<Style> {
None
}
fn view_class(&self) -> Option<StyleClassRef> {
None
}
fn debug_name(&self) -> std::borrow::Cow<'static, str> {
core::any::type_name::<Self>().into()
}
fn update(&mut self, cx: &mut UpdateCx, state: Box<dyn Any>) {
let _ = cx;
let _ = state;
}
fn style_pass(&mut self, cx: &mut StyleCx<'_>) {
for child in self.id().children() {
cx.style_view(child);
}
}
fn layout(&mut self, cx: &mut LayoutCx) -> NodeId {
recursively_layout_view(self.id(), cx)
}
fn compute_layout(&mut self, cx: &mut ComputeLayoutCx) -> Option<Rect> {
default_compute_layout(self.id(), cx)
}
fn event_before_children(&mut self, cx: &mut EventCx, event: &Event) -> EventPropagation {
let _ = cx;
let _ = event;
EventPropagation::Continue
}
fn event_after_children(&mut self, cx: &mut EventCx, event: &Event) -> EventPropagation {
let _ = cx;
let _ = event;
EventPropagation::Continue
}
fn paint(&mut self, cx: &mut PaintCx) {
cx.paint_children(self.id());
}
fn scroll_to(&mut self, cx: &mut AppState, target: ViewId, rect: Option<Rect>) -> bool {
if self.id() == target {
return true;
}
let mut found = false;
for child in self.id().children() {
found |= child.view().borrow_mut().scroll_to(cx, target, rect);
}
found
}
}
impl View for Box<dyn View> {
fn id(&self) -> ViewId {
(**self).id()
}
fn view_style(&self) -> Option<Style> {
(**self).view_style()
}
fn view_class(&self) -> Option<StyleClassRef> {
(**self).view_class()
}
fn debug_name(&self) -> std::borrow::Cow<'static, str> {
(**self).debug_name()
}
fn update(&mut self, cx: &mut UpdateCx, state: Box<dyn Any>) {
(**self).update(cx, state)
}
fn style_pass(&mut self, cx: &mut StyleCx) {
(**self).style_pass(cx)
}
fn layout(&mut self, cx: &mut LayoutCx) -> NodeId {
(**self).layout(cx)
}
fn event_before_children(&mut self, cx: &mut EventCx, event: &Event) -> EventPropagation {
(**self).event_before_children(cx, event)
}
fn event_after_children(&mut self, cx: &mut EventCx, event: &Event) -> EventPropagation {
(**self).event_after_children(cx, event)
}
fn compute_layout(&mut self, cx: &mut ComputeLayoutCx) -> Option<Rect> {
(**self).compute_layout(cx)
}
fn paint(&mut self, cx: &mut PaintCx) {
(**self).paint(cx)
}
fn scroll_to(&mut self, cx: &mut AppState, target: ViewId, rect: Option<Rect>) -> bool {
(**self).scroll_to(cx, target, rect)
}
}
pub fn default_compute_layout(id: ViewId, cx: &mut ComputeLayoutCx) -> Option<Rect> {
let mut layout_rect: Option<Rect> = None;
for child in id.children() {
let child_layout = cx.compute_view_layout(child);
if let Some(child_layout) = child_layout {
if let Some(rect) = layout_rect {
layout_rect = Some(rect.union(child_layout));
} else {
layout_rect = Some(child_layout);
}
}
}
layout_rect
}
pub(crate) fn paint_bg(cx: &mut PaintCx, style: &ViewStyleProps, size: Size) {
let radius = match style.border_radius() {
crate::unit::PxPct::Px(px) => px,
crate::unit::PxPct::Pct(pct) => size.min_side() * (pct / 100.),
};
if radius > 0.0 {
let rect = size.to_rect();
let width = rect.width();
let height = rect.height();
if width > 0.0 && height > 0.0 && radius > width.max(height) / 2.0 {
let radius = width.max(height) / 2.0;
let circle = Circle::new(rect.center(), radius);
let bg = match style.background() {
Some(color) => color,
None => return,
};
cx.fill(&circle, &bg, 0.0);
} else {
paint_box_shadow(cx, style, rect, Some(radius));
let bg = match style.background() {
Some(color) => color,
None => return,
};
let rounded_rect = rect.to_rounded_rect(radius);
cx.fill(&rounded_rect, &bg, 0.0);
}
} else {
paint_box_shadow(cx, style, size.to_rect(), None);
let bg = match style.background() {
Some(color) => color,
None => return,
};
cx.fill(&size.to_rect(), &bg, 0.0);
}
}
fn paint_box_shadow(
cx: &mut PaintCx,
style: &ViewStyleProps,
rect: Rect,
rect_radius: Option<f64>,
) {
if let Some(shadow) = &style.shadow() {
let min = rect.size().min_side();
let h_offset = match shadow.h_offset {
crate::unit::PxPct::Px(px) => px,
crate::unit::PxPct::Pct(pct) => min * (pct / 100.),
};
let v_offset = match shadow.v_offset {
crate::unit::PxPct::Px(px) => px,
crate::unit::PxPct::Pct(pct) => min * (pct / 100.),
};
let spread = match shadow.spread {
crate::unit::PxPct::Px(px) => px,
crate::unit::PxPct::Pct(pct) => min * (pct / 100.),
};
let blur_radius = match shadow.blur_radius {
crate::unit::PxPct::Px(px) => px,
crate::unit::PxPct::Pct(pct) => min * (pct / 100.),
};
let inset = Insets::new(
-h_offset / 2.0,
-v_offset / 2.0,
h_offset / 2.0,
v_offset / 2.0,
);
let rect = rect.inflate(spread, spread).inset(inset);
if let Some(radii) = rect_radius {
let rounded_rect = RoundedRect::from_rect(rect, radii + spread);
cx.fill(&rounded_rect, shadow.color, blur_radius);
} else {
cx.fill(&rect, shadow.color, blur_radius);
}
}
}
pub(crate) fn paint_outline(cx: &mut PaintCx, style: &ViewStyleProps, size: Size) {
let outline = &style.outline().0;
if outline.width == 0. {
return;
}
let half = outline.width / 2.0;
let rect = size.to_rect().inflate(half, half);
let border_radius = match style.border_radius() {
crate::unit::PxPct::Px(px) => px,
crate::unit::PxPct::Pct(pct) => size.min_side() * (pct / 100.),
};
cx.stroke(
&rect.to_rounded_rect(border_radius + half),
&style.outline_color(),
outline,
);
}
pub(crate) fn paint_border(
cx: &mut PaintCx,
layout_style: &LayoutProps,
style: &ViewStyleProps,
size: Size,
) {
let left = layout_style.border_left().0;
let top = layout_style.border_top().0;
let right = layout_style.border_right().0;
let bottom = layout_style.border_bottom().0;
let border_color = style.border_color();
if left.width == top.width
&& top.width == right.width
&& right.width == bottom.width
&& bottom.width == left.width
&& left.width > 0.0
{
let half = left.width / 2.0;
let rect = size.to_rect().inflate(-half, -half);
let radius = match style.border_radius() {
crate::unit::PxPct::Px(px) => px,
crate::unit::PxPct::Pct(pct) => size.min_side() * (pct / 100.),
};
if radius > 0.0 {
let radius = (radius - half).max(0.0);
cx.stroke(&rect.to_rounded_rect(radius), &border_color, &left);
} else {
cx.stroke(&rect, &border_color, &left);
}
} else {
if left.width > 0.0 {
let half = left.width / 2.0;
cx.stroke(
&Line::new(Point::new(half, 0.0), Point::new(half, size.height)),
&border_color,
&left,
);
}
if right.width > 0.0 {
let half = right.width / 2.0;
cx.stroke(
&Line::new(
Point::new(size.width - half, 0.0),
Point::new(size.width - half, size.height),
),
&border_color,
&right,
);
}
if top.width > 0.0 {
let half = top.width / 2.0;
cx.stroke(
&Line::new(Point::new(0.0, half), Point::new(size.width, half)),
&border_color,
&top,
);
}
if bottom.width > 0.0 {
let half = bottom.width / 2.0;
cx.stroke(
&Line::new(
Point::new(0.0, size.height - half),
Point::new(size.width, size.height - half),
),
&border_color,
&bottom,
);
}
}
}
#[allow(dead_code)]
pub(crate) fn view_tab_navigation(root_view: ViewId, app_state: &mut AppState, backwards: bool) {
let start = app_state
.focus
.unwrap_or(app_state.prev_focus.unwrap_or(root_view));
let tree_iter = |id: ViewId| {
if backwards {
view_tree_previous(root_view, id).unwrap_or_else(|| view_nested_last_child(root_view))
} else {
view_tree_next(id).unwrap_or(root_view)
}
};
let mut new_focus = tree_iter(start);
while new_focus != start && !app_state.can_focus(new_focus) {
new_focus = tree_iter(new_focus);
}
app_state.clear_focus();
app_state.update_focus(new_focus, true);
}
fn view_tree_next(id: ViewId) -> Option<ViewId> {
if let Some(child) = id.children().into_iter().next() {
return Some(child);
}
let mut ancestor = id;
loop {
if let Some(next_sibling) = view_next_sibling(ancestor) {
return Some(next_sibling);
}
ancestor = ancestor.parent()?;
}
}
fn view_next_sibling(id: ViewId) -> Option<ViewId> {
let parent = id.parent();
let Some(parent) = parent else {
return None;
};
let children = parent.children();
let pos = children.iter().position(|v| v == &id)?;
if pos + 1 < children.len() {
Some(children[pos + 1])
} else {
None
}
}
fn view_tree_previous(root_view: ViewId, id: ViewId) -> Option<ViewId> {
view_previous_sibling(id)
.map(view_nested_last_child)
.or_else(|| {
(root_view != id).then_some(
id.parent()
.unwrap_or_else(|| view_nested_last_child(root_view)),
)
})
}
fn view_previous_sibling(id: ViewId) -> Option<ViewId> {
let parent = id.parent();
let Some(parent) = parent else {
return None;
};
let children = parent.children();
let pos = children.iter().position(|v| v == &id).unwrap();
if pos > 0 {
Some(children[pos - 1])
} else {
None
}
}
fn view_nested_last_child(view: ViewId) -> ViewId {
let mut last_child = view;
while let Some(new_last_child) = last_child.children().pop() {
last_child = new_last_child;
}
last_child
}
#[allow(dead_code)]
pub(crate) fn view_debug_tree(root_view: ViewId) {
let mut views = vec![(root_view, Vec::new())];
while let Some((current_view, active_lines)) = views.pop() {
if let Some((leaf, root)) = active_lines.split_last() {
for line in root {
print!("{}", if *line { "│ " } else { " " });
}
print!("{}", if *leaf { "├── " } else { "└── " });
}
println!(
"{:?} {}",
current_view,
current_view.view().borrow().debug_name()
);
let mut children = current_view.children();
if let Some(last_child) = children.pop() {
views.push((last_child, [active_lines.as_slice(), &[false]].concat()));
}
views.extend(
children
.into_iter()
.rev()
.map(|child| (child, [active_lines.as_slice(), &[true]].concat())),
);
}
}