Object Styles panel: a story's paragraph style counts as an override; a new style with no effect on sets none

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
paaspaas00andClaude Opus 5.5 committed 2026-10-21 14:51:14 +02:00
1 parent 7ae3c35fd5
commit f45b02fcc4
2 files changed
+47 -4

No files matched your search

+20 -2
View File
@@ -71,10 +71,11 @@ fn included(d: &Dialog, key: &str) -> bool {
d.fields.get("inc").and_then(|i| i[key].as_bool()).unwrap_or(false)
}
/// Whether the style sets effects: it did, or one is on now.
/// Whether the style sets effects: one is on now, or (editing) the style set them already. A new
/// style with every effect off sets none, so applying it leaves the objects' own effects.
fn sets_effects(d: &Dialog) -> bool {
let fx = d.fields.get("fx").cloned().unwrap_or(Value::Null);
d.b("hadEffects") || crate::effects_dialog::PAGES.iter().skip(1).any(|(k, _)| crate::effects_dialog::is_on(&fx, k))
(!d.b("new") && d.b("hadEffects")) || crate::effects_dialog::PAGES.iter().skip(1).any(|(k, _)| crate::effects_dialog::is_on(&fx, k))
}
/// The style's attributes as the commands take them (null: not in the style).
@@ -388,6 +389,23 @@ pub fn show(app: &mut DesignApp, ui: &mut egui::Ui, d: &mut Dialog) {
mod tests {
use super::*;
/// A new style made from an object with no effect on sets no effects, so applying it keeps
/// other objects' own.
#[test]
fn a_new_style_without_effects_on_sets_none() {
let (mut app, _) = app_with_frame();
open_new(&mut app);
let d = app.ui.dialog.clone().unwrap();
assert!(d.b("hadEffects"));
assert_eq!(attrs(&d)["effects"], Value::Null);
if let Some(d) = app.ui.dialog.as_mut() {
d.fields.insert("name".into(), json!("Plain"));
d.fields.insert("apply".into(), json!(false));
}
crate::dialogs::confirm(&mut app).unwrap();
assert!(app.session.doc().unwrap().doc.styles.object_style("Plain").unwrap().effects.is_none());
}
fn app_with_frame() -> (DesignApp, u64) {
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());
app.run("file.new", json!({})).unwrap();
+27 -2
View File
@@ -105,15 +105,20 @@ pub fn character(app: &mut DesignApp, ui: &mut egui::Ui) {
list(app, ui, false);
}
/// The selected object's style, and whether the object differs from it (an override: `+`).
/// The selected object's style, and whether the object differs from it (an override: `+`): its
/// own attributes, or a text frame's paragraphs in another style than the object style's.
fn object_style_of_selection(app: &DesignApp) -> Option<(String, bool)> {
let st = app.session.active()?;
let it = st.selection.items.first().and_then(|i| st.doc.item(*i))?;
let mut styled = it.clone();
let mut over = false;
if let Some(os) = st.doc.styles.object_style(&it.object_style) {
os.apply_to(&mut styled);
if let (Some(ps), Some(tf)) = (&os.paragraph_style, it.text_frame()) {
over = st.doc.story(tf.story).is_some_and(|s| s.paras.iter().any(|f| f.style != *ps));
}
}
Some((it.object_style.clone(), styled != *it))
Some((it.object_style.clone(), over || styled != *it))
}
/// Window › Styles › Object Styles, as InDesign's: the selected object's style on top (`+` when
@@ -307,6 +312,26 @@ mod tests {
use super::*;
/// A text frame whose paragraphs left its object style's paragraph style shows the override
/// mark, and Clear Overrides puts them back.
#[test]
fn a_paragraph_style_change_is_an_object_style_override() {
let mut app = DesignApp::new(designcraft_engine::Session::new(), Default::default());
app.run("file.new", serde_json::json!({})).unwrap();
app.run("style.paragraph.create", serde_json::json!({"name": "Caption"})).unwrap();
app.run("style.object.create", serde_json::json!({"name": "Captioned", "fromSelection": false, "paragraphStyle": "Caption"})).unwrap();
let f = app.run("frame.create", serde_json::json!({"rect": [36, 36, 300, 200], "content": "text", "text": "One"})).unwrap();
app.run("style.object.apply", serde_json::json!({"name": "Captioned", "ids": [f["id"]]})).unwrap();
app.run("selection.set", serde_json::json!({"ids": [f["id"]]})).unwrap();
assert_eq!(object_style_of_selection(&app), Some(("Captioned".into(), false)));
app.run("text.select", serde_json::json!({"story": f["story"], "anchor": 0, "focus": 3})).unwrap();
app.run("style.paragraph.apply", serde_json::json!({"name": designcraft_doc::BASIC_PARAGRAPH})).unwrap();
app.run("selection.set", serde_json::json!({"ids": [f["id"]]})).unwrap();
assert_eq!(object_style_of_selection(&app), Some(("Captioned".into(), true)), "the + mark");
app.run("style.object.clearOverrides", serde_json::json!({})).unwrap();
assert_eq!(object_style_of_selection(&app), Some(("Captioned".into(), false)));
}
#[test]
fn selected_style_context_menu_redefines_its_format() {
let mut app = DesignApp::new(designcraft_engine::Session::new(), crate::Services::default());