mirror of
https://github.com/storytold/deckcraft.git
synced 2026-10-24 10:37:03 -06:00
Merge PR #687 from NikitkaRa: UI: Create New Theme Fonts dialog edits the theme in one step
# Conflicts: # crates/engine/src/cmd/design.rs # crates/ui-egui/src/dialogs.rs
This commit is contained in:
@@ -23,6 +23,7 @@
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (Photo Album dialog; breadth stays 79%, ready ~55%; previous: minor, hours to ~95% per audience) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (Edit Photo Album; breadth stays 79%, ready ~55%; previous: trivial, Photo Album dialog) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (Create New Theme Colors; breadth and ready percentages unchanged; previous: hours to ~95% per audience) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (Create New Theme Fonts; breadth and ready percentages unchanged; previous: hours to ~95% per audience) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
|
||||
DeckCraft aims at full PowerPoint parity, and to be better: faster, open (a documented zip+JSON
|
||||
format plus PPTX), scriptable by agents (CLI, JSON control channel, MCP), and available everywhere
|
||||
@@ -180,6 +181,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
| 2026-10-14 | Edit Photo Album (`insert.photoAlbumEdit`): reopen the album's slides and re-flow them in one undo step, with black and white, rotation, brightness, contrast and an album theme. Breadth stays 79%, ready ~55% |
|
||||
| 2026-10-14 | Photo Album dialog (`insert.photoAlbum`): pictures and text boxes become slides in the open presentation, one undo step. Breadth stays 79%, ready ~55% |
|
||||
| 2026-10-14 | Design › Colors › Customize Colors… opens Create New Theme Colors: the open master's scheme name and twelve theme colours, applied in one undo. Command count stays 226; dialogs 27 → 28 |
|
||||
| 2026-10-14 | Design › Fonts › Customize Fonts… opens Create New Theme Fonts: the open master's scheme name and its heading and body latin faces, applied in one undo. Command count stays 226; dialogs 27 → 28 |
|
||||
| 2026-10-13 | Slide transitions like PowerPoint: every transition and option measured frame by frame against PowerPoint's video export and retuned (curves, directions, soft edges, 3-D perspective, contents moving over backgrounds); PowerPoint's own XML elements and defaults read and written (diagonal Wipe, Zoom, Shape In/Out, Glitter Hexagons, Shred In/Out, Page Curl Single); `deckcraft-cli render-transition` renders frames headless |
|
||||
| 2026-10-13 | Wild text cases measured against PowerPoint (102 synthetic slides): bidi by run language, East Asian vertical text upright, mongolianVert/wordArtVert/wordArtVertRtl, underline styles and strikethrough at the font's metrics, centre/right/decimal tabs, East Asian, Thai, Hindi, Arabic and Hebrew numbering, small caps, all-caps "ß", transparent text, soft hyphens, kinsoku, rtlCol and just/dist anchoring, emoji presentation, fallback faces that no longer move the baseline |
|
||||
| 2026-10-13 | Render matches PowerPoint more closely on wild shape, picture and table cases: even-odd custom paths, dashes and caps, arrowheads, radial and path gradients, tile rects, rotWithShape, gradient lines, shadows cast by what is drawn (group and perspective too), group fill, picture borders, tiles, reflection, recolour (biLevel, alphaInv, brightness/contrast), table borders and grid default, 20+ preset shapes by their ECMA formulas, default miter joins, and approximate 3-D bevels, cameras and face lighting |
|
||||
@@ -228,6 +230,7 @@ app's JSON control channel (`deckcraft --control PORT`).
|
||||
| 2026-10-14 | trivial | Progress log: Photo Album dialog. Breadth stays 79%, ready ~55% |
|
||||
| 2026-10-14 | trivial | Progress log: Edit Photo Album. Breadth stays 79%, ready ~55% |
|
||||
| 2026-10-14 | trivial | Progress log: Create New Theme Colors |
|
||||
| 2026-10-14 | trivial | Progress log: Create New Theme Fonts |
|
||||
| 2026-10-13 | trivial | Progress log: wild text cases |
|
||||
| 2026-10-13 | trivial | Progress log: slide show for interactive decks |
|
||||
| 2026-10-12 | trivial | Progress log: File tab (Backstage, #76) |
|
||||
|
||||
+194
-15
@@ -25,7 +25,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_doc,
|
||||
colors
|
||||
),
|
||||
cmd!("design.fonts", "Fonts", ["Design", "Variants"], None, "{name} | {major, minor}", has_doc, fonts),
|
||||
cmd!("design.fonts", "Fonts", ["Design", "Variants"], None, "{} opens Create New Theme Fonts | {name} | {major, minor}", has_doc, fonts),
|
||||
cmd!(
|
||||
"design.background",
|
||||
"Format Background",
|
||||
@@ -204,28 +204,109 @@ fn next_scheme(
|
||||
}
|
||||
|
||||
fn fonts(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let scheme = if let Some(name) = str_param(p, "name") {
|
||||
builtin_font_schemes()
|
||||
.into_iter()
|
||||
.find(|f| f.name.eq_ignore_ascii_case(name))
|
||||
.ok_or_else(|| bad("design.fonts", format!("no font scheme `{name}`")))?
|
||||
} else {
|
||||
let major = str_param(p, "major").ok_or_else(|| bad("design.fonts", "give `name`, or `major` and `minor`"))?;
|
||||
let minor = str_param(p, "minor").unwrap_or(major);
|
||||
deckcraft_model::theme::FontScheme {
|
||||
name: "Custom".into(),
|
||||
major: deckcraft_model::theme::FontSet { latin: major.into(), ..Default::default() },
|
||||
minor: deckcraft_model::theme::FontSet { latin: minor.into(), ..Default::default() },
|
||||
let name_v = p.get("name");
|
||||
let major_v = p.get("major");
|
||||
let minor_v = p.get("minor");
|
||||
if name_v.is_none() && major_v.is_none() && minor_v.is_none() {
|
||||
let scheme = current_fonts(s)?;
|
||||
s.ui_requests.push(crate::UiRequest::Dialog { id: "themeFonts".into(), params: font_dialog_params(&scheme) });
|
||||
return ok();
|
||||
}
|
||||
let given = match name_v {
|
||||
None => None,
|
||||
Some(Value::String(n)) => {
|
||||
let n = n.trim();
|
||||
if n.is_empty() {
|
||||
return Err(bad("design.fonts", "name is empty"));
|
||||
}
|
||||
Some(n.to_string())
|
||||
}
|
||||
Some(_) => return Err(bad("design.fonts", "`name` must be a string")),
|
||||
};
|
||||
let major = face_param("major", major_v)?;
|
||||
let minor = face_param("minor", minor_v)?;
|
||||
let current = current_fonts(s)?;
|
||||
let next = next_font_scheme(¤t, given.as_deref(), major.as_deref(), minor.as_deref())?;
|
||||
if next == current {
|
||||
return ok();
|
||||
}
|
||||
s.edit(|doc, sel| {
|
||||
let mi = master_index(doc, sel);
|
||||
Arc::make_mut(doc.masters.get_mut(mi).ok_or_else(|| bad("design.fonts", "no master"))?).theme.fonts = scheme;
|
||||
Arc::make_mut(doc.masters.get_mut(mi).ok_or_else(|| bad("design.fonts", "no master"))?).theme.fonts = next;
|
||||
Ok(())
|
||||
})?;
|
||||
ok()
|
||||
}
|
||||
|
||||
fn current_fonts(s: &Session) -> Result<deckcraft_model::theme::FontScheme> {
|
||||
let st = s.doc()?;
|
||||
let mi = master_index(&st.doc, &st.selection);
|
||||
st.doc.masters.get(mi).map(|m| m.theme.fonts.clone()).ok_or_else(|| bad("design.fonts", "no master"))
|
||||
}
|
||||
|
||||
fn font_dialog_params(scheme: &deckcraft_model::theme::FontScheme) -> Value {
|
||||
json!({"name": scheme.name, "major": scheme.major.latin, "minor": scheme.minor.latin})
|
||||
}
|
||||
|
||||
/// `None` when the key is absent. A present face must be a non-blank string.
|
||||
fn face_param(key: &str, v: Option<&Value>) -> Result<Option<String>> {
|
||||
match v {
|
||||
None => Ok(None),
|
||||
Some(Value::String(s)) => {
|
||||
let s = s.trim();
|
||||
if s.is_empty() { Err(bad("design.fonts", format!("`{key}` is empty"))) } else { Ok(Some(s.to_string())) }
|
||||
}
|
||||
Some(_) => Err(bad("design.fonts", format!("`{key}` must be a string"))),
|
||||
}
|
||||
}
|
||||
|
||||
/// A builtin `name` is applied first, then `major` / `minor` replace only the latin faces (East
|
||||
/// Asian, complex-script and per-script slots stay). The stored name is the given string when
|
||||
/// faces are overlaid or the name is not a builtin; a name-only builtin keeps its own name.
|
||||
/// Faces without a name are a custom scheme: an omitted `minor` copies `major`.
|
||||
fn next_font_scheme(
|
||||
current: &deckcraft_model::theme::FontScheme,
|
||||
given: Option<&str>,
|
||||
major: Option<&str>,
|
||||
minor: Option<&str>,
|
||||
) -> Result<deckcraft_model::theme::FontScheme> {
|
||||
let mut next = current.clone();
|
||||
if let Some(name) = given {
|
||||
let replaced = if let Some(builtin) = builtin_font_schemes().into_iter().find(|f| f.name.eq_ignore_ascii_case(name)) {
|
||||
next = builtin;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
if !replaced && major.is_none() && minor.is_none() {
|
||||
return Err(bad("design.fonts", format!("no font scheme `{name}`")));
|
||||
}
|
||||
if major.is_some() || minor.is_some() || !replaced {
|
||||
next.name = name.to_string();
|
||||
}
|
||||
}
|
||||
if given.is_none() {
|
||||
if let Some(face) = major {
|
||||
next.major.latin = face.to_string();
|
||||
if minor.is_none() {
|
||||
next.minor.latin = face.to_string();
|
||||
}
|
||||
}
|
||||
if let Some(face) = minor {
|
||||
next.minor.latin = face.to_string();
|
||||
}
|
||||
next.name = "Custom".into();
|
||||
} else {
|
||||
if let Some(face) = major {
|
||||
next.major.latin = face.to_string();
|
||||
}
|
||||
if let Some(face) = minor {
|
||||
next.minor.latin = face.to_string();
|
||||
}
|
||||
}
|
||||
Ok(next)
|
||||
}
|
||||
|
||||
fn background(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let bg: Option<Background> = if bool_or(p, "reset", false) {
|
||||
None
|
||||
@@ -643,7 +724,7 @@ fn insert_placeholder(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use deckcraft_color::SchemeSlot;
|
||||
use deckcraft_model::theme::builtin_color_schemes;
|
||||
use deckcraft_model::theme::{FontScheme, ScriptFont, builtin_color_schemes, builtin_font_schemes};
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use crate::{Session, UiRequest};
|
||||
@@ -652,6 +733,10 @@ mod tests {
|
||||
s.doc().unwrap().doc.masters[0].theme.colors.clone()
|
||||
}
|
||||
|
||||
fn font_scheme(s: &Session) -> FontScheme {
|
||||
s.doc().unwrap().doc.masters[0].theme.fonts.clone()
|
||||
}
|
||||
|
||||
fn undo_len(s: &Session) -> usize {
|
||||
s.doc().unwrap().history.undo.len()
|
||||
}
|
||||
@@ -732,4 +817,98 @@ mod tests {
|
||||
assert_eq!(scheme(&s).name, "Meadow");
|
||||
assert_eq!(scheme(&s).get(SchemeSlot::Dk1), meadow.get(SchemeSlot::Dk1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fonts_dialog_apply_and_reject() {
|
||||
let mut s = Session::with_new();
|
||||
let original = font_scheme(&s);
|
||||
let n = undo_len(&s);
|
||||
s.execute("design.fonts", &json!({})).unwrap();
|
||||
s.execute("design.fonts", &Value::Null).unwrap();
|
||||
s.execute("design.fonts", &json!([1, 2])).unwrap();
|
||||
assert_eq!(font_scheme(&s), original);
|
||||
assert_eq!(undo_len(&s), n, "opening the dialog does not edit");
|
||||
assert_eq!(s.ui_requests.len(), 3);
|
||||
match &s.ui_requests[0] {
|
||||
UiRequest::Dialog { id, params } => {
|
||||
assert_eq!(id, "themeFonts");
|
||||
assert_eq!(params["name"], original.name);
|
||||
assert_eq!(params["major"], original.major.latin);
|
||||
assert_eq!(params["minor"], original.minor.latin);
|
||||
}
|
||||
other => panic!("expected a dialog, got {other:?}"),
|
||||
}
|
||||
|
||||
s.execute("design.fonts", &json!({"name": "classic"})).unwrap();
|
||||
let classic = builtin_font_schemes().into_iter().find(|f| f.name == "Classic").unwrap();
|
||||
assert_eq!(font_scheme(&s), classic, "a builtin name keeps that scheme, including its own name");
|
||||
assert_eq!(undo_len(&s), n + 1);
|
||||
assert_eq!(s.doc().unwrap().history.undo.last().unwrap().label, "Fonts");
|
||||
s.execute("design.fonts", &json!({"name": "Classic"})).unwrap();
|
||||
assert_eq!(undo_len(&s), n + 1, "the same scheme is not an undo step");
|
||||
|
||||
let err = s.execute("design.fonts", &json!({"name": "Nope"})).unwrap_err().to_string();
|
||||
assert!(err.contains("no font scheme `Nope`"), "{err}");
|
||||
assert!(s.execute("design.fonts", &json!({"name": " "})).is_err());
|
||||
assert!(s.execute("design.fonts", &json!({"name": 1})).is_err());
|
||||
assert!(s.execute("design.fonts", &json!({"major": ""})).is_err());
|
||||
assert!(s.execute("design.fonts", &json!({"major": " "})).is_err());
|
||||
assert!(s.execute("design.fonts", &json!({"minor": 1})).is_err());
|
||||
assert!(s.execute("design.fonts", &json!({"name": "Classic", "major": ""})).is_err());
|
||||
assert_eq!(font_scheme(&s), classic);
|
||||
assert_eq!(undo_len(&s), n + 1);
|
||||
|
||||
s.edit(|doc, _| {
|
||||
let fonts = &mut std::sync::Arc::make_mut(&mut doc.masters[0]).theme.fonts;
|
||||
fonts.major.ea = "EA Major".into();
|
||||
fonts.minor.cs = "CS Minor".into();
|
||||
fonts.major.scripts.push(ScriptFont { script: "Arab".into(), typeface: "Noto Naskh Arabic".into() });
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
let marked = font_scheme(&s);
|
||||
let n = undo_len(&s);
|
||||
|
||||
s.execute("design.fonts", &json!({"major": " Lato "})).unwrap();
|
||||
let custom = font_scheme(&s);
|
||||
assert_eq!(custom.name, "Custom");
|
||||
assert_eq!(custom.major.latin, "Lato");
|
||||
assert_eq!(custom.minor.latin, "Lato", "an omitted minor copies major");
|
||||
assert_eq!(custom.major.ea, "EA Major");
|
||||
assert_eq!(custom.minor.cs, "CS Minor");
|
||||
assert_eq!(custom.major.scripts, marked.major.scripts);
|
||||
assert_eq!(undo_len(&s), n + 1);
|
||||
|
||||
s.execute("design.fonts", &json!({"minor": "Roboto"})).unwrap();
|
||||
assert_eq!(font_scheme(&s).name, "Custom");
|
||||
assert_eq!(font_scheme(&s).major.latin, "Lato");
|
||||
assert_eq!(font_scheme(&s).minor.latin, "Roboto");
|
||||
assert_eq!(font_scheme(&s).major.ea, "EA Major");
|
||||
assert_eq!(undo_len(&s), n + 2);
|
||||
|
||||
s.execute("design.fonts", &json!({"name": "Classic", "major": "Inter"})).unwrap();
|
||||
assert_eq!(font_scheme(&s).name, "Classic");
|
||||
assert_eq!(font_scheme(&s).major.latin, "Inter");
|
||||
assert_eq!(font_scheme(&s).minor.latin, "Open Sans");
|
||||
assert_eq!(font_scheme(&s).major.ea, "");
|
||||
assert!(font_scheme(&s).major.scripts.is_empty(), "a builtin is applied before the latin overlay");
|
||||
assert_eq!(undo_len(&s), n + 3);
|
||||
|
||||
s.edit(|doc, _| {
|
||||
std::sync::Arc::make_mut(&mut doc.masters[0]).theme.fonts.major.ea = "Kept".into();
|
||||
Ok(())
|
||||
})
|
||||
.unwrap();
|
||||
s.execute("design.fonts", &json!({"name": "Talk", "minor": "Nunito Sans"})).unwrap();
|
||||
assert_eq!(font_scheme(&s).name, "Talk");
|
||||
assert_eq!(font_scheme(&s).major.latin, "Inter");
|
||||
assert_eq!(font_scheme(&s).minor.latin, "Nunito Sans");
|
||||
assert_eq!(font_scheme(&s).major.ea, "Kept");
|
||||
assert_eq!(s.doc().unwrap().history.undo.last().unwrap().label, "Fonts");
|
||||
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert_eq!(font_scheme(&s).name, "Classic");
|
||||
assert_eq!(font_scheme(&s).major.ea, "Kept");
|
||||
assert_eq!(font_scheme(&s).minor.latin, "Open Sans");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,8 +12,8 @@ pub mod insert;
|
||||
pub mod inspect;
|
||||
pub mod media;
|
||||
pub mod merge;
|
||||
pub mod points;
|
||||
pub mod picture_style;
|
||||
pub mod points;
|
||||
pub mod recolor;
|
||||
pub mod review;
|
||||
pub mod selection;
|
||||
|
||||
@@ -62,6 +62,7 @@ fn title(id: &str) -> &'static str {
|
||||
"bullets" => "Bullets and Numbering",
|
||||
"quit" => "DeckCraft",
|
||||
"themeColors" => "Create New Theme Colors",
|
||||
"themeFonts" => "Create New Theme Fonts",
|
||||
_ => "DeckCraft",
|
||||
}
|
||||
}
|
||||
@@ -76,15 +77,23 @@ pub fn show(app: &mut SlideApp, ctx: &egui::Context) {
|
||||
.resizable(false)
|
||||
.anchor(Align2::CENTER_CENTER, vec2(0.0, -40.0))
|
||||
.open(&mut open);
|
||||
// Two columns of colour rows plus Save must fit the first frame. A later resize clips the
|
||||
// buttons (the window size sticks) and clicks miss them.
|
||||
// Two columns of colour rows plus Save must fit the first frame (Theme Colors), as must Save
|
||||
// in Theme Fonts. A later resize clips the buttons (the window size sticks) and clicks miss them.
|
||||
if d.id == "themeColors" {
|
||||
window = window.default_size(vec2(760.0, 420.0)).min_width(720.0).min_height(420.0);
|
||||
} else if d.id == "themeFonts" {
|
||||
window = window.default_size(vec2(460.0, 320.0)).min_width(420.0).min_height(300.0);
|
||||
}
|
||||
window.show(ctx, |ui| {
|
||||
ui.set_min_width(if d.id == "themeColors" { 700.0 } else { 340.0 });
|
||||
ui.set_min_width(match d.id.as_str() {
|
||||
"themeColors" => 700.0,
|
||||
"themeFonts" => 400.0,
|
||||
_ => 340.0,
|
||||
});
|
||||
if d.id == "themeColors" {
|
||||
ui.set_min_height(360.0);
|
||||
} else if d.id == "themeFonts" {
|
||||
ui.set_min_height(280.0);
|
||||
}
|
||||
close = body(app, ui, &mut d);
|
||||
});
|
||||
@@ -552,6 +561,117 @@ fn theme_colors_dialog(app: &mut SlideApp, ui: &mut Ui, d: &mut Dialog) -> bool
|
||||
cancel
|
||||
}
|
||||
|
||||
fn seed_theme_fonts(d: &mut Dialog) {
|
||||
if d.fields.contains_key("seeded") {
|
||||
return;
|
||||
}
|
||||
let name = d.params.get("name").and_then(Value::as_str).unwrap_or("Custom");
|
||||
let major = d.params.get("major").and_then(Value::as_str).unwrap_or("Inter");
|
||||
let minor = d.params.get("minor").and_then(Value::as_str).unwrap_or(major);
|
||||
d.fields.insert("schemeName".into(), name.to_string());
|
||||
d.fields.insert("major".into(), major.to_string());
|
||||
d.fields.insert("minor".into(), minor.to_string());
|
||||
d.fields.insert("seeded".into(), "1".into());
|
||||
}
|
||||
|
||||
fn theme_font_choices(app: &SlideApp, selected: &str) -> Vec<String> {
|
||||
let mut v = crate::ribbon::font_families(app);
|
||||
for scheme in deckcraft_model::theme::builtin_font_schemes() {
|
||||
v.push(scheme.major.latin);
|
||||
v.push(scheme.minor.latin);
|
||||
}
|
||||
if !selected.is_empty() {
|
||||
v.push(selected.to_string());
|
||||
}
|
||||
let mut seen = std::collections::HashSet::new();
|
||||
v.retain(|face| !face.is_empty() && seen.insert(face.clone()));
|
||||
v
|
||||
}
|
||||
|
||||
fn font_combo(ui: &mut Ui, label: &str, choices: &[String], selected: &mut String) {
|
||||
let resp = egui::ComboBox::from_id_salt(label).selected_text(selected.as_str()).width(240.0).show_ui(ui, |ui| {
|
||||
for face in choices {
|
||||
if ui.selectable_label(selected == face, face).clicked() {
|
||||
*selected = face.clone();
|
||||
}
|
||||
}
|
||||
});
|
||||
let shown = selected.clone();
|
||||
let access = label.to_string();
|
||||
resp.response.widget_info(move || {
|
||||
let mut info = egui::WidgetInfo::labeled(egui::WidgetType::ComboBox, true, access.clone());
|
||||
info.current_text_value = Some(shown.clone());
|
||||
info
|
||||
});
|
||||
}
|
||||
|
||||
fn theme_font_payload(d: &mut Dialog) -> Result<Value, String> {
|
||||
let name = d.get("schemeName", "").trim().to_string();
|
||||
if name.is_empty() {
|
||||
return Err("Enter a scheme name.".into());
|
||||
}
|
||||
let major = d.get("major", "").trim().to_string();
|
||||
if major.is_empty() {
|
||||
return Err("Choose a heading font.".into());
|
||||
}
|
||||
let minor = d.get("minor", "").trim().to_string();
|
||||
if minor.is_empty() {
|
||||
return Err("Choose a body font.".into());
|
||||
}
|
||||
Ok(json!({"name": name, "major": major, "minor": minor}))
|
||||
}
|
||||
|
||||
/// Create New Theme Fonts. Returns true when the dialog should close.
|
||||
fn theme_fonts_dialog(app: &mut SlideApp, ui: &mut Ui, d: &mut Dialog) -> bool {
|
||||
seed_theme_fonts(d);
|
||||
let major = d.get("major", "Inter");
|
||||
let minor = d.get("minor", "Inter");
|
||||
let headings = theme_font_choices(app, &major);
|
||||
let bodies = theme_font_choices(app, &minor);
|
||||
let mut major = major;
|
||||
let mut minor = minor;
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_sized(vec2(110.0, 18.0), egui::Label::new("Heading:"));
|
||||
font_combo(ui, "Heading font", &headings, &mut major);
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_sized(vec2(110.0, 18.0), egui::Label::new("Body:"));
|
||||
font_combo(ui, "Body font", &bodies, &mut minor);
|
||||
});
|
||||
d.fields.insert("major".into(), major.clone());
|
||||
d.fields.insert("minor".into(), minor.clone());
|
||||
ui.add_space(4.0);
|
||||
// Theme faces are not egui families; naming an unbound family panics. The lines show the
|
||||
// chosen names at heading and body sizes.
|
||||
ui.label(egui::RichText::new(format!("Heading sample {major}")).size(20.0));
|
||||
ui.label(egui::RichText::new(format!("Body sample {minor}")).size(14.0));
|
||||
ui.add_space(4.0);
|
||||
let mut name = d.get("schemeName", "");
|
||||
ui.horizontal(|ui| {
|
||||
ui.add_sized(vec2(110.0, 18.0), egui::Label::new("Name:"));
|
||||
let resp = ui.add(egui::TextEdit::singleline(&mut name).desired_width(240.0).id_salt("schemeName"));
|
||||
let shown = name.clone();
|
||||
resp.widget_info(move || {
|
||||
let mut info = egui::WidgetInfo::labeled(egui::WidgetType::TextEdit, true, "Scheme name");
|
||||
info.current_text_value = Some(shown.clone());
|
||||
info
|
||||
});
|
||||
});
|
||||
d.fields.insert("schemeName".into(), name);
|
||||
ui.label(egui::RichText::new("Heading and body fonts for this presentation.").size(11.0).color(Tokens::get(ui.ctx()).text_dim));
|
||||
let (ok, cancel) = buttons(ui, "Save");
|
||||
if ok {
|
||||
match theme_font_payload(d) {
|
||||
Ok(payload) => return app.run("design.fonts", payload).is_ok(),
|
||||
Err(msg) => {
|
||||
app.set_status(msg);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
cancel
|
||||
}
|
||||
|
||||
/// Returns true when the dialog should close.
|
||||
fn body(app: &mut SlideApp, ui: &mut Ui, d: &mut Dialog) -> bool {
|
||||
let run = |app: &mut SlideApp, id: &str, p: Value| {
|
||||
@@ -1301,6 +1421,7 @@ fn body(app: &mut SlideApp, ui: &mut Ui, d: &mut Dialog) -> bool {
|
||||
}
|
||||
"photoAlbum" => photo_album_dialog(app, ui, d),
|
||||
"themeColors" => theme_colors_dialog(app, ui, d),
|
||||
"themeFonts" => theme_fonts_dialog(app, ui, d),
|
||||
_ => {
|
||||
ui.label(format!("“{}” is not available yet.", d.id));
|
||||
let (ok, cancel) = buttons(ui, "OK");
|
||||
@@ -1651,3 +1772,7 @@ mod photo_album_tests;
|
||||
#[cfg(test)]
|
||||
#[path = "theme_colors_tests.rs"]
|
||||
mod theme_colors_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "theme_fonts_tests.rs"]
|
||||
mod theme_fonts_tests;
|
||||
|
||||
@@ -1212,18 +1212,18 @@ fn js_now() -> f64 {
|
||||
js_sys::Date::now()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod eyedropper_tests;
|
||||
#[cfg(test)]
|
||||
mod animpane_tests;
|
||||
#[cfg(test)]
|
||||
mod eyedropper_tests;
|
||||
#[cfg(test)]
|
||||
mod huge_deck_tests;
|
||||
#[cfg(test)]
|
||||
mod ime_tests;
|
||||
#[cfg(test)]
|
||||
mod ui_soak_tests;
|
||||
#[cfg(test)]
|
||||
mod slide_size_tests;
|
||||
#[cfg(test)]
|
||||
mod ui_soak_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -1355,12 +1355,18 @@ fn design(app: &mut SlideApp, ui: &mut Ui) {
|
||||
}
|
||||
});
|
||||
let r = big_button(ui, Icon::Fonts, "Fonts", true);
|
||||
r.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, true, "Fonts"));
|
||||
egui::Popup::menu(&r).width(220.0).show(|ui| {
|
||||
for fs in deckcraft_model::theme::builtin_font_schemes() {
|
||||
if ui.button(format!("{}\n {} / {}", fs.name, fs.major.latin, fs.minor.latin)).clicked() {
|
||||
run(app, "design.fonts", json!({"name": fs.name}));
|
||||
}
|
||||
}
|
||||
ui.separator();
|
||||
if ui.button("Customize Fonts…").clicked() {
|
||||
run(app, "design.fonts", json!({}));
|
||||
ui.close();
|
||||
}
|
||||
});
|
||||
let r = big_button(ui, Icon::BackgroundStyles, "Background\nStyles", true);
|
||||
egui::Popup::menu(&r).show(|ui| {
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
//! Design › Fonts › Customize Fonts… opens Create New Theme Fonts and saves in one step.
|
||||
|
||||
use deckcraft_engine::Session;
|
||||
use egui_kittest::Harness;
|
||||
use egui_kittest::kittest::Queryable;
|
||||
|
||||
use crate::{Services, SlideApp};
|
||||
|
||||
fn harness() -> Harness<'static, SlideApp> {
|
||||
let mut app = SlideApp::new(Session::with_new(), Services::default());
|
||||
app.ui.tab = "Design".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
|
||||
}
|
||||
|
||||
fn scheme(h: &Harness<'_, SlideApp>) -> deckcraft_model::theme::FontScheme {
|
||||
h.state().session.active().unwrap().doc.masters[0].theme.fonts.clone()
|
||||
}
|
||||
|
||||
fn undo_len(h: &Harness<'_, SlideApp>) -> usize {
|
||||
h.state().session.active().unwrap().history.undo.len()
|
||||
}
|
||||
|
||||
fn open_dialog(h: &mut Harness<'_, SlideApp>) {
|
||||
h.get_by_label("Fonts").click();
|
||||
h.run_steps(2);
|
||||
h.get_by_label("Customize Fonts…").click();
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.state().dialog.as_ref().map(|d| d.id.as_str()), Some("themeFonts"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn customize_fonts_opens_from_the_design_tab() {
|
||||
let mut h = harness();
|
||||
let before = scheme(&h);
|
||||
let n = undo_len(&h);
|
||||
open_dialog(&mut h);
|
||||
assert_eq!(h.get_by_label("Scheme name").value().as_deref(), Some(before.name.as_str()));
|
||||
assert_eq!(h.get_by_label("Heading font").value().as_deref(), Some(before.major.latin.as_str()));
|
||||
assert_eq!(h.get_by_label("Body font").value().as_deref(), Some(before.minor.latin.as_str()));
|
||||
let _ = h.get_by_label(&format!("Heading sample {}", before.major.latin));
|
||||
let _ = h.get_by_label(&format!("Body sample {}", before.minor.latin));
|
||||
let _ = h.get_by_label("Save");
|
||||
let _ = h.get_by_label("Cancel");
|
||||
let save = h.get_by_label("Save").rect();
|
||||
let win = h.get_by_label("Create New Theme Fonts").rect();
|
||||
assert!(win.contains(save.center()), "Save {save:?} is outside the dialog window {win:?}");
|
||||
h.get_by_label("Save").click();
|
||||
h.run_steps(2);
|
||||
assert!(h.state().dialog.is_none());
|
||||
assert_eq!(scheme(&h), before);
|
||||
assert_eq!(undo_len(&h), n, "saving the current fonts is not an undo step");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_applies_the_name_and_both_faces() {
|
||||
let mut h = harness();
|
||||
let before = scheme(&h);
|
||||
let n = undo_len(&h);
|
||||
open_dialog(&mut h);
|
||||
{
|
||||
let d = h.state_mut().dialog.as_mut().unwrap();
|
||||
d.fields.insert("schemeName".into(), "Talk".into());
|
||||
d.fields.insert("major".into(), "Lato".into());
|
||||
d.fields.insert("minor".into(), "Roboto".into());
|
||||
}
|
||||
h.get_by_label("Save").click();
|
||||
h.run_steps(2);
|
||||
assert!(h.state().dialog.is_none(), "status: {:?}", h.state().status);
|
||||
let after = scheme(&h);
|
||||
assert_eq!(after.name, "Talk");
|
||||
assert_eq!(after.major.latin, "Lato");
|
||||
assert_eq!(after.minor.latin, "Roboto");
|
||||
assert_eq!(after.major.ea, before.major.ea);
|
||||
assert_eq!(undo_len(&h), n + 1);
|
||||
assert_eq!(h.state().session.active().unwrap().history.undo.last().unwrap().label, "Fonts");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_blank_heading_keeps_the_dialog_open() {
|
||||
let mut h = harness();
|
||||
let before = scheme(&h);
|
||||
let n = undo_len(&h);
|
||||
open_dialog(&mut h);
|
||||
h.state_mut().dialog.as_mut().unwrap().fields.insert("major".into(), " ".into());
|
||||
h.get_by_label("Save").click();
|
||||
h.run_steps(2);
|
||||
assert_eq!(h.state().dialog.as_ref().map(|d| d.id.as_str()), Some("themeFonts"));
|
||||
assert_eq!(scheme(&h), before);
|
||||
assert_eq!(undo_len(&h), n);
|
||||
let status = h.state().status.as_ref().map(|(t, _)| t.clone()).unwrap_or_default();
|
||||
assert!(status.contains("heading"), "{status}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cancel_closes_without_editing() {
|
||||
let mut h = harness();
|
||||
let before = scheme(&h);
|
||||
let n = undo_len(&h);
|
||||
open_dialog(&mut h);
|
||||
h.state_mut().dialog.as_mut().unwrap().fields.insert("major".into(), "Lato".into());
|
||||
h.get_by_label("Cancel").click();
|
||||
h.run_steps(2);
|
||||
assert!(h.state().dialog.is_none());
|
||||
assert_eq!(scheme(&h), before);
|
||||
assert_eq!(undo_len(&h), n);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn typing_a_name_replaces_the_scheme_name() {
|
||||
let mut h = harness();
|
||||
let n = undo_len(&h);
|
||||
open_dialog(&mut h);
|
||||
h.get_by_label("Scheme name").click();
|
||||
h.run_steps(1);
|
||||
h.key_press_modifiers(egui::Modifiers::COMMAND, egui::Key::A);
|
||||
h.run_steps(1);
|
||||
h.get_by_label("Scheme name").type_text("Talk");
|
||||
h.run_steps(1);
|
||||
let shown = h.get_by_label("Scheme name").value().unwrap_or_default();
|
||||
assert_eq!(shown, "Talk", "the name field should replace its text, got {shown}");
|
||||
h.get_by_label("Save").click();
|
||||
h.run_steps(2);
|
||||
assert!(h.state().dialog.is_none(), "status: {:?}", h.state().status);
|
||||
assert_eq!(scheme(&h).name, "Talk");
|
||||
assert_eq!(scheme(&h).major.latin, "Inter");
|
||||
assert_eq!(scheme(&h).minor.latin, "Inter");
|
||||
assert_eq!(undo_len(&h), n + 1);
|
||||
}
|
||||
@@ -5,6 +5,7 @@
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (Photo Album dialog, `insert.photoAlbum`; 227 commands, 28 dialogs; previous: minor, transition frames) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (Edit Photo Album, `insert.photoAlbumEdit`; 228 commands, 28 dialogs; previous: trivial, Photo Album dialog) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (Create New Theme Colors dialog; previous: transition frames) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (Create New Theme Fonts dialog; previous: transition frames) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
|
||||
How DeckCraft is built today. The binding rules (layering, everything is a command, thin UI, never
|
||||
break wasm, never crash) are in [AGENTS.md](../AGENTS.md) §3–§5; this document describes what
|
||||
@@ -32,6 +33,8 @@ nothing below L6 uses egui, eframe, winit or rfd.
|
||||
| L6 | `ui-egui`, `mcp` | The PowerPoint-style interface (ribbon with 10 tabs + contextual tabs, canvas, thumbnails, panes, 28 dialogs, show and presenter windows, the JSON control channel); the MCP server (JSON-RPC 2.0 over stdio), headless or connected to a running app |
|
||||
| L5 | `engine` | Sessions, documents, selection, undo history on shared slide snapshots, the command registry (226 commands in `cmd/*`), pointer tools, keys, AutoRecover, the panic guard (`guard.rs`) |
|
||||
| L6 | `ui-egui`, `mcp` | The PowerPoint-style interface (ribbon with 10 tabs + contextual tabs, canvas, thumbnails, panes, 28 dialogs, show and presenter windows, the JSON control channel); the MCP server (JSON-RPC 2.0 over stdio), headless or connected to a running app |
|
||||
| L5 | `engine` | Sessions, documents, selection, undo history on shared slide snapshots, the command registry (226 commands in `cmd/*`), pointer tools, keys, AutoRecover, the panic guard (`guard.rs`) |
|
||||
| L6 | `ui-egui`, `mcp` | The PowerPoint-style interface (ribbon with 10 tabs + contextual tabs, canvas, thumbnails, panes, 28 dialogs, show and presenter windows, the JSON control channel); the MCP server (JSON-RPC 2.0 over stdio), headless or connected to a running app |
|
||||
| apps | `deckcraft` (desktop: eframe/wgpu, cpal audio, logging), `deckcraft-cli` (run, render, convert, mcp, app), `deckcraft-web` (trunk; WebGPU with WebGL2 fallback), `xtask` | Exempt from layering |
|
||||
|
||||
The media codecs and containers are clean-room pure Rust, 59k lines, shared in spirit with
|
||||
@@ -93,5 +96,6 @@ macOS, Windows, Linux, FreeBSD and web artifacts.
|
||||
| 2026-10-14 | trivial | Photo Album dialog and `insert.photoAlbum` (227 commands, 28 dialogs) |
|
||||
| 2026-10-14 | trivial | Edit Photo Album (`insert.photoAlbumEdit`) reopens the Photo Album dialog and re-flows those slides (228 commands, 28 dialogs) |
|
||||
| 2026-10-14 | minor | Create New Theme Colors (`design.colors` with no params); command count stays 226 |
|
||||
| 2026-10-14 | minor | Create New Theme Fonts (`design.fonts` with no params); command count stays 226 |
|
||||
| 2026-10-13 | minor | Transitions: perspective meshes, background/content parts, CPU compositor |
|
||||
| 2026-10-10 | major | Created from the code on main (d556ff9) and AGENTS.md §4 |
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (Photo Album is one of 28 dialogs; Font, Bullets, Find, Replace Fonts, Action Settings, Print and Insert Object remain; previous: minor, 3-D bevel, camera and face lighting) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (Edit Photo Album reuses the Photo Album dialog, still 28; Font, Bullets, Find, Replace Fonts, Action Settings, Print and Insert Object remain; previous: trivial, Photo Album counted) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (Create New Theme Colors; dialogs 27 → 28; previous: 3-D bevel, camera and face lighting) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (Create New Theme Fonts; dialogs 27 → 28; previous: 3-D bevel, camera and face lighting) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
|
||||
Every known shortfall, one entry each, ranked by how much it stops someone who presents with
|
||||
PowerPoint from switching. This is the work list: pick from the top. Numbers are summarized in
|
||||
@@ -306,6 +307,11 @@ Detail in [ui-parity.md](ui-parity.md).
|
||||
Customize Colors…) edits the open master's twelve theme colours, not a saved library of named
|
||||
schemes. Still missing: Font, Bullets and Numbering, Find/Replace dialog, Replace Fonts, Action
|
||||
Settings, Print, Insert Object. 8–14 h.
|
||||
- **Dialogs**: 28 dialogs vs PowerPoint's ~60. Create New Theme Fonts (Design › Fonts ›
|
||||
Customize Fonts…) edits the open master's heading and body latin faces and the scheme name.
|
||||
It is not a saved library of theme fonts, and it does not pick East Asian or complex-script
|
||||
faces. Still missing: Font, Bullets and Numbering, Find/Replace dialog, Replace Fonts, Action
|
||||
Settings, Print, Insert Object. 8–14 h.
|
||||
- **Command palette defaults to an Export entry that fails** (#79). 0.5 h.
|
||||
|
||||
## Hardware
|
||||
@@ -383,6 +389,7 @@ Detail in [localization-parity.md](localization-parity.md).
|
||||
| 2026-10-14 | trivial | Photo Album dialog counted (28 vs ~60). Font, Bullets, Find, Replace Fonts, Action Settings, Print and Insert Object remain |
|
||||
| 2026-10-14 | trivial | Edit Photo Album reuses the Photo Album dialog (still 28 vs ~60). Font, Bullets, Find, Replace Fonts, Action Settings, Print and Insert Object remain |
|
||||
| 2026-10-14 | minor | Create New Theme Colors is in (dialogs 27 → 28). Font, Bullets and Numbering, Find/Replace, Replace Fonts, Action Settings, Print and Insert Object stay open |
|
||||
| 2026-10-14 | minor | Create New Theme Fonts is in (dialogs 27 → 28): heading and body latin on the open master. Font, Bullets and Numbering, Find/Replace, Replace Fonts, Action Settings, Print and Insert Object stay open |
|
||||
| 2026-10-13 | minor | Shape, picture and table rendering checked against PowerPoint on ~150 synthetic edge-case slides; 3-D bevels, cameras and face lighting now render approximately (gap narrowed to extrusion, rigs and the 3-D pane) |
|
||||
| 2026-10-13 | minor | Interactive decks: triggers, return-to-slide state and click rules landed in the slide show; the remaining pieces are one new entry under Features |
|
||||
| 2026-10-12 | minor | IME composition in slide text and notes, and CJK interface text with real glyphs (#101): the IME gap is now Korean on the web and table-cell underlines |
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (automation 227 commands; breadth stays 79%, ready ~55%; previous: minor, hours to ~95% per audience) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (automation 228 commands; breadth stays 79%, ready ~55%; previous: trivial, automation 227 commands) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (Create New Theme Colors noted under what works today; percentages unchanged; previous: hours to ~95% per audience) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (Create New Theme Fonts noted under what works today; percentages unchanged; previous: hours to ~95% per audience) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
|
||||
The authoritative answer to "how close is DeckCraft to PowerPoint, and how much work is left?".
|
||||
[ROADMAP.md](../ROADMAP.md) summarizes it; [gaps.md](gaps.md) itemizes every shortfall;
|
||||
@@ -223,6 +224,10 @@ Carried from the 2026-10-07 ROADMAP status and updated with what has landed sinc
|
||||
backgrounds (solid, gradient, picture, pattern), slide size presets. Design › Colors ›
|
||||
Customize Colors… opens Create New Theme Colors and edits the open master's twelve theme
|
||||
colours (one scheme on that master, not a library of saved schemes).
|
||||
backgrounds (solid, gradient, picture, pattern), slide size presets. Design › Fonts ›
|
||||
Customize Fonts… opens Create New Theme Fonts and edits the open master's heading and body
|
||||
latin faces (one scheme on that master, not a library of saved font schemes, and not East
|
||||
Asian or complex-script faces).
|
||||
- **Shapes:** ~180 preset geometries with adjustment handles, text boxes, pictures (crop,
|
||||
corrections, animated GIFs that play and pause), tables with styles, charts (column, bar, line,
|
||||
area, pie, doughnut, scatter and more), basic SmartArt, groups, connectors glued to sites,
|
||||
@@ -298,6 +303,7 @@ scored done (16 open issues).
|
||||
| 2026-10-14 | trivial | Working today: 227 commands. Breadth stays 79%, ready ~55%. The d556ff9 snapshot (226 commands, 27 dialogs) is unchanged |
|
||||
| 2026-10-14 | trivial | Working today: 228 commands. Breadth stays 79%, ready ~55%. The d556ff9 snapshot (226 commands, 27 dialogs) is unchanged |
|
||||
| 2026-10-14 | minor | What works today: Create New Theme Colors. The d556ff9 snapshot (226 commands, 27 dialogs) and the breadth / ready percentages are unchanged |
|
||||
| 2026-10-14 | minor | What works today: Create New Theme Fonts. The d556ff9 snapshot (226 commands, 27 dialogs) and the breadth / ready percentages are unchanged |
|
||||
| 2026-10-10 | minor | Added the readiness-by-audience table: each number gets its own hours to ~95% (full 340–600, mainstream 150–250, essentials 50–90). The full number stays an additive weighted sum; no change to the percentages |
|
||||
| 2026-10-10 | minor | Added the mainstream practitioner (~53%) and essentials user (~62%) numbers with written weights, discounts and user evidence. Features dimension recomputed from written area weights (66% judgement → 62.4% weighted mean), which moves ready for real work from ~56% to ~55%; stage unchanged |
|
||||
| 2026-10-10 | minor | Stage checked against the core-workflow gate (all six pass); stays alpha |
|
||||
|
||||
@@ -11,10 +11,12 @@
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (Photo Album dialog; 28 dialogs; breadth stays 79%, UI ~60%; previous: minor, File tab / Backstage, #76) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** trivial (Edit Photo Album reuses the Photo Album dialog; 28 dialogs; breadth stays 79%, UI ~60%; previous: trivial, Photo Album dialog) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (Create New Theme Colors; previous: File tab / Backstage, #76) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (Create New Theme Fonts; previous: File tab / Backstage, #76) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
|
||||
> **Last reviewed:** 2026-10-12 · **Last updated:** 2026-10-14 · **Change:** minor (File › New starter decks; previous: minor, text editing keys observed in PowerPoint for Windows, #268) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (Slide Sorter sections: add, rename, collapse, drag a whole section; previous: text editing keys, #268) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (Create New Theme Colors; previous: text editing keys, #268) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
> **Last reviewed:** 2026-10-14 · **Last updated:** 2026-10-14 · **Change:** minor (Create New Theme Fonts; previous: text editing keys, #268) · **Target:** Microsoft PowerPoint for Mac 16.113 (Microsoft 365)
|
||||
|
||||
How DeckCraft looks and feels next to PowerPoint: the shell, the ribbon, menus, panes, dialogs,
|
||||
direct manipulation, keyboard and precision. Observations of PowerPoint are kept locally in
|
||||
@@ -82,6 +84,7 @@ menu colours taken from the theme tokens (menu `chrome`, page `card`).
|
||||
| Dialogs | ~60 (Font, Paragraph, Bullets and Numbering, Find, Replace, Replace Fonts, Hyperlink, Action Settings, Header and Footer, Slide Size, Set Up Show, Custom Shows, Print, Page Setup, Insert Object, Symbol, Photo Album, Equation, Chart data sheet, SmartArt chooser, Preferences…) | 28 (`crates/ui-egui/src/dialogs.rs`): Table, Slide Size, Header and Footer, Hyperlink, Set Up Show, Zoom, Comment, Alt Text, Outline, Rename (section, shape, layout), Paragraph, About, Preferences, Export, Symbol, Photo Album, Equation (placeholder), Spelling, Accessibility, Language, Custom Shows, Chart Data, Trim Media, SmartArt chooser, Quit, Notes Pages |
|
||||
| Dialogs | ~60 (Font, Paragraph, Bullets and Numbering, Find, Replace, Replace Fonts, Hyperlink, Action Settings, Header and Footer, Slide Size, Set Up Show, Custom Shows, Print, Page Setup, Insert Object, Symbol, Photo Album, Equation, Chart data sheet, SmartArt chooser, Preferences…) | 28 (`crates/ui-egui/src/dialogs.rs`): Table, Slide Size, Header and Footer, Hyperlink, Set Up Show, Zoom, Comment, Alt Text, Outline, Rename (section, shape, layout), Paragraph, About, Preferences, Export, Symbol, Photo Album (Edit Photo Album reopens this dialog), Equation (placeholder), Spelling, Accessibility, Language, Custom Shows, Chart Data, Trim Media, SmartArt chooser, Quit, Notes Pages |
|
||||
| Dialogs | ~60 (Font, Paragraph, Bullets and Numbering, Find, Replace, Replace Fonts, Hyperlink, Action Settings, Header and Footer, Slide Size, Set Up Show, Custom Shows, Print, Page Setup, Insert Object, Symbol, Equation, Chart data sheet, SmartArt chooser, Preferences…) | 28 (`crates/ui-egui/src/dialogs.rs`): Table, Slide Size, Header and Footer, Hyperlink, Set Up Show, Zoom, Comment, Alt Text, Outline, Rename (section, shape, layout), Paragraph, About, Preferences, Export, Symbol, Equation (placeholder), Spelling, Accessibility, Language, Custom Shows, Chart Data, Trim Media, SmartArt chooser, Quit, Notes Pages, Create New Theme Colors |
|
||||
| Dialogs | ~60 (Font, Paragraph, Bullets and Numbering, Find, Replace, Replace Fonts, Hyperlink, Action Settings, Header and Footer, Slide Size, Set Up Show, Custom Shows, Print, Page Setup, Insert Object, Symbol, Equation, Chart data sheet, SmartArt chooser, Preferences…) | 28 (`crates/ui-egui/src/dialogs.rs`): Table, Slide Size, Header and Footer, Hyperlink, Set Up Show, Zoom, Comment, Alt Text, Outline, Rename (section, shape, layout), Paragraph, About, Preferences, Export, Symbol, Equation (placeholder), Spelling, Accessibility, Language, Custom Shows, Chart Data, Trim Media, SmartArt chooser, Quit, Notes Pages, Create New Theme Fonts |
|
||||
|
||||
## Direct manipulation
|
||||
|
||||
@@ -159,6 +162,7 @@ on the slide as shapes. No pixel comparison against PowerPoint screenshots has b
|
||||
| Native macOS menu bar from `menu_tree()`, filled out to PowerPoint's menu items | 3–5 |
|
||||
| Format Shape pane depth | 6–10 |
|
||||
| Missing dialogs (Font, Bullets and Numbering, Find/Replace, Replace Fonts, Action Settings, Print, Insert Object; Create New Theme Colors is in) | 8–14 |
|
||||
| Missing dialogs (Font, Bullets and Numbering, Find/Replace, Replace Fonts, Action Settings, Print, Insert Object; Create New Theme Fonts is in) | 8–14 |
|
||||
| Window chrome, system appearance, palette fixes (#41, #71, #79) | 3–5 |
|
||||
| Grid Options, edit points UI, crop depth | 4–7 |
|
||||
| Arrange All (New Window is one extra window) | 2–3 |
|
||||
@@ -182,6 +186,7 @@ on the slide as shapes. No pixel comparison against PowerPoint screenshots has b
|
||||
| 2026-10-14 | trivial | Photo Album dialog (28 dialogs). Breadth stays 79%, UI ~60% |
|
||||
| 2026-10-14 | trivial | Edit Photo Album reuses the Photo Album dialog (28 dialogs). Breadth stays 79%, UI ~60% |
|
||||
| 2026-10-14 | minor | Design › Colors › Customize Colors… opens Create New Theme Colors (the open master's twelve theme colours; dialogs 27 → 28) |
|
||||
| 2026-10-14 | minor | Design › Fonts › Customize Fonts… opens Create New Theme Fonts (heading and body latin on the open master; dialogs 27 → 28) |
|
||||
| 2026-10-12 | minor | File tab (Backstage) observed in PowerPoint for Windows and matched (#76); the start screen is File › Home |
|
||||
| 2026-10-12 | minor | Off-slide objects drawn and editable around the slide in Normal view (#270) |
|
||||
| 2026-10-12 | minor | Selection Pane matched to PowerPoint (#133): drag and drop, arrows, groups, eye, Lock |
|
||||
|
||||
Reference in New Issue
Block a user