diff --git a/crates/ui-egui/src/panes.rs b/crates/ui-egui/src/panes.rs index 69dbbe99f..9675d2a95 100644 --- a/crates/ui-egui/src/panes.rs +++ b/crates/ui-egui/src/panes.rs @@ -308,13 +308,29 @@ fn format_chart(app: &mut SheetApp, ui: &mut Ui) { ui.collapsing("Size", |ui| { // Keep the value being typed or dragged between frames; reloading it from the chart every frame threw the edit away. let draft = egui::Id::unique(("gridcraft.chart_size_draft", id)); - let (mut w, mut h) = ui.data_mut(|d| d.get_temp::<(f32, f32)>(draft)).unwrap_or((chart.anchor.width, chart.anchor.height)); - let a = ui.add(egui::DragValue::new(&mut w).prefix("Width ").range(40.0..=4000.0)); - let b = ui.add(egui::DragValue::new(&mut h).prefix("Height ").range(40.0..=4000.0)); + let current = (chart.anchor.width, chart.anchor.height); + let (mut w, mut h) = ui.data_mut(|d| d.get_temp::<(f32, f32)>(draft)).unwrap_or(current); + // A file can make a chart larger than the 4,000 points a field otherwise reaches. The + // range keeps such a size, so the field shows it instead of clamping it to 4,000. + let range = |v: f32| 40.0..=v.max(4000.0); + let a = ui.add(egui::DragValue::new(&mut w).prefix("Width ").range(range(current.0))); + let b = ui.add(egui::DragValue::new(&mut h).prefix("Height ").range(range(current.1))); if a.drag_stopped() || a.lost_focus() || b.drag_stopped() || b.lost_focus() { ui.data_mut(|d| d.remove::<(f32, f32)>(draft)); if !ui.input(|i| i.key_pressed(egui::Key::Escape)) { - app.run_or_alert("chart.set", json!({"chart": id, "width": w, "height": h})); + // Only an edited dimension is sent: the other keeps what the chart has, however + // large a file made it. + let mut params = serde_json::Map::new(); + params.insert("chart".into(), json!(id)); + if w != current.0 { + params.insert("width".into(), json!(w)); + } + if h != current.1 { + params.insert("height".into(), json!(h)); + } + if params.len() > 1 { + app.run_or_alert("chart.set", Json::Object(params)); + } } } else if a.dragged() || a.has_focus() || b.dragged() || b.has_focus() { ui.data_mut(|d| d.insert_temp(draft, (w, h))); diff --git a/crates/ui-egui/tests/it/chart_resize.rs b/crates/ui-egui/tests/it/chart_resize.rs index 2ae8707df..36934fbcb 100644 --- a/crates/ui-egui/tests/it/chart_resize.rs +++ b/crates/ui-egui/tests/it/chart_resize.rs @@ -118,6 +118,51 @@ fn dragging_the_size_field_resizes_the_chart() { assert!(w1 > w0 + 20.0, "width went from {w0} to {w1}"); } +/// Makes the chart 12,000 wide and reopens the workbook from its XLSX bytes, the way a file can +/// carry a chart larger than the 4,000 points the Size fields otherwise reach; returns its id. +fn imported_oversized_chart(h: &mut Harness, id: u32) -> u32 { + { + let wb = std::sync::Arc::make_mut(&mut h.state_mut().session.doc_mut().unwrap().wb); + wb.sheet_mut(0).unwrap().charts.iter_mut().find(|c| c.id == id).unwrap().anchor.width = 12_000.0; + } + let saved = h.state_mut().run("file.saveBytes", json!({"format": "xlsx"})).unwrap(); + h.state_mut().run("file.open", json!({"name": "wide.xlsx", "base64": saved["base64"]})).unwrap(); + h.run_steps(4); + let id = h.state().session.doc().unwrap().wb.active().unwrap().charts[0].id; + let (w, hh) = size(h, id); + assert_eq!((w.round(), hh.round()), (12_000.0, 200.0), "the reader keeps the file's size"); + id +} + +/// A chart a file made 12,000 wide shows that width in the Size fields, and a new height sends +/// the height alone, so nothing shrinks the width to the fields' 4,000 reach on the way. +#[test] +fn the_size_fields_keep_an_oversized_imported_chart_width_when_the_height_changes() { + let (mut h, id) = fixture(); + let id = imported_oversized_chart(&mut h, id); + open_size_fields(&mut h, id); + let (width_value, height_field) = { + let mut fields = h.get_all_by_role(Role::SpinButton); + let width = fields.next().unwrap(); + let height = fields.next().unwrap(); + (width.value(), height.rect()) + }; + assert!(width_value.as_deref().is_some_and(|v| v.contains("12000")), "the field shows the file's width: {width_value:?}"); + h.input_mut().events.push(Event::PointerMoved(height_field.center())); + button(&mut h, height_field.center(), true); + button(&mut h, height_field.center(), false); + for digit in ["2", "0", "1"] { + h.input_mut().events.push(Event::Text(digit.into())); + h.run_steps(2); + } + h.key_press(Key::Enter); + h.run_steps(2); + let (w, hh) = size(&h, id); + assert_eq!((w.round(), hh), (12_000.0, 201.0), "the width stays and the height is the typed one"); + let (_, p) = h.state().session.journal.iter().rev().find(|(cmd, _)| cmd == "chart.set").unwrap(); + assert!(p.get("width").is_none(), "a height edit sends no width: {p}"); +} + fn right_click(h: &mut Harness, pos: egui::Pos2, wobble: egui::Vec2) { h.input_mut().events.push(Event::PointerMoved(pos)); h.run_steps(2);