mirror of
https://github.com/storytold/designcraft.git
synced 2026-10-29 12:46:53 -06:00
Integration: #846 stroke edges of a partly selected merged cell are classified against the selection grown to whole cells, in table.setCell and table.getCellStroke
This commit is contained in:
@@ -940,19 +940,23 @@ fn set_cell(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
// Local edits outrank previously imported edges, including explicit zero/None.
|
||||
let priority = if stroke.is_some() { next_cell_stroke_priority(t) } else { 0 };
|
||||
let rg = g.range;
|
||||
// Edges are inner or outer within the selection grown to whole merged cells, so a
|
||||
// merged cell selected through one of its grid positions keeps its own outline.
|
||||
let outline = t.expand_range(rg);
|
||||
// Each chosen edge starts from the stroke drawn there, which both copies of a shared
|
||||
// edge then take, so the edit changes only the attributes it names.
|
||||
let mut bases = std::collections::HashMap::new();
|
||||
if let Some((_, edges)) = &stroke {
|
||||
for (r, c) in t.selected_owners(rg) {
|
||||
let Some(cell) = t.cell(r, c) else { continue };
|
||||
for (i, on) in edges.sides(rg, (r, c), (cell.row_span, cell.col_span), rtl).into_iter().enumerate() {
|
||||
for (i, on) in edges.sides(outline, (r, c), (cell.row_span, cell.col_span), rtl).into_iter().enumerate() {
|
||||
if let Some(base) = drawn_side(t, &owners, (r, c), i).filter(|_| on) {
|
||||
bases.insert((r, c, i), base);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut edited = std::collections::HashSet::new();
|
||||
for (r, c) in t.selected_owners(rg) {
|
||||
let Some(cell) = t.cell_mut(r, c) else { continue };
|
||||
if let Some(f) = &fill {
|
||||
@@ -974,6 +978,7 @@ fn set_cell(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
if let Some((edit, _)) = &stroke {
|
||||
for i in 0..4 {
|
||||
if let Some(base) = bases.remove(&(r, c, i)) {
|
||||
edited.insert((r, c, i));
|
||||
cell.strokes[i] = edit.apply(base);
|
||||
cell.stroke_defined[i] = true;
|
||||
cell.stroke_priorities[i] = priority;
|
||||
@@ -982,31 +987,36 @@ fn set_cell(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
}
|
||||
}
|
||||
// Keep both copies of unmerged shared boundaries consistent. Left/right name
|
||||
// physical sides, also in RTL tables.
|
||||
// Keep both copies of unmerged shared boundaries consistent: each edited edge is
|
||||
// copied to its neighbour. Left/right name physical sides, also in RTL tables.
|
||||
let mirror = |t: &mut Table, from: (usize, usize), edge: usize, to: (usize, usize), opposite: usize| {
|
||||
if edited.contains(&(from.0, from.1, edge)) {
|
||||
mirror_cell_edge(t, &owners, from, edge, to, opposite);
|
||||
}
|
||||
};
|
||||
if let Some((_, edges)) = &stroke {
|
||||
if edges.top
|
||||
&& let Some(row) = rg.r0.checked_sub(1)
|
||||
{
|
||||
for c in rg.c0..=rg.c1 {
|
||||
mirror_cell_edge(t, &owners, (rg.r0, c), 0, (row, c), 2);
|
||||
mirror(t, (rg.r0, c), 0, (row, c), 2);
|
||||
}
|
||||
}
|
||||
if edges.bottom && rg.r1.saturating_add(1) < nr {
|
||||
for c in rg.c0..=rg.c1 {
|
||||
mirror_cell_edge(t, &owners, (rg.r1, c), 2, (rg.r1 + 1, c), 0);
|
||||
mirror(t, (rg.r1, c), 2, (rg.r1 + 1, c), 0);
|
||||
}
|
||||
}
|
||||
if (if rtl { edges.right } else { edges.left })
|
||||
&& let Some(col) = rg.c0.checked_sub(1)
|
||||
{
|
||||
for r in rg.r0..=rg.r1 {
|
||||
mirror_cell_edge(t, &owners, (r, rg.c0), if rtl { 3 } else { 1 }, (r, col), if rtl { 1 } else { 3 });
|
||||
mirror(t, (r, rg.c0), if rtl { 3 } else { 1 }, (r, col), if rtl { 1 } else { 3 });
|
||||
}
|
||||
}
|
||||
if (if rtl { edges.left } else { edges.right }) && rg.c1.saturating_add(1) < nc {
|
||||
for r in rg.r0..=rg.r1 {
|
||||
mirror_cell_edge(t, &owners, (r, rg.c1), if rtl { 1 } else { 3 }, (r, rg.c1 + 1), if rtl { 3 } else { 1 });
|
||||
mirror(t, (r, rg.c1), if rtl { 1 } else { 3 }, (r, rg.c1 + 1), if rtl { 3 } else { 1 });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1023,7 +1033,7 @@ fn get_cell_stroke(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let st = s.doc()?;
|
||||
let t = st.doc.story(g.story).and_then(|x| x.tables.get(&g.table)).ok_or_else(|| bad(ID, "no table"))?;
|
||||
let owners = t.owners();
|
||||
let nc = t.ncols();
|
||||
let outline = t.expand_range(g.range);
|
||||
let rtl = t.options.direction == designcraft_doc::TextDirection::RightToLeft;
|
||||
// One value per key, or null once two edges (cells) disagree.
|
||||
fn merge(slot: &mut Option<Value>, v: Value) {
|
||||
@@ -1038,28 +1048,23 @@ fn get_cell_stroke(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let (mut fill, mut fill_tint) = (None, None);
|
||||
// Each drawn edge once: a shared edge is a side of both its cells.
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
for r in g.range.r0..=g.range.r1 {
|
||||
for c in g.range.c0..=g.range.c1 {
|
||||
if owners.get(r * nc + c) != Some(&(r, c)) {
|
||||
for (r, c) in t.selected_owners(g.range) {
|
||||
let Some(cell) = t.cell(r, c) else { continue };
|
||||
merge(&mut fill, json!(cell.fill));
|
||||
merge(&mut fill_tint, json!(cell.fill_tint));
|
||||
for (i, on) in edges.sides(outline, (r, c), (cell.row_span, cell.col_span), rtl).into_iter().enumerate() {
|
||||
if !on {
|
||||
continue;
|
||||
}
|
||||
let Some(cell) = t.cell(r, c) else { continue };
|
||||
merge(&mut fill, json!(cell.fill));
|
||||
merge(&mut fill_tint, json!(cell.fill_tint));
|
||||
for (i, on) in edges.sides(g.range, (r, c), (cell.row_span, cell.col_span), rtl).into_iter().enumerate() {
|
||||
if !on {
|
||||
for side in compose::table::cell_side_strokes(t, &owners, (r, c), i) {
|
||||
let (x, y) = (Some((r, c)), side.neighbor);
|
||||
if !seen.insert((i % 2, side.boundary, x.min(y), x.max(y))) {
|
||||
continue;
|
||||
}
|
||||
for side in compose::table::cell_side_strokes(t, &owners, (r, c), i) {
|
||||
let (x, y) = (Some((r, c)), side.neighbor);
|
||||
if !seen.insert((i % 2, side.boundary, x.min(y), x.max(y))) {
|
||||
continue;
|
||||
}
|
||||
let e = side.stroke;
|
||||
let values = [json!(e.weight), json!(e.color), json!(e.tint), json!(e.kind), json!(e.gap_color), json!(e.gap_tint)];
|
||||
for (slot, v) in stroke.iter_mut().zip(values) {
|
||||
merge(slot, v);
|
||||
}
|
||||
let e = side.stroke;
|
||||
let values = [json!(e.weight), json!(e.color), json!(e.tint), json!(e.kind), json!(e.gap_color), json!(e.gap_tint)];
|
||||
for (slot, v) in stroke.iter_mut().zip(values) {
|
||||
merge(slot, v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,7 +209,9 @@ fn selected_merged_owners_receive_cell_options_and_unique_style_counts() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn covered_owner_strokes_keep_literal_edge_masks_and_neighbors() {
|
||||
fn selecting_part_of_a_merged_cell_strokes_its_whole_outline() {
|
||||
// Row 0 is one merged cell; selecting its second grid position selects the whole cell, so
|
||||
// all four of its edges are outer edges.
|
||||
for rtl in [false, true] {
|
||||
for outer in [false, true] {
|
||||
let (mut s, sid, tid, _) = selected_merged_table(true);
|
||||
@@ -218,24 +220,26 @@ fn covered_owner_strokes_keep_literal_edge_masks_and_neighbors() {
|
||||
}
|
||||
s.execute("table.select", &json!({"rows": [0, 0], "cols": [1, 1]})).unwrap();
|
||||
let before = table_for(&s, sid, tid).clone();
|
||||
s.execute("table.setCell", &json!({"stroke": {"weight": 3, "edges": if outer { "outer" } else { "inner" }}})).unwrap();
|
||||
let edges = if outer { "outer" } else { "inner" };
|
||||
s.execute("table.setCell", &json!({"stroke": {"weight": 3, "edges": edges}})).unwrap();
|
||||
let after = table_for(&s, sid, tid);
|
||||
let mask = if rtl { [true, true, true, false] } else { [true, false, true, true] };
|
||||
let expected = mask.map(|edge| if outer { edge } else { !edge });
|
||||
let owner = after.cell(0, 0).unwrap();
|
||||
assert_eq!(owner.border_overrides, expected);
|
||||
assert_eq!(owner.stroke_defined, expected);
|
||||
for (i, on) in expected.into_iter().enumerate() {
|
||||
if on {
|
||||
assert_eq!(owner.strokes[i].weight, 3.0);
|
||||
assert_eq!(owner.stroke_priorities[i], 1);
|
||||
} else {
|
||||
assert_eq!(owner.strokes[i], before.cell(0, 0).unwrap().strokes[i]);
|
||||
}
|
||||
if outer {
|
||||
assert_eq!(owner.border_overrides, [true; 4]);
|
||||
assert_eq!(owner.stroke_defined, [true; 4]);
|
||||
assert!(owner.strokes.iter().all(|e| e.weight == 3.0));
|
||||
assert_eq!(owner.stroke_priorities, [1; 4]);
|
||||
} else {
|
||||
assert_eq!(Some(owner), before.cell(0, 0), "a single merged cell has no inner edges");
|
||||
}
|
||||
for (r, c) in [(0, 1), (1, 0), (1, 1)] {
|
||||
assert_eq!(after.cell(r, c), before.cell(r, c), "strict merged/covered mirror guard preserves {r},{c}");
|
||||
}
|
||||
let shown = s.execute("table.getCellStroke", &json!({"edges": "outer"})).unwrap();
|
||||
assert!(shown["edges"].as_u64().unwrap() >= 4, "the merged cell's outline is read: {shown}");
|
||||
if outer {
|
||||
assert_eq!(shown["weight"], 3.0);
|
||||
}
|
||||
assert_eq!(s.doc().unwrap().selection.cells.unwrap().range, doc::CellRange::cell(0, 1));
|
||||
check(&s);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user