From 1601fd250ac551536ae0143d356ce512695da0ac Mon Sep 17 00:00:00 2001 From: justinjohn0306 Date: Mon, 12 Oct 2026 15:39:40 +0530 Subject: [PATCH] Slide Size: choose Portrait or Landscape (#198) The Slide Size dialog gets an Orientation choice that swaps width and height, and design.slideSize takes orientation: portrait|landscape so a preset can be made portrait from the CLI and MCP too. Co-Authored-By: Claude Opus 5.5 --- crates/engine/src/cmd/design.rs | 8 +++++++- crates/engine/src/tests.rs | 5 +++++ crates/ui-egui/src/dialogs.rs | 11 +++++++++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/crates/engine/src/cmd/design.rs b/crates/engine/src/cmd/design.rs index 641ddffc..7a4e0ed3 100644 --- a/crates/engine/src/cmd/design.rs +++ b/crates/engine/src/cmd/design.rs @@ -33,7 +33,7 @@ pub fn specs() -> Vec { "Slide Size", ["Design", "Customize"], None, - "{preset?: widescreen|standard|… | w, h: pt, scale?: maximize|ensureFit|none}", + "{preset?: widescreen|standard|… | w, h: pt, orientation?: portrait|landscape, scale?: maximize|ensureFit|none}", has_doc, slide_size ), @@ -251,6 +251,12 @@ fn slide_size(s: &mut Session, p: &Value) -> Result { f64_param(p, "h").ok_or_else(|| bad("design.slideSize", "missing `h`"))?.clamp(72.0, 4032.0), ) }; + let size = match str_param(p, "orientation") { + Some("portrait") if size.width > size.height => Size::new(size.height, size.width), + Some("landscape") if size.height > size.width => Size::new(size.height, size.width), + Some("portrait" | "landscape") | None => size, + Some(o) => return Err(bad("design.slideSize", format!("unknown orientation `{o}`"))), + }; let mode = str_param(p, "scale").unwrap_or("ensureFit").to_string(); s.edit(|doc, _| { let old = doc.slide_size; diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index 9aefd0b7..7df053c6 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -244,6 +244,11 @@ fn themes_and_slide_size() { assert_eq!(s.doc().unwrap().doc.masters[0].theme.name, "Ember"); s.execute("design.slideSize", &json!({"preset": "standard"})).unwrap(); assert_eq!(s.doc().unwrap().doc.slide_size.width, 720.0); + s.execute("design.slideSize", &json!({"preset": "widescreen", "orientation": "portrait"})).unwrap(); + assert_eq!((s.doc().unwrap().doc.slide_size.width, s.doc().unwrap().doc.slide_size.height), (540.0, 960.0)); + s.execute("design.slideSize", &json!({"w": 540.0, "h": 960.0, "orientation": "landscape"})).unwrap(); + assert_eq!((s.doc().unwrap().doc.slide_size.width, s.doc().unwrap().doc.slide_size.height), (960.0, 540.0)); + assert!(s.execute("design.slideSize", &json!({"preset": "standard", "orientation": "sideways"})).is_err()); s.execute("design.headerFooter", &json!({"slideNumber": true, "footer": true, "footerText": "Hello"})).unwrap(); let v = s.execute("slide.inspect", &json!({})).unwrap(); assert!(v["shapes"].as_array().unwrap().iter().any(|x| x["placeholder"] == "sldNum")); diff --git a/crates/ui-egui/src/dialogs.rs b/crates/ui-egui/src/dialogs.rs index 73ce180b..a8896215 100644 --- a/crates/ui-egui/src/dialogs.rs +++ b/crates/ui-egui/src/dialogs.rs @@ -148,6 +148,17 @@ fn body(app: &mut SlideApp, ui: &mut Ui, d: &mut Dialog) -> bool { }); field(ui, d, "Width (in):", "w", &format!("{:.2}", cur.width / 72.0)); field(ui, d, "Height (in):", "h", &format!("{:.2}", cur.height / 72.0)); + let (w, h) = (num(d, "w", cur.width / 72.0), num(d, "h", cur.height / 72.0)); + ui.horizontal(|ui| { + ui.label("Orientation:"); + let portrait = ui.radio(h > w, "Portrait").clicked(); + let landscape = ui.radio(w >= h, "Landscape").clicked(); + // Picking the other orientation swaps the width and height fields. + if portrait && w > h || landscape && h > w { + d.fields.insert("w".into(), format!("{h:.2}")); + d.fields.insert("h".into(), format!("{w:.2}")); + } + }); let maximize = check(ui, d, "Maximize content (else ensure fit)", "max", false); let (ok, cancel) = buttons(ui, "OK"); if ok {