pub(crate) struct SubtreeOps<'a> {
pub(crate) visible_count: &'a dyn Fn() -> usize,
pub(crate) row: &'a dyn Fn(usize) -> Option<(usize, bool, bool)>,
pub(crate) set_expanded: &'a dyn Fn(usize, bool),
}
impl SubtreeOps<'_> {
fn depth_of(&self, index: usize) -> Option<usize> {
(self.row)(index).map(|(d, _, _)| d)
}
}
pub(crate) fn expand_subtree(ops: &SubtreeOps, row: usize) -> bool {
let Some((base_depth, has_children, _)) = (ops.row)(row) else {
return false;
};
if !has_children {
return false;
}
(ops.set_expanded)(row, true);
let mut i = row + 1;
while i < (ops.visible_count)() {
let Some((depth, child_has_children, expanded)) = (ops.row)(i) else {
break;
};
if depth <= base_depth {
break; }
if child_has_children && !expanded {
(ops.set_expanded)(i, true);
}
i += 1;
}
true
}
pub(crate) fn collapse_subtree(ops: &SubtreeOps, row: usize) -> bool {
let Some((base_depth, has_children, _)) = (ops.row)(row) else {
return false;
};
if !has_children {
return false;
}
while let Some(target) = last_expanded_descendant(ops, row, base_depth) {
(ops.set_expanded)(target, false);
}
(ops.set_expanded)(row, false);
true
}
fn last_expanded_descendant(ops: &SubtreeOps, row: usize, base_depth: usize) -> Option<usize> {
let mut found = None;
let mut i = row + 1;
while i < (ops.visible_count)() {
let Some((depth, has_children, expanded)) = (ops.row)(i) else {
break;
};
if depth <= base_depth {
break;
}
if has_children && expanded {
found = Some(i);
}
i += 1;
}
found
}
pub(crate) fn first_child(ops: &SubtreeOps, row: usize) -> Option<usize> {
let (depth, has_children, expanded) = (ops.row)(row)?;
if !has_children || !expanded {
return None;
}
let child = row + 1;
(child < (ops.visible_count)() && ops.depth_of(child) == Some(depth + 1)).then_some(child)
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
struct FakeTree {
nodes: Vec<(usize, bool)>,
expanded: RefCell<Vec<bool>>,
}
impl FakeTree {
fn new(nodes: Vec<(usize, bool)>) -> Self {
let n = nodes.len();
Self {
nodes,
expanded: RefCell::new(vec![false; n]),
}
}
fn visible(&self) -> Vec<usize> {
let expanded = self.expanded.borrow();
let mut out = Vec::new();
let mut hidden_below: Option<usize> = None;
for (i, &(depth, _)) in self.nodes.iter().enumerate() {
if let Some(d) = hidden_below {
if depth > d {
continue;
}
hidden_below = None;
}
out.push(i);
if !expanded[i] {
hidden_below = Some(depth);
}
}
out
}
fn ops(
&self,
) -> (
impl Fn() -> usize,
impl Fn(usize) -> Option<(usize, bool, bool)>,
impl Fn(usize, bool),
) {
let count = || self.visible().len();
let row = move |i: usize| {
let vis = self.visible();
vis.get(i).map(|&id| {
let (depth, has_children) = self.nodes[id];
(depth, has_children, self.expanded.borrow()[id])
})
};
let set = move |i: usize, on: bool| {
let vis = self.visible();
if let Some(&id) = vis.get(i) {
self.expanded.borrow_mut()[id] = on;
}
};
(count, row, set)
}
}
fn sample() -> FakeTree {
FakeTree::new(vec![
(0, true), (1, true), (2, false), (2, false), (1, true), (2, false), (1, false), ])
}
#[test]
fn expanding_a_subtree_opens_every_descendant_in_one_press() {
let t = sample();
let (c, r, s) = t.ops();
let ops = SubtreeOps {
visible_count: &c,
row: &r,
set_expanded: &s,
};
assert!(expand_subtree(&ops, 0));
assert_eq!(t.visible(), vec![0, 1, 2, 3, 4, 5, 6], "the whole tree");
}
#[test]
fn collapsing_a_subtree_folds_the_descendants_too() {
let t = sample();
let (c, r, s) = t.ops();
let ops = SubtreeOps {
visible_count: &c,
row: &r,
set_expanded: &s,
};
expand_subtree(&ops, 0);
assert!(collapse_subtree(&ops, 0));
assert_eq!(t.visible(), vec![0]);
(ops.set_expanded)(0, true);
assert_eq!(t.visible(), vec![0, 1, 4, 6]);
}
#[test]
fn a_leaf_reports_that_it_did_nothing() {
let t = sample();
let (c, r, s) = t.ops();
let ops = SubtreeOps {
visible_count: &c,
row: &r,
set_expanded: &s,
};
expand_subtree(&ops, 0);
assert!(!expand_subtree(&ops, 6));
assert!(!collapse_subtree(&ops, 6));
}
#[test]
fn expanding_a_subtree_leaves_the_rows_outside_it_alone() {
let t = sample();
let (c, r, s) = t.ops();
let ops = SubtreeOps {
visible_count: &c,
row: &r,
set_expanded: &s,
};
(ops.set_expanded)(0, true); assert!(expand_subtree(&ops, 1));
assert_eq!(t.visible(), vec![0, 1, 2, 3, 4, 6], "b stays folded");
}
#[test]
fn right_on_an_open_node_finds_its_first_child() {
let t = sample();
let (c, r, s) = t.ops();
let ops = SubtreeOps {
visible_count: &c,
row: &r,
set_expanded: &s,
};
assert_eq!(first_child(&ops, 0), None);
(ops.set_expanded)(0, true);
assert_eq!(first_child(&ops, 0), Some(1));
assert_eq!(first_child(&ops, 3), None);
}
}