mirror of
https://github.com/storytold/deckcraft.git
synced 2026-11-11 16:37:06 -06:00
Format: Fill offers Slide background fill
Format Shape's Fill section had no Slide background fill option, and
shape.fill had no parameter for it. A shape opened from a file with
useBgFill showed Automatic chosen.
shape.fill {background: true} now fills a shape with the slide
background, and Format Shape lists Slide background fill after Pattern
fill. It has no Color button or Transparency slider. Saving writes
useBgFill, and the slide background shows through the shape.
This commit is contained in:
@@ -90,7 +90,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Shape Fill",
|
||||
["Shape Format", "Shape Styles"],
|
||||
None,
|
||||
"{color? | none?: true | gradient?: {stops: [[pos, color]], angle?, kind?: linear|radial} | picture?: base64 | pattern?: {preset, fg, bg} | transparency?: 0..1 | reset?: true, ids?}; pictureProps?: {tile?: bool, offsetX?, offsetY?: pt, scaleX?, scaleY?: 0..10, align?: tl|t|tr|l|ctr|r|bl|b|br, mirror?: none|x|y|xy, stretch?: [l, t, r, b] fractions, rotateWithShape?: bool} adjusts a picture fill; a group also formats its members",
|
||||
"{color? | none?: true | background?: true (the slide background shows through) | gradient?: {stops: [[pos, color]], angle?, kind?: linear|radial} | picture?: base64 | pattern?: {preset, fg, bg} | transparency?: 0..1 | reset?: true, ids?}; pictureProps?: {tile?: bool, offsetX?, offsetY?: pt, scaleX?, scaleY?: 0..10, align?: tl|t|tr|l|ctr|r|bl|b|br, mirror?: none|x|y|xy, stretch?: [l, t, r, b] fractions, rotateWithShape?: bool} adjusts a picture fill; a group also formats its members",
|
||||
has_selection,
|
||||
fill
|
||||
),
|
||||
@@ -561,6 +561,8 @@ fn fill(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
Some(None)
|
||||
} else if bool_or(p, "none", false) {
|
||||
Some(Some(Fill::None))
|
||||
} else if bool_or(p, "background", false) {
|
||||
Some(Some(Fill::Background))
|
||||
} else if let Some(c) = color_param(p, "color") {
|
||||
Some(Some(Fill::solid(c)))
|
||||
} else if let Some(g) = p.get("gradient") {
|
||||
@@ -584,7 +586,10 @@ fn fill(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let transparency = f64_param(p, "transparency");
|
||||
let pic_props = p.get("pictureProps").filter(|v| v.is_object());
|
||||
if new_fill.is_none() && transparency.is_none() && pic_props.is_none() {
|
||||
return Err(bad("shape.fill", "give `color`, `none`, `gradient`, `pattern`, `picture`, `pictureProps`, `transparency` or `reset`"));
|
||||
return Err(bad(
|
||||
"shape.fill",
|
||||
"give `color`, `none`, `background`, `gradient`, `pattern`, `picture`, `pictureProps`, `transparency` or `reset`",
|
||||
));
|
||||
}
|
||||
// Transparency on an inherited (style) fill needs the effective colour.
|
||||
let st = s.doc()?;
|
||||
@@ -1616,6 +1621,30 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn background_fill_shows_the_slide_background_and_survives_a_save() {
|
||||
let mut s = Session::with_new();
|
||||
s.execute("slide.new", &json!({"layout": "blank"})).unwrap();
|
||||
s.execute("design.background", &json!({"color": "#0000c8"})).unwrap();
|
||||
let id = ShapeId(s.execute("shape.insert", &json!({"preset": "rect", "rect": [100, 100, 200, 100]})).unwrap()["id"].as_u64().unwrap() as u32);
|
||||
let inside = |s: &mut Session| s.execute("slide.colorAt", &json!({"x": 200, "y": 150})).unwrap()["color"].clone();
|
||||
assert_ne!(inside(&mut s), "0000C8", "a new shape takes the theme's fill");
|
||||
s.execute("shape.fill", &json!({"background": true})).unwrap();
|
||||
assert_eq!(s.doc().unwrap().shape(id).unwrap().fill, Some(Fill::Background));
|
||||
assert_eq!(inside(&mut s), "0000C8", "the slide background shows through the shape");
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(s.doc().unwrap().shape(id).unwrap().fill, None, "one undo step returns to the theme's fill");
|
||||
s.execute("edit.redo", &json!({})).unwrap();
|
||||
// Transparency changes a solid or picture fill only.
|
||||
s.execute("shape.fill", &json!({"transparency": 0.5})).unwrap();
|
||||
assert_eq!(s.doc().unwrap().shape(id).unwrap().fill, Some(Fill::Background));
|
||||
|
||||
let bytes = deckcraft_pptx::export(&s.doc().unwrap().doc).unwrap();
|
||||
let back = deckcraft_pptx::import(&bytes).unwrap();
|
||||
let filled = back.slides.iter().flat_map(|sl| sl.shapes.iter()).filter(|sh| sh.fill == Some(Fill::Background)).count();
|
||||
assert_eq!(filled, 1, "the shape keeps the background fill after a save and reopen");
|
||||
}
|
||||
|
||||
fn rotation(s: &Session) -> f64 {
|
||||
let st = s.active().unwrap();
|
||||
let id = st.selection.shapes[0];
|
||||
|
||||
@@ -456,6 +456,7 @@ fn format_shape(app: &mut SlideApp, ui: &mut Ui) {
|
||||
Some(Fill::Gradient(_)) => "gradient",
|
||||
Some(Fill::Picture(_)) => "picture",
|
||||
Some(Fill::Pattern(_)) => "pattern",
|
||||
Some(Fill::Background) => "background",
|
||||
_ => "auto",
|
||||
};
|
||||
for (l, k) in [
|
||||
@@ -464,6 +465,7 @@ fn format_shape(app: &mut SlideApp, ui: &mut Ui) {
|
||||
("Gradient fill", "gradient"),
|
||||
("Picture or texture fill", "picture"),
|
||||
("Pattern fill", "pattern"),
|
||||
("Slide background fill", "background"),
|
||||
("Automatic", "auto"),
|
||||
] {
|
||||
if ui.radio(kind == k, l).clicked() {
|
||||
@@ -477,6 +479,7 @@ fn format_shape(app: &mut SlideApp, ui: &mut Ui) {
|
||||
),
|
||||
"pattern" => run(app, "shape.fill", json!({"pattern": {"preset": "pct25", "fg": "accent1", "bg": "bg1"}})),
|
||||
"picture" => pick_fill_picture(app),
|
||||
"background" => run(app, "shape.fill", json!({"background": true})),
|
||||
_ => run(app, "shape.fill", json!({"reset": true})),
|
||||
}
|
||||
}
|
||||
@@ -1679,6 +1682,27 @@ mod tests {
|
||||
h.run_steps(2);
|
||||
assert!(alpha(&h).is_some_and(|a| (a - 0.6).abs() < 1e-9), "{:?}", alpha(&h));
|
||||
}
|
||||
|
||||
/// Slide background fill is one of the Fill options. It has no Color button or Transparency
|
||||
/// slider of its own.
|
||||
#[test]
|
||||
fn slide_background_fill_is_a_fill_option() {
|
||||
let mut h = fill_pane(None);
|
||||
let fill = |h: &Harness<'_, SlideApp>| h.state().session.active().unwrap().selected_shapes()[0].fill.clone();
|
||||
let undo = h.state().session.active().unwrap().history.undo.len();
|
||||
h.get_by_label("Slide background fill").click();
|
||||
h.run_steps(3);
|
||||
assert_eq!(fill(&h), Some(Fill::Background));
|
||||
assert_eq!(h.state().session.active().unwrap().history.undo.len(), undo + 1, "one undo step");
|
||||
assert_eq!(h.get_by_label("Slide background fill").accesskit_node().toggled(), Some(egui::accesskit::Toggled::True));
|
||||
assert_eq!(color_buttons(&h), 1, "only the line's Color button");
|
||||
assert_eq!(h.query_all_by_role_and_label(egui::accesskit::Role::Slider, "Transparency").count(), 0);
|
||||
// A shape that already has the background fill shows this option chosen, not Automatic.
|
||||
let h = fill_pane(Some(json!({"background": true})));
|
||||
assert_eq!(h.get_by_label("Slide background fill").accesskit_node().toggled(), Some(egui::accesskit::Toggled::True));
|
||||
let automatic = h.query_all_by_label("Automatic").filter(|n| n.accesskit_node().toggled() == Some(egui::accesskit::Toggled::True)).count();
|
||||
assert_eq!(automatic, 1, "only the line's Automatic is chosen");
|
||||
}
|
||||
}
|
||||
|
||||
/// Review › Thesaurus: look a word up; clicking a synonym puts it in place of the selected word
|
||||
|
||||
Reference in New Issue
Block a user