use crate::tree::{RNode, try_with_tree, with_tree};
use day_reactive::Scope;
use day_spec::{MoveVerdict, TreeSource, props::RowHeight};
use std::collections::HashMap;
use std::rc::Rc;
pub struct TreeDriver {
pub row_height: RowHeight,
pub children_len: Box<dyn Fn(Option<u64>) -> usize>,
pub child_token: Box<dyn Fn(Option<u64>, usize) -> u64>,
pub expandable: Box<dyn Fn(u64) -> bool>,
pub section_header: Box<dyn Fn(u64) -> bool>,
pub expanded: Box<dyn Fn(u64) -> bool>,
pub build: Box<dyn Fn(u64, RNode) -> TreeBuiltRow>,
pub type_select_text: Box<dyn Fn(u64) -> String>,
pub resolve_row: RowResolver,
pub row_menu: Option<RowMenuFn>,
pub moves: Option<TreeMovesDriver>,
}
pub type RowResolver = Box<dyn Fn(&str) -> Option<u64>>;
pub type RowMenuFn = Box<dyn Fn(u64) -> Vec<day_spec::MenuItem>>;
pub struct TreeMovesDriver {
#[allow(clippy::type_complexity)]
pub can_move: Box<dyn Fn(u64, Option<u64>, Option<usize>) -> MoveVerdict>,
#[allow(clippy::type_complexity)]
pub moved: Box<dyn Fn(u64, Option<u64>, Option<usize>)>,
}
pub struct TreeBuiltRow {
pub scope: Scope,
pub rebind: Rc<dyn Fn(u64)>,
}
pub(crate) struct TreeBoundCell {
pub anchor: RNode,
pub scope: Scope,
pub rebind: Rc<dyn Fn(u64)>,
pub native_width: Option<f64>,
pub native_height: Option<f64>,
pub laid_width: Option<f64>,
pub laid_height: Option<f64>,
}
pub(crate) struct TreeState {
pub driver: Rc<TreeDriver>,
pub cells: HashMap<usize, TreeBoundCell>,
}
pub enum TreeCellStep {
Build {
anchor: RNode,
},
Rebind {
rebind: Rc<dyn Fn(u64)>,
anchor: RNode,
},
}
pub fn install_tree(node: RNode, driver: TreeDriver) {
with_tree(|t| t.install_tree(node, driver));
}
pub fn tree_reload(node: RNode) {
with_tree(|t| t.tree_reload(node));
}
pub fn tree_set_expanded(node: RNode, token: u64, expanded: bool) {
with_tree(|t| t.tree_set_expanded(node, token, expanded));
}
pub fn tree_set_selected(node: RNode, tokens: Vec<u64>) {
with_tree(|t| t.tree_set_selected(node, tokens));
}
pub fn tree_reveal(node: RNode, token: u64) {
with_tree(|t| t.tree_reveal(node, token));
}
pub fn tree_driver(node: RNode) -> Option<Rc<TreeDriver>> {
with_tree(|t| t.tree_driver(node))
}
pub fn tree_try_move(
node: RNode,
token: u64,
parent: Option<u64>,
index: Option<usize>,
) -> Result<(), &'static str> {
let driver = tree_driver(node).ok_or("no tree at this node")?;
let Some(mv) = driver.moves.as_ref() else {
return Err("tree is not movable");
};
match (mv.can_move)(token, parent, index) {
MoveVerdict::Deny => Err("move denied by the guard"),
MoveVerdict::Allow => {
(mv.moved)(token, parent, index);
Ok(())
}
}
}
pub fn tree_visible_rows(driver: &TreeDriver) -> Vec<(u64, u16)> {
let mut out = Vec::new();
let mut stack: Vec<(Option<u64>, u16)> = vec![(None, 0)];
while let Some((parent, depth)) = stack.pop() {
if let Some(tok) = parent {
out.push((tok, depth - 1));
if !(driver.expandable)(tok) || !(driver.expanded)(tok) {
continue;
}
}
let n = (driver.children_len)(parent);
for i in (0..n).rev() {
stack.push((Some((driver.child_token)(parent, i)), depth + 1));
}
}
out
}
pub(crate) fn make_tree_source(node: RNode, driver: Rc<TreeDriver>) -> TreeSource {
let (d_len, d_tok, d_exp, d_type, d_menu, d_moves, d_bind) = (
driver.clone(),
driver.clone(),
driver.clone(),
driver.clone(),
driver.clone(),
driver.clone(),
driver,
);
let d_header = d_exp.clone();
let d_open = d_exp.clone();
TreeSource {
section_header: Rc::new(move |t| (d_header.section_header)(t)),
expanded: Rc::new(move |t| (d_open.expanded)(t)),
children_len: Rc::new(move |p| (d_len.children_len)(p)),
child_token: Rc::new(move |p, i| (d_tok.child_token)(p, i)),
expandable: Rc::new(move |t| (d_exp.expandable)(t)),
type_select_text: Rc::new(move |t| (d_type.type_select_text)(t)),
row_menu: d_menu.row_menu.as_ref().map(|_| {
let d = d_menu.clone();
Rc::new(move |t: u64| (d.row_menu.as_ref().expect("mapped from Some"))(t))
as Rc<dyn Fn(u64) -> Vec<day_spec::MenuItem>>
}),
bind_row: Rc::new(move |token, cell| {
let key = cell as usize;
let Some(step) = try_with_tree(|t| t.tree_prepare_cell(node, key, cell)) else {
return;
};
match step {
TreeCellStep::Build { anchor } => {
let built = (d_bind.build)(token, anchor);
with_tree(|t| t.tree_store_cell(node, key, anchor, built));
}
TreeCellStep::Rebind { rebind, .. } => rebind(token),
}
day_reactive::flush_sync();
with_tree(|t| t.tree_layout_cell(node, key));
}),
recycle: Rc::new(move |cell| {
let _ = try_with_tree(|t| t.tree_recycle_cell(node, cell as usize));
}),
layout_cell: Rc::new(move |cell, width| {
let _ = try_with_tree(|t| t.tree_layout_cell_width(node, cell as usize, width));
}),
layout_cell_size: Rc::new(move |cell, size| {
let _ = try_with_tree(|t| t.tree_layout_cell_size(node, cell as usize, size));
}),
cell_height: Rc::new(move |cell| {
try_with_tree(|t| t.tree_cell_height(node, cell as usize)).unwrap_or(0.0)
}),
moves: d_moves.moves.as_ref().map(|_| day_spec::TreeMoves {
can_move: {
let d = d_moves.clone();
Rc::new(move |tok, parent, idx| {
(d.moves.as_ref().expect("mapped from Some").can_move)(tok, parent, idx)
})
},
move_node: {
let d = d_moves.clone();
Rc::new(move |tok, parent, idx| {
(d.moves.as_ref().expect("mapped from Some").moved)(tok, parent, idx)
})
},
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn fixture(open: &'static [u64]) -> TreeDriver {
fn kids(parent: Option<u64>) -> &'static [u64] {
match parent {
None => &[1, 5],
Some(1) => &[2, 3],
Some(3) => &[4],
_ => &[],
}
}
TreeDriver {
section_header: Box::new(|_| false),
row_height: RowHeight::Automatic,
children_len: Box::new(|p| kids(p).len()),
child_token: Box::new(|p, i| kids(p)[i]),
expandable: Box::new(|t| !kids(Some(t)).is_empty()),
expanded: Box::new(move |t| open.contains(&t)),
build: Box::new(|_, _| unreachable!("flattener never builds")),
type_select_text: Box::new(|_| String::new()),
resolve_row: Box::new(|_| None),
row_menu: None,
moves: None,
}
}
#[test]
fn flattener_descends_only_into_expanded_rows() {
assert_eq!(tree_visible_rows(&fixture(&[])), [(1, 0), (5, 0)]);
assert_eq!(
tree_visible_rows(&fixture(&[1])),
[(1, 0), (2, 1), (3, 1), (5, 0)]
);
assert_eq!(
tree_visible_rows(&fixture(&[1, 3])),
[(1, 0), (2, 1), (3, 1), (4, 2), (5, 0)]
);
assert_eq!(tree_visible_rows(&fixture(&[3])), [(1, 0), (5, 0)]);
}
}