diff --git a/crates/engine/src/cmd/data.rs b/crates/engine/src/cmd/data.rs index 721357fec..4828edeba 100644 --- a/crates/engine/src/cmd/data.rs +++ b/crates/engine/src/cmd/data.rs @@ -1454,6 +1454,34 @@ fn show_levels(s: &mut Session, p: &Json) -> Result { /// Data ▸ Outline ▸ Settings: whether summary rows sit below their detail and summary columns /// right of it. A changed direction moves each collapsed group's mark (+) to its new summary line. +/// Moves the collapsed marks of a line axis to the summary line on its new side (`on` is the new +/// setting, `was_before` the old one). A line that nested groups share keeps its mark on the outermost +/// group whose detail is all hidden, or the outermost when none is. +fn move_marks(map: &mut std::collections::BTreeMap, was_before: bool, on: bool) { + let mut marked: Vec = + outline::groups_with(map, was_before).into_iter().filter(|g| g.has_summary() && map.get(&g.summary()).is_some_and(|e| e.collapsed)).collect(); + // A mark on a line that nested groups share moves with the outermost of them whose + // detail is all hidden, or with the outermost when none is. + let mut owner: std::collections::BTreeMap = std::collections::BTreeMap::new(); + for g in &marked { + let key = (!g.collapsed, g.level); + let best = owner.entry(g.summary()).or_insert(key); + *best = (*best).min(key); + } + marked.retain(|g| owner.get(&g.summary()) == Some(&(!g.collapsed, g.level))); + for g in &marked { + if let Some(e) = map.get_mut(&g.summary()) { + e.collapsed = false; + } + } + for g in marked { + let g = outline::OutlineGroup { summary_before: !on, ..g }; + if g.has_summary() { + map.entry(g.summary()).or_default().collapsed = true; + } + } +} + fn outline_settings(s: &mut Session, p: &Json) -> Result { let si = target_sheet(s, p)?; edit(s, |cx| { @@ -1469,31 +1497,7 @@ fn outline_settings(s: &mut Session, p: &Json) -> Result { if was_before != on { continue; } - let map = if rows { &mut sh.rows } else { &mut sh.cols }; - let mut marked: Vec = outline::groups_with(map, was_before) - .into_iter() - .filter(|g| g.has_summary() && map.get(&g.summary()).is_some_and(|e| e.collapsed)) - .collect(); - // A mark on a line that nested groups share moves with the outermost of them whose - // detail is all hidden, or with the outermost when none is. - let mut owner: std::collections::BTreeMap = std::collections::BTreeMap::new(); - for g in &marked { - let key = (!g.collapsed, g.level); - let best = owner.entry(g.summary()).or_insert(key); - *best = (*best).min(key); - } - marked.retain(|g| owner.get(&g.summary()) == Some(&(!g.collapsed, g.level))); - for g in &marked { - if let Some(e) = map.get_mut(&g.summary()) { - e.collapsed = false; - } - } - for g in marked { - let g = outline::OutlineGroup { summary_before: !on, ..g }; - if g.has_summary() { - map.entry(g.summary()).or_default().collapsed = true; - } - } + move_marks(if rows { &mut sh.rows } else { &mut sh.cols }, was_before, on); } Ok(json!({"summaryBelow": sh.summary_below, "summaryRight": sh.summary_right})) }) diff --git a/crates/ui-egui/src/outline.rs b/crates/ui-egui/src/outline.rs index 9096d1611..d192cee22 100644 --- a/crates/ui-egui/src/outline.rs +++ b/crates/ui-egui/src/outline.rs @@ -165,6 +165,35 @@ impl Dir<'_> { } /// The band's buttons and brackets for this frame. +/// The bracket for one group's detail, and its tick, on the lane `across`; `button` is the group's +/// summary button when it is on screen. +fn group_bracket(out: &mut Layout, dir: &Dir, grp: &OutlineGroup, button: Option, across: f32, side: f32) { + let first = dir.first_visible(grp.start, grp.end); + let Some((first_line, (a0, _))) = first else { return }; + if grp.summary_before { + // From the button down (or across) to the last detail line, with the tick there. + let a0 = button.map_or(a0, |mid| mid + side / 2.0); + let end = dir.start(grp.end) + dir.size(grp.end); + let a1 = end.min(dir.hi); + if a1 <= a0 { + return; + } + let tick = (end <= dir.hi).then(|| [dir.point(a1 - 0.5, across), dir.point(a1 - 0.5, across + side / 2.0)]); + out.brackets.push(Bracket { line: [dir.point(a0, across), dir.point(a1 - 0.5, across)], tick }); + return; + } + let a1 = match button { + Some(mid) => mid - side / 2.0, + None => dir.start(grp.summary()).min(dir.hi), + }; + if a1 <= a0 { + return; + } + let start_shown = first_line == grp.start && (a0 - dir.start(grp.start)).abs() < 0.5; + let tick = start_shown.then(|| [dir.point(a0 + 0.5, across), dir.point(a0 + 0.5, across + side / 2.0)]); + out.brackets.push(Bracket { line: [dir.point(a0 + 0.5, across), dir.point(a1, across)], tick }); +} + pub fn layout(geo: &Geo, sh: &Sheet) -> Layout { let mut out = Layout::default(); let zoom = (geo.z / DISPLAY_SCALE).max(0.1); @@ -212,30 +241,7 @@ pub fn layout(geo: &Geo, sh: &Sheet) -> Layout { continue; } // The bracket, over the part of the detail that is on screen. - let first = dir.first_visible(grp.start, grp.end); - let Some((first_line, (a0, _))) = first else { continue }; - if grp.summary_before { - // From the button down (or across) to the last detail line, with the tick there. - let a0 = button.map_or(a0, |mid| mid + side / 2.0); - let end = dir.start(grp.end) + dir.size(grp.end); - let a1 = end.min(dir.hi); - if a1 <= a0 { - continue; - } - let tick = (end <= dir.hi).then(|| [dir.point(a1 - 0.5, across), dir.point(a1 - 0.5, across + side / 2.0)]); - out.brackets.push(Bracket { line: [dir.point(a0, across), dir.point(a1 - 0.5, across)], tick }); - continue; - } - let a1 = match button { - Some(mid) => mid - side / 2.0, - None => dir.start(grp.summary()).min(dir.hi), - }; - if a1 <= a0 { - continue; - } - let start_shown = first_line == grp.start && (a0 - dir.start(grp.start)).abs() < 0.5; - let tick = start_shown.then(|| [dir.point(a0 + 0.5, across), dir.point(a0 + 0.5, across + side / 2.0)]); - out.brackets.push(Bracket { line: [dir.point(a0 + 0.5, across), dir.point(a1, across)], tick }); + group_bracket(&mut out, dir, grp, button, across, side); } } out