mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-11-07 23:00:35 +00:00
Merge pull request #5827 from mockydevs/fix/appearance-opacity-default-5742
Appearance: Delete Selected Item on an Opacity row sets it back to Default (#5742)
This commit is contained in:
@@ -13,7 +13,8 @@
|
||||
//! (dropped on its row) or onto the object; Alt copies instead. Dropped on Duplicate Selected Item
|
||||
//! or Delete Selected Item, a fill/stroke row (with the other selected rows, when it is one of
|
||||
//! them) or an effect row is copied or removed. Cmd-click and Shift-click select several
|
||||
//! fills/strokes for Duplicate and Delete.
|
||||
//! fills/strokes for Duplicate and Delete. Delete on a selected Opacity row sets it back to
|
||||
//! Default (100%, Normal).
|
||||
|
||||
use std::sync::OnceLock;
|
||||
|
||||
@@ -59,11 +60,16 @@ impl Sel {
|
||||
Sel::None | Sel::Effect(_) | Sel::Contents | Sel::Opacity => None,
|
||||
}
|
||||
}
|
||||
/// Is a fill/stroke or effect row selected (what Duplicate and Delete act on)? An Opacity row
|
||||
/// is not an item: it can't be copied or removed.
|
||||
/// Is a fill/stroke or effect row selected (what Duplicate acts on)? An Opacity row is not an
|
||||
/// item: it can't be copied.
|
||||
fn editable(self) -> bool {
|
||||
matches!(self, Sel::Item(_) | Sel::Effect(_) | Sel::ItemEffect(..))
|
||||
}
|
||||
/// Does Delete act on the selected row? A fill/stroke or effect row is removed; deleting an
|
||||
/// Opacity row sets it back to Default (100%, Normal) (#5742).
|
||||
fn deletable(self) -> bool {
|
||||
self.editable() || matches!(self, Sel::Opacity | Sel::ItemOpacity(_))
|
||||
}
|
||||
}
|
||||
|
||||
/// The selected row of `node` (the first selected object): a selected effect row while that
|
||||
@@ -490,7 +496,7 @@ fn delete_key(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
let node = first_subject(app);
|
||||
let sel = current_sel(app, &ctx, node.as_ref());
|
||||
if keys
|
||||
&& sel.editable()
|
||||
&& sel.deletable()
|
||||
&& ctx.memory(|m| m.focused().is_none())
|
||||
&& ui.input_mut(|i| i.consume_key(egui::Modifiers::NONE, egui::Key::Delete) || i.consume_key(egui::Modifiers::NONE, egui::Key::Backspace))
|
||||
{
|
||||
@@ -1088,7 +1094,8 @@ fn opacity_row(app: &mut VectorcraftApp, ui: &mut Ui, item: Option<usize>, opaci
|
||||
let link = name_link(ui, pos2(lx, r.center().y), ("ap-op-link", item), tl!("Opacity:")).clicked();
|
||||
if link || resp.clicked() {
|
||||
select_row(app, ui.ctx(), this);
|
||||
// The panel has the keyboard (Delete then removes nothing, rather than the object).
|
||||
// The panel has the keyboard (Delete then sets the row back to Default, rather than
|
||||
// deleting the object).
|
||||
super::set_row_keys(ui.ctx(), ROW_KEYS, true);
|
||||
}
|
||||
if link {
|
||||
@@ -1415,7 +1422,8 @@ fn bottom(app: &mut VectorcraftApp, ui: &mut Ui, node: Option<&Node>, sel: Sel)
|
||||
if duplicate.clicked() {
|
||||
duplicate_selected(app, ui, node, sel);
|
||||
}
|
||||
let delete = widgets::icon_button_enabled(ui, "trash-2", tl!("Delete Selected Item"), false, can, 24.0);
|
||||
let can_delete = can || (has && sel.deletable());
|
||||
let delete = widgets::icon_button_enabled(ui, "trash-2", tl!("Delete Selected Item"), false, can_delete, 24.0);
|
||||
if delete.clicked() {
|
||||
delete_selected(app, ui, node, sel);
|
||||
}
|
||||
@@ -1558,8 +1566,20 @@ fn highlight_copies(app: &mut VectorcraftApp, ctx: &egui::Context, n: &Node, sel
|
||||
set_pstate(ctx, "ap-multi", Highlight { owner: Some(n.id), rows, order, anchor, active });
|
||||
}
|
||||
|
||||
/// Remove the highlighted fills/strokes or effects; a removed item effect leaves its fill/stroke selected.
|
||||
/// Remove the highlighted fills/strokes or effects; a removed item effect leaves its fill/stroke
|
||||
/// selected. A selected Opacity row is set back to Default (100%, Normal) and stays selected.
|
||||
fn delete_selected(app: &mut VectorcraftApp, ui: &Ui, node: Option<&Node>, sel: Sel) {
|
||||
match sel {
|
||||
Sel::Opacity => {
|
||||
app.run("transparency.set", json!({"item": null, "opacity": 100, "blend": "Normal"})).ok();
|
||||
return;
|
||||
}
|
||||
Sel::ItemOpacity(i) => {
|
||||
app.run("transparency.set", aligned(app, json!({"item": i, "opacity": 100, "blend": "Normal"}))).ok();
|
||||
return;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let ok = match selected_effects(ui.ctx(), node, sel) {
|
||||
Some((item, ks)) => app.run("effect.remove", aligned(app, json!({"item": item, "indices": ks}))).is_ok(),
|
||||
None if matches!(sel, Sel::Item(_)) => {
|
||||
@@ -1616,7 +1636,7 @@ pub fn menu(app: &mut VectorcraftApp, ui: &mut Ui) {
|
||||
if menu_item(ui, tl!("Duplicate Item"), has && sel.editable(), false) {
|
||||
duplicate_selected(app, ui, node.as_ref(), sel);
|
||||
}
|
||||
if menu_item(ui, tl!("Remove Item"), has && sel.editable(), false) {
|
||||
if menu_item(ui, tl!("Remove Item"), has && sel.deletable(), false) {
|
||||
delete_selected(app, ui, node.as_ref(), sel);
|
||||
}
|
||||
if menu_item(ui, tl!("Clear Appearance"), has, false) {
|
||||
|
||||
@@ -85,6 +85,47 @@ fn removing_items_keeps_a_surviving_item_opacity_row_selected() {
|
||||
assert_eq!(app.session.appearance_item(), None);
|
||||
}
|
||||
|
||||
/// #5742: Delete Selected Item on an Opacity row sets it back to Default (100%, Normal): the
|
||||
/// object's own, or a fill's, leaving the other untouched. Duplicate stays off for it.
|
||||
#[test]
|
||||
fn deleting_an_opacity_row_sets_it_back_to_default() {
|
||||
let mut app = VectorcraftApp::new(vectorcraft_engine::Session::new(), Default::default());
|
||||
app.run("file.new", json!({"width": 100, "height": 100})).unwrap();
|
||||
let id = app.run("shape.rectangle", json!({"x": 0, "y": 0, "width": 20, "height": 20})).unwrap()["id"].as_u64().unwrap();
|
||||
app.run("select.set", json!({ "ids": [id] })).unwrap();
|
||||
app.run("transparency.set", json!({"item": null, "opacity": 50, "blend": "Multiply"})).unwrap();
|
||||
app.run("transparency.set", json!({"item": 0, "opacity": 40, "blend": "Screen"})).unwrap();
|
||||
let owner = Some(NodeId(id));
|
||||
let ctx = egui::Context::default();
|
||||
let delete = |app: &mut VectorcraftApp| {
|
||||
let mut out = ctx.run_ui(egui::RawInput::default(), |ui| {
|
||||
let node = first_subject(app);
|
||||
let sel = current_sel(app, ui.ctx(), node.as_ref());
|
||||
assert!(sel.deletable() && !sel.editable(), "{sel:?}");
|
||||
delete_selected(app, ui, node.as_ref(), sel);
|
||||
});
|
||||
out.textures_delta.clear();
|
||||
};
|
||||
let node = |app: &VectorcraftApp| first_subject(app).unwrap();
|
||||
// The fill's Opacity row: only the fill goes back to Default.
|
||||
app.run("appearance.setActiveItem", json!({ "index": 0 })).unwrap();
|
||||
set_pstate(&ctx, "ap-sel", (owner, Sel::ItemOpacity(0)));
|
||||
delete(&mut app);
|
||||
let n = node(&app);
|
||||
assert_eq!((n.appearance.items[0].opacity(), n.appearance.items[0].blend()), (1.0, BlendMode::Normal));
|
||||
assert_eq!((n.opacity, n.blend), (0.5, BlendMode::Multiply));
|
||||
assert_eq!(pstate::<(Option<NodeId>, Sel)>(&ctx, "ap-sel").1, Sel::ItemOpacity(0));
|
||||
// The object's Opacity row.
|
||||
app.run("appearance.setActiveItem", json!({ "index": null })).unwrap();
|
||||
set_pstate(&ctx, "ap-sel", (owner, Sel::Opacity));
|
||||
delete(&mut app);
|
||||
let n = node(&app);
|
||||
assert_eq!(opacity_text(n.opacity, n.blend), "Default");
|
||||
// One step, so Undo brings the object's opacity back.
|
||||
app.run("edit.undo", json!({})).unwrap();
|
||||
assert_eq!(node(&app).opacity, 0.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opacity_labels() {
|
||||
assert_eq!(opacity_text(1.0, BlendMode::Normal), "Default");
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
# UI and interaction parity
|
||||
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-05 · **Change:** minor (Pencil and Paintbrush: a new stroke with Keep selected off leaves nothing selected, #5752; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged)
|
||||
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-05 · **Change:** minor (Appearance panel: Delete Selected Item on an Opacity row sets it back to Default, #5742; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged)
|
||||
|
||||
## Feature submission evidence
|
||||
|
||||
@@ -38,6 +38,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard
|
||||
|
||||
| Date | Change | Summary |
|
||||
|---|---|---|
|
||||
| 2026-11-05 | minor | Appearance panel: Delete Selected Item (and Delete/Backspace, and Remove Item in the panel menu) on a selected Opacity row, the object's or a fill's or stroke's, sets it back to Default (100%, Normal) as one undo step instead of being dimmed (#5742); scores and estimates unchanged |
|
||||
| 2026-11-05 | minor | Selection Tools add-on: Select Linked, Embedded, Missing and Modified select the images and placed documents with that link status (Object › Plug-ins › Selection Tools and the Links panel menu; Shift adds), and Select by Link Status… lists them by name, status and format to pick from, optionally zooming to them; what is hidden, locked, on a template layer or outside the isolated group is left out and the status bar says how many (#5526, a VectorCraft addition); scores and estimates unchanged (estimated). |
|
||||
| 2026-11-05 | minor | Pencil and Paintbrush: a new stroke with Keep selected off leaves nothing selected, the earlier selection included (#5752); scores and estimates unchanged |
|
||||
| 2026-11-05 | minor | Layers panel: a layer whose Layer Options › Print is off shows its name italic, as the reference application shows non-printing layers, and keeps it while the row is dragged (#5739); scores and estimates unchanged |
|
||||
|
||||
Reference in New Issue
Block a user