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:
dotCooCoo committed 2026-10-25 15:42:46 -07:00
1 parent 13ed8889da
commit 71352733d4
4 files changed
+57 -3

No files matched your search

+1
View File
@@ -160,6 +160,7 @@ app's JSON control channel (`deckcraft --control PORT`).
| 2026-10-20 | Chart number formats with explicit empty positive, negative or zero sections hide the corresponding labels, including all values with three semicolons; a blank whole format still uses General. Synthetic formatter regressions |
| 2026-10-22 | Chart bars and their legend keys draw the series' own pattern fill (`a:pattFill`, now read and written) and draw nothing for `a:noFill`, with the series outline where it has one, as PowerPoint for Mac does (synthetic probe); they took the palette's colour before. Lines, areas and markers use a pattern's foreground colour |
| 2026-10-24 | Format Shape and Format Background ▸ Pattern fill: a gallery of the 48 preset patterns PowerPoint offers, drawn in the current colors, with Foreground and Background color menus. Format Shape could only set 25 % in accent 1 on background 1, and Format Background listed 10 presets by their XML names |
| 2026-10-25 | Format Shape ▸ Fill: Slide background fill shows the slide background through the shape (`shape.fill {background: true}`, saved as `useBgFill`). A shape opened from a file with this fill showed it as Automatic, and the pane offered no way to set it |
| 2026-10-21 | Reuse Slides: pick which slides of the other presentation to insert from a list (`slide.reuseList`, #1103) |
| 2026-10-21 | Slide list and slide sorter: Shift+click selects a range of slides and Ctrl+Shift+click adds it to the selection (`slide.selectRange`, #1105) |
| 2026-10-18 | Native chart sidecar bytes move from JSON arrays into referenced ZIP entries, with shared media byte/item limits and bounded reads; older inline sidecars still load. Synthetic format regressions cover large workbooks and malformed members |
+31 -2
View File
@@ -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];
+24
View File
@@ -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
+1 -1
View File
@@ -65,7 +65,7 @@ menu colours taken from the theme tokens (menu `chrome`, page `card`).
| Pane / dialog | PowerPoint | DeckCraft |
|---|---|---|
| Format Shape / Picture / Text pane (Fill & Line, Effects, Size & Properties, Picture, Text Box) | full | partial: fill (solid transparency follows the selection), gradient editor, picture/texture fill (source, tile, offsets, scale, alignment, mirror, transparency, rotate with shape), pattern fill (gallery of the 48 presets PowerPoint offers, foreground and background colors), line (dash, compound, cap, join, begin/end arrows and sizes), shadow/glow/reflection details (colour, transparency, blur, angle, distance, size), soft edges, 3-D format and rotation (bevels, depth, contour, material, lighting, camera presets, X/Y/Z rotation), size (lock aspect ratio) |
| Format Shape / Picture / Text pane (Fill & Line, Effects, Size & Properties, Picture, Text Box) | full | partial: fill (solid transparency follows the selection), gradient editor, picture/texture fill (source, tile, offsets, scale, alignment, mirror, transparency, rotate with shape), pattern fill (gallery of the 48 presets PowerPoint offers, foreground and background colors), slide background fill, line (dash, compound, cap, join, begin/end arrows and sizes), shadow/glow/reflection details (colour, transparency, blur, angle, distance, size), soft edges, 3-D format and rotation (bevels, depth, contour, material, lighting, camera presets, X/Y/Z rotation), size (lock aspect ratio) |
| Slide Master | Insert Layout, Rename, Delete, Preserve, Insert Placeholder, Title and Footers | Insert Layout, Rename, Delete, Insert Placeholder, and Preserve, Title and Footers checkboxes (`master.preserve`, `master.placeholders`). A master thumbnail selects the master |
| Format Background pane | yes | yes: solid transparency; picture transparency, tile, offset, scale, alignment and mirror; Apply to All copies a picture. Stretch insets and rotate-with-shape are command parameters |
| Selection Pane | front-most first, groups fold, drag to restack, Bring Forward / Send Backward arrows, Show All / Hide All, eye, padlock (Lock), rename in place | same: drag restacks among siblings (a child stays in its group), Ctrl+click toggles, Shift+click selects a run, arrows / Home / End / F2 / Esc in the list, right-click menu |