mirror of
https://github.com/storytold/vectorcraft.git
synced 2026-10-25 10:17:02 -06:00
Merge pull request #2344 from VL420/feat/task-bar-vectorize
G1: Image Trace, Crop Image and Expand in the Contextual Task Bar
This commit is contained in:
@@ -2243,7 +2243,19 @@ fn task_bar(app: &mut VectorcraftApp, ui: &mut Ui, xf: &Xf) {
|
||||
let first = st.selection.objects.first().and_then(|id| st.doc.node(*id)).cloned();
|
||||
let is_group = first.as_ref().is_some_and(|f| matches!(f.kind, NodeKind::Group { .. }));
|
||||
let is_text = first.as_ref().is_some_and(|f| matches!(f.kind, NodeKind::Text(_)));
|
||||
let is_image = first.as_ref().is_some_and(|f| matches!(f.kind, NodeKind::Image(_)));
|
||||
// An Image Trace object: the group the engine names "Image Trace", its source image first.
|
||||
let is_trace = is_group
|
||||
&& first.as_ref().is_some_and(|f| {
|
||||
f.name.as_deref() == Some("Image Trace") && f.children().is_some_and(|c| c.first().is_some_and(|i| matches!(i.kind, NodeKind::Image(_))))
|
||||
});
|
||||
let mut items: Vec<(&str, &str, &str)> = vec![]; // (label, icon, command)
|
||||
// As in the reference app, an image offers Image Trace and Crop Image, and traced art offers
|
||||
// Expand (<https://community.adobe.com/announcements-651/contextual-task-bar-in-illustrator-2025-quick-actions-when-you-need-them-1546580>).
|
||||
if n == 1 && is_image {
|
||||
items.push((tl!("Image Trace"), "image", TASK_BAR_IMAGE_TRACE));
|
||||
items.push((tl!("Crop Image"), "dc-pf-crop", "ui.cropImage"));
|
||||
}
|
||||
// Direct-selected anchors take the place of a path's Offset Path and Simplify.
|
||||
let anchors = !st.selection.anchors.is_empty();
|
||||
if anchors {
|
||||
@@ -2253,11 +2265,15 @@ fn task_bar(app: &mut VectorcraftApp, ui: &mut Ui, xf: &Xf) {
|
||||
if n > 1 {
|
||||
items.push((tl!("Group"), "group", "object.group"));
|
||||
items.push((tl!("Unite"), "squares-unite", "object.pathfinder.unite"));
|
||||
} else if is_trace {
|
||||
items.push((tl!("Expand"), "maximize-2", "imageTrace.expand"));
|
||||
} else if is_group {
|
||||
items.push((tl!("Ungroup"), "ungroup", "object.ungroup"));
|
||||
items.push((tl!("Isolate"), "square-dashed", "object.isolate"));
|
||||
} else if is_text {
|
||||
items.push((tl!("Create Outlines"), "type", "type.createOutlines"));
|
||||
} else if is_image {
|
||||
// Offset Path and Simplify don't apply to an image.
|
||||
} else if !anchors {
|
||||
items.push((tl!("Offset Path"), "square-dashed", "object.path.offsetPath"));
|
||||
items.push((tl!("Simplify"), "spline", "object.path.simplify"));
|
||||
@@ -2376,11 +2392,18 @@ fn task_bar(app: &mut VectorcraftApp, ui: &mut Ui, xf: &Xf) {
|
||||
}
|
||||
ui.ctx().request_repaint();
|
||||
}
|
||||
if let Some((c, p)) = run {
|
||||
crate::menus::invoke(app, c, p);
|
||||
match run {
|
||||
// Image Trace traces the image at once with the Default preset. As a quick trace from
|
||||
// Properties or the Control bar, it leaves the Image Trace panel as it was.
|
||||
Some((TASK_BAR_IMAGE_TRACE, _)) => crate::menus::invoke(app, "imageTrace.make", json!({"preset": "Default"})),
|
||||
Some((c, p)) => crate::menus::invoke(app, c, p),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
|
||||
/// The Contextual Task Bar's Image Trace button: a trace with the Default preset.
|
||||
const TASK_BAR_IMAGE_TRACE: &str = "taskBar.imageTrace";
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -4940,6 +4963,49 @@ mod tests {
|
||||
assert!(app.ui.dialog.is_none(), "no Recolor button without colours");
|
||||
}
|
||||
|
||||
/// The Contextual Task Bar shows Image Trace first for a selected image: it traces the image
|
||||
/// with the Default preset and leaves the Image Trace panel as it was. For the Image Trace
|
||||
/// object it made, the first button is Expand, which keeps only the traced paths.
|
||||
#[test]
|
||||
fn the_task_bar_traces_an_image_then_expands_the_trace() {
|
||||
let mut app = VectorcraftApp::new(Session::new(), Default::default());
|
||||
app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap();
|
||||
let img = image::RgbaImage::from_fn(20, 20, |x, _| image::Rgba(if x < 10 { [0, 0, 0, 255] } else { [255, 255, 255, 255] }));
|
||||
let mut png = vec![];
|
||||
img.write_to(&mut std::io::Cursor::new(&mut png), image::ImageFormat::Png).unwrap();
|
||||
let data = vectorcraft_format::base64_encode(&png);
|
||||
app.run("file.place", json!({"name": "half.png", "dataBase64": data, "link": false, "at": [200, 150]})).unwrap();
|
||||
app.select_tool("selection");
|
||||
let ctx = egui::Context::default();
|
||||
let click_first = |app: &mut VectorcraftApp| {
|
||||
frame(app, &ctx, vec![]);
|
||||
frame(app, &ctx, vec![]);
|
||||
let bar = task_bar_rect(&ctx).expect("the task bar shows");
|
||||
// Left to right: the frame's margin, the handle and its spacing, then the first button.
|
||||
let at = pos2(bar.left() + 8.0 + 4.0 + 6.0 + 12.0, bar.center().y);
|
||||
let button =
|
||||
|pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() };
|
||||
frame(app, &ctx, vec![egui::Event::PointerMoved(at)]);
|
||||
frame(app, &ctx, vec![button(true)]);
|
||||
frame(app, &ctx, vec![button(false)]);
|
||||
};
|
||||
let first = |app: &VectorcraftApp| {
|
||||
let st = app.session.active().unwrap();
|
||||
st.selection.objects.first().and_then(|id| st.doc.node(*id)).cloned().unwrap()
|
||||
};
|
||||
assert!(matches!(first(&app).kind, NodeKind::Image(_)));
|
||||
click_first(&mut app);
|
||||
assert_eq!(first(&app).name.as_deref(), Some("Image Trace"), "the image was traced");
|
||||
assert_eq!(app.ui.open_panel, None, "the Image Trace panel is left as it was");
|
||||
|
||||
click_first(&mut app);
|
||||
let expanded = first(&app);
|
||||
assert_ne!(expanded.name.as_deref(), Some("Image Trace"), "the trace was expanded");
|
||||
let mut images = 0;
|
||||
expanded.walk(&mut |n| images += usize::from(matches!(n.kind, NodeKind::Image(_))));
|
||||
assert_eq!(images, 0, "only the traced paths are kept");
|
||||
}
|
||||
|
||||
/// The Contextual Task Bar's handle drags it: unpinned it keeps that offset as it follows the
|
||||
/// selection, pinned it stays put when the selection changes, unpinned again it follows from
|
||||
/// there, and Reset Bar Position puts it back under the selection. It never leaves the canvas,
|
||||
|
||||
@@ -423,6 +423,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur
|
||||
- Numeric fields keep the pointer's pressed character as the text-drag start when movement exceeds the click threshold (#1326, in part); a completed focus click selects the unit too. Pixel/point, plain and optional fields are regression-tested in both directions, before/after focus, with replacement text; keyboard focus, double-click, arrows and wheel keep their prior behavior. Native egui input confirms partial replacement gives 17 px instead of 700 px, one Undo/Redo, window captures and an exact saved-document read in a separate CLI process. Short movements still counted as clicks can select all; that case remains open.
|
||||
- Unit fields and length strings take `%` in arithmetic: `3cm*50%` reads as 1.5 cm, and `50pt+25%` reads as 62.5 pt.
|
||||
- The contextual task bar's handle drags it anywhere on the canvas (kept whole on it as the window resizes); unpinned it keeps that offset as it follows the selection, and its More Options menu hides it, pins its position across selection changes (`window.taskBar.pin`), resets it under the selection (`window.taskBar.reset`) and shows the Properties panel. As in Illustrator the position isn't kept across launches. Not done: the bar's tool-specific variants (only the selection tools show it).
|
||||
- With one image selected, the contextual task bar offers Image Trace, which traces the image at once with the Default preset (the Image Trace panel is left as it was), and Crop Image; with an Image Trace object it offers Expand, which keeps only the traced paths. As in Illustrator ("Selecting images displays options like Image Trace, Retype, and Crop Image"; "You can also quickly expand traced artwork from the bar": [Contextual Task Bar announcement](https://community.adobe.com/announcements-651/contextual-task-bar-in-illustrator-2025-quick-actions-when-you-need-them-1546580)). Offset Path and Simplify, which don't apply to an image, aren't offered for one. Not done: Retype, which the app doesn't have.
|
||||
- Tool flyouts tear off: dragging or clicking the tear-off bar on a flyout's right side (or releasing the long press over it) floats its group as a strip of tool buttons, moved by the same bar and put back in the toolbar by the × at its top (`window.floatTools`). While a group floats, the presses that would open its flyout raise the strip instead. Floating strips are kept with the preferences and in user workspaces. Not done: turning a strip vertical, docking it.
|
||||
- Panels undock (#495): a dock tab, a panel icon or a popped-out panel's title dragged out of the dock floats that panel inside the window, and the strip right of the dock's tabs floats the whole tabbed group. A floating group moves by its title bar, a tab dragged out of it floats on its own, and dropped on another group's title bar or tabs it stacks with that group; dropped on the dock, which lights up under the pointer, its panels go back where they live (the tabbed group or the icon column), as its × and the panel menus' Float Panel / Dock Panel do (`window.panel.float`, `window.panel.dock`). The Tools panel floats by its title bar and docks again on the window's left edge. Floating groups stack into sets: dropped on the upper or lower half of another group (a line shows where), a group joins it right above or below it, and the set's groups stack top to bottom, each showing one panel, moving together by the top group's title bar, whose × docks them all; a group dragged by its tab strip leaves the set (`window.panel.float {below, above}`). A double-click on a tab collapses a floating group to its tabs (`collapsed`). Sets dropped on the left edge of the icon column or of another dock column dock as dock columns (a multi-column dock, `window.panel.dock {column}`), whose groups stack, tab and tear out as a floating set's do, and whose top strip floats the column again. Floating panels, their sets and the Tools panel's position are kept with the preferences and in user workspaces, and clamped into a smaller window. Not done: panels in separate OS windows, docking a panel into the default tabbed group it doesn't belong to, reordering tabs by dragging, collapsing a set or dock column to icons, dock columns right of the default dock, resizing floating panels and the groups of a set.
|
||||
- The dock's double arrow collapses Properties, Layers and Libraries to icons at the top of the icon column, popping out like the other icon panels, and expands them again (`window.collapseDock`, kept with the preferences and in saved workspaces). The icon column's own arrow (expanding the icon panels into a full column) is not done.
|
||||
|
||||
Reference in New Issue
Block a user