Wuss bagpuss - #9
Merged
Merged
Conversation
dpt
commented
Sep 4, 2026
Owner
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>
Wuss sharp
Adds an eleventh launcher task to the interactive wuss test: the two bundled composite demo images (resources/composite/A.png and B.png) composited under a Porter-Duff rule that cycles through all twelve operators, over an alpha checkerboard so transparency reads as transparent rather than white. composite() takes no offset or alpha parameter, so the animation is a per-frame edit on scratch copies: the destination is restored from a pristine B and the source's alpha channel is scaled by a triangle ramp (0 to 255 and back across each rule's turn). Alpha is non-premultiplied, so only the alpha byte is touched. Pacing is a per-idle frame counter, as in ball.c; clicking advances the rule and the wheel adjusts the cycle speed. The bitmap clone/convert helpers are copied from composite-test.c, where they are statics rather than library functions. Widens the launcher window to fit the longer entry name. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adjust-clicking a scroll arrow now steps against the direction the arrow points, so a single arrow can be worked both ways without moving the pointer. Toggle-size remains Select-only. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ction Reflows the Doxygen parameter blocks in window.h and wuss.h, and renames the README's "Wuss" section to "Windowing".
Select/Menu/Adjust were consecutive enumerated values, so a chord such as Select+Adjust could not be expressed at all. They become flags -- Select 4, Menu 2, Adjust 1, matching the RISC OS button order -- with a wuss_BUTTON_NONE of zero, and every internal comparison switches from equality to a bit test. Where a chord is ambiguous Select wins: a Select+Adjust click on a scroll arrow scrolls the way the arrow points rather than backwards, and on the back icon sends the window to the back. BREAKING CHANGE: wuss_button_t's values have changed, and client code comparing the reported button for equality must now test with '&' or it will fail to match a chord. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Matches the per-area layout of the other docs (databases, datastruct, framebuf, geom, io).
Names the terms the document already leans on: the three coordinate spaces (screen, window-local, virtual content), content area versus visible bounds, furniture and its parts, and the RISC OS button conventions. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
doc already caps how large a resize-drag or toggle-size can make a window; the floor was the fixed WUSS_MIN_CONTENT, so a task whose content stops making sense below some size had no way to say so. wuss_window_create gains a min_doc alongside doc, set once at creation. wuss__min_content clamps it up to WUSS_MIN_CONTENT, so a window can never be squeezed too small to grab, and down to doc, so a minimum can never demand a window larger than the document it shows. Pass (0,0) for the built-in floor. BREAKING CHANGE: wuss_window_create takes a min_doc argument between doc and window. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The launcher entry table is now const, so the per-row "running" flag can no longer live in it. Move it into launcher_task as a fixed-size bool array indexed by entry, capped at LAUNCHER_MAX_ENTRIES. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace inline (point_t) { x, y } and (size2d_t) { w, h } compound
literals across the tree with POINT(x, y) / SIZE2D(w, h) helpers defined
alongside their types.
Also folds in a pre-existing null-pointer guard in bmfont_get_info and
minor alignment tidy-ups that were already in the working tree.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Resize-drag recomputed width/height as pointer minus content origin on every move, discarding where within the resize icon the mouse-down click landed. The window's corner snapped to the raw pointer position on the first move, jumping if the click wasn't at the icon's exact corner pixel. Store the pointer's offset from the content box's bottom-right corner at mouse-down (mirroring how a titlebar drag already stores its content-relative offset), and subtract it on each move, so the grabbed point stays under the pointer through the drag. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TWwV3bFEi9z5MZc5wqiJzZ
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Add a work-area icon subsystem: static labels and clickable bevelled buttons that Wuss draws inside a window's content area and hit-tests before the content task sees a click. Icon boxes are in virtual document space so they scroll with the content; button clicks and hovers reach the task as wuss_EVENT_ICON, while labels and hidden or disabled icons fall through as wuss_EVENT_MOUSE. Includes the public wuss/icon.h API, per-window icon storage, drawing, mouse routing integration, and an icons test task. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The icons task drew its backdrop grid and x/y axis rulers pinned to the window edge, so Wuss's scroll blit displaced the painted pixels without repainting them, smearing the labels. Anchor all task drawing to document space so it scrolls rigidly with the content, as the blit assumes; add a bmfont-drawn coordinate ruler along the document x=0 and y=0 lines. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Loops wuss_icon_create over a spec array with all-or-nothing rollback: on the first failure any icons already created by the call are destroyed and no handles are written. Converts the icons test task to use it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a BORDER margin around the bitmap, a min-doc resize floor, and a pink background so the border and transparent pixels are visible. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
image_create took the repo root and built the PNG path itself; it now takes the full filename, with path assembly moved to the spawn_image caller in wuss-test.c. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
New entry point taking a content size instead of a box: wuss packs the window (furniture included) into the first free screen region via a packer_t it owns, top-left order, tracking occupied area across calls so successive auto-placed windows tile. Cascades from the previous placement when no region fits. The slot is released back to the pool on close, and on the first wuss_window_move / wuss_window_resize (a titlebar drag counts as a move), after which wuss stops tracking the window's position. Adds packer_release() to geom/packer as the inverse of packer_place_*; released areas are not coalesced, which is sufficient for whole-window placement. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Switch every test task from a hardcoded BOX_POS_SIZE origin to wuss_window_create_placed, passing just the content size. Windows now tile from the top-left instead of landing at fixed coordinates. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
packer_place_by now searches for the box plus a configurable gutter strip along its two inner edges, so boxes placed by location never end up flush. The gutter defaults to 0, leaving every existing caller unchanged. The returned position is still the un-inflated box. wuss_window_create_placed sets a fixed WUSS_PLACE_GUTTER and stores the gutter-inflated region in wuss_window::packed so the whole reservation, not just the footprint, is handed back on close/move. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Each wireframe model in the sofa test task now draws a small white square at every projected vertex, on top of the edges. Also adds a Cobra Mk III model alongside the existing ship. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…lines The anti-aliased fixed-point line rasteriser had three undefined behaviours, all reachable with long or off-screen endpoints: - FIX16_ONE * dy_f8 (and grad_f16 * dx) overflowed 32-bit int once a line exceeded ~32k fixed-point units; compute those in long long. - INT_TO_FIX8(iy) left-shifted pixel Y coordinates that can be negative; use a multiply instead. - the yf recurrence left-shifted a possibly-negative fix8 value before shifting back; form the fix16 sum by multiply, then arithmetic-shift down. Adds test_wu_fix8_extreme_coords covering large and off-screen endpoints, and fixes the test draw() helper's own negative left shift. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
An ADJUST pick on a menu row delivers MENU_SELECT but, RISC OS style, leaves the chain open rather than tearing it down. A task's tick, however, is baked into the row's icon selected-state once at wuss_menu_open time (wuss_menu_item_t.flags is read only when the chain is (re)built) -- so a task that only edits its own item array on MENU_SELECT never sees the change while that same chain stays open. Add wuss_menu_set_ticked(handle, menu, index): finds the open level whose menu matches, ticks index and unticks every other row's icon directly, invalidating the ones that changed. chars.c now keeps the handle from wuss_menu_open and calls this after an ADJUST re-pick switches font, so the picker's tick tracks the current font even with the menu held open. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
wuss_create's fonts[] is now an array of wuss_font_desc_t (font handle, wuss_font_class_t, and a borrowed leafname), replacing the old bare bmfont_t* array. wuss_FONT_CLASS_SYSTEM marks a slot as chrome/decoration only (e.g. the symbol font menu ticks and submenu arrows are drawn from), as opposed to a normal user-selectable text font. wuss_fontmenu_create takes the owning wuss_t and skips any SYSTEM-class slot whose name matches a scanned leafname, so a font picker no longer offers chrome fonts as text choices. Matching is by name, not by loading the bmfont_t, preserving the menu's lazy load-on-select design. Adds wuss_get_font_class_n/wuss_get_font_name_n getters and updates all wuss_create/wuss_fontmenu_create call sites (apps/wuss demo, chars/text test tasks, wuss-test.c) accordingly, plus a test covering SYSTEM-class exclusion. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Byproduct of recent font-slot tagging/picker work on wuss.
redraw_window drew a window's chrome (titlebar, scrollbars, outline) against the raw dirty/full rect with no occlusion clipping at all -- only its content went through wuss__clip_to_visible. A window redrawing its own dirty region while partly covered by a higher window would repaint its furniture straight back over that higher window's pixels. Also exclude wuss_NO_BACKGROUND windows' content boxes from the desktop backdrop's pre-fill, so a task that skips or only partially repaints its content sees whatever was already on screen instead of a flash of backdrop colour. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replace hand-picked spec index arithmetic (g, m, g+N offsets into one flat array) with a layout cursor threaded through per-group helper functions (icons_add_intro/buttons/radios/bitmaps/pattern/menu), each appending its own specs and advancing a document y. A named enum reserves the exact spec count per group so the total can't silently drift from the array bound. Expand the demo to cover every wuss_icon_type_t: bordered FRAME groups for buttons (normal/default/disabled), radios+option, bitmaps, and a new PATTERN swatch group, plus a SWATCH and a DISABLED entry added to the menu strip. Window and document grown to fit; the scrolled-away proof button is kept. Fix two -Wcomment warnings from *ptr inside doc comments. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
create.c, icon/draw.c and bmtext/draw.c queried font_width via bmfont_get_info but never used it, needing a NOT_USED() escape hatch. Pass NULL for outputs callers don't need instead.
Replace open-coded floor/ceiling ifs and min/max ternaries across wuss, framebuf, geom, datastruct, text and utils with the MIN/MAX/CLAMP macros from base/utils.h. Adds the include where a file didn't already pull it in. Left the vendored libraries/fortify/fortify.c untouched.
16x16 grid instead of 32x8, cell shows index in system font stacked above the glyph. Window now resizable/scrollable and placed with a 64,64 offset instead of pinned at origin.
…hain wuss__menu_flash_finish's MENU_SELECT delivery can re-enter and close the whole chain even for a keep_open (ADJUST) pick, e.g. when the owner's handler calls wuss_menu_close(). The old guard only bailed out for the non-keep_open case, which frees the chain itself; the re-entrant client-triggered free was not covered, so a fast ADJUST re-pick could dereference the freed self->icons[index] afterwards. Replace the prev_keep_open check with a liveness check against the live chain (wuss__menu_node_for), matching the existing pattern used by the IDLE flash-step handler.
wuss_task_destroy freed the task without checking whether the live menu chain (or an in-flight pick flash) still recorded it as owner. A later row pick or flash completion would then call wuss__deliver on the freed task through that dangling owner pointer. wuss_destroy already closes menu_chain before freeing tasks during whole-manager teardown; add the equivalent check to the single-task path, closing the chain first when this task is its owner.
…own early wuss__menu_close_from froze every chain node unconditionally without finishing an in-flight pick flash first, so a click outside the menu (or another wuss_menu_open/close) landing during the highlight flash silently dropped the MENU_SELECT the flash was standing in for. Deliver the flash's captured MENU_SELECT directly here instead of going through wuss__menu_flash_finish, whose own chain teardown would race the caller's (the chain is already unlinked from wuss->menu_chain by the time close_from runs).
…a menu row wuss__menu_open_window moved the window to the submenu anchor before checking wuss_window_set_hidden's PRE_SHOW veto result. On veto only the menu node was freed; the window itself was left permanently repositioned at the anchor even though it never became visible, so its next legitimate show rendered in the wrong place. Capture the window's prior position first and restore it on veto.
wuss_destroy freed each leftover task's block directly, skipping the QUIT event. Per the task_data-ownership contract in include/wuss/task.h a task's client-owned allocations are freed only from its QUIT handler, so a still-registered task (e.g. a menu-spawned demo window not yet closed) leaked its task_data on whole-manager teardown. Deliver QUIT before freeing each task block; windows are already gone by this point in the sweep, so QUIT handlers see the same view they would from wuss_task_destroy. Add wuss__task_from_link alongside the existing wuss__window_from_link, naming the cast rather than doing it inline.
…happen extract_advance_widths's end-of-row bounds check ran after the row's pixel reads, including the per-character refill inside the inner loop still marked with a TODO. A font PNG whose width isn't an exact multiple of CHARS_PER_ROW produces a truncating gridwidth, so the inner loop can iterate past the row's -- or the buffer's -- last valid word before any check catches it. Add an explicit end-of-buffer pointer and check it at both read sites, before the read rather than after.
… in release scr->span is NULL when screen_init's pixelfmt_t has no span-registry entry; the assert guarding the fill dispatch is compiled out under NDEBUG, so a release build crashed on the NULL dereference instead of getting the old hand-rolled switch's safe do-nothing default. screen_fill_rect calls screen_fill_hline per row, so this covers it too.
fonts[3]/descs[3] and the font-loading loop bound were three separate hardcoded literal 3s, unrelated to wuss_MAX_FONTS (4) and to each other; wuss_create's nfonts-vs-wuss_MAX_FONTS validation runs only after both arrays are already fully populated in the caller, so it can't protect against them. Introduce WUSS_MAIN_NFONTS, used for both array sizes and the loop bound, with a compile-time check against wuss_MAX_FONTS.
resources/bmfonts/digits.png was renamed/split into Digits-Regular.png and Digits-Bold.png; the doc's example image still pointed at the old deleted path.
The header comment still named the old libraries/wuss/get-font.c path with a generic blurb; other files moved into core/ in the same diff got their headers updated, this one didn't.
Pages workflow only triggered on master; add develop so the emscripten demo deploys on develop branch pushes as well.
Covers the Emscripten/wasm build and Pages deploy, shared fontmenu/colourmenu components, menu/icon polish, and the wuss_create font-array and fontmenu allocator breaking changes, plus the QUIT-delivery, menu UAF, bmfont bounds and screen_fill_hline fixes landed since.
SharedCLibrary (-mlibscl) has no opendir/readdir/closedir, so bmfont_enumerate() failed to link on RISC OS. Add a TARGET_RISCOS branch using OSLib's xosgbpb_dir_entries instead, and actually define TARGET_RISCOS as a compiler macro (it was only a CMake-scope variable before, so the #ifdef was dead code). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The fixture dir gained Symbols.png but the test's hardcoded MAXFONTS stayed at 8, so bmfont_enumerate_test failed. Symbols can't just join bmfonts[] though: the clipping/layout tests draw Latin lorem_ipsum through every font in that list, and Symbols' small glyph table overflows on ASCII input. Split into bmfonts[] (8, Latin-drawing tests) and a separate bmfonts_enum_extra[] checked only by the enumerate test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
git's index had daydream/gliderrider/henry/tiny.png in lowercase while the working files on disk are Daydream/GliderRider/Henry/Tiny.png. macOS's case-insensitive filesystem hid the mismatch locally, but a case-sensitive Linux checkout (e.g. the Ubuntu release build) sees the lowercase names, bmfont_enumerate's names don't match wuss-test's expected font names, and the fontmenu check fails. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
wuss_test still referenced the fixture as "tiny.png"; after the bmfonts case-fix commit the file is Tiny.png, so on case-sensitive filesystems bmfont_create fails and left 'font' uninitialised. The FlashFail unwind path then called bmfont_destroy on that garbage pointer -- segfault on Linux CI, masked on macOS's case-insensitive filesystem. Also init font = NULL at declaration so any future load failure hits a safe no-op free instead of relying on every call site remembering to set it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
MSVC has no dirent.h. Enumerate via FindFirstFileA/FindNextFileA instead of opendir/readdir on that toolchain; POSIX and RISC OS paths unchanged. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
wuss_BACKDROP_COLOUR already casts its compound literal to wuss_backdrop_t; casting the macro result again gave MSVC C2440 (cannot convert type to itself). GCC/Clang tolerated it, MSVC did not. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.