mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-10 09:59:47 -06:00
M3.8: split the paint/panel command modules and the dialogs into per-area files
No behaviour change: every command keeps its id, label, menu, shortcut and params doc. - Engine: swatch.* -> cmd/swatch.rs, graphicStyle.* -> cmd/style.rs, appearance.* (incl. copyFrom) -> cmd/appearance.rs, stroke.set/setAdvanced -> cmd/stroke.rs, paint.editGradient/setGradientGeom -> cmd/gradient.rs. Shared helpers in cmd/mod.rs: leaf_targets/paint_targets (replacing three copies) and unique_name (swatch and graphic style naming). - UI: dialogs.rs -> dialogs/ with one file per dialog and a DialogSpec registry (one DialogKind row per dialog); shared field widgets in dialogs/form.rs. Headless frame tests draw and confirm every dialog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d99853ed2a
commit
b7969a6a5d
@@ -1,9 +1,9 @@
|
||||
//! Commands backing transform/utility tools: free distort, eyedropper, gradient vector, artboard move.
|
||||
//! Commands backing transform/utility tools: free distort, eyedropper colour sampling, artboard move.
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use vectorcraft_color::{Gradient, GradientGeom, GradientPaint, Paint};
|
||||
use vectorcraft_color::Paint;
|
||||
use vectorcraft_doc::{Appearance, Node, NodeId, NodeKind};
|
||||
use vectorcraft_geom::{Affine, Point, Rect, Vec2};
|
||||
|
||||
@@ -22,15 +22,6 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_doc,
|
||||
distort
|
||||
),
|
||||
cmd!(
|
||||
"appearance.copyFrom",
|
||||
"Eyedropper",
|
||||
[],
|
||||
None,
|
||||
"{source: id, ids?} copy fill, stroke, weight, opacity and blend from `source` to ids (default: selection) and to the paint defaults",
|
||||
has_doc,
|
||||
copy_from
|
||||
),
|
||||
cmd!(
|
||||
"paint.sampleColor",
|
||||
"Sample Color",
|
||||
@@ -40,15 +31,6 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
has_doc,
|
||||
sample_color
|
||||
),
|
||||
cmd!(
|
||||
"paint.setGradientGeom",
|
||||
"Gradient Vector",
|
||||
[],
|
||||
None,
|
||||
"{start: [x,y], end: [x,y], ids?, stroke?: bool} set the gradient vector (solid paints become the default gradient)",
|
||||
has_doc,
|
||||
set_gradient_geom
|
||||
),
|
||||
cmd!(
|
||||
"artboard.move",
|
||||
"Move Artboard",
|
||||
@@ -190,72 +172,6 @@ fn distort(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
|
||||
// ---------- eyedropper ----------
|
||||
|
||||
/// Leaves whose appearance a paint command changes (groups expand to their contents; compound
|
||||
/// paths own their children's appearance).
|
||||
fn leaf_targets(s: &Session, ids: &[NodeId]) -> Result<Vec<NodeId>> {
|
||||
let d = &s.doc()?.doc;
|
||||
let mut out = vec![];
|
||||
for id in ids {
|
||||
let Some(n) = d.node(*id) else { continue };
|
||||
match &n.kind {
|
||||
NodeKind::Group { .. } | NodeKind::Layer { .. } => n.walk(&mut |c| {
|
||||
if !c.is_container() || matches!(c.kind, NodeKind::Compound { .. }) {
|
||||
out.push(c.id)
|
||||
}
|
||||
}),
|
||||
_ => out.push(*id),
|
||||
}
|
||||
}
|
||||
let comp: Vec<NodeId> = out.iter().filter(|id| matches!(d.node(**id).map(|n| &n.kind), Some(NodeKind::Compound { .. }))).copied().collect();
|
||||
out.retain(|id| !comp.iter().any(|c| d.parent_of(*id) == Some(*c)));
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn copy_from(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let src_id = id_param(p, "source").ok_or_else(|| bad("appearance.copyFrom", "missing `source` id"))?;
|
||||
let src = s.doc()?.doc.node(src_id).cloned().ok_or(EngineError::NoNode(src_id))?;
|
||||
let appearance = match &src.kind {
|
||||
NodeKind::Text(t) => match t.runs.first() {
|
||||
Some(r) => Appearance::basic(r.style.fill.clone(), r.style.stroke.clone(), r.style.stroke_width),
|
||||
None => src.appearance.clone(),
|
||||
},
|
||||
_ => src.appearance.clone(),
|
||||
};
|
||||
s.paint.fill = appearance.fill_paint();
|
||||
s.paint.stroke = appearance.stroke_paint();
|
||||
if appearance.stroke().is_some() {
|
||||
s.paint.stroke_width = appearance.stroke_width();
|
||||
}
|
||||
let ids = match ids_param(p, "ids") {
|
||||
Some(v) => v,
|
||||
None => selected_roots(s)?,
|
||||
};
|
||||
let mut targets = leaf_targets(s, &ids)?;
|
||||
targets.retain(|id| *id != src_id);
|
||||
if targets.is_empty() {
|
||||
return Ok(json!({ "ids": [] }));
|
||||
}
|
||||
let (opacity, blend) = (src.opacity, src.blend);
|
||||
s.edit("Eyedropper", |d, _| {
|
||||
for id in &targets {
|
||||
let Some(n) = d.node_mut(*id) else { continue };
|
||||
n.opacity = opacity;
|
||||
n.blend = blend;
|
||||
if let NodeKind::Text(t) = &mut n.kind {
|
||||
for r in &mut t.runs {
|
||||
r.style.fill = appearance.fill_paint();
|
||||
r.style.stroke = appearance.stroke_paint();
|
||||
r.style.stroke_width = appearance.stroke_width();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
n.appearance = appearance.clone();
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(json!({ "ids": targets.iter().map(|i| i.0).collect::<Vec<_>>() }))
|
||||
}
|
||||
|
||||
fn sample_color(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let stroke = p.get("stroke").and_then(Value::as_bool).unwrap_or(!s.fill_active);
|
||||
let c = p.get("color").ok_or_else(|| bad("paint.sampleColor", "missing color"))?;
|
||||
@@ -268,46 +184,6 @@ fn sample_color(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
(spec.run)(s, &q)
|
||||
}
|
||||
|
||||
// ---------- gradient tool ----------
|
||||
|
||||
fn set_gradient_geom(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let start = point_param(p, "start").ok_or_else(|| bad("paint.setGradientGeom", "missing start [x,y]"))?;
|
||||
let end = point_param(p, "end").ok_or_else(|| bad("paint.setGradientGeom", "missing end [x,y]"))?;
|
||||
let stroke = bool_or(p, "stroke", false);
|
||||
let ids = match ids_param(p, "ids") {
|
||||
Some(v) => v,
|
||||
None => selected_roots(s)?,
|
||||
};
|
||||
let targets = leaf_targets(s, &ids)?;
|
||||
if targets.is_empty() {
|
||||
return Err(EngineError::Other("nothing selected".into()));
|
||||
}
|
||||
s.edit("Gradient", |d, _| {
|
||||
for id in &targets {
|
||||
let Some(n) = d.node_mut(*id) else { continue };
|
||||
if matches!(n.kind, NodeKind::Text(_)) {
|
||||
continue;
|
||||
}
|
||||
let cur = if stroke { n.appearance.stroke_paint() } else { n.appearance.fill_paint() };
|
||||
let mut gp = match cur {
|
||||
Paint::Gradient(g) => *g,
|
||||
_ => GradientPaint::new(Gradient::default()),
|
||||
};
|
||||
let aspect = gp.geom.map(|g| g.aspect).unwrap_or(1.0);
|
||||
gp.geom = Some(GradientGeom { start, end, aspect });
|
||||
gp.angle = GradientGeom { start, end, aspect }.angle_deg();
|
||||
let paint = Paint::Gradient(Box::new(gp));
|
||||
if stroke {
|
||||
n.appearance.set_stroke(paint);
|
||||
} else {
|
||||
n.appearance.set_fill(paint);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(json!({ "ids": targets.iter().map(|i| i.0).collect::<Vec<_>>() }))
|
||||
}
|
||||
|
||||
// ---------- artboards ----------
|
||||
|
||||
fn artboard_move(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
|
||||
Reference in New Issue
Block a user