iris: mark() -- a drawn disclosure triangle, instead of a codepoint the phone lacks

The tool cards' open/closed marks were U+25B8/25BE/25B4 in whatever face
resolved. That worked while iris bundled its own fonts; since the move to
the platform collection on 2026-09-07 Iris's phone draws an empty box and
this machine draws a dot -- UI_RULES' 'don't rely on characters the
platform might not have'.

iris::widget::mark rasterises one oversampled, antialiased triangle into
the ordinary texture path and scales it into the box the caller asks for,
so it needs no new primitive and is correct at any density. Its two tests
check the shape points where it was asked to and leaves its corners
clear, which is the half nobody would look at on a device that renders it
wrong.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
irisandClaude Opus 5 committed 2026-09-08 14:01:31 -04:00
1 parent af1b0c5ab2
commit e5a90c6135
4 files changed
+261 -9

No files matched your search

+57
View File
@@ -8,6 +8,63 @@ capability that moved. Small and trivial changes do not go here.
An entry gives the date, what changed, why, and a short before/after where
it helps judge the change without the session that made it. Newest first.
## 2026-09-08: a gesture can be cancelled, and the pointer belongs to the input handler
Two changes to how a drag ends, from defects on Iris's phone (a code
fence panned sideways made the transcript jump on the next tap, and made
the fence itself snap back).
**`CursorSense::Cancel`, and `GestureOutcome::Cancelled`.** Taking
pointer capture cuts every other widget off from the press completely --
no `PressEnd`, no `Drop` -- so anything else tracking that press was left
with a gesture open at an origin belonging to a finger long gone, and the
next touch anywhere was measured from it. A widget that loses a capture
race is now told, exactly once. It is a separate sense from `Drop`
deliberately: `Drop` means "your gesture finished" and callers act on it
(a fling, a tap, a link followed), which is precisely wrong here.
**`CursorSense::drag_senses()`** is what a widget driving a `DragGesture`
registers -- the frames plus `unclick`, `Drop` and `Cancel`. Both ways a
gesture can end, stated once rather than remembered per call site;
forgetting `Drop` is what left a `Scroll` panning from a stale position.
**The pointer's state left `UiRenderState`.** `capture_pointer`,
`release_pointer` and `captured_pointer` are gone from it. Capture and
the pressed set are `PointerInput` -- the cursor senses' `Event::Global`,
a new associated type for state an event owns that belongs to no single
widget -- held by the event manager that runs the dispatch and reached
by `&mut`, with no lock anywhere. A handler asks through
`ctx.data.pointer` (`PointerRequests`: `capture(id)`, `release()`,
`holder()`).
// before -- interior mutability on whatever structure was reachable
ctx.data.render.capture_pointer(id);
// after
ctx.data.pointer.capture(id);
`DragGesture::handle` and `Scroll::drag` take `&PointerRequests` where
they took `&UiRenderState`. `task_on` also lost a `Data: Send` bound it
never needed -- the future it spawns never sees the event's data, and
that bound was the whole reason the pointer state had been behind a
`Mutex`.
## 2026-09-08: `mark(dir, dp, colour)` -- a drawn triangle, and a scroll area's opening edge
**`iris::widget::mark`** draws a filled, antialiased triangle pointing
along a `Dir`, at a size in dp. It replaces the disclosure codepoints
U+25B8/25BE/25B4, which were a bet that the platform's fonts have them --
once iris stopped bundling its own faces, Iris's phone drew an empty box.
It rasterises one oversampled bitmap into the ordinary texture path and
scales it into the box asked for, so no new primitive was needed and it
is correct at any density.
**`scrollable_on` now opens at the beginning of its content, and
`scrollable_to_end(axis)` is the other one** -- pinned to the end and
staying there while the content grows, which is what a composer wants and
what everything did before. A code fence was opening at the end of its
longest line, in the middle of a word. `Scroll::new` takes the edge as a
third argument rather than deciding for its caller.
## 2026-09-08: masks have a shape -- `.masked_by(shape)`, and clipping applies to touch
A mask no longer carries a rectangle. It carries **the slot of a