Skip to content

feat(donut): slice labels that stay inside their slice, icons and Show modes, group rings that follow the donut, and names along the ring - #1370

Open
lightscape-jm wants to merge 6 commits into
KnifeLemon:mainfrom
lightscape-jm:feat/donut-labels
Open

lightscape-jm wants to merge 6 commits into
KnifeLemon:mainfrom
lightscape-jm:feat/donut-labels

Conversation

@lightscape-jm

@lightscape-jm lightscape-jm commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

What does this change?

Closes #870, closes #871, closes #872.

Why one PR for three issues (agreed in #870). All three change how a donut's text is laid out. #870 sets the rules (the safe zone, one name size per ring, when to shorten, icons and captions). #871 changes the shape of a group's ring, and #872 picks the direction of the ring's names from that shape. Tuned one at a time, each would have needed retuning when the next landed, and a donut change from someone else in between would have meant a third pass. There are six commits: one per issue, in order (#870, #871, #872), so each can be read or reverted apart; then one that retunes the rules for all three together, from renders with the app's own fonts; one that adds short donut names where German and English were cut; and one that splits the two layout sweeps into a test case per language, so each stays under CI's five-minute hang limit.

#870: labels that stay inside their slice

  • Safe zone. Every name, caption and icon stays inside its slice, clear of the edges, the hole and a group's chevron. Today a long name runs over the edge or the chevron in German, Japanese and Chinese (screenshots). The margin is kept from the letters themselves (their outlines, each line at its own width), about the room today's donut leaves, so where today fits a name, this PR places it the same way: on the slice's middle, with its caption.
  • Centred. A name goes on its slice's middle first, moved sideways to as much room left of it as right (a slice that runs aslant has more on one side), and drops a caption it doesn't need before it moves. Lines wrap at today's width. A group slice keeps its label clear of the chevron itself, not of a band across the slice, so its label is centred as any other's.
  • One name size per ring, 13 px at the default size. A name that doesn't fit is shortened with "…" rather than every name made smaller; the centre names the slice under the pointer in full (DonutItem.FullName). The ring steps down half a pixel at a time only where a name would otherwise be cut by just a letter or two, or so short it no longer reads, or (in Icon & text) to keep an icon above every name.
  • Short donut names for long translations. Where a default donut cut a name, a short form goes in the catalogs (preset.<name>.donut, as Half size and Pixelate already have): German "Freistellen", "Kleiner machen", "PDF verbinden", "Ohne Metadaten"…, English "Compress" and "Cut out". The other languages carry the same keys with their full names, for translators to shorten.
  • Icons and Show modes. A curated set of Material Design Icons. Each donut has a Show mode (Settings → Donut toolbar), and a preset can override it: Auto (icon above the name where every slice of the ring has room for both and loses nothing for it, neither a caption nor its place on the middle; else names), Icon & text, Text only, Icon only. Icon & text keeps its icons: where an icon and its name don't fit (the eight-slice back faces), the ring's names step down rather than be cut to a word ("Merge…"); a slice set to Icon only or Text only, or given a short name, lets them keep their size. Plain format conversions have no icon; their name is their label.
  • Captions show per slice, where they fit under the name, beside an icon too. The two that tell slices apart ([Feature]: Donut: a switch to hide the output format under slice names #869: "Copy to folder" / "Move to folder", and the format under two slices of one name) stay with Show output format off, and two slices of one name always look alike.
  • Short name and icon per preset in the preset and group editors; Label scale per donut.

#871: group rings that follow the donut

The ring grows and shrinks with the donut, and two settings in Appearance set its size (75–150 %) and how far it spreads (100–300 %). Every group's ring slices are equally wide.

#872: names along the ring

On a group's ring, names bend along the curve where the ring is wide and run out from the centre where it's deep, one direction for the ring. Scripts whose letters join or reorder (Arabic, Persian, Thai, the Indic scripts) are drawn level with the ring instead of bent, and Chinese, Japanese and Korean stay upright. A preset's Text direction (Straight, Out from the centre, Along the ring) overrides it, on the donut's face too; the face's own names stay upright by default (turned, they read sideways on the top and bottom slices; bent, they lose their caption line). Where a name is bent its icon sits above it as read; on a ring's chips, too shallow for that, beside it.

Screenshots

Faces (today on top, this PR below): English, German, Japanese and Chinese. Where today fits a name, it sits where it did; where today runs over an edge or cuts a word, it doesn't.

Faces, today and this PR

Group rings: today, then this PR at the default ring, at ring size 150 % and at spread 200 %, in English, Japanese and Arabic.

Group rings, today and this PR

Show modes on the image and PDF back faces: Auto, Icon & text, Text only, Icon only, and Auto with Show output format off.

The four Show modes

Names along the ring (#872): wide group rings bend their names; a face whose presets are set to Along the ring bends its top and bottom names.

Names along the ring

Branched from current main (f4038aa).

Type of change

  • Bug fix
  • New feature or improvement
  • New format or engine
  • Translation
  • Documentation
  • Build, CI or dependencies

How was it tested?

  • dotnet format --verify-no-changes --severity warn
  • dotnet test on macOS: Core 1,246 passed; App 2,351 passed. The 2 App tests that fail also fail on unmodified main on my Mac (RenderTests.Engines_page_shows_versions…, DonutHighlightTests.A_lost_animation_frame…).
  • Linux (Docker, Ubuntu 24.04, the app's fonts): App label, donut, ring, toolbar and translation tests, 668 passed. Core 1,247 passed; FolderCopyTests.Original_that_cannot_be_deleted… fails there as root, on unmodified main too.
  • Windows 11 ARM64: the same App tests, 668 passed; Core 1,244 passed.
  • Layout sweeps in the tests over every built-in donut (front, back and every group's ring), three donut sizes, three Label scales, three ring sizes and spreads, the four Show modes, Show output format on and off and every language: SliceLabelFitTests (inside the safe zone, one size and direction per ring, Auto never mixes, same-named slices alike) and SliceLabelReadabilityTests (never cut where it has room whole, never a lesser style where every slice has room for more).
  • Each rule added in the retune has a test that fails with the rule taken out (placement off the middle line, cuts by a letter or two, one long word, CJK upright on a ring, icons above bent names, same-named slices, needed captions, names shortened under an icon wrapping, the letters' margin, centring and its balance, the chevron, Auto icons that cost nothing).
  • Every English face slice sits on its slice's middle with every caption today shows (SliceLabelFitTests.English_names_…).
  • Rendered with the app's own fonts about 1,000 donuts in 10 languages (English, German, Japanese, Chinese, Korean, Arabic, Persian, Thai, Hindi, Bengali), reviewed against today's renders by reviewers working independently over several rounds; what they found is fixed or listed below.
  • Not tried by hand in the app.

Trade-offs, on purpose: a caption drops where it doesn't fit under its name at the ring's size (today the name shrinks for it); Label scale grows the names only where the slices have room for it; Thai has no spaces, so a long Thai name is shortened rather than wrapped; in Icon & text, one long word beside its icon ("Compress" on the PDF back face) and a bent two-line name under its icon ("Remove background" on a face set to Along the ring) are shortened, as there's no room to wrap them there.

Already so on main, not changed here: the centre says "1 files" and "1 × Folder" (untranslated); the German hint "Tab Vorderseite" is cut to "Tab Vorder…".

🤖 Generated with Claude Code

lightscape-jm and others added 5 commits November 10, 2026 13:28
…d Show modes

For KnifeLemon#870. Slice names no longer touch or cross a slice's edges, and a slice can show an icon.

- Safe zone: every label stays inside its slice, inset on all four sides and clear of a group's chevron
  (SliceLabelLayout). Names wrap at word breaks (Chinese and Japanese by character) and are shortened with "…"
  only when they fit nowhere whole; the centre names the slice under the pointer in full.
- One label style, one name size, one icon size and one text direction per ring (PlanRing), never a stub like
  "Tri…".
- Show: Auto, Icon & text, Text only, Icon only, per donut (Settings > Donut toolbar) and per preset or group.
- Label scale, 80-160 % per donut. Names, icons, the chevron and the centre text follow the donut's size too.
- Icons: a curated set of Material Design Icons, with defaults for the built-in tools and presets; plain format
  conversions have none. An icon picker in the preset and group editors, with a Short name for the donut.
- Strings in all 22 catalogs; ARCHITECTURE.md "Slice labels".
- Tests: the layout, the editors, the picker and settings, and SliceLabelFitTests, a fit check of every built-in
  donut at three sizes, three Label scales, the four Show modes and every language.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…spread settings

For KnifeLemon#871. A group's ring now grows and shrinks with the donut, and Settings > Donut toolbar > Appearance can make
it larger or fan it out wider. Until now it was 46 px deep whatever the donut's size.

- The ring's gap, depth and narrowest slice, and the window's room for it, follow radius / 150 times Group ring
  size (75-150 %). At the defaults and radius 150 the ring is the same as before.
- Group ring spread (100-300 %) widens each ring slice, up to one turn round the donut.
- Ring labels and icons follow the ring's size, and the Label scale as far as the ring has room. Pointer
  tolerances stay in pixels.
- DonutSettings.GroupRingSize and GroupRingSpread default to 1 and read as 1 from older settings, so no schema
  step; they are clamped on load.
- Strings in all 22 catalogs; ARCHITECTURE.md updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…qually wide ring slices

For KnifeLemon#872.

- On a slice wider than it is deep (a group's ring), names bend along the ring, letter by letter and never upside
  down; deep slices run straight or out from the centre. One direction per ring, chosen from its shape. Scripts
  whose letters join or reorder (Arabic, Persian, Thai, the Indic scripts) are drawn level with the ring instead.
- The preset and group editors get Text direction (Automatic, Straight, Out from the centre, Along the ring) next
  to Show; a slice with its own direction keeps it.
- Icon and name show together wherever there's room: before a bent name on a wide slice, before a name out from
  the centre on a deep one.
- Every group's ring slices are as wide as a group of three's, so Group ring spread looks the same on every group.
- Strings in all 22 catalogs.
- Tests: SliceLabelReadabilityTests, every built-in donut, its back face and every group's ring over donut sizes,
  holes, Label scales, ring sizes and spreads, Show modes, directions and six scripts, judging room apart from the
  layout; it fails where a slice shows less than it has room for, or a ring's names run different ways.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The October rules were measured without the embedded fonts (git-ignored, missing
from those trees), on the narrower system fallback. Rendered with Noto Sans on
today's donuts (eight slices since KnifeLemon#806) they made names smaller than today,
dropped every caption on the PDF back face, showed no icons in Auto, and let
KnifeLemon#872 bend the names of every six-slice face. Retuned over about 1,000 renders in
10 languages, reviewed independently:

- Names keep the ring's size and are shortened with "…" rather than made
  smaller; a ring steps down half a pixel at a time only where a name would be
  cut by a letter or two, or so far it no longer reads. A group's ring, of short
  words, still steps down first.
- Each slice picks the best its Show allows at the ring's size and direction;
  Auto shows icons on all of a ring's names or on none. Captions show per slice
  where they fit, beside icons too; the two that tell slices apart (KnifeLemon#869) stay
  with Show output format off, and slices of one name look alike.
- A block that fits nowhere on a slice's middle line is tried round from it (a
  three-slice face's slices run aslant).
- The face keeps its names upright unless a preset's Text direction says
  otherwise; Chinese, Japanese and Korean stay upright on a group's ring too; a
  script that can't bend is drawn level with the ring.
- Icons sit above a bent name as read, one placement for a ring, beside it only
  on a ring's shallow chips.
- Japanese and Chinese lines never end on an opening bracket or start on a
  closing one; a shortened name wraps like a whole one.
- Icons for the built-ins added since October (Annotate, Add background,
  Metadata, Upscale, the N-up PDF and Crop video groups); the Actions branch's
  entries, which name no upstream preset, are gone.
- Icon & text keeps its icons: a name shortened under its icon wraps as a name alone does ("Remove / metad…"),
  and where an icon and a name that still reads don't fit (the eight-slice back faces) the ring steps down rather
  than cut names to a word.
- Spacing as today's: the margin, max(2 px, 2.5 % of the depth), is kept from the letters (their outlines, each line
  at its own width), not from the lines' boxes, which left 10-13 px round English names where today leaves 3-5.
- A block goes on its slice's middle first, moved sideways to as much room left of it as right, and drops a caption
  it doesn't need there before it moves; lines wrap at today's width. A group slice keeps clear of its chevron, not
  of a band across the slice, so its label is centred as any other's; an icon alone steps round the chevron.
- Auto shows icons only where they cost no slice its caption or its place on the middle.

Tests: every rule above has one that fails with the rule taken out;
SliceLabelFitTests and SliceLabelReadabilityTests sweep every built-in donut
and ring, size, scale, Show mode, caption setting and language against them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A long name gets a short form for the donut (preset.<name>.donut, as Half size
and Pixelate already have) rather than being shrunk or cut by the layout. German
gets one for every name a default donut cut (Freistellen, Kleiner machen, PDF /
Videos / Audio verbinden, Bilder vereinen, Ohne Metadaten, Einzelbilder,
Zuschneiden, Vektorbild, Ein ZIP), English two (Compress, Cut out). Every other
language carries the same keys with its full name until a translator shortens
it. The centre, the editors and the lists keep the full name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@github-actions github-actions Bot added the feature New features label Nov 10, 2026
… limit

SliceLabelFitTests and SliceLabelReadabilityTests each swept every language in one test, 11 and 6 minutes; CI's
--blame-hang-timeout 5m aborted the run in them on every runner. Each language is now its own case (the slowest about
a minute), with the same checks; the readability report is written per language.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New features

Projects

None yet

1 participant