mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-29 20:27:05 -06:00
Merge pull request #4331 from Silvertree2010/fix/4206-bug-gradient-mesh-node-level-tra
Apply Transparency opacity to the clicked gradient mesh point
This commit is contained in:
@@ -343,6 +343,15 @@ impl TransparencyInfo {
|
||||
ids,
|
||||
}
|
||||
}
|
||||
|
||||
/// The opacity of the focused gradient mesh `point` (see `transparency.set`) instead of its
|
||||
/// object's.
|
||||
fn with_mesh_point(mut self, point: Option<(NodeId, usize, f32)>) -> Self {
|
||||
if let Some((_, _, o)) = point {
|
||||
self.opacity = Some((o as f64 * 10_000.0).round() / 100.0);
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// The nodes of `ids` that exist: looked up one by one when few, else in one walk of the tree.
|
||||
@@ -370,7 +379,10 @@ impl Session {
|
||||
/// edited (`transparency.info` without ids).
|
||||
pub fn transparency_info(&self) -> TransparencyInfo {
|
||||
match (self.active(), transparency_targets(self, &Value::Null)) {
|
||||
(Some(st), Ok(ids)) => TransparencyInfo::of(&st.doc, ids),
|
||||
(Some(st), Ok(ids)) => {
|
||||
let point = super::paint::focused_mesh_point_of(self, &ids);
|
||||
TransparencyInfo::of(&st.doc, ids).with_mesh_point(point)
|
||||
}
|
||||
_ => TransparencyInfo::default(),
|
||||
}
|
||||
}
|
||||
@@ -378,7 +390,9 @@ impl Session {
|
||||
|
||||
fn info(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
let st = s.doc()?;
|
||||
let i = TransparencyInfo::of(&st.doc, transparency_targets(s, p)?);
|
||||
let ids = transparency_targets(s, p)?;
|
||||
let point = super::paint::focused_mesh_point_of(s, &ids);
|
||||
let i = TransparencyInfo::of(&st.doc, ids).with_mesh_point(point);
|
||||
Ok(json!({
|
||||
"ids": i.ids.iter().map(|id| id.0).collect::<Vec<_>>(),
|
||||
"opacity": i.opacity,
|
||||
|
||||
@@ -71,7 +71,7 @@ pub fn specs() -> Vec<CommandSpec> {
|
||||
"Transparency",
|
||||
["Window", "Transparency"],
|
||||
None,
|
||||
"{ids?|id?, opacity?: 0..100, blend?: name, isolate?, knockout?: \"on\"|\"off\"|\"neutral\"|bool (true = on, false = neutral), knockoutShape?: bool, item?: appearance item index|null, target?: \"object\"|\"contents\", aligned?: bool (index and item are rows of the first target's stack: each other target edits its equal one, as the Appearance panel does for a mixed selection)} for `ids`, the selection, or the object whose opacity mask is being edited; opacity and blend go to the targeted fill/stroke item (omitted: the Appearance panel's active item, else the objects)",
|
||||
"{ids?|id?, opacity?: 0..100, blend?: name, isolate?, knockout?: \"on\"|\"off\"|\"neutral\"|bool (true = on, false = neutral), knockoutShape?: bool, item?: appearance item index|null, target?: \"object\"|\"contents\", aligned?: bool (index and item are rows of the first target's stack: each other target edits its equal one, as the Appearance panel does for a mixed selection)} for `ids`, the selection, or the object whose opacity mask is being edited; opacity and blend go to the targeted fill/stroke item (omitted: the Appearance panel's active item, else the objects; a gradient mesh point last clicked with the Mesh tool or Direct Selection takes the opacity itself)",
|
||||
has_doc,
|
||||
transparency
|
||||
),
|
||||
@@ -602,6 +602,14 @@ fn focused_mesh_point(s: &Session) -> Option<(NodeId, usize)> {
|
||||
Some((NodeId(pt.get("id")?.as_u64()?), usize::try_from(pt.get("index")?.as_u64()?).ok()?))
|
||||
}
|
||||
|
||||
/// The focused mesh point (see [`focused_mesh_point`]) with its opacity, when `ids` is just the
|
||||
/// gradient mesh it belongs to.
|
||||
pub(crate) fn focused_mesh_point_of(s: &Session, ids: &[NodeId]) -> Option<(NodeId, usize, f32)> {
|
||||
let (id, index) = focused_mesh_point(s).filter(|(id, _)| ids == [*id])?;
|
||||
let NodeKind::Mesh(m) = &s.doc().ok()?.doc.node(id)?.kind else { return None };
|
||||
Some((id, index, m.points.get(index)?.opacity))
|
||||
}
|
||||
|
||||
/// A type run's stroke; a painted stroke on a run without one gets 1 pt.
|
||||
fn set_run_stroke(style: &mut CharStyle, paint: Paint) {
|
||||
if !paint.is_none() {
|
||||
@@ -745,6 +753,19 @@ fn transparency(s: &mut Session, p: &Value) -> Result<Value> {
|
||||
q.remove("item");
|
||||
q.remove("id");
|
||||
q.insert("ids".into(), json!(ids.iter().map(|id| id.0).collect::<Vec<_>>()));
|
||||
// The gradient mesh point last clicked with the Mesh tool or Direct Selection takes the
|
||||
// opacity itself, so only the mesh around it fades (#4206).
|
||||
if matches!(item, ItemTarget::Top)
|
||||
&& let Some(o) = q.get("opacity").and_then(Value::as_f64)
|
||||
&& let Some((id, index, _)) = focused_mesh_point_of(s, &ids)
|
||||
{
|
||||
q.remove("opacity");
|
||||
let opacity = f64::from(super::opacitymask::percent(o));
|
||||
s.execute("object.mesh.setPointColor", &json!({"id": id.0, "index": index, "opacity": opacity}))?;
|
||||
if q.keys().all(|k| k == "ids") {
|
||||
return ok();
|
||||
}
|
||||
}
|
||||
let ItemTarget::Item { index, explicit, aligned } = item else {
|
||||
return s.execute("object.setProps", &Value::Object(q));
|
||||
};
|
||||
|
||||
@@ -187,6 +187,52 @@ fn a_clicked_mesh_point_takes_the_fill_colour() {
|
||||
assert_eq!((after[0], after[3]), (before[0], before[3]));
|
||||
}
|
||||
|
||||
fn mesh_opacities(s: &Session, id: NodeId) -> Vec<f32> {
|
||||
match &s.doc().unwrap().doc.node(id).unwrap().kind {
|
||||
NodeKind::Mesh(m) => m.points.iter().map(|p| p.opacity).collect(),
|
||||
_ => panic!("not a mesh"),
|
||||
}
|
||||
}
|
||||
|
||||
/// The Transparency panel's opacity on a clicked mesh point fades that point alone, not the whole
|
||||
/// object (#4206).
|
||||
#[test]
|
||||
fn a_clicked_mesh_point_takes_the_opacity() {
|
||||
let mut s = session();
|
||||
let m = rect(&mut s, json!("#ff0000"));
|
||||
s.execute("object.mesh.create", &json!({"rows": 2, "cols": 2})).unwrap();
|
||||
s.execute("select.set", &json!({"ids": [m.0]})).unwrap();
|
||||
let v = ViewInfo::default();
|
||||
let click = |s: &mut Session, i: usize| {
|
||||
let NodeKind::Mesh(g) = &s.doc().unwrap().doc.node(m).unwrap().kind else { panic!("not a mesh") };
|
||||
let q = g.points[i].p;
|
||||
for kind in [PointerKind::Down, PointerKind::Up] {
|
||||
s.pointer(&PointerEvent::new(kind, q.x, q.y), v).unwrap();
|
||||
}
|
||||
};
|
||||
let object_opacity = |s: &Session| s.doc().unwrap().doc.node(m).unwrap().opacity;
|
||||
for (tool, point) in [("mesh", 4), ("directSelection", 4)] {
|
||||
s.select_tool(tool, v).unwrap();
|
||||
click(&mut s, point);
|
||||
let undo = s.doc().unwrap().history.undo.len();
|
||||
s.execute("transparency.set", &json!({"opacity": 30})).unwrap();
|
||||
let o = mesh_opacities(&s, m);
|
||||
assert!((o[4] - 0.3).abs() < 1e-6, "{tool}: the clicked point fades: {o:?}");
|
||||
assert!(o.iter().enumerate().all(|(i, a)| i == 4 || *a == 1.0), "{tool}: the other points stay opaque: {o:?}");
|
||||
assert_eq!(object_opacity(&s), 1.0, "{tool}: the object stays opaque");
|
||||
assert_eq!(s.transparency_info().opacity, Some(30.0), "{tool}: the panel shows the point's opacity");
|
||||
assert_eq!(s.doc().unwrap().history.undo.len(), undo + 1, "one undo step");
|
||||
s.execute("edit.undo", &json!({})).unwrap();
|
||||
assert!(mesh_opacities(&s, m).iter().all(|a| *a == 1.0));
|
||||
}
|
||||
// With no point focused the object takes the opacity.
|
||||
s.select_tool("selection", v).unwrap();
|
||||
s.execute("select.set", &json!({"ids": [m.0]})).unwrap();
|
||||
s.execute("transparency.set", &json!({"opacity": 30})).unwrap();
|
||||
assert!((object_opacity(&s) - 0.3).abs() < 1e-6);
|
||||
assert!(mesh_opacities(&s, m).iter().all(|a| *a == 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blend_moves_a_mesh_to_the_blended_colour() {
|
||||
let mut s = session();
|
||||
|
||||
Reference in New Issue
Block a user