mirror of
https://github.com/storytold/gridcraft.git
synced 2026-11-10 20:40:05 -06:00
* E37: migrate GridCraft to egui 0.37 Pin the reviewed fork and shared dependencies, adapt input and rendering to the new frame lifecycle, and preserve bounded capture handling and font notices. Add representative accessibility checks and meaningful control labels. Validation: full repository CI passed all ten stages, with 2,249 tests passed, zero failed and one existing timing test ignored. Native workflows and paired performance qualification remain documented separately. * egui037: record final published pins and local qualification * M0.37: fix RTL clipping and preserve screenshot budgets * test: share migrated GridCraft UI harness setup * egui037: document migrated UI and regression contracts * egui-037: align popup test fonts and avoid redundant tooltip probes
169 lines
7.4 KiB
Rust
169 lines
7.4 KiB
Rust
//! Resizing a chart by dragging its bottom-right corner or with the Format Chart Area Size fields, through real input.
|
|
|
|
use egui::{Event, Key, Modifiers, PointerButton, accesskit::Role, pos2, vec2};
|
|
use egui_kittest::kittest::Queryable as _;
|
|
use gridcraft_engine::Session;
|
|
use gridcraft_engine::core::CellRef;
|
|
use gridcraft_ui_egui::{SheetApp, grid::Geo};
|
|
use serde_json::json;
|
|
|
|
type Harness = egui_kittest::Harness<'static, SheetApp>;
|
|
|
|
fn fixture() -> (Harness, u32) {
|
|
let mut session = Session::new();
|
|
session.new_workbook();
|
|
session.execute("cell.set", json!({"cell": "A1", "input": "1"})).unwrap();
|
|
session.execute("cell.set", json!({"cell": "A2", "input": "2"})).unwrap();
|
|
session.execute("selection.set", json!({"range": "A1:A2"})).unwrap();
|
|
let chart = session.execute("insert.chart", json!({"type": "column", "at": "D3", "width": 300, "height": 200})).unwrap();
|
|
let id = chart["chart"].as_u64().unwrap() as u32;
|
|
let mut h = crate::common::builder().with_size(vec2(1200.0, 800.0)).build_ui_state(
|
|
|ui, app: &mut SheetApp| {
|
|
let ctx = ui.ctx().clone();
|
|
app.logic(&ctx);
|
|
app.ui(ui);
|
|
},
|
|
crate::common::app(session),
|
|
);
|
|
h.run_steps(4);
|
|
(h, id)
|
|
}
|
|
|
|
fn size(h: &Harness, id: u32) -> (f32, f32) {
|
|
let a = h.state().session.doc().unwrap().wb.active().unwrap().charts.iter().find(|c| c.id == id).unwrap().anchor;
|
|
(a.width, a.height)
|
|
}
|
|
|
|
fn corner(h: &Harness, id: u32) -> egui::Pos2 {
|
|
let app = h.state();
|
|
let sh = app.session.doc().unwrap().wb.active().unwrap();
|
|
let geo = Geo::new(sh, app.grid.rect.unwrap(), app.view().scroll);
|
|
let a = sh.charts.iter().find(|c| c.id == id).unwrap().anchor;
|
|
let min = geo.cell_rect(sh, CellRef::parse("D3").unwrap()).min + vec2(a.dx, a.dy) * geo.z;
|
|
min + vec2(a.width, a.height) * geo.z
|
|
}
|
|
|
|
fn button(h: &mut Harness, pos: egui::Pos2, pressed: bool) {
|
|
h.input_mut().events.push(Event::PointerButton { pos, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE });
|
|
h.run_steps(2);
|
|
}
|
|
|
|
#[test]
|
|
fn dragging_the_corner_handle_resizes_the_chart() {
|
|
let (mut h, id) = fixture();
|
|
let (w0, h0) = size(&h, id);
|
|
let start = corner(&h, id);
|
|
// Select the chart first, as the user does before grabbing a handle.
|
|
let inside = start - vec2(40.0, 40.0);
|
|
h.input_mut().events.push(Event::PointerMoved(inside));
|
|
h.run_steps(2);
|
|
button(&mut h, inside, true);
|
|
button(&mut h, inside, false);
|
|
assert_eq!(h.state().selected_chart, Some(id));
|
|
|
|
h.input_mut().events.push(Event::PointerMoved(start));
|
|
h.run_steps(2);
|
|
assert_eq!(h.output().platform_output.cursor_icon, egui::CursorIcon::ResizeNwSe, "the corner shows a resize cursor");
|
|
button(&mut h, start, true);
|
|
// The pointer moves well past the drag threshold in one step, like a quick drag.
|
|
let end = start + vec2(60.0, 30.0);
|
|
h.input_mut().events.push(Event::PointerMoved(pos2(end.x, end.y)));
|
|
h.run_steps(3);
|
|
button(&mut h, end, false);
|
|
let (w1, h1) = size(&h, id);
|
|
assert!(w1 > w0 + 40.0 && h1 > h0 + 20.0, "chart grew from {w0}x{h0} to {w1}x{h1}");
|
|
}
|
|
|
|
/// Opens Format Chart Area for the chart and expands its Size section; returns the Width field's rect.
|
|
fn open_size_fields(h: &mut Harness, id: u32) -> egui::Rect {
|
|
h.state_mut().selected_chart = Some(id);
|
|
h.state_mut().grid.pane = Some("formatChart".into());
|
|
h.run_steps(2);
|
|
h.get_by_role_and_label(Role::DisclosureTriangle, "Size").click();
|
|
h.run_steps(2);
|
|
h.get_all_by_role(Role::SpinButton).next().unwrap().rect()
|
|
}
|
|
|
|
#[test]
|
|
fn typing_a_width_in_the_size_field_resizes_the_chart() {
|
|
let (mut h, id) = fixture();
|
|
let (_, h0) = size(&h, id);
|
|
let field = open_size_fields(&mut h, id);
|
|
h.input_mut().events.push(Event::PointerMoved(field.center()));
|
|
button(&mut h, field.center(), true);
|
|
button(&mut h, field.center(), false);
|
|
// Clicking the field selects its text; each keystroke must survive the next frame.
|
|
for digit in ["6", "4", "0"] {
|
|
h.input_mut().events.push(Event::Text(digit.into()));
|
|
h.run_steps(2);
|
|
}
|
|
h.key_press(Key::Enter);
|
|
h.run_steps(2);
|
|
assert_eq!(size(&h, id), (640.0, h0));
|
|
}
|
|
|
|
#[test]
|
|
fn dragging_the_size_field_resizes_the_chart() {
|
|
let (mut h, id) = fixture();
|
|
let (w0, _) = size(&h, id);
|
|
let field = open_size_fields(&mut h, id);
|
|
h.input_mut().events.push(Event::PointerMoved(field.center()));
|
|
button(&mut h, field.center(), true);
|
|
for step in 1..=6 {
|
|
h.input_mut().events.push(Event::PointerMoved(field.center() + vec2(10.0 * step as f32, 0.0)));
|
|
h.run_steps(1);
|
|
}
|
|
button(&mut h, field.center() + vec2(60.0, 0.0), false);
|
|
let (w1, _) = size(&h, id);
|
|
assert!(w1 > w0 + 20.0, "width went from {w0} to {w1}");
|
|
}
|
|
|
|
fn right_click(h: &mut Harness, pos: egui::Pos2, wobble: egui::Vec2) {
|
|
h.input_mut().events.push(Event::PointerMoved(pos));
|
|
h.run_steps(2);
|
|
h.input_mut().events.push(Event::PointerButton { pos, button: PointerButton::Secondary, pressed: true, modifiers: Modifiers::NONE });
|
|
h.run_steps(2);
|
|
let end = pos + wobble;
|
|
if wobble != vec2(0.0, 0.0) {
|
|
h.input_mut().events.push(Event::PointerMoved(end));
|
|
h.run_steps(2);
|
|
}
|
|
h.input_mut().events.push(Event::PointerButton { pos: end, button: PointerButton::Secondary, pressed: false, modifiers: Modifiers::NONE });
|
|
h.run_steps(2);
|
|
}
|
|
|
|
#[test]
|
|
/// A right-click selects the chart and opens its menu rather than the cell menu without moving the chart; Delete removes it, and an outside right-click still opens the cell menu.
|
|
fn right_clicking_a_chart_opens_its_menu_not_the_cells() {
|
|
// #2067: a right-click on a chart opened the menu of the cell under it, or nothing at all.
|
|
for wobble in [vec2(0.0, 0.0), vec2(20.0, 10.0)] {
|
|
let (mut h, id) = fixture();
|
|
let (w0, h0) = size(&h, id);
|
|
let inside = corner(&h, id) - vec2(100.0, 80.0);
|
|
right_click(&mut h, inside, wobble);
|
|
assert_eq!(h.state().selected_chart, Some(id), "the right-clicked chart is selected ({wobble:?})");
|
|
assert!(matches!(h.state().grid.object_menu, Some((_, "chart", i)) if i == id), "the chart's menu opens ({wobble:?})");
|
|
assert!(h.state().grid.context_menu.is_none(), "the cell menu stays closed ({wobble:?})");
|
|
assert_eq!(size(&h, id), (w0, h0));
|
|
let a = h.state().session.doc().unwrap().wb.active().unwrap().charts.iter().find(|c| c.id == id).unwrap().anchor;
|
|
assert_eq!(a.cell, CellRef::parse("D3").unwrap(), "a right-button drag does not move the chart ({wobble:?})");
|
|
}
|
|
// Delete from the menu removes the chart.
|
|
let (mut h, id) = fixture();
|
|
let inside = corner(&h, id) - vec2(100.0, 80.0);
|
|
right_click(&mut h, inside, vec2(0.0, 0.0));
|
|
h.get_by_role_and_label(Role::Button, "Delete").click();
|
|
h.run_steps(3);
|
|
assert!(h.state().session.doc().unwrap().wb.active().unwrap().charts.is_empty());
|
|
assert!(h.state().grid.object_menu.is_none());
|
|
// Outside the chart a right-click still opens the cell menu.
|
|
let (mut h, _) = fixture();
|
|
let cell = {
|
|
let app = h.state();
|
|
let sh = app.session.doc().unwrap().wb.active().unwrap();
|
|
Geo::new(sh, app.grid.rect.unwrap(), app.view().scroll).cell_rect(sh, CellRef::parse("A10").unwrap()).center()
|
|
};
|
|
right_click(&mut h, cell, vec2(0.0, 0.0));
|
|
assert!(h.state().grid.context_menu.is_some() && h.state().grid.object_menu.is_none());
|
|
}
|