mirror of
https://github.com/storytold/wordcraft.git
synced 2026-10-28 12:27:11 -06:00
Tables: one selected cell's border drags in its row only (#970)
Selecting one cell (Table Layout > Select > Select Cell, or a click along its left edge) and dragging its left or right border moved the border in the whole column: one selected cell wasn't told apart from text selected inside it. `ResizeTarget::limit_to_selection` now takes the selected cells from `Session::cell_range` (#637) when they're in the dragged table, so one selected cell's border moves in its row only, as in Word, like the border of cells selected across (#720). Text selected within a cell still moves the whole column. Tests: engine `a_selected_cells_border_moves_only_in_its_row`; kittest `a_selected_cells_border_drags_in_its_row_only` (a click along the cell's edge, then a real drag). Progress docs: ui-parity, ROADMAP. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Brandon Thomas <echelon@gmail.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
Brandon Thomas
parent
5c8dba229f
commit
cb6418dc6c
@@ -278,6 +278,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm
|
||||
| 2026-10-11 | Hide White Space (#645): `view.hideWhiteSpace` (View › Show checkbox, or double-click the gap between pages) collapses margins, headers and footers between pages to a thin line in Print Layout; screen only, page count unchanged |
|
||||
| 2026-10-11 | Fonts: Palatino Linotype and Book Antiqua fall back to Palatino (macOS) or TeX Gyre Pagella / P052 / URW Palladio (Linux) before generic serif (#670) |
|
||||
|---|---|
|
||||
| 2026-10-12 | Tables: dragging the border of one selected cell (Select Cell, or a click along its left edge) moves it in that cell's row only, as in Word, like the border of cells selected across (#962) |
|
||||
| 2026-10-13 | Korean interface catalog completed: all 2,463 entries translated; the ko-KR system locale and saved language choice select the interface. The UI prefers Korean embedded/system font faces so Hangul renders instead of boxes, and print settings and printer feedback are translated. Korean proofing remains unavailable; macOS Korean IME fixes are still pending (#163, #164) |
|
||||
| 2026-10-12 | Word default shortcuts expanded (gap #24): 140 commands bound on macOS (169 combinations), 158 on Linux/Windows (188 combinations). Bound Style Inspector (Shift+F1), Macros (Alt+F8), Strikethrough (Mod+Shift+X), Find Next (Shift+F4), Copy/Paste (Mod+Insert, Shift+Insert), Go Back (Mod+Alt+Z), File Open/Save/Save As/Print (Mod+Alt+F2, Alt+Shift+F2, Alt+F2, Mod+Alt+I), Field navigation (Alt+F1, Alt+Shift+F1), Outline show first line (Alt+Shift+L), Next Footnote (Alt+Shift+>), Link to Previous (Alt+Shift+R), AutoText (Alt+F3), Spelling on Mac (Mod+Alt+L) |
|
||||
| 2026-10-13 | Web and Draft Layout now render visible full-resolution raster tiles for exceptionally tall pages rather than downsampling the entire page into one texture; verified with a 440-paragraph headless UI reproduction |
|
||||
@@ -395,6 +396,7 @@ Ranked, to beta (detail and remaining milestones in [docs/roadmap.md](docs/roadm
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-12 | trivial | Progress log: one selected cell's border drags in its row only (#962) |
|
||||
| 2026-10-12 | minor | Progress log: Word default shortcuts expanded to 140 commands on macOS (169 combinations) and 158 on Linux/Windows (188 combinations), addressing gap #24 |
|
||||
| 2026-10-13 | trivial | Web/Draft long-page blur fixed with visible full-resolution raster tiles |
|
||||
| 2026-10-14 | trivial | Progress log: VML freeforms read and kept on save |
|
||||
|
||||
@@ -210,30 +210,20 @@ impl ResizeTarget {
|
||||
}
|
||||
}
|
||||
|
||||
/// When the selection runs across cells of this table (Word selects them whole) and the
|
||||
/// column border grabbed at page y `y` is one of theirs, in one of their rows, the drag
|
||||
/// moves it only in those rows (`only`), as Word does with selected cells' borders. Not the
|
||||
/// table's left edge, which moves the whole table, nor a border of a cell merged down into
|
||||
/// rows outside the selection. Text selected within one cell isn't a cell selection.
|
||||
/// When cells of this table are selected whole (one cell too) or the selection runs across
|
||||
/// cells of it (Word selects them whole), and the column border grabbed at page y `y` is one
|
||||
/// of theirs, in one of their rows, the drag moves it only in those rows (`only`), as Word
|
||||
/// does with selected cells' borders. Not the table's left edge, which moves the whole table,
|
||||
/// nor a border of a cell merged down into rows outside the selection. Text selected within
|
||||
/// one cell isn't a cell selection.
|
||||
pub fn limit_to_selection(&mut self, s: &Session, y: f32) {
|
||||
self.only.clear();
|
||||
let Edge::Column(line) = self.edge else { return };
|
||||
let (a, b) = s.sel.ordered();
|
||||
if line == 0 || s.sel.is_collapsed() || a.story != self.story || b.story != self.story {
|
||||
if line == 0 {
|
||||
return;
|
||||
}
|
||||
let (Some(t), Some((ra, ca)), Some((rb, cb))) =
|
||||
(s.doc.table(self.story, &self.table), cell_in(&self.table, &a.path), cell_in(&self.table, &b.path))
|
||||
else {
|
||||
return;
|
||||
};
|
||||
if (ra, ca) == (rb, cb) {
|
||||
return;
|
||||
}
|
||||
let span = |r: usize, c: usize| t.rows.get(r).and_then(|row| row.cells.get(c)).map_or(1, Cell::span);
|
||||
let (ga, gb) = (t.grid_col(ra, ca), t.grid_col(rb, cb));
|
||||
let (lo, hi) = (ga.min(gb), (ga + span(ra, ca)).max(gb + span(rb, cb)));
|
||||
let (r0, r1) = (ra.min(rb), ra.max(rb));
|
||||
let Some(t) = s.doc.table(self.story, &self.table) else { return };
|
||||
let Some(((r0, r1), (lo, hi))) = self.selected_block(s, t) else { return };
|
||||
let here = self.rows.iter().find(|r| y >= r.top && y <= r.bottom).map(|r| r.row);
|
||||
if line < lo || line > hi || !here.is_some_and(|r| (r0..=r1).contains(&r)) {
|
||||
return;
|
||||
@@ -258,6 +248,30 @@ impl ResizeTarget {
|
||||
}
|
||||
self.only = only;
|
||||
}
|
||||
|
||||
/// The selected cells of this table, as their rows (first, last) and the grid lines of
|
||||
/// their outer borders (left, right): whole cells (`Session::cell_range`), or a selection
|
||||
/// running from one of its cells into another. `None` for text within one cell.
|
||||
fn selected_block(&self, s: &Session, t: &Table) -> Option<((usize, usize), (usize, usize))> {
|
||||
let lines = |cells: &[(usize, usize)]| -> Option<(usize, usize)> {
|
||||
let span = |r: usize, c: usize| t.rows.get(r).and_then(|row| row.cells.get(c)).map_or(1, Cell::span);
|
||||
let lo = cells.iter().map(|&(r, c)| t.grid_col(r, c)).min()?;
|
||||
let hi = cells.iter().map(|&(r, c)| t.grid_col(r, c).saturating_add(span(r, c))).max()?;
|
||||
Some((lo, hi))
|
||||
};
|
||||
if let Some(range) = s.cell_range().filter(|c| c.story == self.story && c.table == self.table) {
|
||||
return Some((range.rows, lines(&range.cells(t))?));
|
||||
}
|
||||
let (a, b) = s.sel.ordered();
|
||||
if s.sel.is_collapsed() || a.story != self.story || b.story != self.story {
|
||||
return None;
|
||||
}
|
||||
let ((ra, ca), (rb, cb)) = (cell_in(&self.table, &a.path)?, cell_in(&self.table, &b.path)?);
|
||||
if (ra, ca) == (rb, cb) {
|
||||
return None;
|
||||
}
|
||||
Some(((ra.min(rb), ra.max(rb)), lines(&[(ra, ca), (rb, cb)])?))
|
||||
}
|
||||
}
|
||||
|
||||
/// Moves the border right of column `i` of `w` (column widths, left to right) by up to `dx`
|
||||
@@ -964,6 +978,27 @@ mod tests {
|
||||
assert_eq!(first_table(&s), start, "one undo step each");
|
||||
}
|
||||
|
||||
/// One selected cell (Select Cell, unlike its text) is a cell selection too: its border
|
||||
/// moves in its row only (#962).
|
||||
#[test]
|
||||
fn a_selected_cells_border_moves_only_in_its_row() {
|
||||
let mut s = table_session(3, 3);
|
||||
let (b, c) = (cell_rect(&mut s, 1, 1), cell_rect(&mut s, 1, 2));
|
||||
let y = b.y + b.h / 2.0;
|
||||
s.run("caret.set", &json!({"page": 0, "x": b.x + b.w / 2.0, "y": y})).unwrap();
|
||||
s.run("table.selectCell", &json!({})).unwrap();
|
||||
assert!(s.cell_range().is_some());
|
||||
let start = first_table(&s);
|
||||
s.run("table.resizeDrag", &json!({"page": 0, "x": b.right(), "y": y, "toX": b.right() + 30.0, "toY": y})).unwrap();
|
||||
let (b1, c1) = (cell_rect(&mut s, 1, 1), cell_rect(&mut s, 1, 2));
|
||||
assert!((b1.w - (b.w + 30.0)).abs() < 1.0 && (c1.w - (c.w - 30.0)).abs() < 1.0, "{b1:?} {c1:?}");
|
||||
for r in [0, 2] {
|
||||
assert!((cell_rect(&mut s, r, 1).right() - b.right()).abs() < 0.5, "row {r} keeps its border");
|
||||
}
|
||||
s.run("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(first_table(&s), start, "one undo step");
|
||||
}
|
||||
|
||||
/// A cell merged down past the selection keeps one width: a border of it moves in the whole
|
||||
/// column.
|
||||
#[test]
|
||||
|
||||
@@ -565,6 +565,28 @@ pub(crate) mod tests {
|
||||
assert!(widths(&mut h).is_empty(), "gone with the drag");
|
||||
}
|
||||
|
||||
/// A cell selected with a click along its left edge: dragging its right border with the
|
||||
/// mouse moves it in that cell's row only (#962).
|
||||
#[test]
|
||||
fn a_selected_cells_border_drags_in_its_row_only() {
|
||||
let mut h = table_window();
|
||||
let (above, b, c) = (cell(&mut h, 0, 1), cell(&mut h, 1, 1), cell(&mut h, 1, 2));
|
||||
let y = b.y + b.h / 2.0;
|
||||
let at = screen(&mut h, b.x + 4.5, y);
|
||||
h.event(egui::Event::PointerMoved(at));
|
||||
h.step();
|
||||
button(&h, at, true);
|
||||
h.step();
|
||||
button(&h, at, false);
|
||||
steps(&mut h, 2);
|
||||
assert_eq!(h.state().session.cell_range().map(|r| (r.rows, r.cols)), Some(((1, 1), (1, 1))), "the cell is selected");
|
||||
let (start, end) = (screen(&mut h, b.right(), y), screen(&mut h, b.right() + 30.0, y));
|
||||
drag(&mut h, start, end);
|
||||
let (b1, c1) = (cell(&mut h, 1, 1), cell(&mut h, 1, 2));
|
||||
assert!((b1.w - (b.w + 30.0)).abs() < 1.0 && (c1.w - (c.w - 30.0)).abs() < 1.0, "{b1:?} {c1:?}");
|
||||
assert!((cell(&mut h, 0, 1).right() - above.right()).abs() < 0.5, "the first row keeps its border");
|
||||
}
|
||||
|
||||
/// Clicks inside a cell still place the caret (the border doesn't steal them), and Esc drops a
|
||||
/// drag without changing anything.
|
||||
#[test]
|
||||
|
||||
@@ -52,6 +52,7 @@ Document title opens Rename Document; the account badge opens Options › Genera
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-10-12 | trivial | Tables on the page: one selected cell (Select Cell, or a click along its left edge) is a cell selection when its border is dragged: the border moves in its row only (#962) |
|
||||
| 2026-10-12 | minor | Default keyboard shortcuts expanded to 140 commands on macOS (169 combinations) and 158 on Linux/Windows (188 combinations): Style Inspector (Shift+F1), Macros (Alt+F8), Strikethrough (Mod+Shift+X), Find Next (Shift+F4), Copy/Paste Insert keys (Mod+Insert, Shift+Insert), Go Back (Mod+Alt+Z), File Open/Save/Save As/Print (Mod+Alt+F2, Alt+Shift+F2, Alt+F2, Mod+Alt+I), Field navigation (Alt+F1, Alt+Shift+F1), Outline show first line (Alt+Shift+L), Next Footnote (Alt+Shift+>), Link to Previous (Alt+Shift+R), AutoText (Alt+F3), Spelling on Mac (Mod+Alt+L) |
|
||||
| 2026-10-13 | trivial | Web/Draft Layout long-page blur fixed using visible full-resolution raster tiles, tested against a 440-paragraph document |
|
||||
| 2026-10-14 | trivial | A click on the ribbon's chrome that isn't a command button (a Styles gallery tile, a tab name, empty ribbon space, a View check box, a status-bar item) hands the keyboard to the page in the same frame, as a ribbon command button does: typing right after the click reaches the document instead of being swallowed (#1093 follow-up) |
|
||||
|
||||
Reference in New Issue
Block a user