From 00e84f90a4c7a4eba7465939da948e2ed0b70324 Mon Sep 17 00:00:00 2001 From: David Thomas Date: Sun, 30 Aug 2026 22:29:21 +0100 Subject: [PATCH 01/79] fix(wuss): clamp scroll offset after a resize reveals past-extent content Enlarging a window's viewport (or growing it past the document extent) could scroll content that no longer exists into view. wuss_window_resize now re-clamps window->scroll via wuss__scroll_clamp after recomputing the visible box; if the offset moved, the whole content box (and the scrollbar well) is invalidated, since the grown/shrunk-sliver logic assumes an unchanged interior. Co-Authored-By: Claude Sonnet 5 --- libraries/wuss/window/resize.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/libraries/wuss/window/resize.c b/libraries/wuss/window/resize.c index bcaca69..1e95d90 100644 --- a/libraries/wuss/window/resize.c +++ b/libraries/wuss/window/resize.c @@ -35,6 +35,7 @@ result_t wuss_window_resize(wuss_window_t *window, size2d_t size) int outline_px, titlebar_height; box_t before; point_t carve; + point_t clamped; if (!wuss__size_ok(size.w, size.h)) return result_WUSS_TOO_SMALL; @@ -52,6 +53,24 @@ result_t wuss_window_resize(wuss_window_t *window, size2d_t size) wuss__notify_open(window); + /* Enlarging the viewport (or growing it past the doc extent) can scroll + * content that no longer exists into view; pull the offset back so only + * the document extent is ever shown. The interior has moved relative to + * the window, so the whole content box needs repainting -- the + * grown/shrunk-sliver logic below assumes an unchanged interior. */ + clamped = wuss__scroll_clamp(window, window->scroll); + if (clamped.x != window->scroll.x || clamped.y != window->scroll.y) + { + box_t content; + + window->scroll = clamped; + wuss__content_box(window, &content); + wuss__invalidate_clipped(window, &content); +#ifdef WUSS_FURNITURE + wuss__furniture_invalidate(window); +#endif + } + if (window->flags & wuss_WINDOW_NO_RESIZE_BLIT) { /* This task's content isn't just anchored positions plus furniture -- From dbcb8113aa699c857cc3ad2249f5054247944516 Mon Sep 17 00:00:00 2001 From: David Thomas Date: Sun, 30 Aug 2026 23:02:55 +0100 Subject: [PATCH 02/79] feat(wuss): allow the desktop and window backgrounds to be a pattern fill Introduces wuss_backdrop_t ({ colour, pattern, pattern_bg }), replacing the bare wuss_colour_t for wuss_config_t::backdrop and for the bg parameter of wuss_window_create(), wuss_window_create_placed() and wuss_window_set_background(). A non-SOLID pattern is tiled with screen_fill_pattern(): the desktop phased to the screen origin, a window's content phased to its scroll origin so the fill stays locked to the content. wuss__validate_backdrop() and wuss__fill_backdrop() are shared out to a new backdrop.c. wuss_BACKDROP_COLOUR() / wuss_BACKDROP_PATTERN() build the struct as a compound literal; the flat-colour case is wuss_BACKDROP_COLOUR(old_value). The icons test task now uses a CROSSHATCH pattern backdrop instead of drawing its own grid. A transparent LABEL icon over a patterned window backdrop blends its glyphs against the pattern's background colour, not the foreground. Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 9 +++ CMakeLists.txt | 1 + docs/windowing/wuss.md | 8 ++- include/wuss/window.h | 37 ++++++----- include/wuss/wuss.h | 50 ++++++++++++-- libraries/wuss/backdrop.c | 43 +++++++++++++ libraries/wuss/create.c | 28 ++++---- libraries/wuss/icon/draw.c | 8 ++- libraries/wuss/impl.h | 21 +++++- libraries/wuss/redraw.c | 27 +++----- libraries/wuss/test/tasks/ball.c | 2 +- libraries/wuss/test/tasks/blank.c | 5 +- libraries/wuss/test/tasks/chars.c | 2 +- libraries/wuss/test/tasks/checker.c | 4 +- libraries/wuss/test/tasks/curve.c | 2 +- libraries/wuss/test/tasks/gradient.c | 2 +- libraries/wuss/test/tasks/icons.c | 18 ++---- libraries/wuss/test/tasks/icons.h | 1 - libraries/wuss/test/tasks/image.c | 2 +- libraries/wuss/test/tasks/launcher.c | 2 +- libraries/wuss/test/tasks/palette.c | 2 +- libraries/wuss/test/tasks/porter-duff.c | 2 +- libraries/wuss/test/tasks/sofa.c | 2 +- libraries/wuss/test/tasks/text.c | 2 +- libraries/wuss/test/wuss-test.c | 86 +++++++++++++------------ libraries/wuss/window/create-placed.c | 2 +- libraries/wuss/window/create.c | 4 +- libraries/wuss/window/set-background.c | 4 +- 28 files changed, 244 insertions(+), 132 deletions(-) create mode 100644 libraries/wuss/backdrop.c diff --git a/CHANGELOG.md b/CHANGELOG.md index cc9b7fb..22d033b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,15 @@ _Unreleased_ until one is cut. ### Changed +- **Breaking:** the window/desktop background is now a `wuss_backdrop_t` + (`{ colour, pattern, pattern_bg }`) instead of a bare `wuss_colour_t`: + `wuss_config_t::backdrop`, and the `bg` parameter of + `wuss_window_create()`, `wuss_window_create_placed()` and + `wuss_window_set_background()`. A non-`screen_PATTERN_SOLID` `pattern` fills + with `screen_fill_pattern()` — the desktop phased to the screen origin, a + window's content phased to its scroll origin so the pattern stays locked to + the content. `wuss_BACKDROP_COLOUR(c)` and `wuss_BACKDROP_PATTERN(c, p, b)` + build one; the flat-colour case is `wuss_BACKDROP_COLOUR(old_value)`. - **Breaking:** `wuss_button_t` values are now flags (`wuss_BUTTON_SELECT` 4, `wuss_BUTTON_MENU` 2, `wuss_BUTTON_ADJUST` 1, `wuss_BUTTON_NONE` 0) so chords such as Select+Adjust can be reported. Client code comparing a diff --git a/CMakeLists.txt b/CMakeLists.txt index a322d52..1df7a23 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -326,6 +326,7 @@ set(UTILS_SOURCES libraries/utils/primes/primes.c) set(WUSS_CORE_SOURCES + libraries/wuss/backdrop.c libraries/wuss/create.c libraries/wuss/destroy.c libraries/wuss/get-font.c diff --git a/docs/windowing/wuss.md b/docs/windowing/wuss.md index 0871dd5..a90913d 100644 --- a/docs/windowing/wuss.md +++ b/docs/windowing/wuss.md @@ -20,7 +20,9 @@ result_t wuss_create(screen_t *scr, wuss_t **wuss); ``` -`font` and `palette` may both be NULL, for unlabelled titlebars and a built-in default palette respectively. `config` may be NULL for default titlebar height/colours. `config->backdrop` sets a desktop background colour painted behind windows on every redraw, or `wuss_NO_BACKGROUND` (the default when `config` is NULL) to leave the background untouched and require the caller to repaint it itself. `wuss_get_font` reads back the font passed in (or NULL), for a task that wants to draw its own content in the same face as titlebars. +`font` and `palette` may both be NULL, for unlabelled titlebars and a built-in default palette respectively. `config` may be NULL for default titlebar height/colours. `config->backdrop` is a `wuss_backdrop_t` — a flat colour, or an 8x8 fill pattern — painted behind windows on every redraw; set its `colour` to `wuss_NO_BACKGROUND` (the default when `config` is NULL) to leave the background untouched and require the caller to repaint it itself. A non-`screen_PATTERN_SOLID` `pattern` tiles that pattern in `colour` over `pattern_bg`, phased to a fixed screen origin so it does not crawl between full and dirty-region redraws. `wuss_get_font` reads back the font passed in (or NULL), for a task that wants to draw its own content in the same face as titlebars. + +A `wuss_backdrop_t` is `{ colour, pattern, pattern_bg }`; the `wuss_BACKDROP_COLOUR(c)` and `wuss_BACKDROP_PATTERN(c, p, b)` macros build one as a compound literal. Destroy with `wuss_destroy`, which also destroys any windows still open on it. @@ -30,13 +32,13 @@ Create a window with a content bounding box, optional title, appearance flags, a ```C result_t wuss_window_create(wuss_t *wuss, const box_t *content, const char *title, - wuss_window_flags_t flags, wuss_colour_t bg, + wuss_window_flags_t flags, wuss_backdrop_t bg, const wuss_task_t *task, size2d_t doc, size2d_t min_doc, wuss_window_t **window); ``` -`bg` is filled in by wuss before each redraw event, or `wuss_NO_BACKGROUND` for the task to draw its own background (avoids a redundant fill behind an opaque task); changeable later via `wuss_window_set_background`. +`bg` is a `wuss_backdrop_t` (flat colour or 8x8 pattern, as for `config->backdrop`), filled in by wuss before each redraw event; set its `colour` to `wuss_NO_BACKGROUND` for the task to draw its own background (avoids a redundant fill behind an opaque task). Any pattern is phased to the window's scroll origin so it stays locked to the content as the window scrolls. Changeable later via `wuss_window_set_background`, which also takes a `wuss_backdrop_t`. `doc` is the virtual document extent behind the horizontal/vertical scrollbars' sausage proportion; pass `content`'s own width/height for a window with nothing to scroll. It is also the ceiling a resize-drag or toggle-size grows the content area to. Set once at creation, immutable thereafter. diff --git a/include/wuss/window.h b/include/wuss/window.h index efdc746..601e51e 100644 --- a/include/wuss/window.h +++ b/include/wuss/window.h @@ -47,10 +47,13 @@ extern "C" * \param[in] flags Appearance flags, e.g. wuss_WINDOW_NO_TITLEBAR / * wuss_WINDOW_NO_OUTLINE, OR'd together, or * wuss_WINDOW_NONE for the default furniture. - * \param[in] bg Content background, filled by Wuss before each redraw, or - * wuss_NO_BACKGROUND for the task to draw its own - * background (avoids a redundant fill behind an opaque - * task). Changeable later via wuss_window_set_background. + * \param[in] bg Content background, filled by Wuss before each redraw: a + * flat colour or an 8x8 fill pattern (see wuss_backdrop_t). + * Set its colour to wuss_NO_BACKGROUND for the task to draw + * its own background (avoids a redundant fill behind an + * opaque task). Any pattern is phased to the window's + * scroll origin so it stays locked to the content. + * Changeable later via wuss_window_set_background. * \param[in] task Content delegate. Copied in. May be NULL for a window * with no content handling. * \param[in] doc Virtual document extent, for the scrollbars' sausage @@ -63,14 +66,15 @@ extern "C" * window unusably small or larger than its document. * \param[out] window Newly created window. Becomes the topmost window. * \return \ref result_OK on success, \ref result_WUSS_TOO_SMALL if content's - * width or height is not positive, \ref result_WUSS_BAD_COLOUR if bg is - * out of range for the palette, or another appropriate result code. + * width or height is not positive, \ref result_WUSS_BAD_COLOUR if any + * of bg's colours are out of range for the palette, or another + * appropriate result code. */ result_t wuss_window_create(wuss_t *wuss, const box_t *content, const char *title, wuss_window_flags_t flags, - wuss_colour_t bg, + wuss_backdrop_t bg, const wuss_task_t *task, size2d_t doc, size2d_t min_doc, @@ -109,7 +113,7 @@ result_t wuss_window_create_placed(wuss_t *wuss, size2d_t size, const char *title, wuss_window_flags_t flags, - wuss_colour_t bg, + wuss_backdrop_t bg, const wuss_task_t *task, size2d_t doc, size2d_t min_doc, @@ -208,17 +212,18 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p); void wuss_window_get_scroll(const wuss_window_t *window, point_t *p); /** - * Change a window's background colour, invalidating its content area so the - * next redraw picks up the new fill. + * Change a window's background, invalidating its content area so the next + * redraw picks up the new fill. * * \param[in] window Window to change. - * \param[in] bg New content background, as an index into the system - * palette, or wuss_NO_BACKGROUND to hand background painting - * back to the task. - * \return \ref result_OK on success, \ref result_WUSS_BAD_COLOUR if bg is out - * of range for the palette. + * \param[in] bg New content background: a flat colour or an 8x8 fill + * pattern (see wuss_backdrop_t). Set its colour to + * wuss_NO_BACKGROUND to hand background painting back to the + * task. + * \return \ref result_OK on success, \ref result_WUSS_BAD_COLOUR if any of bg's + * colours are out of range for the palette. */ -result_t wuss_window_set_background(wuss_window_t *window, wuss_colour_t bg); +result_t wuss_window_set_background(wuss_window_t *window, wuss_backdrop_t bg); #ifdef __cplusplus } diff --git a/include/wuss/wuss.h b/include/wuss/wuss.h index de8d74b..14b4bd6 100644 --- a/include/wuss/wuss.h +++ b/include/wuss/wuss.h @@ -18,6 +18,7 @@ extern "C" #include "base/result.h" #include "framebuf/bmfont.h" +#include "framebuf/screen.h" #include "geom/box.h" #include "geom/point.h" #include "geom/size.h" @@ -176,12 +177,51 @@ typedef enum wuss_zorder } wuss_zorder_t; +/** + * Desktop background specification: a flat colour, or an 8x8 fill pattern. Used + * by wuss_config_t. Always honoured regardless of the WUSS_FURNITURE / + * WUSS_ICONS options. + */ +typedef struct wuss_backdrop +{ + /** + * Fill colour, or wuss_NO_BACKGROUND to leave the background untouched (the + * caller must then repaint it itself before wuss_redraw / wuss_redraw_dirty). + * When pattern is not screen_PATTERN_SOLID this is the pattern's foreground + * (set-bit) colour. + */ + wuss_colour_t colour; + + /** + * Fill pattern. screen_PATTERN_SOLID (the default) fills flat in colour; any + * other value tiles that pattern in colour over pattern_bg, phased to a fixed + * screen origin so it stays put across dirty-region redraws. Ignored when + * colour is wuss_NO_BACKGROUND. + */ + screen_pattern_t pattern; + + /** Pattern background (clear-bit) colour; used only when pattern is not + * screen_PATTERN_SOLID. */ + wuss_colour_t pattern_bg; +} +wuss_backdrop_t; + +/** A flat-colour wuss_backdrop_t (or wuss_NO_BACKGROUND for none), as a + * compound literal -- the common case where no fill pattern is wanted. */ +#define wuss_BACKDROP_COLOUR(c) \ + ((wuss_backdrop_t) { (c), screen_PATTERN_SOLID, wuss_NO_BACKGROUND }) + +/** A patterned wuss_backdrop_t: 8x8 pattern p tiled in colour c over + * background colour b. */ +#define wuss_BACKDROP_PATTERN(c, p, b) ((wuss_backdrop_t) { (c), (p), (b) }) + /** * Optional creation-time configuration. * * \note titlebar_height and palette are ignored when the library is built with * WUSS_FURNITURE off; bevel is ignored when built with both - * WUSS_FURNITURE and WUSS_ICONS off. backdrop is always honoured. + * WUSS_FURNITURE and WUSS_ICONS off. backdrop is always honoured. See the + * backdrop sub-struct for its own notes. */ typedef struct wuss_config { @@ -207,12 +247,8 @@ typedef struct wuss_config } bevel; - /** - * Desktop background colour, painted behind windows on every redraw, or - * wuss_NO_BACKGROUND to leave the background untouched (the caller must then - * repaint it itself before wuss_redraw / wuss_redraw_dirty). - */ - wuss_colour_t backdrop; + /** Desktop background, painted behind windows on every redraw. */ + wuss_backdrop_t backdrop; } wuss_config_t; diff --git a/libraries/wuss/backdrop.c b/libraries/wuss/backdrop.c new file mode 100644 index 0000000..7da138f --- /dev/null +++ b/libraries/wuss/backdrop.c @@ -0,0 +1,43 @@ +/* backdrop.c -- wuss - shared backdrop validation and fill */ + +#include "framebuf/screen.h" + +#include "impl.h" + +result_t wuss__validate_backdrop(const wuss_t *wuss, + const wuss_backdrop_t *backdrop) +{ + if (backdrop->colour == wuss_NO_BACKGROUND) + return result_OK; + + if (backdrop->colour < 0 || backdrop->colour >= wuss->npalette) + return result_WUSS_BAD_COLOUR; + + if (backdrop->pattern < 0 || backdrop->pattern >= screen_PATTERN__LIMIT) + return result_WUSS_BAD_COLOUR; + + if (backdrop->pattern != screen_PATTERN_SOLID && + (backdrop->pattern_bg < 0 || backdrop->pattern_bg >= wuss->npalette)) + return result_WUSS_BAD_COLOUR; + + return result_OK; +} + +void wuss__fill_backdrop(screen_t *scr, + const colour_t *palette, + const wuss_backdrop_t *backdrop, + const box_t *area, + int origin_x, + int origin_y) +{ + if (backdrop->colour == wuss_NO_BACKGROUND) + return; + + if (backdrop->pattern == screen_PATTERN_SOLID) + screen_draw_rect(scr, area->x0, area->y0, box_size(area), + palette[backdrop->colour]); + else + screen_fill_pattern(scr, area, backdrop->pattern, origin_x, origin_y, + palette[backdrop->colour], + palette[backdrop->pattern_bg]); +} diff --git a/libraries/wuss/create.c b/libraries/wuss/create.c index 086dd58..3553ef4 100644 --- a/libraries/wuss/create.c +++ b/libraries/wuss/create.c @@ -14,17 +14,16 @@ #include "impl.h" #if defined(WUSS_FURNITURE) || defined(WUSS_ICONS) -/* Range-check the bevel colours and (when set) the backdrop against the - * palette. Shared by the furniture and icons-only paths so the accepted - * range, the error code and the freed-pointer set stay in one place. */ +/* Range-check the bevel colours and the backdrop against the palette. Shared + * by the furniture and icons-only paths so the accepted range, the error code + * and the freed-pointer set stay in one place. */ static result_t validate_bevel_backdrop(const wuss_t *w, wuss_colour_t blight, wuss_colour_t bdark) { if (blight < 0 || blight >= w->npalette || bdark < 0 || bdark >= w->npalette || - (w->backdrop != wuss_NO_BACKGROUND && - (w->backdrop < 0 || w->backdrop >= w->npalette))) + wuss__validate_backdrop(w, &w->backdrop) != result_OK) return result_WUSS_BAD_COLOUR; return result_OK; @@ -87,18 +86,26 @@ result_t wuss_create(screen_t *scr, w->npalette = palette_PICO8__LENGTH; } + if (config != NULL) + { + w->backdrop = config->backdrop; + } + else + { + w->backdrop.colour = wuss_NO_BACKGROUND; + w->backdrop.pattern = screen_PATTERN_SOLID; + w->backdrop.pattern_bg = wuss_NO_BACKGROUND; + } + #ifdef WUSS_FURNITURE if (config != NULL) { pal = config->palette; blight = config->bevel.light; bdark = config->bevel.dark; - w->backdrop = config->backdrop; } else { - w->backdrop = wuss_NO_BACKGROUND; - if (palette == NULL) { bg = palette_PICO8_DARK_BLUE; @@ -159,8 +166,6 @@ result_t wuss_create(screen_t *scr, w->titlebar_height = WUSS_DEFAULT_TITLEBAR_HEIGHT; } #else /* !WUSS_FURNITURE */ - w->backdrop = (config != NULL) ? config->backdrop : wuss_NO_BACKGROUND; - #ifdef WUSS_ICONS if (config != NULL) { @@ -181,8 +186,7 @@ result_t wuss_create(screen_t *scr, w->bevel_light = blight; w->bevel_dark = bdark; #else - if (w->backdrop != wuss_NO_BACKGROUND && - (w->backdrop < 0 || w->backdrop >= w->npalette)) + if (wuss__validate_backdrop(w, &w->backdrop) != result_OK) { free(w->palette); free(w); diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index 2a06be2..e6102ee 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -88,9 +88,13 @@ void wuss__icon_draw(wuss_t *wuss, screen_draw_rect(scr, b.x0, b.y0, SIZE2D(b.x1 - b.x0, b.y1 - b.y0), bg); } - else if (icon->window->bg != wuss_NO_BACKGROUND) + else if (icon->window->bg.colour != wuss_NO_BACKGROUND) { - bg = wuss->palette[icon->window->bg]; + /* blend against the dominant backdrop colour: for a pattern fill + * that's its background (clear-bit) colour, not the foreground */ + bg = (icon->window->bg.pattern != screen_PATTERN_SOLID) + ? wuss->palette[icon->window->bg.pattern_bg] + : wuss->palette[icon->window->bg.colour]; } else { diff --git a/libraries/wuss/impl.h b/libraries/wuss/impl.h index fa8f52d..5982e9e 100644 --- a/libraries/wuss/impl.h +++ b/libraries/wuss/impl.h @@ -62,7 +62,7 @@ struct wuss wuss_colour_t bevel_light; /* work-area button top/left edge */ wuss_colour_t bevel_dark; /* work-area button bottom/right edge */ #endif - wuss_colour_t backdrop; /* wuss_NO_BACKGROUND for none */ + wuss_backdrop_t backdrop; /* colour==wuss_NO_BACKGROUND: none */ #ifdef WUSS_FURNITURE int titlebar_height; #endif @@ -92,7 +92,7 @@ struct wuss_window box_t visible; /* full on-screen footprint: content expanded * outward by any titlebar/outline furniture */ wuss_task_t task; - wuss_colour_t bg; + wuss_backdrop_t bg; /* content background; colour==wuss_NO_BACKGROUND: none */ wuss_window_flags_t flags; point_t scroll; /* offset into virtual content space of the * content box's top-left; see wuss_window_set_scroll */ @@ -119,6 +119,23 @@ struct wuss_window wuss_window_t *wuss__window_at(wuss_t *wuss, point_t p); +/* Range-check a backdrop spec against the palette: its colour, and -- when a + * non-solid pattern is set -- its pattern index and background colour. + * Returns result_OK or result_WUSS_BAD_COLOUR. */ +result_t wuss__validate_backdrop(const wuss_t *wuss, + const wuss_backdrop_t *backdrop); + +/* Paint "backdrop" into "area" on "scr": a flat fill in the SOLID case, + * otherwise the 8x8 pattern tiled in colour over pattern_bg, phased against + * (origin_x, origin_y). No-op when colour is wuss_NO_BACKGROUND. Caller sets + * scr->clip. */ +void wuss__fill_backdrop(screen_t *scr, + const colour_t *palette, + const wuss_backdrop_t *backdrop, + const box_t *area, + int origin_x, + int origin_y); + /* clamp "desired" to the window's scrollable range; step the current offset * by "delta" and apply it. Core (furniture-independent) -- used by the wheel * and, when built, the scrollbar furniture. */ diff --git a/libraries/wuss/redraw.c b/libraries/wuss/redraw.c index d86e356..aa6463b 100644 --- a/libraries/wuss/redraw.c +++ b/libraries/wuss/redraw.c @@ -34,10 +34,11 @@ static void redraw_window(wuss_t *wuss, { wuss->scr->clip = pieces[i]; - if (win->bg != wuss_NO_BACKGROUND) - screen_draw_rect(wuss->scr, - content.x0, content.y0, box_size(&content), - wuss->palette[win->bg]); + /* phase any pattern against the scroll origin so it locks to the + * content rather than crawling as the window scrolls */ + wuss__fill_backdrop(wuss->scr, wuss->palette, &win->bg, &content, + content.x0 - win->scroll.x, + content.y0 - win->scroll.y); if (win->task.handle != NULL) { @@ -105,12 +106,8 @@ result_t wuss_redraw(wuss_t *wuss) full.x1 = wuss->scr->size.w; full.y1 = wuss->scr->size.h; - if (wuss->backdrop != wuss_NO_BACKGROUND) - { - wuss->scr->clip = full; - screen_draw_rect(wuss->scr, full.x0, full.y0, box_size(&full), - wuss->palette[wuss->backdrop]); - } + wuss->scr->clip = full; + wuss__fill_backdrop(wuss->scr, wuss->palette, &wuss->backdrop, &full, 0, 0); rc = result_OK; redraw_from(wuss, wuss->z_order.next, &full, &rc); @@ -137,13 +134,9 @@ result_t wuss_redraw_dirty(wuss_t *wuss) rc = result_OK; for (i = 0; i < wuss->ndirty; i++) { - if (wuss->backdrop != wuss_NO_BACKGROUND) - { - wuss->scr->clip = wuss->dirty[i]; - screen_draw_rect(wuss->scr, - wuss->dirty[i].x0, wuss->dirty[i].y0, SIZE2D(wuss->dirty[i].x1 - wuss->dirty[i].x0, wuss->dirty[i].y1 - wuss->dirty[i].y0), - wuss->palette[wuss->backdrop]); - } + wuss->scr->clip = wuss->dirty[i]; + wuss__fill_backdrop(wuss->scr, wuss->palette, &wuss->backdrop, + &wuss->dirty[i], 0, 0); redraw_from(wuss, wuss->z_order.next, &wuss->dirty[i], &rc); } diff --git a/libraries/wuss/test/tasks/ball.c b/libraries/wuss/test/tasks/ball.c index 2ee7a42..7c77ab6 100644 --- a/libraries/wuss/test/tasks/ball.c +++ b/libraries/wuss/test/tasks/ball.c @@ -34,7 +34,7 @@ result_t ball_create(wuss_t *wuss, const colour_t *palette, ball_task_t *task) SIZE2D(200, 160), "Bouncing Ball", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate, SIZE2D(200, 160), SIZE2D(0, 0), diff --git a/libraries/wuss/test/tasks/blank.c b/libraries/wuss/test/tasks/blank.c index 603bec2..b54fa4c 100644 --- a/libraries/wuss/test/tasks/blank.c +++ b/libraries/wuss/test/tasks/blank.c @@ -31,7 +31,7 @@ result_t blank_create(wuss_t *wuss, int npalette, blank_task_t *task) SIZE2D(200, 160), NULL, wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE, - palette_PICO8_GREEN, + wuss_BACKDROP_COLOUR(palette_PICO8_GREEN), &delegate, SIZE2D(200, 160), SIZE2D(0, 0), @@ -51,7 +51,8 @@ static result_t blank_idle(void *task_data) bc->frame_count = 0; bc->index = (bc->index + 1) % bc->npalette; - rc = wuss_window_set_background(bc->window, bc->index); + rc = wuss_window_set_background(bc->window, + wuss_BACKDROP_COLOUR(bc->index)); if (rc != result_OK) logf_warning("blank_idle: wuss_window_set_background(%d) failed", bc->index); diff --git a/libraries/wuss/test/tasks/chars.c b/libraries/wuss/test/tasks/chars.c index 1680d00..9a4b155 100644 --- a/libraries/wuss/test/tasks/chars.c +++ b/libraries/wuss/test/tasks/chars.c @@ -55,7 +55,7 @@ result_t chars_create(wuss_t *wuss, wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate, sz, SIZE2D(0, 0), diff --git a/libraries/wuss/test/tasks/checker.c b/libraries/wuss/test/tasks/checker.c index cecbbc4..00d8949 100644 --- a/libraries/wuss/test/tasks/checker.c +++ b/libraries/wuss/test/tasks/checker.c @@ -38,7 +38,7 @@ result_t checker_create(wuss_t *wuss, SIZE2D(160, 160), "Checker 1", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate, SIZE2D(160, 160), SIZE2D(0, 0), @@ -50,7 +50,7 @@ result_t checker_create(wuss_t *wuss, SIZE2D(160, 160), "Checker 2", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate, SIZE2D(160, 160), SIZE2D(0, 0), diff --git a/libraries/wuss/test/tasks/curve.c b/libraries/wuss/test/tasks/curve.c index 09456f0..74248e3 100644 --- a/libraries/wuss/test/tasks/curve.c +++ b/libraries/wuss/test/tasks/curve.c @@ -46,7 +46,7 @@ result_t curve_create(wuss_t *wuss, SIZE2D(220, 160), "Curve", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate, SIZE2D(220, 160), SIZE2D(0, 0), diff --git a/libraries/wuss/test/tasks/gradient.c b/libraries/wuss/test/tasks/gradient.c index 0602325..6f2d8e6 100644 --- a/libraries/wuss/test/tasks/gradient.c +++ b/libraries/wuss/test/tasks/gradient.c @@ -43,7 +43,7 @@ result_t gradient_create(wuss_t *wuss, gradient_task_t *task) SIZE2D(GRADIENT_OPEN_WIDTH, GRADIENT_OPEN_HEIGHT), "Gradient", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate, SIZE2D(GRADIENT_DOC_WIDTH, GRADIENT_DOC_HEIGHT), SIZE2D(0, 0), diff --git a/libraries/wuss/test/tasks/icons.c b/libraries/wuss/test/tasks/icons.c index a995335..2e8084a 100644 --- a/libraries/wuss/test/tasks/icons.c +++ b/libraries/wuss/test/tasks/icons.c @@ -35,7 +35,6 @@ result_t icons_create(wuss_t *wuss, result_t rc; task->font = font; - task->ink = palette[palette_PICO8_LAVENDER]; task->label = palette[palette_PICO8_DARK_BLUE]; task->paper = palette[palette_PICO8_LIGHT_GREY]; /* the window bg, below */ task->window = NULL; @@ -49,7 +48,9 @@ result_t icons_create(wuss_t *wuss, SIZE2D(ICONS_DOC_W, 160), "Icons", wuss_WINDOW_NONE, - palette_PICO8_LIGHT_GREY, + wuss_BACKDROP_PATTERN(palette_PICO8_LAVENDER, + screen_PATTERN_CROSSHATCH, + palette_PICO8_LIGHT_GREY), &delegate, SIZE2D(ICONS_DOC_W, ICONS_DOC_H), SIZE2D(0, 0), @@ -165,16 +166,9 @@ static result_t icons_redraw(const wuss_event_t *event, void *task_data) * blit assumes. Nothing here is pinned to a window edge. Every draw is * clipped to the dirty rectangle (content) so partial redraws stay cheap. */ - /* a faint grid across the whole work area */ - for (x = icons_grid_first(bounds->x0, scroll.x, content->x0); - x < content->x1; - x += ICONS_GRID) - screen_draw_line(scr, x, content->y0, x, content->y1 - 1, tcx->ink); - - for (y = icons_grid_first(bounds->y0, scroll.y, content->y0); - y < content->y1; - y += ICONS_GRID) - screen_draw_line(scr, content->x0, y, content->x1 - 1, y, tcx->ink); + /* the faint crosshatch backdrop is now the window's own bg (a + * wuss_backdrop_t pattern fill), painted by Wuss before this event and + * phase-locked to the scroll origin -- nothing to draw here */ /* x-axis ruler: document x printed just below the y=0 line, at each * labelled grid column. Scrolls with the document like the grid. */ diff --git a/libraries/wuss/test/tasks/icons.h b/libraries/wuss/test/tasks/icons.h index aebf0ea..9e9fb82 100644 --- a/libraries/wuss/test/tasks/icons.h +++ b/libraries/wuss/test/tasks/icons.h @@ -17,7 +17,6 @@ typedef struct icons_task { wuss_window_t *window; bmfont_t *font; - colour_t ink; /* grid lines */ colour_t label; /* axis coordinate text */ colour_t paper; /* window bg, for bmfont_draw glyph blending */ wuss_icon_t *button; /* "Press me" */ diff --git a/libraries/wuss/test/tasks/image.c b/libraries/wuss/test/tasks/image.c index 943836e..3ef7f76 100644 --- a/libraries/wuss/test/tasks/image.c +++ b/libraries/wuss/test/tasks/image.c @@ -47,7 +47,7 @@ result_t image_create(wuss_t *wuss, SIZE2D(sz.w, sz.h * 2 / 3), "Image", wuss_WINDOW_NONE, - palette_PICO8_PINK, + wuss_BACKDROP_COLOUR(palette_PICO8_PINK), &delegate, sz, SIZE2D(32, 32), diff --git a/libraries/wuss/test/tasks/launcher.c b/libraries/wuss/test/tasks/launcher.c index d01b565..5cf882d 100644 --- a/libraries/wuss/test/tasks/launcher.c +++ b/libraries/wuss/test/tasks/launcher.c @@ -47,7 +47,7 @@ result_t launcher_create(wuss_t *wuss, sz, "Launcher", wuss_WINDOW_NO_CLOSE, - palette_PICO8_LIGHT_GREY, + wuss_BACKDROP_COLOUR(palette_PICO8_LIGHT_GREY), &delegate, sz, SIZE2D(0, 0), diff --git a/libraries/wuss/test/tasks/palette.c b/libraries/wuss/test/tasks/palette.c index dfcc2ee..ac7da4e 100644 --- a/libraries/wuss/test/tasks/palette.c +++ b/libraries/wuss/test/tasks/palette.c @@ -30,7 +30,7 @@ result_t palette_create(wuss_t *wuss, SIZE2D(100, 100), "Palette", wuss_WINDOW_NO_RESIZE_BLIT, /* swatch grid is laid out across the whole window, so a resize must redraw all of it, not just the newly (un)covered edge */ - palette_PICO8_BLACK, + wuss_BACKDROP_COLOUR(palette_PICO8_BLACK), &delegate, SIZE2D(100, 100), SIZE2D(0, 0), diff --git a/libraries/wuss/test/tasks/porter-duff.c b/libraries/wuss/test/tasks/porter-duff.c index 14fc1dc..525db42 100644 --- a/libraries/wuss/test/tasks/porter-duff.c +++ b/libraries/wuss/test/tasks/porter-duff.c @@ -197,7 +197,7 @@ result_t porter_duff_create(wuss_t *wuss, SIZE2D(PD_SIZE, PD_SIZE + PD_LABEL_HEIGHT), "Porter-Duff", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate, SIZE2D(PD_SIZE, PD_SIZE + PD_LABEL_HEIGHT), SIZE2D(0, 0), diff --git a/libraries/wuss/test/tasks/sofa.c b/libraries/wuss/test/tasks/sofa.c index 28cef25..ab8d951 100644 --- a/libraries/wuss/test/tasks/sofa.c +++ b/libraries/wuss/test/tasks/sofa.c @@ -373,7 +373,7 @@ result_t sofa_create(wuss_t *wuss, const colour_t *palette, sofa_task_t *task) SIZE2D(180, 160), "Sofa", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate, SIZE2D(180, 160), SIZE2D(0, 0), diff --git a/libraries/wuss/test/tasks/text.c b/libraries/wuss/test/tasks/text.c index aa09443..719b96f 100644 --- a/libraries/wuss/test/tasks/text.c +++ b/libraries/wuss/test/tasks/text.c @@ -53,7 +53,7 @@ result_t text_create(wuss_t *wuss, sz, "Lorem Ipsum", wuss_WINDOW_NO_RESIZE_BLIT, /* paragraph reflows across the whole window, so a resize must redraw all of it, not just the newly (un)covered edge */ - palette_PICO8_BLUE, + wuss_BACKDROP_COLOUR(palette_PICO8_BLUE), &delegate, sz, SIZE2D(0, 0), diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index 6017f6f..71f8194 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -354,7 +354,9 @@ static result_t wuss_interactive_test(const char *resources) config.palette.scroll.sausages = palette_PICO8_LIGHT_GREY; config.bevel.light = palette_PICO8_WHITE; config.bevel.dark = palette_PICO8_DARK_GREY; - config.backdrop = palette_PICO8_LIGHT_GREY; + config.backdrop.colour = palette_PICO8_LIGHT_GREY; + config.backdrop.pattern = screen_PATTERN_SOLID; + config.backdrop.pattern_bg = wuss_NO_BACKGROUND; rc = wuss_create(&scr, font, palette, NELEMS(palette), &config, &wuss); if (rc != result_OK) @@ -725,7 +727,7 @@ result_t wuss_test(const char *resources) &box_a, "toosmall", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), NULL, box_size(&box_a), SIZE2D(0, 0), @@ -751,7 +753,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_a, box_size(&box_a), SIZE2D(0, 0), @@ -774,7 +776,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_b, box_size(&box_b), SIZE2D(0, 0), @@ -1060,7 +1062,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_d, box_size(&box_d), SIZE2D(0, 0), @@ -1108,7 +1110,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_e, box_size(&box_e), SIZE2D(0, 0), @@ -1129,7 +1131,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_f, box_size(&box_f), SIZE2D(0, 0), @@ -1165,11 +1167,12 @@ result_t wuss_test(const char *resources) printf("test: wuss_window_set_background\n"); - rc = wuss_window_set_background(win_d, 999); + rc = wuss_window_set_background(win_d, wuss_BACKDROP_COLOUR(999)); if (rc != result_WUSS_BAD_COLOUR) goto Failure; - rc = wuss_window_set_background(win_d, palette_PICO8_ORANGE); + rc = wuss_window_set_background(win_d, + wuss_BACKDROP_COLOUR(palette_PICO8_ORANGE)); if (rc != result_OK) goto Failure; @@ -1197,7 +1200,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_h, box_size(&box_h), SIZE2D(0, 0), @@ -1218,7 +1221,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_g, box_size(&box_g), SIZE2D(0, 0), @@ -1280,7 +1283,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_i, box_size(&box_i), SIZE2D(0, 0), @@ -1301,7 +1304,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_j, box_size(&box_j), SIZE2D(0, 0), @@ -1352,7 +1355,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_m, box_size(&box_m), SIZE2D(0, 0), @@ -1405,7 +1408,7 @@ result_t wuss_test(const char *resources) &box_h, "H", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_h, box_size(&box_h), SIZE2D(0, 0), @@ -1424,7 +1427,7 @@ result_t wuss_test(const char *resources) &box_g, "G", wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_g, box_size(&box_g), SIZE2D(0, 0), @@ -1544,7 +1547,7 @@ result_t wuss_test(const char *resources) box_m.x0 = 10; box_m.y0 = 10; box_m.x1 = 210; box_m.y1 = 210; /* 200x200 content, floored at 80x60 */ rc = wuss_window_create(wuss, &box_m, "M", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_m, SIZE2D(200, 200), SIZE2D(80, 60), &win_m); @@ -1595,7 +1598,7 @@ result_t wuss_test(const char *resources) box_t_win.x0 = 10; box_t_win.y0 = 10; box_t_win.x1 = 50; box_t_win.y1 = 50; /* 40x40 content, room to grow to a 200x200 doc */ rc = wuss_window_create(wuss, &box_t_win, "T", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_t, SIZE2D(200, 200), SIZE2D(0, 0), &win_t); if (rc != result_OK) @@ -1761,7 +1764,7 @@ result_t wuss_test(const char *resources) box_r.x0 = 10; box_r.y0 = 10; box_r.x1 = 50; box_r.y1 = 50; /* 40x40 content; doc bigger than that, so it starts scrollable */ rc = wuss_window_create(wuss, &box_r, "R", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_r, SIZE2D(70, 70), SIZE2D(0, 0), &win_r); if (rc != result_OK) @@ -1859,7 +1862,7 @@ result_t wuss_test(const char *resources) box_nb.x0 = 10; box_nb.y0 = 10; box_nb.x1 = 50; box_nb.y1 = 50; /* 40x40 content, room to grow to a 200x200 doc */ rc = wuss_window_create(wuss, &box_nb, "NB", wuss_WINDOW_NO_RESIZE_BLIT, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_nb, SIZE2D(200, 200), SIZE2D(0, 0), &win_nb); if (rc != result_OK) @@ -1938,7 +1941,8 @@ result_t wuss_test(const char *resources) box_u.x0 = 80; box_u.y0 = 80; box_u.x1 = 120; box_u.y1 = 120; /* 40x40 content */ - rc = wuss_window_create(wuss, &box_u, "U", wuss_WINDOW_NONE, wuss_NO_BACKGROUND, /* scrollbars on: carve.x/y = icon size */ + rc = wuss_window_create(wuss, &box_u, "U", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), /* scrollbars on: carve.x/y = icon size */ &delegate_u, SIZE2D(70, 70), SIZE2D(0, 0), &win_u); /* doc size well within the 200x200 screen: growth is doc-limited, not screen-limited */ if (rc != result_OK) @@ -2046,7 +2050,7 @@ result_t wuss_test(const char *resources) * Doc big enough that maximize is * screen-limited, not doc-limited. */ rc = wuss_window_create(wuss, &box_v, "V", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_v, SIZE2D(200, 200), SIZE2D(0, 0), &win_v); if (rc != result_OK) @@ -2159,7 +2163,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_k, box_size(&box_k), SIZE2D(0, 0), @@ -2180,7 +2184,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_l, box_size(&box_l), SIZE2D(0, 0), @@ -2247,7 +2251,7 @@ result_t wuss_test(const char *resources) "M2", wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_m2, box_size(&box_m2), SIZE2D(0, 0), @@ -2338,7 +2342,7 @@ result_t wuss_test(const char *resources) box_nb2.x0 = 0; box_nb2.y0 = 0; box_nb2.x1 = 40; box_nb2.y1 = 40; rc = wuss_window_create(wuss, &box_nb2, "NB2", wuss_WINDOW_NO_RESIZE_BLIT, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_nb2, box_size(&box_nb2), SIZE2D(0, 0), @@ -2390,7 +2394,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_n, box_size(&box_n), SIZE2D(0, 0), @@ -2409,7 +2413,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_o, box_size(&box_o), SIZE2D(0, 0), @@ -2494,7 +2498,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_b, box_size(&box_b), SIZE2D(0, 0), @@ -2513,7 +2517,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_a, box_size(&box_a), SIZE2D(0, 0), @@ -2597,7 +2601,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_b, box_size(&box_b), SIZE2D(0, 0), @@ -2616,7 +2620,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_a, box_size(&box_a), SIZE2D(0, 0), @@ -2677,7 +2681,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_b, box_size(&box_b), SIZE2D(0, 0), @@ -2696,7 +2700,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_a, box_size(&box_a), SIZE2D(0, 0), @@ -2790,7 +2794,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_b, box_size(&box_b), SIZE2D(0, 0), @@ -2809,7 +2813,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_a, box_size(&box_a), SIZE2D(0, 0), @@ -2886,7 +2890,7 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_c, box_size(&box_c), SIZE2D(0, 0), @@ -2921,7 +2925,7 @@ result_t wuss_test(const char *resources) box_s.x0 = 10; box_s.y0 = 10; box_s.x1 = 60; box_s.y1 = 60; /* 50x50 content onto a 200x200 doc: room to scroll */ rc = wuss_window_create(wuss, &box_s, "S", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_s, SIZE2D(200, 200), SIZE2D(0, 0), &win_s); if (rc != result_OK) @@ -2997,7 +3001,7 @@ result_t wuss_test(const char *resources) &box_r, "rules", wuss_WINDOW_NONE, /* all furniture present */ - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_r, SIZE2D(400, 400), SIZE2D(0, 0), @@ -3042,7 +3046,7 @@ result_t wuss_test(const char *resources) SIZE2D(40, 30), "P", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_p, SIZE2D(40, 30), SIZE2D(0, 0), @@ -3066,7 +3070,7 @@ result_t wuss_test(const char *resources) SIZE2D(40, 30), "P", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_p, SIZE2D(40, 30), SIZE2D(0, 0), diff --git a/libraries/wuss/window/create-placed.c b/libraries/wuss/window/create-placed.c index 6088a28..22ee483 100644 --- a/libraries/wuss/window/create-placed.c +++ b/libraries/wuss/window/create-placed.c @@ -78,7 +78,7 @@ result_t wuss_window_create_placed(wuss_t *wuss, size2d_t size, const char *title, wuss_window_flags_t flags, - wuss_colour_t bg, + wuss_backdrop_t bg, const wuss_task_t *task, size2d_t doc, size2d_t min_doc, diff --git a/libraries/wuss/window/create.c b/libraries/wuss/window/create.c index 11bb445..a738017 100644 --- a/libraries/wuss/window/create.c +++ b/libraries/wuss/window/create.c @@ -14,7 +14,7 @@ result_t wuss_window_create(wuss_t *wuss, const box_t *content, const char *title, wuss_window_flags_t flags, - wuss_colour_t bg, + wuss_backdrop_t bg, const wuss_task_t *task, size2d_t doc, size2d_t min_doc, @@ -94,7 +94,7 @@ result_t wuss_window_create(wuss_t *wuss, else memset(&win->task, 0, sizeof(win->task)); - if (bg != wuss_NO_BACKGROUND && (bg < 0 || bg >= wuss->npalette)) + if (wuss__validate_backdrop(wuss, &bg) != result_OK) { free(win); return result_WUSS_BAD_COLOUR; diff --git a/libraries/wuss/window/set-background.c b/libraries/wuss/window/set-background.c index 8cfe7cb..bd704c4 100644 --- a/libraries/wuss/window/set-background.c +++ b/libraries/wuss/window/set-background.c @@ -2,11 +2,11 @@ #include "../impl.h" -result_t wuss_window_set_background(wuss_window_t *window, wuss_colour_t bg) +result_t wuss_window_set_background(wuss_window_t *window, wuss_backdrop_t bg) { box_t content; - if (bg != wuss_NO_BACKGROUND && (bg < 0 || bg >= window->wuss->npalette)) + if (wuss__validate_backdrop(window->wuss, &bg) != result_OK) return result_WUSS_BAD_COLOUR; window->bg = bg; From a4184246b7b82cad706be1bb4c6abba8cedaf567 Mon Sep 17 00:00:00 2001 From: David Thomas Date: Sun, 30 Aug 2026 23:12:42 +0100 Subject: [PATCH 03/79] fix(wuss): keep a 2px gap at each end of the scrollbar well The sausage could butt right up against the arrow buttons at the scroll extremes. Scale and position it over the well minus a cosmetic end gap at each end, dropped when the well is too short to spare it. drag_sausage maps pointer movement over the same track so dragging stays 1:1. Co-Authored-By: Claude Sonnet 5 --- libraries/wuss/furniture.h | 5 +++-- libraries/wuss/furniture/hscroll-box.c | 22 ++++++++++++++-------- libraries/wuss/furniture/scroll-action.c | 11 ++++++++--- libraries/wuss/furniture/vscroll-box.c | 22 ++++++++++++++-------- 4 files changed, 39 insertions(+), 21 deletions(-) diff --git a/libraries/wuss/furniture.h b/libraries/wuss/furniture.h index 4b5b7bd..1a90f18 100644 --- a/libraries/wuss/furniture.h +++ b/libraries/wuss/furniture.h @@ -8,8 +8,9 @@ #include "wuss/wuss.h" -#define WUSS_MIN_SAUSAGE 6 /* scrollbar sausage never shrinks below this, however small the content fraction */ -#define WUSS_SCROLL_STEP 20 /* pixels stepped per scrollbar arrow click */ +#define WUSS_MIN_SAUSAGE 6 /* scrollbar sausage never shrinks below this, however small the content fraction */ +#define WUSS_SCROLL_END_GAP 2 /* sausage along-axis margin from its well's ends, purely cosmetic */ +#define WUSS_SCROLL_STEP 20 /* pixels stepped per scrollbar arrow click */ /* Which region of a window's border (or its content) a point falls in. */ typedef enum wuss_furniture_region diff --git a/libraries/wuss/furniture/hscroll-box.c b/libraries/wuss/furniture/hscroll-box.c index 31bbdc9..ce452db 100644 --- a/libraries/wuss/furniture/hscroll-box.c +++ b/libraries/wuss/furniture/hscroll-box.c @@ -70,27 +70,33 @@ int wuss__hscroll_well_px(const wuss_window_t *window) void wuss__hscroll_sausage_box(const wuss_window_t *window, box_t *out) { box_t well, content; - int well_px, content_size, doc_size, sausage_px, sausage_x0, inset; + int well_px, track_px, end_gap, content_size, doc_size, sausage_px, sausage_x0, inset; wuss__hscroll_well_box(window, &well); wuss__content_box(window, &content); - well_px = well.x1 - well.x0; + well_px = well.x1 - well.x0; + + /* Leave a cosmetic gap at each end of the well; drop it if the well is + * too short to spare two gaps plus a minimum sausage. */ + end_gap = (well_px > 2 * WUSS_SCROLL_END_GAP + WUSS_MIN_SAUSAGE) ? WUSS_SCROLL_END_GAP : 0; + track_px = well_px - 2 * end_gap; + content_size = content.x1 - content.x0; doc_size = window->doc.w; if (doc_size < content_size) doc_size = content_size; - sausage_px = (doc_size > 0) ? well_px * content_size / doc_size : well_px; + sausage_px = (doc_size > 0) ? track_px * content_size / doc_size : track_px; if (sausage_px < WUSS_MIN_SAUSAGE) sausage_px = WUSS_MIN_SAUSAGE; - if (sausage_px > well_px) - sausage_px = well_px; + if (sausage_px > track_px) + sausage_px = track_px; - if (doc_size > content_size && well_px > sausage_px) - sausage_x0 = well.x0 + (well_px - sausage_px) * window->scroll.x / (doc_size - content_size); + if (doc_size > content_size && track_px > sausage_px) + sausage_x0 = well.x0 + end_gap + (track_px - sausage_px) * window->scroll.x / (doc_size - content_size); else - sausage_x0 = well.x0; + sausage_x0 = well.x0 + end_gap; /* Inset the sausage from the well's long edges, but only while that * leaves something to draw: a tiny titlebar font can make the well diff --git a/libraries/wuss/furniture/scroll-action.c b/libraries/wuss/furniture/scroll-action.c index 6ac7a49..a8965eb 100644 --- a/libraries/wuss/furniture/scroll-action.c +++ b/libraries/wuss/furniture/scroll-action.c @@ -8,7 +8,7 @@ void wuss__furniture_drag_sausage(wuss_window_t *window, int horizontal) { box_t content; - int well_px, content_size, doc_size, max_scroll, new_scroll; + int well_px, track_px, end_gap, content_size, doc_size, max_scroll, new_scroll; wuss__content_box(window, &content); @@ -25,14 +25,19 @@ void wuss__furniture_drag_sausage(wuss_window_t *window, doc_size = window->doc.h; } + /* Sausage travels over the well minus the cosmetic end gaps; must match + * wuss__*scroll_sausage_box so drag tracks the pointer 1:1. */ + end_gap = (well_px > 2 * WUSS_SCROLL_END_GAP + WUSS_MIN_SAUSAGE) ? WUSS_SCROLL_END_GAP : 0; + track_px = well_px - 2 * end_gap; + max_scroll = doc_size - content_size; if (max_scroll < 0) max_scroll = 0; - if (well_px <= 0 || doc_size <= content_size) + if (track_px <= 0 || doc_size <= content_size) new_scroll = 0; else - new_scroll = scroll_start + delta_px * doc_size / well_px; + new_scroll = scroll_start + delta_px * doc_size / track_px; if (new_scroll < 0) new_scroll = 0; diff --git a/libraries/wuss/furniture/vscroll-box.c b/libraries/wuss/furniture/vscroll-box.c index ba873db..f87eda4 100644 --- a/libraries/wuss/furniture/vscroll-box.c +++ b/libraries/wuss/furniture/vscroll-box.c @@ -72,27 +72,33 @@ int wuss__vscroll_well_px(const wuss_window_t *window) void wuss__vscroll_sausage_box(const wuss_window_t *window, box_t *out) { box_t well, content; - int well_px, content_size, doc_size, sausage_px, sausage_y0, inset; + int well_px, track_px, end_gap, content_size, doc_size, sausage_px, sausage_y0, inset; wuss__vscroll_well_box(window, &well); wuss__content_box(window, &content); - well_px = well.y1 - well.y0; + well_px = well.y1 - well.y0; + + /* Leave a cosmetic gap at each end of the well; drop it if the well is + * too short to spare two gaps plus a minimum sausage. */ + end_gap = (well_px > 2 * WUSS_SCROLL_END_GAP + WUSS_MIN_SAUSAGE) ? WUSS_SCROLL_END_GAP : 0; + track_px = well_px - 2 * end_gap; + content_size = content.y1 - content.y0; doc_size = window->doc.h; if (doc_size < content_size) doc_size = content_size; - sausage_px = (doc_size > 0) ? well_px * content_size / doc_size : well_px; + sausage_px = (doc_size > 0) ? track_px * content_size / doc_size : track_px; if (sausage_px < WUSS_MIN_SAUSAGE) sausage_px = WUSS_MIN_SAUSAGE; - if (sausage_px > well_px) - sausage_px = well_px; + if (sausage_px > track_px) + sausage_px = track_px; - if (doc_size > content_size && well_px > sausage_px) - sausage_y0 = well.y0 + (well_px - sausage_px) * window->scroll.y / (doc_size - content_size); + if (doc_size > content_size && track_px > sausage_px) + sausage_y0 = well.y0 + end_gap + (track_px - sausage_px) * window->scroll.y / (doc_size - content_size); else - sausage_y0 = well.y0; + sausage_y0 = well.y0 + end_gap; /* Inset the sausage from the well's long edges, but only while that * leaves something to draw: a tiny titlebar font can make the well From 28023e540cb4062d2cf8ac59b64917625c946ac2 Mon Sep 17 00:00:00 2001 From: David Thomas Date: Sun, 30 Aug 2026 23:39:41 +0100 Subject: [PATCH 04/79] refactor(wuss): drop PICO-8 dependency from the NULL-palette default path wuss_create no longer includes framebuf/palettes.h or assumes a palette length. A NULL palette now falls back to a built-in two-entry black/white palette instead of define_pico8_palette, and the furniture default colours use the generic bg=0, fg=(npalette>1)?1:0 scheme unconditionally. The two alloc/copy branches are merged. wuss-test: set_background test indexed palette_PICO8_ORANGE against the default palette; use index 1, which is valid for the two-entry fallback. Co-Authored-By: Claude Sonnet 5 --- libraries/wuss/create.c | 60 ++++++++++++++------------------- libraries/wuss/test/wuss-test.c | 3 +- 2 files changed, 26 insertions(+), 37 deletions(-) diff --git a/libraries/wuss/create.c b/libraries/wuss/create.c index 3553ef4..767f415 100644 --- a/libraries/wuss/create.c +++ b/libraries/wuss/create.c @@ -9,10 +9,17 @@ #endif #include "base/utils.h" -#include "framebuf/palettes.h" #include "impl.h" +/* Built-in fallback palette when the caller passes NULL: black and white. + * wuss makes no other assumptions about palette contents or length. */ +static const colour_t wuss__default_palette[] = +{ + { 0xFF000000 }, /* 0: black */ + { 0xFFFFFFFF } /* 1: white */ +}; + #if defined(WUSS_FURNITURE) || defined(WUSS_ICONS) /* Range-check the bevel colours and the backdrop against the palette. Shared * by the furniture and icons-only paths so the accepted range, the error code @@ -57,34 +64,25 @@ result_t wuss_create(screen_t *scr, if (w == NULL) return result_OOM; - if (palette != NULL) + if (palette == NULL) { - if (npalette <= 0) - { - free(w); - return result_BAD_ARG; - } - - w->palette = malloc(npalette * sizeof(*w->palette)); - if (w->palette == NULL) - { - free(w); - return result_OOM; - } - memcpy(w->palette, palette, npalette * sizeof(*w->palette)); - w->npalette = npalette; + palette = wuss__default_palette; + npalette = NELEMS(wuss__default_palette); } - else + else if (npalette <= 0) { - w->palette = malloc(palette_PICO8__LENGTH * sizeof(*w->palette)); - if (w->palette == NULL) - { - free(w); - return result_OOM; - } - define_pico8_palette(w->palette); - w->npalette = palette_PICO8__LENGTH; + free(w); + return result_BAD_ARG; + } + + w->palette = malloc(npalette * sizeof(*w->palette)); + if (w->palette == NULL) + { + free(w); + return result_OOM; } + memcpy(w->palette, palette, npalette * sizeof(*w->palette)); + w->npalette = npalette; if (config != NULL) { @@ -106,16 +104,8 @@ result_t wuss_create(screen_t *scr, } else { - if (palette == NULL) - { - bg = palette_PICO8_DARK_BLUE; - fg = palette_PICO8_WHITE; - } - else - { - bg = 0; - fg = (w->npalette > 1) ? 1 : 0; - } + bg = 0; + fg = (w->npalette > 1) ? 1 : 0; pal.title.bg = bg; pal.title.fg = fg; diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index 71f8194..95d846c 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -1171,8 +1171,7 @@ result_t wuss_test(const char *resources) if (rc != result_WUSS_BAD_COLOUR) goto Failure; - rc = wuss_window_set_background(win_d, - wuss_BACKDROP_COLOUR(palette_PICO8_ORANGE)); + rc = wuss_window_set_background(win_d, wuss_BACKDROP_COLOUR(1)); if (rc != result_OK) goto Failure; From cbc02d0457ea14278aef2c0ccb496e0291353930 Mon Sep 17 00:00:00 2001 From: David Thomas Date: Sun, 30 Aug 2026 23:41:46 +0100 Subject: [PATCH 05/79] feat(palettes): add define_wimp16_palette and a WUSS_PALETTE selector Adds the RISC OS desktop (Wimp) 16-colour palette in native Wimp index order, with palette_WIMP16_* names and a palette_WIMP16__LENGTH. The interactive wuss test picks it up via WUSS_PALETTE=wimp16; the default stays PICO-8. Its desktop backdrop now uses a DOTS pattern over light grey instead of a flat fill. Co-Authored-By: Claude Sonnet 5 --- include/framebuf/palettes.h | 28 ++++++++++++++++++++++++++ libraries/framebuf/palettes/palettes.c | 22 ++++++++++++++++++++ libraries/wuss/test/wuss-test.c | 15 ++++++++++---- 3 files changed, 61 insertions(+), 4 deletions(-) diff --git a/include/framebuf/palettes.h b/include/framebuf/palettes.h index 3beabdc..2829810 100644 --- a/include/framebuf/palettes.h +++ b/include/framebuf/palettes.h @@ -25,6 +25,26 @@ #define palette_PICO8_LIGHT_PEACH (15) #define palette_PICO8__LENGTH (16) +/* RISC OS desktop (Wimp) 16-colour palette, in native Wimp index order */ + +#define palette_WIMP16_WHITE (0) +#define palette_WIMP16_GREY_87 (1) +#define palette_WIMP16_GREY_75 (2) +#define palette_WIMP16_GREY_62 (3) +#define palette_WIMP16_GREY_50 (4) +#define palette_WIMP16_GREY_37 (5) +#define palette_WIMP16_GREY_25 (6) +#define palette_WIMP16_BLACK (7) +#define palette_WIMP16_DARK_BLUE (8) +#define palette_WIMP16_YELLOW (9) +#define palette_WIMP16_GREEN (10) +#define palette_WIMP16_RED (11) +#define palette_WIMP16_CREAM (12) +#define palette_WIMP16_DARK_GREEN (13) +#define palette_WIMP16_ORANGE (14) +#define palette_WIMP16_LIGHT_BLUE (15) +#define palette_WIMP16__LENGTH (16) + /** * Define the PICO-8 palette. * @@ -32,4 +52,12 @@ */ void define_pico8_palette(colour_t palette[palette_PICO8__LENGTH]); +/** + * Define the RISC OS 16-colour desktop (Wimp) palette in native Wimp index + * order, addressed by the `palette_WIMP16_*` names. + * + * \param[out] palette RISC OS 16-colour palette. + */ +void define_wimp16_palette(colour_t palette[palette_WIMP16__LENGTH]); + #endif /* PALETTES_H */ diff --git a/libraries/framebuf/palettes/palettes.c b/libraries/framebuf/palettes/palettes.c index 46816b4..2f1b79b 100644 --- a/libraries/framebuf/palettes/palettes.c +++ b/libraries/framebuf/palettes/palettes.c @@ -23,3 +23,25 @@ void define_pico8_palette(colour_t palette[palette_PICO8__LENGTH]) palette[palette_PICO8_PINK ] = colour_rgb(0xFF, 0x77, 0xA8); palette[palette_PICO8_LIGHT_PEACH] = colour_rgb(0xFF, 0xCC, 0xAA); } + +/* The standard RISC OS desktop (Wimp) 16-colour palette in native Wimp index + * order: a greyscale ramp (0-7) then eight colours. */ +void define_wimp16_palette(colour_t palette[palette_WIMP16__LENGTH]) +{ + palette[palette_WIMP16_WHITE ] = colour_rgb(0xFF, 0xFF, 0xFF); + palette[palette_WIMP16_GREY_87 ] = colour_rgb(0xDD, 0xDD, 0xDD); + palette[palette_WIMP16_GREY_75 ] = colour_rgb(0xBB, 0xBB, 0xBB); + palette[palette_WIMP16_GREY_62 ] = colour_rgb(0x99, 0x99, 0x99); + palette[palette_WIMP16_GREY_50 ] = colour_rgb(0x77, 0x77, 0x77); + palette[palette_WIMP16_GREY_37 ] = colour_rgb(0x55, 0x55, 0x55); + palette[palette_WIMP16_GREY_25 ] = colour_rgb(0x33, 0x33, 0x33); + palette[palette_WIMP16_BLACK ] = colour_rgb(0x00, 0x00, 0x00); + palette[palette_WIMP16_DARK_BLUE ] = colour_rgb(0x00, 0x44, 0x99); + palette[palette_WIMP16_YELLOW ] = colour_rgb(0xEE, 0xEE, 0x00); + palette[palette_WIMP16_GREEN ] = colour_rgb(0x00, 0xCC, 0x00); + palette[palette_WIMP16_RED ] = colour_rgb(0xDD, 0x00, 0x00); + palette[palette_WIMP16_CREAM ] = colour_rgb(0xEE, 0xEE, 0xBB); + palette[palette_WIMP16_DARK_GREEN] = colour_rgb(0x55, 0x88, 0x00); + palette[palette_WIMP16_ORANGE ] = colour_rgb(0xFF, 0xBB, 0x00); + palette[palette_WIMP16_LIGHT_BLUE] = colour_rgb(0x00, 0xBB, 0xFF); +} diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index 95d846c..9fb03dd 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -277,7 +277,14 @@ static result_t wuss_interactive_test(const char *resources) bool pixel_stress_pending; int i; - define_pico8_palette(palette); + { + const char *palette_name = getenv("WUSS_PALETTE"); /* "riscos16" selects the RISC OS 16-colour palette; default is PICO-8 */ + + if (palette_name != NULL && strcmp(palette_name, "wimp16") == 0) + define_wimp16_palette(palette); + else + define_pico8_palette(palette); + } leafname = path_join_leafname("digits", "png"); filename = path_join_filename(resources, 3, "resources", "bmfonts", leafname); @@ -354,9 +361,9 @@ static result_t wuss_interactive_test(const char *resources) config.palette.scroll.sausages = palette_PICO8_LIGHT_GREY; config.bevel.light = palette_PICO8_WHITE; config.bevel.dark = palette_PICO8_DARK_GREY; - config.backdrop.colour = palette_PICO8_LIGHT_GREY; - config.backdrop.pattern = screen_PATTERN_SOLID; - config.backdrop.pattern_bg = wuss_NO_BACKGROUND; + config.backdrop.colour = palette_PICO8_WHITE; + config.backdrop.pattern = screen_PATTERN_DOTS; + config.backdrop.pattern_bg = palette_PICO8_LIGHT_GREY; rc = wuss_create(&scr, font, palette, NELEMS(palette), &config, &wuss); if (rc != result_OK) From 963f19e3f2d4df67e28684a41fb1c4bc7dd2f6cc Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 00:17:37 +0100 Subject: [PATCH 06/79] fix(wuss): MENU on furniture should have no effect The title/close and resize/scrollbar-well drag-start branches were gated only on MOUSE_DOWN, so a middle-button (MENU) press on furniture would start a window move or resize drag. Gate them on SELECT|ADJUST as the other furniture branches already are. Co-Authored-By: Claude Sonnet 5 --- libraries/wuss/mouse-click.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libraries/wuss/mouse-click.c b/libraries/wuss/mouse-click.c index 8028deb..fa4ffa0 100644 --- a/libraries/wuss/mouse-click.c +++ b/libraries/wuss/mouse-click.c @@ -124,7 +124,8 @@ result_t wuss_mouse_click(wuss_t *wuss, if (region == wuss_FURNITURE_CLOSE || region == wuss_FURNITURE_TITLE) { - if (action == wuss_MOUSE_DOWN) + if (action == wuss_MOUSE_DOWN && + (button & (wuss_BUTTON_SELECT | wuss_BUTTON_ADJUST))) { box_t content; @@ -144,7 +145,8 @@ result_t wuss_mouse_click(wuss_t *wuss, region == wuss_FURNITURE_VSCROLL_WELL || region == wuss_FURNITURE_HSCROLL_WELL) { - if (action == wuss_MOUSE_DOWN) + if (action == wuss_MOUSE_DOWN && + (button & (wuss_BUTTON_SELECT | wuss_BUTTON_ADJUST))) { point_t scroll; From f971b8760290432d5042ad1ab4241da29cfe00ed Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 01:08:36 +0100 Subject: [PATCH 07/79] fix(wuss): Fix missing text task content when scrolled horizontally --- libraries/wuss/test/tasks/text.c | 28 ++++++++++++++++++---------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/libraries/wuss/test/tasks/text.c b/libraries/wuss/test/tasks/text.c index 719b96f..028dd2f 100644 --- a/libraries/wuss/test/tasks/text.c +++ b/libraries/wuss/test/tasks/text.c @@ -67,15 +67,16 @@ static result_t text_redraw(const wuss_event_t *event, void *task_data) text_task_t *tcx; screen_t *scr; const box_t *bounds; - int font_width, font_height, sy; + int font_width, font_height, sx, sy; const char *string; int stringlen; - point_t pos; + point_t pos0, pos1; tcx = task_data; scr = event->data.redraw.scr; bounds = event->data.redraw.bounds; + sx = event->data.redraw.scroll.x; sy = event->data.redraw.scroll.y; bmfont_get_info(tcx->font, &font_width, &font_height); @@ -83,15 +84,23 @@ static result_t text_redraw(const wuss_event_t *event, void *task_data) string = paragraph; stringlen = (int) strlen(paragraph); - pos.x = bounds->x0 + 4; - pos.y = bounds->y0 - sy + 4; +#define INSET 4 +#define LEADING 2 + + pos0.x = bounds->x0 - sx + INSET; + pos0.y = bounds->y0 - sy + INSET; + pos1.x = bounds->x1 - sx - INSET; + pos1.y = bounds->y1 - sy - INSET; - while (stringlen > 0 && pos.y + font_height <= bounds->y1) + while (stringlen) { - int absolute_break, friendly_break; + int target_width; + int absolute_break; bmfont_width_t width; + int friendly_break; - bmfont_measure(tcx->font, string, stringlen, bounds->x1 - 4 - pos.x, &absolute_break, &width); + target_width = pos1.x - pos0.x; + bmfont_measure(tcx->font, string, stringlen, target_width, &absolute_break, &width); friendly_break = absolute_break; if (absolute_break < stringlen) @@ -104,7 +113,7 @@ static result_t text_redraw(const wuss_event_t *event, void *task_data) friendly_break = absolute_break; /* no space to break at: hard break */ } - bmfont_draw(tcx->font, scr, string, friendly_break, tcx->fg, tcx->bg, &pos, NULL); + bmfont_draw(tcx->font, scr, string, friendly_break, tcx->fg, tcx->bg, &pos0, NULL); string += friendly_break; stringlen -= friendly_break; @@ -114,8 +123,7 @@ static result_t text_redraw(const wuss_event_t *event, void *task_data) stringlen--; } - pos.x = bounds->x0 + 4; - pos.y += font_height + 2; + pos0.y += font_height + LEADING; } return result_OK; From be48120239ae4377da8bea15098ecdd97e12886b Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 10:27:36 +0100 Subject: [PATCH 08/79] docs(wuss): explain how redraw coordinate spaces relate Add a subsection under Scrolling with an ASCII nested-box diagram of screen origin / visible / content box / clip piece / scroll / doc extent, the fields each maps to, and the screen<->document conversion formulae. Co-Authored-By: Claude Sonnet 5 --- docs/windowing/wuss.md | 52 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/docs/windowing/wuss.md b/docs/windowing/wuss.md index a90913d..0ff6a7b 100644 --- a/docs/windowing/wuss.md +++ b/docs/windowing/wuss.md @@ -127,6 +127,58 @@ Each window carries a scroll offset, `(0, 0)` by default: the point in the task' - window-local `x`/`y` delivered in mouse/scroll events (and expected in `wuss_window_invalidate`'s `local_box`) are in virtual content space, i.e. already shifted by the scroll offset. - a redraw event's `content` is still the on-screen (unscrolled) content box; a task reads the offset itself via `wuss_window_get_scroll` to work out which part of its content to paint there. +### How the coordinate spaces relate + +Everything a redraw callback gets is *screen space* except `scroll`, which is a +*virtual content space* offset. + +``` +screen (0,0) --- wuss->scr, origin top-left + +-------------------------------------------------+ + | win->visible (whole on-screen footprint) | + | +-----------------------------------------+ | + | | titlebar + 1px outline (carved off) | | + | +--------------------------------+--------+ | wuss__content_box() = + | | content box = redraw.bounds | vscroll| | visible + | | (bounds.x0,y0) . | (carve)| | - outline + | | . . +-------------+ .| | | - titlebar + | | . | redraw. | this .| | | - furniture carve + | | . | content | piece .| | | + | | . | (one clip | only .| | | + | | . | piece) | .| | | + | | . +-------------+ .| | | + | | . . . . . . . . . . . . . . .| | | + | +--------------------------------+--------+ | + | | hscroll (carve) | | + | +-----------------------------------------+ | + +-------------------------------------------------+ + +virtual content space: size win->doc, its own origin (0,0). + win->scroll = which point of doc sits at bounds.x0,y0. + doc extends past the content box (right + bottom); that + overhang is exactly what scroll ranges over. +``` + +- `win->visible` — window's whole on-screen box (screen space); set by Wuss at create/move/resize/toggle. `wuss_window_get_visible_bounds`. +- content box / `redraw.bounds` — `visible` with outline, titlebar and any scrollbar/resize carve removed (`wuss__content_box`). `wuss_window_get_content_bounds`. +- `clipped` (internal) — `bounds` intersected with the dirty rect, before subtracting windows above. +- `redraw.content` — `clipped` minus whatever higher windows cover, split into non-overlapping pieces; the callback fires once per piece with `scr->clip` already set to it. Touch only pixels inside it. +- `win->scroll` / `redraw.scroll` — document-space offset; the task sets it via `wuss_window_set_scroll`, Wuss clamps it to `[0, doc - viewport]`. +- `win->doc` — virtual content extent, fixed at window create; drives the scrollbar sausage size and the scroll clamp. + +Conversions (as used in `mouse-move.c`, `scroll.c`, `scroll-step.c`): + +``` +screen -> document: doc.x = screen.x - bounds.x0 + scroll.x (same for y) +document -> screen: screen.x = bounds.x0 - scroll.x + doc.x +scroll clamp: max = doc - (bounds.x1 - bounds.x0); clamp(scroll, 0, max) +``` + +In a redraw callback: start drawing at `bounds.x0 - scroll.x`, +`bounds.y0 - scroll.y`, then paint the whole content normally — the framebuffer +clip (`scr->clip` = `redraw.content`) discards anything outside the piece. See +`libraries/wuss/test/tasks/text.c`. + ## Redrawing - `wuss_redraw` repaints every window, back-to-front, unconditionally, having first painted the configured backdrop colour (see Setup) behind them, if any. From 7d4644ee18f6a33b958fe12289fd29a34f306f9c Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 11:20:58 +0100 Subject: [PATCH 09/79] refactor(wuss): split the text task's layout and rendering text_redraw did line-breaking and drawing in one loop mixed with screen and scroll state. Pull the wrapping into a pure text_layout() that returns an array of spans, and the drawing into text_render(); text_redraw is now just glue. No behaviour change. Co-Authored-By: Claude Sonnet 5 --- libraries/wuss/test/tasks/text.c | 115 ++++++++++++++++++++++--------- 1 file changed, 82 insertions(+), 33 deletions(-) diff --git a/libraries/wuss/test/tasks/text.c b/libraries/wuss/test/tasks/text.c index 028dd2f..4835035 100644 --- a/libraries/wuss/test/tasks/text.c +++ b/libraries/wuss/test/tasks/text.c @@ -62,45 +62,38 @@ result_t text_create(wuss_t *wuss, return rc; } -static result_t text_redraw(const wuss_event_t *event, void *task_data) -{ - text_task_t *tcx; - screen_t *scr; - const box_t *bounds; - int font_width, font_height, sx, sy; - const char *string; - int stringlen; - point_t pos0, pos1; - - tcx = task_data; - - scr = event->data.redraw.scr; - bounds = event->data.redraw.bounds; - sx = event->data.redraw.scroll.x; - sy = event->data.redraw.scroll.y; - - bmfont_get_info(tcx->font, &font_width, &font_height); +#define INSET 4 +#define LEADING 2 +#define MAX_LINES 64 /* paragraph is short and fixed; overflow is dropped */ - string = paragraph; - stringlen = (int) strlen(paragraph); +typedef struct text_line +{ + const char *str; + int len; +} +text_line_t; + +/* Break "string" into lines that each fit "wrap_width" pixels in "font", + * writing up to "max" of them to "lines". Returns the line count. Pure + * layout: no drawing, no screen or scroll state. */ +static int text_layout(bmfont_t *font, + const char *string, + int stringlen, + int wrap_width, + text_line_t *lines, + int max) +{ + int nlines; -#define INSET 4 -#define LEADING 2 - - pos0.x = bounds->x0 - sx + INSET; - pos0.y = bounds->y0 - sy + INSET; - pos1.x = bounds->x1 - sx - INSET; - pos1.y = bounds->y1 - sy - INSET; + nlines = 0; - while (stringlen) + while (stringlen > 0 && nlines < max) { - int target_width; int absolute_break; bmfont_width_t width; int friendly_break; - target_width = pos1.x - pos0.x; - bmfont_measure(tcx->font, string, stringlen, target_width, &absolute_break, &width); + bmfont_measure(font, string, stringlen, wrap_width, &absolute_break, &width); friendly_break = absolute_break; if (absolute_break < stringlen) @@ -113,7 +106,9 @@ static result_t text_redraw(const wuss_event_t *event, void *task_data) friendly_break = absolute_break; /* no space to break at: hard break */ } - bmfont_draw(tcx->font, scr, string, friendly_break, tcx->fg, tcx->bg, &pos0, NULL); + lines[nlines].str = string; + lines[nlines].len = friendly_break; + nlines++; string += friendly_break; stringlen -= friendly_break; @@ -122,9 +117,63 @@ static result_t text_redraw(const wuss_event_t *event, void *task_data) string++; stringlen--; } + } + + return nlines; +} + +/* Draw pre-laid-out "lines" stacked from "origin", advancing by the font + * height plus LEADING per line. */ +static void text_render(bmfont_t *font, + screen_t *scr, + const text_line_t *lines, + int nlines, + colour_t fg, + colour_t bg, + point_t origin) +{ + int font_width, font_height; + point_t pos; + int i; + + bmfont_get_info(font, &font_width, &font_height); - pos0.y += font_height + LEADING; + pos = origin; + for (i = 0; i < nlines; i++) + { + bmfont_draw(font, scr, lines[i].str, lines[i].len, fg, bg, &pos, NULL); + pos.y += font_height + LEADING; } +} + +static result_t text_redraw(const wuss_event_t *event, void *task_data) +{ + text_task_t *tcx; + screen_t *scr; + const box_t *bounds; + int sx, sy; + text_line_t lines[MAX_LINES]; + int nlines; + point_t origin; + + tcx = task_data; + + scr = event->data.redraw.scr; + bounds = event->data.redraw.bounds; + sx = event->data.redraw.scroll.x; + sy = event->data.redraw.scroll.y; + + nlines = text_layout(tcx->font, + paragraph, + (int) strlen(paragraph), + (bounds->x1 - INSET) - (bounds->x0 + INSET), + lines, + MAX_LINES); + + origin.x = bounds->x0 - sx + INSET; + origin.y = bounds->y0 - sy + INSET; + + text_render(tcx->font, scr, lines, nlines, tcx->fg, tcx->bg, origin); return result_OK; } From da29ca7e9895f7e21f76ec469d3130c6ffe44031 Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 11:39:14 +0100 Subject: [PATCH 10/79] feat(bmtext): factor pixel-wrapped text layout into a library New text/bmtext module: bmtext_layout() breaks a string into lines that fit a pixel width in a bmfont (via bmfont_measure, so proportional fonts wrap correctly), bmtext_draw() draws pre-laid-out lines stacked. The wuss text-task test now calls these instead of carrying its own copy. Co-Authored-By: Claude Sonnet 5 --- CMakeLists.txt | 3 + include/text/bmtext.h | 89 +++++++++++++++++++++++++ libraries/text/bmtext/draw.c | 32 +++++++++ libraries/text/bmtext/layout.c | 53 +++++++++++++++ libraries/wuss/test/tasks/text.c | 110 +++++-------------------------- 5 files changed, 192 insertions(+), 95 deletions(-) create mode 100644 include/text/bmtext.h create mode 100644 libraries/text/bmtext/draw.c create mode 100644 libraries/text/bmtext/layout.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 1df7a23..69c7b6a 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -105,6 +105,7 @@ set(PUBLIC_HEADERS include/io/stream.h include/test/all-tests.h include/test/txtscr.h + include/text/bmtext.h include/text/txtfmt.h include/utils/array.h include/utils/barith.h @@ -285,6 +286,8 @@ set(TESTLIB_SOURCES libraries/test/txtscr/txtscr.c) set(TEXT_SOURCES + libraries/text/bmtext/draw.c + libraries/text/bmtext/layout.c libraries/text/txtfmt/create.c libraries/text/txtfmt/destroy.c libraries/text/txtfmt/get-length.c diff --git a/include/text/bmtext.h b/include/text/bmtext.h new file mode 100644 index 0000000..f3b3811 --- /dev/null +++ b/include/text/bmtext.h @@ -0,0 +1,89 @@ +/* bmtext.h -- word-wrap and draw a paragraph in a bitmap font */ + +/** + * \file bmtext.h + * + * Splits a string into lines that each fit a given pixel width when drawn in a + * \ref bmfont_t, then draws those lines stacked. + * + * Unlike \ref txtfmt (which wraps at character counts, for monospaced text) + * this measures each candidate line with \ref bmfont_measure, so it wraps + * proportional fonts correctly. + * + * - Breaks at the last space that still fits; hard-breaks a word with no space + * in it. - Runs of whitespace at a break are swallowed. - Layout is pure: it + * touches no screen or scroll state. + */ + +#ifndef DPTLIB_BMTEXT_H +#define DPTLIB_BMTEXT_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "base/result.h" +#include "framebuf/bmfont.h" +#include "framebuf/screen.h" +#include "geom/point.h" + +/** + * One laid-out line: a pointer into the caller's string plus a length. Not + * NUL-terminated. + */ +typedef struct bmtext_line +{ + const char *str; + int len; +} +bmtext_line_t; + +/** + * Break \p string into lines that each fit \p wrap_width pixels in \p font. + * + * The line pointers refer into \p string itself, so it must outlive \p lines. + * + * \param[in] font Bitmap font the lines will be drawn in. + * \param[in] string Text to wrap. + * \param[in] stringlen Length of \p string in bytes. + * \param[in] wrap_width Width to wrap to, in pixels. + * \param[out] lines Filled with up to \p max lines. + * \param[in] max Capacity of \p lines. Lines past this are dropped. + * + * \return Number of lines written to \p lines. + */ +int bmtext_layout(bmfont_t *font, + const char *string, + int stringlen, + int wrap_width, + bmtext_line_t *lines, + int max); + +/** + * Draw \p nlines pre-laid-out \p lines stacked downward from \p origin, + * advancing by the font height plus \p leading pixels per line. + * + * \param[in] font Bitmap font to draw in. + * \param[in] scr Screen to draw on. + * \param[in] lines Lines from \ref bmtext_layout. + * \param[in] nlines Number of lines. + * \param[in] fg Foreground colour. + * \param[in] bg Background colour, for glyph blending. + * \param[in] leading Extra pixels between lines. + * \param[in] origin Top-left of the first line, in pixels. + */ +void bmtext_draw(bmfont_t *font, + screen_t *scr, + const bmtext_line_t *lines, + int nlines, + colour_t fg, + colour_t bg, + int leading, + point_t origin); + +#ifdef __cplusplus +} +#endif + +#endif /* DPTLIB_BMTEXT_H */ diff --git a/libraries/text/bmtext/draw.c b/libraries/text/bmtext/draw.c new file mode 100644 index 0000000..e8d9eba --- /dev/null +++ b/libraries/text/bmtext/draw.c @@ -0,0 +1,32 @@ +/* draw.c -- draw pre-laid-out bitmap-font lines */ + +#include + +#include "framebuf/bmfont.h" +#include "framebuf/screen.h" +#include "geom/point.h" + +#include "text/bmtext.h" + +void bmtext_draw(bmfont_t *font, + screen_t *scr, + const bmtext_line_t *lines, + int nlines, + colour_t fg, + colour_t bg, + int leading, + point_t origin) +{ + int font_width, font_height; + point_t pos; + int i; + + bmfont_get_info(font, &font_width, &font_height); + + pos = origin; + for (i = 0; i < nlines; i++) + { + bmfont_draw(font, scr, lines[i].str, lines[i].len, fg, bg, &pos, NULL); + pos.y += font_height + leading; + } +} diff --git a/libraries/text/bmtext/layout.c b/libraries/text/bmtext/layout.c new file mode 100644 index 0000000..2470d8c --- /dev/null +++ b/libraries/text/bmtext/layout.c @@ -0,0 +1,53 @@ +/* layout.c -- break a string into pixel-fitted lines */ + +#include + +#include "framebuf/bmfont.h" + +#include "text/bmtext.h" + +int bmtext_layout(bmfont_t *font, + const char *string, + int stringlen, + int wrap_width, + bmtext_line_t *lines, + int max) +{ + int nlines; + + nlines = 0; + + while (stringlen > 0 && nlines < max) + { + int absolute_break; + bmfont_width_t width; + int friendly_break; + + bmfont_measure(font, string, stringlen, wrap_width, &absolute_break, &width); + + friendly_break = absolute_break; + if (absolute_break < stringlen) + { + /* line didn't fit whole: try to break at the last space within it */ + for (friendly_break = absolute_break - 1; friendly_break > 0; friendly_break--) + if (isspace((unsigned char) string[friendly_break])) + break; + if (friendly_break <= 0) + friendly_break = absolute_break; /* no space to break at: hard break */ + } + + lines[nlines].str = string; + lines[nlines].len = friendly_break; + nlines++; + + string += friendly_break; + stringlen -= friendly_break; + while (stringlen > 0 && isspace((unsigned char) *string)) + { + string++; + stringlen--; + } + } + + return nlines; +} diff --git a/libraries/wuss/test/tasks/text.c b/libraries/wuss/test/tasks/text.c index 4835035..80688ea 100644 --- a/libraries/wuss/test/tasks/text.c +++ b/libraries/wuss/test/tasks/text.c @@ -4,7 +4,6 @@ #include -#include #include #include @@ -17,6 +16,7 @@ #include "framebuf/palettes.h" #include "geom/box.h" #include "geom/point.h" +#include "text/bmtext.h" #include "text.h" @@ -66,95 +66,15 @@ result_t text_create(wuss_t *wuss, #define LEADING 2 #define MAX_LINES 64 /* paragraph is short and fixed; overflow is dropped */ -typedef struct text_line -{ - const char *str; - int len; -} -text_line_t; - -/* Break "string" into lines that each fit "wrap_width" pixels in "font", - * writing up to "max" of them to "lines". Returns the line count. Pure - * layout: no drawing, no screen or scroll state. */ -static int text_layout(bmfont_t *font, - const char *string, - int stringlen, - int wrap_width, - text_line_t *lines, - int max) -{ - int nlines; - - nlines = 0; - - while (stringlen > 0 && nlines < max) - { - int absolute_break; - bmfont_width_t width; - int friendly_break; - - bmfont_measure(font, string, stringlen, wrap_width, &absolute_break, &width); - - friendly_break = absolute_break; - if (absolute_break < stringlen) - { - /* line didn't fit whole: try to break at the last space within it */ - for (friendly_break = absolute_break - 1; friendly_break > 0; friendly_break--) - if (isspace((unsigned char) string[friendly_break])) - break; - if (friendly_break <= 0) - friendly_break = absolute_break; /* no space to break at: hard break */ - } - - lines[nlines].str = string; - lines[nlines].len = friendly_break; - nlines++; - - string += friendly_break; - stringlen -= friendly_break; - while (stringlen > 0 && isspace((unsigned char) *string)) - { - string++; - stringlen--; - } - } - - return nlines; -} - -/* Draw pre-laid-out "lines" stacked from "origin", advancing by the font - * height plus LEADING per line. */ -static void text_render(bmfont_t *font, - screen_t *scr, - const text_line_t *lines, - int nlines, - colour_t fg, - colour_t bg, - point_t origin) -{ - int font_width, font_height; - point_t pos; - int i; - - bmfont_get_info(font, &font_width, &font_height); - - pos = origin; - for (i = 0; i < nlines; i++) - { - bmfont_draw(font, scr, lines[i].str, lines[i].len, fg, bg, &pos, NULL); - pos.y += font_height + LEADING; - } -} - static result_t text_redraw(const wuss_event_t *event, void *task_data) { - text_task_t *tcx; - screen_t *scr; - const box_t *bounds; - int sx, sy; - text_line_t lines[MAX_LINES]; - int nlines; - point_t origin; + text_task_t *tcx; + screen_t *scr; + const box_t *bounds; + int sx, sy; + bmtext_line_t lines[MAX_LINES]; + int nlines; + point_t origin; tcx = task_data; @@ -163,17 +83,17 @@ static result_t text_redraw(const wuss_event_t *event, void *task_data) sx = event->data.redraw.scroll.x; sy = event->data.redraw.scroll.y; - nlines = text_layout(tcx->font, - paragraph, - (int) strlen(paragraph), - (bounds->x1 - INSET) - (bounds->x0 + INSET), - lines, - MAX_LINES); + nlines = bmtext_layout(tcx->font, + paragraph, + (int) strlen(paragraph), + (bounds->x1 - INSET) - (bounds->x0 + INSET), + lines, + MAX_LINES); origin.x = bounds->x0 - sx + INSET; origin.y = bounds->y0 - sy + INSET; - text_render(tcx->font, scr, lines, nlines, tcx->fg, tcx->bg, origin); + bmtext_draw(tcx->font, scr, lines, nlines, tcx->fg, tcx->bg, LEADING, origin); return result_OK; } From e9ce0ae299467afb1812d545b4549b3145229e72 Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 11:40:27 +0100 Subject: [PATCH 11/79] docs(readme): add Text section for txtfmt and bmtext Co-Authored-By: Claude Sonnet 5 --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 2f17af0..67c36fc 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,11 @@ DPTLib is my platform independent C library. It contains a wide variety of funct - [`test/txtscr.h`](https://github.com/dpt/DPTLib/blob/master/include/test/txtscr.h) — text format 'screen' +### Text + +- [`text/txtfmt.h`](https://github.com/dpt/DPTLib/blob/master/include/text/txtfmt.h) — word-wrap a string to a character width (for monospaced text) +- [`text/bmtext.h`](https://github.com/dpt/DPTLib/blob/master/include/text/bmtext.h) — word-wrap and draw a paragraph to a pixel width in a `bmfont` + ### Utilities - [`utils/array.h`](https://github.com/dpt/DPTLib/blob/master/include/utils/array.h) — array utilities From d759a6cf6e20348e022fcf0ea1c8cf8fda9352c1 Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 18:35:42 +0100 Subject: [PATCH 12/79] refactor(wuss): extract the interactive demo into a standalone app The SDL interactive driver (spawn_* callbacks, launcher table and the main loop) lived inside libraries/wuss/test/wuss-test.c, reachable only as a tail call from the wuss unit test when built with USE_SDL. That dragged SDL3 linkage, a USE_SDL compile-def and all the tasks/*.c modules onto DPTLibTest. Move it to apps/wuss/main.c as its own `wuss` executable, gated by a new BUILD_APPS option (which requires WUSS_FURNITURE and WUSS_ICONS and pulls SDL3). wuss-test.c is now SDL-free and holds only the unit test, which DPTLibTest still runs. The old wuss_interactive_test is renamed run_wuss. Co-Authored-By: Claude Sonnet 5 --- CMakeLists.txt | 86 +++-- apps/wuss/main.c | 570 ++++++++++++++++++++++++++++++++ libraries/wuss/test/wuss-test.c | 541 +----------------------------- 3 files changed, 632 insertions(+), 565 deletions(-) create mode 100644 apps/wuss/main.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 69c7b6a..6949ba1 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,8 +30,8 @@ endif(CCACHE_FOUND) option(USE_FORTIFY "Use Fortify" OFF) option(USE_ASAN "Build with AddressSanitizer and UndefinedBehaviorSanitizer" OFF) option(DPTLIB_IMAGES_READ_ONLY "Remove libpng write support" OFF) -option(WUSS_FURNITURE "Build the wuss window-furniture subsystem" ON) -option(WUSS_ICONS "Build the wuss in-content icon subsystem" ON) +option(WUSS_FURNITURE "Build the Wuss window-furniture subsystem" ON) +option(WUSS_ICONS "Build the Wuss in-content icon subsystem" ON) # Referencing CMAKE_TOOLCHAIN_FILE avoids a warning on rebuilds. if(NOT ${CMAKE_TOOLCHAIN_FILE} STREQUAL "") @@ -564,6 +564,7 @@ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include option(BUILD_TESTS "Build test program" OFF) option(BUILD_SDL_TESTS "Build tests that use SDL" OFF) +option(BUILD_APPS "Build standalone apps (Wuss interactive demo)" OFF) if(BUILD_TESTS) set(TEST_SOURCES @@ -591,27 +592,8 @@ if(BUILD_TESTS) libraries/utils/bsearch/test/bsearch-test.c libraries/utils/pack/test/pack-test.c libraries/datastruct/vector/test/vector-test.c - libraries/wuss/test/tasks/ball.c - libraries/wuss/test/tasks/blank.c - libraries/wuss/test/tasks/chars.c - libraries/wuss/test/tasks/checker.c - libraries/wuss/test/tasks/curve.c - libraries/wuss/test/tasks/gradient.c - libraries/wuss/test/tasks/image.c - libraries/wuss/test/tasks/palette.c - libraries/wuss/test/tasks/porter-duff.c - libraries/wuss/test/tasks/sofa.c - libraries/wuss/test/tasks/text.c libraries/wuss/test/wuss-test.c) - # The icon-driven launcher and the icons task only build when both wuss - # subsystems are present; the SDL interactive driver needs them too. - if(WUSS_FURNITURE AND WUSS_ICONS) - list(APPEND TEST_SOURCES - libraries/wuss/test/tasks/icons.c - libraries/wuss/test/tasks/launcher.c) - endif() - source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${TEST_SOURCES}) # Avoid a warning from CMake @@ -634,19 +616,69 @@ if(BUILD_TESTS) target_link_libraries(DPTLibTest m) endif() + # Some unit tests (e.g. bmfont-test) gate an interactive SDL mode on + # USE_SDL; BUILD_SDL_TESTS links SDL3 and defines it for the test binary. if(BUILD_SDL_TESTS) - if(NOT (WUSS_FURNITURE AND WUSS_ICONS)) - message(FATAL_ERROR "BUILD_SDL_TESTS requires WUSS_FURNITURE and WUSS_ICONS") - endif() - find_package(SDL3 REQUIRED) find_package(SDL3_image REQUIRED) target_link_libraries(DPTLibTest SDL3::SDL3 SDL3_image::SDL3_image) - - target_compile_definitions(DPTLibTest PUBLIC USE_SDL) + target_compile_definitions(DPTLibTest PRIVATE USE_SDL) endif() install(TARGETS DPTLibTest RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}) endif() + + +# Standalone apps +# + +if(BUILD_APPS) + # The Wuss interactive demo: its own executable, built from the SDL task + # modules plus apps/wuss/main.c (which was the old in-test SDL driver). + if(NOT (WUSS_FURNITURE AND WUSS_ICONS)) + message(FATAL_ERROR "BUILD_APPS requires WUSS_FURNITURE and WUSS_ICONS") + endif() + + find_package(SDL3 REQUIRED) + find_package(SDL3_image REQUIRED) + + set(WUSS_APP_SOURCES + apps/wuss/main.c + libraries/wuss/test/tasks/ball.c + libraries/wuss/test/tasks/blank.c + libraries/wuss/test/tasks/chars.c + libraries/wuss/test/tasks/checker.c + libraries/wuss/test/tasks/curve.c + libraries/wuss/test/tasks/gradient.c + libraries/wuss/test/tasks/icons.c + libraries/wuss/test/tasks/image.c + libraries/wuss/test/tasks/launcher.c + libraries/wuss/test/tasks/palette.c + libraries/wuss/test/tasks/porter-duff.c + libraries/wuss/test/tasks/sofa.c + libraries/wuss/test/tasks/text.c) + + source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${WUSS_APP_SOURCES}) + + add_executable(wuss ${WUSS_APP_SOURCES}) + + set_target_properties(wuss PROPERTIES + DESCRIPTION "DPTLib Wuss interactive demo" + C_STANDARD 99 + OUTPUT_NAME_DEBUG wuss-debug + OUTPUT_NAME_RELEASE wuss + OUTPUT_NAME_RELWITHDEBINFO wuss-relwithdebinfo + OUTPUT_NAME_MINSIZEREL wuss-minsizerel) + + target_include_directories(wuss PRIVATE libraries/wuss/test) + target_compile_definitions(wuss PRIVATE USE_SDL) + target_link_libraries(wuss DPTLib SDL3::SDL3 SDL3_image::SDL3_image) + if(NOT MSVC) + target_link_libraries(wuss m) + endif() + + install(TARGETS wuss RUNTIME + DESTINATION ${CMAKE_INSTALL_PREFIX}) +endif() diff --git a/apps/wuss/main.c b/apps/wuss/main.c new file mode 100644 index 0000000..b04bb25 --- /dev/null +++ b/apps/wuss/main.c @@ -0,0 +1,570 @@ +/* main.c -- Wuss - interactive minimal window manager demo */ + +#include +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/result.h" +#include "base/utils.h" +#include "framebuf/bitmap.h" +#include "framebuf/bmfont.h" +#include "framebuf/colour.h" +#include "framebuf/palettes.h" +#include "framebuf/pixelfmt.h" +#include "framebuf/screen.h" +#include "geom/box.h" +#include "io/path.h" +#include "wuss/wuss.h" +#include "wuss/window.h" + +#include + +#include "tasks/ball.h" +#include "tasks/blank.h" +#include "tasks/chars.h" +#include "tasks/checker.h" +#include "tasks/curve.h" +#include "tasks/gradient.h" +#include "tasks/icons.h" +#include "tasks/image.h" +#include "tasks/launcher.h" +#include "tasks/palette.h" +#include "tasks/porter-duff.h" +#include "tasks/sofa.h" +#include "tasks/text.h" + +/* ----------------------------------------------------------------------- */ + +/* Screen pixel format for the interactive test: 1 = 32bpp pixelfmt_bgrx8888 + * (feeds SDL directly, no per-frame conversion); 0 = pixelfmt_p4 paletted + * (exercises screen_copy_rect's nibble-packed blit path instead). */ +#define WUSS_TEST_32BPP 0 + +/* the launcher's spawn callbacks take no arguments, so the pieces they need + * are stashed here instead; run_wuss runs at most once per + * process, so file-scope statics are as good as a context struct */ +static wuss_t *g_wuss; +static const colour_t *g_palette; +static int g_npalette; +static const char *g_resources; +static bmfont_t *g_daydream_font; + +/* Each spawn allocates a fresh per-instance task block so a task may run in + * several windows at once; the block is owned by its window and freed by the + * task's wuss_EVENT_CLOSE handler. If create fails, or (for the font-less + * chars task) returns OK without opening a window, the block is freed here -- + * otherwise it would leak. */ + +static result_t spawn_ball(void) +{ + ball_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = ball_create(g_wuss, g_palette, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_text(void) +{ + text_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = text_create(g_wuss, g_palette, g_daydream_font, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_blank(void) +{ + blank_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = blank_create(g_wuss, g_npalette, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_chars(void) +{ + chars_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = chars_create(g_wuss, g_palette, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_palette(void) +{ + palette_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = palette_create(g_wuss, g_palette, g_npalette, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_image(void) +{ + image_task_t *t; + const char *leafname; + const char *filename; + char buf[DPTLIB_MAXPATH]; + const char *ninepatch; + result_t rc; + + t = calloc(1, sizeof(*t)); + if (t == NULL) return result_OOM; + + leafname = path_join_leafname("jessica", "png"); + filename = path_join_filename(g_resources, 3, "resources", "images", leafname); + strcpy(buf, filename); + ninepatch = path_join_filename(g_resources, 3, "resources", "wuss", + path_join_leafname("9tile", "png")); + + rc = image_create(g_wuss, g_palette, buf, ninepatch, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_checker(void) +{ + checker_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = checker_create(g_wuss, g_palette, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_curve(void) +{ + curve_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = curve_create(g_wuss, g_palette, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_sofa(void) +{ + sofa_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = sofa_create(g_wuss, g_palette, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_gradient(void) +{ + gradient_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = gradient_create(g_wuss, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_icons(void) +{ + icons_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = icons_create(g_wuss, g_palette, g_daydream_font, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_porter_duff(void) +{ + porter_duff_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = porter_duff_create(g_wuss, g_palette, g_daydream_font, g_resources, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static const launcher_entry_t g_launcher_entries[] = +{ + { "Ball", spawn_ball }, + { "Text", spawn_text }, + { "Blank", spawn_blank }, + { "Chars", spawn_chars }, + { "Palette", spawn_palette }, + { "Image", spawn_image }, + { "Checker", spawn_checker }, + { "Curve", spawn_curve }, + { "Sofa", spawn_sofa }, + { "Gradient", spawn_gradient }, + { "Icons", spawn_icons }, + { "Porter-Duff", spawn_porter_duff } +}; + +static wuss_button_t sdl_button_to_wuss(Uint8 button) +{ + switch (button) + { + case SDL_BUTTON_MIDDLE: return wuss_BUTTON_MENU; + case SDL_BUTTON_RIGHT: return wuss_BUTTON_ADJUST; + default: return wuss_BUTTON_SELECT; + } +} + +/* SDL delivers mouse coordinates in window space, which F2 can scale away + * from the fixed-size Wuss screen; map back down to screen space */ +static void sdl_pos_to_scr(SDL_Window *window, + int scr_width, + int scr_height, + float in_x, + float in_y, + int *out_x, + int *out_y) +{ + int win_w, win_h; + + SDL_GetWindowSize(window, &win_w, &win_h); + + *out_x = (int) (in_x * scr_width / win_w); + *out_y = (int) (in_y * scr_height / win_h); +} + +/* click windows to bring to front, drag titlebars to move, resize the + * SDL window to see the Wuss screen scale; F2 doubles the SDL window size, + * Shift-F2 halves it; F3 redraws the whole screen one pixel at a time, to + * catch tasks whose drawing routines misbehave under a 1x1 clip; Q or + * close to quit */ +static result_t run_wuss(const char *resources) +{ + const int scr_width = 640; + const int scr_height = 480; +#if WUSS_TEST_32BPP + const int rowbytes = scr_width * 4; /* pixelfmt_bgrx8888: 4 bytes/pixel */ +#else + const int rowbytes = scr_width / 2; /* pixelfmt_p4: 2 pixels/byte */ +#endif + + result_t rc; + const char *leafname; + const char *filename; + bmfont_t *font; + bmfont_t *daydream_font; + void *pixels; + bitmap_t bm; +#if !WUSS_TEST_32BPP + bitmap_t *disp; +#endif + screen_t scr; + colour_t palette[16]; + wuss_t *wuss; + launcher_task_t launcher_task; + SDL_Window *window; + SDL_Renderer *renderer; + SDL_Texture *texture; + bool quit; + bool garbage_pending; + bool pixel_stress_pending; + int i; + + { + const char *palette_name = getenv("WUSS_PALETTE"); /* "riscos16" selects the RISC OS 16-colour palette; default is PICO-8 */ + + if (palette_name != NULL && strcmp(palette_name, "wimp16") == 0) + define_wimp16_palette(palette); + else + define_pico8_palette(palette); + } + + leafname = path_join_leafname("digits", "png"); + filename = path_join_filename(resources, 3, "resources", "bmfonts", leafname); + rc = bmfont_create(filename, &font); + if (rc != result_OK) + goto Failure; + + leafname = path_join_leafname("daydream", "png"); + filename = path_join_filename(resources, 3, "resources", "bmfonts", leafname); + rc = bmfont_create(filename, &daydream_font); + if (rc != result_OK) + goto Failure; + + pixels = malloc(rowbytes * scr_height); + if (pixels == NULL) + goto Failure; + +#if WUSS_TEST_32BPP + rc = bitmap_init(&bm, SIZE2D(scr_width, scr_height), pixelfmt_bgrx8888, rowbytes, palette, pixels); +#else + rc = bitmap_init(&bm, SIZE2D(scr_width, scr_height), pixelfmt_p4, rowbytes, palette, pixels); +#endif + if (rc != result_OK) + goto Failure; + + bitmap_clear(&bm, palette[palette_PICO8_WHITE]); + + screen_for_bitmap(&scr, &bm); + + if (!SDL_Init(SDL_INIT_VIDEO)) + { + fprintf(stderr, "Error: SDL_Init: %s\n", SDL_GetError()); + goto Failure; + } + + window = SDL_CreateWindow("Wuss", scr_width, scr_height, 0); + if (window == NULL) + { + fprintf(stderr, "Error: SDL_CreateWindow: %s\n", SDL_GetError()); + goto Failure; + } + + renderer = SDL_CreateRenderer(window, NULL); + if (renderer == NULL) + { + fprintf(stderr, "Error: SDL_CreateRenderer: %s\n", SDL_GetError()); + goto Failure; + } + + texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STREAMING, + scr_width, scr_height); + if (texture == NULL) + { + fprintf(stderr, "Error: SDL_CreateTexture: %s\n", SDL_GetError()); + goto Failure; + } + + SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_NONE); + SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); /* keep pixels crisp when F2 scales the window up */ + + { + wuss_config_t config; + + config.titlebar_height = 0; + config.palette.title.bg = palette_PICO8_DARK_BLUE; + config.palette.title.fg = palette_PICO8_WHITE; + config.palette.back = palette_PICO8_GREEN; + config.palette.close = palette_PICO8_RED; + config.palette.toggle = palette_PICO8_ORANGE; + config.palette.resize = palette_PICO8_LAVENDER; + config.palette.scroll.arrows = palette_PICO8_BLUE; + config.palette.scroll.wells = palette_PICO8_DARK_BLUE; + config.palette.scroll.sausages = palette_PICO8_LIGHT_GREY; + config.bevel.light = palette_PICO8_WHITE; + config.bevel.dark = palette_PICO8_DARK_GREY; + config.backdrop.colour = palette_PICO8_WHITE; + config.backdrop.pattern = screen_PATTERN_DOTS; + config.backdrop.pattern_bg = palette_PICO8_LIGHT_GREY; + + rc = wuss_create(&scr, font, palette, NELEMS(palette), &config, &wuss); + if (rc != result_OK) + goto Failure; + } + + g_wuss = wuss; + g_palette = palette; + g_npalette = NELEMS(palette); + g_resources = resources; + g_daydream_font = daydream_font; + + rc = launcher_create(wuss, g_launcher_entries, NELEMS(g_launcher_entries), &launcher_task); + if (rc != result_OK) + goto Failure; + + quit = false; + garbage_pending = false; + pixel_stress_pending = false; + + wuss_redraw(wuss); + + while (!quit) + { + SDL_Event event; + + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_EVENT_QUIT: + quit = true; + break; + + case SDL_EVENT_KEY_UP: + if (event.key.key == SDLK_Q) + quit = true; + else if (event.key.key == SDLK_F1 && (event.key.mod & SDL_KMOD_SHIFT)) + wuss_redraw(wuss); + else if (event.key.key == SDLK_F1) + garbage_pending = true; + else if (event.key.key == SDLK_F3) + pixel_stress_pending = true; + else if (event.key.key == SDLK_F2) + { + int w, h; + + SDL_GetWindowSize(window, &w, &h); + if (event.key.mod & SDL_KMOD_SHIFT) + SDL_SetWindowSize(window, w / 2, h / 2); + else + SDL_SetWindowSize(window, w * 2, h * 2); + } + break; + + case SDL_EVENT_MOUSE_BUTTON_DOWN: + { + int x, y; + + sdl_pos_to_scr(window, scr_width, scr_height, event.button.x, event.button.y, &x, &y); + wuss_mouse_click(wuss, POINT(x, y), sdl_button_to_wuss(event.button.button), wuss_MOUSE_DOWN, NULL); + } + break; + + case SDL_EVENT_MOUSE_BUTTON_UP: + { + int x, y; + + sdl_pos_to_scr(window, scr_width, scr_height, event.button.x, event.button.y, &x, &y); + wuss_mouse_click(wuss, POINT(x, y), sdl_button_to_wuss(event.button.button), wuss_MOUSE_UP, NULL); + } + break; + + case SDL_EVENT_MOUSE_MOTION: + { + int x, y; + + sdl_pos_to_scr(window, scr_width, scr_height, event.motion.x, event.motion.y, &x, &y); + wuss_mouse_move(wuss, POINT(x, y), NULL); + } + break; + + case SDL_EVENT_MOUSE_WHEEL: + { + int x, y; + + sdl_pos_to_scr(window, scr_width, scr_height, event.wheel.mouse_x, event.wheel.mouse_y, &x, &y); + wuss_scroll(wuss, POINT(x, y), (int) event.wheel.y, NULL); + } + break; + + default: + break; + } + } + + wuss_idle(wuss); + + if (garbage_pending) + { + /* full-screen corruption, drawn over whatever's already on screen + * (windows included) and left untouched: nothing here is invalidated, + * so it stays put until something actually redraws over it, e.g. the + * ball's own small per-frame dirty rect eating a trail through it, or + * a window being dragged across it */ + unsigned char *p; + size_t n; + size_t i; + + p = pixels; + n = (size_t) rowbytes * scr_height; + for (i = 0; i < n; i++) + p[i] = (unsigned char) rand(); + + garbage_pending = false; + } + else if (pixel_stress_pending) + { + /* redraw the whole screen one pixel at a time: each wuss_redraw_dirty + * call is flushed before the next pixel is invalidated, so no two + * pixels are ever coalesced into one redraw. A task whose drawing + * routine assumes it always gets handed a multi-pixel/aligned clip + * (rather than trusting scr->clip) will visibly misdraw here even + * though it looks fine under normal, larger dirty regions. */ + int x, y; + + for (y = 0; y < scr_height; y++) + { + for (x = 0; x < scr_width; x++) + { + box_t px; + + px.x0 = x; px.y0 = y; + px.x1 = x + 1; px.y1 = y + 1; + + wuss_invalidate(wuss, &px); + wuss_redraw_dirty(wuss); + } + } + + pixel_stress_pending = false; + } + else + { + wuss_redraw_dirty(wuss); + } + +#if WUSS_TEST_32BPP + SDL_UpdateTexture(texture, NULL, bm.base, bm.rowbytes); /* bm is already bgrx8888: no conversion needed */ +#else + rc = bitmap_convert(&bm, pixelfmt_bgrx8888, &disp); + if (rc == result_OK) + { + SDL_UpdateTexture(texture, NULL, disp->base, disp->rowbytes); + free(disp->base); + free(disp); + } +#endif + + SDL_RenderTexture(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); + + SDL_Delay(1000 / 60); + } + + /* ponytail: wuss_destroy() below frees every still-open window but not the + * per-instance task block hung off it, so any task window left open at quit + * leaks its block. Harmless at process exit; add a wuss close callback if a + * task ever needs deterministic teardown. */ + launcher_destroy(&launcher_task); + + wuss_destroy(wuss); + bmfont_destroy(font); + bmfont_destroy(daydream_font); + + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + + free(pixels); + + return result_TEST_PASSED; + + +Failure: + + printf("run_wuss: failed (rc=0x%X)\n", rc); + + return result_TEST_FAILED; +} + +/* ----------------------------------------------------------------------- */ + +int main(int argc, char *argv[]) +{ + const char *resources = "."; + int i; + result_t rc; + + for (i = 1; i < argc; i++) + if (strcmp(argv[i], "-resources") == 0 && i + 1 < argc) + resources = argv[++i]; + + rc = run_wuss(resources); + + return rc == result_TEST_PASSED ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index 9fb03dd..b38be4e 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -25,538 +25,9 @@ /* ----------------------------------------------------------------------- */ -#ifdef USE_SDL - -#include - -#include "tasks/ball.h" -#include "tasks/blank.h" -#include "tasks/chars.h" -#include "tasks/checker.h" -#include "tasks/curve.h" -#include "tasks/gradient.h" -#include "tasks/icons.h" -#include "tasks/image.h" -#include "tasks/launcher.h" -#include "tasks/palette.h" -#include "tasks/porter-duff.h" -#include "tasks/sofa.h" -#include "tasks/text.h" - -/* Screen pixel format for the interactive test: 1 = 32bpp pixelfmt_bgrx8888 - * (feeds SDL directly, no per-frame conversion); 0 = pixelfmt_p4 paletted - * (exercises screen_copy_rect's nibble-packed blit path instead). */ -#define WUSS_TEST_32BPP 0 - -/* the launcher's spawn callbacks take no arguments, so the pieces they need - * are stashed here instead; wuss_interactive_test runs at most once per - * process, so file-scope statics are as good as a context struct */ -static wuss_t *g_wuss; -static const colour_t *g_palette; -static int g_npalette; -static const char *g_resources; -static bmfont_t *g_daydream_font; - -/* Each spawn allocates a fresh per-instance task block so a task may run in - * several windows at once; the block is owned by its window and freed by the - * task's wuss_EVENT_CLOSE handler. If create fails, or (for the font-less - * chars task) returns OK without opening a window, the block is freed here -- - * otherwise it would leak. */ - -static result_t spawn_ball(void) -{ - ball_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = ball_create(g_wuss, g_palette, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_text(void) -{ - text_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = text_create(g_wuss, g_palette, g_daydream_font, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_blank(void) -{ - blank_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = blank_create(g_wuss, g_npalette, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_chars(void) -{ - chars_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = chars_create(g_wuss, g_palette, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_palette(void) -{ - palette_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = palette_create(g_wuss, g_palette, g_npalette, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_image(void) -{ - image_task_t *t; - const char *leafname; - const char *filename; - char buf[DPTLIB_MAXPATH]; - const char *ninepatch; - result_t rc; - - t = calloc(1, sizeof(*t)); - if (t == NULL) return result_OOM; - - leafname = path_join_leafname("jessica", "png"); - filename = path_join_filename(g_resources, 3, "resources", "images", leafname); - strcpy(buf, filename); - ninepatch = path_join_filename(g_resources, 3, "resources", "wuss", - path_join_leafname("9tile", "png")); - - rc = image_create(g_wuss, g_palette, buf, ninepatch, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_checker(void) -{ - checker_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = checker_create(g_wuss, g_palette, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_curve(void) -{ - curve_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = curve_create(g_wuss, g_palette, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_sofa(void) -{ - sofa_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = sofa_create(g_wuss, g_palette, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_gradient(void) -{ - gradient_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = gradient_create(g_wuss, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_icons(void) -{ - icons_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = icons_create(g_wuss, g_palette, g_daydream_font, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_porter_duff(void) -{ - porter_duff_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = porter_duff_create(g_wuss, g_palette, g_daydream_font, g_resources, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static const launcher_entry_t g_launcher_entries[] = -{ - { "Ball", spawn_ball }, - { "Text", spawn_text }, - { "Blank", spawn_blank }, - { "Chars", spawn_chars }, - { "Palette", spawn_palette }, - { "Image", spawn_image }, - { "Checker", spawn_checker }, - { "Curve", spawn_curve }, - { "Sofa", spawn_sofa }, - { "Gradient", spawn_gradient }, - { "Icons", spawn_icons }, - { "Porter-Duff", spawn_porter_duff } -}; - -static wuss_button_t sdl_button_to_wuss(Uint8 button) -{ - switch (button) - { - case SDL_BUTTON_MIDDLE: return wuss_BUTTON_MENU; - case SDL_BUTTON_RIGHT: return wuss_BUTTON_ADJUST; - default: return wuss_BUTTON_SELECT; - } -} - -/* SDL delivers mouse coordinates in window space, which F2 can scale away - * from the fixed-size Wuss screen; map back down to screen space */ -static void sdl_pos_to_scr(SDL_Window *window, - int scr_width, - int scr_height, - float in_x, - float in_y, - int *out_x, - int *out_y) -{ - int win_w, win_h; - - SDL_GetWindowSize(window, &win_w, &win_h); - - *out_x = (int) (in_x * scr_width / win_w); - *out_y = (int) (in_y * scr_height / win_h); -} - -/* click windows to bring to front, drag titlebars to move, resize the - * SDL window to see the Wuss screen scale; F2 doubles the SDL window size, - * Shift-F2 halves it; F3 redraws the whole screen one pixel at a time, to - * catch tasks whose drawing routines misbehave under a 1x1 clip; Q or - * close to quit */ -static result_t wuss_interactive_test(const char *resources) -{ - const int scr_width = 640; - const int scr_height = 480; -#if WUSS_TEST_32BPP - const int rowbytes = scr_width * 4; /* pixelfmt_bgrx8888: 4 bytes/pixel */ -#else - const int rowbytes = scr_width / 2; /* pixelfmt_p4: 2 pixels/byte */ -#endif - - result_t rc; - const char *leafname; - const char *filename; - bmfont_t *font; - bmfont_t *daydream_font; - void *pixels; - bitmap_t bm; -#if !WUSS_TEST_32BPP - bitmap_t *disp; -#endif - screen_t scr; - colour_t palette[16]; - wuss_t *wuss; - launcher_task_t launcher_task; - SDL_Window *window; - SDL_Renderer *renderer; - SDL_Texture *texture; - bool quit; - bool garbage_pending; - bool pixel_stress_pending; - int i; - - { - const char *palette_name = getenv("WUSS_PALETTE"); /* "riscos16" selects the RISC OS 16-colour palette; default is PICO-8 */ - - if (palette_name != NULL && strcmp(palette_name, "wimp16") == 0) - define_wimp16_palette(palette); - else - define_pico8_palette(palette); - } - - leafname = path_join_leafname("digits", "png"); - filename = path_join_filename(resources, 3, "resources", "bmfonts", leafname); - rc = bmfont_create(filename, &font); - if (rc != result_OK) - goto Failure; - - leafname = path_join_leafname("daydream", "png"); - filename = path_join_filename(resources, 3, "resources", "bmfonts", leafname); - rc = bmfont_create(filename, &daydream_font); - if (rc != result_OK) - goto Failure; - - pixels = malloc(rowbytes * scr_height); - if (pixels == NULL) - goto Failure; - -#if WUSS_TEST_32BPP - rc = bitmap_init(&bm, SIZE2D(scr_width, scr_height), pixelfmt_bgrx8888, rowbytes, palette, pixels); -#else - rc = bitmap_init(&bm, SIZE2D(scr_width, scr_height), pixelfmt_p4, rowbytes, palette, pixels); -#endif - if (rc != result_OK) - goto Failure; - - bitmap_clear(&bm, palette[palette_PICO8_WHITE]); - - screen_for_bitmap(&scr, &bm); - - if (!SDL_Init(SDL_INIT_VIDEO)) - { - fprintf(stderr, "Error: SDL_Init: %s\n", SDL_GetError()); - goto Failure; - } - - window = SDL_CreateWindow("DPTLib Wuss Test", scr_width, scr_height, 0); - if (window == NULL) - { - fprintf(stderr, "Error: SDL_CreateWindow: %s\n", SDL_GetError()); - goto Failure; - } - - renderer = SDL_CreateRenderer(window, NULL); - if (renderer == NULL) - { - fprintf(stderr, "Error: SDL_CreateRenderer: %s\n", SDL_GetError()); - goto Failure; - } - - texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, - SDL_TEXTUREACCESS_STREAMING, - scr_width, scr_height); - if (texture == NULL) - { - fprintf(stderr, "Error: SDL_CreateTexture: %s\n", SDL_GetError()); - goto Failure; - } - - SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_NONE); - SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); /* keep pixels crisp when F2 scales the window up */ - - { - wuss_config_t config; - - config.titlebar_height = 0; - config.palette.title.bg = palette_PICO8_DARK_BLUE; - config.palette.title.fg = palette_PICO8_WHITE; - config.palette.back = palette_PICO8_GREEN; - config.palette.close = palette_PICO8_RED; - config.palette.toggle = palette_PICO8_ORANGE; - config.palette.resize = palette_PICO8_LAVENDER; - config.palette.scroll.arrows = palette_PICO8_BLUE; - config.palette.scroll.wells = palette_PICO8_DARK_BLUE; - config.palette.scroll.sausages = palette_PICO8_LIGHT_GREY; - config.bevel.light = palette_PICO8_WHITE; - config.bevel.dark = palette_PICO8_DARK_GREY; - config.backdrop.colour = palette_PICO8_WHITE; - config.backdrop.pattern = screen_PATTERN_DOTS; - config.backdrop.pattern_bg = palette_PICO8_LIGHT_GREY; - - rc = wuss_create(&scr, font, palette, NELEMS(palette), &config, &wuss); - if (rc != result_OK) - goto Failure; - } - - g_wuss = wuss; - g_palette = palette; - g_npalette = NELEMS(palette); - g_resources = resources; - g_daydream_font = daydream_font; - - rc = launcher_create(wuss, g_launcher_entries, NELEMS(g_launcher_entries), &launcher_task); - if (rc != result_OK) - goto Failure; - - quit = false; - garbage_pending = false; - pixel_stress_pending = false; - - wuss_redraw(wuss); - - while (!quit) - { - SDL_Event event; - - while (SDL_PollEvent(&event)) - { - switch (event.type) - { - case SDL_EVENT_QUIT: - quit = true; - break; - - case SDL_EVENT_KEY_UP: - if (event.key.key == SDLK_Q) - quit = true; - else if (event.key.key == SDLK_F1 && (event.key.mod & SDL_KMOD_SHIFT)) - wuss_redraw(wuss); - else if (event.key.key == SDLK_F1) - garbage_pending = true; - else if (event.key.key == SDLK_F3) - pixel_stress_pending = true; - else if (event.key.key == SDLK_F2) - { - int w, h; - - SDL_GetWindowSize(window, &w, &h); - if (event.key.mod & SDL_KMOD_SHIFT) - SDL_SetWindowSize(window, w / 2, h / 2); - else - SDL_SetWindowSize(window, w * 2, h * 2); - } - break; - - case SDL_EVENT_MOUSE_BUTTON_DOWN: - { - int x, y; - - sdl_pos_to_scr(window, scr_width, scr_height, event.button.x, event.button.y, &x, &y); - wuss_mouse_click(wuss, POINT(x, y), sdl_button_to_wuss(event.button.button), wuss_MOUSE_DOWN, NULL); - } - break; - - case SDL_EVENT_MOUSE_BUTTON_UP: - { - int x, y; - - sdl_pos_to_scr(window, scr_width, scr_height, event.button.x, event.button.y, &x, &y); - wuss_mouse_click(wuss, POINT(x, y), sdl_button_to_wuss(event.button.button), wuss_MOUSE_UP, NULL); - } - break; - - case SDL_EVENT_MOUSE_MOTION: - { - int x, y; - - sdl_pos_to_scr(window, scr_width, scr_height, event.motion.x, event.motion.y, &x, &y); - wuss_mouse_move(wuss, POINT(x, y), NULL); - } - break; - - case SDL_EVENT_MOUSE_WHEEL: - { - int x, y; - - sdl_pos_to_scr(window, scr_width, scr_height, event.wheel.mouse_x, event.wheel.mouse_y, &x, &y); - wuss_scroll(wuss, POINT(x, y), (int) event.wheel.y, NULL); - } - break; - - default: - break; - } - } - - wuss_idle(wuss); - - if (garbage_pending) - { - /* full-screen corruption, drawn over whatever's already on screen - * (windows included) and left untouched: nothing here is invalidated, - * so it stays put until something actually redraws over it, e.g. the - * ball's own small per-frame dirty rect eating a trail through it, or - * a window being dragged across it */ - unsigned char *p; - size_t n; - size_t i; - - p = pixels; - n = (size_t) rowbytes * scr_height; - for (i = 0; i < n; i++) - p[i] = (unsigned char) rand(); - - garbage_pending = false; - } - else if (pixel_stress_pending) - { - /* redraw the whole screen one pixel at a time: each wuss_redraw_dirty - * call is flushed before the next pixel is invalidated, so no two - * pixels are ever coalesced into one redraw. A task whose drawing - * routine assumes it always gets handed a multi-pixel/aligned clip - * (rather than trusting scr->clip) will visibly misdraw here even - * though it looks fine under normal, larger dirty regions. */ - int x, y; - - for (y = 0; y < scr_height; y++) - { - for (x = 0; x < scr_width; x++) - { - box_t px; - - px.x0 = x; px.y0 = y; - px.x1 = x + 1; px.y1 = y + 1; - - wuss_invalidate(wuss, &px); - wuss_redraw_dirty(wuss); - } - } - - pixel_stress_pending = false; - } - else - { - wuss_redraw_dirty(wuss); - } - -#if WUSS_TEST_32BPP - SDL_UpdateTexture(texture, NULL, bm.base, bm.rowbytes); /* bm is already bgrx8888: no conversion needed */ -#else - rc = bitmap_convert(&bm, pixelfmt_bgrx8888, &disp); - if (rc == result_OK) - { - SDL_UpdateTexture(texture, NULL, disp->base, disp->rowbytes); - free(disp->base); - free(disp); - } -#endif - - SDL_RenderTexture(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); - - SDL_Delay(1000 / 60); - } - - /* ponytail: wuss_destroy() below frees every still-open window but not the - * per-instance task block hung off it, so any task window left open at quit - * leaks its block. Harmless at process exit; add a wuss close callback if a - * task ever needs deterministic teardown. */ - launcher_destroy(&launcher_task); - - wuss_destroy(wuss); - bmfont_destroy(font); - bmfont_destroy(daydream_font); - - SDL_DestroyTexture(texture); - SDL_DestroyRenderer(renderer); - SDL_DestroyWindow(window); - SDL_Quit(); - - free(pixels); - - return result_TEST_PASSED; - - -Failure: - - printf("wuss_interactive_test: failed (rc=0x%X)\n", rc); - - return result_TEST_FAILED; -} - -#endif /* USE_SDL */ +/* The SDL interactive driver that used to live here (spawn_* callbacks, the + * launcher table and the run_wuss loop) is now the standalone `wuss` app + * in apps/wuss/main.c. This file is the Wuss unit test only. */ /* ----------------------------------------------------------------------- */ @@ -3111,12 +2582,6 @@ result_t wuss_test(const char *resources) free(pixels); -#ifdef USE_SDL - rc = wuss_interactive_test(resources); - if (rc != result_TEST_PASSED) - goto Failure; -#endif - return result_TEST_PASSED; From 88f9172a78252220ee802bd28223a795045b6e18 Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 18:49:03 +0100 Subject: [PATCH 13/79] fix(framebuf): avoid undefined shifts flagged by UBSan curve: left-shifting negative point deltas by FIX16_SHIFT is undefined; multiply by FIX16_ONE instead (same codegen). bmfont: extract_advance_widths shifted currbits by bitsperchar without the width==32 guard that the glyph decoder already has, giving a shift-by-32 when charwidth is 16. Co-Authored-By: Claude Sonnet 5 --- libraries/framebuf/bmfont/bmfont.c | 2 +- libraries/framebuf/curve/curve.c | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/libraries/framebuf/bmfont/bmfont.c b/libraries/framebuf/bmfont/bmfont.c index 00d3b87..f7581eb 100644 --- a/libraries/framebuf/bmfont/bmfont.c +++ b/libraries/framebuf/bmfont/bmfont.c @@ -158,7 +158,7 @@ static result_t extract_advance_widths(bmfont_t *bmfont, assert(ncurrbits >= bitsperchar); adw_px = currbits & mask; /* extract high bits */ - currbits <<= bitsperchar; /* discard used bits */ + currbits = (bitsperchar == 32) ? 0 : currbits << bitsperchar; /* discard used bits */ ncurrbits -= bitsperchar; if (array_grow((void **) &bmfont->adw, diff --git a/libraries/framebuf/curve/curve.c b/libraries/framebuf/curve/curve.c index b3ce5fc..fd244d1 100644 --- a/libraries/framebuf/curve/curve.c +++ b/libraries/framebuf/curve/curve.c @@ -328,14 +328,14 @@ void curve_bezier_cubic(point_t p0, #define MULFIX16(x,y) (int) (((long long) (x) * (y)) >> FIX16_SHIFT) - cx = 3 * ((p1.x - p0.x) << FIX16_SHIFT); - cy = 3 * ((p1.y - p0.y) << FIX16_SHIFT); + cx = 3 * ((p1.x - p0.x) * FIX16_ONE); + cy = 3 * ((p1.y - p0.y) * FIX16_ONE); - bx = 3 * ((p2.x - p1.x) << FIX16_SHIFT) - cx; - by = 3 * ((p2.y - p1.y) << FIX16_SHIFT) - cy; + bx = 3 * ((p2.x - p1.x) * FIX16_ONE) - cx; + by = 3 * ((p2.y - p1.y) * FIX16_ONE) - cy; - ax = ((p3.x - p0.x) << FIX16_SHIFT) - cx - bx; - ay = ((p3.y - p0.y) << FIX16_SHIFT) - cy - by; + ax = ((p3.x - p0.x) * FIX16_ONE) - cx - bx; + ay = ((p3.y - p0.y) * FIX16_ONE) - cy - by; h = FIX16_ONE / nsteps; hh = MULFIX16(h,h); @@ -355,8 +355,8 @@ void curve_bezier_cubic(point_t p0, point = &points[0]; - curx = p0.x << FIX16_SHIFT; - cury = p0.y << FIX16_SHIFT; + curx = p0.x * FIX16_ONE; + cury = p0.y * FIX16_ONE; for (i = 0; ; i++) { From b0e0f409fdf56987fd04b70b34301092389ef8d0 Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 18:55:20 +0100 Subject: [PATCH 14/79] test(curve): cut headless frame count from 1000 to 100 The no-SDL path looped 1000 frames of full curve calculation and software line rasterisation on an 800x600 buffer; under ASan/UBSan that ran ~5.5s. 100 frames covers every draw path and finishes in ~0.5s. Co-Authored-By: Claude Sonnet 5 --- libraries/framebuf/curve/test/curve-test.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/framebuf/curve/test/curve-test.c b/libraries/framebuf/curve/test/curve-test.c index 5cca083..1c8e7f2 100644 --- a/libraries/framebuf/curve/test/curve-test.c +++ b/libraries/framebuf/curve/test/curve-test.c @@ -688,7 +688,7 @@ static result_t curve_interactive_test(curveteststate_t *state) } } #else - if (frame > 1000) + if (frame > 100) /* headless: enough frames to cover every draw path */ quit = 1; #endif From 0c36e0afd51991bcd70eaae77e0b75750bec5247 Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 19:25:59 +0100 Subject: [PATCH 15/79] fix(packer): reclaim released space exactly instead of fragmenting packer_release only absorbed a freed slot when it sat wholly inside one existing free area. A window footprint straddles several free fragments, so it was appended as an overlapping box and remove_area never coalesced. Over repeated open/close cycles the free list decayed into slivers and large placements stopped fitting despite the pixels being free. Track every box carved out by packer_place_at / packer_place_by in a placed[] list. packer_release now drops the matching placed box and rebuilds the free list from the margins minus the still-live boxes -- exact reclaim, no coalescing heuristics. packer_clear resets the placed list since its swathe has no matching release. O(nplaced^2) per release; nplaced is a handful in practice. Adds packer test4: four rounds of placing six mixed-size boxes and releasing all of them, asserting the free list collapses back to the whole page each round and a full-page box then fits. --- include/geom/packer.h | 9 +- libraries/geom/packer/impl.h | 6 ++ libraries/geom/packer/packer.c | 88 ++++++++++++++++- libraries/geom/packer/test/packer-test.c | 117 +++++++++++++++++++++++ 4 files changed, 211 insertions(+), 9 deletions(-) diff --git a/include/geom/packer.h b/include/geom/packer.h index 184f7d4..c936009 100644 --- a/include/geom/packer.h +++ b/include/geom/packer.h @@ -93,10 +93,11 @@ void packer_set_gutter(T *packer, int gutter); * Returns a previously placed area to the free pool: the inverse of * packer_place_at / packer_place_by. * - * Adjacent released areas are not coalesced, so a single placement that would - * span two separately-released areas will not fit until the gap between them is - * also freed; placing a box that fits wholly within one free area is - * unaffected. packer_get_consumed_area is not narrowed by a release. + * Free areas that share a full edge are coalesced after each release, so + * repeated place/release cycles reclaim the whole page rather than fragmenting + * it. A placement spanning two free areas that only partially overlap + * (staggered edges) still will not fit until the gap between them is freed too. + * packer_get_consumed_area is not narrowed by a release. * * \param[in] packer Packer to release into. * \param[in] area Area to release. Clipped to the packer's margins. Copied. diff --git a/libraries/geom/packer/impl.h b/libraries/geom/packer/impl.h index f8beff0..e17c62c 100644 --- a/libraries/geom/packer/impl.h +++ b/libraries/geom/packer/impl.h @@ -21,6 +21,12 @@ struct packer int allocedareas; int usedareas; + box_t *placed; /* boxes carved out by packer_place_*, kept + so packer_release can rebuild the free + list minus the ones still live */ + int allocedplaced; + int nplaced; + box_t dims; /* page size */ box_t margins; /* page size minus margins (but not the diff --git a/libraries/geom/packer/packer.c b/libraries/geom/packer/packer.c index a542c5e..b0715b1 100644 --- a/libraries/geom/packer/packer.c +++ b/libraries/geom/packer/packer.c @@ -40,6 +40,10 @@ packer_t *packer_create(const box_t *dims) p->areas[0] = *dims; p->usedareas = 1; + p->placed = NULL; + p->allocedplaced = 0; + p->nplaced = 0; + p->dims = *dims; p->margins = *dims; @@ -61,6 +65,7 @@ void packer_destroy(packer_t *doomed) if (doomed == NULL) return; + free(doomed->placed); free(doomed->areas); free(doomed); } @@ -396,9 +401,12 @@ int packer_next_width(packer_t *packer, packer_loc_t loc) /* ----------------------------------------------------------------------- */ +static result_t note_placed(packer_t *packer, const box_t *area); + result_t packer_place_at(packer_t *packer, const box_t *area) { - box_t b; + result_t err; + box_t b; /* subtract the margins */ @@ -407,21 +415,82 @@ result_t packer_place_at(packer_t *packer, const box_t *area) if (box_is_empty(&b)) return result_PACKER_EMPTY; - return remove_area(packer, &b); + err = remove_area(packer, &b); + if (err) + return err; + + return note_placed(packer, &b); +} + +/* Rebuild the free list from scratch: the whole margin, minus every box + * currently recorded as placed. Called after a release so repeated + * place/release cycles reclaim the whole page exactly, rather than leaving + * the free list fragmented into unusable slivers. O(placed^2); 'placed' is + * the live box count, a handful in practice. */ +static result_t rebuild_free_list(packer_t *packer) +{ + result_t err; + int i; + + packer->areas[0] = packer->margins; + packer->usedareas = 1; + packer->sorted = 0; + + for (i = 0; i < packer->nplaced; i++) + { + err = remove_area(packer, &packer->placed[i]); + if (err) + return err; + } + + return result_OK; +} + +/* Record 'area' (a rect just carved out of the free list) as placed, so a + * later packer_release can rebuild the free list without it. */ +static result_t note_placed(packer_t *packer, const box_t *area) +{ + if (packer->nplaced + 1 > packer->allocedplaced) + { + int n; + box_t *p; + + n = packer->allocedplaced ? packer->allocedplaced * 2 : INITIALAREAS; + p = realloc(packer->placed, n * sizeof(*p)); + if (p == NULL) + return result_OOM; + + packer->placed = p; + packer->allocedplaced = n; + } + + packer->placed[packer->nplaced++] = *area; + + return result_OK; } result_t packer_release(packer_t *packer, const box_t *area) { box_t b; + int i; (void) box_intersection(&packer->margins, area, &b); if (box_is_empty(&b)) return result_PACKER_EMPTY; - /* ponytail: no coalescing with neighbouring free areas -- a released - * fragment is usable on its own, which is all whole-box placement needs; - * tile-style reuse spanning two released areas would need a merge pass */ + /* forget the matching placed box (exact match, else closest by origin); + * a release with no recorded placement just adds free space directly */ + for (i = 0; i < packer->nplaced; i++) + { + if (packer->placed[i].x0 == b.x0 && packer->placed[i].y0 == b.y0 && + packer->placed[i].x1 == b.x1 && packer->placed[i].y1 == b.y1) + { + packer->placed[i] = packer->placed[--packer->nplaced]; + return rebuild_free_list(packer); + } + } + return add_area(packer, &b); } @@ -520,6 +589,10 @@ result_t packer_place_by(packer_t *packer, if (err) return err; + err = note_placed(packer, &consume); + if (err) + return err; + if (pos) *pos = &packer->placed_area; @@ -579,6 +652,11 @@ result_t packer_clear(packer_t *packer, packer_cleardir_t clear) if (err) return err; + /* the cleared swathe crosses placed boxes without a matching release; drop + * the placed list so a later packer_release falls back to adding free + * space directly rather than resurrecting cleared area on a rebuild */ + packer->nplaced = 0; + return result_OK; } diff --git a/libraries/geom/packer/test/packer-test.c b/libraries/geom/packer/test/packer-test.c index 523c4f5..dcece14 100644 --- a/libraries/geom/packer/test/packer-test.c +++ b/libraries/geom/packer/test/packer-test.c @@ -507,6 +507,119 @@ static int test3(void) return 0; +failure: + + packer_destroy(packer); + return 1; +} + +/* count_and_last: packer_map callback recording the free-area count and the + * last area visited. */ +struct scan +{ + int count; + box_t last; +}; + +static result_t count_and_last(const box_t *area, void *opaque) +{ + struct scan *s = opaque; + + s->count++; + s->last = *area; + + return result_OK; +} + +/* packer_release coalescing: filling a page with mixed sizes then releasing + * every slot must collapse the free list back to the whole page, so a + * full-page box fits again. */ +static int test4(void) +{ + static const box_t pagedims = { 0, 0, 300, 300 }; + + static const int w[6] = { 40, 55, 40, 70, 40, 50 }; + static const int h[6] = { 30, 40, 30, 35, 30, 45 }; + + packer_t *packer; + const box_t *slot; + box_t held[6]; + box_t full; + struct scan s; + result_t err; + int cycle; + int i; + + printf("test4: packer_release coalescing\n"); + + packer = packer_create(&pagedims); + if (packer == NULL) + return 1; + + packer_set_gutter(packer, 4); + + /* several open/close rounds: the free list must not drift */ + for (cycle = 0; cycle < 4; cycle++) + { + for (i = 0; i < 6; i++) + { + err = packer_place_by(packer, packer_LOC_BOTTOM_LEFT, w[i], h[i], &slot); + if (err) + { + printf("test4: cycle %d place %d failed (%d)\n", cycle, i, err); + goto failure; + } + + /* release what place_by consumed: the box plus its gutter strip */ + held[i] = *slot; + held[i].x1 = slot->x1 + 4; + held[i].y1 = slot->y1 + 4; + } + + for (i = 5; i >= 0; i--) + { + err = packer_release(packer, &held[i]); + if (err) + { + printf("test4: cycle %d release %d failed (%d)\n", cycle, i, err); + goto failure; + } + } + + /* everything is back: exactly one free area, the whole margin */ + s.count = 0; + err = packer_map(packer, count_and_last, &s); + if (err) + goto failure; + + if (s.count != 1) + { + printf("test4: cycle %d left %d free areas, wanted 1\n", cycle, s.count); + goto failure; + } + + if (s.last.x0 != 0 || s.last.y0 != 0 || + s.last.x1 != 300 || s.last.y1 != 300) + { + printf("test4: cycle %d free area <%d,%d-%d,%d>, wanted <0,0-300,300>\n", + cycle, s.last.x0, s.last.y0, s.last.x1, s.last.y1); + goto failure; + } + } + + /* and a box spanning the whole page now fits */ + full.x0 = 0; full.y0 = 0; full.x1 = 300; full.y1 = 300; + err = packer_place_at(packer, &full); + if (err) + { + printf("test4: full-page placement after reclaim failed (%d)\n", err); + goto failure; + } + + packer_destroy(packer); + return 0; + + failure: packer_destroy(packer); @@ -531,6 +644,10 @@ result_t packer_test(const char *resources) if (err) goto failure; + err = test4(); + if (err) + goto failure; + return result_TEST_PASSED; From 0f6c5ef7e7837dac19b954142ff0463de390e321 Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 20:04:38 +0100 Subject: [PATCH 16/79] docs(packer): document packer_release and exact space reclaim --- docs/geom/packer.md | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/docs/geom/packer.md b/docs/geom/packer.md index e9e65e0..6cb5a7c 100644 --- a/docs/geom/packer.md +++ b/docs/geom/packer.md @@ -64,6 +64,27 @@ result_t packer_place_at(packer_t *packer, This places an element at a specific location, ignoring margins. +## Releasing Elements + +Use `packer_release()` to hand a previously placed area back to the free pool: + +```C +result_t packer_release(packer_t *packer, + const box_t *area); +``` + +Pass the same box that `packer_place_at()` placed, or the area consumed by +`packer_place_by()` (the returned footprint plus its gutter strip). The packer +matches it against its record of placed boxes and rebuilds the free list from +the container minus every box still live, so repeated place/release cycles +reclaim the whole area exactly rather than fragmenting it into unusable +slivers. `area` is clipped to the margins and copied. + +A release whose box matches no recorded placement (for example after +`packer_clear()`, which drops the placement record) simply adds the area to +the free pool without a rebuild. `packer_get_consumed_area()` is not narrowed +by a release. + ## Layout Management Use `packer_clear()` to move past consumed areas: @@ -106,7 +127,8 @@ typedef result_t (packer_map_fn_t)(const box_t *area, void *opaque); The packer can return these specific result codes: - `result_PACKER_DIDNT_FIT` - Element couldn't be placed in available space -- `result_PACKER_EMPTY` - No elements have been placed yet +- `result_PACKER_EMPTY` - No elements have been placed yet, or a released area + lay entirely outside the margins ## Cleanup From 7f4dc1cefb707513d875a7b1d1bd3e78530d8cdf Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 20:13:37 +0100 Subject: [PATCH 17/79] docs: Update --- docs/geom/packer.md | 17 +++---------- docs/windowing/wuss.md | 56 ++++++++++++++++++++---------------------- 2 files changed, 30 insertions(+), 43 deletions(-) diff --git a/docs/geom/packer.md b/docs/geom/packer.md index 6cb5a7c..d18002f 100644 --- a/docs/geom/packer.md +++ b/docs/geom/packer.md @@ -73,17 +73,9 @@ result_t packer_release(packer_t *packer, const box_t *area); ``` -Pass the same box that `packer_place_at()` placed, or the area consumed by -`packer_place_by()` (the returned footprint plus its gutter strip). The packer -matches it against its record of placed boxes and rebuilds the free list from -the container minus every box still live, so repeated place/release cycles -reclaim the whole area exactly rather than fragmenting it into unusable -slivers. `area` is clipped to the margins and copied. - -A release whose box matches no recorded placement (for example after -`packer_clear()`, which drops the placement record) simply adds the area to -the free pool without a rebuild. `packer_get_consumed_area()` is not narrowed -by a release. +Pass the same box that `packer_place_at()` placed, or the area consumed by `packer_place_by()` (the returned footprint plus its gutter strip). The packer matches it against its record of placed boxes and rebuilds the free list from the container minus every box still live, so repeated place/release cycles reclaim the whole area exactly rather than fragmenting it into unusable slivers. `area` is clipped to the margins and copied. + +A release whose box matches no recorded placement (for example after `packer_clear()`, which drops the placement record) simply adds the area to the free pool without a rebuild. `packer_get_consumed_area()` is not narrowed by a release. ## Layout Management @@ -127,8 +119,7 @@ typedef result_t (packer_map_fn_t)(const box_t *area, void *opaque); The packer can return these specific result codes: - `result_PACKER_DIDNT_FIT` - Element couldn't be placed in available space -- `result_PACKER_EMPTY` - No elements have been placed yet, or a released area - lay entirely outside the margins +- `result_PACKER_EMPTY` - No elements have been placed yet, or a released area lay entirely outside the margins ## Cleanup diff --git a/docs/windowing/wuss.md b/docs/windowing/wuss.md index 0ff6a7b..b1d0ef9 100644 --- a/docs/windowing/wuss.md +++ b/docs/windowing/wuss.md @@ -95,10 +95,10 @@ typedef struct wuss_event wuss_event_kind_t kind; union { - struct { screen_t *scr; const box_t *content; } redraw; - struct { wuss_mouse_action_t action; point_t point; wuss_button_t button; } mouse; + struct { screen_t *scr; const box_t *content; } redraw; + struct { wuss_mouse_action_t action; point_t point; wuss_button_t button; } mouse; struct { wuss_icon_t *icon; wuss_mouse_action_t action; wuss_button_t button; } icon; - struct { point_t point; int delta; } scroll; + struct { point_t point; int delta; } scroll; } data; } @@ -129,29 +129,28 @@ Each window carries a scroll offset, `(0, 0)` by default: the point in the task' ### How the coordinate spaces relate -Everything a redraw callback gets is *screen space* except `scroll`, which is a -*virtual content space* offset. +Everything a redraw callback gets is _screen space_ except `scroll`, which is a _virtual content space_ offset. ``` screen (0,0) --- wuss->scr, origin top-left - +-------------------------------------------------+ - | win->visible (whole on-screen footprint) | - | +-----------------------------------------+ | - | | titlebar + 1px outline (carved off) | | - | +--------------------------------+--------+ | wuss__content_box() = - | | content box = redraw.bounds | vscroll| | visible - | | (bounds.x0,y0) . | (carve)| | - outline - | | . . +-------------+ .| | | - titlebar - | | . | redraw. | this .| | | - furniture carve - | | . | content | piece .| | | - | | . | (one clip | only .| | | - | | . | piece) | .| | | - | | . +-------------+ .| | | - | | . . . . . . . . . . . . . . .| | | - | +--------------------------------+--------+ | - | | hscroll (carve) | | - | +-----------------------------------------+ | - +-------------------------------------------------+ + +-----------------------------------------------+ + | win->visible (whole on-screen footprint) | + | +-----------------------------------------+ | + | | titlebar + 1px outline (carved off) | | + | +--------------------------------+--------+ | wuss__content_box() = + | | content box = redraw.bounds | vscroll| | visible + | | (bounds.x0,y0) . | (carve)| | - outline + | | . . +-------------+ .| | | - titlebar + | | . | redraw. | this .| | | - furniture carve + | | . | content | piece .| | | + | | . | (one clip | only .| | | + | | . | piece) | .| | | + | | . +-------------+ .| | | + | | . . . . . . . . . . . . . . .| | | + | +--------------------------------+--------+ | + | | hscroll (carve) | | + | +-----------------------------------------+ | + +-----------------------------------------------+ virtual content space: size win->doc, its own origin (0,0). win->scroll = which point of doc sits at bounds.x0,y0. @@ -169,15 +168,12 @@ virtual content space: size win->doc, its own origin (0,0). Conversions (as used in `mouse-move.c`, `scroll.c`, `scroll-step.c`): ``` -screen -> document: doc.x = screen.x - bounds.x0 + scroll.x (same for y) -document -> screen: screen.x = bounds.x0 - scroll.x + doc.x -scroll clamp: max = doc - (bounds.x1 - bounds.x0); clamp(scroll, 0, max) +screen -> document: doc.x = screen.x - bounds.x0 + scroll.x (same for y) +document -> screen: screen.x = bounds.x0 - scroll.x + doc.x +scroll clamp: max = doc - (bounds.x1 - bounds.x0); clamp(scroll, 0, max) ``` -In a redraw callback: start drawing at `bounds.x0 - scroll.x`, -`bounds.y0 - scroll.y`, then paint the whole content normally — the framebuffer -clip (`scr->clip` = `redraw.content`) discards anything outside the piece. See -`libraries/wuss/test/tasks/text.c`. +In a redraw callback: start drawing at `bounds.x0 - scroll.x`, `bounds.y0 - scroll.y`, then paint the whole content normally — the framebuffer clip (`scr->clip` = `redraw.content`) discards anything outside the piece. See `libraries/wuss/test/tasks/text.c`. ## Redrawing From a28eecd806937b7233c2e8540ed5750b38ddc466 Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 21:48:47 +0100 Subject: [PATCH 18/79] feat(wuss): add grouping-frame icon type and label justification New wuss_ICON_TYPE_FRAME draws a one-pixel grouping box with the top edge broken around an optional caption, RISC OS group-box style. Not interactive: clicks fall through as wuss_EVENT_MOUSE (hit-test already matches BUTTON only, so no change there). wuss_ICON_TYPE_LABEL gains wuss_ICON_FLAGS_JUSTIFY_RIGHT / _CENTRE; the label draw path now measures the text and positions it left (default), right or centred within the bbox. Frame caption geometry is WUSS_FRAME_CAPTION_INSET / _PAD in impl.h. The icons test task shows a frame with a right- and a centre-justified label. First stage of the staged wuss UI toolkit plan. Co-Authored-By: Claude Sonnet 5 --- include/wuss/icon.h | 31 +++++++++---- libraries/wuss/icon/create.c | 3 +- libraries/wuss/icon/draw.c | 76 ++++++++++++++++++++++++++++++- libraries/wuss/impl.h | 5 ++ libraries/wuss/test/tasks/icons.c | 36 +++++++++++++-- 5 files changed, 134 insertions(+), 17 deletions(-) diff --git a/include/wuss/icon.h b/include/wuss/icon.h index be9eaa1..ad41b81 100644 --- a/include/wuss/icon.h +++ b/include/wuss/icon.h @@ -57,23 +57,36 @@ typedef enum wuss_icon_type * and pressed-state visual feedback; clicks and * hovers are delivered to the task as * wuss_EVENT_ICON. */ - wuss_ICON_TYPE_PATTERN /**< Bounding box filled with a repeating two-colour + wuss_ICON_TYPE_PATTERN, /**< Bounding box filled with a repeating two-colour * 8x8 tile (spec.pattern) in fg/bg, phased to * document space so it scrolls rigidly with * content. Not interactive: clicks fall through * to the task as wuss_EVENT_MOUSE. text is * ignored. */ + wuss_ICON_TYPE_FRAME /**< A grouping box: a one-pixel rectangle in fg + * around the bounding box, broken at the top-left + * for an optional caption (text) drawn over the + * window background. Not interactive: clicks fall + * through to the task as wuss_EVENT_MOUSE. */ } wuss_icon_type_t; /** Icon appearance and behaviour flags, combinable with bitwise OR. */ typedef enum wuss_icon_flags { - wuss_ICON_FLAGS_NONE = 0, - wuss_ICON_FLAGS_HIDDEN = 1 << 0, /**< Not drawn, not hit-tested. */ - wuss_ICON_FLAGS_DISABLED = 1 << 1 /**< Drawn greyed; clicks fall through to - * the task as wuss_EVENT_MOUSE rather - * than raising wuss_EVENT_ICON. */ + wuss_ICON_FLAGS_NONE = 0, + wuss_ICON_FLAGS_HIDDEN = 1 << 0, /**< Not drawn, not hit-tested. */ + wuss_ICON_FLAGS_DISABLED = 1 << 1, /**< Drawn greyed; clicks fall through + * to the task as wuss_EVENT_MOUSE + * rather than raising + * wuss_EVENT_ICON. */ + wuss_ICON_FLAGS_JUSTIFY_RIGHT = 1 << 2, /**< wuss_ICON_TYPE_LABEL: right-align + * the text in the bounding box + * instead of the default left. */ + wuss_ICON_FLAGS_JUSTIFY_CENTRE = 1 << 3 /**< wuss_ICON_TYPE_LABEL: centre the + * text in the bounding box. Takes + * precedence over + * wuss_ICON_FLAGS_JUSTIFY_RIGHT. */ } wuss_icon_flags_t; @@ -94,9 +107,9 @@ typedef struct wuss_icon_spec wuss_colour_t fg; /**< Text colour, as an index into the system * palette. */ wuss_colour_t bg; /**< Fill/bevel base colour, as an index into the - * system palette. A label may pass - * wuss_NO_BACKGROUND for text with no fill; a - * button or pattern icon must pass a real + * system palette. A label or frame may pass + * wuss_NO_BACKGROUND for text/outline with no + * fill; a button or pattern icon must pass a real * index. */ screen_pattern_t pattern; /**< Tile for wuss_ICON_TYPE_PATTERN; ignored by * other types. Zero (screen_PATTERN_SOLID) is a diff --git a/libraries/wuss/icon/create.c b/libraries/wuss/icon/create.c index 86686b5..96b6f29 100644 --- a/libraries/wuss/icon/create.c +++ b/libraries/wuss/icon/create.c @@ -28,7 +28,8 @@ result_t wuss_icon_create(wuss_window_t *window, if (spec->type != wuss_ICON_TYPE_LABEL && spec->type != wuss_ICON_TYPE_BUTTON && - spec->type != wuss_ICON_TYPE_PATTERN) + spec->type != wuss_ICON_TYPE_PATTERN && + spec->type != wuss_ICON_TYPE_FRAME) return result_WUSS_BAD_ICON; if ((spec->type == wuss_ICON_TYPE_BUTTON || diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index e6102ee..e4edb3f 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -103,10 +103,82 @@ void wuss__icon_draw(wuss_t *wuss, if (have_font) { - point_t pos; + point_t pos; + int interior_w, split_point; + bmfont_width_t width; + + interior_w = (b.x1 - b.x0) - 2; + if (interior_w < 1) + interior_w = 1; + + bmfont_measure(wuss->font, icon->text, (int) strlen(icon->text), + interior_w, &split_point, &width); - pos.x = b.x0 + 1; + if (icon->flags & wuss_ICON_FLAGS_JUSTIFY_CENTRE) + pos.x = b.x0 + ((b.x1 - b.x0) - width) / 2; + else if (icon->flags & wuss_ICON_FLAGS_JUSTIFY_RIGHT) + pos.x = b.x1 - 1 - width; + else + pos.x = b.x0 + 1; pos.y = b.y0 + (b.y1 - b.y0 - font_height) / 2; + + bmfont_draw(wuss->font, scr, icon->text, (int) strlen(icon->text), + fg, bg, &pos, NULL); + } + } + break; + + case wuss_ICON_TYPE_FRAME: + { + colour_t bg; + int cap_w, cap_x, gap_x0, gap_x1, mid_y; + + /* the caption sits over whatever ground the label case would blend + * against: an explicit bg, else the window's dominant backdrop, else + * fall back to fg so bmfont still has a blend colour */ + if (icon->bg != wuss_NO_BACKGROUND) + bg = wuss->palette[icon->bg]; + else if (icon->window->bg.colour != wuss_NO_BACKGROUND) + bg = (icon->window->bg.pattern != screen_PATTERN_SOLID) + ? wuss->palette[icon->window->bg.pattern_bg] + : wuss->palette[icon->window->bg.colour]; + else + bg = fg; + + cap_w = 0; + if (have_font) + { + int split_point; + bmfont_width_t width; + + bmfont_measure(wuss->font, icon->text, (int) strlen(icon->text), + (b.x1 - b.x0) - WUSS_FRAME_CAPTION_INSET * 2, + &split_point, &width); + cap_w = width; + } + + mid_y = b.y0 + font_height / 2; + + /* left, right and bottom edges are unbroken */ + screen_draw_line(scr, b.x0, mid_y, b.x0, b.y1 - 1, fg); + screen_draw_line(scr, b.x1 - 1, mid_y, b.x1 - 1, b.y1 - 1, fg); + screen_draw_line(scr, b.x0, b.y1 - 1, b.x1 - 1, b.y1 - 1, fg); + + /* the top edge is broken around the caption */ + cap_x = b.x0 + WUSS_FRAME_CAPTION_INSET; + gap_x0 = cap_x - WUSS_FRAME_CAPTION_PAD; + gap_x1 = cap_x + cap_w + WUSS_FRAME_CAPTION_PAD; + if (gap_x0 > b.x0) + screen_draw_line(scr, b.x0, mid_y, gap_x0, mid_y, fg); + if (gap_x1 < b.x1 - 1) + screen_draw_line(scr, gap_x1, mid_y, b.x1 - 1, mid_y, fg); + + if (have_font && cap_w > 0) + { + point_t pos; + + pos.x = cap_x; + pos.y = b.y0; bmfont_draw(wuss->font, scr, icon->text, (int) strlen(icon->text), fg, bg, &pos, NULL); } diff --git a/libraries/wuss/impl.h b/libraries/wuss/impl.h index 5982e9e..71a1c85 100644 --- a/libraries/wuss/impl.h +++ b/libraries/wuss/impl.h @@ -35,6 +35,11 @@ #define WUSS_PLACE_GUTTER 6 /* px left between windows auto-placed by wuss_window_create_placed */ #define WUSS_BUTTON_INSET 3 /* shared by close/back/toggle/resize furniture buttons and scrollbar breadth */ + +#ifdef WUSS_ICONS +#define WUSS_FRAME_CAPTION_INSET 8 /* x offset of a wuss_ICON_TYPE_FRAME caption from the frame's left edge */ +#define WUSS_FRAME_CAPTION_PAD 2 /* gap left in the frame's top edge either side of the caption */ +#endif #define WUSS_MIN_CONTENT 20 /* resize-drag floor: content can never be squeezed smaller than this */ #define WUSS_SCROLL_INSET 2 /* sausage cross-axis margin from its well's edges, purely cosmetic */ #define WUSS_DIVIDER_PX 1 /* interior rule between the content area and the furniture on its right/bottom */ diff --git a/libraries/wuss/test/tasks/icons.c b/libraries/wuss/test/tasks/icons.c index 2e8084a..a3e150c 100644 --- a/libraries/wuss/test/tasks/icons.c +++ b/libraries/wuss/test/tasks/icons.c @@ -27,11 +27,14 @@ result_t icons_create(wuss_t *wuss, bmfont_t *font, icons_task_t *task) { - /* [0..3] plus one swatch per built-in pattern */ + /* [0..3], one swatch per built-in pattern, then [.. +3] a grouping frame + * with two differently-justified labels inside it */ + enum { ICONS_NSPECS = 4 + screen_PATTERN__LIMIT + 3 }; wuss_task_t delegate; - wuss_icon_spec_t specs[4 + screen_PATTERN__LIMIT]; - wuss_icon_t *made[4 + screen_PATTERN__LIMIT]; + wuss_icon_spec_t specs[ICONS_NSPECS]; + wuss_icon_t *made[ICONS_NSPECS]; int p; + int g; /* index of the first frame spec */ result_t rc; task->font = font; @@ -102,8 +105,31 @@ result_t icons_create(wuss_t *wuss, specs[4 + p].pattern = (screen_pattern_t) p; } - rc = wuss_icon_create_array(task->window, specs, - 4 + screen_PATTERN__LIMIT, made); + /* [g] a grouping frame down the document, with [g+1] a right-justified and + * [g+2] a centred label sat inside it */ + g = 4 + screen_PATTERN__LIMIT; + + specs[g].bbox = (box_t) BOX_POS_SIZE(28, 300, 170, 70); + specs[g].type = wuss_ICON_TYPE_FRAME; + specs[g].text = "Grouping frame"; + specs[g].fg = palette_PICO8_DARK_BLUE; + specs[g].bg = wuss_NO_BACKGROUND; + + specs[g + 1].bbox = (box_t) BOX_POS_SIZE(38, 320, 150, 14); + specs[g + 1].type = wuss_ICON_TYPE_LABEL; + specs[g + 1].text = "right"; + specs[g + 1].fg = palette_PICO8_DARK_BLUE; + specs[g + 1].bg = wuss_NO_BACKGROUND; + specs[g + 1].flags = wuss_ICON_FLAGS_JUSTIFY_RIGHT; + + specs[g + 2].bbox = (box_t) BOX_POS_SIZE(38, 342, 150, 14); + specs[g + 2].type = wuss_ICON_TYPE_LABEL; + specs[g + 2].text = "centre"; + specs[g + 2].fg = palette_PICO8_DARK_BLUE; + specs[g + 2].bg = wuss_NO_BACKGROUND; + specs[g + 2].flags = wuss_ICON_FLAGS_JUSTIFY_CENTRE; + + rc = wuss_icon_create_array(task->window, specs, ICONS_NSPECS, made); if (rc != result_OK) goto failure; From 16fb5d631f9dd1c147e221877bc3ba9e0c1f0eab Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 21:52:01 +0100 Subject: [PATCH 19/79] feat(wuss): add default action button styling for work-area buttons A wuss_ICON_TYPE_BUTTON created with the new wuss_ICON_FLAGS_DEFAULT flag draws as a flat accent-filled rectangle inside a one-pixel accent-text border, standing out from the bevelled ordinary buttons around it (RISC OS's default action button). The accent colours come from the new wuss_config_t::accent {bg, fg} pair, cached on struct wuss beside the bevel shades and validated the same way; both default to the titlebar colours when config is NULL. apps/wuss/main.c sets config.accent (its config is assigned field by field, so an unset sub-struct would be indeterminate). The icons test task marks its 'Press me' button as the default. Second stage of the staged wuss UI toolkit plan. Co-Authored-By: Claude Sonnet 5 --- apps/wuss/main.c | 2 ++ include/wuss/icon.h | 12 +++++++++--- include/wuss/wuss.h | 16 +++++++++++++++- libraries/wuss/create.c | 23 +++++++++++++++++++--- libraries/wuss/icon/draw.c | 32 +++++++++++++++++++++++-------- libraries/wuss/impl.h | 2 ++ libraries/wuss/test/tasks/icons.c | 14 ++++++++------ 7 files changed, 80 insertions(+), 21 deletions(-) diff --git a/apps/wuss/main.c b/apps/wuss/main.c index b04bb25..02f5414 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -357,6 +357,8 @@ static result_t run_wuss(const char *resources) config.palette.scroll.sausages = palette_PICO8_LIGHT_GREY; config.bevel.light = palette_PICO8_WHITE; config.bevel.dark = palette_PICO8_DARK_GREY; + config.accent.bg = palette_PICO8_DARK_BLUE; + config.accent.fg = palette_PICO8_WHITE; config.backdrop.colour = palette_PICO8_WHITE; config.backdrop.pattern = screen_PATTERN_DOTS; config.backdrop.pattern_bg = palette_PICO8_LIGHT_GREY; diff --git a/include/wuss/icon.h b/include/wuss/icon.h index ad41b81..3b69357 100644 --- a/include/wuss/icon.h +++ b/include/wuss/icon.h @@ -83,10 +83,16 @@ typedef enum wuss_icon_flags wuss_ICON_FLAGS_JUSTIFY_RIGHT = 1 << 2, /**< wuss_ICON_TYPE_LABEL: right-align * the text in the bounding box * instead of the default left. */ - wuss_ICON_FLAGS_JUSTIFY_CENTRE = 1 << 3 /**< wuss_ICON_TYPE_LABEL: centre the - * text in the bounding box. Takes - * precedence over + wuss_ICON_FLAGS_JUSTIFY_CENTRE = 1 << 3, /**< wuss_ICON_TYPE_LABEL: centre + * the text in the bounding box. + * Takes precedence over * wuss_ICON_FLAGS_JUSTIFY_RIGHT. */ + wuss_ICON_FLAGS_DEFAULT = 1 << 4 /**< wuss_ICON_TYPE_BUTTON: draw as a + * default action button, in the + * window manager's accent colours + * (see wuss_config_t::accent) + * instead of the ordinary bevel. + * Ignored by other icon types. */ } wuss_icon_flags_t; diff --git a/include/wuss/wuss.h b/include/wuss/wuss.h index 14b4bd6..b36a36a 100644 --- a/include/wuss/wuss.h +++ b/include/wuss/wuss.h @@ -219,7 +219,7 @@ wuss_backdrop_t; * Optional creation-time configuration. * * \note titlebar_height and palette are ignored when the library is built with - * WUSS_FURNITURE off; bevel is ignored when built with both + * WUSS_FURNITURE off; bevel and accent are ignored when built with both * WUSS_FURNITURE and WUSS_ICONS off. backdrop is always honoured. See the * backdrop sub-struct for its own notes. */ @@ -247,6 +247,20 @@ typedef struct wuss_config } bevel; + /** + * Fill and text colours for a default action button -- a work-area button + * icon created with wuss_ICON_FLAGS_DEFAULT, drawn to stand out from the + * ordinary bevelled buttons around it (RISC OS's "default action button"). + * Both default to the titlebar colours (bg / fg) when config is NULL. Ignored + * when both WUSS_FURNITURE and WUSS_ICONS are off. + */ + struct + { + wuss_colour_t bg; /**< Default-button fill. */ + wuss_colour_t fg; /**< Default-button text. */ + } + accent; + /** Desktop background, painted behind windows on every redraw. */ wuss_backdrop_t backdrop; } diff --git a/libraries/wuss/create.c b/libraries/wuss/create.c index 767f415..13172f6 100644 --- a/libraries/wuss/create.c +++ b/libraries/wuss/create.c @@ -26,10 +26,14 @@ static const colour_t wuss__default_palette[] = * and the freed-pointer set stay in one place. */ static result_t validate_bevel_backdrop(const wuss_t *w, wuss_colour_t blight, - wuss_colour_t bdark) + wuss_colour_t bdark, + wuss_colour_t abg, + wuss_colour_t afg) { if (blight < 0 || blight >= w->npalette || bdark < 0 || bdark >= w->npalette || + abg < 0 || abg >= w->npalette || + afg < 0 || afg >= w->npalette || wuss__validate_backdrop(w, &w->backdrop) != result_OK) return result_WUSS_BAD_COLOUR; @@ -51,6 +55,7 @@ result_t wuss_create(screen_t *scr, #endif #if defined(WUSS_FURNITURE) || defined(WUSS_ICONS) wuss_colour_t blight, bdark; + wuss_colour_t abg, afg; #endif #ifdef WUSS_FURNITURE int font_height; @@ -101,6 +106,8 @@ result_t wuss_create(screen_t *scr, pal = config->palette; blight = config->bevel.light; bdark = config->bevel.dark; + abg = config->accent.bg; + afg = config->accent.fg; } else { @@ -119,6 +126,8 @@ result_t wuss_create(screen_t *scr, blight = 0; bdark = 0; + abg = bg; /* default action button: the titlebar colours */ + afg = fg; } if (pal.title.bg < 0 || pal.title.bg >= w->npalette || @@ -130,7 +139,7 @@ result_t wuss_create(screen_t *scr, pal.scroll.arrows < 0 || pal.scroll.arrows >= w->npalette || pal.scroll.wells < 0 || pal.scroll.wells >= w->npalette || pal.scroll.sausages < 0 || pal.scroll.sausages >= w->npalette || - validate_bevel_backdrop(w, blight, bdark) != result_OK) + validate_bevel_backdrop(w, blight, bdark, abg, afg) != result_OK) { free(w->palette); free(w); @@ -140,6 +149,8 @@ result_t wuss_create(screen_t *scr, w->furniture_colours = pal; w->bevel_light = blight; w->bevel_dark = bdark; + w->accent_bg = abg; + w->accent_fg = afg; if (config != NULL && config->titlebar_height > 0) { @@ -161,13 +172,17 @@ result_t wuss_create(screen_t *scr, { blight = config->bevel.light; bdark = config->bevel.dark; + abg = config->accent.bg; + afg = config->accent.fg; } else { blight = 0; bdark = 0; + abg = 0; + afg = (w->npalette > 1) ? 1 : 0; } - if (validate_bevel_backdrop(w, blight, bdark) != result_OK) + if (validate_bevel_backdrop(w, blight, bdark, abg, afg) != result_OK) { free(w->palette); free(w); @@ -175,6 +190,8 @@ result_t wuss_create(screen_t *scr, } w->bevel_light = blight; w->bevel_dark = bdark; + w->accent_bg = abg; + w->accent_fg = afg; #else if (wuss__validate_backdrop(w, &w->backdrop) != result_OK) { diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index e4edb3f..902ff2d 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -187,16 +187,32 @@ void wuss__icon_draw(wuss_t *wuss, case wuss_ICON_TYPE_BUTTON: { - colour_t light, dark, base; - int pressed; + colour_t light, dark, base, label; + int pressed, is_default; - base = wuss->palette[icon->bg]; - light = wuss->palette[wuss->bevel_light]; - dark = wuss->palette[wuss->bevel_dark]; - pressed = icon->pressed; + pressed = icon->pressed; + is_default = (icon->flags & wuss_ICON_FLAGS_DEFAULT) != 0; + + if (is_default) + { + /* default action button: a flat accent-filled rectangle inside a + * one-pixel accent-text border, distinct from the bevelled ordinary + * buttons around it */ + base = wuss->palette[wuss->accent_bg]; + light = wuss->palette[wuss->accent_fg]; + dark = light; + label = wuss->palette[wuss->accent_fg]; + } + else + { + base = wuss->palette[icon->bg]; + light = wuss->palette[wuss->bevel_light]; + dark = wuss->palette[wuss->bevel_dark]; + label = fg; + } if (icon->flags & wuss_ICON_FLAGS_DISABLED) - fg = dark; /* greyed: label sinks toward the dark bevel shade */ + label = wuss->palette[wuss->bevel_dark]; /* greyed: sink toward dark */ if (pressed) icon_bevel(scr, &b, base, dark, light); @@ -225,7 +241,7 @@ void wuss__icon_draw(wuss_t *wuss, } bmfont_draw(wuss->font, scr, icon->text, (int) strlen(icon->text), - fg, base, &pos, NULL); + label, base, &pos, NULL); } } break; diff --git a/libraries/wuss/impl.h b/libraries/wuss/impl.h index 71a1c85..169ab7d 100644 --- a/libraries/wuss/impl.h +++ b/libraries/wuss/impl.h @@ -66,6 +66,8 @@ struct wuss #if defined(WUSS_FURNITURE) || defined(WUSS_ICONS) wuss_colour_t bevel_light; /* work-area button top/left edge */ wuss_colour_t bevel_dark; /* work-area button bottom/right edge */ + wuss_colour_t accent_bg; /* default action button fill */ + wuss_colour_t accent_fg; /* default action button text */ #endif wuss_backdrop_t backdrop; /* colour==wuss_NO_BACKGROUND: none */ #ifdef WUSS_FURNITURE diff --git a/libraries/wuss/test/tasks/icons.c b/libraries/wuss/test/tasks/icons.c index a3e150c..ff8612e 100644 --- a/libraries/wuss/test/tasks/icons.c +++ b/libraries/wuss/test/tasks/icons.c @@ -73,12 +73,14 @@ result_t icons_create(wuss_t *wuss, specs[0].fg = palette_PICO8_DARK_BLUE; specs[0].bg = wuss_NO_BACKGROUND; - /* [1] the button that bumps the counter */ - specs[1].bbox = (box_t) BOX_POS_SIZE(28, 50, 80, 22); - specs[1].type = wuss_ICON_TYPE_BUTTON; - specs[1].text = "Press me"; - specs[1].fg = palette_PICO8_BLACK; - specs[1].bg = palette_PICO8_LIGHT_GREY; + /* [1] the button that bumps the counter -- a default action button, so it + * shows the accent styling */ + specs[1].bbox = (box_t) BOX_POS_SIZE(28, 50, 80, 22); + specs[1].type = wuss_ICON_TYPE_BUTTON; + specs[1].text = "Press me"; + specs[1].fg = palette_PICO8_BLACK; + specs[1].bg = palette_PICO8_LIGHT_GREY; + specs[1].flags = wuss_ICON_FLAGS_DEFAULT; /* [2] the counter label beside it */ specs[2].bbox = (box_t) BOX_POS_SIZE(120, 50, 120, 22); From 408df3f24f3e77f5b7cb5cbfcd9ddd43ea95b4e3 Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 22:07:22 +0100 Subject: [PATCH 20/79] feat(wuss): add radio and option button icons New icon types wuss_ICON_TYPE_RADIO and wuss_ICON_TYPE_OPTION with latched selected state on the icon (not the task). Radios take an int group; selecting one clears every other selected radio on the same window sharing that non-zero group. Options toggle independently. New public API wuss_icon_get_selected / wuss_icon_set_selected; the latter respects group exclusivity but delivers no task event. A user click latches the new state on MOUSE_UP before wuss_EVENT_ICON fires, so the handler sees it; ADJUST-click toggles a radio. Glyphs are primitive-drawn (square ring + centre fill for radio, box + tick for option), font-height, at the left of the bbox with the label to the right. hit-test.c makes both types interactive. The icons test task grows a three-radio group, a standalone option and a label echoing the last change. Co-Authored-By: Claude Sonnet 5 --- CMakeLists.txt | 2 + include/wuss/icon.h | 49 +++++++++++++++-- libraries/wuss/icon.h | 11 +++- libraries/wuss/icon/create.c | 14 +++-- libraries/wuss/icon/draw.c | 88 ++++++++++++++++++++++++++++++ libraries/wuss/icon/get-selected.c | 12 ++++ libraries/wuss/icon/hit-test.c | 4 +- libraries/wuss/icon/set-selected.c | 51 +++++++++++++++++ libraries/wuss/mouse-click.c | 9 +++ libraries/wuss/test/tasks/icons.c | 54 ++++++++++++++++-- libraries/wuss/test/tasks/icons.h | 2 + 11 files changed, 278 insertions(+), 18 deletions(-) create mode 100644 libraries/wuss/icon/get-selected.c create mode 100644 libraries/wuss/icon/set-selected.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 6949ba1..573c36d 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -381,6 +381,7 @@ set(WUSS_ICON_SOURCES libraries/wuss/icon/draw.c libraries/wuss/icon/free.c libraries/wuss/icon/get-bbox.c + libraries/wuss/icon/get-selected.c libraries/wuss/icon/get-text.c libraries/wuss/icon/get-type.c libraries/wuss/icon/get-window.c @@ -388,6 +389,7 @@ set(WUSS_ICON_SOURCES libraries/wuss/icon/invalidate.c libraries/wuss/icon/screen-box.c libraries/wuss/icon/set-hidden.c + libraries/wuss/icon/set-selected.c libraries/wuss/icon/set-text.c libraries/wuss/icon.h) diff --git a/include/wuss/icon.h b/include/wuss/icon.h index 3b69357..7deab92 100644 --- a/include/wuss/icon.h +++ b/include/wuss/icon.h @@ -63,11 +63,23 @@ typedef enum wuss_icon_type * content. Not interactive: clicks fall through * to the task as wuss_EVENT_MOUSE. text is * ignored. */ - wuss_ICON_TYPE_FRAME /**< A grouping box: a one-pixel rectangle in fg + wuss_ICON_TYPE_FRAME, /**< A grouping box: a one-pixel rectangle in fg * around the bounding box, broken at the top-left * for an optional caption (text) drawn over the * window background. Not interactive: clicks fall * through to the task as wuss_EVENT_MOUSE. */ + wuss_ICON_TYPE_RADIO, /**< A radio button: a small ring at the left of the + * bounding box, filled when selected, with the + * label (text) to its right. Interactive: a click + * selects it and clears every other selected + * radio sharing its non-zero group, then the task + * is told via wuss_EVENT_ICON. */ + wuss_ICON_TYPE_OPTION /**< An option button: a small box at the left of + * the bounding box, ticked when selected, with + * the label (text) to its right. Interactive: a + * click toggles its own selected state (group is + * ignored), then the task is told via + * wuss_EVENT_ICON. */ } wuss_icon_type_t; @@ -113,13 +125,19 @@ typedef struct wuss_icon_spec wuss_colour_t fg; /**< Text colour, as an index into the system * palette. */ wuss_colour_t bg; /**< Fill/bevel base colour, as an index into the - * system palette. A label or frame may pass - * wuss_NO_BACKGROUND for text/outline with no - * fill; a button or pattern icon must pass a real - * index. */ + * system palette. A label, frame, radio or option + * icon may pass wuss_NO_BACKGROUND for no fill + * behind its text/glyph; a button or pattern icon + * must pass a real index. */ screen_pattern_t pattern; /**< Tile for wuss_ICON_TYPE_PATTERN; ignored by * other types. Zero (screen_PATTERN_SOLID) is a * safe default for zero-initialised specs. */ + int group; /**< wuss_ICON_TYPE_RADIO: exclusive-selection group. + * Selecting a radio clears every other selected + * radio on the same window with the same group. + * Zero (the default) means "no group": such a + * radio still toggles but never clears another. + * Ignored by all other icon types. */ wuss_icon_flags_t flags; /**< Appearance/behaviour flags. */ } wuss_icon_spec_t; @@ -225,6 +243,27 @@ const char *wuss_icon_get_text(const wuss_icon_t *icon); */ wuss_window_t *wuss_icon_get_window(const wuss_icon_t *icon); +/** + * Fetch a radio or option icon's selected (latched) state. + * + * \param[in] icon Icon to query. + * \return Non-zero if selected, zero otherwise. Always zero for icon types that + * have no latched state. + */ +int wuss_icon_get_selected(const wuss_icon_t *icon); + +/** + * Set a radio or option icon's selected state, invalidating it so the next + * redraw repaints it. For a radio with a non-zero group, selecting it (passing + * non-zero) also clears every other selected radio on the same window with that + * group. No task event is delivered -- this is the programmatic path, distinct + * from a user click. A no-op for icon types with no latched state. + * + * \param[in] icon Icon to change. + * \param[in] selected Non-zero to select, zero to deselect. + */ +void wuss_icon_set_selected(wuss_icon_t *icon, int selected); + #endif /* WUSS_ICONS */ #ifdef __cplusplus diff --git a/libraries/wuss/icon.h b/libraries/wuss/icon.h index 13c2c67..6674056 100644 --- a/libraries/wuss/icon.h +++ b/libraries/wuss/icon.h @@ -19,11 +19,18 @@ struct wuss_icon char *text; /* owned; never NULL ("" instead) */ wuss_colour_t fg; wuss_colour_t bg; - screen_pattern_t pattern; /* wuss_ICON_TYPE_PATTERN tile; 0 otherwise */ + screen_pattern_t pattern; /* wuss_ICON_TYPE_PATTERN tile; 0 otherwise */ + int group; /* radio: exclusive-selection group; 0 = none */ wuss_icon_flags_t flags; - int pressed; /* button: 1 while held with the pointer inside */ + int pressed; /* button: 1 while held with the pointer inside */ + int selected; /* radio/option: 1 while latched on */ }; +/* Set icon->selected, invalidating it. For a radio with a non-zero group, + * selecting it also clears every other selected radio on the same window with + * that group. Ignored for types with no latched state. */ +void wuss__icon_select(wuss_icon_t *icon, int selected); + /* Map an icon bbox (virtual document space) into screen space: * screen = content.x0 - scroll.x + bbox. wuss__icon_draw paints through this * so hit-testing and invalidation cannot drift from what is drawn. */ diff --git a/libraries/wuss/icon/create.c b/libraries/wuss/icon/create.c index 96b6f29..2f3026d 100644 --- a/libraries/wuss/icon/create.c +++ b/libraries/wuss/icon/create.c @@ -29,7 +29,9 @@ result_t wuss_icon_create(wuss_window_t *window, if (spec->type != wuss_ICON_TYPE_LABEL && spec->type != wuss_ICON_TYPE_BUTTON && spec->type != wuss_ICON_TYPE_PATTERN && - spec->type != wuss_ICON_TYPE_FRAME) + spec->type != wuss_ICON_TYPE_FRAME && + spec->type != wuss_ICON_TYPE_RADIO && + spec->type != wuss_ICON_TYPE_OPTION) return result_WUSS_BAD_ICON; if ((spec->type == wuss_ICON_TYPE_BUTTON || @@ -77,10 +79,12 @@ result_t wuss_icon_create(wuss_window_t *window, it->type = spec->type; it->fg = spec->fg; it->bg = spec->bg; - it->pattern = (spec->type == wuss_ICON_TYPE_PATTERN) ? spec->pattern - : screen_PATTERN_SOLID; - it->flags = spec->flags; - it->pressed = 0; + it->pattern = (spec->type == wuss_ICON_TYPE_PATTERN) ? spec->pattern + : screen_PATTERN_SOLID; + it->group = (spec->type == wuss_ICON_TYPE_RADIO) ? spec->group : 0; + it->flags = spec->flags; + it->pressed = 0; + it->selected = 0; window->icons[window->nicons++] = it; diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index 902ff2d..63f2f56 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -245,5 +245,93 @@ void wuss__icon_draw(wuss_t *wuss, } } break; + + case wuss_ICON_TYPE_RADIO: + case wuss_ICON_TYPE_OPTION: + { + colour_t glyph, bg; + box_t g; + int gsz, gy, tx; + + /* the glyph is a square the height of the font (min 8), sat at the left + * of the box and vertically centred; the label follows to its right */ + gsz = (font_height >= 8) ? font_height : 8; + if (gsz > b.y1 - b.y0) + gsz = b.y1 - b.y0; + gy = b.y0 + (b.y1 - b.y0 - gsz) / 2; + + g.x0 = b.x0; + g.y0 = gy; + g.x1 = b.x0 + gsz; + g.y1 = gy + gsz; + + glyph = (icon->flags & wuss_ICON_FLAGS_DISABLED) + ? wuss->palette[wuss->bevel_dark] + : fg; + + /* ground the glyph blends against: explicit bg, else the window backdrop, + * else fg (bmfont still needs a colour) -- same ladder as the label case */ + if (icon->bg != wuss_NO_BACKGROUND) + bg = wuss->palette[icon->bg]; + else if (icon->window->bg.colour != wuss_NO_BACKGROUND) + bg = (icon->window->bg.pattern != screen_PATTERN_SOLID) + ? wuss->palette[icon->window->bg.pattern_bg] + : wuss->palette[icon->window->bg.colour]; + else + bg = glyph; + + if (icon->bg != wuss_NO_BACKGROUND) + screen_draw_rect(scr, b.x0, b.y0, + SIZE2D(b.x1 - b.x0, b.y1 - b.y0), bg); + + if (icon->type == wuss_ICON_TYPE_RADIO) + { + /* a square ring (no circle primitive); a solid centre when selected */ + screen_draw_line(scr, g.x0 + 2, g.y0, g.x1 - 3, g.y0, glyph); + screen_draw_line(scr, g.x0 + 2, g.y1 - 1, g.x1 - 3, g.y1 - 1, glyph); + screen_draw_line(scr, g.x0, g.y0 + 2, g.x0, g.y1 - 3, glyph); + screen_draw_line(scr, g.x1 - 1, g.y0 + 2, g.x1 - 1, g.y1 - 3, glyph); + if (icon->selected) + screen_draw_rect(scr, g.x0 + 3, g.y0 + 3, + SIZE2D(gsz - 6, gsz - 6), glyph); + } + else + { + /* a box; a tick (two strokes) when selected */ + screen_draw_line(scr, g.x0, g.y0, g.x1 - 1, g.y0, glyph); + screen_draw_line(scr, g.x0, g.y1 - 1, g.x1 - 1, g.y1 - 1, glyph); + screen_draw_line(scr, g.x0, g.y0, g.x0, g.y1 - 1, glyph); + screen_draw_line(scr, g.x1 - 1, g.y0, g.x1 - 1, g.y1 - 1, glyph); + if (icon->selected) + { + screen_draw_line(scr, g.x0 + 2, g.y0 + gsz / 2, + g.x0 + gsz / 2 - 1, g.y1 - 3, glyph); + screen_draw_line(scr, g.x0 + gsz / 2 - 1, g.y1 - 3, + g.x1 - 3, g.y0 + 2, glyph); + } + } + + if (have_font) + { + point_t pos; + int interior_w, split_point; + bmfont_width_t width; + + tx = g.x1 + 4; + interior_w = (b.x1 - tx) - 1; + if (interior_w < 1) + interior_w = 1; + + bmfont_measure(wuss->font, icon->text, (int) strlen(icon->text), + interior_w, &split_point, &width); + NOT_USED(width); + + pos.x = tx; + pos.y = b.y0 + (b.y1 - b.y0 - font_height) / 2; + bmfont_draw(wuss->font, scr, icon->text, (int) strlen(icon->text), + glyph, bg, &pos, NULL); + } + } + break; } } diff --git a/libraries/wuss/icon/get-selected.c b/libraries/wuss/icon/get-selected.c new file mode 100644 index 0000000..6d8e06f --- /dev/null +++ b/libraries/wuss/icon/get-selected.c @@ -0,0 +1,12 @@ +/* get-selected.c -- wuss - query a radio/option icon's latched state */ + +#include + +#include "../impl.h" + +int wuss_icon_get_selected(const wuss_icon_t *icon) +{ + assert(icon != NULL); + + return icon->selected; +} diff --git a/libraries/wuss/icon/hit-test.c b/libraries/wuss/icon/hit-test.c index d7579e2..ac63f88 100644 --- a/libraries/wuss/icon/hit-test.c +++ b/libraries/wuss/icon/hit-test.c @@ -14,7 +14,9 @@ wuss_icon_t *wuss__icon_hit_test(wuss_window_t *window, point_t doc_point) { it = window->icons[i]; - if (it->type != wuss_ICON_TYPE_BUTTON) + if (it->type != wuss_ICON_TYPE_BUTTON && + it->type != wuss_ICON_TYPE_RADIO && + it->type != wuss_ICON_TYPE_OPTION) continue; if (it->flags & (wuss_ICON_FLAGS_HIDDEN | wuss_ICON_FLAGS_DISABLED)) diff --git a/libraries/wuss/icon/set-selected.c b/libraries/wuss/icon/set-selected.c new file mode 100644 index 0000000..72ec17c --- /dev/null +++ b/libraries/wuss/icon/set-selected.c @@ -0,0 +1,51 @@ +/* set-selected.c -- wuss - latch a radio/option icon's selected state */ + +#include + +#include "../impl.h" + +void wuss__icon_select(wuss_icon_t *icon, int selected) +{ + wuss_window_t *window; + wuss_icon_t *other; + int i; + + assert(icon != NULL); + + if (icon->type != wuss_ICON_TYPE_RADIO && + icon->type != wuss_ICON_TYPE_OPTION) + return; + + selected = selected ? 1 : 0; + + /* selecting a grouped radio clears its siblings first */ + if (selected && + icon->type == wuss_ICON_TYPE_RADIO && + icon->group != 0) + { + window = icon->window; + for (i = 0; i < window->nicons; i++) + { + other = window->icons[i]; + if (other == icon) + continue; + if (other->type != wuss_ICON_TYPE_RADIO || other->group != icon->group) + continue; + if (!other->selected) + continue; + other->selected = 0; + wuss__icon_invalidate(other); + } + } + + if (icon->selected == selected) + return; + + icon->selected = selected; + wuss__icon_invalidate(icon); +} + +void wuss_icon_set_selected(wuss_icon_t *icon, int selected) +{ + wuss__icon_select(icon, selected); +} diff --git a/libraries/wuss/mouse-click.c b/libraries/wuss/mouse-click.c index fa4ffa0..ec35940 100644 --- a/libraries/wuss/mouse-click.c +++ b/libraries/wuss/mouse-click.c @@ -208,6 +208,15 @@ result_t wuss_mouse_click(wuss_t *wuss, icon->pressed = 0; wuss->pressed_icon = NULL; wuss__icon_invalidate(icon); + + /* a completed click latches radio/option state before the task is + * told, so the wuss_EVENT_ICON handler sees the new value */ + if (icon->type == wuss_ICON_TYPE_OPTION) + wuss__icon_select(icon, !icon->selected); + else if (icon->type == wuss_ICON_TYPE_RADIO) + wuss__icon_select(icon, + (button & wuss_BUTTON_ADJUST) ? !icon->selected + : 1); } event.kind = wuss_EVENT_ICON; diff --git a/libraries/wuss/test/tasks/icons.c b/libraries/wuss/test/tasks/icons.c index ff8612e..feda262 100644 --- a/libraries/wuss/test/tasks/icons.c +++ b/libraries/wuss/test/tasks/icons.c @@ -28,13 +28,15 @@ result_t icons_create(wuss_t *wuss, icons_task_t *task) { /* [0..3], one swatch per built-in pattern, then [.. +3] a grouping frame - * with two differently-justified labels inside it */ - enum { ICONS_NSPECS = 4 + screen_PATTERN__LIMIT + 3 }; + * with two differently-justified labels inside it, then [.. +5] three grouped + * radios, a standalone option and a label echoing the selection */ + enum { ICONS_NSPECS = 4 + screen_PATTERN__LIMIT + 3 + 5 }; wuss_task_t delegate; wuss_icon_spec_t specs[ICONS_NSPECS]; wuss_icon_t *made[ICONS_NSPECS]; int p; int g; /* index of the first frame spec */ + int r; /* radio index */ result_t rc; task->font = font; @@ -44,6 +46,8 @@ result_t icons_create(wuss_t *wuss, task->button = NULL; task->counter = NULL; task->count = 0; + task->opt = NULL; + task->state = NULL; delegate = wuss_task_start(icons_handle, task); @@ -131,12 +135,38 @@ result_t icons_create(wuss_t *wuss, specs[g + 2].bg = wuss_NO_BACKGROUND; specs[g + 2].flags = wuss_ICON_FLAGS_JUSTIFY_CENTRE; + /* [g+3..g+5] three radios sharing group 1, [g+6] a standalone option, + * [g+7] a label echoing whichever control last changed */ + for (r = 0; r < 3; r++) + { + specs[g + 3 + r].bbox = (box_t) BOX_POS_SIZE(28, 380 + r * 20, 150, 16); + specs[g + 3 + r].type = wuss_ICON_TYPE_RADIO; + specs[g + 3 + r].text = (r == 0) ? "Red" : (r == 1) ? "Green" : "Blue"; + specs[g + 3 + r].fg = palette_PICO8_DARK_BLUE; + specs[g + 3 + r].bg = wuss_NO_BACKGROUND; + specs[g + 3 + r].group = 1; + } + + specs[g + 6].bbox = (box_t) BOX_POS_SIZE(28, 444, 150, 16); + specs[g + 6].type = wuss_ICON_TYPE_OPTION; + specs[g + 6].text = "Wireframe"; + specs[g + 6].fg = palette_PICO8_DARK_BLUE; + specs[g + 6].bg = wuss_NO_BACKGROUND; + + specs[g + 7].bbox = (box_t) BOX_POS_SIZE(28, 464, 180, 14); + specs[g + 7].type = wuss_ICON_TYPE_LABEL; + specs[g + 7].text = "(no selection)"; + specs[g + 7].fg = palette_PICO8_DARK_BLUE; + specs[g + 7].bg = wuss_NO_BACKGROUND; + rc = wuss_icon_create_array(task->window, specs, ICONS_NSPECS, made); if (rc != result_OK) goto failure; task->button = made[1]; task->counter = made[2]; + task->opt = made[g + 6]; + task->state = made[g + 7]; return result_OK; @@ -236,13 +266,27 @@ static result_t icons_redraw(const wuss_event_t *event, void *task_data) static result_t icons_icon(const wuss_event_t *event, void *task_data) { icons_task_t *tcx; - char buf[16]; + wuss_icon_t *icon; + char buf[48]; - tcx = task_data; + tcx = task_data; + icon = event->data.icon.icon; + + /* a radio/option latches on MOUSE_UP -- report the state then */ + if (event->data.icon.action == wuss_MOUSE_UP && + (wuss_icon_get_type(icon) == wuss_ICON_TYPE_RADIO || + wuss_icon_get_type(icon) == wuss_ICON_TYPE_OPTION)) + { + snprintf(buf, sizeof(buf), "%s: %s%s", + wuss_icon_get_text(icon), + wuss_icon_get_selected(icon) ? "on" : "off", + (icon == tcx->opt) ? "" : " (radio)"); + return wuss_icon_set_text(tcx->state, buf); + } if (event->data.icon.action != wuss_MOUSE_DOWN) return result_OK; - if (event->data.icon.icon != tcx->button) + if (icon != tcx->button) return result_OK; tcx->count++; diff --git a/libraries/wuss/test/tasks/icons.h b/libraries/wuss/test/tasks/icons.h index 9e9fb82..29da3f2 100644 --- a/libraries/wuss/test/tasks/icons.h +++ b/libraries/wuss/test/tasks/icons.h @@ -22,6 +22,8 @@ typedef struct icons_task wuss_icon_t *button; /* "Press me" */ wuss_icon_t *counter; /* label showing hit count */ int count; + wuss_icon_t *opt; /* standalone option button */ + wuss_icon_t *state; /* label echoing radio/option selection */ } icons_task_t; From 6018ff6ad92a91dc6ab2e5ae6e0380368e467556 Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 22:31:06 +0100 Subject: [PATCH 21/79] feat(wuss): add plain bitmap icons New icon type wuss_ICON_TYPE_BITMAP draws a caller-owned const bitmap_t * at the top-left of the bbox, alpha-blended and clipped to the box, no scaling. The bitmap is borrowed, not copied, and must outlive the icon (unlike text). New flag wuss_ICON_FLAGS_INTERACTIVE makes a bitmap icon hit-testable and raise wuss_EVENT_ICON like a button; without it the icon is pure decoration and clicks fall through as wuss_EVENT_MOUSE. Other icon types ignore the flag. draw.c narrows a stack copy of the screen's clip to the icon box before the blit. create.c rejects a bitmap spec with no bitmap. hit-test.c treats an interactive bitmap like the other clickable types. The icons test task loads resources/wuss/9tile.png and shows one decorative and one interactive bitmap icon; icons_create gains a resources argument. Co-Authored-By: Claude Sonnet 5 --- apps/wuss/main.c | 2 +- include/wuss/icon.h | 31 +++++++++++-- libraries/wuss/icon.h | 1 + libraries/wuss/icon/create.c | 17 ++++--- libraries/wuss/icon/draw.c | 23 ++++++++++ libraries/wuss/icon/hit-test.c | 15 +++++-- libraries/wuss/test/tasks/icons.c | 75 ++++++++++++++++++++++++------- libraries/wuss/test/tasks/icons.h | 5 +++ 8 files changed, 139 insertions(+), 30 deletions(-) diff --git a/apps/wuss/main.c b/apps/wuss/main.c index 02f5414..2e62328 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -177,7 +177,7 @@ static result_t spawn_icons(void) icons_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = icons_create(g_wuss, g_palette, g_daydream_font, t); + rc = icons_create(g_wuss, g_palette, g_daydream_font, g_resources, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } diff --git a/include/wuss/icon.h b/include/wuss/icon.h index 7deab92..17cf28f 100644 --- a/include/wuss/icon.h +++ b/include/wuss/icon.h @@ -74,12 +74,22 @@ typedef enum wuss_icon_type * selects it and clears every other selected * radio sharing its non-zero group, then the task * is told via wuss_EVENT_ICON. */ - wuss_ICON_TYPE_OPTION /**< An option button: a small box at the left of + wuss_ICON_TYPE_OPTION, /**< An option button: a small box at the left of * the bounding box, ticked when selected, with * the label (text) to its right. Interactive: a * click toggles its own selected state (group is * ignored), then the task is told via * wuss_EVENT_ICON. */ + wuss_ICON_TYPE_BITMAP /**< A caller-owned bitmap (spec.bitmap) drawn at + * the top-left of the bounding box, alpha-blended + * against what is already there, clipped to the + * box; no scaling. The bitmap is borrowed, not + * copied, and must outlive the icon (unlike + * text). fg, bg, text and pattern are ignored. + * Not interactive unless + * wuss_ICON_FLAGS_INTERACTIVE is set, in which + * case clicks raise wuss_EVENT_ICON like a + * button. */ } wuss_icon_type_t; @@ -99,12 +109,20 @@ typedef enum wuss_icon_flags * the text in the bounding box. * Takes precedence over * wuss_ICON_FLAGS_JUSTIFY_RIGHT. */ - wuss_ICON_FLAGS_DEFAULT = 1 << 4 /**< wuss_ICON_TYPE_BUTTON: draw as a + wuss_ICON_FLAGS_DEFAULT = 1 << 4, /**< wuss_ICON_TYPE_BUTTON: draw as a * default action button, in the * window manager's accent colours * (see wuss_config_t::accent) * instead of the ordinary bevel. * Ignored by other icon types. */ + wuss_ICON_FLAGS_INTERACTIVE = 1 << 5 /**< wuss_ICON_TYPE_BITMAP: hit-test + * the icon and raise wuss_EVENT_ICON + * on a click, like a button. + * Without it a bitmap icon is pure + * decoration and clicks fall through + * as wuss_EVENT_MOUSE. Ignored by + * other icon types (interactive or + * not by their nature). */ } wuss_icon_flags_t; @@ -132,6 +150,11 @@ typedef struct wuss_icon_spec screen_pattern_t pattern; /**< Tile for wuss_ICON_TYPE_PATTERN; ignored by * other types. Zero (screen_PATTERN_SOLID) is a * safe default for zero-initialised specs. */ + const bitmap_t *bitmap; /**< wuss_ICON_TYPE_BITMAP: the image to draw. + * Borrowed, not copied; must outlive the icon. + * Ignored by other types; NULL (the default) is + * only valid when type is not + * wuss_ICON_TYPE_BITMAP. */ int group; /**< wuss_ICON_TYPE_RADIO: exclusive-selection group. * Selecting a radio clears every other selected * radio on the same window with the same group. @@ -155,8 +178,8 @@ wuss_icon_spec_t; * does not need it. * \return \ref result_OK on success, \ref result_OOM on allocation failure, * \ref result_WUSS_BAD_COLOUR if fg or bg is out of range for the - * palette, or \ref result_WUSS_BAD_ICON if type is unknown or a button - * spec has no fill colour. + * palette, or \ref result_WUSS_BAD_ICON if type is unknown, a button or + * pattern spec has no fill colour, or a bitmap spec has no bitmap. */ result_t wuss_icon_create(wuss_window_t *window, const wuss_icon_spec_t *spec, diff --git a/libraries/wuss/icon.h b/libraries/wuss/icon.h index 6674056..a638b49 100644 --- a/libraries/wuss/icon.h +++ b/libraries/wuss/icon.h @@ -20,6 +20,7 @@ struct wuss_icon wuss_colour_t fg; wuss_colour_t bg; screen_pattern_t pattern; /* wuss_ICON_TYPE_PATTERN tile; 0 otherwise */ + const bitmap_t *bitmap; /* wuss_ICON_TYPE_BITMAP image; borrowed, or NULL */ int group; /* radio: exclusive-selection group; 0 = none */ wuss_icon_flags_t flags; int pressed; /* button: 1 while held with the pointer inside */ diff --git a/libraries/wuss/icon/create.c b/libraries/wuss/icon/create.c index 2f3026d..2a53b1d 100644 --- a/libraries/wuss/icon/create.c +++ b/libraries/wuss/icon/create.c @@ -31,7 +31,8 @@ result_t wuss_icon_create(wuss_window_t *window, spec->type != wuss_ICON_TYPE_PATTERN && spec->type != wuss_ICON_TYPE_FRAME && spec->type != wuss_ICON_TYPE_RADIO && - spec->type != wuss_ICON_TYPE_OPTION) + spec->type != wuss_ICON_TYPE_OPTION && + spec->type != wuss_ICON_TYPE_BITMAP) return result_WUSS_BAD_ICON; if ((spec->type == wuss_ICON_TYPE_BUTTON || @@ -39,6 +40,9 @@ result_t wuss_icon_create(wuss_window_t *window, spec->bg == wuss_NO_BACKGROUND) return result_WUSS_BAD_ICON; + if (spec->type == wuss_ICON_TYPE_BITMAP && spec->bitmap == NULL) + return result_WUSS_BAD_ICON; + if (spec->type == wuss_ICON_TYPE_PATTERN && (spec->pattern < 0 || spec->pattern >= screen_PATTERN__LIMIT)) return result_WUSS_BAD_ICON; @@ -74,13 +78,14 @@ result_t wuss_icon_create(wuss_window_t *window, } memcpy(it->text, src, len + 1); - it->window = window; - it->bbox = spec->bbox; - it->type = spec->type; - it->fg = spec->fg; - it->bg = spec->bg; + it->window = window; + it->bbox = spec->bbox; + it->type = spec->type; + it->fg = spec->fg; + it->bg = spec->bg; it->pattern = (spec->type == wuss_ICON_TYPE_PATTERN) ? spec->pattern : screen_PATTERN_SOLID; + it->bitmap = (spec->type == wuss_ICON_TYPE_BITMAP) ? spec->bitmap : NULL; it->group = (spec->type == wuss_ICON_TYPE_RADIO) ? spec->group : 0; it->flags = spec->flags; it->pressed = 0; diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index 63f2f56..eb1861a 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -333,5 +333,28 @@ void wuss__icon_draw(wuss_t *wuss, } } break; + + case wuss_ICON_TYPE_BITMAP: + { + screen_t clipped; + + if (icon->bitmap == NULL) + break; + + /* screen_draw_bitmap clips to scr->clip and does not scale, so narrow + * the clip to the icon box (intersected with whatever redraw already + * set) and blit at the box's top-left */ + clipped = *scr; + if (clipped.clip.x0 < b.x0) clipped.clip.x0 = b.x0; + if (clipped.clip.y0 < b.y0) clipped.clip.y0 = b.y0; + if (clipped.clip.x1 > b.x1) clipped.clip.x1 = b.x1; + if (clipped.clip.y1 > b.y1) clipped.clip.y1 = b.y1; + if (clipped.clip.x1 <= clipped.clip.x0 || + clipped.clip.y1 <= clipped.clip.y0) + break; + + screen_draw_bitmap(&clipped, b.x0, b.y0, icon->bitmap); + } + break; } } diff --git a/libraries/wuss/icon/hit-test.c b/libraries/wuss/icon/hit-test.c index ac63f88..9cb66bf 100644 --- a/libraries/wuss/icon/hit-test.c +++ b/libraries/wuss/icon/hit-test.c @@ -14,10 +14,19 @@ wuss_icon_t *wuss__icon_hit_test(wuss_window_t *window, point_t doc_point) { it = window->icons[i]; - if (it->type != wuss_ICON_TYPE_BUTTON && - it->type != wuss_ICON_TYPE_RADIO && - it->type != wuss_ICON_TYPE_OPTION) + /* a bitmap icon is interactive only when it asks to be; the other + * clickable types always are */ + if (it->type == wuss_ICON_TYPE_BITMAP) + { + if (!(it->flags & wuss_ICON_FLAGS_INTERACTIVE)) + continue; + } + else if (it->type != wuss_ICON_TYPE_BUTTON && + it->type != wuss_ICON_TYPE_RADIO && + it->type != wuss_ICON_TYPE_OPTION) + { continue; + } if (it->flags & (wuss_ICON_FLAGS_HIDDEN | wuss_ICON_FLAGS_DISABLED)) continue; diff --git a/libraries/wuss/test/tasks/icons.c b/libraries/wuss/test/tasks/icons.c index feda262..2a188b1 100644 --- a/libraries/wuss/test/tasks/icons.c +++ b/libraries/wuss/test/tasks/icons.c @@ -11,11 +11,13 @@ #include "fortify/fortify.h" #endif +#include "framebuf/bitmap.h" #include "framebuf/palettes.h" #include "framebuf/screen.h" #include "geom/box.h" #include "geom/point.h" #include "geom/size.h" +#include "io/path.h" #include "icons.h" @@ -25,29 +27,40 @@ result_t icons_create(wuss_t *wuss, const colour_t *palette, bmfont_t *font, + const char *resources, icons_task_t *task) { /* [0..3], one swatch per built-in pattern, then [.. +3] a grouping frame * with two differently-justified labels inside it, then [.. +5] three grouped - * radios, a standalone option and a label echoing the selection */ - enum { ICONS_NSPECS = 4 + screen_PATTERN__LIMIT + 3 + 5 }; + * radios, a standalone option and a label echoing the selection, then [.. +2] + * a decorative bitmap icon and an interactive one that bumps the counter */ + enum { ICONS_NSPECS = 4 + screen_PATTERN__LIMIT + 3 + 5 + 2 }; wuss_task_t delegate; wuss_icon_spec_t specs[ICONS_NSPECS]; wuss_icon_t *made[ICONS_NSPECS]; + const char *sprite_path; int p; - int g; /* index of the first frame spec */ - int r; /* radio index */ + int g; /* index of the first frame spec */ + int r; /* radio index */ + int nspecs; /* live spec count (sprite icons are optional) */ result_t rc; - task->font = font; - task->label = palette[palette_PICO8_DARK_BLUE]; - task->paper = palette[palette_PICO8_LIGHT_GREY]; /* the window bg, below */ - task->window = NULL; - task->button = NULL; - task->counter = NULL; - task->count = 0; - task->opt = NULL; - task->state = NULL; + task->font = font; + task->label = palette[palette_PICO8_DARK_BLUE]; + task->paper = palette[palette_PICO8_LIGHT_GREY]; /* the window bg, below */ + task->window = NULL; + task->button = NULL; + task->counter = NULL; + task->count = 0; + task->opt = NULL; + task->state = NULL; + task->has_sprite = 0; + task->hotspot = NULL; + + sprite_path = path_join_filename(resources, 3, "resources", "wuss", + path_join_leafname("9tile", "png")); + if (bitmap_load_png(&task->sprite, sprite_path) == result_OK) + task->has_sprite = 1; delegate = wuss_task_start(icons_handle, task); @@ -63,7 +76,11 @@ result_t icons_create(wuss_t *wuss, SIZE2D(0, 0), &task->window); if (rc != result_OK) + { + if (task->has_sprite) + free(task->sprite.base); return rc; + } memset(specs, 0, sizeof(specs)); @@ -159,7 +176,27 @@ result_t icons_create(wuss_t *wuss, specs[g + 7].fg = palette_PICO8_DARK_BLUE; specs[g + 7].bg = wuss_NO_BACKGROUND; - rc = wuss_icon_create_array(task->window, specs, ICONS_NSPECS, made); + nspecs = g + 8; + + /* [g+8] a decorative bitmap, [g+9] an interactive one that bumps the + * counter -- only if the sprite loaded */ + if (task->has_sprite) + { + specs[g + 8].bbox = (box_t) BOX_POS_SIZE(28, 484, task->sprite.size.w, + task->sprite.size.h); + specs[g + 8].type = wuss_ICON_TYPE_BITMAP; + specs[g + 8].bitmap = &task->sprite; + + specs[g + 9].bbox = (box_t) BOX_POS_SIZE(120, 484, task->sprite.size.w, + task->sprite.size.h); + specs[g + 9].type = wuss_ICON_TYPE_BITMAP; + specs[g + 9].bitmap = &task->sprite; + specs[g + 9].flags = wuss_ICON_FLAGS_INTERACTIVE; + + nspecs = g + 10; + } + + rc = wuss_icon_create_array(task->window, specs, nspecs, made); if (rc != result_OK) goto failure; @@ -167,12 +204,16 @@ result_t icons_create(wuss_t *wuss, task->counter = made[2]; task->opt = made[g + 6]; task->state = made[g + 7]; + if (task->has_sprite) + task->hotspot = made[g + 9]; return result_OK; failure: wuss_window_close(task->window); task->window = NULL; + if (task->has_sprite) + free(task->sprite.base); return rc; } @@ -286,7 +327,7 @@ static result_t icons_icon(const wuss_event_t *event, void *task_data) if (event->data.icon.action != wuss_MOUSE_DOWN) return result_OK; - if (icon != tcx->button) + if (icon != tcx->button && icon != tcx->hotspot) return result_OK; tcx->count++; @@ -312,7 +353,9 @@ result_t icons_handle(wuss_window_t *window, return icons_icon(event, task_data); case wuss_EVENT_CLOSE: - wuss_window_close(window); + wuss_window_close(window); /* frees the icons, which only borrowed sprite */ + if (tcx->has_sprite) + free(tcx->sprite.base); free(tcx); /* calloc'd per instance by the spawner */ return result_OK; diff --git a/libraries/wuss/test/tasks/icons.h b/libraries/wuss/test/tasks/icons.h index 29da3f2..6d91d95 100644 --- a/libraries/wuss/test/tasks/icons.h +++ b/libraries/wuss/test/tasks/icons.h @@ -5,6 +5,7 @@ #ifdef USE_SDL +#include "framebuf/bitmap.h" #include "framebuf/bmfont.h" #include "framebuf/colour.h" #include "wuss/icon.h" @@ -24,6 +25,9 @@ typedef struct icons_task int count; wuss_icon_t *opt; /* standalone option button */ wuss_icon_t *state; /* label echoing radio/option selection */ + bitmap_t sprite; /* borrowed by the two BITMAP icons; freed on close */ + int has_sprite; + wuss_icon_t *hotspot; /* the interactive BITMAP icon */ } icons_task_t; @@ -33,6 +37,7 @@ wuss_event_fn_t icons_handle; result_t icons_create(wuss_t *wuss, const colour_t *palette, bmfont_t *font, + const char *resources, icons_task_t *task); From f1fba1b29c2bc1c61f69e8e8d0a8e91b4745b6db Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 22:41:39 +0100 Subject: [PATCH 22/79] feat(wuss): add menu-entry icons and hover tracking New icon type wuss_ICON_TYPE_MENU_ENTRY: a full-width row drawn in fg over the window background, inverting to the accent colours while the pointer is over it. Reuses the selected flag for a left-edge tick; wuss_ICON_FLAGS_SUBMENU adds a right-edge arrow; wuss_ICON_FLAGS_SEPARATOR draws a horizontal rule instead of text and makes the row inert. Disabled entries never highlight. Hover tracking: struct wuss gains hover_icon, struct wuss_icon gains hovered. wuss__icon_set_hover swaps the flag between the old and new hovered icon and invalidates whichever changed. mouse-move.c calls it with the icon under the pointer, or NULL at every early-out (no window, over furniture, no task). Cleared on window close and icon delete. This is the hook the menu helper (WUSS_MENUS) will use for submenu-on-hover. The icons test task grows a four-row menu strip: plain, ticked, submenu, separator. Co-Authored-By: Claude Sonnet 5 --- CMakeLists.txt | 1 + include/wuss/icon.h | 27 ++++++++++- libraries/wuss/create.c | 1 + libraries/wuss/icon.h | 9 +++- libraries/wuss/icon/create.c | 4 +- libraries/wuss/icon/delete.c | 2 + libraries/wuss/icon/draw.c | 77 +++++++++++++++++++++++++++++++ libraries/wuss/icon/hit-test.c | 9 +++- libraries/wuss/icon/set-hover.c | 28 +++++++++++ libraries/wuss/impl.h | 4 ++ libraries/wuss/mouse-move.c | 21 ++++++++- libraries/wuss/test/tasks/icons.c | 40 ++++++++++++++-- libraries/wuss/window/close.c | 2 + 13 files changed, 215 insertions(+), 10 deletions(-) create mode 100644 libraries/wuss/icon/set-hover.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 573c36d..7f69191 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -389,6 +389,7 @@ set(WUSS_ICON_SOURCES libraries/wuss/icon/invalidate.c libraries/wuss/icon/screen-box.c libraries/wuss/icon/set-hidden.c + libraries/wuss/icon/set-hover.c libraries/wuss/icon/set-selected.c libraries/wuss/icon/set-text.c libraries/wuss/icon.h) diff --git a/include/wuss/icon.h b/include/wuss/icon.h index 17cf28f..50203b5 100644 --- a/include/wuss/icon.h +++ b/include/wuss/icon.h @@ -80,7 +80,7 @@ typedef enum wuss_icon_type * click toggles its own selected state (group is * ignored), then the task is told via * wuss_EVENT_ICON. */ - wuss_ICON_TYPE_BITMAP /**< A caller-owned bitmap (spec.bitmap) drawn at + wuss_ICON_TYPE_BITMAP, /**< A caller-owned bitmap (spec.bitmap) drawn at * the top-left of the bounding box, alpha-blended * against what is already there, clipped to the * box; no scaling. The bitmap is borrowed, not @@ -90,6 +90,19 @@ typedef enum wuss_icon_type * wuss_ICON_FLAGS_INTERACTIVE is set, in which * case clicks raise wuss_EVENT_ICON like a * button. */ + wuss_ICON_TYPE_MENU_ENTRY /**< A menu row: text left-justified across the + * bounding box, drawn in fg over the window + * background, or inverted (window manager's title + * colours) while the pointer is over it. An + * optional tick at the left edge when the icon is + * selected (see wuss_icon_set_selected), and an + * optional submenu arrow at the right edge with + * wuss_ICON_FLAGS_SUBMENU. + * wuss_ICON_FLAGS_SEPARATOR draws a horizontal + * rule instead of text. Interactive: a click + * raises wuss_EVENT_ICON like a button; a + * disabled entry never highlights and its clicks + * fall through. */ } wuss_icon_type_t; @@ -115,7 +128,7 @@ typedef enum wuss_icon_flags * (see wuss_config_t::accent) * instead of the ordinary bevel. * Ignored by other icon types. */ - wuss_ICON_FLAGS_INTERACTIVE = 1 << 5 /**< wuss_ICON_TYPE_BITMAP: hit-test + wuss_ICON_FLAGS_INTERACTIVE = 1 << 5, /**< wuss_ICON_TYPE_BITMAP: hit-test * the icon and raise wuss_EVENT_ICON * on a click, like a button. * Without it a bitmap icon is pure @@ -123,6 +136,16 @@ typedef enum wuss_icon_flags * as wuss_EVENT_MOUSE. Ignored by * other icon types (interactive or * not by their nature). */ + wuss_ICON_FLAGS_SUBMENU = 1 << 6, /**< wuss_ICON_TYPE_MENU_ENTRY: draw a + * right-pointing arrow at the right + * edge, marking an entry that opens + * a submenu. Ignored by other + * types. */ + wuss_ICON_FLAGS_SEPARATOR = 1 << 7 /**< wuss_ICON_TYPE_MENU_ENTRY: draw a + * horizontal rule across the + * bounding box instead of text; the + * entry is inert (not hit-tested). + * Ignored by other types. */ } wuss_icon_flags_t; diff --git a/libraries/wuss/create.c b/libraries/wuss/create.c index 13172f6..db80d01 100644 --- a/libraries/wuss/create.c +++ b/libraries/wuss/create.c @@ -211,6 +211,7 @@ result_t wuss_create(screen_t *scr, #endif #ifdef WUSS_ICONS w->pressed_icon = NULL; + w->hover_icon = NULL; #endif w->ndirty = 0; diff --git a/libraries/wuss/icon.h b/libraries/wuss/icon.h index a638b49..7794f4e 100644 --- a/libraries/wuss/icon.h +++ b/libraries/wuss/icon.h @@ -24,7 +24,9 @@ struct wuss_icon int group; /* radio: exclusive-selection group; 0 = none */ wuss_icon_flags_t flags; int pressed; /* button: 1 while held with the pointer inside */ - int selected; /* radio/option: 1 while latched on */ + int selected; /* radio/option: 1 while latched on; menu entry: + * 1 draws a tick */ + int hovered; /* menu entry: 1 while the pointer is over it */ }; /* Set icon->selected, invalidating it. For a radio with a non-zero group, @@ -32,6 +34,11 @@ struct wuss_icon * that group. Ignored for types with no latched state. */ void wuss__icon_select(wuss_icon_t *icon, int selected); +/* Make "icon" (may be NULL) the hovered icon: clears the hovered flag on the + * previous wuss->hover_icon and sets it on the new one, invalidating whichever + * of the two changed. A no-op if nothing changed. */ +void wuss__icon_set_hover(wuss_t *wuss, wuss_icon_t *icon); + /* Map an icon bbox (virtual document space) into screen space: * screen = content.x0 - scroll.x + bbox. wuss__icon_draw paints through this * so hit-testing and invalidation cannot drift from what is drawn. */ diff --git a/libraries/wuss/icon/create.c b/libraries/wuss/icon/create.c index 2a53b1d..7258864 100644 --- a/libraries/wuss/icon/create.c +++ b/libraries/wuss/icon/create.c @@ -32,7 +32,8 @@ result_t wuss_icon_create(wuss_window_t *window, spec->type != wuss_ICON_TYPE_FRAME && spec->type != wuss_ICON_TYPE_RADIO && spec->type != wuss_ICON_TYPE_OPTION && - spec->type != wuss_ICON_TYPE_BITMAP) + spec->type != wuss_ICON_TYPE_BITMAP && + spec->type != wuss_ICON_TYPE_MENU_ENTRY) return result_WUSS_BAD_ICON; if ((spec->type == wuss_ICON_TYPE_BUTTON || @@ -90,6 +91,7 @@ result_t wuss_icon_create(wuss_window_t *window, it->flags = spec->flags; it->pressed = 0; it->selected = 0; + it->hovered = 0; window->icons[window->nicons++] = it; diff --git a/libraries/wuss/icon/delete.c b/libraries/wuss/icon/delete.c index 5f5932b..0642fac 100644 --- a/libraries/wuss/icon/delete.c +++ b/libraries/wuss/icon/delete.c @@ -20,6 +20,8 @@ void wuss_icon_delete(wuss_icon_t *icon) if (window->wuss->pressed_icon == icon) window->wuss->pressed_icon = NULL; + if (window->wuss->hover_icon == icon) + window->wuss->hover_icon = NULL; wuss__icon_invalidate(icon); diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index eb1861a..dafacd4 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -356,5 +356,82 @@ void wuss__icon_draw(wuss_t *wuss, screen_draw_bitmap(&clipped, b.x0, b.y0, icon->bitmap); } break; + + case wuss_ICON_TYPE_MENU_ENTRY: + { + colour_t ink, ground; + int disabled, highlit, pad, mid_y; + + disabled = (icon->flags & wuss_ICON_FLAGS_DISABLED) != 0; + highlit = icon->hovered && !disabled; + pad = 4; + + /* highlight inverts to the accent colours (the window manager's + * emphasis pair); otherwise ink over the row's own/inherited ground */ + if (highlit) + { + ground = wuss->palette[wuss->accent_bg]; + ink = wuss->palette[wuss->accent_fg]; + } + else + { + if (icon->bg != wuss_NO_BACKGROUND) + ground = wuss->palette[icon->bg]; + else if (icon->window->bg.colour != wuss_NO_BACKGROUND) + ground = (icon->window->bg.pattern != screen_PATTERN_SOLID) + ? wuss->palette[icon->window->bg.pattern_bg] + : wuss->palette[icon->window->bg.colour]; + else + ground = fg; /* bmfont still needs a blend colour */ + ink = disabled ? wuss->palette[wuss->bevel_dark] : fg; + } + + if (highlit || icon->bg != wuss_NO_BACKGROUND) + screen_draw_rect(scr, b.x0, b.y0, + SIZE2D(b.x1 - b.x0, b.y1 - b.y0), ground); + + if (icon->flags & wuss_ICON_FLAGS_SEPARATOR) + { + mid_y = b.y0 + (b.y1 - b.y0) / 2; + screen_draw_line(scr, b.x0 + pad, mid_y, b.x1 - 1 - pad, mid_y, ink); + break; + } + + /* left-edge tick when selected */ + if (icon->selected) + { + int cx, cy, h; + + h = (font_height >= 8) ? font_height : 8; + cx = b.x0 + pad; + cy = b.y0 + (b.y1 - b.y0 - h) / 2; + screen_draw_line(scr, cx, cy + h / 2, cx + h / 2 - 1, cy + h - 2, ink); + screen_draw_line(scr, cx + h / 2 - 1, cy + h - 2, cx + h - 2, cy, ink); + } + + /* right-edge arrow for a submenu entry */ + if (icon->flags & wuss_ICON_FLAGS_SUBMENU) + { + int ax, ay, r, dy; + + r = (font_height >= 8) ? font_height / 3 : 3; + ax = b.x1 - 1 - pad - r; + ay = b.y0 + (b.y1 - b.y0) / 2; + for (dy = -r; dy <= r; dy++) + screen_draw_line(scr, ax, ay + dy, + ax + (r - (dy < 0 ? -dy : dy)), ay + dy, ink); + } + + if (have_font) + { + point_t pos; + + pos.x = b.x0 + pad + font_height; /* leave room for a tick */ + pos.y = b.y0 + (b.y1 - b.y0 - font_height) / 2; + bmfont_draw(wuss->font, scr, icon->text, (int) strlen(icon->text), + ink, ground, &pos, NULL); + } + } + break; } } diff --git a/libraries/wuss/icon/hit-test.c b/libraries/wuss/icon/hit-test.c index 9cb66bf..e0a12f8 100644 --- a/libraries/wuss/icon/hit-test.c +++ b/libraries/wuss/icon/hit-test.c @@ -14,13 +14,18 @@ wuss_icon_t *wuss__icon_hit_test(wuss_window_t *window, point_t doc_point) { it = window->icons[i]; - /* a bitmap icon is interactive only when it asks to be; the other - * clickable types always are */ + /* a bitmap icon is interactive only when it asks to be; a menu separator + * never is; the other clickable types always are */ if (it->type == wuss_ICON_TYPE_BITMAP) { if (!(it->flags & wuss_ICON_FLAGS_INTERACTIVE)) continue; } + else if (it->type == wuss_ICON_TYPE_MENU_ENTRY) + { + if (it->flags & wuss_ICON_FLAGS_SEPARATOR) + continue; + } else if (it->type != wuss_ICON_TYPE_BUTTON && it->type != wuss_ICON_TYPE_RADIO && it->type != wuss_ICON_TYPE_OPTION) diff --git a/libraries/wuss/icon/set-hover.c b/libraries/wuss/icon/set-hover.c new file mode 100644 index 0000000..6ae8d73 --- /dev/null +++ b/libraries/wuss/icon/set-hover.c @@ -0,0 +1,28 @@ +/* set-hover.c -- wuss - track which work-area icon the pointer is over */ + +#include + +#include "../impl.h" + +void wuss__icon_set_hover(wuss_t *wuss, wuss_icon_t *icon) +{ + wuss_icon_t *prev; + + prev = wuss->hover_icon; + if (prev == icon) + return; + + if (prev != NULL) + { + prev->hovered = 0; + wuss__icon_invalidate(prev); + } + + wuss->hover_icon = icon; + + if (icon != NULL) + { + icon->hovered = 1; + wuss__icon_invalidate(icon); + } +} diff --git a/libraries/wuss/impl.h b/libraries/wuss/impl.h index 169ab7d..92d5a4b 100644 --- a/libraries/wuss/impl.h +++ b/libraries/wuss/impl.h @@ -89,6 +89,10 @@ struct wuss * idle; released on any MOUSE_UP * even if a new window now covers * its owner */ + wuss_icon_t *hover_icon; /* icon the pointer is currently + * over, NULL when none; drives + * hover-highlight repaint of + * menu-entry icons */ #endif }; diff --git a/libraries/wuss/mouse-move.c b/libraries/wuss/mouse-move.c index e07144e..fa1c9ea 100644 --- a/libraries/wuss/mouse-move.c +++ b/libraries/wuss/mouse-move.c @@ -46,14 +46,31 @@ result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit) *hit = win; if (win == NULL) + { +#ifdef WUSS_ICONS + wuss__icon_set_hover(wuss, NULL); +#endif return result_OK; + } #ifdef WUSS_FURNITURE if (wuss__furniture_hit_test(win, POINT(x, y)) != wuss_FURNITURE_CONTENT) + { +#ifdef WUSS_ICONS + wuss__icon_set_hover(wuss, NULL); +#endif return result_OK; + } #endif - if (win->task.handle != NULL) + if (win->task.handle == NULL) + { +#ifdef WUSS_ICONS + wuss__icon_set_hover(wuss, NULL); +#endif + return result_OK; + } + { box_t content; point_t doc_point; @@ -70,6 +87,8 @@ result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit) icon = wuss__icon_hit_test(win, doc_point); + wuss__icon_set_hover(wuss, icon); + /* Clear the pressed state of any button the pointer has left. This does * not re-press a button on drag-back-in, and does not track which mouse * button is held -- wuss keeps no persistent "button down over content" diff --git a/libraries/wuss/test/tasks/icons.c b/libraries/wuss/test/tasks/icons.c index 2a188b1..7a1dc38 100644 --- a/libraries/wuss/test/tasks/icons.c +++ b/libraries/wuss/test/tasks/icons.c @@ -22,7 +22,7 @@ #include "icons.h" #define ICONS_DOC_W 220 -#define ICONS_DOC_H 520 /* taller than the window, so scrolling is exercised */ +#define ICONS_DOC_H 640 /* taller than the window, so scrolling is exercised */ result_t icons_create(wuss_t *wuss, const colour_t *palette, @@ -33,8 +33,9 @@ result_t icons_create(wuss_t *wuss, /* [0..3], one swatch per built-in pattern, then [.. +3] a grouping frame * with two differently-justified labels inside it, then [.. +5] three grouped * radios, a standalone option and a label echoing the selection, then [.. +2] - * a decorative bitmap icon and an interactive one that bumps the counter */ - enum { ICONS_NSPECS = 4 + screen_PATTERN__LIMIT + 3 + 5 + 2 }; + * a decorative bitmap icon and an interactive one that bumps the counter, + * then [.. +4] a strip of menu-entry icons that hover-highlight */ + enum { ICONS_NSPECS = 4 + screen_PATTERN__LIMIT + 3 + 5 + 2 + 4 }; wuss_task_t delegate; wuss_icon_spec_t specs[ICONS_NSPECS]; wuss_icon_t *made[ICONS_NSPECS]; @@ -42,6 +43,7 @@ result_t icons_create(wuss_t *wuss, int p; int g; /* index of the first frame spec */ int r; /* radio index */ + int m; /* index of the first menu-entry spec */ int nspecs; /* live spec count (sprite icons are optional) */ result_t rc; @@ -196,6 +198,37 @@ result_t icons_create(wuss_t *wuss, nspecs = g + 10; } + /* [nspecs..nspecs+3] a menu-entry strip: plain, ticked, submenu, separator. + * Hover the pointer over them to see the highlight track. */ + m = nspecs; + + specs[m].bbox = (box_t) BOX_POS_SIZE(28, 524, 160, 16); + specs[m].type = wuss_ICON_TYPE_MENU_ENTRY; + specs[m].text = "Open"; + specs[m].fg = palette_PICO8_DARK_BLUE; + specs[m].bg = wuss_NO_BACKGROUND; + + specs[m + 1].bbox = (box_t) BOX_POS_SIZE(28, 542, 160, 16); + specs[m + 1].type = wuss_ICON_TYPE_MENU_ENTRY; + specs[m + 1].text = "Show grid"; + specs[m + 1].fg = palette_PICO8_DARK_BLUE; + specs[m + 1].bg = wuss_NO_BACKGROUND; + + specs[m + 2].bbox = (box_t) BOX_POS_SIZE(28, 560, 160, 16); + specs[m + 2].type = wuss_ICON_TYPE_MENU_ENTRY; + specs[m + 2].text = "Export"; + specs[m + 2].fg = palette_PICO8_DARK_BLUE; + specs[m + 2].bg = wuss_NO_BACKGROUND; + specs[m + 2].flags = wuss_ICON_FLAGS_SUBMENU; + + specs[m + 3].bbox = (box_t) BOX_POS_SIZE(28, 578, 160, 10); + specs[m + 3].type = wuss_ICON_TYPE_MENU_ENTRY; + specs[m + 3].fg = palette_PICO8_DARK_BLUE; + specs[m + 3].bg = wuss_NO_BACKGROUND; + specs[m + 3].flags = wuss_ICON_FLAGS_SEPARATOR; + + nspecs = m + 4; + rc = wuss_icon_create_array(task->window, specs, nspecs, made); if (rc != result_OK) goto failure; @@ -206,6 +239,7 @@ result_t icons_create(wuss_t *wuss, task->state = made[g + 7]; if (task->has_sprite) task->hotspot = made[g + 9]; + wuss_icon_set_selected(made[m + 1], 1); /* "Show grid" starts ticked */ return result_OK; diff --git a/libraries/wuss/window/close.c b/libraries/wuss/window/close.c index 570425d..759572f 100644 --- a/libraries/wuss/window/close.c +++ b/libraries/wuss/window/close.c @@ -23,6 +23,8 @@ void wuss_window_close(wuss_window_t *doomed) #ifdef WUSS_ICONS if (wuss->pressed_icon != NULL && wuss->pressed_icon->window == doomed) wuss->pressed_icon = NULL; + if (wuss->hover_icon != NULL && wuss->hover_icon->window == doomed) + wuss->hover_icon = NULL; #endif wuss__release_packed(doomed); From f19964ceb75f8c92f2ddd8d45e56fb5dea2cbae6 Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 22:52:55 +0100 Subject: [PATCH 23/79] feat(wuss): add WUSS_MENUS pop-up menu helper New compile-time option WUSS_MENUS (implies WUSS_ICONS) guarding a thin helper for RISC OS-style pop-up menus: a menu is a borderless window of wuss_ICON_TYPE_MENU_ENTRY icons, but wuss owns layout, on-screen placement, submenu chaining on hover and whole-chain dismissal. - include/wuss/menu.h: caller-owned immutable wuss_menu_t / wuss_menu_item_t data model; wuss_menu_open / wuss_menu_close / wuss_menu_is_open; wuss_menu_select_fn_t leaf-selection callback (SELECT closes the chain, ADJUST keeps it open). - libraries/wuss/menu/menu.c + libraries/wuss/menu.h: struct wuss__menu chain nodes (window + per-item icon handles + parent/child links). Measures the widest label, sizes and places a borderless window, maps item flags (DASHED/DISABLED/submenu/TICKED) onto icon flags, and runs the chain from an internal window delegate. - struct wuss gains menu_chain; mouse-click.c dismisses the chain on a MOUSE_DOWN outside every menu window; create.c inits it, destroy.c tears down any still-open chain. - CMakeLists.txt: WUSS_MENUS option, WUSS_MENU_SOURCES, PUBLIC_HEADERS. - apps/wuss/main.c: a "Menu" launcher entry opening a static demo tree. Co-Authored-By: Claude Sonnet 5 --- CMakeLists.txt | 17 ++ apps/wuss/main.c | 48 ++++- include/wuss/menu.h | 114 +++++++++++ libraries/wuss/create.c | 3 + libraries/wuss/destroy.c | 6 + libraries/wuss/impl.h | 9 + libraries/wuss/menu.h | 34 +++ libraries/wuss/menu/menu.c | 387 +++++++++++++++++++++++++++++++++++ libraries/wuss/mouse-click.c | 8 + 9 files changed, 625 insertions(+), 1 deletion(-) create mode 100644 include/wuss/menu.h create mode 100644 libraries/wuss/menu.h create mode 100644 libraries/wuss/menu/menu.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 7f69191..a9f5f69 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -32,6 +32,7 @@ option(USE_ASAN "Build with AddressSanitizer and UndefinedBehaviorSanitizer" OFF option(DPTLIB_IMAGES_READ_ONLY "Remove libpng write support" OFF) option(WUSS_FURNITURE "Build the Wuss window-furniture subsystem" ON) option(WUSS_ICONS "Build the Wuss in-content icon subsystem" ON) +option(WUSS_MENUS "Build the Wuss pop-up menu helper (implies WUSS_ICONS)" ON) # Referencing CMAKE_TOOLCHAIN_FILE avoids a warning on rebuilds. if(NOT ${CMAKE_TOOLCHAIN_FILE} STREQUAL "") @@ -122,6 +123,9 @@ set(PUBLIC_HEADERS if(WUSS_ICONS) list(APPEND PUBLIC_HEADERS include/wuss/icon.h) endif() +if(WUSS_MENUS) + list(APPEND PUBLIC_HEADERS include/wuss/menu.h) +endif() # The public headers must be set as properties of the library, not as # target_sources. The quoting is essential. @@ -394,13 +398,23 @@ set(WUSS_ICON_SOURCES libraries/wuss/icon/set-text.c libraries/wuss/icon.h) +set(WUSS_MENU_SOURCES + libraries/wuss/menu/menu.c + libraries/wuss/menu.h) + set(WUSS_SOURCES ${WUSS_CORE_SOURCES}) if(WUSS_FURNITURE) list(APPEND WUSS_SOURCES ${WUSS_FURNITURE_SOURCES}) endif() +if(WUSS_MENUS) + set(WUSS_ICONS ON) # the menu helper needs the icon subsystem +endif() if(WUSS_ICONS) list(APPEND WUSS_SOURCES ${WUSS_ICON_SOURCES}) endif() +if(WUSS_MENUS) + list(APPEND WUSS_SOURCES ${WUSS_MENU_SOURCES}) +endif() set(ALL_SOURCES ${PUBLIC_HEADERS} @@ -458,6 +472,9 @@ endif() if(WUSS_ICONS) target_compile_definitions(DPTLib PUBLIC WUSS_ICONS) endif() +if(WUSS_MENUS) + target_compile_definitions(DPTLib PUBLIC WUSS_MENUS) +endif() if(NOT TARGET_RISCOS) set(CMAKE_FIND_FRAMEWORK NEVER) diff --git a/apps/wuss/main.c b/apps/wuss/main.c index 2e62328..f9cf4b7 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -20,6 +20,7 @@ #include "io/path.h" #include "wuss/wuss.h" #include "wuss/window.h" +#include "wuss/menu.h" #include @@ -192,6 +193,50 @@ static result_t spawn_porter_duff(void) return result_OK; } +/* A static demo menu tree for the pop-up helper: a submenu, a couple of + * ticked rows and a dashed separator. wuss_menu_open never mutates it. */ +static const wuss_menu_item_t g_menu_export_items[] = +{ + { "As PNG", wuss_MENU_ITEM_NONE, NULL }, + { "As JPEG", wuss_MENU_ITEM_NONE, NULL }, + { "As GIF", wuss_MENU_ITEM_DISABLED, NULL } +}; +static const wuss_menu_t g_menu_export = +{ + g_menu_export_items, NELEMS(g_menu_export_items) +}; + +static const wuss_menu_item_t g_menu_items[] = +{ + { "Open", wuss_MENU_ITEM_NONE, NULL }, + { "Show grid", wuss_MENU_ITEM_TICKED, NULL }, + { "Wireframe", wuss_MENU_ITEM_TICKED, NULL }, + { "Export", wuss_MENU_ITEM_NONE, &g_menu_export }, + { NULL, wuss_MENU_ITEM_DASHED, NULL }, + { "Quit", wuss_MENU_ITEM_NONE, NULL } +}; +static const wuss_menu_t g_menu = +{ + g_menu_items, NELEMS(g_menu_items) +}; + +static void menu_selected(const wuss_menu_t *menu, + int index, + wuss_button_t button, + void *ctx) +{ + NOT_USED(button); + NOT_USED(ctx); + printf("menu: picked \"%s\"\n", + menu->items[index].text ? menu->items[index].text : "(sep)"); +} + +static result_t spawn_menu(void) +{ + return wuss_menu_open(g_wuss, &g_menu, POINT(120, 120), + menu_selected, NULL, NULL); +} + static const launcher_entry_t g_launcher_entries[] = { { "Ball", spawn_ball }, @@ -205,7 +250,8 @@ static const launcher_entry_t g_launcher_entries[] = { "Sofa", spawn_sofa }, { "Gradient", spawn_gradient }, { "Icons", spawn_icons }, - { "Porter-Duff", spawn_porter_duff } + { "Porter-Duff", spawn_porter_duff }, + { "Menu", spawn_menu } }; static wuss_button_t sdl_button_to_wuss(Uint8 button) diff --git a/include/wuss/menu.h b/include/wuss/menu.h new file mode 100644 index 0000000..9322f48 --- /dev/null +++ b/include/wuss/menu.h @@ -0,0 +1,114 @@ +/* menu.h -- wuss pop-up menu helper */ + +/** + * \file menu.h + * + * A thin helper for RISC OS-style pop-up menus: a menu is a borderless window + * populated with wuss_ICON_TYPE_MENU_ENTRY icons, but wuss owns the plumbing -- + * layout, placement, submenu chaining on hover and whole-chain dismissal on a + * click outside or a leaf selection. + * + * Menus are described by caller-owned, immutable wuss_menu_t / wuss_menu_item_t + * structures (which may be static). The helper never mutates them; a task that + * wants a tick to change just edits its own array and reopens the menu. + * + * Built only when WUSS_MENUS is defined (which implies WUSS_ICONS). + */ + +#ifndef WUSS_MENU_H +#define WUSS_MENU_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "base/result.h" +#include "geom/point.h" + +#include "wuss/wuss.h" + +/* ----------------------------------------------------------------------- */ + +/** Per-item flags for a wuss_menu_item_t. OR'd together. */ +typedef enum wuss_menu_item_flags +{ + wuss_MENU_ITEM_NONE = 0, + wuss_MENU_ITEM_TICKED = 1 << 0, /**< draw a tick at the item's left edge */ + wuss_MENU_ITEM_DASHED = 1 << 1, /**< this item is a separator rule; text + * is ignored */ + wuss_MENU_ITEM_DISABLED = 1 << 2 /**< greyed, never highlights, not + * selectable */ +} +wuss_menu_item_flags_t; + +/** One row of a menu. */ +typedef struct wuss_menu_item +{ + const char *text; /**< row label; NULL is treated as "" */ + wuss_menu_item_flags_t flags; /**< see wuss_menu_item_flags_t */ + const struct wuss_menu *submenu; /**< non-NULL: draw a right arrow and open + * this menu to the right on hover */ +} +wuss_menu_item_t; + +/** A menu: an array of items the caller owns. */ +typedef struct wuss_menu +{ + const wuss_menu_item_t *items; + int nitems; +} +wuss_menu_t; + +/** Opaque handle to an open menu chain. */ +typedef struct wuss__menu *wuss_menu_handle_t; + +/** + * Called when the pointer is released over a selectable leaf item. + * + * \param[in] menu The menu the item belongs to (a submenu, if nested). + * \param[in] index Index of the item within \c menu->items. + * \param[in] button wuss_button_t flags for the release; test with '&'. ADJUST + * keeps the chain open, SELECT closes it. + * \param[in] ctx As passed to wuss_menu_open. + */ +typedef void (wuss_menu_select_fn_t)(const wuss_menu_t *menu, + int index, + wuss_button_t button, + void *ctx); + +/* ----------------------------------------------------------------------- */ + +/** + * Open \p menu as a pop-up at \p at (screen space), nudged to stay on screen. + * Any menu chain already open is closed first. The chain lives until a leaf is + * SELECT-picked, a click lands outside every menu window, or wuss_menu_close is + * called. + * + * \param[in] wuss Window manager. + * \param[in] menu Menu to show; borrowed, must outlive the open chain. + * \param[in] at Where to put the menu's top-left, screen space. + * \param[in] on_select Leaf-selection callback, or NULL. + * \param[in] ctx Opaque pointer passed back to \p on_select. + * \param[out] out Filled with the chain handle, or NULL if not wanted. + * \return \ref result_OK, \ref result_OOM, or a wuss_window_create code. + */ +result_t wuss_menu_open(wuss_t *wuss, + const wuss_menu_t *menu, + point_t at, + wuss_menu_select_fn_t *on_select, + void *ctx, + wuss_menu_handle_t *out); + +/** Close a menu chain and every window in it. Safe to pass a stale or NULL + * handle. */ +void wuss_menu_close(wuss_menu_handle_t handle); + +/** Non-zero while \p handle refers to a currently open chain. */ +int wuss_menu_is_open(wuss_menu_handle_t handle); + +#ifdef __cplusplus +} +#endif + +#endif /* WUSS_MENU_H */ diff --git a/libraries/wuss/create.c b/libraries/wuss/create.c index db80d01..acb6ba4 100644 --- a/libraries/wuss/create.c +++ b/libraries/wuss/create.c @@ -213,6 +213,9 @@ result_t wuss_create(screen_t *scr, w->pressed_icon = NULL; w->hover_icon = NULL; #endif +#ifdef WUSS_MENUS + w->menu_chain = NULL; +#endif w->ndirty = 0; diff --git a/libraries/wuss/destroy.c b/libraries/wuss/destroy.c index 372fd51..7bc4bf0 100644 --- a/libraries/wuss/destroy.c +++ b/libraries/wuss/destroy.c @@ -15,6 +15,12 @@ void wuss_destroy(wuss_t *doomed) if (doomed == NULL) return; +#ifdef WUSS_MENUS + /* Drop any open menu chain first: its windows are freed by the z_order + * sweep below, but the chain nodes and their icon-handle arrays are not. */ + wuss_menu_close(doomed->menu_chain); +#endif + e = doomed->z_order.next; while (e != NULL) { diff --git a/libraries/wuss/impl.h b/libraries/wuss/impl.h index 92d5a4b..49d004b 100644 --- a/libraries/wuss/impl.h +++ b/libraries/wuss/impl.h @@ -21,6 +21,9 @@ #ifdef WUSS_ICONS #include "icon.h" #endif +#ifdef WUSS_MENUS +#include "menu.h" +#endif #define WUSS_TITLE_MAX 63 #define WUSS_DEFAULT_TITLEBAR_HEIGHT 20 @@ -94,6 +97,12 @@ struct wuss * hover-highlight repaint of * menu-entry icons */ #endif +#ifdef WUSS_MENUS + struct wuss__menu *menu_chain; /* head (root) of the open pop-up + * menu chain, NULL when none open; + * consulted by mouse-click.c to + * dismiss on a click-away */ +#endif }; struct wuss_window diff --git a/libraries/wuss/menu.h b/libraries/wuss/menu.h new file mode 100644 index 0000000..4012f6d --- /dev/null +++ b/libraries/wuss/menu.h @@ -0,0 +1,34 @@ +/* menu.h -- wuss pop-up menu helper, internal */ + +#ifndef WUSS_MENU_IMPL_H +#define WUSS_MENU_IMPL_H + +#include "geom/point.h" + +#include "wuss/wuss.h" +#include "wuss/menu.h" + +/* One open level of a menu chain: its window and per-item icon handles, plus a + * link to the level it opened from. The head (root) is wuss->menu_chain; each + * child points at its parent so wuss_menu_close can walk leaf-to-root. */ +struct wuss__menu +{ + wuss_t *wuss; + wuss_window_t *window; /* the borderless menu window */ + const wuss_menu_t *menu; /* borrowed source description */ + wuss_icon_t **icons; /* owned array, menu->nitems entries, in + * item order */ + struct wuss__menu *parent; /* level this one opened from, NULL at root */ + struct wuss__menu *child; /* open submenu level, NULL when none */ + wuss_menu_select_fn_t *on_select; + void *ctx; + int open_index; /* item index whose submenu `child` is, + * -1 when no child open */ +}; + +/* Called from wuss_mouse_click on a MOUSE_DOWN before the window hit-test: if a + * menu chain is open and `hit` is not one of its windows, close the whole chain + * and return 1 (the click is spent on dismissal). Returns 0 otherwise. */ +int wuss__menu_click_outside(wuss_t *wuss, const wuss_window_t *hit); + +#endif /* WUSS_MENU_IMPL_H */ diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c new file mode 100644 index 0000000..40b150b --- /dev/null +++ b/libraries/wuss/menu/menu.c @@ -0,0 +1,387 @@ +/* menu.c -- wuss pop-up menu helper */ + +#include +#include +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/result.h" +#include "base/utils.h" +#include "framebuf/bmfont.h" +#include "geom/box.h" +#include "geom/point.h" +#include "geom/size.h" + +#include "wuss/icon.h" +#include "wuss/task.h" +#include "wuss/window.h" +#include "wuss/wuss.h" + +#include "../impl.h" + +/* Row padding above/below the glyph, and the gutters left for the tick (left) + * and the submenu arrow (right). All in pixels. */ +#define WUSS_MENU_ROW_PAD 3 +#define WUSS_MENU_TICK_W 14 +#define WUSS_MENU_ARROW_W 14 +#define WUSS_MENU_TEXT_PAD 6 +#define WUSS_MENU_SUBMENU_OVERLAP 2 /* px a submenu overlaps its parent's right edge */ + +/* ----------------------------------------------------------------------- */ + +static result_t wuss__menu_spawn(wuss_t *wuss, + const wuss_menu_t *menu, + point_t at, + struct wuss__menu *parent, + wuss_menu_select_fn_t *on_select, + void *ctx, + struct wuss__menu **out); + +/* Close `node` and everything it opened, leaf-first. The caller clears any + * parent's child pointer. */ +static void wuss__menu_close_from(struct wuss__menu *node) +{ + if (node == NULL) + return; + + wuss__menu_close_from(node->child); + node->child = NULL; + + wuss_window_close(node->window); + free(node->icons); + free(node); +} + +/* ----------------------------------------------------------------------- */ + +/* The menu window's task delegate. task_data is the struct wuss__menu. */ +static result_t wuss__menu_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) +{ + struct wuss__menu *self; + const wuss_icon_t *icon; + const wuss_menu_item_t *item; + int index; + int i; + + NOT_USED(window); + + self = task_data; + + if (event->kind != wuss_EVENT_ICON) + return result_OK; + + icon = event->data.icon.icon; + index = -1; + for (i = 0; i < self->menu->nitems; i++) + { + if (self->icons[i] == icon) + { + index = i; + break; + } + } + if (index < 0) + return result_OK; + + item = &self->menu->items[index]; + + if (item->flags & (wuss_MENU_ITEM_DASHED | wuss_MENU_ITEM_DISABLED)) + return result_OK; + + if (event->data.icon.action == wuss_MOUSE_MOVE) + { + box_t ib; + box_t wb; + point_t at; + + /* Hovering a different row closes any submenu the previous row opened. */ + if (self->child != NULL && self->open_index != index) + { + wuss__menu_close_from(self->child); + self->child = NULL; + self->open_index = -1; + } + + if (item->submenu == NULL || self->child != NULL) + return result_OK; + + wuss_window_get_visible_bounds(self->window, &wb); + wuss_icon_get_bbox(icon, &ib); + + /* The menu is unscrolled, so a row's document y equals its screen y + * offset from the window's top. */ + at.x = wb.x1 - WUSS_MENU_SUBMENU_OVERLAP; + at.y = wb.y0 + ib.y0; + + if (wuss__menu_spawn(self->wuss, item->submenu, at, self, + self->on_select, self->ctx, &self->child) == result_OK) + self->open_index = index; + + return result_OK; + } + + if (event->data.icon.action == wuss_MOUSE_UP) + { + wuss_button_t button; + struct wuss__menu *root; + + button = event->data.icon.button; + + if (item->submenu != NULL) + return result_OK; /* a submenu row opens on hover, it is not a pick */ + + if (self->on_select != NULL) + self->on_select(self->menu, index, button, self->ctx); + + if (button & wuss_BUTTON_ADJUST) + return result_OK; /* ADJUST keeps the chain open */ + + root = self; + while (root->parent != NULL) + root = root->parent; + + root->wuss->menu_chain = NULL; + wuss__menu_close_from(root); + } + + return result_OK; +} + +/* ----------------------------------------------------------------------- */ + +/* Measure the menu, create its borderless window and one MENU_ENTRY icon per + * item, and hang the node off *out. */ +static result_t wuss__menu_spawn(wuss_t *wuss, + const wuss_menu_t *menu, + point_t at, + struct wuss__menu *parent, + wuss_menu_select_fn_t *on_select, + void *ctx, + struct wuss__menu **out) +{ + struct wuss__menu *node; + wuss_icon_spec_t *specs; + wuss_task_t task; + result_t rc; + int fw, fh; + int pitch; + int widest; + int width, height; + int i; + size2d_t size; + + assert(wuss != NULL); + assert(menu != NULL); + assert(wuss->font != NULL); + + if (menu->nitems <= 0) + return result_WUSS_BAD_ICON; + + bmfont_get_info(wuss->font, &fw, &fh); + NOT_USED(fw); + pitch = fh + 2 * WUSS_MENU_ROW_PAD; + + widest = 0; + for (i = 0; i < menu->nitems; i++) + { + const char *text; + int split; + bmfont_width_t w; + + text = menu->items[i].text ? menu->items[i].text : ""; + if (bmfont_measure(wuss->font, text, (int) strlen(text), + INT_MAX, &split, &w) == result_OK && (int) w > widest) + widest = (int) w; + } + + width = WUSS_MENU_TICK_W + widest + WUSS_MENU_TEXT_PAD + WUSS_MENU_ARROW_W; + height = pitch * menu->nitems; + + /* Nudge onto the screen where it can be, but keep the top-left on screen. */ + if (at.x + width > wuss->scr->size.w) + at.x = wuss->scr->size.w - width; + if (at.y + height > wuss->scr->size.h) + at.y = wuss->scr->size.h - height; + if (at.x < 0) + at.x = 0; + if (at.y < 0) + at.y = 0; + + node = malloc(sizeof(*node)); + if (node == NULL) + return result_OOM; + + node->icons = calloc((size_t) menu->nitems, sizeof(*node->icons)); + specs = calloc((size_t) menu->nitems, sizeof(*specs)); + if (node->icons == NULL || specs == NULL) + { + free(specs); + free(node->icons); + free(node); + return result_OOM; + } + + node->wuss = wuss; + node->window = NULL; + node->menu = menu; + node->parent = parent; + node->child = NULL; + node->on_select = on_select; + node->ctx = ctx; + node->open_index = -1; + + for (i = 0; i < menu->nitems; i++) + { + const wuss_menu_item_t *item; + wuss_icon_flags_t flags; + + item = &menu->items[i]; + flags = wuss_ICON_FLAGS_NONE; + + if (item->flags & wuss_MENU_ITEM_DASHED) + flags |= wuss_ICON_FLAGS_SEPARATOR; + if (item->flags & wuss_MENU_ITEM_DISABLED) + flags |= wuss_ICON_FLAGS_DISABLED; + if (item->submenu != NULL) + flags |= wuss_ICON_FLAGS_SUBMENU; + + specs[i].type = wuss_ICON_TYPE_MENU_ENTRY; + specs[i].bbox.x0 = 0; + specs[i].bbox.y0 = i * pitch; + specs[i].bbox.x1 = width; + specs[i].bbox.y1 = i * pitch + pitch; + specs[i].text = item->text ? item->text : ""; + specs[i].fg = 0; + specs[i].bg = wuss_NO_BACKGROUND; + specs[i].flags = flags; + } + + size = SIZE2D(width, height); + task = wuss_task_start(wuss__menu_handle, node); + + rc = wuss_window_create_placed(wuss, size, NULL, + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE + | wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK + | wuss_WINDOW_NO_TOGGLE_SIZE + | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL + | wuss_WINDOW_NO_RESIZE, + wuss_BACKDROP_COLOUR(1), + &task, size, size, &node->window); + if (rc != result_OK) + { + free(specs); + free(node->icons); + free(node); + return rc; + } + + wuss_window_move(node->window, at); + + rc = wuss_icon_create_array(node->window, specs, menu->nitems, node->icons); + free(specs); + if (rc != result_OK) + { + wuss_window_close(node->window); + free(node->icons); + free(node); + return rc; + } + + for (i = 0; i < menu->nitems; i++) + { + if (menu->items[i].flags & wuss_MENU_ITEM_TICKED) + wuss_icon_set_selected(node->icons[i], 1); + } + + *out = node; + return result_OK; +} + +/* ----------------------------------------------------------------------- */ + +result_t wuss_menu_open(wuss_t *wuss, + const wuss_menu_t *menu, + point_t at, + wuss_menu_select_fn_t *on_select, + void *ctx, + wuss_menu_handle_t *out) +{ + struct wuss__menu *root; + result_t rc; + + assert(wuss != NULL); + assert(menu != NULL); + + if (wuss->menu_chain != NULL) + { + wuss__menu_close_from(wuss->menu_chain); + wuss->menu_chain = NULL; + } + + rc = wuss__menu_spawn(wuss, menu, at, NULL, on_select, ctx, &root); + if (rc != result_OK) + return rc; + + wuss->menu_chain = root; + if (out != NULL) + *out = root; + return result_OK; +} + +void wuss_menu_close(wuss_menu_handle_t handle) +{ + struct wuss__menu *root; + + if (handle == NULL) + return; + + root = handle; + while (root->parent != NULL) + root = root->parent; + + if (root->wuss->menu_chain != root) + return; /* stale handle */ + + root->wuss->menu_chain = NULL; + wuss__menu_close_from(root); +} + +int wuss_menu_is_open(wuss_menu_handle_t handle) +{ + struct wuss__menu *root; + + if (handle == NULL) + return 0; + + root = handle; + while (root->parent != NULL) + root = root->parent; + + return root->wuss->menu_chain == root; +} + +/* ----------------------------------------------------------------------- */ + +int wuss__menu_click_outside(wuss_t *wuss, const wuss_window_t *hit) +{ + struct wuss__menu *node; + + if (wuss->menu_chain == NULL) + return 0; + + for (node = wuss->menu_chain; node != NULL; node = node->child) + { + if (node->window == hit) + return 0; + } + + wuss__menu_close_from(wuss->menu_chain); + wuss->menu_chain = NULL; + return 1; +} diff --git a/libraries/wuss/mouse-click.c b/libraries/wuss/mouse-click.c index ec35940..cf9c735 100644 --- a/libraries/wuss/mouse-click.c +++ b/libraries/wuss/mouse-click.c @@ -49,6 +49,14 @@ result_t wuss_mouse_click(wuss_t *wuss, if (hit != NULL) *hit = win; +#ifdef WUSS_MENUS + /* A press outside every window in the open menu chain dismisses the chain + * and is spent doing so. Presses inside the chain fall through to the menu + * window's own delegate. */ + if (action == wuss_MOUSE_DOWN && wuss__menu_click_outside(wuss, win)) + return result_OK; +#endif + if (win == NULL) return result_OK; From db6e02d377249013b49f61298072fff991eba072 Mon Sep 17 00:00:00 2001 From: David Thomas Date: Mon, 31 Aug 2026 23:14:52 +0100 Subject: [PATCH 24/79] feat(wuss): add wuss_menu_create_from_desc descriptor parser Port PrivateEye's menu_create_from_desc token machine to build a heap wuss_menu_t tree from a compact string: - ',' separates items, '|' marks a dashed separator above - '{ ... }' after an item is its submenu (first token is a title, discarded for source compatibility with the Wimp descriptors) - per-token '!' ticks, '~' shades, '>' pulls a const wuss_menu_t * submenu from the varargs - '%s' substitutes the next const char * vararg '>' submenus are deep-copied so the whole result tree is owned uniformly and freed by one wuss_menu_destroy. getname() trims trailing whitespace so "Foo { " yields "Foo". Also fix a Stage 6a crash: wuss__menu_spawn passed an empty label to bmfont_measure (asserts textlen > 0) for a separator row; skip empty labels when measuring and when drawing a MENU_ENTRY. Guarded by WUSS_MENUS. wuss-test.c gains a parser self-check. Co-Authored-By: Claude Sonnet 5 --- CMakeLists.txt | 1 + include/wuss/menu.h | 38 ++ libraries/wuss/icon/draw.c | 2 +- libraries/wuss/menu/create-from-desc.c | 471 +++++++++++++++++++++++++ libraries/wuss/menu/menu.c | 6 +- libraries/wuss/test/wuss-test.c | 73 ++++ 6 files changed, 589 insertions(+), 2 deletions(-) create mode 100644 libraries/wuss/menu/create-from-desc.c diff --git a/CMakeLists.txt b/CMakeLists.txt index a9f5f69..8c1ba83 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -400,6 +400,7 @@ set(WUSS_ICON_SOURCES set(WUSS_MENU_SOURCES libraries/wuss/menu/menu.c + libraries/wuss/menu/create-from-desc.c libraries/wuss/menu.h) set(WUSS_SOURCES ${WUSS_CORE_SOURCES}) diff --git a/include/wuss/menu.h b/include/wuss/menu.h index 9322f48..ca286f9 100644 --- a/include/wuss/menu.h +++ b/include/wuss/menu.h @@ -107,6 +107,44 @@ void wuss_menu_close(wuss_menu_handle_t handle); /** Non-zero while \p handle refers to a currently open chain. */ int wuss_menu_is_open(wuss_menu_handle_t handle); +/* ----------------------------------------------------------------------- */ + +/** + * Build a wuss_menu_t tree from a compact descriptor string. The syntax is + * lifted from PrivateEye's menu_create_from_desc. A comma separates items. A + * leading '|' marks an item a dashed separator above it + * (wuss_MENU_ITEM_DASHED). A '{ ... }' group after an item is that item's + * submenu; as in the Wimp original the first token inside the braces is a title + * and is discarded, so the same descriptor strings port across unchanged. A + * per-token prefix '!' ticks the item, '~' shades (disables) it, and '>' + * attaches a submenu pulled as a const wuss_menu_t * from the varargs + * rather than from a following '{ }' block. A "%s" in a token substitutes the + * next const char * vararg. The '>' and "%s" varargs are consumed in + * the order they are encountered scanning left to right. + * + * Example: wuss_menu_create_from_desc(&m, "App, %s { %s, !Grid, ~Export, + * |Quit }", "File", "File"). + * + * The whole tree, including copied label text, is one heap allocation graph + * owned by the caller; free it with wuss_menu_destroy. wuss_menu_open treats a + * desc-built tree exactly like a static literal. + * + * \param[out] out Filled with the root menu on success, untouched on failure. + * \param[in] desc Descriptor string. + * \return \ref result_OK, \ref result_OOM, or \ref result_BAD_ARG for a + * malformed descriptor (unbalanced braces, empty token, too deep). + */ +result_t wuss_menu_create_from_desc(wuss_menu_t **out, const char *desc, ...); + +/** + * Free a tree built by wuss_menu_create_from_desc, including every submenu and + * copied label. Safe to pass NULL. Never call this on a static or + * caller-assembled wuss_menu_t. + * + * \param[in] menu Root menu returned by wuss_menu_create_from_desc, or NULL. + */ +void wuss_menu_destroy(wuss_menu_t *menu); + #ifdef __cplusplus } #endif diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index dafacd4..60a4826 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -422,7 +422,7 @@ void wuss__icon_draw(wuss_t *wuss, ax + (r - (dy < 0 ? -dy : dy)), ay + dy, ink); } - if (have_font) + if (have_font && icon->text != NULL && icon->text[0] != '\0') { point_t pos; diff --git a/libraries/wuss/menu/create-from-desc.c b/libraries/wuss/menu/create-from-desc.c new file mode 100644 index 0000000..80bbaef --- /dev/null +++ b/libraries/wuss/menu/create-from-desc.c @@ -0,0 +1,471 @@ +/* create-from-desc.c -- wuss - build a menu tree from a descriptor string */ + +/* The token machine (Token / Parser / getopt / getname / getnext / isdelim) + * and the explicit menu stack are ported from PrivateEye's + * libs/appengine/wimp/menu/create-from-desc.c. What changed for wuss: the + * output is a heap wuss_menu_t tree rather than a wimp_menu, there is no title + * row (the first token of a { } block is parsed then discarded, so descriptor + * strings stay source-compatible), and a '>' submenu is deep-copied from the + * const wuss_menu_t * vararg so the whole result tree is owned uniformly and + * freed by one wuss_menu_destroy. */ + +#include +#include +#include +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/result.h" + +#include "wuss/menu.h" + +/* ----------------------------------------------------------------------- */ + +enum { WUSS_MENU_DESC_DEPTH = 8 }; /* nested { } limit, as the Wimp original */ +enum { WUSS_MENU_DESC_TEXT = 64 }; /* per-token expansion buffer */ + +/* ----------------------------------------------------------------------- */ + +typedef enum { Name, Sep, DashSep, Push, Pop, End, Error } Token; + +enum { Tick = 1 << 0, Shade = 1 << 1, SubMenu = 1 << 2 }; + +typedef struct +{ + const char *p; + const char *start, *end; /* end exclusive */ + unsigned int flags; +} +Parser; + +static void getopt_(Parser *parser) +{ + const char *p; + int c; + + parser->flags = 0; + p = parser->p; + + for (;;) + { + c = *p; + if (c == '!') parser->flags |= Tick; + else if (c == '>') parser->flags |= SubMenu; + else if (c == '~') parser->flags |= Shade; + else { parser->p = p; return; } + p++; + } +} + +static int isdelim(int c) +{ + return c == ',' || c == '|' || c == '{' || c == '}' || c == '\0'; +} + +static void getname(Parser *parser) +{ + const char *p; + + getopt_(parser); + p = parser->p; + parser->start = p; + + while (!isdelim(*p)) + p++; + + parser->p = p; + + /* trim trailing whitespace so "Foo { " yields the name "Foo", not "Foo " */ + while (p > parser->start && isspace((unsigned char) p[-1])) + p--; + + parser->end = p; +} + +static Token getnext(Parser *parser) +{ + while (isspace((unsigned char) *parser->p)) + parser->p++; + + if (!isdelim(*parser->p)) + { + getname(parser); + if (parser->start == parser->end) + return Error; + return Name; + } + + switch (*parser->p++) + { + case ',': return Sep; + case '|': return DashSep; + case '{': return Push; + case '}': return Pop; + } + + assert(parser->p[-1] == '\0'); + return End; +} + +/* ----------------------------------------------------------------------- */ + +void wuss_menu_destroy(wuss_menu_t *menu) +{ + int i; + + if (menu == NULL) + return; + + for (i = 0; i < menu->nitems; i++) + { + free((void *) menu->items[i].text); + wuss_menu_destroy((wuss_menu_t *) menu->items[i].submenu); + } + free((void *) menu->items); + free(menu); +} + +/* Deep-copy a caller-supplied menu tree so a '>' submenu can be owned by, and + * freed with, the result of wuss_menu_create_from_desc. */ +static result_t menu_deep_copy(const wuss_menu_t *src, wuss_menu_t **out) +{ + wuss_menu_t *m; + wuss_menu_item_t *items; + int i; + result_t rc; + + m = malloc(sizeof(*m)); + if (m == NULL) + return result_OOM; + + items = (src->nitems > 0) + ? calloc((size_t) src->nitems, sizeof(*items)) + : NULL; + if (src->nitems > 0 && items == NULL) + { + free(m); + return result_OOM; + } + + m->items = items; + m->nitems = src->nitems; + + for (i = 0; i < src->nitems; i++) + { + items[i].text = strdup(src->items[i].text ? src->items[i].text : ""); + items[i].flags = src->items[i].flags; + items[i].submenu = NULL; + + if (items[i].text == NULL) + { + m->nitems = i; /* only [0, i) are populated -- keeps destroy correct */ + wuss_menu_destroy(m); + return result_OOM; + } + + if (src->items[i].submenu != NULL) + { + wuss_menu_t *child; + + rc = menu_deep_copy(src->items[i].submenu, &child); + if (rc != result_OK) + { + m->nitems = i + 1; + wuss_menu_destroy(m); + return rc; + } + items[i].submenu = child; + } + } + + *out = m; + return result_OK; +} + +/* ----------------------------------------------------------------------- */ + +/* A menu under construction: a growable wuss_menu_item_t array plus the + * separator flag owed to the next-emitted item. */ +typedef struct +{ + wuss_menu_item_t *items; + int n; + int cap; + int pending_dash; +} +Building; + +static result_t build_add(Building *b, + const char *text, + unsigned int opt, + wuss_menu_t *submenu) +{ + wuss_menu_item_t *it; + char *copy; + + if (b->n == b->cap) + { + int cap; + wuss_menu_item_t *grown; + + /* ponytail: double on demand, no shrink-to-fit -- the array is small and + * freed as one block, trailing slack costs nothing */ + cap = b->cap ? b->cap * 2 : 4; + grown = realloc(b->items, (size_t) cap * sizeof(*grown)); + if (grown == NULL) + return result_OOM; + b->items = grown; + b->cap = cap; + } + + copy = strdup(text ? text : ""); + if (copy == NULL) + return result_OOM; + + it = &b->items[b->n++]; + it->text = copy; + it->flags = wuss_MENU_ITEM_NONE; + it->submenu = submenu; + + if (b->pending_dash) + { + it->flags |= wuss_MENU_ITEM_DASHED; + b->pending_dash = 0; + } + if (opt & Tick) + it->flags |= wuss_MENU_ITEM_TICKED; + if (opt & Shade) + it->flags |= wuss_MENU_ITEM_DISABLED; + + return result_OK; +} + +/* Seal a Building into a heap wuss_menu_t, transferring its item array. */ +static wuss_menu_t *build_seal(Building *b) +{ + wuss_menu_t *m; + + m = malloc(sizeof(*m)); + if (m == NULL) + return NULL; + + m->items = b->items; + m->nitems = b->n; + + b->items = NULL; + b->n = b->cap = 0; + + return m; +} + +/* Free an unsealed Building (error path): everything in it is ours. */ +static void build_discard(Building *b) +{ + int i; + + for (i = 0; i < b->n; i++) + { + free((void *) b->items[i].text); + wuss_menu_destroy((wuss_menu_t *) b->items[i].submenu); + } + free(b->items); + b->items = NULL; + b->n = b->cap = 0; +} + +/* ----------------------------------------------------------------------- */ + +result_t wuss_menu_create_from_desc(wuss_menu_t **out, const char *desc, ...) +{ + Parser parser; + Building stack[WUSS_MENU_DESC_DEPTH]; + int need_title[WUSS_MENU_DESC_DEPTH]; + int sp; + va_list ap; + result_t rc; + char text[WUSS_MENU_DESC_TEXT]; + int i; + + assert(out != NULL); + assert(desc != NULL); + + parser.p = desc; + + for (i = 0; i < WUSS_MENU_DESC_DEPTH; i++) + { + stack[i].items = NULL; + stack[i].n = stack[i].cap = stack[i].pending_dash = 0; + need_title[i] = 0; + } + + sp = 0; + rc = result_OK; + + va_start(ap, desc); + + for (;;) + { + Token tok; + + tok = getnext(&parser); + + if (tok == End) + break; + + if (tok == Error) + { + rc = result_BAD_ARG; + break; + } + + if (tok == Sep) + continue; + + if (tok == DashSep) + { + stack[sp].pending_dash = 1; + continue; + } + + if (tok == Push) + { + if (sp + 1 >= WUSS_MENU_DESC_DEPTH) + { + rc = result_BAD_ARG; /* too deep */ + break; + } + sp++; + need_title[sp] = 1; /* first token inside { } is a title, discard it */ + continue; + } + + if (tok == Pop) + { + wuss_menu_t *sub; + + if (sp == 0) + { + rc = result_BAD_ARG; /* } without { */ + break; + } + + sub = build_seal(&stack[sp]); + if (sub == NULL) + { + rc = result_OOM; + break; + } + sp--; + + if (stack[sp].n == 0) + { + wuss_menu_destroy(sub); + rc = result_BAD_ARG; /* { } with no preceding item */ + break; + } + stack[sp].items[stack[sp].n - 1].submenu = sub; + continue; + } + + /* tok == Name */ + { + char *q; + const char *s; + unsigned int opt; + int overflow; + + opt = parser.flags; + q = text; + overflow = 0; + + for (s = parser.start; s != parser.end; s++) + { + if (*s == '%' && (s + 1) != parser.end && s[1] == 's') + { + const char *arg; + size_t len; + + arg = va_arg(ap, const char *); + len = strlen(arg); + if ((size_t) (q - text) + len >= sizeof(text)) + { + overflow = 1; + break; + } + memcpy(q, arg, len); + q += len; + s++; + } + else + { + if ((size_t) (q - text) + 1 >= sizeof(text)) + { + overflow = 1; + break; + } + *q++ = *s; + } + } + + if (overflow) + { + rc = result_BAD_ARG; + break; + } + *q = '\0'; + + if (need_title[sp]) + { + need_title[sp] = 0; /* the submenu title -- parsed, not emitted */ + continue; + } + + { + wuss_menu_t *submenu; + + submenu = NULL; + if (opt & SubMenu) + { + const wuss_menu_t *borrowed; + + borrowed = va_arg(ap, const wuss_menu_t *); + rc = menu_deep_copy(borrowed, &submenu); + if (rc != result_OK) + break; + } + + rc = build_add(&stack[sp], text, opt, submenu); + if (rc != result_OK) + { + wuss_menu_destroy(submenu); + break; + } + } + } + } + + va_end(ap); + + if (rc == result_OK && sp != 0) + rc = result_BAD_ARG; /* unclosed { */ + + if (rc != result_OK) + { + for (i = 0; i < WUSS_MENU_DESC_DEPTH; i++) + build_discard(&stack[i]); + return rc; + } + + *out = build_seal(&stack[0]); + if (*out == NULL) + { + build_discard(&stack[0]); + return result_OOM; + } + + return result_OK; +} diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index 40b150b..c8e5a8d 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -191,11 +191,15 @@ static result_t wuss__menu_spawn(wuss_t *wuss, for (i = 0; i < menu->nitems; i++) { const char *text; + int len; int split; bmfont_width_t w; text = menu->items[i].text ? menu->items[i].text : ""; - if (bmfont_measure(wuss->font, text, (int) strlen(text), + len = (int) strlen(text); + if (len == 0) + continue; /* separator row, nothing to measure */ + if (bmfont_measure(wuss->font, text, len, INT_MAX, &split, &w) == result_OK && (int) w > widest) widest = (int) w; } diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index b38be4e..ce1ec4a 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -20,6 +20,9 @@ #include "io/path.h" #include "wuss/wuss.h" #include "wuss/window.h" +#ifdef WUSS_MENUS +#include "wuss/menu.h" +#endif #include "test/all-tests.h" @@ -2578,6 +2581,76 @@ result_t wuss_test(const char *resources) if (tc_a.stop_count != 1 || tc_b.stop_count != 1) goto Failure; +#ifdef WUSS_MENUS + printf("test: wuss_menu_create_from_desc parses a descriptor tree\n"); + { + static const wuss_menu_item_t borrowed_items[] = + { + { "Info", wuss_MENU_ITEM_NONE, NULL }, + { "About", wuss_MENU_ITEM_NONE, NULL } + }; + static const wuss_menu_t borrowed = + { + borrowed_items, NELEMS(borrowed_items) + }; + wuss_menu_t *m; + const wuss_menu_t *sub; + + /* root: App, Grid(!), Export(~), Quit(|), Help(> borrowed) + * with "File" substituted for both %s. */ + rc = wuss_menu_create_from_desc(&m, + "App, %s { %s, !Grid, ~Export, |Quit }, >Help", + "File", "File", &borrowed); + if (rc != result_OK) + goto Failure; + + if (m->nitems != 3) goto MenuFail; + if (strcmp(m->items[0].text, "App") != 0) goto MenuFail; + if (m->items[0].submenu != NULL) goto MenuFail; + + /* items[1] "File" carries the { } submenu; its first token was the title + * and is not emitted, so the submenu has 3 rows */ + if (strcmp(m->items[1].text, "File") != 0) goto MenuFail; + sub = m->items[1].submenu; + printf(" sub1=%p nitems=%d\n", (void *) sub, sub ? sub->nitems : -1); + if (sub != NULL) + printf(" sub1[0]=%s f=%u [1]=%s f=%u [2]=%s f=%u\n", + sub->items[0].text, sub->items[0].flags, + sub->nitems > 1 ? sub->items[1].text : "?", + sub->nitems > 1 ? sub->items[1].flags : 0, + sub->nitems > 2 ? sub->items[2].text : "?", + sub->nitems > 2 ? sub->items[2].flags : 0); + if (sub == NULL || sub->nitems != 3) goto MenuFail; + if (strcmp(sub->items[0].text, "Grid") != 0) goto MenuFail; + if (!(sub->items[0].flags & wuss_MENU_ITEM_TICKED)) goto MenuFail; + if (!(sub->items[1].flags & wuss_MENU_ITEM_DISABLED)) goto MenuFail; + if (!(sub->items[2].flags & wuss_MENU_ITEM_DASHED)) goto MenuFail; + if (strcmp(sub->items[2].text, "Quit") != 0) goto MenuFail; + + /* items[2] "Help" got a deep copy of the borrowed menu */ + if (strcmp(m->items[2].text, "Help") != 0) goto MenuFail; + sub = m->items[2].submenu; + if (sub == NULL || sub == &borrowed) goto MenuFail; + if (sub->nitems != 2) goto MenuFail; + if (strcmp(sub->items[1].text, "About") != 0) goto MenuFail; + + wuss_menu_destroy(m); + + /* malformed: unbalanced brace */ + rc = wuss_menu_create_from_desc(&m, "A { B, C"); + if (rc != result_BAD_ARG) + goto MenuFail; + + rc = result_OK; + goto MenuOK; + +MenuFail: + printf("wuss_test: menu-desc check failed\n"); + return result_TEST_FAILED; +MenuOK: ; + } +#endif + wuss_destroy(wuss); free(pixels); From e69f71ad90f00f6f8ca0aa97662b393271b7c9b2 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 00:02:54 +0100 Subject: [PATCH 25/79] feat(wuss): menu appearance fixes and screen_draw_dashed_line - menus default to a white (nearest-palette) backdrop - menus keep their titlebar furniture (drop wuss_WINDOW_NO_TITLEBAR); submenu positioning switched to content bounds so rows still align under the titlebar - add screen_draw_dashed_line (Bresenham + dash-period counter) and use it for menu separator rows; covered by a new screen test - menus open under the pointer: cache wuss->pointer in both mouse entry points, expose wuss_get_pointer, shift the root menu content top-left so the pointer lands over row 0 - menu-entry highlight now swaps the row's own resolved fg/bg instead of pulling the window-manager accent pair - drop stray debug printfs left in wuss-test.c Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- CMakeLists.txt | 1 + apps/wuss/main.c | 2 +- include/framebuf/screen.h | 25 +++++++ include/wuss/wuss.h | 11 +++ libraries/framebuf/screen/screen-draw.c | 75 ++++++++++++++++++++ libraries/framebuf/screen/test/screen-test.c | 68 +++++++++++++++++- libraries/wuss/create.c | 30 ++++++++ libraries/wuss/get-pointer.c | 12 ++++ libraries/wuss/icon/draw.c | 36 +++++----- libraries/wuss/impl.h | 3 + libraries/wuss/menu/menu.c | 22 ++++-- libraries/wuss/mouse-click.c | 2 + libraries/wuss/mouse-move.c | 2 + libraries/wuss/test/wuss-test.c | 8 --- 14 files changed, 263 insertions(+), 34 deletions(-) create mode 100644 libraries/wuss/get-pointer.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 8c1ba83..a341d93 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -337,6 +337,7 @@ set(WUSS_CORE_SOURCES libraries/wuss/create.c libraries/wuss/destroy.c libraries/wuss/get-font.c + libraries/wuss/get-pointer.c libraries/wuss/idle.c libraries/wuss/impl.h libraries/wuss/invalidate.c diff --git a/apps/wuss/main.c b/apps/wuss/main.c index f9cf4b7..5ff8af3 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -233,7 +233,7 @@ static void menu_selected(const wuss_menu_t *menu, static result_t spawn_menu(void) { - return wuss_menu_open(g_wuss, &g_menu, POINT(120, 120), + return wuss_menu_open(g_wuss, &g_menu, wuss_get_pointer(g_wuss), menu_selected, NULL, NULL); } diff --git a/include/framebuf/screen.h b/include/framebuf/screen.h index 309f205..12bdd4a 100644 --- a/include/framebuf/screen.h +++ b/include/framebuf/screen.h @@ -239,6 +239,31 @@ void screen_draw_line(screen_t *scr, int y1, colour_t colour); +/** + * Draws a stippled line: `on` pixels drawn, then `off` skipped, repeating along + * the line. Bresenham stepping, so the dash period is measured in steps not + * Euclidean distance. `on` <= 0 draws nothing; `off` <= 0 gives a solid line. + * + * Coordinates are `int`s and inclusive. + * + * \param[in] scr Screen to draw upon. + * \param[in] x0 X coordinate of first point of line. + * \param[in] y0 Y coordinate of first point of line. + * \param[in] x1 X coordinate of second point of line. + * \param[in] y1 Y coordinate of second point of line. + * \param[in] on Length of each dash, in steps. + * \param[in] off Gap between dashes, in steps. + * \param[in] colour Colour of line. + */ +void screen_draw_dashed_line(screen_t *scr, + int x0, + int y0, + int x1, + int y1, + int on, + int off, + colour_t colour); + /** * Draws a line (fixed-point Wu version with anti-aliasing). * diff --git a/include/wuss/wuss.h b/include/wuss/wuss.h index b36a36a..439b1d5 100644 --- a/include/wuss/wuss.h +++ b/include/wuss/wuss.h @@ -306,6 +306,17 @@ void wuss_destroy(wuss_t *doomed); */ bmfont_t *wuss_get_font(const wuss_t *wuss); +/** + * The last pointer position seen by wuss_mouse_click or wuss_mouse_move, screen + * space. (0,0) until the first mouse event. Handy for opening a pop-up menu + * under the pointer from a task's icon handler, which gets no coordinate of its + * own. + * + * \param[in] wuss Window manager. + * \return Last pointer position, screen space. + */ +point_t wuss_get_pointer(const wuss_t *wuss); + /** * Redraw every window, back-to-front, having first painted the configured * backdrop colour (see wuss_config_t::backdrop) behind them, if any. diff --git a/libraries/framebuf/screen/screen-draw.c b/libraries/framebuf/screen/screen-draw.c index 26523ab..40a2f01 100644 --- a/libraries/framebuf/screen/screen-draw.c +++ b/libraries/framebuf/screen/screen-draw.c @@ -433,6 +433,81 @@ void screen_draw_line(screen_t *scr, } } +void screen_draw_dashed_line(screen_t *scr, + int x0, + int y0, + int x1, + int y1, + int on, + int off, + colour_t colour) +{ + box_t clip_box; + box_t bounds; + int rx0, ry0, rx1, ry1; + int dx, dy; + int adx, ady; + int sx, sy; + int error, e2; + int period; + int phase; + + if (on <= 0) + return; + if (off < 0) + off = 0; + period = on + off; + + if (screen_get_clip(scr, &clip_box)) + return; /* invalid clipped screen */ + + rx0 = x0; + ry0 = y0; + rx1 = x1; + ry1 = y1; + if (line_clip(&clip_box, &rx0, &ry0, &rx1, &ry1) == 0) + return; + + screen_get_bounds(scr, &bounds); + (void) line_clip(&bounds, &x0, &y0, &x1, &y1); + + dx = x1 - x0; + adx = abs(dx); + sx = SGN(dx); + + dy = y1 - y0; + ady = -abs(dy); + sy = SGN(dy); + + error = adx + ady; + phase = 0; + + for (;;) + { + if (phase < on) + screen_draw_pixel(scr, x0, y0, colour); + if (++phase >= period) + phase = 0; + + if (x0 == x1 && y0 == y1) + break; + + e2 = 2 * error; + if (e2 >= ady) + { + if (x0 == x1) { break; } + error += ady; + x0 += sx; + } + if (e2 <= adx) + { + if (y0 == y1) { break; } + error += adx; + y0 += sy; + } + } +} + void screen_draw_line_wu_fix8(screen_t *scr, fix8_t x0_f8, fix8_t y0_f8, diff --git a/libraries/framebuf/screen/test/screen-test.c b/libraries/framebuf/screen/test/screen-test.c index 2d14a00..3d903ef 100644 --- a/libraries/framebuf/screen/test/screen-test.c +++ b/libraries/framebuf/screen/test/screen-test.c @@ -449,6 +449,71 @@ static result_t test_fill_pattern(void) /* ----------------------------------------------------------------------- */ +static result_t test_dashed_line(void) +{ + static testscreen_t ts; + static testscreen_t enc; + + int fg; + int x; + int on_count, off_count; + + fg = (int) np_encode(&enc, 255, 0, 0); + + /* Horizontal line y=10, x in [0,19], on=2 off=2: phase cycles + * 0,1 (drawn) 2,3 (skipped) starting at x=0. */ + testscreen_init(&ts); + screen_draw_dashed_line(&ts.scr, 0, 10, 19, 10, 2, 2, colour_rgb(255, 0, 0)); + + on_count = off_count = 0; + for (x = 0; x <= 19; x++) + { + int lit = (np_at(&ts, x, 10) == fg); + int want = ((x % 4) < 2); + if (lit != want) + { + printf("screen: dashed_line wrong at x=%d (lit=%d want=%d)\n", + x, lit, want); + return result_TEST_FAILED; + } + if (lit) on_count++; else off_count++; + } + if (on_count != 10 || off_count != 10) + { + printf("screen: dashed_line dash ratio off (on=%d off=%d)\n", + on_count, off_count); + return result_TEST_FAILED; + } + + /* off <= 0 gives a solid line. */ + testscreen_init(&ts); + screen_draw_dashed_line(&ts.scr, 0, 5, 9, 5, 3, 0, colour_rgb(255, 0, 0)); + for (x = 0; x <= 9; x++) + { + if (np_at(&ts, x, 5) != fg) + { + printf("screen: dashed_line with off=0 left a gap at x=%d\n", x); + return result_TEST_FAILED; + } + } + + /* on <= 0 draws nothing. */ + testscreen_init(&ts); + screen_draw_dashed_line(&ts.scr, 0, 7, 9, 7, 0, 4, colour_rgb(255, 0, 0)); + for (x = 0; x <= 9; x++) + { + if (np_at(&ts, x, 7) != (int) (pixelfmt_bgrx8888_t) BACKGROUND) + { + printf("screen: dashed_line with on=0 drew a pixel at x=%d\n", x); + return result_TEST_FAILED; + } + } + + return result_TEST_PASSED; +} + +/* ----------------------------------------------------------------------- */ + result_t screen_test(const char *resources) { typedef result_t (*screentestfn)(void); @@ -459,7 +524,8 @@ result_t screen_test(const char *resources) test_clipping_still_happens, test_wu_fix8_extreme_coords, test_ninepatch, - test_fill_pattern + test_fill_pattern, + test_dashed_line }; result_t rc; diff --git a/libraries/wuss/create.c b/libraries/wuss/create.c index acb6ba4..f1b559b 100644 --- a/libraries/wuss/create.c +++ b/libraries/wuss/create.c @@ -89,6 +89,34 @@ result_t wuss_create(screen_t *scr, memcpy(w->palette, palette, npalette * sizeof(*w->palette)); w->npalette = npalette; + /* Cache the palette index nearest to opaque white, for menu backdrops and + * anything else wanting "paper". Sum-of-squared-channel-distance to white; + * ties keep the first. */ + { + unsigned long best_d; + int i; + + w->white = 0; + best_d = ~0UL; + for (i = 0; i < npalette; i++) + { + unsigned int px; + long dr, dg, db; + unsigned long d; + + px = w->palette[i].primary; + dr = 255 - (long) ((px >> 16) & 0xFF); + dg = 255 - (long) ((px >> 8) & 0xFF); + db = 255 - (long) ( px & 0xFF); + d = (unsigned long) (dr * dr + dg * dg + db * db); + if (d < best_d) + { + best_d = d; + w->white = i; + } + } + } + if (config != NULL) { w->backdrop = config->backdrop; @@ -222,6 +250,8 @@ result_t wuss_create(screen_t *scr, w->layout = NULL; w->cascade.x = 0; w->cascade.y = 0; + w->pointer.x = 0; + w->pointer.y = 0; list_init(&w->z_order); diff --git a/libraries/wuss/get-pointer.c b/libraries/wuss/get-pointer.c new file mode 100644 index 0000000..ee538d8 --- /dev/null +++ b/libraries/wuss/get-pointer.c @@ -0,0 +1,12 @@ +/* get-pointer.c -- wuss - minimal window manager */ + +#include <assert.h> + +#include "impl.h" + +point_t wuss_get_pointer(const wuss_t *wuss) +{ + assert(wuss != NULL); + + return wuss->pointer; +} diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index 60a4826..0de3fdd 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -359,31 +359,30 @@ void wuss__icon_draw(wuss_t *wuss, case wuss_ICON_TYPE_MENU_ENTRY: { - colour_t ink, ground; + colour_t ink, ground, tmp; int disabled, highlit, pad, mid_y; disabled = (icon->flags & wuss_ICON_FLAGS_DISABLED) != 0; highlit = icon->hovered && !disabled; pad = 4; - /* highlight inverts to the accent colours (the window manager's - * emphasis pair); otherwise ink over the row's own/inherited ground */ - if (highlit) - { - ground = wuss->palette[wuss->accent_bg]; - ink = wuss->palette[wuss->accent_fg]; - } + /* resolve the row's own ink over its own/inherited ground */ + if (icon->bg != wuss_NO_BACKGROUND) + ground = wuss->palette[icon->bg]; + else if (icon->window->bg.colour != wuss_NO_BACKGROUND) + ground = (icon->window->bg.pattern != screen_PATTERN_SOLID) + ? wuss->palette[icon->window->bg.pattern_bg] + : wuss->palette[icon->window->bg.colour]; else + ground = fg; /* bmfont still needs a blend colour */ + ink = disabled ? wuss->palette[wuss->bevel_dark] : fg; + + /* highlight simply swaps the row's fg/bg */ + if (highlit) { - if (icon->bg != wuss_NO_BACKGROUND) - ground = wuss->palette[icon->bg]; - else if (icon->window->bg.colour != wuss_NO_BACKGROUND) - ground = (icon->window->bg.pattern != screen_PATTERN_SOLID) - ? wuss->palette[icon->window->bg.pattern_bg] - : wuss->palette[icon->window->bg.colour]; - else - ground = fg; /* bmfont still needs a blend colour */ - ink = disabled ? wuss->palette[wuss->bevel_dark] : fg; + tmp = ink; + ink = ground; + ground = tmp; } if (highlit || icon->bg != wuss_NO_BACKGROUND) @@ -393,7 +392,8 @@ void wuss__icon_draw(wuss_t *wuss, if (icon->flags & wuss_ICON_FLAGS_SEPARATOR) { mid_y = b.y0 + (b.y1 - b.y0) / 2; - screen_draw_line(scr, b.x0 + pad, mid_y, b.x1 - 1 - pad, mid_y, ink); + screen_draw_dashed_line(scr, b.x0 + pad, mid_y, b.x1 - 1 - pad, mid_y, + 2, 2, ink); break; } diff --git a/libraries/wuss/impl.h b/libraries/wuss/impl.h index 49d004b..5944123 100644 --- a/libraries/wuss/impl.h +++ b/libraries/wuss/impl.h @@ -63,6 +63,7 @@ struct wuss bmfont_t *font; /* nullable, not owned */ colour_t *palette; /* owned */ int npalette; + wuss_colour_t white; /* palette index nearest to white */ #ifdef WUSS_FURNITURE wuss_palette_t furniture_colours; #endif @@ -87,6 +88,8 @@ struct wuss * created on first auto-placement */ point_t cascade; /* next cascade offset, used once the * layout packer has no room left */ + point_t pointer; /* last pointer position, screen + * space, from any mouse click/move */ #ifdef WUSS_ICONS wuss_icon_t *pressed_icon; /* button icon held down, NULL when * idle; released on any MOUSE_UP diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index c8e5a8d..e7ea9f1 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -111,11 +111,12 @@ static result_t wuss__menu_handle(wuss_window_t *window, if (item->submenu == NULL || self->child != NULL) return result_OK; - wuss_window_get_visible_bounds(self->window, &wb); + wuss_window_get_content_bounds(self->window, &wb); wuss_icon_get_bbox(icon, &ib); - /* The menu is unscrolled, so a row's document y equals its screen y - * offset from the window's top. */ + /* wb is the content box in screen space; the menu is unscrolled, so a + * row's document y is its offset from the content top. The child's own + * titlebar sits above `at`, so a submenu row lines up with its parent. */ at.x = wb.x1 - WUSS_MENU_SUBMENU_OVERLAP; at.y = wb.y0 + ib.y0; @@ -269,13 +270,16 @@ static result_t wuss__menu_spawn(wuss_t *wuss, size = SIZE2D(width, height); task = wuss_task_start(wuss__menu_handle, node); - rc = wuss_window_create_placed(wuss, size, NULL, - wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE + /* ponytail: menus carry no title string yet; a bare titlebar is enough for + * the drag/knock-back affordance. Add wuss_menu_t.title if a caption is + * wanted. */ + rc = wuss_window_create_placed(wuss, size, "", + wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - wuss_BACKDROP_COLOUR(1), + wuss_BACKDROP_COLOUR(wuss->white), &task, size, size, &node->window); if (rc != result_OK) { @@ -328,6 +332,12 @@ result_t wuss_menu_open(wuss_t *wuss, wuss->menu_chain = NULL; } + /* RISC OS convention: the pointer opens the menu sitting a little inside its + * first item, not on the top-left corner. Shift the content top-left up and + * left so `at` (the pointer) lands over row 0. */ + at.x -= WUSS_MENU_TICK_W; + at.y -= WUSS_MENU_ROW_PAD; + rc = wuss__menu_spawn(wuss, menu, at, NULL, on_select, ctx, &root); if (rc != result_OK) return rc; diff --git a/libraries/wuss/mouse-click.c b/libraries/wuss/mouse-click.c index cf9c735..d513a7f 100644 --- a/libraries/wuss/mouse-click.c +++ b/libraries/wuss/mouse-click.c @@ -15,6 +15,8 @@ result_t wuss_mouse_click(wuss_t *wuss, x = p.x; y = p.y; + wuss->pointer = p; + #ifdef WUSS_ICONS /* Release a held button icon on any MOUSE_UP, before the hit-test picks a * window: the up may land on a window that opened over the icon's owner on diff --git a/libraries/wuss/mouse-move.c b/libraries/wuss/mouse-move.c index fa1c9ea..99f0e72 100644 --- a/libraries/wuss/mouse-move.c +++ b/libraries/wuss/mouse-move.c @@ -10,6 +10,8 @@ result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit) x = p.x; y = p.y; + wuss->pointer = p; + #ifdef WUSS_FURNITURE if (wuss->furniture.dragging != NULL) { diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index ce1ec4a..8c9d5d1 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -2612,14 +2612,6 @@ result_t wuss_test(const char *resources) * and is not emitted, so the submenu has 3 rows */ if (strcmp(m->items[1].text, "File") != 0) goto MenuFail; sub = m->items[1].submenu; - printf(" sub1=%p nitems=%d\n", (void *) sub, sub ? sub->nitems : -1); - if (sub != NULL) - printf(" sub1[0]=%s f=%u [1]=%s f=%u [2]=%s f=%u\n", - sub->items[0].text, sub->items[0].flags, - sub->nitems > 1 ? sub->items[1].text : "?", - sub->nitems > 1 ? sub->items[1].flags : 0, - sub->nitems > 2 ? sub->items[2].text : "?", - sub->nitems > 2 ? sub->items[2].flags : 0); if (sub == NULL || sub->nitems != 3) goto MenuFail; if (strcmp(sub->items[0].text, "Grid") != 0) goto MenuFail; if (!(sub->items[0].flags & wuss_MENU_ITEM_TICKED)) goto MenuFail; From a50c2bae768b9c05f77f6fd728719afd6d659c33 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 00:16:53 +0100 Subject: [PATCH 26/79] fix(wuss): draw menu titlebars The menu helper created its window at a packer slot then wuss_window_move'd it to the pointer. wuss_window_move blits the window's already-rendered pixels to the new spot, but a just-created window has none, so the titlebar landed unpainted (a thin sliver was all that got invalidated). - wuss__menu_spawn now creates the window at the target point directly with wuss_window_create; no post-move - add wuss_menu_t.title (first field) so the titlebar has a caption; menu.c passes it, wuss_menu_create_from_desc leaves it NULL (still discards the descriptor's { } title token), menu_deep_copy carries it, wuss_menu_destroy frees it - demo menus in apps/wuss titled; test literals updated for the new field Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- apps/wuss/main.c | 4 ++-- include/wuss/menu.h | 3 +++ libraries/wuss/menu/create-from-desc.c | 13 +++++++++++ libraries/wuss/menu/menu.c | 32 +++++++++++++++----------- libraries/wuss/test/wuss-test.c | 2 +- 5 files changed, 38 insertions(+), 16 deletions(-) diff --git a/apps/wuss/main.c b/apps/wuss/main.c index 5ff8af3..6086d1a 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -203,7 +203,7 @@ static const wuss_menu_item_t g_menu_export_items[] = }; static const wuss_menu_t g_menu_export = { - g_menu_export_items, NELEMS(g_menu_export_items) + "Export", g_menu_export_items, NELEMS(g_menu_export_items) }; static const wuss_menu_item_t g_menu_items[] = @@ -217,7 +217,7 @@ static const wuss_menu_item_t g_menu_items[] = }; static const wuss_menu_t g_menu = { - g_menu_items, NELEMS(g_menu_items) + "Display", g_menu_items, NELEMS(g_menu_items) }; static void menu_selected(const wuss_menu_t *menu, diff --git a/include/wuss/menu.h b/include/wuss/menu.h index ca286f9..4a348c4 100644 --- a/include/wuss/menu.h +++ b/include/wuss/menu.h @@ -55,6 +55,9 @@ wuss_menu_item_t; /** A menu: an array of items the caller owns. */ typedef struct wuss_menu { + const char *title; /**< titlebar caption; NULL is treated as "" + * (wuss_menu_create_from_desc leaves this + * NULL) */ const wuss_menu_item_t *items; int nitems; } diff --git a/libraries/wuss/menu/create-from-desc.c b/libraries/wuss/menu/create-from-desc.c index 80bbaef..3b8e307 100644 --- a/libraries/wuss/menu/create-from-desc.c +++ b/libraries/wuss/menu/create-from-desc.c @@ -125,6 +125,7 @@ void wuss_menu_destroy(wuss_menu_t *menu) free((void *) menu->items[i].text); wuss_menu_destroy((wuss_menu_t *) menu->items[i].submenu); } + free((void *) menu->title); free((void *) menu->items); free(menu); } @@ -151,6 +152,14 @@ static result_t menu_deep_copy(const wuss_menu_t *src, wuss_menu_t **out) return result_OOM; } + m->title = src->title ? strdup(src->title) : NULL; + if (src->title != NULL && m->title == NULL) + { + free(items); + free(m); + return result_OOM; + } + m->items = items; m->nitems = src->nitems; @@ -253,6 +262,10 @@ static wuss_menu_t *build_seal(Building *b) if (m == NULL) return NULL; + /* ponytail: the descriptor's { } title token is still discarded, so a + * parser-built menu has no caption. Wire it through Building if that + * changes. */ + m->title = NULL; m->items = b->items; m->nitems = b->n; diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index e7ea9f1..4ff8c13 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -176,6 +176,7 @@ static result_t wuss__menu_spawn(wuss_t *wuss, int width, height; int i; size2d_t size; + box_t content; assert(wuss != NULL); assert(menu != NULL); @@ -270,17 +271,24 @@ static result_t wuss__menu_spawn(wuss_t *wuss, size = SIZE2D(width, height); task = wuss_task_start(wuss__menu_handle, node); - /* ponytail: menus carry no title string yet; a bare titlebar is enough for - * the drag/knock-back affordance. Add wuss_menu_t.title if a caption is - * wanted. */ - rc = wuss_window_create_placed(wuss, size, "", - wuss_WINDOW_NO_OUTLINE - | wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK - | wuss_WINDOW_NO_TOGGLE_SIZE - | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL - | wuss_WINDOW_NO_RESIZE, - wuss_BACKDROP_COLOUR(wuss->white), - &task, size, size, &node->window); + /* `at` is the content top-left, already clamped on screen above. Create the + * window there directly -- creating it elsewhere and wuss_window_move-ing it + * afterwards would blit its not-yet-rendered pixels and leave the titlebar + * unpainted. */ + content.x0 = at.x; + content.y0 = at.y; + content.x1 = at.x + width; + content.y1 = at.y + height; + + rc = wuss_window_create(wuss, &content, + menu->title ? menu->title : "", + wuss_WINDOW_NO_OUTLINE + | wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK + | wuss_WINDOW_NO_TOGGLE_SIZE + | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL + | wuss_WINDOW_NO_RESIZE, + wuss_BACKDROP_COLOUR(wuss->white), + &task, size, size, &node->window); if (rc != result_OK) { free(specs); @@ -289,8 +297,6 @@ static result_t wuss__menu_spawn(wuss_t *wuss, return rc; } - wuss_window_move(node->window, at); - rc = wuss_icon_create_array(node->window, specs, menu->nitems, node->icons); free(specs); if (rc != result_OK) diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index 8c9d5d1..d0512bd 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -2591,7 +2591,7 @@ result_t wuss_test(const char *resources) }; static const wuss_menu_t borrowed = { - borrowed_items, NELEMS(borrowed_items) + "Help", borrowed_items, NELEMS(borrowed_items) }; wuss_menu_t *m; const wuss_menu_t *sub; From 98b2dbb634168f849761988b8f1ab78a47978878 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 00:44:40 +0100 Subject: [PATCH 27/79] feat(wuss): dashed menu items draw a rule above and stay pickable wuss_MENU_ITEM_DASHED / wuss_ICON_FLAGS_SEPARATOR previously meant "this entry IS the rule, its text is ignored". Flip it: a dashed entry now draws a dashed rule along its own top edge and then renders its label as normal, remaining hit-testable. An entry that carries the flag with no text is still a bare, inert rule. - create-from-desc: '|' sets DASHED on the following item itself, no synthetic rule row - menu.c: DASHED entries are no longer skipped by the pick guard - icon/hit-test: a SEPARATOR MENU_ENTRY is inert only when its text is empty - icon/draw: SEPARATOR draws the rule in the row's ink (visible when inverted) then falls through to tick/arrow/text - apps/wuss: drop the standalone rule row, mark Quit DASHED - also lands the wuss_menu_create_from_desc SDL launcher test and the root-title-from-first-token descriptor change Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- apps/wuss/main.c | 46 +++++++++++++++++++++++--- include/wuss/icon.h | 17 ++++++---- include/wuss/menu.h | 38 +++++++++++---------- libraries/wuss/icon/draw.c | 9 ++--- libraries/wuss/icon/hit-test.c | 8 +++-- libraries/wuss/menu/create-from-desc.c | 29 +++++++++++++++- libraries/wuss/menu/menu.c | 4 +-- libraries/wuss/test/wuss-test.c | 15 +++++---- 8 files changed, 122 insertions(+), 44 deletions(-) diff --git a/apps/wuss/main.c b/apps/wuss/main.c index 6086d1a..b06613c 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -194,7 +194,8 @@ static result_t spawn_porter_duff(void) } /* A static demo menu tree for the pop-up helper: a submenu, a couple of - * ticked rows and a dashed separator. wuss_menu_open never mutates it. */ + * ticked rows and a dashed rule above the final entry. wuss_menu_open never + * mutates it. */ static const wuss_menu_item_t g_menu_export_items[] = { { "As PNG", wuss_MENU_ITEM_NONE, NULL }, @@ -212,8 +213,7 @@ static const wuss_menu_item_t g_menu_items[] = { "Show grid", wuss_MENU_ITEM_TICKED, NULL }, { "Wireframe", wuss_MENU_ITEM_TICKED, NULL }, { "Export", wuss_MENU_ITEM_NONE, &g_menu_export }, - { NULL, wuss_MENU_ITEM_DASHED, NULL }, - { "Quit", wuss_MENU_ITEM_NONE, NULL } + { "Quit", wuss_MENU_ITEM_DASHED, NULL } }; static const wuss_menu_t g_menu = { @@ -237,6 +237,41 @@ static result_t spawn_menu(void) menu_selected, NULL, NULL); } +/* Same menu shape built from a descriptor string, to exercise + * wuss_menu_create_from_desc. The tree must outlive the open chain, so it is + * kept here and rebuilt (previous one freed) on each open. Freed for good in + * run_wuss's teardown. */ +static wuss_menu_t *g_menu_desc; + +static const wuss_menu_item_t g_menu_desc_export_items[] = +{ + { "As PNG", wuss_MENU_ITEM_NONE, NULL }, + { "As JPEG", wuss_MENU_ITEM_NONE, NULL }, + { "As GIF", wuss_MENU_ITEM_DISABLED, NULL } +}; +static const wuss_menu_t g_menu_desc_export = +{ + "Export", g_menu_desc_export_items, NELEMS(g_menu_desc_export_items) +}; + +static result_t spawn_menu_desc(void) +{ + wuss_menu_t *m; + result_t rc; + + rc = wuss_menu_create_from_desc(&m, + "Display, Open, !Show grid, !Wireframe, >Export, |Quit", + &g_menu_desc_export); + if (rc != result_OK) + return rc; + + wuss_menu_destroy(g_menu_desc); + g_menu_desc = m; + + return wuss_menu_open(g_wuss, g_menu_desc, wuss_get_pointer(g_wuss), + menu_selected, NULL, NULL); +} + static const launcher_entry_t g_launcher_entries[] = { { "Ball", spawn_ball }, @@ -251,7 +286,8 @@ static const launcher_entry_t g_launcher_entries[] = { "Gradient", spawn_gradient }, { "Icons", spawn_icons }, { "Porter-Duff", spawn_porter_duff }, - { "Menu", spawn_menu } + { "Menu", spawn_menu }, + { "Menu (desc)", spawn_menu_desc } }; static wuss_button_t sdl_button_to_wuss(Uint8 button) @@ -579,6 +615,8 @@ static result_t run_wuss(const char *resources) * task ever needs deterministic teardown. */ launcher_destroy(&launcher_task); + wuss_menu_destroy(g_menu_desc); + wuss_destroy(wuss); bmfont_destroy(font); bmfont_destroy(daydream_font); diff --git a/include/wuss/icon.h b/include/wuss/icon.h index 50203b5..13c709b 100644 --- a/include/wuss/icon.h +++ b/include/wuss/icon.h @@ -98,9 +98,12 @@ typedef enum wuss_icon_type * selected (see wuss_icon_set_selected), and an * optional submenu arrow at the right edge with * wuss_ICON_FLAGS_SUBMENU. - * wuss_ICON_FLAGS_SEPARATOR draws a horizontal - * rule instead of text. Interactive: a click - * raises wuss_EVENT_ICON like a button; a + * wuss_ICON_FLAGS_SEPARATOR draws a dashed rule + * along the top edge before the text, marking a + * group boundary; the entry keeps its label and + * stays interactive (an entry with the flag and + * no text is just a bare rule). Interactive: a + * click raises wuss_EVENT_ICON like a button; a * disabled entry never highlights and its clicks * fall through. */ } @@ -142,9 +145,11 @@ typedef enum wuss_icon_flags * a submenu. Ignored by other * types. */ wuss_ICON_FLAGS_SEPARATOR = 1 << 7 /**< wuss_ICON_TYPE_MENU_ENTRY: draw a - * horizontal rule across the - * bounding box instead of text; the - * entry is inert (not hit-tested). + * dashed rule along the entry's top + * edge, then the text as normal; the + * entry stays interactive. An entry + * carrying the flag with no text is + * a bare rule and is not hit-tested. * Ignored by other types. */ } wuss_icon_flags_t; diff --git a/include/wuss/menu.h b/include/wuss/menu.h index 4a348c4..7d28c74 100644 --- a/include/wuss/menu.h +++ b/include/wuss/menu.h @@ -35,8 +35,9 @@ typedef enum wuss_menu_item_flags { wuss_MENU_ITEM_NONE = 0, wuss_MENU_ITEM_TICKED = 1 << 0, /**< draw a tick at the item's left edge */ - wuss_MENU_ITEM_DASHED = 1 << 1, /**< this item is a separator rule; text - * is ignored */ + wuss_MENU_ITEM_DASHED = 1 << 1, /**< draw a dashed separator immediately + * above this item; the item keeps its + * own text and stays selectable */ wuss_MENU_ITEM_DISABLED = 1 << 2 /**< greyed, never highlights, not * selectable */ } @@ -55,9 +56,7 @@ wuss_menu_item_t; /** A menu: an array of items the caller owns. */ typedef struct wuss_menu { - const char *title; /**< titlebar caption; NULL is treated as "" - * (wuss_menu_create_from_desc leaves this - * NULL) */ + const char *title; /**< titlebar caption; NULL treated as "" */ const wuss_menu_item_t *items; int nitems; } @@ -114,26 +113,29 @@ int wuss_menu_is_open(wuss_menu_handle_t handle); /** * Build a wuss_menu_t tree from a compact descriptor string. The syntax is - * lifted from PrivateEye's menu_create_from_desc. A comma separates items. A - * leading '|' marks an item a dashed separator above it - * (wuss_MENU_ITEM_DASHED). A '{ ... }' group after an item is that item's - * submenu; as in the Wimp original the first token inside the braces is a title - * and is discarded, so the same descriptor strings port across unchanged. A - * per-token prefix '!' ticks the item, '~' shades (disables) it, and '>' - * attaches a submenu pulled as a <tt>const wuss_menu_t *</tt> from the varargs - * rather than from a following '{ }' block. A "%s" in a token substitutes the - * next <tt>const char *</tt> vararg. The '>' and "%s" varargs are consumed in - * the order they are encountered scanning left to right. + * lifted from PrivateEye's menu_create_from_desc. A comma separates items. The + * very first token is the root menu's titlebar caption, not an item -- exactly + * as the first token inside a '{ }' is that submenu's title -- so the same + * descriptor strings port across unchanged. Submenus keep the Wimp behaviour of + * discarding their title token; only the root's is kept. A leading '|' on an + * item sets wuss_MENU_ITEM_DASHED on it: a dashed rule is drawn above the item + * while the item keeps its own label and stays selectable. A '{ ... }' group + * after an item is that item's submenu. A per-token prefix '!' ticks the item, + * '~' shades (disables) it, and '>' attaches a submenu pulled as a <tt>const + * wuss_menu_t *</tt> from the varargs rather than from a following '{ }' block. + * A "%s" in a token substitutes the next <tt>const char *</tt> vararg. The '>' + * and "%s" varargs are consumed in the order they are encountered scanning left + * to right. * - * Example: <tt>wuss_menu_create_from_desc(&m, "App, %s { %s, !Grid, ~Export, - * |Quit }", "File", "File")</tt>. + * Example: <tt>wuss_menu_create_from_desc(&m, "Display, Open, !Grid, ~Export, + * |Quit")</tt> -- "Display" is the caption, the menu has four rows. * * The whole tree, including copied label text, is one heap allocation graph * owned by the caller; free it with wuss_menu_destroy. wuss_menu_open treats a * desc-built tree exactly like a static literal. * * \param[out] out Filled with the root menu on success, untouched on failure. - * \param[in] desc Descriptor string. + * \param[in] desc Descriptor string; its first token is the root caption. * \return \ref result_OK, \ref result_OOM, or \ref result_BAD_ARG for a * malformed descriptor (unbalanced braces, empty token, too deep). */ diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index 0de3fdd..33ab5cc 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -389,12 +389,13 @@ void wuss__icon_draw(wuss_t *wuss, screen_draw_rect(scr, b.x0, b.y0, SIZE2D(b.x1 - b.x0, b.y1 - b.y0), ground); + /* a dashed rule along the entry's top edge, then the text as normal; + * drawn in the row's ink so it stays visible when the row is inverted */ if (icon->flags & wuss_ICON_FLAGS_SEPARATOR) { - mid_y = b.y0 + (b.y1 - b.y0) / 2; - screen_draw_dashed_line(scr, b.x0 + pad, mid_y, b.x1 - 1 - pad, mid_y, - 2, 2, ink); - break; + mid_y = b.y0 + 1; + screen_draw_dashed_line(scr, b.x0 + pad, mid_y, + b.x1 - 1 - pad, mid_y, 2, 2, ink); } /* left-edge tick when selected */ diff --git a/libraries/wuss/icon/hit-test.c b/libraries/wuss/icon/hit-test.c index e0a12f8..79fb799 100644 --- a/libraries/wuss/icon/hit-test.c +++ b/libraries/wuss/icon/hit-test.c @@ -14,8 +14,10 @@ wuss_icon_t *wuss__icon_hit_test(wuss_window_t *window, point_t doc_point) { it = window->icons[i]; - /* a bitmap icon is interactive only when it asks to be; a menu separator - * never is; the other clickable types always are */ + /* a bitmap icon is interactive only when it asks to be; a menu entry that + * is a bare rule (SEPARATOR flag, no text) is inert, but one that only + * carries a rule above its own label stays pickable; the other clickable + * types always are */ if (it->type == wuss_ICON_TYPE_BITMAP) { if (!(it->flags & wuss_ICON_FLAGS_INTERACTIVE)) @@ -23,7 +25,7 @@ wuss_icon_t *wuss__icon_hit_test(wuss_window_t *window, point_t doc_point) } else if (it->type == wuss_ICON_TYPE_MENU_ENTRY) { - if (it->flags & wuss_ICON_FLAGS_SEPARATOR) + if ((it->flags & wuss_ICON_FLAGS_SEPARATOR) && it->text[0] == '\0') continue; } else if (it->type != wuss_ICON_TYPE_BUTTON && diff --git a/libraries/wuss/menu/create-from-desc.c b/libraries/wuss/menu/create-from-desc.c index 3b8e307..94ed53a 100644 --- a/libraries/wuss/menu/create-from-desc.c +++ b/libraries/wuss/menu/create-from-desc.c @@ -240,11 +240,14 @@ static result_t build_add(Building *b, it->flags = wuss_MENU_ITEM_NONE; it->submenu = submenu; + /* '|' before this item means "draw a dashed rule above it"; the item keeps + * its own text and stays pickable. */ if (b->pending_dash) { it->flags |= wuss_MENU_ITEM_DASHED; b->pending_dash = 0; } + if (opt & Tick) it->flags |= wuss_MENU_ITEM_TICKED; if (opt & Shade) @@ -297,6 +300,7 @@ result_t wuss_menu_create_from_desc(wuss_menu_t **out, const char *desc, ...) Parser parser; Building stack[WUSS_MENU_DESC_DEPTH]; int need_title[WUSS_MENU_DESC_DEPTH]; + char *titles[WUSS_MENU_DESC_DEPTH]; int sp; va_list ap; result_t rc; @@ -313,8 +317,14 @@ result_t wuss_menu_create_from_desc(wuss_menu_t **out, const char *desc, ...) stack[i].items = NULL; stack[i].n = stack[i].cap = stack[i].pending_dash = 0; need_title[i] = 0; + titles[i] = NULL; } + /* the descriptor's first token is the root menu's title, exactly as the + * first token inside a { } is that submenu's -- so the same strings port + * from PrivateEye unchanged */ + need_title[0] = 1; + sp = 0; rc = result_OK; @@ -433,7 +443,18 @@ result_t wuss_menu_create_from_desc(wuss_menu_t **out, const char *desc, ...) if (need_title[sp]) { - need_title[sp] = 0; /* the submenu title -- parsed, not emitted */ + /* the menu's title token -- captured, not emitted as an item; kept + * only for the root (sp 0), submenus get no caption */ + need_title[sp] = 0; + if (sp == 0) + { + titles[0] = strdup(text); + if (titles[0] == NULL) + { + rc = result_OOM; + break; + } + } continue; } @@ -469,7 +490,10 @@ result_t wuss_menu_create_from_desc(wuss_menu_t **out, const char *desc, ...) if (rc != result_OK) { for (i = 0; i < WUSS_MENU_DESC_DEPTH; i++) + { build_discard(&stack[i]); + free(titles[i]); + } return rc; } @@ -477,8 +501,11 @@ result_t wuss_menu_create_from_desc(wuss_menu_t **out, const char *desc, ...) if (*out == NULL) { build_discard(&stack[0]); + free(titles[0]); return result_OOM; } + (*out)->title = titles[0]; /* ownership transferred; NULL if none given */ + return result_OK; } diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index 4ff8c13..c085598 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -91,7 +91,7 @@ static result_t wuss__menu_handle(wuss_window_t *window, item = &self->menu->items[index]; - if (item->flags & (wuss_MENU_ITEM_DASHED | wuss_MENU_ITEM_DISABLED)) + if (item->flags & wuss_MENU_ITEM_DISABLED) return result_OK; if (event->data.icon.action == wuss_MOUSE_MOVE) @@ -200,7 +200,7 @@ static result_t wuss__menu_spawn(wuss_t *wuss, text = menu->items[i].text ? menu->items[i].text : ""; len = (int) strlen(text); if (len == 0) - continue; /* separator row, nothing to measure */ + continue; /* empty label (bare rule row); nothing to measure */ if (bmfont_measure(wuss->font, text, len, INT_MAX, &split, &w) == result_OK && (int) w > widest) widest = (int) w; diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index d0512bd..9335870 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -2596,28 +2596,31 @@ result_t wuss_test(const char *resources) wuss_menu_t *m; const wuss_menu_t *sub; - /* root: App, <title discarded> Grid(!), Export(~), Quit(|), Help(> borrowed) - * with "File" substituted for both %s. */ + /* "Root" is the root caption (first token), then items App, + * File{ %s title discarded, Grid(!), Export(~), |Quit }, Help(> borrowed), + * with "File" substituted for both %s. '|Quit' sets DASHED on Quit itself + * (rule drawn above it, text kept), so the submenu holds 3 rows. */ rc = wuss_menu_create_from_desc(&m, - "App, %s { %s, !Grid, ~Export, |Quit }, >Help", + "Root, App, %s { %s, !Grid, ~Export, |Quit }, >Help", "File", "File", &borrowed); if (rc != result_OK) goto Failure; if (m->nitems != 3) goto MenuFail; + if (m->title == NULL || strcmp(m->title, "Root") != 0) goto MenuFail; if (strcmp(m->items[0].text, "App") != 0) goto MenuFail; if (m->items[0].submenu != NULL) goto MenuFail; /* items[1] "File" carries the { } submenu; its first token was the title - * and is not emitted, so the submenu has 3 rows */ + * and is not emitted; '|Quit' keeps Quit's text and flags it DASHED */ if (strcmp(m->items[1].text, "File") != 0) goto MenuFail; sub = m->items[1].submenu; if (sub == NULL || sub->nitems != 3) goto MenuFail; if (strcmp(sub->items[0].text, "Grid") != 0) goto MenuFail; if (!(sub->items[0].flags & wuss_MENU_ITEM_TICKED)) goto MenuFail; if (!(sub->items[1].flags & wuss_MENU_ITEM_DISABLED)) goto MenuFail; - if (!(sub->items[2].flags & wuss_MENU_ITEM_DASHED)) goto MenuFail; if (strcmp(sub->items[2].text, "Quit") != 0) goto MenuFail; + if (!(sub->items[2].flags & wuss_MENU_ITEM_DASHED)) goto MenuFail; /* items[2] "Help" got a deep copy of the borrowed menu */ if (strcmp(m->items[2].text, "Help") != 0) goto MenuFail; @@ -2629,7 +2632,7 @@ result_t wuss_test(const char *resources) wuss_menu_destroy(m); /* malformed: unbalanced brace */ - rc = wuss_menu_create_from_desc(&m, "A { B, C"); + rc = wuss_menu_create_from_desc(&m, "T, A { B, C"); if (rc != result_BAD_ARG) goto MenuFail; From bfca21293cdc53d9b9c08ed1ef3465edd8eb0587 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 09:19:05 +0100 Subject: [PATCH 28/79] feat(wuss): pluggable allocator via wuss_alloc_t Add wuss_alloc_t (malloc/realloc/free hooks) and a wuss_alloc stdlib default. wuss_create takes a const wuss_alloc_t * (NULL selects wuss_alloc); the hooks are copied into struct wuss and reached through wuss__malloc/ wuss__realloc/wuss__free helpers in impl.h. Every heap block a wuss_t owns -- the instance, its palette, windows, icons, icon-pointer arrays and menu nodes -- now allocates through them. menu/create-from-desc.c is left on stdlib: its API takes no wuss_t. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- apps/wuss/main.c | 3 ++- include/wuss/wuss.h | 24 ++++++++++++++++++++++ libraries/wuss/create.c | 29 +++++++++++++++++--------- libraries/wuss/destroy.c | 6 +++--- libraries/wuss/icon/create.c | 8 ++++---- libraries/wuss/icon/delete.c | 4 ++-- libraries/wuss/icon/free.c | 11 ++++++---- libraries/wuss/icon/set-text.c | 6 ++++-- libraries/wuss/impl.h | 21 +++++++++++++++++++ libraries/wuss/menu/menu.c | 36 +++++++++++++++++++-------------- libraries/wuss/test/wuss-test.c | 8 ++++---- libraries/wuss/window/close.c | 2 +- libraries/wuss/window/create.c | 4 ++-- 13 files changed, 114 insertions(+), 48 deletions(-) diff --git a/apps/wuss/main.c b/apps/wuss/main.c index b06613c..98fd074 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -445,7 +445,8 @@ static result_t run_wuss(const char *resources) config.backdrop.pattern = screen_PATTERN_DOTS; config.backdrop.pattern_bg = palette_PICO8_LIGHT_GREY; - rc = wuss_create(&scr, font, palette, NELEMS(palette), &config, &wuss); + rc = wuss_create(&scr, font, palette, NELEMS(palette), &config, NULL, + &wuss); if (rc != result_OK) goto Failure; } diff --git a/include/wuss/wuss.h b/include/wuss/wuss.h index 439b1d5..b57259c 100644 --- a/include/wuss/wuss.h +++ b/include/wuss/wuss.h @@ -16,6 +16,8 @@ extern "C" { #endif +#include <stddef.h> + #include "base/result.h" #include "framebuf/bmfont.h" #include "framebuf/screen.h" @@ -46,6 +48,24 @@ typedef struct wuss_window wuss_window_t; * library is built with the WUSS_ICONS option on. */ typedef struct wuss_icon wuss_icon_t; +/** + * Allocator hooks used by a wuss_t for every heap block it owns (the instance + * itself, windows, icons, menu nodes). The three members must behave like the C + * library malloc / realloc / free -- same argument and return conventions, + * realloc(NULL, n) == malloc(n), free(NULL) a no-op. Passed to wuss_create and + * copied in; NULL there selects wuss_alloc (plain stdlib). + */ +typedef struct wuss_alloc +{ + void *(*malloc)(size_t size); + void *(*realloc)(void *ptr, size_t size); + void (*free)(void *ptr); +} +wuss_alloc_t; + +/** The default allocator: the C library malloc / realloc / free. */ +extern const wuss_alloc_t wuss_alloc; + /** * Mouse buttons, RISC OS-style: Select is the primary action, Adjust the * secondary action, Menu pops up a menu. @@ -278,6 +298,9 @@ wuss_config_t; * \param[in] npalette Number of entries in palette. Ignored if palette is * NULL. * \param[in] config Creation-time configuration, or NULL for defaults. + * \param[in] alloc Allocator hooks, copied in, or NULL for \ref wuss_alloc + * (plain stdlib). Must outlive nothing -- only the three + * function pointers are kept. * \param[out] wuss Newly created window manager. * \return \ref result_OK on success, \ref result_WUSS_BAD_COLOUR if any of * config's palette entries are out of range for the palette, or another @@ -288,6 +311,7 @@ result_t wuss_create(screen_t *scr, const colour_t *palette, int npalette, const wuss_config_t *config, + const wuss_alloc_t *alloc, wuss_t **wuss); /** diff --git a/libraries/wuss/create.c b/libraries/wuss/create.c index f1b559b..f1282b8 100644 --- a/libraries/wuss/create.c +++ b/libraries/wuss/create.c @@ -12,6 +12,10 @@ #include "impl.h" +/* The default allocator: plain stdlib. wuss_create copies this in when its + * alloc argument is NULL. */ +const wuss_alloc_t wuss_alloc = { malloc, realloc, free }; + /* Built-in fallback palette when the caller passes NULL: black and white. * wuss makes no other assumptions about palette contents or length. */ static const colour_t wuss__default_palette[] = @@ -46,8 +50,10 @@ result_t wuss_create(screen_t *scr, const colour_t *palette, int npalette, const wuss_config_t *config, + const wuss_alloc_t *alloc, wuss_t **wuss) { + wuss_alloc_t al; wuss_t *w; #ifdef WUSS_FURNITURE wuss_palette_t pal; @@ -65,9 +71,12 @@ result_t wuss_create(screen_t *scr, assert(scr != NULL); assert(wuss != NULL); - w = malloc(sizeof(*w)); + al = (alloc != NULL) ? *alloc : wuss_alloc; + + w = al.malloc(sizeof(*w)); if (w == NULL) return result_OOM; + w->alloc = al; if (palette == NULL) { @@ -76,14 +85,14 @@ result_t wuss_create(screen_t *scr, } else if (npalette <= 0) { - free(w); + wuss__free(w, w); return result_BAD_ARG; } - w->palette = malloc(npalette * sizeof(*w->palette)); + w->palette = wuss__malloc(w, npalette * sizeof(*w->palette)); if (w->palette == NULL) { - free(w); + wuss__free(w, w); return result_OOM; } memcpy(w->palette, palette, npalette * sizeof(*w->palette)); @@ -169,8 +178,8 @@ result_t wuss_create(screen_t *scr, pal.scroll.sausages < 0 || pal.scroll.sausages >= w->npalette || validate_bevel_backdrop(w, blight, bdark, abg, afg) != result_OK) { - free(w->palette); - free(w); + wuss__free(w, w->palette); + wuss__free(w, w); return result_WUSS_BAD_COLOUR; } @@ -212,8 +221,8 @@ result_t wuss_create(screen_t *scr, } if (validate_bevel_backdrop(w, blight, bdark, abg, afg) != result_OK) { - free(w->palette); - free(w); + wuss__free(w, w->palette); + wuss__free(w, w); return result_WUSS_BAD_COLOUR; } w->bevel_light = blight; @@ -223,8 +232,8 @@ result_t wuss_create(screen_t *scr, #else if (wuss__validate_backdrop(w, &w->backdrop) != result_OK) { - free(w->palette); - free(w); + wuss__free(w, w->palette); + wuss__free(w, w); return result_WUSS_BAD_COLOUR; } #endif diff --git a/libraries/wuss/destroy.c b/libraries/wuss/destroy.c index 7bc4bf0..9ea3327 100644 --- a/libraries/wuss/destroy.c +++ b/libraries/wuss/destroy.c @@ -30,11 +30,11 @@ void wuss_destroy(wuss_t *doomed) #ifdef WUSS_ICONS wuss__icons_free((wuss_window_t *) e); #endif - free(e); + wuss__free(doomed, e); e = next; } packer_destroy(doomed->layout); - free(doomed->palette); - free(doomed); + wuss__free(doomed, doomed->palette); + wuss__free(doomed, doomed); /* reads doomed->alloc.free before freeing */ } diff --git a/libraries/wuss/icon/create.c b/libraries/wuss/icon/create.c index 7258864..d29f815 100644 --- a/libraries/wuss/icon/create.c +++ b/libraries/wuss/icon/create.c @@ -58,23 +58,23 @@ result_t wuss_icon_create(wuss_window_t *window, if (window->nicons == window->cap_icons) { newcap = (window->cap_icons == 0) ? 4 : window->cap_icons * 2; - grown = realloc(window->icons, newcap * sizeof(*window->icons)); + grown = wuss__realloc(w, window->icons, newcap * sizeof(*window->icons)); if (grown == NULL) return result_OOM; window->icons = grown; window->cap_icons = newcap; } - it = malloc(sizeof(*it)); + it = wuss__malloc(w, sizeof(*it)); if (it == NULL) return result_OOM; src = (spec->text != NULL) ? spec->text : ""; len = strlen(src); - it->text = malloc(len + 1); + it->text = wuss__malloc(w, len + 1); if (it->text == NULL) { - free(it); + wuss__free(w, it); return result_OOM; } memcpy(it->text, src, len + 1); diff --git a/libraries/wuss/icon/delete.c b/libraries/wuss/icon/delete.c index 0642fac..35d495d 100644 --- a/libraries/wuss/icon/delete.c +++ b/libraries/wuss/icon/delete.c @@ -34,6 +34,6 @@ void wuss_icon_delete(wuss_icon_t *icon) } } - free(icon->text); - free(icon); + wuss__free(window->wuss, icon->text); + wuss__free(window->wuss, icon); } diff --git a/libraries/wuss/icon/free.c b/libraries/wuss/icon/free.c index d7cbac7..2630b26 100644 --- a/libraries/wuss/icon/free.c +++ b/libraries/wuss/icon/free.c @@ -10,15 +10,18 @@ void wuss__icons_free(wuss_window_t *window) { - int i; + wuss_t *w; + int i; + + w = window->wuss; for (i = 0; i < window->nicons; i++) { - free(window->icons[i]->text); - free(window->icons[i]); + wuss__free(w, window->icons[i]->text); + wuss__free(w, window->icons[i]); } - free(window->icons); + wuss__free(w, window->icons); window->icons = NULL; window->nicons = 0; window->cap_icons = 0; diff --git a/libraries/wuss/icon/set-text.c b/libraries/wuss/icon/set-text.c index 3a5eca2..3f67a6c 100644 --- a/libraries/wuss/icon/set-text.c +++ b/libraries/wuss/icon/set-text.c @@ -11,19 +11,21 @@ result_t wuss_icon_set_text(wuss_icon_t *icon, const char *text) { + wuss_t *w; const char *src; char *dup; size_t len; + w = icon->window->wuss; src = (text != NULL) ? text : ""; len = strlen(src); - dup = malloc(len + 1); + dup = wuss__malloc(w, len + 1); if (dup == NULL) return result_OOM; memcpy(dup, src, len + 1); - free(icon->text); + wuss__free(w, icon->text); icon->text = dup; wuss__icon_invalidate(icon); diff --git a/libraries/wuss/impl.h b/libraries/wuss/impl.h index 5944123..97eecd3 100644 --- a/libraries/wuss/impl.h +++ b/libraries/wuss/impl.h @@ -61,6 +61,9 @@ struct wuss { screen_t *scr; bmfont_t *font; /* nullable, not owned */ + wuss_alloc_t alloc; /* malloc/realloc/free hooks, copied in + * by wuss_create; used for every heap + * block this wuss_t owns */ colour_t *palette; /* owned */ int npalette; wuss_colour_t white; /* palette index nearest to white */ @@ -142,6 +145,24 @@ struct wuss_window wuss_window_t *wuss__window_at(wuss_t *wuss, point_t p); +/* Allocation through a wuss_t's configured hooks (see struct wuss::alloc). + * Every heap block a wuss_t owns -- windows, icons, icon-pointer arrays, menu + * nodes -- goes through these so a caller can supply its own allocator. */ +static inline void *wuss__malloc(const wuss_t *wuss, size_t size) +{ + return wuss->alloc.malloc(size); +} + +static inline void *wuss__realloc(const wuss_t *wuss, void *ptr, size_t size) +{ + return wuss->alloc.realloc(ptr, size); +} + +static inline void wuss__free(const wuss_t *wuss, void *ptr) +{ + wuss->alloc.free(ptr); +} + /* Range-check a backdrop spec against the palette: its colour, and -- when a * non-solid pattern is set -- its pattern index and background colour. * Returns result_OK or result_WUSS_BAD_COLOUR. */ diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index c085598..a55ce44 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -51,9 +51,14 @@ static void wuss__menu_close_from(struct wuss__menu *node) wuss__menu_close_from(node->child); node->child = NULL; - wuss_window_close(node->window); - free(node->icons); - free(node); + { + wuss_t *w; + + w = node->wuss; + wuss_window_close(node->window); + wuss__free(w, node->icons); + wuss__free(w, node); + } } /* ----------------------------------------------------------------------- */ @@ -219,19 +224,20 @@ static result_t wuss__menu_spawn(wuss_t *wuss, if (at.y < 0) at.y = 0; - node = malloc(sizeof(*node)); + node = wuss__malloc(wuss, sizeof(*node)); if (node == NULL) return result_OOM; - node->icons = calloc((size_t) menu->nitems, sizeof(*node->icons)); - specs = calloc((size_t) menu->nitems, sizeof(*specs)); + node->icons = wuss__malloc(wuss, (size_t) menu->nitems * sizeof(*node->icons)); + specs = wuss__malloc(wuss, (size_t) menu->nitems * sizeof(*specs)); if (node->icons == NULL || specs == NULL) { - free(specs); - free(node->icons); - free(node); + wuss__free(wuss, specs); + wuss__free(wuss, node->icons); + wuss__free(wuss, node); return result_OOM; } + memset(node->icons, 0, (size_t) menu->nitems * sizeof(*node->icons)); node->wuss = wuss; node->window = NULL; @@ -291,19 +297,19 @@ static result_t wuss__menu_spawn(wuss_t *wuss, &task, size, size, &node->window); if (rc != result_OK) { - free(specs); - free(node->icons); - free(node); + wuss__free(wuss, specs); + wuss__free(wuss, node->icons); + wuss__free(wuss, node); return rc; } rc = wuss_icon_create_array(node->window, specs, menu->nitems, node->icons); - free(specs); + wuss__free(wuss, specs); if (rc != result_OK) { wuss_window_close(node->window); - free(node->icons); - free(node); + wuss__free(wuss, node->icons); + wuss__free(wuss, node); return rc; } diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index 9335870..46018d2 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -177,7 +177,7 @@ result_t wuss_test(const char *resources) bad_config.palette.scroll.arrows = 0; bad_config.palette.scroll.wells = 0; bad_config.palette.scroll.sausages = 0; - rc = wuss_create(&scr, NULL, NULL, 0, &bad_config, &bad_wuss); + rc = wuss_create(&scr, NULL, NULL, 0, &bad_config, NULL, &bad_wuss); if (rc != result_WUSS_BAD_COLOUR) goto Failure; @@ -186,7 +186,7 @@ result_t wuss_test(const char *resources) { wuss_t *custom_wuss; - rc = wuss_create(&scr, NULL, custom_palette, 2, NULL, &custom_wuss); + rc = wuss_create(&scr, NULL, custom_palette, 2, NULL, NULL, &custom_wuss); if (rc != result_OK) goto Failure; wuss_destroy(custom_wuss); @@ -194,7 +194,7 @@ result_t wuss_test(const char *resources) printf("test: wuss_create with default palette\n"); - rc = wuss_create(&scr, NULL, NULL, 0, NULL, &wuss); + rc = wuss_create(&scr, NULL, NULL, 0, NULL, NULL, &wuss); if (rc != result_OK) goto Failure; @@ -2702,7 +2702,7 @@ result_t wuss_test(const char *resources) printf("test: wuss_create (core)\n"); - rc = wuss_create(&scr, NULL, NULL, 0, NULL, &wuss); + rc = wuss_create(&scr, NULL, NULL, 0, NULL, NULL, &wuss); if (rc != result_OK) goto Failure; diff --git a/libraries/wuss/window/close.c b/libraries/wuss/window/close.c index 759572f..e24e137 100644 --- a/libraries/wuss/window/close.c +++ b/libraries/wuss/window/close.c @@ -37,5 +37,5 @@ void wuss_window_close(wuss_window_t *doomed) wuss__icons_free(doomed); #endif - free(doomed); + wuss__free(wuss, doomed); } diff --git a/libraries/wuss/window/create.c b/libraries/wuss/window/create.c index a738017..5935d85 100644 --- a/libraries/wuss/window/create.c +++ b/libraries/wuss/window/create.c @@ -34,7 +34,7 @@ result_t wuss_window_create(wuss_t *wuss, if (!wuss__size_ok(width, height)) return result_WUSS_TOO_SMALL; - win = malloc(sizeof(*win)); + win = wuss__malloc(wuss, sizeof(*win)); if (win == NULL) return result_OOM; @@ -96,7 +96,7 @@ result_t wuss_window_create(wuss_t *wuss, if (wuss__validate_backdrop(wuss, &bg) != result_OK) { - free(win); + wuss__free(wuss, win); return result_WUSS_BAD_COLOUR; } win->bg = bg; From 2725ceed5ac6c4efbfbe7bd432218d6314417e38 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 09:37:33 +0100 Subject: [PATCH 29/79] refactor(wuss): split wuss__icon_draw into per-type sub-functions Each switch case in wuss__icon_draw now delegates to a static wuss__icon_draw_* helper taking a shared icon_draw_ctx_t. The explicit-bg / window-backdrop / fallback blend-ground ladder that was repeated across the label, frame, radio/option and menu-entry cases is factored into icon_blend_ground. Behaviour is unchanged; the pattern case still receives the content box and scroll so its tile origin does not move. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/icon/draw.c | 781 ++++++++++++++++++++----------------- 1 file changed, 416 insertions(+), 365 deletions(-) diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index 33ab5cc..f60220c 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -13,6 +13,24 @@ /* ----------------------------------------------------------------------- */ +/* Shared per-draw state, resolved once by wuss__icon_draw and handed to each + * type's draw helper. "b" is the icon's screen box; "fg" is its resolved + * foreground; "font_height" is 0 and "have_font" is 0 when there is no font or + * no text to draw. */ +typedef struct icon_draw_ctx +{ + wuss_t *wuss; + screen_t *scr; + const wuss_icon_t *icon; + box_t b; + colour_t fg; + int font_height; + int have_font; +} +icon_draw_ctx_t; + +/* ----------------------------------------------------------------------- */ + static void icon_bevel(screen_t *scr, const box_t *b, colour_t fill, @@ -28,6 +46,385 @@ static void icon_bevel(screen_t *scr, screen_draw_line(scr, b->x1 - 1, b->y0, b->x1 - 1, b->y1 - 1, dark); } +/* Resolve the ground an icon's text/glyph blends against: an explicit icon bg, + * else the window's dominant backdrop colour (a pattern fill blends against its + * clear-bit colour, not its foreground), else "fallback" so bmfont still has a + * blend colour. */ +static colour_t icon_blend_ground(const icon_draw_ctx_t *c, colour_t fallback) +{ + const wuss_icon_t *icon = c->icon; + + if (icon->bg != wuss_NO_BACKGROUND) + return c->wuss->palette[icon->bg]; + + if (icon->window->bg.colour != wuss_NO_BACKGROUND) + return (icon->window->bg.pattern != screen_PATTERN_SOLID) + ? c->wuss->palette[icon->window->bg.pattern_bg] + : c->wuss->palette[icon->window->bg.colour]; + + return fallback; +} + +/* ----------------------------------------------------------------------- */ + +static void wuss__icon_draw_pattern(const icon_draw_ctx_t *c, + const box_t *content, + point_t scroll) +{ + const wuss_icon_t *icon = c->icon; + colour_t pat_fg; + + /* disabled: fold the pattern into its own ground so it reads as greyed, + * mirroring the button's fg-swap */ + pat_fg = (icon->flags & wuss_ICON_FLAGS_DISABLED) + ? c->wuss->palette[icon->bg] + : c->fg; + + screen_fill_pattern(c->scr, &c->b, icon->pattern, + content->x0 - scroll.x, content->y0 - scroll.y, + pat_fg, c->wuss->palette[icon->bg]); +} + +/* ----------------------------------------------------------------------- */ + +static void wuss__icon_draw_label(const icon_draw_ctx_t *c) +{ + const wuss_icon_t *icon = c->icon; + const box_t *b = &c->b; + colour_t bg; + point_t pos; + int interior_w, split_point; + bmfont_width_t width; + + if (icon->bg != wuss_NO_BACKGROUND) + { + bg = c->wuss->palette[icon->bg]; + screen_draw_rect(c->scr, b->x0, b->y0, + SIZE2D(b->x1 - b->x0, b->y1 - b->y0), bg); + } + else + { + bg = icon_blend_ground(c, c->fg); + } + + if (!c->have_font) + return; + + interior_w = (b->x1 - b->x0) - 2; + if (interior_w < 1) + interior_w = 1; + + bmfont_measure(c->wuss->font, icon->text, (int) strlen(icon->text), + interior_w, &split_point, &width); + + if (icon->flags & wuss_ICON_FLAGS_JUSTIFY_CENTRE) + pos.x = b->x0 + ((b->x1 - b->x0) - width) / 2; + else if (icon->flags & wuss_ICON_FLAGS_JUSTIFY_RIGHT) + pos.x = b->x1 - 1 - width; + else + pos.x = b->x0 + 1; + pos.y = b->y0 + (b->y1 - b->y0 - c->font_height) / 2; + + bmfont_draw(c->wuss->font, c->scr, icon->text, (int) strlen(icon->text), + c->fg, bg, &pos, NULL); +} + +/* ----------------------------------------------------------------------- */ + +static void wuss__icon_draw_frame(const icon_draw_ctx_t *c) +{ + const wuss_icon_t *icon = c->icon; + const box_t *b = &c->b; + colour_t bg; + int cap_w, cap_x, gap_x0, gap_x1, mid_y; + + bg = icon_blend_ground(c, c->fg); + + cap_w = 0; + if (c->have_font) + { + int split_point; + bmfont_width_t width; + + bmfont_measure(c->wuss->font, icon->text, (int) strlen(icon->text), + (b->x1 - b->x0) - WUSS_FRAME_CAPTION_INSET * 2, + &split_point, &width); + cap_w = width; + } + + mid_y = b->y0 + c->font_height / 2; + + /* left, right and bottom edges are unbroken */ + screen_draw_line(c->scr, b->x0, mid_y, b->x0, b->y1 - 1, c->fg); + screen_draw_line(c->scr, b->x1 - 1, mid_y, b->x1 - 1, b->y1 - 1, c->fg); + screen_draw_line(c->scr, b->x0, b->y1 - 1, b->x1 - 1, b->y1 - 1, c->fg); + + /* the top edge is broken around the caption */ + cap_x = b->x0 + WUSS_FRAME_CAPTION_INSET; + gap_x0 = cap_x - WUSS_FRAME_CAPTION_PAD; + gap_x1 = cap_x + cap_w + WUSS_FRAME_CAPTION_PAD; + if (gap_x0 > b->x0) + screen_draw_line(c->scr, b->x0, mid_y, gap_x0, mid_y, c->fg); + if (gap_x1 < b->x1 - 1) + screen_draw_line(c->scr, gap_x1, mid_y, b->x1 - 1, mid_y, c->fg); + + if (c->have_font && cap_w > 0) + { + point_t pos; + + pos.x = cap_x; + pos.y = b->y0; + bmfont_draw(c->wuss->font, c->scr, icon->text, (int) strlen(icon->text), + c->fg, bg, &pos, NULL); + } +} + +/* ----------------------------------------------------------------------- */ + +static void wuss__icon_draw_button(const icon_draw_ctx_t *c) +{ + const wuss_icon_t *icon = c->icon; + const box_t *b = &c->b; + colour_t light, dark, base, label; + int pressed, is_default; + + pressed = icon->pressed; + is_default = (icon->flags & wuss_ICON_FLAGS_DEFAULT) != 0; + + if (is_default) + { + /* default action button: a flat accent-filled rectangle inside a + * one-pixel accent-text border, distinct from the bevelled ordinary + * buttons around it */ + base = c->wuss->palette[c->wuss->accent_bg]; + light = c->wuss->palette[c->wuss->accent_fg]; + dark = light; + label = c->wuss->palette[c->wuss->accent_fg]; + } + else + { + base = c->wuss->palette[icon->bg]; + light = c->wuss->palette[c->wuss->bevel_light]; + dark = c->wuss->palette[c->wuss->bevel_dark]; + label = c->fg; + } + + if (icon->flags & wuss_ICON_FLAGS_DISABLED) + label = c->wuss->palette[c->wuss->bevel_dark]; /* greyed: sink toward dark */ + + if (pressed) + icon_bevel(c->scr, b, base, dark, light); + else + icon_bevel(c->scr, b, base, light, dark); + + if (c->have_font) + { + point_t pos; + int interior_w, split_point; + bmfont_width_t width; + + interior_w = (b->x1 - b->x0) - 2; + if (interior_w < 1) + interior_w = 1; + + bmfont_measure(c->wuss->font, icon->text, (int) strlen(icon->text), + interior_w, &split_point, &width); + + pos.x = b->x0 + ((b->x1 - b->x0) - width) / 2; + pos.y = b->y0 + (b->y1 - b->y0 - c->font_height) / 2; + if (pressed) + { + pos.x += 1; + pos.y += 1; + } + + bmfont_draw(c->wuss->font, c->scr, icon->text, (int) strlen(icon->text), + label, base, &pos, NULL); + } +} + +/* ----------------------------------------------------------------------- */ + +/* wuss_ICON_TYPE_RADIO and wuss_ICON_TYPE_OPTION: a font-height square glyph at + * the left, vertically centred, with the label to its right. RADIO draws a + * square ring with a solid centre when selected; OPTION draws a box with a tick + * when selected. */ +static void wuss__icon_draw_radio_option(const icon_draw_ctx_t *c) +{ + const wuss_icon_t *icon = c->icon; + const box_t *b = &c->b; + colour_t glyph, bg; + box_t g; + int gsz, gy, tx; + + gsz = (c->font_height >= 8) ? c->font_height : 8; + if (gsz > b->y1 - b->y0) + gsz = b->y1 - b->y0; + gy = b->y0 + (b->y1 - b->y0 - gsz) / 2; + + g.x0 = b->x0; + g.y0 = gy; + g.x1 = b->x0 + gsz; + g.y1 = gy + gsz; + + glyph = (icon->flags & wuss_ICON_FLAGS_DISABLED) + ? c->wuss->palette[c->wuss->bevel_dark] + : c->fg; + + bg = icon_blend_ground(c, glyph); + + if (icon->bg != wuss_NO_BACKGROUND) + screen_draw_rect(c->scr, b->x0, b->y0, + SIZE2D(b->x1 - b->x0, b->y1 - b->y0), bg); + + if (icon->type == wuss_ICON_TYPE_RADIO) + { + /* a square ring (no circle primitive); a solid centre when selected */ + screen_draw_line(c->scr, g.x0 + 2, g.y0, g.x1 - 3, g.y0, glyph); + screen_draw_line(c->scr, g.x0 + 2, g.y1 - 1, g.x1 - 3, g.y1 - 1, glyph); + screen_draw_line(c->scr, g.x0, g.y0 + 2, g.x0, g.y1 - 3, glyph); + screen_draw_line(c->scr, g.x1 - 1, g.y0 + 2, g.x1 - 1, g.y1 - 3, glyph); + if (icon->selected) + screen_draw_rect(c->scr, g.x0 + 3, g.y0 + 3, + SIZE2D(gsz - 6, gsz - 6), glyph); + } + else + { + /* a box; a tick (two strokes) when selected */ + screen_draw_line(c->scr, g.x0, g.y0, g.x1 - 1, g.y0, glyph); + screen_draw_line(c->scr, g.x0, g.y1 - 1, g.x1 - 1, g.y1 - 1, glyph); + screen_draw_line(c->scr, g.x0, g.y0, g.x0, g.y1 - 1, glyph); + screen_draw_line(c->scr, g.x1 - 1, g.y0, g.x1 - 1, g.y1 - 1, glyph); + if (icon->selected) + { + screen_draw_line(c->scr, g.x0 + 2, g.y0 + gsz / 2, + g.x0 + gsz / 2 - 1, g.y1 - 3, glyph); + screen_draw_line(c->scr, g.x0 + gsz / 2 - 1, g.y1 - 3, + g.x1 - 3, g.y0 + 2, glyph); + } + } + + if (c->have_font) + { + point_t pos; + int interior_w, split_point; + bmfont_width_t width; + + tx = g.x1 + 4; + interior_w = (b->x1 - tx) - 1; + if (interior_w < 1) + interior_w = 1; + + bmfont_measure(c->wuss->font, icon->text, (int) strlen(icon->text), + interior_w, &split_point, &width); + NOT_USED(width); + + pos.x = tx; + pos.y = b->y0 + (b->y1 - b->y0 - c->font_height) / 2; + bmfont_draw(c->wuss->font, c->scr, icon->text, (int) strlen(icon->text), + glyph, bg, &pos, NULL); + } +} + +/* ----------------------------------------------------------------------- */ + +static void wuss__icon_draw_bitmap(const icon_draw_ctx_t *c) +{ + const box_t *b = &c->b; + screen_t clipped; + + if (c->icon->bitmap == NULL) + return; + + /* screen_draw_bitmap clips to scr->clip and does not scale, so narrow the + * clip to the icon box (intersected with whatever redraw already set) and + * blit at the box's top-left */ + clipped = *c->scr; + if (clipped.clip.x0 < b->x0) clipped.clip.x0 = b->x0; + if (clipped.clip.y0 < b->y0) clipped.clip.y0 = b->y0; + if (clipped.clip.x1 > b->x1) clipped.clip.x1 = b->x1; + if (clipped.clip.y1 > b->y1) clipped.clip.y1 = b->y1; + if (clipped.clip.x1 <= clipped.clip.x0 || + clipped.clip.y1 <= clipped.clip.y0) + return; + + screen_draw_bitmap(&clipped, b->x0, b->y0, c->icon->bitmap); +} + +/* ----------------------------------------------------------------------- */ + +static void wuss__icon_draw_menu_entry(const icon_draw_ctx_t *c) +{ + const wuss_icon_t *icon = c->icon; + const box_t *b = &c->b; + colour_t ink, ground, tmp; + int disabled, highlit, pad, mid_y; + + disabled = (icon->flags & wuss_ICON_FLAGS_DISABLED) != 0; + highlit = icon->hovered && !disabled; + pad = 4; + + /* resolve the row's own ink over its own/inherited ground */ + ground = icon_blend_ground(c, c->fg); + ink = disabled ? c->wuss->palette[c->wuss->bevel_dark] : c->fg; + + /* highlight simply swaps the row's fg/bg */ + if (highlit) + { + tmp = ink; + ink = ground; + ground = tmp; + } + + if (highlit || icon->bg != wuss_NO_BACKGROUND) + screen_draw_rect(c->scr, b->x0, b->y0, + SIZE2D(b->x1 - b->x0, b->y1 - b->y0), ground); + + /* a dashed rule along the entry's top edge, then the text as normal; drawn in + * the row's ink so it stays visible when the row is inverted */ + if (icon->flags & wuss_ICON_FLAGS_SEPARATOR) + { + mid_y = b->y0 + 1; + screen_draw_dashed_line(c->scr, b->x0 + pad, mid_y, + b->x1 - 1 - pad, mid_y, 2, 2, ink); + } + + /* left-edge tick when selected */ + if (icon->selected) + { + int cx, cy, h; + + h = (c->font_height >= 8) ? c->font_height : 8; + cx = b->x0 + pad; + cy = b->y0 + (b->y1 - b->y0 - h) / 2; + screen_draw_line(c->scr, cx, cy + h / 2, cx + h / 2 - 1, cy + h - 2, ink); + screen_draw_line(c->scr, cx + h / 2 - 1, cy + h - 2, cx + h - 2, cy, ink); + } + + /* right-edge arrow for a submenu entry */ + if (icon->flags & wuss_ICON_FLAGS_SUBMENU) + { + int ax, ay, r, dy; + + r = (c->font_height >= 8) ? c->font_height / 3 : 3; + ax = b->x1 - 1 - pad - r; + ay = b->y0 + (b->y1 - b->y0) / 2; + for (dy = -r; dy <= r; dy++) + screen_draw_line(c->scr, ax, ay + dy, + ax + (r - (dy < 0 ? -dy : dy)), ay + dy, ink); + } + + if (c->have_font && icon->text != NULL && icon->text[0] != '\0') + { + point_t pos; + + pos.x = b->x0 + pad + c->font_height; /* leave room for a tick */ + pos.y = b->y0 + (b->y1 - b->y0 - c->font_height) / 2; + bmfont_draw(c->wuss->font, c->scr, icon->text, (int) strlen(icon->text), + ink, ground, &pos, NULL); + } +} + /* ----------------------------------------------------------------------- */ void wuss__icon_draw(wuss_t *wuss, @@ -35,404 +432,58 @@ void wuss__icon_draw(wuss_t *wuss, const box_t *content, point_t scroll) { - screen_t *scr; - box_t b; - colour_t fg; - int font_width, font_height; - int have_font; + icon_draw_ctx_t c; + int font_width; if (icon->flags & wuss_ICON_FLAGS_HIDDEN) return; - scr = wuss->scr; - - wuss__icon_box_to_screen(content, scroll, &icon->bbox, &b); + wuss__icon_box_to_screen(content, scroll, &icon->bbox, &c.b); - if (b.x1 <= b.x0 || b.y1 <= b.y0) + if (c.b.x1 <= c.b.x0 || c.b.y1 <= c.b.y0) return; - fg = wuss->palette[icon->fg]; + c.wuss = wuss; + c.scr = wuss->scr; + c.icon = icon; + c.fg = wuss->palette[icon->fg]; - have_font = (wuss->font != NULL && icon->text[0] != '\0'); - if (have_font) - bmfont_get_info(wuss->font, &font_width, &font_height); + c.have_font = (wuss->font != NULL && icon->text[0] != '\0'); + if (c.have_font) + bmfont_get_info(wuss->font, &font_width, &c.font_height); else - font_width = font_height = 0; + font_width = c.font_height = 0; NOT_USED(font_width); switch (icon->type) { case wuss_ICON_TYPE_PATTERN: - { - colour_t pat_fg; - - /* disabled: fold the pattern into its own ground so it reads as greyed, - * mirroring the button's fg-swap */ - pat_fg = (icon->flags & wuss_ICON_FLAGS_DISABLED) - ? wuss->palette[icon->bg] - : fg; - - screen_fill_pattern(scr, &b, icon->pattern, - content->x0 - scroll.x, content->y0 - scroll.y, - pat_fg, wuss->palette[icon->bg]); - } + wuss__icon_draw_pattern(&c, content, scroll); break; case wuss_ICON_TYPE_LABEL: - { - colour_t bg; - - if (icon->bg != wuss_NO_BACKGROUND) - { - bg = wuss->palette[icon->bg]; - screen_draw_rect(scr, b.x0, b.y0, - SIZE2D(b.x1 - b.x0, b.y1 - b.y0), bg); - } - else if (icon->window->bg.colour != wuss_NO_BACKGROUND) - { - /* blend against the dominant backdrop colour: for a pattern fill - * that's its background (clear-bit) colour, not the foreground */ - bg = (icon->window->bg.pattern != screen_PATTERN_SOLID) - ? wuss->palette[icon->window->bg.pattern_bg] - : wuss->palette[icon->window->bg.colour]; - } - else - { - bg = fg; /* bmfont needs a blend colour; nothing better to offer */ - } - - if (have_font) - { - point_t pos; - int interior_w, split_point; - bmfont_width_t width; - - interior_w = (b.x1 - b.x0) - 2; - if (interior_w < 1) - interior_w = 1; - - bmfont_measure(wuss->font, icon->text, (int) strlen(icon->text), - interior_w, &split_point, &width); - - if (icon->flags & wuss_ICON_FLAGS_JUSTIFY_CENTRE) - pos.x = b.x0 + ((b.x1 - b.x0) - width) / 2; - else if (icon->flags & wuss_ICON_FLAGS_JUSTIFY_RIGHT) - pos.x = b.x1 - 1 - width; - else - pos.x = b.x0 + 1; - pos.y = b.y0 + (b.y1 - b.y0 - font_height) / 2; - - bmfont_draw(wuss->font, scr, icon->text, (int) strlen(icon->text), - fg, bg, &pos, NULL); - } - } + wuss__icon_draw_label(&c); break; case wuss_ICON_TYPE_FRAME: - { - colour_t bg; - int cap_w, cap_x, gap_x0, gap_x1, mid_y; - - /* the caption sits over whatever ground the label case would blend - * against: an explicit bg, else the window's dominant backdrop, else - * fall back to fg so bmfont still has a blend colour */ - if (icon->bg != wuss_NO_BACKGROUND) - bg = wuss->palette[icon->bg]; - else if (icon->window->bg.colour != wuss_NO_BACKGROUND) - bg = (icon->window->bg.pattern != screen_PATTERN_SOLID) - ? wuss->palette[icon->window->bg.pattern_bg] - : wuss->palette[icon->window->bg.colour]; - else - bg = fg; - - cap_w = 0; - if (have_font) - { - int split_point; - bmfont_width_t width; - - bmfont_measure(wuss->font, icon->text, (int) strlen(icon->text), - (b.x1 - b.x0) - WUSS_FRAME_CAPTION_INSET * 2, - &split_point, &width); - cap_w = width; - } - - mid_y = b.y0 + font_height / 2; - - /* left, right and bottom edges are unbroken */ - screen_draw_line(scr, b.x0, mid_y, b.x0, b.y1 - 1, fg); - screen_draw_line(scr, b.x1 - 1, mid_y, b.x1 - 1, b.y1 - 1, fg); - screen_draw_line(scr, b.x0, b.y1 - 1, b.x1 - 1, b.y1 - 1, fg); - - /* the top edge is broken around the caption */ - cap_x = b.x0 + WUSS_FRAME_CAPTION_INSET; - gap_x0 = cap_x - WUSS_FRAME_CAPTION_PAD; - gap_x1 = cap_x + cap_w + WUSS_FRAME_CAPTION_PAD; - if (gap_x0 > b.x0) - screen_draw_line(scr, b.x0, mid_y, gap_x0, mid_y, fg); - if (gap_x1 < b.x1 - 1) - screen_draw_line(scr, gap_x1, mid_y, b.x1 - 1, mid_y, fg); - - if (have_font && cap_w > 0) - { - point_t pos; - - pos.x = cap_x; - pos.y = b.y0; - bmfont_draw(wuss->font, scr, icon->text, (int) strlen(icon->text), - fg, bg, &pos, NULL); - } - } + wuss__icon_draw_frame(&c); break; case wuss_ICON_TYPE_BUTTON: - { - colour_t light, dark, base, label; - int pressed, is_default; - - pressed = icon->pressed; - is_default = (icon->flags & wuss_ICON_FLAGS_DEFAULT) != 0; - - if (is_default) - { - /* default action button: a flat accent-filled rectangle inside a - * one-pixel accent-text border, distinct from the bevelled ordinary - * buttons around it */ - base = wuss->palette[wuss->accent_bg]; - light = wuss->palette[wuss->accent_fg]; - dark = light; - label = wuss->palette[wuss->accent_fg]; - } - else - { - base = wuss->palette[icon->bg]; - light = wuss->palette[wuss->bevel_light]; - dark = wuss->palette[wuss->bevel_dark]; - label = fg; - } - - if (icon->flags & wuss_ICON_FLAGS_DISABLED) - label = wuss->palette[wuss->bevel_dark]; /* greyed: sink toward dark */ - - if (pressed) - icon_bevel(scr, &b, base, dark, light); - else - icon_bevel(scr, &b, base, light, dark); - - if (have_font) - { - point_t pos; - int interior_w, split_point; - bmfont_width_t width; - - interior_w = (b.x1 - b.x0) - 2; - if (interior_w < 1) - interior_w = 1; - - bmfont_measure(wuss->font, icon->text, (int) strlen(icon->text), - interior_w, &split_point, &width); - - pos.x = b.x0 + ((b.x1 - b.x0) - width) / 2; - pos.y = b.y0 + (b.y1 - b.y0 - font_height) / 2; - if (pressed) - { - pos.x += 1; - pos.y += 1; - } - - bmfont_draw(wuss->font, scr, icon->text, (int) strlen(icon->text), - label, base, &pos, NULL); - } - } + wuss__icon_draw_button(&c); break; case wuss_ICON_TYPE_RADIO: case wuss_ICON_TYPE_OPTION: - { - colour_t glyph, bg; - box_t g; - int gsz, gy, tx; - - /* the glyph is a square the height of the font (min 8), sat at the left - * of the box and vertically centred; the label follows to its right */ - gsz = (font_height >= 8) ? font_height : 8; - if (gsz > b.y1 - b.y0) - gsz = b.y1 - b.y0; - gy = b.y0 + (b.y1 - b.y0 - gsz) / 2; - - g.x0 = b.x0; - g.y0 = gy; - g.x1 = b.x0 + gsz; - g.y1 = gy + gsz; - - glyph = (icon->flags & wuss_ICON_FLAGS_DISABLED) - ? wuss->palette[wuss->bevel_dark] - : fg; - - /* ground the glyph blends against: explicit bg, else the window backdrop, - * else fg (bmfont still needs a colour) -- same ladder as the label case */ - if (icon->bg != wuss_NO_BACKGROUND) - bg = wuss->palette[icon->bg]; - else if (icon->window->bg.colour != wuss_NO_BACKGROUND) - bg = (icon->window->bg.pattern != screen_PATTERN_SOLID) - ? wuss->palette[icon->window->bg.pattern_bg] - : wuss->palette[icon->window->bg.colour]; - else - bg = glyph; - - if (icon->bg != wuss_NO_BACKGROUND) - screen_draw_rect(scr, b.x0, b.y0, - SIZE2D(b.x1 - b.x0, b.y1 - b.y0), bg); - - if (icon->type == wuss_ICON_TYPE_RADIO) - { - /* a square ring (no circle primitive); a solid centre when selected */ - screen_draw_line(scr, g.x0 + 2, g.y0, g.x1 - 3, g.y0, glyph); - screen_draw_line(scr, g.x0 + 2, g.y1 - 1, g.x1 - 3, g.y1 - 1, glyph); - screen_draw_line(scr, g.x0, g.y0 + 2, g.x0, g.y1 - 3, glyph); - screen_draw_line(scr, g.x1 - 1, g.y0 + 2, g.x1 - 1, g.y1 - 3, glyph); - if (icon->selected) - screen_draw_rect(scr, g.x0 + 3, g.y0 + 3, - SIZE2D(gsz - 6, gsz - 6), glyph); - } - else - { - /* a box; a tick (two strokes) when selected */ - screen_draw_line(scr, g.x0, g.y0, g.x1 - 1, g.y0, glyph); - screen_draw_line(scr, g.x0, g.y1 - 1, g.x1 - 1, g.y1 - 1, glyph); - screen_draw_line(scr, g.x0, g.y0, g.x0, g.y1 - 1, glyph); - screen_draw_line(scr, g.x1 - 1, g.y0, g.x1 - 1, g.y1 - 1, glyph); - if (icon->selected) - { - screen_draw_line(scr, g.x0 + 2, g.y0 + gsz / 2, - g.x0 + gsz / 2 - 1, g.y1 - 3, glyph); - screen_draw_line(scr, g.x0 + gsz / 2 - 1, g.y1 - 3, - g.x1 - 3, g.y0 + 2, glyph); - } - } - - if (have_font) - { - point_t pos; - int interior_w, split_point; - bmfont_width_t width; - - tx = g.x1 + 4; - interior_w = (b.x1 - tx) - 1; - if (interior_w < 1) - interior_w = 1; - - bmfont_measure(wuss->font, icon->text, (int) strlen(icon->text), - interior_w, &split_point, &width); - NOT_USED(width); - - pos.x = tx; - pos.y = b.y0 + (b.y1 - b.y0 - font_height) / 2; - bmfont_draw(wuss->font, scr, icon->text, (int) strlen(icon->text), - glyph, bg, &pos, NULL); - } - } + wuss__icon_draw_radio_option(&c); break; case wuss_ICON_TYPE_BITMAP: - { - screen_t clipped; - - if (icon->bitmap == NULL) - break; - - /* screen_draw_bitmap clips to scr->clip and does not scale, so narrow - * the clip to the icon box (intersected with whatever redraw already - * set) and blit at the box's top-left */ - clipped = *scr; - if (clipped.clip.x0 < b.x0) clipped.clip.x0 = b.x0; - if (clipped.clip.y0 < b.y0) clipped.clip.y0 = b.y0; - if (clipped.clip.x1 > b.x1) clipped.clip.x1 = b.x1; - if (clipped.clip.y1 > b.y1) clipped.clip.y1 = b.y1; - if (clipped.clip.x1 <= clipped.clip.x0 || - clipped.clip.y1 <= clipped.clip.y0) - break; - - screen_draw_bitmap(&clipped, b.x0, b.y0, icon->bitmap); - } + wuss__icon_draw_bitmap(&c); break; case wuss_ICON_TYPE_MENU_ENTRY: - { - colour_t ink, ground, tmp; - int disabled, highlit, pad, mid_y; - - disabled = (icon->flags & wuss_ICON_FLAGS_DISABLED) != 0; - highlit = icon->hovered && !disabled; - pad = 4; - - /* resolve the row's own ink over its own/inherited ground */ - if (icon->bg != wuss_NO_BACKGROUND) - ground = wuss->palette[icon->bg]; - else if (icon->window->bg.colour != wuss_NO_BACKGROUND) - ground = (icon->window->bg.pattern != screen_PATTERN_SOLID) - ? wuss->palette[icon->window->bg.pattern_bg] - : wuss->palette[icon->window->bg.colour]; - else - ground = fg; /* bmfont still needs a blend colour */ - ink = disabled ? wuss->palette[wuss->bevel_dark] : fg; - - /* highlight simply swaps the row's fg/bg */ - if (highlit) - { - tmp = ink; - ink = ground; - ground = tmp; - } - - if (highlit || icon->bg != wuss_NO_BACKGROUND) - screen_draw_rect(scr, b.x0, b.y0, - SIZE2D(b.x1 - b.x0, b.y1 - b.y0), ground); - - /* a dashed rule along the entry's top edge, then the text as normal; - * drawn in the row's ink so it stays visible when the row is inverted */ - if (icon->flags & wuss_ICON_FLAGS_SEPARATOR) - { - mid_y = b.y0 + 1; - screen_draw_dashed_line(scr, b.x0 + pad, mid_y, - b.x1 - 1 - pad, mid_y, 2, 2, ink); - } - - /* left-edge tick when selected */ - if (icon->selected) - { - int cx, cy, h; - - h = (font_height >= 8) ? font_height : 8; - cx = b.x0 + pad; - cy = b.y0 + (b.y1 - b.y0 - h) / 2; - screen_draw_line(scr, cx, cy + h / 2, cx + h / 2 - 1, cy + h - 2, ink); - screen_draw_line(scr, cx + h / 2 - 1, cy + h - 2, cx + h - 2, cy, ink); - } - - /* right-edge arrow for a submenu entry */ - if (icon->flags & wuss_ICON_FLAGS_SUBMENU) - { - int ax, ay, r, dy; - - r = (font_height >= 8) ? font_height / 3 : 3; - ax = b.x1 - 1 - pad - r; - ay = b.y0 + (b.y1 - b.y0) / 2; - for (dy = -r; dy <= r; dy++) - screen_draw_line(scr, ax, ay + dy, - ax + (r - (dy < 0 ? -dy : dy)), ay + dy, ink); - } - - if (have_font && icon->text != NULL && icon->text[0] != '\0') - { - point_t pos; - - pos.x = b.x0 + pad + font_height; /* leave room for a tick */ - pos.y = b.y0 + (b.y1 - b.y0 - font_height) / 2; - bmfont_draw(wuss->font, scr, icon->text, (int) strlen(icon->text), - ink, ground, &pos, NULL); - } - } + wuss__icon_draw_menu_entry(&c); break; } } From 0c38ad60a18ec9acb6c927bb00e69e9ddd82daef Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 09:52:33 +0100 Subject: [PATCH 30/79] chore: path-relative header comments across all sources The first-line header comment of every first-party .c/.h file now carries its path relative to libraries/, include/ or apps/ instead of a bare basename: /* draw.c -- wuss - draw a work-area icon */ -> /* wuss/icon/draw.c -- draw a work-area icon */ The description after '--' is preserved; a redundant "<module> - " (or "<module> test - ") prefix is dropped, except where that prefix plus the module's own tagline is the whole description (kept as "wuss - minimal window manager"). Files with no header, or only a stale/bare filename, gain a path-only header. Vendored trees (libraries/fortify, include/fortify) are untouched. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- apps/test/main.c | 2 +- apps/wuss/main.c | 2 +- include/base/debug.h | 2 +- include/base/result.h | 2 +- include/base/types.h | 2 +- include/base/utils.h | 2 +- include/databases/digest-db.h | 2 +- include/databases/filename-db.h | 2 +- include/databases/pickle-reader-hash.h | 2 +- include/databases/pickle-writer-hash.h | 2 +- include/databases/pickle.h | 2 +- include/databases/tag-db.h | 2 +- include/datastruct/atom.h | 2 +- include/datastruct/bitarr.h | 2 +- include/datastruct/bitfifo.h | 2 +- include/datastruct/bitvec.h | 2 +- include/datastruct/cache.h | 2 +- include/datastruct/hash.h | 2 +- include/datastruct/hlist.h | 2 +- include/datastruct/list.h | 2 +- include/datastruct/ntree.h | 2 +- include/datastruct/vector.h | 2 +- include/framebuf/bitmap-set.h | 2 +- include/framebuf/bitmap.h | 2 +- include/framebuf/bmfont.h | 2 +- include/framebuf/colour.h | 2 +- include/framebuf/composite.h | 2 +- include/framebuf/curve.h | 2 +- include/framebuf/palettes.h | 2 +- include/framebuf/pixelfmt.h | 2 +- include/framebuf/screen.h | 2 +- include/framebuf/span-bgrx8888.h | 2 +- include/framebuf/span-p4.h | 2 +- include/framebuf/span-registry.h | 2 +- include/framebuf/span-rgba8888.h | 2 +- include/framebuf/span-rgbx8888.h | 2 +- include/framebuf/span-xbgr8888.h | 2 +- include/framebuf/span.h | 2 +- include/geom/box.h | 2 +- include/geom/layout.h | 2 +- include/geom/line.h | 2 +- include/geom/packer.h | 2 +- include/geom/point.h | 2 +- include/geom/size.h | 2 +- include/io/path.h | 2 +- include/io/stream-mem.h | 2 +- include/io/stream-mtfcomp.h | 2 +- include/io/stream-packbits.h | 2 +- include/io/stream-stdio.h | 2 +- include/io/stream.h | 2 +- include/test/all-tests.h | 2 +- include/test/txtscr.h | 2 +- include/text/bmtext.h | 2 +- include/text/txtfmt.h | 2 +- include/utils/array.h | 2 +- include/utils/barith.h | 2 +- include/utils/bsearch.h | 2 +- include/utils/bytesex.h | 2 +- include/utils/fxp.h | 2 +- include/utils/maths.h | 2 +- include/utils/pack.h | 2 +- include/utils/primes.h | 2 +- include/wuss/icon.h | 2 +- include/wuss/menu.h | 2 +- include/wuss/task.h | 2 +- include/wuss/window.h | 2 +- include/wuss/wuss.h | 2 +- libraries/databases/digest-db/digest-db.c | 2 +- libraries/databases/filename-db/filename-db.c | 2 +- libraries/databases/pickle/delete.c | 2 +- libraries/databases/pickle/hash-reader.c | 2 +- libraries/databases/pickle/hash-writer.c | 2 +- libraries/databases/pickle/pickle.c | 2 +- libraries/databases/pickle/test/pickle-test.c | 1 + libraries/databases/pickle/unpickle.c | 2 +- libraries/databases/tag-db/tag-db.c | 2 +- libraries/databases/tag-db/test/tag-db-test.c | 1 + libraries/datastruct/atom/create.c | 2 +- libraries/datastruct/atom/delete-block.c | 2 +- libraries/datastruct/atom/delete.c | 2 +- libraries/datastruct/atom/destroy.c | 2 +- libraries/datastruct/atom/for-block.c | 2 +- libraries/datastruct/atom/get.c | 2 +- libraries/datastruct/atom/impl.h | 2 +- libraries/datastruct/atom/new.c | 2 +- libraries/datastruct/atom/set.c | 2 +- libraries/datastruct/atom/test/atom-test.c | 1 + libraries/datastruct/bitarr/count.c | 2 +- libraries/datastruct/bitarr/test/bitarr-test.c | 1 + libraries/datastruct/bitfifo/bitfifo.c | 2 +- libraries/datastruct/bitfifo/test/bitfifo-test.c | 1 + libraries/datastruct/bitvec/and.c | 2 +- libraries/datastruct/bitvec/clear-all.c | 2 +- libraries/datastruct/bitvec/clear.c | 2 +- libraries/datastruct/bitvec/count.c | 2 +- libraries/datastruct/bitvec/create.c | 2 +- libraries/datastruct/bitvec/destroy.c | 2 +- libraries/datastruct/bitvec/ensure.c | 2 +- libraries/datastruct/bitvec/eq.c | 2 +- libraries/datastruct/bitvec/get.c | 2 +- libraries/datastruct/bitvec/impl.h | 2 +- libraries/datastruct/bitvec/length.c | 2 +- libraries/datastruct/bitvec/next.c | 2 +- libraries/datastruct/bitvec/or.c | 2 +- libraries/datastruct/bitvec/set-all.c | 2 +- libraries/datastruct/bitvec/set.c | 2 +- libraries/datastruct/bitvec/test/bitvec-test.c | 1 + libraries/datastruct/bitvec/toggle.c | 2 +- libraries/datastruct/cache/cache.c | 2 +- libraries/datastruct/cache/test/cache-test.c | 1 + libraries/datastruct/hash/count.c | 2 +- libraries/datastruct/hash/create.c | 2 +- libraries/datastruct/hash/destroy.c | 2 +- libraries/datastruct/hash/impl.h | 2 +- libraries/datastruct/hash/insert.c | 2 +- libraries/datastruct/hash/lookup-node.c | 2 +- libraries/datastruct/hash/lookup.c | 2 +- libraries/datastruct/hash/remove.c | 2 +- libraries/datastruct/hash/test/hash-test.c | 1 + libraries/datastruct/hash/walk-cont.c | 2 +- libraries/datastruct/hash/walk.c | 2 +- libraries/datastruct/hlist/append.c | 1 + libraries/datastruct/hlist/copy.c | 1 + libraries/datastruct/hlist/free.c | 1 + libraries/datastruct/hlist/impl.h | 2 +- libraries/datastruct/hlist/length.c | 1 + libraries/datastruct/hlist/list.c | 1 + libraries/datastruct/hlist/map.c | 1 + libraries/datastruct/hlist/pop.c | 1 + libraries/datastruct/hlist/push.c | 1 + libraries/datastruct/hlist/reverse.c | 1 + libraries/datastruct/hlist/to-array.c | 1 + libraries/datastruct/list/add-head.c | 1 + libraries/datastruct/list/add-tail.c | 1 + libraries/datastruct/list/find.c | 1 + libraries/datastruct/list/init.c | 1 + libraries/datastruct/list/remove.c | 1 + libraries/datastruct/list/test/list-test.c | 1 + libraries/datastruct/list/walk.c | 1 + libraries/datastruct/ntree/copy.c | 2 +- libraries/datastruct/ntree/delete.c | 2 +- libraries/datastruct/ntree/depth.c | 2 +- libraries/datastruct/ntree/first-child.c | 2 +- libraries/datastruct/ntree/free.c | 2 +- libraries/datastruct/ntree/get-data.c | 2 +- libraries/datastruct/ntree/impl.h | 2 +- libraries/datastruct/ntree/insert-after.c | 2 +- libraries/datastruct/ntree/insert-before.c | 2 +- libraries/datastruct/ntree/insert.c | 2 +- libraries/datastruct/ntree/last-child.c | 2 +- libraries/datastruct/ntree/max-height.c | 2 +- libraries/datastruct/ntree/n-nodes.c | 2 +- libraries/datastruct/ntree/new.c | 2 +- libraries/datastruct/ntree/next-sibling.c | 2 +- libraries/datastruct/ntree/nth-child.c | 2 +- libraries/datastruct/ntree/parent.c | 2 +- libraries/datastruct/ntree/prepend.c | 2 +- libraries/datastruct/ntree/prev-sibling.c | 2 +- libraries/datastruct/ntree/set-data.c | 2 +- libraries/datastruct/ntree/test/ntree-test.c | 1 + libraries/datastruct/ntree/unlink.c | 2 +- libraries/datastruct/ntree/walk.c | 2 +- libraries/datastruct/vector/clear.c | 2 +- libraries/datastruct/vector/create.c | 2 +- libraries/datastruct/vector/destroy.c | 2 +- libraries/datastruct/vector/ensure.c | 2 +- libraries/datastruct/vector/get.c | 2 +- libraries/datastruct/vector/impl.h | 2 +- libraries/datastruct/vector/insert.c | 2 +- libraries/datastruct/vector/length.c | 2 +- libraries/datastruct/vector/set-length.c | 2 +- libraries/datastruct/vector/set-width.c | 2 +- libraries/datastruct/vector/set.c | 2 +- libraries/datastruct/vector/test/vector-test.c | 1 + libraries/datastruct/vector/width.c | 2 +- libraries/framebuf/bitmap/bitmap.c | 2 +- libraries/framebuf/bitmap/load.c | 2 +- libraries/framebuf/bitmap/save.c | 2 +- libraries/framebuf/bmfont/bmfont.c | 2 +- libraries/framebuf/bmfont/test/bmfont-test.c | 2 +- libraries/framebuf/colour/colour.c | 2 +- libraries/framebuf/composite/composite.c | 2 +- libraries/framebuf/composite/test/composite-test.c | 2 +- libraries/framebuf/curve/curve.c | 2 +- libraries/framebuf/curve/test/curve-test.c | 2 +- libraries/framebuf/palettes/palettes.c | 2 +- libraries/framebuf/pixelfmt/log2bpp.c | 2 +- libraries/framebuf/screen/screen-copy-rect.c | 2 +- libraries/framebuf/screen/screen-draw-ninepatch.c | 2 +- libraries/framebuf/screen/screen-draw.c | 2 +- libraries/framebuf/screen/screen-fill-pattern.c | 2 +- libraries/framebuf/screen/screen.c | 2 +- libraries/framebuf/screen/test/screen-test.c | 2 +- libraries/framebuf/span-registry/get.c | 2 +- libraries/framebuf/span-registry/regdata.h | 2 +- libraries/framebuf/span/all8888-generic.c | 2 +- libraries/framebuf/span/all8888.c | 2 +- libraries/framebuf/span/all8888.h | 2 +- libraries/framebuf/span/bgrx8888.c | 2 +- libraries/framebuf/span/p4.c | 2 +- libraries/framebuf/span/rgbx8888.c | 2 +- libraries/framebuf/span/xbgr8888.c | 2 +- libraries/geom/box/clipped.c | 2 +- libraries/geom/box/contains-box.c | 2 +- libraries/geom/box/contains-point.c | 2 +- libraries/geom/box/could-hold.c | 2 +- libraries/geom/box/extend.c | 2 +- libraries/geom/box/grow.c | 2 +- libraries/geom/box/intersection.c | 2 +- libraries/geom/box/intersects.c | 2 +- libraries/geom/box/is-empty.c | 2 +- libraries/geom/box/reset.c | 2 +- libraries/geom/box/round.c | 2 +- libraries/geom/box/round4.c | 2 +- libraries/geom/box/size.c | 2 +- libraries/geom/box/test/box-test.c | 1 + libraries/geom/box/translated.c | 2 +- libraries/geom/box/union.c | 2 +- libraries/geom/layout/layout.c | 2 +- libraries/geom/layout/test/layout-test.c | 1 + libraries/geom/line/line.c | 2 +- libraries/geom/packer/impl.h | 2 +- libraries/geom/packer/packer.c | 2 +- libraries/geom/packer/test/packer-test.c | 1 + libraries/io/path/path.c | 2 +- libraries/io/stream/stream-mem.c | 2 +- libraries/io/stream/stream-mtfcomp.c | 2 +- libraries/io/stream/stream-packbitscomp.c | 2 +- libraries/io/stream/stream-packbitsdecomp.c | 2 +- libraries/io/stream/stream-stdio.c | 2 +- libraries/io/stream/stream.c | 2 +- libraries/io/stream/test/stream-test.c | 1 + libraries/test/txtscr/txtscr.c | 2 +- libraries/text/bmtext/draw.c | 2 +- libraries/text/bmtext/layout.c | 2 +- libraries/text/txtfmt/create.c | 2 +- libraries/text/txtfmt/destroy.c | 2 +- libraries/text/txtfmt/get-length.c | 2 +- libraries/text/txtfmt/get-line.c | 2 +- libraries/text/txtfmt/get-nlines.c | 2 +- libraries/text/txtfmt/get-wrapped-width.c | 2 +- libraries/text/txtfmt/impl.h | 2 +- libraries/text/txtfmt/print.c | 2 +- libraries/text/txtfmt/test/txtfmt-test.c | 2 +- libraries/text/txtfmt/wrap.c | 2 +- libraries/utils/array/delelem.c | 1 + libraries/utils/array/delelems.c | 1 + libraries/utils/array/grow.c | 2 +- libraries/utils/array/shrink.c | 2 +- libraries/utils/array/squeeze.c | 1 + libraries/utils/array/stretch.c | 1 + libraries/utils/array/test/array-test.c | 1 + libraries/utils/barith/barith.c | 2 +- libraries/utils/bsearch/bsearch-impl.h | 2 +- libraries/utils/bsearch/bsearch-int.c | 2 +- libraries/utils/bsearch/bsearch-short.c | 2 +- libraries/utils/bsearch/bsearch-uint.c | 2 +- libraries/utils/bsearch/bsearch-ushort.c | 2 +- libraries/utils/bsearch/test/bsearch-test.c | 1 + libraries/utils/bytesex/rev-l-block.c | 2 +- libraries/utils/bytesex/rev-l-m.c | 2 +- libraries/utils/bytesex/rev-l.c | 2 +- libraries/utils/bytesex/rev-s-block.c | 2 +- libraries/utils/bytesex/rev-s-m.c | 2 +- libraries/utils/bytesex/rev-s-pair-m.c | 2 +- libraries/utils/bytesex/rev-s-pair.c | 2 +- libraries/utils/bytesex/rev-s.c | 2 +- libraries/utils/bytesex/util.h | 2 +- libraries/utils/fxp/smull-fxp16.c | 2 +- libraries/utils/fxp/umull-fxp16.c | 2 +- libraries/utils/maths/degs-to-rads.c | 2 +- libraries/utils/maths/gcd.c | 2 +- libraries/utils/pack/pack.c | 2 +- libraries/utils/pack/test/pack-test.c | 2 +- libraries/utils/pack/unpack.c | 2 +- libraries/utils/primes/primes.c | 2 +- libraries/wuss/backdrop.c | 2 +- libraries/wuss/create.c | 2 +- libraries/wuss/destroy.c | 2 +- libraries/wuss/furniture.h | 2 +- libraries/wuss/furniture/back-box.c | 2 +- libraries/wuss/furniture/close-box.c | 2 +- libraries/wuss/furniture/content-box.c | 2 +- libraries/wuss/furniture/drag-resize.c | 2 +- libraries/wuss/furniture/draw.c | 2 +- libraries/wuss/furniture/hit-test.c | 2 +- libraries/wuss/furniture/hscroll-box.c | 2 +- libraries/wuss/furniture/invalidate.c | 2 +- libraries/wuss/furniture/resize-box.c | 2 +- libraries/wuss/furniture/scroll-action.c | 2 +- libraries/wuss/furniture/titlebar-box.c | 2 +- libraries/wuss/furniture/toggle-action.c | 2 +- libraries/wuss/furniture/toggle-box.c | 2 +- libraries/wuss/furniture/vscroll-box.c | 2 +- libraries/wuss/get-font.c | 2 +- libraries/wuss/get-pointer.c | 2 +- libraries/wuss/icon.h | 2 +- libraries/wuss/icon/create-array.c | 2 +- libraries/wuss/icon/create.c | 2 +- libraries/wuss/icon/delete.c | 2 +- libraries/wuss/icon/draw.c | 2 +- libraries/wuss/icon/free.c | 2 +- libraries/wuss/icon/get-bbox.c | 2 +- libraries/wuss/icon/get-selected.c | 2 +- libraries/wuss/icon/get-text.c | 2 +- libraries/wuss/icon/get-type.c | 2 +- libraries/wuss/icon/get-window.c | 2 +- libraries/wuss/icon/hit-test.c | 2 +- libraries/wuss/icon/invalidate.c | 2 +- libraries/wuss/icon/screen-box.c | 2 +- libraries/wuss/icon/set-hidden.c | 2 +- libraries/wuss/icon/set-hover.c | 2 +- libraries/wuss/icon/set-selected.c | 2 +- libraries/wuss/icon/set-text.c | 2 +- libraries/wuss/idle.c | 2 +- libraries/wuss/impl.h | 2 +- libraries/wuss/invalidate.c | 2 +- libraries/wuss/menu.h | 2 +- libraries/wuss/menu/create-from-desc.c | 2 +- libraries/wuss/menu/menu.c | 2 +- libraries/wuss/mouse-click.c | 2 +- libraries/wuss/mouse-move.c | 2 +- libraries/wuss/redraw.c | 2 +- libraries/wuss/scroll-step.c | 2 +- libraries/wuss/scroll.c | 2 +- libraries/wuss/task/start.c | 2 +- libraries/wuss/task/stop.c | 2 +- libraries/wuss/test/tasks/ball.c | 2 +- libraries/wuss/test/tasks/ball.h | 2 +- libraries/wuss/test/tasks/blank.c | 2 +- libraries/wuss/test/tasks/blank.h | 2 +- libraries/wuss/test/tasks/chars.c | 2 +- libraries/wuss/test/tasks/chars.h | 2 +- libraries/wuss/test/tasks/checker.c | 2 +- libraries/wuss/test/tasks/checker.h | 2 +- libraries/wuss/test/tasks/curve.c | 2 +- libraries/wuss/test/tasks/curve.h | 2 +- libraries/wuss/test/tasks/gradient.c | 2 +- libraries/wuss/test/tasks/gradient.h | 2 +- libraries/wuss/test/tasks/icons.c | 2 +- libraries/wuss/test/tasks/icons.h | 2 +- libraries/wuss/test/tasks/image.c | 2 +- libraries/wuss/test/tasks/image.h | 2 +- libraries/wuss/test/tasks/launcher.c | 2 +- libraries/wuss/test/tasks/launcher.h | 2 +- libraries/wuss/test/tasks/palette.c | 2 +- libraries/wuss/test/tasks/palette.h | 2 +- libraries/wuss/test/tasks/porter-duff.c | 2 +- libraries/wuss/test/tasks/porter-duff.h | 2 +- libraries/wuss/test/tasks/sofa.c | 2 +- libraries/wuss/test/tasks/sofa.h | 2 +- libraries/wuss/test/tasks/text.c | 2 +- libraries/wuss/test/tasks/text.h | 2 +- libraries/wuss/test/wuss-test.c | 2 +- libraries/wuss/window/at.c | 2 +- libraries/wuss/window/close.c | 2 +- libraries/wuss/window/create-placed.c | 2 +- libraries/wuss/window/create.c | 2 +- libraries/wuss/window/get-content-bounds.c | 2 +- libraries/wuss/window/get-scroll.c | 2 +- libraries/wuss/window/get-visible-bounds.c | 2 +- libraries/wuss/window/invalidate.c | 2 +- libraries/wuss/window/move.c | 2 +- libraries/wuss/window/resize.c | 2 +- libraries/wuss/window/restack.c | 2 +- libraries/wuss/window/set-background.c | 2 +- libraries/wuss/window/set-scroll.c | 2 +- 367 files changed, 367 insertions(+), 330 deletions(-) diff --git a/apps/test/main.c b/apps/test/main.c index 435b3b8..aaf9f21 100644 --- a/apps/test/main.c +++ b/apps/test/main.c @@ -1,4 +1,4 @@ -// +/* test/main.c */ // main.c // DPTLibTest // diff --git a/apps/wuss/main.c b/apps/wuss/main.c index 98fd074..0c7f404 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -1,4 +1,4 @@ -/* main.c -- Wuss - interactive minimal window manager demo */ +/* wuss/main.c -- Wuss - interactive minimal window manager demo */ #include <stdio.h> #include <stdlib.h> diff --git a/include/base/debug.h b/include/base/debug.h index fd7249f..ca74f43 100644 --- a/include/base/debug.h +++ b/include/base/debug.h @@ -1,4 +1,4 @@ -/* debug.h -- debugging and logging macros */ +/* base/debug.h -- debugging and logging macros */ #ifndef BASE_DEBUG_H #define BASE_DEBUG_H diff --git a/include/base/result.h b/include/base/result.h index 5ae5f83..d8021f3 100644 --- a/include/base/result.h +++ b/include/base/result.h @@ -1,4 +1,4 @@ -/* result.h -- generic function return values */ +/* base/result.h -- generic function return values */ #ifndef BASE_RESULT_H #define BASE_RESULT_H diff --git a/include/base/types.h b/include/base/types.h index 77570e3..f53e2f9 100644 --- a/include/base/types.h +++ b/include/base/types.h @@ -1,4 +1,4 @@ -/* types.h -- fixed-width integer types */ +/* base/types.h -- fixed-width integer types */ #ifndef BASE_TYPES_H #define BASE_TYPES_H diff --git a/include/base/utils.h b/include/base/utils.h index 3d3cbd7..8e6b389 100644 --- a/include/base/utils.h +++ b/include/base/utils.h @@ -1,4 +1,4 @@ -/* utils.h -- various utilities */ +/* base/utils.h -- various utilities */ #ifndef BASE_UTILS_H #define BASE_UTILS_H diff --git a/include/databases/digest-db.h b/include/databases/digest-db.h index e91eaca..287ffc5 100644 --- a/include/databases/digest-db.h +++ b/include/databases/digest-db.h @@ -1,4 +1,4 @@ -/* digest-db.h -- digest database */ +/* databases/digest-db.h -- digest database */ /** * \file digest-db.h diff --git a/include/databases/filename-db.h b/include/databases/filename-db.h index 1a68715..d0a2bf5 100644 --- a/include/databases/filename-db.h +++ b/include/databases/filename-db.h @@ -1,4 +1,4 @@ -/* filename-db.h -- filename database */ +/* databases/filename-db.h -- filename database */ /** * \file filename-db.h diff --git a/include/databases/pickle-reader-hash.h b/include/databases/pickle-reader-hash.h index 86c6d77..e43b3ee 100644 --- a/include/databases/pickle-reader-hash.h +++ b/include/databases/pickle-reader-hash.h @@ -1,4 +1,4 @@ -/* pickle-reader-hash.h -- glue methods to let pickle read from hashes */ +/* databases/pickle-reader-hash.h -- glue methods to let pickle read from hashes */ #ifndef DATABASES_PICKLE_READER_HASH_H #define DATABASES_PICKLE_READER_HASH_H diff --git a/include/databases/pickle-writer-hash.h b/include/databases/pickle-writer-hash.h index 560e5f5..e0888a1 100644 --- a/include/databases/pickle-writer-hash.h +++ b/include/databases/pickle-writer-hash.h @@ -1,4 +1,4 @@ -/* pickle-writer-hash.h -- glue methods to let pickle write to hashes */ +/* databases/pickle-writer-hash.h -- glue methods to let pickle write to hashes */ #ifndef DATABASES_PICKLE_WRITER_HASH_H #define DATABASES_PICKLE_WRITER_HASH_H diff --git a/include/databases/pickle.h b/include/databases/pickle.h index eaa48b1..88c1d7a 100644 --- a/include/databases/pickle.h +++ b/include/databases/pickle.h @@ -1,4 +1,4 @@ -/* pickle.h -- (de-)serialising associative arrays */ +/* databases/pickle.h -- (de-)serialising associative arrays */ /** * \file pickle.h diff --git a/include/databases/tag-db.h b/include/databases/tag-db.h index 3160812..dcbc01d 100644 --- a/include/databases/tag-db.h +++ b/include/databases/tag-db.h @@ -1,4 +1,4 @@ -/* tag-db.h -- tag database */ +/* databases/tag-db.h -- tag database */ /** * \file tag-db.h diff --git a/include/datastruct/atom.h b/include/datastruct/atom.h index 3d5b9e9..285c0ee 100644 --- a/include/datastruct/atom.h +++ b/include/datastruct/atom.h @@ -1,4 +1,4 @@ -/* atom.h -- indexed data block storage */ +/* datastruct/atom.h -- indexed data block storage */ /** * \file atom.h diff --git a/include/datastruct/bitarr.h b/include/datastruct/bitarr.h index 56b082f..e7398bf 100644 --- a/include/datastruct/bitarr.h +++ b/include/datastruct/bitarr.h @@ -1,4 +1,4 @@ -/* bitarr.h -- arrays of bits */ +/* datastruct/bitarr.h -- arrays of bits */ /** * \file bitarr.h diff --git a/include/datastruct/bitfifo.h b/include/datastruct/bitfifo.h index f7ec8e5..c73182b 100644 --- a/include/datastruct/bitfifo.h +++ b/include/datastruct/bitfifo.h @@ -1,4 +1,4 @@ -/* bitfifo.h -- fifo which stores bits */ +/* datastruct/bitfifo.h -- fifo which stores bits */ /** * \file bitfifo.h diff --git a/include/datastruct/bitvec.h b/include/datastruct/bitvec.h index 7fdbdf1..8d5d641 100644 --- a/include/datastruct/bitvec.h +++ b/include/datastruct/bitvec.h @@ -1,4 +1,4 @@ -/* bitvec.h -- flexible arrays of bits */ +/* datastruct/bitvec.h -- flexible arrays of bits */ /** * \file bitvec.h diff --git a/include/datastruct/cache.h b/include/datastruct/cache.h index 164a18e..ce0aeb4 100644 --- a/include/datastruct/cache.h +++ b/include/datastruct/cache.h @@ -1,4 +1,4 @@ -/* cache.h -- generic single-block cache */ +/* datastruct/cache.h -- generic single-block cache */ /** * \file cache.h diff --git a/include/datastruct/hash.h b/include/datastruct/hash.h index 7506757..bf68ed2 100644 --- a/include/datastruct/hash.h +++ b/include/datastruct/hash.h @@ -1,4 +1,4 @@ -/* hash.h -- associative arrays */ +/* datastruct/hash.h -- associative arrays */ /** * \file hash.h diff --git a/include/datastruct/hlist.h b/include/datastruct/hlist.h index a8ffb6b..5b73388 100644 --- a/include/datastruct/hlist.h +++ b/include/datastruct/hlist.h @@ -1,4 +1,4 @@ -/* hlist.h -- "Hanson" linked list library */ +/* datastruct/hlist.h -- "Hanson" linked list library */ /* See chapter 5 of C Interfaces and Implementations. */ diff --git a/include/datastruct/list.h b/include/datastruct/list.h index ff8cdf8..ebdb986 100644 --- a/include/datastruct/list.h +++ b/include/datastruct/list.h @@ -1,4 +1,4 @@ -/* list.h -- linked lists */ +/* datastruct/list.h -- linked lists */ /** * \file list.h diff --git a/include/datastruct/ntree.h b/include/datastruct/ntree.h index 9e7d54a..a2c160c 100644 --- a/include/datastruct/ntree.h +++ b/include/datastruct/ntree.h @@ -1,4 +1,4 @@ -/* ntree.h -- n-ary trees */ +/* datastruct/ntree.h -- n-ary trees */ /** * \file ntree.h diff --git a/include/datastruct/vector.h b/include/datastruct/vector.h index 2ed1752..5750e37 100644 --- a/include/datastruct/vector.h +++ b/include/datastruct/vector.h @@ -1,4 +1,4 @@ -/* vector.h -- flexible arrays */ +/* datastruct/vector.h -- flexible arrays */ /** * \file vector.h diff --git a/include/framebuf/bitmap-set.h b/include/framebuf/bitmap-set.h index 809ea43..3363117 100644 --- a/include/framebuf/bitmap-set.h +++ b/include/framebuf/bitmap-set.h @@ -1,4 +1,4 @@ -/* bitmap-set.h -- a set of bitmap images */ +/* framebuf/bitmap-set.h -- a set of bitmap images */ #ifndef FRAMEBUF_BITMAP_SET_H #define FRAMEBUF_BITMAP_SET_H diff --git a/include/framebuf/bitmap.h b/include/framebuf/bitmap.h index 59305c4..2fecfd6 100644 --- a/include/framebuf/bitmap.h +++ b/include/framebuf/bitmap.h @@ -1,4 +1,4 @@ -/* bitmap.h -- bitmap image type */ +/* framebuf/bitmap.h -- bitmap image type */ #ifndef FRAMEBUF_BITMAP_H #define FRAMEBUF_BITMAP_H diff --git a/include/framebuf/bmfont.h b/include/framebuf/bmfont.h index e46cd9f..b8f7d9b 100644 --- a/include/framebuf/bmfont.h +++ b/include/framebuf/bmfont.h @@ -1,4 +1,4 @@ -/* bmfont.h -- proportional bitmap font engine */ +/* framebuf/bmfont.h -- proportional bitmap font engine */ #ifndef DPTLIB_BMFONT_H #define DPTLIB_BMFONT_H diff --git a/include/framebuf/colour.h b/include/framebuf/colour.h index 53ba312..452f1be 100644 --- a/include/framebuf/colour.h +++ b/include/framebuf/colour.h @@ -1,4 +1,4 @@ -/* colour.h -- colour type */ +/* framebuf/colour.h -- colour type */ #ifndef FRAMEBUF_COLOUR_H #define FRAMEBUF_COLOUR_H diff --git a/include/framebuf/composite.h b/include/framebuf/composite.h index cd577fc..219878e 100644 --- a/include/framebuf/composite.h +++ b/include/framebuf/composite.h @@ -1,4 +1,4 @@ -/* composite.h -- Porter-Duff image compositing */ +/* framebuf/composite.h -- Porter-Duff image compositing */ #ifndef COMPOSITE_H #define COMPOSITE_H diff --git a/include/framebuf/curve.h b/include/framebuf/curve.h index ecc5dc6..a8d171c 100644 --- a/include/framebuf/curve.h +++ b/include/framebuf/curve.h @@ -1,4 +1,4 @@ -/* curve.h -- bezier calculations */ +/* framebuf/curve.h -- bezier calculations */ #ifndef DPTLIB_CURVE_H #define DPTLIB_CURVE_H diff --git a/include/framebuf/palettes.h b/include/framebuf/palettes.h index 2829810..41500e0 100644 --- a/include/framebuf/palettes.h +++ b/include/framebuf/palettes.h @@ -1,4 +1,4 @@ -/* palettes.h -- standard palettes */ +/* framebuf/palettes.h -- standard palettes */ #ifndef PALETTES_H #define PALETTES_H diff --git a/include/framebuf/pixelfmt.h b/include/framebuf/pixelfmt.h index b20b288..c8b0f63 100644 --- a/include/framebuf/pixelfmt.h +++ b/include/framebuf/pixelfmt.h @@ -1,4 +1,4 @@ -/* pixelfmt.h -- pixel formats */ +/* framebuf/pixelfmt.h -- pixel formats */ #ifndef FRAMEBUF_PIXELFMT_H #define FRAMEBUF_PIXELFMT_H diff --git a/include/framebuf/screen.h b/include/framebuf/screen.h index 12bdd4a..25fa4d1 100644 --- a/include/framebuf/screen.h +++ b/include/framebuf/screen.h @@ -1,4 +1,4 @@ -/* screen.h -- screen type */ +/* framebuf/screen.h -- screen type */ #ifndef FRAMEBUF_SCREEN_H #define FRAMEBUF_SCREEN_H diff --git a/include/framebuf/span-bgrx8888.h b/include/framebuf/span-bgrx8888.h index e4bf23c..f956fda 100644 --- a/include/framebuf/span-bgrx8888.h +++ b/include/framebuf/span-bgrx8888.h @@ -1,4 +1,4 @@ -/* span-bgrx8888.h -- BGRX8888 format plot methods */ +/* framebuf/span-bgrx8888.h -- BGRX8888 format plot methods */ #ifndef SPAN_BGRX8888_H #define SPAN_BGRX8888_H diff --git a/include/framebuf/span-p4.h b/include/framebuf/span-p4.h index f49edf5..776f7f6 100644 --- a/include/framebuf/span-p4.h +++ b/include/framebuf/span-p4.h @@ -1,4 +1,4 @@ -/* span-p4.h -- P4 (4bpp paletted) format plot methods */ +/* framebuf/span-p4.h -- P4 (4bpp paletted) format plot methods */ #ifndef SPAN_P4_H #define SPAN_P4_H diff --git a/include/framebuf/span-registry.h b/include/framebuf/span-registry.h index b1eb34f..77685f1 100644 --- a/include/framebuf/span-registry.h +++ b/include/framebuf/span-registry.h @@ -1,4 +1,4 @@ -/* span-registry.h -- registry of plotting methods */ +/* framebuf/span-registry.h -- registry of plotting methods */ #ifndef SPAN_REGISTRY_H #define SPAN_REGISTRY_H diff --git a/include/framebuf/span-rgba8888.h b/include/framebuf/span-rgba8888.h index 4aec7fd..bad2cc3 100644 --- a/include/framebuf/span-rgba8888.h +++ b/include/framebuf/span-rgba8888.h @@ -1,4 +1,4 @@ -/* span-rgba8888.h -- RGBA8888 format plot methods */ +/* framebuf/span-rgba8888.h -- RGBA8888 format plot methods */ #ifndef SPAN_RGBA8888_H #define SPAN_RGBA8888_H diff --git a/include/framebuf/span-rgbx8888.h b/include/framebuf/span-rgbx8888.h index e516b63..0c12e03 100644 --- a/include/framebuf/span-rgbx8888.h +++ b/include/framebuf/span-rgbx8888.h @@ -1,4 +1,4 @@ -/* span-rgbx8888.h -- RGBX8888 format plot methods */ +/* framebuf/span-rgbx8888.h -- RGBX8888 format plot methods */ #ifndef SPAN_RGBX8888_H #define SPAN_RGBX8888_H diff --git a/include/framebuf/span-xbgr8888.h b/include/framebuf/span-xbgr8888.h index 67d29fe..8640c97 100644 --- a/include/framebuf/span-xbgr8888.h +++ b/include/framebuf/span-xbgr8888.h @@ -1,4 +1,4 @@ -/* span-xbgr8888.h -- XBGR8888 format plot methods */ +/* framebuf/span-xbgr8888.h -- XBGR8888 format plot methods */ #ifndef SPAN_XBGR8888_H #define SPAN_XBGR8888_H diff --git a/include/framebuf/span.h b/include/framebuf/span.h index 724ff62..6b1a3f4 100644 --- a/include/framebuf/span.h +++ b/include/framebuf/span.h @@ -1,4 +1,4 @@ -/* span.h -- interface of plotting methods */ +/* framebuf/span.h -- interface of plotting methods */ #ifndef SPAN_H #define SPAN_H diff --git a/include/geom/box.h b/include/geom/box.h index a2fd073..1c61869 100644 --- a/include/geom/box.h +++ b/include/geom/box.h @@ -1,4 +1,4 @@ -/* box.h -- box type */ +/* geom/box.h -- box type */ #ifndef GEOM_BOX_H #define GEOM_BOX_H diff --git a/include/geom/layout.h b/include/geom/layout.h index 131c876..e56a94c 100644 --- a/include/geom/layout.h +++ b/include/geom/layout.h @@ -1,4 +1,4 @@ -/* layout.h -- laying out elements using the packer */ +/* geom/layout.h -- laying out elements using the packer */ #ifndef GEOM_LAYOUT_H #define GEOM_LAYOUT_H diff --git a/include/geom/line.h b/include/geom/line.h index c57849d..73dadab 100644 --- a/include/geom/line.h +++ b/include/geom/line.h @@ -1,4 +1,4 @@ -/* line.h -- lines */ +/* geom/line.h -- lines */ #ifndef GEOM_LINE_H #define GEOM_LINE_H diff --git a/include/geom/packer.h b/include/geom/packer.h index c936009..20ff397 100644 --- a/include/geom/packer.h +++ b/include/geom/packer.h @@ -1,4 +1,4 @@ -/* packer.h -- box packing for layout */ +/* geom/packer.h -- box packing for layout */ #ifndef GEOM_PACKER_H #define GEOM_PACKER_H diff --git a/include/geom/point.h b/include/geom/point.h index a136084..36ee4be 100644 --- a/include/geom/point.h +++ b/include/geom/point.h @@ -1,4 +1,4 @@ -/* point.h -- point type */ +/* geom/point.h -- point type */ #ifndef GEOM_POINT_H #define GEOM_POINT_H diff --git a/include/geom/size.h b/include/geom/size.h index 5d66d4c..0210cc4 100644 --- a/include/geom/size.h +++ b/include/geom/size.h @@ -1,4 +1,4 @@ -/* size.h -- size type */ +/* geom/size.h -- size type */ #ifndef GEOM_SIZE_H #define GEOM_SIZE_H diff --git a/include/io/path.h b/include/io/path.h index 8a12df5..0cb354f 100644 --- a/include/io/path.h +++ b/include/io/path.h @@ -1,4 +1,4 @@ -/* path.h -- filename path handling */ +/* io/path.h -- filename path handling */ #ifndef DPTLIB_PATH #define DPTLIB_PATH diff --git a/include/io/stream-mem.h b/include/io/stream-mem.h index 599f973..bf48c14 100644 --- a/include/io/stream-mem.h +++ b/include/io/stream-mem.h @@ -1,4 +1,4 @@ -/* stream-mem.c -- memory block IO stream implementation */ +/* io/stream-mem.h -- memory block IO stream implementation */ #ifndef STREAM_MEM_H #define STREAM_MEM_H diff --git a/include/io/stream-mtfcomp.h b/include/io/stream-mtfcomp.h index ffe7f6d..7d17d03 100644 --- a/include/io/stream-mtfcomp.h +++ b/include/io/stream-mtfcomp.h @@ -1,4 +1,4 @@ -/* stream-mtfcomp.h -- "move to front" adaptive compression stream */ +/* io/stream-mtfcomp.h -- "move to front" adaptive compression stream */ #ifndef STREAM_MTFCOMP_H #define STREAM_MTFCOMP_H diff --git a/include/io/stream-packbits.h b/include/io/stream-packbits.h index 836066b..cdd70e2 100644 --- a/include/io/stream-packbits.h +++ b/include/io/stream-packbits.h @@ -1,4 +1,4 @@ -/* stream-packbits.h -- PackBits compression */ +/* io/stream-packbits.h -- PackBits compression */ #ifndef STREAM_PACKBITS_H #define STREAM_PACKBITS_H diff --git a/include/io/stream-stdio.h b/include/io/stream-stdio.h index 71bcd4f..a769c70 100644 --- a/include/io/stream-stdio.h +++ b/include/io/stream-stdio.h @@ -1,4 +1,4 @@ -/* stream-stdio.c -- C standard IO stream implementation */ +/* io/stream-stdio.h -- C standard IO stream implementation */ #ifndef STREAM_STDIO_H #define STREAM_STDIO_H diff --git a/include/io/stream.h b/include/io/stream.h index 7e676fd..6eed921 100644 --- a/include/io/stream.h +++ b/include/io/stream.h @@ -1,4 +1,4 @@ -/* stream.h -- stream system */ +/* io/stream.h -- stream system */ /** * \file Stream (interface). diff --git a/include/test/all-tests.h b/include/test/all-tests.h index 9986c3a..aca2be7 100644 --- a/include/test/all-tests.h +++ b/include/test/all-tests.h @@ -1,4 +1,4 @@ -/* all-tests.h */ +/* test/all-tests.h */ #ifndef TESTS_ALL_TESTS_H #define TESTS_ALL_TESTS_H diff --git a/include/test/txtscr.h b/include/test/txtscr.h index 0dcf619..40c6c6f 100644 --- a/include/test/txtscr.h +++ b/include/test/txtscr.h @@ -1,4 +1,4 @@ -/* txtscr.h -- text format 'screen' */ +/* test/txtscr.h -- text format 'screen' */ #ifndef TEST_TXTSCR_H #define TEST_TXTSCR_H diff --git a/include/text/bmtext.h b/include/text/bmtext.h index f3b3811..adee453 100644 --- a/include/text/bmtext.h +++ b/include/text/bmtext.h @@ -1,4 +1,4 @@ -/* bmtext.h -- word-wrap and draw a paragraph in a bitmap font */ +/* text/bmtext.h -- word-wrap and draw a paragraph in a bitmap font */ /** * \file bmtext.h diff --git a/include/text/txtfmt.h b/include/text/txtfmt.h index dc087c0..6893703 100644 --- a/include/text/txtfmt.h +++ b/include/text/txtfmt.h @@ -1,4 +1,4 @@ -/* txtfmt.h -- text formatting */ +/* text/txtfmt.h -- text formatting */ /** * \file txtfmt.h diff --git a/include/utils/array.h b/include/utils/array.h index 26ae20d..b25d366 100644 --- a/include/utils/array.h +++ b/include/utils/array.h @@ -1,4 +1,4 @@ -/* array.h -- array utilities */ +/* utils/array.h -- array utilities */ /** * \file array.h diff --git a/include/utils/barith.h b/include/utils/barith.h index 053ccd7..6800806 100644 --- a/include/utils/barith.h +++ b/include/utils/barith.h @@ -1,4 +1,4 @@ -/* barith.h -- binary arithmetic */ +/* utils/barith.h -- binary arithmetic */ #ifndef UTILS_BARITH_H #define UTILS_BARITH_H diff --git a/include/utils/bsearch.h b/include/utils/bsearch.h index 7405f2e..2fed337 100644 --- a/include/utils/bsearch.h +++ b/include/utils/bsearch.h @@ -1,4 +1,4 @@ -/* bsearch.h -- binary searching arrays */ +/* utils/bsearch.h -- binary searching arrays */ #ifndef UTILS_BSEARCH_H #define UTILS_BSEARCH_H diff --git a/include/utils/bytesex.h b/include/utils/bytesex.h index 71508c0..d3af2d8 100644 --- a/include/utils/bytesex.h +++ b/include/utils/bytesex.h @@ -1,4 +1,4 @@ -/* bytesex.h -- reversing bytesex */ +/* utils/bytesex.h -- reversing bytesex */ #ifndef UTILS_BYTESEX_H #define UTILS_BYTESEX_H diff --git a/include/utils/fxp.h b/include/utils/fxp.h index 4043cb9..d5bfe9a 100644 --- a/include/utils/fxp.h +++ b/include/utils/fxp.h @@ -1,4 +1,4 @@ -/* fxp.h -- fixed point helpers */ +/* utils/fxp.h -- fixed point helpers */ #ifndef UTILS_FXP_H #define UTILS_FXP_H diff --git a/include/utils/maths.h b/include/utils/maths.h index 50df4a1..aef58a0 100644 --- a/include/utils/maths.h +++ b/include/utils/maths.h @@ -1,4 +1,4 @@ -/* maths.h -- maths utils */ +/* utils/maths.h -- maths utils */ #ifndef UTILS_MATHS_H #define UTILS_MATHS_H diff --git a/include/utils/pack.h b/include/utils/pack.h index 43bf89a..d61f4bb 100644 --- a/include/utils/pack.h +++ b/include/utils/pack.h @@ -1,4 +1,4 @@ -/* pack.h -- structure packing and unpacking helpers */ +/* utils/pack.h -- structure packing and unpacking helpers */ /** * \file Pack (interface). diff --git a/include/utils/primes.h b/include/utils/primes.h index 8948abf..1937c84 100644 --- a/include/utils/primes.h +++ b/include/utils/primes.h @@ -1,4 +1,4 @@ -/* primes.h -- cache of prime numbers */ +/* utils/primes.h -- cache of prime numbers */ #ifndef UTILS_PRIMES_H #define UTILS_PRIMES_H diff --git a/include/wuss/icon.h b/include/wuss/icon.h index 13c709b..8a6f9b4 100644 --- a/include/wuss/icon.h +++ b/include/wuss/icon.h @@ -1,4 +1,4 @@ -/* icon.h -- wuss work-area icons */ +/* wuss/icon.h -- wuss work-area icons */ /** * \file icon.h diff --git a/include/wuss/menu.h b/include/wuss/menu.h index 7d28c74..463fad8 100644 --- a/include/wuss/menu.h +++ b/include/wuss/menu.h @@ -1,4 +1,4 @@ -/* menu.h -- wuss pop-up menu helper */ +/* wuss/menu.h -- wuss pop-up menu helper */ /** * \file menu.h diff --git a/include/wuss/task.h b/include/wuss/task.h index 02b65a4..b2cf1d6 100644 --- a/include/wuss/task.h +++ b/include/wuss/task.h @@ -1,4 +1,4 @@ -/* task.h -- wuss task API */ +/* wuss/task.h -- wuss task API */ /** * \file task.h diff --git a/include/wuss/window.h b/include/wuss/window.h index 601e51e..1d830d9 100644 --- a/include/wuss/window.h +++ b/include/wuss/window.h @@ -1,4 +1,4 @@ -/* window.h -- wuss window API */ +/* wuss/window.h -- wuss window API */ /** * \file window.h diff --git a/include/wuss/wuss.h b/include/wuss/wuss.h index b57259c..fa43c69 100644 --- a/include/wuss/wuss.h +++ b/include/wuss/wuss.h @@ -1,4 +1,4 @@ -/* wuss.h -- minimal window manager */ +/* wuss/wuss.h -- minimal window manager */ /** * \file wuss.h diff --git a/libraries/databases/digest-db/digest-db.c b/libraries/databases/digest-db/digest-db.c index c09e0e9..ed22e84 100644 --- a/libraries/databases/digest-db/digest-db.c +++ b/libraries/databases/digest-db/digest-db.c @@ -1,4 +1,4 @@ -/* digest-db.c -- digest database */ +/* databases/digest-db/digest-db.c -- digest database */ #include <limits.h> #include <stddef.h> diff --git a/libraries/databases/filename-db/filename-db.c b/libraries/databases/filename-db/filename-db.c index abe5202..9ba41f7 100644 --- a/libraries/databases/filename-db/filename-db.c +++ b/libraries/databases/filename-db/filename-db.c @@ -1,4 +1,4 @@ -/* filename-db.c -- filename database */ +/* databases/filename-db/filename-db.c -- filename database */ /* filenamedb maps md5 digests to filenames so that we can search for files * and retrieve filenames */ diff --git a/libraries/databases/pickle/delete.c b/libraries/databases/pickle/delete.c index a517f4e..e455387 100644 --- a/libraries/databases/pickle/delete.c +++ b/libraries/databases/pickle/delete.c @@ -1,4 +1,4 @@ -/* delete.c -- delete a pickle file */ +/* databases/pickle/delete.c -- delete a pickle file */ #include <assert.h> #include <stdio.h> diff --git a/libraries/databases/pickle/hash-reader.c b/libraries/databases/pickle/hash-reader.c index d878c83..e1ee74b 100644 --- a/libraries/databases/pickle/hash-reader.c +++ b/libraries/databases/pickle/hash-reader.c @@ -1,4 +1,4 @@ -/* hash-reader.c -- glue methods to let pickle operate on hashes */ +/* databases/pickle/hash-reader.c -- glue methods to let pickle operate on hashes */ #include <assert.h> #include <ctype.h> diff --git a/libraries/databases/pickle/hash-writer.c b/libraries/databases/pickle/hash-writer.c index 2949dcf..3b892f7 100644 --- a/libraries/databases/pickle/hash-writer.c +++ b/libraries/databases/pickle/hash-writer.c @@ -1,4 +1,4 @@ -/* hash-writer.c -- glue methods to let pickle operate on hashes */ +/* databases/pickle/hash-writer.c -- glue methods to let pickle operate on hashes */ #include <assert.h> #include <ctype.h> diff --git a/libraries/databases/pickle/pickle.c b/libraries/databases/pickle/pickle.c index 55bcf06..eadb172 100644 --- a/libraries/databases/pickle/pickle.c +++ b/libraries/databases/pickle/pickle.c @@ -1,4 +1,4 @@ -/* pickle.c -- serialise an associative array to file */ +/* databases/pickle/pickle.c -- serialise an associative array to file */ #include <assert.h> #include <ctype.h> diff --git a/libraries/databases/pickle/test/pickle-test.c b/libraries/databases/pickle/test/pickle-test.c index 59ab398..8b38f70 100644 --- a/libraries/databases/pickle/test/pickle-test.c +++ b/libraries/databases/pickle/test/pickle-test.c @@ -1,3 +1,4 @@ +/* databases/pickle/test/pickle-test.c */ #include <assert.h> #include <stdio.h> diff --git a/libraries/databases/pickle/unpickle.c b/libraries/databases/pickle/unpickle.c index fce030b..a0c2be2 100644 --- a/libraries/databases/pickle/unpickle.c +++ b/libraries/databases/pickle/unpickle.c @@ -1,4 +1,4 @@ -/* unpickle.c -- deserialise an associative array from file */ +/* databases/pickle/unpickle.c -- deserialise an associative array from file */ #include <assert.h> #include <ctype.h> diff --git a/libraries/databases/tag-db/tag-db.c b/libraries/databases/tag-db/tag-db.c index 5397b36..b6ce93a 100644 --- a/libraries/databases/tag-db/tag-db.c +++ b/libraries/databases/tag-db/tag-db.c @@ -1,4 +1,4 @@ -/* tag-db.c -- tag database */ +/* databases/tag-db/tag-db.c -- tag database */ // TODO // cope with tags with spaces (quoted for saving and loading) diff --git a/libraries/databases/tag-db/test/tag-db-test.c b/libraries/databases/tag-db/test/tag-db-test.c index 076b119..8e7a777 100644 --- a/libraries/databases/tag-db/test/tag-db-test.c +++ b/libraries/databases/tag-db/test/tag-db-test.c @@ -1,3 +1,4 @@ +/* databases/tag-db/test/tag-db-test.c */ #include <assert.h> #include <stdio.h> diff --git a/libraries/datastruct/atom/create.c b/libraries/datastruct/atom/create.c index 90d51b6..1a61b68 100644 --- a/libraries/datastruct/atom/create.c +++ b/libraries/datastruct/atom/create.c @@ -1,4 +1,4 @@ -/* create.c -- atoms */ +/* datastruct/atom/create.c -- atoms */ #include <stdlib.h> diff --git a/libraries/datastruct/atom/delete-block.c b/libraries/datastruct/atom/delete-block.c index 1a9ca72..6ededec 100644 --- a/libraries/datastruct/atom/delete-block.c +++ b/libraries/datastruct/atom/delete-block.c @@ -1,4 +1,4 @@ -/* delete-block.c -- atoms */ +/* datastruct/atom/delete-block.c -- atoms */ #include <stdlib.h> diff --git a/libraries/datastruct/atom/delete.c b/libraries/datastruct/atom/delete.c index a7be93f..a6735de 100644 --- a/libraries/datastruct/atom/delete.c +++ b/libraries/datastruct/atom/delete.c @@ -1,4 +1,4 @@ -/* delete.c -- atoms */ +/* datastruct/atom/delete.c -- atoms */ #include <assert.h> #include <string.h> diff --git a/libraries/datastruct/atom/destroy.c b/libraries/datastruct/atom/destroy.c index 3dcf647..493587c 100644 --- a/libraries/datastruct/atom/destroy.c +++ b/libraries/datastruct/atom/destroy.c @@ -1,4 +1,4 @@ -/* destroy.c -- atoms */ +/* datastruct/atom/destroy.c -- atoms */ #include <stdlib.h> diff --git a/libraries/datastruct/atom/for-block.c b/libraries/datastruct/atom/for-block.c index c67db91..c8f27f4 100644 --- a/libraries/datastruct/atom/for-block.c +++ b/libraries/datastruct/atom/for-block.c @@ -1,4 +1,4 @@ -/* for-block.c -- atoms */ +/* datastruct/atom/for-block.c -- atoms */ #include <assert.h> #include <limits.h> diff --git a/libraries/datastruct/atom/get.c b/libraries/datastruct/atom/get.c index ef63a7f..6b42307 100644 --- a/libraries/datastruct/atom/get.c +++ b/libraries/datastruct/atom/get.c @@ -1,4 +1,4 @@ -/* get.c -- atoms */ +/* datastruct/atom/get.c -- atoms */ #include <assert.h> #include <stddef.h> diff --git a/libraries/datastruct/atom/impl.h b/libraries/datastruct/atom/impl.h index 35c7e13..1237330 100644 --- a/libraries/datastruct/atom/impl.h +++ b/libraries/datastruct/atom/impl.h @@ -1,4 +1,4 @@ -/* impl.h -- atoms */ +/* datastruct/atom/impl.h -- atoms */ /* An atom_set consists of a series of block pools and location pools. * diff --git a/libraries/datastruct/atom/new.c b/libraries/datastruct/atom/new.c index 58663d6..026ebeb 100644 --- a/libraries/datastruct/atom/new.c +++ b/libraries/datastruct/atom/new.c @@ -1,4 +1,4 @@ -/* new.c -- atoms */ +/* datastruct/atom/new.c -- atoms */ #include <assert.h> #include <stdlib.h> diff --git a/libraries/datastruct/atom/set.c b/libraries/datastruct/atom/set.c index 0b70245..68d1a8d 100644 --- a/libraries/datastruct/atom/set.c +++ b/libraries/datastruct/atom/set.c @@ -1,4 +1,4 @@ -/* set.c -- atoms */ +/* datastruct/atom/set.c -- atoms */ #include <assert.h> #include <string.h> diff --git a/libraries/datastruct/atom/test/atom-test.c b/libraries/datastruct/atom/test/atom-test.c index 0fbabed..78743de 100644 --- a/libraries/datastruct/atom/test/atom-test.c +++ b/libraries/datastruct/atom/test/atom-test.c @@ -1,3 +1,4 @@ +/* datastruct/atom/test/atom-test.c */ #include <stdio.h> #include <stdlib.h> diff --git a/libraries/datastruct/bitarr/count.c b/libraries/datastruct/bitarr/count.c index 475562c..b3a8965 100644 --- a/libraries/datastruct/bitarr/count.c +++ b/libraries/datastruct/bitarr/count.c @@ -1,4 +1,4 @@ -/* count.c -- arrays of bits */ +/* datastruct/bitarr/count.c -- arrays of bits */ #include <stddef.h> diff --git a/libraries/datastruct/bitarr/test/bitarr-test.c b/libraries/datastruct/bitarr/test/bitarr-test.c index 91a566b..e8403c0 100644 --- a/libraries/datastruct/bitarr/test/bitarr-test.c +++ b/libraries/datastruct/bitarr/test/bitarr-test.c @@ -1,3 +1,4 @@ +/* datastruct/bitarr/test/bitarr-test.c */ #include <stdio.h> #include <stdlib.h> diff --git a/libraries/datastruct/bitfifo/bitfifo.c b/libraries/datastruct/bitfifo/bitfifo.c index cd0e6b1..ba21551 100644 --- a/libraries/datastruct/bitfifo/bitfifo.c +++ b/libraries/datastruct/bitfifo/bitfifo.c @@ -1,4 +1,4 @@ -/* bitfifo.c -- fifo which stores bits */ +/* datastruct/bitfifo/bitfifo.c -- fifo which stores bits */ #include <assert.h> #include <string.h> diff --git a/libraries/datastruct/bitfifo/test/bitfifo-test.c b/libraries/datastruct/bitfifo/test/bitfifo-test.c index d46bf44..d346539 100644 --- a/libraries/datastruct/bitfifo/test/bitfifo-test.c +++ b/libraries/datastruct/bitfifo/test/bitfifo-test.c @@ -1,3 +1,4 @@ +/* datastruct/bitfifo/test/bitfifo-test.c */ #include <limits.h> #include <stdio.h> diff --git a/libraries/datastruct/bitvec/and.c b/libraries/datastruct/bitvec/and.c index b291e99..d6eceab 100644 --- a/libraries/datastruct/bitvec/and.c +++ b/libraries/datastruct/bitvec/and.c @@ -1,4 +1,4 @@ -/* and.c -- bit vectors */ +/* datastruct/bitvec/and.c -- bit vectors */ #include <stdlib.h> diff --git a/libraries/datastruct/bitvec/clear-all.c b/libraries/datastruct/bitvec/clear-all.c index f17259e..8b24e7e 100644 --- a/libraries/datastruct/bitvec/clear-all.c +++ b/libraries/datastruct/bitvec/clear-all.c @@ -1,4 +1,4 @@ -/* clear-all.c -- bit vectors */ +/* datastruct/bitvec/clear-all.c -- bit vectors */ #include <string.h> diff --git a/libraries/datastruct/bitvec/clear.c b/libraries/datastruct/bitvec/clear.c index 166b232..982ac63 100644 --- a/libraries/datastruct/bitvec/clear.c +++ b/libraries/datastruct/bitvec/clear.c @@ -1,4 +1,4 @@ -/* clear.c -- bit vectors */ +/* datastruct/bitvec/clear.c -- bit vectors */ #include "datastruct/bitvec.h" diff --git a/libraries/datastruct/bitvec/count.c b/libraries/datastruct/bitvec/count.c index 51e548a..6834dc2 100644 --- a/libraries/datastruct/bitvec/count.c +++ b/libraries/datastruct/bitvec/count.c @@ -1,4 +1,4 @@ -/* count.c -- bit vectors */ +/* datastruct/bitvec/count.c -- bit vectors */ #include "utils/barith.h" #include "datastruct/bitvec.h" diff --git a/libraries/datastruct/bitvec/create.c b/libraries/datastruct/bitvec/create.c index 56f2b45..459797c 100644 --- a/libraries/datastruct/bitvec/create.c +++ b/libraries/datastruct/bitvec/create.c @@ -1,4 +1,4 @@ -/* create.c -- bit vectors */ +/* datastruct/bitvec/create.c -- bit vectors */ #include <assert.h> #include <stdlib.h> diff --git a/libraries/datastruct/bitvec/destroy.c b/libraries/datastruct/bitvec/destroy.c index ece3d5c..70a1eb0 100644 --- a/libraries/datastruct/bitvec/destroy.c +++ b/libraries/datastruct/bitvec/destroy.c @@ -1,4 +1,4 @@ -/* destroy.c -- bit vectors */ +/* datastruct/bitvec/destroy.c -- bit vectors */ #include <stdlib.h> diff --git a/libraries/datastruct/bitvec/ensure.c b/libraries/datastruct/bitvec/ensure.c index 62adab4..02f74c0 100644 --- a/libraries/datastruct/bitvec/ensure.c +++ b/libraries/datastruct/bitvec/ensure.c @@ -1,4 +1,4 @@ -/* ensure.c -- bit vectors */ +/* datastruct/bitvec/ensure.c -- bit vectors */ #include <stdlib.h> #include <string.h> diff --git a/libraries/datastruct/bitvec/eq.c b/libraries/datastruct/bitvec/eq.c index b7e4d50..3e852c1 100644 --- a/libraries/datastruct/bitvec/eq.c +++ b/libraries/datastruct/bitvec/eq.c @@ -1,4 +1,4 @@ -/* eq.c -- bit vectors */ +/* datastruct/bitvec/eq.c -- bit vectors */ #include <string.h> diff --git a/libraries/datastruct/bitvec/get.c b/libraries/datastruct/bitvec/get.c index e2b270e..a548b47 100644 --- a/libraries/datastruct/bitvec/get.c +++ b/libraries/datastruct/bitvec/get.c @@ -1,4 +1,4 @@ -/* get.c -- bit vectors */ +/* datastruct/bitvec/get.c -- bit vectors */ #include "datastruct/bitvec.h" diff --git a/libraries/datastruct/bitvec/impl.h b/libraries/datastruct/bitvec/impl.h index e94f1fb..7e312de 100644 --- a/libraries/datastruct/bitvec/impl.h +++ b/libraries/datastruct/bitvec/impl.h @@ -1,4 +1,4 @@ -/* impl.h -- bit vectors */ +/* datastruct/bitvec/impl.h -- bit vectors */ #ifndef DATASTRUCT_BITVEC_IMPL_H #define DATASTRUCT_BITVEC_IMPL_H diff --git a/libraries/datastruct/bitvec/length.c b/libraries/datastruct/bitvec/length.c index 401d674..3c48419 100644 --- a/libraries/datastruct/bitvec/length.c +++ b/libraries/datastruct/bitvec/length.c @@ -1,4 +1,4 @@ -/* length.c -- bit vectors */ +/* datastruct/bitvec/length.c -- bit vectors */ #include <stddef.h> diff --git a/libraries/datastruct/bitvec/next.c b/libraries/datastruct/bitvec/next.c index 67c540b..0bb2d99 100644 --- a/libraries/datastruct/bitvec/next.c +++ b/libraries/datastruct/bitvec/next.c @@ -1,4 +1,4 @@ -/* next.c -- bit vectors */ +/* datastruct/bitvec/next.c -- bit vectors */ #include "utils/barith.h" #include "datastruct/bitvec.h" diff --git a/libraries/datastruct/bitvec/or.c b/libraries/datastruct/bitvec/or.c index 5724ef3..cc06de1 100644 --- a/libraries/datastruct/bitvec/or.c +++ b/libraries/datastruct/bitvec/or.c @@ -1,4 +1,4 @@ -/* or.c -- bit vectors */ +/* datastruct/bitvec/or.c -- bit vectors */ #include <stdlib.h> diff --git a/libraries/datastruct/bitvec/set-all.c b/libraries/datastruct/bitvec/set-all.c index d913afa..368aa71 100644 --- a/libraries/datastruct/bitvec/set-all.c +++ b/libraries/datastruct/bitvec/set-all.c @@ -1,4 +1,4 @@ -/* set-all.c -- bit vectors */ +/* datastruct/bitvec/set-all.c -- bit vectors */ #include <string.h> diff --git a/libraries/datastruct/bitvec/set.c b/libraries/datastruct/bitvec/set.c index 2cefd9b..58c4a69 100644 --- a/libraries/datastruct/bitvec/set.c +++ b/libraries/datastruct/bitvec/set.c @@ -1,4 +1,4 @@ -/* set.c -- bit vectors */ +/* datastruct/bitvec/set.c -- bit vectors */ #include "base/result.h" diff --git a/libraries/datastruct/bitvec/test/bitvec-test.c b/libraries/datastruct/bitvec/test/bitvec-test.c index 9716545..fb708dd 100644 --- a/libraries/datastruct/bitvec/test/bitvec-test.c +++ b/libraries/datastruct/bitvec/test/bitvec-test.c @@ -1,3 +1,4 @@ +/* datastruct/bitvec/test/bitvec-test.c */ #include <limits.h> #include <stdio.h> diff --git a/libraries/datastruct/bitvec/toggle.c b/libraries/datastruct/bitvec/toggle.c index 94a29bc..4105b9b 100644 --- a/libraries/datastruct/bitvec/toggle.c +++ b/libraries/datastruct/bitvec/toggle.c @@ -1,4 +1,4 @@ -/* toggle.c -- bit vectors */ +/* datastruct/bitvec/toggle.c -- bit vectors */ #include "base/result.h" diff --git a/libraries/datastruct/cache/cache.c b/libraries/datastruct/cache/cache.c index 1a8c1cc..0b25c78 100644 --- a/libraries/datastruct/cache/cache.c +++ b/libraries/datastruct/cache/cache.c @@ -1,4 +1,4 @@ -/* cache.c -- generic single block cache */ +/* datastruct/cache/cache.c -- generic single block cache */ #include <assert.h> #include <limits.h> diff --git a/libraries/datastruct/cache/test/cache-test.c b/libraries/datastruct/cache/test/cache-test.c index c1704c9..e4fbaaa 100644 --- a/libraries/datastruct/cache/test/cache-test.c +++ b/libraries/datastruct/cache/test/cache-test.c @@ -1,3 +1,4 @@ +/* datastruct/cache/test/cache-test.c */ #include <limits.h> #include <stdio.h> diff --git a/libraries/datastruct/hash/count.c b/libraries/datastruct/hash/count.c index 0d24af2..02fbe31 100644 --- a/libraries/datastruct/hash/count.c +++ b/libraries/datastruct/hash/count.c @@ -1,4 +1,4 @@ -/* count.c -- hash */ +/* datastruct/hash/count.c -- hash */ #include "datastruct/hash.h" diff --git a/libraries/datastruct/hash/create.c b/libraries/datastruct/hash/create.c index 70f2380..eb2387f 100644 --- a/libraries/datastruct/hash/create.c +++ b/libraries/datastruct/hash/create.c @@ -1,4 +1,4 @@ -/* create.c -- hash */ +/* datastruct/hash/create.c -- hash */ #include <stdlib.h> #include <string.h> diff --git a/libraries/datastruct/hash/destroy.c b/libraries/datastruct/hash/destroy.c index 8aa8252..2c06ca9 100644 --- a/libraries/datastruct/hash/destroy.c +++ b/libraries/datastruct/hash/destroy.c @@ -1,4 +1,4 @@ -/* destroy.c -- hash */ +/* datastruct/hash/destroy.c -- hash */ #include <stdlib.h> diff --git a/libraries/datastruct/hash/impl.h b/libraries/datastruct/hash/impl.h index 808a790..9403e83 100644 --- a/libraries/datastruct/hash/impl.h +++ b/libraries/datastruct/hash/impl.h @@ -1,4 +1,4 @@ -/* impl.h -- hash */ +/* datastruct/hash/impl.h -- hash */ #ifndef DATASTRUCT_HASH_IMPL_H #define DATASTRUCT_HASH_IMPL_H diff --git a/libraries/datastruct/hash/insert.c b/libraries/datastruct/hash/insert.c index df66175..0644aee 100644 --- a/libraries/datastruct/hash/insert.c +++ b/libraries/datastruct/hash/insert.c @@ -1,4 +1,4 @@ -/* insert.c -- hash */ +/* datastruct/hash/insert.c -- hash */ #include <stdlib.h> diff --git a/libraries/datastruct/hash/lookup-node.c b/libraries/datastruct/hash/lookup-node.c index 6b6c435..42ee144 100644 --- a/libraries/datastruct/hash/lookup-node.c +++ b/libraries/datastruct/hash/lookup-node.c @@ -1,4 +1,4 @@ -/* lookup-node.c -- hash */ +/* datastruct/hash/lookup-node.c -- hash */ #include <stdlib.h> diff --git a/libraries/datastruct/hash/lookup.c b/libraries/datastruct/hash/lookup.c index 8f66503..3671c38 100644 --- a/libraries/datastruct/hash/lookup.c +++ b/libraries/datastruct/hash/lookup.c @@ -1,4 +1,4 @@ -/* lookup.c -- hash */ +/* datastruct/hash/lookup.c -- hash */ #include <stdlib.h> diff --git a/libraries/datastruct/hash/remove.c b/libraries/datastruct/hash/remove.c index a3a1ce8..8222d6a 100644 --- a/libraries/datastruct/hash/remove.c +++ b/libraries/datastruct/hash/remove.c @@ -1,4 +1,4 @@ -/* remove.c -- hash */ +/* datastruct/hash/remove.c -- hash */ #include <stdlib.h> diff --git a/libraries/datastruct/hash/test/hash-test.c b/libraries/datastruct/hash/test/hash-test.c index 7d807b2..c896d5d 100644 --- a/libraries/datastruct/hash/test/hash-test.c +++ b/libraries/datastruct/hash/test/hash-test.c @@ -1,3 +1,4 @@ +/* datastruct/hash/test/hash-test.c */ #include <stdio.h> #include <string.h> diff --git a/libraries/datastruct/hash/walk-cont.c b/libraries/datastruct/hash/walk-cont.c index 4fa822f..db3b44c 100644 --- a/libraries/datastruct/hash/walk-cont.c +++ b/libraries/datastruct/hash/walk-cont.c @@ -1,4 +1,4 @@ -/* walk-cont.c -- hash */ +/* datastruct/hash/walk-cont.c -- hash */ #include <assert.h> #include <stdlib.h> diff --git a/libraries/datastruct/hash/walk.c b/libraries/datastruct/hash/walk.c index 6388c62..d876d44 100644 --- a/libraries/datastruct/hash/walk.c +++ b/libraries/datastruct/hash/walk.c @@ -1,4 +1,4 @@ -/* walk.c -- hash */ +/* datastruct/hash/walk.c -- hash */ #include <stdlib.h> diff --git a/libraries/datastruct/hlist/append.c b/libraries/datastruct/hlist/append.c index 601242b..3bcfbd5 100644 --- a/libraries/datastruct/hlist/append.c +++ b/libraries/datastruct/hlist/append.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/append.c */ #include <stdarg.h> #include <stddef.h> diff --git a/libraries/datastruct/hlist/copy.c b/libraries/datastruct/hlist/copy.c index 49fdd4c..12240d4 100644 --- a/libraries/datastruct/hlist/copy.c +++ b/libraries/datastruct/hlist/copy.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/copy.c */ #include <stdarg.h> #include <stddef.h> diff --git a/libraries/datastruct/hlist/free.c b/libraries/datastruct/hlist/free.c index 5823b50..3e8dfc3 100644 --- a/libraries/datastruct/hlist/free.c +++ b/libraries/datastruct/hlist/free.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/free.c */ #include <assert.h> #include <stdarg.h> diff --git a/libraries/datastruct/hlist/impl.h b/libraries/datastruct/hlist/impl.h index 32b30fa..b3e533d 100644 --- a/libraries/datastruct/hlist/impl.h +++ b/libraries/datastruct/hlist/impl.h @@ -1,3 +1,3 @@ -/* impl.h -- hlist */ +/* datastruct/hlist/impl.h -- hlist */ #define T hlist_t diff --git a/libraries/datastruct/hlist/length.c b/libraries/datastruct/hlist/length.c index b52c161..4153576 100644 --- a/libraries/datastruct/hlist/length.c +++ b/libraries/datastruct/hlist/length.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/length.c */ #include <stdarg.h> #include <stddef.h> diff --git a/libraries/datastruct/hlist/list.c b/libraries/datastruct/hlist/list.c index a8178df..07cd0ff 100644 --- a/libraries/datastruct/hlist/list.c +++ b/libraries/datastruct/hlist/list.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/list.c */ #include <stdarg.h> #include <stddef.h> diff --git a/libraries/datastruct/hlist/map.c b/libraries/datastruct/hlist/map.c index 183567b..d0a3acd 100644 --- a/libraries/datastruct/hlist/map.c +++ b/libraries/datastruct/hlist/map.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/map.c */ #include <assert.h> #include <stdarg.h> diff --git a/libraries/datastruct/hlist/pop.c b/libraries/datastruct/hlist/pop.c index 03a0902..9d3d3e4 100644 --- a/libraries/datastruct/hlist/pop.c +++ b/libraries/datastruct/hlist/pop.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/pop.c */ #include <stdarg.h> #include <stddef.h> diff --git a/libraries/datastruct/hlist/push.c b/libraries/datastruct/hlist/push.c index fefe1cd..2e5add1 100644 --- a/libraries/datastruct/hlist/push.c +++ b/libraries/datastruct/hlist/push.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/push.c */ #include <stdarg.h> #include <stddef.h> diff --git a/libraries/datastruct/hlist/reverse.c b/libraries/datastruct/hlist/reverse.c index 49467f1..31647c8 100644 --- a/libraries/datastruct/hlist/reverse.c +++ b/libraries/datastruct/hlist/reverse.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/reverse.c */ #include <stdarg.h> #include <stddef.h> diff --git a/libraries/datastruct/hlist/to-array.c b/libraries/datastruct/hlist/to-array.c index 26da4b1..be907e2 100644 --- a/libraries/datastruct/hlist/to-array.c +++ b/libraries/datastruct/hlist/to-array.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/to-array.c */ #include <stdarg.h> #include <stddef.h> diff --git a/libraries/datastruct/list/add-head.c b/libraries/datastruct/list/add-head.c index f7e1d6b..59b9f6c 100644 --- a/libraries/datastruct/list/add-head.c +++ b/libraries/datastruct/list/add-head.c @@ -1,3 +1,4 @@ +/* datastruct/list/add-head.c */ #include "datastruct/list.h" diff --git a/libraries/datastruct/list/add-tail.c b/libraries/datastruct/list/add-tail.c index bb60237..b500ba5 100644 --- a/libraries/datastruct/list/add-tail.c +++ b/libraries/datastruct/list/add-tail.c @@ -1,3 +1,4 @@ +/* datastruct/list/add-tail.c */ #include "datastruct/list.h" diff --git a/libraries/datastruct/list/find.c b/libraries/datastruct/list/find.c index 7db71f0..e283cb8 100644 --- a/libraries/datastruct/list/find.c +++ b/libraries/datastruct/list/find.c @@ -1,3 +1,4 @@ +/* datastruct/list/find.c */ #include "datastruct/list.h" diff --git a/libraries/datastruct/list/init.c b/libraries/datastruct/list/init.c index c9cfcac..d91c25d 100644 --- a/libraries/datastruct/list/init.c +++ b/libraries/datastruct/list/init.c @@ -1,3 +1,4 @@ +/* datastruct/list/init.c */ #include "datastruct/list.h" diff --git a/libraries/datastruct/list/remove.c b/libraries/datastruct/list/remove.c index 532a722..bf73e24 100644 --- a/libraries/datastruct/list/remove.c +++ b/libraries/datastruct/list/remove.c @@ -1,3 +1,4 @@ +/* datastruct/list/remove.c */ #include <assert.h> diff --git a/libraries/datastruct/list/test/list-test.c b/libraries/datastruct/list/test/list-test.c index a6e959c..ed2964e 100644 --- a/libraries/datastruct/list/test/list-test.c +++ b/libraries/datastruct/list/test/list-test.c @@ -1,3 +1,4 @@ +/* datastruct/list/test/list-test.c */ #include <stdio.h> diff --git a/libraries/datastruct/list/walk.c b/libraries/datastruct/list/walk.c index 37c7ed9..bbbee8c 100644 --- a/libraries/datastruct/list/walk.c +++ b/libraries/datastruct/list/walk.c @@ -1,3 +1,4 @@ +/* datastruct/list/walk.c */ #include "datastruct/list.h" diff --git a/libraries/datastruct/ntree/copy.c b/libraries/datastruct/ntree/copy.c index 6214491..3870dad 100644 --- a/libraries/datastruct/ntree/copy.c +++ b/libraries/datastruct/ntree/copy.c @@ -1,4 +1,4 @@ -/* copy.c -- n-ary tree */ +/* datastruct/ntree/copy.c -- n-ary tree */ #include <stdlib.h> diff --git a/libraries/datastruct/ntree/delete.c b/libraries/datastruct/ntree/delete.c index 8730160..3c640ad 100644 --- a/libraries/datastruct/ntree/delete.c +++ b/libraries/datastruct/ntree/delete.c @@ -1,4 +1,4 @@ -/* delete.c -- n-ary tree */ +/* datastruct/ntree/delete.c -- n-ary tree */ #include <assert.h> diff --git a/libraries/datastruct/ntree/depth.c b/libraries/datastruct/ntree/depth.c index e64d4e3..491e4d2 100644 --- a/libraries/datastruct/ntree/depth.c +++ b/libraries/datastruct/ntree/depth.c @@ -1,4 +1,4 @@ -/* depth.c -- n-ary tree */ +/* datastruct/ntree/depth.c -- n-ary tree */ #include "datastruct/ntree.h" diff --git a/libraries/datastruct/ntree/first-child.c b/libraries/datastruct/ntree/first-child.c index 8bcbf63..2e89680 100644 --- a/libraries/datastruct/ntree/first-child.c +++ b/libraries/datastruct/ntree/first-child.c @@ -1,4 +1,4 @@ -/* first-child.c -- n-ary tree */ +/* datastruct/ntree/first-child.c -- n-ary tree */ #include <stdlib.h> diff --git a/libraries/datastruct/ntree/free.c b/libraries/datastruct/ntree/free.c index 06b345e..06b0323 100644 --- a/libraries/datastruct/ntree/free.c +++ b/libraries/datastruct/ntree/free.c @@ -1,4 +1,4 @@ -/* free.c -- n-ary tree */ +/* datastruct/ntree/free.c -- n-ary tree */ #include <assert.h> #include <stdlib.h> diff --git a/libraries/datastruct/ntree/get-data.c b/libraries/datastruct/ntree/get-data.c index 07647d9..f842bde 100644 --- a/libraries/datastruct/ntree/get-data.c +++ b/libraries/datastruct/ntree/get-data.c @@ -1,4 +1,4 @@ -/* get-data.c -- n-ary tree */ +/* datastruct/ntree/get-data.c -- n-ary tree */ #include "datastruct/ntree.h" diff --git a/libraries/datastruct/ntree/impl.h b/libraries/datastruct/ntree/impl.h index dec9802..21c6ecc 100644 --- a/libraries/datastruct/ntree/impl.h +++ b/libraries/datastruct/ntree/impl.h @@ -1,4 +1,4 @@ -/* impl.h -- n-ary tree */ +/* datastruct/ntree/impl.h -- n-ary tree */ #ifndef NTREE_IMPL_H #define NTREE_IMPL_H diff --git a/libraries/datastruct/ntree/insert-after.c b/libraries/datastruct/ntree/insert-after.c index 6ef8f7f..94d7889 100644 --- a/libraries/datastruct/ntree/insert-after.c +++ b/libraries/datastruct/ntree/insert-after.c @@ -1,4 +1,4 @@ -/* insert-after.c -- n-ary tree */ +/* datastruct/ntree/insert-after.c -- n-ary tree */ #include <assert.h> #include <stdlib.h> diff --git a/libraries/datastruct/ntree/insert-before.c b/libraries/datastruct/ntree/insert-before.c index ae44f4a..5afd562 100644 --- a/libraries/datastruct/ntree/insert-before.c +++ b/libraries/datastruct/ntree/insert-before.c @@ -1,4 +1,4 @@ -/* insert-before.c -- n-ary tree */ +/* datastruct/ntree/insert-before.c -- n-ary tree */ #include <assert.h> #include <stdlib.h> diff --git a/libraries/datastruct/ntree/insert.c b/libraries/datastruct/ntree/insert.c index 26e9019..2bb87f1 100644 --- a/libraries/datastruct/ntree/insert.c +++ b/libraries/datastruct/ntree/insert.c @@ -1,4 +1,4 @@ -/* insert.c -- n-ary tree */ +/* datastruct/ntree/insert.c -- n-ary tree */ #include <stdlib.h> diff --git a/libraries/datastruct/ntree/last-child.c b/libraries/datastruct/ntree/last-child.c index 5dd9a03..800eac4 100644 --- a/libraries/datastruct/ntree/last-child.c +++ b/libraries/datastruct/ntree/last-child.c @@ -1,4 +1,4 @@ -/* last-child.c -- n-ary tree */ +/* datastruct/ntree/last-child.c -- n-ary tree */ #include "datastruct/ntree.h" diff --git a/libraries/datastruct/ntree/max-height.c b/libraries/datastruct/ntree/max-height.c index 6735c76..30df91f 100644 --- a/libraries/datastruct/ntree/max-height.c +++ b/libraries/datastruct/ntree/max-height.c @@ -1,4 +1,4 @@ -/* max-height.c -- n-ary tree */ +/* datastruct/ntree/max-height.c -- n-ary tree */ #include "datastruct/ntree.h" diff --git a/libraries/datastruct/ntree/n-nodes.c b/libraries/datastruct/ntree/n-nodes.c index 76a1812..80189c9 100644 --- a/libraries/datastruct/ntree/n-nodes.c +++ b/libraries/datastruct/ntree/n-nodes.c @@ -1,4 +1,4 @@ -/* n-nodes.c -- n-ary tree */ +/* datastruct/ntree/n-nodes.c -- n-ary tree */ #include "base/utils.h" diff --git a/libraries/datastruct/ntree/new.c b/libraries/datastruct/ntree/new.c index 24aeb2b..fa1dd1a 100644 --- a/libraries/datastruct/ntree/new.c +++ b/libraries/datastruct/ntree/new.c @@ -1,4 +1,4 @@ -/* new.c -- n-ary tree */ +/* datastruct/ntree/new.c -- n-ary tree */ #include <stdlib.h> diff --git a/libraries/datastruct/ntree/next-sibling.c b/libraries/datastruct/ntree/next-sibling.c index 57d3d73..617c234 100644 --- a/libraries/datastruct/ntree/next-sibling.c +++ b/libraries/datastruct/ntree/next-sibling.c @@ -1,4 +1,4 @@ -/* next-sibling.c -- n-ary tree */ +/* datastruct/ntree/next-sibling.c -- n-ary tree */ #include <stdlib.h> diff --git a/libraries/datastruct/ntree/nth-child.c b/libraries/datastruct/ntree/nth-child.c index feef026..cf11b39 100644 --- a/libraries/datastruct/ntree/nth-child.c +++ b/libraries/datastruct/ntree/nth-child.c @@ -1,4 +1,4 @@ -/* nth-child.c -- n-ary tree */ +/* datastruct/ntree/nth-child.c -- n-ary tree */ #include "datastruct/ntree.h" diff --git a/libraries/datastruct/ntree/parent.c b/libraries/datastruct/ntree/parent.c index a947a76..ed756cf 100644 --- a/libraries/datastruct/ntree/parent.c +++ b/libraries/datastruct/ntree/parent.c @@ -1,4 +1,4 @@ -/* parent.c -- n-ary tree */ +/* datastruct/ntree/parent.c -- n-ary tree */ #include <stdlib.h> diff --git a/libraries/datastruct/ntree/prepend.c b/libraries/datastruct/ntree/prepend.c index 42d0a91..e5820a9 100644 --- a/libraries/datastruct/ntree/prepend.c +++ b/libraries/datastruct/ntree/prepend.c @@ -1,4 +1,4 @@ -/* prepend.c -- n-ary tree */ +/* datastruct/ntree/prepend.c -- n-ary tree */ #include <stdlib.h> diff --git a/libraries/datastruct/ntree/prev-sibling.c b/libraries/datastruct/ntree/prev-sibling.c index 48526b5..0486635 100644 --- a/libraries/datastruct/ntree/prev-sibling.c +++ b/libraries/datastruct/ntree/prev-sibling.c @@ -1,4 +1,4 @@ -/* prev-sibling.c -- n-ary tree */ +/* datastruct/ntree/prev-sibling.c -- n-ary tree */ #include <stdlib.h> diff --git a/libraries/datastruct/ntree/set-data.c b/libraries/datastruct/ntree/set-data.c index 611b418..533a669 100644 --- a/libraries/datastruct/ntree/set-data.c +++ b/libraries/datastruct/ntree/set-data.c @@ -1,4 +1,4 @@ -/* set-data.c -- n-ary tree */ +/* datastruct/ntree/set-data.c -- n-ary tree */ #include "datastruct/ntree.h" diff --git a/libraries/datastruct/ntree/test/ntree-test.c b/libraries/datastruct/ntree/test/ntree-test.c index 8a9c895..9aa5417 100644 --- a/libraries/datastruct/ntree/test/ntree-test.c +++ b/libraries/datastruct/ntree/test/ntree-test.c @@ -1,3 +1,4 @@ +/* datastruct/ntree/test/ntree-test.c */ #include <stdio.h> #include <stdlib.h> diff --git a/libraries/datastruct/ntree/unlink.c b/libraries/datastruct/ntree/unlink.c index c9df723..94838fd 100644 --- a/libraries/datastruct/ntree/unlink.c +++ b/libraries/datastruct/ntree/unlink.c @@ -1,4 +1,4 @@ -/* unlink.c -- n-ary tree */ +/* datastruct/ntree/unlink.c -- n-ary tree */ #include <assert.h> #include <stdlib.h> diff --git a/libraries/datastruct/ntree/walk.c b/libraries/datastruct/ntree/walk.c index e9e4e21..2232072 100644 --- a/libraries/datastruct/ntree/walk.c +++ b/libraries/datastruct/ntree/walk.c @@ -1,4 +1,4 @@ -/* walk.c -- n-ary tree */ +/* datastruct/ntree/walk.c -- n-ary tree */ #include <assert.h> #include <stdlib.h> diff --git a/libraries/datastruct/vector/clear.c b/libraries/datastruct/vector/clear.c index 0345e94..da5096a 100644 --- a/libraries/datastruct/vector/clear.c +++ b/libraries/datastruct/vector/clear.c @@ -1,4 +1,4 @@ -/* clear.c -- vector - flexible array */ +/* datastruct/vector/clear.c -- flexible array */ #include <stdlib.h> diff --git a/libraries/datastruct/vector/create.c b/libraries/datastruct/vector/create.c index bdc03a3..ab86122 100644 --- a/libraries/datastruct/vector/create.c +++ b/libraries/datastruct/vector/create.c @@ -1,4 +1,4 @@ -/* create.c -- vector - flexible array */ +/* datastruct/vector/create.c -- flexible array */ #include <stdlib.h> diff --git a/libraries/datastruct/vector/destroy.c b/libraries/datastruct/vector/destroy.c index b4fce1a..1cf7a36 100644 --- a/libraries/datastruct/vector/destroy.c +++ b/libraries/datastruct/vector/destroy.c @@ -1,4 +1,4 @@ -/* destroy.c -- vector - flexible array */ +/* datastruct/vector/destroy.c -- flexible array */ #include <stdlib.h> diff --git a/libraries/datastruct/vector/ensure.c b/libraries/datastruct/vector/ensure.c index 29bcf67..39b44e8 100644 --- a/libraries/datastruct/vector/ensure.c +++ b/libraries/datastruct/vector/ensure.c @@ -1,4 +1,4 @@ -/* ensure.c -- vector - flexible array */ +/* datastruct/vector/ensure.c -- flexible array */ #include <stdlib.h> diff --git a/libraries/datastruct/vector/get.c b/libraries/datastruct/vector/get.c index 817ddc1..b6d995a 100644 --- a/libraries/datastruct/vector/get.c +++ b/libraries/datastruct/vector/get.c @@ -1,4 +1,4 @@ -/* get.c -- vector - flexible array */ +/* datastruct/vector/get.c -- flexible array */ #include <assert.h> #include <stdlib.h> diff --git a/libraries/datastruct/vector/impl.h b/libraries/datastruct/vector/impl.h index 7ffc593..143a0fc 100644 --- a/libraries/datastruct/vector/impl.h +++ b/libraries/datastruct/vector/impl.h @@ -1,4 +1,4 @@ -/* impl.h -- vector - flexible array */ +/* datastruct/vector/impl.h -- flexible array */ #ifndef IMPL_H #define IMPL_H diff --git a/libraries/datastruct/vector/insert.c b/libraries/datastruct/vector/insert.c index f171d2e..5a09f62 100644 --- a/libraries/datastruct/vector/insert.c +++ b/libraries/datastruct/vector/insert.c @@ -1,4 +1,4 @@ -/* insert.c -- vector - flexible array */ +/* datastruct/vector/insert.c -- flexible array */ #include <assert.h> #include <stdlib.h> diff --git a/libraries/datastruct/vector/length.c b/libraries/datastruct/vector/length.c index 87e33c8..29a17a2 100644 --- a/libraries/datastruct/vector/length.c +++ b/libraries/datastruct/vector/length.c @@ -1,4 +1,4 @@ -/* length.c -- vector - flexible array */ +/* datastruct/vector/length.c -- flexible array */ #include <stdlib.h> diff --git a/libraries/datastruct/vector/set-length.c b/libraries/datastruct/vector/set-length.c index 3e9a0d8..4d20b92 100644 --- a/libraries/datastruct/vector/set-length.c +++ b/libraries/datastruct/vector/set-length.c @@ -1,4 +1,4 @@ -/* set-length.c -- vector - flexible array */ +/* datastruct/vector/set-length.c -- flexible array */ #include <stdlib.h> diff --git a/libraries/datastruct/vector/set-width.c b/libraries/datastruct/vector/set-width.c index 7a222e9..26d4fd6 100644 --- a/libraries/datastruct/vector/set-width.c +++ b/libraries/datastruct/vector/set-width.c @@ -1,4 +1,4 @@ -/* set-width.c -- vector - flexible array */ +/* datastruct/vector/set-width.c -- flexible array */ #include <stdlib.h> diff --git a/libraries/datastruct/vector/set.c b/libraries/datastruct/vector/set.c index 7f5e5c9..0a2a39d 100644 --- a/libraries/datastruct/vector/set.c +++ b/libraries/datastruct/vector/set.c @@ -1,4 +1,4 @@ -/* set.c -- vector - flexible array */ +/* datastruct/vector/set.c -- flexible array */ #include <assert.h> #include <string.h> diff --git a/libraries/datastruct/vector/test/vector-test.c b/libraries/datastruct/vector/test/vector-test.c index 2b7ec06..48fba96 100644 --- a/libraries/datastruct/vector/test/vector-test.c +++ b/libraries/datastruct/vector/test/vector-test.c @@ -1,3 +1,4 @@ +/* datastruct/vector/test/vector-test.c */ #include <stdio.h> diff --git a/libraries/datastruct/vector/width.c b/libraries/datastruct/vector/width.c index 2493051..4d3081b 100644 --- a/libraries/datastruct/vector/width.c +++ b/libraries/datastruct/vector/width.c @@ -1,4 +1,4 @@ -/* width.c -- vector - flexible array */ +/* datastruct/vector/width.c -- flexible array */ #include <stdlib.h> diff --git a/libraries/framebuf/bitmap/bitmap.c b/libraries/framebuf/bitmap/bitmap.c index af839a2..bef5f88 100644 --- a/libraries/framebuf/bitmap/bitmap.c +++ b/libraries/framebuf/bitmap/bitmap.c @@ -1,4 +1,4 @@ -/* bitmap.c */ +/* framebuf/bitmap/bitmap.c */ #include <assert.h> #include <stddef.h> diff --git a/libraries/framebuf/bitmap/load.c b/libraries/framebuf/bitmap/load.c index b4b3fba..85f3be7 100644 --- a/libraries/framebuf/bitmap/load.c +++ b/libraries/framebuf/bitmap/load.c @@ -1,4 +1,4 @@ -/* load.c */ +/* framebuf/bitmap/load.c */ #include <assert.h> #include <stdlib.h> diff --git a/libraries/framebuf/bitmap/save.c b/libraries/framebuf/bitmap/save.c index 7be936e..4de7ee2 100755 --- a/libraries/framebuf/bitmap/save.c +++ b/libraries/framebuf/bitmap/save.c @@ -1,4 +1,4 @@ -/* save.c */ +/* framebuf/bitmap/save.c */ #include <assert.h> #include <stdlib.h> diff --git a/libraries/framebuf/bmfont/bmfont.c b/libraries/framebuf/bmfont/bmfont.c index f7581eb..534c999 100644 --- a/libraries/framebuf/bmfont/bmfont.c +++ b/libraries/framebuf/bmfont/bmfont.c @@ -1,4 +1,4 @@ -/* bmfont.c */ +/* framebuf/bmfont/bmfont.c */ /* TODOS / IDEAS * diff --git a/libraries/framebuf/bmfont/test/bmfont-test.c b/libraries/framebuf/bmfont/test/bmfont-test.c index c9caed0..86457d8 100644 --- a/libraries/framebuf/bmfont/test/bmfont-test.c +++ b/libraries/framebuf/bmfont/test/bmfont-test.c @@ -1,4 +1,4 @@ -/* bmfont-test.c */ +/* framebuf/bmfont/test/bmfont-test.c */ #include <assert.h> #include <ctype.h> diff --git a/libraries/framebuf/colour/colour.c b/libraries/framebuf/colour/colour.c index 1b77838..e7b58b8 100644 --- a/libraries/framebuf/colour/colour.c +++ b/libraries/framebuf/colour/colour.c @@ -1,4 +1,4 @@ -/* colour.c */ +/* framebuf/colour/colour.c */ #include <assert.h> #include <stddef.h> diff --git a/libraries/framebuf/composite/composite.c b/libraries/framebuf/composite/composite.c index 3aa29ac..b2859cd 100644 --- a/libraries/framebuf/composite/composite.c +++ b/libraries/framebuf/composite/composite.c @@ -1,4 +1,4 @@ -/* composite.c +/* framebuf/composite/composite.c * * An implementation of Porter-Duff image compositing. * diff --git a/libraries/framebuf/composite/test/composite-test.c b/libraries/framebuf/composite/test/composite-test.c index fdb0fe9..eac2cdc 100644 --- a/libraries/framebuf/composite/test/composite-test.c +++ b/libraries/framebuf/composite/test/composite-test.c @@ -1,4 +1,4 @@ -/* composite-test.c */ +/* framebuf/composite/test/composite-test.c */ #include <assert.h> #include <stdio.h> diff --git a/libraries/framebuf/curve/curve.c b/libraries/framebuf/curve/curve.c index fd244d1..dc74c5e 100644 --- a/libraries/framebuf/curve/curve.c +++ b/libraries/framebuf/curve/curve.c @@ -1,4 +1,4 @@ -/* bezier.c */ +/* framebuf/curve/curve.c */ // TODO // diff --git a/libraries/framebuf/curve/test/curve-test.c b/libraries/framebuf/curve/test/curve-test.c index 1c8e7f2..2a92644 100644 --- a/libraries/framebuf/curve/test/curve-test.c +++ b/libraries/framebuf/curve/test/curve-test.c @@ -1,4 +1,4 @@ -/* curve-test.c */ +/* framebuf/curve/test/curve-test.c */ #include <assert.h> #include <ctype.h> diff --git a/libraries/framebuf/palettes/palettes.c b/libraries/framebuf/palettes/palettes.c index 2f1b79b..ac3b258 100644 --- a/libraries/framebuf/palettes/palettes.c +++ b/libraries/framebuf/palettes/palettes.c @@ -1,4 +1,4 @@ -/* palettes.c */ +/* framebuf/palettes/palettes.c */ #include "framebuf/colour.h" diff --git a/libraries/framebuf/pixelfmt/log2bpp.c b/libraries/framebuf/pixelfmt/log2bpp.c index dbc78a3..1d59351 100644 --- a/libraries/framebuf/pixelfmt/log2bpp.c +++ b/libraries/framebuf/pixelfmt/log2bpp.c @@ -1,4 +1,4 @@ -/* log2bpp.c -- given a pixel format return its log2 bits-per-pixel size */ +/* framebuf/pixelfmt/log2bpp.c -- given a pixel format return its log2 bits-per-pixel size */ #include <assert.h> diff --git a/libraries/framebuf/screen/screen-copy-rect.c b/libraries/framebuf/screen/screen-copy-rect.c index 5ae5231..46c5f55 100644 --- a/libraries/framebuf/screen/screen-copy-rect.c +++ b/libraries/framebuf/screen/screen-copy-rect.c @@ -1,4 +1,4 @@ -/* screen-copy-rect.c -- screen - same-screen rectangle copy */ +/* framebuf/screen/screen-copy-rect.c -- same-screen rectangle copy */ #include <string.h> diff --git a/libraries/framebuf/screen/screen-draw-ninepatch.c b/libraries/framebuf/screen/screen-draw-ninepatch.c index b0b7a79..6dac660 100644 --- a/libraries/framebuf/screen/screen-draw-ninepatch.c +++ b/libraries/framebuf/screen/screen-draw-ninepatch.c @@ -1,4 +1,4 @@ -/* screen-draw-ninepatch.c -- 9-patch bitmap drawing */ +/* framebuf/screen/screen-draw-ninepatch.c -- 9-patch bitmap drawing */ #include <assert.h> diff --git a/libraries/framebuf/screen/screen-draw.c b/libraries/framebuf/screen/screen-draw.c index 40a2f01..5c77046 100644 --- a/libraries/framebuf/screen/screen-draw.c +++ b/libraries/framebuf/screen/screen-draw.c @@ -1,4 +1,4 @@ -/* screen-draw.c */ +/* framebuf/screen/screen-draw.c */ #include <assert.h> #include <string.h> diff --git a/libraries/framebuf/screen/screen-fill-pattern.c b/libraries/framebuf/screen/screen-fill-pattern.c index d0613b6..47fda84 100644 --- a/libraries/framebuf/screen/screen-fill-pattern.c +++ b/libraries/framebuf/screen/screen-fill-pattern.c @@ -1,4 +1,4 @@ -/* screen-fill-pattern.c -- fill a box with a repeating 8x8 two-colour pattern */ +/* framebuf/screen/screen-fill-pattern.c -- fill a box with a repeating 8x8 two-colour pattern */ #include <assert.h> #include <string.h> diff --git a/libraries/framebuf/screen/screen.c b/libraries/framebuf/screen/screen.c index e15fafd..7cac1f5 100644 --- a/libraries/framebuf/screen/screen.c +++ b/libraries/framebuf/screen/screen.c @@ -1,4 +1,4 @@ -/* screen.c */ +/* framebuf/screen/screen.c */ #include <assert.h> #include <string.h> diff --git a/libraries/framebuf/screen/test/screen-test.c b/libraries/framebuf/screen/test/screen-test.c index 3d903ef..8cf267f 100644 --- a/libraries/framebuf/screen/test/screen-test.c +++ b/libraries/framebuf/screen/test/screen-test.c @@ -1,4 +1,4 @@ -/* screen-test.c -- test screen drawing */ +/* framebuf/screen/test/screen-test.c -- test screen drawing */ #include <stdio.h> #include <string.h> diff --git a/libraries/framebuf/span-registry/get.c b/libraries/framebuf/span-registry/get.c index b68e550..52f99df 100644 --- a/libraries/framebuf/span-registry/get.c +++ b/libraries/framebuf/span-registry/get.c @@ -1,4 +1,4 @@ -/* get.c */ +/* framebuf/span-registry/get.c */ #include <stddef.h> diff --git a/libraries/framebuf/span-registry/regdata.h b/libraries/framebuf/span-registry/regdata.h index aae1c27..37b3fe1 100644 --- a/libraries/framebuf/span-registry/regdata.h +++ b/libraries/framebuf/span-registry/regdata.h @@ -1,4 +1,4 @@ -/* regdata.h */ +/* framebuf/span-registry/regdata.h */ #ifndef SPAN_REGISTRY_REGDATA_H #define SPAN_REGISTRY_REGDATA_H diff --git a/libraries/framebuf/span/all8888-generic.c b/libraries/framebuf/span/all8888-generic.c index 0306fed..9d34b5f 100644 --- a/libraries/framebuf/span/all8888-generic.c +++ b/libraries/framebuf/span/all8888-generic.c @@ -1,4 +1,4 @@ -/* all8888-blend.c -- alpha blending common to all 8888 formats */ +/* framebuf/span/all8888-generic.c -- alpha blending common to all 8888 formats */ #include "base/utils.h" diff --git a/libraries/framebuf/span/all8888.c b/libraries/framebuf/span/all8888.c index 9f9f2f0..51922e3 100644 --- a/libraries/framebuf/span/all8888.c +++ b/libraries/framebuf/span/all8888.c @@ -1,4 +1,4 @@ -/* all8888.c */ +/* framebuf/span/all8888.c */ #include <string.h> diff --git a/libraries/framebuf/span/all8888.h b/libraries/framebuf/span/all8888.h index f5b285b..028e41c 100644 --- a/libraries/framebuf/span/all8888.h +++ b/libraries/framebuf/span/all8888.h @@ -1,4 +1,4 @@ -/* all8888.h -- span handlers common to all 8888 formats */ +/* framebuf/span/all8888.h -- span handlers common to all 8888 formats */ #ifndef SPAN_ALL8888_H #define SPAN_ALL8888_H diff --git a/libraries/framebuf/span/bgrx8888.c b/libraries/framebuf/span/bgrx8888.c index 25aaea0..bafceaf 100644 --- a/libraries/framebuf/span/bgrx8888.c +++ b/libraries/framebuf/span/bgrx8888.c @@ -1,4 +1,4 @@ -/* bgrx8888.c */ +/* framebuf/span/bgrx8888.c */ #include <string.h> diff --git a/libraries/framebuf/span/p4.c b/libraries/framebuf/span/p4.c index eb401a8..80cfca3 100644 --- a/libraries/framebuf/span/p4.c +++ b/libraries/framebuf/span/p4.c @@ -1,4 +1,4 @@ -/* p4.c -- P4 (4bpp paletted) format plot methods */ +/* framebuf/span/p4.c -- P4 (4bpp paletted) format plot methods */ #include <stddef.h> diff --git a/libraries/framebuf/span/rgbx8888.c b/libraries/framebuf/span/rgbx8888.c index 0142444..6f1efe3 100644 --- a/libraries/framebuf/span/rgbx8888.c +++ b/libraries/framebuf/span/rgbx8888.c @@ -1,4 +1,4 @@ -/* rgbx8888.c */ +/* framebuf/span/rgbx8888.c */ #include <string.h> diff --git a/libraries/framebuf/span/xbgr8888.c b/libraries/framebuf/span/xbgr8888.c index 1dc8544..a2c8acd 100644 --- a/libraries/framebuf/span/xbgr8888.c +++ b/libraries/framebuf/span/xbgr8888.c @@ -1,4 +1,4 @@ -/* xbgr8888.c */ +/* framebuf/span/xbgr8888.c */ #include <string.h> diff --git a/libraries/geom/box/clipped.c b/libraries/geom/box/clipped.c index 0348ba9..fc575f6 100644 --- a/libraries/geom/box/clipped.c +++ b/libraries/geom/box/clipped.c @@ -1,4 +1,4 @@ -/* clipped.c -- calculate clipped away edge sizes */ +/* geom/box/clipped.c -- calculate clipped away edge sizes */ #include "base/utils.h" diff --git a/libraries/geom/box/contains-box.c b/libraries/geom/box/contains-box.c index 868ac77..83b206b 100644 --- a/libraries/geom/box/contains-box.c +++ b/libraries/geom/box/contains-box.c @@ -1,4 +1,4 @@ -/* contains-box.c -- return true if box "inside" is contained by box "outside" */ +/* geom/box/contains-box.c -- return true if box "inside" is contained by box "outside" */ #include "geom/box.h" diff --git a/libraries/geom/box/contains-point.c b/libraries/geom/box/contains-point.c index b13ebf9..650f225 100644 --- a/libraries/geom/box/contains-point.c +++ b/libraries/geom/box/contains-point.c @@ -1,4 +1,4 @@ -/* contains-point.c -- return true if "box" contains the point (x,y) */ +/* geom/box/contains-point.c -- return true if "box" contains the point (x,y) */ #include "base/utils.h" diff --git a/libraries/geom/box/could-hold.c b/libraries/geom/box/could-hold.c index 203c5a5..f787ec9 100644 --- a/libraries/geom/box/could-hold.c +++ b/libraries/geom/box/could-hold.c @@ -1,4 +1,4 @@ -/* could-hold.c -- return true if "box" can hold a box of size (w,h) */ +/* geom/box/could-hold.c -- return true if "box" can hold a box of size (w,h) */ #include "geom/box.h" diff --git a/libraries/geom/box/extend.c b/libraries/geom/box/extend.c index 39005c0..9d9f390 100644 --- a/libraries/geom/box/extend.c +++ b/libraries/geom/box/extend.c @@ -1,4 +1,4 @@ -/* extend.c -- extend box "b" to include (x,y). */ +/* geom/box/extend.c -- extend box "b" to include (x,y). */ #include <stdarg.h> diff --git a/libraries/geom/box/grow.c b/libraries/geom/box/grow.c index dfbcf6d..e07aee9 100644 --- a/libraries/geom/box/grow.c +++ b/libraries/geom/box/grow.c @@ -1,4 +1,4 @@ -/* grow.c -- increases the size of "box" by "change" */ +/* geom/box/grow.c -- increases the size of "box" by "change" */ #include "geom/box.h" diff --git a/libraries/geom/box/intersection.c b/libraries/geom/box/intersection.c index 4d1bdf7..d8af008 100644 --- a/libraries/geom/box/intersection.c +++ b/libraries/geom/box/intersection.c @@ -1,4 +1,4 @@ -/* intersection.c -- compute "c" the result of intersecting boxes "a" and "b" */ +/* geom/box/intersection.c -- compute "c" the result of intersecting boxes "a" and "b" */ #include "base/utils.h" diff --git a/libraries/geom/box/intersects.c b/libraries/geom/box/intersects.c index c9e6b68..e436000 100644 --- a/libraries/geom/box/intersects.c +++ b/libraries/geom/box/intersects.c @@ -1,4 +1,4 @@ -/* intersects.c -- return true if box "a" overlaps box "b" */ +/* geom/box/intersects.c -- return true if box "a" overlaps box "b" */ #include "geom/box.h" diff --git a/libraries/geom/box/is-empty.c b/libraries/geom/box/is-empty.c index 8fcaedb..57d5f14 100644 --- a/libraries/geom/box/is-empty.c +++ b/libraries/geom/box/is-empty.c @@ -1,4 +1,4 @@ -/* is-empty.c -- return whether the specified box is empty */ +/* geom/box/is-empty.c -- return whether the specified box is empty */ #include "geom/box.h" diff --git a/libraries/geom/box/reset.c b/libraries/geom/box/reset.c index 0a785b2..88286d2 100644 --- a/libraries/geom/box/reset.c +++ b/libraries/geom/box/reset.c @@ -1,4 +1,4 @@ -/* reset.c -- set box 'b' to be invalid */ +/* geom/box/reset.c -- set box 'b' to be invalid */ #include <limits.h> diff --git a/libraries/geom/box/round.c b/libraries/geom/box/round.c index 6979b8a..e983898 100644 --- a/libraries/geom/box/round.c +++ b/libraries/geom/box/round.c @@ -1,4 +1,4 @@ -/* round.c -- round a box's coords so they're a multiple of log2 x,y */ +/* geom/box/round.c -- round a box's coords so they're a multiple of log2 x,y */ #include "base/utils.h" diff --git a/libraries/geom/box/round4.c b/libraries/geom/box/round4.c index 89ed7bf..66ee929 100644 --- a/libraries/geom/box/round4.c +++ b/libraries/geom/box/round4.c @@ -1,4 +1,4 @@ -/* round4.c -- round a box's coords so they're a multiple of 4 */ +/* geom/box/round4.c -- round a box's coords so they're a multiple of 4 */ #include "geom/box.h" diff --git a/libraries/geom/box/size.c b/libraries/geom/box/size.c index d5b0fd2..4a4d771 100644 --- a/libraries/geom/box/size.c +++ b/libraries/geom/box/size.c @@ -1,4 +1,4 @@ -/* size.c -- return the size of the specified box */ +/* geom/box/size.c -- return the size of the specified box */ #include "geom/box.h" #include "geom/size.h" diff --git a/libraries/geom/box/test/box-test.c b/libraries/geom/box/test/box-test.c index efd8c69..9b474ab 100644 --- a/libraries/geom/box/test/box-test.c +++ b/libraries/geom/box/test/box-test.c @@ -1,3 +1,4 @@ +/* geom/box/test/box-test.c */ #include <limits.h> #include <stdio.h> diff --git a/libraries/geom/box/translated.c b/libraries/geom/box/translated.c index f8d2d19..c84d56d 100644 --- a/libraries/geom/box/translated.c +++ b/libraries/geom/box/translated.c @@ -1,4 +1,4 @@ -/* translated.c -- translate box "b" by (x,y) producing new box "t" */ +/* geom/box/translated.c -- translate box "b" by (x,y) producing new box "t" */ #include "base/utils.h" diff --git a/libraries/geom/box/union.c b/libraries/geom/box/union.c index 36b431f..9944ffe 100644 --- a/libraries/geom/box/union.c +++ b/libraries/geom/box/union.c @@ -1,4 +1,4 @@ -/* union.c -- return a box "c" that contains both boxes "a" and "b" */ +/* geom/box/union.c -- return a box "c" that contains both boxes "a" and "b" */ #include "base/utils.h" diff --git a/libraries/geom/layout/layout.c b/libraries/geom/layout/layout.c index 3908150..2c23fe1 100644 --- a/libraries/geom/layout/layout.c +++ b/libraries/geom/layout/layout.c @@ -1,4 +1,4 @@ -/* layout.c -- laying out elements using the packer */ +/* geom/layout/layout.c -- laying out elements using the packer */ #include <stddef.h> diff --git a/libraries/geom/layout/test/layout-test.c b/libraries/geom/layout/test/layout-test.c index b1db833..cad978f 100644 --- a/libraries/geom/layout/test/layout-test.c +++ b/libraries/geom/layout/test/layout-test.c @@ -1,3 +1,4 @@ +/* geom/layout/test/layout-test.c */ #include <limits.h> #include <stdio.h> diff --git a/libraries/geom/line/line.c b/libraries/geom/line/line.c index 107b84a..3d925bb 100644 --- a/libraries/geom/line/line.c +++ b/libraries/geom/line/line.c @@ -1,4 +1,4 @@ -/* line.c -- Cohen-Sutherland line clipping algorithm */ +/* geom/line/line.c -- Cohen-Sutherland line clipping algorithm */ #include "base/utils.h" #include "geom/box.h" diff --git a/libraries/geom/packer/impl.h b/libraries/geom/packer/impl.h index e17c62c..119ce17 100644 --- a/libraries/geom/packer/impl.h +++ b/libraries/geom/packer/impl.h @@ -1,4 +1,4 @@ -/* impl.h -- box packing for layout */ +/* geom/packer/impl.h -- box packing for layout */ #ifndef IMPL_H #define IMPL_H diff --git a/libraries/geom/packer/packer.c b/libraries/geom/packer/packer.c index b0715b1..324919f 100644 --- a/libraries/geom/packer/packer.c +++ b/libraries/geom/packer/packer.c @@ -1,4 +1,4 @@ -/* packer.c -- box packing for layout */ +/* geom/packer/packer.c -- box packing for layout */ #include <assert.h> #include <limits.h> diff --git a/libraries/geom/packer/test/packer-test.c b/libraries/geom/packer/test/packer-test.c index dcece14..ac0015b 100644 --- a/libraries/geom/packer/test/packer-test.c +++ b/libraries/geom/packer/test/packer-test.c @@ -1,3 +1,4 @@ +/* geom/packer/test/packer-test.c */ #include <limits.h> #include <stdio.h> diff --git a/libraries/io/path/path.c b/libraries/io/path/path.c index d1bb657..42acdc1 100644 --- a/libraries/io/path/path.c +++ b/libraries/io/path/path.c @@ -1,4 +1,4 @@ -/* path.c */ +/* io/path/path.c */ #include <assert.h> #include <stdio.h> diff --git a/libraries/io/stream/stream-mem.c b/libraries/io/stream/stream-mem.c index c490504..81ac0c0 100644 --- a/libraries/io/stream/stream-mem.c +++ b/libraries/io/stream/stream-mem.c @@ -1,4 +1,4 @@ -/* stream-mem.c -- memory block IO stream implementation */ +/* io/stream/stream-mem.c -- memory block IO stream implementation */ #include <assert.h> #include <stddef.h> diff --git a/libraries/io/stream/stream-mtfcomp.c b/libraries/io/stream/stream-mtfcomp.c index 3c0ea8b..e897159 100644 --- a/libraries/io/stream/stream-mtfcomp.c +++ b/libraries/io/stream/stream-mtfcomp.c @@ -1,4 +1,4 @@ -/* stream-mtfcomp.c -- "Move to front" adaptive compression stream */ +/* io/stream/stream-mtfcomp.c -- "Move to front" adaptive compression stream */ #include <assert.h> #include <stddef.h> diff --git a/libraries/io/stream/stream-packbitscomp.c b/libraries/io/stream/stream-packbitscomp.c index a9630c4..3cb529a 100644 --- a/libraries/io/stream/stream-packbitscomp.c +++ b/libraries/io/stream/stream-packbitscomp.c @@ -1,4 +1,4 @@ -/* stream-packbitscomp.c -- PackBits compression */ +/* io/stream/stream-packbitscomp.c -- PackBits compression */ #include <assert.h> #include <stddef.h> diff --git a/libraries/io/stream/stream-packbitsdecomp.c b/libraries/io/stream/stream-packbitsdecomp.c index d4a7500..7828e41 100644 --- a/libraries/io/stream/stream-packbitsdecomp.c +++ b/libraries/io/stream/stream-packbitsdecomp.c @@ -1,4 +1,4 @@ -/* stream-packbitsdecomp.c -- PackBits decompression */ +/* io/stream/stream-packbitsdecomp.c -- PackBits decompression */ #include <assert.h> #include <stddef.h> diff --git a/libraries/io/stream/stream-stdio.c b/libraries/io/stream/stream-stdio.c index 8d6074b..8ed97dc 100644 --- a/libraries/io/stream/stream-stdio.c +++ b/libraries/io/stream/stream-stdio.c @@ -1,4 +1,4 @@ -/* stream-stdio.c -- C standard IO stream implementation */ +/* io/stream/stream-stdio.c -- C standard IO stream implementation */ #include <assert.h> #include <stddef.h> diff --git a/libraries/io/stream/stream.c b/libraries/io/stream/stream.c index 6101315..b28966f 100644 --- a/libraries/io/stream/stream.c +++ b/libraries/io/stream/stream.c @@ -1,4 +1,4 @@ -/* stream.c -- stream system support functions */ +/* io/stream/stream.c -- stream system support functions */ #include <assert.h> #include <stdio.h> diff --git a/libraries/io/stream/test/stream-test.c b/libraries/io/stream/test/stream-test.c index d100a67..696dc19 100644 --- a/libraries/io/stream/test/stream-test.c +++ b/libraries/io/stream/test/stream-test.c @@ -1,3 +1,4 @@ +/* io/stream/test/stream-test.c */ #include <assert.h> #include <stdio.h> diff --git a/libraries/test/txtscr/txtscr.c b/libraries/test/txtscr/txtscr.c index 2a0c537..d567086 100644 --- a/libraries/test/txtscr/txtscr.c +++ b/libraries/test/txtscr/txtscr.c @@ -1,4 +1,4 @@ -/* txtscr.c -- text format 'screen' */ +/* test/txtscr/txtscr.c -- text format 'screen' */ #include <stdio.h> #include <stdlib.h> diff --git a/libraries/text/bmtext/draw.c b/libraries/text/bmtext/draw.c index e8d9eba..1f50835 100644 --- a/libraries/text/bmtext/draw.c +++ b/libraries/text/bmtext/draw.c @@ -1,4 +1,4 @@ -/* draw.c -- draw pre-laid-out bitmap-font lines */ +/* text/bmtext/draw.c -- draw pre-laid-out bitmap-font lines */ #include <stddef.h> diff --git a/libraries/text/bmtext/layout.c b/libraries/text/bmtext/layout.c index 2470d8c..b6f30b6 100644 --- a/libraries/text/bmtext/layout.c +++ b/libraries/text/bmtext/layout.c @@ -1,4 +1,4 @@ -/* layout.c -- break a string into pixel-fitted lines */ +/* text/bmtext/layout.c -- break a string into pixel-fitted lines */ #include <ctype.h> diff --git a/libraries/text/txtfmt/create.c b/libraries/text/txtfmt/create.c index 19f2999..47516ba 100644 --- a/libraries/text/txtfmt/create.c +++ b/libraries/text/txtfmt/create.c @@ -1,4 +1,4 @@ -/* create.c -- txtfmt - text formatting */ +/* text/txtfmt/create.c -- text formatting */ #include <stdlib.h> #include <string.h> diff --git a/libraries/text/txtfmt/destroy.c b/libraries/text/txtfmt/destroy.c index b176e22..aaed4e3 100644 --- a/libraries/text/txtfmt/destroy.c +++ b/libraries/text/txtfmt/destroy.c @@ -1,4 +1,4 @@ -/* destroy.c -- txtfmt - text formatting */ +/* text/txtfmt/destroy.c -- text formatting */ #include <stdlib.h> diff --git a/libraries/text/txtfmt/get-length.c b/libraries/text/txtfmt/get-length.c index 21e0a23..b3e2fd6 100644 --- a/libraries/text/txtfmt/get-length.c +++ b/libraries/text/txtfmt/get-length.c @@ -1,4 +1,4 @@ -/* get-length.c -- txtfmt - text formatting */ +/* text/txtfmt/get-length.c -- text formatting */ #include "text/txtfmt.h" diff --git a/libraries/text/txtfmt/get-line.c b/libraries/text/txtfmt/get-line.c index 4be628f..247bd8f 100644 --- a/libraries/text/txtfmt/get-line.c +++ b/libraries/text/txtfmt/get-line.c @@ -1,4 +1,4 @@ -/* get-line.c -- txtfmt - text formatting */ +/* text/txtfmt/get-line.c -- text formatting */ #include "text/txtfmt.h" diff --git a/libraries/text/txtfmt/get-nlines.c b/libraries/text/txtfmt/get-nlines.c index 03c0ba6..c9bc341 100644 --- a/libraries/text/txtfmt/get-nlines.c +++ b/libraries/text/txtfmt/get-nlines.c @@ -1,4 +1,4 @@ -/* get-nlines.c -- txtfmt - text formatting */ +/* text/txtfmt/get-nlines.c -- text formatting */ #include "text/txtfmt.h" diff --git a/libraries/text/txtfmt/get-wrapped-width.c b/libraries/text/txtfmt/get-wrapped-width.c index 06f1d8c..2b104cd 100644 --- a/libraries/text/txtfmt/get-wrapped-width.c +++ b/libraries/text/txtfmt/get-wrapped-width.c @@ -1,4 +1,4 @@ -/* get-wrapped-width.c -- txtfmt - text formatting */ +/* text/txtfmt/get-wrapped-width.c -- text formatting */ #include "text/txtfmt.h" diff --git a/libraries/text/txtfmt/impl.h b/libraries/text/txtfmt/impl.h index 7a4ced4..3725bfa 100644 --- a/libraries/text/txtfmt/impl.h +++ b/libraries/text/txtfmt/impl.h @@ -1,4 +1,4 @@ -/* impl.h -- txtfmt - text formatting */ +/* text/txtfmt/impl.h -- text formatting */ #ifndef IMPL_H #define IMPL_H diff --git a/libraries/text/txtfmt/print.c b/libraries/text/txtfmt/print.c index a6a898e..13ef73d 100644 --- a/libraries/text/txtfmt/print.c +++ b/libraries/text/txtfmt/print.c @@ -1,4 +1,4 @@ -/* print.c -- txtfmt - text formatting */ +/* text/txtfmt/print.c -- text formatting */ #include <stdio.h> diff --git a/libraries/text/txtfmt/test/txtfmt-test.c b/libraries/text/txtfmt/test/txtfmt-test.c index 9cc0edb..a1f28b5 100644 --- a/libraries/text/txtfmt/test/txtfmt-test.c +++ b/libraries/text/txtfmt/test/txtfmt-test.c @@ -1,4 +1,4 @@ -/* txtfmt-test.c -- txtfmt - text formatting */ +/* text/txtfmt/test/txtfmt-test.c -- text formatting */ #include <stdio.h> diff --git a/libraries/text/txtfmt/wrap.c b/libraries/text/txtfmt/wrap.c index dc7632e..8d64842 100644 --- a/libraries/text/txtfmt/wrap.c +++ b/libraries/text/txtfmt/wrap.c @@ -1,4 +1,4 @@ -/* wrap.c -- txtfmt - text formatting */ +/* text/txtfmt/wrap.c -- text formatting */ #include <stdlib.h> #include <string.h> diff --git a/libraries/utils/array/delelem.c b/libraries/utils/array/delelem.c index 35493cc..5f5d55e 100644 --- a/libraries/utils/array/delelem.c +++ b/libraries/utils/array/delelem.c @@ -1,3 +1,4 @@ +/* utils/array/delelem.c */ #include <stdlib.h> #include <string.h> diff --git a/libraries/utils/array/delelems.c b/libraries/utils/array/delelems.c index aac0064..2b3ad7f 100644 --- a/libraries/utils/array/delelems.c +++ b/libraries/utils/array/delelems.c @@ -1,3 +1,4 @@ +/* utils/array/delelems.c */ #include <stdlib.h> #include <string.h> diff --git a/libraries/utils/array/grow.c b/libraries/utils/array/grow.c index 4295290..5dbb51a 100644 --- a/libraries/utils/array/grow.c +++ b/libraries/utils/array/grow.c @@ -1,4 +1,4 @@ -/* grow.c -- grow an array */ +/* utils/array/grow.c -- grow an array */ #include <assert.h> #include <stdlib.h> diff --git a/libraries/utils/array/shrink.c b/libraries/utils/array/shrink.c index 0b8de40..98828cd 100644 --- a/libraries/utils/array/shrink.c +++ b/libraries/utils/array/shrink.c @@ -1,4 +1,4 @@ -/* shrink.c -- shrink wrap an array */ +/* utils/array/shrink.c -- shrink wrap an array */ #include <assert.h> #include <stdlib.h> diff --git a/libraries/utils/array/squeeze.c b/libraries/utils/array/squeeze.c index eef61de..d4a7193 100644 --- a/libraries/utils/array/squeeze.c +++ b/libraries/utils/array/squeeze.c @@ -1,3 +1,4 @@ +/* utils/array/squeeze.c */ #include <assert.h> #include <string.h> diff --git a/libraries/utils/array/stretch.c b/libraries/utils/array/stretch.c index b106400..eac7377 100644 --- a/libraries/utils/array/stretch.c +++ b/libraries/utils/array/stretch.c @@ -1,3 +1,4 @@ +/* utils/array/stretch.c */ #include <assert.h> #include <string.h> diff --git a/libraries/utils/array/test/array-test.c b/libraries/utils/array/test/array-test.c index 537ee49..c9e998f 100644 --- a/libraries/utils/array/test/array-test.c +++ b/libraries/utils/array/test/array-test.c @@ -1,3 +1,4 @@ +/* utils/array/test/array-test.c */ #include <stdio.h> #include <stdlib.h> diff --git a/libraries/utils/barith/barith.c b/libraries/utils/barith/barith.c index 07e95d4..711a3a1 100644 --- a/libraries/utils/barith/barith.c +++ b/libraries/utils/barith/barith.c @@ -1,4 +1,4 @@ -/* barith.c -- define all of barith */ +/* utils/barith/barith.c -- define all of barith */ #define BARITH_INLINE diff --git a/libraries/utils/bsearch/bsearch-impl.h b/libraries/utils/bsearch/bsearch-impl.h index 914142e..6ee9ec5 100644 --- a/libraries/utils/bsearch/bsearch-impl.h +++ b/libraries/utils/bsearch/bsearch-impl.h @@ -1,4 +1,4 @@ -/* bsearch-impl.h -- binary searching arrays */ +/* utils/bsearch/bsearch-impl.h -- binary searching arrays */ #if !defined(TYPE) || !defined(NAME) #error TYPE and NAME must be defined. diff --git a/libraries/utils/bsearch/bsearch-int.c b/libraries/utils/bsearch/bsearch-int.c index eb0549c..93b3fc0 100644 --- a/libraries/utils/bsearch/bsearch-int.c +++ b/libraries/utils/bsearch/bsearch-int.c @@ -1,4 +1,4 @@ -/* bsearch-int.c -- binary searching arrays */ +/* utils/bsearch/bsearch-int.c -- binary searching arrays */ #define TYPE int #define NAME bsearch_int diff --git a/libraries/utils/bsearch/bsearch-short.c b/libraries/utils/bsearch/bsearch-short.c index 528ca7d..aa7c22c 100644 --- a/libraries/utils/bsearch/bsearch-short.c +++ b/libraries/utils/bsearch/bsearch-short.c @@ -1,4 +1,4 @@ -/* bsearch-short.c -- binary searching arrays */ +/* utils/bsearch/bsearch-short.c -- binary searching arrays */ #define TYPE short #define NAME bsearch_short diff --git a/libraries/utils/bsearch/bsearch-uint.c b/libraries/utils/bsearch/bsearch-uint.c index 725cc58..1b73bd9 100644 --- a/libraries/utils/bsearch/bsearch-uint.c +++ b/libraries/utils/bsearch/bsearch-uint.c @@ -1,4 +1,4 @@ -/* bsearch-uint.c -- binary searching arrays */ +/* utils/bsearch/bsearch-uint.c -- binary searching arrays */ #define TYPE unsigned int #define NAME bsearch_uint diff --git a/libraries/utils/bsearch/bsearch-ushort.c b/libraries/utils/bsearch/bsearch-ushort.c index 0cdc930..eb30e4b 100644 --- a/libraries/utils/bsearch/bsearch-ushort.c +++ b/libraries/utils/bsearch/bsearch-ushort.c @@ -1,4 +1,4 @@ -/* bsearch-ushort.c -- binary searching arrays */ +/* utils/bsearch/bsearch-ushort.c -- binary searching arrays */ #define TYPE unsigned short #define NAME bsearch_ushort diff --git a/libraries/utils/bsearch/test/bsearch-test.c b/libraries/utils/bsearch/test/bsearch-test.c index f2798af..cbe6b7d 100644 --- a/libraries/utils/bsearch/test/bsearch-test.c +++ b/libraries/utils/bsearch/test/bsearch-test.c @@ -1,3 +1,4 @@ +/* utils/bsearch/test/bsearch-test.c */ #include <limits.h> #include <stdio.h> diff --git a/libraries/utils/bytesex/rev-l-block.c b/libraries/utils/bytesex/rev-l-block.c index dd652f0..887fa50 100644 --- a/libraries/utils/bytesex/rev-l-block.c +++ b/libraries/utils/bytesex/rev-l-block.c @@ -1,4 +1,4 @@ -/* rev-l-block.c -- reversing bytesex */ +/* utils/bytesex/rev-l-block.c -- reversing bytesex */ #include <assert.h> diff --git a/libraries/utils/bytesex/rev-l-m.c b/libraries/utils/bytesex/rev-l-m.c index 3c9befb..822fc49 100644 --- a/libraries/utils/bytesex/rev-l-m.c +++ b/libraries/utils/bytesex/rev-l-m.c @@ -1,4 +1,4 @@ -/* rev-l-m.c -- reversing bytesex */ +/* utils/bytesex/rev-l-m.c -- reversing bytesex */ #include "utils/bytesex.h" diff --git a/libraries/utils/bytesex/rev-l.c b/libraries/utils/bytesex/rev-l.c index 9124cf4..39367bc 100644 --- a/libraries/utils/bytesex/rev-l.c +++ b/libraries/utils/bytesex/rev-l.c @@ -1,4 +1,4 @@ -/* rev-l.c -- reversing bytesex */ +/* utils/bytesex/rev-l.c -- reversing bytesex */ #include "utils/bytesex.h" diff --git a/libraries/utils/bytesex/rev-s-block.c b/libraries/utils/bytesex/rev-s-block.c index 118fa58..cd24f23 100644 --- a/libraries/utils/bytesex/rev-s-block.c +++ b/libraries/utils/bytesex/rev-s-block.c @@ -1,4 +1,4 @@ -/* rev-s-block.c -- reversing bytesex */ +/* utils/bytesex/rev-s-block.c -- reversing bytesex */ #include <assert.h> diff --git a/libraries/utils/bytesex/rev-s-m.c b/libraries/utils/bytesex/rev-s-m.c index d5bf7e4..8b181e3 100644 --- a/libraries/utils/bytesex/rev-s-m.c +++ b/libraries/utils/bytesex/rev-s-m.c @@ -1,4 +1,4 @@ -/* rev-s-m.c -- reversing bytesex */ +/* utils/bytesex/rev-s-m.c -- reversing bytesex */ #include "utils/bytesex.h" diff --git a/libraries/utils/bytesex/rev-s-pair-m.c b/libraries/utils/bytesex/rev-s-pair-m.c index c4f0327..2310477 100644 --- a/libraries/utils/bytesex/rev-s-pair-m.c +++ b/libraries/utils/bytesex/rev-s-pair-m.c @@ -1,4 +1,4 @@ -/* rev-s-pair-m.c -- reversing bytesex */ +/* utils/bytesex/rev-s-pair-m.c -- reversing bytesex */ #include "utils/bytesex.h" diff --git a/libraries/utils/bytesex/rev-s-pair.c b/libraries/utils/bytesex/rev-s-pair.c index b251a68..c98cab4 100644 --- a/libraries/utils/bytesex/rev-s-pair.c +++ b/libraries/utils/bytesex/rev-s-pair.c @@ -1,4 +1,4 @@ -/* rev-s-pair.c -- reversing bytesex */ +/* utils/bytesex/rev-s-pair.c -- reversing bytesex */ #include "utils/bytesex.h" diff --git a/libraries/utils/bytesex/rev-s.c b/libraries/utils/bytesex/rev-s.c index 392e910..646e0da 100644 --- a/libraries/utils/bytesex/rev-s.c +++ b/libraries/utils/bytesex/rev-s.c @@ -1,4 +1,4 @@ -/* rev-s.c -- reversing bytesex */ +/* utils/bytesex/rev-s.c -- reversing bytesex */ #include "utils/bytesex.h" diff --git a/libraries/utils/bytesex/util.h b/libraries/utils/bytesex/util.h index f41523e..c6b153a 100644 --- a/libraries/utils/bytesex/util.h +++ b/libraries/utils/bytesex/util.h @@ -1,4 +1,4 @@ -/* util.h -- utils for reversing bytesex */ +/* utils/bytesex/util.h -- utils for reversing bytesex */ #include <limits.h> diff --git a/libraries/utils/fxp/smull-fxp16.c b/libraries/utils/fxp/smull-fxp16.c index 6c4509a..44ecd19 100644 --- a/libraries/utils/fxp/smull-fxp16.c +++ b/libraries/utils/fxp/smull-fxp16.c @@ -1,4 +1,4 @@ -/* smull-fxp16.c -- fixed point helpers */ +/* utils/fxp/smull-fxp16.c -- fixed point helpers */ #include "utils/fxp.h" diff --git a/libraries/utils/fxp/umull-fxp16.c b/libraries/utils/fxp/umull-fxp16.c index e15a846..3037730 100644 --- a/libraries/utils/fxp/umull-fxp16.c +++ b/libraries/utils/fxp/umull-fxp16.c @@ -1,4 +1,4 @@ -/* umull-fxp16.c -- fixed point helpers */ +/* utils/fxp/umull-fxp16.c -- fixed point helpers */ #include "utils/fxp.h" diff --git a/libraries/utils/maths/degs-to-rads.c b/libraries/utils/maths/degs-to-rads.c index 891ffe2..1e1f0f9 100644 --- a/libraries/utils/maths/degs-to-rads.c +++ b/libraries/utils/maths/degs-to-rads.c @@ -1,4 +1,4 @@ -/* degs-to-rads.c -- degrees to radians */ +/* utils/maths/degs-to-rads.c -- degrees to radians */ #include "utils/maths.h" diff --git a/libraries/utils/maths/gcd.c b/libraries/utils/maths/gcd.c index 9216dbe..f4e2d53 100644 --- a/libraries/utils/maths/gcd.c +++ b/libraries/utils/maths/gcd.c @@ -1,4 +1,4 @@ -/* gcd.c -- greatest common divisor */ +/* utils/maths/gcd.c -- greatest common divisor */ #include "utils/maths.h" diff --git a/libraries/utils/pack/pack.c b/libraries/utils/pack/pack.c index aacce62..26dd7bb 100644 --- a/libraries/utils/pack/pack.c +++ b/libraries/utils/pack/pack.c @@ -1,4 +1,4 @@ -/* pack.c -- structure packing */ +/* utils/pack/pack.c -- structure packing */ #include <assert.h> #include <ctype.h> diff --git a/libraries/utils/pack/test/pack-test.c b/libraries/utils/pack/test/pack-test.c index bed208b..64d7e04 100644 --- a/libraries/utils/pack/test/pack-test.c +++ b/libraries/utils/pack/test/pack-test.c @@ -1,4 +1,4 @@ -/* pack-test.c -- tests for pack/unpack */ +/* utils/pack/test/pack-test.c -- tests for pack/unpack */ #include <limits.h> #include <stdio.h> diff --git a/libraries/utils/pack/unpack.c b/libraries/utils/pack/unpack.c index 01f2911..3ffec99 100644 --- a/libraries/utils/pack/unpack.c +++ b/libraries/utils/pack/unpack.c @@ -1,4 +1,4 @@ -/* unpack.c -- structure unpacking */ +/* utils/pack/unpack.c -- structure unpacking */ #include <assert.h> #include <ctype.h> diff --git a/libraries/utils/primes/primes.c b/libraries/utils/primes/primes.c index b39fcd6..9d25d61 100644 --- a/libraries/utils/primes/primes.c +++ b/libraries/utils/primes/primes.c @@ -1,4 +1,4 @@ -/* primes.c -- cache of prime numbers */ +/* utils/primes/primes.c -- cache of prime numbers */ #include "base/utils.h" diff --git a/libraries/wuss/backdrop.c b/libraries/wuss/backdrop.c index 7da138f..ae69186 100644 --- a/libraries/wuss/backdrop.c +++ b/libraries/wuss/backdrop.c @@ -1,4 +1,4 @@ -/* backdrop.c -- wuss - shared backdrop validation and fill */ +/* wuss/backdrop.c -- shared backdrop validation and fill */ #include "framebuf/screen.h" diff --git a/libraries/wuss/create.c b/libraries/wuss/create.c index f1282b8..ad11546 100644 --- a/libraries/wuss/create.c +++ b/libraries/wuss/create.c @@ -1,4 +1,4 @@ -/* create.c -- wuss - minimal window manager */ +/* wuss/create.c -- wuss - minimal window manager */ #include <assert.h> #include <stdlib.h> diff --git a/libraries/wuss/destroy.c b/libraries/wuss/destroy.c index 9ea3327..dd974e5 100644 --- a/libraries/wuss/destroy.c +++ b/libraries/wuss/destroy.c @@ -1,4 +1,4 @@ -/* destroy.c -- wuss - minimal window manager */ +/* wuss/destroy.c -- wuss - minimal window manager */ #include <stdlib.h> diff --git a/libraries/wuss/furniture.h b/libraries/wuss/furniture.h index 1a90f18..22744f0 100644 --- a/libraries/wuss/furniture.h +++ b/libraries/wuss/furniture.h @@ -1,4 +1,4 @@ -/* furniture.h -- wuss - minimal window manager */ +/* wuss/furniture.h -- wuss - minimal window manager */ #ifndef WUSS_FURNITURE_IMPL_H #define WUSS_FURNITURE_IMPL_H diff --git a/libraries/wuss/furniture/back-box.c b/libraries/wuss/furniture/back-box.c index deab123..c2d92f2 100644 --- a/libraries/wuss/furniture/back-box.c +++ b/libraries/wuss/furniture/back-box.c @@ -1,4 +1,4 @@ -/* back-box.c -- wuss - minimal window manager */ +/* wuss/furniture/back-box.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/furniture/close-box.c b/libraries/wuss/furniture/close-box.c index 424af7c..3972fd0 100644 --- a/libraries/wuss/furniture/close-box.c +++ b/libraries/wuss/furniture/close-box.c @@ -1,4 +1,4 @@ -/* close-box.c -- wuss - minimal window manager */ +/* wuss/furniture/close-box.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/furniture/content-box.c b/libraries/wuss/furniture/content-box.c index 72f379e..14249a4 100644 --- a/libraries/wuss/furniture/content-box.c +++ b/libraries/wuss/furniture/content-box.c @@ -1,4 +1,4 @@ -/* content-box.c -- wuss - minimal window manager */ +/* wuss/furniture/content-box.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/furniture/drag-resize.c b/libraries/wuss/furniture/drag-resize.c index 9a92a3a..3e0d02c 100644 --- a/libraries/wuss/furniture/drag-resize.c +++ b/libraries/wuss/furniture/drag-resize.c @@ -1,4 +1,4 @@ -/* drag-resize.c -- wuss - minimal window manager */ +/* wuss/furniture/drag-resize.c -- wuss - minimal window manager */ #include "base/utils.h" diff --git a/libraries/wuss/furniture/draw.c b/libraries/wuss/furniture/draw.c index a30cb44..98a7c65 100644 --- a/libraries/wuss/furniture/draw.c +++ b/libraries/wuss/furniture/draw.c @@ -1,4 +1,4 @@ -/* draw.c -- wuss - minimal window manager */ +/* wuss/furniture/draw.c -- wuss - minimal window manager */ #include <string.h> diff --git a/libraries/wuss/furniture/hit-test.c b/libraries/wuss/furniture/hit-test.c index d6e0705..15eb197 100644 --- a/libraries/wuss/furniture/hit-test.c +++ b/libraries/wuss/furniture/hit-test.c @@ -1,4 +1,4 @@ -/* hit-test.c -- wuss - minimal window manager */ +/* wuss/furniture/hit-test.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/furniture/hscroll-box.c b/libraries/wuss/furniture/hscroll-box.c index ce452db..b3b2cf8 100644 --- a/libraries/wuss/furniture/hscroll-box.c +++ b/libraries/wuss/furniture/hscroll-box.c @@ -1,4 +1,4 @@ -/* hscroll-box.c -- wuss - minimal window manager */ +/* wuss/furniture/hscroll-box.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/furniture/invalidate.c b/libraries/wuss/furniture/invalidate.c index dc2e3ba..0b7d210 100644 --- a/libraries/wuss/furniture/invalidate.c +++ b/libraries/wuss/furniture/invalidate.c @@ -1,4 +1,4 @@ -/* invalidate.c -- wuss - minimal window manager */ +/* wuss/furniture/invalidate.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/furniture/resize-box.c b/libraries/wuss/furniture/resize-box.c index 143ab98..789a2e6 100644 --- a/libraries/wuss/furniture/resize-box.c +++ b/libraries/wuss/furniture/resize-box.c @@ -1,4 +1,4 @@ -/* resize-box.c -- wuss - minimal window manager */ +/* wuss/furniture/resize-box.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/furniture/scroll-action.c b/libraries/wuss/furniture/scroll-action.c index a8965eb..8b84d75 100644 --- a/libraries/wuss/furniture/scroll-action.c +++ b/libraries/wuss/furniture/scroll-action.c @@ -1,4 +1,4 @@ -/* scroll-action.c -- wuss - minimal window manager */ +/* wuss/furniture/scroll-action.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/furniture/titlebar-box.c b/libraries/wuss/furniture/titlebar-box.c index 52a9dad..0d1faa4 100644 --- a/libraries/wuss/furniture/titlebar-box.c +++ b/libraries/wuss/furniture/titlebar-box.c @@ -1,4 +1,4 @@ -/* titlebar-box.c -- wuss - minimal window manager */ +/* wuss/furniture/titlebar-box.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/furniture/toggle-action.c b/libraries/wuss/furniture/toggle-action.c index 6a330d6..7e2724b 100644 --- a/libraries/wuss/furniture/toggle-action.c +++ b/libraries/wuss/furniture/toggle-action.c @@ -1,4 +1,4 @@ -/* toggle-action.c -- wuss - minimal window manager */ +/* wuss/furniture/toggle-action.c -- wuss - minimal window manager */ #include "base/utils.h" diff --git a/libraries/wuss/furniture/toggle-box.c b/libraries/wuss/furniture/toggle-box.c index 4d9cf3f..2a77a58 100644 --- a/libraries/wuss/furniture/toggle-box.c +++ b/libraries/wuss/furniture/toggle-box.c @@ -1,4 +1,4 @@ -/* toggle-box.c -- wuss - minimal window manager */ +/* wuss/furniture/toggle-box.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/furniture/vscroll-box.c b/libraries/wuss/furniture/vscroll-box.c index f87eda4..1d936b7 100644 --- a/libraries/wuss/furniture/vscroll-box.c +++ b/libraries/wuss/furniture/vscroll-box.c @@ -1,4 +1,4 @@ -/* vscroll-box.c -- wuss - minimal window manager */ +/* wuss/furniture/vscroll-box.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/get-font.c b/libraries/wuss/get-font.c index 206cd95..f54fabd 100644 --- a/libraries/wuss/get-font.c +++ b/libraries/wuss/get-font.c @@ -1,4 +1,4 @@ -/* get-font.c -- wuss - minimal window manager */ +/* wuss/get-font.c -- wuss - minimal window manager */ #include <assert.h> diff --git a/libraries/wuss/get-pointer.c b/libraries/wuss/get-pointer.c index ee538d8..db0b6d4 100644 --- a/libraries/wuss/get-pointer.c +++ b/libraries/wuss/get-pointer.c @@ -1,4 +1,4 @@ -/* get-pointer.c -- wuss - minimal window manager */ +/* wuss/get-pointer.c -- wuss - minimal window manager */ #include <assert.h> diff --git a/libraries/wuss/icon.h b/libraries/wuss/icon.h index 7794f4e..bc5dc48 100644 --- a/libraries/wuss/icon.h +++ b/libraries/wuss/icon.h @@ -1,4 +1,4 @@ -/* icon.h -- wuss - work-area icons, internal */ +/* wuss/icon.h -- work-area icons, internal */ #ifndef WUSS_ICON_IMPL_H #define WUSS_ICON_IMPL_H diff --git a/libraries/wuss/icon/create-array.c b/libraries/wuss/icon/create-array.c index 5da8893..07bdef4 100644 --- a/libraries/wuss/icon/create-array.c +++ b/libraries/wuss/icon/create-array.c @@ -1,4 +1,4 @@ -/* create-array.c -- wuss - create several work-area icons at once */ +/* wuss/icon/create-array.c -- create several work-area icons at once */ #include <assert.h> #include <stddef.h> diff --git a/libraries/wuss/icon/create.c b/libraries/wuss/icon/create.c index d29f815..a9d481b 100644 --- a/libraries/wuss/icon/create.c +++ b/libraries/wuss/icon/create.c @@ -1,4 +1,4 @@ -/* create.c -- wuss - create a work-area icon */ +/* wuss/icon/create.c -- create a work-area icon */ #include <assert.h> #include <stdlib.h> diff --git a/libraries/wuss/icon/delete.c b/libraries/wuss/icon/delete.c index 35d495d..097690c 100644 --- a/libraries/wuss/icon/delete.c +++ b/libraries/wuss/icon/delete.c @@ -1,4 +1,4 @@ -/* delete.c -- wuss - destroy a work-area icon */ +/* wuss/icon/delete.c -- destroy a work-area icon */ #include <stdlib.h> diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index f60220c..5f15307 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -1,4 +1,4 @@ -/* draw.c -- wuss - draw a work-area icon */ +/* wuss/icon/draw.c -- draw a work-area icon */ #include <string.h> diff --git a/libraries/wuss/icon/free.c b/libraries/wuss/icon/free.c index 2630b26..d8a0843 100644 --- a/libraries/wuss/icon/free.c +++ b/libraries/wuss/icon/free.c @@ -1,4 +1,4 @@ -/* free.c -- wuss - free a window's whole icon store */ +/* wuss/icon/free.c -- free a window's whole icon store */ #include <stdlib.h> diff --git a/libraries/wuss/icon/get-bbox.c b/libraries/wuss/icon/get-bbox.c index 55bdbd8..700f96d 100644 --- a/libraries/wuss/icon/get-bbox.c +++ b/libraries/wuss/icon/get-bbox.c @@ -1,4 +1,4 @@ -/* get-bbox.c -- wuss - read a work-area icon's bounding box */ +/* wuss/icon/get-bbox.c -- read a work-area icon's bounding box */ #include "../impl.h" diff --git a/libraries/wuss/icon/get-selected.c b/libraries/wuss/icon/get-selected.c index 6d8e06f..cb2add4 100644 --- a/libraries/wuss/icon/get-selected.c +++ b/libraries/wuss/icon/get-selected.c @@ -1,4 +1,4 @@ -/* get-selected.c -- wuss - query a radio/option icon's latched state */ +/* wuss/icon/get-selected.c -- query a radio/option icon's latched state */ #include <assert.h> diff --git a/libraries/wuss/icon/get-text.c b/libraries/wuss/icon/get-text.c index 2978cdb..783f685 100644 --- a/libraries/wuss/icon/get-text.c +++ b/libraries/wuss/icon/get-text.c @@ -1,4 +1,4 @@ -/* get-text.c -- wuss - read a work-area icon's label */ +/* wuss/icon/get-text.c -- read a work-area icon's label */ #include "../impl.h" diff --git a/libraries/wuss/icon/get-type.c b/libraries/wuss/icon/get-type.c index 84b3409..2335286 100644 --- a/libraries/wuss/icon/get-type.c +++ b/libraries/wuss/icon/get-type.c @@ -1,4 +1,4 @@ -/* get-type.c -- wuss - read a work-area icon's type */ +/* wuss/icon/get-type.c -- read a work-area icon's type */ #include "../impl.h" diff --git a/libraries/wuss/icon/get-window.c b/libraries/wuss/icon/get-window.c index 9921af6..43fa012 100644 --- a/libraries/wuss/icon/get-window.c +++ b/libraries/wuss/icon/get-window.c @@ -1,4 +1,4 @@ -/* get-window.c -- wuss - read a work-area icon's owning window */ +/* wuss/icon/get-window.c -- read a work-area icon's owning window */ #include "../impl.h" diff --git a/libraries/wuss/icon/hit-test.c b/libraries/wuss/icon/hit-test.c index 79fb799..b4045fb 100644 --- a/libraries/wuss/icon/hit-test.c +++ b/libraries/wuss/icon/hit-test.c @@ -1,4 +1,4 @@ -/* hit-test.c -- wuss - work-area icon hit testing */ +/* wuss/icon/hit-test.c -- work-area icon hit testing */ #include "geom/box.h" diff --git a/libraries/wuss/icon/invalidate.c b/libraries/wuss/icon/invalidate.c index 91c9e47..d702fb3 100644 --- a/libraries/wuss/icon/invalidate.c +++ b/libraries/wuss/icon/invalidate.c @@ -1,4 +1,4 @@ -/* invalidate.c -- wuss - mark a work-area icon's bbox dirty */ +/* wuss/icon/invalidate.c -- mark a work-area icon's bbox dirty */ #include "../impl.h" diff --git a/libraries/wuss/icon/screen-box.c b/libraries/wuss/icon/screen-box.c index 59e4e4e..f1abad2 100644 --- a/libraries/wuss/icon/screen-box.c +++ b/libraries/wuss/icon/screen-box.c @@ -1,4 +1,4 @@ -/* screen-box.c -- wuss - work-area icon bbox to screen space */ +/* wuss/icon/screen-box.c -- work-area icon bbox to screen space */ #include "geom/box.h" #include "geom/point.h" diff --git a/libraries/wuss/icon/set-hidden.c b/libraries/wuss/icon/set-hidden.c index c76e35a..7adfee0 100644 --- a/libraries/wuss/icon/set-hidden.c +++ b/libraries/wuss/icon/set-hidden.c @@ -1,4 +1,4 @@ -/* set-hidden.c -- wuss - show or hide a work-area icon */ +/* wuss/icon/set-hidden.c -- show or hide a work-area icon */ #include "../impl.h" diff --git a/libraries/wuss/icon/set-hover.c b/libraries/wuss/icon/set-hover.c index 6ae8d73..cb5deb8 100644 --- a/libraries/wuss/icon/set-hover.c +++ b/libraries/wuss/icon/set-hover.c @@ -1,4 +1,4 @@ -/* set-hover.c -- wuss - track which work-area icon the pointer is over */ +/* wuss/icon/set-hover.c -- track which work-area icon the pointer is over */ #include <stddef.h> diff --git a/libraries/wuss/icon/set-selected.c b/libraries/wuss/icon/set-selected.c index 72ec17c..ea6c356 100644 --- a/libraries/wuss/icon/set-selected.c +++ b/libraries/wuss/icon/set-selected.c @@ -1,4 +1,4 @@ -/* set-selected.c -- wuss - latch a radio/option icon's selected state */ +/* wuss/icon/set-selected.c -- latch a radio/option icon's selected state */ #include <assert.h> diff --git a/libraries/wuss/icon/set-text.c b/libraries/wuss/icon/set-text.c index 3f67a6c..2c5d517 100644 --- a/libraries/wuss/icon/set-text.c +++ b/libraries/wuss/icon/set-text.c @@ -1,4 +1,4 @@ -/* set-text.c -- wuss - replace a work-area icon's label */ +/* wuss/icon/set-text.c -- replace a work-area icon's label */ #include <stdlib.h> #include <string.h> diff --git a/libraries/wuss/idle.c b/libraries/wuss/idle.c index c8de362..e0be5fb 100644 --- a/libraries/wuss/idle.c +++ b/libraries/wuss/idle.c @@ -1,4 +1,4 @@ -/* idle.c -- wuss - minimal window manager */ +/* wuss/idle.c -- wuss - minimal window manager */ #include "impl.h" diff --git a/libraries/wuss/impl.h b/libraries/wuss/impl.h index 97eecd3..7046e5c 100644 --- a/libraries/wuss/impl.h +++ b/libraries/wuss/impl.h @@ -1,4 +1,4 @@ -/* impl.h -- wuss - minimal window manager */ +/* wuss/impl.h -- wuss - minimal window manager */ #ifndef IMPL_H #define IMPL_H diff --git a/libraries/wuss/invalidate.c b/libraries/wuss/invalidate.c index 1bca419..1b02743 100644 --- a/libraries/wuss/invalidate.c +++ b/libraries/wuss/invalidate.c @@ -1,4 +1,4 @@ -/* invalidate.c -- wuss - minimal window manager */ +/* wuss/invalidate.c -- wuss - minimal window manager */ #include <assert.h> diff --git a/libraries/wuss/menu.h b/libraries/wuss/menu.h index 4012f6d..9df3ca2 100644 --- a/libraries/wuss/menu.h +++ b/libraries/wuss/menu.h @@ -1,4 +1,4 @@ -/* menu.h -- wuss pop-up menu helper, internal */ +/* wuss/menu.h -- wuss pop-up menu helper, internal */ #ifndef WUSS_MENU_IMPL_H #define WUSS_MENU_IMPL_H diff --git a/libraries/wuss/menu/create-from-desc.c b/libraries/wuss/menu/create-from-desc.c index 94ed53a..1d8c16a 100644 --- a/libraries/wuss/menu/create-from-desc.c +++ b/libraries/wuss/menu/create-from-desc.c @@ -1,4 +1,4 @@ -/* create-from-desc.c -- wuss - build a menu tree from a descriptor string */ +/* wuss/menu/create-from-desc.c -- build a menu tree from a descriptor string */ /* The token machine (Token / Parser / getopt / getname / getnext / isdelim) * and the explicit menu stack are ported from PrivateEye's diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index a55ce44..15cdaa8 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -1,4 +1,4 @@ -/* menu.c -- wuss pop-up menu helper */ +/* wuss/menu/menu.c -- wuss pop-up menu helper */ #include <assert.h> #include <limits.h> diff --git a/libraries/wuss/mouse-click.c b/libraries/wuss/mouse-click.c index d513a7f..f6f961f 100644 --- a/libraries/wuss/mouse-click.c +++ b/libraries/wuss/mouse-click.c @@ -1,4 +1,4 @@ -/* mouse-click.c -- wuss - minimal window manager */ +/* wuss/mouse-click.c -- wuss - minimal window manager */ #include "impl.h" diff --git a/libraries/wuss/mouse-move.c b/libraries/wuss/mouse-move.c index 99f0e72..d1546b6 100644 --- a/libraries/wuss/mouse-move.c +++ b/libraries/wuss/mouse-move.c @@ -1,4 +1,4 @@ -/* mouse-move.c -- wuss - minimal window manager */ +/* wuss/mouse-move.c -- wuss - minimal window manager */ #include "impl.h" diff --git a/libraries/wuss/redraw.c b/libraries/wuss/redraw.c index aa6463b..52ad93f 100644 --- a/libraries/wuss/redraw.c +++ b/libraries/wuss/redraw.c @@ -1,4 +1,4 @@ -/* redraw.c -- wuss - minimal window manager */ +/* wuss/redraw.c -- wuss - minimal window manager */ #include "impl.h" diff --git a/libraries/wuss/scroll-step.c b/libraries/wuss/scroll-step.c index c69766e..ab01d5f 100644 --- a/libraries/wuss/scroll-step.c +++ b/libraries/wuss/scroll-step.c @@ -1,4 +1,4 @@ -/* scroll-step.c -- wuss - clamp and apply a scroll offset */ +/* wuss/scroll-step.c -- clamp and apply a scroll offset */ #include "impl.h" diff --git a/libraries/wuss/scroll.c b/libraries/wuss/scroll.c index bcabbb4..1eb54af 100644 --- a/libraries/wuss/scroll.c +++ b/libraries/wuss/scroll.c @@ -1,4 +1,4 @@ -/* scroll.c -- wuss - minimal window manager */ +/* wuss/scroll.c -- wuss - minimal window manager */ #include "impl.h" diff --git a/libraries/wuss/task/start.c b/libraries/wuss/task/start.c index 65eb812..f9736b4 100644 --- a/libraries/wuss/task/start.c +++ b/libraries/wuss/task/start.c @@ -1,4 +1,4 @@ -/* start.c -- wuss - minimal window manager */ +/* wuss/task/start.c -- wuss - minimal window manager */ #include <stddef.h> diff --git a/libraries/wuss/task/stop.c b/libraries/wuss/task/stop.c index b8cbd80..deffd84 100644 --- a/libraries/wuss/task/stop.c +++ b/libraries/wuss/task/stop.c @@ -1,4 +1,4 @@ -/* stop.c -- wuss - minimal window manager */ +/* wuss/task/stop.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/test/tasks/ball.c b/libraries/wuss/test/tasks/ball.c index 7c77ab6..e1f3497 100644 --- a/libraries/wuss/test/tasks/ball.c +++ b/libraries/wuss/test/tasks/ball.c @@ -1,4 +1,4 @@ -/* ball.c -- wuss test - bouncing ball task */ +/* wuss/test/tasks/ball.c -- bouncing ball task */ #ifdef USE_SDL diff --git a/libraries/wuss/test/tasks/ball.h b/libraries/wuss/test/tasks/ball.h index f42fb57..ac87f60 100644 --- a/libraries/wuss/test/tasks/ball.h +++ b/libraries/wuss/test/tasks/ball.h @@ -1,4 +1,4 @@ -/* ball.h -- wuss test - bouncing ball task */ +/* wuss/test/tasks/ball.h -- bouncing ball task */ #ifndef TASKS_BALL_H #define TASKS_BALL_H diff --git a/libraries/wuss/test/tasks/blank.c b/libraries/wuss/test/tasks/blank.c index b54fa4c..b97f67a 100644 --- a/libraries/wuss/test/tasks/blank.c +++ b/libraries/wuss/test/tasks/blank.c @@ -1,4 +1,4 @@ -/* blank.c -- wuss test - colour-cycling task */ +/* wuss/test/tasks/blank.c -- colour-cycling task */ #ifdef USE_SDL diff --git a/libraries/wuss/test/tasks/blank.h b/libraries/wuss/test/tasks/blank.h index b1ffd74..4473961 100644 --- a/libraries/wuss/test/tasks/blank.h +++ b/libraries/wuss/test/tasks/blank.h @@ -1,4 +1,4 @@ -/* blank.h -- wuss test - colour-cycling task */ +/* wuss/test/tasks/blank.h -- colour-cycling task */ #ifndef TASKS_BLANK_H #define TASKS_BLANK_H diff --git a/libraries/wuss/test/tasks/chars.c b/libraries/wuss/test/tasks/chars.c index 9a4b155..ec53066 100644 --- a/libraries/wuss/test/tasks/chars.c +++ b/libraries/wuss/test/tasks/chars.c @@ -1,4 +1,4 @@ -/* chars.c -- wuss test - system font glyph grid task */ +/* wuss/test/tasks/chars.c -- system font glyph grid task */ #ifdef USE_SDL diff --git a/libraries/wuss/test/tasks/chars.h b/libraries/wuss/test/tasks/chars.h index 5df4199..277b669 100644 --- a/libraries/wuss/test/tasks/chars.h +++ b/libraries/wuss/test/tasks/chars.h @@ -1,4 +1,4 @@ -/* chars.h -- wuss test - system font glyph grid task */ +/* wuss/test/tasks/chars.h -- system font glyph grid task */ #ifndef TASKS_CHARS_H #define TASKS_CHARS_H diff --git a/libraries/wuss/test/tasks/checker.c b/libraries/wuss/test/tasks/checker.c index 00d8949..04f2bfd 100644 --- a/libraries/wuss/test/tasks/checker.c +++ b/libraries/wuss/test/tasks/checker.c @@ -1,4 +1,4 @@ -/* checker.c -- wuss test - checkerboard task */ +/* wuss/test/tasks/checker.c -- checkerboard task */ #ifdef USE_SDL diff --git a/libraries/wuss/test/tasks/checker.h b/libraries/wuss/test/tasks/checker.h index ca3983c..0793713 100644 --- a/libraries/wuss/test/tasks/checker.h +++ b/libraries/wuss/test/tasks/checker.h @@ -1,4 +1,4 @@ -/* checker.h -- wuss test - checkerboard task */ +/* wuss/test/tasks/checker.h -- checkerboard task */ #ifndef TASKS_CHECKER_H #define TASKS_CHECKER_H diff --git a/libraries/wuss/test/tasks/curve.c b/libraries/wuss/test/tasks/curve.c index 74248e3..ce17708 100644 --- a/libraries/wuss/test/tasks/curve.c +++ b/libraries/wuss/test/tasks/curve.c @@ -1,4 +1,4 @@ -/* curve.c -- wuss test - draggable Bezier curve task */ +/* wuss/test/tasks/curve.c -- draggable Bezier curve task */ #ifdef USE_SDL diff --git a/libraries/wuss/test/tasks/curve.h b/libraries/wuss/test/tasks/curve.h index de7a2e8..3d3ad50 100644 --- a/libraries/wuss/test/tasks/curve.h +++ b/libraries/wuss/test/tasks/curve.h @@ -1,4 +1,4 @@ -/* curve.h -- wuss test - draggable Bezier curve task */ +/* wuss/test/tasks/curve.h -- draggable Bezier curve task */ #ifndef TASKS_CURVE_H #define TASKS_CURVE_H diff --git a/libraries/wuss/test/tasks/gradient.c b/libraries/wuss/test/tasks/gradient.c index 6f2d8e6..e536128 100644 --- a/libraries/wuss/test/tasks/gradient.c +++ b/libraries/wuss/test/tasks/gradient.c @@ -1,4 +1,4 @@ -/* gradient.c -- wuss test - gradient fill task */ +/* wuss/test/tasks/gradient.c -- gradient fill task */ #ifdef USE_SDL diff --git a/libraries/wuss/test/tasks/gradient.h b/libraries/wuss/test/tasks/gradient.h index 8f72007..9f47559 100644 --- a/libraries/wuss/test/tasks/gradient.h +++ b/libraries/wuss/test/tasks/gradient.h @@ -1,4 +1,4 @@ -/* gradient.h -- wuss test - gradient fill task */ +/* wuss/test/tasks/gradient.h -- gradient fill task */ #ifndef TASKS_GRADIENT_H #define TASKS_GRADIENT_H diff --git a/libraries/wuss/test/tasks/icons.c b/libraries/wuss/test/tasks/icons.c index 7a1dc38..5b92365 100644 --- a/libraries/wuss/test/tasks/icons.c +++ b/libraries/wuss/test/tasks/icons.c @@ -1,4 +1,4 @@ -/* icons.c -- wuss test - work-area icons task */ +/* wuss/test/tasks/icons.c -- work-area icons task */ #ifdef USE_SDL #include "framebuf/palettes.h" diff --git a/libraries/wuss/test/tasks/icons.h b/libraries/wuss/test/tasks/icons.h index 6d91d95..98ec074 100644 --- a/libraries/wuss/test/tasks/icons.h +++ b/libraries/wuss/test/tasks/icons.h @@ -1,4 +1,4 @@ -/* icons.h -- wuss test - work-area icons task */ +/* wuss/test/tasks/icons.h -- work-area icons task */ #ifndef TASKS_ICONS_H #define TASKS_ICONS_H diff --git a/libraries/wuss/test/tasks/image.c b/libraries/wuss/test/tasks/image.c index 3ef7f76..16e159b 100644 --- a/libraries/wuss/test/tasks/image.c +++ b/libraries/wuss/test/tasks/image.c @@ -1,4 +1,4 @@ -/* image.c -- wuss test - static bitmap image task */ +/* wuss/test/tasks/image.c -- static bitmap image task */ #ifdef USE_SDL diff --git a/libraries/wuss/test/tasks/image.h b/libraries/wuss/test/tasks/image.h index f62eb92..7b9ff9c 100644 --- a/libraries/wuss/test/tasks/image.h +++ b/libraries/wuss/test/tasks/image.h @@ -1,4 +1,4 @@ -/* image.h -- wuss test - static bitmap image task */ +/* wuss/test/tasks/image.h -- static bitmap image task */ #ifndef TASKS_IMAGE_H #define TASKS_IMAGE_H diff --git a/libraries/wuss/test/tasks/launcher.c b/libraries/wuss/test/tasks/launcher.c index 5cf882d..db96740 100644 --- a/libraries/wuss/test/tasks/launcher.c +++ b/libraries/wuss/test/tasks/launcher.c @@ -1,4 +1,4 @@ -/* launcher.c -- wuss test - clickable list of names that spawn other tasks */ +/* wuss/test/tasks/launcher.c -- clickable list of names that spawn other tasks */ #ifdef USE_SDL diff --git a/libraries/wuss/test/tasks/launcher.h b/libraries/wuss/test/tasks/launcher.h index 573f3e0..8dd2b5c 100644 --- a/libraries/wuss/test/tasks/launcher.h +++ b/libraries/wuss/test/tasks/launcher.h @@ -1,4 +1,4 @@ -/* launcher.h -- wuss test - clickable list of names that spawn other tasks */ +/* wuss/test/tasks/launcher.h -- clickable list of names that spawn other tasks */ #ifndef TASKS_LAUNCHER_H #define TASKS_LAUNCHER_H diff --git a/libraries/wuss/test/tasks/palette.c b/libraries/wuss/test/tasks/palette.c index ac7da4e..69cb38f 100644 --- a/libraries/wuss/test/tasks/palette.c +++ b/libraries/wuss/test/tasks/palette.c @@ -1,4 +1,4 @@ -/* palette.c -- wuss test - desktop palette swatch grid task */ +/* wuss/test/tasks/palette.c -- desktop palette swatch grid task */ #ifdef USE_SDL diff --git a/libraries/wuss/test/tasks/palette.h b/libraries/wuss/test/tasks/palette.h index 8463df9..ea8b3cc 100644 --- a/libraries/wuss/test/tasks/palette.h +++ b/libraries/wuss/test/tasks/palette.h @@ -1,4 +1,4 @@ -/* palette.h -- wuss test - desktop palette swatch grid task */ +/* wuss/test/tasks/palette.h -- desktop palette swatch grid task */ #ifndef TASKS_PALETTE_H #define TASKS_PALETTE_H diff --git a/libraries/wuss/test/tasks/porter-duff.c b/libraries/wuss/test/tasks/porter-duff.c index 525db42..d961388 100644 --- a/libraries/wuss/test/tasks/porter-duff.c +++ b/libraries/wuss/test/tasks/porter-duff.c @@ -1,4 +1,4 @@ -/* porter-duff.c -- wuss test - animated Porter-Duff compositing task */ +/* wuss/test/tasks/porter-duff.c -- animated Porter-Duff compositing task */ #ifdef USE_SDL diff --git a/libraries/wuss/test/tasks/porter-duff.h b/libraries/wuss/test/tasks/porter-duff.h index 3a7f210..361722f 100644 --- a/libraries/wuss/test/tasks/porter-duff.h +++ b/libraries/wuss/test/tasks/porter-duff.h @@ -1,4 +1,4 @@ -/* porter-duff.h -- wuss test - animated Porter-Duff compositing task */ +/* wuss/test/tasks/porter-duff.h -- animated Porter-Duff compositing task */ #ifndef TASKS_PORTER_DUFF_H #define TASKS_PORTER_DUFF_H diff --git a/libraries/wuss/test/tasks/sofa.c b/libraries/wuss/test/tasks/sofa.c index ab8d951..a8521b5 100644 --- a/libraries/wuss/test/tasks/sofa.c +++ b/libraries/wuss/test/tasks/sofa.c @@ -1,4 +1,4 @@ -/* sofa.c -- wuss test - rotating wireframe sofa and spaceship task */ +/* wuss/test/tasks/sofa.c -- rotating wireframe sofa and spaceship task */ #ifdef USE_SDL diff --git a/libraries/wuss/test/tasks/sofa.h b/libraries/wuss/test/tasks/sofa.h index 1a8c9ff..35c28c7 100644 --- a/libraries/wuss/test/tasks/sofa.h +++ b/libraries/wuss/test/tasks/sofa.h @@ -1,4 +1,4 @@ -/* sofa.h -- wuss test - rotating wireframe sofa task */ +/* wuss/test/tasks/sofa.h -- rotating wireframe sofa task */ #ifndef TASKS_SOFA_H #define TASKS_SOFA_H diff --git a/libraries/wuss/test/tasks/text.c b/libraries/wuss/test/tasks/text.c index 80688ea..c079d9f 100644 --- a/libraries/wuss/test/tasks/text.c +++ b/libraries/wuss/test/tasks/text.c @@ -1,4 +1,4 @@ -/* text.c -- wuss test - static paragraph task */ +/* wuss/test/tasks/text.c -- static paragraph task */ #ifdef USE_SDL diff --git a/libraries/wuss/test/tasks/text.h b/libraries/wuss/test/tasks/text.h index d7a985a..2b94ae9 100644 --- a/libraries/wuss/test/tasks/text.h +++ b/libraries/wuss/test/tasks/text.h @@ -1,4 +1,4 @@ -/* text.h -- wuss test - static paragraph task */ +/* wuss/test/tasks/text.h -- static paragraph task */ #ifndef TASKS_TEXT_H #define TASKS_TEXT_H diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index 46018d2..13824f1 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -1,4 +1,4 @@ -/* wuss-test.c -- wuss - minimal window manager */ +/* wuss/test/wuss-test.c -- wuss - minimal window manager */ #include <stdio.h> #include <stdlib.h> diff --git a/libraries/wuss/window/at.c b/libraries/wuss/window/at.c index 963f428..bad019d 100644 --- a/libraries/wuss/window/at.c +++ b/libraries/wuss/window/at.c @@ -1,4 +1,4 @@ -/* at.c -- wuss - minimal window manager */ +/* wuss/window/at.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/window/close.c b/libraries/wuss/window/close.c index e24e137..91c32ed 100644 --- a/libraries/wuss/window/close.c +++ b/libraries/wuss/window/close.c @@ -1,4 +1,4 @@ -/* close.c -- wuss - minimal window manager */ +/* wuss/window/close.c -- wuss - minimal window manager */ #include <stdlib.h> diff --git a/libraries/wuss/window/create-placed.c b/libraries/wuss/window/create-placed.c index 22ee483..2ff3e00 100644 --- a/libraries/wuss/window/create-placed.c +++ b/libraries/wuss/window/create-placed.c @@ -1,4 +1,4 @@ -/* create-placed.c -- wuss - window creation with wuss-chosen position */ +/* wuss/window/create-placed.c -- window creation with wuss-chosen position */ #include <assert.h> diff --git a/libraries/wuss/window/create.c b/libraries/wuss/window/create.c index 5935d85..1a5119e 100644 --- a/libraries/wuss/window/create.c +++ b/libraries/wuss/window/create.c @@ -1,4 +1,4 @@ -/* create.c -- wuss - minimal window manager */ +/* wuss/window/create.c -- wuss - minimal window manager */ #include <assert.h> #include <stdlib.h> diff --git a/libraries/wuss/window/get-content-bounds.c b/libraries/wuss/window/get-content-bounds.c index be2187d..9eda68e 100644 --- a/libraries/wuss/window/get-content-bounds.c +++ b/libraries/wuss/window/get-content-bounds.c @@ -1,4 +1,4 @@ -/* get-content-bounds.c -- wuss - minimal window manager */ +/* wuss/window/get-content-bounds.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/window/get-scroll.c b/libraries/wuss/window/get-scroll.c index 18d09d8..b234519 100644 --- a/libraries/wuss/window/get-scroll.c +++ b/libraries/wuss/window/get-scroll.c @@ -1,4 +1,4 @@ -/* get-scroll.c -- wuss - minimal window manager */ +/* wuss/window/get-scroll.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/window/get-visible-bounds.c b/libraries/wuss/window/get-visible-bounds.c index b33f3be..e7e0876 100644 --- a/libraries/wuss/window/get-visible-bounds.c +++ b/libraries/wuss/window/get-visible-bounds.c @@ -1,4 +1,4 @@ -/* get-visible-bounds.c -- wuss - minimal window manager */ +/* wuss/window/get-visible-bounds.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/window/invalidate.c b/libraries/wuss/window/invalidate.c index accaa78..5556496 100644 --- a/libraries/wuss/window/invalidate.c +++ b/libraries/wuss/window/invalidate.c @@ -1,4 +1,4 @@ -/* invalidate.c -- wuss - minimal window manager */ +/* wuss/window/invalidate.c -- wuss - minimal window manager */ #include <string.h> diff --git a/libraries/wuss/window/move.c b/libraries/wuss/window/move.c index a23eaa1..916bd36 100644 --- a/libraries/wuss/window/move.c +++ b/libraries/wuss/window/move.c @@ -1,4 +1,4 @@ -/* move.c -- wuss - minimal window manager */ +/* wuss/window/move.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/window/resize.c b/libraries/wuss/window/resize.c index 1e95d90..d66664b 100644 --- a/libraries/wuss/window/resize.c +++ b/libraries/wuss/window/resize.c @@ -1,4 +1,4 @@ -/* resize.c -- wuss - minimal window manager */ +/* wuss/window/resize.c -- wuss - minimal window manager */ #include "base/utils.h" diff --git a/libraries/wuss/window/restack.c b/libraries/wuss/window/restack.c index 5192bdf..d4d7ee9 100644 --- a/libraries/wuss/window/restack.c +++ b/libraries/wuss/window/restack.c @@ -1,4 +1,4 @@ -/* restack.c -- wuss - minimal window manager */ +/* wuss/window/restack.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/window/set-background.c b/libraries/wuss/window/set-background.c index bd704c4..1c5cf95 100644 --- a/libraries/wuss/window/set-background.c +++ b/libraries/wuss/window/set-background.c @@ -1,4 +1,4 @@ -/* set-background.c -- wuss - minimal window manager */ +/* wuss/window/set-background.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/window/set-scroll.c b/libraries/wuss/window/set-scroll.c index e137101..6a0107b 100644 --- a/libraries/wuss/window/set-scroll.c +++ b/libraries/wuss/window/set-scroll.c @@ -1,4 +1,4 @@ -/* set-scroll.c -- wuss - minimal window manager */ +/* wuss/window/set-scroll.c -- wuss - minimal window manager */ #include "../impl.h" From 429bbabf122f10748861aef81b99b6e58ab59ecc Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 09:59:46 +0100 Subject: [PATCH 31/79] perf(wuss): skip icon redraw on hover when appearance is hover-independent wuss__icon_set_hover invalidated every icon the pointer entered or left, but only wuss_ICON_TYPE_MENU_ENTRY changes appearance on hover. Buttons and all other types ignore icon->hovered, so the redraw was wasted work. Gate the invalidate calls behind a type check; the hovered flag still updates for all types. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/icon/set-hover.c | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/libraries/wuss/icon/set-hover.c b/libraries/wuss/icon/set-hover.c index cb5deb8..8fc1ec7 100644 --- a/libraries/wuss/icon/set-hover.c +++ b/libraries/wuss/icon/set-hover.c @@ -4,6 +4,14 @@ #include "../impl.h" +/* Only wuss_ICON_TYPE_MENU_ENTRY changes appearance on hover (see + * wuss__icon_draw_menu_entry). Buttons and every other type ignore icon->hovered, + * so redrawing them on pointer enter/leave is wasted work. */ +static int wuss__icon_hover_visible(const wuss_icon_t *icon) +{ + return icon->type == wuss_ICON_TYPE_MENU_ENTRY; +} + void wuss__icon_set_hover(wuss_t *wuss, wuss_icon_t *icon) { wuss_icon_t *prev; @@ -15,7 +23,8 @@ void wuss__icon_set_hover(wuss_t *wuss, wuss_icon_t *icon) if (prev != NULL) { prev->hovered = 0; - wuss__icon_invalidate(prev); + if (wuss__icon_hover_visible(prev)) + wuss__icon_invalidate(prev); } wuss->hover_icon = icon; @@ -23,6 +32,7 @@ void wuss__icon_set_hover(wuss_t *wuss, wuss_icon_t *icon) if (icon != NULL) { icon->hovered = 1; - wuss__icon_invalidate(icon); + if (wuss__icon_hover_visible(icon)) + wuss__icon_invalidate(icon); } } From 8d191a070a9ad044e0a7c7396fb0985f4fd21608 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 10:18:03 +0100 Subject: [PATCH 32/79] fix(wuss): re-resolve the hovered icon after a scroll Scrolling moves content under a stationary pointer, but wuss_scroll never re-ran the icon hit-test, so hover_icon stayed pinned to the icon that was under the pointer before the scroll -- a highlighted wuss_ICON_TYPE_MENU_ENTRY kept its highlight after scrolling out from under the pointer. Add wuss__scroll_rehover to recompute the hovered icon on both scroll paths. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/scroll.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/libraries/wuss/scroll.c b/libraries/wuss/scroll.c index 1eb54af..d956954 100644 --- a/libraries/wuss/scroll.c +++ b/libraries/wuss/scroll.c @@ -2,6 +2,30 @@ #include "impl.h" +/* Scrolling moves content under a stationary pointer, so the icon the pointer + * now sits over may differ from before. Re-resolve the hovered icon so a + * highlighted wuss_ICON_TYPE_MENU_ENTRY does not keep its highlight after + * scrolling out from under the pointer. */ +static void wuss__scroll_rehover(wuss_t *wuss, + wuss_window_t *win, + point_t screen_point) +{ +#ifdef WUSS_ICONS + box_t content; + point_t doc_point; + + wuss__content_box(win, &content); + doc_point.x = screen_point.x - content.x0 + win->scroll.x; + doc_point.y = screen_point.y - content.y0 + win->scroll.y; + + wuss__icon_set_hover(wuss, wuss__icon_hit_test(win, doc_point)); +#else + (void) wuss; + (void) win; + (void) screen_point; +#endif +} + result_t wuss_scroll(wuss_t *wuss, point_t p, int delta, wuss_window_t **hit) { wuss_window_t *win; @@ -42,11 +66,13 @@ result_t wuss_scroll(wuss_t *wuss, point_t p, int delta, wuss_window_t **hit) event.data.scroll.delta = delta; wuss__scroll_step(win, POINT(0, delta)); + wuss__scroll_rehover(wuss, win, POINT(x, y)); return win->task.handle(win, &event, win->task.task_data); } wuss__scroll_step(win, POINT(0, delta)); + wuss__scroll_rehover(wuss, win, POINT(x, y)); return result_OK; } From 8510756cf5a433f1e6330cedc83ef5284a002fda Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 10:18:11 +0100 Subject: [PATCH 33/79] perf(wuss): blit the still-valid content when a resize re-clamps the scroll Growing a window that was scrolled to its limit pulls the scroll offset back so only the document extent stays in view. wuss_window_resize repainted the whole content box on that re-clamp, matching the toggle-size path's pessimism -- but that path runs after the window has already been resized on screen, whereas a direct wuss_window_resize (interactive drag-resize) runs with the screen still showing this window's content at the old geometry, so the old content pixels are a valid blit source. Blit the old content box, minus whatever windows above it were covering (wuss__clip_to_visible), shifted by the scroll delta; the clip bounds every destination to the new content box. Only the occluded columns and the newly-revealed strip then need a real repaint. Falls back to a full content-box invalidate when the pixel format can't blit. Adds two tests: the topmost case and the not-topmost (occluded) case. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/test/wuss-test.c | 209 ++++++++++++++++++++++++++++++++ libraries/wuss/window/resize.c | 52 +++++++- 2 files changed, 256 insertions(+), 5 deletions(-) diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index 13824f1..c2901c1 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -1324,6 +1324,215 @@ result_t wuss_test(const char *resources) wuss_window_close(win_r); } + printf("test: wuss_window_resize on a topmost window at max scroll blits the still-valid content rather than redrawing it all\n"); + + { + /* Unlike the toggle-size path above, a direct wuss_window_resize (the + * interactive drag-resize path) runs with the screen still showing this + * window's content at the old geometry and old scroll offset. When the + * grow forces a scroll re-clamp, the overlap of the old and new content + * boxes is genuine on-screen content that just needs sliding by the + * scroll delta -- only the newly-exposed strip needs a real repaint. */ + test_task_t tc_d; + wuss_task_t delegate_d; + box_t box_d, content_before; + wuss_window_t *win_d; + point_t scroll_d; + int i; + int top_x, top_y, mid_x, mid_y, bottom_x, bottom_y; + int top_dirty, mid_dirty, bottom_dirty; + + tc_d.redraw_count = 0; + tc_d.mouse_count = 0; + delegate_d.handle = test_handle; + delegate_d.task_data = &tc_d; + + box_d.x0 = 10; box_d.y0 = 10; + box_d.x1 = 90; box_d.y1 = 90; /* 80x80 content; doc taller, so it starts scrollable */ + rc = wuss_window_create(wuss, &box_d, "D", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate_d, + SIZE2D(80, 140), SIZE2D(0, 0), &win_d); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush the create's own invalidate */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_d, &content_before); + + /* scroll to the bottom: max_y = doc.h (140) - content height (80) = 60 */ + wuss_window_set_scroll(win_d, POINT(0, 60)); + rc = wuss_redraw_dirty(wuss); /* flush the scroll's own invalidate */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_scroll(win_d, &scroll_d); + if (scroll_d.y != 60) + goto Failure; + + /* Three probe points at the same x, well clear of the scrollbar column. + * The re-clamp drops scroll.y from 60 to 0, so on-screen content slides + * DOWN by 60px: the top ~60px of the content box is newly revealed and + * must be repainted, everything below it was already on screen and must + * be reused by the blit. */ + top_x = content_before.x0 + 8; + top_y = content_before.y0 + 8; /* < 60px down: in the revealed strip */ + mid_x = content_before.x0 + 8; + mid_y = content_before.y0 + 70; /* > 60px down: reused */ + bottom_x = content_before.x0 + 8; + bottom_y = content_before.y1 - 8; /* near old viewport bottom: reused */ + + /* grow the content taller than the doc: content height goes to >= 140, + * so max_y drops to 0 and scroll.y is clamped back from 60 to 0 */ + rc = wuss_window_resize(win_d, SIZE2D(80, 160)); + if (rc != result_OK) + goto Failure; + + wuss_window_get_scroll(win_d, &scroll_d); + if (scroll_d.y != 0) + goto Failure; /* the grow must have re-clamped the offset */ + + if (wuss_get_dirty_count(wuss) == 0) + goto Failure; + + top_dirty = 0; + mid_dirty = 0; + bottom_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + box_t region; + + wuss_get_dirty(wuss, i, ®ion); + if (box_contains_point(®ion, top_x, top_y)) + top_dirty = 1; + if (box_contains_point(®ion, mid_x, mid_y)) + mid_dirty = 1; + if (box_contains_point(®ion, bottom_x, bottom_y)) + bottom_dirty = 1; + } + + if (!top_dirty) + goto Failure; /* the newly-revealed strip must be repainted */ + + if (mid_dirty || bottom_dirty) + goto Failure; /* content that stayed on screen must be reused by the + * blit-and-shift, not redrawn -- a full content-box + * invalidate has regressed the optimisation */ + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_d); + } + + printf("test: wuss_window_resize blits the un-occluded content even when the window is not topmost\n"); + + { + /* Same as above, but another window covers the right half of D's + * content. The blit source is the old content box MINUS what the + * occluder was covering (that area holds the occluder's pixels, not + * D's): the un-occluded left half is still slid into place, only the + * occluded columns and the newly-revealed top strip need a repaint. */ + test_task_t tc_d, tc_o; + wuss_task_t delegate_d, delegate_o; + box_t box_d, box_o, content_before; + wuss_window_t *win_d, *win_o; + point_t scroll_d; + int i, split_x; + int left_x, left_y, right_x, right_y; + int left_dirty, right_dirty; + + tc_d.redraw_count = 0; tc_d.mouse_count = 0; + tc_o.redraw_count = 0; tc_o.mouse_count = 0; + delegate_d.handle = test_handle; delegate_d.task_data = &tc_d; + delegate_o.handle = test_handle; delegate_o.task_data = &tc_o; + + box_d.x0 = 10; box_d.y0 = 10; + box_d.x1 = 110; box_d.y1 = 90; /* 100x80 content; doc taller, so scrollable */ + rc = wuss_window_create(wuss, &box_d, "D", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate_d, + SIZE2D(100, 140), SIZE2D(0, 0), &win_d); + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_d, &content_before); + split_x = (content_before.x0 + content_before.x1) / 2; + + /* occluder covering the right half of D's content box and beyond */ + box_o.x0 = split_x; box_o.y0 = content_before.y0 - 5; + box_o.x1 = content_before.x1 + 40; box_o.y1 = content_before.y1 + 40; + rc = wuss_window_create(wuss, &box_o, "O", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate_o, + SIZE2D(box_o.x1 - box_o.x0, box_o.y1 - box_o.y0), + SIZE2D(0, 0), &win_o); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush both creates */ + if (rc != result_OK) + goto Failure; + + wuss_window_set_scroll(win_d, POINT(0, 60)); /* max_y = 140 - 80 = 60 */ + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_get_scroll(win_d, &scroll_d); + if (scroll_d.y != 60) + goto Failure; + + /* left probe: un-occluded, below the ~60px revealed strip -- must be + * reused by the blit. right probe: under the occluder -- was never D's + * pixels on screen, so it must be repainted. Both at the same y. */ + left_x = content_before.x0 + 8; + left_y = content_before.y1 - 8; + right_x = split_x + 8; + right_y = content_before.y1 - 8; + + rc = wuss_window_resize(win_d, SIZE2D(100, 160)); /* grow past doc height */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_scroll(win_d, &scroll_d); + if (scroll_d.y != 0) + goto Failure; + + if (wuss_get_dirty_count(wuss) == 0) + goto Failure; + + left_dirty = 0; + right_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + box_t region; + + wuss_get_dirty(wuss, i, ®ion); + if (box_contains_point(®ion, left_x, left_y)) + left_dirty = 1; + if (box_contains_point(®ion, right_x, right_y)) + right_dirty = 1; + } + + if (left_dirty) + goto Failure; /* un-occluded content must be reused by the blit */ + + if (!right_dirty) + goto Failure; /* content that was hidden behind the occluder holds no + * valid D pixels on screen -- it must be repainted */ + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_o); + wuss_window_close(win_d); + } + printf("test: wuss_WINDOW_NO_RESIZE_BLIT redraws the whole window instead of blitting\n"); { diff --git a/libraries/wuss/window/resize.c b/libraries/wuss/window/resize.c index d66664b..67f3133 100644 --- a/libraries/wuss/window/resize.c +++ b/libraries/wuss/window/resize.c @@ -1,6 +1,7 @@ /* wuss/window/resize.c -- wuss - minimal window manager */ #include "base/utils.h" +#include "geom/box.h" #include "../impl.h" @@ -33,8 +34,9 @@ static void invalidate_grown_or_shrunk(wuss_window_t *window, result_t wuss_window_resize(wuss_window_t *window, size2d_t size) { int outline_px, titlebar_height; - box_t before; + box_t before, before_content; point_t carve; + point_t old_scroll; point_t clamped; if (!wuss__size_ok(size.w, size.h)) @@ -46,6 +48,8 @@ result_t wuss_window_resize(wuss_window_t *window, size2d_t size) outline_px = wuss__outline_px(window); titlebar_height = wuss__titlebar_height(window); before = window->visible; + old_scroll = window->scroll; + wuss__content_box(window, &before_content); wuss__furniture_carve_for(window->flags, wuss__button_size(window), &carve); window->visible.x1 = window->visible.x0 + size.w + 2 * outline_px + carve.x; @@ -55,17 +59,55 @@ result_t wuss_window_resize(wuss_window_t *window, size2d_t size) /* Enlarging the viewport (or growing it past the doc extent) can scroll * content that no longer exists into view; pull the offset back so only - * the document extent is ever shown. The interior has moved relative to - * the window, so the whole content box needs repainting -- the - * grown/shrunk-sliver logic below assumes an unchanged interior. */ + * the document extent is ever shown. The content pixels already on screen + * are still this window's own rendering, just at the old scroll offset -- + * so slide them by the scroll delta and only the newly-exposed strip(s) + * need a real repaint. The valid source is the old content box minus + * whatever windows above it were covering (those areas hold occluder + * pixels, not this window's), so blit it piece by piece; the clip bounds + * every destination to the new content box, discarding anything sliding + * off the old box's far edge rather than dragging stale pixels in. If the + * screen format can't blit, fall back to repainting the whole content + * box. Either way the grown/shrunk-sliver logic below still assumes an + * unchanged interior, which now holds. */ clamped = wuss__scroll_clamp(window, window->scroll); if (clamped.x != window->scroll.x || clamped.y != window->scroll.y) { box_t content; + box_t src[WUSS_MAX_INVALIDATE_PIECES]; + box_t copied[WUSS_MAX_INVALIDATE_PIECES]; + box_t dirty[WUSS_MAX_INVALIDATE_PIECES]; + int dx, dy, nsrc, ncopied, ndirty, i; + dx = clamped.x - old_scroll.x; + dy = clamped.y - old_scroll.y; window->scroll = clamped; wuss__content_box(window, &content); - wuss__invalidate_clipped(window, &content); + + nsrc = wuss__clip_to_visible(window, &before_content, src); + ncopied = 0; + window->wuss->scr->clip = content; + for (i = 0; i < nsrc; i++) + { + box_t got; + + if (screen_copy_rect(window->wuss->scr, &src[i], + POINT(src[i].x0 - dx, src[i].y0 - dy), + &got) != 0 && + ncopied < WUSS_MAX_INVALIDATE_PIECES) + copied[ncopied++] = got; + } + + if (ncopied > 0) + { + ndirty = wuss__subtract_boxes(&content, copied, ncopied, dirty); + for (i = 0; i < ndirty; i++) + wuss_invalidate(window->wuss, &dirty[i]); + } + else + { + wuss__invalidate_clipped(window, &content); + } #ifdef WUSS_FURNITURE wuss__furniture_invalidate(window); #endif From 5d952421199b817e02365d1e15c7bbc3e5a77ad9 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 10:19:33 +0100 Subject: [PATCH 34/79] perf(wuss): blit un-occluded content on scroll when the window is not topmost wuss_window_set_scroll only took its blit-and-shift path when the window was topmost; otherwise it repainted the whole content box. Windows above occlude only part of the content, and wuss__clip_to_visible already computes the un-occluded remainder -- blit those pieces shifted by the scroll delta, then repaint only the occluded regions plus the newly-exposed edge strip (wuss__subtract_boxes). Falls back to a full invalidate when the pixel format can't blit. Mirrors the resize re-clamp path. Adds a not-topmost (occluded) set-scroll test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/test/wuss-test.c | 86 ++++++++++++++++++++++++++++++ libraries/wuss/window/set-scroll.c | 44 ++++++++++----- 2 files changed, 116 insertions(+), 14 deletions(-) diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index c2901c1..244293a 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -1533,6 +1533,92 @@ result_t wuss_test(const char *resources) wuss_window_close(win_d); } + printf("test: wuss_window_set_scroll blits the un-occluded content even when the window is not topmost\n"); + + { + /* A window covered on its right half by another still blits its + * un-occluded left half on a programmatic scroll: only the occluded + * columns and the newly-exposed edge strip need a repaint. */ + test_task_t tc_s, tc_o; + wuss_task_t delegate_s, delegate_o; + box_t box_s, box_o, content; + wuss_window_t *win_s, *win_o; + int i, split_x; + int left_x, left_y, right_x, right_y; + int left_dirty, right_dirty; + + tc_s.redraw_count = 0; tc_s.mouse_count = 0; + tc_o.redraw_count = 0; tc_o.mouse_count = 0; + delegate_s.handle = test_handle; delegate_s.task_data = &tc_s; + delegate_o.handle = test_handle; delegate_o.task_data = &tc_o; + + box_s.x0 = 10; box_s.y0 = 10; + box_s.x1 = 110; box_s.y1 = 90; /* 100x80 content; doc taller, so scrollable */ + rc = wuss_window_create(wuss, &box_s, "S", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate_s, + SIZE2D(100, 200), SIZE2D(0, 0), &win_s); + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_s, &content); + split_x = (content.x0 + content.x1) / 2; + + box_o.x0 = split_x; box_o.y0 = content.y0 - 5; + box_o.x1 = content.x1 + 40; box_o.y1 = content.y1 + 40; + rc = wuss_window_create(wuss, &box_o, "O", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate_o, + SIZE2D(box_o.x1 - box_o.x0, box_o.y1 - box_o.y0), + SIZE2D(0, 0), &win_o); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush both creates */ + if (rc != result_OK) + goto Failure; + + /* left probe: un-occluded, near the bottom -- content there just slides + * up by the scroll delta, so the blit must reuse it. right probe: under + * the occluder -- never S's pixels on screen, so it must be repainted. */ + left_x = content.x0 + 8; + left_y = content.y1 - 20; + right_x = split_x + 8; + right_y = content.y1 - 20; + + wuss_window_set_scroll(win_s, POINT(0, 12)); + + if (wuss_get_dirty_count(wuss) == 0) + goto Failure; + + left_dirty = 0; + right_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + box_t region; + + wuss_get_dirty(wuss, i, ®ion); + if (box_contains_point(®ion, left_x, left_y)) + left_dirty = 1; + if (box_contains_point(®ion, right_x, right_y)) + right_dirty = 1; + } + + if (left_dirty) + goto Failure; /* un-occluded content must be reused by the blit */ + + if (!right_dirty) + goto Failure; /* content hidden behind the occluder holds no valid S + * pixels on screen -- it must be repainted */ + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_o); + wuss_window_close(win_s); + } + printf("test: wuss_WINDOW_NO_RESIZE_BLIT redraws the whole window instead of blitting\n"); { diff --git a/libraries/wuss/window/set-scroll.c b/libraries/wuss/window/set-scroll.c index 6a0107b..2aca658 100644 --- a/libraries/wuss/window/set-scroll.c +++ b/libraries/wuss/window/set-scroll.c @@ -4,8 +4,11 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p) { - box_t content, copied; - int dx, dy; + box_t content; + box_t src[WUSS_MAX_INVALIDATE_PIECES]; + box_t copied[WUSS_MAX_INVALIDATE_PIECES]; + box_t dirty[WUSS_MAX_INVALIDATE_PIECES]; + int dx, dy, nsrc, ncopied, ndirty, i; dx = p.x - window->scroll.x; dy = p.y - window->scroll.y; @@ -22,19 +25,32 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p) wuss__furniture_invalidate(window); #endif - if (window->wuss->z_order.next == &window->link) + /* The valid blit source is the content box minus whatever windows above it + * were covering (those areas hold occluder pixels, not this window's), so + * slide it piece by piece by the scroll delta. The clip keeps every + * destination inside the content box, so a piece near the trailing edge + * shifts partly out and that area falls into the repaint set below along + * with the occluded regions. Falls back to a full content invalidate when + * the pixel format can't blit. */ + nsrc = wuss__clip_to_visible(window, &content, src); + ncopied = 0; + window->wuss->scr->clip = content; + for (i = 0; i < nsrc; i++) { - /* Topmost, so every pixel in "content" is genuinely this window's own - * rendering: slide it by the scroll delta (clipped to the viewport, so - * source and destination both stay inside "content") and only the - * newly-exposed edge strip(s) need an actual repaint. */ - window->wuss->scr->clip = content; - if (screen_copy_rect(window->wuss->scr, &content, - POINT(content.x0 - dx, content.y0 - dy), &copied)) - { - wuss__invalidate_minus(window->wuss, &content, &copied); - return; - } + box_t got; + + if (screen_copy_rect(window->wuss->scr, &src[i], + POINT(src[i].x0 - dx, src[i].y0 - dy), &got) != 0 && + ncopied < WUSS_MAX_INVALIDATE_PIECES) + copied[ncopied++] = got; + } + + if (ncopied > 0) + { + ndirty = wuss__subtract_boxes(&content, copied, ncopied, dirty); + for (i = 0; i < ndirty; i++) + wuss_invalidate(window->wuss, &dirty[i]); + return; } wuss__invalidate_clipped(window, &content); From 4c54556802bcddc46078ef7cd4a7edb8419a4c97 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 10:28:08 +0100 Subject: [PATCH 35/79] fix(wuss): clip scroll/resize blit repaint set to visible area The blit-and-shift optimisation in wuss_window_set_scroll and wuss_window_resize computed its repaint remainder as the content box minus the blitted pieces and queued each survivor via raw wuss_invalidate. That is screen-space and does not subtract occluders, so the columns hidden behind a window above got marked dirty and the occluding window was repainted for nothing -- its pixels were already correct. Route the remainder through wuss__invalidate_clipped instead, which subtracts windows above this one before queueing. Rewrite the two not-topmost tests to assert the occluded region stays clean while the un-occluded newly-exposed strip still repaints. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/test/wuss-test.c | 114 +++++++++++++++++------------ libraries/wuss/window/resize.c | 6 +- libraries/wuss/window/set-scroll.c | 11 ++- 3 files changed, 79 insertions(+), 52 deletions(-) diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index 244293a..9bfa325 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -1428,22 +1428,23 @@ result_t wuss_test(const char *resources) wuss_window_close(win_d); } - printf("test: wuss_window_resize blits the un-occluded content even when the window is not topmost\n"); + printf("test: wuss_window_resize on a non-topmost window blits the un-occluded content and never dirties the occluder\n"); { - /* Same as above, but another window covers the right half of D's - * content. The blit source is the old content box MINUS what the - * occluder was covering (that area holds the occluder's pixels, not - * D's): the un-occluded left half is still slid into place, only the - * occluded columns and the newly-revealed top strip need a repaint. */ + /* Another window covers the right half of D's content. The blit source + * is the old content box MINUS what the occluder covered, so the + * un-occluded left half is slid into place; the right half still shows + * the occluder's own correct pixels, so it must stay clean -- dirtying + * it would redraw the occluding window for nothing. Only the un-occluded + * newly-revealed top strip needs a repaint. */ test_task_t tc_d, tc_o; wuss_task_t delegate_d, delegate_o; box_t box_d, box_o, content_before; wuss_window_t *win_d, *win_o; point_t scroll_d; int i, split_x; - int left_x, left_y, right_x, right_y; - int left_dirty, right_dirty; + int occluded_x, occluded_y, kept_x, kept_y, strip_x, strip_y; + int occluded_dirty, kept_dirty, strip_dirty; tc_d.redraw_count = 0; tc_d.mouse_count = 0; tc_o.redraw_count = 0; tc_o.mouse_count = 0; @@ -1486,13 +1487,15 @@ result_t wuss_test(const char *resources) if (scroll_d.y != 60) goto Failure; - /* left probe: un-occluded, below the ~60px revealed strip -- must be - * reused by the blit. right probe: under the occluder -- was never D's - * pixels on screen, so it must be repainted. Both at the same y. */ - left_x = content_before.x0 + 8; - left_y = content_before.y1 - 8; - right_x = split_x + 8; - right_y = content_before.y1 - 8; + /* occluded: under O -- must stay clean. kept: un-occluded, below the + * ~60px revealed strip -- reused by the blit, must stay clean. strip: + * un-occluded, in the newly-revealed top band -- must be repainted. */ + occluded_x = split_x + 8; + occluded_y = content_before.y1 - 8; + kept_x = content_before.x0 + 8; + kept_y = content_before.y1 - 8; + strip_x = content_before.x0 + 8; + strip_y = content_before.y0 + 8; rc = wuss_window_resize(win_d, SIZE2D(100, 160)); /* grow past doc height */ if (rc != result_OK) @@ -1505,25 +1508,31 @@ result_t wuss_test(const char *resources) if (wuss_get_dirty_count(wuss) == 0) goto Failure; - left_dirty = 0; - right_dirty = 0; + occluded_dirty = 0; + kept_dirty = 0; + strip_dirty = 0; for (i = 0; i < wuss_get_dirty_count(wuss); i++) { box_t region; wuss_get_dirty(wuss, i, ®ion); - if (box_contains_point(®ion, left_x, left_y)) - left_dirty = 1; - if (box_contains_point(®ion, right_x, right_y)) - right_dirty = 1; + if (box_contains_point(®ion, occluded_x, occluded_y)) + occluded_dirty = 1; + if (box_contains_point(®ion, kept_x, kept_y)) + kept_dirty = 1; + if (box_contains_point(®ion, strip_x, strip_y)) + strip_dirty = 1; } - if (left_dirty) + if (occluded_dirty) + goto Failure; /* under the occluder: its pixels are already correct, + * dirtying this would redraw the occluding window */ + + if (kept_dirty) goto Failure; /* un-occluded content must be reused by the blit */ - if (!right_dirty) - goto Failure; /* content that was hidden behind the occluder holds no - * valid D pixels on screen -- it must be repainted */ + if (!strip_dirty) + goto Failure; /* the un-occluded newly-revealed strip must be repainted */ rc = wuss_redraw_dirty(wuss); if (rc != result_OK) @@ -1533,19 +1542,21 @@ result_t wuss_test(const char *resources) wuss_window_close(win_d); } - printf("test: wuss_window_set_scroll blits the un-occluded content even when the window is not topmost\n"); + printf("test: wuss_window_set_scroll on a non-topmost window blits the un-occluded content and never dirties the occluder\n"); { /* A window covered on its right half by another still blits its - * un-occluded left half on a programmatic scroll: only the occluded - * columns and the newly-exposed edge strip need a repaint. */ + * un-occluded left half on a programmatic scroll. The right half shows + * the occluder's own correct pixels, so it must stay clean -- dirtying + * it would redraw the occluding window. Only the un-occluded + * newly-exposed edge strip needs a repaint. */ test_task_t tc_s, tc_o; wuss_task_t delegate_s, delegate_o; box_t box_s, box_o, content; wuss_window_t *win_s, *win_o; int i, split_x; - int left_x, left_y, right_x, right_y; - int left_dirty, right_dirty; + int occluded_x, occluded_y, kept_x, kept_y, strip_x, strip_y; + int occluded_dirty, kept_dirty, strip_dirty; tc_s.redraw_count = 0; tc_s.mouse_count = 0; tc_o.redraw_count = 0; tc_o.mouse_count = 0; @@ -1578,38 +1589,47 @@ result_t wuss_test(const char *resources) if (rc != result_OK) goto Failure; - /* left probe: un-occluded, near the bottom -- content there just slides - * up by the scroll delta, so the blit must reuse it. right probe: under - * the occluder -- never S's pixels on screen, so it must be repainted. */ - left_x = content.x0 + 8; - left_y = content.y1 - 20; - right_x = split_x + 8; - right_y = content.y1 - 20; + /* kept: un-occluded, near the bottom -- content there just slides up by + * the scroll delta, so the blit must reuse it. occluded: under O -- must + * stay clean. strip: un-occluded, in the newly-exposed bottom band -- + * must be repainted. */ + kept_x = content.x0 + 8; + kept_y = content.y0 + 8; + occluded_x = split_x + 8; + occluded_y = content.y1 - 20; + strip_x = content.x0 + 8; + strip_y = content.y1 - 6; wuss_window_set_scroll(win_s, POINT(0, 12)); if (wuss_get_dirty_count(wuss) == 0) goto Failure; - left_dirty = 0; - right_dirty = 0; + kept_dirty = 0; + occluded_dirty = 0; + strip_dirty = 0; for (i = 0; i < wuss_get_dirty_count(wuss); i++) { box_t region; wuss_get_dirty(wuss, i, ®ion); - if (box_contains_point(®ion, left_x, left_y)) - left_dirty = 1; - if (box_contains_point(®ion, right_x, right_y)) - right_dirty = 1; + if (box_contains_point(®ion, kept_x, kept_y)) + kept_dirty = 1; + if (box_contains_point(®ion, occluded_x, occluded_y)) + occluded_dirty = 1; + if (box_contains_point(®ion, strip_x, strip_y)) + strip_dirty = 1; } - if (left_dirty) + if (occluded_dirty) + goto Failure; /* under the occluder: its pixels are already correct, + * dirtying this would redraw the occluding window */ + + if (kept_dirty) goto Failure; /* un-occluded content must be reused by the blit */ - if (!right_dirty) - goto Failure; /* content hidden behind the occluder holds no valid S - * pixels on screen -- it must be repainted */ + if (!strip_dirty) + goto Failure; /* the un-occluded newly-exposed strip must be repainted */ rc = wuss_redraw_dirty(wuss); if (rc != result_OK) diff --git a/libraries/wuss/window/resize.c b/libraries/wuss/window/resize.c index 67f3133..e6a961c 100644 --- a/libraries/wuss/window/resize.c +++ b/libraries/wuss/window/resize.c @@ -100,9 +100,13 @@ result_t wuss_window_resize(wuss_window_t *window, size2d_t size) if (ncopied > 0) { + /* Repaint the content box minus what the blit reused, each survivor + * clipped to this window's visible area: parts that were behind an + * occluder still show the occluder's own correct pixels, so leaving + * them out keeps the resize from redrawing the occluding window. */ ndirty = wuss__subtract_boxes(&content, copied, ncopied, dirty); for (i = 0; i < ndirty; i++) - wuss_invalidate(window->wuss, &dirty[i]); + wuss__invalidate_clipped(window, &dirty[i]); } else { diff --git a/libraries/wuss/window/set-scroll.c b/libraries/wuss/window/set-scroll.c index 2aca658..4e7f473 100644 --- a/libraries/wuss/window/set-scroll.c +++ b/libraries/wuss/window/set-scroll.c @@ -29,9 +29,8 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p) * were covering (those areas hold occluder pixels, not this window's), so * slide it piece by piece by the scroll delta. The clip keeps every * destination inside the content box, so a piece near the trailing edge - * shifts partly out and that area falls into the repaint set below along - * with the occluded regions. Falls back to a full content invalidate when - * the pixel format can't blit. */ + * shifts partly out and that area falls into the repaint set below. Falls + * back to a full content invalidate when the pixel format can't blit. */ nsrc = wuss__clip_to_visible(window, &content, src); ncopied = 0; window->wuss->scr->clip = content; @@ -47,9 +46,13 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p) if (ncopied > 0) { + /* Repaint the content box minus what the blit reused, then clip each + * survivor to this window's visible area: the parts that were behind an + * occluder still show the occluder's own correct pixels, so leaving + * them out keeps the scroll from redrawing the occluding window. */ ndirty = wuss__subtract_boxes(&content, copied, ncopied, dirty); for (i = 0; i < ndirty; i++) - wuss_invalidate(window->wuss, &dirty[i]); + wuss__invalidate_clipped(window, &dirty[i]); return; } From 8a65a20b6e8ddea1695a0815f1ab1555631fc076 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 10:34:34 +0100 Subject: [PATCH 36/79] feat(wuss): add Lissajous figure client Plots x=sin(a*t+phase), y=sin(b*t) as a ring of dots; the phase drifts each idle tick so the figure morphs. Select cycles the frequency pair, Adjust reverses the drift. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- CMakeLists.txt | 1 + apps/wuss/main.c | 12 ++ libraries/wuss/test/tasks/lissajous.c | 168 ++++++++++++++++++++++++++ libraries/wuss/test/tasks/lissajous.h | 37 ++++++ 4 files changed, 218 insertions(+) create mode 100644 libraries/wuss/test/tasks/lissajous.c create mode 100644 libraries/wuss/test/tasks/lissajous.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a341d93..d862008 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -677,6 +677,7 @@ if(BUILD_APPS) libraries/wuss/test/tasks/icons.c libraries/wuss/test/tasks/image.c libraries/wuss/test/tasks/launcher.c + libraries/wuss/test/tasks/lissajous.c libraries/wuss/test/tasks/palette.c libraries/wuss/test/tasks/porter-duff.c libraries/wuss/test/tasks/sofa.c diff --git a/apps/wuss/main.c b/apps/wuss/main.c index 0c7f404..2de23bc 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -33,6 +33,7 @@ #include "tasks/icons.h" #include "tasks/image.h" #include "tasks/launcher.h" +#include "tasks/lissajous.h" #include "tasks/palette.h" #include "tasks/porter-duff.h" #include "tasks/sofa.h" @@ -153,6 +154,16 @@ static result_t spawn_curve(void) return result_OK; } +static result_t spawn_lissajous(void) +{ + lissajous_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = lissajous_create(g_wuss, g_palette, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + static result_t spawn_sofa(void) { sofa_task_t *t = calloc(1, sizeof(*t)); @@ -282,6 +293,7 @@ static const launcher_entry_t g_launcher_entries[] = { "Image", spawn_image }, { "Checker", spawn_checker }, { "Curve", spawn_curve }, + { "Lissajous", spawn_lissajous }, { "Sofa", spawn_sofa }, { "Gradient", spawn_gradient }, { "Icons", spawn_icons }, diff --git a/libraries/wuss/test/tasks/lissajous.c b/libraries/wuss/test/tasks/lissajous.c new file mode 100644 index 0000000..bcf94b0 --- /dev/null +++ b/libraries/wuss/test/tasks/lissajous.c @@ -0,0 +1,168 @@ +/* wuss/test/tasks/lissajous.c -- Lissajous figure task */ + +#ifdef USE_SDL + +#include <math.h> +#include <stdlib.h> + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/utils.h" +#include "framebuf/palettes.h" +#include "geom/box.h" + +#include "lissajous.h" + +/* frequency pairs cycled by a Select click */ +static const int lissajous_freqs[][2] = +{ + { 3, 2 }, { 5, 4 }, { 3, 4 }, { 5, 6 }, { 1, 2 }, { 7, 4 } +}; + +result_t lissajous_create(wuss_t *wuss, + const colour_t *palette, + lissajous_task_t *task) +{ + wuss_task_t delegate; + + task->bg = palette[palette_PICO8_DARK_BLUE]; + task->fg = palette[palette_PICO8_YELLOW]; + task->freq_index = 0; + task->a = lissajous_freqs[0][0]; + task->b = lissajous_freqs[0][1]; + task->phase = 0.0; + task->drift = 0.01; + + delegate = wuss_task_start(lissajous_handle, task); /* redraw paints its own background every frame */ + + return wuss_window_create_placed(wuss, + SIZE2D(220, 220), + "Lissajous", + wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate, + SIZE2D(220, 220), + SIZE2D(0, 0), + &task->window); +} + +static result_t lissajous_redraw(const wuss_event_t *event, void *task_data) +{ + lissajous_task_t *lc; + screen_t *scr; + const box_t *content, *bounds; + int sx, sy; + int width, height, cx, cy, rx, ry; + int i; + + lc = task_data; + + scr = event->data.redraw.scr; + content = event->data.redraw.content; + bounds = event->data.redraw.bounds; + sx = event->data.redraw.scroll.x; + sy = event->data.redraw.scroll.y; + + screen_draw_rect(scr, content->x0, content->y0, box_size(content), lc->bg); + + width = bounds->x1 - bounds->x0; + height = bounds->y1 - bounds->y0; + cx = bounds->x0 - sx + width / 2; + cy = bounds->y0 - sy + height / 2; + rx = width / 2 - 10; + ry = height / 2 - 10; + + for (i = 0; i < LISSAJOUS_POINTS; i++) + { + double t; + int px, py; + + t = (double) i / LISSAJOUS_POINTS * 2.0 * M_PI; + px = cx + (int) (rx * sin(lc->a * t + lc->phase)); + py = cy + (int) (ry * sin(lc->b * t)); + + screen_draw_pixel(scr, px, py, lc->fg); + } + + return result_OK; +} + +static result_t lissajous_mouse(wuss_window_t *window, + wuss_mouse_action_t action, + wuss_button_t button, + void *task_data) +{ + lissajous_task_t *lc; + + NOT_USED(window); + + lc = task_data; + + if (action != wuss_MOUSE_DOWN) + return result_OK; + + if (button & wuss_BUTTON_SELECT) + { + lc->freq_index = (lc->freq_index + 1) % (int) NELEMS(lissajous_freqs); + lc->a = lissajous_freqs[lc->freq_index][0]; + lc->b = lissajous_freqs[lc->freq_index][1]; + wuss_window_invalidate_all(lc->window); /* whole figure changes */ + } + else if (button & wuss_BUTTON_ADJUST) + { + lc->drift = -lc->drift; + } + + return result_OK; +} + +static result_t lissajous_idle(void *task_data) +{ + lissajous_task_t *lc; + + lc = task_data; + + lc->phase += lc->drift; + if (lc->phase > 2.0 * M_PI) + lc->phase -= 2.0 * M_PI; + else if (lc->phase < 0.0) + lc->phase += 2.0 * M_PI; + + wuss_window_invalidate_all(lc->window); /* ponytail: repaint whole content; figure fills it and is cheap */ + + return result_OK; +} + +result_t lissajous_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) +{ + lissajous_task_t *lc; + + lc = task_data; + + switch (event->kind) + { + case wuss_EVENT_REDRAW: + return lissajous_redraw(event, task_data); + + case wuss_EVENT_MOUSE: + return lissajous_mouse(window, event->data.mouse.action, + event->data.mouse.button, task_data); + + case wuss_EVENT_IDLE: + return lissajous_idle(task_data); + + case wuss_EVENT_CLOSE: + wuss_window_close(window); + free(lc); /* task_data was calloc'd per instance by the spawner */ + return result_OK; + + default: + return result_OK; + } +} + +#endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/lissajous.h b/libraries/wuss/test/tasks/lissajous.h new file mode 100644 index 0000000..d9b85be --- /dev/null +++ b/libraries/wuss/test/tasks/lissajous.h @@ -0,0 +1,37 @@ +/* wuss/test/tasks/lissajous.h -- Lissajous figure task */ + +#ifndef TASKS_LISSAJOUS_H +#define TASKS_LISSAJOUS_H + +#ifdef USE_SDL + +#include "framebuf/colour.h" +#include "wuss/window.h" + +#define LISSAJOUS_POINTS 512 /* samples plotted along the curve */ + +/* window task: a Lissajous figure x=sin(a*t+phase), y=sin(b*t) plotted as a + * ring of dots. The phase drifts each idle tick so the figure slowly morphs. + * Select cycles the frequency pair (a,b); Adjust reverses the drift. */ +typedef struct lissajous_task +{ + wuss_window_t *window; + colour_t bg, fg; + int a, b; /* frequency ratio */ + double phase; + double drift; + int freq_index; +} +lissajous_task_t; + +wuss_event_fn_t lissajous_handle; + +/* create the Lissajous window against the given wuss instance; "task" is a + * per-instance block owned by the window and freed when it closes */ +result_t lissajous_create(wuss_t *wuss, + const colour_t *palette, + lissajous_task_t *task); + +#endif /* USE_SDL */ + +#endif /* TASKS_LISSAJOUS_H */ From ff7d341baa03c575befcd6096290c84f4a83d843 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 10:44:37 +0100 Subject: [PATCH 37/79] refactor(wuss): use box lib primitives for icon/invalidate box ops Replace open-coded box clamps and translates with box_intersection, box_is_empty and box_translated. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/icon/draw.c | 9 ++------- libraries/wuss/icon/screen-box.c | 5 +---- libraries/wuss/window/invalidate.c | 6 ++---- 3 files changed, 5 insertions(+), 15 deletions(-) diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index 5f15307..29d5f32 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -340,12 +340,7 @@ static void wuss__icon_draw_bitmap(const icon_draw_ctx_t *c) * clip to the icon box (intersected with whatever redraw already set) and * blit at the box's top-left */ clipped = *c->scr; - if (clipped.clip.x0 < b->x0) clipped.clip.x0 = b->x0; - if (clipped.clip.y0 < b->y0) clipped.clip.y0 = b->y0; - if (clipped.clip.x1 > b->x1) clipped.clip.x1 = b->x1; - if (clipped.clip.y1 > b->y1) clipped.clip.y1 = b->y1; - if (clipped.clip.x1 <= clipped.clip.x0 || - clipped.clip.y1 <= clipped.clip.y0) + if (box_intersection(&c->scr->clip, b, &clipped.clip)) return; screen_draw_bitmap(&clipped, b->x0, b->y0, c->icon->bitmap); @@ -440,7 +435,7 @@ void wuss__icon_draw(wuss_t *wuss, wuss__icon_box_to_screen(content, scroll, &icon->bbox, &c.b); - if (c.b.x1 <= c.b.x0 || c.b.y1 <= c.b.y0) + if (box_is_empty(&c.b)) return; c.wuss = wuss; diff --git a/libraries/wuss/icon/screen-box.c b/libraries/wuss/icon/screen-box.c index f1abad2..3f63051 100644 --- a/libraries/wuss/icon/screen-box.c +++ b/libraries/wuss/icon/screen-box.c @@ -14,8 +14,5 @@ void wuss__icon_box_to_screen(const box_t *content, const box_t *bbox, box_t *out) { - out->x0 = content->x0 - scroll.x + bbox->x0; - out->y0 = content->y0 - scroll.y + bbox->y0; - out->x1 = content->x0 - scroll.x + bbox->x1; - out->y1 = content->y0 - scroll.y + bbox->y1; + box_translated(bbox, content->x0 - scroll.x, content->y0 - scroll.y, out); } diff --git a/libraries/wuss/window/invalidate.c b/libraries/wuss/window/invalidate.c index 5556496..7107911 100644 --- a/libraries/wuss/window/invalidate.c +++ b/libraries/wuss/window/invalidate.c @@ -220,10 +220,8 @@ void wuss_window_invalidate(wuss_window_t *window, const box_t *local_box) local_box = &whole; } - screen_box.x0 = content.x0 - window->scroll.x + local_box->x0; - screen_box.y0 = content.y0 - window->scroll.y + local_box->y0; - screen_box.x1 = content.x0 - window->scroll.x + local_box->x1; - screen_box.y1 = content.y0 - window->scroll.y + local_box->y1; + box_translated(local_box, content.x0 - window->scroll.x, + content.y0 - window->scroll.y, &screen_box); wuss__invalidate_clipped(window, &screen_box); } From def9ef932836075031812b789de57f723bee7715 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 11:32:59 +0100 Subject: [PATCH 38/79] feat(screen): add screen_draw_lines and unfilled screen_draw_rect Add screen_draw_lines (connected polyline) and repurpose screen_draw_rect as a one-pixel unfilled rectangle outline built on it, with a filled fallback for degenerate sizes. Cover both with unit tests in screen-test. Rename the primitives so draw/fill intent is explicit: screen_draw_pixel -> screen_set_pixel screen_draw_rect -> screen_fill_rect screen_draw_square -> screen_fill_square Use the new outline in the OPTION icon glyph (4 draw_line calls -> 1). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- include/framebuf/screen.h | 45 ++++++- libraries/framebuf/curve/test/curve-test.c | 6 +- libraries/framebuf/screen/screen-draw.c | 61 +++++++-- libraries/framebuf/screen/test/screen-test.c | 124 ++++++++++++++++++- libraries/framebuf/span/p4.c | 2 +- libraries/wuss/backdrop.c | 2 +- libraries/wuss/furniture/draw.c | 38 +++--- libraries/wuss/icon/draw.c | 16 ++- libraries/wuss/test/tasks/ball.c | 4 +- libraries/wuss/test/tasks/chars.c | 2 +- libraries/wuss/test/tasks/checker.c | 2 +- libraries/wuss/test/tasks/curve.c | 4 +- libraries/wuss/test/tasks/gradient.c | 2 +- libraries/wuss/test/tasks/lissajous.c | 4 +- libraries/wuss/test/tasks/palette.c | 2 +- libraries/wuss/test/tasks/porter-duff.c | 2 +- libraries/wuss/test/tasks/sofa.c | 4 +- 17 files changed, 260 insertions(+), 60 deletions(-) diff --git a/include/framebuf/screen.h b/include/framebuf/screen.h index 25fa4d1..2c89ec3 100644 --- a/include/framebuf/screen.h +++ b/include/framebuf/screen.h @@ -63,7 +63,7 @@ int screen_get_clip(const screen_t *scr, box_t *clip); * \param[in] y Y coordinate of pixel to draw. * \param[in] colour Colour of pixel. */ -void screen_draw_pixel(screen_t *scr, int x, int y, colour_t colour); +void screen_set_pixel(screen_t *scr, int x, int y, colour_t colour); /** * Draws a solid rectangle. @@ -74,14 +74,14 @@ void screen_draw_pixel(screen_t *scr, int x, int y, colour_t colour); * \param[in] size Width and height of rectangle. * \param[in] colour Colour of rectangle. */ -void screen_draw_rect(screen_t *scr, +void screen_fill_rect(screen_t *scr, int x, int y, size2d_t size, colour_t colour); /** - * Special case of `screen_draw_rect`. + * Special case of `screen_fill_rect`. * * \param[in] scr Screen to draw upon. * \param[in] x X coordinate of leftmost point of rectangle. @@ -89,7 +89,7 @@ void screen_draw_rect(screen_t *scr, * \param[in] size Size of rectangle. * \param[in] colour Colour of rectangle. */ -void screen_draw_square(screen_t *scr, +void screen_fill_square(screen_t *scr, int x, int y, int size, colour_t colour); @@ -239,6 +239,43 @@ void screen_draw_line(screen_t *scr, int y1, colour_t colour); +/** + * Draws a connected polyline through `npoints` points: a `screen_draw_line` + * segment between each adjacent pair. For a closed shape repeat the first point + * as the last. Fewer than 2 points draws nothing. + * + * Segments share their joint pixel, which is plotted by both adjacent segments; + * harmless for a solid colour. + * + * Coordinates are `int`s and inclusive. + * + * \param[in] scr Screen to draw upon. + * \param[in] points Array of `npoints` points. + * \param[in] npoints Number of points in `points`. + * \param[in] colour Colour of the polyline. + */ +void screen_draw_lines(screen_t *scr, + const point_t *points, + int npoints, + colour_t colour); + +/** + * Draws a one-pixel unfilled rectangle outline. `size` is inclusive of both + * edges, matching `screen_fill_rect`. A degenerate size (<= 1 in either axis) + * falls back to a filled `screen_fill_rect`. + * + * \param[in] scr Screen to draw upon. + * \param[in] x X coordinate of leftmost point of rectangle. + * \param[in] y Y coordinate of topmost point of rectangle. + * \param[in] size Width and height of rectangle. + * \param[in] colour Colour of the outline. + */ +void screen_draw_rect(screen_t *scr, + int x, + int y, + size2d_t size, + colour_t colour); + /** * Draws a stippled line: `on` pixels drawn, then `off` skipped, repeating along * the line. Bresenham stepping, so the dash period is measured in steps not diff --git a/libraries/framebuf/curve/test/curve-test.c b/libraries/framebuf/curve/test/curve-test.c index 2a92644..a78021e 100644 --- a/libraries/framebuf/curve/test/curve-test.c +++ b/libraries/framebuf/curve/test/curve-test.c @@ -336,7 +336,7 @@ static void draw_all_control_points(curveteststate_t *state) colour = state->palette[8 + set]; - screen_draw_square(&state->scr, b.x0, b.y0, BLOBSZ, colour); + screen_fill_square(&state->scr, b.x0, b.y0, BLOBSZ, colour); box_union(&b, &state->overalldirty, &state->overalldirty); @@ -366,8 +366,8 @@ static void draw_a_curve(curveteststate_t *state) if (state->opt.draw_endpoints) { - screen_draw_pixel(&state->scr, b.x0, b.y0, state->palette[palette_PICO8_GREEN]); - screen_draw_pixel(&state->scr, b.x1, b.y1, state->palette[palette_PICO8_RED]); + screen_set_pixel(&state->scr, b.x0, b.y0, state->palette[palette_PICO8_GREEN]); + screen_set_pixel(&state->scr, b.x1, b.y1, state->palette[palette_PICO8_RED]); } switch (state->opt.method) diff --git a/libraries/framebuf/screen/screen-draw.c b/libraries/framebuf/screen/screen-draw.c index 5c77046..91bf7d1 100644 --- a/libraries/framebuf/screen/screen-draw.c +++ b/libraries/framebuf/screen/screen-draw.c @@ -20,7 +20,7 @@ * blow the stack (relevant on RISC OS). */ #define BITMAP_BLIT_CHUNK 256 -void screen_draw_pixel(screen_t *scr, int x, int y, colour_t colour) +void screen_set_pixel(screen_t *scr, int x, int y, colour_t colour) { box_t clip; pixelfmt_any_t pxl; @@ -138,7 +138,7 @@ static void screen_blend_pixel(screen_t *scr, /* ----------------------------------------------------------------------- */ -void screen_draw_rect(screen_t *scr, +void screen_fill_rect(screen_t *scr, int x, int y, size2d_t size, @@ -215,9 +215,9 @@ void screen_draw_rect(screen_t *scr, } } -void screen_draw_square(screen_t *scr, int x, int y, int size, colour_t colour) +void screen_fill_square(screen_t *scr, int x, int y, int size, colour_t colour) { - screen_draw_rect(scr, x, y, SIZE2D(size, size), colour); + screen_fill_rect(scr, x, y, SIZE2D(size, size), colour); } /* ----------------------------------------------------------------------- */ @@ -254,7 +254,7 @@ void screen_draw_bitmap(screen_t *scr, int x, int y, const bitmap_t *src) { /* Paletted screen: no linear channel bits to blend, so fall back to * alpha-tested (skip fully transparent, else nearest palette match) - * rather than true blending, matching screen_draw_pixel's case 2. */ + * rather than true blending, matching screen_set_pixel's case 2. */ const unsigned char *srcrow; unsigned char *dstbase; int yy; @@ -412,7 +412,7 @@ void screen_draw_line(screen_t *scr, for (;;) { - screen_draw_pixel(scr, x0, y0, colour); + screen_set_pixel(scr, x0, y0, colour); if (x0 == x1 && y0 == y1) break; @@ -433,6 +433,51 @@ void screen_draw_line(screen_t *scr, } } +void screen_draw_lines(screen_t *scr, + const point_t *points, + int npoints, + colour_t colour) +{ + int i; + + if (points == NULL || npoints < 2) + return; + + /* ponytail: per-segment call; joint pixels double-plot, fine for solid fill */ + for (i = 1; i < npoints; i++) + screen_draw_line(scr, + points[i - 1].x, points[i - 1].y, + points[i].x, points[i].y, + colour); +} + +void screen_draw_rect(screen_t *scr, + int x, + int y, + size2d_t size, + colour_t colour) +{ + int x1, y1; + point_t p[5]; + + if (size.w <= 1 || size.h <= 1) + { + screen_fill_rect(scr, x, y, size, colour); + return; + } + + x1 = x + size.w - 1; + y1 = y + size.h - 1; + + p[0].x = x; p[0].y = y; + p[1].x = x1; p[1].y = y; + p[2].x = x1; p[2].y = y1; + p[3].x = x; p[3].y = y1; + p[4].x = x; p[4].y = y; + + screen_draw_lines(scr, p, 5, colour); +} + void screen_draw_dashed_line(screen_t *scr, int x0, int y0, @@ -485,7 +530,7 @@ void screen_draw_dashed_line(screen_t *scr, for (;;) { if (phase < on) - screen_draw_pixel(scr, x0, y0, colour); + screen_set_pixel(scr, x0, y0, colour); if (++phase >= period) phase = 0; @@ -672,7 +717,7 @@ void screen_draw_line_wu_float(screen_t *scr, return; /* invalid clipped screen */ /* This discards the fractional part of the coordinates so for now just use it - * to discard lines. screen_draw_pixel() will be doing clipping too later. */ + * to discard lines. screen_set_pixel() will be doing clipping too later. */ x0 = fx0; y0 = fy0; x1 = fx1; diff --git a/libraries/framebuf/screen/test/screen-test.c b/libraries/framebuf/screen/test/screen-test.c index 8cf267f..2ffcdb3 100644 --- a/libraries/framebuf/screen/test/screen-test.c +++ b/libraries/framebuf/screen/test/screen-test.c @@ -10,6 +10,7 @@ #include "framebuf/pixelfmt.h" #include "framebuf/screen.h" #include "geom/box.h" +#include "geom/point.h" #include "utils/fxp.h" #include "test/all-tests.h" @@ -256,7 +257,7 @@ static const int np_rgb[3][3][3] = static pixelfmt_bgrx8888_t np_encode(testscreen_t *ts, int r, int g, int b) { testscreen_init(ts); - screen_draw_pixel(&ts->scr, 0, 0, colour_rgb(r, g, b)); + screen_set_pixel(&ts->scr, 0, 0, colour_rgb(r, g, b)); return ts->pixels[0]; } @@ -514,6 +515,123 @@ static result_t test_dashed_line(void) /* ----------------------------------------------------------------------- */ +static result_t test_draw_lines(void) +{ + static testscreen_t ts; + static testscreen_t enc; + + const point_t chain[] = { { 4, 4 }, { 20, 4 }, { 20, 20 } }; + int fg, bg; + int x, y; + + fg = (int) np_encode(&enc, 255, 0, 0); + bg = (int) (pixelfmt_bgrx8888_t) BACKGROUND; + + /* An open two-segment polyline: both segments drawn, the shared joint + * pixel lit, nothing else. */ + testscreen_init(&ts); + screen_draw_lines(&ts.scr, chain, NELEMS(chain), colour_rgb(255, 0, 0)); + + for (x = 4; x <= 20; x++) + if (np_at(&ts, x, 4) != fg) + { + printf("screen: draw_lines gap on segment 1 at x=%d\n", x); + return result_TEST_FAILED; + } + for (y = 4; y <= 20; y++) + if (np_at(&ts, 20, y) != fg) + { + printf("screen: draw_lines gap on segment 2 at y=%d\n", y); + return result_TEST_FAILED; + } + if (np_at(&ts, 5, 5) != bg || np_at(&ts, 4, 20) != bg) + { + printf("screen: draw_lines drew outside the polyline\n"); + return result_TEST_FAILED; + } + + /* Fewer than two points is a no-op. */ + testscreen_init(&ts); + screen_draw_lines(&ts.scr, chain, 1, colour_rgb(255, 0, 0)); + screen_draw_lines(&ts.scr, NULL, 5, colour_rgb(255, 0, 0)); + for (y = 0; y < HEIGHT; y++) + for (x = 0; x < WIDTH; x++) + if (np_at(&ts, x, y) != bg) + { + printf("screen: draw_lines with <2 points drew at (%d,%d)\n", x, y); + return result_TEST_FAILED; + } + + return result_TEST_PASSED; +} + +/* ----------------------------------------------------------------------- */ + +static result_t test_draw_rect(void) +{ + static testscreen_t ts; + static testscreen_t enc; + + int fg, bg; + int x, y; + + fg = (int) np_encode(&enc, 0, 255, 0); + bg = (int) (pixelfmt_bgrx8888_t) BACKGROUND; + + /* 10x8 outline at (5,5): edges lit, interior and exterior background. + * size is inclusive, so the far edges are at x=14, y=12. */ + testscreen_init(&ts); + screen_draw_rect(&ts.scr, 5, 5, SIZE2D(10, 8), colour_rgb(0, 255, 0)); + + for (x = 5; x <= 14; x++) + if (np_at(&ts, x, 5) != fg || np_at(&ts, x, 12) != fg) + { + printf("screen: draw_rect horizontal edge gap at x=%d\n", x); + return result_TEST_FAILED; + } + for (y = 5; y <= 12; y++) + if (np_at(&ts, 5, y) != fg || np_at(&ts, 14, y) != fg) + { + printf("screen: draw_rect vertical edge gap at y=%d\n", y); + return result_TEST_FAILED; + } + if (np_at(&ts, 9, 8) != bg) /* interior */ + { + printf("screen: draw_rect filled its interior\n"); + return result_TEST_FAILED; + } + if (np_at(&ts, 4, 5) != bg || np_at(&ts, 15, 5) != bg || + np_at(&ts, 5, 4) != bg || np_at(&ts, 5, 13) != bg) + { + printf("screen: draw_rect drew outside the rect\n"); + return result_TEST_FAILED; + } + + /* Degenerate size (<= 1 in an axis) falls back to a filled rect. */ + testscreen_init(&ts); + screen_draw_rect(&ts.scr, 3, 3, SIZE2D(1, 6), colour_rgb(0, 255, 0)); + for (y = 3; y <= 8; y++) + if (np_at(&ts, 3, y) != fg) + { + printf("screen: draw_rect degenerate fallback gap at y=%d\n", y); + return result_TEST_FAILED; + } + + /* Honours the screen clip. */ + testscreen_init(&ts); + ts.scr.clip = (box_t) { 0, 0, 10, 64 }; + screen_draw_rect(&ts.scr, 5, 5, SIZE2D(10, 8), colour_rgb(0, 255, 0)); + if (np_at(&ts, 5, 5) != fg || np_at(&ts, 14, 5) != bg) + { + printf("screen: draw_rect ignored the screen clip\n"); + return result_TEST_FAILED; + } + + return result_TEST_PASSED; +} + +/* ----------------------------------------------------------------------- */ + result_t screen_test(const char *resources) { typedef result_t (*screentestfn)(void); @@ -525,7 +643,9 @@ result_t screen_test(const char *resources) test_wu_fix8_extreme_coords, test_ninepatch, test_fill_pattern, - test_dashed_line + test_dashed_line, + test_draw_lines, + test_draw_rect }; result_t rc; diff --git a/libraries/framebuf/span/p4.c b/libraries/framebuf/span/p4.c index 80cfca3..d35e083 100644 --- a/libraries/framebuf/span/p4.c +++ b/libraries/framebuf/span/p4.c @@ -14,7 +14,7 @@ /* pixel values here are unpacked palette indices, one per byte (not the * two-nibbles-per-byte layout used in screen memory); packing/unpacking * into the actual screen bytes is the caller's job, as with - * screen_draw_pixel's other pixel formats. src2 is an array of colour_t, + * screen_set_pixel's other pixel formats. src2 is an array of colour_t, * not pre-quantised pixels, so the blend happens in full RGB precision * before re-quantising to the nearest palette entry. */ static void span_p4_blendconst(void *vdst, diff --git a/libraries/wuss/backdrop.c b/libraries/wuss/backdrop.c index ae69186..cc7071c 100644 --- a/libraries/wuss/backdrop.c +++ b/libraries/wuss/backdrop.c @@ -34,7 +34,7 @@ void wuss__fill_backdrop(screen_t *scr, return; if (backdrop->pattern == screen_PATTERN_SOLID) - screen_draw_rect(scr, area->x0, area->y0, box_size(area), + screen_fill_rect(scr, area->x0, area->y0, box_size(area), palette[backdrop->colour]); else screen_fill_pattern(scr, area, backdrop->pattern, origin_x, origin_y, diff --git a/libraries/wuss/furniture/draw.c b/libraries/wuss/furniture/draw.c index 98a7c65..d7433bf 100644 --- a/libraries/wuss/furniture/draw.c +++ b/libraries/wuss/furniture/draw.c @@ -22,7 +22,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&titlebar, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, + screen_fill_rect(wuss->scr, titlebar.x0, titlebar.y0, box_size(&titlebar), wuss->palette[wuss->furniture_colours.title.bg]); @@ -86,7 +86,7 @@ void wuss__furniture_draw(wuss_t *wuss, box_t close; wuss__close_box(window, &close); - screen_draw_rect(wuss->scr, + screen_fill_rect(wuss->scr, close.x0, close.y0, box_size(&close), wuss->palette[wuss->furniture_colours.close]); } @@ -96,7 +96,7 @@ void wuss__furniture_draw(wuss_t *wuss, box_t back; wuss__back_box(window, &back); - screen_draw_rect(wuss->scr, + screen_fill_rect(wuss->scr, back.x0, back.y0, box_size(&back), wuss->palette[wuss->furniture_colours.back]); } @@ -106,7 +106,7 @@ void wuss__furniture_draw(wuss_t *wuss, box_t toggle; wuss__toggle_box(window, &toggle); - screen_draw_rect(wuss->scr, + screen_fill_rect(wuss->scr, toggle.x0, toggle.y0, box_size(&toggle), wuss->palette[wuss->furniture_colours.toggle]); } @@ -120,7 +120,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&resize, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, + screen_fill_rect(wuss->scr, resize.x0, resize.y0, box_size(&resize), wuss->palette[wuss->furniture_colours.resize]); } @@ -134,7 +134,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&up, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, up.x0, up.y0, box_size(&up), + screen_fill_rect(wuss->scr, up.x0, up.y0, box_size(&up), wuss->palette[wuss->furniture_colours.scroll.arrows]); } @@ -142,7 +142,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&down, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, down.x0, down.y0, box_size(&down), + screen_fill_rect(wuss->scr, down.x0, down.y0, box_size(&down), wuss->palette[wuss->furniture_colours.scroll.arrows]); } @@ -150,7 +150,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&well, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, well.x0, well.y0, box_size(&well), + screen_fill_rect(wuss->scr, well.x0, well.y0, box_size(&well), wuss->palette[wuss->furniture_colours.scroll.wells]); } @@ -158,7 +158,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&sausage, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, sausage.x0, sausage.y0, box_size(&sausage), + screen_fill_rect(wuss->scr, sausage.x0, sausage.y0, box_size(&sausage), wuss->palette[wuss->furniture_colours.scroll.sausages]); } } @@ -171,7 +171,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&left, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, left.x0, left.y0, box_size(&left), + screen_fill_rect(wuss->scr, left.x0, left.y0, box_size(&left), wuss->palette[wuss->furniture_colours.scroll.arrows]); } @@ -179,7 +179,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&right, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, right.x0, right.y0, box_size(&right), + screen_fill_rect(wuss->scr, right.x0, right.y0, box_size(&right), wuss->palette[wuss->furniture_colours.scroll.arrows]); } @@ -187,7 +187,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&well, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, well.x0, well.y0, box_size(&well), + screen_fill_rect(wuss->scr, well.x0, well.y0, box_size(&well), wuss->palette[wuss->furniture_colours.scroll.wells]); } @@ -195,7 +195,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&sausage, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, sausage.x0, sausage.y0, box_size(&sausage), + screen_fill_rect(wuss->scr, sausage.x0, sausage.y0, box_size(&sausage), wuss->palette[wuss->furniture_colours.scroll.sausages]); } } @@ -218,7 +218,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&rule, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, rule.x0, rule.y0, box_size(&rule), + screen_fill_rect(wuss->scr, rule.x0, rule.y0, box_size(&rule), wuss->palette[wuss->furniture_colours.title.bg]); } } @@ -232,7 +232,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&rule, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, rule.x0, rule.y0, box_size(&rule), + screen_fill_rect(wuss->scr, rule.x0, rule.y0, box_size(&rule), wuss->palette[wuss->furniture_colours.title.bg]); } } @@ -248,9 +248,9 @@ void wuss__furniture_draw(wuss_t *wuss, border = wuss->palette[wuss->furniture_colours.title.bg]; /* no dedicated outline class; matches titlebar chrome */ wuss->scr->clip = visible_clipped; - screen_draw_rect(wuss->scr, window->visible.x0, window->visible.y0, SIZE2D(width, 1), border); - screen_draw_rect(wuss->scr, window->visible.x0, window->visible.y1 - 1, SIZE2D(width, 1), border); - screen_draw_rect(wuss->scr, window->visible.x0, window->visible.y0, SIZE2D(1, height), border); - screen_draw_rect(wuss->scr, window->visible.x1 - 1, window->visible.y0, SIZE2D(1, height), border); + screen_fill_rect(wuss->scr, window->visible.x0, window->visible.y0, SIZE2D(width, 1), border); + screen_fill_rect(wuss->scr, window->visible.x0, window->visible.y1 - 1, SIZE2D(width, 1), border); + screen_fill_rect(wuss->scr, window->visible.x0, window->visible.y0, SIZE2D(1, height), border); + screen_fill_rect(wuss->scr, window->visible.x1 - 1, window->visible.y0, SIZE2D(1, height), border); } } diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index 29d5f32..729aa7f 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -37,7 +37,7 @@ static void icon_bevel(screen_t *scr, colour_t light, colour_t dark) { - screen_draw_rect(scr, b->x0, b->y0, + screen_fill_rect(scr, b->x0, b->y0, SIZE2D(b->x1 - b->x0, b->y1 - b->y0), fill); screen_draw_line(scr, b->x0, b->y0, b->x1 - 1, b->y0, light); @@ -99,7 +99,7 @@ static void wuss__icon_draw_label(const icon_draw_ctx_t *c) if (icon->bg != wuss_NO_BACKGROUND) { bg = c->wuss->palette[icon->bg]; - screen_draw_rect(c->scr, b->x0, b->y0, + screen_fill_rect(c->scr, b->x0, b->y0, SIZE2D(b->x1 - b->x0, b->y1 - b->y0), bg); } else @@ -274,7 +274,7 @@ static void wuss__icon_draw_radio_option(const icon_draw_ctx_t *c) bg = icon_blend_ground(c, glyph); if (icon->bg != wuss_NO_BACKGROUND) - screen_draw_rect(c->scr, b->x0, b->y0, + screen_fill_rect(c->scr, b->x0, b->y0, SIZE2D(b->x1 - b->x0, b->y1 - b->y0), bg); if (icon->type == wuss_ICON_TYPE_RADIO) @@ -285,16 +285,14 @@ static void wuss__icon_draw_radio_option(const icon_draw_ctx_t *c) screen_draw_line(c->scr, g.x0, g.y0 + 2, g.x0, g.y1 - 3, glyph); screen_draw_line(c->scr, g.x1 - 1, g.y0 + 2, g.x1 - 1, g.y1 - 3, glyph); if (icon->selected) - screen_draw_rect(c->scr, g.x0 + 3, g.y0 + 3, + screen_fill_rect(c->scr, g.x0 + 3, g.y0 + 3, SIZE2D(gsz - 6, gsz - 6), glyph); } else { /* a box; a tick (two strokes) when selected */ - screen_draw_line(c->scr, g.x0, g.y0, g.x1 - 1, g.y0, glyph); - screen_draw_line(c->scr, g.x0, g.y1 - 1, g.x1 - 1, g.y1 - 1, glyph); - screen_draw_line(c->scr, g.x0, g.y0, g.x0, g.y1 - 1, glyph); - screen_draw_line(c->scr, g.x1 - 1, g.y0, g.x1 - 1, g.y1 - 1, glyph); + screen_draw_rect(c->scr, g.x0, g.y0, + SIZE2D(g.x1 - g.x0, g.y1 - g.y0), glyph); if (icon->selected) { screen_draw_line(c->scr, g.x0 + 2, g.y0 + gsz / 2, @@ -372,7 +370,7 @@ static void wuss__icon_draw_menu_entry(const icon_draw_ctx_t *c) } if (highlit || icon->bg != wuss_NO_BACKGROUND) - screen_draw_rect(c->scr, b->x0, b->y0, + screen_fill_rect(c->scr, b->x0, b->y0, SIZE2D(b->x1 - b->x0, b->y1 - b->y0), ground); /* a dashed rule along the entry's top edge, then the text as normal; drawn in diff --git a/libraries/wuss/test/tasks/ball.c b/libraries/wuss/test/tasks/ball.c index e1f3497..12cd408 100644 --- a/libraries/wuss/test/tasks/ball.c +++ b/libraries/wuss/test/tasks/ball.c @@ -57,7 +57,7 @@ static result_t ball_redraw(const wuss_event_t *event, void *task_data) sx = event->data.redraw.scroll.x; sy = event->data.redraw.scroll.y; - screen_draw_rect(scr, content->x0, content->y0, box_size(content), + screen_fill_rect(scr, content->x0, content->y0, box_size(content), bc->bg); for (i = 0; i < bc->nballs; i++) @@ -66,7 +66,7 @@ static result_t ball_redraw(const wuss_event_t *event, void *task_data) b = &bc->balls[i]; - screen_draw_rect(scr, bounds->x0 - sx + b->x - b->radius, bounds->y0 - sy + b->y - b->radius, SIZE2D(b->radius * 2, b->radius * 2), bc->ball); + screen_fill_rect(scr, bounds->x0 - sx + b->x - b->radius, bounds->y0 - sy + b->y - b->radius, SIZE2D(b->radius * 2, b->radius * 2), bc->ball); } return result_OK; diff --git a/libraries/wuss/test/tasks/chars.c b/libraries/wuss/test/tasks/chars.c index ec53066..5b12ea8 100644 --- a/libraries/wuss/test/tasks/chars.c +++ b/libraries/wuss/test/tasks/chars.c @@ -101,7 +101,7 @@ static result_t chars_redraw(const wuss_event_t *event, void *task_data) x = bounds->x0 - sx + col * cell_w; y = bounds->y0 - sy + row * cell_h; - screen_draw_rect(scr, x, y, SIZE2D(cell_w, cell_h), cc->bg); + screen_fill_rect(scr, x, y, SIZE2D(cell_w, cell_h), cc->bg); if (i < first || i >= first + count) continue; /* no glyph for this byte value: leave the cell blank */ diff --git a/libraries/wuss/test/tasks/checker.c b/libraries/wuss/test/tasks/checker.c index 04f2bfd..f8163a3 100644 --- a/libraries/wuss/test/tasks/checker.c +++ b/libraries/wuss/test/tasks/checker.c @@ -100,7 +100,7 @@ static result_t checker_redraw(wuss_window_t *window, default: band = lx / band_px + ly / band_px; break; /* CHECKERBOARD */ } - screen_draw_pixel(scr, x, y, (band & 1) ? cc->black : cc->white); + screen_set_pixel(scr, x, y, (band & 1) ? cc->black : cc->white); } } diff --git a/libraries/wuss/test/tasks/curve.c b/libraries/wuss/test/tasks/curve.c index ce17708..d99b430 100644 --- a/libraries/wuss/test/tasks/curve.c +++ b/libraries/wuss/test/tasks/curve.c @@ -75,7 +75,7 @@ static result_t curve_redraw(const wuss_event_t *event, curve_task_t *task) sx = event->data.redraw.scroll.x; sy = event->data.redraw.scroll.y; - screen_draw_rect(scr, content->x0, content->y0, box_size(content), + screen_fill_rect(scr, content->x0, content->y0, box_size(content), task->bg); prev = task->points[0]; @@ -98,7 +98,7 @@ static result_t curve_redraw(const wuss_event_t *event, curve_task_t *task) { cur = task->points[i]; cur.x += bounds->x0 - sx; cur.y += bounds->y0 - sy; - screen_draw_square(scr, cur.x - half, cur.y - half, CURVE_BLOBSZ, task->blob); + screen_fill_square(scr, cur.x - half, cur.y - half, CURVE_BLOBSZ, task->blob); } return result_OK; diff --git a/libraries/wuss/test/tasks/gradient.c b/libraries/wuss/test/tasks/gradient.c index e536128..b8b746e 100644 --- a/libraries/wuss/test/tasks/gradient.c +++ b/libraries/wuss/test/tasks/gradient.c @@ -71,7 +71,7 @@ static result_t gradient_redraw(const wuss_event_t *event, void *task_data) lx = x - bounds->x0 + sx; ly = y - bounds->y0 + sy; - screen_draw_pixel(scr, x, y, + screen_set_pixel(scr, x, y, colour_rgb(dither(lx * 255 / GRADIENT_DOC_WIDTH, lx, ly), dither(ly * 255 / GRADIENT_DOC_HEIGHT, lx, ly), dither(255 - (lx + ly) * 255 / (GRADIENT_DOC_WIDTH + GRADIENT_DOC_HEIGHT), lx, ly))); diff --git a/libraries/wuss/test/tasks/lissajous.c b/libraries/wuss/test/tasks/lissajous.c index bcf94b0..cae15c9 100644 --- a/libraries/wuss/test/tasks/lissajous.c +++ b/libraries/wuss/test/tasks/lissajous.c @@ -65,7 +65,7 @@ static result_t lissajous_redraw(const wuss_event_t *event, void *task_data) sx = event->data.redraw.scroll.x; sy = event->data.redraw.scroll.y; - screen_draw_rect(scr, content->x0, content->y0, box_size(content), lc->bg); + screen_fill_rect(scr, content->x0, content->y0, box_size(content), lc->bg); width = bounds->x1 - bounds->x0; height = bounds->y1 - bounds->y0; @@ -83,7 +83,7 @@ static result_t lissajous_redraw(const wuss_event_t *event, void *task_data) px = cx + (int) (rx * sin(lc->a * t + lc->phase)); py = cy + (int) (ry * sin(lc->b * t)); - screen_draw_pixel(scr, px, py, lc->fg); + screen_set_pixel(scr, px, py, lc->fg); } return result_OK; diff --git a/libraries/wuss/test/tasks/palette.c b/libraries/wuss/test/tasks/palette.c index 69cb38f..0bf9cde 100644 --- a/libraries/wuss/test/tasks/palette.c +++ b/libraries/wuss/test/tasks/palette.c @@ -73,7 +73,7 @@ static result_t palette_redraw(const wuss_event_t *event, void *task_data) x = bounds->x0 - sx + col * cell_w; y = bounds->y0 - sy + row * cell_h; - screen_draw_rect(scr, x, y, SIZE2D(cell_w, cell_h), pc->palette[i]); + screen_fill_rect(scr, x, y, SIZE2D(cell_w, cell_h), pc->palette[i]); } return result_OK; diff --git a/libraries/wuss/test/tasks/porter-duff.c b/libraries/wuss/test/tasks/porter-duff.c index d961388..d7d8290 100644 --- a/libraries/wuss/test/tasks/porter-duff.c +++ b/libraries/wuss/test/tasks/porter-duff.c @@ -275,7 +275,7 @@ static void porter_duff_draw_checkerboard(const porter_duff_task_t *pd, ly = y - bounds->y0; band = lx / PD_CHECKER_BAND + ly / PD_CHECKER_BAND; - screen_draw_pixel(scr, x, y, (band & 1) ? pd->dark : pd->light); + screen_set_pixel(scr, x, y, (band & 1) ? pd->dark : pd->light); } } diff --git a/libraries/wuss/test/tasks/sofa.c b/libraries/wuss/test/tasks/sofa.c index a8521b5..c334176 100644 --- a/libraries/wuss/test/tasks/sofa.c +++ b/libraries/wuss/test/tasks/sofa.c @@ -347,7 +347,7 @@ static void draw_vertex_dots(screen_t *scr, half = SOFA_VERTEX_DOT / 2; for (i = 0; i < nvertices; i++) - screen_draw_square(scr, + screen_fill_square(scr, FIX8_ROUND_TO_INT(screen[i].x) - half, FIX8_ROUND_TO_INT(screen[i].y) - half, SOFA_VERTEX_DOT, @@ -397,7 +397,7 @@ static result_t sofa_redraw(const wuss_event_t *event, void *task_data) sx = event->data.redraw.scroll.x; sy = event->data.redraw.scroll.y; - screen_draw_rect(scr, + screen_fill_rect(scr, content->x0, content->y0, box_size(content), sc->bg); From 143654d4f5f807b0d914ca12da6d8514cc4d7f93 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 12:11:27 +0100 Subject: [PATCH 39/79] refactor(wuss): route core->furniture calls through a dispatch table Introduce wuss__furniture_ops_t, a struct of the seven furniture entry points the wuss core calls (hit_test, draw, invalidate, invalidate_for, toggle_size, drag_resize, drag_sausage). struct wuss holds a pointer to one, defaulted by wuss_create to wuss__furniture_default_ops (the existing built-in furniture, now named in furniture/ops.c). The twelve core call sites in redraw, mouse-click, mouse-move and window/{resize,set-scroll} dispatch through the table instead of calling the functions by name. The inline geometry helpers in impl.h and the wuss__furniture_drag_kind switch stay direct: they are the compile-time furniture/no-furniture seam, not a runtime policy seam. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- CMakeLists.txt | 1 + libraries/wuss/create.c | 1 + libraries/wuss/furniture.h | 25 +++++++++++++++++++++++++ libraries/wuss/furniture/ops.c | 16 ++++++++++++++++ libraries/wuss/impl.h | 5 ++++- libraries/wuss/mouse-click.c | 4 ++-- libraries/wuss/mouse-move.c | 8 ++++---- libraries/wuss/redraw.c | 2 +- libraries/wuss/window/resize.c | 6 +++--- libraries/wuss/window/set-scroll.c | 2 +- 10 files changed, 58 insertions(+), 12 deletions(-) create mode 100644 libraries/wuss/furniture/ops.c diff --git a/CMakeLists.txt b/CMakeLists.txt index d862008..b01414d 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -371,6 +371,7 @@ set(WUSS_FURNITURE_SOURCES libraries/wuss/furniture/hit-test.c libraries/wuss/furniture/hscroll-box.c libraries/wuss/furniture/invalidate.c + libraries/wuss/furniture/ops.c libraries/wuss/furniture/resize-box.c libraries/wuss/furniture/scroll-action.c libraries/wuss/furniture/titlebar-box.c diff --git a/libraries/wuss/create.c b/libraries/wuss/create.c index ad11546..027e3ec 100644 --- a/libraries/wuss/create.c +++ b/libraries/wuss/create.c @@ -245,6 +245,7 @@ result_t wuss_create(screen_t *scr, w->furniture.dragging = NULL; w->furniture.drag.x = 0; w->furniture.drag.y = 0; + w->furniture_ops = &wuss__furniture_default_ops; #endif #ifdef WUSS_ICONS w->pressed_icon = NULL; diff --git a/libraries/wuss/furniture.h b/libraries/wuss/furniture.h index 22744f0..0bf378b 100644 --- a/libraries/wuss/furniture.h +++ b/libraries/wuss/furniture.h @@ -85,6 +85,31 @@ void wuss__furniture_invalidate(wuss_window_t *window); void wuss__furniture_invalidate_for(wuss_window_t *window, const box_t *visible); +/* Dispatch table the wuss core reaches window furniture through: everything + * the core (z-order, mouse routing, redraw, window lifecycle) calls that + * lives in the furniture .c files. A caller could point wuss::furniture_ops at its + * own table to replace the furniture wholesale; wuss_create defaults it to + * wuss__furniture_default_ops. The geometry helpers in impl.h + * (wuss__titlebar_box, wuss__content_box, wuss__furniture_carve_for, the + * titlebar-height/button-size inlines) are a compile-time seam between the + * furniture and no-furniture builds and stay out of this table. */ +typedef struct wuss__furniture_ops +{ + wuss_furniture_region_t (*hit_test)(const wuss_window_t *window, point_t p); + void (*draw)(wuss_t *wuss, wuss_window_t *window, const box_t *full); + void (*invalidate)(wuss_window_t *window); + void (*invalidate_for)(wuss_window_t *window, const box_t *visible); + void (*toggle_size)(wuss_window_t *window); + void (*drag_resize)(wuss_window_t *window, point_t p); + void (*drag_sausage)(wuss_window_t *window, + int delta_px, + int scroll_start, + int horizontal); +} +wuss__furniture_ops_t; + +extern const wuss__furniture_ops_t wuss__furniture_default_ops; + /* geometry: titlebar icons */ void wuss__back_box(const wuss_window_t *window, box_t *out); void wuss__toggle_box(const wuss_window_t *window, box_t *out); diff --git a/libraries/wuss/furniture/ops.c b/libraries/wuss/furniture/ops.c new file mode 100644 index 0000000..1578916 --- /dev/null +++ b/libraries/wuss/furniture/ops.c @@ -0,0 +1,16 @@ +/* wuss/furniture/ops.c -- wuss - core-to-furniture dispatch table */ + +#include "../impl.h" + +/* The built-in furniture: wuss_create points every window's furniture_ops + * here unless a caller overrides it. */ +const wuss__furniture_ops_t wuss__furniture_default_ops = +{ + wuss__furniture_hit_test, + wuss__furniture_draw, + wuss__furniture_invalidate, + wuss__furniture_invalidate_for, + wuss__furniture_toggle_size, + wuss__furniture_drag_resize, + wuss__furniture_drag_sausage +}; diff --git a/libraries/wuss/impl.h b/libraries/wuss/impl.h index 7046e5c..9e308f8 100644 --- a/libraries/wuss/impl.h +++ b/libraries/wuss/impl.h @@ -82,7 +82,10 @@ struct wuss #endif list_t z_order; /* anchor; head = topmost window */ #ifdef WUSS_FURNITURE - struct wuss__furniture furniture; + struct wuss__furniture furniture; /* drag state */ + const wuss__furniture_ops_t *furniture_ops; /* core->furniture dispatch; + * wuss_create defaults it to + * wuss__furniture_default_ops */ #endif box_t dirty[WUSS_MAX_DIRTY]; /* accumulated by wuss_invalidate; reset by a redraw */ int ndirty; diff --git a/libraries/wuss/mouse-click.c b/libraries/wuss/mouse-click.c index f6f961f..d0bf85d 100644 --- a/libraries/wuss/mouse-click.c +++ b/libraries/wuss/mouse-click.c @@ -66,7 +66,7 @@ result_t wuss_mouse_click(wuss_t *wuss, { wuss_furniture_region_t region; - region = wuss__furniture_hit_test(win, POINT(x, y)); + region = wuss->furniture_ops->hit_test(win, POINT(x, y)); if (region == wuss_FURNITURE_CLOSE && action == wuss_MOUSE_DOWN && @@ -111,7 +111,7 @@ result_t wuss_mouse_click(wuss_t *wuss, { case wuss_FURNITURE_TOGGLE_SIZE: if (button & wuss_BUTTON_SELECT) - wuss__furniture_toggle_size(win); + wuss->furniture_ops->toggle_size(win); break; case wuss_FURNITURE_VSCROLL_UP: wuss__scroll_step(win, POINT(0, -step)); diff --git a/libraries/wuss/mouse-move.c b/libraries/wuss/mouse-move.c index d1546b6..a0495cf 100644 --- a/libraries/wuss/mouse-move.c +++ b/libraries/wuss/mouse-move.c @@ -22,15 +22,15 @@ result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit) switch (wuss->furniture.drag_kind) { case wuss_FURNITURE_DRAG_RESIZE: - wuss__furniture_drag_resize(win, POINT(x, y)); + wuss->furniture_ops->drag_resize(win, POINT(x, y)); break; case wuss_FURNITURE_DRAG_VSCROLL_SAUSAGE: - wuss__furniture_drag_sausage(win, y - wuss->furniture.drag.y, wuss->furniture.drag_scroll_start, 0); + wuss->furniture_ops->drag_sausage(win, y - wuss->furniture.drag.y, wuss->furniture.drag_scroll_start, 0); break; case wuss_FURNITURE_DRAG_HSCROLL_SAUSAGE: - wuss__furniture_drag_sausage(win, x - wuss->furniture.drag.x, wuss->furniture.drag_scroll_start, 1); + wuss->furniture_ops->drag_sausage(win, x - wuss->furniture.drag.x, wuss->furniture.drag_scroll_start, 1); break; case wuss_FURNITURE_DRAG_MOVE: @@ -56,7 +56,7 @@ result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit) } #ifdef WUSS_FURNITURE - if (wuss__furniture_hit_test(win, POINT(x, y)) != wuss_FURNITURE_CONTENT) + if (wuss->furniture_ops->hit_test(win, POINT(x, y)) != wuss_FURNITURE_CONTENT) { #ifdef WUSS_ICONS wuss__icon_set_hover(wuss, NULL); diff --git a/libraries/wuss/redraw.c b/libraries/wuss/redraw.c index 52ad93f..cca9001 100644 --- a/libraries/wuss/redraw.c +++ b/libraries/wuss/redraw.c @@ -17,7 +17,7 @@ static void redraw_window(wuss_t *wuss, return; /* offscreen */ #ifdef WUSS_FURNITURE - wuss__furniture_draw(wuss, win, full); + wuss->furniture_ops->draw(wuss, win, full); #endif wuss__content_box(win, &content); diff --git a/libraries/wuss/window/resize.c b/libraries/wuss/window/resize.c index e6a961c..9c8e736 100644 --- a/libraries/wuss/window/resize.c +++ b/libraries/wuss/window/resize.c @@ -113,7 +113,7 @@ result_t wuss_window_resize(wuss_window_t *window, size2d_t size) wuss__invalidate_clipped(window, &content); } #ifdef WUSS_FURNITURE - wuss__furniture_invalidate(window); + window->wuss->furniture_ops->invalidate(window); #endif } @@ -146,8 +146,8 @@ result_t wuss_window_resize(wuss_window_t *window, size2d_t size) #ifdef WUSS_FURNITURE if (window->visible.x1 - window->visible.x0 > before.x1 - before.x0 || window->visible.y1 - window->visible.y0 > before.y1 - before.y0) - wuss__furniture_invalidate_for(window, &before); - wuss__furniture_invalidate(window); + window->wuss->furniture_ops->invalidate_for(window, &before); + window->wuss->furniture_ops->invalidate(window); #endif } diff --git a/libraries/wuss/window/set-scroll.c b/libraries/wuss/window/set-scroll.c index 4e7f473..0acf264 100644 --- a/libraries/wuss/window/set-scroll.c +++ b/libraries/wuss/window/set-scroll.c @@ -22,7 +22,7 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p) #ifdef WUSS_FURNITURE /* the scrollbar sausage position depends on scroll, so its well needs * redrawing too -- content invalidation alone never touches it */ - wuss__furniture_invalidate(window); + window->wuss->furniture_ops->invalidate(window); #endif /* The valid blit source is the content box minus whatever windows above it From 74d23fed26f179e09888c8fc96b3fa686e3edfea Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 12:34:07 +0100 Subject: [PATCH 40/79] feat(wuss): make menu separators their own short inert row A separator is no longer a dashed rule drawn above a still-pickable label. The '|' descriptor prefix now emits a standalone DASHED item (empty text) before the real one, laid out at its own short height and skipped by hit-testing, so the pointer never interacts with it. Also route create-from-desc's item array growth through array_grow instead of a hand-rolled doubling realloc. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- include/wuss/icon.h | 12 +++--- include/wuss/menu.h | 25 ++++++------ libraries/wuss/icon/draw.c | 6 +-- libraries/wuss/icon/hit-test.c | 3 +- libraries/wuss/menu/create-from-desc.c | 53 ++++++++++++++------------ libraries/wuss/menu/menu.c | 23 +++++++++-- libraries/wuss/test/wuss-test.c | 13 ++++--- 7 files changed, 77 insertions(+), 58 deletions(-) diff --git a/include/wuss/icon.h b/include/wuss/icon.h index 8a6f9b4..ff3bcd9 100644 --- a/include/wuss/icon.h +++ b/include/wuss/icon.h @@ -144,13 +144,11 @@ typedef enum wuss_icon_flags * edge, marking an entry that opens * a submenu. Ignored by other * types. */ - wuss_ICON_FLAGS_SEPARATOR = 1 << 7 /**< wuss_ICON_TYPE_MENU_ENTRY: draw a - * dashed rule along the entry's top - * edge, then the text as normal; the - * entry stays interactive. An entry - * carrying the flag with no text is - * a bare rule and is not hit-tested. - * Ignored by other types. */ + wuss_ICON_FLAGS_SEPARATOR = 1 << 7 /**< wuss_ICON_TYPE_MENU_ENTRY: the + * entry is a separator -- a centred + * dashed rule, no text, and not + * hit-tested. Ignored by other + * types. */ } wuss_icon_flags_t; diff --git a/include/wuss/menu.h b/include/wuss/menu.h index 463fad8..759fd64 100644 --- a/include/wuss/menu.h +++ b/include/wuss/menu.h @@ -35,9 +35,10 @@ typedef enum wuss_menu_item_flags { wuss_MENU_ITEM_NONE = 0, wuss_MENU_ITEM_TICKED = 1 << 0, /**< draw a tick at the item's left edge */ - wuss_MENU_ITEM_DASHED = 1 << 1, /**< draw a dashed separator immediately - * above this item; the item keeps its - * own text and stays selectable */ + wuss_MENU_ITEM_DASHED = 1 << 1, /**< this item is a separator: its own + * short row holding a centred dashed + * rule. The text is ignored and the row + * does not respond to the pointer */ wuss_MENU_ITEM_DISABLED = 1 << 2 /**< greyed, never highlights, not * selectable */ } @@ -118,17 +119,17 @@ int wuss_menu_is_open(wuss_menu_handle_t handle); * as the first token inside a '{ }' is that submenu's title -- so the same * descriptor strings port across unchanged. Submenus keep the Wimp behaviour of * discarding their title token; only the root's is kept. A leading '|' on an - * item sets wuss_MENU_ITEM_DASHED on it: a dashed rule is drawn above the item - * while the item keeps its own label and stays selectable. A '{ ... }' group - * after an item is that item's submenu. A per-token prefix '!' ticks the item, - * '~' shades (disables) it, and '>' attaches a submenu pulled as a <tt>const - * wuss_menu_t *</tt> from the varargs rather than from a following '{ }' block. - * A "%s" in a token substitutes the next <tt>const char *</tt> vararg. The '>' - * and "%s" varargs are consumed in the order they are encountered scanning left - * to right. + * item inserts a separator row above it: its own short, inert row holding a + * centred dashed rule. A '{ ... }' group after an item is that item's submenu. + * A per-token prefix '!' ticks the item, '~' shades (disables) it, and '>' + * attaches a submenu pulled as a <tt>const wuss_menu_t *</tt> from the varargs + * rather than from a following '{ }' block. A "%s" in a token substitutes the + * next <tt>const char *</tt> vararg. The '>' and "%s" varargs are consumed in + * the order they are encountered scanning left to right. * * Example: <tt>wuss_menu_create_from_desc(&m, "Display, Open, !Grid, ~Export, - * |Quit")</tt> -- "Display" is the caption, the menu has four rows. + * |Quit")</tt> -- "Display" is the caption; the menu has four items plus a + * separator row before "Quit". * * The whole tree, including copied label text, is one heap allocation graph * owned by the caller; free it with wuss_menu_destroy. wuss_menu_open treats a diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index 729aa7f..796017e 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -373,11 +373,11 @@ static void wuss__icon_draw_menu_entry(const icon_draw_ctx_t *c) screen_fill_rect(c->scr, b->x0, b->y0, SIZE2D(b->x1 - b->x0, b->y1 - b->y0), ground); - /* a dashed rule along the entry's top edge, then the text as normal; drawn in - * the row's ink so it stays visible when the row is inverted */ + /* a separator is its own short, textless row: a dashed rule centred in it, + * drawn in the row's ink so it stays visible if the row is inverted */ if (icon->flags & wuss_ICON_FLAGS_SEPARATOR) { - mid_y = b->y0 + 1; + mid_y = b->y0 + (b->y1 - b->y0) / 2; screen_draw_dashed_line(c->scr, b->x0 + pad, mid_y, b->x1 - 1 - pad, mid_y, 2, 2, ink); } diff --git a/libraries/wuss/icon/hit-test.c b/libraries/wuss/icon/hit-test.c index b4045fb..78b8761 100644 --- a/libraries/wuss/icon/hit-test.c +++ b/libraries/wuss/icon/hit-test.c @@ -15,8 +15,7 @@ wuss_icon_t *wuss__icon_hit_test(wuss_window_t *window, point_t doc_point) it = window->icons[i]; /* a bitmap icon is interactive only when it asks to be; a menu entry that - * is a bare rule (SEPARATOR flag, no text) is inert, but one that only - * carries a rule above its own label stays pickable; the other clickable + * is a separator (SEPARATOR flag, no text) is inert; the other clickable * types always are */ if (it->type == wuss_ICON_TYPE_BITMAP) { diff --git a/libraries/wuss/menu/create-from-desc.c b/libraries/wuss/menu/create-from-desc.c index 1d8c16a..f0a6f0c 100644 --- a/libraries/wuss/menu/create-from-desc.c +++ b/libraries/wuss/menu/create-from-desc.c @@ -20,6 +20,7 @@ #endif #include "base/result.h" +#include "utils/array.h" #include "wuss/menu.h" @@ -208,28 +209,17 @@ typedef struct } Building; -static result_t build_add(Building *b, - const char *text, - unsigned int opt, - wuss_menu_t *submenu) +static result_t build_emit(Building *b, + const char *text, + unsigned int flags, + wuss_menu_t *submenu) { wuss_menu_item_t *it; char *copy; - if (b->n == b->cap) - { - int cap; - wuss_menu_item_t *grown; - - /* ponytail: double on demand, no shrink-to-fit -- the array is small and - * freed as one block, trailing slack costs nothing */ - cap = b->cap ? b->cap * 2 : 4; - grown = realloc(b->items, (size_t) cap * sizeof(*grown)); - if (grown == NULL) - return result_OOM; - b->items = grown; - b->cap = cap; - } + if (array_grow((void **) &b->items, sizeof(*b->items), + b->n, &b->cap, 1, 4)) + return result_OOM; copy = strdup(text ? text : ""); if (copy == NULL) @@ -237,23 +227,36 @@ static result_t build_add(Building *b, it = &b->items[b->n++]; it->text = copy; - it->flags = wuss_MENU_ITEM_NONE; + it->flags = flags; it->submenu = submenu; + return result_OK; +} - /* '|' before this item means "draw a dashed rule above it"; the item keeps - * its own text and stays pickable. */ +static result_t build_add(Building *b, + const char *text, + unsigned int opt, + wuss_menu_t *submenu) +{ + unsigned int flags; + result_t rc; + + /* '|' before this item means "insert a separator row above it": a distinct + * DASHED item with no text of its own, emitted before the real one. */ if (b->pending_dash) { - it->flags |= wuss_MENU_ITEM_DASHED; b->pending_dash = 0; + rc = build_emit(b, "", wuss_MENU_ITEM_DASHED, NULL); + if (rc != result_OK) + return rc; } + flags = wuss_MENU_ITEM_NONE; if (opt & Tick) - it->flags |= wuss_MENU_ITEM_TICKED; + flags |= wuss_MENU_ITEM_TICKED; if (opt & Shade) - it->flags |= wuss_MENU_ITEM_DISABLED; + flags |= wuss_MENU_ITEM_DISABLED; - return result_OK; + return build_emit(b, text, flags, submenu); } /* Seal a Building into a heap wuss_menu_t, transferring its item array. */ diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index 15cdaa8..6fdccc8 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -177,6 +177,8 @@ static result_t wuss__menu_spawn(wuss_t *wuss, result_t rc; int fw, fh; int pitch; + int sep_h; + int y; int widest; int width, height; int i; @@ -193,6 +195,7 @@ static result_t wuss__menu_spawn(wuss_t *wuss, bmfont_get_info(wuss->font, &fw, &fh); NOT_USED(fw); pitch = fh + 2 * WUSS_MENU_ROW_PAD; + sep_h = 2 * WUSS_MENU_ROW_PAD; widest = 0; for (i = 0; i < menu->nitems; i++) @@ -212,7 +215,10 @@ static result_t wuss__menu_spawn(wuss_t *wuss, } width = WUSS_MENU_TICK_W + widest + WUSS_MENU_TEXT_PAD + WUSS_MENU_ARROW_W; - height = pitch * menu->nitems; + + height = 0; + for (i = 0; i < menu->nitems; i++) + height += (menu->items[i].flags & wuss_MENU_ITEM_DASHED) ? sep_h : pitch; /* Nudge onto the screen where it can be, but keep the top-left on screen. */ if (at.x + width > wuss->scr->size.w) @@ -248,10 +254,12 @@ static result_t wuss__menu_spawn(wuss_t *wuss, node->ctx = ctx; node->open_index = -1; + y = 0; for (i = 0; i < menu->nitems; i++) { const wuss_menu_item_t *item; wuss_icon_flags_t flags; + int row_h; item = &menu->items[i]; flags = wuss_ICON_FLAGS_NONE; @@ -263,15 +271,22 @@ static result_t wuss__menu_spawn(wuss_t *wuss, if (item->submenu != NULL) flags |= wuss_ICON_FLAGS_SUBMENU; + /* a separator is its own short, inert row: force the text empty so + * wuss__icon_hit_test treats it as a bare rule */ + row_h = (flags & wuss_ICON_FLAGS_SEPARATOR) ? sep_h : pitch; + specs[i].type = wuss_ICON_TYPE_MENU_ENTRY; specs[i].bbox.x0 = 0; - specs[i].bbox.y0 = i * pitch; + specs[i].bbox.y0 = y; specs[i].bbox.x1 = width; - specs[i].bbox.y1 = i * pitch + pitch; - specs[i].text = item->text ? item->text : ""; + specs[i].bbox.y1 = y + row_h; + specs[i].text = (flags & wuss_ICON_FLAGS_SEPARATOR) + ? "" : (item->text ? item->text : ""); specs[i].fg = 0; specs[i].bg = wuss_NO_BACKGROUND; specs[i].flags = flags; + + y += row_h; } size = SIZE2D(width, height); diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index 9bfa325..eefbca5 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -2913,8 +2913,8 @@ result_t wuss_test(const char *resources) /* "Root" is the root caption (first token), then items App, * File{ %s title discarded, Grid(!), Export(~), |Quit }, Help(> borrowed), - * with "File" substituted for both %s. '|Quit' sets DASHED on Quit itself - * (rule drawn above it, text kept), so the submenu holds 3 rows. */ + * with "File" substituted for both %s. '|Quit' inserts a standalone DASHED + * separator item before Quit, so the submenu holds 4 rows. */ rc = wuss_menu_create_from_desc(&m, "Root, App, %s { %s, !Grid, ~Export, |Quit }, >Help", "File", "File", &borrowed); @@ -2927,15 +2927,18 @@ result_t wuss_test(const char *resources) if (m->items[0].submenu != NULL) goto MenuFail; /* items[1] "File" carries the { } submenu; its first token was the title - * and is not emitted; '|Quit' keeps Quit's text and flags it DASHED */ + * and is not emitted; '|Quit' inserts a separate DASHED separator row + * before Quit, which keeps its own text and no DASHED flag */ if (strcmp(m->items[1].text, "File") != 0) goto MenuFail; sub = m->items[1].submenu; - if (sub == NULL || sub->nitems != 3) goto MenuFail; + if (sub == NULL || sub->nitems != 4) goto MenuFail; if (strcmp(sub->items[0].text, "Grid") != 0) goto MenuFail; if (!(sub->items[0].flags & wuss_MENU_ITEM_TICKED)) goto MenuFail; if (!(sub->items[1].flags & wuss_MENU_ITEM_DISABLED)) goto MenuFail; - if (strcmp(sub->items[2].text, "Quit") != 0) goto MenuFail; + if (sub->items[2].text[0] != '\0') goto MenuFail; if (!(sub->items[2].flags & wuss_MENU_ITEM_DASHED)) goto MenuFail; + if (strcmp(sub->items[3].text, "Quit") != 0) goto MenuFail; + if (sub->items[3].flags & wuss_MENU_ITEM_DASHED) goto MenuFail; /* items[2] "Help" got a deep copy of the borrowed menu */ if (strcmp(m->items[2].text, "Help") != 0) goto MenuFail; From 76aaee2b340e7cd0f719ea2d47183d433a7cf96b Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 12:53:15 +0100 Subject: [PATCH 41/79] refactor(wuss): condense icon pressed/selected/hovered into a flags word Replace the three loose int fields on struct wuss_icon with a single wuss_icon_state_t bitflags word and wuss__icon_pressed/selected/hovered/ set_state accessors, mirroring wuss_window_state_t. Leaves room for more per-instance state without growing the struct. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/icon.h | 52 ++++++++++++++++++++++++------ libraries/wuss/icon/create.c | 4 +-- libraries/wuss/icon/draw.c | 10 +++--- libraries/wuss/icon/get-selected.c | 2 +- libraries/wuss/icon/set-hover.c | 6 ++-- libraries/wuss/icon/set-selected.c | 8 ++--- libraries/wuss/mouse-click.c | 14 ++++---- libraries/wuss/mouse-move.c | 4 +-- 8 files changed, 66 insertions(+), 34 deletions(-) diff --git a/libraries/wuss/icon.h b/libraries/wuss/icon.h index bc5dc48..366aa84 100644 --- a/libraries/wuss/icon.h +++ b/libraries/wuss/icon.h @@ -11,24 +11,58 @@ #include "wuss/wuss.h" #include "wuss/icon.h" +/* Per-instance transient state, kept as bitflags with wuss__icon_* accessors + * (mirrors wuss_window_state_t) so more can be added without growing the + * struct. Not part of the public appearance API. */ +typedef enum wuss_icon_state +{ + wuss_ICON_STATE_NONE = 0, + wuss_ICON_STATE_PRESSED = 1 << 0, /* button: held with the pointer inside */ + wuss_ICON_STATE_SELECTED = 1 << 1, /* radio/option: latched on; menu entry: + * draw a tick */ + wuss_ICON_STATE_HOVERED = 1 << 2 /* menu entry: pointer is over it */ +} +wuss_icon_state_t; + struct wuss_icon { wuss_window_t *window; /* owner; back-pointer for invalidate/get_window */ box_t bbox; /* virtual document space */ wuss_icon_type_t type; char *text; /* owned; never NULL ("" instead) */ - wuss_colour_t fg; - wuss_colour_t bg; - screen_pattern_t pattern; /* wuss_ICON_TYPE_PATTERN tile; 0 otherwise */ - const bitmap_t *bitmap; /* wuss_ICON_TYPE_BITMAP image; borrowed, or NULL */ - int group; /* radio: exclusive-selection group; 0 = none */ + wuss_colour_t fg, bg; + screen_pattern_t pattern; /* wuss_ICON_TYPE_PATTERN tile; 0 otherwise */ + const bitmap_t *bitmap; /* wuss_ICON_TYPE_BITMAP image; borrowed, or NULL */ + int group; /* radio: exclusive-selection group; 0 = none */ wuss_icon_flags_t flags; - int pressed; /* button: 1 while held with the pointer inside */ - int selected; /* radio/option: 1 while latched on; menu entry: - * 1 draws a tick */ - int hovered; /* menu entry: 1 while the pointer is over it */ + wuss_icon_state_t state; }; +static inline int wuss__icon_pressed(const wuss_icon_t *icon) +{ + return (icon->state & wuss_ICON_STATE_PRESSED) != 0; +} + +static inline int wuss__icon_selected(const wuss_icon_t *icon) +{ + return (icon->state & wuss_ICON_STATE_SELECTED) != 0; +} + +static inline int wuss__icon_hovered(const wuss_icon_t *icon) +{ + return (icon->state & wuss_ICON_STATE_HOVERED) != 0; +} + +static inline void wuss__icon_set_state(wuss_icon_t *icon, + wuss_icon_state_t bit, + int on) +{ + if (on) + icon->state |= bit; + else + icon->state &= (wuss_icon_state_t) ~bit; +} + /* Set icon->selected, invalidating it. For a radio with a non-zero group, * selecting it also clears every other selected radio on the same window with * that group. Ignored for types with no latched state. */ diff --git a/libraries/wuss/icon/create.c b/libraries/wuss/icon/create.c index a9d481b..7cd21c7 100644 --- a/libraries/wuss/icon/create.c +++ b/libraries/wuss/icon/create.c @@ -89,9 +89,7 @@ result_t wuss_icon_create(wuss_window_t *window, it->bitmap = (spec->type == wuss_ICON_TYPE_BITMAP) ? spec->bitmap : NULL; it->group = (spec->type == wuss_ICON_TYPE_RADIO) ? spec->group : 0; it->flags = spec->flags; - it->pressed = 0; - it->selected = 0; - it->hovered = 0; + it->state = wuss_ICON_STATE_NONE; window->icons[window->nicons++] = it; diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index 796017e..d1de16e 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -188,7 +188,7 @@ static void wuss__icon_draw_button(const icon_draw_ctx_t *c) colour_t light, dark, base, label; int pressed, is_default; - pressed = icon->pressed; + pressed = wuss__icon_pressed(icon); is_default = (icon->flags & wuss_ICON_FLAGS_DEFAULT) != 0; if (is_default) @@ -284,7 +284,7 @@ static void wuss__icon_draw_radio_option(const icon_draw_ctx_t *c) screen_draw_line(c->scr, g.x0 + 2, g.y1 - 1, g.x1 - 3, g.y1 - 1, glyph); screen_draw_line(c->scr, g.x0, g.y0 + 2, g.x0, g.y1 - 3, glyph); screen_draw_line(c->scr, g.x1 - 1, g.y0 + 2, g.x1 - 1, g.y1 - 3, glyph); - if (icon->selected) + if (wuss__icon_selected(icon)) screen_fill_rect(c->scr, g.x0 + 3, g.y0 + 3, SIZE2D(gsz - 6, gsz - 6), glyph); } @@ -293,7 +293,7 @@ static void wuss__icon_draw_radio_option(const icon_draw_ctx_t *c) /* a box; a tick (two strokes) when selected */ screen_draw_rect(c->scr, g.x0, g.y0, SIZE2D(g.x1 - g.x0, g.y1 - g.y0), glyph); - if (icon->selected) + if (wuss__icon_selected(icon)) { screen_draw_line(c->scr, g.x0 + 2, g.y0 + gsz / 2, g.x0 + gsz / 2 - 1, g.y1 - 3, glyph); @@ -354,7 +354,7 @@ static void wuss__icon_draw_menu_entry(const icon_draw_ctx_t *c) int disabled, highlit, pad, mid_y; disabled = (icon->flags & wuss_ICON_FLAGS_DISABLED) != 0; - highlit = icon->hovered && !disabled; + highlit = wuss__icon_hovered(icon) && !disabled; pad = 4; /* resolve the row's own ink over its own/inherited ground */ @@ -383,7 +383,7 @@ static void wuss__icon_draw_menu_entry(const icon_draw_ctx_t *c) } /* left-edge tick when selected */ - if (icon->selected) + if (wuss__icon_selected(icon)) { int cx, cy, h; diff --git a/libraries/wuss/icon/get-selected.c b/libraries/wuss/icon/get-selected.c index cb2add4..3029e5d 100644 --- a/libraries/wuss/icon/get-selected.c +++ b/libraries/wuss/icon/get-selected.c @@ -8,5 +8,5 @@ int wuss_icon_get_selected(const wuss_icon_t *icon) { assert(icon != NULL); - return icon->selected; + return wuss__icon_selected(icon); } diff --git a/libraries/wuss/icon/set-hover.c b/libraries/wuss/icon/set-hover.c index 8fc1ec7..08ef518 100644 --- a/libraries/wuss/icon/set-hover.c +++ b/libraries/wuss/icon/set-hover.c @@ -5,7 +5,7 @@ #include "../impl.h" /* Only wuss_ICON_TYPE_MENU_ENTRY changes appearance on hover (see - * wuss__icon_draw_menu_entry). Buttons and every other type ignore icon->hovered, + * wuss__icon_draw_menu_entry). Buttons and every other type ignore the hover state, * so redrawing them on pointer enter/leave is wasted work. */ static int wuss__icon_hover_visible(const wuss_icon_t *icon) { @@ -22,7 +22,7 @@ void wuss__icon_set_hover(wuss_t *wuss, wuss_icon_t *icon) if (prev != NULL) { - prev->hovered = 0; + wuss__icon_set_state(prev, wuss_ICON_STATE_HOVERED, 0); if (wuss__icon_hover_visible(prev)) wuss__icon_invalidate(prev); } @@ -31,7 +31,7 @@ void wuss__icon_set_hover(wuss_t *wuss, wuss_icon_t *icon) if (icon != NULL) { - icon->hovered = 1; + wuss__icon_set_state(icon, wuss_ICON_STATE_HOVERED, 1); if (wuss__icon_hover_visible(icon)) wuss__icon_invalidate(icon); } diff --git a/libraries/wuss/icon/set-selected.c b/libraries/wuss/icon/set-selected.c index ea6c356..6344ac6 100644 --- a/libraries/wuss/icon/set-selected.c +++ b/libraries/wuss/icon/set-selected.c @@ -31,17 +31,17 @@ void wuss__icon_select(wuss_icon_t *icon, int selected) continue; if (other->type != wuss_ICON_TYPE_RADIO || other->group != icon->group) continue; - if (!other->selected) + if (!wuss__icon_selected(other)) continue; - other->selected = 0; + wuss__icon_set_state(other, wuss_ICON_STATE_SELECTED, 0); wuss__icon_invalidate(other); } } - if (icon->selected == selected) + if (wuss__icon_selected(icon) == selected) return; - icon->selected = selected; + wuss__icon_set_state(icon, wuss_ICON_STATE_SELECTED, selected); wuss__icon_invalidate(icon); } diff --git a/libraries/wuss/mouse-click.c b/libraries/wuss/mouse-click.c index d0bf85d..91c4ef6 100644 --- a/libraries/wuss/mouse-click.c +++ b/libraries/wuss/mouse-click.c @@ -26,9 +26,9 @@ result_t wuss_mouse_click(wuss_t *wuss, wuss_icon_t *pressed = wuss->pressed_icon; wuss->pressed_icon = NULL; - if (pressed->pressed) + if (wuss__icon_pressed(pressed)) { - pressed->pressed = 0; + wuss__icon_set_state(pressed, wuss_ICON_STATE_PRESSED, 0); wuss__icon_invalidate(pressed); } } @@ -209,23 +209,23 @@ result_t wuss_mouse_click(wuss_t *wuss, if (action == wuss_MOUSE_DOWN && (button & (wuss_BUTTON_SELECT | wuss_BUTTON_ADJUST))) { - icon->pressed = 1; + wuss__icon_set_state(icon, wuss_ICON_STATE_PRESSED, 1); wuss->pressed_icon = icon; wuss__icon_invalidate(icon); } - else if (action == wuss_MOUSE_UP && icon->pressed) + else if (action == wuss_MOUSE_UP && wuss__icon_pressed(icon)) { - icon->pressed = 0; + wuss__icon_set_state(icon, wuss_ICON_STATE_PRESSED, 0); wuss->pressed_icon = NULL; wuss__icon_invalidate(icon); /* a completed click latches radio/option state before the task is * told, so the wuss_EVENT_ICON handler sees the new value */ if (icon->type == wuss_ICON_TYPE_OPTION) - wuss__icon_select(icon, !icon->selected); + wuss__icon_select(icon, !wuss__icon_selected(icon)); else if (icon->type == wuss_ICON_TYPE_RADIO) wuss__icon_select(icon, - (button & wuss_BUTTON_ADJUST) ? !icon->selected + (button & wuss_BUTTON_ADJUST) ? !wuss__icon_selected(icon) : 1); } diff --git a/libraries/wuss/mouse-move.c b/libraries/wuss/mouse-move.c index a0495cf..3245328 100644 --- a/libraries/wuss/mouse-move.c +++ b/libraries/wuss/mouse-move.c @@ -99,9 +99,9 @@ result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit) { wuss_icon_t *it = win->icons[k]; - if (it->pressed && it != icon) + if (wuss__icon_pressed(it) && it != icon) { - it->pressed = 0; + wuss__icon_set_state(it, wuss_ICON_STATE_PRESSED, 0); if (wuss->pressed_icon == it) wuss->pressed_icon = NULL; wuss__icon_invalidate(it); From 94cdbf289ae1524d7b7ecf86372d19b78e94a1b2 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 13:06:36 +0100 Subject: [PATCH 42/79] feat(screen): add 17-level Bayer dither fill patterns screen_pattern_t gains screen_PATTERN_BAYER0..BAYER16, a 4x4 ordered dither ramp from empty to solid, indexable as BAYER0 + level. BAYER8 duplicates GREY50 and BAYER16 duplicates SOLID; both kept so the level arithmetic stays simple. Tiles are the 4x4 matrix repeated across the existing 8x8 tile rows. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- include/framebuf/screen.h | 25 +++++++++++++++++++ .../framebuf/screen/screen-fill-pattern.c | 24 +++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/include/framebuf/screen.h b/include/framebuf/screen.h index 2c89ec3..6483b6f 100644 --- a/include/framebuf/screen.h +++ b/include/framebuf/screen.h @@ -108,6 +108,31 @@ typedef enum screen_pattern screen_PATTERN_DOTS, /**< Sparse dots. */ screen_PATTERN_GRID, /**< Thin grid lines. */ screen_PATTERN_CROSSHATCH, /**< Crossed thin lines. */ + + /** + * 4x4 ordered (Bayer) dither, coverage levels 0 (empty) to 16 (solid). Index + * by level as `screen_PATTERN_BAYER0 + level`. `screen_PATTERN_BAYER8` is + * identical to `screen_PATTERN_GREY50` and `screen_PATTERN_BAYER16` to + * `screen_PATTERN_SOLID`; both are kept so the level arithmetic stays simple. + */ + screen_PATTERN_BAYER0, + screen_PATTERN_BAYER1, + screen_PATTERN_BAYER2, + screen_PATTERN_BAYER3, + screen_PATTERN_BAYER4, + screen_PATTERN_BAYER5, + screen_PATTERN_BAYER6, + screen_PATTERN_BAYER7, + screen_PATTERN_BAYER8, + screen_PATTERN_BAYER9, + screen_PATTERN_BAYER10, + screen_PATTERN_BAYER11, + screen_PATTERN_BAYER12, + screen_PATTERN_BAYER13, + screen_PATTERN_BAYER14, + screen_PATTERN_BAYER15, + screen_PATTERN_BAYER16, + screen_PATTERN__LIMIT /**< Count of patterns; not itself a pattern. */ } screen_pattern_t; diff --git a/libraries/framebuf/screen/screen-fill-pattern.c b/libraries/framebuf/screen/screen-fill-pattern.c index 47fda84..84b8427 100644 --- a/libraries/framebuf/screen/screen-fill-pattern.c +++ b/libraries/framebuf/screen/screen-fill-pattern.c @@ -19,7 +19,29 @@ static const unsigned char patterns[screen_PATTERN__LIMIT][8] = { 0x88, 0x44, 0x22, 0x11, 0x88, 0x44, 0x22, 0x11 }, /* DIAGONAL */ { 0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00 }, /* DOTS */ { 0xFF, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, /* GRID */ - { 0x80, 0x41, 0x22, 0x14, 0x08, 0x14, 0x22, 0x41 } /* CROSSHATCH */ + { 0x80, 0x41, 0x22, 0x14, 0x08, 0x14, 0x22, 0x41 }, /* CROSSHATCH */ + + /* 4x4 ordered (Bayer) dither, levels 0..16. Level N sets the pixels whose + * threshold in the 4x4 matrix is < N, tiled twice to fill the 8x8 row. + * BAYER8 equals GREY50 and BAYER16 equals SOLID; kept in the run so a + * caller can index by level as screen_PATTERN_BAYER0 + level. */ + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* BAYER0 */ + { 0x88, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00 }, /* BAYER1 */ + { 0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00 }, /* BAYER2 */ + { 0xAA, 0x00, 0x22, 0x00, 0xAA, 0x00, 0x22, 0x00 }, /* BAYER3 */ + { 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00 }, /* BAYER4 */ + { 0xAA, 0x44, 0xAA, 0x00, 0xAA, 0x44, 0xAA, 0x00 }, /* BAYER5 */ + { 0xAA, 0x44, 0xAA, 0x11, 0xAA, 0x44, 0xAA, 0x11 }, /* BAYER6 */ + { 0xAA, 0x55, 0xAA, 0x11, 0xAA, 0x55, 0xAA, 0x11 }, /* BAYER7 */ + { 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55 }, /* BAYER8 */ + { 0xEE, 0x55, 0xAA, 0x55, 0xEE, 0x55, 0xAA, 0x55 }, /* BAYER9 */ + { 0xEE, 0x55, 0xBB, 0x55, 0xEE, 0x55, 0xBB, 0x55 }, /* BAYER10 */ + { 0xFF, 0x55, 0xBB, 0x55, 0xFF, 0x55, 0xBB, 0x55 }, /* BAYER11 */ + { 0xFF, 0x55, 0xFF, 0x55, 0xFF, 0x55, 0xFF, 0x55 }, /* BAYER12 */ + { 0xFF, 0xDD, 0xFF, 0x55, 0xFF, 0xDD, 0xFF, 0x55 }, /* BAYER13 */ + { 0xFF, 0xDD, 0xFF, 0x77, 0xFF, 0xDD, 0xFF, 0x77 }, /* BAYER14 */ + { 0xFF, 0xFF, 0xFF, 0x77, 0xFF, 0xFF, 0xFF, 0x77 }, /* BAYER15 */ + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } /* BAYER16 */ }; void screen_fill_pattern(screen_t *scr, From ecbb75df41ad822d57bcfeb3e84443dda493253d Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 13:16:25 +0100 Subject: [PATCH 43/79] refactor(wuss): show pattern swatches as a 16x16 grid The icons task laid its fill-pattern swatches out as a single tall column of 90x36 cells. With the pattern set now 25 entries that column ran far past the document; show them as 16x16 swatches on a 6-wide grid instead. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/test/tasks/icons.c | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/libraries/wuss/test/tasks/icons.c b/libraries/wuss/test/tasks/icons.c index 5b92365..41b144c 100644 --- a/libraries/wuss/test/tasks/icons.c +++ b/libraries/wuss/test/tasks/icons.c @@ -30,11 +30,12 @@ result_t icons_create(wuss_t *wuss, const char *resources, icons_task_t *task) { - /* [0..3], one swatch per built-in pattern, then [.. +3] a grouping frame - * with two differently-justified labels inside it, then [.. +5] three grouped - * radios, a standalone option and a label echoing the selection, then [.. +2] - * a decorative bitmap icon and an interactive one that bumps the counter, - * then [.. +4] a strip of menu-entry icons that hover-highlight */ + /* [0..3] heading, counter button, counter label and a scrolled-away button, + * then [.. +N] one swatch per built-in fill pattern, then [.. +3] a grouping + * frame with two differently-justified labels inside it, then [.. +5] three + * grouped radios, a standalone option and a label echoing the selection, + * then [.. +2] a decorative bitmap icon and an interactive one that bumps + * the counter, then [.. +4] a strip of menu-entry icons that hover-highlight */ enum { ICONS_NSPECS = 4 + screen_PATTERN__LIMIT + 3 + 5 + 2 + 4 }; wuss_task_t delegate; wuss_icon_spec_t specs[ICONS_NSPECS]; @@ -119,11 +120,20 @@ result_t icons_create(wuss_t *wuss, specs[3].fg = palette_PICO8_BLACK; specs[3].bg = palette_PICO8_LIGHT_GREY; - /* [4..] one swatch per built-in pattern, in a column down the document so - * they scroll through the window and stay phase-locked while doing so */ + /* [4..] one 16x16 swatch per built-in fill pattern, laid out as a grid down + * the document so it scrolls through the window and stays phase-locked while + * doing so */ for (p = 0; p < screen_PATTERN__LIMIT; p++) { - specs[4 + p].bbox = (box_t) BOX_POS_SIZE(28, 90 + p * 44, 90, 36); + enum { ICONS_SWATCH_COLS = 6, ICONS_SWATCH_PITCH = 20 }; + int col, row; + + col = p % ICONS_SWATCH_COLS; + row = p / ICONS_SWATCH_COLS; + + specs[4 + p].bbox = (box_t) BOX_POS_SIZE(28 + col * ICONS_SWATCH_PITCH, + 90 + row * ICONS_SWATCH_PITCH, + 16, 16); specs[4 + p].type = wuss_ICON_TYPE_PATTERN; specs[4 + p].fg = palette_PICO8_DARK_BLUE; specs[4 + p].bg = palette_PICO8_LIGHT_GREY; From d105a9d18ffdd853ceaba7683efda0e0c54fd1c5 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 13:24:13 +0100 Subject: [PATCH 44/79] feat(screen): use the full 8x8 65-level Bayer dither ramp Replaces the 17-level 4x4 Bayer ramp with the full 8x8 one: 65 coverage levels, one per threshold in the recursively-built 8x8 matrix, still indexed as screen_PATTERN_BAYER0 + level. The 65 levels past BAYER0 are left unnamed in the enum with BAYER_LIMIT one past the end; a static assert pins the tile table length to screen_PATTERN__LIMIT. BAYER32 equals GREY50 and BAYER64 equals SOLID, kept for simple arithmetic. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- include/framebuf/screen.h | 31 ++----- .../framebuf/screen/screen-fill-pattern.c | 92 +++++++++++++++---- 2 files changed, 83 insertions(+), 40 deletions(-) diff --git a/include/framebuf/screen.h b/include/framebuf/screen.h index 6483b6f..f8e8f08 100644 --- a/include/framebuf/screen.h +++ b/include/framebuf/screen.h @@ -110,30 +110,19 @@ typedef enum screen_pattern screen_PATTERN_CROSSHATCH, /**< Crossed thin lines. */ /** - * 4x4 ordered (Bayer) dither, coverage levels 0 (empty) to 16 (solid). Index - * by level as `screen_PATTERN_BAYER0 + level`. `screen_PATTERN_BAYER8` is - * identical to `screen_PATTERN_GREY50` and `screen_PATTERN_BAYER16` to - * `screen_PATTERN_SOLID`; both are kept so the level arithmetic stays simple. + * 8x8 ordered (Bayer) dither, one entry per coverage level 0 (empty) to 64 + * (solid). Index by level as `screen_PATTERN_BAYER0 + level`; + * `screen_PATTERN_BAYER_LIMIT` is one past the last. `screen_PATTERN_BAYER0` + * matches nothing else here, `screen_PATTERN_BAYER32` equals + * `screen_PATTERN_GREY50` and `screen_PATTERN_BAYER64` equals + * `screen_PATTERN_SOLID`; the duplicates are kept so the level arithmetic + * stays simple. */ screen_PATTERN_BAYER0, - screen_PATTERN_BAYER1, - screen_PATTERN_BAYER2, - screen_PATTERN_BAYER3, - screen_PATTERN_BAYER4, - screen_PATTERN_BAYER5, - screen_PATTERN_BAYER6, - screen_PATTERN_BAYER7, - screen_PATTERN_BAYER8, - screen_PATTERN_BAYER9, - screen_PATTERN_BAYER10, - screen_PATTERN_BAYER11, - screen_PATTERN_BAYER12, - screen_PATTERN_BAYER13, - screen_PATTERN_BAYER14, - screen_PATTERN_BAYER15, - screen_PATTERN_BAYER16, + screen_PATTERN_BAYER_LIMIT = screen_PATTERN_BAYER0 + 65, - screen_PATTERN__LIMIT /**< Count of patterns; not itself a pattern. */ + screen_PATTERN__LIMIT = screen_PATTERN_BAYER_LIMIT + /**< Count of patterns; not itself a pattern. */ } screen_pattern_t; diff --git a/libraries/framebuf/screen/screen-fill-pattern.c b/libraries/framebuf/screen/screen-fill-pattern.c index 84b8427..5d5ed8a 100644 --- a/libraries/framebuf/screen/screen-fill-pattern.c +++ b/libraries/framebuf/screen/screen-fill-pattern.c @@ -21,29 +21,83 @@ static const unsigned char patterns[screen_PATTERN__LIMIT][8] = { 0xFF, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, /* GRID */ { 0x80, 0x41, 0x22, 0x14, 0x08, 0x14, 0x22, 0x41 }, /* CROSSHATCH */ - /* 4x4 ordered (Bayer) dither, levels 0..16. Level N sets the pixels whose - * threshold in the 4x4 matrix is < N, tiled twice to fill the 8x8 row. - * BAYER8 equals GREY50 and BAYER16 equals SOLID; kept in the run so a + /* 8x8 ordered (Bayer) dither, one row per coverage level 0..64. Level N sets + * the pixels whose threshold in the recursively-built 8x8 matrix is < N. + * BAYER32 equals GREY50 and BAYER64 equals SOLID; kept in the run so a * caller can index by level as screen_PATTERN_BAYER0 + level. */ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* BAYER0 */ - { 0x88, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00 }, /* BAYER1 */ - { 0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00 }, /* BAYER2 */ - { 0xAA, 0x00, 0x22, 0x00, 0xAA, 0x00, 0x22, 0x00 }, /* BAYER3 */ - { 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00 }, /* BAYER4 */ - { 0xAA, 0x44, 0xAA, 0x00, 0xAA, 0x44, 0xAA, 0x00 }, /* BAYER5 */ - { 0xAA, 0x44, 0xAA, 0x11, 0xAA, 0x44, 0xAA, 0x11 }, /* BAYER6 */ - { 0xAA, 0x55, 0xAA, 0x11, 0xAA, 0x55, 0xAA, 0x11 }, /* BAYER7 */ - { 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55 }, /* BAYER8 */ - { 0xEE, 0x55, 0xAA, 0x55, 0xEE, 0x55, 0xAA, 0x55 }, /* BAYER9 */ - { 0xEE, 0x55, 0xBB, 0x55, 0xEE, 0x55, 0xBB, 0x55 }, /* BAYER10 */ - { 0xFF, 0x55, 0xBB, 0x55, 0xFF, 0x55, 0xBB, 0x55 }, /* BAYER11 */ - { 0xFF, 0x55, 0xFF, 0x55, 0xFF, 0x55, 0xFF, 0x55 }, /* BAYER12 */ - { 0xFF, 0xDD, 0xFF, 0x55, 0xFF, 0xDD, 0xFF, 0x55 }, /* BAYER13 */ - { 0xFF, 0xDD, 0xFF, 0x77, 0xFF, 0xDD, 0xFF, 0x77 }, /* BAYER14 */ - { 0xFF, 0xFF, 0xFF, 0x77, 0xFF, 0xFF, 0xFF, 0x77 }, /* BAYER15 */ - { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } /* BAYER16 */ + { 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* BAYER1 */ + { 0x80, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00 }, /* BAYER2 */ + { 0x80, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00 }, /* BAYER3 */ + { 0x88, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00 }, /* BAYER4 */ + { 0x88, 0x00, 0x20, 0x00, 0x88, 0x00, 0x00, 0x00 }, /* BAYER5 */ + { 0x88, 0x00, 0x20, 0x00, 0x88, 0x00, 0x02, 0x00 }, /* BAYER6 */ + { 0x88, 0x00, 0x20, 0x00, 0x88, 0x00, 0x22, 0x00 }, /* BAYER7 */ + { 0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00 }, /* BAYER8 */ + { 0x88, 0x00, 0xA2, 0x00, 0x88, 0x00, 0x22, 0x00 }, /* BAYER9 */ + { 0x88, 0x00, 0xA2, 0x00, 0x88, 0x00, 0x2A, 0x00 }, /* BAYER10 */ + { 0x88, 0x00, 0xA2, 0x00, 0x88, 0x00, 0xAA, 0x00 }, /* BAYER11 */ + { 0x88, 0x00, 0xAA, 0x00, 0x88, 0x00, 0xAA, 0x00 }, /* BAYER12 */ + { 0xA8, 0x00, 0xAA, 0x00, 0x88, 0x00, 0xAA, 0x00 }, /* BAYER13 */ + { 0xA8, 0x00, 0xAA, 0x00, 0x8A, 0x00, 0xAA, 0x00 }, /* BAYER14 */ + { 0xA8, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00 }, /* BAYER15 */ + { 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00 }, /* BAYER16 */ + { 0xAA, 0x40, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00 }, /* BAYER17 */ + { 0xAA, 0x40, 0xAA, 0x00, 0xAA, 0x04, 0xAA, 0x00 }, /* BAYER18 */ + { 0xAA, 0x40, 0xAA, 0x00, 0xAA, 0x44, 0xAA, 0x00 }, /* BAYER19 */ + { 0xAA, 0x44, 0xAA, 0x00, 0xAA, 0x44, 0xAA, 0x00 }, /* BAYER20 */ + { 0xAA, 0x44, 0xAA, 0x10, 0xAA, 0x44, 0xAA, 0x00 }, /* BAYER21 */ + { 0xAA, 0x44, 0xAA, 0x10, 0xAA, 0x44, 0xAA, 0x01 }, /* BAYER22 */ + { 0xAA, 0x44, 0xAA, 0x10, 0xAA, 0x44, 0xAA, 0x11 }, /* BAYER23 */ + { 0xAA, 0x44, 0xAA, 0x11, 0xAA, 0x44, 0xAA, 0x11 }, /* BAYER24 */ + { 0xAA, 0x44, 0xAA, 0x51, 0xAA, 0x44, 0xAA, 0x11 }, /* BAYER25 */ + { 0xAA, 0x44, 0xAA, 0x51, 0xAA, 0x44, 0xAA, 0x15 }, /* BAYER26 */ + { 0xAA, 0x44, 0xAA, 0x51, 0xAA, 0x44, 0xAA, 0x55 }, /* BAYER27 */ + { 0xAA, 0x44, 0xAA, 0x55, 0xAA, 0x44, 0xAA, 0x55 }, /* BAYER28 */ + { 0xAA, 0x54, 0xAA, 0x55, 0xAA, 0x44, 0xAA, 0x55 }, /* BAYER29 */ + { 0xAA, 0x54, 0xAA, 0x55, 0xAA, 0x45, 0xAA, 0x55 }, /* BAYER30 */ + { 0xAA, 0x54, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55 }, /* BAYER31 */ + { 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55 }, /* BAYER32 */ + { 0xAA, 0xD5, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55 }, /* BAYER33 */ + { 0xAA, 0xD5, 0xAA, 0x55, 0xAA, 0x5D, 0xAA, 0x55 }, /* BAYER34 */ + { 0xAA, 0xD5, 0xAA, 0x55, 0xAA, 0xDD, 0xAA, 0x55 }, /* BAYER35 */ + { 0xAA, 0xDD, 0xAA, 0x55, 0xAA, 0xDD, 0xAA, 0x55 }, /* BAYER36 */ + { 0xAA, 0xDD, 0xAA, 0x75, 0xAA, 0xDD, 0xAA, 0x55 }, /* BAYER37 */ + { 0xAA, 0xDD, 0xAA, 0x75, 0xAA, 0xDD, 0xAA, 0x57 }, /* BAYER38 */ + { 0xAA, 0xDD, 0xAA, 0x75, 0xAA, 0xDD, 0xAA, 0x77 }, /* BAYER39 */ + { 0xAA, 0xDD, 0xAA, 0x77, 0xAA, 0xDD, 0xAA, 0x77 }, /* BAYER40 */ + { 0xAA, 0xDD, 0xAA, 0xF7, 0xAA, 0xDD, 0xAA, 0x77 }, /* BAYER41 */ + { 0xAA, 0xDD, 0xAA, 0xF7, 0xAA, 0xDD, 0xAA, 0x7F }, /* BAYER42 */ + { 0xAA, 0xDD, 0xAA, 0xF7, 0xAA, 0xDD, 0xAA, 0xFF }, /* BAYER43 */ + { 0xAA, 0xDD, 0xAA, 0xFF, 0xAA, 0xDD, 0xAA, 0xFF }, /* BAYER44 */ + { 0xAA, 0xFD, 0xAA, 0xFF, 0xAA, 0xDD, 0xAA, 0xFF }, /* BAYER45 */ + { 0xAA, 0xFD, 0xAA, 0xFF, 0xAA, 0xDF, 0xAA, 0xFF }, /* BAYER46 */ + { 0xAA, 0xFD, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF }, /* BAYER47 */ + { 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF }, /* BAYER48 */ + { 0xEA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF }, /* BAYER49 */ + { 0xEA, 0xFF, 0xAA, 0xFF, 0xAE, 0xFF, 0xAA, 0xFF }, /* BAYER50 */ + { 0xEA, 0xFF, 0xAA, 0xFF, 0xEE, 0xFF, 0xAA, 0xFF }, /* BAYER51 */ + { 0xEE, 0xFF, 0xAA, 0xFF, 0xEE, 0xFF, 0xAA, 0xFF }, /* BAYER52 */ + { 0xEE, 0xFF, 0xBA, 0xFF, 0xEE, 0xFF, 0xAA, 0xFF }, /* BAYER53 */ + { 0xEE, 0xFF, 0xBA, 0xFF, 0xEE, 0xFF, 0xAB, 0xFF }, /* BAYER54 */ + { 0xEE, 0xFF, 0xBA, 0xFF, 0xEE, 0xFF, 0xBB, 0xFF }, /* BAYER55 */ + { 0xEE, 0xFF, 0xBB, 0xFF, 0xEE, 0xFF, 0xBB, 0xFF }, /* BAYER56 */ + { 0xEE, 0xFF, 0xFB, 0xFF, 0xEE, 0xFF, 0xBB, 0xFF }, /* BAYER57 */ + { 0xEE, 0xFF, 0xFB, 0xFF, 0xEE, 0xFF, 0xBF, 0xFF }, /* BAYER58 */ + { 0xEE, 0xFF, 0xFB, 0xFF, 0xEE, 0xFF, 0xFF, 0xFF }, /* BAYER59 */ + { 0xEE, 0xFF, 0xFF, 0xFF, 0xEE, 0xFF, 0xFF, 0xFF }, /* BAYER60 */ + { 0xFE, 0xFF, 0xFF, 0xFF, 0xEE, 0xFF, 0xFF, 0xFF }, /* BAYER61 */ + { 0xFE, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF }, /* BAYER62 */ + { 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, /* BAYER63 */ + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } /* BAYER64 */ }; +/* the enum leaves the 65 Bayer levels unnamed past BAYER0, so keep the table + * length pinned to it here */ +typedef char + patterns_len_matches_enum[(sizeof patterns / sizeof patterns[0]) == + screen_PATTERN__LIMIT ? 1 : -1]; + void screen_fill_pattern(screen_t *scr, const box_t *box, screen_pattern_t pattern, From 9bcf4ad5623abc0635a81dd5da172846cb255660 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 14:03:33 +0100 Subject: [PATCH 45/79] feat(wuss): clamp windows so they can never grow larger than the screen New wuss__max_content_on_screen helper gives the largest content size whose visible box still fits the screen from the window's current top-left, floored at WUSS_MIN_CONTENT so a window jammed against the far edge stays grabbable. wuss_window_create and wuss_window_resize both clamp through it, covering client calls, drag-resize and creation; toggle-size already had an equivalent bound of its own. Adds create/resize clamp tests to both wuss_test bodies. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/impl.h | 27 ++++++++ libraries/wuss/test/wuss-test.c | 106 ++++++++++++++++++++++++++++++++ libraries/wuss/window/create.c | 16 +++++ libraries/wuss/window/resize.c | 11 ++++ 4 files changed, 160 insertions(+) diff --git a/libraries/wuss/impl.h b/libraries/wuss/impl.h index 9e308f8..84be126 100644 --- a/libraries/wuss/impl.h +++ b/libraries/wuss/impl.h @@ -401,4 +401,31 @@ static inline void wuss__furniture_carve_for(wuss_window_flags_t flags, } #endif /* WUSS_FURNITURE */ +/* Largest content width/height whose visible box (content + outline + + * titlebar + scrollbar/resize carve) still fits the screen from the + * window's current top-left. Never returns below WUSS_MIN_CONTENT: a + * window jammed hard against the far edge stays grabbable even though it + * then overhangs. Shared by wuss_window_create and wuss_window_resize so + * no path can produce a window larger than the screen. */ +static inline void wuss__max_content_on_screen(const wuss_window_t *window, + size2d_t *max) +{ + int outline_px, titlebar_height; + point_t carve; + + outline_px = wuss__outline_px(window); + titlebar_height = wuss__titlebar_height(window); + wuss__furniture_carve_for(window->flags, wuss__button_size(window), &carve); + + max->w = window->wuss->scr->size.w - window->visible.x0 + - 2 * outline_px - carve.x; + max->h = window->wuss->scr->size.h - window->visible.y0 + - 2 * outline_px - titlebar_height - carve.y; + + if (max->w < WUSS_MIN_CONTENT) + max->w = WUSS_MIN_CONTENT; + if (max->h < WUSS_MIN_CONTENT) + max->h = WUSS_MIN_CONTENT; +} + #endif /* IMPL_H */ diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index eefbca5..f203cab 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -528,6 +528,59 @@ result_t wuss_test(const char *resources) if (width != 52 || height != 72) goto Failure; + printf("test: window_resize can never grow a window past the screen\n"); + + /* screen is 200x200; win_a's visible box sits at (0,25) with a 1px + * outline all round and a 20px titlebar. asking for a 500x500 content + * area must clamp so the visible box still fits: width 200-0-2, + * height 200-25-2-20. */ + rc = wuss_window_resize(win_a, SIZE2D(500, 500)); + if (rc != result_OK) + goto Failure; + wuss_window_get_content_bounds(win_a, &content); + if (content.x1 - content.x0 != 198 || content.y1 - content.y0 != 153) + goto Failure; + wuss_window_get_visible_bounds(win_a, &visible); + if (visible.x1 != 200 || visible.y1 != 200) + goto Failure; + + /* a request that already fits is honoured verbatim */ + rc = wuss_window_resize(win_a, SIZE2D(60, 40)); + if (rc != result_OK) + goto Failure; + wuss_window_get_content_bounds(win_a, &content); + if (content.x1 - content.x0 != 60 || content.y1 - content.y0 != 40) + goto Failure; + + /* restore for the tests that follow */ + rc = wuss_window_resize(win_a, SIZE2D(50, 50)); + if (rc != result_OK) + goto Failure; + + printf("test: window_create can never make a window bigger than the " + "screen\n"); + + { + box_t box_big; + wuss_window_t *win_big; + + /* content box asks for 10,10..400,400; the on-screen nudge pulls the + * visible top-left (10-1 outline) back to (0,0), then the clamp caps + * the content at 200 - 2*1 - 20(titlebar) tall, 200 - 2*1 wide. */ + box_big.x0 = 10; box_big.y0 = 10; box_big.x1 = 400; box_big.y1 = 400; + rc = wuss_window_create(wuss, &box_big, "BIG", wuss_WINDOW_NO_VSCROLL | + wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), NULL, + SIZE2D(400, 400), SIZE2D(0, 0), &win_big); + if (rc != result_OK) + goto Failure; + wuss_window_get_visible_bounds(win_big, &visible); + if (visible.x0 != 0 || visible.y0 != 0 || + visible.x1 != 200 || visible.y1 != 200) + goto Failure; + wuss_window_close(win_big); + } + printf("test: title-less, no-outline window has no furniture, so visible == content\n"); tc_d.redraw_count = 0; @@ -3060,6 +3113,59 @@ result_t wuss_test(const char *resources) content.x1 != 100 || content.y1 != 100) goto Failure; + printf("test: window_resize can never grow a window past the screen\n"); + + /* screen is 200x200; win_a sits at (0,0), chromeless. asking for a + * 500x500 content area must clamp to the 200x200 screen. */ + rc = wuss_window_resize(win_a, SIZE2D(500, 500)); + if (rc != result_OK) + goto Failure; + wuss_window_get_content_bounds(win_a, &content); + if (content.x1 - content.x0 != 200 || content.y1 - content.y0 != 200) + goto Failure; + + /* a request that already fits is left exactly as asked */ + rc = wuss_window_resize(win_a, SIZE2D(120, 90)); + if (rc != result_OK) + goto Failure; + wuss_window_get_content_bounds(win_a, &content); + if (content.x1 - content.x0 != 120 || content.y1 - content.y0 != 90) + goto Failure; + + /* a window whose top-left is offset only gets the space that is left */ + wuss_window_move(win_a, POINT(60, 40)); + rc = wuss_window_resize(win_a, SIZE2D(500, 500)); + if (rc != result_OK) + goto Failure; + wuss_window_get_content_bounds(win_a, &content); + if (content.x1 - content.x0 != 140 || content.y1 - content.y0 != 160) + goto Failure; + wuss_window_move(win_a, POINT(0, 0)); + rc = wuss_window_resize(win_a, SIZE2D(100, 100)); + if (rc != result_OK) + goto Failure; + + printf("test: window_create can never make a window bigger than the " + "screen\n"); + + { + box_t box_big; + wuss_window_t *win_big; + + /* chromeless and the on-screen nudge drags the top-left back to (0,0), + * so the clamp caps content at the full 200x200 screen */ + box_big.x0 = 10; box_big.y0 = 10; box_big.x1 = 400; box_big.y1 = 400; + rc = wuss_window_create(wuss, &box_big, "BIG", chromeless, + wuss_NO_BACKGROUND, NULL, SIZE2D(400, 400), + SIZE2D(0, 0), &win_big); + if (rc != result_OK) + goto Failure; + wuss_window_get_content_bounds(win_big, &content); + if (content.x1 - content.x0 != 200 || content.y1 - content.y0 != 200) + goto Failure; + wuss_window_close(win_big); + } + printf("test: redraw delivers REDRAW events\n"); wuss_redraw(wuss); diff --git a/libraries/wuss/window/create.c b/libraries/wuss/window/create.c index 1a5119e..130659f 100644 --- a/libraries/wuss/window/create.c +++ b/libraries/wuss/window/create.c @@ -74,6 +74,22 @@ result_t wuss_window_create(wuss_t *wuss, win->visible.y0 += dy; win->visible.y1 += dy; win->flags = flags; + + /* clamp so the window can never be born larger than the screen; done + * after the on-screen nudge above so it measures from the final + * top-left. only the bottom-right corner moves. */ + { + size2d_t max; + + wuss__max_content_on_screen(win, &max); + if (win->visible.x1 - win->visible.x0 - 2 * outline_px - carve.x > max.w) + win->visible.x1 = win->visible.x0 + 2 * outline_px + carve.x + max.w; + if (win->visible.y1 - win->visible.y0 - 2 * outline_px + - titlebar_height - carve.y > max.h) + win->visible.y1 = win->visible.y0 + 2 * outline_px + titlebar_height + + carve.y + max.h; + } + win->scroll.x = 0; win->scroll.y = 0; win->doc = doc; diff --git a/libraries/wuss/window/resize.c b/libraries/wuss/window/resize.c index 9c8e736..5c4a46a 100644 --- a/libraries/wuss/window/resize.c +++ b/libraries/wuss/window/resize.c @@ -45,6 +45,17 @@ result_t wuss_window_resize(wuss_window_t *window, size2d_t size) /* a manual resize desyncs the window from its layout-packer slot */ wuss__release_packed(window); + /* a window can never grow larger than the screen */ + { + size2d_t max; + + wuss__max_content_on_screen(window, &max); + if (size.w > max.w) + size.w = max.w; + if (size.h > max.h) + size.h = max.h; + } + outline_px = wuss__outline_px(window); titlebar_height = wuss__titlebar_height(window); before = window->visible; From c7322c40dd590f0eedd2fd775f56e2d4802e45a9 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 14:27:16 +0100 Subject: [PATCH 46/79] fix(wuss): give menus an outline wuss__menu_spawn passed wuss_WINDOW_NO_OUTLINE, so furniture drawing skipped the 1px border. Drop the flag; the outline sits outside the content box and does not overlap the menu rows. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/menu/menu.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index 6fdccc8..5934e1e 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -303,8 +303,7 @@ static result_t wuss__menu_spawn(wuss_t *wuss, rc = wuss_window_create(wuss, &content, menu->title ? menu->title : "", - wuss_WINDOW_NO_OUTLINE - | wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK + wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, From 90418b08323511ca4bdd27694a5f473cfced2426 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 14:30:04 +0100 Subject: [PATCH 47/79] fix(wuss): stop the menu outline cropping the last row Since menu windows gained an outline, wuss__menu_spawn's on-screen clamp still measured only the content box. The window's visible box also spans the outline on four sides and the titlebar above, so a menu opened near a screen edge had its visible box pushed past the screen, and wuss_window_create's own size clamp trimmed the content -- dropping the bottom row (e.g. a trailing "Quit"). Clamp against the full visible extent instead. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/menu/menu.c | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index 5934e1e..6d8a9d1 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -182,6 +182,9 @@ static result_t wuss__menu_spawn(wuss_t *wuss, int widest; int width, height; int i; + int outline_px; + int titlebar_height; + wuss_window_flags_t menu_flags; size2d_t size; box_t content; @@ -192,6 +195,11 @@ static result_t wuss__menu_spawn(wuss_t *wuss, if (menu->nitems <= 0) return result_WUSS_BAD_ICON; + menu_flags = wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK + | wuss_WINDOW_NO_TOGGLE_SIZE + | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL + | wuss_WINDOW_NO_RESIZE; + bmfont_get_info(wuss->font, &fw, &fh); NOT_USED(fw); pitch = fh + 2 * WUSS_MENU_ROW_PAD; @@ -220,15 +228,22 @@ static result_t wuss__menu_spawn(wuss_t *wuss, for (i = 0; i < menu->nitems; i++) height += (menu->items[i].flags & wuss_MENU_ITEM_DASHED) ? sep_h : pitch; - /* Nudge onto the screen where it can be, but keep the top-left on screen. */ - if (at.x + width > wuss->scr->size.w) - at.x = wuss->scr->size.w - width; - if (at.y + height > wuss->scr->size.h) - at.y = wuss->scr->size.h - height; - if (at.x < 0) - at.x = 0; - if (at.y < 0) - at.y = 0; + /* Nudge onto the screen where it can be, but keep the top-left on screen. + * `at` is the content top-left; the window's visible box also spans the + * outline on all four sides and the titlebar above, so clamp against that + * extent -- otherwise wuss_window_create's own on-screen size clamp trims + * the content and the last row (e.g. a trailing "Quit") is cropped. */ + outline_px = wuss__outline_px_for(menu_flags); + titlebar_height = wuss__titlebar_height_for(wuss, menu_flags); + + if (at.x + width + outline_px > wuss->scr->size.w) + at.x = wuss->scr->size.w - width - outline_px; + if (at.y + height + outline_px > wuss->scr->size.h) + at.y = wuss->scr->size.h - height - outline_px; + if (at.x - outline_px < 0) + at.x = outline_px; + if (at.y - outline_px - titlebar_height < 0) + at.y = outline_px + titlebar_height; node = wuss__malloc(wuss, sizeof(*node)); if (node == NULL) @@ -303,10 +318,7 @@ static result_t wuss__menu_spawn(wuss_t *wuss, rc = wuss_window_create(wuss, &content, menu->title ? menu->title : "", - wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK - | wuss_WINDOW_NO_TOGGLE_SIZE - | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL - | wuss_WINDOW_NO_RESIZE, + menu_flags, wuss_BACKDROP_COLOUR(wuss->white), &task, size, size, &node->window); if (rc != result_OK) From b5561a60ee4c14acce3b68c283ead85b825ed7c7 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 14:34:11 +0100 Subject: [PATCH 48/79] Revert "fix(wuss): stop the menu outline cropping the last row" This reverts commit 90418b08323511ca4bdd27694a5f473cfced2426. --- libraries/wuss/menu/menu.c | 38 +++++++++++++------------------------- 1 file changed, 13 insertions(+), 25 deletions(-) diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index 6d8a9d1..5934e1e 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -182,9 +182,6 @@ static result_t wuss__menu_spawn(wuss_t *wuss, int widest; int width, height; int i; - int outline_px; - int titlebar_height; - wuss_window_flags_t menu_flags; size2d_t size; box_t content; @@ -195,11 +192,6 @@ static result_t wuss__menu_spawn(wuss_t *wuss, if (menu->nitems <= 0) return result_WUSS_BAD_ICON; - menu_flags = wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK - | wuss_WINDOW_NO_TOGGLE_SIZE - | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL - | wuss_WINDOW_NO_RESIZE; - bmfont_get_info(wuss->font, &fw, &fh); NOT_USED(fw); pitch = fh + 2 * WUSS_MENU_ROW_PAD; @@ -228,22 +220,15 @@ static result_t wuss__menu_spawn(wuss_t *wuss, for (i = 0; i < menu->nitems; i++) height += (menu->items[i].flags & wuss_MENU_ITEM_DASHED) ? sep_h : pitch; - /* Nudge onto the screen where it can be, but keep the top-left on screen. - * `at` is the content top-left; the window's visible box also spans the - * outline on all four sides and the titlebar above, so clamp against that - * extent -- otherwise wuss_window_create's own on-screen size clamp trims - * the content and the last row (e.g. a trailing "Quit") is cropped. */ - outline_px = wuss__outline_px_for(menu_flags); - titlebar_height = wuss__titlebar_height_for(wuss, menu_flags); - - if (at.x + width + outline_px > wuss->scr->size.w) - at.x = wuss->scr->size.w - width - outline_px; - if (at.y + height + outline_px > wuss->scr->size.h) - at.y = wuss->scr->size.h - height - outline_px; - if (at.x - outline_px < 0) - at.x = outline_px; - if (at.y - outline_px - titlebar_height < 0) - at.y = outline_px + titlebar_height; + /* Nudge onto the screen where it can be, but keep the top-left on screen. */ + if (at.x + width > wuss->scr->size.w) + at.x = wuss->scr->size.w - width; + if (at.y + height > wuss->scr->size.h) + at.y = wuss->scr->size.h - height; + if (at.x < 0) + at.x = 0; + if (at.y < 0) + at.y = 0; node = wuss__malloc(wuss, sizeof(*node)); if (node == NULL) @@ -318,7 +303,10 @@ static result_t wuss__menu_spawn(wuss_t *wuss, rc = wuss_window_create(wuss, &content, menu->title ? menu->title : "", - menu_flags, + wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK + | wuss_WINDOW_NO_TOGGLE_SIZE + | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL + | wuss_WINDOW_NO_RESIZE, wuss_BACKDROP_COLOUR(wuss->white), &task, size, size, &node->window); if (rc != result_OK) From 5689b2ad364e0f422fd1d5d836ededb533b6e659 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 14:34:11 +0100 Subject: [PATCH 49/79] Revert "fix(wuss): give menus an outline" This reverts commit c7322c40dd590f0eedd2fd775f56e2d4802e45a9. --- libraries/wuss/menu/menu.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index 5934e1e..6fdccc8 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -303,7 +303,8 @@ static result_t wuss__menu_spawn(wuss_t *wuss, rc = wuss_window_create(wuss, &content, menu->title ? menu->title : "", - wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK + wuss_WINDOW_NO_OUTLINE + | wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, From ccfdef56f69233c1eb495aa139108e1f09e7f8b8 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 14:41:39 +0100 Subject: [PATCH 50/79] fix(wuss): restore the Quit row in the app demo menu 74d23fe redefined wuss_MENU_ITEM_DASHED from "pickable item with a rule above it" to "textless short separator row". The descriptor parser was updated to match, but the app's hardcoded g_menu still set DASHED on the Quit item itself, so menu.c discarded its text and drew a 6px blank rule instead of a Quit entry. Split it into a standalone DASHED separator followed by a plain Quit item. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- apps/wuss/main.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/apps/wuss/main.c b/apps/wuss/main.c index 2de23bc..fc43c76 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -205,8 +205,8 @@ static result_t spawn_porter_duff(void) } /* A static demo menu tree for the pop-up helper: a submenu, a couple of - * ticked rows and a dashed rule above the final entry. wuss_menu_open never - * mutates it. */ + * ticked rows and a standalone dashed rule row above the final entry. + * wuss_menu_open never mutates it. */ static const wuss_menu_item_t g_menu_export_items[] = { { "As PNG", wuss_MENU_ITEM_NONE, NULL }, @@ -224,7 +224,8 @@ static const wuss_menu_item_t g_menu_items[] = { "Show grid", wuss_MENU_ITEM_TICKED, NULL }, { "Wireframe", wuss_MENU_ITEM_TICKED, NULL }, { "Export", wuss_MENU_ITEM_NONE, &g_menu_export }, - { "Quit", wuss_MENU_ITEM_DASHED, NULL } + { "", wuss_MENU_ITEM_DASHED, NULL }, + { "Quit", wuss_MENU_ITEM_NONE, NULL } }; static const wuss_menu_t g_menu = { From ea0413de743f5ccf7f81b6df7f560649995c82f4 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 14:41:43 +0100 Subject: [PATCH 51/79] Reapply "fix(wuss): give menus an outline" This reverts commit 5689b2ad364e0f422fd1d5d836ededb533b6e659. --- libraries/wuss/menu/menu.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index 6fdccc8..5934e1e 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -303,8 +303,7 @@ static result_t wuss__menu_spawn(wuss_t *wuss, rc = wuss_window_create(wuss, &content, menu->title ? menu->title : "", - wuss_WINDOW_NO_OUTLINE - | wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK + wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, From 158490ff944878d18be00b680cbdae1a3d4d5652 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 14:41:43 +0100 Subject: [PATCH 52/79] Reapply "fix(wuss): stop the menu outline cropping the last row" This reverts commit b5561a60ee4c14acce3b68c283ead85b825ed7c7. --- libraries/wuss/menu/menu.c | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index 5934e1e..6d8a9d1 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -182,6 +182,9 @@ static result_t wuss__menu_spawn(wuss_t *wuss, int widest; int width, height; int i; + int outline_px; + int titlebar_height; + wuss_window_flags_t menu_flags; size2d_t size; box_t content; @@ -192,6 +195,11 @@ static result_t wuss__menu_spawn(wuss_t *wuss, if (menu->nitems <= 0) return result_WUSS_BAD_ICON; + menu_flags = wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK + | wuss_WINDOW_NO_TOGGLE_SIZE + | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL + | wuss_WINDOW_NO_RESIZE; + bmfont_get_info(wuss->font, &fw, &fh); NOT_USED(fw); pitch = fh + 2 * WUSS_MENU_ROW_PAD; @@ -220,15 +228,22 @@ static result_t wuss__menu_spawn(wuss_t *wuss, for (i = 0; i < menu->nitems; i++) height += (menu->items[i].flags & wuss_MENU_ITEM_DASHED) ? sep_h : pitch; - /* Nudge onto the screen where it can be, but keep the top-left on screen. */ - if (at.x + width > wuss->scr->size.w) - at.x = wuss->scr->size.w - width; - if (at.y + height > wuss->scr->size.h) - at.y = wuss->scr->size.h - height; - if (at.x < 0) - at.x = 0; - if (at.y < 0) - at.y = 0; + /* Nudge onto the screen where it can be, but keep the top-left on screen. + * `at` is the content top-left; the window's visible box also spans the + * outline on all four sides and the titlebar above, so clamp against that + * extent -- otherwise wuss_window_create's own on-screen size clamp trims + * the content and the last row (e.g. a trailing "Quit") is cropped. */ + outline_px = wuss__outline_px_for(menu_flags); + titlebar_height = wuss__titlebar_height_for(wuss, menu_flags); + + if (at.x + width + outline_px > wuss->scr->size.w) + at.x = wuss->scr->size.w - width - outline_px; + if (at.y + height + outline_px > wuss->scr->size.h) + at.y = wuss->scr->size.h - height - outline_px; + if (at.x - outline_px < 0) + at.x = outline_px; + if (at.y - outline_px - titlebar_height < 0) + at.y = outline_px + titlebar_height; node = wuss__malloc(wuss, sizeof(*node)); if (node == NULL) @@ -303,10 +318,7 @@ static result_t wuss__menu_spawn(wuss_t *wuss, rc = wuss_window_create(wuss, &content, menu->title ? menu->title : "", - wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK - | wuss_WINDOW_NO_TOGGLE_SIZE - | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL - | wuss_WINDOW_NO_RESIZE, + menu_flags, wuss_BACKDROP_COLOUR(wuss->white), &task, size, size, &node->window); if (rc != result_OK) From a299ebcdac15088e251d4f17945596476e17f528 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 15:44:58 +0100 Subject: [PATCH 53/79] refactor(wuss): make menu separators a flag on a real entry plus a RULE icon wuss_ICON_FLAGS_SEPARATOR / wuss_MENU_ITEM_DASHED no longer turn a row into a standalone inert separator. The flagged item stays a normal interactive entry with its own label; the dashed line is drawn by a new inert wuss_ICON_TYPE_RULE icon laid out separately above the entry. - icon.h: add wuss_ICON_TYPE_RULE; reword SEPARATOR flag and MENU_ENTRY docs - icon/create.c: accept the new type - icon/draw.c: add wuss__icon_draw_rule; drop the dashed-rule branch from wuss__icon_draw_menu_entry - icon/hit-test.c: RULE falls through as inert; drop the dead SEPARATOR/empty special case - menu/menu.c: emit a RULE icon of height sep_h before each dashed item; entries are now full-height; node->icons stays item-indexed (rule icons are owned by the window and not stored) - menu/create-from-desc.c: '|' sets DASHED on the prefixed item instead of emitting a blank separator item - tests and the wuss demo app updated to the new model Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- apps/wuss/main.c | 3 +- include/wuss/icon.h | 37 ++++++++---- include/wuss/menu.h | 28 +++++---- libraries/wuss/icon/create.c | 3 +- libraries/wuss/icon/draw.c | 30 ++++++---- libraries/wuss/icon/hit-test.c | 13 ++-- libraries/wuss/menu/create-from-desc.c | 14 ++--- libraries/wuss/menu/menu.c | 83 +++++++++++++++++++------- libraries/wuss/test/tasks/icons.c | 19 ++++-- libraries/wuss/test/wuss-test.c | 14 ++--- 10 files changed, 151 insertions(+), 93 deletions(-) diff --git a/apps/wuss/main.c b/apps/wuss/main.c index fc43c76..a11a8c8 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -224,8 +224,7 @@ static const wuss_menu_item_t g_menu_items[] = { "Show grid", wuss_MENU_ITEM_TICKED, NULL }, { "Wireframe", wuss_MENU_ITEM_TICKED, NULL }, { "Export", wuss_MENU_ITEM_NONE, &g_menu_export }, - { "", wuss_MENU_ITEM_DASHED, NULL }, - { "Quit", wuss_MENU_ITEM_NONE, NULL } + { "Quit", wuss_MENU_ITEM_DASHED, NULL } }; static const wuss_menu_t g_menu = { diff --git a/include/wuss/icon.h b/include/wuss/icon.h index ff3bcd9..92aa592 100644 --- a/include/wuss/icon.h +++ b/include/wuss/icon.h @@ -90,7 +90,7 @@ typedef enum wuss_icon_type * wuss_ICON_FLAGS_INTERACTIVE is set, in which * case clicks raise wuss_EVENT_ICON like a * button. */ - wuss_ICON_TYPE_MENU_ENTRY /**< A menu row: text left-justified across the + wuss_ICON_TYPE_MENU_ENTRY, /**< A menu row: text left-justified across the * bounding box, drawn in fg over the window * background, or inverted (window manager's title * colours) while the pointer is over it. An @@ -98,14 +98,22 @@ typedef enum wuss_icon_type * selected (see wuss_icon_set_selected), and an * optional submenu arrow at the right edge with * wuss_ICON_FLAGS_SUBMENU. - * wuss_ICON_FLAGS_SEPARATOR draws a dashed rule - * along the top edge before the text, marking a - * group boundary; the entry keeps its label and - * stays interactive (an entry with the flag and - * no text is just a bare rule). Interactive: a - * click raises wuss_EVENT_ICON like a button; a - * disabled entry never highlights and its clicks - * fall through. */ + * wuss_ICON_FLAGS_SEPARATOR marks the entry as + * following a group boundary: the dashed rule + * itself is a separate wuss_ICON_TYPE_RULE icon + * laid out above the entry, not drawn by the + * entry. The entry keeps its label and stays + * fully interactive. Interactive: a click raises + * wuss_EVENT_ICON like a button; a disabled + * entry never highlights and its clicks fall + * through. */ + wuss_ICON_TYPE_RULE /**< A horizontal dashed rule centred in the + * bounding box, drawn in fg over the window + * background. Purely decorative: never + * hit-tested, never highlights. text, bg and + * pattern are ignored. Used between menu rows to + * render the line a wuss_ICON_FLAGS_SEPARATOR + * entry sits below. */ } wuss_icon_type_t; @@ -145,10 +153,13 @@ typedef enum wuss_icon_flags * a submenu. Ignored by other * types. */ wuss_ICON_FLAGS_SEPARATOR = 1 << 7 /**< wuss_ICON_TYPE_MENU_ENTRY: the - * entry is a separator -- a centred - * dashed rule, no text, and not - * hit-tested. Ignored by other - * types. */ + * entry follows a group boundary. It + * stays a normal interactive row + * with its own label; the dashed + * rule above it is laid out and + * drawn as a separate + * wuss_ICON_TYPE_RULE icon. Ignored + * by other types. */ } wuss_icon_flags_t; diff --git a/include/wuss/menu.h b/include/wuss/menu.h index 759fd64..bbb36d9 100644 --- a/include/wuss/menu.h +++ b/include/wuss/menu.h @@ -35,10 +35,12 @@ typedef enum wuss_menu_item_flags { wuss_MENU_ITEM_NONE = 0, wuss_MENU_ITEM_TICKED = 1 << 0, /**< draw a tick at the item's left edge */ - wuss_MENU_ITEM_DASHED = 1 << 1, /**< this item is a separator: its own - * short row holding a centred dashed - * rule. The text is ignored and the row - * does not respond to the pointer */ + wuss_MENU_ITEM_DASHED = 1 << 1, /**< draw a dashed rule above this item, + * marking a group boundary. The item is + * otherwise an ordinary row: it keeps + * its label and responds to the pointer. + * The rule is laid out and drawn + * separately and is not interactive */ wuss_MENU_ITEM_DISABLED = 1 << 2 /**< greyed, never highlights, not * selectable */ } @@ -119,17 +121,17 @@ int wuss_menu_is_open(wuss_menu_handle_t handle); * as the first token inside a '{ }' is that submenu's title -- so the same * descriptor strings port across unchanged. Submenus keep the Wimp behaviour of * discarding their title token; only the root's is kept. A leading '|' on an - * item inserts a separator row above it: its own short, inert row holding a - * centred dashed rule. A '{ ... }' group after an item is that item's submenu. - * A per-token prefix '!' ticks the item, '~' shades (disables) it, and '>' - * attaches a submenu pulled as a <tt>const wuss_menu_t *</tt> from the varargs - * rather than from a following '{ }' block. A "%s" in a token substitutes the - * next <tt>const char *</tt> vararg. The '>' and "%s" varargs are consumed in - * the order they are encountered scanning left to right. + * item draws a dashed rule above it, marking a group boundary; the item itself + * stays an ordinary interactive row. A '{ ... }' group after an item is that + * item's submenu. A per-token prefix '!' ticks the item, '~' shades (disables) + * it, and '>' attaches a submenu pulled as a <tt>const wuss_menu_t *</tt> from + * the varargs rather than from a following '{ }' block. A "%s" in a token + * substitutes the next <tt>const char *</tt> vararg. The '>' and "%s" varargs + * are consumed in the order they are encountered scanning left to right. * * Example: <tt>wuss_menu_create_from_desc(&m, "Display, Open, !Grid, ~Export, - * |Quit")</tt> -- "Display" is the caption; the menu has four items plus a - * separator row before "Quit". + * |Quit")</tt> -- "Display" is the caption; the menu has four items, with a + * dashed rule above "Quit". * * The whole tree, including copied label text, is one heap allocation graph * owned by the caller; free it with wuss_menu_destroy. wuss_menu_open treats a diff --git a/libraries/wuss/icon/create.c b/libraries/wuss/icon/create.c index 7cd21c7..c45c8cb 100644 --- a/libraries/wuss/icon/create.c +++ b/libraries/wuss/icon/create.c @@ -33,7 +33,8 @@ result_t wuss_icon_create(wuss_window_t *window, spec->type != wuss_ICON_TYPE_RADIO && spec->type != wuss_ICON_TYPE_OPTION && spec->type != wuss_ICON_TYPE_BITMAP && - spec->type != wuss_ICON_TYPE_MENU_ENTRY) + spec->type != wuss_ICON_TYPE_MENU_ENTRY && + spec->type != wuss_ICON_TYPE_RULE) return result_WUSS_BAD_ICON; if ((spec->type == wuss_ICON_TYPE_BUTTON || diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index d1de16e..8a9b087 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -351,7 +351,7 @@ static void wuss__icon_draw_menu_entry(const icon_draw_ctx_t *c) const wuss_icon_t *icon = c->icon; const box_t *b = &c->b; colour_t ink, ground, tmp; - int disabled, highlit, pad, mid_y; + int disabled, highlit, pad; disabled = (icon->flags & wuss_ICON_FLAGS_DISABLED) != 0; highlit = wuss__icon_hovered(icon) && !disabled; @@ -373,15 +373,6 @@ static void wuss__icon_draw_menu_entry(const icon_draw_ctx_t *c) screen_fill_rect(c->scr, b->x0, b->y0, SIZE2D(b->x1 - b->x0, b->y1 - b->y0), ground); - /* a separator is its own short, textless row: a dashed rule centred in it, - * drawn in the row's ink so it stays visible if the row is inverted */ - if (icon->flags & wuss_ICON_FLAGS_SEPARATOR) - { - mid_y = b->y0 + (b->y1 - b->y0) / 2; - screen_draw_dashed_line(c->scr, b->x0 + pad, mid_y, - b->x1 - 1 - pad, mid_y, 2, 2, ink); - } - /* left-edge tick when selected */ if (wuss__icon_selected(icon)) { @@ -420,6 +411,21 @@ static void wuss__icon_draw_menu_entry(const icon_draw_ctx_t *c) /* ----------------------------------------------------------------------- */ +/* wuss_ICON_TYPE_RULE: a dashed rule centred in the box, drawn in the icon's + * fg over whatever ground it inherits. Inert -- see wuss__icon_hit_test. */ +static void wuss__icon_draw_rule(const icon_draw_ctx_t *c) +{ + const box_t *b = &c->b; + int pad, mid_y; + + pad = 4; + mid_y = b->y0 + (b->y1 - b->y0) / 2; + screen_draw_dashed_line(c->scr, b->x0 + pad, mid_y, + b->x1 - 1 - pad, mid_y, 2, 2, c->fg); +} + +/* ----------------------------------------------------------------------- */ + void wuss__icon_draw(wuss_t *wuss, const wuss_icon_t *icon, const box_t *content, @@ -478,5 +484,9 @@ void wuss__icon_draw(wuss_t *wuss, case wuss_ICON_TYPE_MENU_ENTRY: wuss__icon_draw_menu_entry(&c); break; + + case wuss_ICON_TYPE_RULE: + wuss__icon_draw_rule(&c); + break; } } diff --git a/libraries/wuss/icon/hit-test.c b/libraries/wuss/icon/hit-test.c index 78b8761..2e9ab7c 100644 --- a/libraries/wuss/icon/hit-test.c +++ b/libraries/wuss/icon/hit-test.c @@ -14,22 +14,17 @@ wuss_icon_t *wuss__icon_hit_test(wuss_window_t *window, point_t doc_point) { it = window->icons[i]; - /* a bitmap icon is interactive only when it asks to be; a menu entry that - * is a separator (SEPARATOR flag, no text) is inert; the other clickable - * types always are */ + /* a bitmap icon is interactive only when it asks to be; RULE and the + * static types are always inert; the other types always click */ if (it->type == wuss_ICON_TYPE_BITMAP) { if (!(it->flags & wuss_ICON_FLAGS_INTERACTIVE)) continue; } - else if (it->type == wuss_ICON_TYPE_MENU_ENTRY) - { - if ((it->flags & wuss_ICON_FLAGS_SEPARATOR) && it->text[0] == '\0') - continue; - } else if (it->type != wuss_ICON_TYPE_BUTTON && it->type != wuss_ICON_TYPE_RADIO && - it->type != wuss_ICON_TYPE_OPTION) + it->type != wuss_ICON_TYPE_OPTION && + it->type != wuss_ICON_TYPE_MENU_ENTRY) { continue; } diff --git a/libraries/wuss/menu/create-from-desc.c b/libraries/wuss/menu/create-from-desc.c index f0a6f0c..5418e28 100644 --- a/libraries/wuss/menu/create-from-desc.c +++ b/libraries/wuss/menu/create-from-desc.c @@ -199,7 +199,7 @@ static result_t menu_deep_copy(const wuss_menu_t *src, wuss_menu_t **out) /* ----------------------------------------------------------------------- */ /* A menu under construction: a growable wuss_menu_item_t array plus the - * separator flag owed to the next-emitted item. */ + * pending '|' dash flag owed to the next-added item. */ typedef struct { wuss_menu_item_t *items; @@ -238,19 +238,17 @@ static result_t build_add(Building *b, wuss_menu_t *submenu) { unsigned int flags; - result_t rc; - /* '|' before this item means "insert a separator row above it": a distinct - * DASHED item with no text of its own, emitted before the real one. */ + flags = wuss_MENU_ITEM_NONE; + + /* '|' before this item draws a dashed rule above it: set DASHED on the item + * itself, which stays an ordinary interactive row. */ if (b->pending_dash) { b->pending_dash = 0; - rc = build_emit(b, "", wuss_MENU_ITEM_DASHED, NULL); - if (rc != result_OK) - return rc; + flags |= wuss_MENU_ITEM_DASHED; } - flags = wuss_MENU_ITEM_NONE; if (opt & Tick) flags |= wuss_MENU_ITEM_TICKED; if (opt & Shade) diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index 6d8a9d1..9cd73ef 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -173,6 +173,7 @@ static result_t wuss__menu_spawn(wuss_t *wuss, { struct wuss__menu *node; wuss_icon_spec_t *specs; + wuss_icon_t **made; wuss_task_t task; result_t rc; int fw, fh; @@ -182,6 +183,9 @@ static result_t wuss__menu_spawn(wuss_t *wuss, int widest; int width, height; int i; + int ndashed; + int nspecs; + int s; int outline_px; int titlebar_height; wuss_window_flags_t menu_flags; @@ -205,6 +209,12 @@ static result_t wuss__menu_spawn(wuss_t *wuss, pitch = fh + 2 * WUSS_MENU_ROW_PAD; sep_h = 2 * WUSS_MENU_ROW_PAD; + ndashed = 0; + for (i = 0; i < menu->nitems; i++) + if (menu->items[i].flags & wuss_MENU_ITEM_DASHED) + ndashed++; + nspecs = menu->nitems + ndashed; + widest = 0; for (i = 0; i < menu->nitems; i++) { @@ -224,9 +234,8 @@ static result_t wuss__menu_spawn(wuss_t *wuss, width = WUSS_MENU_TICK_W + widest + WUSS_MENU_TEXT_PAD + WUSS_MENU_ARROW_W; - height = 0; - for (i = 0; i < menu->nitems; i++) - height += (menu->items[i].flags & wuss_MENU_ITEM_DASHED) ? sep_h : pitch; + /* every item is a full row now; a dashed item also gets a sep_h rule above */ + height = menu->nitems * pitch + ndashed * sep_h; /* Nudge onto the screen where it can be, but keep the top-left on screen. * `at` is the content top-left; the window's visible box also spans the @@ -250,9 +259,11 @@ static result_t wuss__menu_spawn(wuss_t *wuss, return result_OOM; node->icons = wuss__malloc(wuss, (size_t) menu->nitems * sizeof(*node->icons)); - specs = wuss__malloc(wuss, (size_t) menu->nitems * sizeof(*specs)); - if (node->icons == NULL || specs == NULL) + specs = wuss__malloc(wuss, (size_t) nspecs * sizeof(*specs)); + made = wuss__malloc(wuss, (size_t) nspecs * sizeof(*made)); + if (node->icons == NULL || specs == NULL || made == NULL) { + wuss__free(wuss, made); wuss__free(wuss, specs); wuss__free(wuss, node->icons); wuss__free(wuss, node); @@ -269,39 +280,53 @@ static result_t wuss__menu_spawn(wuss_t *wuss, node->ctx = ctx; node->open_index = -1; + /* One MENU_ENTRY icon per item, in item order, preceded by an inert + * wuss_ICON_TYPE_RULE icon for each dashed item. specs[] therefore holds + * nspecs entries; s walks it while i walks the items. */ y = 0; + s = 0; for (i = 0; i < menu->nitems; i++) { const wuss_menu_item_t *item; wuss_icon_flags_t flags; - int row_h; item = &menu->items[i]; flags = wuss_ICON_FLAGS_NONE; if (item->flags & wuss_MENU_ITEM_DASHED) + { flags |= wuss_ICON_FLAGS_SEPARATOR; + + specs[s].type = wuss_ICON_TYPE_RULE; + specs[s].bbox.x0 = 0; + specs[s].bbox.y0 = y; + specs[s].bbox.x1 = width; + specs[s].bbox.y1 = y + sep_h; + specs[s].text = ""; + specs[s].fg = 0; + specs[s].bg = wuss_NO_BACKGROUND; + specs[s].flags = wuss_ICON_FLAGS_NONE; + s++; + y += sep_h; + } + if (item->flags & wuss_MENU_ITEM_DISABLED) flags |= wuss_ICON_FLAGS_DISABLED; if (item->submenu != NULL) flags |= wuss_ICON_FLAGS_SUBMENU; - /* a separator is its own short, inert row: force the text empty so - * wuss__icon_hit_test treats it as a bare rule */ - row_h = (flags & wuss_ICON_FLAGS_SEPARATOR) ? sep_h : pitch; - - specs[i].type = wuss_ICON_TYPE_MENU_ENTRY; - specs[i].bbox.x0 = 0; - specs[i].bbox.y0 = y; - specs[i].bbox.x1 = width; - specs[i].bbox.y1 = y + row_h; - specs[i].text = (flags & wuss_ICON_FLAGS_SEPARATOR) - ? "" : (item->text ? item->text : ""); - specs[i].fg = 0; - specs[i].bg = wuss_NO_BACKGROUND; - specs[i].flags = flags; - - y += row_h; + specs[s].type = wuss_ICON_TYPE_MENU_ENTRY; + specs[s].bbox.x0 = 0; + specs[s].bbox.y0 = y; + specs[s].bbox.x1 = width; + specs[s].bbox.y1 = y + pitch; + specs[s].text = item->text ? item->text : ""; + specs[s].fg = 0; + specs[s].bg = wuss_NO_BACKGROUND; + specs[s].flags = flags; + s++; + + y += pitch; } size = SIZE2D(width, height); @@ -323,27 +348,39 @@ static result_t wuss__menu_spawn(wuss_t *wuss, &task, size, size, &node->window); if (rc != result_OK) { + wuss__free(wuss, made); wuss__free(wuss, specs); wuss__free(wuss, node->icons); wuss__free(wuss, node); return rc; } - rc = wuss_icon_create_array(node->window, specs, menu->nitems, node->icons); + rc = wuss_icon_create_array(node->window, specs, nspecs, made); wuss__free(wuss, specs); if (rc != result_OK) { + wuss__free(wuss, made); wuss_window_close(node->window); wuss__free(wuss, node->icons); wuss__free(wuss, node); return rc; } + /* keep only the entry handles, item-indexed; the rule icons stay owned by + * the window and need no further handling. Walk made[] with the same + * rule-then-entry interleave as the specs loop. */ + s = 0; for (i = 0; i < menu->nitems; i++) { + if (menu->items[i].flags & wuss_MENU_ITEM_DASHED) + s++; + node->icons[i] = made[s]; + s++; + if (menu->items[i].flags & wuss_MENU_ITEM_TICKED) wuss_icon_set_selected(node->icons[i], 1); } + wuss__free(wuss, made); *out = node; return result_OK; diff --git a/libraries/wuss/test/tasks/icons.c b/libraries/wuss/test/tasks/icons.c index 41b144c..f8cb6c0 100644 --- a/libraries/wuss/test/tasks/icons.c +++ b/libraries/wuss/test/tasks/icons.c @@ -36,7 +36,7 @@ result_t icons_create(wuss_t *wuss, * grouped radios, a standalone option and a label echoing the selection, * then [.. +2] a decorative bitmap icon and an interactive one that bumps * the counter, then [.. +4] a strip of menu-entry icons that hover-highlight */ - enum { ICONS_NSPECS = 4 + screen_PATTERN__LIMIT + 3 + 5 + 2 + 4 }; + enum { ICONS_NSPECS = 4 + screen_PATTERN__LIMIT + 3 + 5 + 2 + 5 }; wuss_task_t delegate; wuss_icon_spec_t specs[ICONS_NSPECS]; wuss_icon_t *made[ICONS_NSPECS]; @@ -208,8 +208,9 @@ result_t icons_create(wuss_t *wuss, nspecs = g + 10; } - /* [nspecs..nspecs+3] a menu-entry strip: plain, ticked, submenu, separator. - * Hover the pointer over them to see the highlight track. */ + /* [nspecs..nspecs+4] a menu-entry strip: plain, ticked, submenu, then a + * standalone dashed rule and a SEPARATOR-flagged entry below it. Hover the + * pointer over them to see the highlight track; the rule stays inert. */ m = nspecs; specs[m].bbox = (box_t) BOX_POS_SIZE(28, 524, 160, 16); @@ -232,12 +233,18 @@ result_t icons_create(wuss_t *wuss, specs[m + 2].flags = wuss_ICON_FLAGS_SUBMENU; specs[m + 3].bbox = (box_t) BOX_POS_SIZE(28, 578, 160, 10); - specs[m + 3].type = wuss_ICON_TYPE_MENU_ENTRY; + specs[m + 3].type = wuss_ICON_TYPE_RULE; specs[m + 3].fg = palette_PICO8_DARK_BLUE; specs[m + 3].bg = wuss_NO_BACKGROUND; - specs[m + 3].flags = wuss_ICON_FLAGS_SEPARATOR; - nspecs = m + 4; + specs[m + 4].bbox = (box_t) BOX_POS_SIZE(28, 588, 160, 16); + specs[m + 4].type = wuss_ICON_TYPE_MENU_ENTRY; + specs[m + 4].text = "Quit"; + specs[m + 4].fg = palette_PICO8_DARK_BLUE; + specs[m + 4].bg = wuss_NO_BACKGROUND; + specs[m + 4].flags = wuss_ICON_FLAGS_SEPARATOR; + + nspecs = m + 5; rc = wuss_icon_create_array(task->window, specs, nspecs, made); if (rc != result_OK) diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index f203cab..4d80377 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -2966,8 +2966,8 @@ result_t wuss_test(const char *resources) /* "Root" is the root caption (first token), then items App, * File{ %s title discarded, Grid(!), Export(~), |Quit }, Help(> borrowed), - * with "File" substituted for both %s. '|Quit' inserts a standalone DASHED - * separator item before Quit, so the submenu holds 4 rows. */ + * with "File" substituted for both %s. '|Quit' sets DASHED on Quit itself, + * so the submenu holds 3 rows. */ rc = wuss_menu_create_from_desc(&m, "Root, App, %s { %s, !Grid, ~Export, |Quit }, >Help", "File", "File", &borrowed); @@ -2980,18 +2980,16 @@ result_t wuss_test(const char *resources) if (m->items[0].submenu != NULL) goto MenuFail; /* items[1] "File" carries the { } submenu; its first token was the title - * and is not emitted; '|Quit' inserts a separate DASHED separator row - * before Quit, which keeps its own text and no DASHED flag */ + * and is not emitted; '|Quit' sets DASHED on the Quit row itself, which + * keeps its label */ if (strcmp(m->items[1].text, "File") != 0) goto MenuFail; sub = m->items[1].submenu; - if (sub == NULL || sub->nitems != 4) goto MenuFail; + if (sub == NULL || sub->nitems != 3) goto MenuFail; if (strcmp(sub->items[0].text, "Grid") != 0) goto MenuFail; if (!(sub->items[0].flags & wuss_MENU_ITEM_TICKED)) goto MenuFail; if (!(sub->items[1].flags & wuss_MENU_ITEM_DISABLED)) goto MenuFail; - if (sub->items[2].text[0] != '\0') goto MenuFail; + if (strcmp(sub->items[2].text, "Quit") != 0) goto MenuFail; if (!(sub->items[2].flags & wuss_MENU_ITEM_DASHED)) goto MenuFail; - if (strcmp(sub->items[3].text, "Quit") != 0) goto MenuFail; - if (sub->items[3].flags & wuss_MENU_ITEM_DASHED) goto MenuFail; /* items[2] "Help" got a deep copy of the borrowed menu */ if (strcmp(m->items[2].text, "Help") != 0) goto MenuFail; From cdef3f72d19618d23fdcc7caad706ce3b4172c5d Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 15:56:38 +0100 Subject: [PATCH 54/79] fix(wuss): stop scroll blitting content over a mid-content occluder wuss_window_set_scroll clipped the blit source against occluding windows but not the destination. A source slice below a smaller window on top, slid by the scroll delta, could land its destination behind that window; screen_copy_rect only clips to the content box, so it painted the scrolling window's content over the occluder and marked the area 'copied', excluding it from the repaint set so nothing restored it. Clip each blit destination against the occluders too and copy only the surviving sub-pieces, each with its own matching source offset. Adds a wuss test that paints two windows in distinct colours and reads back the framebuffer behind the occluder after a scroll. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/test/wuss-test.c | 112 +++++++++++++++++++++++++++++ libraries/wuss/window/set-scroll.c | 34 ++++++--- 2 files changed, 135 insertions(+), 11 deletions(-) diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index 4d80377..bac8d4f 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -1,5 +1,6 @@ /* wuss/test/wuss-test.c -- wuss - minimal window manager */ +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> @@ -96,6 +97,33 @@ static result_t test_handle(wuss_window_t *window, return result_OK; } +/* A redraw handler that floods its dirty box with a fixed colour, so a test + * can read back the framebuffer and tell whose pixels ended up where. The + * colour is passed via task_data (a pointer to a colour_t). */ +static result_t paint_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) +{ + const colour_t *col; + + NOT_USED(window); + + if (event->kind != wuss_EVENT_REDRAW) + return result_OK; + + col = task_data; + screen_fill_rect(event->data.redraw.scr, + event->data.redraw.content->x0, + event->data.redraw.content->y0, + SIZE2D(event->data.redraw.content->x1 + - event->data.redraw.content->x0, + event->data.redraw.content->y1 + - event->data.redraw.content->y0), + *col); + + return result_OK; +} + /* ----------------------------------------------------------------------- */ /* Total area covered by the dirty list, counting overlapped pixels once. @@ -1692,6 +1720,90 @@ result_t wuss_test(const char *resources) wuss_window_close(win_s); } + printf("test: wuss_window_set_scroll never blits a scrolled window's content over a mid-content occluder\n"); + + { + /* Regression: a small window O sits in the middle of a larger scrollable + * window M's content, covering neither M's top nor bottom edge. A + * programmatic vertical scroll of M takes a blit source slice from below + * O and shifts it up by the scroll delta, so its destination overlaps O. + * screen_copy_rect only clips to the content box, so without the fix it + * paints M's content over O and marks that area "copied" -- excluding it + * from the repaint set, so O's pixels stay overpainted with M's colour. + * With the fix each destination is clipped against the occluders and only + * the un-occluded sub-pieces are blitted, leaving O's own pixels intact. + * Read the framebuffer behind O to check. */ + colour_t cm, co; + uint32_t fb_m, fb_o; + test_task_t tc_m, tc_o; + wuss_task_t delegate_m, delegate_o; + box_t box_m, box_o, content; + wuss_window_t *win_m, *win_o; + int occ_x, occ_y, mid_x, mid_y, delta; + + cm = colour_rgb(0x11, 0x22, 0x33); + co = colour_rgb(0xcc, 0xdd, 0xee); + tc_m.redraw_count = 0; tc_m.mouse_count = 0; + tc_o.redraw_count = 0; tc_o.mouse_count = 0; + delegate_m.handle = paint_handle; delegate_m.task_data = &cm; + delegate_o.handle = paint_handle; delegate_o.task_data = &co; + + box_m.x0 = 10; box_m.y0 = 10; + box_m.x1 = 110; box_m.y1 = 110; /* 100x100 content; doc taller, so scrollable */ + rc = wuss_window_create(wuss, &box_m, "M", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate_m, + SIZE2D(100, 300), SIZE2D(0, 0), &win_m); + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_m, &content); + + /* occluder spanning M's full content width, a band in the vertical + * middle -- so wuss__clip_to_visible splits M's blit source into a top + * and a bottom band with nothing behind O. Scrolling down slides the + * bottom band up; part of its destination lands behind O, yet no dirty + * repaint region touches O, so without the fix O stays overpainted. */ + box_o.x0 = content.x0 - 5; box_o.y0 = content.y0 + 40; + box_o.x1 = content.x1 + 5; box_o.y1 = content.y0 + 70; + rc = wuss_window_create(wuss, &box_o, "O", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate_o, + SIZE2D(box_o.x1 - box_o.x0, box_o.y1 - box_o.y0), + SIZE2D(0, 0), &win_o); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush both creates: M then O paint */ + if (rc != result_OK) + goto Failure; + + /* sample the framebuffer: occ_* sits behind O, mid_* sits in M's content + * clear of O -- record what each colour actually renders as */ + occ_x = (box_o.x0 + box_o.x1) / 2; + occ_y = box_o.y1 - 3; /* near O's bottom: the bottom band's blit + * destination reaches up to here */ + mid_x = content.x0 + 5; + mid_y = content.y0 + 5; + fb_o = ((const uint32_t *) pixels)[occ_y * 200 + occ_x]; + fb_m = ((const uint32_t *) pixels)[mid_y * 200 + mid_x]; + if (fb_o == fb_m) + goto Failure; /* the two windows must render distinguishable pixels */ + + delta = 20; + wuss_window_set_scroll(win_m, POINT(0, delta)); + rc = wuss_redraw_dirty(wuss); /* apply the scroll's blit + any repaint */ + if (rc != result_OK) + goto Failure; + + /* the pixel behind O must still be O's, not M's content blitted over it */ + if (((const uint32_t *) pixels)[occ_y * 200 + occ_x] != fb_o) + goto Failure; + + wuss_window_close(win_o); + wuss_window_close(win_m); + } + printf("test: wuss_WINDOW_NO_RESIZE_BLIT redraws the whole window instead of blitting\n"); { diff --git a/libraries/wuss/window/set-scroll.c b/libraries/wuss/window/set-scroll.c index 0acf264..3aec584 100644 --- a/libraries/wuss/window/set-scroll.c +++ b/libraries/wuss/window/set-scroll.c @@ -8,7 +8,8 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p) box_t src[WUSS_MAX_INVALIDATE_PIECES]; box_t copied[WUSS_MAX_INVALIDATE_PIECES]; box_t dirty[WUSS_MAX_INVALIDATE_PIECES]; - int dx, dy, nsrc, ncopied, ndirty, i; + box_t vis[WUSS_MAX_INVALIDATE_PIECES]; + int dx, dy, nsrc, ncopied, ndirty, nvis, i, j; dx = p.x - window->scroll.x; dy = p.y - window->scroll.y; @@ -26,22 +27,33 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p) #endif /* The valid blit source is the content box minus whatever windows above it - * were covering (those areas hold occluder pixels, not this window's), so - * slide it piece by piece by the scroll delta. The clip keeps every - * destination inside the content box, so a piece near the trailing edge - * shifts partly out and that area falls into the repaint set below. Falls - * back to a full content invalidate when the pixel format can't blit. */ + * were covering (those areas hold occluder pixels, not this window's). + * Sliding a source piece by the scroll delta can still land its + * destination on screen a higher window owns -- screen_copy_rect only + * clips to the content box, so it would paint over the occluder there. + * Clip each destination against the occluders too and blit only the + * surviving sub-pieces, each with its own matching source offset; the + * rest falls into the repaint set below. Falls back to a full content + * invalidate when the pixel format can't blit. */ nsrc = wuss__clip_to_visible(window, &content, src); ncopied = 0; window->wuss->scr->clip = content; for (i = 0; i < nsrc; i++) { - box_t got; + box_t want; - if (screen_copy_rect(window->wuss->scr, &src[i], - POINT(src[i].x0 - dx, src[i].y0 - dy), &got) != 0 && - ncopied < WUSS_MAX_INVALIDATE_PIECES) - copied[ncopied++] = got; + box_translated(&src[i], -dx, -dy, &want); + nvis = wuss__clip_to_visible(window, &want, vis); + for (j = 0; j < nvis; j++) + { + box_t ssub, got; + + box_translated(&vis[j], dx, dy, &ssub); /* source for this dest piece */ + if (screen_copy_rect(window->wuss->scr, &ssub, + POINT(ssub.x0 - dx, ssub.y0 - dy), &got) != 0 && + ncopied < WUSS_MAX_INVALIDATE_PIECES) + copied[ncopied++] = got; + } } if (ncopied > 0) From b1db2c65dfa0b5d4c9989e1ae619368e9fa0cad0 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 16:21:03 +0100 Subject: [PATCH 55/79] fix(wuss): order scroll blit sub-pieces so one doesn't clobber another's source When a small window floats mid-content over a larger scrollable window with a gap all round it, wuss__clip_to_visible carves the scrolled window's blittable content into bands around the occluder. A vertical scroll shifts every band by the same delta, and one band's shifted destination can land on another band's still-unread source. Blitting the bands in clip-emit order then double-shifts the content near the occluder's bottom edge. Collect all blit sub-pieces first, order them with wuss__order_pieces (the same topological sort wuss_window_move already used, now promoted from static in move.c to a shared internal in invalidate.c), and fall back to a full content invalidate on a cycle or piece-budget overflow. Adds a regression test: paint_handle now paints one-pixel rows whose blue channel encodes document Y, so the test can read the gap column beside the occluder back and assert each row slid by exactly the scroll delta. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/impl.h | 10 +++ libraries/wuss/test/wuss-test.c | 131 +++++++++++++++++++++++++---- libraries/wuss/window/invalidate.c | 54 ++++++++++++ libraries/wuss/window/move.c | 54 ------------ libraries/wuss/window/set-scroll.c | 61 ++++++++++---- 5 files changed, 224 insertions(+), 86 deletions(-) diff --git a/libraries/wuss/impl.h b/libraries/wuss/impl.h index 84be126..a74a5b1 100644 --- a/libraries/wuss/impl.h +++ b/libraries/wuss/impl.h @@ -222,6 +222,16 @@ int wuss__subtract_boxes(const box_t *whole, int ncuts, box_t *out); +/* Given "n" single-rect blits, each moving "clean[i]" to "dest[i]", find an + * order in which no blit's destination overwrites a still-unread source of a + * later blit. Writes the piece indices to "order" (capacity + * WUSS_MAX_INVALIDATE_PIECES) and returns non-zero on success; returns zero + * when the overlap graph has a cycle and no safe order exists. */ +int wuss__order_pieces(const box_t *clean, + const box_t *dest, + int n, + int *order); + /* Notify a window's task that it has been moved or resized, via * wuss_EVENT_OPEN; the return value is discarded, matching how furniture * drawing and other in-line notifications are treated. */ diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index bac8d4f..addd00e 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -97,29 +97,45 @@ static result_t test_handle(wuss_window_t *window, return result_OK; } -/* A redraw handler that floods its dirty box with a fixed colour, so a test - * can read back the framebuffer and tell whose pixels ended up where. The - * colour is passed via task_data (a pointer to a colour_t). */ +/* A redraw handler that paints its content as one-pixel horizontal lines + * whose colour encodes the document-space Y of each row, so a test can read + * the framebuffer back and tell not just whose pixels are where but whether + * a blit slid them by the right amount. task_data points to a colour_t used + * only as a base hue (its blue channel is replaced per row). NULL task_data + * means flood with a single fixed colour instead. */ static result_t paint_handle(wuss_window_t *window, const wuss_event_t *event, void *task_data) { - const colour_t *col; + const box_t *clip; + const box_t *bounds; + point_t scroll; + int y, doc_y; NOT_USED(window); if (event->kind != wuss_EVENT_REDRAW) return result_OK; - col = task_data; - screen_fill_rect(event->data.redraw.scr, - event->data.redraw.content->x0, - event->data.redraw.content->y0, - SIZE2D(event->data.redraw.content->x1 - - event->data.redraw.content->x0, - event->data.redraw.content->y1 - - event->data.redraw.content->y0), - *col); + clip = event->data.redraw.content; + bounds = event->data.redraw.bounds; + scroll = event->data.redraw.scroll; + + if (task_data == NULL) + { + screen_fill_rect(event->data.redraw.scr, clip->x0, clip->y0, + SIZE2D(clip->x1 - clip->x0, clip->y1 - clip->y0), + colour_rgb(0xcc, 0xdd, 0xee)); + return result_OK; + } + + for (y = clip->y0; y < clip->y1; y++) + { + doc_y = y - bounds->y0 + scroll.y; + screen_fill_rect(event->data.redraw.scr, clip->x0, y, + SIZE2D(clip->x1 - clip->x0, 1), + colour_rgb(0x20, 0x40, doc_y & 0xff)); + } return result_OK; } @@ -1733,7 +1749,7 @@ result_t wuss_test(const char *resources) * With the fix each destination is clipped against the occluders and only * the un-occluded sub-pieces are blitted, leaving O's own pixels intact. * Read the framebuffer behind O to check. */ - colour_t cm, co; + colour_t cm; uint32_t fb_m, fb_o; test_task_t tc_m, tc_o; wuss_task_t delegate_m, delegate_o; @@ -1742,11 +1758,10 @@ result_t wuss_test(const char *resources) int occ_x, occ_y, mid_x, mid_y, delta; cm = colour_rgb(0x11, 0x22, 0x33); - co = colour_rgb(0xcc, 0xdd, 0xee); tc_m.redraw_count = 0; tc_m.mouse_count = 0; tc_o.redraw_count = 0; tc_o.mouse_count = 0; delegate_m.handle = paint_handle; delegate_m.task_data = &cm; - delegate_o.handle = paint_handle; delegate_o.task_data = &co; + delegate_o.handle = paint_handle; delegate_o.task_data = NULL; box_m.x0 = 10; box_m.y0 = 10; box_m.x1 = 110; box_m.y1 = 110; /* 100x100 content; doc taller, so scrollable */ @@ -1804,6 +1819,90 @@ result_t wuss_test(const char *resources) wuss_window_close(win_m); } + printf("test: wuss_window_set_scroll orders its blit sub-pieces so one never clobbers another's source\n"); + + { + /* Regression: a small window O floats in the middle of a larger + * scrollable window M, with a gap all round it. wuss__clip_to_visible + * carves M's blittable content into bands around O; a vertical scroll + * shifts each band by the same delta, and one band's shifted + * destination lands on another band's still-unread source. Blitting the + * bands in clip-emit order corrupts M's own content -- pixels near O's + * bottom edge end up double-shifted. The blit must be ordered (or fall + * back). paint_handle paints M as one-pixel rows whose blue channel + * encodes document Y, so a mis-shifted row is detectable exactly. */ + colour_t cm; + test_task_t tc_m, tc_o; + wuss_task_t delegate_m, delegate_o; + box_t box_m, box_o, content, ovis; + wuss_window_t *win_m, *win_o; + int gx, gy, delta, bad; + + cm = colour_rgb(0x11, 0x22, 0x33); + tc_m.redraw_count = 0; tc_m.mouse_count = 0; + tc_o.redraw_count = 0; tc_o.mouse_count = 0; + delegate_m.handle = paint_handle; delegate_m.task_data = &cm; + delegate_o.handle = paint_handle; delegate_o.task_data = NULL; + + box_m.x0 = 10; box_m.y0 = 10; + box_m.x1 = 110; box_m.y1 = 110; /* 100x100 content; doc taller, scrollable */ + rc = wuss_window_create(wuss, &box_m, "M", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate_m, + SIZE2D(100, 300), SIZE2D(0, 0), &win_m); + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_m, &content); + + /* O strictly inside M's content, gap on every side */ + box_o.x0 = content.x0 + 25; box_o.y0 = content.y0 + 25; + box_o.x1 = content.x0 + 75; box_o.y1 = content.y0 + 65; + rc = wuss_window_create(wuss, &box_o, "O", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate_o, + SIZE2D(box_o.x1 - box_o.x0, box_o.y1 - box_o.y0), + SIZE2D(0, 0), &win_o); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush both creates */ + if (rc != result_OK) + goto Failure; + + /* probe the gap column just right of O: pure M content, must slide up by + * exactly the scroll delta with no discontinuity */ + wuss_window_get_visible_bounds(win_o, &ovis); + gx = (ovis.x1 + content.x1) / 2; + delta = 20; + + wuss_window_set_scroll(win_m, POINT(0, delta)); + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + /* every row in the gap column, across O's vertical span, must show the + * document Y that scrolling put there: doc_y = (screen_y - content.y0) + + * delta, low byte carried in blue by paint_handle */ + bad = 0; + for (gy = ovis.y0 + 2; gy < ovis.y1 - 2; gy++) + { + uint32_t px, blue; + + if (gy < content.y0 || gy >= content.y1) + continue; + px = ((const uint32_t *) pixels)[gy * 200 + gx]; + blue = px & 0xff; + if (blue != (uint32_t) ((gy - content.y0 + delta) & 0xff)) + bad = 1; + } + if (bad) + goto Failure; /* a band blit clobbered another band's source */ + + wuss_window_close(win_o); + wuss_window_close(win_m); + } + printf("test: wuss_WINDOW_NO_RESIZE_BLIT redraws the whole window instead of blitting\n"); { diff --git a/libraries/wuss/window/invalidate.c b/libraries/wuss/window/invalidate.c index 7107911..1d4bb5d 100644 --- a/libraries/wuss/window/invalidate.c +++ b/libraries/wuss/window/invalidate.c @@ -148,6 +148,60 @@ int wuss__subtract_boxes(const box_t *whole, return ncur; } +/* Sequential single-rect blits (each a self-consistent memmove) can still + * corrupt each other when one piece's destination lands on another piece's + * still-unread source -- but that only actually matters if no blit order + * avoids it. Build the "must happen before" graph (piece j before piece i + * whenever dest[i] would overwrite clean[j]'s still-unread source) and + * topologically sort it: any window with more than one occluder-carved + * piece near a shared edge -- e.g. two bands split by a corner occluder -- + * routinely has one such pairwise overlap without there being a genuine + * cycle, and rejecting those outright regressed plain corner-occlusion + * drags into full fallback redraws. Only an actual cycle (i must precede j + * and j must precede i) has no safe order and needs the fallback. */ +int wuss__order_pieces(const box_t *clean, + const box_t *dest, + int n, + int *order) +{ + int adj[WUSS_MAX_INVALIDATE_PIECES][WUSS_MAX_INVALIDATE_PIECES]; + int indeg[WUSS_MAX_INVALIDATE_PIECES]; + int queue[WUSS_MAX_INVALIDATE_PIECES]; + int i, j, head, tail, nout, u; + + for (i = 0; i < n; i++) + indeg[i] = 0; + for (j = 0; j < n; j++) + for (i = 0; i < n; i++) + adj[j][i] = 0; + + for (i = 0; i < n; i++) + for (j = 0; j < n; j++) + if (i != j && !adj[j][i] && box_intersects(&dest[i], &clean[j])) + { + adj[j][i] = 1; /* j must be blitted before i */ + indeg[i]++; + } + + tail = 0; + for (i = 0; i < n; i++) + if (indeg[i] == 0) + queue[tail++] = i; + + head = nout = 0; + while (head < tail) + { + u = queue[head++]; + order[nout++] = u; + + for (i = 0; i < n; i++) + if (adj[u][i] && --indeg[i] == 0) + queue[tail++] = i; + } + + return nout == n; +} + /* Invalidate the parts of "window"'s footprint that are hidden behind other * windows at the current z-order -- the rest of its footprint is already * showing its own correct pixels, so redrawing that too would just be diff --git a/libraries/wuss/window/move.c b/libraries/wuss/window/move.c index 916bd36..6bb923d 100644 --- a/libraries/wuss/window/move.c +++ b/libraries/wuss/window/move.c @@ -14,60 +14,6 @@ static void translate_box(const box_t *box, int dx, int dy, box_t *out) /* p is the window's content top-left; the furniture offset (outline plus * any titlebar) is constant for a given window, so the footprint just * follows it */ -/* Sequential single-rect blits (each a self-consistent memmove) can still - * corrupt each other when one piece's destination lands on another piece's - * still-unread source -- but that only actually matters if no blit order - * avoids it. Build the "must happen before" graph (piece j before piece i - * whenever dest[i] would overwrite clean[j]'s still-unread source) and - * topologically sort it: any window with more than one occluder-carved - * piece near a shared edge -- e.g. two bands split by a corner occluder -- - * routinely has one such pairwise overlap without there being a genuine - * cycle, and rejecting those outright regressed plain corner-occlusion - * drags into full fallback redraws. Only an actual cycle (i must precede j - * and j must precede i) has no safe order and needs the fallback. */ -static int wuss__order_pieces(const box_t *clean, - const box_t *dest, - int n, - int *order) -{ - int adj[WUSS_MAX_INVALIDATE_PIECES][WUSS_MAX_INVALIDATE_PIECES]; - int indeg[WUSS_MAX_INVALIDATE_PIECES]; - int queue[WUSS_MAX_INVALIDATE_PIECES]; - int i, j, head, tail, nout, u; - - for (i = 0; i < n; i++) - indeg[i] = 0; - for (j = 0; j < n; j++) - for (i = 0; i < n; i++) - adj[j][i] = 0; - - for (i = 0; i < n; i++) - for (j = 0; j < n; j++) - if (i != j && !adj[j][i] && box_intersects(&dest[i], &clean[j])) - { - adj[j][i] = 1; /* j must be blitted before i */ - indeg[i]++; - } - - tail = 0; - for (i = 0; i < n; i++) - if (indeg[i] == 0) - queue[tail++] = i; - - head = nout = 0; - while (head < tail) - { - u = queue[head++]; - order[nout++] = u; - - for (i = 0; i < n; i++) - if (adj[u][i] && --indeg[i] == 0) - queue[tail++] = i; - } - - return nout == n; -} - void wuss_window_move(wuss_window_t *window, point_t p) { box_t clean[WUSS_MAX_INVALIDATE_PIECES]; diff --git a/libraries/wuss/window/set-scroll.c b/libraries/wuss/window/set-scroll.c index 3aec584..df1552a 100644 --- a/libraries/wuss/window/set-scroll.c +++ b/libraries/wuss/window/set-scroll.c @@ -6,10 +6,14 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p) { box_t content; box_t src[WUSS_MAX_INVALIDATE_PIECES]; + box_t blit_src[WUSS_MAX_INVALIDATE_PIECES]; + box_t blit_dest[WUSS_MAX_INVALIDATE_PIECES]; box_t copied[WUSS_MAX_INVALIDATE_PIECES]; box_t dirty[WUSS_MAX_INVALIDATE_PIECES]; box_t vis[WUSS_MAX_INVALIDATE_PIECES]; - int dx, dy, nsrc, ncopied, ndirty, nvis, i, j; + int order[WUSS_MAX_INVALIDATE_PIECES]; + int dx, dy, nsrc, nblit, ncopied, ndirty, nvis, i, j, idx; + int overflow, blit_failed; dx = p.x - window->scroll.x; dy = p.y - window->scroll.y; @@ -31,14 +35,12 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p) * Sliding a source piece by the scroll delta can still land its * destination on screen a higher window owns -- screen_copy_rect only * clips to the content box, so it would paint over the occluder there. - * Clip each destination against the occluders too and blit only the - * surviving sub-pieces, each with its own matching source offset; the - * rest falls into the repaint set below. Falls back to a full content - * invalidate when the pixel format can't blit. */ - nsrc = wuss__clip_to_visible(window, &content, src); - ncopied = 0; - window->wuss->scr->clip = content; - for (i = 0; i < nsrc; i++) + * Clip each destination against the occluders too and keep only the + * surviving sub-pieces, each with its own matching source offset. */ + nsrc = wuss__clip_to_visible(window, &content, src); + nblit = 0; + overflow = 0; + for (i = 0; i < nsrc && !overflow; i++) { box_t want; @@ -46,17 +48,44 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p) nvis = wuss__clip_to_visible(window, &want, vis); for (j = 0; j < nvis; j++) { - box_t ssub, got; + if (nblit == WUSS_MAX_INVALIDATE_PIECES) + { + overflow = 1; + break; + } + blit_dest[nblit] = vis[j]; + box_translated(&vis[j], dx, dy, &blit_src[nblit]); + nblit++; + } + } + + /* Each sub-piece blit is a self-consistent memmove, but one piece's + * destination can land on another piece's still-unread source (e.g. the + * bands carved around a floating occluder overlap once shifted). Order + * them so that never happens; fall back to a full content invalidate if + * no safe order exists or the piece budget overflowed. */ + window->wuss->scr->clip = content; + ncopied = 0; + blit_failed = nsrc == 0 || overflow || + !wuss__order_pieces(blit_src, blit_dest, nblit, order); + + for (i = 0; i < nblit && !blit_failed; i++) + { + box_t got; - box_translated(&vis[j], dx, dy, &ssub); /* source for this dest piece */ - if (screen_copy_rect(window->wuss->scr, &ssub, - POINT(ssub.x0 - dx, ssub.y0 - dy), &got) != 0 && - ncopied < WUSS_MAX_INVALIDATE_PIECES) - copied[ncopied++] = got; + idx = order[i]; + if (screen_copy_rect(window->wuss->scr, &blit_src[idx], + POINT(blit_dest[idx].x0, blit_dest[idx].y0), + &got) == 0) + { + blit_failed = 1; + break; } + if (ncopied < WUSS_MAX_INVALIDATE_PIECES) + copied[ncopied++] = got; } - if (ncopied > 0) + if (!blit_failed && ncopied > 0) { /* Repaint the content box minus what the blit reused, then clip each * survivor to this window's visible area: the parts that were behind an From 12214977eb4b551ef1a2daea46ed8c4d69e54015 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 19:03:26 +0100 Subject: [PATCH 56/79] docs(wuss): put icon enum/struct member docs before each member Move the trailing /**< member comments in wuss_icon_type, wuss_icon_flags and wuss_icon_spec to /** blocks preceding each member. Also lower the wrap tooling's default width from 80 to 77 columns (wrap_doxygen.py and wrap_protos.py, plus their tests), rewrap every public header's Doxygen blocks to the new limit, and run wrap_protos.py across the tree to rewrap prototypes that now exceed it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- include/databases/digest-db.h | 6 +- include/databases/filename-db.h | 4 +- include/databases/pickle.h | 19 +- include/datastruct/atom.h | 31 +- include/datastruct/bitarr.h | 23 +- include/datastruct/cache.h | 13 +- include/datastruct/hash.h | 4 +- include/datastruct/vector.h | 22 +- include/framebuf/bmfont.h | 6 +- include/framebuf/curve.h | 15 +- include/framebuf/screen.h | 97 +++--- include/framebuf/span.h | 20 +- include/geom/box.h | 8 +- include/geom/layout.h | 4 +- include/geom/line.h | 15 +- include/geom/packer.h | 27 +- include/io/stream-packbits.h | 8 +- include/io/stream.h | 3 +- include/text/bmtext.h | 13 +- include/text/txtfmt.h | 8 +- include/utils/array.h | 8 +- include/utils/pack.h | 18 +- include/wuss/icon.h | 323 +++++++++----------- include/wuss/menu.h | 69 +++-- include/wuss/task.h | 12 +- include/wuss/window.h | 131 ++++---- include/wuss/wuss.h | 171 ++++++----- libraries/datastruct/ntree/insert-before.c | 4 +- libraries/framebuf/bitmap/bitmap.c | 4 +- libraries/framebuf/screen/screen-draw.c | 6 +- libraries/io/stream/stream-packbitsdecomp.c | 4 +- libraries/wuss/furniture/invalidate.c | 3 +- libraries/wuss/icon/draw.c | 3 +- libraries/wuss/impl.h | 6 +- libraries/wuss/test/tasks/ball.c | 4 +- libraries/wuss/test/tasks/ball.h | 4 +- libraries/wuss/test/tasks/porter-duff.c | 3 +- libraries/wuss/test/tasks/sofa.c | 8 +- libraries/wuss/test/tasks/sofa.h | 4 +- libraries/wuss/window/get-content-bounds.c | 3 +- libraries/wuss/window/get-visible-bounds.c | 3 +- libraries/wuss/window/invalidate.c | 8 +- libraries/wuss/window/set-background.c | 3 +- tools/test_wrap_doxygen.py | 6 +- tools/test_wrap_protos.py | 8 +- tools/wrap_doxygen.py | 2 +- tools/wrap_protos.py | 2 +- 47 files changed, 609 insertions(+), 557 deletions(-) diff --git a/include/databases/digest-db.h b/include/databases/digest-db.h index 287ffc5..d4002c8 100644 --- a/include/databases/digest-db.h +++ b/include/databases/digest-db.h @@ -5,9 +5,9 @@ * * Digest database. * - * digestdb is a wrapper around an atom set specifically for holding (128-bit) - * digests. It is used by tagdb and filenamedb to share the cost of storing - * digest values by replacing 128-bit digests with smaller indices. + * digestdb is a wrapper around an atom set specifically for holding + * (128-bit) digests. It is used by tagdb and filenamedb to share the cost of + * storing digest values by replacing 128-bit digests with smaller indices. */ #ifndef DATABASES_DIGEST_DB_H diff --git a/include/databases/filename-db.h b/include/databases/filename-db.h index d0a2bf5..fd02e3d 100644 --- a/include/databases/filename-db.h +++ b/include/databases/filename-db.h @@ -5,8 +5,8 @@ * * Filename database. * - * The filenamedb is an associative array which maps keys (such as digests) to - * filenames. The data is stored on disc. + * The filenamedb is an associative array which maps keys (such as digests) + * to filenames. The data is stored on disc. * * PrivateEye uses this to find out where an image lives given its digest. */ diff --git a/include/databases/pickle.h b/include/databases/pickle.h index 88c1d7a..0026ca7 100644 --- a/include/databases/pickle.h +++ b/include/databases/pickle.h @@ -6,8 +6,8 @@ * Storage of associative arrays. * * Its name borrowed from Python, this module provides pickle_pickle() and - * pickle_unpickle() which respectively serialise or deserialise an associative - * array to a file of the form: + * pickle_unpickle() which respectively serialise or deserialise an + * associative array to a file of the form: * * #<comments> <version> <key><separator><value> (zero or more) * @@ -240,13 +240,14 @@ pickle_unformat_methods_t; /* ----------------------------------------------------------------------- */ /** - * Serialise associative array 'assocarr' to the file 'filename'. Interpret the - * contents of the associative array using the methods in 'reader'. Format the - * keys and values for storage using the methods in 'format'. + * Serialise associative array 'assocarr' to the file 'filename'. Interpret + * the contents of the associative array using the methods in 'reader'. + * Format the keys and values for storage using the methods in 'format'. * * \param[in] filename Filename to save to. * \param[in] assocarr Associative array to pickle. - * \param[in] reader Interfaces for reading from the associative array. + * \param[in] reader Interfaces for reading from the associative + * array. * \param[in] format Interfaces for formatting the retrieved values. * \param[in] opaque Opaque pointer passed into interfaces. * @@ -259,9 +260,9 @@ result_t pickle_pickle(const char *filename, void *opaque); /** - * Populate associative array 'assocarr' from the file 'filename'. Insert into - * the associative array using the methods in 'writer'. Parse the keys and - * values from storage using the methods in 'unformat'. + * Populate associative array 'assocarr' from the file 'filename'. Insert + * into the associative array using the methods in 'writer'. Parse the keys + * and values from storage using the methods in 'unformat'. * * \param[in] filename Filename to read from. * \param[in] assocarr Associative array to pickle. diff --git a/include/datastruct/atom.h b/include/datastruct/atom.h index 285c0ee..e4a173b 100644 --- a/include/datastruct/atom.h +++ b/include/datastruct/atom.h @@ -6,15 +6,16 @@ * Indexed data block store. * * Atoms are indices assigned to blocks of stored data. Identical data blocks - * are assigned the same atom. Atoms belonging to the same set can be directly - * compared avoiding the need to memcmp, strcmp, or otherwise linearly compare - * the contents of the respective data blocks. + * are assigned the same atom. Atoms belonging to the same set can be + * directly compared avoiding the need to memcmp, strcmp, or otherwise + * linearly compare the contents of the respective data blocks. * - * Data blocks can be retrieved by quoting an atom to atom_get. This returns a - * pointer to the data block along with its length. + * Data blocks can be retrieved by quoting an atom to atom_get. This returns + * a pointer to the data block along with its length. * - * To avoid heap overhead, atoms are stored in a series of fixed-size pools of - * memory. The size of the pools may be specified when the set is first created. + * To avoid heap overhead, atoms are stored in a series of fixed-size pools + * of memory. The size of the pools may be specified when the set is first + * created. */ #ifndef DATASTRUCT_ATOM_H @@ -56,13 +57,15 @@ atom_set_t *atom_create(void); /** * Create a new atom set using the specified data sizes. * - * \param locpoolsz Size of a location pool, or zero for the default. Set this - * to the number of entries you typically expect to store. - * \param blkpoolsz Size of a block pool, or zero for the default. No inserted - * data block may be larger than this. Increasing this value - * will use fewer individual block pools, reducing heap - * overhead, at the expense of potentially greater wasted space - * should the block pool remain not fully allocated. + * \param locpoolsz Size of a location pool, or zero for the default. Set + * this to the number of entries you typically expect to + * store. + * \param blkpoolsz Size of a block pool, or zero for the default. No + * inserted data block may be larger than this. Increasing + * this value will use fewer individual block pools, + * reducing heap overhead, at the expense of potentially + * greater wasted space should the block pool remain not + * fully allocated. * * \return New atom set, or NULL if out of memory. */ diff --git a/include/datastruct/bitarr.h b/include/datastruct/bitarr.h index e7398bf..783f3d4 100644 --- a/include/datastruct/bitarr.h +++ b/include/datastruct/bitarr.h @@ -5,9 +5,9 @@ * * Arrays of bits. * - * Bit arrays are an array of bits. They are of a fixed length and allocated by - * the client. The bit array library provides functions to manipulate bit arrays - * but not allocate them. + * Bit arrays are an array of bits. They are of a fixed length and allocated + * by the client. The bit array library provides functions to manipulate bit + * arrays but not allocate them. * * \see Bit Vector for manipulating a dynamically allocated bit array. * @@ -54,7 +54,8 @@ typedef unsigned int bitarr_elem_t; #define BITARR_MASK (BITARR_BITS - 1) /** - * Return the number of elements required to store the specified number of bits. + * Return the number of elements required to store the specified number of + * bits. */ #define BITARR_ELEMS(nbits) ((nbits + BITARR_BITS - 1) >> BITARR_SHIFT) @@ -62,15 +63,15 @@ typedef unsigned int bitarr_elem_t; * Declare a bit array with the specified number of bits. * * This uses the OSLib trick of declaring a macro to define a type and also - * declaring an 'equivalent' struct. In practice they're not actually equivalent - * as the former, bitarr_ARRAY, is an anonymous struct of a given length and the - * bitarr_t is a struct containing an array of 'UNKNOWN' length, which is - * actually defined as 1. + * declaring an 'equivalent' struct. In practice they're not actually + * equivalent as the former, bitarr_ARRAY, is an anonymous struct of a given + * length and the bitarr_t is a struct containing an array of 'UNKNOWN' + * length, which is actually defined as 1. * * This works out all right, letting us declare bit arrays by specifying the - * number of bits we require, but we end up needing to cast out declared entries - * to (bitarr_t *) when calling a 'real' function, e.g. bitarr_count which looks - * awkward. + * number of bits we require, but we end up needing to cast out declared + * entries to (bitarr_t *) when calling a 'real' function, e.g. bitarr_count + * which looks awkward. */ #define bitarr_ARRAY(nbits) \ struct { \ diff --git a/include/datastruct/cache.h b/include/datastruct/cache.h index ce0aeb4..6bbd548 100644 --- a/include/datastruct/cache.h +++ b/include/datastruct/cache.h @@ -46,8 +46,8 @@ typedef unsigned int cachekey_t; /** * Create a cache. * - * \param[in] config Pointer to cache parameters, or NULL for default cache - * parameters. + * \param[in] config Pointer to cache parameters, or NULL for default + * cache parameters. * \param[in] length Byte length of the cache to allocate. * \param[out] cache Returned pointer to the created cache. * @@ -67,8 +67,8 @@ void cache_destroy(cache_t *doomed); /** * Create a cache in a supplied block of memory. * - * \param[in] config Pointer to cache parameters, or NULL for default cache - * parameters. + * \param[in] config Pointer to cache parameters, or NULL for default + * cache parameters. * \param[in] block Pointer to memory to use. * \param[in] length Byte length of block. * \param[out] cache Returned pointer to the created cache. @@ -104,8 +104,9 @@ void *cache_get(cache_t *cache, cachekey_t key); * \param[in] key Key. * \param[in] data Pointer to data to store. * \param[in] length Length of data. - * \param[out] inserted Returned pointer to the inserted data, or NULL if not - * wanted. This valid until the next cache_put operation. + * \param[out] inserted Returned pointer to the inserted data, or NULL if + * not wanted. This valid until the next cache_put + * operation. * * \return Error indication. */ diff --git a/include/datastruct/hash.h b/include/datastruct/hash.h index bf68ed2..02794d2 100644 --- a/include/datastruct/hash.h +++ b/include/datastruct/hash.h @@ -5,8 +5,8 @@ * * Hash is an associative array. * - * The interface presently forces you to malloc all keys, and values passed in, - * yourself. + * The interface presently forces you to malloc all keys, and values passed + * in, yourself. */ #ifndef DATASTRUCT_HASH_H diff --git a/include/datastruct/vector.h b/include/datastruct/vector.h index 5750e37..4a4b9c6 100644 --- a/include/datastruct/vector.h +++ b/include/datastruct/vector.h @@ -3,10 +3,11 @@ /** * \file vector.h * - * Vector is an abstracted array which can be resized by both length and element - * width. + * Vector is an abstracted array which can be resized by both length and + * element width. * - * Elements are of a fixed size, stored contiguously and are addressed by index. + * Elements are of a fixed size, stored contiguously and are addressed by + * index. * * \warning If the vector is altered then pointers into the vector may be * invalidated (should the block move when reallocated). @@ -82,8 +83,8 @@ result_t vector_set_length(vector_t *vector, unsigned int length); /* ----------------------------------------------------------------------- */ /** - * Reserve space for at least the specified number of elements in the specified - * vector. + * Reserve space for at least the specified number of elements in the + * specified vector. * * \param[in] vector Vector to change. * \param[in] need Required length. @@ -106,8 +107,8 @@ size_t vector_width(const vector_t *vector); /** * Change the byte width of element stored in the specified vector. * - * If the element width is reduced then any extra bytes are lost. If increased, - * then zeroes are inserted. + * If the element width is reduced then any extra bytes are lost. If + * increased, then zeroes are inserted. * * \param[in] vector Vector to change. * \param[in] width New element width. @@ -154,7 +155,8 @@ void vector_set(vector_t *vector, unsigned int index, const void *value); result_t vector_insert(vector_t *vector, const void *value); /** - * Insert many elements at the end of the vector, allocating memory if required. + * Insert many elements at the end of the vector, allocating memory if + * required. * * \param[in] vector Vector to change. * \param[in] values Array of values to insert. @@ -162,7 +164,9 @@ result_t vector_insert(vector_t *vector, const void *value); * * \return result_OK or result_OOM. */ -result_t vector_insert_many(vector_t *vector, const void *values, int nvalues); +result_t vector_insert_many(vector_t *vector, + const void *values, + int nvalues); /* ----------------------------------------------------------------------- */ diff --git a/include/framebuf/bmfont.h b/include/framebuf/bmfont.h index b8f7d9b..417cfa1 100644 --- a/include/framebuf/bmfont.h +++ b/include/framebuf/bmfont.h @@ -39,9 +39,9 @@ void bmfont_destroy(bmfont_t *bmfont); void bmfont_get_info(bmfont_t *bmfont, int *width, int *height); /** - * Read the number of glyphs in the specified bitmap font. Glyphs are laid out - * contiguously starting at ' ' (space, 0x20), so a char c has a glyph iff c >= - * ' ' and c < ' ' + bmfont_get_count(bmfont). + * Read the number of glyphs in the specified bitmap font. Glyphs are laid + * out contiguously starting at ' ' (space, 0x20), so a char c has a glyph + * iff c >= ' ' and c < ' ' + bmfont_get_count(bmfont). * * \param[in] bmfont Bitmap font to query. * \return Number of glyphs in the font. diff --git a/include/framebuf/curve.h b/include/framebuf/curve.h index a8d171c..d8e9b15 100644 --- a/include/framebuf/curve.h +++ b/include/framebuf/curve.h @@ -11,8 +11,8 @@ /** * Return the point on the line defined by points `p0` and `p1` at time `t`. * - * This is a straight linear interpolation between the two points: there is no - * curvature. + * This is a straight linear interpolation between the two points: there is + * no curvature. * * \param[in] p0 Start point. * \param[in] p1 End point. @@ -116,8 +116,8 @@ point_t curve_bezier_point_on_cubic_r(point_t p0, fix16_t t); /** - * As for \ref curve_bezier_point_on_quartic but is written in terms of cubics - * (and in turn of quads). + * As for \ref curve_bezier_point_on_quartic but is written in terms of + * cubics (and in turn of quads). * * \param[in] p0 Start point. * \param[in] p1 Control point 1. @@ -135,8 +135,8 @@ point_t curve_bezier_point_on_quartic_r(point_t p0, fix16_t t); /** - * As for \ref curve_bezier_point_on_quintic but is written in terms of quartics - * (and in turn of cubics, etc.). + * As for \ref curve_bezier_point_on_quintic but is written in terms of + * quartics (and in turn of cubics, etc.). * * \param[in] p0 Start point. * \param[in] p1 Control point 1. @@ -156,7 +156,8 @@ point_t curve_bezier_point_on_quintic_r(point_t p0, fix16_t t); /** - * As for \ref curve_bezier_cubic but uses forward differencing (float version). + * As for \ref curve_bezier_cubic but uses forward differencing (float + * version). * * \param[in] p0 Start point. * \param[in] p1 Control point 1. diff --git a/include/framebuf/screen.h b/include/framebuf/screen.h index f8e8f08..455a786 100644 --- a/include/framebuf/screen.h +++ b/include/framebuf/screen.h @@ -90,13 +90,14 @@ void screen_fill_rect(screen_t *scr, * \param[in] colour Colour of rectangle. */ void screen_fill_square(screen_t *scr, - int x, int y, - int size, - colour_t colour); + int x, + int y, + int size, + colour_t colour); /** - * Built-in 8x8 fill patterns for `screen_fill_pattern`. Each is a 1-bit tile: - * set bits take the foreground colour, clear bits the background. + * Built-in 8x8 fill patterns for `screen_fill_pattern`. Each is a 1-bit + * tile: set bits take the foreground colour, clear bits the background. */ typedef enum screen_pattern { @@ -112,11 +113,11 @@ typedef enum screen_pattern /** * 8x8 ordered (Bayer) dither, one entry per coverage level 0 (empty) to 64 * (solid). Index by level as `screen_PATTERN_BAYER0 + level`; - * `screen_PATTERN_BAYER_LIMIT` is one past the last. `screen_PATTERN_BAYER0` - * matches nothing else here, `screen_PATTERN_BAYER32` equals - * `screen_PATTERN_GREY50` and `screen_PATTERN_BAYER64` equals - * `screen_PATTERN_SOLID`; the duplicates are kept so the level arithmetic - * stays simple. + * `screen_PATTERN_BAYER_LIMIT` is one past the last. + * `screen_PATTERN_BAYER0` matches nothing else here, + * `screen_PATTERN_BAYER32` equals `screen_PATTERN_GREY50` and + * `screen_PATTERN_BAYER64` equals `screen_PATTERN_SOLID`; the duplicates + * are kept so the level arithmetic stays simple. */ screen_PATTERN_BAYER0, screen_PATTERN_BAYER_LIMIT = screen_PATTERN_BAYER0 + 65, @@ -129,10 +130,10 @@ screen_pattern_t; /** * Fills a box with a repeating 8x8 two-colour pattern. * - * The tile is phased against (`origin_x`, `origin_y`): that coordinate is the - * one that would map to the box's top-left corner. Passing the caller's own - * scroll origin keeps the pattern locked to content as the box moves, rather - * than crawling with it. + * The tile is phased against (`origin_x`, `origin_y`): that coordinate is + * the one that would map to the box's top-left corner. Passing the caller's + * own scroll origin keeps the pattern locked to content as the box moves, + * rather than crawling with it. * * Clipped to the screen's clip region. * @@ -153,12 +154,13 @@ void screen_fill_pattern(screen_t *scr, colour_t bg); /** - * Draws a bitmap, alpha-blending it against the screen where the bitmap has an - * alpha channel. On paletted screens, which have no linear channel bits to - * blend, this falls back to alpha-tested transparency instead (drawn at full - * strength, or not at all). + * Draws a bitmap, alpha-blending it against the screen where the bitmap has + * an alpha channel. On paletted screens, which have no linear channel bits + * to blend, this falls back to alpha-tested transparency instead (drawn at + * full strength, or not at all). * - * The bitmap is clipped to the screen's clip region. No scaling is performed. + * The bitmap is clipped to the screen's clip region. No scaling is + * performed. * * \param[in] scr Screen to draw upon. * \param[in] x X coordinate of leftmost point to draw bitmap at. @@ -174,18 +176,18 @@ enum }; /** - * Draws a "9-patch": a resizable frame built from a source image that is a 3x3 - * grid of equal cells. The source width and height must each be a positive - * multiple of 3; the cell size is a third of each. Given a destination box, the - * four corner cells are drawn at their natural size in the destination corners, - * the four edge cells are tiled along the destination edges, and the centre - * cell is tiled across the interior. + * Draws a "9-patch": a resizable frame built from a source image that is a + * 3x3 grid of equal cells. The source width and height must each be a + * positive multiple of 3; the cell size is a third of each. Given a + * destination box, the four corner cells are drawn at their natural size in + * the destination corners, the four edge cells are tiled along the + * destination edges, and the centre cell is tiled across the interior. * - * If the destination is narrower or shorter than two cells the opposing corners - * overlap and each is clipped to its own half; the edges and centre are then - * omitted. Drawing is clipped to both the destination box and the screen's clip - * region, which is restored on return. Cells are blended exactly as - * `screen_draw_bitmap` does. No scaling is performed. + * If the destination is narrower or shorter than two cells the opposing + * corners overlap and each is clipped to its own half; the edges and centre + * are then omitted. Drawing is clipped to both the destination box and the + * screen's clip region, which is restored on return. Cells are blended + * exactly as `screen_draw_bitmap` does. No scaling is performed. * * \param[in] scr Screen to draw upon. * \param[in] dst Destination box to fill with the frame. @@ -206,18 +208,18 @@ void screen_draw_ninepatch(screen_t *scr, * copying is done in the correct row order to handle that safely. * * Both the source and destination are clipped to the screen's clip region, - * shrinking together so the copied area always maps source pixel to destination - * pixel 1:1. + * shrinking together so the copied area always maps source pixel to + * destination pixel 1:1. * * Callers must check the return value and fall back to a normal * invalidate/redraw when it's false (e.g. out of memory, or an unknown pixel * format), since a declined copy leaves the destination untouched. * * If "src" or the intended destination falls partly off-screen, the actual - * copied area shrinks to what both ends have in common on-screen: callers must - * invalidate whatever part of their intended (unclipped) destination falls - * outside "copied_dst", since it has no valid source pixels to have been copied - * from and so is left untouched, not merely stale. + * copied area shrinks to what both ends have in common on-screen: callers + * must invalidate whatever part of their intended (unclipped) destination + * falls outside "copied_dst", since it has no valid source pixels to have + * been copied from and so is left untouched, not merely stale. * * \param[in] scr Screen to copy within. * \param[in] src Screen-space region to copy from. @@ -226,8 +228,8 @@ void screen_draw_ninepatch(screen_t *scr, * smaller than intended if either end was partly * off-screen). Pass NULL if not needed. Left unset if * the copy was declined. - * \return True if the copy was performed, false if declined (unsupported pixel - * format). + * \return True if the copy was performed, false if declined (unsupported + * pixel format). */ int screen_copy_rect(screen_t *scr, const box_t *src, @@ -255,11 +257,11 @@ void screen_draw_line(screen_t *scr, /** * Draws a connected polyline through `npoints` points: a `screen_draw_line` - * segment between each adjacent pair. For a closed shape repeat the first point - * as the last. Fewer than 2 points draws nothing. + * segment between each adjacent pair. For a closed shape repeat the first + * point as the last. Fewer than 2 points draws nothing. * - * Segments share their joint pixel, which is plotted by both adjacent segments; - * harmless for a solid colour. + * Segments share their joint pixel, which is plotted by both adjacent + * segments; harmless for a solid colour. * * Coordinates are `int`s and inclusive. * @@ -275,8 +277,8 @@ void screen_draw_lines(screen_t *scr, /** * Draws a one-pixel unfilled rectangle outline. `size` is inclusive of both - * edges, matching `screen_fill_rect`. A degenerate size (<= 1 in either axis) - * falls back to a filled `screen_fill_rect`. + * edges, matching `screen_fill_rect`. A degenerate size (<= 1 in either + * axis) falls back to a filled `screen_fill_rect`. * * \param[in] scr Screen to draw upon. * \param[in] x X coordinate of leftmost point of rectangle. @@ -291,9 +293,10 @@ void screen_draw_rect(screen_t *scr, colour_t colour); /** - * Draws a stippled line: `on` pixels drawn, then `off` skipped, repeating along - * the line. Bresenham stepping, so the dash period is measured in steps not - * Euclidean distance. `on` <= 0 draws nothing; `off` <= 0 gives a solid line. + * Draws a stippled line: `on` pixels drawn, then `off` skipped, repeating + * along the line. Bresenham stepping, so the dash period is measured in + * steps not Euclidean distance. `on` <= 0 draws nothing; `off` <= 0 gives a + * solid line. * * Coordinates are `int`s and inclusive. * diff --git a/include/framebuf/span.h b/include/framebuf/span.h index 6b1a3f4..bda935e 100644 --- a/include/framebuf/span.h +++ b/include/framebuf/span.h @@ -17,17 +17,17 @@ typedef void (span_copy_t)(void *dst, const void *src, int length); /** * Type of a "blend constant pixels" function. * - * This will blend the respective source pixels by the specified constant alpha - * value, writing the results to the destination buffer (like Porter-Duff Source - * Over Destination). + * This will blend the respective source pixels by the specified constant + * alpha value, writing the results to the destination buffer (like + * Porter-Duff Source Over Destination). * * \param[out] dst Destination pixels. * \param[in] src1 Source pixels 1. * \param[in] src2 Source pixels 2. * \param[in] length Length of pixels to blend. * \param[in] alpha Constant alpha value (0..255). - * \param[in] context Format-specific extra data (e.g. a palette for an indexed - * format); ignored where not needed, pass NULL. + * \param[in] context Format-specific extra data (e.g. a palette for an + * indexed format); ignored where not needed, pass NULL. */ typedef void (span_blendconst_t)(void *dst, const void *src1, @@ -39,9 +39,9 @@ typedef void (span_blendconst_t)(void *dst, /** * Type of a "blend array of pixels" function. * - * This will blend the respective source pixels by the specified alpha values, - * writing the results to the destination buffer (like Porter-Duff Source Over - * Destination). + * This will blend the respective source pixels by the specified alpha + * values, writing the results to the destination buffer (like Porter-Duff + * Source Over Destination). * * \param[out] dst Destination pixels. * \param[in] src1 Source pixels 1. @@ -58,8 +58,8 @@ typedef void (span_blendarray_t)(void *dst, /** * Defines a span. * - * A span is a group of functions that combine runs of pixels. They are keyed by - * pixel format. + * A span is a group of functions that combine runs of pixels. They are keyed + * by pixel format. */ typedef struct span { diff --git a/include/geom/box.h b/include/geom/box.h index 1c61869..da7751c 100644 --- a/include/geom/box.h +++ b/include/geom/box.h @@ -30,8 +30,8 @@ typedef os_box box_t; #endif /** - * Initialises a box to an invalid state that will still produce a valid result - * when intersected with. + * Initialises a box to an invalid state that will still produce a valid + * result when intersected with. */ #define BOX_INIT { INT_MAX, INT_MAX, INT_MIN, INT_MIN } @@ -49,8 +49,8 @@ size2d_t box_size(const box_t *b); /** * Reset the box to an invalid state. * - * This sets x0,y0 to INT_MAX and the x1,y1 to INT_MIN. This is an invalid box - * but will still produce a valid result when intersected with. + * This sets x0,y0 to INT_MAX and the x1,y1 to INT_MIN. This is an invalid + * box but will still produce a valid result when intersected with. * * \param[in] b The box to reset. */ diff --git a/include/geom/layout.h b/include/geom/layout.h index e56a94c..ded5e37 100644 --- a/include/geom/layout.h +++ b/include/geom/layout.h @@ -61,8 +61,8 @@ layout_spec_t; * \param[in] nelements Number of layout elements given. * \param[out] boxes An array of boxes to be populated. * \param[in] nboxes Number of boxes available. - * \return \ref result_OK on success, result_LAYOUT_BUFFER_FULL if too few boxes - * were supplied, or appropriate result code otherwise. + * \return \ref result_OK on success, result_LAYOUT_BUFFER_FULL if too few + * boxes were supplied, or appropriate result code otherwise. */ result_t layout_place(const layout_spec_t *spec, const layout_element_t *elements, diff --git a/include/geom/line.h b/include/geom/line.h index 73dadab..a8cc766 100644 --- a/include/geom/line.h +++ b/include/geom/line.h @@ -11,14 +11,15 @@ extern "C" #include "geom/box.h" /** - * Clips the line (x0,y0)-(x1,y1) by box `clip` and returns the clipped points - * in `x0` and co. + * Clips the line (x0,y0)-(x1,y1) by box `clip` and returns the clipped + * points in `x0` and co. * - * The returned points are rounded to the nearest integer position on the clip - * boundary, so they vary with the clip box given. Callers which step along the - * line incrementally (Bresenham, Wu, etc.) must not seed their error terms from - * them if the pixels drawn are to be independent of the clip box passed in; use - * the return value to reject and step from the original endpoints. + * The returned points are rounded to the nearest integer position on the + * clip boundary, so they vary with the clip box given. Callers which step + * along the line incrementally (Bresenham, Wu, etc.) must not seed their + * error terms from them if the pixels drawn are to be independent of the + * clip box passed in; use the return value to reject and step from the + * original endpoints. * * \param[in] clip Rectangular clip region. * \param[in,out] x0 X coordinate of first point of line (modified). diff --git a/include/geom/packer.h b/include/geom/packer.h index 20ff397..cc7aaa3 100644 --- a/include/geom/packer.h +++ b/include/geom/packer.h @@ -79,10 +79,10 @@ result_t packer_place_at(T *packer, /** * Sets a gutter for packer_place_by: the width in pixels of a strip it - * additionally reserves along each placed box's two edges facing away from the - * search corner, so boxes placed by location never end up flush against each - * other. Default 0 (no gutter). Negative values are treated as 0. Does not - * affect packer_place_at. + * additionally reserves along each placed box's two edges facing away from + * the search corner, so boxes placed by location never end up flush against + * each other. Default 0 (no gutter). Negative values are treated as 0. Does + * not affect packer_place_at. * * \param[in] packer Packer to configure. * \param[in] gutter Gutter width in pixels. @@ -94,22 +94,23 @@ void packer_set_gutter(T *packer, int gutter); * packer_place_at / packer_place_by. * * Free areas that share a full edge are coalesced after each release, so - * repeated place/release cycles reclaim the whole page rather than fragmenting - * it. A placement spanning two free areas that only partially overlap - * (staggered edges) still will not fit until the gap between them is freed too. - * packer_get_consumed_area is not narrowed by a release. + * repeated place/release cycles reclaim the whole page rather than + * fragmenting it. A placement spanning two free areas that only partially + * overlap (staggered edges) still will not fit until the gap between them is + * freed too. packer_get_consumed_area is not narrowed by a release. * * \param[in] packer Packer to release into. - * \param[in] area Area to release. Clipped to the packer's margins. Copied. - * \return \ref result_OK, or \ref result_PACKER_EMPTY if 'area' lies entirely - * outside the margins. + * \param[in] area Area to release. Clipped to the packer's margins. + * Copied. + * \return \ref result_OK, or \ref result_PACKER_EMPTY if 'area' lies + * entirely outside the margins. */ result_t packer_release(T *packer, const box_t *area); /** - * Places a box of dimensions (w,h) in the next free area determined by location - * 'loc'. + * Places a box of dimensions (w,h) in the next free area determined by + * location 'loc'. * * \param[in] packer Packer to place box. * \param[in] loc Direction to search from for the next available area. diff --git a/include/io/stream-packbits.h b/include/io/stream-packbits.h index cdd70e2..10b25ca 100644 --- a/include/io/stream-packbits.h +++ b/include/io/stream-packbits.h @@ -20,7 +20,9 @@ extern "C" * stream. * \return \ref result_OK on success, or appropriate result code otherwise. */ -result_t stream_packbitscomp_create(stream_t *input, int bufsz, stream_t **s); +result_t stream_packbitscomp_create(stream_t *input, + int bufsz, + stream_t **s); /** * Create a PackBits decompression stream. @@ -31,7 +33,9 @@ result_t stream_packbitscomp_create(stream_t *input, int bufsz, stream_t **s); * stream. * \return \ref result_OK on success, or appropriate result code otherwise. */ -result_t stream_packbitsdecomp_create(stream_t *input, int bufsz, stream_t **s); +result_t stream_packbitsdecomp_create(stream_t *input, + int bufsz, + stream_t **s); #ifdef __cplusplus } diff --git a/include/io/stream.h b/include/io/stream.h index 6eed921..ff1e353 100644 --- a/include/io/stream.h +++ b/include/io/stream.h @@ -3,7 +3,8 @@ /** * \file Stream (interface). * - * A stream is a generic interface which can be used to wrap sources of bytes. + * A stream is a generic interface which can be used to wrap sources of + * bytes. * * Single byte and block operations are supported. Byte access is efficient: * implemented as a macro. diff --git a/include/text/bmtext.h b/include/text/bmtext.h index adee453..8bc6f4d 100644 --- a/include/text/bmtext.h +++ b/include/text/bmtext.h @@ -3,16 +3,16 @@ /** * \file bmtext.h * - * Splits a string into lines that each fit a given pixel width when drawn in a - * \ref bmfont_t, then draws those lines stacked. + * Splits a string into lines that each fit a given pixel width when drawn in + * a \ref bmfont_t, then draws those lines stacked. * * Unlike \ref txtfmt (which wraps at character counts, for monospaced text) * this measures each candidate line with \ref bmfont_measure, so it wraps * proportional fonts correctly. * - * - Breaks at the last space that still fits; hard-breaks a word with no space - * in it. - Runs of whitespace at a break are swallowed. - Layout is pure: it - * touches no screen or scroll state. + * - Breaks at the last space that still fits; hard-breaks a word with no + * space in it. - Runs of whitespace at a break are swallowed. - Layout is + * pure: it touches no screen or scroll state. */ #ifndef DPTLIB_BMTEXT_H @@ -42,7 +42,8 @@ bmtext_line_t; /** * Break \p string into lines that each fit \p wrap_width pixels in \p font. * - * The line pointers refer into \p string itself, so it must outlive \p lines. + * The line pointers refer into \p string itself, so it must outlive \p + * lines. * * \param[in] font Bitmap font the lines will be drawn in. * \param[in] string Text to wrap. diff --git a/include/text/txtfmt.h b/include/text/txtfmt.h index 6893703..65867ce 100644 --- a/include/text/txtfmt.h +++ b/include/text/txtfmt.h @@ -80,8 +80,8 @@ int txtfmt_get_nlines(const txtfmt_t *tx); /** * Returns the wrapped width of a txtfmt. * - * e.g. If you wrap some text containing a word 10 characters long it'll never - * get any thinner than 10. + * e.g. If you wrap some text containing a word 10 characters long it'll + * never get any thinner than 10. * * \param[in] tx Txtfmt to query. * @@ -92,8 +92,8 @@ int txtfmt_get_wrapped_width(const txtfmt_t *tx); /** * Retrieve a line produced by the last wrap. * - * The returned pointer refers into the txtfmt's own copy of the string and is - * valid until the next call to txtfmt_wrap or txtfmt_destroy. It is not + * The returned pointer refers into the txtfmt's own copy of the string and + * is valid until the next call to txtfmt_wrap or txtfmt_destroy. It is not * NUL-terminated: use the returned length. * * \param[in] tx Txtfmt to query. diff --git a/include/utils/array.h b/include/utils/array.h index b25d366..228c33b 100644 --- a/include/utils/array.h +++ b/include/utils/array.h @@ -19,8 +19,8 @@ extern "C" /* ----------------------------------------------------------------------- */ /** - * Signifies an array of unknown length, e.g. when used as the size of an array - * which is the final member of a struct. + * Signifies an array of unknown length, e.g. when used as the size of an + * array which is the final member of a struct. */ #define UNKNOWN 1 @@ -80,8 +80,8 @@ void array_squeeze2(unsigned char *base, * * Presently the growth strategy is doubling. * - * 'block' can be NULL to perform an initial alloc. Start with used == allocated - * == 0. + * 'block' can be NULL to perform an initial alloc. Start with used == + * allocated == 0. * * \param block Pointer to pointer to block. Updated on success. * \param elemsize Element size in bytes. diff --git a/include/utils/pack.h b/include/utils/pack.h index d61f4bb..dc41271 100644 --- a/include/utils/pack.h +++ b/include/utils/pack.h @@ -5,9 +5,9 @@ * * Structure packing and unpacking routines. * - * Inspired by printf, scanf and the Python 'struct' module these allow a string - * composed of formatting character to specify how data should be marshalled - * into memory. + * Inspired by printf, scanf and the Python 'struct' module these allow a + * string composed of formatting character to specify how data should be + * marshalled into memory. */ #ifndef UTILS_PACK_H @@ -42,8 +42,8 @@ extern "C" * pack(outbuf, "2si", 0x2000, 12345, 1 << 31); * * - * Using '*' instead of a count invokes array mode: the next argument is used as - * an array length and the next after that as an array base pointer. + * Using '*' instead of a count invokes array mode: the next argument is used + * as an array length and the next after that as an array base pointer. * * Example: * @@ -74,8 +74,8 @@ size_t pack(unsigned char *outbuf, const char *fmt, ...); * Retrieves three characters and an int from 'inbuf'. * * - * Using '*' instead of a count invokes array mode: the next argument is used as - * an array length and the next after that as an array base pointer. + * Using '*' instead of a count invokes array mode: the next argument is used + * as an array length and the next after that as an array base pointer. * * Example: * @@ -92,8 +92,8 @@ size_t pack(unsigned char *outbuf, const char *fmt, ...); * (Note that these specifiers are all different than the formatting * characters). * - * With these qualifers, sign becomes important. You can write CSIQ for unsigned - * arguments, or csiq for signed arguments. + * With these qualifers, sign becomes important. You can write CSIQ for + * unsigned arguments, or csiq for signed arguments. * * Example: * diff --git a/include/wuss/icon.h b/include/wuss/icon.h index 92aa592..39636e8 100644 --- a/include/wuss/icon.h +++ b/include/wuss/icon.h @@ -3,9 +3,9 @@ /** * \file icon.h * - * Work-area icons: 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. + * Work-area icons: 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. * * An icon's bounding box is given in virtual document space -- the same * coordinate space as wuss_EVENT_MOUSE's point and wuss_window_invalidate's @@ -14,9 +14,9 @@ * content bounds and scroll offset. * * Wuss fills a window's background, draws its icons, then delivers - * wuss_EVENT_REDRAW, so a task is free to paint over or around icon pixels. A - * click on a wuss_ICON_TYPE_BUTTON reaches the task as wuss_EVENT_ICON; clicks - * on a label, or on a hidden or disabled icon, fall through as + * wuss_EVENT_REDRAW, so a task is free to paint over or around icon pixels. + * A click on a wuss_ICON_TYPE_BUTTON reaches the task as wuss_EVENT_ICON; + * clicks on a label, or on a hidden or disabled icon, fall through as * wuss_EVENT_MOUSE. */ @@ -45,75 +45,62 @@ extern "C" * in wuss.h so wuss_event_t can name it regardless of this option. */ /** - * What an icon looks like and how it behaves. The enum is left open so sprite - * and editable-text icons can be added later without breaking existing specs. + * What an icon looks like and how it behaves. The enum is left open so + * sprite and editable-text icons can be added later without breaking + * existing specs. */ typedef enum wuss_icon_type { - wuss_ICON_TYPE_LABEL = 0, /**< Static text drawn with the window manager's - * font. Not interactive: clicks fall through to - * the task as wuss_EVENT_MOUSE. */ - wuss_ICON_TYPE_BUTTON, /**< Bevelled rectangle with a centred text label - * and pressed-state visual feedback; clicks and - * hovers are delivered to the task as - * wuss_EVENT_ICON. */ - wuss_ICON_TYPE_PATTERN, /**< Bounding box filled with a repeating two-colour - * 8x8 tile (spec.pattern) in fg/bg, phased to - * document space so it scrolls rigidly with - * content. Not interactive: clicks fall through - * to the task as wuss_EVENT_MOUSE. text is - * ignored. */ - wuss_ICON_TYPE_FRAME, /**< A grouping box: a one-pixel rectangle in fg - * around the bounding box, broken at the top-left - * for an optional caption (text) drawn over the - * window background. Not interactive: clicks fall - * through to the task as wuss_EVENT_MOUSE. */ - wuss_ICON_TYPE_RADIO, /**< A radio button: a small ring at the left of the - * bounding box, filled when selected, with the - * label (text) to its right. Interactive: a click - * selects it and clears every other selected - * radio sharing its non-zero group, then the task - * is told via wuss_EVENT_ICON. */ - wuss_ICON_TYPE_OPTION, /**< An option button: a small box at the left of - * the bounding box, ticked when selected, with - * the label (text) to its right. Interactive: a - * click toggles its own selected state (group is - * ignored), then the task is told via - * wuss_EVENT_ICON. */ - wuss_ICON_TYPE_BITMAP, /**< A caller-owned bitmap (spec.bitmap) drawn at - * the top-left of the bounding box, alpha-blended - * against what is already there, clipped to the - * box; no scaling. The bitmap is borrowed, not - * copied, and must outlive the icon (unlike - * text). fg, bg, text and pattern are ignored. - * Not interactive unless - * wuss_ICON_FLAGS_INTERACTIVE is set, in which - * case clicks raise wuss_EVENT_ICON like a - * button. */ - wuss_ICON_TYPE_MENU_ENTRY, /**< A menu row: text left-justified across the - * bounding box, drawn in fg over the window - * background, or inverted (window manager's title - * colours) while the pointer is over it. An - * optional tick at the left edge when the icon is - * selected (see wuss_icon_set_selected), and an - * optional submenu arrow at the right edge with - * wuss_ICON_FLAGS_SUBMENU. - * wuss_ICON_FLAGS_SEPARATOR marks the entry as - * following a group boundary: the dashed rule - * itself is a separate wuss_ICON_TYPE_RULE icon - * laid out above the entry, not drawn by the - * entry. The entry keeps its label and stays - * fully interactive. Interactive: a click raises - * wuss_EVENT_ICON like a button; a disabled - * entry never highlights and its clicks fall - * through. */ - wuss_ICON_TYPE_RULE /**< A horizontal dashed rule centred in the - * bounding box, drawn in fg over the window - * background. Purely decorative: never - * hit-tested, never highlights. text, bg and - * pattern are ignored. Used between menu rows to - * render the line a wuss_ICON_FLAGS_SEPARATOR - * entry sits below. */ + /** Static text drawn with the window manager's font. Not interactive: clicks + * fall through to the task as wuss_EVENT_MOUSE. */ + wuss_ICON_TYPE_LABEL = 0, + /** Bevelled rectangle with a centred text label and pressed-state visual + * feedback; clicks and hovers are delivered to the task as + * wuss_EVENT_ICON. */ + wuss_ICON_TYPE_BUTTON, + /** Bounding box filled with a repeating two-colour 8x8 tile (spec.pattern) in + * fg/bg, phased to document space so it scrolls rigidly with content. Not + * interactive: clicks fall through to the task as wuss_EVENT_MOUSE. text is + * ignored. */ + wuss_ICON_TYPE_PATTERN, + /** A grouping box: a one-pixel rectangle in fg around the bounding box, + * broken at the top-left for an optional caption (text) drawn over the + * window background. Not interactive: clicks fall through to the task as + * wuss_EVENT_MOUSE. */ + wuss_ICON_TYPE_FRAME, + /** A radio button: a small ring at the left of the bounding box, filled when + * selected, with the label (text) to its right. Interactive: a click selects + * it and clears every other selected radio sharing its non-zero group, then + * the task is told via wuss_EVENT_ICON. */ + wuss_ICON_TYPE_RADIO, + /** An option button: a small box at the left of the bounding box, ticked when + * selected, with the label (text) to its right. Interactive: a click toggles + * its own selected state (group is ignored), then the task is told via + * wuss_EVENT_ICON. */ + wuss_ICON_TYPE_OPTION, + /** A caller-owned bitmap (spec.bitmap) drawn at the top-left of the bounding + * box, alpha-blended against what is already there, clipped to the box; no + * scaling. The bitmap is borrowed, not copied, and must outlive the icon + * (unlike text). fg, bg, text and pattern are ignored. Not interactive + * unless wuss_ICON_FLAGS_INTERACTIVE is set, in which case clicks raise + * wuss_EVENT_ICON like a button. */ + wuss_ICON_TYPE_BITMAP, + /** A menu row: text left-justified across the bounding box, drawn in fg over + * the window background, or inverted (window manager's title colours) while + * the pointer is over it. An optional tick at the left edge when the icon is + * selected (see wuss_icon_set_selected), and an optional submenu arrow at + * the right edge with wuss_ICON_FLAGS_SUBMENU. wuss_ICON_FLAGS_SEPARATOR + * marks the entry as following a group boundary: the dashed rule itself is a + * separate wuss_ICON_TYPE_RULE icon laid out above the entry, not drawn by + * the entry. The entry keeps its label and stays fully interactive. + * Interactive: a click raises wuss_EVENT_ICON like a button; a disabled + * entry never highlights and its clicks fall through. */ + wuss_ICON_TYPE_MENU_ENTRY, + /** A horizontal dashed rule centred in the bounding box, drawn in fg over the + * window background. Purely decorative: never hit-tested, never highlights. + * text, bg and pattern are ignored. Used between menu rows to render the + * line a wuss_ICON_FLAGS_SEPARATOR entry sits below. */ + wuss_ICON_TYPE_RULE } wuss_icon_type_t; @@ -121,84 +108,73 @@ wuss_icon_type_t; typedef enum wuss_icon_flags { wuss_ICON_FLAGS_NONE = 0, - wuss_ICON_FLAGS_HIDDEN = 1 << 0, /**< Not drawn, not hit-tested. */ - wuss_ICON_FLAGS_DISABLED = 1 << 1, /**< Drawn greyed; clicks fall through - * to the task as wuss_EVENT_MOUSE - * rather than raising - * wuss_EVENT_ICON. */ - wuss_ICON_FLAGS_JUSTIFY_RIGHT = 1 << 2, /**< wuss_ICON_TYPE_LABEL: right-align - * the text in the bounding box - * instead of the default left. */ - wuss_ICON_FLAGS_JUSTIFY_CENTRE = 1 << 3, /**< wuss_ICON_TYPE_LABEL: centre - * the text in the bounding box. - * Takes precedence over - * wuss_ICON_FLAGS_JUSTIFY_RIGHT. */ - wuss_ICON_FLAGS_DEFAULT = 1 << 4, /**< wuss_ICON_TYPE_BUTTON: draw as a - * default action button, in the - * window manager's accent colours - * (see wuss_config_t::accent) - * instead of the ordinary bevel. - * Ignored by other icon types. */ - wuss_ICON_FLAGS_INTERACTIVE = 1 << 5, /**< wuss_ICON_TYPE_BITMAP: hit-test - * the icon and raise wuss_EVENT_ICON - * on a click, like a button. - * Without it a bitmap icon is pure - * decoration and clicks fall through - * as wuss_EVENT_MOUSE. Ignored by - * other icon types (interactive or - * not by their nature). */ - wuss_ICON_FLAGS_SUBMENU = 1 << 6, /**< wuss_ICON_TYPE_MENU_ENTRY: draw a - * right-pointing arrow at the right - * edge, marking an entry that opens - * a submenu. Ignored by other - * types. */ - wuss_ICON_FLAGS_SEPARATOR = 1 << 7 /**< wuss_ICON_TYPE_MENU_ENTRY: the - * entry follows a group boundary. It - * stays a normal interactive row - * with its own label; the dashed - * rule above it is laid out and - * drawn as a separate - * wuss_ICON_TYPE_RULE icon. Ignored - * by other types. */ + /** Not drawn, not hit-tested. */ + wuss_ICON_FLAGS_HIDDEN = 1 << 0, + /** Drawn greyed; clicks fall through to the task as wuss_EVENT_MOUSE rather + * than raising wuss_EVENT_ICON. */ + wuss_ICON_FLAGS_DISABLED = 1 << 1, + /** wuss_ICON_TYPE_LABEL: right-align the text in the bounding box instead of + * the default left. */ + wuss_ICON_FLAGS_JUSTIFY_RIGHT = 1 << 2, + /** wuss_ICON_TYPE_LABEL: centre the text in the bounding box. Takes + * precedence over wuss_ICON_FLAGS_JUSTIFY_RIGHT. */ + wuss_ICON_FLAGS_JUSTIFY_CENTRE = 1 << 3, + /** wuss_ICON_TYPE_BUTTON: draw as a default action button, in the window + * manager's accent colours (see wuss_config_t::accent) instead of the + * ordinary bevel. Ignored by other icon types. */ + wuss_ICON_FLAGS_DEFAULT = 1 << 4, + /** wuss_ICON_TYPE_BITMAP: hit-test the icon and raise wuss_EVENT_ICON on a + * click, like a button. Without it a bitmap icon is pure decoration and + * clicks fall through as wuss_EVENT_MOUSE. Ignored by other icon types + * (interactive or not by their nature). */ + wuss_ICON_FLAGS_INTERACTIVE = 1 << 5, + /** wuss_ICON_TYPE_MENU_ENTRY: draw a right-pointing arrow at the right edge, + * marking an entry that opens a submenu. Ignored by other types. */ + wuss_ICON_FLAGS_SUBMENU = 1 << 6, + /** wuss_ICON_TYPE_MENU_ENTRY: the entry follows a group boundary. It stays a + * normal interactive row with its own label; the dashed rule above it is + * laid out and drawn as a separate wuss_ICON_TYPE_RULE icon. Ignored by + * other types. */ + wuss_ICON_FLAGS_SEPARATOR = 1 << 7 } wuss_icon_flags_t; /** - * Description of an icon at creation. Copied by value into the icon; the caller - * keeps ownership of \c text, which is copied. + * Description of an icon at creation. Copied by value into the icon; the + * caller keeps ownership of \c text, which is copied. * - * A RISC OS-style validation string is deliberately omitted for now; a later \c - * validation field would stay source-compatible for callers that + * A RISC OS-style validation string is deliberately omitted for now; a later + * \c validation field would stay source-compatible for callers that * zero-initialise the spec. */ typedef struct wuss_icon_spec { - box_t bbox; /**< Bounding box, virtual document space, - * inclusive-exclusive. */ - wuss_icon_type_t type; /**< Icon type. */ - const char *text; /**< NUL-terminated label; copied. NULL means "". */ - wuss_colour_t fg; /**< Text colour, as an index into the system - * palette. */ - wuss_colour_t bg; /**< Fill/bevel base colour, as an index into the - * system palette. A label, frame, radio or option - * icon may pass wuss_NO_BACKGROUND for no fill - * behind its text/glyph; a button or pattern icon - * must pass a real index. */ - screen_pattern_t pattern; /**< Tile for wuss_ICON_TYPE_PATTERN; ignored by - * other types. Zero (screen_PATTERN_SOLID) is a - * safe default for zero-initialised specs. */ - const bitmap_t *bitmap; /**< wuss_ICON_TYPE_BITMAP: the image to draw. - * Borrowed, not copied; must outlive the icon. - * Ignored by other types; NULL (the default) is - * only valid when type is not - * wuss_ICON_TYPE_BITMAP. */ - int group; /**< wuss_ICON_TYPE_RADIO: exclusive-selection group. - * Selecting a radio clears every other selected - * radio on the same window with the same group. - * Zero (the default) means "no group": such a - * radio still toggles but never clears another. - * Ignored by all other icon types. */ - wuss_icon_flags_t flags; /**< Appearance/behaviour flags. */ + /** Bounding box, virtual document space, inclusive-exclusive. */ + box_t bbox; + /** Icon type. */ + wuss_icon_type_t type; + /** NUL-terminated label; copied. NULL means "". */ + const char *text; + /** Text colour, as an index into the system palette. */ + wuss_colour_t fg; + /** Fill/bevel base colour, as an index into the system palette. A label, + * frame, radio or option icon may pass wuss_NO_BACKGROUND for no fill behind + * its text/glyph; a button or pattern icon must pass a real index. */ + wuss_colour_t bg; + /** Tile for wuss_ICON_TYPE_PATTERN; ignored by other types. Zero + * (screen_PATTERN_SOLID) is a safe default for zero-initialised specs. */ + screen_pattern_t pattern; + /** wuss_ICON_TYPE_BITMAP: the image to draw. Borrowed, not copied; must + * outlive the icon. Ignored by other types; NULL (the default) is only valid + * when type is not wuss_ICON_TYPE_BITMAP. */ + const bitmap_t *bitmap; + /** wuss_ICON_TYPE_RADIO: exclusive-selection group. Selecting a radio clears + * every other selected radio on the same window with the same group. Zero + * (the default) means "no group": such a radio still toggles but never + * clears another. Ignored by all other icon types. */ + int group; + /** Appearance/behaviour flags. */ + wuss_icon_flags_t flags; } wuss_icon_spec_t; @@ -206,36 +182,38 @@ wuss_icon_spec_t; /** * Create an icon on a window. The icon is owned by the window and freed when - * the window is closed (or the window manager destroyed). Its bounding box is - * invalidated so the next redraw paints it. + * the window is closed (or the window manager destroyed). Its bounding box + * is invalidated so the next redraw paints it. * * \param[in] window Window to attach the icon to. * \param[in] spec Icon description; copied. - * \param[out] icon Filled in with the new icon handle, or NULL if the caller - * does not need it. + * \param[out] icon Filled in with the new icon handle, or NULL if the + * caller does not need it. * \return \ref result_OK on success, \ref result_OOM on allocation failure, * \ref result_WUSS_BAD_COLOUR if fg or bg is out of range for the - * palette, or \ref result_WUSS_BAD_ICON if type is unknown, a button or - * pattern spec has no fill colour, or a bitmap spec has no bitmap. + * palette, or \ref result_WUSS_BAD_ICON if type is unknown, a button + * or pattern spec has no fill colour, or a bitmap spec has no + * bitmap. */ result_t wuss_icon_create(wuss_window_t *window, const wuss_icon_spec_t *spec, wuss_icon_t **icon); /** - * Create several icons on a window in one call, as if by \ref wuss_icon_create - * for each. Either all \c nspecs icons are created, or none are: on the first - * failure any icons already created by this call are destroyed and no handles - * are written. + * Create several icons on a window in one call, as if by \ref + * wuss_icon_create for each. Either all \c nspecs icons are created, or none + * are: on the first failure any icons already created by this call are + * destroyed and no handles are written. * * \param[in] window Window to attach the icons to. * \param[in] specs Array of \c nspecs icon descriptions; each copied. * \param[in] nspecs Number of entries in \c specs. Zero is a no-op. - * \param[out] icons Array of \c nspecs handles, filled in on success, or NULL - * if the caller does not need them. Untouched on failure. - * \return \ref result_OK on success, or the first failing \ref wuss_icon_create - * code (\ref result_OOM, \ref result_WUSS_BAD_COLOUR, \ref - * result_WUSS_BAD_ICON). + * \param[out] icons Array of \c nspecs handles, filled in on success, or + * NULL if the caller does not need them. Untouched on + * failure. + * \return \ref result_OK on success, or the first failing \ref + * wuss_icon_create code (\ref result_OOM, \ref + * result_WUSS_BAD_COLOUR, \ref result_WUSS_BAD_ICON). */ result_t wuss_icon_create_array(wuss_window_t *window, const wuss_icon_spec_t *specs, @@ -243,27 +221,27 @@ result_t wuss_icon_create_array(wuss_window_t *window, wuss_icon_t **icons); /** - * Destroy an icon, unlinking it from its window and invalidating its bounding - * box so the next redraw clears it. Safe to pass NULL. + * Destroy an icon, unlinking it from its window and invalidating its + * bounding box so the next redraw clears it. Safe to pass NULL. * * \param[in] icon Icon to destroy, or NULL. */ void wuss_icon_delete(wuss_icon_t *icon); /** - * Replace an icon's label text. The new text is copied. Invalidates the icon's - * bounding box. + * Replace an icon's label text. The new text is copied. Invalidates the + * icon's bounding box. * * \param[in] icon Icon to change. * \param[in] text New NUL-terminated label; copied. NULL means "". - * \return \ref result_OK on success, \ref result_OOM on allocation failure (the - * icon keeps its old text). + * \return \ref result_OK on success, \ref result_OOM on allocation failure + * (the icon keeps its old text). */ result_t wuss_icon_set_text(wuss_icon_t *icon, const char *text); /** - * Show or hide an icon, toggling wuss_ICON_FLAGS_HIDDEN. Invalidates the icon's - * bounding box. + * Show or hide an icon, toggling wuss_ICON_FLAGS_HIDDEN. Invalidates the + * icon's bounding box. * * \param[in] icon Icon to change. * \param[in] hidden Non-zero to hide the icon, zero to show it. @@ -290,8 +268,8 @@ wuss_icon_type_t wuss_icon_get_type(const wuss_icon_t *icon); * Fetch an icon's current label text. * * \param[in] icon Icon to query. - * \return The label, never NULL (may be ""). Owned by the icon; valid until the - * next wuss_icon_set_text or wuss_icon_delete on it. + * \return The label, never NULL (may be ""). Owned by the icon; valid until + * the next wuss_icon_set_text or wuss_icon_delete on it. */ const char *wuss_icon_get_text(const wuss_icon_t *icon); @@ -307,17 +285,18 @@ wuss_window_t *wuss_icon_get_window(const wuss_icon_t *icon); * Fetch a radio or option icon's selected (latched) state. * * \param[in] icon Icon to query. - * \return Non-zero if selected, zero otherwise. Always zero for icon types that - * have no latched state. + * \return Non-zero if selected, zero otherwise. Always zero for icon types + * that have no latched state. */ int wuss_icon_get_selected(const wuss_icon_t *icon); /** * Set a radio or option icon's selected state, invalidating it so the next - * redraw repaints it. For a radio with a non-zero group, selecting it (passing - * non-zero) also clears every other selected radio on the same window with that - * group. No task event is delivered -- this is the programmatic path, distinct - * from a user click. A no-op for icon types with no latched state. + * redraw repaints it. For a radio with a non-zero group, selecting it + * (passing non-zero) also clears every other selected radio on the same + * window with that group. No task event is delivered -- this is the + * programmatic path, distinct from a user click. A no-op for icon types with + * no latched state. * * \param[in] icon Icon to change. * \param[in] selected Non-zero to select, zero to deselect. diff --git a/include/wuss/menu.h b/include/wuss/menu.h index bbb36d9..ead3d27 100644 --- a/include/wuss/menu.h +++ b/include/wuss/menu.h @@ -3,14 +3,15 @@ /** * \file menu.h * - * A thin helper for RISC OS-style pop-up menus: a menu is a borderless window - * populated with wuss_ICON_TYPE_MENU_ENTRY icons, but wuss owns the plumbing -- - * layout, placement, submenu chaining on hover and whole-chain dismissal on a - * click outside or a leaf selection. + * A thin helper for RISC OS-style pop-up menus: a menu is a borderless + * window populated with wuss_ICON_TYPE_MENU_ENTRY icons, but wuss owns the + * plumbing -- layout, placement, submenu chaining on hover and whole-chain + * dismissal on a click outside or a leaf selection. * - * Menus are described by caller-owned, immutable wuss_menu_t / wuss_menu_item_t - * structures (which may be static). The helper never mutates them; a task that - * wants a tick to change just edits its own array and reopens the menu. + * Menus are described by caller-owned, immutable wuss_menu_t / + * wuss_menu_item_t structures (which may be static). The helper never + * mutates them; a task that wants a tick to change just edits its own array + * and reopens the menu. * * Built only when WUSS_MENUS is defined (which implies WUSS_ICONS). */ @@ -73,8 +74,8 @@ typedef struct wuss__menu *wuss_menu_handle_t; * * \param[in] menu The menu the item belongs to (a submenu, if nested). * \param[in] index Index of the item within \c menu->items. - * \param[in] button wuss_button_t flags for the release; test with '&'. ADJUST - * keeps the chain open, SELECT closes it. + * \param[in] button wuss_button_t flags for the release; test with '&'. + * ADJUST keeps the chain open, SELECT closes it. * \param[in] ctx As passed to wuss_menu_open. */ typedef void (wuss_menu_select_fn_t)(const wuss_menu_t *menu, @@ -85,10 +86,10 @@ typedef void (wuss_menu_select_fn_t)(const wuss_menu_t *menu, /* ----------------------------------------------------------------------- */ /** - * Open \p menu as a pop-up at \p at (screen space), nudged to stay on screen. - * Any menu chain already open is closed first. The chain lives until a leaf is - * SELECT-picked, a click lands outside every menu window, or wuss_menu_close is - * called. + * Open \p menu as a pop-up at \p at (screen space), nudged to stay on + * screen. Any menu chain already open is closed first. The chain lives until + * a leaf is SELECT-picked, a click lands outside every menu window, or + * wuss_menu_close is called. * * \param[in] wuss Window manager. * \param[in] menu Menu to show; borrowed, must outlive the open chain. @@ -116,28 +117,30 @@ int wuss_menu_is_open(wuss_menu_handle_t handle); /** * Build a wuss_menu_t tree from a compact descriptor string. The syntax is - * lifted from PrivateEye's menu_create_from_desc. A comma separates items. The - * very first token is the root menu's titlebar caption, not an item -- exactly - * as the first token inside a '{ }' is that submenu's title -- so the same - * descriptor strings port across unchanged. Submenus keep the Wimp behaviour of - * discarding their title token; only the root's is kept. A leading '|' on an - * item draws a dashed rule above it, marking a group boundary; the item itself - * stays an ordinary interactive row. A '{ ... }' group after an item is that - * item's submenu. A per-token prefix '!' ticks the item, '~' shades (disables) - * it, and '>' attaches a submenu pulled as a <tt>const wuss_menu_t *</tt> from - * the varargs rather than from a following '{ }' block. A "%s" in a token - * substitutes the next <tt>const char *</tt> vararg. The '>' and "%s" varargs - * are consumed in the order they are encountered scanning left to right. + * lifted from PrivateEye's menu_create_from_desc. A comma separates items. + * The very first token is the root menu's titlebar caption, not an item -- + * exactly as the first token inside a '{ }' is that submenu's title -- so + * the same descriptor strings port across unchanged. Submenus keep the Wimp + * behaviour of discarding their title token; only the root's is kept. A + * leading '|' on an item draws a dashed rule above it, marking a group + * boundary; the item itself stays an ordinary interactive row. A '{ ... }' + * group after an item is that item's submenu. A per-token prefix '!' ticks + * the item, '~' shades (disables) it, and '>' attaches a submenu pulled as a + * <tt>const wuss_menu_t *</tt> from the varargs rather than from a following + * '{ }' block. A "%s" in a token substitutes the next <tt>const char *</tt> + * vararg. The '>' and "%s" varargs are consumed in the order they are + * encountered scanning left to right. * - * Example: <tt>wuss_menu_create_from_desc(&m, "Display, Open, !Grid, ~Export, - * |Quit")</tt> -- "Display" is the caption; the menu has four items, with a - * dashed rule above "Quit". + * Example: <tt>wuss_menu_create_from_desc(&m, "Display, Open, !Grid, + * ~Export, |Quit")</tt> -- "Display" is the caption; the menu has four + * items, with a dashed rule above "Quit". * * The whole tree, including copied label text, is one heap allocation graph - * owned by the caller; free it with wuss_menu_destroy. wuss_menu_open treats a - * desc-built tree exactly like a static literal. + * owned by the caller; free it with wuss_menu_destroy. wuss_menu_open treats + * a desc-built tree exactly like a static literal. * - * \param[out] out Filled with the root menu on success, untouched on failure. + * \param[out] out Filled with the root menu on success, untouched on + * failure. * \param[in] desc Descriptor string; its first token is the root caption. * \return \ref result_OK, \ref result_OOM, or \ref result_BAD_ARG for a * malformed descriptor (unbalanced braces, empty token, too deep). @@ -145,8 +148,8 @@ int wuss_menu_is_open(wuss_menu_handle_t handle); result_t wuss_menu_create_from_desc(wuss_menu_t **out, const char *desc, ...); /** - * Free a tree built by wuss_menu_create_from_desc, including every submenu and - * copied label. Safe to pass NULL. Never call this on a static or + * Free a tree built by wuss_menu_create_from_desc, including every submenu + * and copied label. Safe to pass NULL. Never call this on a static or * caller-assembled wuss_menu_t. * * \param[in] menu Root menu returned by wuss_menu_create_from_desc, or NULL. diff --git a/include/wuss/task.h b/include/wuss/task.h index b2cf1d6..37e4159 100644 --- a/include/wuss/task.h +++ b/include/wuss/task.h @@ -3,8 +3,8 @@ /** * \file task.h * - * A Wuss task: the content delegate a window hands its drawing and input events - * to, and the events themselves. + * A Wuss task: the content delegate a window hands its drawing and input + * events to, and the events themselves. */ #ifndef WUSS_TASK_H @@ -25,7 +25,9 @@ extern "C" /* ----------------------------------------------------------------------- */ -/** Which kind of event a wuss_event_t carries; more will be added over time. */ +/** + * Which kind of event a wuss_event_t carries; more will be added over time. + */ typedef enum wuss_event_kind { wuss_EVENT_IDLE, /**< Wuss has finished its pending tasks. */ @@ -131,7 +133,9 @@ typedef result_t (wuss_event_fn_t)(wuss_window_t *window, const wuss_event_t *event, void *task_data); -/** A window's content delegate. Copied by value into the window at creation. */ +/** + * A window's content delegate. Copied by value into the window at creation. + */ typedef struct wuss_task { /** diff --git a/include/wuss/window.h b/include/wuss/window.h index 1d830d9..8319742 100644 --- a/include/wuss/window.h +++ b/include/wuss/window.h @@ -3,8 +3,8 @@ /** * \file window.h * - * A Wuss window: creation, destruction, positioning, sizing and task delegation - * of content drawing and mouse handling. + * A Wuss window: creation, destruction, positioning, sizing and task + * delegation of content drawing and mouse handling. */ #ifndef WUSS_WINDOW_H @@ -29,45 +29,49 @@ extern "C" /** * Create a window. * - * Furniture (titlebar/outline) is added outside \p content, not carved out of - * it: before clamping, the window's content area is exactly \p content, and its - * on-screen footprint (see wuss_window_get_visible_bounds) is \p content - * expanded outward by whatever furniture flags request. If that footprint would - * then fall off the top or left edge of the screen, the window (content - * included) is nudged right/down just enough to bring it flush with the edge, - * so the titlebar/close icon stay reachable; a window wider or taller than the - * screen keeps its top-left corner on-screen instead. The bottom/right edges - * are not clamped. + * Furniture (titlebar/outline) is added outside \p content, not carved out + * of it: before clamping, the window's content area is exactly \p content, + * and its on-screen footprint (see wuss_window_get_visible_bounds) is \p + * content expanded outward by whatever furniture flags request. If that + * footprint would then fall off the top or left edge of the screen, the + * window (content included) is nudged right/down just enough to bring it + * flush with the edge, so the titlebar/close icon stay reachable; a window + * wider or taller than the screen keeps its top-left corner on-screen + * instead. The bottom/right edges are not clamped. * * \param[in] wuss Window manager to create the window on. - * \param[in] content Requested content-area bounds, screen space. Copied in. - * \param[in] title Titlebar label, or NULL for none. Copied in, truncated if - * too long. Ignored if flags includes + * \param[in] content Requested content-area bounds, screen space. Copied + * in. + * \param[in] title Titlebar label, or NULL for none. Copied in, truncated + * if too long. Ignored if flags includes * wuss_WINDOW_NO_TITLEBAR. * \param[in] flags Appearance flags, e.g. wuss_WINDOW_NO_TITLEBAR / * wuss_WINDOW_NO_OUTLINE, OR'd together, or * wuss_WINDOW_NONE for the default furniture. - * \param[in] bg Content background, filled by Wuss before each redraw: a - * flat colour or an 8x8 fill pattern (see wuss_backdrop_t). - * Set its colour to wuss_NO_BACKGROUND for the task to draw - * its own background (avoids a redundant fill behind an - * opaque task). Any pattern is phased to the window's - * scroll origin so it stays locked to the content. - * Changeable later via wuss_window_set_background. + * \param[in] bg Content background, filled by Wuss before each redraw: + * a flat colour or an 8x8 fill pattern (see + * wuss_backdrop_t). Set its colour to wuss_NO_BACKGROUND + * for the task to draw its own background (avoids a + * redundant fill behind an opaque task). Any pattern is + * phased to the window's scroll origin so it stays + * locked to the content. Changeable later via + * wuss_window_set_background. * \param[in] task Content delegate. Copied in. May be NULL for a window * with no content handling. * \param[in] doc Virtual document extent, for the scrollbars' sausage * proportions; pass content's own width and height for a * window with nothing to scroll. Also the ceiling a - * resize-drag or toggle-size will grow the content area to. + * resize-drag or toggle-size will grow the content area + * to. * \param[in] min_doc Minimum content size a resize-drag or toggle-size will - * shrink to. Pass (0,0) for the built-in floor. Clamped to - * doc, and to the built-in floor, so it can never make a - * window unusably small or larger than its document. + * shrink to. Pass (0,0) for the built-in floor. Clamped + * to doc, and to the built-in floor, so it can never + * make a window unusably small or larger than its + * document. * \param[out] window Newly created window. Becomes the topmost window. * \return \ref result_OK on success, \ref result_WUSS_TOO_SMALL if content's - * width or height is not positive, \ref result_WUSS_BAD_COLOUR if any - * of bg's colours are out of range for the palette, or another + * width or height is not positive, \ref result_WUSS_BAD_COLOUR if + * any of bg's colours are out of range for the palette, or another * appropriate result code. */ result_t wuss_window_create(wuss_t *wuss, @@ -83,21 +87,21 @@ result_t wuss_window_create(wuss_t *wuss, /** * Create a window, letting Wuss choose its position. * - * As wuss_window_create, but instead of a content box you pass just the content - * size; Wuss places the window (furniture included) in the first free screen - * region, packed towards the top-left, tracking occupied area across calls so - * successive auto-placed windows tile rather than stack. When no region is - * large enough the window is cascaded from the previous placement, stepping by - * a titlebar height and wrapping at the screen edge. + * As wuss_window_create, but instead of a content box you pass just the + * content size; Wuss places the window (furniture included) in the first + * free screen region, packed towards the top-left, tracking occupied area + * across calls so successive auto-placed windows tile rather than stack. + * When no region is large enough the window is cascaded from the previous + * placement, stepping by a titlebar height and wrapping at the screen edge. * - * The chosen slot is returned to the pool when the window is closed, or when it - * is first moved or resized via wuss_window_move / wuss_window_resize (after - * which Wuss no longer tracks its position). A window dragged by its titlebar - * counts as moved. + * The chosen slot is returned to the pool when the window is closed, or when + * it is first moved or resized via wuss_window_move / wuss_window_resize + * (after which Wuss no longer tracks its position). A window dragged by its + * titlebar counts as moved. * * \param[in] wuss Window manager to create the window on. - * \param[in] size Requested content-area size. Width and height must both - * be positive. + * \param[in] size Requested content-area size. Width and height must + * both be positive. * \param[in] title Titlebar label, as wuss_window_create. * \param[in] flags Appearance flags, as wuss_window_create. * \param[in] bg Content background, as wuss_window_create. @@ -105,9 +109,10 @@ result_t wuss_window_create(wuss_t *wuss, * \param[in] doc Virtual document extent, as wuss_window_create. * \param[in] min_doc Minimum content size, as wuss_window_create. * \param[out] window Newly created window. Becomes the topmost window. - * \return \ref result_OK on success, \ref result_WUSS_TOO_SMALL if size's width - * or height is not positive, \ref result_OOM if the layout tracker - * could not be created, or another result code from wuss_window_create. + * \return \ref result_OK on success, \ref result_WUSS_TOO_SMALL if size's + * width or height is not positive, \ref result_OOM if the layout + * tracker could not be created, or another result code from + * wuss_window_create. */ result_t wuss_window_create_placed(wuss_t *wuss, size2d_t size, @@ -139,8 +144,8 @@ void wuss_window_move(wuss_window_t *window, point_t p); * * \param[in] window Window to resize. * \param[in] size New content size. - * \return \ref result_OK on success, \ref result_WUSS_TOO_SMALL if size's width - * or height is not positive. + * \return \ref result_OK on success, \ref result_WUSS_TOO_SMALL if size's + * width or height is not positive. */ result_t wuss_window_resize(wuss_window_t *window, size2d_t size); @@ -163,8 +168,8 @@ void wuss_window_get_visible_bounds(const wuss_window_t *window, box_t *visible); /** - * Fetch a window's current content-area bounds, screen space (as requested at - * creation, or adjusted by a subsequent move/resize; excludes any + * Fetch a window's current content-area bounds, screen space (as requested + * at creation, or adjusted by a subsequent move/resize; excludes any * titlebar/outline furniture). Useful for computing invalidation regions * outside of a redraw callback. * @@ -175,14 +180,15 @@ void wuss_window_get_content_bounds(const wuss_window_t *window, box_t *content); /** - * Mark a region of a window's content as dirty, for the next wuss_redraw_dirty - * call. Content changes are opaque to Wuss, so tasks must call this themselves - * (e.g. the union of an animated element's old and new positions). + * Mark a region of a window's content as dirty, for the next + * wuss_redraw_dirty call. Content changes are opaque to Wuss, so tasks must + * call this themselves (e.g. the union of an animated element's old and new + * positions). * * \param[in] window Window whose content changed. - * \param[in] local_box Region, in window-local content coordinates (as passed - * to the task's mouse callback), or NULL to mark the whole - * content area dirty. + * \param[in] local_box Region, in window-local content coordinates (as + * passed to the task's mouse callback), or NULL to mark + * the whole content area dirty. */ void wuss_window_invalidate(wuss_window_t *window, const box_t *local_box); @@ -192,11 +198,11 @@ void wuss_window_invalidate(wuss_window_t *window, const box_t *local_box); wuss_window_invalidate((window), NULL) /** - * Set a window's scroll offset: the point in virtual content space that appears - * at the content area's top-left. Larger offsets bring later content into view. - * Invalidates the content area so the next redraw picks up the new offset; the - * task's redraw callback is responsible for using the offset (via - * wuss_window_get_scroll) to draw the right portion of its content. + * Set a window's scroll offset: the point in virtual content space that + * appears at the content area's top-left. Larger offsets bring later content + * into view. Invalidates the content area so the next redraw picks up the + * new offset; the task's redraw callback is responsible for using the offset + * (via wuss_window_get_scroll) to draw the right portion of its content. * * \param[in] window Window to scroll. * \param[in] p New scroll offset. @@ -218,12 +224,13 @@ void wuss_window_get_scroll(const wuss_window_t *window, point_t *p); * \param[in] window Window to change. * \param[in] bg New content background: a flat colour or an 8x8 fill * pattern (see wuss_backdrop_t). Set its colour to - * wuss_NO_BACKGROUND to hand background painting back to the - * task. - * \return \ref result_OK on success, \ref result_WUSS_BAD_COLOUR if any of bg's - * colours are out of range for the palette. + * wuss_NO_BACKGROUND to hand background painting back to + * the task. + * \return \ref result_OK on success, \ref result_WUSS_BAD_COLOUR if any of + * bg's colours are out of range for the palette. */ -result_t wuss_window_set_background(wuss_window_t *window, wuss_backdrop_t bg); +result_t wuss_window_set_background(wuss_window_t *window, + wuss_backdrop_t bg); #ifdef __cplusplus } diff --git a/include/wuss/wuss.h b/include/wuss/wuss.h index fa43c69..358b3f8 100644 --- a/include/wuss/wuss.h +++ b/include/wuss/wuss.h @@ -32,7 +32,8 @@ extern "C" /** A palette index was out of range. */ #define result_WUSS_BAD_COLOUR (result_BASE_WUSS + 1) /** - * An icon spec was malformed (unknown type, or BUTTON without a fill colour). + * An icon spec was malformed (unknown type, or BUTTON without a fill + * colour). */ #define result_WUSS_BAD_ICON (result_BASE_WUSS + 2) @@ -49,11 +50,12 @@ typedef struct wuss_window wuss_window_t; typedef struct wuss_icon wuss_icon_t; /** - * Allocator hooks used by a wuss_t for every heap block it owns (the instance - * itself, windows, icons, menu nodes). The three members must behave like the C - * library malloc / realloc / free -- same argument and return conventions, - * realloc(NULL, n) == malloc(n), free(NULL) a no-op. Passed to wuss_create and - * copied in; NULL there selects wuss_alloc (plain stdlib). + * Allocator hooks used by a wuss_t for every heap block it owns (the + * instance itself, windows, icons, menu nodes). The three members must + * behave like the C library malloc / realloc / free -- same argument and + * return conventions, realloc(NULL, n) == malloc(n), free(NULL) a no-op. + * Passed to wuss_create and copied in; NULL there selects wuss_alloc (plain + * stdlib). */ typedef struct wuss_alloc { @@ -72,8 +74,8 @@ extern const wuss_alloc_t wuss_alloc; * * These are flags, OR'd together, so that chords (e.g. Select and Adjust * pressed together) can be reported. The bit values match the RISC OS button - * order. Test them with '&' rather than comparing for equality, or a chord will - * match no button at all. + * order. Test them with '&' rather than comparing for equality, or a chord + * will match no button at all. */ typedef enum wuss_button { @@ -99,7 +101,8 @@ wuss_mouse_action_t; typedef int wuss_colour_t; /** - * Sentinel for wuss_window_create's bg meaning "no automatic background fill". + * Sentinel for wuss_window_create's bg meaning "no automatic background + * fill". */ #define wuss_NO_BACKGROUND ((wuss_colour_t) -1) @@ -181,8 +184,8 @@ typedef enum wuss_window_flags /** * A resize (drag or toggle-size) always fully redraws the window's content * instead of blitting the preserved region -- for a task whose rendering - * depends on the window's size in ways redraw can't patch incrementally (e.g. - * a palette that lays itself out across the whole window). + * depends on the window's size in ways redraw can't patch incrementally + * (e.g. a palette that lays itself out across the whole window). */ wuss_WINDOW_NO_RESIZE_BLIT = 1 << 8 } @@ -198,25 +201,25 @@ typedef enum wuss_zorder wuss_zorder_t; /** - * Desktop background specification: a flat colour, or an 8x8 fill pattern. Used - * by wuss_config_t. Always honoured regardless of the WUSS_FURNITURE / + * Desktop background specification: a flat colour, or an 8x8 fill pattern. + * Used by wuss_config_t. Always honoured regardless of the WUSS_FURNITURE / * WUSS_ICONS options. */ typedef struct wuss_backdrop { /** - * Fill colour, or wuss_NO_BACKGROUND to leave the background untouched (the - * caller must then repaint it itself before wuss_redraw / wuss_redraw_dirty). - * When pattern is not screen_PATTERN_SOLID this is the pattern's foreground - * (set-bit) colour. + * Fill colour, or wuss_NO_BACKGROUND to leave the background untouched + * (the caller must then repaint it itself before wuss_redraw / + * wuss_redraw_dirty). When pattern is not screen_PATTERN_SOLID this is the + * pattern's foreground (set-bit) colour. */ wuss_colour_t colour; /** - * Fill pattern. screen_PATTERN_SOLID (the default) fills flat in colour; any - * other value tiles that pattern in colour over pattern_bg, phased to a fixed - * screen origin so it stays put across dirty-region redraws. Ignored when - * colour is wuss_NO_BACKGROUND. + * Fill pattern. screen_PATTERN_SOLID (the default) fills flat in colour; + * any other value tiles that pattern in colour over pattern_bg, phased to + * a fixed screen origin so it stays put across dirty-region redraws. + * Ignored when colour is wuss_NO_BACKGROUND. */ screen_pattern_t pattern; @@ -238,16 +241,16 @@ wuss_backdrop_t; /** * Optional creation-time configuration. * - * \note titlebar_height and palette are ignored when the library is built with - * WUSS_FURNITURE off; bevel and accent are ignored when built with both - * WUSS_FURNITURE and WUSS_ICONS off. backdrop is always honoured. See the - * backdrop sub-struct for its own notes. + * \note titlebar_height and palette are ignored when the library is built + * with WUSS_FURNITURE off; bevel and accent are ignored when built + * with both WUSS_FURNITURE and WUSS_ICONS off. backdrop is always + * honoured. See the backdrop sub-struct for its own notes. */ typedef struct wuss_config { /** - * Titlebar height in pixels, or 0 to derive from font metrics (or a built-in - * fallback if no font). Ignored when WUSS_FURNITURE is off. + * Titlebar height in pixels, or 0 to derive from font metrics (or a + * built-in fallback if no font). Ignored when WUSS_FURNITURE is off. */ int titlebar_height; @@ -255,10 +258,11 @@ typedef struct wuss_config wuss_palette_t palette; /** - * Bevelled work-area button edge shades, as indices into the system palette: - * light on the top/left edges, dark on the bottom/right (swapped when the - * button is pressed). Both default to the titlebar fill colour when config is - * NULL. Ignored when both WUSS_FURNITURE and WUSS_ICONS are off. + * Bevelled work-area button edge shades, as indices into the system + * palette: light on the top/left edges, dark on the bottom/right (swapped + * when the button is pressed). Both default to the titlebar fill colour + * when config is NULL. Ignored when both WUSS_FURNITURE and WUSS_ICONS are + * off. */ struct { @@ -271,8 +275,8 @@ typedef struct wuss_config * Fill and text colours for a default action button -- a work-area button * icon created with wuss_ICON_FLAGS_DEFAULT, drawn to stand out from the * ordinary bevelled buttons around it (RISC OS's "default action button"). - * Both default to the titlebar colours (bg / fg) when config is NULL. Ignored - * when both WUSS_FURNITURE and WUSS_ICONS are off. + * Both default to the titlebar colours (bg / fg) when config is NULL. + * Ignored when both WUSS_FURNITURE and WUSS_ICONS are off. */ struct { @@ -289,8 +293,8 @@ wuss_config_t; /** * Create a window manager. * - * \param[in] scr Screen to draw windows onto. Not owned; must outlive the - * wuss_t. + * \param[in] scr Screen to draw windows onto. Not owned; must outlive + * the wuss_t. * \param[in] font Font used to draw titlebar labels, or NULL to draw * titlebars unlabelled. Not owned. * \param[in] palette System palette, copied in, or NULL to use a built-in @@ -298,13 +302,13 @@ wuss_config_t; * \param[in] npalette Number of entries in palette. Ignored if palette is * NULL. * \param[in] config Creation-time configuration, or NULL for defaults. - * \param[in] alloc Allocator hooks, copied in, or NULL for \ref wuss_alloc - * (plain stdlib). Must outlive nothing -- only the three - * function pointers are kept. + * \param[in] alloc Allocator hooks, copied in, or NULL for \ref + * wuss_alloc (plain stdlib). Must outlive nothing -- + * only the three function pointers are kept. * \param[out] wuss Newly created window manager. * \return \ref result_OK on success, \ref result_WUSS_BAD_COLOUR if any of - * config's palette entries are out of range for the palette, or another - * appropriate result code. + * config's palette entries are out of range for the palette, or + * another appropriate result code. */ result_t wuss_create(screen_t *scr, bmfont_t *font, @@ -322,8 +326,8 @@ result_t wuss_create(screen_t *scr, void wuss_destroy(wuss_t *doomed); /** - * Fetch the system font (see wuss_create), for tasks to draw their own content - * in the same face as window titlebars. + * Fetch the system font (see wuss_create), for tasks to draw their own + * content in the same face as window titlebars. * * \param[in] wuss Window manager. * \return System font, or NULL if none was given to wuss_create. @@ -331,10 +335,10 @@ void wuss_destroy(wuss_t *doomed); bmfont_t *wuss_get_font(const wuss_t *wuss); /** - * The last pointer position seen by wuss_mouse_click or wuss_mouse_move, screen - * space. (0,0) until the first mouse event. Handy for opening a pop-up menu - * under the pointer from a task's icon handler, which gets no coordinate of its - * own. + * The last pointer position seen by wuss_mouse_click or wuss_mouse_move, + * screen space. (0,0) until the first mouse event. Handy for opening a + * pop-up menu under the pointer from a task's icon handler, which gets no + * coordinate of its own. * * \param[in] wuss Window manager. * \return Last pointer position, screen space. @@ -353,10 +357,11 @@ point_t wuss_get_pointer(const wuss_t *wuss); result_t wuss_redraw(wuss_t *wuss); /** - * Mark a screen-space region dirty. Window creation, destruction, move, resize - * and bring-to-front invalidate their own affected regions automatically; tasks - * must call this themselves when their content changes (e.g. an animation), - * passing the union of the old and new screen-space areas that need repainting. + * Mark a screen-space region dirty. Window creation, destruction, move, + * resize and bring-to-front invalidate their own affected regions + * automatically; tasks must call this themselves when their content changes + * (e.g. an animation), passing the union of the old and new screen-space + * areas that need repainting. * * \param[in] wuss Window manager. * \param[in] box Screen-space region to mark dirty. @@ -367,8 +372,8 @@ result_t wuss_invalidate(wuss_t *wuss, const box_t *box); /** * Redraw only the region accumulated by wuss_invalidate calls (and any * automatic invalidation from window management) since the last redraw, - * back-to-front, then clear the dirty region. Does nothing if nothing is dirty. - * Each dirty region has the configured backdrop colour (see + * back-to-front, then clear the dirty region. Does nothing if nothing is + * dirty. Each dirty region has the configured backdrop colour (see * wuss_config_t::backdrop) painted into it first, if any. * * \param[in] wuss Window manager. @@ -380,9 +385,9 @@ result_t wuss_redraw_dirty(wuss_t *wuss); /** * Fetch the number of dirty regions currently accumulated (see * wuss_invalidate). Regions are self-coalescing: an invalidation already - * covered by an existing region is discarded, and one sharing a complete edge - * with an existing region extends it in place, so this stays small under most - * usage. + * covered by an existing region is discarded, and one sharing a complete + * edge with an existing region extends it in place, so this stays small + * under most usage. * * \param[in] wuss Window manager. * \return Number of dirty regions, 0 if nothing is dirty. @@ -390,28 +395,28 @@ result_t wuss_redraw_dirty(wuss_t *wuss); int wuss_get_dirty_count(const wuss_t *wuss); /** - * Fetch one of the current accumulated dirty regions (see wuss_invalidate). If - * no backdrop colour was configured (see wuss_config_t::backdrop), wuss only - * repaints windows, not background between/behind them, so a caller whose - * invalidations can expose background (e.g. after a window move) should clear - * these regions itself before calling wuss_redraw_dirty. + * Fetch one of the current accumulated dirty regions (see wuss_invalidate). + * If no backdrop colour was configured (see wuss_config_t::backdrop), wuss + * only repaints windows, not background between/behind them, so a caller + * whose invalidations can expose background (e.g. after a window move) + * should clear these regions itself before calling wuss_redraw_dirty. * * \param[in] wuss Window manager. - * \param[in] index Index of the region to fetch, 0 to wuss_get_dirty_count() - - * 1. + * \param[in] index Index of the region to fetch, 0 to + * wuss_get_dirty_count() - 1. * \param[out] out Filled in with the dirty region. */ void wuss_get_dirty(const wuss_t *wuss, int index, box_t *out); /** * Deliver a mouse-down or mouse-up event (action must be wuss_MOUSE_DOWN or - * wuss_MOUSE_UP). Hit-tests the topmost window at (x,y). On a down, a titlebar - * click brings the window to front if button is Select (Adjust and Menu leave - * the z-order unchanged) and starts a drag; on an up, an in-progress drag is - * ended instead of hit-testing (an Adjust click with no move in between sends - * the window to the back rather than dragging it). A click on the window's - * content never changes the z-order and is delivered to the task in - * window-local content coordinates. + * wuss_MOUSE_UP). Hit-tests the topmost window at (x,y). On a down, a + * titlebar click brings the window to front if button is Select (Adjust and + * Menu leave the z-order unchanged) and starts a drag; on an up, an + * in-progress drag is ended instead of hit-testing (an Adjust click with no + * move in between sends the window to the back rather than dragging it). A + * click on the window's content never changes the z-order and is delivered + * to the task in window-local content coordinates. * * \param[in] wuss Window manager. * \param[in] p Screen coordinate. @@ -429,10 +434,10 @@ result_t wuss_mouse_click(wuss_t *wuss, wuss_window_t **hit); /** - * Deliver a mouse-move event. Updates the dragged window's position if a drag - * is active (invalidating the affected region; call wuss_redraw_dirty to - * actually repaint it), otherwise hit-tests and delivers to the window's task - * as per wuss_mouse_click. + * Deliver a mouse-move event. Updates the dragged window's position if a + * drag is active (invalidating the affected region; call wuss_redraw_dirty + * to actually repaint it), otherwise hit-tests and delivers to the window's + * task as per wuss_mouse_click. * * \param[in] wuss Window manager. * \param[in] p Screen coordinate. @@ -445,15 +450,15 @@ result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit); /** * Deliver a scroll event. Hit-tests the topmost window at p as per - * wuss_mouse_click, and delivers to the window's task in window-local content - * coordinates; dropped if the hit window has no scroll callback, or the pointer - * is over its titlebar. + * wuss_mouse_click, and delivers to the window's task in window-local + * content coordinates; dropped if the hit window has no scroll callback, or + * the pointer is over its titlebar. * * \param[in] wuss Window manager. * \param[in] p Screen coordinate. * \param[in] delta Scroll amount; sign and units are caller-defined. - * \param[out] hit Window under the pointer, or NULL if none. May be NULL if - * not needed. + * \param[out] hit Window under the pointer, or NULL if none. May be NULL + * if not needed. * \return \ref result_OK, or a result code returned by the task's scroll * callback. */ @@ -464,13 +469,13 @@ result_t wuss_scroll(wuss_t *wuss, /** * Broadcast a wuss_EVENT_IDLE event to every window's task, in z-order. - * Intended to be called once per main-loop iteration, after other pending input - * has been handled, so tasks can drive their own animation/timers without the - * caller stepping each one individually. + * Intended to be called once per main-loop iteration, after other pending + * input has been handled, so tasks can drive their own animation/timers + * without the caller stepping each one individually. * * \param[in] wuss Window manager whose windows' tasks should go idle. - * \return \ref result_OK on success, else the first non-OK result returned by a - * task's handle callback. + * \return \ref result_OK on success, else the first non-OK result returned + * by a task's handle callback. */ result_t wuss_idle(wuss_t *wuss); diff --git a/libraries/datastruct/ntree/insert-before.c b/libraries/datastruct/ntree/insert-before.c index 5afd562..0a2ccc4 100644 --- a/libraries/datastruct/ntree/insert-before.c +++ b/libraries/datastruct/ntree/insert-before.c @@ -7,7 +7,9 @@ #include "impl.h" -result_t ntree_insert_before(ntree_t *parent, ntree_t *sibling, ntree_t *node) +result_t ntree_insert_before(ntree_t *parent, + ntree_t *sibling, + ntree_t *node) { assert(node->parent == NULL); diff --git a/libraries/framebuf/bitmap/bitmap.c b/libraries/framebuf/bitmap/bitmap.c index bef5f88..c8004b0 100644 --- a/libraries/framebuf/bitmap/bitmap.c +++ b/libraries/framebuf/bitmap/bitmap.c @@ -167,7 +167,9 @@ static result_t bmconv_p4_to_bgrx8888(const bitmap_t *src, bitmap_t **pdst) return rc; } -result_t bitmap_convert(const bitmap_t *src, pixelfmt_t newfmt, bitmap_t **dst) +result_t bitmap_convert(const bitmap_t *src, + pixelfmt_t newfmt, + bitmap_t **dst) { *dst = NULL; diff --git a/libraries/framebuf/screen/screen-draw.c b/libraries/framebuf/screen/screen-draw.c index 91bf7d1..b340905 100644 --- a/libraries/framebuf/screen/screen-draw.c +++ b/libraries/framebuf/screen/screen-draw.c @@ -215,7 +215,11 @@ void screen_fill_rect(screen_t *scr, } } -void screen_fill_square(screen_t *scr, int x, int y, int size, colour_t colour) +void screen_fill_square(screen_t *scr, + int x, + int y, + int size, + colour_t colour) { screen_fill_rect(scr, x, y, SIZE2D(size, size), colour); } diff --git a/libraries/io/stream/stream-packbitsdecomp.c b/libraries/io/stream/stream-packbitsdecomp.c index 7828e41..3dde4a9 100644 --- a/libraries/io/stream/stream-packbitsdecomp.c +++ b/libraries/io/stream/stream-packbitsdecomp.c @@ -170,7 +170,9 @@ static stream_size_t stream_packbitsdecomp_fill(stream_t *s) return stream_remaining(s); } -result_t stream_packbitsdecomp_create(stream_t *input, int bufsz, stream_t **s) +result_t stream_packbitsdecomp_create(stream_t *input, + int bufsz, + stream_t **s) { stream_packbitsdecomp_t *sp; diff --git a/libraries/wuss/furniture/invalidate.c b/libraries/wuss/furniture/invalidate.c index 0b7d210..ad05875 100644 --- a/libraries/wuss/furniture/invalidate.c +++ b/libraries/wuss/furniture/invalidate.c @@ -7,7 +7,8 @@ * (toggle-size) also mark the *old* furniture strips dirty, since a blit * that reused the old pixels leaves stale titlebar/scrollbar/outline * pixels sitting wherever those strips used to be. */ -void wuss__furniture_invalidate_for(wuss_window_t *window, const box_t *visible) +void wuss__furniture_invalidate_for(wuss_window_t *window, + const box_t *visible) { int outline_px; point_t carve; diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index 8a9b087..c4bdcf6 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -50,7 +50,8 @@ static void icon_bevel(screen_t *scr, * else the window's dominant backdrop colour (a pattern fill blends against its * clear-bit colour, not its foreground), else "fallback" so bmfont still has a * blend colour. */ -static colour_t icon_blend_ground(const icon_draw_ctx_t *c, colour_t fallback) +static colour_t icon_blend_ground(const icon_draw_ctx_t *c, + colour_t fallback) { const wuss_icon_t *icon = c->icon; diff --git a/libraries/wuss/impl.h b/libraries/wuss/impl.h index a74a5b1..6e3bccb 100644 --- a/libraries/wuss/impl.h +++ b/libraries/wuss/impl.h @@ -267,7 +267,8 @@ static inline void wuss__release_packed(wuss_window_t *window) * the client's min_doc where it set one, but never below WUSS_MIN_CONTENT (a * window must stay big enough to grab) nor above the window's own doc extent * (a window can't be forced larger than the document it shows). */ -static inline void wuss__min_content(const wuss_window_t *window, size2d_t *min) +static inline void wuss__min_content(const wuss_window_t *window, + size2d_t *min) { min->w = CLAMP(window->min_doc.w, WUSS_MIN_CONTENT, MAX(window->doc.w, WUSS_MIN_CONTENT)); @@ -292,7 +293,8 @@ static inline int wuss__window_toggled(const wuss_window_t *window) return (window->state & wuss_WINDOW_STATE_TOGGLED) != 0; } -static inline void wuss__window_set_toggled(wuss_window_t *window, int toggled) +static inline void wuss__window_set_toggled(wuss_window_t *window, + int toggled) { if (toggled) window->state |= wuss_WINDOW_STATE_TOGGLED; diff --git a/libraries/wuss/test/tasks/ball.c b/libraries/wuss/test/tasks/ball.c index 12cd408..9923b12 100644 --- a/libraries/wuss/test/tasks/ball.c +++ b/libraries/wuss/test/tasks/ball.c @@ -14,7 +14,9 @@ #include "ball.h" -result_t ball_create(wuss_t *wuss, const colour_t *palette, ball_task_t *task) +result_t ball_create(wuss_t *wuss, + const colour_t *palette, + ball_task_t *task) { wuss_task_t delegate; diff --git a/libraries/wuss/test/tasks/ball.h b/libraries/wuss/test/tasks/ball.h index ac87f60..5e54df5 100644 --- a/libraries/wuss/test/tasks/ball.h +++ b/libraries/wuss/test/tasks/ball.h @@ -36,7 +36,9 @@ wuss_event_fn_t ball_handle; /* create the bouncing-ball window against the given wuss instance; "task" is a * per-instance block owned by the window and freed when it closes */ -result_t ball_create(wuss_t *wuss, const colour_t *palette, ball_task_t *task); +result_t ball_create(wuss_t *wuss, + const colour_t *palette, + ball_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/porter-duff.c b/libraries/wuss/test/tasks/porter-duff.c index d7d8290..15db71f 100644 --- a/libraries/wuss/test/tasks/porter-duff.c +++ b/libraries/wuss/test/tasks/porter-duff.c @@ -279,7 +279,8 @@ static void porter_duff_draw_checkerboard(const porter_duff_task_t *pd, } } -static result_t porter_duff_redraw(const wuss_event_t *event, void *task_data) +static result_t porter_duff_redraw(const wuss_event_t *event, + void *task_data) { porter_duff_task_t *pd; screen_t *scr; diff --git a/libraries/wuss/test/tasks/sofa.c b/libraries/wuss/test/tasks/sofa.c index c334176..266117a 100644 --- a/libraries/wuss/test/tasks/sofa.c +++ b/libraries/wuss/test/tasks/sofa.c @@ -354,7 +354,9 @@ static void draw_vertex_dots(screen_t *scr, colour); } -result_t sofa_create(wuss_t *wuss, const colour_t *palette, sofa_task_t *task) +result_t sofa_create(wuss_t *wuss, + const colour_t *palette, + sofa_task_t *task) { wuss_task_t delegate; @@ -516,7 +518,9 @@ static result_t sofa_mouse(wuss_window_t *window, return result_OK; } -static result_t sofa_scroll(wuss_window_t *window, int delta, void *task_data) +static result_t sofa_scroll(wuss_window_t *window, + int delta, + void *task_data) { sofa_task_t *sc; diff --git a/libraries/wuss/test/tasks/sofa.h b/libraries/wuss/test/tasks/sofa.h index 35c28c7..08aeb6d 100644 --- a/libraries/wuss/test/tasks/sofa.h +++ b/libraries/wuss/test/tasks/sofa.h @@ -43,7 +43,9 @@ sofa_task_t; wuss_event_fn_t sofa_handle; /* create the sofa window against the given wuss instance */ -result_t sofa_create(wuss_t *wuss, const colour_t *palette, sofa_task_t *task); +result_t sofa_create(wuss_t *wuss, + const colour_t *palette, + sofa_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/window/get-content-bounds.c b/libraries/wuss/window/get-content-bounds.c index 9eda68e..4a8cbab 100644 --- a/libraries/wuss/window/get-content-bounds.c +++ b/libraries/wuss/window/get-content-bounds.c @@ -2,7 +2,8 @@ #include "../impl.h" -void wuss_window_get_content_bounds(const wuss_window_t *window, box_t *content) +void wuss_window_get_content_bounds(const wuss_window_t *window, + box_t *content) { wuss__content_box(window, content); } diff --git a/libraries/wuss/window/get-visible-bounds.c b/libraries/wuss/window/get-visible-bounds.c index e7e0876..d305ad4 100644 --- a/libraries/wuss/window/get-visible-bounds.c +++ b/libraries/wuss/window/get-visible-bounds.c @@ -2,7 +2,8 @@ #include "../impl.h" -void wuss_window_get_visible_bounds(const wuss_window_t *window, box_t *visible) +void wuss_window_get_visible_bounds(const wuss_window_t *window, + box_t *visible) { *visible = window->visible; } diff --git a/libraries/wuss/window/invalidate.c b/libraries/wuss/window/invalidate.c index 1d4bb5d..33986c6 100644 --- a/libraries/wuss/window/invalidate.c +++ b/libraries/wuss/window/invalidate.c @@ -45,7 +45,9 @@ static void box_subtract_into(const box_t *piece, /* Clip "box" (screen space) down to the parts not already covered by * windows above "window" in the z-order, writing the surviving pieces to * "out" (capacity WUSS_MAX_INVALIDATE_PIECES) and returning their count. */ -int wuss__clip_to_visible(wuss_window_t *window, const box_t *box, box_t *out) +int wuss__clip_to_visible(wuss_window_t *window, + const box_t *box, + box_t *out) { box_t scratch[WUSS_MAX_INVALIDATE_PIECES]; box_t *cur, *nxt, *tmp; @@ -240,7 +242,9 @@ void wuss__invalidate_clipped(wuss_window_t *window, const box_t *box) /* Invalidate the part of "whole" not already covered by "keep" -- used * after a blit has slid a window's pixels from "whole" to "keep", so only * the vacated sliver still needs an actual repaint. */ -void wuss__invalidate_minus(wuss_t *wuss, const box_t *whole, const box_t *keep) +void wuss__invalidate_minus(wuss_t *wuss, + const box_t *whole, + const box_t *keep) { box_t pieces[WUSS_MAX_INVALIDATE_PIECES]; box_t cut; diff --git a/libraries/wuss/window/set-background.c b/libraries/wuss/window/set-background.c index 1c5cf95..5e846a3 100644 --- a/libraries/wuss/window/set-background.c +++ b/libraries/wuss/window/set-background.c @@ -2,7 +2,8 @@ #include "../impl.h" -result_t wuss_window_set_background(wuss_window_t *window, wuss_backdrop_t bg) +result_t wuss_window_set_background(wuss_window_t *window, + wuss_backdrop_t bg) { box_t content; diff --git a/tools/test_wrap_doxygen.py b/tools/test_wrap_doxygen.py index b0fe1a5..f252a2e 100644 --- a/tools/test_wrap_doxygen.py +++ b/tools/test_wrap_doxygen.py @@ -16,10 +16,10 @@ int g(void); '''.splitlines() -out, overflow = process(src, width=80) +out, overflow = process(src, width=77) assert overflow == [], overflow -assert all(len(l) <= 80 for l in out), [l for l in out if len(l) > 80] +assert all(len(l) <= 77 for l in out), [l for l in out if len(l) > 77] assert out[0] == '/**' assert out[1] == ' * A short line.' # continuation lines must keep the "*" one column right of "/**", with a @@ -31,7 +31,7 @@ assert out.count('/**') == 2 # original block + the promoted single-liner # idempotent: reformatting already-reformatted text changes nothing -out2, overflow2 = process(out, width=80) +out2, overflow2 = process(out, width=77) assert out2 == out assert overflow2 == [] diff --git a/tools/test_wrap_protos.py b/tools/test_wrap_protos.py index abef840..c09d3ea 100644 --- a/tools/test_wrap_protos.py +++ b/tools/test_wrap_protos.py @@ -2,7 +2,7 @@ """assert-based self-check for wrap_protos.py""" from wrap_protos import process, split_top_level -W = 80 +W = 77 def one(src): @@ -41,7 +41,7 @@ def test_idempotent(): def test_rewrap_bad_alignment(): - # joined form is >80, and it arrives already (badly) wrapped + # joined form is >77, and it arrives already (badly) wrapped src = ( "static int screen_copy_rectangle_p4(screen_t *scr,\n" " const box_t *s, const box_t *d,\n" @@ -59,7 +59,7 @@ def test_rewrap_bad_alignment(): def test_short_multiline_untouched(): - # already multi-line but joins to <80 -- leave the hand grouping alone + # already multi-line but joins to <77 -- leave the hand grouping alone src = ( "void draw_rect(screen_t *scr,\n" " int x, int y,\n" @@ -117,7 +117,7 @@ def test_variadic_bails(): def test_call_statement_not_touched(): src = (" array_squeeze(v->base, v->used, v->width, width_argument_here); " - "// a call, way over 80 columns of course yes indeed\n") + "// a call, way over 77 columns of course yes indeed\n") assert one(src) is None diff --git a/tools/wrap_doxygen.py b/tools/wrap_doxygen.py index f64906b..4c6b2e8 100644 --- a/tools/wrap_doxygen.py +++ b/tools/wrap_doxygen.py @@ -131,7 +131,7 @@ def flush(): def main(): ap = argparse.ArgumentParser(description=__doc__.splitlines()[0]) ap.add_argument('files', nargs='+') - ap.add_argument('--width', type=int, default=80) + ap.add_argument('--width', type=int, default=77) ap.add_argument('--check', action='store_true', help="don't write; exit 1 if any file would change") args = ap.parse_args() diff --git a/tools/wrap_protos.py b/tools/wrap_protos.py index 0319159..2787af3 100644 --- a/tools/wrap_protos.py +++ b/tools/wrap_protos.py @@ -302,7 +302,7 @@ def main(): description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument('files', nargs='+') - ap.add_argument('-w', '--width', type=int, default=80) + ap.add_argument('-w', '--width', type=int, default=77) ap.add_argument('-n', '--dry-run', action='store_true', help='report files that would change, write nothing') args = ap.parse_args() From cddfdf8cac342d293782c0197ed9ef0d72651054 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 19:26:25 +0100 Subject: [PATCH 57/79] refactor(wuss): replace the demo launcher window with a backdrop menu The interactive wuss app opened a persistent Launcher window full of button icons, one per task. Drop it and launcher.c/.h entirely; a MENU-button click on the bare backdrop now opens a "Tasks" pop-up menu at the pointer via wuss_menu_open. g_task_items and a parallel g_task_spawn table drive it: picking item i calls g_task_spawn[i]. The two menu-helper demo entries sit below a dashed rule. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- CMakeLists.txt | 1 - apps/wuss/main.c | 79 ++++++++++++------ libraries/wuss/test/tasks/launcher.c | 117 --------------------------- libraries/wuss/test/tasks/launcher.h | 49 ----------- 4 files changed, 53 insertions(+), 193 deletions(-) delete mode 100644 libraries/wuss/test/tasks/launcher.c delete mode 100644 libraries/wuss/test/tasks/launcher.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b01414d..bd70cd4 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -677,7 +677,6 @@ if(BUILD_APPS) libraries/wuss/test/tasks/gradient.c libraries/wuss/test/tasks/icons.c libraries/wuss/test/tasks/image.c - libraries/wuss/test/tasks/launcher.c libraries/wuss/test/tasks/lissajous.c libraries/wuss/test/tasks/palette.c libraries/wuss/test/tasks/porter-duff.c diff --git a/apps/wuss/main.c b/apps/wuss/main.c index a11a8c8..a55c743 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -32,7 +32,6 @@ #include "tasks/gradient.h" #include "tasks/icons.h" #include "tasks/image.h" -#include "tasks/launcher.h" #include "tasks/lissajous.h" #include "tasks/palette.h" #include "tasks/porter-duff.h" @@ -283,25 +282,52 @@ static result_t spawn_menu_desc(void) menu_selected, NULL, NULL); } -static const launcher_entry_t g_launcher_entries[] = +/* The task launcher is a MENU-button pop-up over the backdrop rather than a + * window of buttons. g_task_items and g_task_spawn run in lock-step: picking + * item i calls g_task_spawn[i]. */ +typedef result_t (*task_spawn_fn_t)(void); + +static const wuss_menu_item_t g_task_items[] = +{ + { "Ball", wuss_MENU_ITEM_NONE, NULL }, + { "Text", wuss_MENU_ITEM_NONE, NULL }, + { "Blank", wuss_MENU_ITEM_NONE, NULL }, + { "Chars", wuss_MENU_ITEM_NONE, NULL }, + { "Palette", wuss_MENU_ITEM_NONE, NULL }, + { "Image", wuss_MENU_ITEM_NONE, NULL }, + { "Checker", wuss_MENU_ITEM_NONE, NULL }, + { "Curve", wuss_MENU_ITEM_NONE, NULL }, + { "Lissajous", wuss_MENU_ITEM_NONE, NULL }, + { "Sofa", wuss_MENU_ITEM_NONE, NULL }, + { "Gradient", wuss_MENU_ITEM_NONE, NULL }, + { "Icons", wuss_MENU_ITEM_NONE, NULL }, + { "Porter-Duff", wuss_MENU_ITEM_NONE, NULL }, + { "Menu", wuss_MENU_ITEM_DASHED, NULL }, + { "Menu (desc)", wuss_MENU_ITEM_NONE, NULL } +}; +static const task_spawn_fn_t g_task_spawn[] = +{ + spawn_ball, spawn_text, spawn_blank, spawn_chars, spawn_palette, + spawn_image, spawn_checker, spawn_curve, spawn_lissajous, spawn_sofa, + spawn_gradient, spawn_icons, spawn_porter_duff, spawn_menu, spawn_menu_desc +}; +static const wuss_menu_t g_task_menu = { - { "Ball", spawn_ball }, - { "Text", spawn_text }, - { "Blank", spawn_blank }, - { "Chars", spawn_chars }, - { "Palette", spawn_palette }, - { "Image", spawn_image }, - { "Checker", spawn_checker }, - { "Curve", spawn_curve }, - { "Lissajous", spawn_lissajous }, - { "Sofa", spawn_sofa }, - { "Gradient", spawn_gradient }, - { "Icons", spawn_icons }, - { "Porter-Duff", spawn_porter_duff }, - { "Menu", spawn_menu }, - { "Menu (desc)", spawn_menu_desc } + "Tasks", g_task_items, NELEMS(g_task_items) }; +static void task_menu_selected(const wuss_menu_t *menu, + int index, + wuss_button_t button, + void *ctx) +{ + NOT_USED(menu); + NOT_USED(button); + NOT_USED(ctx); + if (index >= 0 && index < (int) NELEMS(g_task_spawn)) + (void) g_task_spawn[index](); +} + static wuss_button_t sdl_button_to_wuss(Uint8 button) { switch (button) @@ -358,7 +384,6 @@ static result_t run_wuss(const char *resources) screen_t scr; colour_t palette[16]; wuss_t *wuss; - launcher_task_t launcher_task; SDL_Window *window; SDL_Renderer *renderer; SDL_Texture *texture; @@ -469,10 +494,6 @@ static result_t run_wuss(const char *resources) g_resources = resources; g_daydream_font = daydream_font; - rc = launcher_create(wuss, g_launcher_entries, NELEMS(g_launcher_entries), &launcher_task); - if (rc != result_OK) - goto Failure; - quit = false; garbage_pending = false; pixel_stress_pending = false; @@ -514,10 +535,18 @@ static result_t run_wuss(const char *resources) case SDL_EVENT_MOUSE_BUTTON_DOWN: { - int x, y; + int x, y; + wuss_button_t button; + wuss_window_t *hit; sdl_pos_to_scr(window, scr_width, scr_height, event.button.x, event.button.y, &x, &y); - wuss_mouse_click(wuss, POINT(x, y), sdl_button_to_wuss(event.button.button), wuss_MOUSE_DOWN, NULL); + button = sdl_button_to_wuss(event.button.button); + wuss_mouse_click(wuss, POINT(x, y), button, wuss_MOUSE_DOWN, &hit); + + /* MENU click on bare backdrop opens the task launcher there */ + if (hit == NULL && (button & wuss_BUTTON_MENU)) + wuss_menu_open(wuss, &g_task_menu, POINT(x, y), + task_menu_selected, NULL, NULL); } break; @@ -626,8 +655,6 @@ static result_t run_wuss(const char *resources) * per-instance task block hung off it, so any task window left open at quit * leaks its block. Harmless at process exit; add a wuss close callback if a * task ever needs deterministic teardown. */ - launcher_destroy(&launcher_task); - wuss_menu_destroy(g_menu_desc); wuss_destroy(wuss); diff --git a/libraries/wuss/test/tasks/launcher.c b/libraries/wuss/test/tasks/launcher.c deleted file mode 100644 index db96740..0000000 --- a/libraries/wuss/test/tasks/launcher.c +++ /dev/null @@ -1,117 +0,0 @@ -/* wuss/test/tasks/launcher.c -- clickable list of names that spawn other tasks */ - -#ifdef USE_SDL - -#include <assert.h> -#include <string.h> - -#ifdef FORTIFY -#include "fortify/fortify.h" -#endif - -#include "base/utils.h" -#include "framebuf/palettes.h" -#include "geom/box.h" - -#include "wuss/icon.h" - -#include "launcher.h" - -#define LAUNCHER_ROW_HEIGHT 22 -#define LAUNCHER_PAD 4 -#define LAUNCHER_WIDTH 160 - -result_t launcher_create(wuss_t *wuss, - const launcher_entry_t *entries, - int nentries, - launcher_task_t *task) -{ - wuss_task_t delegate; - wuss_icon_spec_t specs[LAUNCHER_MAX_ENTRIES]; - size2d_t sz; - int i; - result_t rc; - - assert(nentries <= LAUNCHER_MAX_ENTRIES); - - task->entries = entries; - task->nentries = nentries; - task->window = NULL; - memset(task->icons, 0, sizeof(task->icons)); - - delegate = wuss_task_start(launcher_handle, task); - sz = SIZE2D(LAUNCHER_WIDTH, - LAUNCHER_PAD * 2 + nentries * LAUNCHER_ROW_HEIGHT); - - rc = wuss_window_create_placed(wuss, - sz, - "Launcher", - wuss_WINDOW_NO_CLOSE, - wuss_BACKDROP_COLOUR(palette_PICO8_LIGHT_GREY), - &delegate, - sz, - SIZE2D(0, 0), - &task->window); - if (rc != result_OK) - return rc; - - memset(specs, 0, sizeof(specs)); - - for (i = 0; i < nentries; i++) - { - specs[i].bbox = (box_t) BOX_POS_SIZE(LAUNCHER_PAD, - LAUNCHER_PAD + i * LAUNCHER_ROW_HEIGHT, - LAUNCHER_WIDTH - LAUNCHER_PAD * 2, - LAUNCHER_ROW_HEIGHT - 2); - specs[i].type = wuss_ICON_TYPE_BUTTON; - specs[i].text = entries[i].name; - specs[i].fg = palette_PICO8_BLACK; - specs[i].bg = palette_PICO8_LIGHT_GREY; - } - - rc = wuss_icon_create_array(task->window, specs, nentries, task->icons); - if (rc != result_OK) - { - wuss_window_close(task->window); - task->window = NULL; - return rc; - } - - return result_OK; -} - -void launcher_destroy(launcher_task_t *task) -{ - wuss_window_close(task->window); -} - -static result_t launcher_icon(launcher_task_t *lc, const wuss_icon_t *icon) -{ - int i; - - for (i = 0; i < lc->nentries; i++) - if (lc->icons[i] == icon) - return lc->entries[i].spawn(); - - return result_OK; -} - -result_t launcher_handle(wuss_window_t *window, - const wuss_event_t *event, - void *task_data) -{ - NOT_USED(window); - - switch (event->kind) - { - case wuss_EVENT_ICON: - if (event->data.icon.action != wuss_MOUSE_DOWN) - return result_OK; - return launcher_icon(task_data, event->data.icon.icon); - - default: - return result_OK; - } -} - -#endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/launcher.h b/libraries/wuss/test/tasks/launcher.h deleted file mode 100644 index 8dd2b5c..0000000 --- a/libraries/wuss/test/tasks/launcher.h +++ /dev/null @@ -1,49 +0,0 @@ -/* wuss/test/tasks/launcher.h -- clickable list of names that spawn other tasks */ - -#ifndef TASKS_LAUNCHER_H -#define TASKS_LAUNCHER_H - -#ifdef USE_SDL - -#include <stdbool.h> - -#include "wuss/icon.h" -#include "wuss/window.h" - -typedef result_t (*launcher_spawn_fn_t)(void); - -/* one clickable row */ -typedef struct launcher_entry -{ - const char *name; - launcher_spawn_fn_t spawn; -} -launcher_entry_t; - -/* each row is a work-area button icon; clicking one spawns a fresh instance - * of its task, so a row may be clicked any number of times */ -#define LAUNCHER_MAX_ENTRIES 32 - -typedef struct launcher_task -{ - const launcher_entry_t *entries; /* owned by the caller, must outlive the window */ - int nentries; - wuss_icon_t *icons[LAUNCHER_MAX_ENTRIES]; - wuss_window_t *window; -} -launcher_task_t; - -wuss_event_fn_t launcher_handle; - -/* create a window of button icons, one per entry; each click on a button - * calls its spawn function */ -result_t launcher_create(wuss_t *wuss, - const launcher_entry_t *entries, - int nentries, - launcher_task_t *task); - -void launcher_destroy(launcher_task_t *task); - -#endif /* USE_SDL */ - -#endif /* TASKS_LAUNCHER_H */ From 9d817271e1afd2d218656d110c3804f3e3427fa5 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 19:39:33 +0100 Subject: [PATCH 58/79] refactor(wuss): validate icon type with a switch not a chain of != Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/icon/create.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/libraries/wuss/icon/create.c b/libraries/wuss/icon/create.c index c45c8cb..9393061 100644 --- a/libraries/wuss/icon/create.c +++ b/libraries/wuss/icon/create.c @@ -26,16 +26,22 @@ result_t wuss_icon_create(wuss_window_t *window, w = window->wuss; - if (spec->type != wuss_ICON_TYPE_LABEL && - spec->type != wuss_ICON_TYPE_BUTTON && - spec->type != wuss_ICON_TYPE_PATTERN && - spec->type != wuss_ICON_TYPE_FRAME && - spec->type != wuss_ICON_TYPE_RADIO && - spec->type != wuss_ICON_TYPE_OPTION && - spec->type != wuss_ICON_TYPE_BITMAP && - spec->type != wuss_ICON_TYPE_MENU_ENTRY && - spec->type != wuss_ICON_TYPE_RULE) + switch (spec->type) + { + case wuss_ICON_TYPE_LABEL: + case wuss_ICON_TYPE_BUTTON: + case wuss_ICON_TYPE_PATTERN: + case wuss_ICON_TYPE_FRAME: + case wuss_ICON_TYPE_RADIO: + case wuss_ICON_TYPE_OPTION: + case wuss_ICON_TYPE_BITMAP: + case wuss_ICON_TYPE_MENU_ENTRY: + case wuss_ICON_TYPE_RULE: + break; + + default: return result_WUSS_BAD_ICON; + } if ((spec->type == wuss_ICON_TYPE_BUTTON || spec->type == wuss_ICON_TYPE_PATTERN) && From 8cff9e48a57f3cc05d05404b783fcb3ad9005368 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 19:39:33 +0100 Subject: [PATCH 59/79] refactor(wuss): pass NULL for the unused width out-param of bmfont_get_info Drops the fw local and its NOT_USED in wuss__menu_spawn. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/menu/menu.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index 9cd73ef..2e2619e 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -25,10 +25,10 @@ /* Row padding above/below the glyph, and the gutters left for the tick (left) * and the submenu arrow (right). All in pixels. */ -#define WUSS_MENU_ROW_PAD 3 -#define WUSS_MENU_TICK_W 14 -#define WUSS_MENU_ARROW_W 14 -#define WUSS_MENU_TEXT_PAD 6 +#define WUSS_MENU_ROW_PAD 4 +#define WUSS_MENU_TICK_W 14 +#define WUSS_MENU_ARROW_W 14 +#define WUSS_MENU_TEXT_PAD 6 #define WUSS_MENU_SUBMENU_OVERLAP 2 /* px a submenu overlaps its parent's right edge */ /* ----------------------------------------------------------------------- */ @@ -176,7 +176,7 @@ static result_t wuss__menu_spawn(wuss_t *wuss, wuss_icon_t **made; wuss_task_t task; result_t rc; - int fw, fh; + int fh; int pitch; int sep_h; int y; @@ -204,8 +204,7 @@ static result_t wuss__menu_spawn(wuss_t *wuss, | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE; - bmfont_get_info(wuss->font, &fw, &fh); - NOT_USED(fw); + bmfont_get_info(wuss->font, NULL, &fh); pitch = fh + 2 * WUSS_MENU_ROW_PAD; sep_h = 2 * WUSS_MENU_ROW_PAD; From de2058841d2949c3929d9ff7aa5953a722495e05 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 19:39:33 +0100 Subject: [PATCH 60/79] feat(wuss): add a 'Quit Wuss' task-menu entry that shuts the app down Also space out the menu-table declarations one blank line apart. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- apps/wuss/main.c | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/apps/wuss/main.c b/apps/wuss/main.c index a55c743..a8be549 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -53,6 +53,7 @@ static const colour_t *g_palette; static int g_npalette; static const char *g_resources; static bmfont_t *g_daydream_font; +static bool g_quit; /* set by the "Quit Wuss" task-menu entry */ /* Each spawn allocates a fresh per-instance task block so a task may run in * several windows at once; the block is owned by its window and freed by the @@ -212,6 +213,7 @@ static const wuss_menu_item_t g_menu_export_items[] = { "As JPEG", wuss_MENU_ITEM_NONE, NULL }, { "As GIF", wuss_MENU_ITEM_DISABLED, NULL } }; + static const wuss_menu_t g_menu_export = { "Export", g_menu_export_items, NELEMS(g_menu_export_items) @@ -225,6 +227,7 @@ static const wuss_menu_item_t g_menu_items[] = { "Export", wuss_MENU_ITEM_NONE, &g_menu_export }, { "Quit", wuss_MENU_ITEM_DASHED, NULL } }; + static const wuss_menu_t g_menu = { "Display", g_menu_items, NELEMS(g_menu_items) @@ -259,6 +262,7 @@ static const wuss_menu_item_t g_menu_desc_export_items[] = { "As JPEG", wuss_MENU_ITEM_NONE, NULL }, { "As GIF", wuss_MENU_ITEM_DISABLED, NULL } }; + static const wuss_menu_t g_menu_desc_export = { "Export", g_menu_desc_export_items, NELEMS(g_menu_desc_export_items) @@ -303,14 +307,24 @@ static const wuss_menu_item_t g_task_items[] = { "Icons", wuss_MENU_ITEM_NONE, NULL }, { "Porter-Duff", wuss_MENU_ITEM_NONE, NULL }, { "Menu", wuss_MENU_ITEM_DASHED, NULL }, - { "Menu (desc)", wuss_MENU_ITEM_NONE, NULL } + { "Menu (desc)", wuss_MENU_ITEM_NONE, NULL }, + { "Quit Wuss", wuss_MENU_ITEM_DASHED, NULL } }; + +static result_t spawn_quit(void) +{ + g_quit = true; + return result_OK; +} + static const task_spawn_fn_t g_task_spawn[] = { spawn_ball, spawn_text, spawn_blank, spawn_chars, spawn_palette, spawn_image, spawn_checker, spawn_curve, spawn_lissajous, spawn_sofa, - spawn_gradient, spawn_icons, spawn_porter_duff, spawn_menu, spawn_menu_desc + spawn_gradient, spawn_icons, spawn_porter_duff, spawn_menu, spawn_menu_desc, + spawn_quit }; + static const wuss_menu_t g_task_menu = { "Tasks", g_task_items, NELEMS(g_task_items) @@ -495,12 +509,13 @@ static result_t run_wuss(const char *resources) g_daydream_font = daydream_font; quit = false; + g_quit = false; garbage_pending = false; pixel_stress_pending = false; wuss_redraw(wuss); - while (!quit) + while (!quit && !g_quit) { SDL_Event event; From 1b94b14320d752f5e23e26b13fac35b8792f1e92 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 21:12:18 +0100 Subject: [PATCH 61/79] fix(wuss): close menu chain before invoking on_select The MOUSE_UP handler in wuss__menu_handle called the on_select callback while the menu node was still live, then continued to touch `self`. A callback that opens a new menu (via wuss_menu_open) frees the whole chain, including the node the dispatcher was running inside, causing a heap-use-after-free on return. Capture the callback, menu and ctx up front, tear the chain down first, then invoke the callback. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/menu/menu.c | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index 2e2619e..81c187e 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -134,26 +134,36 @@ static result_t wuss__menu_handle(wuss_window_t *window, if (event->data.icon.action == wuss_MOUSE_UP) { - wuss_button_t button; - struct wuss__menu *root; + wuss_button_t button; + struct wuss__menu *root; + wuss_menu_select_fn_t *on_select; + const wuss_menu_t *menu; + void *ctx; button = event->data.icon.button; if (item->submenu != NULL) return result_OK; /* a submenu row opens on hover, it is not a pick */ - if (self->on_select != NULL) - self->on_select(self->menu, index, button, self->ctx); + /* Capture what the callback needs, then tear the chain down *before* + * invoking it: on_select may itself open a new menu (freeing this one), + * so `self` must not be touched afterwards. */ + on_select = self->on_select; + menu = self->menu; + ctx = self->ctx; - if (button & wuss_BUTTON_ADJUST) - return result_OK; /* ADJUST keeps the chain open */ + if (!(button & wuss_BUTTON_ADJUST)) + { + root = self; + while (root->parent != NULL) + root = root->parent; - root = self; - while (root->parent != NULL) - root = root->parent; + root->wuss->menu_chain = NULL; + wuss__menu_close_from(root); + } - root->wuss->menu_chain = NULL; - wuss__menu_close_from(root); + if (on_select != NULL) + on_select(menu, index, button, ctx); } return result_OK; From dc90dc7a59e07d5f9633eed5cf19b14bc35716d7 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 21:12:50 +0100 Subject: [PATCH 62/79] style(wuss): drop horizontal padding from menu rule lines Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/icon/draw.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index c4bdcf6..cc48f56 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -417,12 +417,12 @@ static void wuss__icon_draw_menu_entry(const icon_draw_ctx_t *c) static void wuss__icon_draw_rule(const icon_draw_ctx_t *c) { const box_t *b = &c->b; - int pad, mid_y; + int mid_y; - pad = 4; mid_y = b->y0 + (b->y1 - b->y0) / 2; - screen_draw_dashed_line(c->scr, b->x0 + pad, mid_y, - b->x1 - 1 - pad, mid_y, 2, 2, c->fg); + screen_draw_dashed_line(c->scr, + b->x0, mid_y, b->x1 - 1, mid_y, + 2, 2, c->fg); } /* ----------------------------------------------------------------------- */ From 0686a5041fb5619e824c05e824d0d01607c5c7ff Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 21:23:45 +0100 Subject: [PATCH 63/79] refactor(wuss): bunch g_* runtime statics into a single struct The six mutable file-scope statics that carry per-run context to the argument-less spawn callbacks are now fields of one anonymous struct g rather than loose g_wuss/g_palette/... globals. The const menu tables are left as they were. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- apps/wuss/main.c | 68 +++++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/apps/wuss/main.c b/apps/wuss/main.c index a8be549..ba8cba2 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -47,13 +47,17 @@ /* the launcher's spawn callbacks take no arguments, so the pieces they need * are stashed here instead; run_wuss runs at most once per - * process, so file-scope statics are as good as a context struct */ -static wuss_t *g_wuss; -static const colour_t *g_palette; -static int g_npalette; -static const char *g_resources; -static bmfont_t *g_daydream_font; -static bool g_quit; /* set by the "Quit Wuss" task-menu entry */ + * process, so a file-scope struct is as good as a passed-around context */ +static struct +{ + wuss_t *wuss; + const colour_t *palette; + int npalette; + const char *resources; + bmfont_t *daydream_font; + bool quit; /* set by the "Quit Wuss" task-menu entry */ +} +g; /* Each spawn allocates a fresh per-instance task block so a task may run in * several windows at once; the block is owned by its window and freed by the @@ -66,7 +70,7 @@ static result_t spawn_ball(void) ball_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = ball_create(g_wuss, g_palette, t); + rc = ball_create(g.wuss, g.palette, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -76,7 +80,7 @@ static result_t spawn_text(void) text_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = text_create(g_wuss, g_palette, g_daydream_font, t); + rc = text_create(g.wuss, g.palette, g.daydream_font, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -86,7 +90,7 @@ static result_t spawn_blank(void) blank_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = blank_create(g_wuss, g_npalette, t); + rc = blank_create(g.wuss, g.npalette, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -96,7 +100,7 @@ static result_t spawn_chars(void) chars_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = chars_create(g_wuss, g_palette, t); + rc = chars_create(g.wuss, g.palette, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -106,7 +110,7 @@ static result_t spawn_palette(void) palette_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = palette_create(g_wuss, g_palette, g_npalette, t); + rc = palette_create(g.wuss, g.palette, g.npalette, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -124,12 +128,12 @@ static result_t spawn_image(void) if (t == NULL) return result_OOM; leafname = path_join_leafname("jessica", "png"); - filename = path_join_filename(g_resources, 3, "resources", "images", leafname); + filename = path_join_filename(g.resources, 3, "resources", "images", leafname); strcpy(buf, filename); - ninepatch = path_join_filename(g_resources, 3, "resources", "wuss", + ninepatch = path_join_filename(g.resources, 3, "resources", "wuss", path_join_leafname("9tile", "png")); - rc = image_create(g_wuss, g_palette, buf, ninepatch, t); + rc = image_create(g.wuss, g.palette, buf, ninepatch, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -139,7 +143,7 @@ static result_t spawn_checker(void) checker_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = checker_create(g_wuss, g_palette, t); + rc = checker_create(g.wuss, g.palette, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -149,7 +153,7 @@ static result_t spawn_curve(void) curve_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = curve_create(g_wuss, g_palette, t); + rc = curve_create(g.wuss, g.palette, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -159,7 +163,7 @@ static result_t spawn_lissajous(void) lissajous_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = lissajous_create(g_wuss, g_palette, t); + rc = lissajous_create(g.wuss, g.palette, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -169,7 +173,7 @@ static result_t spawn_sofa(void) sofa_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = sofa_create(g_wuss, g_palette, t); + rc = sofa_create(g.wuss, g.palette, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -179,7 +183,7 @@ static result_t spawn_gradient(void) gradient_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = gradient_create(g_wuss, t); + rc = gradient_create(g.wuss, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -189,7 +193,7 @@ static result_t spawn_icons(void) icons_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = icons_create(g_wuss, g_palette, g_daydream_font, g_resources, t); + rc = icons_create(g.wuss, g.palette, g.daydream_font, g.resources, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -199,7 +203,7 @@ static result_t spawn_porter_duff(void) porter_duff_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = porter_duff_create(g_wuss, g_palette, g_daydream_font, g_resources, t); + rc = porter_duff_create(g.wuss, g.palette, g.daydream_font, g.resources, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -246,7 +250,7 @@ static void menu_selected(const wuss_menu_t *menu, static result_t spawn_menu(void) { - return wuss_menu_open(g_wuss, &g_menu, wuss_get_pointer(g_wuss), + return wuss_menu_open(g.wuss, &g_menu, wuss_get_pointer(g.wuss), menu_selected, NULL, NULL); } @@ -282,7 +286,7 @@ static result_t spawn_menu_desc(void) wuss_menu_destroy(g_menu_desc); g_menu_desc = m; - return wuss_menu_open(g_wuss, g_menu_desc, wuss_get_pointer(g_wuss), + return wuss_menu_open(g.wuss, g_menu_desc, wuss_get_pointer(g.wuss), menu_selected, NULL, NULL); } @@ -313,7 +317,7 @@ static const wuss_menu_item_t g_task_items[] = static result_t spawn_quit(void) { - g_quit = true; + g.quit = true; return result_OK; } @@ -502,20 +506,20 @@ static result_t run_wuss(const char *resources) goto Failure; } - g_wuss = wuss; - g_palette = palette; - g_npalette = NELEMS(palette); - g_resources = resources; - g_daydream_font = daydream_font; + g.wuss = wuss; + g.palette = palette; + g.npalette = NELEMS(palette); + g.resources = resources; + g.daydream_font = daydream_font; quit = false; - g_quit = false; + g.quit = false; garbage_pending = false; pixel_stress_pending = false; wuss_redraw(wuss); - while (!quit && !g_quit) + while (!quit && !g.quit) { SDL_Event event; From cd4fd216aefba588c350015dd32cf35b8714f718 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 21:29:18 +0100 Subject: [PATCH 64/79] fix(wuss): don't wheel-scroll windows that declared an axis non-scrollable wuss__scroll_step now zeroes the delta on any axis whose window carries wuss_WINDOW_NO_HSCROLL / wuss_WINDOW_NO_VSCROLL. Pop-up menus set both and have no scrollbar to clamp against; their geometry (submenu placement, hit testing) assumes scroll == 0, so a wheel turn over a menu taller than the screen was corrupting the display. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/scroll-step.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/libraries/wuss/scroll-step.c b/libraries/wuss/scroll-step.c index ab01d5f..5267b00 100644 --- a/libraries/wuss/scroll-step.c +++ b/libraries/wuss/scroll-step.c @@ -32,6 +32,16 @@ void wuss__scroll_step(wuss_window_t *window, point_t delta) { point_t scroll; + /* A window that declared an axis non-scrollable (e.g. a pop-up menu) has no + * scrollbar to clamp against and its geometry assumes scroll == 0, so a wheel + * turn over it must not move the content -- doing so corrupts the display. */ + if (window->flags & wuss_WINDOW_NO_HSCROLL) + delta.x = 0; + if (window->flags & wuss_WINDOW_NO_VSCROLL) + delta.y = 0; + if (delta.x == 0 && delta.y == 0) + return; + wuss_window_get_scroll(window, &scroll); scroll.x += delta.x; scroll.y += delta.y; From ab858641ca23a7e04ee59351455185c1800bd68e Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 21:37:40 +0100 Subject: [PATCH 65/79] feat(wuss): scroll over-tall pop-up menus instead of cropping them When a menu's full height exceeds what the screen leaves for it, cap the window's content height at that maximum and clear wuss_WINDOW_NO_VSCROLL so the window gets a real vertical scrollbar. The document extent passed to wuss_window_create stays the full menu height, so the existing scroll-aware icon draw and hit test move the rows under the viewport with no further work. Submenu placement now subtracts the parent menu's scroll offset when siting a child, and the on-screen nudge accounts for the scrollbar carve on the right edge. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/menu/menu.c | 71 ++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 19 deletions(-) diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index 81c187e..ea59c7b 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -119,11 +119,17 @@ static result_t wuss__menu_handle(wuss_window_t *window, wuss_window_get_content_bounds(self->window, &wb); wuss_icon_get_bbox(icon, &ib); - /* wb is the content box in screen space; the menu is unscrolled, so a - * row's document y is its offset from the content top. The child's own - * titlebar sits above `at`, so a submenu row lines up with its parent. */ - at.x = wb.x1 - WUSS_MENU_SUBMENU_OVERLAP; - at.y = wb.y0 + ib.y0; + /* wb is the content box in screen space; ib is in document space, so the + * row's screen y is its document y less however far the menu is scrolled. + * The child's own titlebar sits above `at`, so a submenu row lines up with + * its parent. */ + { + point_t scroll; + + wuss_window_get_scroll(self->window, &scroll); + at.x = wb.x1 - WUSS_MENU_SUBMENU_OVERLAP; + at.y = wb.y0 + ib.y0 - scroll.y; + } if (wuss__menu_spawn(self->wuss, item->submenu, at, self, self->on_select, self->ctx, &self->child) == result_OK) @@ -192,14 +198,18 @@ static result_t wuss__menu_spawn(wuss_t *wuss, int y; int widest; int width, height; + int doc_h; + int max_h; int i; int ndashed; int nspecs; int s; int outline_px; int titlebar_height; + point_t carve; wuss_window_flags_t menu_flags; - size2d_t size; + size2d_t doc; + size2d_t min_doc; box_t content; assert(wuss != NULL); @@ -214,6 +224,9 @@ static result_t wuss__menu_spawn(wuss_t *wuss, | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE; + outline_px = wuss__outline_px_for(menu_flags); + titlebar_height = wuss__titlebar_height_for(wuss, menu_flags); + bmfont_get_info(wuss->font, NULL, &fh); pitch = fh + 2 * WUSS_MENU_ROW_PAD; sep_h = 2 * WUSS_MENU_ROW_PAD; @@ -243,19 +256,36 @@ static result_t wuss__menu_spawn(wuss_t *wuss, width = WUSS_MENU_TICK_W + widest + WUSS_MENU_TEXT_PAD + WUSS_MENU_ARROW_W; - /* every item is a full row now; a dashed item also gets a sep_h rule above */ - height = menu->nitems * pitch + ndashed * sep_h; + /* every item is a full row now; a dashed item also gets a sep_h rule above. + * doc_h is the whole menu; `height` is what the window actually shows. When + * the menu is taller than the space the screen leaves for it, cap `height` + * and give the window a real vertical scrollbar -- the icon draw and hit + * test already honour window->scroll, so the rows just move under it. */ + doc_h = menu->nitems * pitch + ndashed * sep_h; + + max_h = wuss->scr->size.h - 2 * outline_px - titlebar_height; + + if (doc_h > max_h) + { + height = max_h; + menu_flags &= (wuss_window_flags_t) ~wuss_WINDOW_NO_VSCROLL; + } + else + { + height = doc_h; + } + + wuss__furniture_carve_for(menu_flags, wuss__button_size_for(wuss, menu_flags), + &carve); /* Nudge onto the screen where it can be, but keep the top-left on screen. * `at` is the content top-left; the window's visible box also spans the - * outline on all four sides and the titlebar above, so clamp against that - * extent -- otherwise wuss_window_create's own on-screen size clamp trims - * the content and the last row (e.g. a trailing "Quit") is cropped. */ - outline_px = wuss__outline_px_for(menu_flags); - titlebar_height = wuss__titlebar_height_for(wuss, menu_flags); - - if (at.x + width + outline_px > wuss->scr->size.w) - at.x = wuss->scr->size.w - width - outline_px; + * outline on all four sides, the titlebar above and (for a scrolling menu) + * the scrollbar carve on the right, so clamp against that whole extent -- + * otherwise wuss_window_create's own on-screen size clamp trims the content + * and the last row (e.g. a trailing "Quit") is cropped. */ + if (at.x + width + outline_px + carve.x > wuss->scr->size.w) + at.x = wuss->scr->size.w - width - outline_px - carve.x; if (at.y + height + outline_px > wuss->scr->size.h) at.y = wuss->scr->size.h - height - outline_px; if (at.x - outline_px < 0) @@ -338,8 +368,11 @@ static result_t wuss__menu_spawn(wuss_t *wuss, y += pitch; } - size = SIZE2D(width, height); - task = wuss_task_start(wuss__menu_handle, node); + /* doc spans the whole menu so the window can scroll it; min_doc is what the + * window shows, so its own resize floor never exceeds what fits. */ + doc = SIZE2D(width, doc_h); + min_doc = SIZE2D(width, height); + task = wuss_task_start(wuss__menu_handle, node); /* `at` is the content top-left, already clamped on screen above. Create the * window there directly -- creating it elsewhere and wuss_window_move-ing it @@ -354,7 +387,7 @@ static result_t wuss__menu_spawn(wuss_t *wuss, menu->title ? menu->title : "", menu_flags, wuss_BACKDROP_COLOUR(wuss->white), - &task, size, size, &node->window); + &task, doc, min_doc, &node->window); if (rc != result_OK) { wuss__free(wuss, made); From 631af4c6977908fae9d30fb763c4a038deb978e3 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 21:48:06 +0100 Subject: [PATCH 66/79] refactor(wuss): split pattern swatches out of icons task The icons task baked a swatch-per-fill-pattern grid into its icon-spec array alongside its labels, buttons, radios and menu entries. Move that grid into a standalone "swatches" task and drop it from icons, so each demonstrates one thing. Adds it to the interactive demo's task menu and CMake source list. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- CMakeLists.txt | 1 + apps/wuss/main.c | 16 ++++- libraries/wuss/test/tasks/icons.c | 35 ++-------- libraries/wuss/test/tasks/swatches.c | 101 +++++++++++++++++++++++++++ libraries/wuss/test/tasks/swatches.h | 27 +++++++ 5 files changed, 150 insertions(+), 30 deletions(-) create mode 100644 libraries/wuss/test/tasks/swatches.c create mode 100644 libraries/wuss/test/tasks/swatches.h diff --git a/CMakeLists.txt b/CMakeLists.txt index bd70cd4..845d523 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -681,6 +681,7 @@ if(BUILD_APPS) libraries/wuss/test/tasks/palette.c libraries/wuss/test/tasks/porter-duff.c libraries/wuss/test/tasks/sofa.c + libraries/wuss/test/tasks/swatches.c libraries/wuss/test/tasks/text.c) source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${WUSS_APP_SOURCES}) diff --git a/apps/wuss/main.c b/apps/wuss/main.c index ba8cba2..c23e698 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -36,6 +36,7 @@ #include "tasks/palette.h" #include "tasks/porter-duff.h" #include "tasks/sofa.h" +#include "tasks/swatches.h" #include "tasks/text.h" /* ----------------------------------------------------------------------- */ @@ -198,6 +199,16 @@ static result_t spawn_icons(void) return result_OK; } +static result_t spawn_swatches(void) +{ + swatches_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = swatches_create(g.wuss, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + static result_t spawn_porter_duff(void) { porter_duff_task_t *t = calloc(1, sizeof(*t)); @@ -309,6 +320,7 @@ static const wuss_menu_item_t g_task_items[] = { "Sofa", wuss_MENU_ITEM_NONE, NULL }, { "Gradient", wuss_MENU_ITEM_NONE, NULL }, { "Icons", wuss_MENU_ITEM_NONE, NULL }, + { "Swatches", wuss_MENU_ITEM_NONE, NULL }, { "Porter-Duff", wuss_MENU_ITEM_NONE, NULL }, { "Menu", wuss_MENU_ITEM_DASHED, NULL }, { "Menu (desc)", wuss_MENU_ITEM_NONE, NULL }, @@ -325,8 +337,8 @@ static const task_spawn_fn_t g_task_spawn[] = { spawn_ball, spawn_text, spawn_blank, spawn_chars, spawn_palette, spawn_image, spawn_checker, spawn_curve, spawn_lissajous, spawn_sofa, - spawn_gradient, spawn_icons, spawn_porter_duff, spawn_menu, spawn_menu_desc, - spawn_quit + spawn_gradient, spawn_icons, spawn_swatches, spawn_porter_duff, spawn_menu, + spawn_menu_desc, spawn_quit }; static const wuss_menu_t g_task_menu = diff --git a/libraries/wuss/test/tasks/icons.c b/libraries/wuss/test/tasks/icons.c index f8cb6c0..4adbdb3 100644 --- a/libraries/wuss/test/tasks/icons.c +++ b/libraries/wuss/test/tasks/icons.c @@ -31,17 +31,16 @@ result_t icons_create(wuss_t *wuss, icons_task_t *task) { /* [0..3] heading, counter button, counter label and a scrolled-away button, - * then [.. +N] one swatch per built-in fill pattern, then [.. +3] a grouping - * frame with two differently-justified labels inside it, then [.. +5] three - * grouped radios, a standalone option and a label echoing the selection, - * then [.. +2] a decorative bitmap icon and an interactive one that bumps - * the counter, then [.. +4] a strip of menu-entry icons that hover-highlight */ - enum { ICONS_NSPECS = 4 + screen_PATTERN__LIMIT + 3 + 5 + 2 + 5 }; + * then [.. +3] a grouping frame with two differently-justified labels inside + * it, then [.. +5] three grouped radios, a standalone option and a label + * echoing the selection, then [.. +2] a decorative bitmap icon and an + * interactive one that bumps the counter, then [.. +4] a strip of menu-entry + * icons that hover-highlight */ + enum { ICONS_NSPECS = 4 + 3 + 5 + 2 + 5 }; wuss_task_t delegate; wuss_icon_spec_t specs[ICONS_NSPECS]; wuss_icon_t *made[ICONS_NSPECS]; const char *sprite_path; - int p; int g; /* index of the first frame spec */ int r; /* radio index */ int m; /* index of the first menu-entry spec */ @@ -120,29 +119,9 @@ result_t icons_create(wuss_t *wuss, specs[3].fg = palette_PICO8_BLACK; specs[3].bg = palette_PICO8_LIGHT_GREY; - /* [4..] one 16x16 swatch per built-in fill pattern, laid out as a grid down - * the document so it scrolls through the window and stays phase-locked while - * doing so */ - for (p = 0; p < screen_PATTERN__LIMIT; p++) - { - enum { ICONS_SWATCH_COLS = 6, ICONS_SWATCH_PITCH = 20 }; - int col, row; - - col = p % ICONS_SWATCH_COLS; - row = p / ICONS_SWATCH_COLS; - - specs[4 + p].bbox = (box_t) BOX_POS_SIZE(28 + col * ICONS_SWATCH_PITCH, - 90 + row * ICONS_SWATCH_PITCH, - 16, 16); - specs[4 + p].type = wuss_ICON_TYPE_PATTERN; - specs[4 + p].fg = palette_PICO8_DARK_BLUE; - specs[4 + p].bg = palette_PICO8_LIGHT_GREY; - specs[4 + p].pattern = (screen_pattern_t) p; - } - /* [g] a grouping frame down the document, with [g+1] a right-justified and * [g+2] a centred label sat inside it */ - g = 4 + screen_PATTERN__LIMIT; + g = 4; specs[g].bbox = (box_t) BOX_POS_SIZE(28, 300, 170, 70); specs[g].type = wuss_ICON_TYPE_FRAME; diff --git a/libraries/wuss/test/tasks/swatches.c b/libraries/wuss/test/tasks/swatches.c new file mode 100644 index 0000000..0092d17 --- /dev/null +++ b/libraries/wuss/test/tasks/swatches.c @@ -0,0 +1,101 @@ +/* wuss/test/tasks/swatches.c -- fill-pattern swatch grid task */ + +#ifdef USE_SDL + +#include <stdlib.h> +#include <string.h> + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "framebuf/palettes.h" +#include "framebuf/screen.h" +#include "geom/box.h" +#include "geom/size.h" +#include "wuss/icon.h" + +#include "swatches.h" + +#define SWATCHES_DOC_W 160 +#define SWATCHES_DOC_H 320 /* taller than the window, so scrolling is exercised */ + +result_t swatches_create(wuss_t *wuss, swatches_task_t *task) +{ + enum { SWATCHES_NSPECS = 1 + screen_PATTERN__LIMIT }; + wuss_task_t delegate; + wuss_icon_spec_t specs[SWATCHES_NSPECS]; + int p; + result_t rc; + + task->window = NULL; + + delegate = wuss_task_start(swatches_handle, task); + + rc = wuss_window_create_placed(wuss, + SIZE2D(SWATCHES_DOC_W, 140), + "Swatches", + wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(palette_PICO8_LIGHT_GREY), + &delegate, + SIZE2D(SWATCHES_DOC_W, SWATCHES_DOC_H), + SIZE2D(0, 0), + &task->window); + if (rc != result_OK) + return rc; + + memset(specs, 0, sizeof(specs)); + + /* [0] a heading label */ + specs[0].bbox = (box_t) BOX_POS_SIZE(12, 12, 140, 14); + specs[0].type = wuss_ICON_TYPE_LABEL; + specs[0].text = "Fill patterns:"; + specs[0].fg = palette_PICO8_DARK_BLUE; + specs[0].bg = wuss_NO_BACKGROUND; + + /* [1..] one 16x16 swatch per built-in fill pattern, laid out as a grid down + * the document so it scrolls through the window and stays phase-locked while + * doing so */ + for (p = 0; p < screen_PATTERN__LIMIT; p++) + { + enum { SWATCHES_COLS = 6, SWATCHES_PITCH = 20 }; + int col, row; + + col = p % SWATCHES_COLS; + row = p / SWATCHES_COLS; + + specs[1 + p].bbox = (box_t) BOX_POS_SIZE(12 + col * SWATCHES_PITCH, + 34 + row * SWATCHES_PITCH, + 16, 16); + specs[1 + p].type = wuss_ICON_TYPE_PATTERN; + specs[1 + p].fg = palette_PICO8_DARK_BLUE; + specs[1 + p].bg = palette_PICO8_LIGHT_GREY; + specs[1 + p].pattern = (screen_pattern_t) p; + } + + rc = wuss_icon_create_array(task->window, specs, SWATCHES_NSPECS, NULL); + if (rc != result_OK) + { + wuss_window_close(task->window); + task->window = NULL; + return rc; + } + + return result_OK; +} + +result_t swatches_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) +{ + if (event->kind == wuss_EVENT_CLOSE) + { + wuss_window_close(window); + free(task_data); /* calloc'd per instance by the spawner */ + return result_OK; + } + + return result_OK; +} + +#endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/swatches.h b/libraries/wuss/test/tasks/swatches.h new file mode 100644 index 0000000..c30d7a5 --- /dev/null +++ b/libraries/wuss/test/tasks/swatches.h @@ -0,0 +1,27 @@ +/* wuss/test/tasks/swatches.h -- fill-pattern swatch grid task */ + +#ifndef TASKS_SWATCHES_H +#define TASKS_SWATCHES_H + +#ifdef USE_SDL + +#include "wuss/window.h" + +/* one 16x16 PATTERN icon per built-in screen fill pattern, laid out as a + * grid down a document taller than the window so the swatches scroll + * through it and stay phase-locked while doing so */ +typedef struct swatches_task +{ + wuss_window_t *window; +} +swatches_task_t; + +wuss_event_fn_t swatches_handle; + +/* create the swatches window against the given wuss instance */ +result_t swatches_create(wuss_t *wuss, swatches_task_t *task); + + +#endif /* USE_SDL */ + +#endif /* TASKS_SWATCHES_H */ From 3d437ff33d6ae3eed6e8da1a7271f60604b7858d Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 23:41:16 +0100 Subject: [PATCH 67/79] feat(wuss): add wuss_nearest_colour Find the system-palette entry closest to a given RGB by squared Euclidean distance, ties to the lower index. Also renames the config's furniture-colour struct wuss_palette_t -> wuss_furniture_palette_t and narrows wuss_colour_t to unsigned char. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- CMakeLists.txt | 1 + include/wuss/wuss.h | 21 +++++++++++++---- libraries/wuss/nearest-colour.c | 40 +++++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 libraries/wuss/nearest-colour.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 845d523..3e044bb 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -343,6 +343,7 @@ set(WUSS_CORE_SOURCES libraries/wuss/invalidate.c libraries/wuss/mouse-click.c libraries/wuss/mouse-move.c + libraries/wuss/nearest-colour.c libraries/wuss/redraw.c libraries/wuss/scroll.c libraries/wuss/scroll-step.c diff --git a/include/wuss/wuss.h b/include/wuss/wuss.h index 358b3f8..c1b9c8b 100644 --- a/include/wuss/wuss.h +++ b/include/wuss/wuss.h @@ -98,7 +98,7 @@ wuss_mouse_action_t; /** * An index into a wuss_t's system palette (see wuss_create). Not a colour_t. */ -typedef int wuss_colour_t; +typedef unsigned char wuss_colour_t; /** * Sentinel for wuss_window_create's bg meaning "no automatic background @@ -111,7 +111,7 @@ typedef int wuss_colour_t; * flat colour. Ignored when the library is built with WUSS_FURNITURE off. * Each value is an index into the system palette (see * wuss_create). */ -typedef struct wuss_palette +typedef struct wuss_furniture_palette { struct { @@ -131,7 +131,7 @@ typedef struct wuss_palette } scroll; } -wuss_palette_t; +wuss_furniture_palette_t; /** * Per-window appearance flags, combinable with bitwise OR. @@ -255,7 +255,7 @@ typedef struct wuss_config int titlebar_height; /** Furniture chrome colours. Ignored when WUSS_FURNITURE is off. */ - wuss_palette_t palette; + wuss_furniture_palette_t furniture; /** * Bevelled work-area button edge shades, as indices into the system @@ -345,6 +345,19 @@ bmfont_t *wuss_get_font(const wuss_t *wuss); */ point_t wuss_get_pointer(const wuss_t *wuss); +/** + * Find the system palette entry (see wuss_create) closest to an RGB value, + * by squared Euclidean distance in RGB space. Alpha is ignored. Ties keep + * the lower index. + * + * \param[in] wuss Window manager. + * \param[in] r Red component, 0..255. + * \param[in] g Green component, 0..255. + * \param[in] b Blue component, 0..255. + * \return Palette index, 0..npalette-1. + */ +wuss_colour_t wuss_nearest_colour(const wuss_t *wuss, int r, int g, int b); + /** * Redraw every window, back-to-front, having first painted the configured * backdrop colour (see wuss_config_t::backdrop) behind them, if any. diff --git a/libraries/wuss/nearest-colour.c b/libraries/wuss/nearest-colour.c new file mode 100644 index 0000000..39fe380 --- /dev/null +++ b/libraries/wuss/nearest-colour.c @@ -0,0 +1,40 @@ +/* wuss/nearest-colour.c -- wuss - minimal window manager */ + +#include <assert.h> + +#include "framebuf/pixelfmt.h" + +#include "impl.h" + +wuss_colour_t wuss_nearest_colour(const wuss_t *wuss, int r, int g, int b) +{ + unsigned long best_d; + wuss_colour_t best_i; + int i; + + assert(wuss != NULL); + assert(wuss->npalette > 0); + + best_d = ~0UL; + best_i = 0; + for (i = 0; i < wuss->npalette; i++) + { + unsigned int px; + long dr, dg, db; + unsigned long d; + + /* rgba8888 is 0xAABBGGRR (see pixelfmt.h). */ + px = wuss->palette[i].primary; + dr = r - (long) ( px & 0xFF); + dg = g - (long) ((px >> 8) & 0xFF); + db = b - (long) ((px >> 16) & 0xFF); + d = (unsigned long) (dr * dr + dg * dg + db * db); + if (d < best_d) + { + best_d = d; + best_i = i; + } + } + + return best_i; +} From bdcfd3615f1fb909bf79e0c57d135e8c8c5c964a Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 23:41:23 +0100 Subject: [PATCH 68/79] fix(wuss): draw menu text in the nearest-black palette entry Menu item and rule specs hardcoded fg = palette index 0, which is only black in the default palette; a custom palette with white at index 0 gave white-on-white menu text. Use a new palettecache.black (nearest to RGB 0,0,0), alongside the existing white cache now moved into the same sub-struct. The white-cache loop in create.c is replaced by wuss_nearest_colour, which also fixes its swapped R/B channel read. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/create.c | 35 ++++++--------------------------- libraries/wuss/impl.h | 9 +++++++-- libraries/wuss/menu/menu.c | 6 +++--- libraries/wuss/test/wuss-test.c | 18 ++++++++--------- 4 files changed, 25 insertions(+), 43 deletions(-) diff --git a/libraries/wuss/create.c b/libraries/wuss/create.c index 027e3ec..1675d3b 100644 --- a/libraries/wuss/create.c +++ b/libraries/wuss/create.c @@ -56,7 +56,7 @@ result_t wuss_create(screen_t *scr, wuss_alloc_t al; wuss_t *w; #ifdef WUSS_FURNITURE - wuss_palette_t pal; + wuss_furniture_palette_t pal; wuss_colour_t bg, fg; #endif #if defined(WUSS_FURNITURE) || defined(WUSS_ICONS) @@ -98,33 +98,10 @@ result_t wuss_create(screen_t *scr, memcpy(w->palette, palette, npalette * sizeof(*w->palette)); w->npalette = npalette; - /* Cache the palette index nearest to opaque white, for menu backdrops and - * anything else wanting "paper". Sum-of-squared-channel-distance to white; - * ties keep the first. */ - { - unsigned long best_d; - int i; - - w->white = 0; - best_d = ~0UL; - for (i = 0; i < npalette; i++) - { - unsigned int px; - long dr, dg, db; - unsigned long d; - - px = w->palette[i].primary; - dr = 255 - (long) ((px >> 16) & 0xFF); - dg = 255 - (long) ((px >> 8) & 0xFF); - db = 255 - (long) ( px & 0xFF); - d = (unsigned long) (dr * dr + dg * dg + db * db); - if (d < best_d) - { - best_d = d; - w->white = i; - } - } - } + /* Cache the palette indices nearest to opaque white and black, for menu + * backdrops/text and anything else wanting "paper" or "ink". */ + w->palettecache.white = wuss_nearest_colour(w, 255, 255, 255); + w->palettecache.black = wuss_nearest_colour(w, 0, 0, 0); if (config != NULL) { @@ -140,7 +117,7 @@ result_t wuss_create(screen_t *scr, #ifdef WUSS_FURNITURE if (config != NULL) { - pal = config->palette; + pal = config->furniture; blight = config->bevel.light; bdark = config->bevel.dark; abg = config->accent.bg; diff --git a/libraries/wuss/impl.h b/libraries/wuss/impl.h index 6e3bccb..0c3355d 100644 --- a/libraries/wuss/impl.h +++ b/libraries/wuss/impl.h @@ -66,9 +66,14 @@ struct wuss * block this wuss_t owns */ colour_t *palette; /* owned */ int npalette; - wuss_colour_t white; /* palette index nearest to white */ + struct + { + wuss_colour_t white; /* palette index nearest to white */ + wuss_colour_t black; /* palette index nearest to black */ + } + palettecache; #ifdef WUSS_FURNITURE - wuss_palette_t furniture_colours; + wuss_furniture_palette_t furniture_colours; #endif #if defined(WUSS_FURNITURE) || defined(WUSS_ICONS) wuss_colour_t bevel_light; /* work-area button top/left edge */ diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index ea59c7b..c4b6644 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -342,7 +342,7 @@ static result_t wuss__menu_spawn(wuss_t *wuss, specs[s].bbox.x1 = width; specs[s].bbox.y1 = y + sep_h; specs[s].text = ""; - specs[s].fg = 0; + specs[s].fg = wuss->palettecache.black; specs[s].bg = wuss_NO_BACKGROUND; specs[s].flags = wuss_ICON_FLAGS_NONE; s++; @@ -360,7 +360,7 @@ static result_t wuss__menu_spawn(wuss_t *wuss, specs[s].bbox.x1 = width; specs[s].bbox.y1 = y + pitch; specs[s].text = item->text ? item->text : ""; - specs[s].fg = 0; + specs[s].fg = wuss->palettecache.black; specs[s].bg = wuss_NO_BACKGROUND; specs[s].flags = flags; s++; @@ -386,7 +386,7 @@ static result_t wuss__menu_spawn(wuss_t *wuss, rc = wuss_window_create(wuss, &content, menu->title ? menu->title : "", menu_flags, - wuss_BACKDROP_COLOUR(wuss->white), + wuss_BACKDROP_COLOUR(wuss->palettecache.white), &task, doc, min_doc, &node->window); if (rc != result_OK) { diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index addd00e..29781eb 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -212,15 +212,15 @@ result_t wuss_test(const char *resources) printf("test: wuss_create with bad titlebar colour index\n"); bad_config.titlebar_height = 0; - bad_config.palette.title.bg = 999; - bad_config.palette.title.fg = 0; - bad_config.palette.back = 0; - bad_config.palette.close = 0; - bad_config.palette.toggle = 0; - bad_config.palette.resize = 0; - bad_config.palette.scroll.arrows = 0; - bad_config.palette.scroll.wells = 0; - bad_config.palette.scroll.sausages = 0; + bad_config.furniture.title.bg = 999; + bad_config.furniture.title.fg = 0; + bad_config.furniture.back = 0; + bad_config.furniture.close = 0; + bad_config.furniture.toggle = 0; + bad_config.furniture.resize = 0; + bad_config.furniture.scroll.arrows = 0; + bad_config.furniture.scroll.wells = 0; + bad_config.furniture.scroll.sausages = 0; rc = wuss_create(&scr, NULL, NULL, 0, &bad_config, NULL, &bad_wuss); if (rc != result_WUSS_BAD_COLOUR) goto Failure; From da8633d75cbaa0f9b914a3ff614db7d8a26a3f08 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 23:41:27 +0100 Subject: [PATCH 69/79] refactor(wuss): common out ball invalidation box Three copies of the ball -> window-local invalidation box calc collapse into ball_local_box(); the two click sites pass a degenerate box, idle passes the swept MIN/MAX range. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/test/tasks/ball.c | 53 ++++++++++++++++++++------------ 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/libraries/wuss/test/tasks/ball.c b/libraries/wuss/test/tasks/ball.c index 9923b12..29352ab 100644 --- a/libraries/wuss/test/tasks/ball.c +++ b/libraries/wuss/test/tasks/ball.c @@ -14,14 +14,31 @@ #include "ball.h" -result_t ball_create(wuss_t *wuss, - const colour_t *palette, - ball_task_t *task) +/* Window-local invalidation box covering a ball (or its swept range) whose + * centre spans [vx0,vx1] x [vy0,vy1] in virtual content space. */ +static box_t ball_local_box(int vx0, + int vy0, + int vx1, + int vy1, + int radius, + point_t scroll) +{ + box_t local; + + local.x0 = vx0 - radius - scroll.x; + local.y0 = vy0 - radius - scroll.y; + local.x1 = vx1 + radius - scroll.x; + local.y1 = vy1 + radius - scroll.y; + + return local; +} + +result_t ball_create(wuss_t *wuss, ball_task_t *task) { wuss_task_t delegate; - task->bg = palette[palette_PICO8_RED]; - task->ball = palette[palette_PICO8_WHITE]; + task->bg = colour_rgb(0xFF, 0x00, 0x00); + task->ball = colour_rgb(0xFF, 0xFF, 0xFF); task->nballs = 1; task->balls[0].x = 50; @@ -43,13 +60,13 @@ result_t ball_create(wuss_t *wuss, &task->window); } - static result_t ball_redraw(const wuss_event_t *event, void *task_data) { ball_task_t *bc; screen_t *scr; const box_t *content, *bounds; - int i, sx, sy; + int sx, sy; + int i; bc = task_data; @@ -68,7 +85,10 @@ static result_t ball_redraw(const wuss_event_t *event, void *task_data) b = &bc->balls[i]; - screen_fill_rect(scr, bounds->x0 - sx + b->x - b->radius, bounds->y0 - sy + b->y - b->radius, SIZE2D(b->radius * 2, b->radius * 2), bc->ball); + screen_fill_rect(scr, bounds->x0 - sx + b->x - b->radius, + bounds->y0 - sy + b->y - b->radius, + SIZE2D(b->radius * 2, b->radius * 2), + bc->ball); } return result_OK; @@ -109,10 +129,7 @@ static result_t ball_mouse(wuss_window_t *window, b->dy = (bc->nballs & 2) ? 2 : -2; b->radius = 8; - local.x0 = b->x - b->radius - scroll.x; - local.y0 = b->y - b->radius - scroll.y; - local.x1 = b->x + b->radius - scroll.x; - local.y1 = b->y + b->radius - scroll.y; + local = ball_local_box(b->x, b->y, b->x, b->y, b->radius, scroll); wuss_window_invalidate(bc->window, &local); } else if (button & wuss_BUTTON_ADJUST) @@ -124,10 +141,7 @@ static result_t ball_mouse(wuss_window_t *window, b = &bc->balls[--bc->nballs]; - local.x0 = b->x - b->radius - scroll.x; - local.y0 = b->y - b->radius - scroll.y; - local.x1 = b->x + b->radius - scroll.x; - local.y1 = b->y + b->radius - scroll.y; + local = ball_local_box(b->x, b->y, b->x, b->y, b->radius, scroll); wuss_window_invalidate(bc->window, &local); } @@ -168,10 +182,9 @@ static result_t ball_idle(void *task_data) if (b->y - b->radius < scroll.y) { b->y = scroll.y + b->radius; b->dy = -b->dy; } else if (b->y + b->radius > scroll.y + height){ b->y = scroll.y + height - b->radius; b->dy = -b->dy; } - local.x0 = MIN(old_x, b->x) - b->radius - scroll.x; - local.y0 = MIN(old_y, b->y) - b->radius - scroll.y; - local.x1 = MAX(old_x, b->x) + b->radius - scroll.x; - local.y1 = MAX(old_y, b->y) + b->radius - scroll.y; + local = ball_local_box(MIN(old_x, b->x), MIN(old_y, b->y), + MAX(old_x, b->x), MAX(old_y, b->y), + b->radius, scroll); wuss_window_invalidate(bc->window, &local); } From accb16092fb9e8a1b27977c774919b7024d5e3d5 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 23:42:53 +0100 Subject: [PATCH 70/79] refactor(wuss): drop palette args from test-task constructors The SDL test tasks took palette/npalette just to index a PICO8 lookup for two or three fixed colours; use colour_rgb() literals directly and simplify each *_create signature, with apps/wuss/main.c updated to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- apps/wuss/main.c | 81 +++++++++++++++++---------- libraries/wuss/test/tasks/ball.h | 4 +- libraries/wuss/test/tasks/blank.c | 10 ++-- libraries/wuss/test/tasks/blank.h | 2 +- libraries/wuss/test/tasks/chars.c | 8 +-- libraries/wuss/test/tasks/chars.h | 11 ++-- libraries/wuss/test/tasks/checker.c | 8 +-- libraries/wuss/test/tasks/checker.h | 5 +- libraries/wuss/test/tasks/curve.c | 10 ++-- libraries/wuss/test/tasks/curve.h | 5 +- libraries/wuss/test/tasks/icons.c | 56 +++++++++--------- libraries/wuss/test/tasks/icons.h | 10 ++-- libraries/wuss/test/tasks/image.c | 9 ++- libraries/wuss/test/tasks/image.h | 10 ++-- libraries/wuss/test/tasks/lissajous.c | 8 +-- libraries/wuss/test/tasks/lissajous.h | 4 +- libraries/wuss/test/tasks/sofa.c | 10 ++-- libraries/wuss/test/tasks/sofa.h | 5 +- libraries/wuss/test/tasks/swatches.c | 12 ++-- libraries/wuss/test/tasks/text.c | 13 ++--- libraries/wuss/test/tasks/text.h | 2 - 21 files changed, 140 insertions(+), 143 deletions(-) diff --git a/apps/wuss/main.c b/apps/wuss/main.c index c23e698..11ad04a 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -71,7 +71,7 @@ static result_t spawn_ball(void) ball_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = ball_create(g.wuss, g.palette, t); + rc = ball_create(g.wuss, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -81,7 +81,7 @@ static result_t spawn_text(void) text_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = text_create(g.wuss, g.palette, g.daydream_font, t); + rc = text_create(g.wuss, g.daydream_font, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -91,7 +91,7 @@ static result_t spawn_blank(void) blank_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = blank_create(g.wuss, g.npalette, t); + rc = blank_create(g.wuss, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -101,7 +101,7 @@ static result_t spawn_chars(void) chars_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = chars_create(g.wuss, g.palette, t); + rc = chars_create(g.wuss, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -134,7 +134,7 @@ static result_t spawn_image(void) ninepatch = path_join_filename(g.resources, 3, "resources", "wuss", path_join_leafname("9tile", "png")); - rc = image_create(g.wuss, g.palette, buf, ninepatch, t); + rc = image_create(g.wuss, buf, ninepatch, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -144,7 +144,7 @@ static result_t spawn_checker(void) checker_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = checker_create(g.wuss, g.palette, t); + rc = checker_create(g.wuss, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -154,7 +154,7 @@ static result_t spawn_curve(void) curve_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = curve_create(g.wuss, g.palette, t); + rc = curve_create(g.wuss, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -164,7 +164,7 @@ static result_t spawn_lissajous(void) lissajous_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = lissajous_create(g.wuss, g.palette, t); + rc = lissajous_create(g.wuss, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -174,7 +174,7 @@ static result_t spawn_sofa(void) sofa_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = sofa_create(g.wuss, g.palette, t); + rc = sofa_create(g.wuss, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -194,7 +194,7 @@ static result_t spawn_icons(void) icons_task_t *t = calloc(1, sizeof(*t)); result_t rc; if (t == NULL) return result_OOM; - rc = icons_create(g.wuss, g.palette, g.daydream_font, g.resources, t); + rc = icons_create(g.wuss, g.daydream_font, g.resources, t); if (rc != result_OK || t->window == NULL) { free(t); return rc; } return result_OK; } @@ -421,11 +421,14 @@ static result_t run_wuss(const char *resources) bool garbage_pending; bool pixel_stress_pending; int i; + bool use_wimp16; { - const char *palette_name = getenv("WUSS_PALETTE"); /* "riscos16" selects the RISC OS 16-colour palette; default is PICO-8 */ + /* "riscos16" selects the RISC OS 16-colour palette; default is PICO-8 */ + const char *palette_name = getenv("WUSS_PALETTE"); - if (palette_name != NULL && strcmp(palette_name, "wimp16") == 0) + use_wimp16 = (palette_name != NULL && strcmp(palette_name, "wimp16") == 0); + if (use_wimp16) define_wimp16_palette(palette); else define_pico8_palette(palette); @@ -494,23 +497,43 @@ static result_t run_wuss(const char *resources) { wuss_config_t config; - config.titlebar_height = 0; - config.palette.title.bg = palette_PICO8_DARK_BLUE; - config.palette.title.fg = palette_PICO8_WHITE; - config.palette.back = palette_PICO8_GREEN; - config.palette.close = palette_PICO8_RED; - config.palette.toggle = palette_PICO8_ORANGE; - config.palette.resize = palette_PICO8_LAVENDER; - config.palette.scroll.arrows = palette_PICO8_BLUE; - config.palette.scroll.wells = palette_PICO8_DARK_BLUE; - config.palette.scroll.sausages = palette_PICO8_LIGHT_GREY; - config.bevel.light = palette_PICO8_WHITE; - config.bevel.dark = palette_PICO8_DARK_GREY; - config.accent.bg = palette_PICO8_DARK_BLUE; - config.accent.fg = palette_PICO8_WHITE; - config.backdrop.colour = palette_PICO8_WHITE; - config.backdrop.pattern = screen_PATTERN_DOTS; - config.backdrop.pattern_bg = palette_PICO8_LIGHT_GREY; + if (!use_wimp16) { + config.titlebar_height = 0; + config.furniture.title.bg = palette_PICO8_DARK_BLUE; + config.furniture.title.fg = palette_PICO8_WHITE; + config.furniture.back = palette_PICO8_GREEN; + config.furniture.close = palette_PICO8_RED; + config.furniture.toggle = palette_PICO8_ORANGE; + config.furniture.resize = palette_PICO8_LAVENDER; + config.furniture.scroll.arrows = palette_PICO8_BLUE; + config.furniture.scroll.wells = palette_PICO8_DARK_BLUE; + config.furniture.scroll.sausages = palette_PICO8_LIGHT_GREY; + config.bevel.light = palette_PICO8_WHITE; + config.bevel.dark = palette_PICO8_DARK_GREY; + config.accent.bg = palette_PICO8_DARK_BLUE; + config.accent.fg = palette_PICO8_WHITE; + config.backdrop.colour = palette_PICO8_WHITE; + config.backdrop.pattern = screen_PATTERN_DOTS; + config.backdrop.pattern_bg = palette_PICO8_LIGHT_GREY; + } else { + config.titlebar_height = 0; + config.furniture.title.bg = palette_WIMP16_GREY_75; + config.furniture.title.fg = palette_WIMP16_BLACK; + config.furniture.back = palette_WIMP16_GREEN; + config.furniture.close = palette_WIMP16_RED; + config.furniture.toggle = palette_WIMP16_ORANGE; + config.furniture.resize = palette_WIMP16_LIGHT_BLUE; + config.furniture.scroll.arrows = palette_WIMP16_GREY_50; + config.furniture.scroll.wells = palette_WIMP16_GREY_62; + config.furniture.scroll.sausages = palette_WIMP16_GREY_87; + config.bevel.light = palette_WIMP16_WHITE; + config.bevel.dark = palette_WIMP16_GREY_50; + config.accent.bg = palette_WIMP16_ORANGE; + config.accent.fg = palette_WIMP16_BLACK; + config.backdrop.colour = palette_WIMP16_GREY_50; + config.backdrop.pattern = screen_PATTERN_DOTS; + config.backdrop.pattern_bg = palette_WIMP16_GREY_37; + } rc = wuss_create(&scr, font, palette, NELEMS(palette), &config, NULL, &wuss); diff --git a/libraries/wuss/test/tasks/ball.h b/libraries/wuss/test/tasks/ball.h index 5e54df5..ae373f2 100644 --- a/libraries/wuss/test/tasks/ball.h +++ b/libraries/wuss/test/tasks/ball.h @@ -36,9 +36,7 @@ wuss_event_fn_t ball_handle; /* create the bouncing-ball window against the given wuss instance; "task" is a * per-instance block owned by the window and freed when it closes */ -result_t ball_create(wuss_t *wuss, - const colour_t *palette, - ball_task_t *task); +result_t ball_create(wuss_t *wuss, ball_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/blank.c b/libraries/wuss/test/tasks/blank.c index b97f67a..55d651e 100644 --- a/libraries/wuss/test/tasks/blank.c +++ b/libraries/wuss/test/tasks/blank.c @@ -17,12 +17,12 @@ #define BLANK_CYCLE_FRAMES 30 /* colour advances every half-second at 60fps */ -result_t blank_create(wuss_t *wuss, int npalette, blank_task_t *task) +result_t blank_create(wuss_t *wuss, blank_task_t *task) { wuss_task_t delegate; - task->npalette = npalette; - task->index = palette_PICO8_GREEN; + task->npalette = 16; // TODO: Read max palette index from wuss + task->index = 0; task->frame_count = 0; delegate = wuss_task_start(blank_handle, task); /* wuss fills the content area itself */ @@ -31,7 +31,7 @@ result_t blank_create(wuss_t *wuss, int npalette, blank_task_t *task) SIZE2D(200, 160), NULL, wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE, - wuss_BACKDROP_COLOUR(palette_PICO8_GREEN), + wuss_BACKDROP_COLOUR(task->index), &delegate, SIZE2D(200, 160), SIZE2D(0, 0), @@ -52,7 +52,7 @@ static result_t blank_idle(void *task_data) bc->index = (bc->index + 1) % bc->npalette; rc = wuss_window_set_background(bc->window, - wuss_BACKDROP_COLOUR(bc->index)); + wuss_BACKDROP_COLOUR(bc->index)); if (rc != result_OK) logf_warning("blank_idle: wuss_window_set_background(%d) failed", bc->index); diff --git a/libraries/wuss/test/tasks/blank.h b/libraries/wuss/test/tasks/blank.h index 4473961..3a42f27 100644 --- a/libraries/wuss/test/tasks/blank.h +++ b/libraries/wuss/test/tasks/blank.h @@ -23,7 +23,7 @@ blank_task_t; wuss_event_fn_t blank_handle; /* create the colour-cycling blank window against the given wuss instance */ -result_t blank_create(wuss_t *wuss, int npalette, blank_task_t *task); +result_t blank_create(wuss_t *wuss, blank_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/chars.c b/libraries/wuss/test/tasks/chars.c index 5b12ea8..cf98bec 100644 --- a/libraries/wuss/test/tasks/chars.c +++ b/libraries/wuss/test/tasks/chars.c @@ -21,9 +21,7 @@ #define CHARS_ROWS 8 #define CHARS_PAD 2 -result_t chars_create(wuss_t *wuss, - const colour_t *palette, - chars_task_t *task) +result_t chars_create(wuss_t *wuss, chars_task_t *task) { wuss_task_t delegate; size2d_t sz; @@ -42,8 +40,8 @@ result_t chars_create(wuss_t *wuss, cell_h = font_height + CHARS_PAD * 2; task->font = font; - task->fg = palette[palette_PICO8_BLACK]; - task->bg = palette[palette_PICO8_WHITE]; + task->fg = colour_rgb(0x00, 0x00, 0x00); + task->bg = colour_rgb(0xFF, 0xFF, 0xFF); delegate = wuss_task_start(chars_handle, task); /* chars_redraw paints every cell itself */ sz = SIZE2D(cell_w * CHARS_COLS, cell_h * CHARS_ROWS); diff --git a/libraries/wuss/test/tasks/chars.h b/libraries/wuss/test/tasks/chars.h index 277b669..d43fa12 100644 --- a/libraries/wuss/test/tasks/chars.h +++ b/libraries/wuss/test/tasks/chars.h @@ -13,9 +13,9 @@ * so the whole font can be eyeballed at a glance */ typedef struct chars_task { - wuss_window_t *window; - bmfont_t *font; - colour_t fg, bg; + wuss_window_t *window; + bmfont_t *font; + colour_t fg, bg; } chars_task_t; @@ -23,10 +23,7 @@ wuss_event_fn_t chars_handle; /* create the glyph-grid window against the given wuss instance; does * nothing and returns result_OK if wuss has no system font */ -result_t chars_create(wuss_t *wuss, - const colour_t *palette, - chars_task_t *task); - +result_t chars_create(wuss_t *wuss, chars_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/checker.c b/libraries/wuss/test/tasks/checker.c index f8163a3..7fc5c95 100644 --- a/libraries/wuss/test/tasks/checker.c +++ b/libraries/wuss/test/tasks/checker.c @@ -18,15 +18,13 @@ #define CHECKER_BAND_MIN 1 #define CHECKER_BAND_MAX 32 -result_t checker_create(wuss_t *wuss, - const colour_t *palette, - checker_task_t *task) +result_t checker_create(wuss_t*wuss, checker_task_t *task) { wuss_task_t delegate; result_t rc; - task->black = palette[palette_PICO8_BLACK]; - task->white = palette[palette_PICO8_WHITE]; + task->black = colour_rgb(0x00, 0x00, 0x00); + task->white = colour_rgb(0xFF, 0xFF, 0xFF); task->pattern = checker_PATTERN_CHECKERBOARD; task->pattern2 = checker_PATTERN_VERTICAL; task->band = CHECKER_BAND_DEFAULT; diff --git a/libraries/wuss/test/tasks/checker.h b/libraries/wuss/test/tasks/checker.h index 0793713..9983ecc 100644 --- a/libraries/wuss/test/tasks/checker.h +++ b/libraries/wuss/test/tasks/checker.h @@ -33,10 +33,7 @@ checker_task_t; wuss_event_fn_t checker_handle; /* create the two checkerboard windows against the given wuss instance */ -result_t checker_create(wuss_t *wuss, - const colour_t *palette, - checker_task_t *task); - +result_t checker_create(wuss_t *wuss, checker_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/curve.c b/libraries/wuss/test/tasks/curve.c index d99b430..0f91fb0 100644 --- a/libraries/wuss/test/tasks/curve.c +++ b/libraries/wuss/test/tasks/curve.c @@ -23,15 +23,13 @@ #define CURVE_SEGMENTS_MIN 4 #define CURVE_SEGMENTS_MAX 128 -result_t curve_create(wuss_t *wuss, - const colour_t *palette, - curve_task_t *task) +result_t curve_create(wuss_t *wuss, curve_task_t *task) { wuss_task_t delegate; - task->bg = palette[palette_PICO8_WHITE]; - task->line = palette[palette_PICO8_BLACK]; - task->blob = palette[palette_PICO8_RED]; + task->bg = colour_rgb(0xFF, 0xFF, 0xFF); + task->line = colour_rgb(0x00, 0x00, 0x00); + task->blob = colour_rgb(0xFF, 0x00, 0x00); task->nsegments = CURVE_SEGMENTS_DEFAULT; task->dragging = -1; diff --git a/libraries/wuss/test/tasks/curve.h b/libraries/wuss/test/tasks/curve.h index 3d3ad50..59ce251 100644 --- a/libraries/wuss/test/tasks/curve.h +++ b/libraries/wuss/test/tasks/curve.h @@ -26,10 +26,7 @@ curve_task_t; wuss_event_fn_t curve_handle; /* create the curve window against the given wuss instance */ -result_t curve_create(wuss_t *wuss, - const colour_t *palette, - curve_task_t *task); - +result_t curve_create(wuss_t *wuss, curve_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/icons.c b/libraries/wuss/test/tasks/icons.c index 4adbdb3..e60703a 100644 --- a/libraries/wuss/test/tasks/icons.c +++ b/libraries/wuss/test/tasks/icons.c @@ -24,11 +24,10 @@ #define ICONS_DOC_W 220 #define ICONS_DOC_H 640 /* taller than the window, so scrolling is exercised */ -result_t icons_create(wuss_t *wuss, - const colour_t *palette, - bmfont_t *font, - const char *resources, - icons_task_t *task) +result_t icons_create(wuss_t *wuss, + bmfont_t *font, + const char *resources, + icons_task_t *task) { /* [0..3] heading, counter button, counter label and a scrolled-away button, * then [.. +3] a grouping frame with two differently-justified labels inside @@ -38,6 +37,7 @@ result_t icons_create(wuss_t *wuss, * icons that hover-highlight */ enum { ICONS_NSPECS = 4 + 3 + 5 + 2 + 5 }; wuss_task_t delegate; + wuss_colour_t black, grey5, grey6; wuss_icon_spec_t specs[ICONS_NSPECS]; wuss_icon_t *made[ICONS_NSPECS]; const char *sprite_path; @@ -48,8 +48,8 @@ result_t icons_create(wuss_t *wuss, result_t rc; task->font = font; - task->label = palette[palette_PICO8_DARK_BLUE]; - task->paper = palette[palette_PICO8_LIGHT_GREY]; /* the window bg, below */ + task->label = colour_rgb(0x00, 0x00, 0x00); + task->paper = colour_rgb(0xDD, 0xDD, 0xDD); /* the window bg, below */ task->window = NULL; task->button = NULL; task->counter = NULL; @@ -65,14 +65,18 @@ result_t icons_create(wuss_t *wuss, task->has_sprite = 1; delegate = wuss_task_start(icons_handle, task); + + black = wuss_nearest_colour(wuss, 0x00, 0x00, 0x00); + grey5 = wuss_nearest_colour(wuss, 0xBB, 0xBB, 0xBB); + grey6 = wuss_nearest_colour(wuss, 0xDD, 0xDD, 0xDD); rc = wuss_window_create_placed(wuss, SIZE2D(ICONS_DOC_W, 160), "Icons", wuss_WINDOW_NONE, - wuss_BACKDROP_PATTERN(palette_PICO8_LAVENDER, + wuss_BACKDROP_PATTERN(grey5, screen_PATTERN_CROSSHATCH, - palette_PICO8_LIGHT_GREY), + grey6), &delegate, SIZE2D(ICONS_DOC_W, ICONS_DOC_H), SIZE2D(0, 0), @@ -93,7 +97,7 @@ result_t icons_create(wuss_t *wuss, specs[0].bbox = (box_t) BOX_POS_SIZE(28, 28, 180, 14); specs[0].type = wuss_ICON_TYPE_LABEL; specs[0].text = "Work-area icons:"; - specs[0].fg = palette_PICO8_DARK_BLUE; + specs[0].fg = black; specs[0].bg = wuss_NO_BACKGROUND; /* [1] the button that bumps the counter -- a default action button, so it @@ -101,23 +105,23 @@ result_t icons_create(wuss_t *wuss, specs[1].bbox = (box_t) BOX_POS_SIZE(28, 50, 80, 22); specs[1].type = wuss_ICON_TYPE_BUTTON; specs[1].text = "Press me"; - specs[1].fg = palette_PICO8_BLACK; - specs[1].bg = palette_PICO8_LIGHT_GREY; + specs[1].fg = black; + specs[1].bg = grey6; specs[1].flags = wuss_ICON_FLAGS_DEFAULT; /* [2] the counter label beside it */ specs[2].bbox = (box_t) BOX_POS_SIZE(120, 50, 120, 22); specs[2].type = wuss_ICON_TYPE_LABEL; specs[2].text = "0"; - specs[2].fg = palette_PICO8_DARK_BLUE; + specs[2].fg = black; specs[2].bg = wuss_NO_BACKGROUND; /* [3] a button far down the document, to prove icons scroll and stay clickable */ specs[3].bbox = (box_t) BOX_POS_SIZE(28, 460, 90, 52); specs[3].type = wuss_ICON_TYPE_BUTTON; specs[3].text = "Scrolled"; - specs[3].fg = palette_PICO8_BLACK; - specs[3].bg = palette_PICO8_LIGHT_GREY; + specs[3].fg = black; + specs[3].bg = grey6; /* [g] a grouping frame down the document, with [g+1] a right-justified and * [g+2] a centred label sat inside it */ @@ -126,20 +130,20 @@ result_t icons_create(wuss_t *wuss, specs[g].bbox = (box_t) BOX_POS_SIZE(28, 300, 170, 70); specs[g].type = wuss_ICON_TYPE_FRAME; specs[g].text = "Grouping frame"; - specs[g].fg = palette_PICO8_DARK_BLUE; + specs[g].fg = black; specs[g].bg = wuss_NO_BACKGROUND; specs[g + 1].bbox = (box_t) BOX_POS_SIZE(38, 320, 150, 14); specs[g + 1].type = wuss_ICON_TYPE_LABEL; specs[g + 1].text = "right"; - specs[g + 1].fg = palette_PICO8_DARK_BLUE; + specs[g + 1].fg = black; specs[g + 1].bg = wuss_NO_BACKGROUND; specs[g + 1].flags = wuss_ICON_FLAGS_JUSTIFY_RIGHT; specs[g + 2].bbox = (box_t) BOX_POS_SIZE(38, 342, 150, 14); specs[g + 2].type = wuss_ICON_TYPE_LABEL; specs[g + 2].text = "centre"; - specs[g + 2].fg = palette_PICO8_DARK_BLUE; + specs[g + 2].fg = black; specs[g + 2].bg = wuss_NO_BACKGROUND; specs[g + 2].flags = wuss_ICON_FLAGS_JUSTIFY_CENTRE; @@ -150,7 +154,7 @@ result_t icons_create(wuss_t *wuss, specs[g + 3 + r].bbox = (box_t) BOX_POS_SIZE(28, 380 + r * 20, 150, 16); specs[g + 3 + r].type = wuss_ICON_TYPE_RADIO; specs[g + 3 + r].text = (r == 0) ? "Red" : (r == 1) ? "Green" : "Blue"; - specs[g + 3 + r].fg = palette_PICO8_DARK_BLUE; + specs[g + 3 + r].fg = black; specs[g + 3 + r].bg = wuss_NO_BACKGROUND; specs[g + 3 + r].group = 1; } @@ -158,13 +162,13 @@ result_t icons_create(wuss_t *wuss, specs[g + 6].bbox = (box_t) BOX_POS_SIZE(28, 444, 150, 16); specs[g + 6].type = wuss_ICON_TYPE_OPTION; specs[g + 6].text = "Wireframe"; - specs[g + 6].fg = palette_PICO8_DARK_BLUE; + specs[g + 6].fg = black; specs[g + 6].bg = wuss_NO_BACKGROUND; specs[g + 7].bbox = (box_t) BOX_POS_SIZE(28, 464, 180, 14); specs[g + 7].type = wuss_ICON_TYPE_LABEL; specs[g + 7].text = "(no selection)"; - specs[g + 7].fg = palette_PICO8_DARK_BLUE; + specs[g + 7].fg = black; specs[g + 7].bg = wuss_NO_BACKGROUND; nspecs = g + 8; @@ -195,31 +199,31 @@ result_t icons_create(wuss_t *wuss, specs[m].bbox = (box_t) BOX_POS_SIZE(28, 524, 160, 16); specs[m].type = wuss_ICON_TYPE_MENU_ENTRY; specs[m].text = "Open"; - specs[m].fg = palette_PICO8_DARK_BLUE; + specs[m].fg = black; specs[m].bg = wuss_NO_BACKGROUND; specs[m + 1].bbox = (box_t) BOX_POS_SIZE(28, 542, 160, 16); specs[m + 1].type = wuss_ICON_TYPE_MENU_ENTRY; specs[m + 1].text = "Show grid"; - specs[m + 1].fg = palette_PICO8_DARK_BLUE; + specs[m + 1].fg = black; specs[m + 1].bg = wuss_NO_BACKGROUND; specs[m + 2].bbox = (box_t) BOX_POS_SIZE(28, 560, 160, 16); specs[m + 2].type = wuss_ICON_TYPE_MENU_ENTRY; specs[m + 2].text = "Export"; - specs[m + 2].fg = palette_PICO8_DARK_BLUE; + specs[m + 2].fg = black; specs[m + 2].bg = wuss_NO_BACKGROUND; specs[m + 2].flags = wuss_ICON_FLAGS_SUBMENU; specs[m + 3].bbox = (box_t) BOX_POS_SIZE(28, 578, 160, 10); specs[m + 3].type = wuss_ICON_TYPE_RULE; - specs[m + 3].fg = palette_PICO8_DARK_BLUE; + specs[m + 3].fg = black; specs[m + 3].bg = wuss_NO_BACKGROUND; specs[m + 4].bbox = (box_t) BOX_POS_SIZE(28, 588, 160, 16); specs[m + 4].type = wuss_ICON_TYPE_MENU_ENTRY; specs[m + 4].text = "Quit"; - specs[m + 4].fg = palette_PICO8_DARK_BLUE; + specs[m + 4].fg = black; specs[m + 4].bg = wuss_NO_BACKGROUND; specs[m + 4].flags = wuss_ICON_FLAGS_SEPARATOR; diff --git a/libraries/wuss/test/tasks/icons.h b/libraries/wuss/test/tasks/icons.h index 98ec074..9707ad6 100644 --- a/libraries/wuss/test/tasks/icons.h +++ b/libraries/wuss/test/tasks/icons.h @@ -34,12 +34,10 @@ icons_task_t; wuss_event_fn_t icons_handle; /* create the icons window against the given wuss instance */ -result_t icons_create(wuss_t *wuss, - const colour_t *palette, - bmfont_t *font, - const char *resources, - icons_task_t *task); - +result_t icons_create(wuss_t *wuss, + bmfont_t *font, + const char *resources, + icons_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/image.c b/libraries/wuss/test/tasks/image.c index 16e159b..46b2c3b 100644 --- a/libraries/wuss/test/tasks/image.c +++ b/libraries/wuss/test/tasks/image.c @@ -16,11 +16,10 @@ #define BORDER 16 -result_t image_create(wuss_t *wuss, - const colour_t *palette, - const char *path, - const char *background_path, - image_task_t *task) +result_t image_create(wuss_t *wuss, + const char *path, + const char *background_path, + image_task_t *task) { wuss_task_t delegate; result_t rc; diff --git a/libraries/wuss/test/tasks/image.h b/libraries/wuss/test/tasks/image.h index 7b9ff9c..8943564 100644 --- a/libraries/wuss/test/tasks/image.h +++ b/libraries/wuss/test/tasks/image.h @@ -23,12 +23,10 @@ wuss_event_fn_t image_handle; /* load the PNG at path (and the 9-patch PNG at background_path, drawn tiled * behind it) and create its window against the given wuss instance */ -result_t image_create(wuss_t *wuss, - const colour_t *palette, - const char *path, - const char *background_path, - image_task_t *task); - +result_t image_create(wuss_t *wuss, + const char *path, + const char *background_path, + image_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/lissajous.c b/libraries/wuss/test/tasks/lissajous.c index cae15c9..8c26a67 100644 --- a/libraries/wuss/test/tasks/lissajous.c +++ b/libraries/wuss/test/tasks/lissajous.c @@ -21,14 +21,12 @@ static const int lissajous_freqs[][2] = { 3, 2 }, { 5, 4 }, { 3, 4 }, { 5, 6 }, { 1, 2 }, { 7, 4 } }; -result_t lissajous_create(wuss_t *wuss, - const colour_t *palette, - lissajous_task_t *task) +result_t lissajous_create(wuss_t *wuss, lissajous_task_t *task) { wuss_task_t delegate; - task->bg = palette[palette_PICO8_DARK_BLUE]; - task->fg = palette[palette_PICO8_YELLOW]; + task->bg = colour_rgb(0x00, 0x00, 0x00); + task->fg = colour_rgb(0x00, 0xFF, 0x00); task->freq_index = 0; task->a = lissajous_freqs[0][0]; task->b = lissajous_freqs[0][1]; diff --git a/libraries/wuss/test/tasks/lissajous.h b/libraries/wuss/test/tasks/lissajous.h index d9b85be..36e359a 100644 --- a/libraries/wuss/test/tasks/lissajous.h +++ b/libraries/wuss/test/tasks/lissajous.h @@ -28,9 +28,7 @@ wuss_event_fn_t lissajous_handle; /* create the Lissajous window against the given wuss instance; "task" is a * per-instance block owned by the window and freed when it closes */ -result_t lissajous_create(wuss_t *wuss, - const colour_t *palette, - lissajous_task_t *task); +result_t lissajous_create(wuss_t *wuss, lissajous_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/sofa.c b/libraries/wuss/test/tasks/sofa.c index 266117a..70870bd 100644 --- a/libraries/wuss/test/tasks/sofa.c +++ b/libraries/wuss/test/tasks/sofa.c @@ -354,15 +354,13 @@ static void draw_vertex_dots(screen_t *scr, colour); } -result_t sofa_create(wuss_t *wuss, - const colour_t *palette, - sofa_task_t *task) +result_t sofa_create(wuss_t*wuss, sofa_task_t*task) { wuss_task_t delegate; - task->bg = palette[palette_PICO8_DARK_PURPLE]; - task->line = palette[palette_PICO8_ORANGE]; - task->dot = palette[palette_PICO8_WHITE]; + task->bg = colour_rgb(0x7E, 0x25, 0x53); + task->line = colour_rgb(0xFF, 0xA3, 0x00); + task->dot = colour_rgb(0xFF, 0xFF, 0xFF); task->angle = 0.0; task->zoom = 1.0; task->spinning = true; diff --git a/libraries/wuss/test/tasks/sofa.h b/libraries/wuss/test/tasks/sofa.h index 08aeb6d..280f279 100644 --- a/libraries/wuss/test/tasks/sofa.h +++ b/libraries/wuss/test/tasks/sofa.h @@ -43,10 +43,7 @@ sofa_task_t; wuss_event_fn_t sofa_handle; /* create the sofa window against the given wuss instance */ -result_t sofa_create(wuss_t *wuss, - const colour_t *palette, - sofa_task_t *task); - +result_t sofa_create(wuss_t*wuss, sofa_task_t*task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/swatches.c b/libraries/wuss/test/tasks/swatches.c index 0092d17..b5f436a 100644 --- a/libraries/wuss/test/tasks/swatches.c +++ b/libraries/wuss/test/tasks/swatches.c @@ -26,6 +26,7 @@ result_t swatches_create(wuss_t *wuss, swatches_task_t *task) wuss_task_t delegate; wuss_icon_spec_t specs[SWATCHES_NSPECS]; int p; + wuss_colour_t fg, bg; result_t rc; task->window = NULL; @@ -36,13 +37,16 @@ result_t swatches_create(wuss_t *wuss, swatches_task_t *task) SIZE2D(SWATCHES_DOC_W, 140), "Swatches", wuss_WINDOW_NONE, - wuss_BACKDROP_COLOUR(palette_PICO8_LIGHT_GREY), + wuss_BACKDROP_COLOUR(wuss_nearest_colour(wuss, 0xDD, 0xDD, 0xDD)), &delegate, SIZE2D(SWATCHES_DOC_W, SWATCHES_DOC_H), SIZE2D(0, 0), &task->window); if (rc != result_OK) return rc; + + fg = wuss_nearest_colour(wuss, 0x00, 0x00, 0x00); + bg = wuss_nearest_colour(wuss, 0xFF, 0xFF, 0xFF); memset(specs, 0, sizeof(specs)); @@ -50,7 +54,7 @@ result_t swatches_create(wuss_t *wuss, swatches_task_t *task) specs[0].bbox = (box_t) BOX_POS_SIZE(12, 12, 140, 14); specs[0].type = wuss_ICON_TYPE_LABEL; specs[0].text = "Fill patterns:"; - specs[0].fg = palette_PICO8_DARK_BLUE; + specs[0].fg = fg; specs[0].bg = wuss_NO_BACKGROUND; /* [1..] one 16x16 swatch per built-in fill pattern, laid out as a grid down @@ -68,8 +72,8 @@ result_t swatches_create(wuss_t *wuss, swatches_task_t *task) 34 + row * SWATCHES_PITCH, 16, 16); specs[1 + p].type = wuss_ICON_TYPE_PATTERN; - specs[1 + p].fg = palette_PICO8_DARK_BLUE; - specs[1 + p].bg = palette_PICO8_LIGHT_GREY; + specs[1 + p].fg = fg; + specs[1 + p].bg = bg; specs[1 + p].pattern = (screen_pattern_t) p; } diff --git a/libraries/wuss/test/tasks/text.c b/libraries/wuss/test/tasks/text.c index c079d9f..65a2b23 100644 --- a/libraries/wuss/test/tasks/text.c +++ b/libraries/wuss/test/tasks/text.c @@ -28,18 +28,17 @@ static const char paragraph[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, " "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; -result_t text_create(wuss_t *wuss, - const colour_t *palette, - bmfont_t *font, - text_task_t *task) +result_t text_create(wuss_t *wuss, + bmfont_t *font, + text_task_t *task) { wuss_task_t delegate; size2d_t sz; result_t rc; task->font = font; - task->bg = palette[palette_PICO8_BLUE]; /* matches the "Lorem Ipsum" window's bg, for bmfont_draw's glyph blending */ - task->fg = palette[palette_PICO8_WHITE]; + task->bg = colour_rgb(0xFF, 0xFF, 0xFF); + task->fg = colour_rgb(0x00, 0x00, 0x00); task->frame_count = 0; task->resizing = true; @@ -53,7 +52,7 @@ result_t text_create(wuss_t *wuss, sz, "Lorem Ipsum", wuss_WINDOW_NO_RESIZE_BLIT, /* paragraph reflows across the whole window, so a resize must redraw all of it, not just the newly (un)covered edge */ - wuss_BACKDROP_COLOUR(palette_PICO8_BLUE), + wuss_BACKDROP_COLOUR(wuss_nearest_colour(wuss, 0xFF, 0xFF, 0xFF)), &delegate, sz, SIZE2D(0, 0), diff --git a/libraries/wuss/test/tasks/text.h b/libraries/wuss/test/tasks/text.h index 2b94ae9..f729b78 100644 --- a/libraries/wuss/test/tasks/text.h +++ b/libraries/wuss/test/tasks/text.h @@ -30,11 +30,9 @@ wuss_event_fn_t text_handle; /* create the paragraph-of-text window against the given wuss instance */ result_t text_create(wuss_t *wuss, - const colour_t *palette, bmfont_t *font, text_task_t *task); - #endif /* USE_SDL */ #endif /* TASKS_TEXT_H */ From 5c3bfffa31a16ec5e99f0044a1f8bd7c7e11c8ca Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Tue, 1 Sep 2026 23:49:32 +0100 Subject: [PATCH 71/79] refactor(wuss): table-drive the demo chrome palette, tidy task colour setup run_wuss had two near-identical wuss_config_t fill blocks (PICO-8 vs wimp16) differing only in colour constants; fold them into a 2-row index table so the field wiring exists once. Also hoist the wuss_nearest_colour calls in the icons and swatches tasks above their window-create call and drop stray trailing whitespace. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- apps/wuss/main.c | 76 ++++++++++++++-------------- libraries/wuss/test/tasks/icons.c | 8 +-- libraries/wuss/test/tasks/image.c | 4 +- libraries/wuss/test/tasks/swatches.c | 6 +-- 4 files changed, 46 insertions(+), 48 deletions(-) diff --git a/apps/wuss/main.c b/apps/wuss/main.c index 11ad04a..e400c37 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -495,45 +495,43 @@ static result_t run_wuss(const char *resources) SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); /* keep pixels crisp when F2 scales the window up */ { - wuss_config_t config; - - if (!use_wimp16) { - config.titlebar_height = 0; - config.furniture.title.bg = palette_PICO8_DARK_BLUE; - config.furniture.title.fg = palette_PICO8_WHITE; - config.furniture.back = palette_PICO8_GREEN; - config.furniture.close = palette_PICO8_RED; - config.furniture.toggle = palette_PICO8_ORANGE; - config.furniture.resize = palette_PICO8_LAVENDER; - config.furniture.scroll.arrows = palette_PICO8_BLUE; - config.furniture.scroll.wells = palette_PICO8_DARK_BLUE; - config.furniture.scroll.sausages = palette_PICO8_LIGHT_GREY; - config.bevel.light = palette_PICO8_WHITE; - config.bevel.dark = palette_PICO8_DARK_GREY; - config.accent.bg = palette_PICO8_DARK_BLUE; - config.accent.fg = palette_PICO8_WHITE; - config.backdrop.colour = palette_PICO8_WHITE; - config.backdrop.pattern = screen_PATTERN_DOTS; - config.backdrop.pattern_bg = palette_PICO8_LIGHT_GREY; - } else { - config.titlebar_height = 0; - config.furniture.title.bg = palette_WIMP16_GREY_75; - config.furniture.title.fg = palette_WIMP16_BLACK; - config.furniture.back = palette_WIMP16_GREEN; - config.furniture.close = palette_WIMP16_RED; - config.furniture.toggle = palette_WIMP16_ORANGE; - config.furniture.resize = palette_WIMP16_LIGHT_BLUE; - config.furniture.scroll.arrows = palette_WIMP16_GREY_50; - config.furniture.scroll.wells = palette_WIMP16_GREY_62; - config.furniture.scroll.sausages = palette_WIMP16_GREY_87; - config.bevel.light = palette_WIMP16_WHITE; - config.bevel.dark = palette_WIMP16_GREY_50; - config.accent.bg = palette_WIMP16_ORANGE; - config.accent.fg = palette_WIMP16_BLACK; - config.backdrop.colour = palette_WIMP16_GREY_50; - config.backdrop.pattern = screen_PATTERN_DOTS; - config.backdrop.pattern_bg = palette_WIMP16_GREY_37; - } + /* Furniture/bevel/accent/backdrop colour indices, one row per palette. + * Same field order as the assignments below. */ + static const wuss_colour_t chrome[2][15] = + { + /* PICO-8 */ + { palette_PICO8_DARK_BLUE, palette_PICO8_WHITE, palette_PICO8_GREEN, + palette_PICO8_RED, palette_PICO8_ORANGE, palette_PICO8_LAVENDER, + palette_PICO8_BLUE, palette_PICO8_DARK_BLUE, palette_PICO8_LIGHT_GREY, + palette_PICO8_WHITE, palette_PICO8_DARK_GREY, palette_PICO8_DARK_BLUE, + palette_PICO8_WHITE, palette_PICO8_WHITE, palette_PICO8_LIGHT_GREY }, + /* RISC OS 16-colour Wimp */ + { palette_WIMP16_GREY_75, palette_WIMP16_BLACK, palette_WIMP16_GREEN, + palette_WIMP16_RED, palette_WIMP16_ORANGE, palette_WIMP16_LIGHT_BLUE, + palette_WIMP16_GREY_50, palette_WIMP16_GREY_62, palette_WIMP16_GREY_87, + palette_WIMP16_WHITE, palette_WIMP16_GREY_50, palette_WIMP16_ORANGE, + palette_WIMP16_BLACK, palette_WIMP16_GREY_50, palette_WIMP16_GREY_37 } + }; + const wuss_colour_t *c = chrome[use_wimp16 ? 1 : 0]; + wuss_config_t config; + + config.titlebar_height = 0; + config.furniture.title.bg = c[0]; + config.furniture.title.fg = c[1]; + config.furniture.back = c[2]; + config.furniture.close = c[3]; + config.furniture.toggle = c[4]; + config.furniture.resize = c[5]; + config.furniture.scroll.arrows = c[6]; + config.furniture.scroll.wells = c[7]; + config.furniture.scroll.sausages = c[8]; + config.bevel.light = c[9]; + config.bevel.dark = c[10]; + config.accent.bg = c[11]; + config.accent.fg = c[12]; + config.backdrop.colour = c[13]; + config.backdrop.pattern = screen_PATTERN_DOTS; + config.backdrop.pattern_bg = c[14]; rc = wuss_create(&scr, font, palette, NELEMS(palette), &config, NULL, &wuss); diff --git a/libraries/wuss/test/tasks/icons.c b/libraries/wuss/test/tasks/icons.c index e60703a..97bf3bc 100644 --- a/libraries/wuss/test/tasks/icons.c +++ b/libraries/wuss/test/tasks/icons.c @@ -47,6 +47,10 @@ result_t icons_create(wuss_t *wuss, int nspecs; /* live spec count (sprite icons are optional) */ result_t rc; + black = wuss_nearest_colour(wuss, 0x00, 0x00, 0x00); + grey5 = wuss_nearest_colour(wuss, 0xBB, 0xBB, 0xBB); + grey6 = wuss_nearest_colour(wuss, 0xDD, 0xDD, 0xDD); + task->font = font; task->label = colour_rgb(0x00, 0x00, 0x00); task->paper = colour_rgb(0xDD, 0xDD, 0xDD); /* the window bg, below */ @@ -65,10 +69,6 @@ result_t icons_create(wuss_t *wuss, task->has_sprite = 1; delegate = wuss_task_start(icons_handle, task); - - black = wuss_nearest_colour(wuss, 0x00, 0x00, 0x00); - grey5 = wuss_nearest_colour(wuss, 0xBB, 0xBB, 0xBB); - grey6 = wuss_nearest_colour(wuss, 0xDD, 0xDD, 0xDD); rc = wuss_window_create_placed(wuss, SIZE2D(ICONS_DOC_W, 160), diff --git a/libraries/wuss/test/tasks/image.c b/libraries/wuss/test/tasks/image.c index 46b2c3b..381590d 100644 --- a/libraries/wuss/test/tasks/image.c +++ b/libraries/wuss/test/tasks/image.c @@ -37,7 +37,7 @@ result_t image_create(wuss_t *wuss, } delegate = wuss_task_start(image_handle, task); /* shows through the image's transparent pixels */ - + sz.w = task->bitmap.size.w + BORDER * 2; sz.h = task->bitmap.size.h + BORDER * 2; @@ -72,7 +72,7 @@ static result_t image_redraw(const wuss_event_t *event, void *task_data) by = bounds->y0 - sy + BORDER; #define NINEPATCHSZ 9 - + behind.x0 = bx - NINEPATCHSZ; behind.y0 = by - NINEPATCHSZ; behind.x1 = behind.x0 + ic->bitmap.size.w + NINEPATCHSZ * 2; diff --git a/libraries/wuss/test/tasks/swatches.c b/libraries/wuss/test/tasks/swatches.c index b5f436a..087c506 100644 --- a/libraries/wuss/test/tasks/swatches.c +++ b/libraries/wuss/test/tasks/swatches.c @@ -29,6 +29,9 @@ result_t swatches_create(wuss_t *wuss, swatches_task_t *task) wuss_colour_t fg, bg; result_t rc; + fg = wuss_nearest_colour(wuss, 0x00, 0x00, 0x00); + bg = wuss_nearest_colour(wuss, 0xFF, 0xFF, 0xFF); + task->window = NULL; delegate = wuss_task_start(swatches_handle, task); @@ -44,9 +47,6 @@ result_t swatches_create(wuss_t *wuss, swatches_task_t *task) &task->window); if (rc != result_OK) return rc; - - fg = wuss_nearest_colour(wuss, 0x00, 0x00, 0x00); - bg = wuss_nearest_colour(wuss, 0xFF, 0xFF, 0xFF); memset(specs, 0, sizeof(specs)); From 259c0d51445397552f7cb3709af590867ce30c98 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Wed, 2 Sep 2026 00:00:19 +0100 Subject: [PATCH 72/79] fix(wuss): don't blit stale pixels when scrolls arrive faster than redraws A fast wheel spin delivers several wuss_scroll calls before wuss_redraw_dirty runs. wuss_window_set_scroll's incremental blit is a framebuffer memmove, so a second scroll in the same frame would slide the strip the first scroll exposed -- still marked dirty, not yet repainted -- into the window interior, smearing stale content there permanently. Before the blit, collect the pending dirty rects that overlap the content box (sampled ahead of the furniture invalidate so its unrelated rects are excluded) and subtract them from every clean blit-source piece. The stale areas are no longer blitted; they fall through to the normal content-minus-copied repaint instead. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/window/set-scroll.c | 43 ++++++++++++++++++++++++++++-- 1 file changed, 41 insertions(+), 2 deletions(-) diff --git a/libraries/wuss/window/set-scroll.c b/libraries/wuss/window/set-scroll.c index df1552a..721d23f 100644 --- a/libraries/wuss/window/set-scroll.c +++ b/libraries/wuss/window/set-scroll.c @@ -5,6 +5,8 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p) { box_t content; + box_t stale[WUSS_MAX_DIRTY]; + box_t clean[WUSS_MAX_INVALIDATE_PIECES]; box_t src[WUSS_MAX_INVALIDATE_PIECES]; box_t blit_src[WUSS_MAX_INVALIDATE_PIECES]; box_t blit_dest[WUSS_MAX_INVALIDATE_PIECES]; @@ -12,7 +14,7 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p) box_t dirty[WUSS_MAX_INVALIDATE_PIECES]; box_t vis[WUSS_MAX_INVALIDATE_PIECES]; int order[WUSS_MAX_INVALIDATE_PIECES]; - int dx, dy, nsrc, nblit, ncopied, ndirty, nvis, i, j, idx; + int dx, dy, nstale, nclean, nsrc, nblit, ncopied, ndirty, nvis, i, j, idx; int overflow, blit_failed; dx = p.x - window->scroll.x; @@ -24,6 +26,18 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p) wuss__content_box(window, &content); + /* Any content-box area already sitting in the dirty list holds stale pixels + * (e.g. the strip exposed by an earlier scroll this frame, not yet + * redrawn): a wheel spin can deliver several scrolls before wuss_redraw_dirty + * runs, and the blit below is a framebuffer memmove -- feeding it a stale + * source would smear that stale content into the interior. Collect those + * regions now, before the furniture invalidate adds its own unrelated + * rects, and exclude them from the blit source further down. */ + nstale = 0; + for (i = 0; i < window->wuss->ndirty; i++) + if (box_intersects(&window->wuss->dirty[i], &content)) + stale[nstale++] = window->wuss->dirty[i]; + #ifdef WUSS_FURNITURE /* the scrollbar sausage position depends on scroll, so its well needs * redrawing too -- content invalidation alone never touches it */ @@ -37,9 +51,34 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p) * clips to the content box, so it would paint over the occluder there. * Clip each destination against the occluders too and keep only the * surviving sub-pieces, each with its own matching source offset. */ - nsrc = wuss__clip_to_visible(window, &content, src); + nclean = wuss__clip_to_visible(window, &content, clean); nblit = 0; overflow = 0; + + /* Carve the stale regions out of each clean source piece. Each subtract + * can split a piece into up to four bands, so this can overflow the piece + * budget on a badly fragmented window -- treat that as "no safe fast + * path" and let the blit_failed branch below invalidate what's left. */ + nsrc = 0; + for (i = 0; i < nclean; i++) + { + box_t kept[WUSS_MAX_INVALIDATE_PIECES]; + int nkept, k; + + nkept = wuss__subtract_boxes(&clean[i], stale, nstale, kept); + for (k = 0; k < nkept; k++) + { + if (nsrc == WUSS_MAX_INVALIDATE_PIECES) + { + overflow = 1; + break; + } + src[nsrc++] = kept[k]; + } + if (overflow) + break; + } + for (i = 0; i < nsrc && !overflow; i++) { box_t want; From 0ec6602db88cbad339fa2389372ec4967b5a7776 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Wed, 2 Sep 2026 00:20:04 +0100 Subject: [PATCH 73/79] feat(wuss): swap the system palette mid-session wuss_set_palette copies a new palette over the old, refreshes the cached nearest-black/white indices, broadcasts a new wuss_EVENT_PALETTE to every window's task so they can recache wuss_nearest_colour selections, then invalidates the whole screen. Length must match the palette given to wuss_create. The swatches test task rebuilds its swatch icons on wuss_EVENT_PALETTE; the interactive wuss driver cycles PICO-8 <-> Wimp16 on F4, pushing the new palette into the framebuffer bitmap and then into wuss. Also add bitmap_set_palette, which reuses an already-allocated palette buffer, and refactor bitmap_init to build its palette through it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- CMakeLists.txt | 1 + apps/wuss/main.c | 28 +++++++--- include/framebuf/bitmap.h | 13 +++++ include/wuss/task.h | 8 +-- include/wuss/wuss.h | 25 +++++++++ libraries/framebuf/bitmap/bitmap.c | 48 +++++++++++------ libraries/wuss/set-palette.c | 56 ++++++++++++++++++++ libraries/wuss/test/tasks/swatches.c | 78 ++++++++++++++++++---------- libraries/wuss/test/tasks/swatches.h | 6 +++ libraries/wuss/test/wuss-test.c | 32 ++++++++++++ 10 files changed, 244 insertions(+), 51 deletions(-) create mode 100644 libraries/wuss/set-palette.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e044bb..d133fc8 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -347,6 +347,7 @@ set(WUSS_CORE_SOURCES libraries/wuss/redraw.c libraries/wuss/scroll.c libraries/wuss/scroll-step.c + libraries/wuss/set-palette.c libraries/wuss/task/start.c libraries/wuss/task/stop.c libraries/wuss/window/at.c diff --git a/apps/wuss/main.c b/apps/wuss/main.c index e400c37..e3162a9 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -368,6 +368,13 @@ static wuss_button_t sdl_button_to_wuss(Uint8 button) } } +/* Palettes F4 cycles through, in order. Each fills a colour_t[16]. */ +static void (*const g_palettes[])(colour_t *) = +{ + define_pico8_palette, + define_wimp16_palette +}; + /* SDL delivers mouse coordinates in window space, which F2 can scale away * from the fixed-size Wuss screen; map back down to screen space */ static void sdl_pos_to_scr(SDL_Window *window, @@ -389,8 +396,8 @@ static void sdl_pos_to_scr(SDL_Window *window, /* click windows to bring to front, drag titlebars to move, resize the * SDL window to see the Wuss screen scale; F2 doubles the SDL window size, * Shift-F2 halves it; F3 redraws the whole screen one pixel at a time, to - * catch tasks whose drawing routines misbehave under a 1x1 clip; Q or - * close to quit */ + * catch tasks whose drawing routines misbehave under a 1x1 clip; F4 cycles + * the system palette live (wuss_set_palette); Q or close to quit */ static result_t run_wuss(const char *resources) { const int scr_width = 640; @@ -422,16 +429,15 @@ static result_t run_wuss(const char *resources) bool pixel_stress_pending; int i; bool use_wimp16; + int palette_index; { /* "riscos16" selects the RISC OS 16-colour palette; default is PICO-8 */ const char *palette_name = getenv("WUSS_PALETTE"); use_wimp16 = (palette_name != NULL && strcmp(palette_name, "wimp16") == 0); - if (use_wimp16) - define_wimp16_palette(palette); - else - define_pico8_palette(palette); + palette_index = use_wimp16 ? 1 : 0; + g_palettes[palette_index](palette); } leafname = path_join_leafname("digits", "png"); @@ -573,6 +579,16 @@ static result_t run_wuss(const char *resources) garbage_pending = true; else if (event.key.key == SDLK_F3) pixel_stress_pending = true; + else if (event.key.key == SDLK_F4) + { + /* cycle the system palette live: rebuild the palette, push it into + * the framebuffer bitmap, then tell wuss, which refreshes its own + * copy and pokes every task to recache */ + palette_index = (palette_index + 1) % (int) NELEMS(g_palettes); + g_palettes[palette_index](palette); + bitmap_set_palette(&bm, palette); + wuss_set_palette(wuss, palette, NELEMS(palette)); + } else if (event.key.key == SDLK_F2) { int w, h; diff --git a/include/framebuf/bitmap.h b/include/framebuf/bitmap.h index 2fecfd6..a11361e 100644 --- a/include/framebuf/bitmap.h +++ b/include/framebuf/bitmap.h @@ -47,6 +47,19 @@ result_t bitmap_init(bitmap_t *bm, const colour_t *palette, void *base); +/** + * Replace a bitmap's palette, reusing its existing palette buffer when one + * is already allocated and it is large enough. A no-op returning \ref + * result_OK for formats with no palette (log2bpp > 3). + * + * \param[in] bm Bitmap to repalette. + * \param[in] palette New palette, copied in, or NULL to drop the bitmap's + * palette entirely. + * \return \ref result_OK on success, \ref result_OOM if a palette buffer + * could not be allocated (the bitmap keeps its old palette). + */ +result_t bitmap_set_palette(bitmap_t *bm, const colour_t *palette); + /** * Clear the given bitmap to the specified colour. * diff --git a/include/wuss/task.h b/include/wuss/task.h index 37e4159..b41e650 100644 --- a/include/wuss/task.h +++ b/include/wuss/task.h @@ -37,7 +37,9 @@ typedef enum wuss_event_kind wuss_EVENT_MOUSE, /**< Button down/up over the window's content. */ wuss_EVENT_ICON, /**< A work-area button icon was clicked or hovered. */ wuss_EVENT_SCROLL, /**< Mouse wheel used over the window's content. */ - wuss_EVENT_QUIT /**< Task shutting down, via wuss_task_stop. */ + wuss_EVENT_QUIT, /**< Task shutting down, via wuss_task_stop. */ + wuss_EVENT_PALETTE /**< System palette changed, via wuss_set_palette; + recache any wuss_nearest_colour selections. */ } wuss_event_kind_t; @@ -114,8 +116,8 @@ typedef struct wuss_event } scroll; - /* wuss_EVENT_IDLE, wuss_EVENT_CLOSE, wuss_EVENT_QUIT and - * wuss_EVENT_OPEN carry no data. */ + /* wuss_EVENT_IDLE, wuss_EVENT_CLOSE, wuss_EVENT_QUIT, + * wuss_EVENT_OPEN and wuss_EVENT_PALETTE carry no data. */ } data; } diff --git a/include/wuss/wuss.h b/include/wuss/wuss.h index c1b9c8b..1eb2a89 100644 --- a/include/wuss/wuss.h +++ b/include/wuss/wuss.h @@ -318,6 +318,31 @@ result_t wuss_create(screen_t *scr, const wuss_alloc_t *alloc, wuss_t **wuss); +/** + * Replace the system palette partway through a session. + * + * Copies \p palette in over the existing one (same semantics as + * wuss_create's palette argument), refreshes the cached nearest-black / + * nearest-white indices, broadcasts a \ref wuss_EVENT_PALETTE event to every + * window's task so they can recache any wuss_nearest_colour selections, then + * invalidates the whole screen. The caller is still responsible for the next + * wuss_redraw / wuss_redraw_dirty, and -- on a paletted screen -- for + * updating the screen bitmap's own palette to match. + * + * \param[in] wuss Window manager. + * \param[in] palette New system palette, copied in. + * \param[in] npalette Number of entries in palette. Must equal the count + * passed to wuss_create. + * \return \ref result_OK on success, \ref result_BAD_ARG if npalette does + * not match the current palette length, \ref result_WUSS_BAD_COLOUR + * if a configured furniture/bevel/backdrop colour index is now out + * of range (in which case the palette is left unchanged), else the + * first non-OK result returned by a task's handle callback. + */ +result_t wuss_set_palette(wuss_t *wuss, + const colour_t *palette, + int npalette); + /** * Destroy a window manager, and any windows still open on it. * diff --git a/libraries/framebuf/bitmap/bitmap.c b/libraries/framebuf/bitmap/bitmap.c index c8004b0..139d70a 100644 --- a/libraries/framebuf/bitmap/bitmap.c +++ b/libraries/framebuf/bitmap/bitmap.c @@ -15,8 +15,6 @@ result_t bitmap_init(bitmap_t *bm, const colour_t *palette, void *base) { - int log2bpp; - assert(bm); bm->size = size; @@ -26,25 +24,43 @@ result_t bitmap_init(bitmap_t *bm, bm->span = spanregistry_get(fmt); bm->base = base; - if (palette) - { - log2bpp = pixelfmt_log2bpp(fmt); - if (log2bpp <= 3) - { - int nentries; - colour_t *newpal; + return bitmap_set_palette(bm, palette); +} - nentries = 1 << (1 << log2bpp); - newpal = malloc(nentries * sizeof(*newpal)); - if (newpal == NULL) - return result_OOM; +result_t bitmap_set_palette(bitmap_t *bm, const colour_t *palette) +{ + int log2bpp; + int nentries; + colour_t *pal; - memcpy(newpal, palette, nentries * sizeof(*newpal)); + assert(bm); - bm->palette = newpal; - } + log2bpp = pixelfmt_log2bpp(bm->format); + if (log2bpp > 3) + return result_OK; /* no palette for this format */ + + if (palette == NULL) + { + free(bm->palette); + bm->palette = NULL; + return result_OK; } + nentries = 1 << (1 << log2bpp); + + /* the entry count is fixed by the format, so an existing buffer is always + * the right size to reuse */ + pal = bm->palette; + if (pal == NULL) + { + pal = malloc(nentries * sizeof(*pal)); + if (pal == NULL) + return result_OOM; + } + + memcpy(pal, palette, nentries * sizeof(*pal)); + bm->palette = pal; + return result_OK; } diff --git a/libraries/wuss/set-palette.c b/libraries/wuss/set-palette.c new file mode 100644 index 0000000..6a550a7 --- /dev/null +++ b/libraries/wuss/set-palette.c @@ -0,0 +1,56 @@ +/* wuss/set-palette.c -- swap the system palette mid-session */ + +#include <assert.h> +#include <string.h> + +#include "geom/box.h" + +#include "impl.h" + +result_t wuss_set_palette(wuss_t *wuss, + const colour_t *palette, + int npalette) +{ + wuss_event_t event; + box_t screen; + result_t rc; + list_t *e; + + assert(wuss != NULL); + assert(palette != NULL); + + /* Same length only: keeps every stored furniture/bevel/backdrop colour + * index in range, and matches the fixed-size framebuffer palette on a + * paletted screen. */ + if (npalette != wuss->npalette) + return result_BAD_ARG; + + memcpy(wuss->palette, palette, npalette * sizeof(*wuss->palette)); + + wuss->palettecache.white = wuss_nearest_colour(wuss, 255, 255, 255); + wuss->palettecache.black = wuss_nearest_colour(wuss, 0, 0, 0); + + rc = result_OK; + event.kind = wuss_EVENT_PALETTE; + for (e = wuss->z_order.next; e != NULL; e = e->next) + { + wuss_window_t *win; + result_t crc; + + win = (wuss_window_t *) e; + if (win->task.handle == NULL) + continue; + + crc = win->task.handle(win, &event, win->task.task_data); + if (crc != result_OK && rc == result_OK) + rc = crc; + } + + screen.x0 = 0; + screen.y0 = 0; + screen.x1 = wuss->scr->size.w; + screen.y1 = wuss->scr->size.h; + wuss_invalidate(wuss, &screen); + + return rc; +} diff --git a/libraries/wuss/test/tasks/swatches.c b/libraries/wuss/test/tasks/swatches.c index 087c506..22cdd12 100644 --- a/libraries/wuss/test/tasks/swatches.c +++ b/libraries/wuss/test/tasks/swatches.c @@ -20,33 +20,23 @@ #define SWATCHES_DOC_W 160 #define SWATCHES_DOC_H 320 /* taller than the window, so scrolling is exercised */ -result_t swatches_create(wuss_t *wuss, swatches_task_t *task) +/* Delete the current swatch icons and lay a fresh set out, resolving the + * heading and swatch colours through the palette as it stands now. Called at + * create time and again on wuss_EVENT_PALETTE. */ +static result_t swatches_build(swatches_task_t *task) { - enum { SWATCHES_NSPECS = 1 + screen_PATTERN__LIMIT }; - wuss_task_t delegate; wuss_icon_spec_t specs[SWATCHES_NSPECS]; - int p; wuss_colour_t fg, bg; - result_t rc; - - fg = wuss_nearest_colour(wuss, 0x00, 0x00, 0x00); - bg = wuss_nearest_colour(wuss, 0xFF, 0xFF, 0xFF); - - task->window = NULL; + int p; - delegate = wuss_task_start(swatches_handle, task); + for (p = 0; p < SWATCHES_NSPECS; p++) + { + wuss_icon_delete(task->icons[p]); + task->icons[p] = NULL; + } - rc = wuss_window_create_placed(wuss, - SIZE2D(SWATCHES_DOC_W, 140), - "Swatches", - wuss_WINDOW_NONE, - wuss_BACKDROP_COLOUR(wuss_nearest_colour(wuss, 0xDD, 0xDD, 0xDD)), - &delegate, - SIZE2D(SWATCHES_DOC_W, SWATCHES_DOC_H), - SIZE2D(0, 0), - &task->window); - if (rc != result_OK) - return rc; + fg = wuss_nearest_colour(task->wuss, 0x00, 0x00, 0x00); + bg = wuss_nearest_colour(task->wuss, 0xFF, 0xFF, 0xFF); memset(specs, 0, sizeof(specs)); @@ -77,7 +67,34 @@ result_t swatches_create(wuss_t *wuss, swatches_task_t *task) specs[1 + p].pattern = (screen_pattern_t) p; } - rc = wuss_icon_create_array(task->window, specs, SWATCHES_NSPECS, NULL); + return wuss_icon_create_array(task->window, specs, SWATCHES_NSPECS, + task->icons); +} + +result_t swatches_create(wuss_t *wuss, swatches_task_t *task) +{ + wuss_task_t delegate; + result_t rc; + + task->wuss = wuss; + task->window = NULL; + memset(task->icons, 0, sizeof(task->icons)); + + delegate = wuss_task_start(swatches_handle, task); + + rc = wuss_window_create_placed(wuss, + SIZE2D(SWATCHES_DOC_W, 140), + "Swatches", + wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_nearest_colour(wuss, 0xDD, 0xDD, 0xDD)), + &delegate, + SIZE2D(SWATCHES_DOC_W, SWATCHES_DOC_H), + SIZE2D(0, 0), + &task->window); + if (rc != result_OK) + return rc; + + rc = swatches_build(task); if (rc != result_OK) { wuss_window_close(task->window); @@ -92,14 +109,23 @@ result_t swatches_handle(wuss_window_t *window, const wuss_event_t *event, void *task_data) { - if (event->kind == wuss_EVENT_CLOSE) + swatches_task_t *task; + + task = task_data; + + switch (event->kind) { + case wuss_EVENT_PALETTE: + return swatches_build(task); + + case wuss_EVENT_CLOSE: wuss_window_close(window); free(task_data); /* calloc'd per instance by the spawner */ return result_OK; - } - return result_OK; + default: + return result_OK; + } } #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/swatches.h b/libraries/wuss/test/tasks/swatches.h index c30d7a5..96253d6 100644 --- a/libraries/wuss/test/tasks/swatches.h +++ b/libraries/wuss/test/tasks/swatches.h @@ -5,14 +5,20 @@ #ifdef USE_SDL +#include "framebuf/screen.h" +#include "wuss/icon.h" #include "wuss/window.h" /* one 16x16 PATTERN icon per built-in screen fill pattern, laid out as a * grid down a document taller than the window so the swatches scroll * through it and stay phase-locked while doing so */ +enum { SWATCHES_NSPECS = 1 + screen_PATTERN__LIMIT }; + typedef struct swatches_task { + wuss_t *wuss; /* for re-resolving swatch colours on a palette swap */ wuss_window_t *window; + wuss_icon_t *icons[SWATCHES_NSPECS]; /* current icons, deleted on rebuild */ } swatches_task_t; diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index 29781eb..3de1243 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -46,6 +46,7 @@ typedef struct test_task int close_count; int stop_count; int open_count; + int palette_count; } test_task_t; @@ -90,6 +91,10 @@ static result_t test_handle(wuss_window_t *window, tc->open_count++; break; + case wuss_EVENT_PALETTE: + tc->palette_count++; + break; + default: break; } @@ -3149,6 +3154,33 @@ result_t wuss_test(const char *resources) wuss_window_close(win_p[k]); } + printf("test: wuss_set_palette broadcasts wuss_EVENT_PALETTE and rejects a length mismatch\n"); + { + /* the wuss under test was made with a 2-entry palette (see top of this + * function); a fresh 2-entry palette must broadcast to every task, a + * wrong-length one must be refused without broadcasting */ + static const colour_t swapped[2] = { { 0xFF202020 }, { 0xFFE0E0E0 } }; + static const colour_t too_long[3] = + { { 0xFF000000 }, { 0xFF808080 }, { 0xFFFFFFFF } }; + + tc_a.palette_count = 0; + tc_b.palette_count = 0; + + rc = wuss_set_palette(wuss, swapped, NELEMS(swapped)); + if (rc != result_OK) + goto Failure; + if (tc_a.palette_count != 1 || tc_b.palette_count != 1) + goto Failure; + + rc = wuss_set_palette(wuss, too_long, NELEMS(too_long)); + if (rc != result_BAD_ARG) + goto Failure; + if (tc_a.palette_count != 1 || tc_b.palette_count != 1) + goto Failure; /* rejected call must not have broadcast */ + + rc = result_OK; + } + printf("test: wuss_task_stop sends wuss_EVENT_QUIT to each window's task\n"); tc_a.stop_count = 0; From fe6e0834bfc82849fe379b270bcde07d18dbd749 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Wed, 2 Sep 2026 00:39:22 +0100 Subject: [PATCH 74/79] refactor(wuss): sort the launch menu tasks by name Alphabetise the 14 spawn tasks in g_task_items and reorder g_task_spawn to match. The trailing Menu, Menu (desc) and Quit Wuss entries stay pinned below the separator. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- apps/wuss/main.c | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/apps/wuss/main.c b/apps/wuss/main.c index e3162a9..9103cb3 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -309,19 +309,19 @@ typedef result_t (*task_spawn_fn_t)(void); static const wuss_menu_item_t g_task_items[] = { { "Ball", wuss_MENU_ITEM_NONE, NULL }, - { "Text", wuss_MENU_ITEM_NONE, NULL }, { "Blank", wuss_MENU_ITEM_NONE, NULL }, { "Chars", wuss_MENU_ITEM_NONE, NULL }, - { "Palette", wuss_MENU_ITEM_NONE, NULL }, - { "Image", wuss_MENU_ITEM_NONE, NULL }, { "Checker", wuss_MENU_ITEM_NONE, NULL }, { "Curve", wuss_MENU_ITEM_NONE, NULL }, - { "Lissajous", wuss_MENU_ITEM_NONE, NULL }, - { "Sofa", wuss_MENU_ITEM_NONE, NULL }, { "Gradient", wuss_MENU_ITEM_NONE, NULL }, { "Icons", wuss_MENU_ITEM_NONE, NULL }, - { "Swatches", wuss_MENU_ITEM_NONE, NULL }, + { "Image", wuss_MENU_ITEM_NONE, NULL }, + { "Lissajous", wuss_MENU_ITEM_NONE, NULL }, + { "Palette", wuss_MENU_ITEM_NONE, NULL }, { "Porter-Duff", wuss_MENU_ITEM_NONE, NULL }, + { "Sofa", wuss_MENU_ITEM_NONE, NULL }, + { "Swatches", wuss_MENU_ITEM_NONE, NULL }, + { "Text", wuss_MENU_ITEM_NONE, NULL }, { "Menu", wuss_MENU_ITEM_DASHED, NULL }, { "Menu (desc)", wuss_MENU_ITEM_NONE, NULL }, { "Quit Wuss", wuss_MENU_ITEM_DASHED, NULL } @@ -335,9 +335,9 @@ static result_t spawn_quit(void) static const task_spawn_fn_t g_task_spawn[] = { - spawn_ball, spawn_text, spawn_blank, spawn_chars, spawn_palette, - spawn_image, spawn_checker, spawn_curve, spawn_lissajous, spawn_sofa, - spawn_gradient, spawn_icons, spawn_swatches, spawn_porter_duff, spawn_menu, + spawn_ball, spawn_blank, spawn_chars, spawn_checker, spawn_curve, + spawn_gradient, spawn_icons, spawn_image, spawn_lissajous, spawn_palette, + spawn_porter_duff, spawn_sofa, spawn_swatches, spawn_text, spawn_menu, spawn_menu_desc, spawn_quit }; From 27df2fd265f0d9c372909c81746518ed0fa0a57d Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Wed, 2 Sep 2026 01:00:52 +0100 Subject: [PATCH 75/79] fix(wuss): ignore the mouse-up that follows a menu-opening press A menu is opened from a task's MOUSE_DOWN handler. The matching MOUSE_UP was landing on the fresh menu's row 0 and immediately picking it. Add a wuss_t.menu_eat_up flag set by wuss_menu_open and consumed by the next MOUSE_UP in wuss_mouse_click; any fresh MOUSE_DOWN clears it. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- libraries/wuss/create.c | 1 + libraries/wuss/impl.h | 4 ++++ libraries/wuss/menu/menu.c | 7 +++++++ libraries/wuss/mouse-click.c | 13 +++++++++++++ 4 files changed, 25 insertions(+) diff --git a/libraries/wuss/create.c b/libraries/wuss/create.c index 1675d3b..d7040f9 100644 --- a/libraries/wuss/create.c +++ b/libraries/wuss/create.c @@ -230,6 +230,7 @@ result_t wuss_create(screen_t *scr, #endif #ifdef WUSS_MENUS w->menu_chain = NULL; + w->menu_eat_up = 0; #endif w->ndirty = 0; diff --git a/libraries/wuss/impl.h b/libraries/wuss/impl.h index 0c3355d..8c85732 100644 --- a/libraries/wuss/impl.h +++ b/libraries/wuss/impl.h @@ -116,6 +116,10 @@ struct wuss * menu chain, NULL when none open; * consulted by mouse-click.c to * dismiss on a click-away */ + int menu_eat_up; /* a menu opened on the last + * MOUSE_DOWN; swallow its matching + * MOUSE_UP so the release does not + * immediately pick row 0 */ #endif }; diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index c4b6644..2bd068d 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -460,6 +460,13 @@ result_t wuss_menu_open(wuss_t *wuss, return rc; wuss->menu_chain = root; + + /* A menu is opened from a task's MOUSE_DOWN handler; the matching MOUSE_UP is + * still to come and would land on the fresh menu's row 0. Mark it to be + * eaten. wuss_mouse_click clears this on the next MOUSE_UP whether or not it + * hit the menu. */ + wuss->menu_eat_up = 1; + if (out != NULL) *out = root; return result_OK; diff --git a/libraries/wuss/mouse-click.c b/libraries/wuss/mouse-click.c index 91c4ef6..68037d5 100644 --- a/libraries/wuss/mouse-click.c +++ b/libraries/wuss/mouse-click.c @@ -52,6 +52,19 @@ result_t wuss_mouse_click(wuss_t *wuss, *hit = win; #ifdef WUSS_MENUS + /* The MOUSE_UP that follows the MENU press which opened the chain is spent: + * without this it would land on the new menu's row 0 and pick it. */ + if (action == wuss_MOUSE_UP && wuss->menu_eat_up) + { + wuss->menu_eat_up = 0; + return result_OK; + } + + /* Any fresh press ends the eat-up window: the release it pairs with is a + * real click, not the tail of the menu-opening press. */ + if (action == wuss_MOUSE_DOWN) + wuss->menu_eat_up = 0; + /* A press outside every window in the open menu chain dismisses the chain * and is spent doing so. Presses inside the chain fall through to the menu * window's own delegate. */ From b48fa2802c6c23eae0c2dc3acf8a08691563976b Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Wed, 2 Sep 2026 01:08:30 +0100 Subject: [PATCH 76/79] docs(wuss): changelog the menu mouse-up fix Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22d033b..285ff16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,8 @@ _Unreleased_ until one is cut. ### Fixed +- Opening a menu from a task's mouse-down handler no longer picks the menu's + first row on the matching mouse-up: that release is now swallowed. - Dragging a scrollbar well with Select no longer raises the window; only a resize-icon grab restacks it. - `wuss_window_move()` no longer repaints already-blitted pixels when a drag From 75042220b8420e59661a1b2f23e85ea9c55683e8 Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Wed, 2 Sep 2026 01:11:25 +0100 Subject: [PATCH 77/79] docs: fill in CHANGELOG for changes since 671cb53d Menus (wuss/menu.h + descriptor parser), mid-session palette swap, wuss_nearest_colour, pluggable allocator, bmtext, the extended icon-type set, screen polyline/outline/dashed-line primitives, 8x8 Bayer dither, the Wimp16 palette, plus the draw/fill primitive renames and assorted scroll-redraw fixes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- CHANGELOG.md | 85 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 80 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 285ff16..30b41f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,14 +10,64 @@ _Unreleased_ until one is cut. ### Added -- `wuss/icon.h` — work-area icons: static labels and clickable bevelled - buttons drawn inside a window's content area. Icon boxes are in virtual - document space, so they scroll with the content. Wuss hit-tests buttons - before the content task sees a click and delivers them as `wuss_EVENT_ICON`; - labels and hidden or disabled icons fall through as `wuss_EVENT_MOUSE`. +- `wuss/menu.h` (new `WUSS_MENUS` option, implies `WUSS_ICONS`) — RISC OS-style + pop-up menus. `wuss_menu_open()` shows a caller-owned, immutable + `wuss_menu_t` (title plus an array of `wuss_menu_item_t`) as a borderless + window, nudged to stay on screen and opened under the pointer; wuss owns + layout, submenu chaining on hover and whole-chain dismissal. Per-item flags + cover ticks, disabled rows, dashed separators and submenus. + `wuss_menu_select_fn_t` reports a leaf pick — SELECT closes the chain, ADJUST + keeps it open. `wuss_menu_close()` / `wuss_menu_is_open()` manage a chain by + handle. An over-tall menu gets a real vertical scrollbar instead of being + cropped. +- `wuss_menu_create_from_desc()` / `wuss_menu_destroy()` — build a heap + `wuss_menu_t` tree from a compact descriptor string (PrivateEye's + `menu_create_from_desc` syntax: `,` between items, leading `|` for a dashed + separator, `{ ... }` submenus, `!` tick, `~` shade, `>` and `%s` varargs). + The whole tree is one owned allocation graph freed by `wuss_menu_destroy()`. +- `wuss_set_palette()` — swap the system palette mid-session. Copies the new + palette in, refreshes the cached nearest-black/white indices, broadcasts a + new `wuss_EVENT_PALETTE` to every window's task so it can recache + `wuss_nearest_colour()` selections, then invalidates the whole screen. + Length must match `wuss_create()`'s; a now-out-of-range furniture/bevel/ + backdrop index is rejected with the palette left unchanged. +- `wuss_nearest_colour()` — the system-palette index closest to an RGB value by + squared Euclidean distance, ties to the lower index. +- `wuss_alloc_t` and the `wuss_alloc` stdlib default — pluggable malloc/realloc/ + free hooks. `wuss_create()` takes a new `const wuss_alloc_t *` argument + (NULL selects `wuss_alloc`); every heap block a `wuss_t` owns goes through + the hooks. +- `wuss/icon.h` — work-area icons drawn inside a window's content area, in + virtual document space so they scroll with the content. Wuss hit-tests + interactive icons before the content task sees a click and delivers them as + `wuss_EVENT_ICON`; labels and hidden or disabled icons fall through as + `wuss_EVENT_MOUSE`. Icon types: `LABEL` (with `JUSTIFY_RIGHT` / `_CENTRE` + flags), bevelled `BUTTON` (`DEFAULT` flag draws it as the default action + button), `RADIO` and `OPTION` latching buttons (radios with a non-zero + `group` are mutually exclusive), `FRAME` grouping box, `BITMAP` (a + caller-owned image, hit-tested only with the `INTERACTIVE` flag), `PATTERN` + swatch, `MENU_ENTRY` and inert `RULE` rows. +- `wuss_icon_get_selected()` / `wuss_icon_set_selected()` — query and set a + radio or option icon's latched state; setting a grouped radio clears the + others in its group. No task event — the programmatic path. - `wuss_icon_create_array()` — creates a batch of icons from 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. +- `text/bmtext.h` — `bmtext_layout()` word-wraps a string to a pixel width in a + `bmfont_t` (measuring each candidate line, so proportional fonts wrap + correctly); `bmtext_draw()` draws the laid-out lines stacked. Layout is pure. +- `screen_draw_lines()` — connected polyline; a `screen_draw_line()` segment + between each adjacent pair. +- `screen_draw_rect()` — one-pixel unfilled rectangle outline (falls back to a + fill for a degenerate size). See _Changed_ for the fill-primitive renames. +- `screen_draw_dashed_line()` — Bresenham line with a dash-period counter. +- `screen_PATTERN_BAYER0` .. `screen_PATTERN_BAYER0 + 64` — 8x8 ordered dither, + one fill pattern per coverage level 0 (empty) to 64 (solid), indexed as + `screen_PATTERN_BAYER0 + level`. `BAYER32` == `GREY50`, `BAYER64` == `SOLID`. +- `define_wimp16_palette()` and the `palette_WIMP16_*` names — the RISC OS + desktop 16-colour palette in native Wimp index order. +- `bitmap_set_palette()` — replace a bitmap's palette in place, reusing the + existing palette buffer when it is large enough; NULL drops the palette. - `wuss_window_create_placed()` — creates a window from a content size instead of a box, letting Wuss pack it (furniture included) into the first free screen region. Successive auto-placed windows tile; placement cascades when @@ -57,6 +107,15 @@ _Unreleased_ until one is cut. ### Changed +- **Breaking:** `wuss_create()` takes a `const wuss_alloc_t *alloc` argument + after `config`; pass NULL for the stdlib allocator. +- **Breaking:** `wuss_config_t::palette` is renamed `furniture`, and its type + `wuss_palette_t` renamed `wuss_furniture_palette_t`. `wuss_colour_t` narrows + from `int` to `unsigned char`. +- **Breaking:** the framebuf draw primitives split draw/fill in their names: + `screen_draw_pixel` → `screen_set_pixel`, `screen_draw_rect` → + `screen_fill_rect`, `screen_draw_square` → `screen_fill_square`. `screen_draw_rect` + now names a one-pixel outline. - **Breaking:** the window/desktop background is now a `wuss_backdrop_t` (`{ colour, pattern, pattern_bg }`) instead of a bare `wuss_colour_t`: `wuss_config_t::backdrop`, and the `bg` parameter of @@ -77,16 +136,32 @@ _Unreleased_ until one is cut. - Adjust-clicking a scroll arrow now steps against the direction the arrow points, so one arrow can be worked both ways without moving the pointer. Toggle-size stays Select-only. +- A window can no longer be resized larger than the screen. ### Fixed - Opening a menu from a task's mouse-down handler no longer picks the menu's first row on the matching mouse-up: that release is now swallowed. +- A menu chain is now closed before its `on_select` callback runs, so a + callback that opens another menu no longer fights the one being torn down. +- Menu text is drawn in the nearest-black palette entry rather than assuming + a fixed index, so menus stay legible under any system palette. +- The mouse wheel no longer scrolls a window on an axis it declared + non-scrollable. +- A MENU-button click on window furniture now has no effect, instead of being + routed to the content task. +- A window's scroll offset is re-clamped after a resize reveals content past + the document extent. +- The scrollbar well keeps a 2px gap at each end. - Dragging a scrollbar well with Select no longer raises the window; only a resize-icon grab restacks it. - `wuss_window_move()` no longer repaints already-blitted pixels when a drag past an occluded corner slides one clean piece of the window onto ground another clean piece just vacated. +- Several scroll-redraw glitches fixed: stale pixels when scroll events arrive + faster than redraws, content blitted over a mid-content occluder, blit + sub-pieces clobbering each other's source region, and repaint sets not + clipped to the visible area. The hovered icon is re-resolved after a scroll. - A work-area button held on mouse-down is now released if the click opens a window that covers the button's owner, instead of staying stuck pressed. - Resize-corner drag preserves where within the resize icon the mouse-down From db8d9afc3593ebc0fc6da1f234966abbf857fe2a Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Wed, 2 Sep 2026 01:43:18 +0100 Subject: [PATCH 78/79] feat(wuss): allow a window as a menu sub-menu A wuss_menu_item_t may now carry a caller-owned wuss_window_t in a new `window` field (mutually exclusive with `submenu`). Hovering the row shows that window where a sub-menu would open, using the same anchor maths; moving off the row, a click outside, a leaf SELECT elsewhere, or wuss_destroy all hide it again rather than close it, so the same handle is reused next time. Backing this is a new core wuss_WINDOW_HIDDEN flag plus wuss_window_set_hidden(): a hidden window keeps its z-order slot but is not drawn, not hit-tested and occludes nothing; wuss_window_move still works on it so it can be parked and re-shown in position. Honoured in window/at.c, redraw.c, window/invalidate.c (occlusion), window/create.c (born hidden) and window/move.c (translate only, no blit). create-from-desc.c now initialises the new `window` field -- build_emit grows its array uninitialised, so a desc-built menu was dereferencing garbage on hover. apps/wuss adds a "Details" row wired to a lazily-created hidden window to exercise it interactively. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --- CMakeLists.txt | 1 + apps/wuss/main.c | 47 +++++++++++-- include/wuss/menu.h | 11 +++ include/wuss/window.h | 15 ++++ include/wuss/wuss.h | 13 +++- libraries/wuss/menu.h | 13 +++- libraries/wuss/menu/create-from-desc.c | 2 + libraries/wuss/menu/menu.c | 96 ++++++++++++++++++++------ libraries/wuss/redraw.c | 3 + libraries/wuss/window/at.c | 2 + libraries/wuss/window/create.c | 3 +- libraries/wuss/window/invalidate.c | 2 + libraries/wuss/window/move.c | 12 ++++ libraries/wuss/window/set-hidden.c | 25 +++++++ 14 files changed, 213 insertions(+), 32 deletions(-) create mode 100644 libraries/wuss/window/set-hidden.c diff --git a/CMakeLists.txt b/CMakeLists.txt index d133fc8..472fc25 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -362,6 +362,7 @@ set(WUSS_CORE_SOURCES libraries/wuss/window/resize.c libraries/wuss/window/restack.c libraries/wuss/window/set-background.c + libraries/wuss/window/set-hidden.c libraries/wuss/window/set-scroll.c) set(WUSS_FURNITURE_SOURCES diff --git a/apps/wuss/main.c b/apps/wuss/main.c index 9103cb3..8a2d5f7 100644 --- a/apps/wuss/main.c +++ b/apps/wuss/main.c @@ -234,13 +234,19 @@ static const wuss_menu_t g_menu_export = "Export", g_menu_export_items, NELEMS(g_menu_export_items) }; -static const wuss_menu_item_t g_menu_items[] = -{ - { "Open", wuss_MENU_ITEM_NONE, NULL }, - { "Show grid", wuss_MENU_ITEM_TICKED, NULL }, - { "Wireframe", wuss_MENU_ITEM_TICKED, NULL }, - { "Export", wuss_MENU_ITEM_NONE, &g_menu_export }, - { "Quit", wuss_MENU_ITEM_DASHED, NULL } +/* A caller-owned window wired as a menu item's `.window`: hovering "Details" + * shows it where a submenu would open, and moving off the row (or dismissing + * the menu) hides it again. Created once, lazily, by spawn_menu. */ +static wuss_window_t *g_menu_details_window; + +static wuss_menu_item_t g_menu_items[] = +{ + { "Open", wuss_MENU_ITEM_NONE, NULL, NULL }, + { "Show grid", wuss_MENU_ITEM_TICKED, NULL, NULL }, + { "Wireframe", wuss_MENU_ITEM_TICKED, NULL, NULL }, + { "Export", wuss_MENU_ITEM_NONE, &g_menu_export, NULL }, + { "Details", wuss_MENU_ITEM_NONE, NULL, NULL }, /* .window set in spawn_menu */ + { "Quit", wuss_MENU_ITEM_DASHED, NULL, NULL } }; static const wuss_menu_t g_menu = @@ -259,8 +265,35 @@ static void menu_selected(const wuss_menu_t *menu, menu->items[index].text ? menu->items[index].text : "(sep)"); } +/* index of the "Details" row in g_menu_items */ +#define G_MENU_DETAILS_INDEX 4 + static result_t spawn_menu(void) { + if (g_menu_details_window == NULL) + { + box_t content; + result_t rc; + + /* a small hidden window; wuss fills its background, no task needed */ + content.x0 = 0; + content.y0 = 0; + content.x1 = 180; + content.y1 = 120; + rc = wuss_window_create(g.wuss, &content, "Details", + wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK + | wuss_WINDOW_NO_TOGGLE_SIZE + | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL + | wuss_WINDOW_NO_RESIZE | wuss_WINDOW_HIDDEN, + wuss_BACKDROP_COLOUR(1), + NULL, + SIZE2D(180, 120), SIZE2D(0, 0), + &g_menu_details_window); + if (rc != result_OK) + return rc; + g_menu_items[G_MENU_DETAILS_INDEX].window = g_menu_details_window; + } + return wuss_menu_open(g.wuss, &g_menu, wuss_get_pointer(g.wuss), menu_selected, NULL, NULL); } diff --git a/include/wuss/menu.h b/include/wuss/menu.h index ead3d27..6619b38 100644 --- a/include/wuss/menu.h +++ b/include/wuss/menu.h @@ -28,6 +28,7 @@ extern "C" #include "geom/point.h" #include "wuss/wuss.h" +#include "wuss/window.h" /* ----------------------------------------------------------------------- */ @@ -54,6 +55,16 @@ typedef struct wuss_menu_item wuss_menu_item_flags_t flags; /**< see wuss_menu_item_flags_t */ const struct wuss_menu *submenu; /**< non-NULL: draw a right arrow and open * this menu to the right on hover */ + wuss_window_t *window; /**< non-NULL: draw a right arrow and, on + * hover, show this caller-owned window + * where a submenu would open, hiding it + * again when the pointer leaves the row + * or the chain is dismissed. Create it + * with wuss_WINDOW_HIDDEN. Mutually + * exclusive with \c submenu. The window + * must outlive the open chain -- do not + * wuss_window_close it while its menu is + * open. */ } wuss_menu_item_t; diff --git a/include/wuss/window.h b/include/wuss/window.h index 8319742..cdcca47 100644 --- a/include/wuss/window.h +++ b/include/wuss/window.h @@ -139,6 +139,21 @@ void wuss_window_close(wuss_window_t *doomed); */ void wuss_window_move(wuss_window_t *window, point_t p); +/** + * Show or hide a window without destroying it. + * + * A hidden window keeps its z-order slot and all its state, but is not drawn + * and not hit-tested: it neither occludes lower windows nor catches the + * pointer. wuss_window_move still works while hidden, so a caller can park a + * window off to one side and re-show it in position later. Toggling + * visibility invalidates the window's footprint so the next redraw picks up + * the change. + * + * \param[in] window Window to show or hide. + * \param[in] hidden Non-zero to hide, zero to show. + */ +void wuss_window_set_hidden(wuss_window_t *window, int hidden); + /** * Resize a window's content area, preserving its top-left position. * diff --git a/include/wuss/wuss.h b/include/wuss/wuss.h index 1eb2a89..83d2758 100644 --- a/include/wuss/wuss.h +++ b/include/wuss/wuss.h @@ -187,7 +187,18 @@ typedef enum wuss_window_flags * depends on the window's size in ways redraw can't patch incrementally * (e.g. a palette that lays itself out across the whole window). */ - wuss_WINDOW_NO_RESIZE_BLIT = 1 << 8 + wuss_WINDOW_NO_RESIZE_BLIT = 1 << 8, + + /** + * Created hidden, or hidden later via wuss_window_set_hidden: the window + * keeps its place in the z-order but is not drawn and not hit-tested, so + * it neither occludes other windows nor catches the pointer. Its position + * can still be changed with wuss_window_move while hidden, ready for when + * it is shown again. Unlike the wuss_WINDOW_NO_* bits this one is toggled + * at runtime, and it is honoured regardless of the WUSS_FURNITURE build + * option. + */ + wuss_WINDOW_HIDDEN = 1 << 9 } wuss_window_flags_t; diff --git a/libraries/wuss/menu.h b/libraries/wuss/menu.h index 9df3ca2..4d5a5b1 100644 --- a/libraries/wuss/menu.h +++ b/libraries/wuss/menu.h @@ -14,16 +14,23 @@ struct wuss__menu { wuss_t *wuss; - wuss_window_t *window; /* the borderless menu window */ - const wuss_menu_t *menu; /* borrowed source description */ + wuss_window_t *window; /* the borderless menu window; for a + * borrowed-window level (see borrowed) the + * caller's own window instead */ + const wuss_menu_t *menu; /* borrowed source description; NULL for a + * borrowed-window level */ wuss_icon_t **icons; /* owned array, menu->nitems entries, in - * item order */ + * item order; NULL for a borrowed-window + * level */ struct wuss__menu *parent; /* level this one opened from, NULL at root */ struct wuss__menu *child; /* open submenu level, NULL when none */ wuss_menu_select_fn_t *on_select; void *ctx; int open_index; /* item index whose submenu `child` is, * -1 when no child open */ + int borrowed; /* 1: `window` is a caller-owned window + * shown in place of a submenu -- hide it + * rather than close it on teardown */ }; /* Called from wuss_mouse_click on a MOUSE_DOWN before the window hit-test: if a diff --git a/libraries/wuss/menu/create-from-desc.c b/libraries/wuss/menu/create-from-desc.c index 5418e28..74dc441 100644 --- a/libraries/wuss/menu/create-from-desc.c +++ b/libraries/wuss/menu/create-from-desc.c @@ -169,6 +169,7 @@ static result_t menu_deep_copy(const wuss_menu_t *src, wuss_menu_t **out) items[i].text = strdup(src->items[i].text ? src->items[i].text : ""); items[i].flags = src->items[i].flags; items[i].submenu = NULL; + items[i].window = src->items[i].window; /* borrowed, as in the source */ if (items[i].text == NULL) { @@ -229,6 +230,7 @@ static result_t build_emit(Building *b, it->text = copy; it->flags = flags; it->submenu = submenu; + it->window = NULL; /* array_grow leaves this uninitialised otherwise */ return result_OK; } diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c index 2bd068d..9db1d39 100644 --- a/libraries/wuss/menu/menu.c +++ b/libraries/wuss/menu/menu.c @@ -55,12 +55,70 @@ static void wuss__menu_close_from(struct wuss__menu *node) wuss_t *w; w = node->wuss; - wuss_window_close(node->window); - wuss__free(w, node->icons); + if (node->borrowed) + wuss_window_set_hidden(node->window, 1); /* caller's window: hide, keep */ + else + wuss_window_close(node->window); + wuss__free(w, node->icons); /* NULL for a borrowed level */ wuss__free(w, node); } } +/* Compute where a submenu (or a borrowed window standing in for one) opens + * off the row `icon` in parent level `self`: content top-left in screen + * space, the child's own titlebar then sitting above it so the two rows line + * up. Mirrors the maths in wuss__menu_handle's MOUSE_MOVE branch. */ +static point_t wuss__submenu_anchor(struct wuss__menu *self, + const wuss_icon_t *icon) +{ + box_t wb; + box_t ib; + point_t scroll; + point_t at; + + wuss_window_get_content_bounds(self->window, &wb); + wuss_icon_get_bbox(icon, &ib); + wuss_window_get_scroll(self->window, &scroll); + + at.x = wb.x1 - WUSS_MENU_SUBMENU_OVERLAP; + at.y = wb.y0 + ib.y0 - scroll.y; + return at; +} + +/* Open item `index`'s borrowed window as level `self->child`: position it + * where a submenu would appear, un-hide it, bring it to the front. */ +static result_t wuss__menu_open_window(struct wuss__menu *self, int index) +{ + struct wuss__menu *node; + wuss_window_t *win; + point_t at; + + win = self->menu->items[index].window; + + node = wuss__malloc(self->wuss, sizeof(*node)); + if (node == NULL) + return result_OOM; + + node->wuss = self->wuss; + node->window = win; + node->menu = NULL; + node->icons = NULL; + node->parent = self; + node->child = NULL; + node->on_select = self->on_select; + node->ctx = self->ctx; + node->open_index = -1; + node->borrowed = 1; + + at = wuss__submenu_anchor(self, self->icons[index]); + wuss_window_move(win, at); + wuss_window_set_hidden(win, 0); + wuss_window_restack(win, wuss_ZORDER_FRONT); + + self->child = node; + return result_OK; +} + /* ----------------------------------------------------------------------- */ /* The menu window's task delegate. task_data is the struct wuss__menu. */ @@ -101,8 +159,6 @@ static result_t wuss__menu_handle(wuss_window_t *window, if (event->data.icon.action == wuss_MOUSE_MOVE) { - box_t ib; - box_t wb; point_t at; /* Hovering a different row closes any submenu the previous row opened. */ @@ -113,24 +169,23 @@ static result_t wuss__menu_handle(wuss_window_t *window, self->open_index = -1; } - if (item->submenu == NULL || self->child != NULL) + if (self->child != NULL) return result_OK; - wuss_window_get_content_bounds(self->window, &wb); - wuss_icon_get_bbox(icon, &ib); - - /* wb is the content box in screen space; ib is in document space, so the - * row's screen y is its document y less however far the menu is scrolled. - * The child's own titlebar sits above `at`, so a submenu row lines up with - * its parent. */ + /* A borrowed window opens where a submenu would; a submenu spawns as a + * fresh menu level. Either lines its row 0 up with this row -- the + * child's own titlebar sits above `at`. */ + if (item->window != NULL) { - point_t scroll; - - wuss_window_get_scroll(self->window, &scroll); - at.x = wb.x1 - WUSS_MENU_SUBMENU_OVERLAP; - at.y = wb.y0 + ib.y0 - scroll.y; + if (wuss__menu_open_window(self, index) == result_OK) + self->open_index = index; + return result_OK; } + if (item->submenu == NULL) + return result_OK; + + at = wuss__submenu_anchor(self, icon); if (wuss__menu_spawn(self->wuss, item->submenu, at, self, self->on_select, self->ctx, &self->child) == result_OK) self->open_index = index; @@ -148,8 +203,8 @@ static result_t wuss__menu_handle(wuss_window_t *window, button = event->data.icon.button; - if (item->submenu != NULL) - return result_OK; /* a submenu row opens on hover, it is not a pick */ + if (item->submenu != NULL || item->window != NULL) + return result_OK; /* a submenu/window row opens on hover, not a pick */ /* Capture what the callback needs, then tear the chain down *before* * invoking it: on_select may itself open a new menu (freeing this one), @@ -318,6 +373,7 @@ static result_t wuss__menu_spawn(wuss_t *wuss, node->on_select = on_select; node->ctx = ctx; node->open_index = -1; + node->borrowed = 0; /* One MENU_ENTRY icon per item, in item order, preceded by an inert * wuss_ICON_TYPE_RULE icon for each dashed item. specs[] therefore holds @@ -351,7 +407,7 @@ static result_t wuss__menu_spawn(wuss_t *wuss, if (item->flags & wuss_MENU_ITEM_DISABLED) flags |= wuss_ICON_FLAGS_DISABLED; - if (item->submenu != NULL) + if (item->submenu != NULL || item->window != NULL) flags |= wuss_ICON_FLAGS_SUBMENU; specs[s].type = wuss_ICON_TYPE_MENU_ENTRY; diff --git a/libraries/wuss/redraw.c b/libraries/wuss/redraw.c index cca9001..38a0caf 100644 --- a/libraries/wuss/redraw.c +++ b/libraries/wuss/redraw.c @@ -13,6 +13,9 @@ static void redraw_window(wuss_t *wuss, box_t pieces[WUSS_MAX_INVALIDATE_PIECES]; int npieces, i; + if (win->flags & wuss_WINDOW_HIDDEN) + return; /* not drawn while hidden */ + if (box_intersection(&win->visible, full, &visible_clipped)) return; /* offscreen */ diff --git a/libraries/wuss/window/at.c b/libraries/wuss/window/at.c index bad019d..c450a8f 100644 --- a/libraries/wuss/window/at.c +++ b/libraries/wuss/window/at.c @@ -11,6 +11,8 @@ wuss_window_t *wuss__window_at(wuss_t *wuss, point_t p) wuss_window_t *win; win = (wuss_window_t *) e; + if (win->flags & wuss_WINDOW_HIDDEN) + continue; if (box_contains_point(&win->visible, p.x, p.y)) return win; } diff --git a/libraries/wuss/window/create.c b/libraries/wuss/window/create.c index 130659f..29936b2 100644 --- a/libraries/wuss/window/create.c +++ b/libraries/wuss/window/create.c @@ -133,7 +133,8 @@ result_t wuss_window_create(wuss_t *wuss, list_add_to_head(&wuss->z_order, &win->link); - wuss_invalidate(wuss, &win->visible); + if (!(flags & wuss_WINDOW_HIDDEN)) + wuss_invalidate(wuss, &win->visible); *window = win; diff --git a/libraries/wuss/window/invalidate.c b/libraries/wuss/window/invalidate.c index 33986c6..ac2c4c5 100644 --- a/libraries/wuss/window/invalidate.c +++ b/libraries/wuss/window/invalidate.c @@ -65,6 +65,8 @@ int wuss__clip_to_visible(wuss_window_t *window, int nnext, p; occluder = (wuss_window_t *) e; + if (occluder->flags & wuss_WINDOW_HIDDEN) + continue; /* a hidden window occludes nothing */ nnext = 0; for (p = 0; p < ncur; p++) diff --git a/libraries/wuss/window/move.c b/libraries/wuss/window/move.c index 6bb923d..036dd65 100644 --- a/libraries/wuss/window/move.c +++ b/libraries/wuss/window/move.c @@ -36,6 +36,18 @@ void wuss_window_move(wuss_window_t *window, point_t p) titlebar_height = wuss__titlebar_height(window); before = window->visible; + /* a hidden window has nothing on screen to slide and must paint nothing; + * just translate its footprint so it is in place when shown again */ + if (window->flags & wuss_WINDOW_HIDDEN) + { + window->visible.x0 = p.x - outline_px; + window->visible.y0 = p.y - outline_px - titlebar_height; + window->visible.x1 = window->visible.x0 + width; + window->visible.y1 = window->visible.y0 + height; + wuss__notify_open(window); + return; + } + /* The clean (non-occluded) pieces of "before" are genuinely this * window's own rendering; whatever isn't clean is hidden behind some * other window and has no valid pixels of this window's content to diff --git a/libraries/wuss/window/set-hidden.c b/libraries/wuss/window/set-hidden.c new file mode 100644 index 0000000..76de152 --- /dev/null +++ b/libraries/wuss/window/set-hidden.c @@ -0,0 +1,25 @@ +/* wuss/window/set-hidden.c -- wuss - minimal window manager */ + +#include "../impl.h" + +void wuss_window_set_hidden(wuss_window_t *window, int hidden) +{ + int was_hidden; + + was_hidden = (window->flags & wuss_WINDOW_HIDDEN) != 0; + if (!was_hidden == !hidden) + return; /* no change */ + + if (hidden) + { + /* still on screen: repaint its footprint now, then mark it gone */ + wuss_invalidate(window->wuss, &window->visible); + window->flags |= wuss_WINDOW_HIDDEN; + } + else + { + /* mark it back, then repaint its footprint so it appears */ + window->flags &= (wuss_window_flags_t) ~wuss_WINDOW_HIDDEN; + wuss_invalidate(window->wuss, &window->visible); + } +} From 0b16dae861bf95c7c3a3954259257d9086180a2c Mon Sep 17 00:00:00 2001 From: David Thomas <dave@davespace.co.uk> Date: Wed, 2 Sep 2026 11:08:14 +0100 Subject: [PATCH 79/79] docs(wuss): changelog the window-as-submenu feature Covers db8d9af: wuss_menu_item_t::window, wuss_window_set_hidden() and the wuss_WINDOW_HIDDEN create flag. --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30b41f9..92684e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,16 @@ _Unreleased_ until one is cut. `menu_create_from_desc` syntax: `,` between items, leading `|` for a dashed separator, `{ ... }` submenus, `!` tick, `~` shade, `>` and `%s` varargs). The whole tree is one owned allocation graph freed by `wuss_menu_destroy()`. +- `wuss_menu_item_t::window` — a menu row may carry a caller-owned + `wuss_window_t` instead of a `submenu` (the two are mutually exclusive). + Hovering the row shows that window where a submenu would open, using the same + anchor maths; leaving the row, a click outside, a leaf SELECT elsewhere or + `wuss_destroy()` hide it again rather than close it, so the same handle is + reused on the next hover. Create the window with `wuss_WINDOW_HIDDEN`. +- `wuss_window_set_hidden()` and the `wuss_WINDOW_HIDDEN` create flag — a hidden + window keeps its z-order slot but is not drawn, not hit-tested and occludes + nothing. `wuss_window_move()` still works on it (translate only, no blit) so + it can be parked and re-shown in position. - `wuss_set_palette()` — swap the system palette mid-session. Copies the new palette in, refreshes the cached nearest-black/white indices, broadcasts a new `wuss_EVENT_PALETTE` to every window's task so it can recache