fix(ui): address review notes on the shape Fill/Stroke popover

- The Stroke Color Picker on a shape whose stroke is off (none or 0 px)
  previews the stroke its OK turns on, at the enabled width, even for the
  colour it opened on (CodeRabbit).
- No Color's diagonal uses the theme's danger token, not a fixed red.
- docs/ui-parity.md: the history entry notes Recently Used Colors is a new
  preference, so 12 of 182 do nothing (matching the row).
- The Fill/Stroke swatches clip their checker to the rounded corners, so
  dark themes no longer show white specks there (#8741).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TYsCL5E2GM6hUabP9dmYAv
This commit is contained in:
Claude committed 2026-10-28 08:23:00 +00:00
1 parent 1920ca66b8
commit 1505c42605
5 files changed
+35 -6

No files matched your search

+11 -3
View File
@@ -208,7 +208,10 @@ pub(crate) fn display_doc(app: &mut PhotocraftApp, idx: usize) -> Option<(Arc<Do
app.shape_fill_preview = None;
return None;
};
if d.fields.get("color") == d.fields.get("__orig") {
// OK on the opening colour changes a paint that isn't solid yet (No Color, a gradient), so
// that one previews too.
let solid = d.fields.get("__shapePaintSolid").and_then(Value::as_bool).unwrap_or(true);
if solid && d.fields.get("color") == d.fields.get("__orig") {
return None;
}
let layer = target(app, &d.fields, idx).ok()??;
@@ -228,8 +231,13 @@ pub(crate) fn display_doc(app: &mut PhotocraftApp, idx: usize) -> Option<(Arc<Do
if let LayerContent::Shape(sh) = &mut doc.layer_mut(layer)?.content {
match which {
Paint::Fill => sh.fill = Some(Fill::Solid(color)),
// A stroke that is off (No Color) shows on OK, at its enabled width.
Paint::Stroke => sh.stroke.as_mut().filter(|s| s.width > 0.0)?.paint = Fill::Solid(color),
// A stroke that is off (none, or 0 px) previews as OK makes it: on, at its enabled
// width, with `shape.edit`'s default style when the shape has none.
Paint::Stroke => {
let stroke = sh.stroke.get_or_insert_with(Default::default);
stroke.width = crate::vector_ui::enabled_stroke_width(stroke.width);
stroke.paint = Fill::Solid(color);
}
}
photocraft_engine::vector_cmds::refresh_shape(&st.doc, sh);
}
@@ -118,6 +118,26 @@ fn stroke_picker_previews_commits_and_rejects_stale_or_bad_targets() {
assert!(display_doc(&mut app, 0).is_none());
}
/// A shape without a stroke, or with a 0 px one, previews the stroke the picker's OK turns on:
/// the picked colour at the enabled width, even for the colour it opened on.
#[test]
fn stroke_picker_previews_a_stroke_that_is_off_as_ok_turns_it_on() {
for stroke in [json!(null), json!({"width": 0, "color": "#ff0000"})] {
let mut app = PhotocraftApp::new(photocraft_engine::Session::new(), crate::Services::default());
app.run("file.new", json!({"width": 64, "height": 48})).unwrap();
let id = LayerId(app.run("shape.create", json!({"rect": [8, 8, 40, 30], "fill": "#336699", "stroke": stroke})).unwrap()["layer"].as_u64().unwrap());
let dialog = open_paint(&mut app, Paint::Stroke, Some(id), false, [0.0, 0.0, 0.0, 1.0]).unwrap();
let (shown, _) = display_doc(&mut app, 0).expect("the unchanged colour still previews the stroke OK adds");
let LayerContent::Shape(sh) = &shown.layer(id).unwrap().content else { panic!() };
let previewed = sh.stroke.clone().expect("a previewed stroke");
crate::dialogs::confirm(&mut app, dialog).unwrap();
let LayerContent::Shape(sh) = &app.session.active().unwrap().doc.layer(id).unwrap().content else { panic!() };
let committed = sh.stroke.clone().expect("OK turned the stroke on");
assert_eq!((previewed.paint, previewed.width), (committed.paint, committed.width), "{stroke}: the preview is what OK commits");
assert_eq!(committed.width, 3.0);
}
}
/// From the options bar without a selected shape, OK sets the next shape's colour and turns the
/// paint on, and edits no document.
#[test]
+1 -1
View File
@@ -148,7 +148,7 @@ fn draw_no_color(ui: &egui::Ui, r: Rect, t: &Tokens) {
let p = ui.painter();
p.rect_filled(r, 0.0, Color32::WHITE);
p.rect_stroke(r, 0.0, Stroke::new(1.0, t.field_border), StrokeKind::Inside);
p.line_segment([r.left_bottom() + vec2(1.5, -1.5), r.right_top() + vec2(-1.5, 1.5)], Stroke::new(1.5, Color32::from_rgb(220, 40, 40)));
p.line_segment([r.left_bottom() + vec2(1.5, -1.5), r.right_top() + vec2(-1.5, 1.5)], Stroke::new(1.5, t.danger));
}
fn draw_gradient(ui: &egui::Ui, r: Rect, t: &Tokens) {
+2 -1
View File
@@ -77,7 +77,8 @@ pub(crate) fn swatch(
let (r, resp) = ui.allocate_exact_size(vec2(30.0, 22.0), Sense::click());
resp.widget_info(|| egui::WidgetInfo::labeled(egui::Role::ColorWell, ui.is_enabled(), tip));
let current = fill.and_then(color_of);
crate::widgets::checker(ui.painter(), r.shrink(1.0), 4.0);
// Clipped to the rounded corners: square checker cells showed white past them (#8741, #3208).
crate::widgets::checker_rounded(ui.painter(), r.shrink(1.0), 4.0, t.radius_sm);
match (current, fill) {
(Some(c), _) => {
ui.painter().rect_filled(r.shrink(1.0), t.radius_sm, rgba32(c));
+1 -1
View File
@@ -254,7 +254,7 @@ Paragraph; the rest open as in-app floating windows.
| Date | Change | Summary |
|---|---|---|
| 2026-10-27 | minor | Shape Fill/Stroke popover as Photoshop's (paint types, Color Picker button, Recently Used Colors, swatch folders, with the Gradient tab) and every colour swatch opens the full Color Picker; rows added (partial, done); no percentage changed |
| 2026-10-27 | minor | Shape Fill/Stroke popover as Photoshop's (paint types, Color Picker button, Recently Used Colors, swatch folders, with the Gradient tab) and every colour swatch opens the full Color Picker; rows added (partial, done); Recently Used Colors is a new preference, so 12 of 182 do nothing; no percentage changed |
| 2026-10-27 | minor | Every preference takes effect: 12 of 181 do nothing, the scorecard's count with integration batch 9 (#9775) on main; new layers go above a group whose lock would refuse them (#4329) |
| 2026-10-25 | minor | Every preference takes effect: 12 of 178 do nothing, the scorecard's count (it still said 46 of 149) |
| 2026-10-25 | minor | Art History Brush is in the History group of the Y flyout (partial, TOOL-213-20, #1610) |