Gradients: picking a part of a symbol instance leaves no gradient stop selected

A stop selected on a whole dynamic symbol instance's gradient stayed
selected after Direct Selection picked an object of that instance's art,
because the instance stayed the first selected object. The Gradient panel
kept the stop highlighted and the Gradient tool took Delete and the arrow
keys for it, while every gradient edit is refused for a picked object.

The stop's owner now records whether a part is picked, so the stop is not
selected while one is, and is selected again when the whole instance is.
This commit is contained in:
dotCooCoo
2026-11-11 07:39:47 -08:00
parent 9fc43f2a35
commit b44cbdb048
2 changed files with 35 additions and 3 deletions
+12 -3
View File
@@ -80,19 +80,28 @@ fn select_stop(s: &mut Session, p: &Value) -> Result<Value> {
}
/// Whose gradient a selected stop belongs to: the active document, its first selected object
/// (None: the default paint), the proxy in front and the Appearance panel's active item.
/// Selecting other art, toggling the proxy or picking another item leaves no stop selected.
/// (None: the default paint), whether a part of a symbol instance's art is picked, the proxy in
/// front and the Appearance panel's active item. Selecting other art, picking a part of the
/// instance, toggling the proxy or picking another item leaves no stop selected.
#[derive(Clone, Copy, Debug, PartialEq)]
pub(crate) struct StopOwner {
doc: Option<usize>,
object: Option<NodeId>,
part: bool,
fill: bool,
item: Option<usize>,
}
impl StopOwner {
pub(crate) fn of(s: &Session) -> Self {
Self { doc: s.active, object: s.active().and_then(|d| d.selection.objects.first().copied()), fill: s.fill_active, item: s.appearance_item() }
let st = s.active();
Self {
doc: s.active,
object: st.and_then(|d| d.selection.objects.first().copied()),
part: st.is_some_and(|d| d.selection.symbol_part.is_some()),
fill: s.fill_active,
item: s.appearance_item(),
}
}
}
@@ -903,6 +903,29 @@ fn gradients_and_patterns_on_a_picked_object_are_taken() {
assert_eq!((undo_steps(&s), &s.paint.fill), (undo, &fill), "nothing changes");
}
/// A stop selected on the whole instance's gradient is not selected once a part of its art is
/// picked, so the Gradient tool does not take Delete for it, and is selected again when the whole
/// instance is.
#[test]
fn picking_a_part_leaves_no_gradient_stop_selected() {
let mut s = session();
let inst = pair(&mut s, "dynamic");
let g = json!({"kind": "linear", "stops": [{"offset": 0, "color": "#ff0000"}, {"offset": 1, "color": "#0000ff"}]});
s.execute("select.set", &json!({"ids": [inst.0]})).unwrap();
s.execute("paint.setFill", &json!({"gradient": g})).unwrap();
s.fill_active = true;
let view = ViewInfo::default();
s.select_tool("gradient", view).unwrap();
s.execute("gradient.selectStop", &json!({"index": 1})).unwrap();
assert_eq!(s.selected_stop(), Some(1));
assert!(s.tool_claims_key(vectorcraft_tools::ToolKey::Delete, view));
s.execute("symbol.instance.selectPart", &json!({"id": inst.0, "path": [1]})).unwrap();
assert_eq!(s.selected_stop(), None);
assert!(!s.tool_claims_key(vectorcraft_tools::ToolKey::Delete, view));
s.execute("select.set", &json!({"ids": [inst.0]})).unwrap();
assert_eq!(s.selected_stop(), Some(1));
}
#[test]
fn opacity_and_blend_on_a_picked_object_go_through_transparency_set() {
let mut s = session();