mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
Tables: drag row and column borders to resize them
Hovering a table border shows a resize cursor (new hit kinds TableColumn and TableRow) and dragging it resizes, one undo step per drag, as in PowerPoint: - an inner column border moves between its two columns and the table keeps its width; - the right edge widens or narrows the last column and the table; - a row's bottom border (the last one is the table's bottom edge) sets that row's height, which stays a minimum: rows still grow to fit their text. Columns stop at 12 pt. Borders hidden inside merged cells aren't hit; the left and top edges still move the table. A border drag while a cell is edited keeps editing it. The drag goes through table.columnWidth, which takes a new keepTotal option (the next column absorbs the change), and table.rowHeight. Hit testing is done in the table's own frame, so rotated tables resize along their axes.
This commit is contained in:
@@ -60,12 +60,23 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
cmd!("table.distributeColumns", "Distribute Columns", ["Table Layout", "Cell Size"], None, "{id?}", has_selection, |s, p| distribute(
|
||||
s, p, false
|
||||
)),
|
||||
cmd!("table.columnWidth", "Width", ["Table Layout", "Cell Size"], None, "{col, width: pt, id?}", has_selection, column_width),
|
||||
cmd!(
|
||||
"table.columnWidth",
|
||||
"Width",
|
||||
["Table Layout", "Cell Size"],
|
||||
None,
|
||||
"{col, width: pt, keepTotal?: bool (the next column absorbs the change), id?}",
|
||||
has_selection,
|
||||
column_width
|
||||
),
|
||||
cmd!("table.rowHeight", "Height", ["Table Layout", "Cell Size"], None, "{row, height: pt, id?}", has_selection, row_height),
|
||||
cmd!(noundo "table.selectCells", "Select Cells", [], None, "{from: [r,c], to: [r,c], id?}", has_selection, select_cells),
|
||||
]
|
||||
}
|
||||
|
||||
/// Narrowest a column gets when its border is dragged (points).
|
||||
pub const MIN_COLUMN_WIDTH: f64 = 12.0;
|
||||
|
||||
/// A table as drawn on the slide: where it is, its drawn row heights and its visible cells.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TableFrame {
|
||||
@@ -78,6 +89,15 @@ pub struct TableFrame {
|
||||
pub cells: Vec<(usize, usize, Rect)>,
|
||||
}
|
||||
|
||||
/// A border between table rows or columns that can be dragged.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum TableBorder {
|
||||
/// The right edge of this column (the last one is the table's right edge).
|
||||
Column(usize),
|
||||
/// The bottom edge of this row (the last one is the table's bottom edge).
|
||||
Row(usize),
|
||||
}
|
||||
|
||||
/// The drawn geometry of table shape `sh` on the edited slide (`None` if it isn't a table).
|
||||
pub fn table_frame(st: &DocState, sh: &Shape) -> Option<TableFrame> {
|
||||
let ShapeKind::Table(t) = &sh.kind else { return None };
|
||||
@@ -118,6 +138,38 @@ impl TableFrame {
|
||||
.or_else(|| self.cells.iter().rev().find(|(_, _, r)| inside(r)))
|
||||
.map(|(r, c, _)| (*r, *c))
|
||||
}
|
||||
|
||||
/// The draggable border within `tol` of table-local point `p`: the inner borders and the right
|
||||
/// and bottom edges (the left and top edges move the table). Borders hidden inside merged
|
||||
/// cells don't count.
|
||||
pub fn border_at(&self, p: Point, tol: f64) -> Option<TableBorder> {
|
||||
let (w, h) = (self.width(), self.height());
|
||||
if !(p.x >= -tol && p.y >= -tol && p.x <= w + tol && p.y <= h + tol) {
|
||||
return None;
|
||||
}
|
||||
let q = Point::new(p.x.clamp(0.0, w), p.y.clamp(0.0, h));
|
||||
let mut best: Option<(f64, TableBorder)> = None;
|
||||
let mut x = 0.0;
|
||||
for (i, cw) in self.cols.iter().enumerate() {
|
||||
x += cw.max(0.0);
|
||||
let d = (p.x - x).abs();
|
||||
// A cell ending at this edge at this height shows the border.
|
||||
let shown = self.cells.iter().any(|(_, _, r)| (r.x1 - x).abs() < 1e-6 && q.y >= r.y0 && q.y <= r.y1);
|
||||
if d <= tol && shown && best.is_none_or(|b| d < b.0) {
|
||||
best = Some((d, TableBorder::Column(i)));
|
||||
}
|
||||
}
|
||||
let mut y = 0.0;
|
||||
for (i, rh) in self.heights.iter().enumerate() {
|
||||
y += rh.max(0.0);
|
||||
let d = (p.y - y).abs();
|
||||
let shown = self.cells.iter().any(|(_, _, r)| (r.y1 - y).abs() < 1e-6 && q.x >= r.x0 && q.x <= r.x1);
|
||||
if d <= tol && shown && best.is_none_or(|b| d < b.0) {
|
||||
best = Some((d, TableBorder::Row(i)));
|
||||
}
|
||||
}
|
||||
best.map(|b| b.1)
|
||||
}
|
||||
}
|
||||
|
||||
fn cell_param(p: &Value, key: &str) -> Option<(usize, usize)> {
|
||||
@@ -288,9 +340,27 @@ fn distribute(s: &mut Session, p: &Value, rows: bool) -> Result<Value> {
|
||||
fn column_width(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let (_, c) = at(s, p);
|
||||
let w = f64_param(p, "width").ok_or_else(|| bad("table.columnWidth", "missing `width`"))?.clamp(4.0, 4000.0);
|
||||
let keep_total = bool_or(p, "keepTotal", false);
|
||||
with_table(s, p, "table.columnWidth", move |t| {
|
||||
if let Some(x) = t.cols.get_mut(c) {
|
||||
*x = w;
|
||||
// Keeping the table's width, the column to the right gives or takes the difference (dragging
|
||||
// an inner border). The last column has none: the table gets wider or narrower.
|
||||
let pair = t.cols.get(c).zip(t.cols.get(c + 1)).map(|(a, b)| a.max(0.0) + b.max(0.0)).filter(|x| x.is_finite());
|
||||
match pair {
|
||||
Some(pair) if keep_total => {
|
||||
let min = MIN_COLUMN_WIDTH.min(pair / 2.0);
|
||||
let w = w.max(min).min(pair - min);
|
||||
if let Some(x) = t.cols.get_mut(c) {
|
||||
*x = w;
|
||||
}
|
||||
if let Some(x) = t.cols.get_mut(c + 1) {
|
||||
*x = pair - w;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if let Some(x) = t.cols.get_mut(c) {
|
||||
*x = w;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
|
||||
@@ -557,6 +557,113 @@ fn caret_moves_by_line_in_a_wrapped_cell() {
|
||||
assert!(text_sel(&s).unwrap().caret.1 < down.1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn table_borders_are_hit_where_they_are_drawn() {
|
||||
let mut s = session();
|
||||
let id = table_at(&mut s, &[100.0, 100.0, 100.0], json!([["a", "b", "c"], ["d", "e", "f"]]));
|
||||
let hit = |s: &Session, x: f64, y: f64| s.hit_test(deckcraft_geom::Point::new(x, y), 3.0);
|
||||
assert_eq!(hit(&s, 201.0, 125.0), Some(tools::Hit::TableColumn { id, col: 0 }));
|
||||
assert_eq!(hit(&s, 400.0, 125.0), Some(tools::Hit::TableColumn { id, col: 2 }), "the right edge");
|
||||
assert_eq!(hit(&s, 150.0, 139.0), Some(tools::Hit::TableRow { id, row: 0 }));
|
||||
assert_eq!(hit(&s, 150.0, 181.0), Some(tools::Hit::TableRow { id, row: 1 }), "the bottom edge");
|
||||
assert!(matches!(hit(&s, 100.0, 125.0), Some(tools::Hit::Shape { .. })), "the left edge moves the table");
|
||||
assert!(matches!(hit(&s, 150.0, 125.0), Some(tools::Hit::Shape { .. })));
|
||||
// Hovering a border shows it (the UI picks the resize cursor from the hover).
|
||||
s.pointer(tev(PointerKind::Move, 300.0, 170.0)).unwrap();
|
||||
assert_eq!(s.tool.hover, Some(tools::Hit::TableColumn { id, col: 1 }));
|
||||
// A border inside a merged cell isn't there.
|
||||
s.execute("table.merge", &json!({"id": id, "from": [0, 0], "to": [0, 1]})).unwrap();
|
||||
assert!(matches!(hit(&s, 200.0, 120.0), Some(tools::Hit::Shape { .. })));
|
||||
assert_eq!(hit(&s, 200.0, 160.0), Some(tools::Hit::TableColumn { id, col: 0 }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dragging_column_borders_resizes_columns() {
|
||||
let mut s = session();
|
||||
let id = table_at(&mut s, &[100.0, 100.0, 100.0], json!([["a", "b", "c"], ["d", "e", "f"]]));
|
||||
let undo_depth = s.doc().unwrap().history.undo.len();
|
||||
// An inner border: the two columns share the change, the table keeps its width.
|
||||
s.pointer(tev(PointerKind::Down, 200.0, 125.0)).unwrap();
|
||||
s.pointer(tev(PointerKind::Drag, 215.0, 125.0)).unwrap();
|
||||
s.pointer(tev(PointerKind::Drag, 230.0, 128.0)).unwrap();
|
||||
s.pointer(tev(PointerKind::Up, 230.0, 128.0)).unwrap();
|
||||
assert_eq!(table_of(&s, id).cols, vec![130.0, 70.0, 100.0]);
|
||||
assert_eq!(s.doc().unwrap().shape(id).unwrap().xfrm.unwrap().w, 300.0);
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), undo_depth + 1, "one undo step per drag");
|
||||
// Columns don't collapse.
|
||||
s.pointer(tev(PointerKind::Down, 230.0, 125.0)).unwrap();
|
||||
s.pointer(tev(PointerKind::Drag, 500.0, 125.0)).unwrap();
|
||||
s.pointer(tev(PointerKind::Up, 500.0, 125.0)).unwrap();
|
||||
assert_eq!(table_of(&s, id).cols, vec![200.0 - cmd::table::MIN_COLUMN_WIDTH, cmd::table::MIN_COLUMN_WIDTH, 100.0]);
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(table_of(&s, id).cols, vec![100.0, 100.0, 100.0]);
|
||||
// The right edge: the last column and the table get wider.
|
||||
s.pointer(tev(PointerKind::Down, 400.0, 120.0)).unwrap();
|
||||
s.pointer(tev(PointerKind::Drag, 450.0, 120.0)).unwrap();
|
||||
s.pointer(tev(PointerKind::Up, 450.0, 120.0)).unwrap();
|
||||
assert_eq!(table_of(&s, id).cols, vec![100.0, 100.0, 150.0]);
|
||||
assert_eq!(s.doc().unwrap().shape(id).unwrap().xfrm.unwrap().w, 350.0);
|
||||
// A click on a border without dragging changes nothing.
|
||||
let before = s.doc().unwrap().doc.clone();
|
||||
s.pointer(tev(PointerKind::Down, 200.0, 125.0)).unwrap();
|
||||
s.pointer(tev(PointerKind::Up, 200.0, 125.0)).unwrap();
|
||||
assert!(std::sync::Arc::ptr_eq(&before, &s.doc().unwrap().doc));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dragging_a_row_border_sets_the_row_height() {
|
||||
let mut s = session();
|
||||
let id = table_at(&mut s, &[200.0, 200.0], json!([["a", "b"], ["c", "d"]]));
|
||||
let drawn = |s: &Session| {
|
||||
let st = s.doc().unwrap();
|
||||
tools::table_rows(st, &table_of(s, id))
|
||||
};
|
||||
s.pointer(tev(PointerKind::Down, 150.0, 140.0)).unwrap();
|
||||
s.pointer(tev(PointerKind::Drag, 150.0, 170.0)).unwrap();
|
||||
s.pointer(tev(PointerKind::Up, 150.0, 170.0)).unwrap();
|
||||
assert_eq!(drawn(&s), vec![70.0, 40.0]);
|
||||
let st = s.doc().unwrap();
|
||||
assert_eq!(cmd::xfrm_of(&st.doc, &st.selection, st.shape(id).unwrap()).h, 110.0, "the table grows");
|
||||
// Rows still fit their text: dragging the border up stops at the text.
|
||||
s.pointer(tev(PointerKind::Down, 150.0, 170.0)).unwrap();
|
||||
s.pointer(tev(PointerKind::Drag, 150.0, 60.0)).unwrap();
|
||||
s.pointer(tev(PointerKind::Up, 150.0, 60.0)).unwrap();
|
||||
let h = drawn(&s)[0];
|
||||
assert!(h > 10.0 && h < 40.0, "row 0 shrinks to its text: {h}");
|
||||
// The bottom edge sizes the last row.
|
||||
let bottom = 100.0 + drawn(&s).iter().sum::<f64>();
|
||||
s.pointer(tev(PointerKind::Down, 150.0, bottom)).unwrap();
|
||||
s.pointer(tev(PointerKind::Drag, 150.0, bottom + 20.0)).unwrap();
|
||||
s.pointer(tev(PointerKind::Up, 150.0, bottom + 20.0)).unwrap();
|
||||
assert_eq!(drawn(&s)[1], 60.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dragging_a_border_while_editing_a_cell_keeps_editing() {
|
||||
let mut s = session();
|
||||
let id = table_at(&mut s, &[100.0, 100.0], json!([["a", "b"]]));
|
||||
s.execute("text.edit", &json!({"id": id, "cell": [0, 1]})).unwrap();
|
||||
s.pointer(tev(PointerKind::Down, 200.0, 120.0)).unwrap();
|
||||
s.pointer(tev(PointerKind::Drag, 180.0, 120.0)).unwrap();
|
||||
s.pointer(tev(PointerKind::Up, 180.0, 120.0)).unwrap();
|
||||
assert_eq!(table_of(&s, id).cols, vec![80.0, 120.0]);
|
||||
assert_eq!(text_sel(&s).and_then(|t| t.cell), Some((0, 1)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn column_width_can_keep_the_table_width() {
|
||||
let mut s = session();
|
||||
let id = table_at(&mut s, &[100.0, 100.0, 100.0], json!([["a", "b", "c"]]));
|
||||
s.execute("table.columnWidth", &json!({"id": id, "col": 1, "width": 150.0, "keepTotal": true})).unwrap();
|
||||
assert_eq!(table_of(&s, id).cols, vec![100.0, 150.0, 50.0]);
|
||||
s.execute("table.columnWidth", &json!({"id": id, "col": 1, "width": 1000.0, "keepTotal": true})).unwrap();
|
||||
assert_eq!(table_of(&s, id).cols, vec![100.0, 200.0 - cmd::table::MIN_COLUMN_WIDTH, cmd::table::MIN_COLUMN_WIDTH]);
|
||||
// The last column has no neighbour: the table changes width.
|
||||
s.execute("table.columnWidth", &json!({"id": id, "col": 2, "width": 80.0, "keepTotal": true})).unwrap();
|
||||
assert_eq!(s.doc().unwrap().shape(id).unwrap().xfrm.unwrap().w, 100.0 + (200.0 - cmd::table::MIN_COLUMN_WIDTH) + 80.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rotated_tables_hit_the_cell_under_the_pointer() {
|
||||
let mut s = session();
|
||||
|
||||
@@ -85,6 +85,16 @@ pub enum Hit {
|
||||
id: ShapeId,
|
||||
text: bool,
|
||||
},
|
||||
/// The right edge of table column `col` (dragging it resizes the column).
|
||||
TableColumn {
|
||||
id: ShapeId,
|
||||
col: usize,
|
||||
},
|
||||
/// The bottom edge of table row `row` (dragging it resizes the row).
|
||||
TableRow {
|
||||
id: ShapeId,
|
||||
row: usize,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -129,6 +139,18 @@ enum Drag {
|
||||
TextSelect {
|
||||
shape: ShapeId,
|
||||
},
|
||||
/// Dragging a table border: the right edge of column `index`, or the bottom edge of row
|
||||
/// `index`. `start` is the table-local press point, `size` the column's width or the row's
|
||||
/// drawn height then, `frame` where the table was drawn.
|
||||
TableBorder {
|
||||
id: ShapeId,
|
||||
column: bool,
|
||||
index: usize,
|
||||
start: Point,
|
||||
size: f64,
|
||||
frame: Affine,
|
||||
moved: bool,
|
||||
},
|
||||
Ink {
|
||||
points: Vec<(f64, f64, f32)>,
|
||||
},
|
||||
@@ -369,6 +391,8 @@ impl Session {
|
||||
let hit = self.hit_test(p, tol);
|
||||
let st = self.doc()?;
|
||||
match hit {
|
||||
Some(Hit::TableColumn { id, col }) => self.begin_table_border(id, true, col, p)?,
|
||||
Some(Hit::TableRow { id, row }) => self.begin_table_border(id, false, row, p)?,
|
||||
Some(Hit::Resize { id, handle }) => {
|
||||
let orig = st.shape(id).map(|s| xfrm_of(&st.doc, &st.selection, s)).unwrap_or_default();
|
||||
self.begin("Size");
|
||||
@@ -623,6 +647,26 @@ impl Session {
|
||||
})?;
|
||||
}
|
||||
}
|
||||
Drag::TableBorder { id, column, index, start, size, frame, moved } => {
|
||||
let local = frame.inverse() * p;
|
||||
let d = if column { local.x - start.x } else { local.y - start.y };
|
||||
if !moved && d.abs() < tol.max(1.0) {
|
||||
return Ok(Value::Null);
|
||||
}
|
||||
if !moved {
|
||||
self.begin(if column { "Column Width" } else { "Row Height" });
|
||||
}
|
||||
// An inner column border moves between its two columns (the table keeps its
|
||||
// width); the right edge widens the last column and the table. A row's height is
|
||||
// a minimum: rows still grow to fit their text.
|
||||
if column {
|
||||
let width = (size + d).max(cmd::table::MIN_COLUMN_WIDTH);
|
||||
self.execute("table.columnWidth", &json!({"id": id, "col": index, "width": width, "keepTotal": true}))?;
|
||||
} else {
|
||||
self.execute("table.rowHeight", &json!({"id": id, "row": index, "height": (size + d).max(1.0)}))?;
|
||||
}
|
||||
self.tool.drag = Some(Drag::TableBorder { id, column, index, start, size, frame, moved: true });
|
||||
}
|
||||
Drag::Freeform { mode, mut points, pressed } => {
|
||||
// Dragging scribbles (except for curves, which only take clicked points).
|
||||
if pressed && mode != "curve" && points.last().is_none_or(|l| (*l - p).hypot() >= 1.5) {
|
||||
@@ -814,7 +858,10 @@ impl Session {
|
||||
let Some(sh) = st.shape(t.shape) else { return Ok(None) };
|
||||
let x = xfrm_of(&st.doc, &st.selection, sh);
|
||||
if !x.bounds().inflate(tol, tol).contains(p)
|
||||
|| matches!(self.hit_test(p, tol), Some(Hit::Resize { .. } | Hit::Rotate { .. } | Hit::Adjust { .. }))
|
||||
|| matches!(
|
||||
self.hit_test(p, tol),
|
||||
Some(Hit::Resize { .. } | Hit::Rotate { .. } | Hit::Adjust { .. } | Hit::TableColumn { .. } | Hit::TableRow { .. })
|
||||
)
|
||||
{
|
||||
return Ok(None);
|
||||
}
|
||||
@@ -860,6 +907,24 @@ impl Session {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Press on a table border: dragging it resizes the column or row. Editing a cell of the
|
||||
/// table goes on; otherwise the table becomes the selection.
|
||||
fn begin_table_border(&mut self, id: ShapeId, column: bool, index: usize, p: Point) -> Result<()> {
|
||||
let st = self.doc()?;
|
||||
let Some(f) = st.shape(id).and_then(|sh| cmd::table::table_frame(st, sh)) else { return Ok(()) };
|
||||
let size = if column { f.cols.get(index) } else { f.heights.get(index) }.copied().unwrap_or(0.0);
|
||||
let (start, frame) = (f.local(p), f.affine);
|
||||
if !st.selection.text.as_ref().is_some_and(|t| t.shape == id) {
|
||||
self.select(|_, sel| {
|
||||
sel.shapes = vec![id];
|
||||
sel.text = None;
|
||||
sel.cells = None;
|
||||
})?;
|
||||
}
|
||||
self.tool.drag = Some(Drag::TableBorder { id, column, index, start, size, frame, moved: false });
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_picture(&self, id: ShapeId) -> bool {
|
||||
self.active().and_then(|d| d.shape(id)).is_some_and(|s| matches!(s.kind, ShapeKind::Picture { .. } | ShapeKind::Media(_)))
|
||||
}
|
||||
@@ -1244,6 +1309,15 @@ fn hit_shape(st: &crate::DocState, sh: &Shape, p: Point, tol: f64) -> Option<Hit
|
||||
let x = xfrm_of(&st.doc, &st.selection, sh);
|
||||
let inv = x.affine().inverse();
|
||||
let local = inv * p;
|
||||
// Table borders resize rows and columns.
|
||||
let near = local.x >= -tol && local.y >= -tol && local.x <= x.w + tol && local.y <= x.h + tol;
|
||||
if near && let Some(f) = cmd::table::table_frame(st, sh) {
|
||||
match f.border_at(f.local(p), tol) {
|
||||
Some(cmd::table::TableBorder::Column(col)) => return Some(Hit::TableColumn { id: sh.id, col }),
|
||||
Some(cmd::table::TableBorder::Row(row)) => return Some(Hit::TableRow { id: sh.id, row }),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
match &sh.kind {
|
||||
ShapeKind::Group { children, child } => {
|
||||
// A group is hit when any child is hit; the group is what gets selected.
|
||||
|
||||
@@ -404,6 +404,8 @@ fn pointer(app: &mut SlideApp, ui: &Ui, resp: &egui::Response, xf: Xf) {
|
||||
_ => egui::CursorIcon::ResizeHorizontal,
|
||||
},
|
||||
(_, Some(Hit::Rotate { .. })) => egui::CursorIcon::Alias,
|
||||
(_, Some(Hit::TableColumn { .. })) => egui::CursorIcon::ResizeColumn,
|
||||
(_, Some(Hit::TableRow { .. })) => egui::CursorIcon::ResizeRow,
|
||||
(_, Some(Hit::Adjust { .. } | Hit::LineEnd { .. })) => egui::CursorIcon::PointingHand,
|
||||
(_, Some(Hit::Shape { text: true, .. })) => {
|
||||
if app.session.active().is_some_and(|d| d.selection.text.is_some()) {
|
||||
@@ -772,4 +774,27 @@ mod tests {
|
||||
let (top, _) = deckcraft_engine::cmd::text::caret_line(st, &ts).unwrap();
|
||||
assert!(top.x > 100.0 && top.x < 120.0 && top.y >= 100.0 && top.y < 140.0, "caret {top:?} at the start of cell (0, 0)");
|
||||
}
|
||||
|
||||
/// Table borders show a resize cursor and drag to resize columns.
|
||||
#[test]
|
||||
fn table_borders_resize_with_the_pointer() {
|
||||
let (mut harness, id) = app_with_table();
|
||||
let p = screen(harness.state(), 300.0, 120.0);
|
||||
harness.hover_at(p);
|
||||
harness.step();
|
||||
harness.step();
|
||||
assert_eq!(harness.output().platform_output.cursor_icon, egui::CursorIcon::ResizeColumn);
|
||||
let q = screen(harness.state(), 340.0, 120.0);
|
||||
harness.drag_at(p);
|
||||
harness.step();
|
||||
for k in 1..=4 {
|
||||
harness.hover_at(p + (q - p) * k as f32 / 4.0);
|
||||
harness.step();
|
||||
}
|
||||
harness.drop_at(q);
|
||||
harness.run_steps(2);
|
||||
let st = harness.state().session.active().unwrap();
|
||||
let deckcraft_model::ShapeKind::Table(t) = &st.shape(id).unwrap().kind else { panic!("not a table") };
|
||||
assert!((t.cols[0] - 240.0).abs() < 0.5 && (t.cols[1] - 160.0).abs() < 0.5, "{:?}", t.cols);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user