mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
130 lines
4.5 KiB
Rust
130 lines
4.5 KiB
Rust
//! Selection pane: picking objects from the list, Show All and Hide All. Its other actions are
|
|
//! ordinary commands: `shape.visible` (eye), `shape.lock` (padlock), `shape.rename`,
|
|
//! `arrange.bringForward` / `arrange.sendBackward` (arrows) and `arrange.reorder` (drag and drop).
|
|
|
|
use deckcraft_model::{Shape, ShapeId};
|
|
use serde_json::{Value, json};
|
|
|
|
use super::*;
|
|
use crate::{Result, Session};
|
|
|
|
pub fn specs() -> Vec<CommandSpec> {
|
|
vec![
|
|
cmd!(
|
|
"selection.select",
|
|
"Select",
|
|
["Selection Pane"],
|
|
None,
|
|
"{ids | id, toggle?: bool (Ctrl+click), add?: bool} → {selected}: objects in groups too; a hidden object picked from the list is shown",
|
|
has_slide,
|
|
select
|
|
),
|
|
cmd!("selection.showAll", "Show All", ["Selection Pane"], None, "{} shows every object on the slide, inside groups too", has_slide, show_all),
|
|
cmd!("selection.hideAll", "Hide All", ["Selection Pane"], None, "{} hides every top-level object on the slide", has_slide, hide_all),
|
|
]
|
|
}
|
|
|
|
/// The Selection pane's click. Objects inside groups and top-level objects can't be selected
|
|
/// together (Ctrl+click on the other kind is ignored, as in PowerPoint), and picking a hidden
|
|
/// object from the list shows it again.
|
|
fn select(s: &mut Session, p: &Value) -> Result<Value> {
|
|
let ids = match ids_param(p, "ids") {
|
|
Some(ids) => ids,
|
|
None => id_param(p, "id", "selection.select")?.map(|i| vec![i]).unwrap_or_default(),
|
|
};
|
|
let (toggle, add) = (bool_or(p, "toggle", false), bool_or(p, "add", false));
|
|
let st = s.doc()?;
|
|
let in_group = |id: &ShapeId| deckcraft_model::parent_of(st.shapes(), *id).map(|p| p.is_some());
|
|
let mut valid: Vec<ShapeId> = ids.iter().copied().filter(|i| in_group(i).is_some()).collect();
|
|
if valid.is_empty() {
|
|
return Err(bad("selection.select", "no such shape"));
|
|
}
|
|
let kind = valid.first().and_then(in_group);
|
|
valid.retain(|i| in_group(i) == kind);
|
|
let keep: Vec<ShapeId> = if toggle || add {
|
|
let current = st.selection.shapes.clone();
|
|
if current.first().is_some_and(|c| in_group(c) != kind) {
|
|
// Can't mix objects in groups with top-level ones: nothing changes.
|
|
return Ok(json!({"selected": current}));
|
|
}
|
|
current
|
|
} else {
|
|
vec![]
|
|
};
|
|
let mut next = keep;
|
|
for i in &valid {
|
|
match next.iter().position(|x| x == i) {
|
|
Some(k) if toggle => {
|
|
next.remove(k);
|
|
}
|
|
Some(_) => {}
|
|
None => next.push(*i),
|
|
}
|
|
}
|
|
let reveal: Vec<ShapeId> = next.iter().copied().filter(|i| valid.contains(i) && st.shape(*i).is_some_and(|x| x.hidden)).collect();
|
|
if reveal.is_empty() {
|
|
s.select(|_, sel| {
|
|
sel.text = None;
|
|
sel.cells = None;
|
|
sel.shapes = next.clone();
|
|
})?;
|
|
} else {
|
|
s.edit(|doc, sel| {
|
|
let shapes = crate::shapes_mut(doc, sel).ok_or_else(|| bad("selection.select", "no slide"))?;
|
|
for id in &reveal {
|
|
if let Some(sh) = deckcraft_model::find_shape_mut(shapes, *id) {
|
|
sh.hidden = false;
|
|
}
|
|
}
|
|
sel.text = None;
|
|
sel.cells = None;
|
|
sel.shapes = next.clone();
|
|
Ok(())
|
|
})?;
|
|
}
|
|
Ok(json!({"selected": s.doc()?.selection.shapes}))
|
|
}
|
|
|
|
fn show_all(s: &mut Session, _p: &Value) -> Result<Value> {
|
|
fn any_hidden(v: &[Shape]) -> bool {
|
|
let mut found = false;
|
|
deckcraft_model::walk(v, &mut |sh, _| found |= sh.hidden);
|
|
found
|
|
}
|
|
if !any_hidden(s.doc()?.shapes()) {
|
|
return ok();
|
|
}
|
|
s.edit(|doc, sel| {
|
|
fn rec(v: &mut [Shape], depth: usize) {
|
|
for sh in v {
|
|
sh.hidden = false;
|
|
if depth < 64
|
|
&& let Some(c) = sh.children_mut()
|
|
{
|
|
rec(c, depth + 1);
|
|
}
|
|
}
|
|
}
|
|
rec(crate::shapes_mut(doc, sel).ok_or_else(|| bad("selection.showAll", "no slide"))?, 0);
|
|
Ok(())
|
|
})?;
|
|
ok()
|
|
}
|
|
|
|
fn hide_all(s: &mut Session, _p: &Value) -> Result<Value> {
|
|
if s.doc()?.shapes().iter().all(|x| x.hidden) {
|
|
return ok();
|
|
}
|
|
s.edit(|doc, sel| {
|
|
for sh in crate::shapes_mut(doc, sel).ok_or_else(|| bad("selection.hideAll", "no slide"))? {
|
|
sh.hidden = true;
|
|
}
|
|
Ok(())
|
|
})?;
|
|
ok()
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "selection_tests.rs"]
|
|
mod tests;
|