use std::cell::RefCell;
use std::collections::{HashMap, VecDeque};
use std::hash::Hash;
use std::rc::Rc;
use crate::geometry::{Color, Point, Size};
use crate::input::{Handled, KeyEvent};
use crate::paint::PaintOp;
use crate::reactive::{Owner, RenderEffect};
use crate::semantics::Role;
use crate::style::{
Align, Direction, Edges, Justify, LayoutProps, Length, Overflow, PaintProps, Position, Style,
};
use crate::theme::{ColorToken, ColorValue, RadiusToken, RadiusValue};
use crate::tree::interaction::Handlers;
use crate::tree::{Dirty, WidgetId, WidgetTree};
use crate::variants::{PatchBuilder, StylePatch, Variants};
use crate::widget::{PaintCx, Widget};
pub(crate) type Update = Box<dyn FnOnce(&mut WidgetTree)>;
type ChildStyling = Rc<dyn Fn(&mut Style)>;
#[derive(Clone, Default)]
pub(crate) struct UpdateQueue(Rc<RefCell<VecDeque<Update>>>);
impl UpdateQueue {
pub(crate) fn push(&self, update: Update) {
self.0.borrow_mut().push_back(update);
}
pub(crate) fn apply(&self, tree: &mut WidgetTree) -> bool {
let mut applied = false;
loop {
let next = self.0.borrow_mut().pop_front();
let Some(update) = next else {
return applied;
};
applied = true;
update(tree);
}
}
}
#[derive(Clone)]
pub enum Value<T> {
Static(T),
Dynamic(Rc<dyn Fn() -> T>),
}
macro_rules! static_values {
($($property:ty),* $(,)?) => {
$(
impl From<$property> for Value<$property> {
fn from(value: $property) -> Value<$property> {
Value::Static(value)
}
}
)*
};
}
static_values!(
ColorValue,
RadiusValue,
Length,
Size,
Edges<Length>,
f32,
bool,
Direction,
Align,
Justify,
Point,
);
impl From<String> for Value<String> {
fn from(text: String) -> Value<String> {
Value::Static(text)
}
}
impl From<&str> for Value<String> {
fn from(text: &str) -> Value<String> {
Value::Static(text.to_owned())
}
}
impl From<Color> for Value<ColorValue> {
fn from(color: Color) -> Value<ColorValue> {
Value::Static(color.into())
}
}
impl From<ColorToken> for Value<ColorValue> {
fn from(token: ColorToken) -> Value<ColorValue> {
Value::Static(token.into())
}
}
impl From<f32> for Value<RadiusValue> {
fn from(radius: f32) -> Value<RadiusValue> {
Value::Static(radius.into())
}
}
impl From<RadiusToken> for Value<RadiusValue> {
fn from(token: RadiusToken) -> Value<RadiusValue> {
Value::Static(token.into())
}
}
impl<F: Fn() -> T + 'static, T: 'static> From<F> for Value<T> {
fn from(get: F) -> Value<T> {
Value::Dynamic(Rc::new(get))
}
}
type Install = Box<dyn FnOnce(WidgetId, &UpdateQueue) -> RenderEffect>;
struct Binding {
install: Install,
}
impl Binding {
fn layout<T: 'static>(get: Rc<dyn Fn() -> T>, write: fn(&mut LayoutProps, T)) -> Binding {
Binding {
install: Box::new(move |id, queue| {
let queue = queue.clone();
RenderEffect::new(move || {
let value = get();
let queue = queue.clone();
queue.push(Box::new(move |tree| {
tree.apply_layout(id, move |layout| write(layout, value));
}));
})
}),
}
}
fn paint<T: 'static>(get: Rc<dyn Fn() -> T>, write: fn(&mut PaintProps, T)) -> Binding {
Binding {
install: Box::new(move |id, queue| {
let queue = queue.clone();
RenderEffect::new(move || {
let value = get();
let queue = queue.clone();
queue.push(Box::new(move |tree| {
tree.apply_paint(id, move |paint| write(paint, value));
}));
})
}),
}
}
fn disabled(get: Rc<dyn Fn() -> bool>) -> Binding {
Binding {
install: Box::new(move |id, queue| {
let queue = queue.clone();
RenderEffect::new(move || {
let value = get();
let queue = queue.clone();
queue.push(Box::new(move |tree| {
tree.set_disabled(id, value);
}));
})
}),
}
}
fn a11y_label(get: Rc<dyn Fn() -> String>) -> Binding {
Binding {
install: Box::new(move |id, queue| {
let queue = queue.clone();
RenderEffect::new(move || {
let value = get();
let queue = queue.clone();
queue.push(Box::new(move |tree| {
tree.set_a11y_label(id, Some(value));
}));
})
}),
}
}
fn scroll_offset(get: Rc<dyn Fn() -> Point>) -> Binding {
Binding {
install: Box::new(move |id, queue| {
let queue = queue.clone();
RenderEffect::new(move || {
let value = get();
let queue = queue.clone();
queue.push(Box::new(move |tree| {
tree.set_scroll_offset(id, value);
}));
})
}),
}
}
fn widget<W: Widget, T: 'static>(
get: Rc<dyn Fn() -> T>,
apply: fn(&mut W, T) -> Dirty,
) -> Binding {
Binding {
install: Box::new(move |id, queue| {
let queue = queue.clone();
RenderEffect::new(move || {
let value = get();
let queue = queue.clone();
queue.push(Box::new(move |tree| {
let dirty = match tree.widget_mut::<W>(id) {
Some(widget) => apply(widget, value),
None => return,
};
tree.mark(id, dirty);
}));
})
}),
}
}
}
pub struct Element<W: Widget> {
widget: W,
style: Style,
bindings: Vec<Binding>,
variants: Option<Variants>,
handlers: Handlers,
disabled: bool,
focusable: bool,
a11y_role: Option<Role>,
a11y_label: Option<Value<String>>,
scroll_offset: Point,
child_styling: Option<ChildStyling>,
children: Vec<AnyElement>,
}
impl<W: Widget> Element<W> {
pub fn new(widget: W) -> Element<W> {
Element {
widget,
style: Style::default(),
bindings: Vec::new(),
variants: None,
handlers: Handlers::default(),
disabled: false,
focusable: false,
a11y_role: None,
a11y_label: None,
scroll_offset: Point::default(),
child_styling: None,
children: Vec::new(),
}
}
pub fn child(mut self, child: impl IntoElement) -> Self {
let mut child = child.into_any();
if let Some(style) = &self.child_styling {
style(&mut child.style);
}
self.children.push(child);
self
}
pub fn children(mut self, children: impl IntoIterator<Item = impl IntoElement>) -> Self {
self.children.extend(children.into_iter().map(|child| {
let mut child = child.into_any();
if let Some(style) = &self.child_styling {
style(&mut child.style);
}
child
}));
self
}
pub fn style_children(mut self, style: impl Fn(&mut Style) + 'static) -> Self {
let style = Rc::new(style);
for child in &mut self.children {
style(&mut child.style);
}
self.child_styling = Some(style);
self
}
fn layout_value<T: 'static>(
mut self,
value: impl Into<Value<T>>,
write: fn(&mut LayoutProps, T),
) -> Self {
match value.into() {
Value::Static(value) => write(&mut self.style.layout, value),
Value::Dynamic(get) => self.bindings.push(Binding::layout(get, write)),
}
self
}
fn paint_value<T: 'static>(
mut self,
value: impl Into<Value<T>>,
write: fn(&mut PaintProps, T),
) -> Self {
match value.into() {
Value::Static(value) => write(&mut self.style.paint, value),
Value::Dynamic(get) => self.bindings.push(Binding::paint(get, write)),
}
self
}
pub fn width(self, width: impl Into<Value<Length>>) -> Self {
self.layout_value(width, |layout, width| layout.width = width)
}
pub fn height(self, height: impl Into<Value<Length>>) -> Self {
self.layout_value(height, |layout, height| layout.height = height)
}
pub fn size(self, size: impl Into<Value<Size>>) -> Self {
self.layout_value(size, |layout, size| {
layout.width = Length::px(size.width);
layout.height = Length::px(size.height);
})
}
pub fn min_width(self, width: impl Into<Value<Length>>) -> Self {
self.layout_value(width, |layout, width| layout.min_width = width)
}
pub fn min_height(self, height: impl Into<Value<Length>>) -> Self {
self.layout_value(height, |layout, height| layout.min_height = height)
}
pub fn max_width(self, width: impl Into<Value<Length>>) -> Self {
self.layout_value(width, |layout, width| layout.max_width = width)
}
pub fn max_height(self, height: impl Into<Value<Length>>) -> Self {
self.layout_value(height, |layout, height| layout.max_height = height)
}
pub fn padding(self, padding: impl Into<Value<Length>>) -> Self {
self.layout_value(padding, |layout, padding| {
layout.padding = Edges::all(padding);
})
}
pub fn padding_xy(self, padding: impl Into<Value<Size>>) -> Self {
self.layout_value(padding, |layout, padding| {
layout.padding = Edges::xy(Length::px(padding.width), Length::px(padding.height));
})
}
pub fn margin(self, margin: impl Into<Value<Length>>) -> Self {
self.layout_value(margin, |layout, margin| {
layout.margin = Edges::all(margin);
})
}
pub fn gap(self, gap: impl Into<Value<Size>>) -> Self {
self.layout_value(gap, |layout, gap| layout.gap = gap)
}
pub fn direction(self, direction: impl Into<Value<Direction>>) -> Self {
self.layout_value(direction, |layout, direction| {
layout.direction = direction;
})
}
pub fn row(mut self) -> Self {
self.style.layout.direction = Direction::Row;
self
}
pub fn column(mut self) -> Self {
self.style.layout.direction = Direction::Column;
self
}
pub fn wrap(self, wrap: impl Into<Value<bool>>) -> Self {
self.layout_value(wrap, |layout, wrap| layout.wrap = wrap)
}
pub fn grow(self, grow: impl Into<Value<f32>>) -> Self {
self.layout_value(grow, |layout, grow| layout.grow = grow)
}
pub fn shrink(self, shrink: impl Into<Value<f32>>) -> Self {
self.layout_value(shrink, |layout, shrink| layout.shrink = shrink)
}
pub fn basis(self, basis: impl Into<Value<Length>>) -> Self {
self.layout_value(basis, |layout, basis| layout.basis = basis)
}
pub fn align_items(self, align: impl Into<Value<Align>>) -> Self {
self.layout_value(align, |layout, align| layout.align_items = Some(align))
}
pub fn justify_content(self, justify: impl Into<Value<Justify>>) -> Self {
self.layout_value(justify, |layout, justify| {
layout.justify_content = Some(justify);
})
}
pub fn absolute(mut self) -> Self {
self.style.layout.position = Position::Absolute;
self
}
pub fn inset(self, inset: impl Into<Value<Edges<Length>>>) -> Self {
self.layout_value(inset, |layout, inset| layout.inset = inset)
}
pub fn border(self, width: impl Into<Value<f32>>) -> Self {
self.layout_value(width, |layout, width| {
layout.border_width = Edges::all(width);
})
}
pub fn clip(self, clip: impl Into<Value<bool>>) -> Self {
self.layout_value(clip, |layout, clip| {
layout.overflow = if clip {
Overflow::Clip
} else {
Overflow::Visible
};
})
}
pub fn hidden(self, hidden: impl Into<Value<bool>>) -> Self {
self.layout_value(hidden, |layout, hidden| layout.hidden = hidden)
}
pub fn scroll_offset(mut self, offset: impl Into<Value<Point>>) -> Self {
match offset.into() {
Value::Static(offset) => self.scroll_offset = offset,
Value::Dynamic(get) => self.bindings.push(Binding::scroll_offset(get)),
}
self
}
pub fn background(self, color: impl Into<Value<ColorValue>>) -> Self {
self.paint_value(color, |paint, color| paint.background = Some(color))
}
pub fn border_color(self, color: impl Into<Value<ColorValue>>) -> Self {
self.paint_value(color, |paint, color| paint.border_color = Some(color))
}
pub fn text_color(self, color: impl Into<Value<ColorValue>>) -> Self {
self.paint_value(color, |paint, color| paint.text_color = Some(color))
}
pub fn corner_radius(self, radius: impl Into<Value<RadiusValue>>) -> Self {
self.paint_value(radius, |paint, radius| {
paint.corner_radius = radius;
})
}
fn variant(
mut self,
select: impl FnOnce(&mut Variants) -> &mut Option<StylePatch>,
build: impl FnOnce(PatchBuilder) -> PatchBuilder,
) -> Self {
let builder = build(PatchBuilder::default());
let variants = self.variants.get_or_insert_with(Variants::default);
*select(variants) = Some(builder.patch());
self
}
pub fn hover(self, patch: impl FnOnce(PatchBuilder) -> PatchBuilder) -> Self {
self.variant(|variants| &mut variants.hovered, patch)
}
pub fn focused(self, patch: impl FnOnce(PatchBuilder) -> PatchBuilder) -> Self {
self.variant(|variants| &mut variants.focused, patch)
}
pub fn pressed(self, patch: impl FnOnce(PatchBuilder) -> PatchBuilder) -> Self {
self.variant(|variants| &mut variants.pressed, patch)
}
pub fn disabled_style(self, patch: impl FnOnce(PatchBuilder) -> PatchBuilder) -> Self {
self.variant(|variants| &mut variants.disabled, patch)
}
pub fn on_press(mut self, handler: impl FnMut() + 'static) -> Self {
self.handlers.on_press = Some(Box::new(handler));
self
}
pub fn on_hover(mut self, handler: impl FnMut(bool) + 'static) -> Self {
self.handlers.on_hover = Some(Box::new(handler));
self
}
pub fn on_pointer_down(mut self, handler: impl FnMut(Point) + 'static) -> Self {
self.handlers.on_pointer_down = Some(Box::new(handler));
self
}
pub fn on_press_outside(mut self, handler: impl FnMut() + 'static) -> Self {
self.handlers.on_press_outside = Some(Box::new(handler));
self
}
pub fn on_wheel(mut self, handler: impl FnMut(Point) + 'static) -> Self {
self.handlers.on_wheel = Some(Box::new(handler));
self
}
pub fn on_pointer_move(mut self, handler: impl FnMut(Point) + 'static) -> Self {
self.handlers.on_pointer_move = Some(Box::new(handler));
self
}
pub fn on_pointer_up(mut self, handler: impl FnMut(Point) + 'static) -> Self {
self.handlers.on_pointer_up = Some(Box::new(handler));
self
}
pub fn on_ime_commit(mut self, handler: impl FnMut(&str) + 'static) -> Self {
self.handlers.on_ime_commit = Some(Box::new(handler));
self
}
pub fn on_ime_preedit(
mut self,
handler: impl FnMut(&str, Option<(usize, usize)>) + 'static,
) -> Self {
self.handlers.on_ime_preedit = Some(Box::new(handler));
self
}
pub fn disabled(mut self, disabled: impl Into<Value<bool>>) -> Self {
match disabled.into() {
Value::Static(disabled) => self.disabled = disabled,
Value::Dynamic(get) => self.bindings.push(Binding::disabled(get)),
}
self
}
pub fn role(mut self, role: Role) -> Self {
self.a11y_role = Some(role);
self
}
pub fn accessible_label(mut self, label: impl Into<Value<String>>) -> Self {
self.a11y_label = Some(label.into());
self
}
pub fn focusable(mut self) -> Self {
self.focusable = true;
self
}
pub fn on_key(mut self, handler: impl FnMut(&KeyEvent) -> Handled + 'static) -> Self {
self.handlers.on_key = Some(Box::new(handler));
self.focusable = true;
self
}
pub fn activate_on_keys(mut self) -> Self {
self.handlers.activate_on_keys = true;
self.focusable = true;
self
}
pub fn on_focus(mut self, handler: impl FnMut(bool) + 'static) -> Self {
self.handlers.on_focus = Some(Box::new(handler));
self
}
pub fn bind<T: 'static>(
mut self,
value: impl Into<Value<T>>,
apply: fn(&mut W, T) -> Dirty,
) -> Self {
match value.into() {
Value::Static(value) => {
apply(&mut self.widget, value);
}
Value::Dynamic(get) => self.bindings.push(Binding::widget(get, apply)),
}
self
}
}
pub struct AnyElement {
widget: Box<dyn Widget>,
style: Style,
bindings: Vec<Binding>,
variants: Option<Variants>,
handlers: Handlers,
disabled: bool,
focusable: bool,
a11y_role: Option<Role>,
a11y_label: Option<Value<String>>,
scroll_offset: Point,
children: Vec<AnyElement>,
}
impl AnyElement {
fn new<W: Widget>(element: Element<W>) -> AnyElement {
AnyElement {
widget: Box::new(element.widget),
style: element.style,
bindings: element.bindings,
variants: element.variants,
handlers: element.handlers,
disabled: element.disabled,
focusable: element.focusable,
a11y_role: element.a11y_role,
a11y_label: element.a11y_label,
scroll_offset: element.scroll_offset,
children: element.children,
}
}
}
pub trait IntoElement {
fn into_any(self) -> AnyElement;
}
impl<W: Widget> IntoElement for Element<W> {
fn into_any(self) -> AnyElement {
AnyElement::new(self)
}
}
impl IntoElement for AnyElement {
fn into_any(self) -> AnyElement {
self
}
}
pub(crate) fn mount(
element: AnyElement,
tree: &mut WidgetTree,
queue: &UpdateQueue,
parent: Option<WidgetId>,
) -> WidgetId {
let AnyElement {
widget,
style,
mut bindings,
variants,
handlers,
disabled,
focusable,
a11y_role,
a11y_label,
scroll_offset,
children,
} = element;
let id = tree.insert(parent, widget, style);
tree.set_variants(id, variants.map(Rc::new));
tree.set_disabled(id, disabled);
tree.set_focusable(id, focusable);
tree.set_a11y_role(id, a11y_role);
tree.set_handlers(id, handlers);
let bindings = match a11y_label {
Some(Value::Static(text)) => {
tree.set_a11y_label(id, Some(text));
bindings
}
Some(Value::Dynamic(get)) => {
bindings.push(Binding::a11y_label(get));
bindings
}
None => bindings,
};
if scroll_offset != Point::default() {
tree.set_scroll_offset(id, scroll_offset);
}
let owner = tree.owner(id).expect("the node was just inserted").clone();
for binding in bindings {
owner.with(|| (binding.install)(id, queue));
}
for child in children {
mount(child, tree, queue, Some(id));
}
id
}
pub struct Slot;
impl Widget for Slot {
fn paint(&self, _cx: &PaintCx<'_>, _out: &mut Vec<PaintOp>) {}
}
fn mount_under_slot(
slot: WidgetId,
tree: &mut WidgetTree,
queue: &UpdateQueue,
build: impl FnOnce() -> AnyElement,
) -> Option<(WidgetId, Owner)> {
let owner = tree.owner(slot)?.child();
let element = owner.with(build);
let child = mount(element, tree, queue, Some(slot));
Some((child, owner))
}
fn unmount_child(child: WidgetId, owner: &Owner, tree: &mut WidgetTree) {
tree.remove(child);
owner.cleanup();
}
pub fn show<E: IntoElement>(
when: impl Fn() -> bool + 'static,
build: impl Fn() -> E + 'static,
) -> Element<Slot> {
let build: Rc<dyn Fn() -> AnyElement> = Rc::new(move || build().into_any());
let mut element = Element::new(Slot);
element.bindings.push(Binding {
install: Box::new(move |id, queue| {
let queue = queue.clone();
let mounted: Rc<RefCell<Option<(WidgetId, Owner)>>> = Rc::new(RefCell::new(None));
RenderEffect::new(move || {
let visible = when();
let child_queue = queue.clone();
let mounted = Rc::clone(&mounted);
let build = Rc::clone(&build);
queue.push(Box::new(move |tree| {
let mut mounted = mounted.borrow_mut();
match (visible, mounted.take()) {
(true, None) => {
if let Some(child) =
mount_under_slot(id, tree, &child_queue, || build())
{
*mounted = Some(child);
}
}
(false, Some((child, owner))) => unmount_child(child, &owner, tree),
(_, unchanged) => *mounted = unchanged,
}
}));
})
}),
});
element
}
pub fn for_each<T, K, E>(
items: impl Fn() -> Vec<T> + 'static,
key: impl Fn(&T) -> K + 'static,
row: impl Fn(T) -> E + 'static,
) -> Element<Slot>
where
T: 'static,
K: Eq + Hash + Clone + 'static,
E: IntoElement,
{
let items = Rc::new(items);
let key = Rc::new(key);
let row: Rc<dyn Fn(T) -> AnyElement> = Rc::new(move |item| row(item).into_any());
let mut element = Element::new(Slot);
element.bindings.push(Binding {
install: Box::new(move |slot, queue| {
let queue = queue.clone();
let mounted: Rc<RefCell<HashMap<K, (WidgetId, Owner)>>> =
Rc::new(RefCell::new(HashMap::new()));
let order: Rc<RefCell<Vec<K>>> = Rc::new(RefCell::new(Vec::new()));
RenderEffect::new(move || {
let items = items();
let keys: Vec<K> = items.iter().map(|item| key(item)).collect();
let mut last: HashMap<K, usize> = HashMap::with_capacity(keys.len());
for (at, item_key) in keys.iter().enumerate() {
last.insert(item_key.clone(), at);
}
debug_assert!(
last.len() == keys.len(),
"for_each found a duplicate key, but the keys of a list must be unique"
);
let child_queue = queue.clone();
let mounted = Rc::clone(&mounted);
let order = Rc::clone(&order);
let row = Rc::clone(&row);
queue.push(Box::new(move |tree| {
let mut mounted = mounted.borrow_mut();
let mut order = order.borrow_mut();
mounted.retain(|key, (child, owner)| {
let kept = last.contains_key(key);
if !kept {
unmount_child(*child, owner, tree);
}
kept
});
let mut new_order = Vec::with_capacity(last.len());
let mut children = Vec::with_capacity(last.len());
for (at, (item, item_key)) in items.into_iter().zip(keys).enumerate() {
if last[&item_key] != at {
continue;
}
let child = match mounted.get(&item_key) {
Some((child, _)) => *child,
None => {
let row = Rc::clone(&row);
let Some((child, owner)) =
mount_under_slot(slot, tree, &child_queue, move || row(item))
else {
continue;
};
mounted.insert(item_key.clone(), (child, owner));
child
}
};
children.push(child);
new_order.push(item_key);
}
if *order != new_order {
tree.set_children(slot, children);
*order = new_order;
}
}));
})
}),
});
element
}
#[cfg(test)]
#[path = "element_tests.rs"]
mod tests;