mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-25 02:57:01 -06:00
Format: Pattern fill shows a gallery of every preset with Foreground and Background colors
Format Shape's Pattern fill set 25 % in accent 1 on background 1 and offered no way to change it, and Format Background listed 10 of the 54 presets by their XML names. Both panes now show the same editor: a gallery of the 54 DrawingML presets drawn in the fill's own colors, each named in words, and Foreground and Background color menus. With a pattern fill, Format Shape no longer shows the solid-fill Color button, which replaced the pattern with a solid fill, or the Transparency slider, which did not change a pattern. The renderer exports the preset list and tiles for the gallery to draw.
This commit is contained in:
@@ -149,6 +149,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
| 2026-10-21 | Groups nest at most 32 deep (`arrange.group` refuses more), as deep as PPTX import reads and slides draw: a deck grouped 39 or more levels deep saved without complaint and then could not be opened again |
|
||||
| 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 all 54 preset patterns, 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-21 | Reuse Slides: pick which slides of the other presentation to insert from a list (`slide.reuseList`, #1103) |
|
||||
| 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 |
|
||||
| 2026-10-18 | Slide thumbnails and the Slide Sorter render only the slides in view: on a deck of more than 600 slides they used to evict their own thumbnails, so the pane flashed white and never finished (#969) |
|
||||
|
||||
@@ -53,6 +53,7 @@ pub use extent::{Region, TILE, layout_extent, pasteboard_region, slide_extent};
|
||||
pub use images::{decode as decode_image, gif_frame};
|
||||
pub use morph_path::PathMorph;
|
||||
pub use morph_text::TextMorph;
|
||||
pub use pattern::{is_fg as pattern_is_fg, presets as pattern_presets, tile as pattern_tile};
|
||||
pub use placed::{Placed, PlacedLink, PlacedShape, PlacedText, place_master_texts, place_slide, text_outline};
|
||||
pub use table::cell_body as table_cell_body;
|
||||
pub use table::cell_rects as table_cell_rects;
|
||||
|
||||
@@ -71,6 +71,11 @@ const TILES: &[(&str, [u8; 8])] = &[
|
||||
("zigZag", [0x81, 0x42, 0x24, 0x18, 0x81, 0x42, 0x24, 0x18]),
|
||||
];
|
||||
|
||||
/// Every preset name, in the order of the tiles.
|
||||
pub fn presets() -> impl Iterator<Item = &'static str> {
|
||||
TILES.iter().map(|(n, _)| *n)
|
||||
}
|
||||
|
||||
/// The 8×8 tile of preset `name` (`pct50` for an unknown name).
|
||||
pub fn tile(name: &str) -> [u8; 8] {
|
||||
TILES
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
//! The gradient editor of the Format pane (shape fill and slide background): preset variations,
|
||||
//! type, direction, angle and a stop bar (add, drag, recolour, set transparency, remove stops).
|
||||
//! type, direction, angle and a stop bar (add, drag, recolour, set transparency, remove stops); and
|
||||
//! the pattern editor: a gallery of the preset patterns with foreground and background colors.
|
||||
|
||||
use deckcraft_color::{ColorScheme, ColorTransform, SchemeSlot};
|
||||
use deckcraft_model::ColorRef;
|
||||
use deckcraft_model::style::{Gradient, GradientShape, GradientStop};
|
||||
use deckcraft_model::style::{Gradient, GradientShape, GradientStop, PatternFill};
|
||||
use egui::{Color32, CornerRadius, Rect, Sense, Stroke, Ui, pos2, vec2};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
@@ -321,3 +322,128 @@ pub fn gradient_editor(ui: &mut Ui, id: &str, g: &Gradient, sc: &ColorScheme) ->
|
||||
ui.data_mut(|d| d.insert_temp(sel_id, sel));
|
||||
out
|
||||
}
|
||||
|
||||
/// The gallery names of the DrawingML preset patterns.
|
||||
const PATTERN_LABELS: [(&str, &str); 54] = [
|
||||
("pct5", "5%"),
|
||||
("pct10", "10%"),
|
||||
("pct20", "20%"),
|
||||
("pct25", "25%"),
|
||||
("pct30", "30%"),
|
||||
("pct40", "40%"),
|
||||
("pct50", "50%"),
|
||||
("pct60", "60%"),
|
||||
("pct70", "70%"),
|
||||
("pct75", "75%"),
|
||||
("pct80", "80%"),
|
||||
("pct90", "90%"),
|
||||
("horz", "Horizontal"),
|
||||
("vert", "Vertical"),
|
||||
("ltHorz", "Light horizontal"),
|
||||
("ltVert", "Light vertical"),
|
||||
("dkHorz", "Dark horizontal"),
|
||||
("dkVert", "Dark vertical"),
|
||||
("narHorz", "Narrow horizontal"),
|
||||
("narVert", "Narrow vertical"),
|
||||
("dashHorz", "Dashed horizontal"),
|
||||
("dashVert", "Dashed vertical"),
|
||||
("cross", "Cross"),
|
||||
("dnDiag", "Downward diagonal"),
|
||||
("upDiag", "Upward diagonal"),
|
||||
("ltDnDiag", "Light downward diagonal"),
|
||||
("ltUpDiag", "Light upward diagonal"),
|
||||
("dkDnDiag", "Dark downward diagonal"),
|
||||
("dkUpDiag", "Dark upward diagonal"),
|
||||
("wdDnDiag", "Wide downward diagonal"),
|
||||
("wdUpDiag", "Wide upward diagonal"),
|
||||
("dashDnDiag", "Dashed downward diagonal"),
|
||||
("dashUpDiag", "Dashed upward diagonal"),
|
||||
("diagCross", "Diagonal cross"),
|
||||
("smCheck", "Small checkerboard"),
|
||||
("lgCheck", "Large checkerboard"),
|
||||
("smGrid", "Small grid"),
|
||||
("lgGrid", "Large grid"),
|
||||
("dotGrid", "Dotted grid"),
|
||||
("smConfetti", "Small confetti"),
|
||||
("lgConfetti", "Large confetti"),
|
||||
("horzBrick", "Horizontal brick"),
|
||||
("diagBrick", "Diagonal brick"),
|
||||
("solidDmnd", "Solid diamond"),
|
||||
("openDmnd", "Outlined diamond"),
|
||||
("dotDmnd", "Dotted diamond"),
|
||||
("plaid", "Plaid"),
|
||||
("sphere", "Sphere"),
|
||||
("weave", "Weave"),
|
||||
("divot", "Divot"),
|
||||
("shingle", "Shingle"),
|
||||
("wave", "Wave"),
|
||||
("trellis", "Trellis"),
|
||||
("zigZag", "Zig zag"),
|
||||
];
|
||||
|
||||
/// The name the gallery shows for preset `name` (the preset itself when it has none).
|
||||
pub fn pattern_label(name: &str) -> &str {
|
||||
PATTERN_LABELS.iter().find(|(n, _)| *n == name).map_or(name, |(_, l)| l)
|
||||
}
|
||||
|
||||
/// The command parameter for `pt` (`shape.fill {pattern}` / `design.background {pattern}`).
|
||||
pub fn pattern_json(pt: &PatternFill) -> Value {
|
||||
json!({"preset": pt.preset, "fg": cref_param(&pt.fg), "bg": cref_param(&pt.bg)})
|
||||
}
|
||||
|
||||
/// Paint preset `name` into `r`, two points per pattern cell.
|
||||
fn paint_pattern(p: &egui::Painter, r: Rect, name: &str, fg: Color32, bg: Color32) {
|
||||
const CELL: f32 = 2.0;
|
||||
p.rect_filled(r, CornerRadius::ZERO, bg);
|
||||
let tile = deckcraft_render::pattern_tile(name);
|
||||
let (nx, ny) = ((r.width() / CELL).floor().max(0.0) as usize, (r.height() / CELL).floor().max(0.0) as usize);
|
||||
for y in 0..ny {
|
||||
for x in 0..nx {
|
||||
if deckcraft_render::pattern_is_fg(&tile, x, y) {
|
||||
let min = r.min + vec2(x as f32 * CELL, y as f32 * CELL);
|
||||
p.rect_filled(Rect::from_min_size(min, vec2(CELL, CELL)), CornerRadius::ZERO, fg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The editor for `pt`: a gallery of every preset drawn in the current colors, then Foreground and
|
||||
/// Background color menus. Returns the new pattern when the user changed something.
|
||||
pub fn pattern_editor(ui: &mut Ui, pt: &PatternFill, sc: &ColorScheme) -> Option<PatternFill> {
|
||||
let t = Tokens::get(ui.ctx());
|
||||
let mut out: Option<PatternFill> = None;
|
||||
let (fg, bg) = (c32(&pt.fg, sc), c32(&pt.bg, sc));
|
||||
ui.label("Pattern");
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
ui.spacing_mut().item_spacing = vec2(2.0, 2.0);
|
||||
for name in deckcraft_render::pattern_presets() {
|
||||
let on = pt.preset == name;
|
||||
let (r, resp) = ui.allocate_exact_size(vec2(22.0, 22.0), Sense::click());
|
||||
paint_pattern(ui.painter(), r.shrink(2.0), name, fg, bg);
|
||||
let stroke = if on || resp.hovered() { Stroke::new(2.0, t.accent) } else { Stroke::new(1.0, t.border) };
|
||||
ui.painter().rect_stroke(r.shrink(1.0), CornerRadius::same(2), stroke, egui::StrokeKind::Inside);
|
||||
let label = pattern_label(name);
|
||||
resp.widget_info(|| egui::WidgetInfo::selected(egui::WidgetType::SelectableLabel, true, on, label));
|
||||
if resp.on_hover_text(label).clicked() && !on {
|
||||
out = Some(PatternFill { preset: name.to_string(), ..pt.clone() });
|
||||
}
|
||||
}
|
||||
});
|
||||
for (label, foreground) in [("Foreground", true), ("Background", false)] {
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_sized(vec2(80.0, 20.0), egui::Label::new(label));
|
||||
let cur = if foreground { &pt.fg } else { &pt.bg };
|
||||
let (sw, _) = ui.allocate_exact_size(vec2(18.0, 18.0), Sense::hover());
|
||||
ui.painter().rect(sw, CornerRadius::same(3), c32(cur, sc), Stroke::new(1.0, t.border), egui::StrokeKind::Inside);
|
||||
let r = widgets::drop_button(ui, "", vec2(24.0, 20.0), true);
|
||||
let name = format!("{label} color");
|
||||
r.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, true, &name));
|
||||
widgets::color_popup(&r).show(|ui| {
|
||||
if let Some(Some(c)) = widgets::color_grid(ui, sc, None) {
|
||||
out = Some(if foreground { PatternFill { fg: c, ..pt.clone() } } else { PatternFill { bg: c, ..pt.clone() } });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
+23
-23
@@ -475,11 +475,18 @@ fn format_shape(app: &mut SlideApp, ui: &mut Ui) {
|
||||
}
|
||||
}
|
||||
}
|
||||
color_button(app, ui, "Color", Some("No Fill"), |c| match c {
|
||||
Some(c) => ("shape.fill".into(), json!({"color": cref_param(&c)})),
|
||||
None => ("shape.fill".into(), json!({"none": true})),
|
||||
});
|
||||
// A pattern takes its colors from its own Foreground and Background menus below.
|
||||
let pattern = matches!(sh.fill, Some(Fill::Pattern(_)));
|
||||
if !pattern {
|
||||
color_button(app, ui, "Color", Some("No Fill"), |c| match c {
|
||||
Some(c) => ("shape.fill".into(), json!({"color": cref_param(&c)})),
|
||||
None => ("shape.fill".into(), json!({"none": true})),
|
||||
});
|
||||
}
|
||||
ui.horizontal(|ui| {
|
||||
if pattern {
|
||||
return;
|
||||
}
|
||||
ui.label("Transparency");
|
||||
// Seed from this shape. A shared temp defaulted to 0, so a shape that was
|
||||
// already 40% transparent showed as opaque, and leaving the slider wrote 0.
|
||||
@@ -510,6 +517,11 @@ fn format_shape(app: &mut SlideApp, ui: &mut Ui) {
|
||||
run(app, "shape.fill", json!({"gradient": crate::fillui::gradient_json(&ng)}));
|
||||
}
|
||||
}
|
||||
if let Some(Fill::Pattern(pt)) = &sh.fill
|
||||
&& let Some(np) = crate::fillui::pattern_editor(ui, pt, &scheme(app))
|
||||
{
|
||||
run(app, "shape.fill", json!({"pattern": crate::fillui::pattern_json(&np)}));
|
||||
}
|
||||
ui.add_space(8.0);
|
||||
ui.label(egui::RichText::new("Line").font(theme::bold(13.0)));
|
||||
let none = matches!(sh.line.as_ref().and_then(|l| l.fill.as_ref()), Some(Fill::None));
|
||||
@@ -871,25 +883,9 @@ fn background(app: &mut SlideApp, ui: &mut Ui) {
|
||||
}
|
||||
}
|
||||
Some(Fill::Pattern(pt)) => {
|
||||
current = Some(json!({"pattern": {"preset": pt.preset, "fg": cref_param(&pt.fg), "bg": cref_param(&pt.bg)}}));
|
||||
ui.horizontal_wrapped(|ui| {
|
||||
for preset in ["pct5", "pct20", "pct50", "ltHorz", "ltVert", "smGrid", "dkDnDiag", "wave", "dotGrid", "weave"] {
|
||||
if ui.selectable_label(pt.preset == preset, preset).clicked() {
|
||||
run(app, "design.background", json!({"pattern": {"preset": preset, "fg": cref_param(&pt.fg), "bg": cref_param(&pt.bg)}}));
|
||||
}
|
||||
}
|
||||
});
|
||||
for (label, fg) in [("Foreground", true), ("Background", false)] {
|
||||
ui.horizontal(|ui| {
|
||||
ui.label(label);
|
||||
let r = widgets::drop_button(ui, "Color", vec2(70.0, 20.0), true);
|
||||
widgets::color_popup(&r).show(|ui| {
|
||||
if let Some(Some(c)) = widgets::color_grid(ui, &sc, None) {
|
||||
let (f, b) = if fg { (c, pt.bg.clone()) } else { (pt.fg.clone(), c) };
|
||||
run(app, "design.background", json!({"pattern": {"preset": pt.preset, "fg": cref_param(&f), "bg": cref_param(&b)}}));
|
||||
}
|
||||
});
|
||||
});
|
||||
current = Some(json!({"pattern": crate::fillui::pattern_json(pt)}));
|
||||
if let Some(np) = crate::fillui::pattern_editor(ui, pt, &sc) {
|
||||
run(app, "design.background", json!({"pattern": crate::fillui::pattern_json(&np)}));
|
||||
}
|
||||
}
|
||||
Some(Fill::Picture(pf)) => {
|
||||
@@ -1479,6 +1475,10 @@ mod smartart_tests;
|
||||
#[path = "background_tests.rs"]
|
||||
mod background_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "pattern_fill_tests.rs"]
|
||||
mod pattern_fill_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
||||
|
||||
@@ -0,0 +1,131 @@
|
||||
//! Format Shape and Format Background ▸ Pattern fill: the preset gallery and the Foreground and
|
||||
//! Background color menus.
|
||||
|
||||
use deckcraft_color::Rgba;
|
||||
use deckcraft_engine::Session;
|
||||
use deckcraft_model::style::PatternFill;
|
||||
use deckcraft_model::{Background, ColorRef, Fill, ShapeId};
|
||||
use egui::accesskit::Role;
|
||||
use egui::{Event, Key, Modifiers, PointerButton};
|
||||
use egui_kittest::Harness;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::{Services, SlideApp};
|
||||
|
||||
fn harness(mut app: SlideApp, pane: &str) -> Harness<'static, SlideApp> {
|
||||
app.ui.pane = Some(pane.into());
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1800.0, 1100.0)).build_ui_state(
|
||||
|ui, app: &mut SlideApp| {
|
||||
app.logic(ui.ctx());
|
||||
app.ui(ui);
|
||||
},
|
||||
app,
|
||||
);
|
||||
h.run_steps(3);
|
||||
h
|
||||
}
|
||||
|
||||
/// A new deck with one rectangle selected and filled with `pct25` in accent 1 on background 1.
|
||||
fn patterned_shape() -> (Session, ShapeId) {
|
||||
let mut s = Session::with_new();
|
||||
let r = s.execute("shape.insert", &json!({"preset": "rect", "rect": [80.0, 80.0, 200.0, 120.0]})).unwrap();
|
||||
let id = r.get("id").and_then(|v| v.as_u64()).unwrap();
|
||||
s.execute("edit.select", &json!({"ids": [id]})).unwrap();
|
||||
s.execute("shape.fill", &json!({"pattern": {"preset": "pct25", "fg": "accent1", "bg": "bg1"}})).unwrap();
|
||||
(s, ShapeId(u32::try_from(id).unwrap()))
|
||||
}
|
||||
|
||||
fn shape_pattern(h: &Harness<'_, SlideApp>, id: ShapeId) -> PatternFill {
|
||||
match &h.state().session.active().unwrap().shape(id).unwrap().fill {
|
||||
Some(Fill::Pattern(pt)) => pt.clone(),
|
||||
other => panic!("expected a pattern fill, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn background_pattern(h: &Harness<'_, SlideApp>) -> PatternFill {
|
||||
match &h.state().session.active().unwrap().doc.slides[0].background {
|
||||
Some(Background::Fill { fill: Fill::Pattern(pt) }) => pt.clone(),
|
||||
other => panic!("expected a pattern background, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Open the color menu named `button` and pick `hex` from its More Colors field.
|
||||
fn pick_hex(h: &mut Harness<'_, SlideApp>, button: &str, hex: &str) {
|
||||
h.get_by_label(button).click();
|
||||
h.run_steps(2);
|
||||
// The hex field is the text input on the "More Colors… #" label's row, to its right.
|
||||
let label = h.get_by_label("More Colors… #").rect();
|
||||
let field = h
|
||||
.get_all_by_role(Role::TextInput)
|
||||
.find(|n| n.rect().min.x >= label.max.x - 1.0 && (n.rect().center().y - label.center().y).abs() < 6.0)
|
||||
.map(|n| n.rect().center())
|
||||
.expect("the More Colors hex field");
|
||||
for pressed in [true, false] {
|
||||
h.input_mut().events.push(Event::PointerButton { pos: field, button: PointerButton::Primary, pressed, modifiers: Modifiers::NONE });
|
||||
h.run_steps(2);
|
||||
}
|
||||
h.input_mut().events.push(Event::Text(hex.into()));
|
||||
h.run_steps(2);
|
||||
for pressed in [true, false] {
|
||||
h.input_mut().events.push(Event::Key { key: Key::Enter, physical_key: None, pressed, repeat: false, modifiers: Modifiers::NONE });
|
||||
h.run_steps(2);
|
||||
}
|
||||
h.run_steps(2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shape_pattern_fill_picks_a_preset_from_the_gallery() {
|
||||
let (s, id) = patterned_shape();
|
||||
let mut h = harness(SlideApp::new(s, Services::default()), "format");
|
||||
let _ = h.get_by_label("Pattern fill");
|
||||
assert!(h.query_by_label("Transparency").is_none(), "a pattern has no solid-fill transparency");
|
||||
assert_eq!(deckcraft_render::pattern_presets().count(), 54);
|
||||
for name in deckcraft_render::pattern_presets() {
|
||||
let _ = h.get_by_label(crate::fillui::pattern_label(name));
|
||||
}
|
||||
let undo = h.state().session.active().unwrap().history.undo.len();
|
||||
h.get_by_label("Dark downward diagonal").click();
|
||||
h.run_steps(2);
|
||||
let pt = shape_pattern(&h, id);
|
||||
assert_eq!(pt.preset, "dkDnDiag");
|
||||
assert_eq!((pt.fg, pt.bg), (ColorRef::scheme(deckcraft_color::SchemeSlot::Accent1), ColorRef::scheme(deckcraft_color::SchemeSlot::Bg1)));
|
||||
assert_eq!(h.state().session.active().unwrap().history.undo.len(), undo + 1, "one undo step");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shape_solid_fill_keeps_its_transparency_without_a_pattern_gallery() {
|
||||
let (mut s, _) = patterned_shape();
|
||||
s.execute("shape.fill", &json!({"color": "accent2"})).unwrap();
|
||||
let h = harness(SlideApp::new(s, Services::default()), "format");
|
||||
let _ = h.get_by_label("Transparency");
|
||||
assert!(h.query_by_label("Foreground color").is_none());
|
||||
assert!(h.query_by_label("Zig zag").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shape_pattern_fill_sets_its_foreground_and_background_colors() {
|
||||
let (s, id) = patterned_shape();
|
||||
let mut h = harness(SlideApp::new(s, Services::default()), "format");
|
||||
pick_hex(&mut h, "Foreground color", "00FF00");
|
||||
let pt = shape_pattern(&h, id);
|
||||
assert_eq!(pt.fg, ColorRef::rgb(Rgba::from_hex("00FF00").unwrap()));
|
||||
assert_eq!(pt.preset, "pct25", "the pattern stays");
|
||||
pick_hex(&mut h, "Background color", "112233");
|
||||
let pt = shape_pattern(&h, id);
|
||||
assert_eq!(pt.bg, ColorRef::rgb(Rgba::from_hex("112233").unwrap()));
|
||||
assert_eq!(pt.fg, ColorRef::rgb(Rgba::from_hex("00FF00").unwrap()), "the foreground stays");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn background_pattern_fill_offers_every_preset_and_both_colors() {
|
||||
let mut s = Session::with_new();
|
||||
s.execute("design.background", &json!({"pattern": {"preset": "pct20", "fg": "accent1", "bg": "bg1"}})).unwrap();
|
||||
let mut h = harness(SlideApp::new(s, Services::default()), "background");
|
||||
h.get_by_label("Zig zag").click();
|
||||
h.run_steps(2);
|
||||
assert_eq!(background_pattern(&h).preset, "zigZag");
|
||||
pick_hex(&mut h, "Background color", "FFEE00");
|
||||
let pt = background_pattern(&h);
|
||||
assert_eq!((pt.preset.as_str(), pt.bg), ("zigZag", ColorRef::rgb(Rgba::from_hex("FFEE00").unwrap())));
|
||||
}
|
||||
+1
-1
@@ -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), 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 54 presets, 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) |
|
||||
| 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 |
|
||||
|
||||
Reference in New Issue
Block a user