mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-14 14:41:52 +00:00
Graphs: graphic styles leave a design's art in a series unlinked
graphicStyle.new linked the object it was made from, the art of a column or marker design in a graph series included, and graphicStyle.redefine gave the new look to every object still linked to the style. Redefining the style from another object then restyled that art, which the graph draws again from the design. - graphicStyle.new makes a style from the art without linking it. - graphicStyle.redefine leaves the art's look as it was and unlinks it, as an edited object is, whether the art is a linked object (as in a document saved before this change) or the source. Docs: docs/mcp.md, the params text of graphicStyle.new and graphicStyle.redefine.
This commit is contained in:
@@ -508,6 +508,89 @@ fn paint_targets_leave_a_graph_designs_copy_out() {
|
||||
assert_eq!(super::paint_targets(d, &[copy, art, mark]), [mark]);
|
||||
}
|
||||
|
||||
/// The art of a marker design in series 1 of a line graph: the design's copy and a path inside it.
|
||||
fn marker_design_art() -> (Session, NodeId, NodeId) {
|
||||
let mut s = Session::new();
|
||||
s.execute("file.new", &json!({"width": 800, "height": 800})).unwrap();
|
||||
save_design(&mut s, "Pill");
|
||||
let id = graph(&mut s, "line");
|
||||
s.execute("graph.marker", &json!({"id": id.0, "seriesIndexes": [1], "design": "Pill"})).unwrap();
|
||||
let marker = series(s.doc().unwrap().doc.node(id).unwrap(), 1).children().unwrap()[1].clone();
|
||||
assert!(matches!(marker.kind, NodeKind::Group { .. }), "the design's art");
|
||||
let mut art = vec![];
|
||||
marker.walk(&mut |m| {
|
||||
if matches!(m.kind, NodeKind::Path { .. }) {
|
||||
art.push(m.id);
|
||||
}
|
||||
});
|
||||
(s, marker.id, art[0])
|
||||
}
|
||||
|
||||
/// The ids `graphicStyle.list` reports as linked to style `name`.
|
||||
fn linked_to(s: &mut Session, name: &str) -> Vec<u64> {
|
||||
let list = s.execute("graphicStyle.list", &json!({})).unwrap();
|
||||
let style = list["styles"].as_array().unwrap().iter().find(|g| g["name"] == name).unwrap().clone();
|
||||
let mut ids: Vec<u64> = style["linked"].as_array().unwrap().iter().map(|v| v.as_u64().unwrap()).collect();
|
||||
ids.sort_unstable();
|
||||
ids
|
||||
}
|
||||
|
||||
/// Graphic styles and the art of a column or marker design in a series (the design's copy and a path inside it):
|
||||
/// `graphicStyle.new` makes a style from the art without linking it; `graphicStyle.redefine` leaves the art's look as
|
||||
/// it was and unlinks it, whether the art is linked (as in a document saved before) or the source, while the other
|
||||
/// linked objects follow the new look.
|
||||
#[test]
|
||||
fn graphic_styles_leave_a_graph_designs_art_unlinked() {
|
||||
let make = |marker: bool| {
|
||||
if marker {
|
||||
marker_design_art()
|
||||
} else {
|
||||
let (s, _, copy, art, _) = design_art();
|
||||
(s, copy, art)
|
||||
}
|
||||
};
|
||||
let rows = |s: &Session, id: NodeId| s.doc().unwrap().doc.node(id).unwrap().appearance.clone();
|
||||
let link = |s: &Session, id: NodeId| s.doc().unwrap().doc.node(id).unwrap().extras().graphic_style;
|
||||
let link_directly = |s: &mut Session, ids: &[NodeId], style: u32| {
|
||||
let d = std::sync::Arc::make_mut(&mut s.doc_mut().unwrap().doc);
|
||||
for id in ids {
|
||||
d.node_mut(*id).unwrap().extras_mut().graphic_style = Some(style);
|
||||
}
|
||||
};
|
||||
let rect = |s: &mut Session| {
|
||||
NodeId(s.execute("shape.rectangle", &json!({"x": 700, "y": 700, "width": 10, "height": 10})).unwrap()["id"].as_u64().unwrap())
|
||||
};
|
||||
for marker in [false, true] {
|
||||
for source in [0, 1] {
|
||||
let at = format!("marker {marker}, source {source}");
|
||||
// New: the style is made from the art, which stays unlinked.
|
||||
let (mut s, copy, art) = make(marker);
|
||||
let source = [copy, art][source];
|
||||
s.execute("graphicStyle.new", &json!({"id": source.0, "name": "Art"})).unwrap();
|
||||
assert_eq!(link(&s, source), None, "{at}");
|
||||
assert_eq!(linked_to(&mut s, "Art"), Vec::<u64>::new(), "{at}");
|
||||
// Redefine from another object: linked art keeps its look and is unlinked; a linked rectangle follows.
|
||||
let follower = rect(&mut s);
|
||||
s.execute("graphicStyle.apply", &json!({"name": "Art", "ids": [follower.0]})).unwrap();
|
||||
let style = link(&s, follower).unwrap();
|
||||
link_directly(&mut s, &[copy, art], style);
|
||||
let (copy_was, art_was) = (rows(&s, copy), rows(&s, art));
|
||||
let (blue_rect, blue) = blue_style(&mut s, "Blue", false);
|
||||
s.execute("graphicStyle.redefine", &json!({"name": "Art", "id": blue_rect.0})).unwrap();
|
||||
assert_eq!((rows(&s, copy), rows(&s, art)), (copy_was.clone(), art_was.clone()), "{at}");
|
||||
assert_eq!((link(&s, copy), link(&s, art)), (None, None), "{at}");
|
||||
assert_eq!(rows(&s, follower).fill_paint(), blue, "{at}");
|
||||
assert_eq!(linked_to(&mut s, "Art"), [follower.0.min(blue_rect.0), follower.0.max(blue_rect.0)], "{at}");
|
||||
// Redefine from the art itself: the art is unlinked and the linked rectangle takes the art's look.
|
||||
link_directly(&mut s, &[copy, art], style);
|
||||
s.execute("graphicStyle.redefine", &json!({"name": "Art", "id": source.0})).unwrap();
|
||||
assert_eq!((rows(&s, copy), rows(&s, art)), (copy_was, art_was), "{at}");
|
||||
assert_eq!((link(&s, copy), link(&s, art)), (None, None), "{at}");
|
||||
assert_eq!(linked_to(&mut s, "Art"), [follower.0.min(blue_rect.0), follower.0.max(blue_rect.0)], "{at}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The art of a marker design in a series is refused too.
|
||||
#[test]
|
||||
fn effect_commands_refuse_a_marker_designs_art() {
|
||||
|
||||
@@ -40,7 +40,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"New Graphic Style",
|
||||
["Window", "Graphic Styles"],
|
||||
None,
|
||||
"{name?, id?} a style from object `id` or the first selected object: its appearance, opacity, blend mode, isolate and knockout (a group without fills or strokes of its own lends its topmost object's, type its characters'; a graph series group with no fill, stroke, effect, opacity, blend mode, isolation, knockout or style link of its own stands for the topmost path directly in it; placed gradients are kept relative to its bounds); links the object. Names are made unique → {name}",
|
||||
"{name?, id?} a style from object `id` or the first selected object: its appearance, opacity, blend mode, isolate and knockout (a group without fills or strokes of its own lends its topmost object's, type its characters'; a graph series group with no fill, stroke, effect, opacity, blend mode, isolation, knockout or style link of its own stands for the topmost path directly in it; placed gradients are kept relative to its bounds); links the object, unless it is the art of a graph design in a series. Names are made unique → {name}",
|
||||
has_doc,
|
||||
style_new
|
||||
),
|
||||
@@ -68,7 +68,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Redefine Graphic Style",
|
||||
["Window", "Appearance"],
|
||||
None,
|
||||
"{name?, id?} replace the style (default: the one the object was last linked to) with the look of object `id` or the first selected object (a graph series group with no fill, stroke, effect, opacity, blend mode, isolation, knockout or style link of its own: the topmost path directly in it); the objects still linked to it update, and that object becomes linked. A linked graph series group passes the style to the paths directly in it, other than the source and paths linked to the style whose look was edited, and is left cleared (as appearance.clear) and unlinked. On a linked path directly in a graph series, the graph also stores the paint, weight, opacity and blend mode of the topmost visible fill and stroke and the object's opacity and blend mode (exceptions: docs/mcp.md); on a graph label, the graph stores the label's topmost visible fill and stroke rows and object opacity and blend mode for every label of its kind → {name}",
|
||||
"{name?, id?} replace the style (default: the one the object was last linked to) with the look of object `id` or the first selected object (a graph series group with no fill, stroke, effect, opacity, blend mode, isolation, knockout or style link of its own: the topmost path directly in it); the objects still linked to it update, and that object becomes linked. The art of a graph design in a series keeps its look and is unlinked, as the source or as a linked object. A linked graph series group passes the style to the paths directly in it, other than the source and paths linked to the style whose look was edited, and is left cleared (as appearance.clear) and unlinked. On a linked path directly in a graph series, the graph also stores the paint, weight, opacity and blend mode of the topmost visible fill and stroke and the object's opacity and blend mode (exceptions: docs/mcp.md); on a graph label, the graph stores the label's topmost visible fill and stroke rows and object opacity and blend mode for every label of its kind → {name}",
|
||||
has_doc,
|
||||
style_redefine
|
||||
),
|
||||
@@ -392,8 +392,10 @@ fn style_new(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
None => d.new_graphic_style_name(),
|
||||
};
|
||||
let g = GraphicStyle { id: d.next_graphic_style_id(), ..GraphicStyle::of(name.clone(), captured(n), n) };
|
||||
// The art of a graph design in a series is not linked: the graph draws it again from the design.
|
||||
let link = (!super::graph::is_design_art(d, src)).then_some(g.id);
|
||||
s.edit("New Graphic Style", |d, _| {
|
||||
set_link(d, &[src], Some(g.id));
|
||||
set_link(d, &[src], link);
|
||||
d.graphic_styles.push(g);
|
||||
Ok(())
|
||||
})?;
|
||||
@@ -491,10 +493,12 @@ fn style_redefine(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
s.edit_named("Redefine Graphic Style “{name}”", &name, false, |d, _| {
|
||||
let id = d.graphic_style_id(i);
|
||||
let old = std::mem::replace(&mut d.graphic_styles[i], GraphicStyle { name: name.clone(), id, ..look });
|
||||
// Objects still linked follow the new definition; edited ones are unlinked (they keep
|
||||
// their look), and the source keeps its own.
|
||||
// Objects still linked follow the new definition; edited ones are unlinked (they keep their look), and the
|
||||
// source keeps its own. The art of a graph design in a series is unlinked and keeps its look too, as the
|
||||
// source or as a linked object: the graph draws it again from the design.
|
||||
let linked = objects_where(d, |n| n.extras().graphic_style == Some(id) && n.id != src);
|
||||
let (follow, edited): (Vec<NodeId>, Vec<NodeId>) = linked.into_iter().partition(|nid| d.node(*nid).is_some_and(|n| in_sync(n, &old)));
|
||||
let (follow, edited): (Vec<NodeId>, Vec<NodeId>) =
|
||||
linked.into_iter().partition(|nid| !super::graph::is_design_art(d, *nid) && d.node(*nid).is_some_and(|n| in_sync(n, &old)));
|
||||
set_link(d, &edited, None);
|
||||
// A linked graph series group passes the style to the paths directly inside it, so the graph stores it, and
|
||||
// goes back to the empty appearance the graph draws it with. The source and the edited paths keep their look.
|
||||
@@ -509,7 +513,8 @@ fn style_redefine(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
}
|
||||
super::appearance::capture_series_writes(&before, d, &follow_marks, (true, true), (true, true), false, true);
|
||||
super::graph::capture_label_rows(d, &follow_marks);
|
||||
set_link(d, &[src], Some(id));
|
||||
let link = (!super::graph::is_design_art(d, src)).then_some(id);
|
||||
set_link(d, &[src], link);
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(json!({ "name": name }))
|
||||
|
||||
+3
-1
@@ -3649,7 +3649,9 @@ A graphic style holds an appearance (fills, strokes, effects) plus opacity, blen
|
||||
object's, type its characters'). `graphicStyle.apply {name, ids?}` gives the objects the style and links them;
|
||||
`add: true` adds the style on top of the existing appearance instead. Linked objects stay linked while they keep the
|
||||
style's look: editing their appearance or transparency breaks the link, and `graphicStyle.redefine {name?, id?}`
|
||||
updates only the objects still linked. `graphicStyle.list` returns each style with the ids linked to it and the
|
||||
updates only the objects still linked. The art of a column or marker design in a graph series is never linked:
|
||||
`graphicStyle.new` can make a style from it, and `graphicStyle.redefine` leaves its look as it was and unlinks it,
|
||||
whether it is the source or a linked object. `graphicStyle.list` returns each style with the ids linked to it and the
|
||||
style of the first selected object; `select.same.graphicStyle` selects an object's fellow users.
|
||||
Styles keep placed gradients relative to the bounds of the object they were made from, and each object a
|
||||
style is applied to gets them at the same place relative to its own bounds.
|
||||
|
||||
Reference in New Issue
Block a user