diff --git a/crates/engine/src/cmd/addons/color.rs b/crates/engine/src/cmd/addons/color.rs index ac8947cb4..574003b93 100644 --- a/crates/engine/src/cmd/addons/color.rs +++ b/crates/engine/src/cmd/addons/color.rs @@ -3,9 +3,10 @@ //! Filter writes any colour adjustment into the colours themselves, Select by Color Kind selects the //! objects that use spot colours, carry a live colour adjustment, or are images of a colour model, //! Print Ink Fixes ([`print_fixes`]) prepares a CMYK document's inks for print, Find Nearest -//! Swatch ([`nearest`]) matches colours to a swatch library's, and Create Swatch Legend -//! ([`legend`]) draws labelled chips of the document's swatches. Its panel and its submenu, Object › -//! Plug-ins › Color Effects, are the UI's. +//! Swatch ([`nearest`]) matches colours to a swatch library's, Create Swatch Legend +//! ([`legend`]) draws labelled chips of the document's swatches, and Apply Fill to Stroke and Apply +//! Stroke to Fill ([`copy_paint`]) copy each object's own paint across. Its panel and its submenu, +//! Object › Plug-ins › Color Effects, are the UI's. pub mod legend; mod nearest; @@ -14,13 +15,14 @@ mod print_fixes; mod tests; use serde_json::{Value, json}; -use vectorcraft_doc::{Document, Node, NodeId, NodeKind}; +use vectorcraft_doc::{CharStyle, Document, Node, NodeId, NodeKind}; use vectorcraft_render::effects; use super::super::colorcmds::recolor_links; +use super::super::paint::{restore_unpainted_stroke, set_run_stroke}; use super::super::select::walk_selectable; -use super::super::{CommandSpec, bad, checked_ids_or_selection, cmd, has_doc, has_selection, text_param}; -use crate::{Result, Session}; +use super::super::{CommandSpec, bad, checked_ids_or_selection, cmd, has_doc, has_selection, paint_targets, switch_or, text_param}; +use crate::{EngineError, Result, Session}; /// The kinds Select by Color Kind selects (`addon.color.select`). pub const SELECT_KINDS: [&str; 5] = ["spotColors", "colorEffects", "rgbImages", "cmykImages", "grayImages"]; @@ -72,6 +74,24 @@ pub fn specs() -> Vec { has_selection, nearest::apply ), + cmd!( + "addon.color.fillToStroke", + "Apply Fill to Stroke", + ["Object", "Plug-ins", "Color Effects"], + None, + "{ids?|id?, skipNone?: bool} each target's top stroke takes its own top fill, as it is (a colour in its model, a global or spot colour linked at its tint, a gradient or pattern with its placement, None); a stroke given paint at width 0 gets 1 pt, as Swap Fill and Stroke does. Type: each run's stroke takes its own fill, and while the Type tool has characters selected (no ids), only those. A graph's series marks and labels keep the paint when the graph is drawn again. skipNone: leave alone a target whose fill is None. A target whose stroke already has that paint is left as it is, and when nothing changes no undo step is recorded. The default stroke takes the default fill the same way → {objects}", + has_doc, + |s, p| copy_paint(s, p, true) + ), + cmd!( + "addon.color.strokeToFill", + "Apply Stroke to Fill", + ["Object", "Plug-ins", "Color Effects"], + None, + "{ids?|id?, skipNone?: bool} each target's top fill takes its own top stroke's paint, as addon.color.fillToStroke does the other way; the stroke itself is unchanged. The default fill takes the default stroke → {objects}", + has_doc, + |s, p| copy_paint(s, p, false) + ), ] .into_iter() .chain(legend::specs()) @@ -156,3 +176,97 @@ fn filter(s: &mut Session, p: &Value) -> Result { let swatches = |name: &str| kinds.get(name).copied(); recolor_links(s, &given, label, &|c| map.apply(c), &|c, link, tint| map.apply_link(c, link, tint, &swatches)) } + +/// Apply Fill to Stroke (`to_stroke`) or Apply Stroke to Fill: each target copies one of its own +/// paints onto the other, so a mixed selection keeps each object's colour (#5530). Aimed and +/// undone like Swap Fill and Stroke; characters selected with the Type tool are the only ones that +/// change, as for `paint.setFill`. A graph's series marks and labels give the graph the paint in +/// the same undo step, as `paint.setFill` does, so drawing the graph again keeps it. When nothing +/// changes, the document and its undo history stay as they are. +fn copy_paint(s: &mut Session, p: &Value, to_stroke: bool) -> Result { + let (cmd, label) = + if to_stroke { ("addon.color.fillToStroke", "Apply Fill to Stroke") } else { ("addon.color.strokeToFill", "Apply Stroke to Fill") }; + let skip_none = switch_or(p, "skipNone", false, cmd)?; + // One text style: its stroke takes its fill, or the other way. + let copy_run = |st: &mut CharStyle| { + let from = if to_stroke { st.fill.clone() } else { st.stroke.clone() }; + if (skip_none && from.is_none()) || from == if to_stroke { st.stroke.clone() } else { st.fill.clone() } { + return; + } + if to_stroke { + set_run_stroke(st, from); + } else { + st.fill = from; + } + }; + if p.get("ids").is_none() + && p.get("id").is_none() + && let Some((id, a, b)) = super::super::textedit::editing_range(s)? + && a != b + { + // After Select All in threaded text, every frame's text takes it. + let story = super::super::textedit::editing_story(s); + let ranges: Vec<(NodeId, usize, usize)> = + if story.is_empty() { vec![(id, a, b)] } else { story.iter().map(|f| (*f, 0, usize::MAX)).collect() }; + let ids: Vec = ranges.iter().map(|(f, _, _)| *f).collect(); + let before = s.doc()?.doc.clone(); + let n = super::super::overprint::edit_counted(s, label, |d| { + for &(id, a, b) in &ranges { + let Some(NodeKind::Text(t)) = d.node_mut(id).map(|n| &mut n.kind) else { return Err(EngineError::NoNode(id)) }; + vectorcraft_text::edit::style_range(&mut t.runs, a, b, copy_run); + super::super::typecmd::refresh_bounds(t); + } + Ok(super::super::appearance::count_changed(d, &before, &ids)) + })?; + return Ok(json!({"objects": n})); + } + // A graph's series group stands for its marks, as for `paint.setFill`. + let ids = super::super::graph::paint_targets(&s.doc()?.doc, &paint_targets(s, p)?); + let mut objects = 0; + if !ids.is_empty() { + let before = s.doc()?.doc.clone(); + super::super::overprint::edit_counted(s, label, |d| { + for id in &ids { + let Some(node) = d.node_mut(*id) else { continue }; + if let NodeKind::Text(t) = &mut node.kind { + for r in &mut t.runs { + copy_run(&mut r.style); + } + super::super::typecmd::refresh_bounds(t); + continue; + } + let ap = &mut node.appearance; + let (from, to) = if to_stroke { (ap.fill_paint(), ap.stroke_paint()) } else { (ap.stroke_paint(), ap.fill_paint()) }; + // A missing item reads as None: copying None onto it would add an item that + // changes nothing. + if (skip_none && from.is_none()) || from == to { + continue; + } + if to_stroke { + restore_unpainted_stroke(ap, None, &from); + ap.set_stroke(from); + } else { + ap.set_fill(from); + } + } + objects = super::super::appearance::count_changed(d, &before, &ids); + // The graph keeps the paint its series marks and labels now have. + let series = super::super::graph::capture_series_paints(d, &ids, !to_stroke); + let labels = super::super::graph::capture_label_styles(d, &ids, ©_run); + Ok(objects + usize::from(series || labels)) + })?; + } + // The defaults for new art change the same way, as Swap Fill and Stroke changes them. + let from = if to_stroke { s.paint.fill.clone() } else { s.paint.stroke.clone() }; + if !(skip_none && from.is_none()) { + if to_stroke { + if s.paint.stroke.is_none() && !from.is_none() && s.paint.stroke_width == 0.0 { + s.paint.stroke_width = 1.0; + } + s.paint.stroke = from; + } else { + s.paint.fill = from; + } + } + Ok(json!({"objects": objects})) +} diff --git a/crates/engine/src/cmd/paint.rs b/crates/engine/src/cmd/paint.rs index 2af52fa00..390f6ef49 100644 --- a/crates/engine/src/cmd/paint.rs +++ b/crates/engine/src/cmd/paint.rs @@ -41,24 +41,6 @@ pub fn specs() -> Vec { has_doc, swap ), - cmd!( - "paint.fillToStroke", - "Apply Fill to Stroke", - [], - None, - "{ids?|id?, skipNone?: bool} each target's top stroke takes its own top fill, as it is (a colour in its model, a global or spot colour linked at its tint, a gradient or pattern with its placement, None); a stroke given paint at width 0 gets 1 pt, as Swap Fill and Stroke does. Type: each run's stroke takes its own fill, and while the Type tool has characters selected (no ids), only those. skipNone: leave alone a target whose fill is None. The default stroke takes the default fill the same way → {objects}", - has_doc, - |s, p| copy_paint(s, p, true) - ), - cmd!( - "paint.strokeToFill", - "Apply Stroke to Fill", - [], - None, - "{ids?|id?, skipNone?: bool} each target's top fill takes its own top stroke's paint, as paint.fillToStroke does the other way; the stroke itself is unchanged. The default fill takes the default stroke → {objects}", - has_doc, - |s, p| copy_paint(s, p, false) - ), cmd!( "paint.default", "Default Fill and Stroke", @@ -750,7 +732,7 @@ pub(crate) fn picked_mesh_points_of(s: &Session, ids: &[NodeId]) -> Option Result { ok() } -/// Apply Fill to Stroke (`to_stroke`) or Apply Stroke to Fill: each target copies one of its own -/// paints onto the other, so a mixed selection keeps each object's colour (#5530). Aimed and -/// undone like Swap Fill and Stroke; characters selected with the Type tool are the only ones that -/// change, as for `paint.setFill`. When nothing changes, the document and its undo history stay -/// as they are. -fn copy_paint(s: &mut Session, p: &Value, to_stroke: bool) -> Result { - let (cmd, label) = if to_stroke { ("paint.fillToStroke", "Apply Fill to Stroke") } else { ("paint.strokeToFill", "Apply Stroke to Fill") }; - let skip_none = switch_or(p, "skipNone", false, cmd)?; - // One text style: its stroke takes its fill, or the other way. - let copy_run = |st: &mut CharStyle| { - let from = if to_stroke { st.fill.clone() } else { st.stroke.clone() }; - if skip_none && from.is_none() { - return; - } - if to_stroke { - set_run_stroke(st, from); - } else { - st.fill = from; - } - }; - if p.get("ids").is_none() - && p.get("id").is_none() - && let Some((id, a, b)) = super::textedit::editing_range(s)? - && a != b - { - // After Select All in threaded text, every frame's text takes it. - let story = super::textedit::editing_story(s); - let ranges: Vec<(NodeId, usize, usize)> = - if story.is_empty() { vec![(id, a, b)] } else { story.iter().map(|f| (*f, 0, usize::MAX)).collect() }; - let ids: Vec = ranges.iter().map(|(f, _, _)| *f).collect(); - let before = s.doc()?.doc.clone(); - let n = super::overprint::edit_counted(s, label, |d| { - for &(id, a, b) in &ranges { - let Some(NodeKind::Text(t)) = d.node_mut(id).map(|n| &mut n.kind) else { return Err(EngineError::NoNode(id)) }; - vectorcraft_text::edit::style_range(&mut t.runs, a, b, copy_run); - super::typecmd::refresh_bounds(t); - } - Ok(super::appearance::count_changed(d, &before, &ids)) - })?; - return Ok(json!({"objects": n})); - } - let ids = paint_targets(s, p)?; - let mut n = 0; - if !ids.is_empty() { - let before = s.doc()?.doc.clone(); - n = super::overprint::edit_counted(s, label, |d| { - for id in &ids { - let Some(node) = d.node_mut(*id) else { continue }; - if let NodeKind::Text(t) = &mut node.kind { - for r in &mut t.runs { - copy_run(&mut r.style); - } - super::typecmd::refresh_bounds(t); - continue; - } - let ap = &mut node.appearance; - if to_stroke { - let f = ap.fill_paint(); - if skip_none && f.is_none() { - continue; - } - restore_unpainted_stroke(ap, None, &f); - ap.set_stroke(f); - } else { - let st = ap.stroke_paint(); - if skip_none && st.is_none() { - continue; - } - ap.set_fill(st); - } - } - Ok(super::appearance::count_changed(d, &before, &ids)) - })?; - } - // The defaults for new art change the same way, as Swap Fill and Stroke changes them. - let from = if to_stroke { s.paint.fill.clone() } else { s.paint.stroke.clone() }; - if !(skip_none && from.is_none()) { - if to_stroke { - if s.paint.stroke.is_none() && !from.is_none() && s.paint.stroke_width == 0.0 { - s.paint.stroke_width = 1.0; - } - s.paint.stroke = from; - } else { - s.paint.fill = from; - } - } - Ok(json!({"objects": n})) -} - fn default_paint(s: &mut Session, p: &Value) -> Result { let ids = paint_targets(s, p)?; if !ids.is_empty() { diff --git a/crates/engine/src/tests/paintproxy.rs b/crates/engine/src/tests/paintproxy.rs index 984c8f9f9..b64736b97 100644 --- a/crates/engine/src/tests/paintproxy.rs +++ b/crates/engine/src/tests/paintproxy.rs @@ -434,7 +434,7 @@ fn each_object_copies_its_own_paint_onto_the_other() { s.execute("object.setStrokeWidth", &json!({"ids": [id.0], "width": 0})).ok(); } s.execute("select.set", &json!({"ids": [a.0, b.0]})).unwrap(); - let r = s.execute("paint.fillToStroke", &json!({})).unwrap(); + let r = s.execute("addon.color.fillToStroke", &json!({})).unwrap(); assert_eq!(r["objects"], 2); for (id, hex) in [(a, "#ff0000"), (b, "#0080ff")] { let (fill, stroke, width) = paints(&s, id); @@ -443,7 +443,7 @@ fn each_object_copies_its_own_paint_onto_the_other() { assert!(width > 0.0, "and shows: {width}"); } s.execute("paint.setStroke", &json!({"ids": [a.0], "color": "#00ff00"})).unwrap(); - s.execute("paint.strokeToFill", &json!({"ids": [a.0]})).unwrap(); + s.execute("addon.color.strokeToFill", &json!({"ids": [a.0]})).unwrap(); let (fill, stroke, _) = paints(&s, a); assert_eq!((fill.color().unwrap().to_hex(), stroke.color().unwrap().to_hex()), ("#00ff00".into(), "#00ff00".into())); assert_eq!(paints(&s, b).0.color().unwrap().to_hex(), "#0080ff", "an object not aimed at is left alone"); @@ -457,7 +457,7 @@ fn a_linked_swatch_and_a_gradient_copy_as_they_are() { let a = rect(&mut s); s.execute("swatch.new", &json!({"name": "Ink", "color": "#c82828", "global": true})).unwrap(); s.execute("paint.setFill", &json!({"ids": [a.0], "swatch": "Ink", "tint": 40})).unwrap(); - s.execute("paint.fillToStroke", &json!({"ids": [a.0]})).unwrap(); + s.execute("addon.color.fillToStroke", &json!({"ids": [a.0]})).unwrap(); let (fill, stroke, _) = paints(&s, a); assert!(matches!(&stroke, Paint::Solid { swatch: Some(name), .. } if name == "Ink"), "{stroke:?}"); assert_eq!(stroke, fill); @@ -465,7 +465,7 @@ fn a_linked_swatch_and_a_gradient_copy_as_they_are() { let mut q = g.clone(); q["ids"] = json!([a.0]); s.execute("paint.setFill", &q).unwrap(); - s.execute("paint.fillToStroke", &json!({"ids": [a.0]})).unwrap(); + s.execute("addon.color.fillToStroke", &json!({"ids": [a.0]})).unwrap(); let (fill, stroke, _) = paints(&s, a); assert!(matches!(stroke, Paint::Gradient { .. }), "{stroke:?}"); assert_eq!(stroke, fill, "the same stops and placement"); @@ -479,11 +479,11 @@ fn none_copies_unless_skipped() { s.execute("paint.setFill", &json!({"ids": [a.0], "none": true})).unwrap(); s.execute("paint.setStroke", &json!({"ids": [a.0, b.0], "color": "#123456"})).unwrap(); s.execute("paint.setFill", &json!({"ids": [b.0], "color": "#ff8000"})).unwrap(); - let r = s.execute("paint.fillToStroke", &json!({"ids": [a.0, b.0], "skipNone": true})).unwrap(); + let r = s.execute("addon.color.fillToStroke", &json!({"ids": [a.0, b.0], "skipNone": true})).unwrap(); assert_eq!(r["objects"], 1, "only the object with a fill changed"); assert_eq!(paints(&s, a).1.color().unwrap().to_hex(), "#123456", "skipped: its stroke stays"); assert_eq!(paints(&s, b).1.color().unwrap().to_hex(), "#ff8000"); - s.execute("paint.fillToStroke", &json!({"ids": [a.0]})).unwrap(); + s.execute("addon.color.fillToStroke", &json!({"ids": [a.0]})).unwrap(); assert!(paints(&s, a).1.is_none(), "without skipNone, no fill gives no stroke"); } @@ -497,7 +497,7 @@ fn a_groups_members_each_copy_their_own() { s.execute("select.set", &json!({"ids": [a.0, b.0]})).unwrap(); let g = NodeId(s.execute("object.group", &json!({})).unwrap()["id"].as_u64().unwrap()); s.execute("select.set", &json!({"ids": [g.0]})).unwrap(); - s.execute("paint.fillToStroke", &json!({})).unwrap(); + s.execute("addon.color.fillToStroke", &json!({})).unwrap(); assert_eq!(paints(&s, a).1.color().unwrap().to_hex(), "#ff0000"); assert_eq!(paints(&s, b).1.color().unwrap().to_hex(), "#00ff00"); } @@ -509,7 +509,7 @@ fn each_text_run_copies_its_own_fill() { let id = NodeId(s.execute("text.create", &json!({"x": 10, "y": 50, "text": "Hi"})).unwrap()["id"].as_u64().unwrap()); s.execute("paint.setFill", &json!({"ids": [id.0], "color": "#aa0000"})).unwrap(); s.execute("paint.setStroke", &json!({"ids": [id.0], "none": true})).unwrap(); - s.execute("paint.fillToStroke", &json!({"ids": [id.0]})).unwrap(); + s.execute("addon.color.fillToStroke", &json!({"ids": [id.0]})).unwrap(); let NodeKind::Text(t) = &node(&s, id).kind else { panic!("not text") }; for r in &t.runs { assert_eq!(r.style.stroke, r.style.fill, "{:?}", r.text); @@ -527,18 +527,18 @@ fn one_undo_step_none_when_nothing_changes_and_bad_params_change_nothing() { s.execute("paint.setStroke", &json!({"ids": [id.0], "color": "#ff0000"})).unwrap(); select_with_a_step_to_redo(&mut s, &[id]); let before = history(&s); - s.execute("paint.fillToStroke", &json!({})).unwrap(); - s.execute("paint.strokeToFill", &json!({})).unwrap(); + s.execute("addon.color.fillToStroke", &json!({})).unwrap(); + s.execute("addon.color.strokeToFill", &json!({})).unwrap(); assert_eq!(history(&s), before, "the paints were already the same"); s.execute("paint.setStroke", &json!({"ids": [id.0], "color": "#0000ff"})).unwrap(); let before = history(&s); - s.execute("paint.fillToStroke", &json!({})).unwrap(); + s.execute("addon.color.fillToStroke", &json!({})).unwrap(); assert_eq!(history(&s), (before.0 + 1, 0, true), "one step"); assert_eq!(s.doc().unwrap().history.undo.last().map(|e| e.label.as_str()), Some("Apply Fill to Stroke")); s.execute("edit.undo", &json!({})).unwrap(); assert_eq!(paints(&s, id).1.color().unwrap().to_hex(), "#0000ff"); let defaults = (s.paint.fill.clone(), s.paint.stroke.clone()); - for (cmd, p) in [("paint.fillToStroke", json!({"ids": "not an array"})), ("paint.strokeToFill", json!({"skipNone": "yes"}))] { + for (cmd, p) in [("addon.color.fillToStroke", json!({"ids": "not an array"})), ("addon.color.strokeToFill", json!({"skipNone": "yes"}))] { assert!(s.execute(cmd, &p).is_err(), "{cmd} {p}"); } assert_eq!((s.paint.fill.clone(), s.paint.stroke.clone()), defaults, "the defaults are as they were"); @@ -552,11 +552,120 @@ fn with_nothing_selected_the_defaults_take_it() { s.execute("select.set", &json!({"ids": []})).unwrap(); s.execute("paint.setFill", &json!({"color": "#336699"})).unwrap(); s.execute("paint.setStroke", &json!({"none": true})).unwrap(); - let r = s.execute("paint.fillToStroke", &json!({})).unwrap(); + let r = s.execute("addon.color.fillToStroke", &json!({})).unwrap(); assert_eq!(r["objects"], 0); assert_eq!(s.paint.stroke, s.paint.fill); assert!(s.paint.stroke_width > 0.0); s.execute("paint.setStroke", &json!({"color": "#ff00ff"})).unwrap(); - s.execute("paint.strokeToFill", &json!({})).unwrap(); + s.execute("addon.color.strokeToFill", &json!({})).unwrap(); assert_eq!(s.paint.fill.color().unwrap().to_hex(), "#ff00ff"); } + +/// Copying None onto an item the object doesn't have adds nothing: with no fill and no stroke +/// item at all, neither command records an undo step or touches the object. +#[test] +fn copying_none_onto_a_missing_item_changes_nothing() { + let mut s = session(); + let id = rect(&mut s); + s.edit("No paint items", |d, _| { + d.node_mut(id).unwrap().appearance.items.clear(); + Ok(()) + }) + .unwrap(); + assert_eq!((paints(&s, id).0, paints(&s, id).1), (Paint::None, Paint::None)); + select_with_a_step_to_redo(&mut s, &[id]); + let (before, art) = (history(&s), node(&s, id)); + for cmd in ["addon.color.fillToStroke", "addon.color.strokeToFill"] { + assert_eq!(s.execute(cmd, &json!({})).unwrap()["objects"], 0, "{cmd}"); + assert_eq!(history(&s), before, "{cmd}: no undo step"); + assert_eq!(node(&s, id), art, "{cmd}: no item added"); + } +} + +/// A graph's series and its labels keep the copied paint when the graph is drawn again: the +/// graph takes it in the same undo step, as it does from paint.setFill. +#[test] +fn a_graph_series_and_its_labels_keep_the_copied_paint_when_drawn_again() { + let mut s = session(); + let csv = ",2024,2025\nQ1,3,2\nQ2,5,4"; + let g = NodeId( + s.execute("graph.create", &json!({"type": "column", "x": 100, "y": 100, "width": 300, "height": 200, "csv": csv})).unwrap()["id"] + .as_u64() + .unwrap(), + ); + let series = |s: &Session| node(s, g).children().unwrap().iter().find(|c| c.extras().series_index == Some(0)).unwrap().clone(); + let label = |s: &Session| { + let axes = node(s, g).children().unwrap().iter().find(|c| c.name.as_deref() == Some("Axes")).unwrap().clone(); + axes.children().unwrap().iter().find(|c| matches!(&c.kind, NodeKind::Text(t) if t.plain_text() == "4")).unwrap().clone() + }; + let sid = series(&s).id; + s.execute("paint.setFill", &json!({"ids": [sid.0], "color": "#12ab34"})).unwrap(); + s.execute("paint.setStroke", &json!({"ids": [sid.0], "none": true})).unwrap(); + let lid = label(&s).id; + s.execute("paint.setFill", &json!({"ids": [lid.0], "color": "#e8573f"})).unwrap(); + let steps = s.doc().unwrap().history.undo.len(); + s.execute("addon.color.fillToStroke", &json!({"ids": [sid.0, lid.0]})).unwrap(); + assert_eq!(s.doc().unwrap().history.undo.len(), steps + 1, "one step"); + let kept = s.doc().unwrap().doc.node(g).unwrap().extras().graph.as_deref().unwrap().clone(); + assert_eq!(kept.series_paints[0].stroke.as_ref().and_then(Paint::color).map(|c| c.to_hex()), Some("#12ab34".into())); + assert_eq!(kept.label_styles.values.as_ref().and_then(|st| st.stroke.color()).map(|c| c.to_hex()), Some("#e8573f".into())); + s.execute("graph.setData", &json!({"id": g.0, "rows": [[1, 2], [3, 4]]})).unwrap(); + let marks = series(&s).children().unwrap().to_vec(); + let bars: Vec<_> = marks.iter().filter(|m| matches!(m.kind, NodeKind::Path { .. })).collect(); + assert!(!bars.is_empty()); + for m in bars { + assert_eq!(m.appearance.stroke_paint().color().map(|c| c.to_hex()), Some("#12ab34".into()), "a redrawn mark keeps the stroke"); + } + let redrawn = node(&s, g).children().unwrap().iter().find(|c| c.name.as_deref() == Some("Axes")).unwrap().clone(); + let text = redrawn.children().unwrap().iter().find_map(|c| if let NodeKind::Text(t) = &c.kind { Some(t.clone()) } else { None }).unwrap(); + assert_eq!(text.runs[0].style.stroke.color().map(|c| c.to_hex()), Some("#e8573f".into()), "a redrawn value label keeps the stroke"); + s.execute("edit.undo", &json!({})).unwrap(); + s.execute("edit.undo", &json!({})).unwrap(); + let back = s.doc().unwrap().doc.node(g).unwrap().extras().graph.as_deref().unwrap().clone(); + assert!( + back.series_paints[0].stroke.as_ref().is_none_or(|p| p.is_none()), + "undo takes the graph's stroke back: {:?}", + back.series_paints[0].stroke + ); +} + +/// A series drawn with a marker design: its own marks copy their paint, but the design's art +/// keeps the design's paint, as paint.setFill leaves it (painting it would last only until the +/// graph is drawn again). +#[test] +fn a_marker_designs_art_in_a_series_keeps_its_paint() { + let mut s = session(); + let r = s.execute("shape.rectangle", &json!({"x": 600, "y": 600, "width": 10, "height": 10})).unwrap()["id"].as_u64().unwrap(); + s.execute("paint.setFill", &json!({"ids": [r], "color": "#0000ff"})).unwrap(); + s.execute("paint.setStroke", &json!({"ids": [r], "none": true})).unwrap(); + s.execute("select.set", &json!({"ids": [r]})).unwrap(); + s.execute("graph.design", &json!({"new": "Square"})).unwrap(); + let csv = ",2024,2025\nQ1,3,2\nQ2,5,4"; + let g = NodeId( + s.execute("graph.create", &json!({"type": "line", "x": 100, "y": 100, "width": 300, "height": 200, "csv": csv})).unwrap()["id"] + .as_u64() + .unwrap(), + ); + s.execute("select.set", &json!({"ids": [g.0]})).unwrap(); + s.execute("graph.marker", &json!({"design": "Square"})).unwrap(); + let series = node(&s, g).children().unwrap().iter().find(|c| c.extras().series_index == Some(0)).unwrap().clone(); + // The design's paths sit in groups inside the series; its own marks are its direct children. + let design_strokes = |s: &Session| { + let grp = node(s, series.id); + let mut out = vec![]; + for c in grp.children().unwrap() { + if !matches!(c.kind, NodeKind::Path { .. }) { + c.walk(&mut |n| { + if let NodeKind::Path { .. } = n.kind { + out.push(n.appearance.stroke_paint()); + } + }); + } + } + out + }; + let before = design_strokes(&s); + assert!(!before.is_empty(), "the series draws the design"); + s.execute("addon.color.fillToStroke", &json!({"ids": [series.id.0]})).unwrap(); + assert_eq!(design_strokes(&s), before, "the design's art keeps its paint"); +} diff --git a/crates/engine/src/tests/textedit.rs b/crates/engine/src/tests/textedit.rs index 58e149373..dde0c4bf5 100644 --- a/crates/engine/src/tests/textedit.rs +++ b/crates/engine/src/tests/textedit.rs @@ -3019,7 +3019,7 @@ fn apply_fill_to_stroke_with_characters_selected_changes_only_them() { s.set_tool_option("select", &json!({"start": 0, "end": 6})); s.execute("paint.setFill", &json!({"color": "#00a000"})).unwrap(); let steps = undo_labels(&s).len(); - s.execute("paint.fillToStroke", &json!({})).unwrap(); + s.execute("addon.color.fillToStroke", &json!({})).unwrap(); let t = obj(&s, id); assert_eq!(t.runs[0].text, "Sample"); assert_eq!(t.runs[0].style.stroke, t.runs[0].style.fill, "the selected characters take their fill as a stroke"); diff --git a/crates/ui-egui/src/addons/color.rs b/crates/ui-egui/src/addons/color.rs index 9ea653047..39c73e8c6 100644 --- a/crates/ui-egui/src/addons/color.rs +++ b/crates/ui-egui/src/addons/color.rs @@ -104,6 +104,10 @@ pub fn menu() -> Vec { Item::Cmd("Find Nearest Swatch…", nearest::COMMAND, Value::Null), Item::Cmd("Create Swatch Legend…", legend::COMMAND, Value::Null), Item::Sep, + // Each selected object's stroke takes its own fill, or the other way (#5530). + Item::Cmd("Apply Fill to Stroke", "addon.color.fillToStroke", json!({})), + Item::Cmd("Apply Stroke to Fill", "addon.color.strokeToFill", json!({})), + Item::Sep, Item::Sub( "Select by Color Kind", [ diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 40a3b218a..0429245b2 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -5952,6 +5952,9 @@ anchor Points: Точки: Find Nearest Swatch… Намиране на най-близкия образец… Find Nearest Swatch Намиране на най-близкия образец Apply Nearest Swatch Прилагане на най-близкия образец +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Прилагане на запълването към щриха + Apply Stroke to Fill Прилагане на щриха към запълването Select objects to find the swatches nearest their colors. Изберете обекти, за да намерите най-близките до цветовете им образци. @msg select objects or give `color` изберете обекти или задайте `color` @msg `color` is not a colour `color` не е цвят diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index d7f40ffeb..648a394da 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -6078,6 +6078,9 @@ shape Open Otvoren Find Nearest Swatch… Pronađi najbliži uzorak boje… Find Nearest Swatch Pronađi najbliži uzorak boje Apply Nearest Swatch Primijeni najbliži uzorak boje +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Primijeni ispunu na potez + Apply Stroke to Fill Primijeni potez na ispunu Select objects to find the swatches nearest their colors. Odaberite objekte da pronađete uzorke boja najbliže njihovim bojama. @msg select objects or give `color` odaberite objekte ili navedite `color` @msg `color` is not a colour `color` nije boja diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 7e75ae456..7f1ae8508 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -6087,6 +6087,9 @@ anchor Points: Punts: Find Nearest Swatch… Cerca la mostra més propera… Find Nearest Swatch Cerca la mostra més propera Apply Nearest Swatch Aplica la mostra més propera +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Aplica el farciment al traç + Apply Stroke to Fill Aplica el traç al farciment Select objects to find the swatches nearest their colors. Seleccioneu objectes per trobar les mostres més properes als seus colors. @msg select objects or give `color` seleccioneu objectes o indiqueu `color` @msg `color` is not a colour `color` no és un color diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index 0b5a522c6..6474f2cb6 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -6103,6 +6103,9 @@ anchor Points: Body: Find Nearest Swatch… Najít nejbližší položku vzorníku… Find Nearest Swatch Najít nejbližší položku vzorníku Apply Nearest Swatch Použít nejbližší položku vzorníku +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Použít výplň na tah + Apply Stroke to Fill Použít tah na výplň Select objects to find the swatches nearest their colors. Vyberte objekty, k jejichž barvám chcete najít nejbližší položky vzorníku. @msg select objects or give `color` vyberte objekty nebo zadejte `color` @msg `color` is not a colour `color` není barva diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index cee87b9b9..0015d0d8f 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -5969,6 +5969,9 @@ anchor Points: Punkter: Find Nearest Swatch… Find nærmeste farveprøve… Find Nearest Swatch Find nærmeste farveprøve Apply Nearest Swatch Anvend nærmeste farveprøve +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Anvend udfyldning på kontur + Apply Stroke to Fill Anvend kontur på udfyldning Select objects to find the swatches nearest their colors. Markér objekter for at finde de farveprøver, der ligger tættest på deres farver. @msg select objects or give `color` markér objekter, eller angiv `color` @msg `color` is not a colour `color` er ikke en farve diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index a082489ee..4602d1a3a 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -5688,6 +5688,9 @@ anchor Points: Punkte: Find Nearest Swatch… Ähnlichstes Farbfeld suchen… Find Nearest Swatch Ähnlichstes Farbfeld suchen Apply Nearest Swatch Ähnlichstes Farbfeld anwenden +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Fläche auf Kontur anwenden + Apply Stroke to Fill Kontur auf Fläche anwenden Select objects to find the swatches nearest their colors. Wählen Sie Objekte aus, um die Farbfelder zu finden, die ihren Farben am nächsten kommen. @msg select objects or give `color` wählen Sie Objekte aus oder geben Sie `color` an @msg `color` is not a colour `color` ist keine Farbe diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index bb7438803..5753e5747 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -6054,6 +6054,9 @@ anchor Points: Σημεία: Find Nearest Swatch… Εύρεση πλησιέστερου δείγματος χρώματος… Find Nearest Swatch Εύρεση πλησιέστερου δείγματος χρώματος Apply Nearest Swatch Εφαρμογή πλησιέστερου δείγματος χρώματος +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Εφαρμογή γεμίσματος στο περίγραμμα + Apply Stroke to Fill Εφαρμογή περιγράμματος στο γέμισμα Select objects to find the swatches nearest their colors. Επιλέξτε αντικείμενα για να βρείτε τα δείγματα χρωμάτων που πλησιάζουν περισσότερο τα χρώματά τους. @msg select objects or give `color` επιλέξτε αντικείμενα ή δώστε `color` @msg `color` is not a colour το `color` δεν είναι χρώμα diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 20fbbc82c..925630c8f 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -6045,6 +6045,9 @@ anchor Points: Puntos: Find Nearest Swatch… Buscar muestra más cercana… Find Nearest Swatch Buscar muestra más cercana Apply Nearest Swatch Aplicar muestra más cercana +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Aplicar relleno al trazo + Apply Stroke to Fill Aplicar trazo al relleno Select objects to find the swatches nearest their colors. Seleccione objetos para buscar las muestras más cercanas a sus colores. @msg select objects or give `color` seleccione objetos o indique `color` @msg `color` is not a colour `color` no es un color diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index 67eb1eb5e..489ccafae 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -6133,6 +6133,9 @@ anchor Points: Pisteet: Find Nearest Swatch… Etsi lähin värimalli… Find Nearest Swatch Etsi lähin värimalli Apply Nearest Swatch Käytä lähintä värimallia +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Käytä täyttöä viivaan + Apply Stroke to Fill Käytä viivaa täyttöön Select objects to find the swatches nearest their colors. Valitse objektit, joiden väreille haluat etsiä lähimmät värimallit. @msg select objects or give `color` valitse objekteja tai anna `color` @msg `color` is not a colour `color` ei ole väri diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index d882424f2..f00fd1ba1 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -6098,6 +6098,9 @@ anchor Points: Points : Find Nearest Swatch… Rechercher la nuance la plus proche… Find Nearest Swatch Rechercher la nuance la plus proche Apply Nearest Swatch Appliquer la nuance la plus proche +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Appliquer le fond au contour + Apply Stroke to Fill Appliquer le contour au fond Select objects to find the swatches nearest their colors. Sélectionnez des objets pour trouver les nuances les plus proches de leurs couleurs. @msg select objects or give `color` sélectionnez des objets ou indiquez `color` @msg `color` is not a colour `color` n’est pas une couleur diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 3f6092632..7f350c633 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -6087,6 +6087,9 @@ anchor Points: Pontok: Find Nearest Swatch… Legközelebbi színminta keresése… Find Nearest Swatch Legközelebbi színminta keresése Apply Nearest Swatch Legközelebbi színminta alkalmazása +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Kitöltés alkalmazása a körvonalra + Apply Stroke to Fill Körvonal alkalmazása a kitöltésre Select objects to find the swatches nearest their colors. Jelöljön ki objektumokat a színeikhez legközelebbi színminták megkereséséhez. @msg select objects or give `color` jelöljön ki objektumokat, vagy adja meg ezt: `color` @msg `color` is not a colour a(z) `color` nem szín diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 44c6fa8b5..1511db123 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -5969,6 +5969,9 @@ anchor Points: Titik: Find Nearest Swatch… Cari Swatch Terdekat… Find Nearest Swatch Cari Swatch Terdekat Apply Nearest Swatch Terapkan Swatch Terdekat +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Terapkan isian ke guratan + Apply Stroke to Fill Terapkan guratan ke isian Select objects to find the swatches nearest their colors. Pilih objek untuk menemukan swatch yang paling dekat dengan warnanya. @msg select objects or give `color` pilih objek atau berikan `color` @msg `color` is not a colour `color` bukan warna diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 5b6aed43d..7a4768dda 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -5722,6 +5722,9 @@ anchor Points: Punti: Find Nearest Swatch… Trova campione più vicino… Find Nearest Swatch Trova campione più vicino Apply Nearest Swatch Applica campione più vicino +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Applica riempimento alla traccia + Apply Stroke to Fill Applica traccia al riempimento Select objects to find the swatches nearest their colors. Seleziona gli oggetti per trovare i campioni più vicini ai loro colori. @msg select objects or give `color` seleziona gli oggetti o indica `color` @msg `color` is not a colour `color` non è un colore diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 26ed1e4d0..e17e99d22 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -5787,6 +5787,9 @@ anchor Points: ポイント: Find Nearest Swatch… 最も近いスウォッチを検索… Find Nearest Swatch 最も近いスウォッチを検索 Apply Nearest Swatch 最も近いスウォッチを適用 +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke 塗りを線に適用 + Apply Stroke to Fill 線を塗りに適用 Select objects to find the swatches nearest their colors. カラーに最も近いスウォッチを探すオブジェクトを選択してください。 @msg select objects or give `color` オブジェクトを選択するか `color` を指定してください @msg `color` is not a colour `color` はカラーではありません diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index fe18de7d3..8b26add37 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -6100,6 +6100,9 @@ anchor Points: წერტილები: Find Nearest Swatch… უახლოესი ფერის ნიმუშის პოვნა… Find Nearest Swatch უახლოესი ფერის ნიმუშის პოვნა Apply Nearest Swatch უახლოესი ფერის ნიმუშის გამოყენება +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke შევსების გამოყენება კონტურზე + Apply Stroke to Fill კონტურის გამოყენება შევსებაზე Select objects to find the swatches nearest their colors. აირჩიეთ ობიექტები მათ ფერებთან უახლოესი ფერის ნიმუშების საპოვნელად. @msg select objects or give `color` აირჩიეთ ობიექტები ან მიუთითეთ `color` @msg `color` is not a colour `color` ფერი არ არის diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index f5a6a2be3..680bfce8e 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -5683,6 +5683,9 @@ anchor Points: 포인트: Find Nearest Swatch… 가장 가까운 견본 찾기… Find Nearest Swatch 가장 가까운 견본 찾기 Apply Nearest Swatch 가장 가까운 견본 적용 +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke 칠을 선에 적용 + Apply Stroke to Fill 선을 칠에 적용 Select objects to find the swatches nearest their colors. 색상에 가장 가까운 견본을 찾을 개체를 선택하십시오. @msg select objects or give `color` 개체를 선택하거나 `color`를 지정하십시오 @msg `color` is not a colour `color`은(는) 색상이 아닙니다 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index f205246ca..f46c0a715 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -6013,6 +6013,9 @@ anchor Points: Punten: Find Nearest Swatch… Dichtstbijzijnde staal zoeken… Find Nearest Swatch Dichtstbijzijnde staal zoeken Apply Nearest Swatch Dichtstbijzijnde staal toepassen +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Vulling op streek toepassen + Apply Stroke to Fill Streek op vulling toepassen Select objects to find the swatches nearest their colors. Selecteer objecten om de stalen te vinden die het dichtst bij hun kleuren liggen. @msg select objects or give `color` selecteer objecten of geef `color` op @msg `color` is not a colour `color` is geen kleur diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 942d31241..76551ff0a 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -5957,6 +5957,9 @@ anchor Points: Punkty: Find Nearest Swatch… Znajdź najbliższą próbkę… Find Nearest Swatch Znajdź najbliższą próbkę Apply Nearest Swatch Zastosuj najbliższą próbkę +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Zastosuj wypełnienie do obrysu + Apply Stroke to Fill Zastosuj obrys do wypełnienia Select objects to find the swatches nearest their colors. Zaznacz obiekty, aby znaleźć próbki najbliższe ich kolorom. @msg select objects or give `color` zaznacz obiekty lub podaj `color` @msg `color` is not a colour `color` nie jest kolorem diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 4777ebe37..6bcc5a619 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -6019,6 +6019,9 @@ anchor Points: Pontos: Find Nearest Swatch… Localizar Amostra Mais Próxima… Find Nearest Swatch Localizar Amostra Mais Próxima Apply Nearest Swatch Aplicar Amostra Mais Próxima +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Aplicar Preenchimento ao Traçado + Apply Stroke to Fill Aplicar Traçado ao Preenchimento Select objects to find the swatches nearest their colors. Selecione objetos para localizar as amostras mais próximas de suas cores. @msg select objects or give `color` selecione objetos ou informe `color` @msg `color` is not a colour `color` não é uma cor diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index c31580575..8b7ce252f 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -6080,6 +6080,9 @@ anchor Points: Точки: Find Nearest Swatch… Найти ближайший образец… Find Nearest Swatch Найти ближайший образец Apply Nearest Swatch Применить ближайший образец +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Применить заливку к обводке + Apply Stroke to Fill Применить обводку к заливке Select objects to find the swatches nearest their colors. Выделите объекты, чтобы найти образцы, ближайшие к их цветам. @msg select objects or give `color` выделите объекты или укажите `color` @msg `color` is not a colour `color` не является цветом diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index 663968cbd..689bad0d5 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -6155,6 +6155,9 @@ shape Open Öppen Find Nearest Swatch… Sök närmaste färgruta… Find Nearest Swatch Sök närmaste färgruta Apply Nearest Swatch Använd närmaste färgruta +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Använd fyllning på kontur + Apply Stroke to Fill Använd kontur på fyllning Select objects to find the swatches nearest their colors. Markera objekt för att hitta de färgrutor som ligger närmast deras färger. @msg select objects or give `color` markera objekt eller ange `color` @msg `color` is not a colour `color` är ingen färg diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index bee81bfec..18f29eb36 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -6060,6 +6060,9 @@ anchor Points: Noktalar: Find Nearest Swatch… En Yakın Renk Örneğini Bul… Find Nearest Swatch En Yakın Renk Örneğini Bul Apply Nearest Swatch En Yakın Renk Örneğini Uygula +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Dolguyu Kontura Uygula + Apply Stroke to Fill Konturu Dolguya Uygula Select objects to find the swatches nearest their colors. Renklerine en yakın renk örneklerini bulmak için nesneleri seçin. @msg select objects or give `color` nesneleri seçin veya `color` verin @msg `color` is not a colour `color` bir renk değil diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index ff7cd23e2..e0583951b 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -5702,6 +5702,9 @@ anchor Points: Точки: Find Nearest Swatch… Знайти найближчий зразок… Find Nearest Swatch Знайти найближчий зразок Apply Nearest Swatch Застосувати найближчий зразок +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Застосувати заливку до обведення + Apply Stroke to Fill Застосувати обведення до заливки Select objects to find the swatches nearest their colors. Виділіть об’єкти, щоб знайти зразки, найближчі до їхніх кольорів. @msg select objects or give `color` виділіть об’єкти або вкажіть `color` @msg `color` is not a colour `color` не є кольором diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index ea54335f0..aa47c6762 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -5703,6 +5703,9 @@ anchor Points: Điểm: Find Nearest Swatch… Tìm mẫu màu gần nhất… Find Nearest Swatch Tìm mẫu màu gần nhất Apply Nearest Swatch Áp dụng mẫu màu gần nhất +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke Áp dụng màu tô cho nét + Apply Stroke to Fill Áp dụng nét cho màu tô Select objects to find the swatches nearest their colors. Chọn đối tượng để tìm các mẫu màu gần nhất với màu của chúng. @msg select objects or give `color` chọn đối tượng hoặc cung cấp `color` @msg `color` is not a colour `color` không phải là màu diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 480584b70..c3976d7d9 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -5944,6 +5944,9 @@ anchor Points: 点: Find Nearest Swatch… 查找最接近的色板… Find Nearest Swatch 查找最接近的色板 Apply Nearest Swatch 应用最接近的色板 +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke 将填色应用于描边 + Apply Stroke to Fill 将描边应用于填色 Select objects to find the swatches nearest their colors. 选择对象以查找与其颜色最接近的色板。 @msg select objects or give `color` 请选择对象或提供 `color` @msg `color` is not a colour `color` 不是颜色 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 7e3e1477f..1fcda1b61 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -5740,6 +5740,9 @@ anchor Points: 點: Find Nearest Swatch… 尋找最接近的色票… Find Nearest Swatch 尋找最接近的色票 Apply Nearest Swatch 套用最接近的色票 +# Color Effects add-on: Apply Fill to Stroke and Apply Stroke to Fill (Object › Plug-ins › Color Effects, #5530). + Apply Fill to Stroke 將填色套用至筆畫 + Apply Stroke to Fill 將筆畫套用至填色 Select objects to find the swatches nearest their colors. 選取物件以尋找與其顏色最接近的色票。 @msg select objects or give `color` 請選取物件或提供 `color` @msg `color` is not a colour `color` 不是顏色 diff --git a/crates/ui-egui/src/panels/color.rs b/crates/ui-egui/src/panels/color.rs index 80c567759..643a7ff66 100644 --- a/crates/ui-egui/src/panels/color.rs +++ b/crates/ui-egui/src/panels/color.rs @@ -782,12 +782,6 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) { recolor(app, ui, &tgt, invert, false); } } - // Each selected object's stroke takes its own fill, or the other way (#5530). - for (label, cmd) in [(tl!("Apply Fill to Stroke"), "paint.fillToStroke"), (tl!("Apply Stroke to Fill"), "paint.strokeToFill")] { - if menu_item(ui, label, true, false) { - app.run(cmd, json!({})).ok(); - } - } ui.separator(); if menu_item(ui, tl!("Create New Swatch…"), color.is_some(), false) && let Some(c) = color