mirror of
https://github.com/storytold/designcraft.git
synced 2026-11-14 22:35:26 +00:00
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:
1 parent
7ae3c35fd5
commit
f45b02fcc4
2 files changed
+47
-4
No files matched your search
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in new issue
Block a user