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,
}
impl<'a> BuildCtx<'a> {
pub fn new(next_id: &'a mut u64) -> Self {
Self {
next_id,
has_focus: true,
}
}
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 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 {
Self {
inner: Box::new(view),
}
}
}
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);
}
}
#[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 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()));
}
}