From 93bbe36bc95add61fb56fef14266db8d32d673a1 Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Wed, 28 Oct 2026 03:35:21 -0700 Subject: [PATCH 1/2] Contextual Task Bar: a Stroke chip beside Fill picks the stroke color The task bar's Fill chip had no Stroke counterpart. A Stroke chip now sits beside it, drawn as a ring with the selected object's stroke paint, as the Control bar's Stroke chip is. A click on it brings the Stroke proxy forward and opens the Swatches panel, as a click on the Fill chip does for the Fill proxy. The chip is left out with the Artboard tool, as Fill is. The bar's width estimate, which centers the bar under the selection, grows by the chip's width. The drag test's first square moves 20 pt right so its bar still starts inside the canvas. --- crates/ui-egui/src/canvas/task_bar.rs | 14 ++++++-- crates/ui-egui/src/canvas/tests.rs | 52 ++++++++++++++++++++++++++- docs/target-app-parity.md | 1 + 3 files changed, 64 insertions(+), 3 deletions(-) diff --git a/crates/ui-egui/src/canvas/task_bar.rs b/crates/ui-egui/src/canvas/task_bar.rs index a81d8307f..d492ece08 100644 --- a/crates/ui-egui/src/canvas/task_bar.rs +++ b/crates/ui-egui/src/canvas/task_bar.rs @@ -142,13 +142,14 @@ pub(super) fn task_bar(app: &mut VectorcraftApp, ui: &mut Ui, xf: &Xf) { items.push((tl!("Duplicate"), "copy", "edit.duplicate", none())); } let fill = first.as_ref().map(|f| f.appearance.fill_paint()).unwrap_or_default(); + let stroke = first.as_ref().map(|f| f.appearance.stroke_paint()).unwrap_or_default(); let colored = has_colors(&st.doc, &st.selection.objects); let doc = st.uid; // Under the selection's lowest corner on screen, centered on the selection (the view may be // rotated). let lowest = xf.quad(b).iter().map(|c| c.y).fold(f32::NEG_INFINITY, f32::max); let anchor = pos2(xf.to_screen(b.center()).x, lowest); - let est_w = 118.0 + items.iter().map(|(l, _, _, _)| l.len() as f32 * 7.2 + 44.0).sum::(); + let est_w = 150.0 + items.iter().map(|(l, _, _, _)| l.len() as f32 * 7.2 + 44.0).sum::(); // Its place under the selection, then where the handle moved it, kept on the canvas (with the // bar's size last frame, the estimate before it first shows). let under = pos2(anchor.x - est_w / 2.0, anchor.y + 28.0); @@ -207,7 +208,7 @@ pub(super) fn task_bar(app: &mut VectorcraftApp, ui: &mut Ui, xf: &Xf) { run = Some((*cmd, params.clone())); } } - // The selected objects' Fill, Recolor Artwork and Lock, left out with the Artboard tool. + // The selected objects' Fill, Stroke, Recolor Artwork and Lock, left out with the Artboard tool. if !on_artboards { let (r, resp) = ui.allocate_exact_size(vec2(26.0, 30.0), Sense::click()); widgets::paint_chip(ui, egui::Rect::from_center_size(r.center(), vec2(16.0, 16.0)), &fill); @@ -221,6 +222,15 @@ pub(super) fn task_bar(app: &mut VectorcraftApp, ui: &mut Ui, xf: &Xf) { app.session.fill_active = true; app.ui.open_panel = Some("swatches".into()); } + // The Stroke chip, drawn as a ring as the Control bar's is. + let (r, resp) = ui.allocate_exact_size(vec2(26.0, 30.0), Sense::click()); + let chip = egui::Rect::from_center_size(r.center(), vec2(16.0, 16.0)); + widgets::proxy_chip(ui, chip, &stroke, false, Some(4.0)); + ui.painter().rect_stroke(chip, 0.0, Stroke::new(1.0, t.button_border), StrokeKind::Outside); + if resp.on_hover_text(tl!("Stroke")).clicked() { + app.session.fill_active = false; + app.ui.open_panel = Some("swatches".into()); + } // Recolor Artwork, when the selection has colours to recolour. if colored && widgets::icon_button(ui, "palette", tl!("Recolor Artwork"), false, 30.0).clicked() { run = Some(("ui.recolorDialog", json!({}))); diff --git a/crates/ui-egui/src/canvas/tests.rs b/crates/ui-egui/src/canvas/tests.rs index bb76684dc..d3a0e4d1b 100644 --- a/crates/ui-egui/src/canvas/tests.rs +++ b/crates/ui-egui/src/canvas/tests.rs @@ -6067,6 +6067,56 @@ fn a_pen_tap_within_one_frame_clicks_where_it_tapped() { assert_eq!(doc.doc.art_bounds(), before, "the tap moved nothing"); } +/// The Contextual Task Bar shows a Stroke chip beside the Fill chip, drawn with the selection's +/// stroke paint. A click on it brings the Stroke proxy forward and opens Swatches, as a click on +/// the Fill chip does for the Fill proxy. +#[test] +fn the_task_bar_shows_a_stroke_chip_beside_fill() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); + let id = app.session.execute("shape.rectangle", &json!({"x": 180, "y": 130, "width": 40, "height": 40})).unwrap()["id"].clone(); + app.session.execute("paint.setFill", &json!({"ids": [id], "color": "#cc3322"})).unwrap(); + app.session.execute("paint.setStroke", &json!({"ids": [id], "color": "#2244cc"})).unwrap(); + app.select_tool("selection"); + let ctx = egui::Context::default(); + // Past the bar's fade-in, so its chips draw at full opacity. + for _ in 0..30 { + frame(&mut app, &ctx, vec![]); + } + let raw = egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(Pos2::ZERO, vec2(800.0, 600.0))), ..Default::default() }; + let mut out = ctx.run_ui(raw, |ui| show(&mut app, ui)); + out.textures_delta.clear(); + let bar = task_bar_rect(&ctx).expect("the task bar shows"); + // Right to left: the frame's margin, More Options, Lock, Recolor, then Stroke and Fill. + let stroke_x = bar.right() - 8.0 - 3.0 * (30.0 + 6.0) - 13.0; + let fill_x = stroke_x - 26.0 - 6.0; + let blue = Color32::from_rgb(0x22, 0x44, 0xcc); + let chip_with = |x: f32| { + out.shapes.iter().any(|c| match &c.shape { + egui::Shape::Rect(r) => r.fill == blue && bar.contains(r.rect.center()) && (r.rect.center().x - x).abs() < 4.0, + _ => false, + }) + }; + assert!(chip_with(stroke_x), "the Stroke chip is drawn with the stroke color"); + assert!(!chip_with(fill_x), "the Fill chip is not"); + + let click = |app: &mut VectorcraftApp, x: f32| { + let at = pos2(x, 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)]); + }; + app.session.fill_active = true; + click(&mut app, stroke_x); + assert!(!app.session.fill_active, "the Stroke proxy is in front"); + assert_eq!(app.ui.open_panel.as_deref(), Some("swatches")); + app.ui.open_panel = None; + click(&mut app, fill_x); + assert!(app.session.fill_active, "the Fill proxy is in front"); + assert_eq!(app.ui.open_panel.as_deref(), Some("swatches")); +} + /// The Contextual Task Bar shows Recolor Artwork (a palette) when the selection has colours, /// and it opens the Recolor Artwork dialog; art painted with None has no button. #[test] @@ -6328,7 +6378,7 @@ fn the_task_bar_is_dragged_by_its_handle_pinned_and_reset() { app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); let mut square = |x: f64, y: f64| app.session.execute("shape.rectangle", &json!({"x": x, "y": y, "width": 40, "height": 40})).unwrap()["id"].clone(); - let (a, b) = (square(120.0, 40.0), square(160.0, 150.0)); + let (a, b) = (square(140.0, 40.0), square(160.0, 150.0)); app.select_tool("selection"); let ctx = egui::Context::default(); let bar = || task_bar_rect(&ctx).expect("the task bar shows"); diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index ab2507744..2469d7967 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -560,6 +560,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - 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. This follows Illustrator's bar: for a selected image it includes Image Trace, Retype and Crop Image, and traced artwork can be expanded from it ([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. - With the Artboard tool, the contextual task bar sits under the selected artboards and offers Resize (Artboard Options for the active artboard), Duplicate (each selected artboard, in one undo step, the copies then selected), Rearrange All (with two or more artboards) and Export (Export for Screens with the selected artboards checked). This follows Illustrator's bar: while the Artboard tool is in use, it has options for resizing, duplicating and exporting artboards (the [Contextual Task Bar announcement](https://community.adobe.com/questions-652/contextual-task-bar-in-illustrator-2025-quick-actions-when-you-need-them-1546580); Rearrange and Export on the bar: [Managing Artboards in Illustrator](https://creativepro.com/managing-artboards-in-illustrator/)). - The contextual task bar offers more next steps by selection (): an open path Join and Edit Path (the Direct Selection tool), a closed path Edit Path, a compound path Edit Path and Release Compound Path, a clipping group Edit Clipping Path and Release Clipping Mask instead of Ungroup and Isolate, an image selected with vector art Make Clipping Mask instead of Unite, and point or area type Convert To Area Type or Convert To Point Type beside Create Outlines. Evidence: `crates/ui-egui/src/tests/taskbar.rs`. + - The contextual task bar has a Stroke chip beside the Fill chip, drawn with the selected object's stroke paint; a click brings the Stroke proxy forward and opens the Swatches panel, as a click on the Fill chip does for the Fill proxy ("Fill and Stroke to change the colors": [Contextual Task Bar overview](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/contextual-task-bar-overview.html)). Evidence: `the_task_bar_shows_a_stroke_chip_beside_fill` in `crates/ui-egui/src/canvas/tests.rs`. - With anchor points selected with Direct Selection, the contextual task bar offers Remove Anchor Points, Connect (`path.join`), Cut Path, Corner and Smooth (`path.convertAnchors` with `to` set to `corner` or `smooth`), the commands of the Control bar's anchor buttons. As in Illustrator ("Remove selected anchor points, Connect selected end points, Cut path at selected anchor points, Convert selected anchor points to corner, and Convert selected anchor points to smooth": [Contextual Task Bar overview](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/contextual-task-bar-overview.html)). Not done: Simplify, which the bar offers only while no anchors are selected, and the bar's Smooth item that adjusts a path's curvature, a command the app doesn't have (Object › Path › Smooth). - 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. - Screen readers get a name for the Tools panel's tool buttons, flyout rows, floating strips and tear-off bars, and for the icon buttons, text buttons, toggles, checkboxes, radio buttons, tabs, chips, sliders, angle dials and combo boxes that panels and dialogs draw with `crates/ui-egui/src/widgets.rs`: the text, label or tool tip each one shows, or a name of its own where it shows none (the angle dial, the spinner, the preset arrow, the color wheel and the Reference Point's squares). The tool buttons, toggles, checkboxes, radio buttons, tabs and the Reference Point's nine squares also report whether they are on, an icon button that is lit, such as the chosen Align To button, reports that it is pressed, and the angle dial reports its angle. The status bar's First, Previous, Next and Last Artboard buttons have tool tips. From e251bbf159a131fe4f4e5acc848a9e9bb55c249b Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Wed, 28 Oct 2026 08:01:20 -0700 Subject: [PATCH 2/2] Contextual Task Bar: the Fill and Stroke chips show the proxies' paints The chips drew the first selected object's fill and stroke. They now show the paints the Fill and Stroke proxies show, a group's painted art included, and a "?" when the selected objects' paints differ. A click on either chip shows the Swatches panel through `window.panel.activate`, so it also appears when the panel is floating, closed or in an expanded dock column; an open flyout stays open. The progress doc describes the reference app's bar in its own words. --- crates/ui-egui/src/canvas/task_bar.rs | 30 ++++++++----- crates/ui-egui/src/canvas/tests.rs | 65 +++++++++++++++++++++++++++ docs/target-app-parity.md | 4 +- 3 files changed, 85 insertions(+), 14 deletions(-) diff --git a/crates/ui-egui/src/canvas/task_bar.rs b/crates/ui-egui/src/canvas/task_bar.rs index d492ece08..19fe62199 100644 --- a/crates/ui-egui/src/canvas/task_bar.rs +++ b/crates/ui-egui/src/canvas/task_bar.rs @@ -141,8 +141,9 @@ pub(super) fn task_bar(app: &mut VectorcraftApp, ui: &mut Ui, xf: &Xf) { } items.push((tl!("Duplicate"), "copy", "edit.duplicate", none())); } - let fill = first.as_ref().map(|f| f.appearance.fill_paint()).unwrap_or_default(); - let stroke = first.as_ref().map(|f| f.appearance.stroke_paint()).unwrap_or_default(); + // The paints the Fill and Stroke proxies show, "?" when the selected objects' paints differ. + let (fill, stroke) = crate::panels::current_paints(app); + let (fill_mixed, stroke_mixed) = crate::panels::mixed_paints(app, ui.ctx()); let colored = has_colors(&st.doc, &st.selection.objects); let doc = st.uid; // Under the selection's lowest corner on screen, centered on the selection (the view may be @@ -176,6 +177,7 @@ pub(super) fn task_bar(app: &mut VectorcraftApp, ui: &mut Ui, xf: &Xf) { let pinned = place.pinned; let mut moved = Vec2::ZERO; let mut run: Option<(&str, serde_json::Value)> = None; + let mut show_swatches = false; egui::Area::new(id).order(egui::Order::Middle).fixed_pos(pos).show(ui.ctx(), |ui| { egui::Frame::NONE .fill(t.panel) @@ -211,25 +213,21 @@ pub(super) fn task_bar(app: &mut VectorcraftApp, ui: &mut Ui, xf: &Xf) { // The selected objects' Fill, Stroke, Recolor Artwork and Lock, left out with the Artboard tool. if !on_artboards { let (r, resp) = ui.allocate_exact_size(vec2(26.0, 30.0), Sense::click()); - widgets::paint_chip(ui, egui::Rect::from_center_size(r.center(), vec2(16.0, 16.0)), &fill); - ui.painter().rect_stroke( - egui::Rect::from_center_size(r.center(), vec2(16.0, 16.0)), - 0.0, - Stroke::new(1.0, t.button_border), - StrokeKind::Outside, - ); + let chip = egui::Rect::from_center_size(r.center(), vec2(16.0, 16.0)); + widgets::proxy_chip(ui, chip, &fill, fill_mixed, None); + ui.painter().rect_stroke(chip, 0.0, Stroke::new(1.0, t.button_border), StrokeKind::Outside); if resp.on_hover_text(tl!("Fill")).clicked() { app.session.fill_active = true; - app.ui.open_panel = Some("swatches".into()); + show_swatches = true; } // The Stroke chip, drawn as a ring as the Control bar's is. let (r, resp) = ui.allocate_exact_size(vec2(26.0, 30.0), Sense::click()); let chip = egui::Rect::from_center_size(r.center(), vec2(16.0, 16.0)); - widgets::proxy_chip(ui, chip, &stroke, false, Some(4.0)); + widgets::proxy_chip(ui, chip, &stroke, stroke_mixed, Some(4.0)); ui.painter().rect_stroke(chip, 0.0, Stroke::new(1.0, t.button_border), StrokeKind::Outside); if resp.on_hover_text(tl!("Stroke")).clicked() { app.session.fill_active = false; - app.ui.open_panel = Some("swatches".into()); + show_swatches = true; } // Recolor Artwork, when the selection has colours to recolour. if colored && widgets::icon_button(ui, "palette", tl!("Recolor Artwork"), false, 30.0).clicked() { @@ -272,6 +270,14 @@ pub(super) fn task_bar(app: &mut VectorcraftApp, ui: &mut Ui, xf: &Xf) { } ui.ctx().request_repaint(); } + // The Swatches panel shows where it is docked, floating or closed. A panel in an icon column + // toggles when activated, so an open flyout stays as it is. + if show_swatches + && app.ui.open_panel.as_deref() != Some("swatches") + && let Err(e) = app.run("window.panel.activate", json!({ "panel": "swatches" })) + { + app.ui.status = e; + } 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. diff --git a/crates/ui-egui/src/canvas/tests.rs b/crates/ui-egui/src/canvas/tests.rs index d3a0e4d1b..07ee447a1 100644 --- a/crates/ui-egui/src/canvas/tests.rs +++ b/crates/ui-egui/src/canvas/tests.rs @@ -6117,6 +6117,71 @@ fn the_task_bar_shows_a_stroke_chip_beside_fill() { assert_eq!(app.ui.open_panel.as_deref(), Some("swatches")); } +/// The Contextual Task Bar's Fill and Stroke chips show the paints the proxies show: a group's +/// painted art, and "?" when the selected objects' paints differ. A click on a chip shows the +/// Swatches panel wherever it is, closed included. +#[test] +fn the_task_bar_chips_follow_the_proxies_and_show_swatches_anywhere() { + let mut app = VectorcraftApp::new(Session::new(), Default::default()); + app.session.execute("file.new", &json!({"width": 400, "height": 300})).unwrap(); + let mut square = |x: f64, stroke: &str| { + let id = app.session.execute("shape.rectangle", &json!({"x": x, "y": 130, "width": 40, "height": 40})).unwrap()["id"].clone(); + app.session.execute("paint.setFill", &json!({"ids": [id], "color": "#cc3322"})).unwrap(); + app.session.execute("paint.setStroke", &json!({"ids": [id], "color": stroke})).unwrap(); + id + }; + square(160.0, "#2244cc"); + let second = square(220.0, "#22cc44"); + app.select_tool("selection"); + app.session.execute("select.all", &json!({})).unwrap(); + let ctx = egui::Context::default(); + let shapes = |app: &mut VectorcraftApp| { + for _ in 0..30 { + frame(app, &ctx, vec![]); + } + let raw = egui::RawInput { screen_rect: Some(egui::Rect::from_min_size(Pos2::ZERO, vec2(800.0, 600.0))), ..Default::default() }; + let mut out = ctx.run_ui(raw, |ui| show(app, ui)); + out.textures_delta.clear(); + out.shapes + }; + // Right to left: the frame's margin, More Options, Lock, Recolor, then Stroke and Fill. + let stroke_x = |bar: egui::Rect| bar.right() - 8.0 - 3.0 * (30.0 + 6.0) - 13.0; + let chip_with = |shapes: &[egui::epaint::ClippedShape], bar: egui::Rect, x: f32, color: Color32| { + shapes.iter().any(|c| match &c.shape { + egui::Shape::Rect(r) => r.fill == color && bar.contains(r.rect.center()) && (r.rect.center().x - x).abs() < 4.0, + _ => false, + }) + }; + let (blue, green) = (Color32::from_rgb(0x22, 0x44, 0xcc), Color32::from_rgb(0x22, 0xcc, 0x44)); + + // Two strokes that differ: "?" on white, as the proxy shows them. + let out = shapes(&mut app); + let bar = task_bar_rect(&ctx).expect("the task bar shows"); + assert!(chip_with(&out, bar, stroke_x(bar), Color32::WHITE), "the Stroke chip shows the strokes differ"); + assert!(!chip_with(&out, bar, stroke_x(bar), blue), "the Stroke chip does not show the first object's stroke"); + + // A group shows its painted art's stroke, as the proxy does. + app.session.execute("paint.setStroke", &json!({"ids": [second], "color": "#2244cc"})).unwrap(); + app.session.execute("select.all", &json!({})).unwrap(); + app.session.execute("object.group", &json!({})).unwrap(); + let out = shapes(&mut app); + let bar = task_bar_rect(&ctx).expect("the task bar shows"); + assert!(chip_with(&out, bar, stroke_x(bar), blue), "the Stroke chip shows the group's art's stroke"); + assert!(!chip_with(&out, bar, stroke_x(bar), green)); + + // Swatches closed: a click on the Stroke chip shows it. + app.run("window.panel.close", json!({"panel": "swatches"})).unwrap(); + assert!(!crate::panel_docking::showing(&app.ui, "swatches")); + let at = pos2(stroke_x(bar), bar.center().y); + let button = |pressed| egui::Event::PointerButton { pos: at, button: egui::PointerButton::Primary, pressed, modifiers: Default::default() }; + frame(&mut app, &ctx, vec![egui::Event::PointerMoved(at)]); + frame(&mut app, &ctx, vec![button(true)]); + frame(&mut app, &ctx, vec![button(false)]); + frame(&mut app, &ctx, vec![]); + assert!(!app.session.fill_active, "the Stroke proxy is in front"); + assert!(crate::panel_docking::showing(&app.ui, "swatches"), "the Swatches panel shows"); +} + /// The Contextual Task Bar shows Recolor Artwork (a palette) when the selection has colours, /// and it opens the Recolor Artwork dialog; art painted with None has no button. #[test] diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 2469d7967..c8a4a634d 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -560,8 +560,8 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur - 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. This follows Illustrator's bar: for a selected image it includes Image Trace, Retype and Crop Image, and traced artwork can be expanded from it ([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. - With the Artboard tool, the contextual task bar sits under the selected artboards and offers Resize (Artboard Options for the active artboard), Duplicate (each selected artboard, in one undo step, the copies then selected), Rearrange All (with two or more artboards) and Export (Export for Screens with the selected artboards checked). This follows Illustrator's bar: while the Artboard tool is in use, it has options for resizing, duplicating and exporting artboards (the [Contextual Task Bar announcement](https://community.adobe.com/questions-652/contextual-task-bar-in-illustrator-2025-quick-actions-when-you-need-them-1546580); Rearrange and Export on the bar: [Managing Artboards in Illustrator](https://creativepro.com/managing-artboards-in-illustrator/)). - The contextual task bar offers more next steps by selection (): an open path Join and Edit Path (the Direct Selection tool), a closed path Edit Path, a compound path Edit Path and Release Compound Path, a clipping group Edit Clipping Path and Release Clipping Mask instead of Ungroup and Isolate, an image selected with vector art Make Clipping Mask instead of Unite, and point or area type Convert To Area Type or Convert To Point Type beside Create Outlines. Evidence: `crates/ui-egui/src/tests/taskbar.rs`. - - The contextual task bar has a Stroke chip beside the Fill chip, drawn with the selected object's stroke paint; a click brings the Stroke proxy forward and opens the Swatches panel, as a click on the Fill chip does for the Fill proxy ("Fill and Stroke to change the colors": [Contextual Task Bar overview](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/contextual-task-bar-overview.html)). Evidence: `the_task_bar_shows_a_stroke_chip_beside_fill` in `crates/ui-egui/src/canvas/tests.rs`. - - With anchor points selected with Direct Selection, the contextual task bar offers Remove Anchor Points, Connect (`path.join`), Cut Path, Corner and Smooth (`path.convertAnchors` with `to` set to `corner` or `smooth`), the commands of the Control bar's anchor buttons. As in Illustrator ("Remove selected anchor points, Connect selected end points, Cut path at selected anchor points, Convert selected anchor points to corner, and Convert selected anchor points to smooth": [Contextual Task Bar overview](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/contextual-task-bar-overview.html)). Not done: Simplify, which the bar offers only while no anchors are selected, and the bar's Smooth item that adjusts a path's curvature, a command the app doesn't have (Object › Path › Smooth). + - The contextual task bar has a Stroke chip beside the Fill chip, drawn with the paint the Stroke proxy shows (a "?" when the selected objects' strokes differ, as on the Fill chip); a click brings the Stroke proxy forward and shows the Swatches panel wherever it is docked, floating or closed, as a click on the Fill chip does for the Fill proxy. The reference app's bar has Fill and Stroke controls for changing an object's colors ([Contextual Task Bar overview](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/contextual-task-bar-overview.html)). Evidence: `the_task_bar_shows_a_stroke_chip_beside_fill` and `the_task_bar_chips_follow_the_proxies_and_show_swatches_anywhere` in `crates/ui-egui/src/canvas/tests.rs`. + - With anchor points selected with Direct Selection, the contextual task bar offers Remove Anchor Points, Connect (`path.join`), Cut Path, Corner and Smooth (`path.convertAnchors` with `to` set to `corner` or `smooth`), the commands of the Control bar's anchor buttons. As in Illustrator, whose bar has items that delete the selected anchors, join selected end points, split the path at the selected anchors, and make the selected anchors corner or smooth points ([Contextual Task Bar overview](https://helpx.adobe.com/illustrator/desktop/get-started/learn-the-basics/contextual-task-bar-overview.html)). Not done: Simplify, which the bar offers only while no anchors are selected, and the bar's Smooth item that adjusts a path's curvature, a command the app doesn't have (Object › Path › Smooth). - 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. - Screen readers get a name for the Tools panel's tool buttons, flyout rows, floating strips and tear-off bars, and for the icon buttons, text buttons, toggles, checkboxes, radio buttons, tabs, chips, sliders, angle dials and combo boxes that panels and dialogs draw with `crates/ui-egui/src/widgets.rs`: the text, label or tool tip each one shows, or a name of its own where it shows none (the angle dial, the spinner, the preset arrow, the color wheel and the Reference Point's squares). The tool buttons, toggles, checkboxes, radio buttons, tabs and the Reference Point's nine squares also report whether they are on, an icon button that is lit, such as the chosen Align To button, reports that it is pressed, and the angle dial reports its angle. The status bar's First, Previous, Next and Last Artboard buttons have tool tips. - These dialog sliders, each beside a number field, respond to the keyboard: those of the effect, tool option, brush and transform dialogs, the Perspective Grid, the 3D dialogs, and the flattener options, which the Flattener Preview panel also shows. A click, or Tab, gives a slider the keyboard focus, and an outline shows it. Then → and ↑ step the field's number up to the next whole number and ← and ↓ step it down to the next whole number, Shift with an arrow and Page Up and Page Down step it by ten, and Home and End set the slider's first and last value; no step goes past either end. Screen readers' increment and decrement actions step the slider as → and ← do. The Color panel's, Swatch Options', the Navigator's and the Character panel's sliders, and the Color Picker's channel slider, respond only to the pointer. The keys follow the WAI-ARIA slider pattern (); the reference app's help does not describe keys on a dialog's slider, so the step sizes are VectorCraft's own.