//! Where new nodes go on the canvas: next to their parent or sibling, in a //! free spot. Nothing else moves to make room. use std::collections::BTreeMap; use crate::model::{CanvasPos, CanvasRect, CanvasSize, Document, NodeId}; /// Columns between a parent and its children: room for the branch lines. const BRANCH_GAP: i32 = 6; /// Rows kept free between boxes stacked one above another. const ROW_GAP: i32 = 1; /// Where a new last child of `parent` goes: to the right of the parent, below /// its last child (level with the parent for a first child), then further /// down until it's clear of every other box. pub fn child_spot( doc: &Document, parent: NodeId, size: CanvasSize, rects: &BTreeMap, ) -> CanvasPos { let Some(&parent_rect) = rects.get(&parent) else { return CanvasPos::default() }; match doc.children(parent).last().and_then(|child| rects.get(child)) { Some(last) => free_spot(CanvasPos::new(last.left(), last.bottom() + ROW_GAP), size, rects), None => first_child_spot(parent_rect, size, rects), } } /// Where the first child of a box goes: to its right, level with it, then /// further down until it's clear. The box doesn't need to be in `rects` yet, /// so this works for a parent that's being added in the same edit. pub fn first_child_spot( parent: CanvasRect, size: CanvasSize, rects: &BTreeMap, ) -> CanvasPos { free_spot(CanvasPos::new(parent.right() + BRANCH_GAP, parent.top()), size, rects) } /// Where a new node just after `sibling` goes: right below it, then further /// down until it's clear. pub fn below_spot(sibling: NodeId, size: CanvasSize, rects: &BTreeMap) -> CanvasPos { let Some(&rect) = rects.get(&sibling) else { return CanvasPos::default() }; spot_below(rect, size, rects) } /// Where a box goes below another box: right below it, then further down /// until it's clear. The box above doesn't need to be in `rects`. pub fn spot_below(above: CanvasRect, size: CanvasSize, rects: &BTreeMap) -> CanvasPos { free_spot(CanvasPos::new(above.left(), above.bottom() + ROW_GAP), size, rects) } /// The first spot at `start` or below it where a box of `size` keeps clear of /// every box in `rects` (by a row above and below). pub fn free_spot(start: CanvasPos, size: CanvasSize, rects: &BTreeMap) -> CanvasPos { let mut spot = start; // Each step jumps below the box in the way, and no box is in the way // twice, so there are at most as many steps as boxes. for _ in 0..=rects.len() { let wanted = CanvasRect::new( CanvasPos::new(spot.x, spot.y - ROW_GAP), CanvasSize::new(size.width, size.height + 2 * ROW_GAP as u16), ); match rects.values().filter(|rect| rect.overlaps(wanted)).map(|rect| rect.bottom()).max() { Some(bottom) => spot.y = bottom + ROW_GAP, None => break, } } spot } #[cfg(test)] mod tests { use super::*; use crate::model::test_support::garden; use crate::ui::glyphs::Glyphs; use crate::view::canvas::boxes::box_rects; #[test] fn new_nodes_find_a_free_spot_nearby() { let (doc, g) = garden(); let rects = box_rects(&doc, 30, Glyphs::Unicode); let size = CanvasSize::new(10, 3); assert_eq!(child_spot(&doc, g.root, size, &rects), CanvasPos::new(22, 12), "below the last child"); assert_eq!( child_spot(&doc, g.cedar, size, &rects), CanvasPos::new(55, 4), "first child: level with it" ); assert_eq!( below_spot(g.soil, size, &rects), CanvasPos::new(22, 12), "Raised beds and Watering are in the way" ); } }