* Windows: fix DPI-change problems: freezing, blank windows, drag-n-drop errors
- win32: do_callback queues re-entrant events (WM_DPICHANGED pumped inside
Present, nested WM_SIZE) instead of dropping them; the drops left dpi/geometry
stale, the window blank, and hit-testing offset after display-scale changes.
- win32: WM_DPICHANGED applies the OS-suggested rect and returns 0; removed the
duplicate-delivery compensations the lossless queue obsoletes.
- d3d11: no unbounded Present(1) block after a timed-out frame-latency wait
(DO_NOT_WAIT + paced retry); DXGI errors log instead of panicking.
- dnd: Drag/Drop events carry WindowId and are remapped via dpi_override_scale,
fixing drop-zone offset under UI zoom (Windows OLE, macOS external drops,
Wayland internal drags).
- win32: packaged-build window icons load at native shell sizes via LoadImageW.
* TextFlow: enforce `max_lines` for inline code wraps, list items, and inline widgets
`max_lines` + `text_overflow: Ellipsis` under-counted or skipped visual lines
whenever anything but a plain text run started one, so rich content (rooms-list
previews, anything with inline `<code>` or mention pills) could overflow its
line budget or hard-clip without an ellipsis.
* TextFlow: the inline `<code>` forced wrap left `is_continuation` stale, so a
wrapped code run was granted one extra row AND reported one fewer — a 2-line
overflow per wrap. The wrap now refreshes the continuation state and row
budget, is refused when no line remains, and its prediction probes measure
an unclamped layout (a clamped probe always claimed "fits on one line").
* Layouter: a non-wrapping layout truncated by `max_lines` never reported
`is_truncated` (detection was row-count only), so `max_lines: 1` labels
hard-clipped mid-word instead of showing "…". Width overflow on the
surviving last row now also triggers the ellipsis.
* TextFlow: list items charged two lines each (marker run + content run); the
marker now shares one visual line with the item's first content run.
* Inline child widgets (mention pills, images) bypassed the budget entirely:
Html now gates them on `is_content_truncated()`, charges the rows they open
via `track_inline_content()`, and holds a widget that lands on the last
allowed line in place (`Turtle::set_flow_wrap`) instead of letting the
turtle relocate it onto a row the budget can't pay for. A held widget that
still overruns the line is hidden behind a tracked clip and replaced with a
drawn "…" — the same contract text truncation has.
* TextFlow: fix row alignment and spacing for wrapped text beside inline widgets
On the single-batch text path (Windows/Linux), a multi-row text run never told
the turtle where its internal row boundaries were: every walk since the last
boundary — a bold sender on one visual row, a mention pill on the next — was
centered by `finish_row` against one merged row. Text sat a few px off the
pill baselines, rows around wrapped runs squeezed or overlapped, and an
up-centered pill on a padding-less first row rose above the clip and lost the
top of its rounded corners.
* DrawText: the single-walk path now allocates per visual row and runs
`turtle_new_line_with_spacing` at each internal boundary, keeping one glyph
instance batch. It emits a first-row walk owning the run's align entries and
a last-row walk with an empty range, so the run is never shifted twice.
* Turtle: `FinishedWalk` carries a `RowAlignRole`. A wrapped run's rows are
immovable (their glyphs share one batch): rows holding its visible text are
an `Anchor` — `finish_row_center` centers every shiftable walk on the
anchor's line, shifting a taller pill UP onto the text — while a
whitespace-only first row (a continuation that wrapped on a leading space)
is `Fixed`, so it cannot anchor a row to an invisible line. Anchor shifts
are clamped so no walk's top can rise above the turtle's clip.
* Layouter: `first_row_min_line_spacing_below_in_lpxs` is now honored (it was
stored and hashed but never read). Resumable draws pass the current row's
height plus wrap spacing plus the centering overhang, so a continuation's
second row lands where centered content beside it starts exactly one wrap
gap below the previous row — zero residual shift for uniform-height pills.
Selection capture and the `<code>` wrap probe pass the same floor so every
layout of a run shares one cache entry.
* Turtle: an anchored row returns its bottom forgiveness — the surplus its
allocation extends below the risen content — and `turtle_new_line`
subtracts it, keeping inter-row gaps uniform on both sides of anchored
rows. A turtle's final row keeps its full extent so nothing clips at the
bottom.
* uizoo: Html fixtures for line clamping and pill/text row alignment
Repro and regression fixtures mirroring a chat client's message surfaces:
max_lines clamping with inline <code> at several widths, atomic inline-widget
(pill) relocation/hold/ellipsis under a line budget, and timeline-style
pill+text rows at 9.3pt and 11pt metrics — including a CJK-titled pill and a
padding-0 first-row case that guards against clipped pill tops.
* Layouter: fill the last permitted row by grapheme when ellipsizing
When `max_rows` is set together with ellipsis truncation, word wrapping
would move a word that didn't fit off the final permitted row before
truncation ran, leaving that row ellipsized at the last word boundary
("@…") rather than at the last glyph that fits ("@quokka:…").
Word integrity is meaningless on a row that ends in an ellipsis, so lay
the final permitted row out by grapheme and let truncation cut at the
actual width limit.
* DrawText: account for walk margins when resolving a Fit max bound
The layout bound for a Fit-width walk with a relative max bound was the
raw resolved max, but the turtle's final width — and with it the clip
rect — gets clamped to that max minus the walk's outer margins. On top
of that, a Label passes the same margined walk to its inner DrawText,
re-applying those margins inside the turtle. Text laid out near the
bound therefore extended past the clamped clip, which sliced letters
off the end of untruncated text and cut the appended ellipsis down to
a single dot.
Subtract the walk's own horizontal margins from the resolved bound, and
when the enclosing turtle is itself an unresolved Fit with a max bound
(a Label sizing itself around this text), subtract that turtle's outer
margins too, so the layout bound matches the width the clip is actually
clamped to.
* uizoo: fixture for bounded pill labels ellipsizing at the width cap
Pill-like labels bounded to a fraction of the enclosing width, at
container widths that walk across the cap, plus one inline in an Html
flow. Truncation must always end in a visible trailing ellipsis, never
a bare mid-glyph cut.
* Layouter: keep word-boundary ellipsis on continuation rows
Grapheme-filling the last permitted row is wrong when that row is an
empty continuation: grapheme layout force-places a grapheme wider than
the row's remnant past the width limit with the truncation flag unset,
drawing overflowing text with no ellipsis. Finishing the row instead
truncates within bounds, so continuation rows keep the word-boundary
behavior.
* DrawText: scale the Fit max bound into layout units
The layouter works in unscaled units and row widths are multiplied by
font_scale on output, but the resolved Fit max bound was passed through
in physical units — the same mismatch max_layout_width_for_walk already
divides away. Any font_scale above 1 on a bounded label laid text out
past the clamped clip, reintroducing the sliced-tail bug the bound is
there to prevent.
* Turtle: add Base.Line, a Fit bound relative to the available line width
A static relative bound cannot express what an inline widget's inner
text actually has room for: that depends on the line the widget lands
on, its own leading geometry (icons, padding, spacing), and the
trailing insets after the text. Base.Line resolves a Fit max bound to
exactly that, with the enclosing line's flow selecting between two
measurements that are each final at the moment they are taken:
- A wrapping line can relocate the widget whole onto a fresh row, so
the bound is what a fresh row offers. Content sized to it either
fits where it is, or fits the row the widget is relocated to.
- A non-wrapping line — including one held non-wrapping by the
inline-content clamp on the last permitted row — keeps the widget
in place, so the bound is the remnant up to the line's right edge.
The measurement runs from the current turtle's content origin rather
than its pen, because a Fit max bound is evaluated twice: before the
content is laid out, and again in compute_final_size when the turtle
closes. A pen-relative measurement would collapse the closing clamp
to the leftover width and clip the content it just laid out.
* uizoo: fixtures for Base.Line-bounded pill labels
The four behaviors the line bound guarantees: a long name ellipsizing
at a narrow container's edge, a mid-line pill relocating whole to the
next row at full row width, a pill held on the last clamped row
squeezing visibly into the remnant, and a short name in a wide
container rendering untruncated.
* Turtle/DrawText: keep tiny Fit max bounds from slicing or inverting clips
A line-remnant bound can resolve to nearly zero when the last permitted
row is already full where an inline widget's text begins. Two guards
keep that degenerate range within the whole-glyph truncation contract:
- The Fit max clamp in compute_final_size floors at zero, so a bound
smaller than the walk's margins cannot produce a negative width and
an inverted clip (a pill rendering as bare chrome with no title and
no ellipsis anywhere).
- DrawText treats a bound too narrow for the truncation ellipsis
itself as no bound at all: the layouter appends the ellipsis glyph
unconditionally, so a narrower clip would slice it open. Left
unbounded, the text overflows honestly, letting an enclosing flow's
inline-content clamp hide the widget and draw the ellipsis itself.