use std::any::Any;
use crate::widget::Widget;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct ChangeFlags(u8);
impl ChangeFlags {
pub const NONE: Self = Self(0);
pub const PAINT: Self = Self(0b0000_0001);
pub const LAYOUT: Self = Self(0b0000_0010);
pub const fn contains(self, other: Self) -> bool {
(self.0 & other.0) == other.0
}
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub const fn is_empty(self) -> bool {
self.0 == 0
}
pub const fn needs_paint(self) -> bool {
!self.is_empty()
}
pub const fn needs_layout(self) -> bool {
self.contains(Self::LAYOUT)
}
}
impl core::ops::BitOr for ChangeFlags {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
self.union(rhs)
}
}
impl core::ops::BitOrAssign for ChangeFlags {
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
impl Default for ChangeFlags {
fn default() -> Self {
Self::NONE
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
pub struct WidgetId(pub u64);
impl From<WidgetId> for u64 {
fn from(id: WidgetId) -> Self {
id.0
}
}
pub struct BuildCtx<'a> {
next_id: &'a mut u64,
has_focus: bool,
element_replaced: bool,
}
impl<'a> BuildCtx<'a> {
pub fn new(next_id: &'a mut u64) -> Self {
Self {
next_id,
has_focus: true,
element_replaced: false,
}
}
pub fn alloc_id(&mut self) -> WidgetId {
*self.next_id += 1;
WidgetId(*self.next_id)
}
pub fn has_focus(&self) -> bool {
self.has_focus
}
pub fn set_has_focus(&mut self, has_focus: bool) {
self.has_focus = has_focus;
}
pub fn with_focus_link<R>(&mut self, link_focused: bool, f: impl FnOnce(&mut Self) -> R) -> R {
let outer = self.has_focus;
self.has_focus = outer && link_focused;
let result = f(self);
self.has_focus = outer;
result
}
pub fn mark_element_replaced(&mut self) {
self.element_replaced = true;
}
pub fn track_element_replaced<R>(&mut self, f: impl FnOnce(&mut Self) -> R) -> (R, bool) {
let outer = std::mem::replace(&mut self.element_replaced, false);
let result = f(self);
let replaced = std::mem::replace(&mut self.element_replaced, outer);
(result, replaced)
}
}
pub trait View<State: 'static>: 'static {
type Element: Widget;
fn build(&self, ctx: &mut BuildCtx<'_>) -> Self::Element;
fn rebuild(
&self,
prev: &Self,
element: &mut Self::Element,
ctx: &mut BuildCtx<'_>,
) -> ChangeFlags;
fn teardown(&self, _element: &mut Self::Element, _ctx: &mut BuildCtx<'_>) {}
fn message(&self, _element: &mut Self::Element, _state: &mut State) {}
}
trait ErasedView<State: 'static>: 'static {
fn dyn_build(&self, ctx: &mut BuildCtx<'_>) -> Box<dyn Widget>;
fn dyn_rebuild(
&self,
prev: &dyn ErasedView<State>,
element: &mut Box<dyn Widget>,
ctx: &mut BuildCtx<'_>,
) -> ChangeFlags;
fn dyn_teardown(&self, element: &mut Box<dyn Widget>, ctx: &mut BuildCtx<'_>);
fn as_any(&self) -> &dyn Any;
}
impl<State: 'static, V: View<State>> ErasedView<State> for V {
fn dyn_build(&self, ctx: &mut BuildCtx<'_>) -> Box<dyn Widget> {
Box::new(self.build(ctx))
}
fn dyn_rebuild(
&self,
prev: &dyn ErasedView<State>,
element: &mut Box<dyn Widget>,
ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
if let Some(prev) = prev.as_any().downcast_ref::<V>() {
let element = (**element)
.downcast_mut::<V::Element>()
.expect("erased element type matches its originating view");
self.rebuild(prev, element, ctx)
} else {
prev.dyn_teardown(element, ctx);
*element = self.dyn_build(ctx);
ChangeFlags::LAYOUT | ChangeFlags::PAINT
}
}
fn dyn_teardown(&self, element: &mut Box<dyn Widget>, ctx: &mut BuildCtx<'_>) {
if let Some(element) = (**element).downcast_mut::<V::Element>() {
self.teardown(element, ctx);
}
}
fn as_any(&self) -> &dyn Any {
self
}
}
pub struct AnyView<State: 'static> {
inner: Box<dyn ErasedView<State>>,
}
impl<State: 'static> AnyView<State> {
pub fn new<V: View<State>>(view: V) -> Self {
let mut slot = Some(view);
if let Some(erased) =
(&mut slot as &mut dyn core::any::Any).downcast_mut::<Option<AnyView<State>>>()
{
return erased.take().expect("slot is Some");
}
Self {
inner: Box::new(slot.expect("slot is Some")),
}
}
}
pub fn any<State: 'static, V: View<State>>(view: V) -> AnyView<State> {
AnyView::new(view)
}
impl<State: 'static> View<State> for AnyView<State> {
type Element = Box<dyn Widget>;
fn build(&self, ctx: &mut BuildCtx<'_>) -> Self::Element {
self.inner.dyn_build(ctx)
}
fn rebuild(
&self,
prev: &Self,
element: &mut Self::Element,
ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
self.inner.dyn_rebuild(prev.inner.as_ref(), element, ctx)
}
fn teardown(&self, element: &mut Self::Element, ctx: &mut BuildCtx<'_>) {
self.inner.dyn_teardown(element, ctx);
}
}
#[diagnostic::on_unimplemented(
message = "this is not a child sequence",
note = "a child list is a View, a tuple of up to 12 views (nest tuples beyond 12), an array, a Vec, an Option, or views(iter) for an iterator"
)]
pub trait ViewSeq<State: 'static, M> {
fn extend_views(self, out: &mut Vec<AnyView<State>>);
}
pub mod seq_marker {
use core::marker::PhantomData;
pub enum One {}
pub struct VecOf<M>(PhantomData<fn() -> M>);
pub struct ArrayOf<M>(PhantomData<fn() -> M>);
pub struct OptionOf<M>(PhantomData<fn() -> M>);
pub enum Iter {}
}
impl<State: 'static, V: View<State>> ViewSeq<State, seq_marker::One> for V {
fn extend_views(self, out: &mut Vec<AnyView<State>>) {
out.push(AnyView::new(self));
}
}
impl<State: 'static, M, S: ViewSeq<State, M>> ViewSeq<State, seq_marker::VecOf<M>> for Vec<S> {
fn extend_views(self, out: &mut Vec<AnyView<State>>) {
out.reserve(self.len());
for seq in self {
seq.extend_views(out);
}
}
}
impl<State: 'static, M, S: ViewSeq<State, M>, const N: usize> ViewSeq<State, seq_marker::ArrayOf<M>>
for [S; N]
{
fn extend_views(self, out: &mut Vec<AnyView<State>>) {
out.reserve(N);
for seq in self {
seq.extend_views(out);
}
}
}
impl<State: 'static, M, S: ViewSeq<State, M>> ViewSeq<State, seq_marker::OptionOf<M>>
for Option<S>
{
fn extend_views(self, out: &mut Vec<AnyView<State>>) {
if let Some(seq) = self {
seq.extend_views(out);
}
}
}
macro_rules! impl_view_seq_for_tuples {
($( ($($S:ident $M:ident $idx:tt),+) )+) => {$(
impl<State: 'static, $($M, $S: ViewSeq<State, $M>),+> ViewSeq<State, ($($M,)+)>
for ($($S,)+)
{
fn extend_views(self, out: &mut Vec<AnyView<State>>) {
$(self.$idx.extend_views(out);)+
}
}
)+};
}
impl_view_seq_for_tuples! {
(S0 M0 0)
(S0 M0 0, S1 M1 1)
(S0 M0 0, S1 M1 1, S2 M2 2)
(S0 M0 0, S1 M1 1, S2 M2 2, S3 M3 3)
(S0 M0 0, S1 M1 1, S2 M2 2, S3 M3 3, S4 M4 4)
(S0 M0 0, S1 M1 1, S2 M2 2, S3 M3 3, S4 M4 4, S5 M5 5)
(S0 M0 0, S1 M1 1, S2 M2 2, S3 M3 3, S4 M4 4, S5 M5 5, S6 M6 6)
(S0 M0 0, S1 M1 1, S2 M2 2, S3 M3 3, S4 M4 4, S5 M5 5, S6 M6 6, S7 M7 7)
(S0 M0 0, S1 M1 1, S2 M2 2, S3 M3 3, S4 M4 4, S5 M5 5, S6 M6 6, S7 M7 7, S8 M8 8)
(S0 M0 0, S1 M1 1, S2 M2 2, S3 M3 3, S4 M4 4, S5 M5 5, S6 M6 6, S7 M7 7, S8 M8 8,
S9 M9 9)
(S0 M0 0, S1 M1 1, S2 M2 2, S3 M3 3, S4 M4 4, S5 M5 5, S6 M6 6, S7 M7 7, S8 M8 8,
S9 M9 9, S10 M10 10)
(S0 M0 0, S1 M1 1, S2 M2 2, S3 M3 3, S4 M4 4, S5 M5 5, S6 M6 6, S7 M7 7, S8 M8 8,
S9 M9 9, S10 M10 10, S11 M11 11)
}
pub struct Views<I>(I);
impl<State: 'static, I> ViewSeq<State, seq_marker::Iter> for Views<I>
where
I: IntoIterator,
I::Item: View<State>,
{
fn extend_views(self, out: &mut Vec<AnyView<State>>) {
out.extend(self.0.into_iter().map(AnyView::new));
}
}
pub fn views<State: 'static, I>(iter: I) -> Views<I>
where
I: IntoIterator,
I::Item: View<State>,
{
Views(iter)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::layout::BoxConstraints;
use crate::widget::{LayoutCtx, PaintCtx, PaintScene, Widget};
use kurbo::Size;
use std::cell::Cell;
use std::rc::Rc;
struct WidgetA {
n: u32,
}
impl Widget for WidgetA {
fn layout(&mut self, _ctx: &mut LayoutCtx, _bc: &BoxConstraints) -> Size {
Size::new(self.n as f64, 1.0)
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
}
struct WidgetB;
impl Widget for WidgetB {
fn layout(&mut self, _ctx: &mut LayoutCtx, _bc: &BoxConstraints) -> Size {
Size::new(99.0, 99.0)
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
}
struct ViewA {
n: u32,
torn: Rc<Cell<u32>>,
}
impl View<()> for ViewA {
type Element = WidgetA;
fn build(&self, _ctx: &mut BuildCtx<'_>) -> WidgetA {
WidgetA { n: self.n }
}
fn rebuild(
&self,
prev: &Self,
element: &mut WidgetA,
_ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
if prev.n != self.n {
element.n = self.n;
ChangeFlags::PAINT
} else {
ChangeFlags::NONE
}
}
fn teardown(&self, _element: &mut WidgetA, _ctx: &mut BuildCtx<'_>) {
self.torn.set(self.torn.get() + 1);
}
}
struct ViewB;
impl View<()> for ViewB {
type Element = WidgetB;
fn build(&self, _ctx: &mut BuildCtx<'_>) -> WidgetB {
WidgetB
}
fn rebuild(
&self,
_prev: &Self,
_element: &mut WidgetB,
_ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
ChangeFlags::NONE
}
}
#[test]
fn any_view_same_type_rebuilds_in_place() {
let torn = Rc::new(Cell::new(0));
let mut counter = 0u64;
let mut ctx = BuildCtx::new(&mut counter);
let prev = any(ViewA {
n: 1,
torn: torn.clone(),
});
let mut element = prev.build(&mut ctx);
let next = any(ViewA {
n: 2,
torn: torn.clone(),
});
let flags = next.rebuild(&prev, &mut element, &mut ctx);
assert_eq!(flags, ChangeFlags::PAINT);
assert_eq!(torn.get(), 0);
let a = (*element)
.downcast_mut::<WidgetA>()
.expect("still a WidgetA");
assert_eq!(a.n, 2);
}
#[test]
fn any_view_type_swap_tears_down_and_replaces() {
let torn = Rc::new(Cell::new(0));
let mut counter = 0u64;
let mut ctx = BuildCtx::new(&mut counter);
let prev = any(ViewA {
n: 7,
torn: torn.clone(),
});
let mut element = prev.build(&mut ctx);
let next = any(ViewB);
let flags = next.rebuild(&prev, &mut element, &mut ctx);
assert_eq!(flags, ChangeFlags::LAYOUT | ChangeFlags::PAINT);
assert_eq!(torn.get(), 1, "old view should be torn down exactly once");
assert!((*element).downcast_mut::<WidgetB>().is_some());
assert!((*element).downcast_mut::<WidgetA>().is_none());
}
#[test]
fn any_view_element_works_inside_a_child_pod() {
use crate::widget::ChildPod;
let torn = Rc::new(Cell::new(0));
let mut counter = 0u64;
let mut ctx = BuildCtx::new(&mut counter);
let view = any(ViewA { n: 5, torn });
let element: Box<dyn Widget> = view.build(&mut ctx);
let mut pod = ChildPod::new(Box::new(element));
let mut lctx = LayoutCtx::new();
let size = pod.layout_child(&mut lctx, &BoxConstraints::loose(Size::new(100.0, 100.0)));
assert_eq!(size, Size::new(5.0, 1.0));
}
#[test]
fn change_flags_union_and_contains() {
let both = ChangeFlags::PAINT | ChangeFlags::LAYOUT;
assert!(both.contains(ChangeFlags::PAINT));
assert!(both.contains(ChangeFlags::LAYOUT));
assert!(both.needs_layout());
assert!(both.needs_paint());
assert!(ChangeFlags::NONE.is_empty());
assert!(!ChangeFlags::NONE.needs_paint());
let paint_only = ChangeFlags::PAINT;
assert!(paint_only.needs_paint());
assert!(!paint_only.needs_layout());
}
#[test]
fn change_flags_bitor_assign() {
let mut f = ChangeFlags::NONE;
f |= ChangeFlags::PAINT;
assert!(f.contains(ChangeFlags::PAINT));
assert!(!f.contains(ChangeFlags::LAYOUT));
}
#[test]
fn double_erasure_is_idempotent() {
let torn = Rc::new(Cell::new(0));
let mut counter = 0u64;
let mut ctx = BuildCtx::new(&mut counter);
let double_erased = any(any(ViewA {
n: 5,
torn: torn.clone(),
}));
let mut element = double_erased.build(&mut ctx);
let a = (*element)
.downcast_mut::<WidgetA>()
.expect("double-erased any(any(ViewA)) should have WidgetA element directly");
assert_eq!(a.n, 5);
let double_erased_2 = any(any(ViewA {
n: 10,
torn: torn.clone(),
}));
let flags = double_erased_2.rebuild(&double_erased, &mut element, &mut ctx);
assert_eq!(flags, ChangeFlags::PAINT);
assert_eq!(torn.get(), 0, "no teardown on same-type rebuild");
let a = (*element)
.downcast_mut::<WidgetA>()
.expect("still a WidgetA after rebuild");
assert_eq!(a.n, 10);
}
#[test]
fn build_ctx_allocates_unique_ids() {
let mut counter = 0;
let mut ctx = BuildCtx::new(&mut counter);
let a = ctx.alloc_id();
let b = ctx.alloc_id();
assert_ne!(a, b);
assert_eq!(u64::from(a), 1);
assert_eq!(u64::from(b), 2);
}
#[test]
fn build_ctx_focus_chain_only_narrows_and_restores() {
let mut counter = 0;
let mut ctx = BuildCtx::new(&mut counter);
assert!(
ctx.has_focus(),
"an unseeded context assumes a live chain (see BuildCtx::new)"
);
ctx.with_focus_link(true, |ctx| assert!(ctx.has_focus()));
assert!(ctx.has_focus());
ctx.with_focus_link(false, |ctx| {
assert!(!ctx.has_focus());
ctx.with_focus_link(true, |ctx| {
assert!(
!ctx.has_focus(),
"no descent may widen the chain a cleared ancestor closed"
);
});
assert!(!ctx.has_focus());
});
assert!(
ctx.has_focus(),
"the outer chain is restored, not clobbered"
);
ctx.set_has_focus(false);
assert!(!ctx.has_focus());
ctx.with_focus_link(true, |ctx| assert!(!ctx.has_focus()));
}
#[test]
fn build_ctx_element_replaced_is_consumed_by_the_nearest_scope() {
let mut counter = 0;
let mut ctx = BuildCtx::new(&mut counter);
let ((), replaced) = ctx.track_element_replaced(|_| {});
assert!(!replaced);
let (n, replaced) = ctx.track_element_replaced(|ctx| {
ctx.mark_element_replaced();
7
});
assert_eq!(n, 7);
assert!(replaced);
let (inner, outer) = ctx.track_element_replaced(|ctx| {
let ((), inner) = ctx.track_element_replaced(|ctx| ctx.mark_element_replaced());
inner
});
assert!(inner, "the inner scope consumes its own signal");
assert!(!outer, "a consumed signal does not reach the outer scope");
ctx.mark_element_replaced();
let ((), sibling) = ctx.track_element_replaced(|_| {});
assert!(!sibling, "a scope sees only signals raised inside it");
let ((), outer) = ctx.track_element_replaced(|ctx| {
ctx.mark_element_replaced();
});
assert!(outer);
let ((), _) = ctx.track_element_replaced(|_| {});
assert!(
std::mem::take(&mut ctx.element_replaced),
"the caller's pending signal is restored, not clobbered"
);
}
fn seq<M>(children: impl ViewSeq<(), M>) -> Vec<AnyView<()>> {
let mut out = Vec::new();
children.extend_views(&mut out);
out
}
fn a(n: u32) -> ViewA {
ViewA {
n,
torn: Rc::new(Cell::new(0)),
}
}
fn tags(children: &[AnyView<()>]) -> Vec<u32> {
let mut counter = 0u64;
let mut ctx = BuildCtx::new(&mut counter);
children
.iter()
.map(|child| {
let mut element = child.build(&mut ctx);
if let Some(a) = (*element).downcast_mut::<WidgetA>() {
a.n
} else {
(*element)
.downcast_mut::<WidgetB>()
.expect("element is the concrete widget, not a re-boxed one");
99
}
})
.collect()
}
#[test]
fn view_seq_single_view_is_one_element() {
assert_eq!(tags(&seq(a(4))), vec![4]);
}
#[test]
fn view_seq_tuple_of_mixed_views_keeps_order() {
let children = seq((a(1), ViewB, a(3)));
assert_eq!(children.len(), 3);
assert_eq!(tags(&children), vec![1, 99, 3]);
}
#[test]
fn view_seq_nested_tuples_flatten_in_order() {
let children = seq(((a(1), (ViewB, a(2))), a(3), (a(4),)));
assert_eq!(tags(&children), vec![1, 99, 2, 3, 4]);
}
#[test]
fn view_seq_vec_array_and_option_counts() {
assert_eq!(tags(&seq(vec![a(1), a(2)])), vec![1, 2]);
assert!(seq(Vec::<ViewB>::new()).is_empty());
assert_eq!(tags(&seq([a(5), a(6), a(7)])), vec![5, 6, 7]);
assert_eq!(tags(&seq(Some(a(8)))), vec![8]);
assert!(seq(None::<ViewA>).is_empty());
assert_eq!(tags(&seq((None::<ViewA>, ViewB))), vec![99]);
assert_eq!(
tags(&seq(vec![(a(1), ViewB), (a(2), ViewB)])),
vec![1, 99, 2, 99]
);
}
#[test]
fn view_seq_does_not_rebox_an_any_view_element() {
let children = seq((any(a(1)), any(any(ViewB)), vec![any(a(2))]));
assert_eq!(tags(&children), vec![1, 99, 2]);
}
#[test]
fn view_seq_views_adapter_maps_an_iterator() {
let children = seq(views((1..=3).map(a)));
assert_eq!(tags(&children), vec![1, 2, 3]);
let children = seq((ViewB, views([4, 5].into_iter().map(a))));
assert_eq!(tags(&children), vec![99, 4, 5]);
}
#[test]
fn view_seq_twelve_tuple_compiles_and_nesting_goes_beyond() {
let twelve = (
a(1),
a(2),
a(3),
a(4),
a(5),
a(6),
a(7),
a(8),
a(9),
a(10),
a(11),
a(12),
);
assert_eq!(tags(&seq(twelve)), (1..=12).collect::<Vec<_>>());
let thirteen = (
(
a(1),
a(2),
a(3),
a(4),
a(5),
a(6),
a(7),
a(8),
a(9),
a(10),
a(11),
a(12),
),
a(13),
);
assert_eq!(tags(&seq(thirteen)), (1..=13).collect::<Vec<_>>());
}
}