use std::cell::{RefCell, RefMut};
use std::path::PathBuf;
use std::rc::{Rc, Weak};
use crabshell_reactive::Signal;
use taffy::TaffyTree;
use crate::animation::{self, Transition};
use crate::cx::{Anchor, Cx};
use crate::floating::FloatingState;
use crate::geometry::{Affine, Rect, Size};
use crate::input::{Handlers, Pointer};
use crate::keyboard::Keyboard;
use crate::keyed::KeyedSlot;
use crate::prop::{IntoProp, Prop, PropKind};
use crate::region::Region;
use crate::screencopy::CaptureNode;
use crate::style::{Padding, Style};
use crate::text::TextNode;
use crate::text_input::InputNode;
use crate::widgets::{Align, Element, ElementChild, ElementKind, Fit, IconSource, Justify};
use crate::window::{
Anchors, DEFAULT_NAMESPACE, Hints, KeyboardFocus, Layer, Margins, Output, PanelWindow, Window,
};
pub(crate) type NodeId = taffy::NodeId;
pub(crate) struct Tree {
pub(crate) this: Weak<RefCell<Tree>>,
pub(crate) taffy: TaffyTree<Node>,
pub(crate) root: NodeId,
pub(crate) output: Output,
pub(crate) surface: Surface,
pub(crate) size: Size,
pub(crate) needs_layout: bool,
pub(crate) needs_place: bool,
pub(crate) damage: Vec<Rect>,
pub(crate) pointer: Pointer,
pub(crate) keyboard: Keyboard,
pub(crate) popups: Vec<Window>,
pub(crate) popup: Option<PopupState>,
pub(crate) floating: Option<FloatingState>,
pub(crate) captures: Vec<NodeId>,
}
pub(crate) struct PopupState {
pub(crate) opener: Rc<Anchor>,
pub(crate) close: bool,
pub(crate) alive: Signal<()>,
pub(crate) placement: crate::popup::Placement,
pub(crate) wanted: Size,
pub(crate) limit: Option<Size>,
}
pub(crate) struct Node {
pub(crate) name: &'static str,
pub(crate) path: Rc<str>,
pub(crate) cx: Cx,
pub(crate) style: Style,
pub(crate) kind: NodeKind,
pub(crate) groups: Vec<Vec<NodeId>>,
pub(crate) next_child: usize,
pub(crate) rect: Option<Rect>,
pub(crate) content: Rect,
pub(crate) painted: Rect,
pub(crate) bounds: Rect,
pub(crate) inner_bounds: Rect,
pub(crate) window_bounds: Rect,
pub(crate) window_painted: Rect,
pub(crate) drawn: Rect,
pub(crate) transform: Option<Affine>,
pub(crate) clips: bool,
pub(crate) fade: Option<Fade>,
pub(crate) fades: u64,
pub(crate) changed: bool,
}
pub(crate) enum NodeKind {
Root,
Flex(Flex),
Stack,
Spacer,
Text(TextNode),
Icon(IconNode),
Image(ImageNode),
Capture(CaptureNode),
Scroll(ScrollNode),
Input(Box<InputNode>),
Button(Box<ButtonNode>),
}
pub(crate) struct Flex {
pub(crate) column: bool,
pub(crate) grow: bool,
pub(crate) gap: u32,
pub(crate) padding_vertical: Option<u32>,
pub(crate) padding_horizontal: Option<u32>,
pub(crate) align: Align,
pub(crate) justify: Justify,
}
pub(crate) struct ScrollNode {
pub(crate) signal: Signal<f32>,
pub(crate) offset: f32,
pub(crate) shown: f32,
pub(crate) most: f32,
}
pub(crate) struct ImageNode {
pub(crate) source: Option<PathBuf>,
pub(crate) width: u32,
pub(crate) height: u32,
pub(crate) fit: Fit,
}
pub(crate) struct IconNode {
pub(crate) source: Option<IconSource>,
pub(crate) size: u32,
}
pub(crate) struct ButtonNode {
pub(crate) hover_style: Style,
pub(crate) pressed_style: Style,
pub(crate) focus_style: Style,
pub(crate) handlers: Signal<Handlers>,
pub(crate) hover_signal: Option<Signal<bool>>,
pub(crate) hovered: bool,
pub(crate) pressed: bool,
pub(crate) focused: bool,
}
pub(crate) struct Surface {
pub(crate) anchors: Anchors,
pub(crate) width: u32,
pub(crate) height: u32,
pub(crate) exclusive_zone: Option<i32>,
pub(crate) layer: Layer,
pub(crate) margins: Margins,
pub(crate) keyboard_focus: KeyboardFocus,
pub(crate) namespace: String,
pub(crate) mask: Option<Region>,
pub(crate) blur: Option<Region>,
pub(crate) inhibit_idle: bool,
pub(crate) inhibit_shortcuts: bool,
pub(crate) shortcuts_inhibited: Option<Signal<bool>>,
pub(crate) focus_grab: Option<Signal<bool>>,
pub(crate) compositor_opacity: f32,
pub(crate) opacity_logged: bool,
pub(crate) visible_mask: Option<Region>,
pub(crate) changed: bool,
}
impl Default for Surface {
fn default() -> Surface {
Surface {
anchors: Anchors::NONE,
width: 0,
height: 0,
exclusive_zone: None,
layer: Layer::Top,
margins: Margins::default(),
keyboard_focus: KeyboardFocus::None,
namespace: String::from(DEFAULT_NAMESPACE),
mask: None,
blur: None,
inhibit_idle: false,
inhibit_shortcuts: false,
shortcuts_inhibited: None,
focus_grab: None,
compositor_opacity: 1.0,
opacity_logged: false,
visible_mask: None,
changed: false,
}
}
}
pub(crate) struct Fade {
pub(crate) from: Style,
pub(crate) t: f32,
pub(crate) generation: u64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Change {
None,
Paint,
Place,
Layout,
}
pub(crate) fn borrow(tree: &RefCell<Tree>) -> RefMut<'_, Tree> {
tree
.try_borrow_mut()
.expect("invariant: a window's tree is not borrowed while effects or handlers run")
}
impl Node {
fn new(name: &'static str, path: Rc<str>, cx: Cx, kind: NodeKind) -> Node {
Node {
name,
path,
cx,
style: Style::new(),
kind,
groups: Vec::new(),
next_child: 0,
rect: None,
content: Rect::default(),
painted: Rect::default(),
bounds: Rect::default(),
inner_bounds: Rect::default(),
window_bounds: Rect::default(),
window_painted: Rect::default(),
drawn: Rect::default(),
transform: None,
clips: false,
fade: None,
fades: 0,
changed: true,
}
}
pub(crate) fn effective_style(&self) -> Style {
let NodeKind::Button(button) = &self.kind else {
return self.style;
};
let mut style = self.style;
if button.focused {
style = style.merge(button.focus_style);
}
if button.hovered {
style = style.merge(button.hover_style);
if button.pressed {
style = style.merge(button.pressed_style);
}
}
style
}
pub(crate) fn shown_style(&self) -> Style {
match &self.fade {
Some(fade) => fade.from.lerp(&self.effective_style(), fade.t),
None => self.effective_style(),
}
}
pub(crate) fn clips(&self) -> bool {
self.effective_style().clips() || matches!(self.kind, NodeKind::Scroll(_) | NodeKind::Input(_))
}
pub(crate) fn draws(&self) -> bool {
let style = self.effective_style();
matches!(
self.kind,
NodeKind::Root
| NodeKind::Text(_)
| NodeKind::Icon(_)
| NodeKind::Image(_)
| NodeKind::Capture(_)
| NodeKind::Input(_)
) || style.background.is_some()
|| style.gradient.is_some()
|| style.shadow.is_some()
|| self.fade.is_some()
|| style.border.is_some()
}
pub(crate) fn padding(&self) -> Padding {
let style = self.effective_style().padding.unwrap_or_default();
match &self.kind {
NodeKind::Flex(flex) => {
let (top, bottom) = flex
.padding_vertical
.map_or((style.top, style.bottom), |own| (own, own));
let (left, right) = flex
.padding_horizontal
.map_or((style.left, style.right), |own| (own, own));
Padding {
top,
right,
bottom,
left,
}
}
_ => style,
}
}
}
impl Tree {
pub(crate) fn new(
this: Weak<RefCell<Tree>>,
name: &'static str,
output: Output,
cx: Cx,
path: Rc<str>,
) -> Tree {
let mut taffy = TaffyTree::new();
let anchor = cx.anchor.clone();
let root = taffy
.new_leaf_with_context(
taffy::Style::default(),
Node::new(name, path, cx, NodeKind::Root),
)
.expect("invariant: adding a leaf to taffy never fails");
if let Some(anchor) = anchor {
anchor.set(this.clone(), root);
}
Tree {
this,
taffy,
root,
output,
surface: Surface::default(),
size: Size::default(),
needs_layout: true,
needs_place: false,
damage: Vec::new(),
pointer: Pointer::default(),
keyboard: Keyboard::default(),
popups: Vec::new(),
popup: None,
floating: None,
captures: Vec::new(),
}
}
pub(crate) fn node(&self, id: NodeId) -> Option<&Node> {
self.taffy.get_node_context(id)
}
pub(crate) fn node_mut(&mut self, id: NodeId) -> Option<&mut Node> {
self.taffy.get_node_context_mut(id)
}
pub(crate) fn mount_panel(&mut self, panel: PanelWindow, child: Option<Element>) {
let root = self.root;
let PanelWindow {
output: _,
anchors,
width,
height,
exclusive_zone,
layer,
margins,
keyboard_focus,
namespace,
hints,
on_key,
style,
child: _,
} = panel;
self.keyboard.handler = on_key;
self.bind_opt(root, anchors, "anchors", |tree, _, value| {
tree.surface.set(|s| &mut s.anchors, value);
});
self.bind_opt(root, width, "width", |tree, _, value| {
tree.surface.set(|s| &mut s.width, value);
});
self.bind_opt(root, height, "height", |tree, _, value| {
tree.surface.set(|s| &mut s.height, value);
});
self.bind_opt(root, exclusive_zone, "exclusive_zone", |tree, _, value| {
tree.surface.set(|s| &mut s.exclusive_zone, Some(value));
});
self.bind_opt(root, layer, "layer", |tree, _, value| {
tree.surface.set(|s| &mut s.layer, value);
});
self.bind_opt(root, margins, "margins", |tree, _, value| {
tree.surface.set(|s| &mut s.margins, value);
});
self.bind_opt(root, keyboard_focus, "keyboard_focus", |tree, _, value| {
tree.surface.set(|s| &mut s.keyboard_focus, value);
});
self.bind_opt(root, namespace, "namespace", |tree, _, value| {
tree.surface.set(|s| &mut s.namespace, value);
});
self.bind_compositor_hints(hints);
self.mount_root(style, child);
self.surface.changed = true;
}
pub(crate) fn bind_compositor_hints(&mut self, hints: Hints) {
let Hints {
mask,
compositor_opacity,
visible_mask,
blur,
inhibit_idle,
inhibit_shortcuts,
shortcuts_inhibited,
focus_grab,
} = hints;
self.bind_opt(self.root, mask, "mask", |tree, _, value| {
tree.surface.set(|s| &mut s.mask, value);
});
self.bind_opt(
self.root,
compositor_opacity,
"compositor_opacity",
|tree, id, asked| {
let opacity = if asked.is_nan() {
1.0
} else {
asked.clamp(0.0, 1.0)
};
if opacity != asked && !std::mem::replace(&mut tree.surface.opacity_logged, true) {
let path = tree.node(id).map_or("", |node| &node.path);
tracing::warn!(
"{path}.compositor_opacity of {asked} is outside 0 to 1, so {opacity} is used"
);
}
tree.surface.set(|s| &mut s.compositor_opacity, opacity);
},
);
self.bind_opt(self.root, visible_mask, "visible_mask", |tree, _, value| {
tree.surface.set(|s| &mut s.visible_mask, value);
});
self.bind_opt(self.root, blur, "blur", |tree, _, value| {
tree.surface.set(|s| &mut s.blur, value);
});
self.bind_opt(self.root, inhibit_idle, "inhibit_idle", |tree, _, value| {
tree.surface.set(|s| &mut s.inhibit_idle, value);
});
self.bind_opt(
self.root,
inhibit_shortcuts,
"inhibit_shortcuts",
|tree, _, value| tree.surface.set(|s| &mut s.inhibit_shortcuts, value),
);
self.surface.shortcuts_inhibited = shortcuts_inhibited;
self.surface.focus_grab = focus_grab;
}
pub(crate) fn mount_root(&mut self, style: Option<Prop<Style>>, child: Option<Element>) {
let root = self.root;
self.bind_opt(root, style, "style", set_style);
if let Some(child) = child {
let cx = self.node(root).map(|node| node.cx.clone());
let cx = cx.expect("invariant: the root node exists while the tree does");
let child = self.mount(child, &cx, root);
self.set_group(root, 0, vec![child]);
}
}
pub(crate) fn mount(&mut self, element: Element, parent_cx: &Cx, parent: NodeId) -> NodeId {
let Element {
name,
style,
mut kind,
children,
} = element;
let index = self.next_index(parent);
let parent_path = self.node(parent).map_or("", |node| &node.path);
let path: Rc<str> = match index {
Some(index) => format!("{parent_path}/{name}[{index}]"),
None => format!("{parent_path}/{name}"),
}
.into();
let anchor = Rc::new(Anchor::default());
let cx = parent_cx
.child()
.named(path.to_string())
.at(Rc::clone(&anchor));
let node_kind = match &mut kind {
ElementKind::Flex(props) => NodeKind::Flex(Flex {
column: props.column,
grow: props.grow,
gap: 0,
padding_vertical: None,
padding_horizontal: None,
align: Align::default(),
justify: Justify::default(),
}),
ElementKind::Stack => NodeKind::Stack,
ElementKind::Spacer => NodeKind::Spacer,
ElementKind::Text(_) => NodeKind::Text(TextNode::new()),
ElementKind::Input(props) => NodeKind::Input(Box::new(InputNode::new(
props.text,
cx.signal(props.on_accept.take()),
))),
ElementKind::Scroll(props) => NodeKind::Scroll(ScrollNode {
signal: props.offset.unwrap_or_else(|| cx.signal(0.0)),
offset: 0.0,
shown: 0.0,
most: 0.0,
}),
ElementKind::Image(_) => NodeKind::Image(ImageNode {
source: None,
width: crate::Image::DEFAULT_SIZE,
height: crate::Image::DEFAULT_SIZE,
fit: Fit::default(),
}),
ElementKind::Capture(_) => NodeKind::Capture(CaptureNode::new()),
ElementKind::Icon(_) => NodeKind::Icon(IconNode {
source: None,
size: crate::Icon::DEFAULT_SIZE,
}),
ElementKind::Button(props) => NodeKind::Button(Box::new(ButtonNode {
hover_style: Style::new(),
pressed_style: Style::new(),
focus_style: Style::new(),
handlers: cx.signal(std::mem::take(&mut props.handlers)),
hover_signal: props.hovered,
hovered: false,
pressed: false,
focused: false,
})),
};
let id = self
.taffy
.new_leaf_with_context(
taffy::Style::default(),
Node::new(name, Rc::clone(&path), cx.clone(), node_kind),
)
.expect("invariant: adding a leaf to taffy never fails");
anchor.set(self.this.clone(), id);
self.bind_opt(id, style, "style", set_style);
self.bind_kind(id, kind);
self.mount_children(id, children, &cx, &path);
id
}
fn bind_kind(&mut self, id: NodeId, kind: ElementKind) {
match kind {
ElementKind::Flex(props) => {
self.bind_opt(id, props.gap, "gap", |tree, id, value| {
tree.update_flex(id, |flex| replace(&mut flex.gap, value))
});
self.bind_opt(id, props.padding_vertical, "padding", |tree, id, value| {
tree.update_flex(id, |flex| replace(&mut flex.padding_vertical, Some(value)))
});
self.bind_opt(
id,
props.padding_horizontal,
"padding",
|tree, id, value| {
tree.update_flex(id, |flex| {
replace(&mut flex.padding_horizontal, Some(value))
})
},
);
self.bind_opt(id, props.align, "align", |tree, id, value| {
tree.update_flex(id, |flex| replace(&mut flex.align, value))
});
self.bind_opt(id, props.justify, "justify", |tree, id, value| {
tree.update_flex(id, |flex| replace(&mut flex.justify, value))
});
}
ElementKind::Text(props) => {
self.bind(id, props.text, "text", |tree, id, value| {
tree.update_text(id, |text| text.set_content(value))
});
self.bind_opt(id, props.ellipsis, "ellipsis", |tree, id, value| {
tree.update_text(id, |text| text.set_ellipsis(value))
});
self.bind_opt(id, props.max_lines, "max_lines", |tree, id, value| {
tree.update_text(id, |text| text.set_max_lines(value))
});
}
ElementKind::Input(props) => {
let signal = props.text;
self.bind(
id,
(move || signal.get()).into_prop(),
"text",
|tree, id, value| {
tree.update_input(id, |input| {
input.edit.set_text(value);
input.refresh();
});
},
);
self.bind_opt(id, props.placeholder, "placeholder", |tree, id, value| {
tree.update_input(id, |input| {
input.placeholder = value;
input.refresh();
});
});
self.bind_opt(id, props.password, "password", |tree, id, value| {
tree.update_input(id, |input| {
input.password = value;
input.refresh();
});
});
self.bind_opt(id, props.focus, "focus", |tree, id, value| {
if value {
tree.focus(Some(id));
}
});
}
ElementKind::Scroll(_) => {
if let Some(NodeKind::Scroll(scroll)) = self.node(id).map(|node| &node.kind) {
let signal = scroll.signal;
self.bind(
id,
(move || signal.get()).into_prop(),
"offset",
|tree, id, value| {
tree.update(id, |node| match &mut node.kind {
NodeKind::Scroll(scroll) => replace(&mut scroll.offset, value),
_ => Change::None,
});
},
);
}
}
ElementKind::Image(props) => {
self.bind(id, props.source, "image", |tree, id, value| {
tree.update_image(id, |image| match replace(&mut image.source, Some(value)) {
Change::None => Change::None,
_ => Change::Paint,
})
});
self.bind_opt(id, props.width, "width", |tree, id, value| {
tree.update_image(id, |image| replace(&mut image.width, value))
});
self.bind_opt(id, props.height, "height", |tree, id, value| {
tree.update_image(id, |image| replace(&mut image.height, value))
});
self.bind_opt(id, props.fit, "fit", |tree, id, value| {
tree.update_image(id, |image| match replace(&mut image.fit, value) {
Change::None => Change::None,
_ => Change::Paint,
})
});
}
ElementKind::Capture(props) => self.bind_capture(id, props),
ElementKind::Icon(props) => {
self.bind(id, props.source, "icon", |tree, id, value| {
tree.update_icon(id, |icon| match replace(&mut icon.source, Some(value)) {
Change::None => Change::None,
_ => Change::Paint,
})
});
self.bind_opt(id, props.size, "size", |tree, id, value| {
tree.update_icon(id, |icon| replace(&mut icon.size, value))
});
}
ElementKind::Button(props) => {
self.bind_opt(id, props.hover_style, "hover_style", |tree, id, value| {
tree.update_button_style(id, |button| button.hover_style = value);
});
self.bind_opt(
id,
props.pressed_style,
"pressed_style",
|tree, id, value| {
tree.update_button_style(id, |button| button.pressed_style = value);
},
);
self.bind_opt(id, props.focus_style, "focus_style", |tree, id, value| {
tree.update_button_style(id, |button| button.focus_style = value);
});
}
ElementKind::Stack | ElementKind::Spacer => {}
}
}
fn mount_children(&mut self, id: NodeId, children: Vec<ElementChild>, cx: &Cx, path: &Rc<str>) {
for child in children {
let Some(node) = self.node(id) else {
return;
};
let group = node.groups.len();
match child {
ElementChild::One(element) => {
let child = self.mount(*element, cx, id);
self.set_group(id, group, vec![child]);
}
ElementChild::Keyed(mount) => {
self.set_group(id, group, Vec::new());
mount(KeyedSlot {
tree: self.this.clone(),
container: id,
group,
cx: cx.clone(),
path: Rc::clone(path),
});
}
}
}
}
fn next_index(&mut self, parent: NodeId) -> Option<usize> {
let node = self.node_mut(parent)?;
if !matches!(node.kind, NodeKind::Flex(_) | NodeKind::Stack) {
return None;
}
let index = node.next_child;
node.next_child += 1;
Some(index)
}
pub(crate) fn set_group(&mut self, parent: NodeId, group: usize, nodes: Vec<NodeId>) {
let Some(node) = self.node_mut(parent) else {
return;
};
if group == node.groups.len() {
node.groups.push(nodes);
} else if let Some(slot) = node.groups.get_mut(group) {
*slot = nodes;
}
let children: Vec<NodeId> = node.groups.iter().flatten().copied().collect();
self
.taffy
.set_children(parent, &children)
.expect("invariant: every node in a child group is in the tree");
self.needs_layout = true;
}
pub(crate) fn unmount(&mut self, id: NodeId) {
if let Some(node) = self.taffy.get_node_context(id)
&& node.rect.is_some()
{
add_damage(&mut self.damage, node.window_bounds);
}
let mut removed = Vec::new();
let mut pending = vec![id];
while let Some(next) = pending.pop() {
removed.push(next);
if let Ok(children) = self.taffy.children(next) {
pending.extend(children);
}
}
self.pointer.forget(&removed);
self.captures.retain(|node| !removed.contains(node));
self.keyboard.forget(&removed);
for node in removed.into_iter().rev() {
let _ = self.taffy.remove(node);
}
self.needs_layout = true;
}
pub(crate) fn bind<T: 'static>(
&mut self,
id: NodeId,
prop: Prop<T>,
name: &str,
apply: fn(&mut Tree, NodeId, T),
) {
match prop.0 {
PropKind::Value(value) => apply(self, id, value),
PropKind::Binding(mut compute) => {
let Some(node) = self.node(id) else {
return;
};
let label = format!("{}.{name}", node.path);
let tree = self.this.clone();
node
.cx
.effect(move || {
let value = compute();
if let Some(tree) = tree.upgrade() {
apply(&mut borrow(&tree), id, value);
}
})
.named(label);
}
}
}
pub(crate) fn bind_opt<T: 'static>(
&mut self,
id: NodeId,
prop: Option<Prop<T>>,
name: &str,
apply: fn(&mut Tree, NodeId, T),
) {
if let Some(prop) = prop {
self.bind(id, prop, name, apply);
}
}
pub(crate) fn update(&mut self, id: NodeId, f: impl FnOnce(&mut Node) -> Change) -> Change {
let Some(node) = self.node_mut(id) else {
return Change::None;
};
let change = f(node);
self.invalidate(id, change);
change
}
pub(crate) fn update_style(&mut self, id: NodeId, f: impl FnOnce(&mut Node)) -> Change {
let mut fade = None;
let change = self.update(id, |node| {
let shown = node.shown_style();
let before = node.effective_style();
f(node);
let after = node.effective_style();
if before == after {
return Change::None;
}
node.fade = None;
if !before.same_layout(&after) {
return Change::Layout;
}
if let Some(transition) = after
.transition
.filter(|transition| !transition.is_instant())
&& node.rect.is_some()
{
node.fades += 1;
node.fade = Some(Fade {
from: shown,
t: 0.0,
generation: node.fades,
});
fade = Some((node.fades, transition));
}
if before.shadow != after.shadow {
Change::Layout
} else if !shown.same_transform(&after) {
Change::Place
} else {
Change::Paint
}
});
if let Some((generation, transition)) = fade {
self.start_fade(id, generation, transition);
}
change
}
fn start_fade(&self, id: NodeId, generation: u64, transition: Transition) {
let tree = self.this.clone();
let mut started = animation::start_time();
let label = self.node(id).map_or_else(
|| Rc::from("a fade"),
|node| format!("{}.transition", node.path).into(),
);
animation::start(
label,
Box::new(move |now| {
let Some(tree) = tree.upgrade() else {
return false;
};
let Ok(mut tree) = tree.try_borrow_mut() else {
return true;
};
let begun = *started.get_or_insert(now);
let waiting = transition.waiting(begun, now);
let (t, ended) = transition.progress(begun, now);
let mut mine = false;
tree.update(id, |node| {
if node
.fade
.as_ref()
.is_none_or(|fade| fade.generation != generation)
{
return Change::None;
}
mine = true;
let moves = node
.fade
.as_ref()
.is_some_and(|fade| !fade.from.same_transform(&node.effective_style()));
if ended {
node.fade = None;
} else if waiting {
return Change::None;
} else if let Some(fade) = &mut node.fade {
fade.t = t;
}
if moves { Change::Place } else { Change::Paint }
});
mine && !ended
}),
);
}
fn update_flex(&mut self, id: NodeId, f: impl FnOnce(&mut Flex) -> Change) {
self.update(id, |node| match &mut node.kind {
NodeKind::Flex(flex) => f(flex),
_ => Change::None,
});
}
fn update_text(&mut self, id: NodeId, f: impl FnOnce(&mut TextNode) -> bool) {
self.update(id, |node| match &mut node.kind {
NodeKind::Text(text) => {
if f(text) {
Change::Layout
} else {
Change::None
}
}
_ => Change::None,
});
}
pub(crate) fn update_input(&mut self, id: NodeId, f: impl FnOnce(&mut InputNode)) {
self.update(id, |node| match &mut node.kind {
NodeKind::Input(input) => {
f(input);
Change::Layout
}
_ => Change::None,
});
}
fn update_image(&mut self, id: NodeId, f: impl FnOnce(&mut ImageNode) -> Change) {
self.update(id, |node| match &mut node.kind {
NodeKind::Image(image) => f(image),
_ => Change::None,
});
}
fn update_icon(&mut self, id: NodeId, f: impl FnOnce(&mut IconNode) -> Change) {
self.update(id, |node| match &mut node.kind {
NodeKind::Icon(icon) => f(icon),
_ => Change::None,
});
}
pub(crate) fn update_button_style(
&mut self,
id: NodeId,
f: impl FnOnce(&mut ButtonNode),
) -> Change {
self.update_style(id, |node| {
if let NodeKind::Button(button) = &mut node.kind {
f(button);
}
})
}
pub(crate) fn invalidate(&mut self, id: NodeId, change: Change) {
match change {
Change::None => {}
Change::Paint | Change::Place => {
if let Some(node) = self.taffy.get_node_context_mut(id)
&& node.rect.is_some()
{
add_damage(&mut self.damage, node.window_bounds);
node.drawn = node.window_painted;
}
self.needs_place |= change == Change::Place;
}
Change::Layout => {
if let Some(node) = self.node_mut(id) {
node.changed = true;
}
self.needs_layout = true;
}
}
}
}
impl Surface {
fn set<T: PartialEq>(&mut self, field: impl FnOnce(&mut Surface) -> &mut T, value: T) {
let slot = field(self);
if *slot != value {
*slot = value;
self.changed = true;
}
}
}
fn set_style(tree: &mut Tree, id: NodeId, style: Style) {
tree.update_style(id, |node| node.style = style);
}
pub(crate) fn icon_rect(content: Rect, size: u32) -> Rect {
centered(content, size, size)
}
pub(crate) fn centered(content: Rect, width: u32, height: u32) -> Rect {
let (width, height) = (width as f32, height as f32);
Rect::new(
content.x + ((content.width - width) / 2.0).round(),
content.y + ((content.height - height) / 2.0).round(),
width,
height,
)
}
pub(crate) fn add_damage(damage: &mut Vec<Rect>, rect: Rect) {
if !rect.is_empty() && !damage.contains(&rect) {
damage.push(rect);
}
}
pub(crate) fn replace<T: PartialEq>(slot: &mut T, value: T) -> Change {
if *slot == value {
Change::None
} else {
*slot = value;
Change::Layout
}
}