From f330a02970a645e7daa6eee38731099a7a8331f3 Mon Sep 17 00:00:00 2001 From: xc4pr <88853037+xc4pr@users.noreply.github.com> Date: Fri, 23 Oct 2026 02:19:55 +0200 Subject: [PATCH] Design Ideas: mark the Full-bleed band so a fill the user set is kept (#1201) --- crates/engine/src/cmd/ideas.rs | 42 +++++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/crates/engine/src/cmd/ideas.rs b/crates/engine/src/cmd/ideas.rs index eeda5f21..96d85fff 100644 --- a/crates/engine/src/cmd/ideas.rs +++ b/crates/engine/src/cmd/ideas.rs @@ -84,6 +84,9 @@ fn cover_crop(src: f64, dst: f64) -> [f64; 4] { } } +/// Marks a title whose fill was put there by the full-bleed idea. +const BAND_TAG: &str = "deckcraft.ideaBand"; + /// The light fill behind the title in the full-bleed idea. fn band_fill() -> Fill { Fill::solid(deckcraft_model::ColorRef::rgb(Rgba::rgba(255, 255, 255, 220))) @@ -186,11 +189,17 @@ pub fn apply_idea(doc: &mut Presentation, sel: &Selection, index: usize, id: &st slide.shapes.insert(0, pic); } if let Some(t) = slide.shapes.iter_mut().find(|s| s.ph.as_ref().is_some_and(|p| p.kind.is_title())) { + let marked = t.tags.iter().any(|(k, _)| k == BAND_TAG); if band { t.fill = Some(band_fill()); - } else if t.fill == Some(band_fill()) { - // The band belongs to full-bleed only: another idea takes it away again. + if !marked { + t.tags.push((BAND_TAG.into(), "1".into())); + } + } else if marked { + // The band belongs to full-bleed only: another idea takes it away again. A fill the + // user set (no mark) stays. t.fill = None; + t.tags.retain(|(k, _)| k != BAND_TAG); } } Ok(()) @@ -328,11 +337,38 @@ mod tests { fn switching_from_full_bleed_removes_the_title_band() { let mut s = deck(true); s.execute("design.applyIdea", &json!({"id": "fullBleed"})).unwrap(); - assert!(shape(&s, 500).fill.is_some()); + assert_eq!(shape(&s, 500).fill, Some(band_fill())); s.execute("design.applyIdea", &json!({"id": "pictureLeft"})).unwrap(); assert!(shape(&s, 500).fill.is_none(), "the band belongs to full-bleed only"); } + #[test] + fn a_title_fill_set_by_the_user_survives_other_ideas() { + // Set through the command, and set to exactly the band's own fill. + let mut s = deck(true); + s.execute("shape.fill", &json!({"id": 500, "color": "#ffffff", "transparency": 0.14})).unwrap(); + let user = shape(&s, 500).fill; + assert!(user.is_some()); + s.execute("design.applyIdea", &json!({"id": "pictureLeft"})).unwrap(); + assert_eq!(shape(&s, 500).fill, user); + let mut s = deck(true); + Arc::make_mut(&mut Arc::make_mut(&mut s.doc_mut().unwrap().doc).slides[0]).shapes[0].fill = Some(band_fill()); + s.execute("design.applyIdea", &json!({"id": "pictureLeft"})).unwrap(); + assert_eq!(shape(&s, 500).fill, Some(band_fill()), "not added by an idea, so not removed by one"); + } + + #[test] + fn full_bleed_twice_keeps_the_band_and_undo_after_switching_brings_it_back() { + let mut s = deck(true); + s.execute("design.applyIdea", &json!({"id": "fullBleed"})).unwrap(); + s.execute("design.applyIdea", &json!({"id": "fullBleed"})).unwrap(); + assert_eq!(shape(&s, 500).fill, Some(band_fill())); + s.execute("design.applyIdea", &json!({"id": "framed"})).unwrap(); + assert!(shape(&s, 500).fill.is_none()); + s.execute("edit.undo", &json!({})).unwrap(); + assert_eq!(shape(&s, 500).fill, Some(band_fill())); + } + #[test] fn cover_crop_matches_the_box_aspect() { let c = cover_crop(2.0, 1.0);