Files
gridcraft/crates/ui-egui/tests/it/show_objects.rs
T
prxnvt e7591de2ec Objects: one selection for charts, pictures, shapes and slicers, with eight resize handles
The sheet tracked the selected object as a bare id in `selected_chart`, so
only charts and text boxes drew a selection frame, only the bottom-right
corner resized, and the kind of the selected object had to be guessed from
the id. The selection is now `SheetApp::selected_object: Option<ObjRef>`,
with `selected_chart()` kept for the Chart Design tab and the Format Chart
pane.

- Model: `ObjKind` (chart, image, shape, slicer) and `ObjRef { kind, id }`;
  `Sheet` helpers find, move and remove an object by `ObjRef` and walk every
  object's anchor in a fixed order. `next_object_id` saturates instead of
  overflowing on an id at `u32::MAX` read from a file.
- Commands: `object.delete`, `object.move` and `object.setAnchorMode` accept
  the four kinds through one parameter check, refuse an unknown kind or id
  instead of succeeding silently, and clamp hostile offsets. `object.delete`
  replies with the deleted object. Arrange and the Selection Pane include
  slicers, which stack above the other objects.
- Sheet: the selected object of any kind draws a frame with eight handles
  over every object. Each handle resizes only its own edges, which never
  cross; the body moves the object, and the first drag frame already applies
  the pointer's movement. The pointer shows the handle's resize cursor or a
  move cursor, with the diagonals swapped on a right-to-left sheet, where the
  frame moves with its object.
- Keys: Escape drops the object selection; Delete and Backspace keep
  deleting the selected object (#213). A selection whose object disappears
  (undo, redo, delete, sheet switch) is cleared.
- Double-click: `open_object` routes by kind (a text box edits in place, a
  chart opens Format Chart).
- Control protocol: `ui.inspect` reports `selectedObject {kind, id}` and
  keeps `selectedChart`; `ui.object.select` selects any object and replies
  with its rect. The snapshot example takes `--object <id>`.

Tests: engine tests for every kind by kind and id, the Selection Pane and
hostile parameters; UI tests for selecting each kind (a slicer over a chart
takes the click), all eight handles on four kinds, cursors, Delete,
Backspace and Escape, undo and redo, double-click routing, and resizing on a
right-to-left sheet. Existing tests use the new selection API.
2026-10-31 05:51:02 -04:00

97 lines
3.8 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Ctrl+6 hides and shows every object (Options › Advanced › "For objects, show"), through real input.
use egui::{Color32, Event, Key, Modifiers, PointerButton, epaint::Shape, vec2};
use gridcraft_engine::Session;
use gridcraft_engine::core::CellRef;
use gridcraft_engine::model::ShowObjects;
use gridcraft_ui_egui::{SheetApp, grid::Geo};
use serde_json::json;
type Harness = egui_kittest::Harness<'static, SheetApp>;
const FILL: Color32 = Color32::from_rgb(0x12, 0x34, 0x56);
/// Ctrl on Windows and Linux sets both `ctrl` and `command`.
const CTRL: Modifiers = Modifiers { alt: false, ctrl: true, shift: false, mac_cmd: false, command: true };
/// A rectangle at D3, 144 × 96 points, filled with a colour nothing else on screen uses.
fn fixture() -> (Harness, u32) {
let mut session = Session::new();
session.new_workbook();
let shape = session.execute("insert.shape", json!({"kind": "rectangle", "at": "D3", "fill": "123456"})).unwrap();
let id = shape["shape"].as_u64().unwrap() as u32;
session.execute("selection.set", json!({"range": "A1"})).unwrap();
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 ctrl_6(h: &mut Harness) {
h.input_mut().events.push(Event::Key { key: Key::Num6, physical_key: Some(Key::Num6), pressed: true, repeat: false, modifiers: CTRL });
h.input_mut().events.push(Event::Key { key: Key::Num6, physical_key: Some(Key::Num6), pressed: false, repeat: false, modifiers: CTRL });
h.run_steps(2);
}
fn show_objects(h: &Harness) -> ShowObjects {
h.state().session.active().unwrap().wb.show_objects
}
/// Whether the last frame painted the rectangle's fill.
fn painted(h: &Harness) -> bool {
h.output().shapes.iter().any(|c| matches!(&c.shape, Shape::Rect(r) if r.fill == FILL))
}
/// Inside the rectangle, in the cell E4 (D3 is 64 points wide and 20 high at 100%).
fn inside(h: &Harness) -> egui::Pos2 {
let app = h.state();
let sh = app.session.active().unwrap().wb.active().unwrap();
let geo = Geo::new(sh, app.grid.rect.unwrap(), app.view().scroll);
geo.cell_rect(sh, CellRef::parse("E4").unwrap()).center()
}
fn click(h: &mut Harness, pos: egui::Pos2) {
h.input_mut().events.push(Event::PointerMoved(pos));
h.run_steps(2);
for pressed in [true, false] {
h.input_mut().events.push(Event::PointerButton { pos, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE });
h.run_steps(2);
}
}
fn active_cell(h: &Harness) -> CellRef {
h.state().session.active().unwrap().selection.active
}
#[test]
fn ctrl_6_hides_objects_and_a_second_ctrl_6_shows_them() {
let (mut h, _) = fixture();
assert_eq!(show_objects(&h), ShowObjects::All);
assert!(painted(&h), "the rectangle is drawn to begin with");
ctrl_6(&mut h);
assert_eq!(show_objects(&h), ShowObjects::None);
assert!(!painted(&h), "hidden objects are not drawn");
ctrl_6(&mut h);
assert_eq!(show_objects(&h), ShowObjects::All);
assert!(painted(&h), "a second Ctrl+6 draws them again");
}
#[test]
fn hidden_objects_do_not_take_clicks() {
let (mut h, id) = fixture();
let at = inside(&h);
click(&mut h, at);
assert_eq!(h.state().selected_object.map(|o| o.id), Some(id), "the click lands on the rectangle while it shows");
assert_eq!(active_cell(&h), CellRef::parse("A1").unwrap());
ctrl_6(&mut h);
assert_eq!(h.state().selected_object, None, "hiding objects drops the selection on one");
click(&mut h, at);
assert_eq!(h.state().selected_object, None);
assert_eq!(active_cell(&h), CellRef::parse("E4").unwrap(), "the click selects the cell under the hidden rectangle");
}