graphic design tool
git clone https://git.lucas.co/cce-designer.git
fix: plates loaded at startup fit the real window, not the placeholder
The startup project loads in Application::new, before the compositor's
first configure, so its plate fractions were scaled onto State::new's
1280x800 placeholder. resize() keeps plates at their pixel size, so every
launch in any other window opened with the plates scaled by 1280/width
(and 800/height), and saving then stored the shrunken fractions, which
ratcheted smaller on each save and relaunch. A load before the first
configure now holds the fractions in pending_plates, and the first real
resize applies them again. Later resizes behave as before.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/app.rs | 17 +++++++++++++++++
src/main.rs | 38 ++++++++++++++++++++++++++++++++++++++
src/project.rs | 38 +++++++++++++++++++++++---------------
3 files changed, 78 insertions(+), 15 deletions(-)
diff --git a/src/app.rs b/src/app.rs
index 4b7e8a4..9d1e3b5 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -2129,6 +2129,16 @@ pub struct State {
/// raises the neighbor's bottom edge to the spreadsheet's top.
pub floating_spreadsheet_inset_left: f32,
pub floating_spreadsheet_inset_right: f32,
+ /// Whether the compositor has told us the window's size yet. Until it
+ /// has, `width`/`height` are `State::new`'s 1280x800 placeholder.
+ pub window_configured: bool,
+ /// Plate fractions a load applied against that placeholder, held so the
+ /// first real `resize` can apply them again at the real size. The
+ /// startup project loads before the first configure, and `resize`
+ /// keeps plates at their pixel size, so without this every launch in a
+ /// window other than 1280x800 opened with the plates scaled by
+ /// 1280/width — and a save then stored the scaled fractions.
+ pub pending_plates: Option<PlateGeometry>,
pub loaded_project_path: Option<std::path::PathBuf>,
/// The configured startup project (`DesignSettings::default_project`),
/// mirrored live so "Set As Default" can rewrite it and `save_settings` —
@@ -5769,6 +5779,8 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
floating_spreadsheet_height: 250.0,
floating_spreadsheet_inset_left: 0.0,
floating_spreadsheet_inset_right: 0.0,
+ window_configured: false,
+ pending_plates: None,
loaded_project_path: None,
default_project_setting: settings.default_project.clone(),
gpu_preference: settings.gpu.clone(),
@@ -7759,6 +7771,11 @@ pub(crate) fn geometry_to_spreadsheet_data(geom: &Detail) -> (Vec<String>, Vec<V
self.slots.splitter2.set_rect(self.splitter_layout.splitter2_x, HEADER_H, SPLITTER_W, body_h);
}
+ self.window_configured = true;
+ if let Some(pg) = self.pending_plates.take() {
+ self.apply_plate_geometry(pg);
+ }
+
self.sync_layout();
self.read_panel_offsets();
self.keep_cursor_in_view();
diff --git a/src/main.rs b/src/main.rs
index f3968f1..45a754b 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1407,6 +1407,44 @@ mod tests {
let _ = fs::remove_dir_all(&dir);
}
+ /// The startup project loads before the compositor's first configure, so
+ /// its plate fractions land on `State::new`'s 1280x800 placeholder; the
+ /// first real size must apply them again. Before, the plates kept the
+ /// placeholder's pixels, opening every launch scaled by 1280/width (and
+ /// 800/height), and a save then stored the shrunken fractions.
+ #[test]
+ fn plates_loaded_before_the_first_configure_fit_the_real_window() {
+ let dir = std::env::temp_dir().join(format!("cce_designer_prefconfigure_{}", std::process::id()));
+ let _ = fs::remove_dir_all(&dir);
+ fs::create_dir_all(&dir).unwrap();
+ let mut a = State::new(false);
+ a.resize(1400.0, 1080.0, 1.0);
+ a.execute_menu_action("Show Spreadsheet Pane");
+ a.floating_network_layout.2 = 350.0;
+ a.floating_param_width = 310.0;
+ a.floating_spreadsheet_height = 100.0;
+ a.rebuild_positions();
+ a.save_to_file(&dir).expect("save");
+
+ // No resize before the load: this is the startup order.
+ let mut b = State::new(false);
+ assert!(!b.window_configured);
+ b.load_from_file(&dir).expect("load");
+ b.resize(1400.0, 1080.0, 1.0);
+ assert!((b.left_dock_width() - 350.0).abs() < 0.5, "network width: {}", b.left_dock_width());
+ assert!((b.right_dock_width() - 310.0).abs() < 0.5, "param width: {}", b.right_dock_width());
+ assert!((b.floating_spreadsheet_height - 100.0).abs() < 0.5, "spreadsheet height: {}", b.floating_spreadsheet_height);
+ let pg = b.project_view_state().plates.expect("plates");
+ assert!((pg.network_width - 350.0 / 1400.0).abs() < 1e-4, "a save writes back what was loaded: {:?}", pg);
+
+ // Only the FIRST configure: a later window resize keeps the plates'
+ // pixels, as it always has.
+ b.resize(1000.0, 1080.0, 1.0);
+ assert!((b.left_dock_width() - 350.0).abs() < 0.5, "a later resize rescaled the plate: {}", b.left_dock_width());
+
+ let _ = fs::remove_dir_all(&dir);
+ }
+
/// A window briefly narrower than its plates squeezes them for as long as
/// it lasts and no longer: the layout used to store the clamped size, so
/// one transient shrink (a re-tile, a configure at startup) left every
diff --git a/src/project.rs b/src/project.rs
index 0efa450..0bd58d1 100644
--- a/src/project.rs
+++ b/src/project.rs
@@ -396,22 +396,30 @@ impl State {
self.apply_layout();
}
}
- // Plate geometry: the fractions scale back onto this window, and
- // the layout pass clamps them exactly as a drag would (minimum
- // widths, the spreadsheet's tuck limits). A save with a nonsense
- // value keeps the live geometry rather than loading half of one.
+ // Plate geometry: fractions of the window, see `apply_plate_geometry`.
if let Some(pg) = vs.plates {
- let sane = |f: f32| f.is_finite() && (0.0..=1.0).contains(&f);
- let all = [pg.network_width, pg.params_width, pg.spreadsheet_height, pg.spreadsheet_inset_left, pg.spreadsheet_inset_right];
- if self.width > 1.0 && self.height > 1.0 && all.iter().all(|&f| sane(f)) {
- self.floating_network_layout.2 = pg.network_width * self.width;
- self.floating_param_width = pg.params_width * self.width;
- self.floating_spreadsheet_height = pg.spreadsheet_height * self.height;
- self.floating_spreadsheet_inset_left = pg.spreadsheet_inset_left * self.width;
- self.floating_spreadsheet_inset_right = pg.spreadsheet_inset_right * self.width;
- self.rebuild_positions();
- self.apply_layout();
- }
+ self.apply_plate_geometry(pg);
+ }
+ // Before the first configure the fractions just landed on the
+ // placeholder size; the first real resize applies them again.
+ self.pending_plates = if self.window_configured { None } else { vs.plates };
+ }
+
+ /// Scale saved plate fractions onto this window. The layout pass clamps
+ /// them exactly as a drag would (minimum widths, the spreadsheet's tuck
+ /// limits). A save with a nonsense value keeps the live geometry rather
+ /// than loading half of one.
+ pub(crate) fn apply_plate_geometry(&mut self, pg: crate::app::PlateGeometry) {
+ let sane = |f: f32| f.is_finite() && (0.0..=1.0).contains(&f);
+ let all = [pg.network_width, pg.params_width, pg.spreadsheet_height, pg.spreadsheet_inset_left, pg.spreadsheet_inset_right];
+ if self.width > 1.0 && self.height > 1.0 && all.iter().all(|&f| sane(f)) {
+ self.floating_network_layout.2 = pg.network_width * self.width;
+ self.floating_param_width = pg.params_width * self.width;
+ self.floating_spreadsheet_height = pg.spreadsheet_height * self.height;
+ self.floating_spreadsheet_inset_left = pg.spreadsheet_inset_left * self.width;
+ self.floating_spreadsheet_inset_right = pg.spreadsheet_inset_right * self.width;
+ self.rebuild_positions();
+ self.apply_layout();
}
}