mirror of
https://github.com/storytold/gridcraft.git
synced 2026-10-29 13:16:50 -06:00
Objects: anchor modes — images/charts/shapes follow cells on sort (#5)
Excel anchors a floating object to the grid in one of three ways, stored in the file as the drawing anchor's `editAs`: "move and size with cells" (twoCellAnchor, the default), "move but don't size with cells" (oneCellAnchor / editAs="oneCell") and "don't move or size with cells" (editAs="absolute"). GridCraft had no such concept: xlsx import dropped `editAs`, export always wrote a plain twoCellAnchor, and sorting/filtering never moved an object — so pictures stayed put when their rows were reordered. Model: add `AnchorMode` on `Anchor` (serde default = MoveAndSize, so existing documents round-trip unchanged). xlsx: read the anchor kind and `editAs` into the mode; write it back out (editAs="oneCell"/"absolute" as Excel does). Engine: sorting now moves objects anchored inside the sorted body with their lines, re-fitting "move and size" objects, keeping "move but don't size" objects' size, and leaving absolute ones pinned. Structural insert/delete honours the same rule (previously it moved every object's cell unconditionally and ignored shapes). New `object.setAnchorMode` command sets an object's mode; `arrange.selectionPane` reports it. Tests: sort moves images with their rows; absolute objects stay put across a sort and a row delete; move-only keeps its size; the mode survives an xlsx round-trip; the drawing fixture parses all three anchor kinds.
This commit is contained in:
@@ -124,6 +124,15 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
)),
|
||||
cmd!("object.delete", "Delete Object", [], None, "{kind: chart|image|shape, id}", has_doc, delete_object),
|
||||
cmd!("object.move", "Move Object", [], None, "{kind: chart|image|shape, id, at?: \"C3\", dx?, dy?, width?, height?}", has_doc, move_object),
|
||||
cmd!(
|
||||
"object.setAnchorMode",
|
||||
"Object Properties",
|
||||
[],
|
||||
None,
|
||||
"{kind: chart|image|shape, id, mode: moveAndSize|moveOnly|absolute}",
|
||||
has_doc,
|
||||
set_anchor_mode
|
||||
),
|
||||
cmd!(
|
||||
"insert.link",
|
||||
"Link",
|
||||
@@ -566,6 +575,7 @@ fn insert_chart(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
dy: 0.0,
|
||||
width: f64_param(p, "width").unwrap_or(480.0) as f32,
|
||||
height: f64_param(p, "height").unwrap_or(288.0) as f32,
|
||||
mode: anchor_mode_param(p).unwrap_or_default(),
|
||||
},
|
||||
title,
|
||||
series,
|
||||
@@ -723,6 +733,7 @@ fn insert_picture(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
dy: 0.0,
|
||||
width: f64_param(p, "width").map(|v| v as f32).unwrap_or(w as f32 * scale),
|
||||
height: f64_param(p, "height").map(|v| v as f32).unwrap_or(h as f32 * scale),
|
||||
mode: anchor_mode_param(p).unwrap_or_default(),
|
||||
},
|
||||
data,
|
||||
mime,
|
||||
@@ -764,6 +775,7 @@ fn insert_shape(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
dy: 0.0,
|
||||
width: f64_param(p, "width").unwrap_or(144.0) as f32,
|
||||
height: f64_param(p, "height").unwrap_or(if kind == ShapeKind::Line { 0.0 } else { 96.0 }) as f32,
|
||||
mode: anchor_mode_param(p).unwrap_or_default(),
|
||||
},
|
||||
fill,
|
||||
line,
|
||||
@@ -822,6 +834,27 @@ fn move_object(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Sets an object's anchor mode (Excel's "Move and size with cells" / "Move but don't size with
|
||||
/// cells" / "Don't move or size with cells"), so it follows — or ignores — its rows and columns
|
||||
/// when the sheet is sorted or edited structurally.
|
||||
fn set_anchor_mode(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let id = u32_param(p, "id").ok_or_else(|| bad("object.setAnchorMode", "missing `id`"))?;
|
||||
let kind = str_param(p, "kind").unwrap_or("").to_string();
|
||||
let mode = anchor_mode_param(p).ok_or_else(|| bad("object.setAnchorMode", "mode must be moveAndSize, moveOnly or absolute"))?;
|
||||
let sheet = s.doc()?.wb.active_sheet;
|
||||
edit(s, |cx| {
|
||||
let sh = cx.sheet_mut(sheet)?;
|
||||
let anchor = match kind.as_str() {
|
||||
"chart" => sh.charts.iter_mut().find(|c| c.id == id).map(|c| &mut c.anchor),
|
||||
"image" => sh.images.iter_mut().find(|c| c.id == id).map(|c| &mut c.anchor),
|
||||
_ => sh.shapes.iter_mut().find(|c| c.id == id).map(|c| &mut c.anchor),
|
||||
};
|
||||
let Some(a) = anchor else { return Err(bad("object.setAnchorMode", "no such object")) };
|
||||
a.mode = mode;
|
||||
Ok(json!({"mode": anchor_mode_name(mode)}))
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------- links & comments
|
||||
|
||||
fn insert_link(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
@@ -1134,7 +1167,7 @@ fn insert_icon(s: &mut Session, p: &Json) -> Result<Json> {
|
||||
let shape = Shape {
|
||||
id,
|
||||
kind: ShapeKind::Icon,
|
||||
anchor: Anchor { cell: at, dx: 4.0, dy: 4.0, width: size, height: size },
|
||||
anchor: Anchor { cell: at, dx: 4.0, dy: 4.0, width: size, height: size, mode: anchor_mode_param(p).unwrap_or_default() },
|
||||
fill: color,
|
||||
line: Color::Auto,
|
||||
text: name,
|
||||
|
||||
Reference in New Issue
Block a user