From 5634b5155ac1210a4f08f3e1ecfff6492adf282d Mon Sep 17 00:00:00 2001 From: Korg1ch <57859048+Korg1ch@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:50:02 +0300 Subject: [PATCH 1/2] fix(ui-egui): Slice Select gets its own icon, the Slice knife with a pointer (#7859) --- ATTRIBUTION.md | 3 ++- assets/icons/slice-select.svg | 16 ++++++++++++++++ crates/ui-egui/src/icon_data.rs | 1 + crates/ui-egui/src/icons.rs | 2 +- 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 assets/icons/slice-select.svg diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index cbbbe069a5..1f9a5c8784 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -27,9 +27,10 @@ next to them. | `crates/ui-egui/src/i18n/sr.tsv` | Serbian (Latin, Ekavian) UI translations | PhotoCraft contributors | Original translations of PhotoCraft's English labels | MIT OR Apache-2.0, [`LICENSE-translations.txt`](crates/ui-egui/src/i18n/LICENSE-translations.txt) | | `assets/fonts/Inter-Regular.ttf`, `Inter-Medium.ttf`, `Inter-SemiBold.ttf` | Inter 4.001 (UI font) | The Inter Project Authors (Rasmus Andersson) | | SIL OFL 1.1, [`assets/fonts/OFL-Inter.txt`](assets/fonts/OFL-Inter.txt) | | `assets/fonts/JetBrainsMono-Regular.ttf` | JetBrains Mono 2.305 (numeric font) | The JetBrains Mono Project Authors | | SIL OFL 1.1, [`assets/fonts/OFL-JetBrainsMono.txt`](assets/fonts/OFL-JetBrainsMono.txt) | -| `assets/icons/*.svg` except `slice-knife.svg`, `sampler.svg`, `artboard.svg`, `perspective-crop.svg`, `frame-placeholder.svg`, `ellipse-dashed.svg`, `gradient.svg`, `type-mask.svg`, `type-mask-vertical.svg`, `clip-below.svg`, `clip-release.svg`, `layer-mask.svg`, `adjustment-layer.svg`, `brush-replace.svg`, `quick-selection.svg`, `eraser-background.svg`, `eraser-magic.svg`, `lasso-magnetic.svg`, `direct-select.svg`, `convert-point.svg`, `pen-add-anchor.svg`, `pen-delete-anchor.svg`, `pen-freeform.svg`, `pen-curvature.svg`, `bandage-sparkle.svg`, `painting-symmetry.svg`, `symmetry-*.svg` and `marquee-{row,column}.svg` (113 files; `star-filled.svg` is `star.svg` with a solid fill) | Lucide icons | Lucide Icons and Contributors | (`icons/.svg`; `align-*` are now named `text-align-*` upstream) | ISC, [`assets/icons/LICENSE-lucide.txt`](assets/icons/LICENSE-lucide.txt) | +| `assets/icons/*.svg` except `slice-knife.svg`, `slice-select.svg`, `sampler.svg`, `artboard.svg`, `perspective-crop.svg`, `frame-placeholder.svg`, `ellipse-dashed.svg`, `gradient.svg`, `type-mask.svg`, `type-mask-vertical.svg`, `clip-below.svg`, `clip-release.svg`, `layer-mask.svg`, `adjustment-layer.svg`, `brush-replace.svg`, `quick-selection.svg`, `eraser-background.svg`, `eraser-magic.svg`, `lasso-magnetic.svg`, `direct-select.svg`, `convert-point.svg`, `pen-add-anchor.svg`, `pen-delete-anchor.svg`, `pen-freeform.svg`, `pen-curvature.svg`, `bandage-sparkle.svg`, `painting-symmetry.svg`, `symmetry-*.svg` and `marquee-{row,column}.svg` (113 files; `star-filled.svg` is `star.svg` with a solid fill) | Lucide icons | Lucide Icons and Contributors | (`icons/.svg`; `align-*` are now named `text-align-*` upstream) | ISC, [`assets/icons/LICENSE-lucide.txt`](assets/icons/LICENSE-lucide.txt) | | `assets/icons/{check,chevron-down,chevron-left,chevron-right,chevron-up,chevrons-left,chevrons-right,circle,clock,compass,info,link,link-2,lock,minus,monitor,moon,move,navigation,plus,search,square,trash,triangle,type,x,zoom-in}.svg` | Lucide icons derived from Feather (subset of the row above) | Cole Bemis (Feather), Lucide Contributors | via Lucide | MIT (Feather) and ISC (Lucide), [`assets/icons/LICENSE-lucide.txt`](assets/icons/LICENSE-lucide.txt) | | `assets/icons/slice-knife.svg` | Slice tool glyph | PhotoCraft contributors | Original work | MIT OR Apache-2.0 | +| `assets/icons/slice-select.svg` | Slice Select tool glyph | PhotoCraft contributors | Original work | MIT OR Apache-2.0 | | `assets/icons/sampler.svg` | Color Sampler tool glyph: a target reticle marking a sampled pixel | PhotoCraft contributors | Original work | MIT OR Apache-2.0 | | `assets/icons/convert-point.svg` | Convert Point tool glyph: an angle with a square anchor on its apex, drawn on Lucide's 24 px grid and stroke | PhotoCraft contributors | Original work | MIT OR Apache-2.0 | | `assets/icons/artboard.svg` | Artboard tool glyph: a board with a tick at each end of its edges | PhotoCraft contributors | Original work | MIT OR Apache-2.0 | diff --git a/assets/icons/slice-select.svg b/assets/icons/slice-select.svg new file mode 100644 index 0000000000..a32003ada0 --- /dev/null +++ b/assets/icons/slice-select.svg @@ -0,0 +1,16 @@ + + + + + + diff --git a/crates/ui-egui/src/icon_data.rs b/crates/ui-egui/src/icon_data.rs index 7fdaf4f7d0..049b097c08 100644 --- a/crates/ui-egui/src/icon_data.rs +++ b/crates/ui-egui/src/icon_data.rs @@ -118,6 +118,7 @@ pub static ICONS: &[(&str, &[u8])] = &[ ("settings", include_bytes!("../../../assets/icons/settings.svg")), ("slash", include_bytes!("../../../assets/icons/slash.svg")), ("slice-knife", include_bytes!("../../../assets/icons/slice-knife.svg")), + ("slice-select", include_bytes!("../../../assets/icons/slice-select.svg")), ("sliders-horizontal", include_bytes!("../../../assets/icons/sliders-horizontal.svg")), ("sparkles", include_bytes!("../../../assets/icons/sparkles.svg")), ("spline", include_bytes!("../../../assets/icons/spline.svg")), diff --git a/crates/ui-egui/src/icons.rs b/crates/ui-egui/src/icons.rs index 37d0b4aeab..97a6157af3 100644 --- a/crates/ui-egui/src/icons.rs +++ b/crates/ui-egui/src/icons.rs @@ -109,7 +109,7 @@ pub fn tool_icon(t: Tool) -> &'static str { Tool::Crop => "crop", Tool::PerspectiveCrop => "perspective-crop", Tool::Slice => "slice-knife", - Tool::SliceSelect => "square-dashed-mouse-pointer", + Tool::SliceSelect => "slice-select", Tool::Frame => "frame-placeholder", Tool::Gradient => "gradient", Tool::PaintBucket => "paint-bucket", From 5f8b124efa4b325212026a76195bc9195a777493 Mon Sep 17 00:00:00 2001 From: Korg1ch <57859048+Korg1ch@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:50:03 +0300 Subject: [PATCH 2/2] test(ui-egui): Slice Select's icon differs from Object Selection's and Slice's (#7859) --- crates/ui-egui/src/icons.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/crates/ui-egui/src/icons.rs b/crates/ui-egui/src/icons.rs index 97a6157af3..bb1bba202b 100644 --- a/crates/ui-egui/src/icons.rs +++ b/crates/ui-egui/src/icons.rs @@ -278,6 +278,29 @@ mod tests { } } + #[test] + fn slice_select_icon_is_its_own() { + // #7859: Slice Select reused Object Selection's dashed square with a pointer. As in + // Photoshop it is the Slice knife with a pointer: unlike Object Selection, and the Slice + // tool's knife plus ink of its own (the pointer). + assert_ne!(tool_icon(Tool::SliceSelect), tool_icon(Tool::ObjectSelection)); + assert_ne!(tool_icon(Tool::SliceSelect), tool_icon(Tool::Slice)); + for size in [14_u32, 28] { + let select = ink(tool_icon(Tool::SliceSelect), size); + assert!(select.iter().any(|&pixel| pixel), "Slice Select must have ink at {size}px"); + let object = ink(tool_icon(Tool::ObjectSelection), size); + let shared = select.iter().zip(&object).filter(|(a, b)| **a && **b).count(); + let union = select.iter().zip(&object).filter(|(a, b)| **a || **b).count(); + assert!(union > 0, "icons must have ink at {size}px"); + let overlap = shared as f32 / union as f32; + assert!(overlap < 0.5, "Slice Select and Object Selection must read as distinct: {overlap:.2} of their ink overlaps at {size}px"); + let slice = ink(tool_icon(Tool::Slice), size); + let own = select.iter().zip(&slice).filter(|(a, b)| **a && !**b).count(); + let total = select.iter().filter(|a| **a).count(); + assert!(own as f32 >= 0.3 * total as f32, "the pointer sets Slice Select apart from Slice: {own} of {total} pixels at {size}px"); + } + } + /// Ink bounding box `(width, height)` of a rasterized mask. fn ink_extent(mask: &[bool], size: u32) -> (usize, usize) { let s = size as usize;