use std::any::Any;
use std::cell::{Cell, RefCell};
use std::collections::{HashMap, VecDeque};
use std::rc::Rc;
use day_reactive::Scope;
use day_spec::*;
use slotmap::{Key, KeyData, SlotMap, new_key_type};
use crate::layout::{Alignment, CrossAlign, Layout, PassThrough};
new_key_type! {
pub struct RNode;
}
pub fn rnode_to_id(n: RNode) -> NodeId {
NodeId(n.data().as_ffi())
}
pub fn id_to_rnode(id: NodeId) -> RNode {
RNode::from(KeyData::from_ffi(id.0))
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Flex {
pub grow_w: bool,
pub grow_h: bool,
pub yields_h: bool,
pub is_spacer: bool,
pub is_group: bool,
pub grid: GridFacts,
pub whole_row: bool,
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct GridFacts {
pub is_row: bool,
pub row_valign: Option<CrossAlign>,
pub col_span: u16,
pub align: Option<Alignment>,
}
#[derive(Clone, Debug, Default)]
pub struct NodeProbe {
pub text: String,
pub value: f64,
pub flag: bool,
pub selected: i64,
pub options: Vec<String>,
pub enabled: bool,
pub focused: bool,
}
pub struct NodeData<H> {
pub kind: PieceKind,
pub handle: Option<H>,
pub parent: RNode,
pub children: Vec<RNode>,
pub layout: Rc<dyn Layout>,
pub flex: Flex,
pub scope: Scope,
pub id: Option<String>,
pub parked_id: Option<String>,
pub a11y: day_spec::A11yProps,
pub cache: Vec<((u64, u64), Size)>,
pub baseline_cache: Option<((u64, u64), Option<f64>)>,
pub probe: NodeProbe,
pub needs_measure: bool,
pub needs_layout: bool,
pub last_native_frame: Option<Rect>,
pub is_boundary: bool,
pub scroll_content: Option<Size>,
pub implicit_anim: Option<day_spec::AnimSpec>,
pub tweaked: bool,
}
pub type EventHandler = Rc<dyn Fn(&Event)>;
pub type EventObserver = dyn Fn(NodeId, &Event);
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum WindowRootReply {
Open(RNode),
Pending(RNode),
Unsupported,
}
struct WindowEntry {
root: RNode,
size: Size,
}
#[derive(Clone)]
enum ContextMenuModel {
Items(Vec<day_spec::MenuItem>),
Provider(day_spec::ContextMenuFn),
}
pub struct Tree<B: Toolkit> {
mounted_id_families: std::collections::BTreeSet<String>,
pub toolkit: B,
nodes: SlotMap<RNode, NodeData<B::Handle>>,
windows: Vec<WindowEntry>,
layout_dirty: bool,
handlers: HashMap<RNode, Vec<EventHandler>>,
gestures: HashMap<RNode, Vec<day_spec::GestureKind>>,
element_outer: HashMap<RNode, RNode>,
release_queue: Vec<(day_spec::PieceKind, B::Handle)>,
lists: HashMap<RNode, crate::list::ListState>,
trees: HashMap<RNode, crate::tree_driver::TreeState>,
context_menus: HashMap<RNode, ContextMenuModel>,
implicit_anim_count: usize,
}
impl<B: Toolkit> Tree<B> {
fn clear_subtree_ids(&mut self, anchor: RNode) {
let mut stack = vec![anchor];
while let Some(n) = stack.pop() {
if let Some(d) = self.nodes.get_mut(n) {
if let Some(id) = d.id.take() {
d.parked_id = Some(id);
}
stack.extend(d.children.iter().copied());
}
}
}
fn restore_subtree_ids(&mut self, anchor: RNode) {
let mut stack = vec![anchor];
while let Some(n) = stack.pop() {
if let Some(d) = self.nodes.get_mut(n) {
if let Some(id) = d.parked_id.take()
&& d.id.is_none()
{
d.id = Some(id);
}
stack.extend(d.children.iter().copied());
}
}
}
pub fn new(toolkit: B, root_handle: B::Handle, window_size: Size) -> Self {
let mut nodes = SlotMap::with_key();
let root = nodes.insert(NodeData {
kind: kinds::CONTAINER,
handle: Some(root_handle),
parent: RNode::null(),
children: Vec::new(),
layout: Rc::new(PassThrough),
flex: Flex::default(),
scope: Scope::root(),
id: None,
parked_id: None,
a11y: Default::default(),
cache: Vec::new(),
baseline_cache: None,
probe: NodeProbe::default(),
needs_measure: true,
needs_layout: true,
last_native_frame: None,
scroll_content: None,
implicit_anim: None,
tweaked: false,
is_boundary: true,
});
Tree {
mounted_id_families: Default::default(),
toolkit,
nodes,
windows: vec![WindowEntry {
root,
size: window_size,
}],
layout_dirty: true,
handlers: HashMap::new(),
gestures: HashMap::new(),
element_outer: HashMap::new(),
release_queue: Vec::new(),
lists: HashMap::new(),
trees: HashMap::new(),
context_menus: HashMap::new(),
implicit_anim_count: 0,
}
}
pub(crate) fn create_cell_anchor(
&mut self,
handle: B::Handle,
scope: Scope,
layout: Rc<dyn Layout>,
) -> RNode {
self.nodes.insert(NodeData {
kind: kinds::LIST_CELL,
handle: Some(handle),
parent: RNode::null(),
children: Vec::new(),
layout,
flex: Flex::default(),
scope,
id: None,
parked_id: None,
a11y: Default::default(),
cache: Vec::new(),
baseline_cache: None,
probe: NodeProbe::default(),
needs_measure: true,
needs_layout: true,
last_native_frame: None,
scroll_content: None,
implicit_anim: None,
tweaked: false,
is_boundary: true,
})
}
pub fn root(&self) -> RNode {
self.windows[0].root
}
pub(crate) fn node(&self, n: RNode) -> Option<&NodeData<B::Handle>> {
self.nodes.get(n)
}
pub(crate) fn node_mut(&mut self, n: RNode) -> Option<&mut NodeData<B::Handle>> {
self.nodes.get_mut(n)
}
pub(crate) fn resolve_anim(&self, node: RNode) -> Option<day_spec::AnimSpec> {
if crate::reduce_motion_now() {
return None;
}
if let Some(a) = crate::anim::current_anim() {
return Some(a);
}
if self.implicit_anim_count == 0 {
return None;
}
let mut cur = node;
loop {
let n = self.nodes.get(cur)?;
if n.implicit_anim.is_some() {
return n.implicit_anim;
}
if n.parent == cur {
return None;
}
cur = n.parent;
}
}
fn native_ancestor(&self, mut n: RNode) -> RNode {
loop {
let Some(node) = self.nodes.get(n) else {
return self.windows[0].root;
};
if node.handle.is_some() {
return n;
}
n = node.parent;
}
}
fn native_descendants(&self, container: RNode, out: &mut Vec<RNode>) {
let Some(node) = self.nodes.get(container) else {
return;
};
for &c in &node.children {
match self.nodes.get(c) {
Some(cd) if cd.handle.is_some() => out.push(c),
Some(_) => self.native_descendants(c, out),
None => {}
}
}
}
fn native_index_for(&self, ancestor: RNode, target: RNode) -> usize {
fn walk<B: Toolkit>(tree: &Tree<B>, n: RNode, target: RNode, count: &mut usize) -> bool {
if n == target {
return true;
}
let Some(node) = tree.nodes.get(n) else {
return false;
};
if node.handle.is_some() && n != target {
*count += 1;
return false;
}
for &c in &node.children {
if walk(tree, c, target, count) {
return true;
}
}
false
}
let mut count = 0;
let Some(anc) = self.nodes.get(ancestor) else {
return 0;
};
for &c in &anc.children {
if c == target || self.subtree_contains(c, target) {
let mut cnt = count;
walk(self, c, target, &mut cnt);
return cnt;
}
let mut roots = Vec::new();
match self.nodes.get(c) {
Some(cd) if cd.handle.is_some() => count += 1,
Some(_) => {
self.native_descendants(c, &mut roots);
count += roots.len();
}
None => {}
}
}
count
}
fn subtree_contains(&self, root: RNode, target: RNode) -> bool {
if root == target {
return true;
}
let Some(node) = self.nodes.get(root) else {
return false;
};
node.children
.iter()
.any(|&c| self.subtree_contains(c, target))
}
fn attach_impl(&mut self, parent: RNode, child: RNode, index: usize) {
{
let p = self
.nodes
.get_mut(parent)
.expect("attach to missing parent");
let idx = index.min(p.children.len());
p.children.insert(idx, child);
}
self.nodes
.get_mut(child)
.expect("attach missing child")
.parent = parent;
let ancestor = self.native_ancestor(parent);
let anc_handle = self.nodes[ancestor]
.handle
.clone()
.expect("native ancestor");
let mut roots = Vec::new();
match self.nodes.get(child) {
Some(cd) if cd.handle.is_some() => roots.push(child),
Some(_) => self.native_descendants(child, &mut roots),
None => {}
}
for r in roots {
let idx = self.native_index_for(ancestor, r);
let h = self.nodes[r].handle.clone().unwrap();
self.toolkit.insert(&anc_handle, &h, idx);
}
self.mark_needs_measure_impl(parent);
}
fn remove_subtree_impl(&mut self, node: RNode) {
let parent = self
.nodes
.get(node)
.map(|n| n.parent)
.unwrap_or(RNode::null());
if let Some(p) = self.nodes.get_mut(parent) {
p.children.retain(|&c| c != node);
}
let anc_handle = if parent.is_null() {
None
} else {
let ancestor = self.native_ancestor(parent);
self.nodes.get(ancestor).and_then(|n| n.handle.clone())
};
let mut roots = Vec::new();
match self.nodes.get(node) {
Some(nd) if nd.handle.is_some() => roots.push(node),
Some(_) => self.native_descendants(node, &mut roots),
None => {}
}
if let Some(anc_handle) = anc_handle {
for r in &roots {
let h = self.nodes[*r].handle.clone().unwrap();
self.toolkit.remove(&anc_handle, &h);
}
}
self.collect_and_release(node);
if parent.is_null() {
self.layout_dirty = true;
} else {
self.mark_needs_measure_impl(parent);
}
}
fn collect_and_release(&mut self, start: RNode) {
let mut stack = vec![start];
while let Some(n) = stack.pop() {
self.context_menus.remove(&n);
if let Some(list) = self.lists.remove(&n) {
for (_, cell) in list.cells {
cell.scope.dispose();
stack.push(cell.anchor);
}
}
if let Some(tree) = self.trees.remove(&n) {
for (_, cell) in tree.cells {
cell.scope.dispose();
stack.push(cell.anchor);
}
}
let Some(data) = self.nodes.remove(n) else {
continue;
};
self.handlers.remove(&n);
self.gestures.remove(&n);
self.element_outer.remove(&n);
if data.implicit_anim.is_some() {
self.implicit_anim_count = self.implicit_anim_count.saturating_sub(1);
}
if let Some(h) = data.handle {
if data.kind != kinds::LIST_CELL {
self.release_queue.push((data.kind, h));
}
}
stack.extend(data.children);
}
}
fn mark_needs_measure_impl(&mut self, node: RNode) {
let mut cur = node;
let mut measure = true;
while let Some(n) = self.nodes.get_mut(cur) {
n.needs_layout = true;
if measure {
n.needs_measure = true;
n.cache.clear();
}
if n.is_boundary {
measure = false;
}
if n.parent.is_null() {
break;
}
cur = n.parent;
}
self.layout_dirty = true;
}
fn invalidate_subtree(&mut self, node: RNode) {
let mut stack = vec![node];
while let Some(cur) = stack.pop() {
if let Some(n) = self.nodes.get_mut(cur) {
n.needs_measure = true;
n.cache.clear();
stack.extend(n.children.iter().copied());
}
}
}
fn layout_list_cell_at(&mut self, node: RNode, key: usize, width: f64) {
let Some(state) = self.lists.get(&node) else {
return;
};
let (anchor, stale, header) = match state.cells.get(&key) {
Some(b) if b.active => (b.anchor, b.laid_width != Some(width), b.header),
_ => return,
};
let row_height = if header {
day_spec::props::RowHeight::Automatic
} else {
state.driver.row_height
};
if stale {
self.invalidate_subtree(anchor);
if let Some(b) = self
.lists
.get_mut(&node)
.and_then(|s| s.cells.get_mut(&key))
{
b.laid_width = Some(width);
}
}
let height = match row_height {
day_spec::props::RowHeight::Uniform(h) => h,
day_spec::props::RowHeight::Automatic => {
crate::layout::measure_node(self, anchor, Proposal::new(Some(width), None)).height
}
};
let previous = self
.lists
.get_mut(&node)
.and_then(|s| s.cells.get_mut(&key))
.and_then(|b| b.laid_height.replace(height));
if previous.is_some_and(|p| (p - height).abs() > 0.25)
&& let Some(handle) = self.nodes.get(node).and_then(|n| n.handle.clone())
{
self.toolkit.update(
&handle,
kinds::LIST,
&day_spec::props::ListPatch::RowSizeInvalidated { cell: key, height } as &dyn Any,
None,
);
}
crate::layout::place_node(
self,
anchor,
Rect::new(0.0, 0.0, width, height),
Point::ZERO,
true,
);
self.nodes[anchor].needs_measure = false;
}
fn layout_tree_cell_at(&mut self, node: RNode, key: usize, width: f64) {
let Some(state) = self.trees.get(&node) else {
return;
};
let (anchor, stale) = match state.cells.get(&key) {
Some(b) => (b.anchor, b.laid_width != Some(width)),
None => return,
};
let row_height = state.driver.row_height;
let native_height = state.cells.get(&key).and_then(|b| b.native_height);
if stale {
self.invalidate_subtree(anchor);
if let Some(b) = self
.trees
.get_mut(&node)
.and_then(|s| s.cells.get_mut(&key))
{
b.laid_width = Some(width);
}
}
let height = native_height.unwrap_or_else(|| match row_height {
day_spec::props::RowHeight::Uniform(h) => h,
day_spec::props::RowHeight::Automatic => {
crate::layout::measure_node(self, anchor, Proposal::new(Some(width), None)).height
}
});
if let Some(b) = self
.trees
.get_mut(&node)
.and_then(|s| s.cells.get_mut(&key))
{
b.laid_height = Some(height);
}
crate::layout::place_node(
self,
anchor,
Rect::new(0.0, 0.0, width, height),
Point::ZERO,
true,
);
self.nodes[anchor].needs_measure = false;
}
fn layout_now(&mut self) {
for i in 0..self.windows.len() {
let (root, size) = (self.windows[i].root, self.windows[i].size);
let p = Proposal::exact(size);
crate::layout::measure_node(self, root, p);
crate::layout::place_node(self, root, Rect::from_size(size), Point::ZERO, true);
}
let dirty_cells: Vec<(RNode, usize)> = self
.lists
.iter()
.flat_map(|(list, state)| {
state.cells.iter().filter_map(|(key, cell)| {
if !cell.active {
return None;
}
self.nodes
.get(cell.anchor)
.filter(|n| n.needs_layout)
.map(|_| (*list, *key))
})
})
.collect();
let dirty_tree_cells: Vec<(RNode, usize)> = self
.trees
.iter()
.flat_map(|(tree, state)| {
state.cells.iter().filter_map(|(key, cell)| {
self.nodes
.get(cell.anchor)
.filter(|n| n.needs_layout)
.map(|_| (*tree, *key))
})
})
.collect();
for (tree, key) in dirty_tree_cells {
self.tree_layout_cell(tree, key);
}
for (list, key) in dirty_cells {
self.list_layout_cell(list, key);
}
let queue = std::mem::take(&mut self.release_queue);
for (kind, h) in queue {
self.toolkit.release_piece(kind, &h);
self.toolkit.release(h);
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ScrollTarget {
Top,
Bottom,
Leading,
Trailing,
Offset(Point),
Id(String),
}
pub trait TreeOps {
#[allow(clippy::too_many_arguments)]
fn create_node(
&mut self,
kind: PieceKind,
props: &dyn Any,
layout: Rc<dyn Layout>,
flex: Flex,
native: bool,
is_boundary: bool,
scope: Scope,
) -> RNode;
fn attach(&mut self, parent: RNode, child: RNode);
fn reparent(&mut self, child: RNode, parent: RNode) -> bool;
fn native_window_tabs(&self) -> bool;
fn native_document_tabs(&self) -> bool;
fn group_windows(&mut self, roots: &[RNode]);
fn window_tab_order(&self, root: RNode) -> Vec<day_spec::NodeId>;
fn attach_at(&mut self, parent: RNode, child: RNode, index: usize);
fn reorder_children(&mut self, parent: RNode, order: Vec<RNode>);
fn remove_subtree(&mut self, node: RNode);
fn node_exists(&self, node: RNode) -> bool;
fn dark_mode(&mut self) -> bool;
fn reduce_motion(&mut self) -> bool;
fn set_appearance(&mut self, dark: Option<bool>);
fn set_app_badge(&mut self, badge: &day_spec::AppBadge);
fn on_event(&mut self, node: RNode, h: EventHandler);
fn handlers_for(&self, node: RNode) -> Vec<EventHandler>;
fn native_view_of(&self, node: RNode) -> Option<RNode>;
fn wrappers_above(&self, node: RNode) -> Vec<RNode>;
fn gesture_target(&self, node: RNode, kind: day_spec::GestureKind) -> Option<RNode>;
fn set_element_outer(&mut self, inner: RNode, outer: RNode);
fn set_id(&mut self, node: RNode, id: String);
fn mounted_id_families(&self) -> Vec<String>;
fn set_grid_facts(&mut self, node: RNode, facts: GridFacts);
fn forward_whole_row(&mut self, wrapper: RNode, inner: RNode);
fn set_a11y(&mut self, node: RNode, a11y: A11yProps);
fn announce(&mut self, text: &str, urgent: bool);
fn enable_gesture(&mut self, node: RNode, kind: day_spec::GestureKind);
fn focus_node(&mut self, node: RNode, focused: bool);
fn set_focusable(&mut self, node: RNode, focusable: bool);
fn set_probe_focused(&mut self, node: RNode, focused: bool);
fn set_probe_selected(&mut self, node: RNode, selected: Option<usize>);
fn set_probe_value(&mut self, node: RNode, value: f64, text: String);
fn set_app_menu(&mut self, items: Vec<day_spec::MenuItem>);
fn set_context_menu(&mut self, node: RNode, items: Vec<day_spec::MenuItem>);
fn edit_window_toolbar(&mut self, root: RNode, ops: Vec<day_spec::ToolbarOp>) -> bool;
fn patch_window_toolbar(&mut self, root: RNode, patch: day_spec::ToolbarPatch) -> bool;
fn scroll_to_target(&mut self, node: RNode, target: &ScrollTarget, animated: bool) -> bool;
fn scroll_reveal(&mut self, node: RNode, animated: bool) -> bool;
fn patch(&mut self, node: RNode, patch: Box<dyn Any>, affects_size: bool);
fn replay(&mut self, node: RNode, ops: Vec<DrawOp>);
fn request_frame(&mut self, root: RNode, cb: day_spec::FrameCallback) -> day_spec::CancelFrame;
fn set_implicit_anim(&mut self, node: RNode, anim: Option<day_spec::AnimSpec>);
fn set_node_opacity(&mut self, node: RNode, opacity: f64);
fn set_node_transform(&mut self, node: RNode, t: day_spec::Transform);
fn set_node_selectable(&mut self, node: RNode, selectable: bool);
fn set_node_input_traits(&mut self, node: RNode, traits: &day_spec::InputTraits);
fn set_node_cursor(&mut self, node: RNode, cursor: day_spec::Cursor);
fn set_node_drag_region(&mut self, node: RNode, drag: bool);
fn note_node_tweaked(&mut self, node: RNode) {
let _ = node;
}
fn mark_needs_measure(&mut self, node: RNode);
fn mark_layout_dirty(&mut self);
fn invalidate_all_measurements(&mut self);
fn layout_if_needed(&mut self);
fn set_window_size(&mut self, s: Size);
fn set_route(&mut self, route: &str);
fn child_count(&self, node: RNode) -> usize;
fn first_child(&self, node: RNode) -> Option<RNode>;
fn parent_of(&self, node: RNode) -> Option<RNode>;
fn node_kind(&self, node: RNode) -> Option<PieceKind>;
fn node_id(&self, node: RNode) -> Option<String>;
fn node_handle_any(&self, node: RNode) -> Option<Box<dyn Any>>;
fn node_frame(&self, node: RNode) -> Option<Rect>;
fn node_probe(&self, node: RNode) -> Option<NodeProbe>;
fn node_a11y(&self, node: RNode) -> Option<A11yProps>;
fn read_native(&self, node: RNode) -> Option<day_spec::NativeSnapshot>;
fn a11y_nodes(&self) -> Vec<(String, PieceKind, A11yProps, day_spec::NativeSnapshot)>;
fn find_by_id(&self, id: &str) -> Option<RNode>;
fn toggle_sidebar(&mut self, _host: RNode) -> bool {
false
}
fn native_back(&mut self) -> bool {
false
}
fn prepare_snapshot(
&mut self,
root: Option<RNode>,
revision: u32,
) -> Result<day_spec::capture::Readiness, String>;
fn snapshot(&mut self) -> Result<Vec<u8>, String>;
fn snapshot_origin(&mut self) -> Option<Point>;
fn snapshot_chrome(&mut self) -> Result<Vec<u8>, String>;
fn decode_image(&mut self, req: u64, id: day_spec::BitmapId, bytes: &[u8]);
fn image_info(&mut self, id: day_spec::BitmapId) -> Option<day_spec::BitmapInfo>;
fn image_properties(&mut self, id: day_spec::BitmapId) -> Option<day_spec::ImageProperties>;
fn encode_image(&mut self, req: u64, id: day_spec::BitmapId, spec: &day_spec::EncodeSpec);
fn image_encode_formats(&mut self) -> Vec<day_spec::ImageFormat>;
fn release_image(&mut self, id: day_spec::BitmapId);
fn render_bitmap(
&mut self,
req: u64,
id: day_spec::BitmapId,
ops: &[day_spec::DrawOp],
size: Size,
scale: f64,
);
fn render_pdf(&mut self, req: u64, pages: &[day_spec::PdfPage], info: &day_spec::PdfInfo);
fn window_image_support(&mut self) -> day_spec::Support;
fn font_families(&mut self) -> Vec<day_spec::FontFamilyInfo>;
fn measure_text(
&mut self,
text: &str,
size: f64,
font: &day_spec::CanvasFont,
) -> Option<day_spec::TextMetrics>;
fn ui_idle(&mut self) -> bool;
fn root_node(&self) -> RNode;
fn open_window_root(&mut self, options: &WindowOptions, kind: WindowKind) -> WindowRootReply;
fn adopt_window_root(&mut self, root: RNode, raw: day_spec::RawHandle, size: Size) -> bool;
fn remove_window_root(&mut self, root: RNode);
fn set_root_size(&mut self, root: RNode, s: Size);
fn fit_window(&mut self, root: RNode, size: Size);
fn add_extra_layout_root(&mut self, node: RNode, size: Size);
fn drop_extra_layout_root(&mut self, node: RNode);
fn close_native_window(&mut self, root: RNode);
fn quit_app(&mut self);
fn focus_native_window(&mut self, root: RNode);
fn set_native_window_title(&mut self, root: RNode, title: &str);
fn apply_window(&mut self, root: RNode, change: &day_spec::WindowChange);
fn set_dock_menu(&mut self, items: &[day_spec::MenuItem]);
fn set_status_items(&mut self, items: &[day_spec::StatusItemSpec]);
fn set_app_progress(&mut self, progress: day_spec::AppProgress);
fn set_dock_visible(&mut self, visible: bool);
fn set_keep_running(&mut self, keep: bool);
fn native_window_frame(&self, root: RNode) -> Option<day_spec::Rect>;
fn monitors(&self) -> Vec<day_spec::Monitor>;
fn set_launcher_shortcuts(&mut self, shortcuts: &[day_spec::LauncherShortcut]);
fn snapshot_of(&mut self, root: RNode) -> Result<Vec<u8>, String>;
fn capability(&self, cap: Cap) -> Support;
fn attach_refresh(&mut self, node: RNode, host: NodeId);
fn set_presented(&mut self, node: RNode, visible: bool);
fn supports_lifecycle(&self, phase: day_spec::Lifecycle) -> bool;
fn present(&mut self, req: u64, spec: &present::PresentSpec);
fn dismiss(&mut self, req: u64);
fn open_url(&mut self, url: &str);
fn share_support(&self) -> Support;
fn share_url(&mut self, url: &str, title: &str) -> bool;
fn application_handlers(
&mut self,
query: &day_spec::applications::HandlerQuery,
) -> Result<day_spec::applications::ApplicationHandlers, day_spec::applications::ApplicationError>;
fn open_url_with_application(
&mut self,
url: &str,
application: &str,
completion: day_spec::applications::OpenApplicationCompletion,
);
fn defer_system_gestures(&mut self, edges: day_spec::Edges);
fn set_status_bar_hidden(&mut self, hidden: bool);
fn install_list(&mut self, node: RNode, driver: crate::list::ListDriver);
fn list_prepare_cell(
&mut self,
node: RNode,
key: usize,
cell: RawHandle,
header: bool,
) -> crate::list::CellStep;
fn list_store_cell(
&mut self,
node: RNode,
key: usize,
anchor: RNode,
built: crate::list::BuiltRow,
header: bool,
);
fn list_layout_cell(&mut self, node: RNode, key: usize);
fn list_layout_cell_width(&mut self, node: RNode, key: usize, width: f64);
fn list_cell_height(&self, node: RNode, key: usize) -> f64;
fn list_cell_keys(&self, node: RNode) -> Vec<usize>;
fn list_recycle_cell(&mut self, node: RNode, key: usize);
fn list_release_cell(&mut self, node: RNode, key: usize);
fn list_pin_cell(&mut self, node: RNode, key: usize);
fn list_reload(&mut self, node: RNode);
fn list_splice(&mut self, node: RNode, deltas: Vec<day_spec::props::RowDelta>);
fn set_undo_state(&mut self, state: &day_spec::UndoState);
fn set_edit_state(&mut self, state: &day_spec::EditState);
fn modifiers(&mut self) -> day_spec::Modifiers;
fn list_scroll_to_end(&mut self, node: RNode);
fn list_scroll_to_row(&mut self, node: RNode, row: usize);
fn list_set_selected(&mut self, node: RNode, rows: Vec<usize>);
fn list_set_editing(&mut self, node: RNode, on: bool);
fn list_driver(&mut self, node: RNode) -> Option<std::rc::Rc<crate::list::ListDriver>>;
fn install_tree(&mut self, node: RNode, driver: crate::tree_driver::TreeDriver);
fn tree_prepare_cell(
&mut self,
node: RNode,
key: usize,
cell: RawHandle,
) -> crate::tree_driver::TreeCellStep;
fn tree_store_cell(
&mut self,
node: RNode,
key: usize,
anchor: RNode,
built: crate::tree_driver::TreeBuiltRow,
);
fn tree_layout_cell(&mut self, node: RNode, key: usize);
fn tree_layout_cell_width(&mut self, node: RNode, key: usize, width: f64);
fn tree_layout_cell_size(&mut self, node: RNode, key: usize, size: Size);
fn tree_cell_height(&self, node: RNode, key: usize) -> f64;
fn tree_recycle_cell(&mut self, node: RNode, key: usize);
fn tree_reload(&mut self, node: RNode);
fn tree_set_expanded(&mut self, node: RNode, token: u64, expanded: bool);
fn tree_set_selected(&mut self, node: RNode, tokens: Vec<u64>);
fn tree_reveal(&mut self, node: RNode, token: u64);
fn tree_driver(&mut self, node: RNode) -> Option<std::rc::Rc<crate::tree_driver::TreeDriver>>;
fn set_drag_source(&mut self, node: RNode, source: day_spec::transfer::Source);
fn set_drop_target(&mut self, node: RNode, target: day_spec::transfer::Target);
fn set_context_menu_fn(&mut self, node: RNode, f: day_spec::ContextMenuFn);
fn context_menu_of(&mut self, node: RNode) -> Option<day_spec::ContextMenuFn>;
}
impl<B: Toolkit> TreeOps for Tree<B> {
fn set_presented(&mut self, node: RNode, visible: bool) {
if let Some(handle) = self.nodes.get(node).and_then(|n| n.handle.clone()) {
self.toolkit.set_presented(&handle, visible);
}
}
fn attach_refresh(&mut self, node: RNode, host: NodeId) {
if let Some(handle) = self.nodes.get(node).and_then(|n| n.handle.clone()) {
self.toolkit.attach_refresh(&handle, host);
}
}
fn capability(&self, cap: Cap) -> Support {
self.toolkit.capability(cap)
}
fn supports_lifecycle(&self, phase: day_spec::Lifecycle) -> bool {
self.toolkit.supports_lifecycle(phase)
}
fn present(&mut self, req: u64, spec: &present::PresentSpec) {
self.toolkit.present(req, spec);
}
fn dismiss(&mut self, req: u64) {
self.toolkit.dismiss(req);
}
fn open_url(&mut self, url: &str) {
self.toolkit.open_url(url);
}
fn share_support(&self) -> Support {
self.toolkit.share_support()
}
fn share_url(&mut self, url: &str, title: &str) -> bool {
self.toolkit.share_url(url, title)
}
fn application_handlers(
&mut self,
query: &day_spec::applications::HandlerQuery,
) -> Result<day_spec::applications::ApplicationHandlers, day_spec::applications::ApplicationError>
{
self.toolkit.application_handlers(query)
}
fn open_url_with_application(
&mut self,
url: &str,
application: &str,
completion: day_spec::applications::OpenApplicationCompletion,
) {
self.toolkit
.open_url_with_application(url, application, completion);
}
fn set_appearance(&mut self, dark: Option<bool>) {
self.toolkit.set_appearance(dark);
}
fn set_app_badge(&mut self, badge: &day_spec::AppBadge) {
self.toolkit.set_app_badge(badge);
}
fn defer_system_gestures(&mut self, edges: day_spec::Edges) {
self.toolkit.defer_system_gestures(edges);
}
fn set_status_bar_hidden(&mut self, hidden: bool) {
self.toolkit.set_status_bar_hidden(hidden);
}
fn create_node(
&mut self,
kind: PieceKind,
props: &dyn Any,
layout: Rc<dyn Layout>,
flex: Flex,
native: bool,
is_boundary: bool,
scope: Scope,
) -> RNode {
let mut probe = NodeProbe {
enabled: true,
..Default::default()
};
{
use day_spec::props::*;
if let Some(p) = props.downcast_ref::<LabelProps>() {
probe.text = p.text.clone();
} else if let Some(p) = props.downcast_ref::<NavMenuProps>() {
probe.selected = p.selected.map(|i| i as i64).unwrap_or(-1);
probe.value = probe.selected as f64;
} else if let Some(p) = props.downcast_ref::<ButtonProps>() {
probe.text = p.title.clone();
probe.enabled = p.enabled;
probe.flag = p.selected;
} else if let Some(p) = props.downcast_ref::<ToggleProps>() {
probe.flag = p.on;
probe.enabled = p.enabled;
} else if let Some(p) = props.downcast_ref::<SliderProps>() {
probe.value = p.value;
probe.enabled = p.enabled;
} else if let Some(p) = props.downcast_ref::<TextFieldProps>() {
probe.text = p.text.clone();
probe.enabled = p.enabled;
} else if let Some(p) = props.downcast_ref::<ProgressProps>() {
probe.flag = p.value.is_none();
probe.value = p.value.unwrap_or(0.0);
} else if let Some(p) = props.downcast_ref::<PickerProps>() {
probe.selected = p.selected as i64;
probe.value = p.selected as f64;
probe.text = p.options.get(p.selected).cloned().unwrap_or_default();
probe.options = p.options.clone();
probe.enabled = p.enabled;
} else if let Some(p) = props.downcast_ref::<TextAreaProps>() {
probe.text = p.text.clone();
}
}
let node = self.nodes.insert(NodeData {
kind,
handle: None,
parent: RNode::null(),
children: Vec::new(),
layout,
flex,
scope,
id: None,
parked_id: None,
a11y: Default::default(),
cache: Vec::new(),
baseline_cache: None,
probe,
needs_measure: true,
needs_layout: true,
last_native_frame: None,
scroll_content: None,
implicit_anim: None,
tweaked: false,
is_boundary,
});
if native {
let h = self.toolkit.realize(kind, props, rnode_to_id(node));
self.nodes[node].handle = Some(h);
}
node
}
fn attach(&mut self, parent: RNode, child: RNode) {
let index = self
.nodes
.get(parent)
.map(|p| p.children.len())
.unwrap_or(0);
self.attach_impl(parent, child, index);
}
fn native_window_tabs(&self) -> bool {
self.toolkit.native_window_tabs()
}
fn native_document_tabs(&self) -> bool {
self.toolkit.native_document_tabs()
}
fn group_windows(&mut self, roots: &[RNode]) {
let hosts = roots
.iter()
.filter_map(|r| self.nodes.get(*r)?.handle.clone())
.collect::<Vec<_>>();
self.toolkit.group_windows(&hosts);
}
fn window_tab_order(&self, root: RNode) -> Vec<day_spec::NodeId> {
self.nodes
.get(root)
.and_then(|n| n.handle.as_ref())
.map(|h| self.toolkit.window_tab_order(h))
.unwrap_or_default()
}
fn reparent(&mut self, child: RNode, parent: RNode) -> bool {
if !self.nodes.contains_key(child) || !self.nodes.contains_key(parent) {
return false;
}
let old = self.nodes[child].parent;
if old == parent {
return true;
}
if self.windows.iter().any(|w| w.root == child) {
return false;
}
let mut up = parent;
while let Some(n) = self.nodes.get(up) {
if up == child {
return false;
}
up = n.parent;
}
let ancestor = self.native_ancestor(old);
let handle = self.nodes.get(ancestor).and_then(|n| n.handle.clone());
let mut roots = Vec::new();
if self.nodes[child].handle.is_some() {
roots.push(child);
} else {
self.native_descendants(child, &mut roots);
}
if let Some(h) = handle {
for r in roots {
self.toolkit
.remove(&h, self.nodes[r].handle.as_ref().unwrap());
}
}
if let Some(p) = self.nodes.get_mut(old) {
p.children.retain(|n| *n != child);
}
self.mark_needs_measure_impl(old);
self.attach(parent, child);
self.layout_dirty = true;
true
}
fn attach_at(&mut self, parent: RNode, child: RNode, index: usize) {
self.attach_impl(parent, child, index);
}
fn reorder_children(&mut self, parent: RNode, order: Vec<RNode>) {
if self.nodes.get(parent).is_some_and(|p| p.children == order) {
return;
}
if let Some(p) = self.nodes.get_mut(parent) {
p.children = order;
}
let ancestor = self.native_ancestor(parent);
let anc_handle = self.nodes[ancestor]
.handle
.clone()
.expect("native ancestor");
let mut desired = Vec::new();
self.native_descendants(ancestor, &mut desired);
for (i, r) in desired.iter().enumerate() {
let h = self.nodes[*r].handle.clone().unwrap();
self.toolkit.move_child(&anc_handle, &h, i);
}
self.mark_needs_measure_impl(parent);
}
fn remove_subtree(&mut self, node: RNode) {
debug_assert!(
!self.windows.iter().any(|w| w.root == node),
"remove_subtree called with a window root"
);
self.remove_subtree_impl(node);
}
fn on_event(&mut self, node: RNode, h: EventHandler) {
self.handlers.entry(node).or_default().push(h);
}
fn handlers_for(&self, node: RNode) -> Vec<EventHandler> {
self.handlers.get(&node).cloned().unwrap_or_default()
}
fn native_view_of(&self, node: RNode) -> Option<RNode> {
let mut cur = node;
for _ in 0..16 {
let n = self.nodes.get(cur)?;
if n.handle.is_some() {
return Some(cur);
}
match n.children.as_slice() {
[only] => cur = *only,
_ => return None,
}
}
None
}
fn gesture_target(&self, node: RNode, kind: day_spec::GestureKind) -> Option<RNode> {
let has = |n: RNode| self.gestures.get(&n).is_some_and(|k| k.contains(&kind));
if has(node) {
return Some(node);
}
let mut cur = node;
let mut bound = self.element_outer.get(&node).copied();
while let Some(outer) = bound {
if cur == outer {
bound = self.element_outer.get(&outer).copied();
continue;
}
let Some(parent) = self.nodes.get(cur).map(|n| n.parent) else {
break;
};
if parent == cur || !self.nodes.contains_key(parent) {
break;
}
cur = parent;
if has(cur) {
return Some(cur);
}
}
self.wrappers_above(cur).into_iter().find(|w| has(*w))
}
fn set_element_outer(&mut self, inner: RNode, outer: RNode) {
if inner != outer {
self.element_outer.insert(inner, outer);
}
}
fn wrappers_above(&self, node: RNode) -> Vec<RNode> {
let mut out = Vec::new();
let mut cur = node;
for _ in 0..16 {
let Some(parent) = self.nodes.get(cur).map(|n| n.parent) else {
break;
};
match self.nodes.get(parent) {
Some(p) if p.handle.is_none() && p.children.len() == 1 && parent != cur => {
out.push(parent);
cur = parent;
}
_ => break,
}
}
out
}
fn node_exists(&self, node: RNode) -> bool {
self.nodes.contains_key(node)
}
fn dark_mode(&mut self) -> bool {
self.toolkit.dark_mode()
}
fn reduce_motion(&mut self) -> bool {
self.toolkit.reduce_motion()
}
fn mounted_id_families(&self) -> Vec<String> {
self.mounted_id_families.iter().cloned().collect()
}
fn set_id(&mut self, node: RNode, id: String) {
if self.nodes.contains_key(node) {
self.mounted_id_families
.insert(crate::element_id::family_of(&id).to_owned());
}
if let Some(n) = self.nodes.get_mut(node) {
n.id = Some(id.clone());
n.a11y.merge(&A11yProps {
identifier: Some(id),
..Default::default()
});
if let Some(h) = n.handle.clone() {
self.toolkit.set_a11y(&h, &n.a11y);
}
}
}
fn forward_whole_row(&mut self, wrapper: RNode, inner: RNode) {
let whole = self.nodes.get(inner).is_some_and(|n| n.flex.whole_row);
if let Some(n) = self.nodes.get_mut(wrapper) {
n.flex.whole_row = whole;
}
}
fn set_grid_facts(&mut self, node: RNode, facts: GridFacts) {
if let Some(n) = self.nodes.get_mut(node) {
let g = &mut n.flex.grid;
if facts.is_row {
g.is_row = true;
}
if facts.row_valign.is_some() {
g.row_valign = facts.row_valign;
}
if facts.col_span != 0 {
g.col_span = facts.col_span;
}
if facts.align.is_some() {
g.align = facts.align;
}
}
}
fn announce(&mut self, text: &str, urgent: bool) {
self.toolkit.announce(text, urgent);
}
fn set_a11y(&mut self, node: RNode, a11y: A11yProps) {
if let Some(n) = self.nodes.get_mut(node) {
n.a11y.merge(&a11y);
if n.kind == kinds::CANVAS
&& let Some(text) = n.a11y.value.clone().or_else(|| n.a11y.label.clone())
{
n.probe.text = text;
}
if let Some(h) = n.handle.clone() {
self.toolkit.set_a11y(&h, &n.a11y);
}
}
}
fn enable_gesture(&mut self, node: RNode, kind: day_spec::GestureKind) {
let kinds = self.gestures.entry(node).or_default();
if !kinds.contains(&kind) {
kinds.push(kind);
}
let mut cur = node;
for _ in 0..16 {
let Some(n) = self.nodes.get(cur) else { return };
if let Some(h) = n.handle.clone() {
if kind == day_spec::GestureKind::Tap && n.kind == kinds::BUTTON {
log::warn!(
"tap gesture attached to native button {:?} (id: {:?}): \
use .action(...) instead of .on_tap(...) or .on_tap_at(...). \
A separate gesture recognizer can delay pressed feedback and \
bypass native keyboard, accessibility, and disabled behavior",
rnode_to_id(cur),
n.id,
);
}
self.toolkit.enable_gesture(&h, rnode_to_id(node), kind);
return;
}
match n.children.as_slice() {
[only] => cur = *only,
_ => break,
}
}
log::warn!(
"enable_gesture({kind:?}) found no native view under the target node — \
the gesture will not fire natively (attach it to a piece with a native handle)"
);
}
fn focus_node(&mut self, node: RNode, focused: bool) {
let mut cur = node;
for _ in 0..16 {
let Some(n) = self.nodes.get(cur) else { return };
if let Some(h) = n.handle.clone() {
self.toolkit.focus(&h, rnode_to_id(node), focused);
return;
}
match n.children.as_slice() {
[only] => cur = *only,
_ => return,
}
}
}
fn set_focusable(&mut self, node: RNode, focusable: bool) {
let mut cur = node;
for _ in 0..16 {
let Some(n) = self.nodes.get(cur) else { return };
if let Some(h) = n.handle.clone() {
self.toolkit.set_focusable(&h, rnode_to_id(node), focusable);
return;
}
match n.children.as_slice() {
[only] => cur = *only,
_ => break,
}
}
log::warn!(
"set_focusable found no native view under the target node — \
the piece will not join the focus order"
);
}
fn set_probe_focused(&mut self, node: RNode, focused: bool) {
if let Some(n) = self.nodes.get_mut(node) {
n.probe.focused = focused;
}
for w in self.wrappers_above(node) {
if let Some(p) = self.nodes.get_mut(w) {
p.probe.focused = focused;
}
}
let mut cur = node;
for _ in 0..16 {
let Some(n) = self.nodes.get(cur) else { break };
if n.handle.is_some() && cur != node {
if let Some(p) = self.nodes.get_mut(cur) {
p.probe.focused = focused;
}
break;
}
let next = match n.children.as_slice() {
[only] => *only,
_ => break,
};
if let Some(p) = self.nodes.get_mut(next) {
p.probe.focused = focused;
}
cur = next;
}
FOCUSED.with(|f| match focused {
true => f.set(Some(node)),
false if f.get() == Some(node) => f.set(None),
false => {}
});
}
fn set_probe_selected(&mut self, node: RNode, selected: Option<usize>) {
if let Some(n) = self.nodes.get_mut(node) {
n.probe.selected = selected.map(|i| i as i64).unwrap_or(-1);
n.probe.value = n.probe.selected as f64;
}
}
fn set_probe_value(&mut self, node: RNode, value: f64, text: String) {
if let Some(n) = self.nodes.get_mut(node) {
n.probe.value = value;
n.probe.text = text;
}
}
fn set_app_menu(&mut self, items: Vec<day_spec::MenuItem>) {
collect_menu_families(&items, &mut self.mounted_id_families);
self.toolkit.set_app_menu(&items);
}
fn edit_window_toolbar(&mut self, root: RNode, ops: Vec<day_spec::ToolbarOp>) -> bool {
let Some(h) = self.nodes.get(root).and_then(|n| n.handle.clone()) else {
return false;
};
for op in &ops {
if let day_spec::ToolbarOp::Insert { item, .. } = op {
self.mounted_id_families
.insert(crate::element_id::family_of(&item.id).to_owned());
}
}
self.toolkit.edit_toolbar(&h, &ops)
}
fn patch_window_toolbar(&mut self, root: RNode, patch: day_spec::ToolbarPatch) -> bool {
let Some(h) = self.nodes.get(root).and_then(|n| n.handle.clone()) else {
return false;
};
self.toolkit.update_toolbar(&h, &patch);
true
}
fn set_context_menu(&mut self, node: RNode, items: Vec<day_spec::MenuItem>) {
let target = if self.nodes.get(node).is_some_and(|n| n.handle.is_some()) {
Some(node)
} else {
let mut roots = Vec::new();
self.native_descendants(node, &mut roots);
roots.first().copied()
};
let Some(t) = target else {
log::warn!("context_menu on a subtree with no native view — menu dropped");
return;
};
collect_menu_families(&items, &mut self.mounted_id_families);
let model = ContextMenuModel::Items(items.clone());
self.context_menus.insert(node, model.clone());
self.context_menus.insert(t, model);
if let Some(h) = self.nodes.get(t).and_then(|n| n.handle.clone()) {
self.toolkit.set_context_menu(&h, rnode_to_id(t), &items);
}
}
fn scroll_to_target(&mut self, node: RNode, target: &ScrollTarget, animated: bool) -> bool {
if let ScrollTarget::Id(id) = target {
let Some(el) = self.find_by_id(id) else {
return false;
};
return self.scroll_reveal(el, animated);
}
let Some(n) = self.nodes.get(node) else {
return false;
};
if n.kind == kinds::NAV_MENU {
return match (n.handle.clone(), target) {
(Some(h), ScrollTarget::Top | ScrollTarget::Bottom) => self
.toolkit
.nav_scroll_to_edge(&h, matches!(target, ScrollTarget::Bottom)),
_ => false,
};
}
if n.kind != kinds::SCROLL {
return false;
}
let Some(h) = n.handle.clone() else {
return false;
};
let viewport = n.last_native_frame.map(|f| f.size).unwrap_or(Size::ZERO);
let content = n.scroll_content.unwrap_or(viewport);
let rect = match target {
ScrollTarget::Top | ScrollTarget::Leading => Rect::new(0.0, 0.0, 1.0, 1.0),
ScrollTarget::Bottom => Rect::new(0.0, (content.height - 1.0).max(0.0), 1.0, 1.0),
ScrollTarget::Trailing => Rect::new((content.width - 1.0).max(0.0), 0.0, 1.0, 1.0),
ScrollTarget::Offset(p) => Rect::new(p.x, p.y, viewport.width, viewport.height),
ScrollTarget::Id(_) => unreachable!("routed to scroll_reveal above"),
};
self.toolkit
.scroll_to(&h, rect, animated && !crate::reduce_motion_now());
true
}
fn scroll_reveal(&mut self, node: RNode, animated: bool) -> bool {
let Some(mut rect) = self.nodes.get(node).and_then(|n| n.last_native_frame) else {
return false;
};
let mut anc = match self.nodes.get(node) {
Some(n) => n.parent,
None => return false,
};
loop {
let Some(a) = self.nodes.get(anc) else {
return false; };
if a.kind == kinds::SCROLL {
let Some(h) = a.handle.clone() else {
return false;
};
self.toolkit
.scroll_to(&h, rect, animated && !crate::reduce_motion_now());
return true;
}
if a.handle.is_some()
&& let Some(f) = a.last_native_frame
{
rect.origin.x += f.origin.x;
rect.origin.y += f.origin.y;
}
anc = a.parent;
}
}
fn patch(&mut self, node: RNode, patch: Box<dyn Any>, affects_size: bool) {
{
use day_spec::props::*;
if let Some(n) = self.nodes.get_mut(node) {
if let Some(p) = patch.downcast_ref::<LabelPatch>() {
match p {
LabelPatch::Text(t) => n.probe.text = t.clone(),
LabelPatch::Runs(t, _) => n.probe.text = t.clone(),
LabelPatch::Font(_) | LabelPatch::Color(_) => {}
}
} else if let Some(p) = patch.downcast_ref::<ButtonPatch>() {
match p {
ButtonPatch::Title(t) => n.probe.text = t.clone(),
ButtonPatch::Content(c) => n.probe.text = c.title.clone(),
ButtonPatch::Enabled(e) => n.probe.enabled = *e,
ButtonPatch::Selected(on) => n.probe.flag = *on,
ButtonPatch::Style(_) => {}
}
} else if let Some(p) = patch.downcast_ref::<TogglePatch>() {
match p {
TogglePatch::On(v) => n.probe.flag = *v,
TogglePatch::Enabled(e) => n.probe.enabled = *e,
}
} else if let Some(p) = patch.downcast_ref::<SliderPatch>() {
match p {
SliderPatch::Value(v) => n.probe.value = *v,
SliderPatch::Enabled(e) => n.probe.enabled = *e,
}
} else if let Some(p) = patch.downcast_ref::<PickerPatch>() {
match p {
PickerPatch::Selected(i) => {
n.probe.selected = *i as i64;
n.probe.value = *i as f64;
}
PickerPatch::Options(opts) => n.probe.options = opts.clone(),
PickerPatch::Enabled(e) => n.probe.enabled = *e,
}
let i = n.probe.selected.max(0) as usize;
n.probe.text = n.probe.options.get(i).cloned().unwrap_or_default();
} else if let Some(TextAreaPatch::SetText(t)) =
patch.downcast_ref::<TextAreaPatch>()
{
n.probe.text = t.clone();
} else if let Some(ProgressPatch::Value(v)) = patch.downcast_ref::<ProgressPatch>()
{
n.probe.flag = v.is_none();
n.probe.value = v.unwrap_or(0.0);
} else if let Some(p) = patch.downcast_ref::<TextFieldPatch>() {
match p {
TextFieldPatch::Text { text, .. } => n.probe.text = text.clone(),
TextFieldPatch::Enabled(e) => n.probe.enabled = *e,
_ => {}
}
} else if let Some(NavMenuPatch::Selected(sel)) =
patch.downcast_ref::<NavMenuPatch>()
{
n.probe.selected = sel.map(|i| i as i64).unwrap_or(-1);
n.probe.value = n.probe.selected as f64;
}
}
}
let Some(n) = self.nodes.get(node) else {
return;
};
let kind = n.kind;
if let Some(h) = n.handle.clone() {
let anim = self.resolve_anim(node);
self.toolkit.update(&h, kind, patch.as_ref(), anim.as_ref());
}
if affects_size {
self.mark_needs_measure_impl(node);
}
}
fn set_implicit_anim(&mut self, node: RNode, anim: Option<day_spec::AnimSpec>) {
let had = self
.nodes
.get(node)
.map(|n| n.implicit_anim.is_some())
.unwrap_or(false);
if let Some(n) = self.nodes.get_mut(node) {
n.implicit_anim = anim;
} else {
return;
}
match (had, anim.is_some()) {
(false, true) => self.implicit_anim_count += 1,
(true, false) => self.implicit_anim_count = self.implicit_anim_count.saturating_sub(1),
_ => {}
}
}
fn set_node_opacity(&mut self, node: RNode, opacity: f64) {
let Some(h) = self.nodes.get(node).and_then(|n| n.handle.clone()) else {
return;
};
let anim = self.resolve_anim(node);
self.toolkit.set_opacity(&h, opacity, anim.as_ref());
}
fn set_node_transform(&mut self, node: RNode, t: day_spec::Transform) {
let Some(n) = self.nodes.get(node) else {
return;
};
let Some(h) = n.handle.clone() else {
return;
};
let size = n.last_native_frame.map(|f| f.size).unwrap_or(Size::ZERO);
let anim = self.resolve_anim(node);
self.toolkit.set_transform(&h, t, size, anim.as_ref());
}
fn set_node_selectable(&mut self, node: RNode, selectable: bool) {
let Some(h) = self.nodes.get(node).and_then(|n| n.handle.clone()) else {
return;
};
if let Some(new) = self.toolkit.set_selectable(&h, selectable)
&& let Some(n) = self.nodes.get_mut(node)
{
if n.tweaked {
log::warn!(
"`.selectable()` rebuilt this widget as a different native class, \
discarding an earlier tweak's changes — apply `.selectable()` BEFORE the \
tweak so it runs against the widget that ships (docs/tweaks.md)."
);
}
n.handle = Some(new);
}
}
fn set_node_input_traits(&mut self, node: RNode, traits: &day_spec::InputTraits) {
let Some(h) = self.nodes.get(node).and_then(|n| n.handle.clone()) else {
return;
};
if let Some(new) = self.toolkit.set_input_traits(&h, traits)
&& let Some(n) = self.nodes.get_mut(node)
{
if n.tweaked {
log::warn!(
"a text field's `.secure()` rebuilt this widget as a different native \
class, discarding an earlier tweak's changes (docs/tweaks.md)."
);
}
n.handle = Some(new);
n.last_native_frame = None;
self.mark_needs_measure_impl(node);
}
}
fn set_node_cursor(&mut self, node: RNode, cursor: day_spec::Cursor) {
let Some(h) = self
.native_view_of(node)
.and_then(|n| self.nodes.get(n))
.and_then(|n| n.handle.clone())
else {
return;
};
self.toolkit.set_cursor(&h, cursor);
}
fn set_node_drag_region(&mut self, node: RNode, drag: bool) {
let Some(h) = self
.native_view_of(node)
.and_then(|n| self.nodes.get(n))
.and_then(|n| n.handle.clone())
else {
log::warn!(
"window_drag_region found no native view under the target node; give the \
region a piece with a native view (a background, a row)"
);
return;
};
self.toolkit.set_drag_region(&h, drag);
}
fn note_node_tweaked(&mut self, node: RNode) {
if let Some(n) = self.nodes.get_mut(node) {
n.tweaked = true;
}
}
fn request_frame(&mut self, root: RNode, cb: day_spec::FrameCallback) -> day_spec::CancelFrame {
if let Some(h) = self.nodes.get(root).and_then(|n| n.handle.clone()) {
self.toolkit.request_frame(&h, cb)
} else {
Box::new(|| {})
}
}
fn replay(&mut self, node: RNode, ops: Vec<DrawOp>) {
let Some(n) = self.nodes.get(node) else {
return;
};
let size = n.last_native_frame.map(|f| f.size).unwrap_or(Size::ZERO);
if let Some(h) = n.handle.clone() {
self.toolkit.replay(&h, &ops, size);
}
}
fn mark_needs_measure(&mut self, node: RNode) {
self.mark_needs_measure_impl(node);
}
fn mark_layout_dirty(&mut self) {
self.layout_dirty = true;
}
fn invalidate_all_measurements(&mut self) {
for (_, n) in self.nodes.iter_mut() {
n.needs_measure = true;
n.needs_layout = true;
n.cache.clear();
}
for state in self.lists.values_mut() {
for cell in state.cells.values_mut() {
cell.laid_width = None;
}
}
for state in self.trees.values_mut() {
for cell in state.cells.values_mut() {
cell.laid_width = None;
}
}
self.layout_dirty = true;
}
fn layout_if_needed(&mut self) {
if !self.layout_dirty {
return;
}
self.layout_dirty = false;
self.layout_now();
}
fn set_route(&mut self, route: &str) {
self.toolkit.set_route(route);
}
fn set_window_size(&mut self, s: Size) {
if s != self.windows[0].size {
self.windows[0].size = s;
let root = self.windows[0].root;
self.mark_needs_measure_impl(root);
}
}
fn child_count(&self, node: RNode) -> usize {
self.nodes.get(node).map(|n| n.children.len()).unwrap_or(0)
}
fn first_child(&self, node: RNode) -> Option<RNode> {
self.nodes
.get(node)
.and_then(|n| n.children.first().copied())
}
fn parent_of(&self, node: RNode) -> Option<RNode> {
self.nodes
.get(node)
.map(|n| n.parent)
.filter(|p| !p.is_null() && *p != node)
}
fn node_kind(&self, node: RNode) -> Option<PieceKind> {
self.nodes.get(node).map(|n| n.kind)
}
fn node_id(&self, node: RNode) -> Option<String> {
self.nodes.get(node).and_then(|n| n.id.clone())
}
fn node_handle_any(&self, node: RNode) -> Option<Box<dyn Any>> {
self.nodes
.get(node)
.and_then(|n| n.handle.clone())
.map(|h| Box::new(h) as Box<dyn Any>)
}
fn node_frame(&self, node: RNode) -> Option<Rect> {
self.nodes.get(node).and_then(|n| n.last_native_frame)
}
fn node_probe(&self, node: RNode) -> Option<NodeProbe> {
self.nodes.get(node).map(|n| n.probe.clone())
}
fn node_a11y(&self, node: RNode) -> Option<A11yProps> {
self.nodes.get(node).map(|n| n.a11y.clone())
}
fn read_native(&self, node: RNode) -> Option<day_spec::NativeSnapshot> {
let n = self.nodes.get(node)?;
let h = n.handle.as_ref()?;
Some(self.toolkit.read_native(h))
}
fn a11y_nodes(&self) -> Vec<(String, PieceKind, A11yProps, day_spec::NativeSnapshot)> {
self.nodes
.values()
.filter_map(|n| {
let id = n.id.clone()?;
let h = n.handle.as_ref()?;
Some((id, n.kind, n.a11y.clone(), self.toolkit.read_native(h)))
})
.collect()
}
fn find_by_id(&self, id: &str) -> Option<RNode> {
let focused = crate::windows::focused_window()
.map(|w| id_to_rnode(crate::windows::window_node_id(&w)))
.unwrap_or_else(|| self.root());
let mut fallback = None;
for (key, node) in &self.nodes {
if node.id.as_deref() != Some(id) {
continue;
}
fallback.get_or_insert(key);
let mut ancestor = key;
while let Some(node) = self.nodes.get(ancestor) {
if self.windows.iter().any(|w| w.root == ancestor) {
if ancestor == focused {
return Some(key);
}
break;
}
ancestor = if node.kind == kinds::LIST_CELL && node.parent.is_null() {
self.lists
.iter()
.find_map(|(&owner, state)| {
state
.cells
.values()
.any(|c| c.anchor == ancestor)
.then_some(owner)
})
.or_else(|| {
self.trees.iter().find_map(|(&owner, state)| {
state
.cells
.values()
.any(|c| c.anchor == ancestor)
.then_some(owner)
})
})
.unwrap_or(node.parent)
} else {
node.parent
};
}
}
fallback
}
fn toggle_sidebar(&mut self, host: RNode) -> bool {
let Some(h) = self.nodes.get(host).and_then(|n| n.handle.as_ref()) else {
return false;
};
self.toolkit.toggle_sidebar(h)
}
fn native_back(&mut self) -> bool {
self.toolkit.native_back()
}
fn prepare_snapshot(
&mut self,
root: Option<RNode>,
revision: u32,
) -> Result<day_spec::capture::Readiness, String> {
let host = match root {
Some(root) if self.windows[0].root != root => Some(
self.nodes
.get(root)
.and_then(|n| n.handle.clone())
.ok_or("window is gone")?,
),
_ => None,
};
self.toolkit.prepare_snapshot(host.as_ref(), revision)
}
fn snapshot(&mut self) -> Result<Vec<u8>, String> {
self.toolkit.snapshot_window()
}
fn snapshot_origin(&mut self) -> Option<Point> {
let root = self.windows[0].root;
let h = self.nodes.get(root).and_then(|n| n.handle.clone())?;
self.toolkit.snapshot_origin(&h)
}
fn snapshot_chrome(&mut self) -> Result<Vec<u8>, String> {
self.toolkit.snapshot_window_chrome()
}
fn decode_image(&mut self, req: u64, id: day_spec::BitmapId, bytes: &[u8]) {
self.toolkit.decode_image(req, id, bytes);
}
fn image_info(&mut self, id: day_spec::BitmapId) -> Option<day_spec::BitmapInfo> {
self.toolkit.image_info(id)
}
fn image_properties(&mut self, id: day_spec::BitmapId) -> Option<day_spec::ImageProperties> {
self.toolkit.image_properties(id)
}
fn encode_image(&mut self, req: u64, id: day_spec::BitmapId, spec: &day_spec::EncodeSpec) {
self.toolkit.encode_image(req, id, spec);
}
fn image_encode_formats(&mut self) -> Vec<day_spec::ImageFormat> {
self.toolkit.encode_formats()
}
fn release_image(&mut self, id: day_spec::BitmapId) {
self.toolkit.release_image(id);
}
fn render_bitmap(
&mut self,
req: u64,
id: day_spec::BitmapId,
ops: &[day_spec::DrawOp],
size: Size,
scale: f64,
) {
self.toolkit.render_bitmap(req, id, ops, size, scale);
}
fn render_pdf(&mut self, req: u64, pages: &[day_spec::PdfPage], info: &day_spec::PdfInfo) {
self.toolkit.render_pdf(req, pages, info);
}
fn window_image_support(&mut self) -> day_spec::Support {
self.toolkit.capability(day_spec::Cap::Snapshot)
}
fn font_families(&mut self) -> Vec<day_spec::FontFamilyInfo> {
self.toolkit.font_families()
}
fn measure_text(
&mut self,
text: &str,
size: f64,
font: &day_spec::CanvasFont,
) -> Option<day_spec::TextMetrics> {
self.toolkit.measure_text(text, size, font)
}
fn ui_idle(&mut self) -> bool {
self.toolkit.ui_idle()
}
fn root_node(&self) -> RNode {
self.windows[0].root
}
fn open_window_root(&mut self, options: &WindowOptions, kind: WindowKind) -> WindowRootReply {
let root = self.nodes.insert(NodeData {
kind: kinds::CONTAINER,
handle: None,
parent: RNode::null(),
children: Vec::new(),
layout: Rc::new(PassThrough),
flex: Flex::default(),
scope: Scope::root(),
id: None,
parked_id: None,
a11y: Default::default(),
cache: Vec::new(),
baseline_cache: None,
probe: NodeProbe::default(),
needs_measure: true,
needs_layout: true,
last_native_frame: None,
scroll_content: None,
implicit_anim: None,
tweaked: false,
is_boundary: true,
});
match self.toolkit.open_window(rnode_to_id(root), options, kind) {
day_spec::WindowOpenReply::Open(h) => {
self.nodes[root].handle = Some(h);
self.windows.push(WindowEntry {
root,
size: options.size,
});
self.layout_dirty = true;
WindowRootReply::Open(root)
}
day_spec::WindowOpenReply::Pending => WindowRootReply::Pending(root),
day_spec::WindowOpenReply::Unsupported => {
self.nodes.remove(root);
WindowRootReply::Unsupported
}
}
}
fn adopt_window_root(&mut self, root: RNode, raw: day_spec::RawHandle, size: Size) -> bool {
let Some(node) = self.nodes.get_mut(root) else {
return false; };
let handle = self.toolkit.adopt(raw);
node.handle = Some(handle);
node.needs_measure = true;
self.windows.push(WindowEntry { root, size });
self.layout_dirty = true;
true
}
fn remove_window_root(&mut self, root: RNode) {
if self.windows.len() <= 1 {
self.collect_and_release(root);
self.layout_dirty = true;
return;
}
self.windows.retain(|w| w.root != root);
self.collect_and_release(root);
self.layout_dirty = true;
}
fn set_root_size(&mut self, root: RNode, s: Size) {
if let Some(w) = self.windows.iter_mut().find(|w| w.root == root)
&& w.size != s
{
w.size = s;
self.mark_needs_measure_impl(root);
}
}
fn fit_window(&mut self, root: RNode, size: Size) {
if let Some(h) = self.nodes.get(root).and_then(|n| n.handle.clone()) {
self.toolkit.fit_window(&h, size);
}
self.set_root_size(root, size);
}
fn add_extra_layout_root(&mut self, node: RNode, size: Size) {
if !self.windows.iter().any(|w| w.root == node) {
self.windows.push(WindowEntry { root: node, size });
self.layout_dirty = true;
}
}
fn drop_extra_layout_root(&mut self, node: RNode) {
debug_assert!(
self.windows.first().map(|w| w.root) != Some(node),
"drop_extra_layout_root called with the primary root"
);
self.windows.retain(|w| w.root != node);
}
fn close_native_window(&mut self, root: RNode) {
if let Some(h) = self.nodes.get(root).and_then(|n| n.handle.clone()) {
self.toolkit.close_window(&h);
}
}
fn quit_app(&mut self) {
self.toolkit.quit_app();
}
fn focus_native_window(&mut self, root: RNode) {
if let Some(h) = self.nodes.get(root).and_then(|n| n.handle.clone()) {
self.toolkit.focus_window(&h);
}
}
fn set_native_window_title(&mut self, root: RNode, title: &str) {
if let Some(h) = self.nodes.get(root).and_then(|n| n.handle.clone()) {
self.toolkit.set_window_title(&h, title);
}
}
fn apply_window(&mut self, root: RNode, change: &day_spec::WindowChange) {
if let Some(h) = self.nodes.get(root).and_then(|n| n.handle.clone()) {
self.toolkit.apply_window(&h, change);
}
}
fn set_dock_menu(&mut self, items: &[day_spec::MenuItem]) {
self.toolkit.set_dock_menu(items);
}
fn set_status_items(&mut self, items: &[day_spec::StatusItemSpec]) {
self.toolkit.set_status_items(items);
}
fn set_app_progress(&mut self, progress: day_spec::AppProgress) {
self.toolkit.set_app_progress(progress);
}
fn set_dock_visible(&mut self, visible: bool) {
self.toolkit.set_dock_visible(visible);
}
fn set_keep_running(&mut self, keep: bool) {
self.toolkit.set_keep_running(keep);
}
fn native_window_frame(&self, root: RNode) -> Option<day_spec::Rect> {
let h = self.nodes.get(root).and_then(|n| n.handle.clone())?;
self.toolkit.window_frame(&h)
}
fn monitors(&self) -> Vec<day_spec::Monitor> {
self.toolkit.monitors()
}
fn set_launcher_shortcuts(&mut self, shortcuts: &[day_spec::LauncherShortcut]) {
self.toolkit.set_launcher_shortcuts(shortcuts);
}
fn snapshot_of(&mut self, root: RNode) -> Result<Vec<u8>, String> {
if self.windows[0].root == root {
return self.toolkit.snapshot_window();
}
match self.nodes.get(root).and_then(|n| n.handle.clone()) {
Some(h) => self.toolkit.snapshot_window_of(&h),
None => Err("window is gone".into()),
}
}
fn install_list(&mut self, node: RNode, driver: crate::list::ListDriver) {
let driver = Rc::new(driver);
let sections = driver
.sections
.is_some()
.then(|| crate::list::register_sections(node, driver.clone()));
self.lists.insert(
node,
crate::list::ListState {
driver: driver.clone(),
cells: HashMap::new(),
sections: sections.clone(),
},
);
let source = crate::list::make_source(node, driver, sections);
if let Some(handle) = self.nodes.get(node).and_then(|n| n.handle.clone()) {
self.toolkit.attach_list(&handle, source);
}
}
fn list_prepare_cell(
&mut self,
node: RNode,
key: usize,
cell: RawHandle,
header: bool,
) -> crate::list::CellStep {
if let Some(state) = self.lists.get_mut(&node)
&& state.cells.get(&key).is_some_and(|b| b.header != header)
&& let Some(old) = state.cells.remove(&key)
{
old.scope.dispose();
self.remove_subtree_impl(old.anchor);
}
if let Some(state) = self.lists.get_mut(&node)
&& let Some(bound) = state.cells.get_mut(&key)
{
bound.active = true;
let (rebind, anchor, pinned) = (bound.rebind.clone(), bound.anchor, bound.pinned);
if !pinned {
self.restore_subtree_ids(anchor);
}
return crate::list::CellStep::Rebind { rebind, anchor };
}
let mut metrics = match self.nodes.get(node).and_then(|n| n.handle.clone()) {
Some(list) => self.toolkit.list_row_metrics(&list),
None => day_spec::RowMetrics::default(),
};
if header {
metrics.insets.top = 0.0;
metrics.insets.bottom = 0.0;
metrics.min_height = 0.0;
}
let handle = self.toolkit.adopt(cell);
let anchor = self.create_cell_anchor(
handle,
Scope::child(),
Rc::new(crate::layout::CellMetrics(metrics)),
);
crate::list::CellStep::Build { anchor }
}
fn list_store_cell(
&mut self,
node: RNode,
key: usize,
anchor: RNode,
built: crate::list::BuiltRow,
header: bool,
) {
if let Some(state) = self.lists.get_mut(&node) {
state.cells.insert(
key,
crate::list::BoundCell {
anchor,
active: true,
scope: built.scope,
rebind: built.rebind,
native_width: None,
laid_width: None,
laid_height: None,
header,
pinned: false,
},
);
}
}
fn list_layout_cell(&mut self, node: RNode, key: usize) {
let native = self
.lists
.get(&node)
.and_then(|s| s.cells.get(&key))
.and_then(|c| c.native_width);
let width = native.unwrap_or_else(|| {
self.nodes
.get(node)
.and_then(|n| n.last_native_frame)
.map(|f| f.size.width)
.unwrap_or(self.windows[0].size.width)
});
self.layout_list_cell_at(node, key, width);
}
fn list_layout_cell_width(&mut self, node: RNode, key: usize, width: f64) {
if let Some(cell) = self
.lists
.get_mut(&node)
.and_then(|s| s.cells.get_mut(&key))
{
cell.native_width = Some(width);
}
self.layout_list_cell_at(node, key, width);
}
fn list_cell_height(&self, node: RNode, key: usize) -> f64 {
self.lists
.get(&node)
.and_then(|s| s.cells.get(&key))
.filter(|b| b.active)
.and_then(|b| b.laid_height)
.unwrap_or(0.0)
}
fn list_cell_keys(&self, node: RNode) -> Vec<usize> {
self.lists
.get(&node)
.map(|s| {
s.cells
.iter()
.filter_map(|(key, cell)| cell.active.then_some(*key))
.collect()
})
.unwrap_or_default()
}
fn list_reload(&mut self, node: RNode) {
if let Some(map) = self.lists.get(&node).and_then(|s| s.sections.as_ref()) {
map.invalidate();
}
if let Some(handle) = self.nodes.get(node).and_then(|n| n.handle.clone()) {
self.toolkit.update(
&handle,
kinds::LIST,
&day_spec::props::ListPatch::Reload as &dyn Any,
None,
);
}
}
fn list_splice(&mut self, node: RNode, deltas: Vec<day_spec::props::RowDelta>) {
if self.lists.get(&node).is_some_and(|s| s.sections.is_some()) {
self.list_reload(node);
return;
}
if let Some(handle) = self.nodes.get(node).and_then(|n| n.handle.clone()) {
self.toolkit.update(
&handle,
kinds::LIST,
&day_spec::props::ListPatch::Splice(deltas) as &dyn Any,
None,
);
}
}
fn set_undo_state(&mut self, state: &day_spec::UndoState) {
self.toolkit.set_undo_state(state);
}
fn set_edit_state(&mut self, state: &day_spec::EditState) {
self.toolkit.set_edit_state(state);
}
fn modifiers(&mut self) -> day_spec::Modifiers {
self.toolkit.modifiers()
}
fn list_scroll_to_end(&mut self, node: RNode) {
if self.lists.get(&node).map(|s| (s.driver.len)()).unwrap_or(0) == 0 {
return;
}
if let Some(handle) = self.nodes.get(node).and_then(|n| n.handle.clone()) {
self.toolkit.update(
&handle,
kinds::LIST,
&day_spec::props::ListPatch::ScrollToEnd as &dyn Any,
None,
);
}
}
fn list_scroll_to_row(&mut self, node: RNode, row: usize) {
let len = self.lists.get(&node).map(|s| (s.driver.len)()).unwrap_or(0);
if len == 0 {
return;
}
let mut row = row.min(len - 1);
if let Some(map) = self.lists.get(&node).and_then(|s| s.sections.as_ref()) {
row = map.display_of(row).unwrap_or(row);
}
if let Some(handle) = self.nodes.get(node).and_then(|n| n.handle.clone()) {
self.toolkit.update(
&handle,
kinds::LIST,
&day_spec::props::ListPatch::ScrollToRow(row) as &dyn Any,
None,
);
}
}
fn list_set_editing(&mut self, node: RNode, on: bool) {
if let Some(handle) = self.nodes.get(node).and_then(|n| n.handle.clone()) {
self.toolkit.update(
&handle,
kinds::LIST,
&day_spec::props::ListPatch::Editing(on) as &dyn Any,
None,
);
}
}
fn list_set_selected(&mut self, node: RNode, rows: Vec<usize>) {
let rows = match self.lists.get(&node).and_then(|s| s.sections.as_ref()) {
Some(map) => rows.iter().filter_map(|i| map.display_of(*i)).collect(),
None => rows,
};
if let Some(handle) = self.nodes.get(node).and_then(|n| n.handle.clone()) {
self.toolkit.update(
&handle,
kinds::LIST,
&day_spec::props::ListPatch::Selected(rows) as &dyn Any,
None,
);
}
}
fn list_driver(&mut self, node: RNode) -> Option<std::rc::Rc<crate::list::ListDriver>> {
self.lists.get(&node).map(|s| s.driver.clone())
}
fn install_tree(&mut self, node: RNode, driver: crate::tree_driver::TreeDriver) {
let driver = Rc::new(driver);
self.trees.insert(
node,
crate::tree_driver::TreeState {
driver: driver.clone(),
cells: HashMap::new(),
},
);
let source = crate::tree_driver::make_tree_source(node, driver);
if let Some(handle) = self.nodes.get(node).and_then(|n| n.handle.clone()) {
self.toolkit.attach_tree(&handle, source);
}
}
fn tree_prepare_cell(
&mut self,
node: RNode,
key: usize,
cell: RawHandle,
) -> crate::tree_driver::TreeCellStep {
if let Some(state) = self.trees.get(&node)
&& let Some(bound) = state.cells.get(&key)
{
let (rebind, anchor) = (bound.rebind.clone(), bound.anchor);
self.restore_subtree_ids(anchor);
return crate::tree_driver::TreeCellStep::Rebind { rebind, anchor };
}
let handle = self.toolkit.adopt(cell);
let anchor =
self.create_cell_anchor(handle, Scope::child(), Rc::new(crate::layout::CellCenter));
crate::tree_driver::TreeCellStep::Build { anchor }
}
fn tree_store_cell(
&mut self,
node: RNode,
key: usize,
anchor: RNode,
built: crate::tree_driver::TreeBuiltRow,
) {
if let Some(state) = self.trees.get_mut(&node) {
state.cells.insert(
key,
crate::tree_driver::TreeBoundCell {
anchor,
scope: built.scope,
rebind: built.rebind,
native_width: None,
native_height: None,
laid_width: None,
laid_height: None,
},
);
}
}
fn tree_layout_cell(&mut self, node: RNode, key: usize) {
let native = self
.trees
.get(&node)
.and_then(|s| s.cells.get(&key))
.and_then(|c| c.native_width);
let width = native.unwrap_or_else(|| {
self.nodes
.get(node)
.and_then(|n| n.last_native_frame)
.map(|f| f.size.width)
.unwrap_or(self.windows[0].size.width)
});
self.layout_tree_cell_at(node, key, width);
}
fn tree_layout_cell_width(&mut self, node: RNode, key: usize, width: f64) {
if let Some(cell) = self
.trees
.get_mut(&node)
.and_then(|s| s.cells.get_mut(&key))
{
cell.native_width = Some(width);
}
self.layout_tree_cell_at(node, key, width);
}
fn tree_cell_height(&self, node: RNode, key: usize) -> f64 {
self.trees
.get(&node)
.and_then(|s| s.cells.get(&key))
.and_then(|b| b.laid_height)
.unwrap_or(0.0)
}
fn tree_layout_cell_size(&mut self, node: RNode, key: usize, size: Size) {
let changed = self
.trees
.get_mut(&node)
.and_then(|s| s.cells.get_mut(&key))
.and_then(|cell| {
let changed = cell.native_height != Some(size.height);
cell.native_height = Some(size.height);
changed.then_some(cell.anchor)
});
if let Some(anchor) = changed {
self.invalidate_subtree(anchor);
}
self.tree_layout_cell_width(node, key, size.width);
}
fn tree_recycle_cell(&mut self, node: RNode, key: usize) {
let Some(anchor) = self
.trees
.get(&node)
.and_then(|s| s.cells.get(&key))
.map(|b| b.anchor)
else {
return;
};
self.clear_subtree_ids(anchor);
}
fn list_recycle_cell(&mut self, node: RNode, key: usize) {
let Some(anchor) = self
.lists
.get_mut(&node)
.and_then(|s| s.cells.get_mut(&key))
.map(|b| {
b.active = false;
b.laid_height = None;
b.anchor
})
else {
return;
};
self.clear_subtree_ids(anchor);
}
fn list_release_cell(&mut self, node: RNode, key: usize) {
let Some(cell) = self.lists.get_mut(&node).and_then(|s| s.cells.remove(&key)) else {
return;
};
cell.scope.dispose();
self.remove_subtree_impl(cell.anchor);
}
fn list_pin_cell(&mut self, node: RNode, key: usize) {
let Some(anchor) = self
.lists
.get_mut(&node)
.and_then(|s| s.cells.get_mut(&key))
.map(|b| {
b.pinned = true;
b.anchor
})
else {
return;
};
self.clear_subtree_ids(anchor);
}
fn tree_reload(&mut self, node: RNode) {
if let Some(handle) = self.nodes.get(node).and_then(|n| n.handle.clone()) {
self.toolkit.update(
&handle,
kinds::TREE,
&day_spec::props::TreePatch::Reload as &dyn Any,
None,
);
}
}
fn tree_set_expanded(&mut self, node: RNode, token: u64, expanded: bool) {
if let Some(handle) = self.nodes.get(node).and_then(|n| n.handle.clone()) {
self.toolkit.update(
&handle,
kinds::TREE,
&day_spec::props::TreePatch::Expand(token, expanded) as &dyn Any,
None,
);
}
}
fn tree_set_selected(&mut self, node: RNode, tokens: Vec<u64>) {
if let Some(handle) = self.nodes.get(node).and_then(|n| n.handle.clone()) {
self.toolkit.update(
&handle,
kinds::TREE,
&day_spec::props::TreePatch::Selected(tokens) as &dyn Any,
None,
);
}
}
fn tree_reveal(&mut self, node: RNode, token: u64) {
if let Some(handle) = self.nodes.get(node).and_then(|n| n.handle.clone()) {
self.toolkit.update(
&handle,
kinds::TREE,
&day_spec::props::TreePatch::Reveal(token) as &dyn Any,
None,
);
}
}
fn tree_driver(&mut self, node: RNode) -> Option<std::rc::Rc<crate::tree_driver::TreeDriver>> {
self.trees.get(&node).map(|s| s.driver.clone())
}
fn set_drag_source(&mut self, node: RNode, source: day_spec::transfer::Source) {
if let Some(handle) = self.nodes.get(node).and_then(|n| n.handle.clone()) {
self.toolkit.set_drag_source(&handle, source);
}
}
fn set_drop_target(&mut self, node: RNode, target: day_spec::transfer::Target) {
if let Some(handle) = self.nodes.get(node).and_then(|n| n.handle.clone()) {
self.toolkit.set_drop_target(&handle, target);
}
}
fn set_context_menu_fn(&mut self, node: RNode, f: day_spec::ContextMenuFn) {
let target = if self.nodes.get(node).is_some_and(|n| n.handle.is_some()) {
Some(node)
} else {
let mut roots = Vec::new();
self.native_descendants(node, &mut roots);
roots.first().copied()
};
let Some(target) = target else {
log::warn!("context_menu_fn on a subtree with no native view — menu dropped");
return;
};
let model = ContextMenuModel::Provider(f.clone());
self.context_menus.insert(node, model.clone());
self.context_menus.insert(target, model);
if let Some(handle) = self.nodes.get(target).and_then(|n| n.handle.clone()) {
self.toolkit
.set_context_menu_fn(&handle, rnode_to_id(target), f);
}
}
fn context_menu_of(&mut self, node: RNode) -> Option<day_spec::ContextMenuFn> {
let mut cur = node;
loop {
if let Some(model) = self.context_menus.get(&cur) {
return Some(match model {
ContextMenuModel::Items(items) => {
let items = items.clone();
Rc::new(move |_| items.clone())
}
ContextMenuModel::Provider(f) => f.clone(),
});
}
let parent = self.nodes.get(cur)?.parent;
if parent == cur {
return None;
}
cur = parent;
}
}
}
day_reactive::tls_slots! {
tree;
static TREE: std::mem::ManuallyDrop<RefCell<Option<Box<dyn TreeOps>>>> =
const { std::mem::ManuallyDrop::new(RefCell::new(None)) };
static EVENTS: RefCell<VecDeque<(NodeId, Event)>> = const { RefCell::new(VecDeque::new()) };
static PUMP_PENDING: Cell<bool> = const { Cell::new(false) };
static PUMP_ACTIVE: Cell<bool> = const { Cell::new(false) };
static DEFERRED: RefCell<Vec<Box<dyn FnOnce() + Send>>> = const { RefCell::new(Vec::new()) };
static EVENT_OBSERVER: RefCell<Option<Rc<EventObserver>>> = const { RefCell::new(None) };
static PUMP_GEN: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
static FOCUSED: std::cell::Cell<Option<RNode>> = const { std::cell::Cell::new(None) };
}
pub fn install_tree(tree: Box<dyn TreeOps>) {
crate::nav::clear_controllers();
TREE.with(|t| *t.borrow_mut() = Some(tree));
let _ = crate::image::canvas_exports();
}
pub fn prepare_remount() {
day_reactive::recover_from_panic();
uninstall_tree();
day_reactive::recover_from_panic();
crate::lifecycle::reset_handlers();
crate::menu::reset_menus();
}
pub fn is_mounted() -> bool {
TREE.with(|t| t.borrow().is_some())
}
pub fn uninstall_tree() {
crate::image::reset();
crate::nav::clear_controllers();
crate::windows::reset_windows();
crate::status::reset_status();
crate::reset_ambient();
TREE.with(|t| *t.borrow_mut() = None);
EVENTS.with(|e| e.borrow_mut().clear());
PUMP_PENDING.with(|c| c.set(false));
EVENT_OBSERVER.with(|o| *o.borrow_mut() = None);
}
pub fn with_tree<R>(f: impl FnOnce(&mut dyn TreeOps) -> R) -> R {
let r = TREE.with(|t| {
let mut opt = t.borrow_mut();
let ops = opt.as_mut().expect("day: no tree installed on this thread");
f(ops.as_mut())
});
if PUMP_PENDING.with(|c| c.replace(false)) {
pump_events();
}
run_deferred();
r
}
pub(crate) fn run_posted_or_defer(f: Box<dyn FnOnce() + Send>) {
let borrowed = TREE.with(|t| t.try_borrow_mut().is_err());
if borrowed {
DEFERRED.with(|d| d.borrow_mut().push(f));
} else {
crate::contain_posted_panic(f);
}
}
fn run_deferred() {
loop {
let batch = DEFERRED.with(|d| std::mem::take(&mut *d.borrow_mut()));
if batch.is_empty() {
return;
}
for f in batch {
crate::contain_posted_panic(f);
}
}
}
pub fn capability(cap: day_spec::Cap) -> day_spec::Support {
with_tree(|t| t.capability(cap))
}
pub fn open_url(url: &str) {
if crate::conformance::intercept_open_url(url) {
return;
}
with_tree(|t| t.open_url(url));
}
pub fn announce(text: &str) {
crate::conformance::note_announcement(text);
with_tree(|t| t.announce(text, false));
}
pub fn announce_urgent(text: &str) {
crate::conformance::note_announcement(text);
with_tree(|t| t.announce(text, true));
}
pub fn share_support() -> Support {
with_tree(|t| t.share_support())
}
pub fn share_url(url: &str, title: &str) -> bool {
with_tree(|t| t.share_url(url, title))
}
pub fn application_handlers(
query: &day_spec::applications::HandlerQuery,
) -> Result<day_spec::applications::ApplicationHandlers, day_spec::applications::ApplicationError> {
with_tree(|t| t.application_handlers(query))
}
pub fn default_browser()
-> Result<Option<day_spec::applications::Application>, day_spec::applications::ApplicationError> {
application_handlers(&day_spec::applications::HandlerQuery::Scheme(
"https".into(),
))
.map(|h| h.default)
}
pub async fn open_url_with_application(
url: &str,
application: &str,
) -> Result<(), day_spec::applications::ApplicationError> {
let (send, receive) = day_async::oneshot();
with_tree(|t| {
t.open_url_with_application(url, application, Box::new(move |result| send.send(result)))
});
receive
.await
.unwrap_or(Err(day_spec::applications::ApplicationError::LaunchFailed))
}
pub fn invalidate_size(node: RNode) {
with_tree(|t| {
t.mark_needs_measure(node);
t.mark_layout_dirty();
});
}
pub fn try_with_tree<R>(f: impl FnOnce(&mut dyn TreeOps) -> R) -> Option<R> {
let r = TREE.with(|t| {
let mut opt = t.try_borrow_mut().ok()?;
let ops = opt.as_mut()?;
Some(f(ops.as_mut()))
});
if r.is_some() {
if PUMP_PENDING.with(|c| c.replace(false)) {
pump_events();
}
run_deferred();
}
r
}
pub(crate) fn with_tree_if_free<R>(f: impl FnOnce(&mut dyn TreeOps) -> R) -> Option<R> {
TREE.with(|t| {
let mut opt = t.try_borrow_mut().ok()?;
let ops = opt.as_mut()?;
Some(f(ops.as_mut()))
})
}
pub(crate) fn tree_absent() -> bool {
TREE.try_with(|t| t.try_borrow().map(|o| o.is_none()).unwrap_or(false))
.unwrap_or(true)
}
pub(crate) fn request_pump() {
PUMP_PENDING.with(|c| c.set(true));
}
pub fn has_tree() -> bool {
!tree_absent()
}
pub fn enqueue_event(id: NodeId, ev: Event) {
enqueue_events([(id, ev)]);
}
pub(crate) fn enqueue_native_event(id: NodeId, ev: Event) {
if let Some(ev) = crate::list::translate_native_event(id, ev) {
enqueue_event(id, ev);
}
}
pub fn enqueue_events(evs: impl IntoIterator<Item = (NodeId, Event)>) {
let observer = EVENT_OBSERVER.with(|o| o.borrow().clone());
match observer {
Some(obs) => {
let batch: Vec<(NodeId, Event)> = evs.into_iter().collect();
for (id, ev) in &batch {
obs(*id, ev);
}
EVENTS.with(|e| e.borrow_mut().extend(batch));
}
None => EVENTS.with(|e| e.borrow_mut().extend(evs)),
}
let tree_free = TREE.with(|t| t.try_borrow_mut().is_ok());
if tree_free {
pump_events();
} else {
PUMP_PENDING.with(|c| c.set(true));
}
}
pub fn pump_generation() -> u64 {
PUMP_GEN.with(|g| g.get())
}
pub fn set_event_observer(observer: Option<Box<EventObserver>>) {
EVENT_OBSERVER.with(|o| *o.borrow_mut() = observer.map(Rc::from));
}
pub fn id_of(node: NodeId) -> Option<String> {
try_with_tree(|t| t.node_id(id_to_rnode(node))).flatten()
}
pub fn label_of(node: NodeId) -> Option<String> {
try_with_tree(|t| {
let rnode = id_to_rnode(node);
let a11y = t
.node_a11y(rnode)
.and_then(|a| a.label)
.filter(|s| !s.is_empty());
a11y.or_else(|| {
t.node_probe(rnode)
.map(|p| p.text)
.filter(|s| !s.is_empty())
})
})
.flatten()
}
pub fn pump_events() {
if PUMP_ACTIVE.with(|active| active.replace(true)) {
return;
}
struct PumpGuard;
impl Drop for PumpGuard {
fn drop(&mut self) {
PUMP_ACTIVE.with(|active| active.set(false));
}
}
let _guard = PumpGuard;
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
loop {
pump_events_inner();
if EVENTS.with(|events| events.borrow().is_empty()) {
break;
}
}
}));
if let Err(payload) = result {
let msg = payload
.downcast_ref::<&str>()
.map(|s| (*s).to_string())
.or_else(|| payload.downcast_ref::<String>().cloned())
.unwrap_or_else(|| "unknown panic".to_string());
log::warn!(
"a native event handler panicked and was contained — the app continues, but \
reactive/UI state may be inconsistent until the next interaction. Cause: {msg}"
);
EVENTS.with(|e| e.borrow_mut().clear());
PUMP_PENDING.with(|c| c.set(false));
day_reactive::recover_from_panic();
crate::notify_contained_panic();
}
}
fn pump_events_inner() {
crate::image::flush_deferred_releases();
PUMP_GEN.with(|g| g.set(g.get() + 1));
crate::nav::maybe_notify_route_change();
loop {
let item = EVENTS.with(|e| e.borrow_mut().pop_front());
let Some((id, ev)) = item else { break };
if let Event::PresentResult { req, result } = ev {
crate::present::resolve_presentation(req, result);
continue;
}
if let Event::ImageDecoded { req, result } = ev {
crate::image::resolve_image_decode(req, result);
continue;
}
if let Event::ImageEncoded { req, result } = ev {
crate::image::resolve_image_encode(req, result);
continue;
}
if let Event::BitmapRendered { req, result } = ev {
crate::image::resolve_bitmap_render(req, result);
continue;
}
if let Event::PdfRendered { req, result } = ev {
crate::image::resolve_pdf_render(req, result);
continue;
}
if let Event::MenuAction(action) = ev {
crate::menu::dispatch_menu_action(action);
continue;
}
if let Event::ToolbarChanged { action, value } = &ev {
crate::toolbar::dispatch_toolbar_value(*action, value);
continue;
}
if let Event::Lifecycle(phase) = ev {
crate::lifecycle::dispatch_lifecycle(phase);
continue;
}
if ev == Event::FocusChanged(false) {
let paired = EVENTS.with(|e| {
let mut q = e.borrow_mut();
let gain = q
.iter()
.position(|(gid, gev)| *gid != id && *gev == Event::FocusChanged(true));
gain.map(|i| q.remove(i).expect("indexed event"))
});
if let Some((gid, gev)) = paired {
dispatch_focus_probe(gid, &gev);
dispatch_to_node(gid, &gev);
}
}
if let Event::FocusChanged(_) = ev {
dispatch_focus_probe(id, &ev);
}
dispatch_to_node(id, &ev);
}
day_reactive::flush_sync();
crate::nav::maybe_notify_route_change();
}
pub fn focused_node() -> Option<RNode> {
FOCUSED.with(|f| f.get())
}
fn dispatch_focus_probe(id: NodeId, ev: &Event) {
if let Event::FocusChanged(f) = ev {
let node = id_to_rnode(id);
with_tree(|t| t.set_probe_focused(node, *f));
}
}
pub fn scroll_to(node: RNode, target: ScrollTarget) {
with_tree(|t| {
t.scroll_to_target(node, &target, true);
});
}
fn dispatch_to_node(id: NodeId, ev: &Event) {
let node = if id == day_spec::WINDOW_NODE {
with_tree(|t| t.root_node())
} else {
id_to_rnode(id)
};
let gesture = match ev {
Event::Tap(_) => Some(day_spec::GestureKind::Tap),
Event::LongPress(_) => Some(day_spec::GestureKind::LongPress),
Event::Drag { .. } => Some(day_spec::GestureKind::Drag),
Event::Pan { .. } => Some(day_spec::GestureKind::Pan),
Event::Pinch { .. } => Some(day_spec::GestureKind::Pinch),
Event::Hover { .. } => Some(day_spec::GestureKind::Hover),
_ => None,
};
let node = match gesture.and_then(|k| with_tree(|t| t.gesture_target(node, k))) {
Some(target) => target,
None => node,
};
let mut handlers = with_tree(|t| t.handlers_for(node));
if matches!(ev, Event::Key(_) | Event::FocusChanged(_)) {
with_tree(|t| {
for w in t.wrappers_above(node) {
handlers.extend(t.handlers_for(w));
}
});
}
if handlers.is_empty() {
if cfg!(debug_assertions)
&& matches!(ev, Event::Pressed | Event::Tap(_))
&& !with_tree(|t| t.node_exists(node))
{
log::warn!("{ev:?} dropped — node {id:?} no longer exists (stale reference)");
}
return;
}
day_reactive::batch(|| {
for h in &handlers {
h(ev);
}
});
}
fn collect_menu_families(
items: &[day_spec::MenuItem],
families: &mut std::collections::BTreeSet<String>,
) {
for item in items {
match item {
day_spec::MenuItem::Action { id: Some(id), .. } => {
families.insert(crate::element_id::family_of(id).to_owned());
}
day_spec::MenuItem::Submenu { items, .. } => collect_menu_families(items, families),
_ => {}
}
}
}
#[cfg(test)]
mod thread_exit_tests {
use super::*;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
struct Probe(Arc<AtomicBool>);
impl Drop for Probe {
fn drop(&mut self) {
self.0.store(true, Ordering::SeqCst);
}
}
impl Toolkit for Probe {
type Handle = ();
fn realize(&mut self, _: PieceKind, _: &dyn Any, _: NodeId) -> Self::Handle {}
fn release(&mut self, _: Self::Handle) {}
fn insert(&mut self, _: &Self::Handle, _: &Self::Handle, _: usize) {}
fn remove(&mut self, _: &Self::Handle, _: &Self::Handle) {
panic!("test probe has no attached children to remove");
}
fn move_child(&mut self, _: &Self::Handle, _: &Self::Handle, _: usize) {}
fn measure(&mut self, _: &Self::Handle, _: PieceKind, _: Proposal) -> Size {
Size::new(0.0, 0.0)
}
fn set_frame(&mut self, _: &Self::Handle, _: Rect, _: Option<&AnimSpec>) {}
fn set_input_traits(&mut self, _: &Self::Handle, _: &InputTraits) -> Option<Self::Handle> {
None
}
fn set_event_sink(&mut self, _: EventSink) {}
fn update(&mut self, _: &Self::Handle, _: &'static str, _: &dyn Any, _: Option<&AnimSpec>) {
}
}
fn install_probe(dropped: &Arc<AtomicBool>) {
let tree = Tree::new(Probe(dropped.clone()), (), Size::new(0.0, 0.0));
install_tree(Box::new(tree));
}
#[test]
fn the_tree_is_not_dropped_when_its_thread_ends() {
let dropped = Arc::new(AtomicBool::new(false));
let d = dropped.clone();
std::thread::spawn(move || install_probe(&d))
.join()
.expect("thread");
assert!(
!dropped.load(Ordering::SeqCst),
"the thread-local destructor dropped the tree, and with it the native widgets"
);
}
#[test]
fn uninstall_still_drops_the_tree() {
let dropped = Arc::new(AtomicBool::new(false));
let d = dropped.clone();
std::thread::spawn(move || {
install_probe(&d);
uninstall_tree();
assert!(
d.load(Ordering::SeqCst),
"uninstall_tree must drop the tree"
);
})
.join()
.expect("thread");
}
#[test]
fn removing_an_unattached_node_releases_without_native_detach() {
let dropped = Arc::new(AtomicBool::new(false));
let mut tree = Tree::new(Probe(dropped), (), Size::new(0.0, 0.0));
let scope = Scope::detached();
let node = tree.create_node(
"fixture.detached",
&(),
Rc::new(crate::LeafLayout),
Flex::default(),
true,
false,
scope,
);
tree.remove_subtree(node);
assert!(!tree.node_exists(node));
assert_eq!(
tree.release_queue.len(),
1,
"the unattached native handle must still be released"
);
scope.dispose();
}
#[test]
fn a_post_during_a_tree_borrow_runs_at_the_safe_point() {
std::thread::spawn(|| {
install_probe(&Arc::new(AtomicBool::new(false)));
let ran = Arc::new(AtomicBool::new(false));
let r = ran.clone();
with_tree(|_| {
run_posted_or_defer(Box::new(move || {
with_tree(|_| r.store(true, Ordering::SeqCst));
}));
assert!(!ran.load(Ordering::SeqCst), "ran inside the borrow");
});
assert!(
ran.load(Ordering::SeqCst),
"never ran after the borrow was released"
);
let now = Arc::new(AtomicBool::new(false));
let n = now.clone();
run_posted_or_defer(Box::new(move || n.store(true, Ordering::SeqCst)));
assert!(
now.load(Ordering::SeqCst),
"a post with the tree free must run at once"
);
uninstall_tree();
})
.join()
.expect("thread");
}
}