Wuss sharp - #6
Merged
Merged
Conversation
Lets tasks read the system font handle given to wuss_create, so they can draw their own content in the same face as window titlebars.
Displays every glyph 0-255 from wuss's system font, one per cell, so the whole font can be eyeballed at a glance. Skips itself if wuss was created without a font.
bp[w3] is an unsigned char promoted to int; shifting a byte >= 0x80 left by 24 overflowed signed int range, undefined behaviour caught by UBSan. Cast each byte to uint32_t before shifting.
Wraps the flags previously passed by hand into the ad-hoc build-asan/ directory, so any build tree can opt in with -DUSE_ASAN=YES.
Squared per-channel deltas multiplied by libjpeg's fixed-point weights overflowed signed int at the top of the byte range (e.g. 255 apart on green), undefined behaviour caught by UBSan. Cast each squared delta to unsigned int before the multiply.
bmfont_draw indexes glyphs as c - ' ' with no bound check, and Chars fed it every byte value 0-255; bytes past the font's actual glyph count walked off the glyph table (AddressSanitizer heap-buffer-overflow). Add bmfont_get_count() and have chars_redraw leave a cell blank instead of drawing when its byte value falls outside the font's range.
Adjust click now cycles through sofa, ship, tetrahedron, cube, octahedron, icosahedron and dodecahedron wireframes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace line_clip's w*dy/h maths with a muldiv() helper that widens the multiply using 32-bit half-word arithmetic and does a bit-serial division, so no 64-bit or floating-point intermediate is needed while still avoiding UBSan's signed-overflow trap.
wuss_window_move()'s blit-and-slide optimisation only fired for the topmost window, so dragging a window sent to the back always fell back to a full-footprint redraw even when nothing above it actually overlapped its old position. Replace the topmost check with an explicit occlusion test (wuss__occluded_above) that walks the z-order list above the window and only declines the fast path when some window's visible box genuinely overlaps "before".
Regression test for the fix in wuss_window_move(): a window sent to the back with nothing above it overlapping its old footprint must still take the blit-and-slide fast path, not fall back to a full redraw just because it isn't topmost.
wuss_window_resize() used to invalidate the union of the old and new footprint on every resize, forcing a full repaint even though a resize's top-left corner is fixed and everything within both footprints is unchanged. Add invalidate_grown_or_shrunk() to decompose the delta into the shrunk-away and grown-into slivers, each still clipped against occluders via wuss__invalidate_clipped (unlike wuss__invalidate_minus, which has no occlusion awareness and would have broken the fully-occluded-resize test). Add a regression test asserting the untouched interior corner is never marked dirty and the dirtied area stays below a full repaint. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The grown/shrunk sliver invalidation added in 7a319b9 treats the old ∩ new footprint as fully unchanged, but window furniture (outline, scrollbars) is drawn along the trailing edge of the old footprint. When a window grows, that edge lands inside the new interior and was never marked dirty, leaving stale outline/scrollbar pixels baked into the enlarged window. Mirror the fix already used by toggle-size (toggle-action.c): force the old footprint's furniture dirty when growing, then invalidate furniture at the new position too. Also move the resize regression test's interior probe point below the titlebar, since the titlebar itself is legitimately redrawn now. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The grown/shrunk-sliver optimization assumes a task's content is just anchored positions plus furniture, so the old/new overlap needs no repaint. That's false for a task like the palette swatch grid, which lays itself out across the whole window -- exactly the case wuss_WINDOW_NO_TOGGLE_BLIT already exists to flag for toggle-size, but resize.c never checked it, so such tasks got stale interior content on a plain resize. Fall back to full old-union-new invalidation when the flag is set. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
wuss__occluded_above() only checked the window's old footprint before allowing the move blit fast path. screen_copy_rect() is a raw pixel copy with no awareness of window ownership or z-order, so when a window's NEW footprint landed under an occluder (its old footprint having been clear), the blit pasted stale pixels straight over that occluder with no invalidation to fix it up afterwards -- corrupting it until something unrelated redrew it. Check both the old and new footprint against occluders above the window in z-order before allowing the blit. Add a regression test dragging a clear window onto a topmost occluder and asserting the exposed sliver of the destination gets queued dirty by the fallback clipped redraw. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
dd0dca6 fixed the destination-occlusion corruption bug by declining the blit fast path entirely whenever either the old or new footprint overlapped an occluder above the window in z-order. That's correct but overly broad: it falls back to a full clipped redraw of the whole moved footprint even when only a small part of the new position is covered, discarding the blit's exposed-sliver savings for no reason -- the exposed part is still purely this window's own content. Only the old footprint still needs to be occlusion-free (its pixels must genuinely be this window's own rendering to be worth sliding). The new footprint can still land under an occluder: after the blit, call wuss__invalidate_uncovered() to force exactly the overlapped part back to its rightful owner (the occluder now painted over), leaving the window's own exposed content untouched since the blit already placed it correctly. Rework the regression test from dd0dca6 to assert both halves: the occluded overlap is forced dirty (fixing the corruption), and the exposed sliver is NOT (proving the blit is still used, not a full fallback redraw). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
wuss_window_move() used to require a window's whole old footprint be occlusion-free before blitting; any overlap at all fell back to a full clipped redraw of the moved footprint. Split "before" into its clean (unoccluded) and hidden pieces via wuss__clip_to_visible(), blit each clean piece individually to its translated destination, and only genuinely repaint the translated hidden pieces plus any destination occlusion. Exposes subtract_boxes as wuss__subtract_boxes for reuse here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… against inter-piece clobber wuss_window_move() forced a window's whole current occluded footprint dirty after any blit, even parts a blit never touched (never-blitted occluded source pieces need no such repair) -- unnecessarily redrawing an untouched occluder above it. Sequential per-piece screen_copy_rect() calls could also clobber each other: one piece's destination landing on another still-unread piece's source (e.g. dragging past a thin mid-window occluding band), producing corrupted "bands" in the moved window's own content. Detect this before blitting and fall back to a full clipped redraw rather than risk it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ed move Confirms wuss_window_move never marks an occluder dirty when the piecewise blit is declined (wuss__pieces_would_clobber): with no blit, nothing ever touches the occluder's pixels, so it must stay untouched even as the overlap between the dragged window and the occluder grows during the move. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… clobber risk wuss__pieces_would_clobber declared any pairwise dest/clean overlap unsafe, forcing a full-footprint redraw even when a different blit order would avoid the conflict entirely -- routinely true for the common case of a two-piece L-shaped corner occlusion. Adjust-dragging a window behind a corner occluder was regressing into a full redraw. Replace it with wuss__order_pieces, which builds the "must happen before" dependency graph and topologically sorts it, only falling back when a genuine cycle leaves no safe order. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…nary occluder Blitting the full destination then force-invalidating the occluded overlap to "repair" it forced a spurious repaint of the occluder whenever a window was dragged underneath it. The occluder hasn't moved, so its pixels there are already correct — clip each clean piece's destination against current occlusion before blitting, so the occluded remainder is never touched and needs no repair. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Covers wuss_get_font, wuss_WINDOW_NO_TOGGLE_BLIT, the wuss_get_dirty_count/ wuss_get_dirty(index) pair, and drops the now-false "no finer than a window's bounding box" damage-tracking limitation.
Matches ms-sans-serif.png, which never had the suffix.
The flag guards wuss_window_resize itself, not just the toggle-size icon action, so drag-resizing a flagged window also gets a full redraw instead of blit-preserve. The old name only described one of its two triggers.
Its paragraph reflows across the whole window width, so the default resize optimisation (preserve the unchanged interior, repaint only the grown/shrunk sliver) left stale wrapped lines behind whenever the task's own idle animation resized the window. Same fix already applied to the palette swatch task.
The text's clip rect was the whole titlebar, not the text_x0..text_x1 slot computed to dodge the close/back/toggle icons, so a title too long to fit was drawn past that boundary. It usually stayed hidden because the icon(s) painted after it happened to cover the overrun, but that only holds when the redraw region includes those icons too: a redraw covering just the title (e.g. after a title-only invalidate) left the overrun visible. Now the draw itself is clipped to the slot.
wuss_task_t::bg was mutated at runtime via wuss_window_set_background,
despite the struct being documented as an immutable content delegate
copied in at creation. Move bg to a dedicated wuss_window_create
parameter (and a wuss_window_t field) so window-owned mutable state
lives on the window, not the delegate.
BREAKING CHANGE: wuss_task_t drops its bg field (now {handle, task_data});
wuss_task_start drops its bg parameter; wuss_window_create gains a
wuss_colour_t bg parameter after flags.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Doxygen comments and enum/struct member docs wrapped with nice greedy-fill wrapping, matching the description-column-aligned param style used elsewhere (cache.h, screen.h).
Reflows /** ... */ blocks (\param/\return/\brief/prose paragraphs) greedy-fill, continuation lines aligned under the description column, matching the style used by hand in wuss.h/window.h. Leaves /**< trailing member comments and overlong code lines alone (those need structural judgement) and reports them for manual follow-up.
Run tools/wrap_doxygen.py over every DPTLib-owned .h file (fortify/ excluded, vendored third-party code). /**< trailing member comments left untouched by design; flagged on stderr for manual follow-up. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
wuss_config_t gains a backdrop colour (wuss_NO_BACKGROUND to opt out, matching window bg), painted by wuss_redraw/wuss_redraw_dirty before windows -- previously every caller had to repaint the background itself around each dirty rect. BREAKING: existing wuss_config_t initialisers that build the struct field-by-field must now also set backdrop. wuss-test.c's per-frame background flash is replaced by config.backdrop, and Shift-F1 now forces a full wuss_redraw (backdrop included) to exercise it interactively.
…coded one wuss__icon_size_for used WUSS_DEFAULT_TITLEBAR_HEIGHT as its fallback for a NO_TITLEBAR window that still wants scrollbars/resize, so such a window's furniture didn't match its titled siblings whenever the actual (derived or configured) titlebar height differed from the hardcoded default.
… word Replaces struct wuss_window's standalone toggled int with a wuss_window_state_t bitfield (wuss__window_toggled/wuss__window_set_toggled helpers), separate from the public wuss_window_flags_t appearance flags, so future internal per-window state can add bits instead of int fields.
…:scroll Groups arrows/wells/sausages into a scroll sub-struct so the flat colour list reads as one furniture class instead of nine unrelated fields. BREAKING: existing wuss_config_t initialisers must update palette.arrows/wells/sausages to palette.scroll.arrows/wells/sausages.
Adds a turns counter alongside angle, incremented each time the spin wraps; hitting SOFA_ROTATIONS_PER_MODEL advances to the next shape, same as an Adjust click (which also resets the counter). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Records the wuss__ prefix and internal-bitflags-with-accessors pattern established this session, so future work follows it instead of reinventing per-window state as loose ints. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds WUSS_SCROLL_INSET so the drag handle sits with a small cosmetic margin on its cross-axis instead of filling the well's full breadth. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
launcher_redraw read scroll.y but never scroll.x, so rows stayed put when scrolled horizontally (reachable once the window is resized narrower than LAUNCHER_WIDTH, which enables the hscroll bar). wuss_window_set_scroll's topmost live-blit-and-shift optimisation assumes redraw output is a deterministic function of the scroll offset; violating that shifted real pixels by the scroll delta while the exposed edge strip was redrawn at the stale unscrolled position, corrupting the window's content. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The three line rasterizers seeded their Bresenham/Wu error terms from the endpoints line_clip() had recomputed on the clip boundary. As wuss splits a dirty rectangle into up to 32 pieces and redraws the same logical content under a different clip box for each, the same line took a different pixel path per piece, leaving visible seams at the piece boundaries. Use line_clip() against the clip box for rejection only, discarding its recomputed endpoints, and step from the true endpoints instead, leaving the per-pixel clip in screen_draw_pixel()/screen_blend_pixel() to do the real clipping. Bound the work with the screen bounds, which -- unlike the clip box -- never vary between calls: screen_draw_line() and screen_draw_line_wu_fix8() clip their endpoints against them, and screen_draw_line_wu_float() clamps its mid-point loop to them, fast- forwarding the gradient in closed form. Adds a screen test asserting a line drawn in one go and drawn once per piece of a partition of the canvas produce identical pixels, plus a check that nothing is drawn outside the clip box. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
wuss_mouse_click, wuss_mouse_move and wuss_scroll all add the window's scroll offset before dispatching, so the point a task receives is already in virtual content space. The curve, ball and launcher tasks each added the offset a second time via wuss_window_get_scroll, putting hit-tests, drags, ball spawns and launcher row indices at twice the scroll offset once a window was scrolled. wuss_scroll also computed its event point after stepping the scrollbar, so the point described the content the wheel had just brought under the pointer rather than the content the pointer was over when it turned. Compute the point first. The wuss_EVENT_MOUSE documentation described the point as window-local content coordinates, which is what invited the extra addition; say that the scroll offset is already applied. Adds a wuss test covering all three dispatch paths. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
wuss_idle operates on the window manager, not a single window, so it belongs alongside the other wuss_t entry points. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Move the event and task types, wuss_task_start and wuss_task_stop out of window.h into a new public wuss/task.h, and relocate their sources to libraries/wuss/task/. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add a size2d_t (width, height) type alongside point_t, plus box_size to extract one from a box, and use them for wuss_window_create's document extent, wuss_window_resize's new size and struct wuss_window's stored document extent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replace bitmap_t/screen_t's width and height members with a single size2d_t, and take a size2d_t in bitmap_init, screen_init and screen_draw_rect. Most screen_draw_rect call sites now pass box_size() directly instead of subtracting the box's edges by hand. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The content area's right and bottom edges abutted the scrollbars (or the resize corner) with no dividing line, leaving the frame's interior looking unfinished. Reserve a further pixel on each carved axis in wuss__furniture_carve_for -- so window creation, resize and toggle-size all account for it -- and draw the rule there, in the same colour as the outline. The requested content box is unaffected: like the rest of the furniture, the rule is added outside it, not carved out of it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
wuss__furniture_invalidate_for decided the scrollbar strip widths from the NO_VSCROLL/NO_HSCROLL flags, but wuss__furniture_carve_for still reserves a strip on both axes for a window with both scrollbars off and resize on, to hold the resize icon and the interior rules. Growing such a window left the old icon and rules painted inside the new content area. Drive the strips off the same carve the layout uses. Also: - Floor the scroll sausage's cross-axis inset at 0 when the well is no wider than two insets, which a small titlebar font can produce; the box used to invert and the sausage vanish. - Zero-initialise test_task_t in the scroll-offset test, which only set mouse_count while test_handle increments the other counters. - Correct the screen_copy_rect failure comment in window/move.c and document muldiv's domain in geom/line. - Clamp the chars task's glyph count so no byte above CHAR_MAX reaches bmfont_draw, which indexes its glyph table off a plain char. The resize-sliver test summed per-region dirty areas, double-counting overlaps; it passed by 257 pixels and the extra strips tipped it over. Measure the true union instead. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Sorry @dpt, your pull request is larger than the review limit of 150,000 diff characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.