diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
new file mode 100644
index 00000000..47ed7cd2
--- /dev/null
+++ b/.github/workflows/pages.yml
@@ -0,0 +1,67 @@
+---
+# Build the Wuss WebAssembly demo and publish it to GitHub Pages.
+#
+
+name: Pages
+on:
+ push:
+ branches: [master, develop]
+ workflow_dispatch:
+
+# Allow one concurrent deployment; let a running one finish.
+concurrency:
+ group: pages
+ cancel-in-progress: false
+
+permissions:
+ contents: read
+ pages: write
+ id-token: write
+
+jobs:
+ build:
+ name: Build wasm
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+
+ - name: Install CMake
+ uses: lukka/get-cmake@latest
+
+ - name: Set up Emscripten
+ uses: mymindstorm/setup-emsdk@v14
+ with:
+ version: 4.0.6
+ actions-cache-folder: emsdk-cache
+
+ - name: Configure
+ run: |
+ emcmake cmake -B build-emscripten -G Ninja \
+ -DBUILD_APPS=ON -DBUILD_TESTS=OFF
+
+ - name: Build
+ run: cmake --build build-emscripten --target wuss
+
+ - name: Assemble site
+ run: |
+ mkdir -p _site
+ cp build-emscripten/wuss.html _site/index.html
+ cp build-emscripten/wuss.js build-emscripten/wuss.wasm \
+ build-emscripten/wuss.data _site/
+
+ - name: Upload Pages artifact
+ uses: actions/upload-pages-artifact@v3
+ with:
+ path: _site
+
+ deploy:
+ name: Deploy to Pages
+ needs: build
+ runs-on: ubuntu-latest
+ environment:
+ name: github-pages
+ url: ${{ steps.deployment.outputs.page_url }}
+ steps:
+ - name: Deploy
+ id: deployment
+ uses: actions/deploy-pages@v4
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 00000000..0f14481d
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,382 @@
+# Changelog
+
+All notable changes to DPTLib are recorded here.
+
+The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
+This project does not yet publish versioned releases; entries are grouped under
+_Unreleased_ until one is cut.
+
+## [Unreleased]
+
+### Added
+
+- An Emscripten/WebAssembly build of the interactive `wuss` demo. The
+ run-loop is extracted into `wuss_frame(void *)` over a `wuss_frame_ctx`,
+ driven by `emscripten_set_main_loop_arg` in the browser while desktop and
+ RISC OS still spin it directly. CMake's `EMSCRIPTEN` branch uses the
+ bundled `-sUSE_LIBPNG=1` port instead of `find_package(PNG)`, links
+ Emscripten's SDL3 port (`-sUSE_SDL=3`), and preloads `resources/` into
+ `wuss.data` mounted at MEMFS `/resources`; the target emits `wuss.html`.
+ A GitHub Actions Pages workflow builds and deploys the demo on pushes to
+ `master` and `develop`.
+- `bmfont_enumerate()` — non-recursive scan of a directory's `*.png` files,
+ reporting each as a (name, path) pair (name is the leaf with `.png`
+ stripped) to a caller-supplied callback; the callback may return
+ `result_STOP_WALK` to stop early.
+- `wuss/component/fontmenu.h` and `wuss/component/colourmenu.h` — shared
+ task components (the RISC OS Toolbox analogue), gated on a new
+ `WUSS_COMPONENTS` CMake option (implies `WUSS_MENUS`). `wuss_fontmenu_create()`
+ builds a flat, name-sorted `wuss_menu_t` from the bitmap fonts in a
+ directory (via `bmfont_enumerate()`); `wuss_fontmenu_selected()` recovers
+ the picked font name from a `wuss_EVENT_MENU_SELECT`, and
+ `wuss_fontmenu_set_ticked(fm, index)` mutates the component's own items to
+ tick one row (index -1 clears every tick). `wuss_colourmenu_create()`
+ builds a swatch-row menu, one row per system-palette entry labelled
+ `#RRGGBB`, and resolves a pick back to the palette index. Both components
+ route every allocation through the caller's `wuss_alloc_t` hooks (copied
+ into the handle, not a borrowed `wuss_t *`, so they can safely outlive
+ `wuss_destroy()`).
+- `wuss_menu_item_t` / icon rows gain an optional colour chip
+ (`wuss_MENU_ITEM_SWATCH` / `wuss_ICON_FLAGS_SWATCH` plus a `wuss_colour_t
+ swatch` field), drawn in the left gutter where the tick sits; a chip wins
+ over a selected tick.
+- Font slot 2 (`WUSS_SYMBOL_FONT`) is now consulted to draw the menu
+ selection tick and submenu arrow as glyphs (`WUSS_GLYPH_TICK` `'*'` /
+ `WUSS_GLYPH_SUBMENU` `'>'`) instead of vector strokes, when a font is
+ present in that slot.
+- `wuss_create()`'s `fonts` array elements are now tagged with a
+ `wuss_font_class_t` and a borrowed leafname (`wuss_font_desc_t`), letting
+ a picker such as `wuss_fontmenu` and the system-decoration font (menu
+ ticks/arrows) be identified by class rather than array position.
+ `wuss_FONT_CLASS_SYSTEM` marks a chrome/decoration-only slot.
+- `wuss_icon_plot()` — validates an icon spec exactly as `wuss_icon_create()`
+ does and draws it once through the window manager's screen, retaining
+ nothing; for static content a task can redraw from its own model without
+ a live icon per element.
+- `wuss_window_invalidate_extent()` — marks a window's whole virtual
+ document extent dirty (`window->doc`), not just the visible content
+ rectangle `wuss_window_invalidate_all()` covers, so a change touching the
+ whole document repaints correctly at any scroll position.
+- Clicking in a scrollbar well (not on the sausage) now pages the content
+ one visible extent towards the click, keeping one `WUSS_SCROLL_STEP` of
+ overlap, RISC OS style.
+- `bitmap_fill_pattern()` (see _Changed_ for the rename from
+ `bitmap_draw_pattern`) and `screen_fill_pattern()` now share a single
+ `pattern_t { bits, fg, bg, flags, origin }` describing an 8x8 tile,
+ built via `pattern_from_preset()` (the Bayer ramp and named tiles) or
+ `pattern_from_mask()` (stencil) in the new `framebuf/pattern` module.
+- `screen_fill_hline()` — fills one clipped horizontal run; the per-row
+ primitive `screen_fill_rect()` and `screen_draw_circle()`'s scanline fill
+ now loop over.
+- Symbolic `wuss_colour_t` values (`wuss/wuss.h`). A raw `wuss_colour_t` is a
+ palette index, `0..127`; `wuss_COLOUR_SYMBOLIC` (128) and up name roles the
+ window manager resolves to a concrete index. `wuss_COLOUR_BLACK`,
+ `_WHITE`, `_RED`, `_GREEN`, `_BLUE`, `_YELLOW`, `_CYAN`, `_MAGENTA`,
+ `_GREY` pick the nearest system-palette entry to the named RGB;
+ `wuss_COLOUR_TITLE_BG` / `_TITLE_FG` / `_BUTTON_HILIGHT` / `_BUTTON_SHADOW`
+ / `_ACCENT_BG` / `_ACCENT_FG` / `_BACKDROP` echo the matching
+ `wuss_config_t` field. Accepted anywhere a `wuss_colour_t` is taken —
+ config furniture/bevel/accent/backdrop, `wuss_window_create()` /
+ `wuss_window_set_background()` backgrounds, icon specs — and resolved once
+ when the value is stored, so draw code and `wuss_nearest_colour()` are
+ unaffected. Resolutions are recomputed on `wuss_set_palette()` and
+ `wuss_set_backdrop()`.
+- `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. A leaf pick is
+ delivered to the opening task as `wuss_EVENT_MENU_SELECT` — 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_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. Revealing one fires a veto-able
+ `wuss_EVENT_PRE_SHOW` then `wuss_EVENT_SHOW`; `wuss_window_set_hidden()`
+ returns `result_t` accordingly.
+- `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 registered 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
+ no region fits. The slot is released on close and on the first
+ `wuss_window_move()` / `wuss_window_resize()`. An overall screen margin is
+ kept around all auto-placed windows.
+- `screen_fill_pattern()` — 8x8 two-colour tile fill primitive in
+ `framebuf/screen` with eight built-in patterns (solid, grey50, stripes,
+ diagonal, dots, grid, crosshatch), phase-locked to a caller-supplied origin
+ so a scrolling fill stays put. The tile has only eight distinct rows, so each
+ is expanded to a phase-shifted colour run once up front and the scanline
+ loops index it with no per-pixel bit test; the 32bpp path memcpy's whole
+ 8-pixel runs.
+- `wuss_ICON_TYPE_PATTERN` — a non-interactive work-area icon whose bbox is
+ filled with a `screen_fill_pattern()` pattern in fg/bg, aligned to document
+ space. Clicks fall through as `wuss_EVENT_MOUSE`; disabled swatches fold fg
+ into bg.
+- `WUSS_FURNITURE` and `WUSS_ICONS` CMake options (both default ON) drop the
+ furniture/*.c and icon/*.c files and `#ifdef`-guard every core call site,
+ struct field and helper that references them. With `WUSS_FURNITURE` off every
+ window is chromeless (content box == visible box); with `WUSS_ICONS` off the
+ `wuss_icon_*` API is not compiled. Programmatic and wheel scrolling survive
+ either off via the new core `scroll-step.c`.
+- `screen_draw_ninepatch()` — draws a resizable "9-patch" frame from a source
+ image that is a 3x3 grid of equal cells: corners at natural size, edges and
+ centre tiled, clipped to the destination box and the screen clip.
+- `screen_NINEPATCH_NO_CENTRE` flag for `screen_draw_ninepatch()` to draw only
+ the border and leave the interior untouched.
+- `packer_set_gutter()` — `packer_place_by()` now reserves a configurable
+ gutter strip along the box's two inner edges so located boxes are never
+ flush. Defaults to 0, leaving existing callers unchanged; the returned
+ position is still the un-inflated box.
+- `packer_release()` — inverse of `packer_place_*`, returns an area to the
+ pool. Released areas are not coalesced.
+- `POINT(x, y)` and `SIZE2D(w, h)` compound-literal macros in `geom/point.h`
+ and `geom/size.h`.
+
+### Changed
+
+- **Breaking:** `wuss_create()`'s `fonts` argument is now
+ `const wuss_font_desc_t *` (handle, `wuss_font_class_t`, borrowed
+ leafname) instead of a bare `bmfont_t *const *` array; up to
+ `wuss_MAX_FONTS` (4) slots are stored, slot 0 remaining the system font
+ used for titlebars and any icon that does not select another.
+ `wuss_get_font_n(wuss, index)` reads a given slot; `wuss_get_font` stays
+ slot 0. An icon's flags gain a two-bit font-select field
+ (`wuss_ICON_FLAGS_FONT_MASK`, bits 9-10) with `wuss_ICON_FONT(n)` /
+ `wuss_ICON_FONT_OF(f)` helpers, and titlebars are drawn in font slot 1
+ (the bold weight) when one is supplied, else slot 0.
+- **Breaking:** `wuss_fontmenu_create()` takes a fourth argument,
+ `const wuss_alloc_t *alloc` (NULL selects `wuss_alloc`), routing every
+ block the handle keeps through those hooks instead of libc
+ malloc/calloc/strdup/free, matching `wuss_colourmenu` and `wuss_create`.
+- **Breaking:** `bitmap_draw_pattern()` is renamed `bitmap_fill_pattern()`
+ and now takes `const pattern_t *` and returns `result_t`.
+ `screen_draw_bitmap()` is renamed `screen_copy_bitmap()` and
+ `screen_draw_ninepatch()` renamed `screen_copy_ninepatch()` (its
+ `screen_NINEPATCH_NO_CENTRE` flag is unchanged) — `copy` is now the verb
+ for all pixel transfer, matching `screen_copy_rect()`. `screen_fill_pattern()`
+ now takes `const pattern_t *` instead of a preset enum. Hard renames, no
+ compatibility wrappers.
+- `screen_copy_bitmap()`, `screen_copy_ninepatch()` and `screen_copy_rect()`
+ now all return `result_t` (`result_OK` / `result_NOT_SUPPORTED`) instead
+ of `void` / `int`; `screen_copy_rect()` also returns
+ `result_NOT_SUPPORTED` when clipping leaves nothing to copy, so a caller
+ must fall back to a full redraw in that case.
+- Resizing a window (`wuss_window_resize()`) no longer clamps the result to
+ the visible on-screen strip when the window's top-left is off-screen —
+ the requested content size is applied verbatim and the window may
+ overhang the screen edge, the same latitude toggle-size and drag already
+ allow. `wuss_window_create()` keeps its own screen cap.
+- Toggle-size now grows a window to fill the whole screen (capped per axis
+ at the window's document extent, or uncapped when that extent is 0),
+ nudging the top-left toward the origin by the minimum needed to fit; it
+ previously pinned the top-left and only grew the bottom-right corner, so
+ a window not already near the origin could never fill the screen and a
+ zero document extent toggled to nothing. Restore returns the exact
+ pre-toggle box, position included.
+- **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
+ `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
+ reported button for equality must now test with `&`.
+- **Breaking:** `wuss_window_create()` takes a `min_doc` argument between
+ `doc` and `window`, the minimum content extent a resize-drag or toggle-size
+ will shrink to. It is clamped up to the built-in grab floor and down to
+ `doc`. Pass `(0, 0)` for the built-in floor.
+- 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.
+- **Breaking:** event dispatch is reworked around a registered, opaque
+ `wuss_task_t` that owns its windows and is the sole delivery target.
+ - `wuss_task_start()` / `wuss_task_stop()` and the by-value task-delegate
+ struct are gone. Register a task with
+ `wuss_task_create(wuss, const wuss_task_desc_t *, wuss_task_t **)` —
+ `wuss_task_desc_t` is `{ wuss_window_fn_t *handle; void *task_data;
+ const char *name; }` — and tear it down with
+ `wuss_task_destroy(wuss_task_t *)`, which closes the task's windows,
+ fires one `wuss_EVENT_QUIT` and unregisters it.
+ - `wuss_task_set_autoclose(wuss_task_t *, int)` opts a task into
+ self-destruct: once its last window closes it fires one `wuss_EVENT_QUIT`
+ and unregisters, so it stops receiving `wuss_idle()` / `wuss_set_palette()`
+ broadcasts. Such tasks should free `task_data` from `wuss_EVENT_QUIT`, not
+ `wuss_EVENT_CLOSE`.
+ - `wuss_window_create()` and `wuss_window_create_placed()` no longer take a
+ leading `wuss_t *` or a task-delegate pointer; their first argument is now
+ the owning `wuss_task_t *`. The window inherits that task's handler.
+ - `wuss_event_kind_t` is a single master enum
+ (`REDRAW, MOUSE, ICON, SCROLL, OPEN, PRE_SHOW, SHOW, PRE_CLOSE, CLOSE,
+ IDLE, QUIT, PALETTE, MENU_SELECT`). Handlers must handle unknown kinds
+ (fall through / `default:`).
+ - New veto-able pre-events. `wuss_window_set_hidden()` now returns
+ `result_t`: revealing a hidden window fires `wuss_EVENT_PRE_SHOW` first
+ and a non-OK return keeps it hidden and propagates. New
+ `wuss_window_try_close()` fires `wuss_EVENT_PRE_CLOSE` (non-OK vetoes),
+ then `wuss_EVENT_CLOSE`, then closes; the close icon routes through it.
+ `wuss_window_close()` stays the forced, unvetoable teardown and fires no
+ pre-events.
+ - `wuss_set_palette()` and `wuss_idle()` now broadcast once per *registered
+ task* (in registration order, `window == NULL`), not once per open
+ window's task.
+ - `wuss_menu_open()` takes a `wuss_task_t *` as its first argument. A picked
+ leaf now delivers `wuss_EVENT_MENU_SELECT` (with `window == NULL`) to that
+ task — `data.menu_select` carries `{ const struct wuss_menu *menu; int
+ index; wuss_button_t button; }`. `wuss_menu_select_fn_t` is removed.
+
+### Fixed
+
+- `wuss_destroy()` now delivers `wuss_EVENT_QUIT` to each still-registered
+ task before freeing it, instead of freeing the task block directly. Per
+ the `task_data`-ownership contract a task's client-owned allocations are
+ freed only from its `QUIT` handler, so a still-registered task (e.g. a
+ menu-spawned demo window not yet closed) previously leaked `task_data`
+ on whole-manager teardown.
+- `wuss_task_destroy()` now closes a live menu chain it owns before freeing
+ the task, matching `wuss_destroy()`'s whole-manager teardown; previously
+ a later row pick or in-flight pick-flash completion could call into the
+ freed task through a dangling chain-owner pointer.
+- A click outside a menu (or another `wuss_menu_open()`/`wuss_menu_close()`)
+ landing during a pick's highlight flash no longer silently drops the
+ `wuss_EVENT_MENU_SELECT` the flash was standing in for.
+- A borrowed window opened by hovering a menu row (`wuss_menu_item_t::window`)
+ no longer sticks at the submenu anchor position when its
+ `wuss_EVENT_PRE_SHOW` vetoes the reveal — its prior position is restored.
+- A submenu now opens only while the pointer is in the row's arrow gutter,
+ not anywhere on the row, and closes on any re-entry rather than staying
+ open for every subsequent mouse move over that row.
+- An ADJUST pick that keeps a menu chain open now re-ticks the picked row
+ in place; previously the tick was only applied at `wuss_menu_open()` time
+ so an ADJUST-kept-open menu's tick state went stale.
+- A window's furniture (titlebar, scrollbars, outline) is now clipped to
+ its unoccluded pieces when redrawing its own dirty region; previously
+ only the content redraw was occlusion-clipped, so a partly-covered
+ window repainted furniture pixels straight over the window on top.
+- Bounds-checked `extract_advance_widths()` pixel reads in `bmfont` before
+ they happen, rather than after: a font PNG whose width is not an exact
+ multiple of the glyphs-per-row count could previously run the inner loop
+ past the row's — or the buffer's — last valid word before any check
+ caught it.
+- `bmfont`'s grid-size detector now decodes the image and verifies each
+ candidate cell height against the advance-width strip's pixel markers,
+ rather than taking the first divisor of the image height at or above the
+ grid width. The old heuristic could miss a valid cell height below the
+ grid width and, when the grid width did not divide the image height
+ cleanly, could latch onto a spurious large divisor and overflow the
+ pixel buffer in `bmfont_draw()`.
+- `bmfont` rejects a malformed font grid with `result_PARSE_ERROR` instead
+ of asserting, when `extract_advance_widths()`'s pixel cursor would walk
+ out of the decoded image (previously two `assert`s, compiled out under
+ `NDEBUG`).
+- `screen_fill_hline()` (and so `screen_fill_rect()`, which calls it per
+ row) is a no-op in release builds for a `screen_t` whose pixel format has
+ no span-registry entry, instead of dereferencing the NULL `scr->span` —
+ the guarding `assert` was compiled out under `NDEBUG` so this previously
+ crashed.
+- `wuss_create()`'s font-slot arrays and loop bound in the interactive demo
+ are now sized from one named, compile-time-checked constant
+ (`WUSS_MAIN_NFONTS`, checked against `wuss_MAX_FONTS`) instead of three
+ independent hardcoded literals that could silently drift apart.
+- 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
+ landed, so the window's corner no longer jumps to the raw pointer position
+ on the first move.
+- Removed signed-overflow and negative-shift undefined behaviour in the
+ anti-aliased fixed-point line rasteriser, reachable with long or off-screen
+ endpoints.
+- `screen_draw_ninepatch()` clamps its corner cells so they no longer overlap
+ and double-draw when the destination box is smaller than the source corners.
diff --git a/CLAUDE.md b/CLAUDE.md
index 5f18cf77..7a781ae4 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -8,32 +8,57 @@ DPTLib is a platform-independent C99 library (base, databases, datastruct, frame
## Build
+Build directories are per-config and already created; there is **no** plain
+`build/` — never invoke `./build/DPTLibTest` or `cmake --build build`.
+
+- `build-asan/` — **default**. Debug, core tests (`BUILD_TESTS=YES`). Use this
+ unless told otherwise. Despite the name its cache currently has
+ `USE_ASAN=OFF`; re-run cmake with `-DUSE_ASAN=YES` if you actually need the
+ sanitisers.
+- `build-sdl/` — Release, SDL tests on (`BUILD_SDL_TESTS=ON`). Needed for the
+ interactive `wuss` driver and anything under `libraries/wuss/test/tasks/`.
+- `build-nosdl/` — Release, core tests only.
+- `build-riscos/`— GCCSDK cross build. Leave alone unless working on RISC OS.
+- `build.xc/` — Xcode generator.
+- `build-emscripten/` — WebAssembly build of the `wuss` demo. Not pre-created;
+ configure with `emcmake cmake -B build-emscripten -G Ninja -DBUILD_APPS=ON
+ -DBUILD_TESTS=OFF`, build with `cmake --build build-emscripten --target wuss`,
+ then serve the dir and open `wuss.html` (e.g.
+ `python3 -m http.server -d build-emscripten`). Uses Emscripten's bundled SDL3
+ and libpng ports (`-sUSE_SDL=3`, `-sUSE_LIBPNG=1`); the browser owns the main
+ loop via `emscripten_set_main_loop_arg`; `resources/` is baked into
+ `wuss.data` and mounted at MEMFS `/resources`.
+
+CMake options: `BUILD_TESTS`, `BUILD_SDL_TESTS`, `USE_ASAN` (ASan + UBSan),
+`USE_FORTIFY` (bundled Fortify), `DPTLIB_IMAGES_READ_ONLY` (libpng no write).
+
+(Re)configure a dir only if its `CMakeCache.txt` is missing or you are changing
+options:
```
-mkdir build && cd build
-cmake -DBUILD_TESTS=YES ..
-make -j4
+cmake -B build-asan -G Ninja -DBUILD_TESTS=YES
```
-Useful CMake options:
-- `BUILD_TESTS=YES` — build the `DPTLibTest` self-test executable.
-- `BUILD_SDL_TESTS=YES` — additionally build tests needing SDL2/SDL2_image.
-- `USE_FORTIFY=YES` — link the bundled Fortify memory-debugging library.
-- `DPTLIB_IMAGES_READ_ONLY=YES` — build libpng without write support.
+Build:
+```
+cmake --build build-asan --target DPTLibTest
+```
Requires libpng (`find_package(PNG)` on non-RISC OS). On RISC OS the build fetches and patches zlib/libpng itself via `FetchContent` (see `cmake/*.patch`).
## Testing
-Run all tests (needs `-resources` pointing at the repo root, for test fixture files):
+Run from the **repo root** so `-resources .` resolves the fixture files:
```
-./build/DPTLibTest -resources /path/to/DPTLib
+./build-asan/DPTLibTest -resources .
```
-Run a subset by naming tests (names come from the `tests[]` table in `apps/test/main.c`, e.g. `atom`, `bitvec`, `curve`, `pickle`, `stream`, `packer`):
+Run a subset by naming tests (names come from the `tests[]` table in `apps/test/main.c`, e.g. `atom`, `bitvec`, `curve`, `pickle`, `stream`, `packer`, `wuss`):
```
-./build/DPTLibTest -resources /path/to/DPTLib atom bitvec
+./build-asan/DPTLibTest -resources . atom bitvec
```
+SDL / interactive tests use the `build-sdl` binary instead.
+
Success prints `++ Tests completed in Ns: N of N tests passed.`
### Adding a new test
@@ -45,12 +70,12 @@ Success prints `++ Tests completed in Ns: N of N tests passed.`
## Architecture
-**Module layout.** Each module lives in two places that must be kept in sync:
-- `include//.h` — the public API, always wrapped in `extern "C"`, documented with Doxygen `\file`/`\param`/`\return` comments.
-- `libraries///` — implementation `.c` files (often one function per file, e.g. `libraries/datastruct/vector/{create,destroy,insert,...}.c`), plus a private `impl.h` defining the opaque struct behind the public typedef and any internal-only declarations.
+**Module layout.** Each module is split between `include//.h` (public API, `extern "C"`, Doxygen-documented) and `libraries///` (implementation `.c` files, often one function per file, plus a private `impl.h` for the opaque struct and internal-only declarations).
New source/header files must be added by hand to the relevant `set(..._SOURCES ...)` list and, for public headers, to `PUBLIC_HEADERS`, in `CMakeLists.txt` — there is no globbing.
+**Internal naming (see `wuss`).** Functions declared in a private `impl.h` and not part of the public header use a double-underscore prefix, e.g. `wuss__titlebar_height_for`; internal-only enum constants still use the module's normal single-underscore style (e.g. `wuss_WINDOW_STATE_TOGGLED`), matching public enums. Per-instance internal state that isn't part of the public appearance API (e.g. `struct wuss_window`'s toggled/maximised state) is kept as a bitflags enum with `wuss__window_*` accessor helpers rather than loose `int`/`bool` fields, leaving room to add flags without growing the struct.
+
**Error handling.** No exceptions; functions return `result_t` (`include/base/result.h`). Each module reserves a `result_BASE_` offset block and defines its own `result__*` codes starting from that base. Common generic codes (`result_OK`, `result_OOM`, `result_BAD_ARG`, etc.) live at `result_BASE_GENERIC`. Callers typically check `rc != result_OK` and propagate.
**Debug/logging.** `include/base/debug.h` provides `logf_info/warning/error/fatal/abort`, plus `check(err)` (log-and-`goto failure`) and `sentinel` (unreachable-code marker), both of which are compiled out entirely when `NDEBUG` is set — don't rely on their side effects in release builds.
@@ -68,7 +93,14 @@ New source/header files must be added by hand to the relevant `set(..._SOURCES .
- File header comment format: `/* filename.c -- one-line description */`.
- Section breaks within files use `/* ----- ... ----- */` rule comments.
- Public API docs use Doxygen (`\file`, `\param`, `\return`); a `Doxyfile` exists for generating them.
+- Edit `.c`/`.h` files with the Edit tool, never `sed -i` line-range splices — they corrupt the Allman/2-space layout and can't be verified without re-reading. To inspect exact bytes or indentation, Read the file; don't shell out to `cat -A`/`cat -v`.
+- After editing any `.c`/`.h` function prototype or definition, run `python3 tools/wrap_protos.py `; after editing a header's Doxygen, run `python3 tools/wrap_doxygen.py ` (skip vendored headers).
## Commit messages
Use [Conventional Commits](https://www.conventionalcommits.org/): `[optional scope]: `, e.g. `fix(pickle): handle zero-length blobs`. Common types: `feat`, `fix`, `docs`, `refactor`, `test`, `chore`, `ci`, `build`. Add a `!` before the colon (or a `BREAKING CHANGE:` footer) for breaking changes.
+
+When the user says "commit": stage the relevant files and `git commit` with the
+message passed via repeated `-m` flags (subject, then body). Do **not** write a
+`COMMIT_MSG` / `COMMIT_MSG_TMP` file. Never `git push` unless explicitly asked —
+pushing prompts for an SSH key passphrase and will hang.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 50283d94..9deba578 100755
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -14,7 +14,7 @@ endif()
list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/)
-project(DPTLib VERSION 0.5.0 DESCRIPTION "DPT's C Library" LANGUAGES C)
+project(DPTLib VERSION 0.6.0 DESCRIPTION "DPT's C Library" LANGUAGES C)
# The values set in the toolchain file aren't available until this point.
if(TARGET_RISCOS)
@@ -28,7 +28,12 @@ if(CCACHE_FOUND)
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_MENUS "Build the Wuss pop-up menu helper (implies WUSS_ICONS)" ON)
+option(WUSS_COMPONENTS "Build the Wuss shared task components (implies WUSS_MENUS)" ON)
# Referencing CMAKE_TOOLCHAIN_FILE avoids a warning on rebuilds.
if(NOT ${CMAKE_TOOLCHAIN_FILE} STREQUAL "")
@@ -40,7 +45,7 @@ add_subdirectory(libraries/fortify)
add_library(DPTLib)
set_target_properties(DPTLib PROPERTIES
- VERSION 0.5.0
+ VERSION 0.6.0
DESCRIPTION "DPT's Portable C Library"
C_STANDARD 99
PREFIX "" # remove 'lib' prefix
@@ -79,6 +84,7 @@ set(PUBLIC_HEADERS
include/framebuf/composite.h
include/framebuf/curve.h
include/framebuf/palettes.h
+ include/framebuf/pattern.h
include/framebuf/pixelfmt.h
include/framebuf/screen.h
include/framebuf/span-bgrx8888.h
@@ -93,6 +99,7 @@ set(PUBLIC_HEADERS
include/geom/line.h
include/geom/packer.h
include/geom/point.h
+ include/geom/size.h
include/io/path.h
include/io/stream-mem.h
include/io/stream-mtfcomp.h
@@ -101,6 +108,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
@@ -110,13 +118,29 @@ set(PUBLIC_HEADERS
include/utils/maths.h
include/utils/pack.h
include/utils/primes.h
+ include/wuss/task.h
include/wuss/window.h
include/wuss/wuss.h)
+if(WUSS_ICONS)
+ list(APPEND PUBLIC_HEADERS include/wuss/icon.h)
+endif()
+if(WUSS_MENUS)
+ list(APPEND PUBLIC_HEADERS include/wuss/menu.h)
+ list(APPEND PUBLIC_HEADERS include/wuss/menu-desc.h)
+endif()
+if(WUSS_COMPONENTS)
+ list(APPEND PUBLIC_HEADERS include/wuss/component/fontmenu.h)
+ list(APPEND PUBLIC_HEADERS include/wuss/component/colourmenu.h)
+endif()
+
# The public headers must be set as properties of the library, not as
# target_sources. The quoting is essential.
set_target_properties(DPTLib PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADERS}")
+set(BASE_SOURCES
+ libraries/base/result/result-string.c)
+
set(DATABASE_SOURCES
libraries/databases/digest-db/digest-db.c
libraries/databases/filename-db/filename-db.c
@@ -220,17 +244,24 @@ set(DATASTRUCT_SOURCES
set(FRAMEBUF_SOURCES
libraries/framebuf/bitmap/bitmap.c
+ libraries/framebuf/bitmap/fill-pattern.c
libraries/framebuf/bitmap/load.c
libraries/framebuf/bitmap/save.c
libraries/framebuf/bmfont/bmfont.c
+ libraries/framebuf/bmfont/enumerate.c
libraries/framebuf/colour/colour.c
libraries/framebuf/composite/composite.c
libraries/framebuf/curve/curve.c
libraries/framebuf/palettes/palettes.c
+ libraries/framebuf/pattern/pattern.c
libraries/framebuf/pixelfmt/log2bpp.c
libraries/framebuf/screen/screen.c
+ libraries/framebuf/screen/screen-copy-ninepatch.c
libraries/framebuf/screen/screen-copy-rect.c
libraries/framebuf/screen/screen-draw.c
+ libraries/framebuf/screen/screen-draw-circle.c
+ libraries/framebuf/screen/screen-fill-hline.c
+ libraries/framebuf/screen/screen-fill-pattern.c
libraries/framebuf/span-registry/get.c
libraries/framebuf/span-registry/regdata.h
libraries/framebuf/span/all8888.c
@@ -253,6 +284,7 @@ set(GEOM_SOURCES
libraries/geom/box/reset.c
libraries/geom/box/round.c
libraries/geom/box/round4.c
+ libraries/geom/box/size.c
libraries/geom/box/translated.c
libraries/geom/box/union.c
libraries/geom/layout/layout.c
@@ -273,6 +305,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
@@ -313,48 +347,115 @@ set(UTILS_SOURCES
libraries/utils/pack/unpack.c
libraries/utils/primes/primes.c)
-set(WUSS_SOURCES
- libraries/wuss/create.c
- libraries/wuss/destroy.c
+set(WUSS_CORE_SOURCES
+ libraries/wuss/core/backdrop.c
+ libraries/wuss/core/create.c
+ libraries/wuss/core/destroy.c
+ libraries/wuss/core/get-font.c
+ libraries/wuss/core/get-pointer.c
+ libraries/wuss/core/idle.c
+ libraries/wuss/core/impl.h
+ libraries/wuss/core/invalidate.c
+ libraries/wuss/core/mouse-click.c
+ libraries/wuss/core/mouse-move.c
+ libraries/wuss/core/nearest-colour.c
+ libraries/wuss/core/rebuild-palettecache.c
+ libraries/wuss/core/redraw.c
+ libraries/wuss/core/scroll.c
+ libraries/wuss/core/scroll-step.c
+ libraries/wuss/core/set-backdrop.c
+ libraries/wuss/core/set-palette.c
+ libraries/wuss/core/task/create.c
+ libraries/wuss/core/task/destroy.c
+ libraries/wuss/core/task/set-autoclose.c
+ libraries/wuss/core/task/deliver.c
+ libraries/wuss/core/window/at.c
+ libraries/wuss/core/window/create.c
+ libraries/wuss/core/window/create-placed.c
+ libraries/wuss/core/window/close.c
+ libraries/wuss/core/window/get-content-bounds.c
+ libraries/wuss/core/window/get-scroll.c
+ libraries/wuss/core/window/get-visible-bounds.c
+ libraries/wuss/core/window/invalidate.c
+ libraries/wuss/core/window/move.c
+ libraries/wuss/core/window/resize.c
+ libraries/wuss/core/window/restack.c
+ libraries/wuss/core/window/set-background.c
+ libraries/wuss/core/window/set-hidden.c
+ libraries/wuss/core/window/set-scroll.c
+ libraries/wuss/core/window/try-close.c)
+
+set(WUSS_FURNITURE_SOURCES
libraries/wuss/furniture/back-box.c
libraries/wuss/furniture/close-box.c
libraries/wuss/furniture/content-box.c
libraries/wuss/furniture/drag-resize.c
libraries/wuss/furniture/draw.c
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/scroll-box.c
libraries/wuss/furniture/titlebar-box.c
libraries/wuss/furniture/toggle-action.c
libraries/wuss/furniture/toggle-box.c
- libraries/wuss/furniture/vscroll-box.c
- libraries/wuss/furniture.h
- libraries/wuss/idle.c
- libraries/wuss/impl.h
- libraries/wuss/invalidate.c
- libraries/wuss/mouse-click.c
- libraries/wuss/mouse-move.c
- libraries/wuss/redraw.c
- libraries/wuss/scroll.c
- libraries/wuss/task-start.c
- libraries/wuss/task-stop.c
- libraries/wuss/window/at.c
- libraries/wuss/window/create.c
- libraries/wuss/window/close.c
- libraries/wuss/window/get-content-bounds.c
- libraries/wuss/window/get-scroll.c
- libraries/wuss/window/get-visible-bounds.c
- libraries/wuss/window/invalidate.c
- libraries/wuss/window/move.c
- libraries/wuss/window/resize.c
- libraries/wuss/window/restack.c
- libraries/wuss/window/set-background.c
- libraries/wuss/window/set-scroll.c)
+ libraries/wuss/furniture.h)
+
+set(WUSS_ICON_SOURCES
+ libraries/wuss/icon/create.c
+ libraries/wuss/icon/create-array.c
+ libraries/wuss/icon/delete.c
+ libraries/wuss/icon/draw.c
+ libraries/wuss/icon/free.c
+ libraries/wuss/icon/from-spec.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
+ libraries/wuss/icon/hit-test.c
+ libraries/wuss/icon/invalidate.c
+ libraries/wuss/icon/plot.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)
+
+set(WUSS_MENU_SOURCES
+ libraries/wuss/menu/menu.c
+ libraries/wuss/helpers/create-from-desc.c
+ libraries/wuss/menu.h)
+
+set(WUSS_COMPONENT_SOURCES
+ libraries/wuss/component/fontmenu.c
+ libraries/wuss/component/colourmenu.c)
+
+set(WUSS_SOURCES ${WUSS_CORE_SOURCES})
+if(WUSS_FURNITURE)
+ list(APPEND WUSS_SOURCES ${WUSS_FURNITURE_SOURCES})
+endif()
+if(WUSS_COMPONENTS)
+ set(WUSS_MENUS ON) # the shared components build on the menu helper
+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()
+if(WUSS_COMPONENTS)
+ list(APPEND WUSS_SOURCES ${WUSS_COMPONENT_SOURCES})
+endif()
set(ALL_SOURCES
${PUBLIC_HEADERS}
+ ${BASE_SOURCES}
${DATABASE_SOURCES}
${DATASTRUCT_SOURCES}
${FRAMEBUF_SOURCES}
@@ -387,6 +488,7 @@ if(TARGET_RISCOS)
# OSLib (& anything else in GCCSDK)
target_include_directories(DPTLib PUBLIC $ENV{GCCSDK_INSTALL_ENV}/include)
target_link_libraries(DPTLib PUBLIC $ENV{GCCSDK_INSTALL_ENV}/lib/libOSLib32.a)
+ target_compile_definitions(DPTLib PUBLIC TARGET_RISCOS)
endif()
if(USE_FORTIFY)
@@ -394,11 +496,34 @@ if(USE_FORTIFY)
target_link_libraries(DPTLib PUBLIC Fortify)
endif()
+if(USE_ASAN)
+ target_compile_options(DPTLib PUBLIC -fsanitize=address,undefined -g -O0)
+ target_link_options(DPTLib PUBLIC -fsanitize=address,undefined)
+endif()
+
if(DPTLIB_IMAGES_READ_ONLY)
target_compile_definitions(DPTLib PRIVATE DPTLIB_IMAGES_READ_ONLY)
endif()
-if(NOT TARGET_RISCOS)
+if(WUSS_FURNITURE)
+ target_compile_definitions(DPTLib PUBLIC WUSS_FURNITURE)
+endif()
+if(WUSS_ICONS)
+ target_compile_definitions(DPTLib PUBLIC WUSS_ICONS)
+endif()
+if(WUSS_MENUS)
+ target_compile_definitions(DPTLib PUBLIC WUSS_MENUS)
+endif()
+if(WUSS_COMPONENTS)
+ target_compile_definitions(DPTLib PUBLIC WUSS_COMPONENTS)
+endif()
+
+if(EMSCRIPTEN)
+ # Emscripten's bundled libpng port (pulls in its zlib port); provides the
+ # headers at compile time and the archive at link time.
+ target_compile_options(DPTLib PUBLIC "-sUSE_LIBPNG=1")
+ target_link_options(DPTLib PUBLIC "-sUSE_LIBPNG=1")
+elseif(NOT TARGET_RISCOS)
set(CMAKE_FIND_FRAMEWORK NEVER)
find_package(PNG REQUIRED MODULE)
target_link_libraries(DPTLib PRIVATE PNG::PNG)
@@ -506,6 +631,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
@@ -523,6 +649,7 @@ if(BUILD_TESTS)
libraries/framebuf/bmfont/test/bmfont-test.c
libraries/framebuf/composite/test/composite-test.c
libraries/framebuf/curve/test/curve-test.c
+ libraries/framebuf/screen/test/screen-test.c
libraries/geom/box/test/box-test.c
libraries/geom/layout/test/layout-test.c
libraries/geom/packer/test/packer-test.c
@@ -532,16 +659,6 @@ 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/checker.c
- libraries/wuss/test/tasks/curve.c
- libraries/wuss/test/tasks/gradient.c
- libraries/wuss/test/tasks/image.c
- libraries/wuss/test/tasks/launcher.c
- libraries/wuss/test/tasks/palette.c
- libraries/wuss/test/tasks/sofa.c
- libraries/wuss/test/tasks/text.c
libraries/wuss/test/wuss-test.c)
source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${TEST_SOURCES})
@@ -566,15 +683,98 @@ 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)
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: main.c drives wuss and the task launcher; a
+ # per-platform backend (frontend-*.c) opens the surface and reports input.
+ # RISC OS uses native VDU/OS_Mouse calls; everything else uses SDL.
+ if(NOT (WUSS_FURNITURE AND WUSS_ICONS))
+ message(FATAL_ERROR "BUILD_APPS requires WUSS_FURNITURE and WUSS_ICONS")
+ endif()
+
+ 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/clock.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/lissajous.c
+ 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)
+
+ if(TARGET_RISCOS)
+ list(APPEND WUSS_APP_SOURCES apps/wuss/frontend-riscos.c)
+ else()
+ list(APPEND WUSS_APP_SOURCES apps/wuss/frontend-sdl.c)
+ endif()
+
+ 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 apps/wuss libraries/wuss/test)
+ target_compile_definitions(wuss PRIVATE WUSS_APP)
+ target_link_libraries(wuss DPTLib)
+
+ if(TARGET_RISCOS)
+ # native VDU/OS_Mouse frontend, no SDL
+ elseif(EMSCRIPTEN)
+ # Emscripten ships its own SDL3 port; the browser owns the main loop
+ # (see emscripten_set_main_loop_arg in apps/wuss/main.c). resources/ is
+ # baked into wuss.data and mounted at MEMFS /resources so the runtime
+ # file loads and the font picker's opendir() resolve.
+ target_compile_definitions(wuss PRIVATE USE_SDL)
+ target_compile_options(wuss PRIVATE "-sUSE_SDL=3")
+ target_link_options(wuss PRIVATE
+ "-sUSE_SDL=3"
+ "-sALLOW_MEMORY_GROWTH=1"
+ "-sEXIT_RUNTIME=0"
+ "--preload-file"
+ "${CMAKE_CURRENT_SOURCE_DIR}/resources@/resources")
+ set_target_properties(wuss PROPERTIES SUFFIX ".html")
+ else()
+ find_package(SDL3 REQUIRED)
+ target_compile_definitions(wuss PRIVATE USE_SDL)
+ target_link_libraries(wuss SDL3::SDL3)
+ endif()
+
+ if(NOT MSVC)
+ target_link_libraries(wuss m)
+ endif()
+
+ install(TARGETS wuss RUNTIME
+ DESTINATION ${CMAKE_INSTALL_PREFIX})
+endif()
diff --git a/README.md b/README.md
index e42b101b..67c36fcb 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
# DPTLib
-version 0.5.0
+version 0.6.0
[](https://github.com/dpt/DPTLib/actions)
@@ -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
@@ -91,7 +96,7 @@ DPTLib is my platform independent C library. It contains a wide variety of funct
- [`utils/pack.h`](https://github.com/dpt/DPTLib/blob/master/include/utils/pack.h) — structure packing and unpacking helpers
- [`utils/primes.h`](https://github.com/dpt/DPTLib/blob/master/include/utils/primes.h) — cache of prime numbers
-### Wuss
+### Windowing
- [`wuss/wuss.h`](https://github.com/dpt/DPTLib/blob/master/include/wuss/wuss.h) — minimal window manager {[docs](https://github.com/dpt/DPTLib/blob/master/docs/wuss.md)}
- [`wuss/window.h`](https://github.com/dpt/DPTLib/blob/master/include/wuss/window.h) — window creation, positioning, sizing and client delegation
diff --git a/apps/test/main.c b/apps/test/main.c
index 9080cd2f..aaf9f21d 100644
--- a/apps/test/main.c
+++ b/apps/test/main.c
@@ -1,4 +1,4 @@
-//
+/* test/main.c */
// main.c
// DPTLibTest
//
@@ -54,6 +54,7 @@ static const test_t tests[] =
{ "bmfont", bmfont_test },
{ "composite", composite_test },
{ "curve", curve_test },
+ { "screen", screen_test },
{ "box", box_test },
{ "layout", layout_test },
diff --git a/apps/wuss/frontend-riscos.c b/apps/wuss/frontend-riscos.c
new file mode 100644
index 00000000..3e88712e
--- /dev/null
+++ b/apps/wuss/frontend-riscos.c
@@ -0,0 +1,340 @@
+/* wuss/frontend-riscos.c -- native RISC OS backend for the Wuss demo */
+
+#ifdef WUSS_APP
+#ifdef __riscos
+
+#include
+#include
+
+#include "kernel.h"
+
+#ifdef FORTIFY
+#include "fortify/fortify.h"
+#endif
+
+#include "swis.h"
+
+#include "base/result.h"
+#include "base/utils.h"
+#include "framebuf/bitmap.h"
+#include "framebuf/colour.h"
+#include "framebuf/pixelfmt.h"
+#include "geom/point.h"
+#include "wuss/wuss.h"
+
+#include "frontend.h"
+
+/* This backend takes over the whole screen: it selects a 16-colour linear
+ * mode, points wuss's framebuffer straight at screen memory (so a redraw
+ * lands on the display with no blit) and polls OS_Mouse / the keyboard each
+ * frame. It restores the entry mode on close. There is no Wimp task here --
+ * wuss's own window furniture is the entire UI. */
+
+/* OS_ReadVduVariables indices we ask for, in this order. */
+enum
+{
+ VDUVAR_XEIG = 4, /* OS-units-per-pixel shift, x */
+ VDUVAR_YEIG = 5, /* OS-units-per-pixel shift, y */
+ VDUVAR_LINE_LEN = 6, /* bytes per screen row */
+ VDUVAR_YWIND_LIM = 11, /* max Y in OS units (screen height - 1, << YEIG) */
+ VDUVAR_DISPLAY_START = 149 /* base address of screen memory */
+};
+
+/* A 16-colour mode 640 x 480. Mode 27 is 640x480x16 on RISC OS; if a given
+ * machine lacks it, OS_ScreenMode substitutes the closest match and the code
+ * below reads back whatever it actually got. */
+#define WUSS_RISCOS_MODE 27
+
+struct wuss_frontend
+{
+ int entry_mode; /* mode number to restore on close */
+ int scr_width;
+ int scr_height;
+ void *screen_base; /* screen memory: wuss draws here directly */
+ int rowbytes;
+ int xeig, yeig; /* OS_Mouse returns OS units; >> eig gives pixels */
+ int last_buttons; /* OS_Mouse button state at the previous poll */
+};
+
+/* ----------------------------------------------------------------------- */
+
+/* Program the hardware palette from wuss's colour_t[]. colour_t.primary is
+ * pixelfmt_rgba8888: R in the low byte, then G, then B -- the same byte order
+ * OS_Word 12's palette block wants. */
+static void set_hw_palette(const colour_t *palette, int npalette)
+{
+ int i;
+ int n = MIN(npalette, 16);
+
+ for (i = 0; i < n; i++)
+ {
+ unsigned int rgba = palette[i].primary;
+ unsigned char block[5];
+
+ /* block = logical colour, 16 (set both flash states), R, G, B */
+ block[0] = (unsigned char) i;
+ block[1] = 16;
+ block[2] = (unsigned char) (rgba & 0xFF);
+ block[3] = (unsigned char) ((rgba >> 8) & 0xFF);
+ block[4] = (unsigned char) ((rgba >> 16) & 0xFF);
+
+ _swix(OS_Word, _INR(0,1), 12, block);
+ }
+}
+
+/* ----------------------------------------------------------------------- */
+
+result_t wuss_frontend_open(int width,
+ int height,
+ const colour_t *palette,
+ int npalette,
+ void **pixels,
+ int *rowbytes,
+ pixelfmt_t *fmt,
+ wuss_frontend_t **frontend)
+{
+ wuss_frontend_t *fe;
+ int vars[6]; /* 5 indices + a -1 terminator */
+ int vals[5];
+ _kernel_oserror *err;
+ char msg[160]; /* deferred so it prints after the mode restore */
+
+ msg[0] = '\0';
+
+ fe = calloc(1, sizeof(*fe));
+ if (fe == NULL)
+ return result_OOM;
+
+ /* remember the mode we came in on */
+ err = _swix(OS_ScreenMode, _IN(0) | _OUT(1), 1, &fe->entry_mode);
+ if (err != NULL)
+ {
+ snprintf(msg, sizeof(msg), "OS_ScreenMode read: %s", err->errmess);
+ goto failure;
+ }
+
+ /* switch to the 16-colour demo mode */
+ err = _swix(OS_ScreenMode, _INR(0,1), 0, WUSS_RISCOS_MODE);
+ if (err != NULL)
+ {
+ snprintf(msg, sizeof(msg), "OS_ScreenMode set mode %d: %s",
+ WUSS_RISCOS_MODE, err->errmess);
+ goto failure;
+ }
+
+ /* read back the geometry we actually got */
+ vars[0] = VDUVAR_XEIG;
+ vars[1] = VDUVAR_YEIG;
+ vars[2] = VDUVAR_LINE_LEN;
+ vars[3] = VDUVAR_YWIND_LIM;
+ vars[4] = VDUVAR_DISPLAY_START;
+ vars[5] = -1; /* OS_ReadVduVariables scans until it hits -1 */
+ err = _swix(OS_ReadVduVariables, _INR(0,1), vars, vals);
+ if (err != NULL)
+ {
+ snprintf(msg, sizeof(msg), "OS_ReadVduVariables: %s", err->errmess);
+ goto failure_restore;
+ }
+
+ fe->xeig = vals[0];
+ fe->yeig = vals[1];
+ fe->rowbytes = vals[2];
+ /* VDU var 11 (YWindLimit) is the top pixel row, in pixels not OS units */
+ fe->scr_height = vals[3] + 1;
+ fe->screen_base = (void *) vals[4];
+ fe->scr_width = fe->rowbytes * 2; /* 4bpp: 2 pixels per byte */
+ fe->last_buttons = 0;
+
+ /* the demo asks for a fixed size; if the mode came out smaller the tasks
+ * would draw off-screen, so bail rather than corrupt memory */
+ if (fe->scr_width < width || fe->scr_height < height)
+ {
+ snprintf(msg, sizeof(msg),
+ "mode %d gave %dx%d (xeig=%d yeig=%d linelen=%d ywindlim=%d),"
+ " need %dx%d",
+ WUSS_RISCOS_MODE, fe->scr_width, fe->scr_height,
+ vals[0], vals[1], vals[2], vals[3], width, height);
+ goto failure_restore;
+ }
+
+ /* the mode may be bigger than the demo; hand wuss only the area it asked
+ * for so a redraw can never run off the end of screen memory */
+ fe->scr_width = width;
+ fe->scr_height = height;
+
+ set_hw_palette(palette, npalette);
+
+ /* hide the text cursor: VDU 23,1,0 -- wuss draws over the whole screen and
+ * a blinking caret in the corner would show through */
+ _swix(OS_WriteN, _INR(0,1), "\x17\x01\x00\x00\x00\x00\x00\x00\x00\x00", 10);
+
+ /* OS_ScreenMode leaves the pointer off. Turn on pointer 1 with the default
+ * arrow shape (*Pointer 1 == OS_Byte 106, 1) and confine it to the screen.
+ * wuss draws no cursor of its own, so this is the pointer the user sees. */
+ _swix(OS_Byte, _INR(0,1), 106, 1);
+
+ *pixels = fe->screen_base;
+ *rowbytes = fe->rowbytes;
+ *fmt = pixelfmt_p4; /* RISC OS 4bpp: leftmost pixel in the low nibble */
+ *frontend = fe;
+ return result_OK;
+
+
+failure_restore:
+
+ _swix(OS_ScreenMode, _INR(0,1), 0, fe->entry_mode);
+
+failure:
+
+ /* mode is restored to text by now, so this reaches the screen */
+ if (msg[0] != '\0')
+ fprintf(stderr, "wuss: frontend_open: %s\n", msg);
+ free(fe);
+ return result_TEST_FAILED;
+}
+
+/* Map an OS_Mouse button word (bit 2 = Select/left, bit 1 = Menu/middle,
+ * bit 0 = Adjust/right on a 3-button mouse) to wuss_button_t, whose bit
+ * values already match RISC OS order. */
+static wuss_button_t mouse_buttons_to_wuss(int buttons)
+{
+ wuss_button_t b = wuss_BUTTON_NONE;
+
+ if (buttons & 4) b |= wuss_BUTTON_SELECT;
+ if (buttons & 2) b |= wuss_BUTTON_MENU;
+ if (buttons & 1) b |= wuss_BUTTON_ADJUST;
+ return b;
+}
+
+/* Keys the demo reacts to: negative INKEY scan code -> input kind. Q and
+ * Escape both quit. */
+static const struct
+{
+ int scan;
+ wuss_input_kind_t kind;
+}
+g_keys[] =
+{
+ { -17, wuss_INPUT_QUIT }, /* Q */
+ { -113, wuss_INPUT_QUIT }, /* Escape */
+ { -114, wuss_INPUT_REDRAW_ALL }, /* F1 */
+ { -116, wuss_INPUT_PIXEL_STRESS }, /* F3 */
+ { -117, wuss_INPUT_PALETTE_CYCLE } /* F4 */
+};
+
+static bool key_down(int scan)
+{
+ int r1, r2;
+
+ /* OS_Byte 129 with R1=scan, R2=0xFF: R1 = 0xFF if that key is pressed. */
+ _swix(OS_Byte, _INR(0,2) | _OUTR(1,2), 129, scan & 0xFF, 0xFF, &r1, &r2);
+ return (r1 & 0xFF) == 0xFF;
+}
+
+bool wuss_frontend_poll(wuss_frontend_t *fe, wuss_input_t *event)
+{
+ static unsigned int key_was; /* bit i = g_keys[i] was down last poll */
+
+ unsigned int key_now = 0;
+ int mx, my, buttons, t;
+ int px, py;
+ size_t i;
+
+ /* keyboard first: fire one event on the press edge so a held key doesn't
+ * repeat every frame */
+ for (i = 0; i < NELEMS(g_keys); i++)
+ if (key_down(g_keys[i].scan))
+ key_now |= 1u << i;
+
+ for (i = 0; i < NELEMS(g_keys); i++)
+ {
+ unsigned int bit = 1u << i;
+
+ if ((key_now & bit) && !(key_was & bit))
+ {
+ key_was = key_now;
+ event->kind = g_keys[i].kind;
+ return true;
+ }
+ }
+ key_was = key_now;
+
+ /* poll the mouse: OS_Mouse returns x,y in OS units and a button word */
+ if (_swix(OS_Mouse, _OUTR(0,3), &mx, &my, &buttons, &t) != NULL)
+ return false;
+
+ /* OS units -> pixels; y is bottom-up on RISC OS, wuss wants top-down */
+ px = mx >> fe->xeig;
+ py = (fe->scr_height - 1) - (my >> fe->yeig);
+
+ if (buttons != fe->last_buttons)
+ {
+ int pressed = buttons & ~fe->last_buttons;
+ int released = fe->last_buttons & ~buttons;
+
+ fe->last_buttons = buttons;
+
+ if (pressed)
+ {
+ event->kind = wuss_INPUT_MOUSE_DOWN;
+ event->pos = POINT(px, py);
+ event->button = mouse_buttons_to_wuss(pressed);
+ return true;
+ }
+ if (released)
+ {
+ event->kind = wuss_INPUT_MOUSE_UP;
+ event->pos = POINT(px, py);
+ event->button = mouse_buttons_to_wuss(released);
+ return true;
+ }
+ }
+
+ /* no button change: report the move once, then say the queue is empty */
+ {
+ static int last_px = -1, last_py = -1;
+
+ if (px != last_px || py != last_py)
+ {
+ last_px = px;
+ last_py = py;
+ event->kind = wuss_INPUT_MOUSE_MOVE;
+ event->pos = POINT(px, py);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void wuss_frontend_present(wuss_frontend_t *fe, const bitmap_t *bm)
+{
+ NOT_USED(fe);
+ NOT_USED(bm);
+
+ /* wuss drew straight into screen memory; just pace to the frame rate */
+ _swix(OS_Byte, _IN(0), 19);
+}
+
+void wuss_frontend_set_palette(wuss_frontend_t *fe,
+ const colour_t *palette,
+ int npalette)
+{
+ NOT_USED(fe);
+ set_hw_palette(palette, npalette);
+}
+
+void wuss_frontend_close(wuss_frontend_t *fe)
+{
+ if (fe == NULL)
+ return;
+
+ _swix(OS_Byte, _INR(0,1), 106, 0); /* *Pointer 0: turn the pointer off */
+ _swix(OS_WriteN, _INR(0,1), /* VDU 23,1,1: text cursor back on */
+ "\x17\x01\x01\x00\x00\x00\x00\x00\x00\x00", 10);
+ _swix(OS_ScreenMode, _INR(0,1), 0, fe->entry_mode);
+ free(fe);
+}
+
+#endif /* __riscos */
+#endif /* WUSS_APP */
diff --git a/apps/wuss/frontend-sdl.c b/apps/wuss/frontend-sdl.c
new file mode 100644
index 00000000..206f53a2
--- /dev/null
+++ b/apps/wuss/frontend-sdl.c
@@ -0,0 +1,311 @@
+/* wuss/frontend-sdl.c -- SDL backend for the Wuss interactive demo */
+
+#ifdef WUSS_APP
+#ifdef USE_SDL
+
+#include
+#include
+
+#ifdef FORTIFY
+#include "fortify/fortify.h"
+#endif
+
+#include "base/result.h"
+#include "base/utils.h"
+#include "framebuf/bitmap.h"
+#include "framebuf/colour.h"
+#include "framebuf/pixelfmt.h"
+#include "geom/point.h"
+#include "wuss/wuss.h"
+
+#include
+
+#include "frontend.h"
+
+/* Screen pixel format for the demo: 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_SDL_32BPP 0
+
+/* ----------------------------------------------------------------------- */
+
+struct wuss_frontend
+{
+ SDL_Window *window;
+ SDL_Renderer *renderer;
+ SDL_Texture *texture;
+ int scr_width;
+ int scr_height;
+ void *pixels; /* the private framebuffer handed to the caller */
+};
+
+/* ----------------------------------------------------------------------- */
+
+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);
+}
+
+/* ----------------------------------------------------------------------- */
+
+result_t wuss_frontend_open(int width,
+ int height,
+ const colour_t *palette,
+ int npalette,
+ void **pixels,
+ int *rowbytes,
+ pixelfmt_t *fmt,
+ wuss_frontend_t **frontend)
+{
+ wuss_frontend_t *fe;
+ int stride;
+
+ NOT_USED(palette);
+ NOT_USED(npalette);
+
+#if WUSS_SDL_32BPP
+ stride = width * 4; /* pixelfmt_bgrx8888: 4 bytes/pixel */
+#else
+ stride = width / 2; /* pixelfmt_p4: 2 pixels/byte */
+#endif
+
+ fe = calloc(1, sizeof(*fe));
+ if (fe == NULL)
+ return result_OOM;
+
+ fe->scr_width = width;
+ fe->scr_height = height;
+
+ fe->pixels = malloc((size_t) stride * height);
+ if (fe->pixels == NULL)
+ {
+ free(fe);
+ return result_OOM;
+ }
+
+ if (!SDL_Init(SDL_INIT_VIDEO))
+ {
+ fprintf(stderr, "Error: SDL_Init: %s\n", SDL_GetError());
+ goto failure;
+ }
+
+ fe->window = SDL_CreateWindow("Wuss", width, height, 0);
+ if (fe->window == NULL)
+ {
+ fprintf(stderr, "Error: SDL_CreateWindow: %s\n", SDL_GetError());
+ goto failure;
+ }
+
+ fe->renderer = SDL_CreateRenderer(fe->window, NULL);
+ if (fe->renderer == NULL)
+ {
+ fprintf(stderr, "Error: SDL_CreateRenderer: %s\n", SDL_GetError());
+ goto failure;
+ }
+
+ fe->texture = SDL_CreateTexture(fe->renderer, SDL_PIXELFORMAT_ARGB8888,
+ SDL_TEXTUREACCESS_STREAMING, width, height);
+ if (fe->texture == NULL)
+ {
+ fprintf(stderr, "Error: SDL_CreateTexture: %s\n", SDL_GetError());
+ goto failure;
+ }
+
+ SDL_SetTextureBlendMode(fe->texture, SDL_BLENDMODE_NONE);
+ /* keep pixels crisp when F2 scales the window up */
+ SDL_SetTextureScaleMode(fe->texture, SDL_SCALEMODE_NEAREST);
+
+#if WUSS_SDL_32BPP
+ *fmt = pixelfmt_bgrx8888;
+#else
+ *fmt = pixelfmt_p4;
+#endif
+
+ *pixels = fe->pixels;
+ *rowbytes = stride;
+ *frontend = fe;
+ return result_OK;
+
+
+failure:
+
+ if (fe->texture) SDL_DestroyTexture(fe->texture);
+ if (fe->renderer) SDL_DestroyRenderer(fe->renderer);
+ if (fe->window) SDL_DestroyWindow(fe->window);
+ SDL_Quit();
+ free(fe->pixels);
+ free(fe);
+ return result_TEST_FAILED;
+}
+
+bool wuss_frontend_poll(wuss_frontend_t *fe, wuss_input_t *event)
+{
+ SDL_Event ev;
+
+ for (;;)
+ {
+ if (!SDL_PollEvent(&ev))
+ return false;
+
+ switch (ev.type)
+ {
+ case SDL_EVENT_QUIT:
+ event->kind = wuss_INPUT_QUIT;
+ return true;
+
+ case SDL_EVENT_KEY_UP:
+ if (ev.key.key == SDLK_Q)
+ event->kind = wuss_INPUT_QUIT;
+ else if (ev.key.key == SDLK_F1 && (ev.key.mod & SDL_KMOD_SHIFT))
+ event->kind = wuss_INPUT_GARBAGE;
+ else if (ev.key.key == SDLK_F1)
+ event->kind = wuss_INPUT_REDRAW_ALL;
+ else if (ev.key.key == SDLK_F3)
+ event->kind = wuss_INPUT_PIXEL_STRESS;
+ else if (ev.key.key == SDLK_F4)
+ event->kind = wuss_INPUT_PALETTE_CYCLE;
+ else if (ev.key.key == SDLK_F2)
+ {
+ int w, h;
+
+ /* F2 doubles the SDL window, Shift-F2 halves it: a backend-local
+ * zoom the demo loop never sees. */
+ SDL_GetWindowSize(fe->window, &w, &h);
+ if (ev.key.mod & SDL_KMOD_SHIFT)
+ SDL_SetWindowSize(fe->window, w / 2, h / 2);
+ else
+ SDL_SetWindowSize(fe->window, w * 2, h * 2);
+ continue;
+ }
+ else
+ continue;
+ return true;
+
+ case SDL_EVENT_MOUSE_BUTTON_DOWN:
+ {
+ int x, y;
+
+ sdl_pos_to_scr(fe->window, fe->scr_width, fe->scr_height,
+ ev.button.x, ev.button.y, &x, &y);
+ event->kind = wuss_INPUT_MOUSE_DOWN;
+ event->pos = POINT(x, y);
+ event->button = sdl_button_to_wuss(ev.button.button);
+ }
+ return true;
+
+ case SDL_EVENT_MOUSE_BUTTON_UP:
+ {
+ int x, y;
+
+ sdl_pos_to_scr(fe->window, fe->scr_width, fe->scr_height,
+ ev.button.x, ev.button.y, &x, &y);
+ event->kind = wuss_INPUT_MOUSE_UP;
+ event->pos = POINT(x, y);
+ event->button = sdl_button_to_wuss(ev.button.button);
+ }
+ return true;
+
+ case SDL_EVENT_MOUSE_MOTION:
+ {
+ int x, y;
+
+ sdl_pos_to_scr(fe->window, fe->scr_width, fe->scr_height,
+ ev.motion.x, ev.motion.y, &x, &y);
+ event->kind = wuss_INPUT_MOUSE_MOVE;
+ event->pos = POINT(x, y);
+ }
+ return true;
+
+ case SDL_EVENT_MOUSE_WHEEL:
+ {
+ int x, y;
+
+ sdl_pos_to_scr(fe->window, fe->scr_width, fe->scr_height,
+ ev.wheel.mouse_x, ev.wheel.mouse_y, &x, &y);
+ event->kind = wuss_INPUT_WHEEL;
+ event->pos = POINT(x, y);
+ event->wheel = (int) ev.wheel.y;
+ }
+ return true;
+
+ default:
+ continue;
+ }
+ }
+}
+
+void wuss_frontend_present(wuss_frontend_t *fe, const bitmap_t *bm)
+{
+#if WUSS_SDL_32BPP
+ SDL_UpdateTexture(fe->texture, NULL, bm->base, bm->rowbytes);
+#else
+ bitmap_t *disp;
+
+ /* wuss draws into a paletted bitmap; SDL wants bgrx. bitmap_convert reads
+ * the palette straight off `bm`, which the caller updates on F4, so a live
+ * palette change just shows up in the next converted frame. */
+ if (bitmap_convert(bm, pixelfmt_bgrx8888, &disp) == result_OK)
+ {
+ SDL_UpdateTexture(fe->texture, NULL, disp->base, disp->rowbytes);
+ free(disp->base);
+ free(disp);
+ }
+#endif
+
+ SDL_RenderTexture(fe->renderer, fe->texture, NULL, NULL);
+ SDL_RenderPresent(fe->renderer);
+
+ SDL_Delay(1000 / 60);
+}
+
+void wuss_frontend_set_palette(wuss_frontend_t *fe,
+ const colour_t *palette,
+ int npalette)
+{
+ NOT_USED(fe);
+ NOT_USED(palette);
+ NOT_USED(npalette);
+ /* SDL has no physical palette: the p4 -> bgrx conversion in present() reads
+ * the palette straight off the caller's bitmap_t, which it has already
+ * updated. Nothing to do here. */
+}
+
+void wuss_frontend_close(wuss_frontend_t *fe)
+{
+ if (fe == NULL)
+ return;
+
+ SDL_DestroyTexture(fe->texture);
+ SDL_DestroyRenderer(fe->renderer);
+ SDL_DestroyWindow(fe->window);
+ SDL_Quit();
+
+ free(fe->pixels);
+ free(fe);
+}
+
+#endif /* USE_SDL */
+#endif /* WUSS_APP */
diff --git a/apps/wuss/frontend.h b/apps/wuss/frontend.h
new file mode 100644
index 00000000..5395d5a6
--- /dev/null
+++ b/apps/wuss/frontend.h
@@ -0,0 +1,91 @@
+/* wuss/frontend.h -- platform backend for the Wuss interactive demo */
+
+#ifndef WUSS_FRONTEND_H
+#define WUSS_FRONTEND_H
+
+#ifdef WUSS_APP
+
+#include
+
+#include "base/result.h"
+#include "framebuf/bitmap.h"
+#include "framebuf/colour.h"
+#include "framebuf/pixelfmt.h"
+#include "geom/point.h"
+#include "wuss/wuss.h"
+
+/* One backend drives the demo: SDL on the desktop, native VDU/OS_Mouse calls
+ * on RISC OS. main.c owns the wuss instance and the main loop; the backend
+ * only opens a surface, reports input as normalised wuss_input_t records and
+ * gets the framebuffer onto the screen. */
+
+typedef struct wuss_frontend wuss_frontend_t;
+
+/* Normalised input event kinds. The backend translates whatever the platform
+ * delivers (SDL events, polled OS_Mouse state, key scans) into these; the
+ * loop in main.c acts on them without knowing which backend produced them. */
+typedef enum wuss_input_kind
+{
+ wuss_INPUT_NONE = 0,
+ wuss_INPUT_QUIT, /* window closed, or the quit key */
+ wuss_INPUT_MOUSE_MOVE, /* .pos */
+ wuss_INPUT_MOUSE_DOWN, /* .pos, .button */
+ wuss_INPUT_MOUSE_UP, /* .pos, .button */
+ wuss_INPUT_WHEEL, /* .pos, .wheel */
+ wuss_INPUT_REDRAW_ALL, /* force a full redraw (F1) */
+ wuss_INPUT_GARBAGE, /* corrupt the whole screen for one frame (Shift-F1) */
+ wuss_INPUT_PIXEL_STRESS, /* one-pixel-at-a-time redraw (F3) */
+ wuss_INPUT_PALETTE_CYCLE /* advance to the next system palette (F4) */
+}
+wuss_input_kind_t;
+
+typedef struct wuss_input
+{
+ wuss_input_kind_t kind;
+ point_t pos; /* screen-space pixel coordinates */
+ wuss_button_t button;
+ int wheel; /* wheel delta, +ve = up */
+}
+wuss_input_t;
+
+/* Open a drawing surface `width` x `height` pixels.
+ *
+ * `palette` / `npalette` are the initial system palette: a backend that owns
+ * the physical palette (RISC OS 16-colour mode) programmes it here.
+ *
+ * On return `*pixels` points at storage for width*height pixels at
+ * `*rowbytes` stride, and `*fmt` is the pixel format that storage expects.
+ * The caller wraps this in a bitmap_t and hands it to wuss. The backend may
+ * hand back screen memory directly (no copy on present) or a private buffer
+ * (present() blits it). Either way the caller must not free *pixels; call
+ * wuss_frontend_close to release it.
+ */
+result_t wuss_frontend_open(int width,
+ int height,
+ const colour_t *palette,
+ int npalette,
+ void **pixels,
+ int *rowbytes,
+ pixelfmt_t *fmt,
+ wuss_frontend_t **frontend);
+
+/* Pull the next pending input event. Returns true and fills *event while
+ * events remain; returns false when the queue is empty for this frame. */
+bool wuss_frontend_poll(wuss_frontend_t *frontend, wuss_input_t *event);
+
+/* Push the current framebuffer contents to the screen and pace the frame. A
+ * backend rendering straight into screen memory only waits for vsync here. */
+void wuss_frontend_present(wuss_frontend_t *frontend, const bitmap_t *bm);
+
+/* Push a new system palette to the physical palette, if the backend owns one.
+ * Called after the demo cycles palettes (F4). No-op for SDL. */
+void wuss_frontend_set_palette(wuss_frontend_t *frontend,
+ const colour_t *palette,
+ int npalette);
+
+/* Tear down the surface and free everything wuss_frontend_open allocated. */
+void wuss_frontend_close(wuss_frontend_t *frontend);
+
+#endif /* WUSS_APP */
+
+#endif /* WUSS_FRONTEND_H */
diff --git a/apps/wuss/main.c b/apps/wuss/main.c
new file mode 100644
index 00000000..14f54546
--- /dev/null
+++ b/apps/wuss/main.c
@@ -0,0 +1,885 @@
+/* wuss/main.c -- Wuss - interactive minimal window manager demo */
+
+#include
+#include
+#include
+
+#ifdef __EMSCRIPTEN__
+#include
+#endif
+
+#ifdef FORTIFY
+#include "fortify/fortify.h"
+#endif
+
+#include "base/debug.h"
+#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/task.h"
+#include "wuss/wuss.h"
+#include "wuss/window.h"
+#include "wuss/menu.h"
+#include "wuss/menu-desc.h"
+
+#include "frontend.h"
+
+#include "tasks/ball.h"
+#include "tasks/blank.h"
+#include "tasks/chars.h"
+#include "tasks/checker.h"
+#include "tasks/clock.h"
+#include "tasks/curve.h"
+#include "tasks/gradient.h"
+#include "tasks/icons.h"
+#include "tasks/image.h"
+#include "tasks/lissajous.h"
+#include "tasks/palette.h"
+#include "tasks/porter-duff.h"
+#include "tasks/sofa.h"
+#include "tasks/swatches.h"
+#include "tasks/text.h"
+
+/* ----------------------------------------------------------------------- */
+
+/* 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 a file-scope struct is as good as a passed-around context */
+static struct
+{
+ wuss_t *wuss;
+ wuss_task_t *menu_task; /* owns the menus and the hidden Details window */
+ 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
+ * task's wuss_EVENT_QUIT handler. On any create failure X_create has already
+ * torn down whatever it built and freed the block itself, so the only block
+ * the spawner frees is the font-less chars case: create returns OK but opens
+ * no window, leaving the block with no owner. */
+
+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, t);
+ if (rc != result_OK) return rc;
+ if (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.resources, t);
+ if (rc != result_OK) return rc;
+ if (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, t);
+ if (rc != result_OK) return rc;
+ if (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.resources, t);
+ if (rc != result_OK) return rc;
+ if (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) return rc;
+ if (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("ninepatch", "png"));
+
+ logf_info("wuss: image task loading \"%s\" + \"%s\"", buf, ninepatch);
+ rc = image_create(g.wuss, buf, ninepatch, t);
+ if (rc != result_OK)
+ logf_error("wuss: image_create(\"%s\") failed, rc=0x%X (%s)", buf, rc,
+ result_string(rc));
+ if (rc != result_OK) return rc;
+ if (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, t);
+ if (rc != result_OK) return rc;
+ if (t->window == NULL) { free(t); return rc; }
+ return result_OK;
+}
+
+static result_t spawn_clock(void)
+{
+ clock_task_t *t = calloc(1, sizeof(*t));
+ result_t rc;
+ if (t == NULL) return result_OOM;
+ rc = clock_create(g.wuss, g.daydream_font, t);
+ if (rc != result_OK) return rc;
+ if (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, t);
+ if (rc != result_OK) return rc;
+ if (t->window == NULL) { free(t); return rc; }
+ 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, t);
+ if (rc != result_OK) return rc;
+ if (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, t);
+ if (rc != result_OK) return rc;
+ if (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) return rc;
+ if (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.daydream_font, g.resources, t);
+ if (rc != result_OK)
+ logf_error("wuss: icons_create (resources \"%s\") failed, rc=0x%X (%s)",
+ g.resources, rc, result_string(rc));
+ if (rc != result_OK) return rc;
+ if (t->window == NULL) { free(t); return rc; }
+ 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) return rc;
+ if (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)
+ logf_error("wuss: porter_duff_create (resources \"%s\") failed, "
+ "rc=0x%X (%s)", g.resources, rc, result_string(rc));
+ if (rc != result_OK) return rc;
+ if (t->window == NULL) { free(t); return rc; }
+ return result_OK;
+}
+
+/* A static demo menu tree for the pop-up helper: a submenu, a couple of
+ * 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 },
+ { "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)
+};
+
+/* 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 =
+{
+ "Display", g_menu_items, NELEMS(g_menu_items)
+};
+
+/* g_task_menu / g_launch_menu / g_test_menu picks are dispatched by index;
+ * g_menu / g_menu_desc picks just print. Every menu is opened by g.menu_task,
+ * so one handler sees every wuss_EVENT_MENU_SELECT and tells them apart by
+ * data.menu_select.menu. Defined after the menus / spawn tables it needs. */
+static result_t menu_handle(wuss_window_t *window,
+ const wuss_event_t *event,
+ void *task_data);
+
+/* 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.menu_task, &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),
+ 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.menu_task, &g_menu, wuss_get_pointer(g.wuss), 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.menu_task, g_menu_desc, wuss_get_pointer(g.wuss),
+ NULL);
+}
+
+/* The task launcher is a MENU-button pop-up over the backdrop rather than a
+ * window of buttons. Each leaf menu pairs a *_items table with a *_spawn table
+ * in lock-step: picking row i of that menu calls its spawn[i]. */
+typedef result_t (*task_spawn_fn_t)(void);
+
+/* "Launch" submenu: the demo tasks. */
+static const wuss_menu_item_t g_launch_items[] =
+{
+ { "Ball", wuss_MENU_ITEM_NONE, NULL },
+ { "Blank", wuss_MENU_ITEM_NONE, NULL },
+ { "Chars", wuss_MENU_ITEM_NONE, NULL },
+ { "Checker", wuss_MENU_ITEM_NONE, NULL },
+ { "Clock", wuss_MENU_ITEM_NONE, NULL },
+ { "Curve", wuss_MENU_ITEM_NONE, NULL },
+ { "Gradient", wuss_MENU_ITEM_NONE, NULL },
+ { "Icons", 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 }
+};
+
+static const task_spawn_fn_t g_launch_spawn[] =
+{
+ spawn_ball, spawn_blank, spawn_chars, spawn_checker, spawn_clock, spawn_curve,
+ spawn_gradient, spawn_icons, spawn_image, spawn_lissajous, spawn_palette,
+ spawn_porter_duff, spawn_sofa, spawn_swatches, spawn_text
+};
+
+static const wuss_menu_t g_launch_menu =
+{
+ "Launch", g_launch_items, NELEMS(g_launch_items)
+};
+
+static result_t spawn_quit(void)
+{
+ g.quit = true;
+ return result_OK;
+}
+
+/* "Test" submenu: the menu-system exercisers. */
+static const wuss_menu_item_t g_test_items[] =
+{
+ { "Menu", wuss_MENU_ITEM_NONE, NULL },
+ { "Menu (desc)", wuss_MENU_ITEM_NONE, NULL }
+};
+
+static const task_spawn_fn_t g_test_spawn[] =
+{
+ spawn_menu, spawn_menu_desc
+};
+
+static const wuss_menu_t g_test_menu =
+{
+ "Test", g_test_items, NELEMS(g_test_items)
+};
+
+static const wuss_menu_item_t g_task_items[] =
+{
+ { "Launch", wuss_MENU_ITEM_NONE, &g_launch_menu, NULL },
+ { "Test", wuss_MENU_ITEM_DASHED, &g_test_menu, NULL },
+ { "Quit Wuss", wuss_MENU_ITEM_DASHED, NULL, NULL }
+};
+
+static const task_spawn_fn_t g_task_spawn[] =
+{
+ NULL, /* "Launch" -> submenu g_launch_menu */
+ NULL, /* "Test" -> submenu g_test_menu */
+ spawn_quit
+};
+
+static const wuss_menu_t g_task_menu =
+{
+ "Tasks", g_task_items, NELEMS(g_task_items)
+};
+
+static result_t menu_handle(wuss_window_t *window,
+ const wuss_event_t *event,
+ void *task_data)
+{
+ const wuss_menu_t *menu;
+ int index;
+
+ NOT_USED(window);
+ NOT_USED(task_data);
+
+ if (event->kind != wuss_EVENT_MENU_SELECT)
+ return result_OK;
+
+ menu = event->data.menu_select.menu;
+ index = event->data.menu_select.index;
+
+ if (menu == &g_launch_menu)
+ {
+ if (index >= 0 && index < (int) NELEMS(g_launch_spawn))
+ (void) g_launch_spawn[index]();
+ return result_OK;
+ }
+
+ if (menu == &g_test_menu)
+ {
+ if (index >= 0 && index < (int) NELEMS(g_test_spawn))
+ (void) g_test_spawn[index]();
+ return result_OK;
+ }
+
+ if (menu == &g_task_menu)
+ {
+ if (index >= 0 && index < (int) NELEMS(g_task_spawn) && g_task_spawn[index])
+ (void) g_task_spawn[index]();
+ return result_OK;
+ }
+
+ printf("menu: picked \"%s\"\n",
+ menu->items[index].text ? menu->items[index].text : "(sep)");
+ return result_OK;
+}
+
+/* Palettes the palette-cycle input steps through, in order. Each fills a
+ * colour_t[16]. */
+static void (*const g_palettes[])(colour_t *) =
+{
+ define_pico8_palette,
+ define_wimp16_palette
+};
+
+/* Furniture/bevel/accent/backdrop colour indices, one row per palette. Same
+ * field order as the assignments in run_wuss. */
+static const wuss_colour_t g_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 }
+};
+
+static void fill_chrome_config(wuss_config_t *config, int palette_index)
+{
+ const wuss_colour_t *c = g_chrome[palette_index];
+
+ 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];
+}
+
+/* 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 a multi-pixel/aligned clip (rather than trusting scr->clip) will
+ * visibly misdraw here even though it looks fine under larger dirty regions. */
+static void pixel_stress(wuss_t *wuss, int scr_width, int scr_height)
+{
+ 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);
+ }
+ }
+}
+
+/* one iteration of the event/redraw loop; a struct because Emscripten drives
+ * it as a callback (emscripten_set_main_loop_arg) rather than a plain while */
+struct wuss_frame_ctx
+{
+ wuss_t *wuss;
+ wuss_frontend_t *frontend;
+ bitmap_t *bm;
+ unsigned char *pixels;
+ int rowbytes;
+ int scr_width;
+ int scr_height;
+ colour_t *palette;
+ int npalette;
+ int palette_index;
+};
+
+static void wuss_frame(void *arg)
+{
+ struct wuss_frame_ctx *c = arg;
+ wuss_input_t ev;
+ bool pixel_stress_pending = false;
+ bool garbage_pending = false;
+
+ while (wuss_frontend_poll(c->frontend, &ev))
+ {
+ switch (ev.kind)
+ {
+ case wuss_INPUT_QUIT:
+ g.quit = true;
+ break;
+
+ case wuss_INPUT_REDRAW_ALL:
+ wuss_redraw(c->wuss);
+ break;
+
+ case wuss_INPUT_GARBAGE:
+ garbage_pending = true;
+ break;
+
+ case wuss_INPUT_PIXEL_STRESS:
+ pixel_stress_pending = true;
+ break;
+
+ case wuss_INPUT_PALETTE_CYCLE:
+ /* rebuild the palette, push it into the framebuffer bitmap, tell the
+ * backend (which owns any physical palette), then tell wuss, which
+ * refreshes its own copy and pokes every task to recache */
+ c->palette_index = (c->palette_index + 1) % (int) NELEMS(g_palettes);
+ g_palettes[c->palette_index](c->palette);
+ bitmap_set_palette(c->bm, c->palette);
+ wuss_frontend_set_palette(c->frontend, c->palette, c->npalette);
+ wuss_set_palette(c->wuss, c->palette, c->npalette);
+ break;
+
+ case wuss_INPUT_MOUSE_DOWN:
+ {
+ wuss_window_t *hit;
+
+ wuss_mouse_click(c->wuss, ev.pos, ev.button, wuss_MOUSE_DOWN, &hit);
+
+ /* MENU click on bare backdrop opens the task launcher there */
+ if (hit == NULL && (ev.button & wuss_BUTTON_MENU))
+ wuss_menu_open(g.menu_task, &g_task_menu, ev.pos, NULL);
+ }
+ break;
+
+ case wuss_INPUT_MOUSE_UP:
+ wuss_mouse_click(c->wuss, ev.pos, ev.button, wuss_MOUSE_UP, NULL);
+ break;
+
+ case wuss_INPUT_MOUSE_MOVE:
+ wuss_mouse_move(c->wuss, ev.pos, NULL);
+ break;
+
+ case wuss_INPUT_WHEEL:
+ wuss_scroll(c->wuss, ev.pos, ev.wheel, NULL);
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ wuss_idle(c->wuss);
+
+ if (garbage_pending)
+ {
+ /* corrupt the whole framebuffer and present it, then leave it alone --
+ * wuss only repaints what it knows is dirty, so the junk stays put
+ * until something else invalidates the screen */
+ unsigned char *p;
+ size_t n;
+ size_t i;
+
+ p = c->pixels;
+ n = (size_t) c->rowbytes * c->scr_height;
+ for (i = 0; i < n; i++)
+ p[i] = (unsigned char) rand();
+
+ wuss_frontend_present(c->frontend, c->bm);
+ }
+ else
+ {
+ if (pixel_stress_pending)
+ pixel_stress(c->wuss, c->scr_width, c->scr_height);
+ else
+ wuss_redraw_dirty(c->wuss);
+
+ wuss_frontend_present(c->frontend, c->bm);
+ }
+
+#ifdef __EMSCRIPTEN__
+ if (g.quit)
+ emscripten_cancel_main_loop();
+#endif
+}
+
+/* fonts[]/descs[] below hold [0] regular (system font), [1] bold, [2] symbol
+ * (menu tick / submenu arrow); kept <= wuss_MAX_FONTS so wuss_create's own
+ * nfonts check is never reached with arrays it can no longer fit in */
+#define WUSS_MAIN_NFONTS 3
+#if WUSS_MAIN_NFONTS > wuss_MAX_FONTS
+#error WUSS_MAIN_NFONTS exceeds wuss_MAX_FONTS
+#endif
+
+/* click windows to bring to front, drag titlebars to move; the redraw-all
+ * input redraws the whole screen, the pixel-stress input does it one pixel at
+ * a time to catch tasks that misbehave under a 1x1 clip; the palette-cycle
+ * input swaps the system palette live (wuss_set_palette); the quit input or
+ * closing the window exits */
+static result_t run_wuss(const char *resources)
+{
+ const int scr_width = 640;
+ const int scr_height = 480;
+
+ result_t rc;
+ const char *leafname;
+ const char *filename;
+ bmfont_t *fonts[WUSS_MAIN_NFONTS];
+ static const char *const names[WUSS_MAIN_NFONTS] =
+ { "Digits-Regular", "Digits-Bold", "Symbols" };
+ int nfonts;
+ int i;
+ void *pixels;
+ int rowbytes;
+ pixelfmt_t fmt;
+ bitmap_t bm;
+ screen_t scr;
+ colour_t palette[16];
+ wuss_t *wuss;
+ wuss_frontend_t *frontend;
+ bool use_wimp16;
+ int palette_index;
+
+ {
+ /* "wimp16" 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);
+ palette_index = use_wimp16 ? 1 : 0;
+ g_palettes[palette_index](palette);
+ }
+
+ logf_info("wuss: resources root = \"%s\"", resources);
+
+ {
+ nfonts = 0;
+ for (i = 0; i < WUSS_MAIN_NFONTS; i++)
+ {
+ leafname = path_join_leafname(names[i], "png");
+ filename = path_join_filename(resources, 3, "resources", "bmfonts",
+ leafname);
+ logf_info("wuss: loading font \"%s\"", filename);
+ rc = bmfont_create(filename, &fonts[i]);
+ if (rc != result_OK)
+ {
+ logf_error("wuss: bmfont_create(\"%s\") failed, rc=0x%X (%s)", filename,
+ rc, result_string(rc));
+ goto Failure;
+ }
+ nfonts++;
+ }
+ }
+
+ rc = wuss_frontend_open(scr_width, scr_height, palette, NELEMS(palette),
+ &pixels, &rowbytes, &fmt, &frontend);
+ logf_info("wuss: wuss_frontend_open -> rc=0x%X (%s)", rc, result_string(rc));
+ if (rc != result_OK)
+ goto Failure;
+
+ rc = bitmap_init(&bm, SIZE2D(scr_width, scr_height), fmt, rowbytes, palette,
+ pixels);
+ logf_info("wuss: bitmap_init -> rc=0x%X (%s)", rc, result_string(rc));
+ if (rc != result_OK)
+ goto Failure;
+
+ bitmap_clear(&bm, palette[palette_PICO8_WHITE]);
+
+ screen_for_bitmap(&scr, &bm);
+
+ {
+ wuss_config_t config;
+ wuss_font_desc_t descs[WUSS_MAIN_NFONTS]; /* slot classes/names for the
+ * picker menus -- [2] Symbols is chrome-only,
+ * never a text font choice */
+
+ fill_chrome_config(&config, use_wimp16 ? 1 : 0);
+
+ for (i = 0; i < nfonts; i++)
+ {
+ descs[i].font = fonts[i];
+ descs[i].font_class = (i == 2) ? wuss_FONT_CLASS_SYSTEM
+ : wuss_FONT_CLASS_NONE;
+ descs[i].name = names[i];
+ }
+
+ rc = wuss_create(&scr, descs, nfonts, palette, NELEMS(palette), &config,
+ NULL, &wuss);
+ logf_info("wuss: wuss_create -> rc=0x%X (%s)", rc, result_string(rc));
+ if (rc != result_OK)
+ goto Failure;
+ }
+
+ g.wuss = wuss;
+ g.palette = palette;
+ g.npalette = NELEMS(palette);
+ g.resources = resources;
+ g.daydream_font = fonts[0]; /* tasks draw with the regular weight */
+
+ {
+ wuss_task_desc_t desc;
+
+ desc.handle = menu_handle;
+ desc.task_data = NULL;
+ desc.name = "menu";
+ rc = wuss_task_create(wuss, &desc, &g.menu_task);
+ logf_info("wuss: wuss_task_create(menu) -> rc=0x%X (%s)", rc,
+ result_string(rc));
+ if (rc != result_OK)
+ goto Failure;
+ }
+
+ g.quit = false;
+
+ wuss_redraw(wuss);
+
+ {
+ struct wuss_frame_ctx ctx;
+
+ ctx.wuss = wuss;
+ ctx.frontend = frontend;
+ ctx.bm = &bm;
+ ctx.pixels = pixels;
+ ctx.rowbytes = rowbytes;
+ ctx.scr_width = scr_width;
+ ctx.scr_height = scr_height;
+ ctx.palette = palette;
+ ctx.npalette = NELEMS(palette);
+ ctx.palette_index = palette_index;
+
+#ifdef __EMSCRIPTEN__
+ /* the browser owns the loop; simulate_infinite_loop=1 means this call
+ * never returns, so &ctx (a stack local) stays live and the teardown
+ * below is unreachable -- fine, the page dies on navigation. fps=0 asks
+ * for requestAnimationFrame pacing. */
+ emscripten_set_main_loop_arg(wuss_frame, &ctx, 0, 1);
+#else
+ while (!g.quit)
+ wuss_frame(&ctx);
+#endif
+ }
+
+ /* ponytail: wuss_destroy() below force-closes every still-open window and
+ * frees every registered task node, but not the per-instance task_data
+ * block a spawn_* calloc'd, so any task window left open at quit leaks that
+ * block. Harmless at process exit. */
+ wuss_destroy(wuss); /* also sweeps g.menu_task and closes any open chain */
+
+ /* menus are only safe to free once wuss_destroy has torn down any chain
+ * that was still borrowing them */
+ wuss_menu_destroy(g_menu_desc);
+
+ for (i = 0; i < nfonts; i++)
+ bmfont_destroy(fonts[i]);
+
+ wuss_frontend_close(frontend);
+
+ return result_TEST_PASSED;
+
+
+Failure:
+
+ printf("run_wuss: failed (rc=0x%X: %s)\n", rc, result_string(rc));
+
+ return result_TEST_FAILED;
+}
+
+/* ----------------------------------------------------------------------- */
+
+int main(int argc, char *argv[])
+{
+ /* path_join_filename splices the root and each branch with the platform
+ * separator, so the "here" root differs: "." on Unix, but on RISC OS the
+ * currently-selected directory is "@" ("." there would give "..resources"). */
+#ifdef __riscos
+ const char *resources = "@";
+#else
+ const char *resources = ".";
+#endif
+ 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/docs/framebuf/bmfont.md b/docs/framebuf/bmfont.md
index 19b5c2cd..025686f7 100644
--- a/docs/framebuf/bmfont.md
+++ b/docs/framebuf/bmfont.md
@@ -2,15 +2,15 @@
"bmfont" is a sub-library of DPTLib for drawing proportionally spaced bitmap fonts. It reads font definitions from PNG files like this:
-
+
or this:
-
+
or even this:
-
+
...which have the glyphs laid out in a grid, with extra lines inserted, that define the advance widths.
diff --git a/docs/framebuf/drawing.md b/docs/framebuf/drawing.md
new file mode 100644
index 00000000..a30f9357
--- /dev/null
+++ b/docs/framebuf/drawing.md
@@ -0,0 +1,213 @@
+# [DPTLib](https://github.com/dpt/DPTLib) > framebuf > drawing
+
+This document defines the **taxonomy and naming scheme** for 2D drawing
+operations in the `framebuf` module: primitives, fills, blits, and the paints
+that feed them.
+
+It is a design agreement. Some of the names below are already shipped; others
+are the agreed target for code not yet written. Each function's status is marked
+**[shipped]** or **[planned]**.
+
+## Rationale
+
+`framebuf` grew its drawing primitives one at a time (`screen_draw_line`,
+`screen_fill_rect`, `screen_fill_pattern`, `screen_draw_ninepatch`,
+`bitmap_draw_pattern`, ...). Without a scheme the names had begun to contradict
+each other:
+
+- "pattern" meant two different things (a two-colour repeating tile vs a
+ one-colour stencil mask), spelled with two different verbs (`fill` vs `draw`);
+- `draw` was overloaded across stroking an outline, blitting a raster, and
+ placing text;
+- "ninepatch" (code) / "9-patch" (prose) / "9tile" (art asset) were three
+ spellings of one thing.
+
+New primitives are wanted (filled triangles and polygons, rounded rectangles,
+gradients, sprite-sheet tiling). A scheme fixed now keeps their names
+predictable.
+
+## The four axes
+
+Every drawing operation is one point in a four-axis space. The function name is
+built from the axes in order:
+
+```
+__[_][_]
+```
+
+### Axis 1 — SURFACE (where it draws)
+
+| prefix | surface | clipping |
+|----------|---------|----------|
+| `screen` | `screen_t` — the drawing surface | clips every op to `scr->clip` |
+| `bitmap` | `bitmap_t` — a raw image | none; whole-image or caller-clipped |
+
+`screen_t` is `bitmap_ALL_MEMBERS` plus a `box_t clip`. It is the surface a
+client or the window manager draws onto. `bitmap_*` drawing ops are for
+off-screen construction and whole-image transforms.
+
+The per-pixel-format `span_t` layer (`copy` / `fill` / `blendconst` /
+`blendarray`) sits below both and is not a public drawing verb. Solid fills go
+through `span->fill(row_base, first_pixel, pixel, length)`; `first_pixel` lets
+the P4 handler address an odd nibble so callers never pre-pack. `screen_fill_hline`
+(and thus `screen_fill_rect` and the circle scanline fill) is a thin wrapper over
+it.
+
+### Axis 2 — ACTION (what kind of mark)
+
+| verb | mark | object it takes |
+|---------|------|-----------------|
+| `draw` | a 1D mark along a boundary: an outline, a line, a polyline | a **primitive** |
+| `fill` | a 2D mark over an interior | a **primitive** |
+| `copy` | pixels transferred from another raster | a **raster source** |
+| `clear` | an unconditional whole-surface fill | — |
+
+`draw` and `fill` are the only verbs that name a geometric primitive. `copy` is
+the single verb for all pixel transfer — a bitmap, a ninepatch, a sprite-sheet
+cell, or a screen-to-screen move. `clear` is `bitmap_clear` only.
+
+Text (`bmfont_draw`, `bmtext_draw`) keeps its own verb: a string is neither a
+primitive nor a raster.
+
+### Axis 3 — PRIMITIVE (the shape), for `draw` / `fill`
+
+Ordered by rough complexity:
+
+`pixel`, `line`, `lines` (polyline), `rect`, `square`, `box`, `roundrect`,
+`circle`, `ellipse`, `arc`, `triangle`, `polygon`, `region` (a list of boxes).
+
+`rect` takes `x, y, size2d_t` with **inclusive** edges (historical).
+`box` takes a `const box_t *` — the **half-open** rectangle used everywhere else
+in DPTLib. New code should prefer `box`.
+
+### Axis 4 — PAINT (what goes inside), a suffix
+
+How a `fill` (or `clear`, or a wide `draw` stroke) obtains its pixels:
+
+| paint | suffix | source |
+|----------|-------------|--------|
+| colour | *(none)* | a single `colour_t` |
+| pattern | `_pattern` | an 8x8 repeating tile — a `pattern_t` |
+| gradient | `_gradient` | an interpolated ramp between stops |
+
+Sampling from a raster is **not** a paint suffix — that is `copy`.
+
+## Examples
+
+```
+screen_fill_hline colour fill, one horizontal run [shipped]
+screen_fill_rect colour fill, inclusive-edge rect [shipped]
+screen_fill_box colour fill, half-open box [planned]
+screen_draw_circle 1px circle outline [shipped]
+screen_fill_circle solid disc [shipped]
+screen_fill_pattern pattern fill on the screen [shipped]
+bitmap_fill_pattern pattern fill on a raw bitmap [shipped; was bitmap_draw_pattern]
+screen_fill_rect_gradient gradient fill of a rect [planned]
+screen_copy_bitmap blit a bitmap [shipped; was screen_draw_bitmap]
+screen_copy_ninepatch blit a resizable 9-patch frame [shipped; was screen_draw_ninepatch]
+screen_copy_rect screen-to-screen block move [shipped]
+screen_draw_line_wu_float anti-aliased line, float coords [shipped]
+```
+
+## Patterns
+
+A **pattern** is one concept with one struct and one verb (`fill`):
+
+```C
+typedef struct pattern
+{
+ uint8_t bits[8]; /* one byte per row, MSB = leftmost pixel */
+ colour_t fg; /* painted where a bit is set */
+ colour_t bg; /* painted where a bit is clear (see flags) */
+ unsigned flags; /* pattern_FLAG_STENCIL: leave bg pixels as-is */
+ point_t origin; /* tile phase; {0,0} locks to surface origin */
+}
+pattern_t;
+```
+
+- A plain two-colour tile sets `fg`, `bg` and no flags — every pixel in the area
+ is written.
+- A stencil sets `pattern_FLAG_STENCIL` — only set-bit pixels are written, the
+ rest of the area is untouched. (This is the old `bitmap_draw_pattern`
+ behaviour.)
+- `origin` phases the tile so abutting fills line up regardless of the fill
+ rectangle. `{0,0}` phases against the surface origin.
+
+The built-in tiles — the 65-level Bayer coverage ramp and the named tiles
+(`HSTRIPE`, `GRID`, `CROSSHATCH`, ...) — remain available as a
+`screen_pattern_t` enum, resolved to a `pattern_t` by a
+`pattern_from_preset(screen_pattern_t)` helper.
+
+Format support: 8bpp paletted and 32bpp. Other depths return
+`result_NOT_SUPPORTED`.
+
+## Ninepatch
+
+The one-word lowercase spelling **`ninepatch`** is canonical in all code and
+identifiers. "9-patch" is fine in prose. The art asset is `ninepatch.png` /
+`ninepatch.aseprite`.
+
+`screen_copy_ninepatch(scr, dst, src, flags)` draws a resizable frame from a
+3x3-cell source image (width and height each a positive multiple of 3): the four
+corners are drawn at natural size, the four edges are tiled along their run, and
+the centre is tiled to fill (unless `screen_NINEPATCH_NO_CENTRE` is set). The
+clip is restored on return.
+
+## Error reporting
+
+New drawing ops return **`result_t`**. An op that clips away to nothing is not an
+error — it returns `result_OK` and draws nothing.
+
+Existing `void`-returning `screen_draw_*` ops keep their return type. The three
+`copy` ops are aligned on `result_t`: `screen_copy_bitmap`,
+`screen_copy_ninepatch` and `screen_copy_rect` all return `result_OK` on
+success and `result_NOT_SUPPORTED` when the screen's pixel format has no blit
+path (`screen_copy_rect` also returns `result_NOT_SUPPORTED` when clipping
+leaves nothing to copy, since the caller must then fall back to a full
+redraw). `screen_copy_rect` keeps its `box_t *copied_dst` out-param.
+
+## Rectangle representation
+
+`box_t` (`{x0, y0, x1, y1}`, half-open — `x1`, `y1` exclusive) is the one
+geometric rectangle type. New primitives take `const box_t *`. There is no
+separate `rect_t`. `screen_fill_rect`'s inclusive `x, y, size2d_t` signature is
+retained; `screen_fill_box` / `screen_draw_box` are the preferred `box_t`
+spellings going forward.
+
+## Status summary
+
+**Shipped**
+
+- `screen_set_pixel`
+- `screen_draw_line`, `screen_draw_lines`, `screen_draw_dashed_line`,
+ `screen_draw_line_wu_fix8`, `screen_draw_line_wu_float`
+- `screen_draw_rect`, `screen_fill_rect`, `screen_fill_square`,
+ `screen_fill_hline` (the per-row primitive `screen_fill_rect` and
+ `screen_draw_circle` build on; itself a thin wrapper over `span->fill`)
+- `screen_draw_circle`, `screen_fill_circle`
+- `screen_fill_pattern` (takes `const pattern_t *`)
+- `screen_copy_bitmap`, `screen_copy_ninepatch`, `screen_copy_rect` — all
+ return `result_t`
+- `bitmap_clear`, `bitmap_fill_pattern` (`const pattern_t *`, returns
+ `result_t`)
+- `bmfont_draw`, `bmtext_draw`
+- `composite` — see [composite.md](composite.md)
+
+The rename pass was hard renames, no compatibility wrappers, all in-tree call
+sites updated in the same change: `screen_draw_bitmap` → `screen_copy_bitmap`,
+`screen_draw_ninepatch` → `screen_copy_ninepatch`, `bitmap_draw_pattern` →
+`bitmap_fill_pattern` (now taking a `pattern_t`).
+
+**Planned — new primitives** (priority order)
+
+1. `screen_fill_triangle` / `screen_draw_triangle`
+2. `screen_fill_polygon` / `screen_draw_polygon` (convex first)
+3. `screen_fill_roundrect` / `screen_draw_roundrect`
+4. `screen_fill_rect_gradient` (linear, two-stop, Bayer-dithered)
+5. `screen_copy_cell` (single sprite-sheet cell)
+6. `screen_draw_arc` / `screen_draw_ellipse`
+
+## Related
+
+- [composite.md](composite.md) — Porter-Duff bitmap compositing
+- [bmfont.md](bmfont.md) — proportional bitmap fonts
diff --git a/docs/geom/packer.md b/docs/geom/packer.md
index e9e65e03..d18002f5 100644
--- a/docs/geom/packer.md
+++ b/docs/geom/packer.md
@@ -64,6 +64,19 @@ 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 +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
+- `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
new file mode 100644
index 00000000..59071ff6
--- /dev/null
+++ b/docs/windowing/wuss.md
@@ -0,0 +1,253 @@
+# [DPTLib](https://github.com/dpt/DPTLib) > wuss
+
+"wuss" is a minimal window manager. It owns window creation, positioning, sizing, z-ordering, mouse/scroll event routing and dragging. Window contents are entirely delegated to tasks, which supply a single event-handling callback.
+
+- It draws windows back-to-front, with an optional titlebar and 1px outline per window.
+- It hit-tests and routes mouse-down/up/move and scroll events, including titlebar drag-to-move.
+- It tracks a single dirty region, accumulated automatically by window management (create/destroy/move/resize/bring-to-front) and manually by tasks, for partial redraws.
+- Content drawing and mouse handling are entirely task-supplied; Wuss only fills the content background before delivering a redraw event to the task's handle callback (unless the task opts out with `wuss_NO_BACKGROUND`). Scrolling is Wuss's own default action (see "Scrolling" below); a task may additionally react to `wuss_EVENT_SCROLL` for its own purposes.
+
+## Setup
+
+Create a window manager onto a `screen_t`, with an optional font for titlebar labels and an optional system palette:
+
+```C
+result_t wuss_create(screen_t *scr,
+ bmfont_t *font,
+ const colour_t *palette,
+ int npalette,
+ const wuss_config_t *config,
+ 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` 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.
+
+### Symbolic colours
+
+A `wuss_colour_t` is a system-palette index. Values `0..127` are raw indices; `wuss_COLOUR_SYMBOLIC` (128) and above are *symbolic* — roles wuss resolves to a concrete index against the live palette (and, for the chrome roles, the live config):
+
+- `wuss_COLOUR_BLACK`, `_WHITE`, `_RED`, `_GREEN`, `_BLUE`, `_YELLOW`, `_CYAN`, `_MAGENTA`, `_GREY` — nearest system-palette entry to the named RGB, the same lookup as `wuss_nearest_colour`.
+- `wuss_COLOUR_TITLE_BG`, `_TITLE_FG`, `_BUTTON_HILIGHT`, `_BUTTON_SHADOW`, `_ACCENT_BG`, `_ACCENT_FG`, `_BACKDROP` — echo the matching `wuss_config_t` field (`furniture.title.bg`, `bevel.light`, `bevel.dark`, `backdrop.colour`, …).
+
+Pass a symbolic value anywhere a `wuss_colour_t` is taken: config furniture/bevel/accent/backdrop, `wuss_window_create` / `wuss_window_set_background` backgrounds, icon specs. It is resolved once when stored, so drawing never pays for it and reads back a plain index. `wuss_set_palette` and `wuss_set_backdrop` re-resolve. `wuss_NO_BACKGROUND` is not symbolic and always passes through.
+
+Destroy with `wuss_destroy`, which also destroys any windows still open on it.
+
+## Windows
+
+Create a window with a content bounding box, optional title, appearance flags, a content background and a task delegate:
+
+```C
+result_t wuss_window_create(wuss_t *wuss, const box_t *content, const char *title,
+ 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 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.
+
+`min_doc` is the opposite end: the smallest content size a resize-drag or toggle-size will shrink to. Pass `(0, 0)` for the built-in floor. It is clamped both to that built-in floor, so a window can never be squeezed too small to grab, and to `doc`, so it can never demand a window larger than the document it shows. Also set once at creation.
+
+Furniture is additional to `content`, not carved out of it: the window's content area always ends up exactly the box requested, and its on-screen footprint (`wuss_window_get_visible_bounds`) is `content` expanded outward by whatever furniture flags request — a titlebar above, and/or a 1px outline around all four sides.
+
+`wuss_task_t` holds the task's event callback:
+
+```C
+typedef struct wuss_task
+{
+ wuss_event_fn_t *handle; /* NULL => task receives no events */
+ void *task_data;
+}
+wuss_task_t;
+```
+
+`wuss_task_start` builds one from `handle`/`task_data`.
+
+`flags` combines, by bitwise OR:
+
+- `wuss_WINDOW_NONE` — default: every furniture region drawn.
+- `wuss_WINDOW_NO_TITLEBAR` — no titlebar, and no drag handle.
+- `wuss_WINDOW_NO_OUTLINE` — no 1px border around the window.
+- `wuss_WINDOW_NO_CLOSE` — no close button in the titlebar.
+- `wuss_WINDOW_NO_BACK` — no back button in the titlebar (`wuss_BUTTON_SELECT` sends the window to back, `wuss_BUTTON_ADJUST` brings it to front).
+- `wuss_WINDOW_NO_TOGGLE_SIZE` — no toggle-size button in the titlebar.
+- `wuss_WINDOW_NO_VSCROLL` — no vertical scrollbar on the right edge.
+- `wuss_WINDOW_NO_HSCROLL` — no horizontal scrollbar on the bottom edge.
+- `wuss_WINDOW_NO_RESIZE` — no resize button in the bottom-right corner.
+- `wuss_WINDOW_NO_RESIZE_BLIT` — 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 window size in ways a partial redraw can't patch (e.g. a layout that spans the whole window).
+
+`wuss_WINDOW_NO_CLOSE`/`NO_BACK`/`NO_TOGGLE_SIZE` are ignored if `flags` includes `wuss_WINDOW_NO_TITLEBAR`; `NO_VSCROLL`/`NO_HSCROLL`/`NO_RESIZE`/`NO_RESIZE_BLIT` apply regardless.
+
+All furniture actions (back, toggle-size, resize-drag, scrollbar arrow/thumb) are handled entirely within Wuss via `wuss_mouse_click`/`wuss_mouse_move` — no new client events.
+
+Other window operations: `wuss_window_close`, `wuss_window_move`, `wuss_window_resize` (preserves content top-left), `wuss_window_restack`, `wuss_window_get_visible_bounds` (full on-screen footprint), `wuss_window_get_content_bounds`, `wuss_window_set_background`.
+
+## Task callbacks
+
+```C
+typedef enum wuss_event_kind
+{
+ wuss_EVENT_REDRAW,
+ wuss_EVENT_MOUSE,
+ wuss_EVENT_ICON,
+ wuss_EVENT_SCROLL
+}
+wuss_event_kind_t;
+
+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 { wuss_icon_t *icon; wuss_mouse_action_t action; wuss_button_t button; } icon;
+ struct { point_t point; int delta; } scroll;
+ }
+ data;
+}
+wuss_event_t;
+
+typedef result_t (wuss_event_fn_t)(wuss_window_t *window, const wuss_event_t *event,
+ void *task_data);
+```
+
+A task supplies a single `handle` callback and dispatches on `event->kind`. Only the union member matching `kind` is valid.
+
+For `wuss_EVENT_REDRAW`, `event->data.redraw.scr` is called with `scr->clip` already set to the on-screen, clipped content area; `event->data.redraw.content` gives the window's full (unclipped) content box in screen space, for context.
+
+For `wuss_EVENT_MOUSE` and `wuss_EVENT_SCROLL`, `event->data.mouse.point` and `event->data.scroll.point` are window-local content coordinates: the content area's top-left is `(0,0)` plus the window's current scroll offset (see "Scrolling" below). `event->data.mouse.action` is `wuss_MOUSE_DOWN`/`wuss_MOUSE_UP`/`wuss_MOUSE_MOVE`. A titlebar click never reaches a task's handle callback: it starts a drag (and, for `wuss_BUTTON_SELECT`, brings the window to front) instead. A content click, even on a `wuss_WINDOW_NO_TITLEBAR` window with no drag handle, never changes z-order — only a titlebar click raises a window — so tasks are free to use content clicks for their own purposes without Wuss reordering windows underneath them.
+
+`wuss_EVENT_ICON` is delivered instead of `wuss_EVENT_MOUSE` whenever the pointer is inside a `wuss_ICON_TYPE_BUTTON` icon's bounding box (see "Icons" below): `event->data.icon.icon` names the icon, `action` and `button` carry the same values a `wuss_EVENT_MOUSE` would. `wuss_ICON_TYPE_LABEL` icons, and hidden or disabled icons, never raise it — clicks over them fall through as `wuss_EVENT_MOUSE`.
+
+## Mouse and scroll routing
+
+Feed mouse events in with `wuss_mouse_click` (action `wuss_MOUSE_DOWN` or `wuss_MOUSE_UP`) and `wuss_mouse_move`, and scroll events with `wuss_scroll`, each hit-testing the topmost window at `(x, y)` and delivering to its task in window-local coordinates. All three take an optional `wuss_window_t **hit` out-parameter naming the window under the pointer (or being dragged). Events are dropped if the hit window has no handle callback, or (for scroll) the pointer is over its titlebar.
+
+## Scrolling
+
+Each window carries a scroll offset, `(0, 0)` by default: the point in the task's virtual content space that appears at the content area's top-left. `wuss_window_set_scroll(window, x, y)` moves it (invalidating the content area so the next redraw picks it up); `wuss_window_get_scroll` reads it back. `wuss_scroll` applies this offset itself as Wuss's default scroll action, clamped to `doc` (set at window creation), before also delivering `wuss_EVENT_SCROLL` to the task if it has a handle:
+
+- 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.
+- Within a window, Wuss paints in a fixed order: the window background colour, then its icons (see "Icons" below), then the task's `wuss_EVENT_REDRAW` handler — so a task always draws over the background and any icons, never under them.
+- `wuss_invalidate` / `wuss_window_invalidate` mark a screen-space or window-local region dirty; window management calls these automatically for its own changes, but a task must call one of them itself whenever its content changes on its own (e.g. an animation), passing the union of the old and new areas that need repainting.
+- `wuss_redraw_dirty` repaints only the accumulated dirty region, then clears it, painting the backdrop colour into each dirty region first if one was configured. Without a configured backdrop, Wuss only repaints windows, not the background between/behind them, so a caller whose invalidation can expose background (e.g. after a window move) should clear that region itself first.
+- `wuss_get_dirty_count`/`wuss_get_dirty(wuss, index, out)` fetch the currently accumulated dirty regions (coalesced as they accumulate, up to a fixed cap after which further regions are merged into the last one) without redrawing.
+- A window move or resize is clipped, piece by piece, against whatever's above it in the z-order, and any pixels a move can preserve are blitted directly rather than queued dirty; only the genuinely-changed pieces end up in the dirty region. This is an internal optimisation with no effect on a task's own redraw handling.
+
+## Icons
+
+Taking inspiration from RISC OS, a window can carry **icons**: rectangular UI elements Wuss draws and hit-tests inside the content area. v1 ships two types:
+
+- `wuss_ICON_TYPE_LABEL` — static text. Clicks fall through to the task as `wuss_EVENT_MOUSE`.
+- `wuss_ICON_TYPE_BUTTON` — a bevelled rectangle with a centred label and pressed-state feedback (the bevel inverts and the label shifts one pixel down-right while held). Clicks and hovers arrive as `wuss_EVENT_ICON`.
+
+The enum is left open for sprite and editable-text types later.
+
+Icons are dynamic and owned by their window:
+
+- `wuss_icon_create(window, spec, &icon)` — returns an opaque `wuss_icon_t *`. The spec gives the bounding box, type, text (copied; `NULL` treated as `""`), foreground and background palette indices, and flags. A `wuss_ICON_TYPE_BUTTON` must pass a real `bg`; passing `wuss_NO_BACKGROUND` is rejected with `result_WUSS_BAD_ICON`. An unknown type is also `result_WUSS_BAD_ICON`; an out-of-range `fg`/`bg` is `result_WUSS_BAD_COLOUR`.
+- `wuss_icon_delete(icon)` — NULL-safe.
+- `wuss_icon_set_text(icon, text)`, `wuss_icon_set_hidden(icon, hidden)`.
+- Getters: `wuss_icon_get_bbox`, `wuss_icon_get_type`, `wuss_icon_get_text` (never `NULL`), `wuss_icon_get_window`.
+
+Flags: `wuss_ICON_FLAGS_HIDDEN` (not drawn, not hit-tested) and `wuss_ICON_FLAGS_DISABLED` (drawn greyed; clicks fall through as `wuss_EVENT_MOUSE`).
+
+An icon's bounding box is in **virtual content space** — the same space as `wuss_EVENT_MOUSE`'s `point` and `wuss_window_invalidate`'s box — so icons scroll with the content. The on-screen box is `content-top-left - scroll + bbox`.
+
+Wuss draws icons in creation order (later icons paint on top); hit-testing scans in reverse, so the topmost icon at a point wins. When the pointer leaves a pressed button its pressed state clears; v1 does not re-press on drag-back-in and does not track which mouse button is held.
+
+The bevel's light (top/left) and dark (bottom/right) edge shades come from `config->bevel.light` / `config->bevel.dark` at `wuss_create` time, validated like the other furniture colours; both default to the titlebar fill colour when `config` is `NULL`.
+
+## Glossary
+
+Terms as this document and the API use them. Several are RISC OS conventions, which Wuss follows.
+
+- **Adjust** — the secondary mouse button, `wuss_BUTTON_ADJUST`. Conventionally the variant of an action: Adjust on the back button brings a window to front rather than sending it back, and Adjust on a scroll arrow steps against the direction the arrow points.
+- **Backdrop** — the desktop background colour painted behind all windows, set by `config->backdrop` at `wuss_create` time. `wuss_NO_BACKGROUND` leaves the area behind windows untouched, making it the caller's to repaint.
+- **Button flags** — `wuss_button_t` values are flags (Select 4, Menu 2, Adjust 1), OR'd together so a chord can be reported. Test a reported button with `&`, never for equality.
+- **Chord** — two or more mouse buttons held together, e.g. Select+Adjust. Wuss's own furniture handling resolves an ambiguous chord in Select's favour.
+- **Content area** — the part of a window belonging to its task. Its bounds are exactly what was passed to `wuss_window_create`, furniture being added outside it; read back with `wuss_window_get_content_bounds`.
+- **Dirty region** — the accumulated set of screen-space boxes needing repaint, coalesced as they accumulate. `wuss_redraw_dirty` repaints and clears it.
+- **Document extent** — `doc`, the size of a task's virtual content space, fixed at window creation. Sets how far a window can scroll, the scrollbar sausages' proportions, and the size a resize-drag or toggle-size can grow the content area to.
+- **Minimum extent** — `min_doc`, the smallest content size a resize-drag or toggle-size will leave a window at, fixed at window creation. `(0, 0)` means the built-in floor.
+- **Furniture** — everything Wuss draws around a window's content: outline, titlebar and its buttons, scrollbars, resize button. Drawn outside the content area, never carved out of it. Furniture clicks are handled entirely within Wuss and never reach the task.
+- **Furniture button** — a clickable furniture region in the titlebar or window corner: close, back, toggle-size, resize. (Called an "icon" in earlier revisions; that name now means the work-area element below.)
+- **Handle callback** — a task's single `wuss_event_fn_t`, receiving every event kind and dispatching on `event->kind`. A window whose task has no handle receives no events at all.
+- **Icon** — a rectangular UI element Wuss draws and hit-tests inside a window's content area: a static `wuss_ICON_TYPE_LABEL`, or a clickable bevelled `wuss_ICON_TYPE_BUTTON`. Created with `wuss_icon_create` and owned by its window. Its bounding box is in virtual content space, so it scrolls with the content; its screen position is `content-top-left - scroll + bbox`. See "Icons".
+- **Invalidate** — mark a region dirty for the next `wuss_redraw_dirty`. Window management does this for its own changes; a task must do it for its own content changes.
+- **Menu** — the middle mouse button, `wuss_BUTTON_MENU`. Routed like any other button; Wuss provides no menu widget of its own.
+- **Outline** — the 1px border drawn around a window, suppressed by `wuss_WINDOW_NO_OUTLINE`.
+- **Sausage** — the draggable thumb within a scrollbar well, sized in proportion to how much of the document extent the content area shows.
+- **Screen space** — coordinates in the underlying `screen_t`, origin at its top-left. Visible and content bounds are in screen space.
+- **Select** — the primary mouse button, `wuss_BUTTON_SELECT`. Performs the plain action, and raises a window when used on its titlebar.
+- **Task** — the client of a window: an event callback plus an opaque `task_data` pointer, held in a `wuss_task_t`. Wuss owns the window; the task owns what's drawn inside it.
+- **Titlebar** — the strip above the content area carrying the window's label and its buttons, and the drag handle for moving the window. Suppressed by `wuss_WINDOW_NO_TITLEBAR`.
+- **Toggle size** — the titlebar button that switches a window between its normal size and a maximised size, and back.
+- **Virtual content space** — the task's own full coordinate space, of size `doc`. Mouse and scroll events arrive in it, i.e. with the scroll offset already added.
+- **Visible bounds** — a window's whole on-screen footprint, content plus furniture; `wuss_window_get_visible_bounds`.
+- **Well** — the track a scrollbar's sausage slides along, between the two arrow buttons.
+- **Window-local coordinates** — coordinates relative to the content area's top-left. `wuss_window_invalidate` takes its box in virtual content space, i.e. with the scroll offset already added.
+- **Z-order** — the back-to-front stacking order of windows. Changed with `wuss_window_restack`, or by a Select click on a titlebar; content clicks never change it.
+
+## Limitations
+
+- No menus: `wuss_BUTTON_MENU` is defined and routed like any other button, but Wuss has no built-in menu widget.
diff --git a/docs/wuss.md b/docs/wuss.md
deleted file mode 100644
index 19ba7571..00000000
--- a/docs/wuss.md
+++ /dev/null
@@ -1,129 +0,0 @@
-# [DPTLib](https://github.com/dpt/DPTLib) > wuss
-
-"wuss" is a minimal window manager. It owns window creation, positioning, sizing, z-ordering, mouse/scroll event routing and dragging. Window contents are entirely delegated to tasks, which supply a single event-handling callback.
-
-- It draws windows back-to-front, with an optional titlebar and 1px outline per window.
-- It hit-tests and routes mouse-down/up/move and scroll events, including titlebar drag-to-move.
-- It tracks a single dirty region, accumulated automatically by window management (create/destroy/move/resize/bring-to-front) and manually by tasks, for partial redraws.
-- Content drawing and mouse handling are entirely task-supplied; Wuss only fills the content background before delivering a redraw event to the task's handle callback (unless the task opts out with `wuss_NO_BACKGROUND`). Scrolling is Wuss's own default action (see "Scrolling" below); a task may additionally react to `wuss_EVENT_SCROLL` for its own purposes.
-
-## Setup
-
-Create a window manager onto a `screen_t`, with an optional font for titlebar labels and an optional system palette:
-
-```C
-result_t wuss_create(screen_t *scr,
- bmfont_t *font,
- const colour_t *palette,
- int npalette,
- const wuss_config_t *config,
- 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.
-
-Destroy with `wuss_destroy`, which also destroys any windows still open on it.
-
-## Windows
-
-Create a window with a content bounding box, optional title, appearance flags and a task delegate:
-
-```C
-result_t wuss_window_create(wuss_t *wuss, const box_t *content, const char *title,
- wuss_window_flags_t flags, const wuss_task_t *task,
- int doc_width, int doc_height,
- wuss_window_t **window);
-```
-
-`doc_width`/`doc_height` are the virtual document extent behind the horizontal/vertical scrollbars' sausage proportion; pass `content`'s own width/height for a window with nothing to scroll. Set once at creation, immutable thereafter.
-
-Furniture is additional to `content`, not carved out of it: the window's content area always ends up exactly the box requested, and its on-screen footprint (`wuss_window_get_visible_bounds`) is `content` expanded outward by whatever furniture flags request — a titlebar above, and/or a 1px outline around all four sides.
-
-`wuss_task_t` holds the task's event callback and its content background:
-
-```C
-typedef struct wuss_task
-{
- wuss_event_fn_t *handle; /* NULL => task receives no events */
- void *task_data;
- wuss_colour_t bg; /* filled by wuss before a redraw event, or wuss_NO_BACKGROUND */
-}
-wuss_task_t;
-```
-
-`wuss_task_make` builds one from `handle`/`task_data`/`bg`.
-
-`flags` combines, by bitwise OR:
-
-- `wuss_WINDOW_NONE` — default: every furniture region drawn.
-- `wuss_WINDOW_NO_TITLEBAR` — no titlebar, and no drag handle.
-- `wuss_WINDOW_NO_OUTLINE` — no 1px border around the window.
-- `wuss_WINDOW_NO_CLOSE` — no close icon in the titlebar.
-- `wuss_WINDOW_NO_BACK` — no back icon in the titlebar (`wuss_BUTTON_SELECT` sends the window to back, `wuss_BUTTON_ADJUST` brings it to front).
-- `wuss_WINDOW_NO_TOGGLE_SIZE` — no toggle-size icon in the titlebar.
-- `wuss_WINDOW_NO_VSCROLL` — no vertical scrollbar on the right edge.
-- `wuss_WINDOW_NO_HSCROLL` — no horizontal scrollbar on the bottom edge.
-- `wuss_WINDOW_NO_RESIZE` — no resize icon in the bottom-right corner.
-
-`wuss_WINDOW_NO_CLOSE`/`NO_BACK`/`NO_TOGGLE_SIZE` are ignored if `flags` includes `wuss_WINDOW_NO_TITLEBAR`; `NO_VSCROLL`/`NO_HSCROLL`/`NO_RESIZE` apply regardless.
-
-All furniture actions (back, toggle-size, resize-drag, scrollbar arrow/thumb) are handled entirely within Wuss via `wuss_mouse_click`/`wuss_mouse_move` — no new client events.
-
-Other window operations: `wuss_window_close`, `wuss_window_move`, `wuss_window_resize` (preserves content top-left), `wuss_window_restack`, `wuss_window_get_visible_bounds` (full on-screen footprint), `wuss_window_get_content_bounds`, `wuss_window_set_background`.
-
-## Task callbacks
-
-```C
-typedef enum wuss_event_kind
-{
- wuss_EVENT_REDRAW,
- wuss_EVENT_MOUSE,
- wuss_EVENT_SCROLL
-}
-wuss_event_kind_t;
-
-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; int x, y; wuss_button_t button; } mouse;
- struct { int x, y, delta; } scroll;
- }
- data;
-}
-wuss_event_t;
-
-typedef result_t (wuss_event_fn_t)(wuss_window_t *window, const wuss_event_t *event,
- void *task_data);
-```
-
-A task supplies a single `handle` callback and dispatches on `event->kind`. Only the union member matching `kind` is valid.
-
-For `wuss_EVENT_REDRAW`, `event->data.redraw.scr` is called with `scr->clip` already set to the on-screen, clipped content area; `event->data.redraw.content` gives the window's full (unclipped) content box in screen space, for context.
-
-For `wuss_EVENT_MOUSE` and `wuss_EVENT_SCROLL`, `event->data.mouse.point` and `event->data.scroll.point` are window-local content coordinates: the content area's top-left is `(0,0)` plus the window's current scroll offset (see "Scrolling" below). `event->data.mouse.action` is `wuss_MOUSE_DOWN`/`wuss_MOUSE_UP`/`wuss_MOUSE_MOVE`. A titlebar click never reaches a task's handle callback: it starts a drag (and, for `wuss_BUTTON_SELECT`, brings the window to front) instead. A content click, even on a `wuss_WINDOW_NO_TITLEBAR` window with no drag handle, never changes z-order — only a titlebar click raises a window — so tasks are free to use content clicks for their own purposes without Wuss reordering windows underneath them.
-
-## Mouse and scroll routing
-
-Feed mouse events in with `wuss_mouse_click` (action `wuss_MOUSE_DOWN` or `wuss_MOUSE_UP`) and `wuss_mouse_move`, and scroll events with `wuss_scroll`, each hit-testing the topmost window at `(x, y)` and delivering to its task in window-local coordinates. All three take an optional `wuss_window_t **hit` out-parameter naming the window under the pointer (or being dragged). Events are dropped if the hit window has no handle callback, or (for scroll) the pointer is over its titlebar.
-
-## Scrolling
-
-Each window carries a scroll offset, `(0, 0)` by default: the point in the task's virtual content space that appears at the content area's top-left. `wuss_window_set_scroll(window, x, y)` moves it (invalidating the content area so the next redraw picks it up); `wuss_window_get_scroll` reads it back. `wuss_scroll` applies this offset itself as Wuss's default scroll action, clamped to `doc_width`/`doc_height` (set at window creation), before also delivering `wuss_EVENT_SCROLL` to the task if it has a handle:
-
-- 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.
-
-## Redrawing
-
-- `wuss_redraw` repaints every window, back-to-front, unconditionally.
-- `wuss_invalidate` / `wuss_window_invalidate` mark a screen-space or window-local region dirty; window management calls these automatically for its own changes, but a task must call one of them itself whenever its content changes on its own (e.g. an animation), passing the union of the old and new areas that need repainting.
-- `wuss_redraw_dirty` repaints only the accumulated dirty region, then clears it. Wuss only repaints windows, not the background between/behind them, so a caller whose invalidation can expose background (e.g. after a window move) should clear that region itself first.
-- `wuss_get_dirty` fetches the current accumulated dirty region without redrawing.
-
-## Limitations
-
-- No menus: `wuss_BUTTON_MENU` is defined and routed like any other button, but Wuss has no built-in menu widget.
-- No overlapping-window damage tracking finer than each window's own bounding box.
diff --git a/include/base/debug.h b/include/base/debug.h
index fd7249f8..ca74f437 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 5ae5f834..6515b495 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
@@ -67,4 +67,10 @@ typedef int result_t;
/* ----------------------------------------------------------------------- */
+/* Returns a static human-readable string for the given result code, or
+ * "Unknown error" if unrecognised. Never returns NULL. */
+const char *result_string(result_t err);
+
+/* ----------------------------------------------------------------------- */
+
#endif /* BASE_RESULT_H */
diff --git a/include/base/types.h b/include/base/types.h
index 77570e39..f53e2f9c 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 3d3cbd7a..8e6b389c 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 b771ea41..d4002c8c 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 86d78ea1..fd02e3de 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 86c6d778..e43b3ee5 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 560e5f5b..e0888a17 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 2dde0bd9..0026ca7a 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
@@ -9,9 +9,7 @@
* pickle_unpickle() which respectively serialise or deserialise an
* associative array to a file of the form:
*
- * #
- *
- * (zero or more)
+ * # (zero or more)
*
* When serialising, keys and values are read from through an abstract
* pickle_reader_methods interface. They are then transformed into savable
@@ -248,7 +246,8 @@ pickle_unformat_methods_t;
*
* \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.
*
@@ -262,8 +261,8 @@ result_t pickle_pickle(const char *filename,
/**
* 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'.
+ * 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/databases/tag-db.h b/include/databases/tag-db.h
index 3160812c..dcbc01d7 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 d17c98b7..e4a173b1 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
@@ -57,15 +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
+ * \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 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.
*/
@@ -117,8 +117,8 @@ void atom_delete(atom_set_t *set, atom_t atom);
*
* \param set Atom set.
* \param atom Atom to retrieve.
- * \param[out] length Length of data block, in bytes.
- * NULL if length is not required.
+ * \param[out] length Length of data block, in bytes. NULL if length is not
+ * required.
*
* \return Data block.
*/
@@ -159,8 +159,8 @@ atom_t atom_for_block(atom_set_t *set,
/**
* Delete an existing atom specified by data block.
*
- * This is a convenience function equivalent to:
- * atom_delete(set, atom_for_block(set, block, length)).
+ * This is a convenience function equivalent to: atom_delete(set,
+ * atom_for_block(set, block, length)).
*
* \param set Atom set.
* \param block Data block to retrieve.
diff --git a/include/datastruct/bitarr.h b/include/datastruct/bitarr.h
index 33a81b55..783f3d4b 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
@@ -114,8 +114,7 @@ struct bitarr
do { BITARR_OP(arr, bit, |=); } while (0)
/**
- * Clear a single bit.
- * */
+ * Clear a single bit. */
#define bitarr_clear(arr, bit) \
do { BITARR_OP(arr, bit, &= ~); } while (0)
diff --git a/include/datastruct/bitfifo.h b/include/datastruct/bitfifo.h
index 509b6560..c73182b5 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
@@ -55,10 +55,10 @@ void bitfifo_destroy(T *doomed);
/* writes bits to 'head' onwards, wrapping around if required */
/* fifo will reject attempts to store more bits than there is space for */
-result_t bitfifo_enqueue(T *fifo,
+result_t bitfifo_enqueue(T *fifo,
const unsigned int *newbits,
- unsigned int newbitsoffset,
- size_t nnewbits);
+ unsigned int newbitsoffset,
+ size_t nnewbits);
/* reads bits from 'tail' onwards, wrapping around if required */
result_t bitfifo_dequeue(T *fifo,
diff --git a/include/datastruct/bitvec.h b/include/datastruct/bitvec.h
index 7fdbdf1e..8d5d641f 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 1041d9fa..6bbd5481 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
@@ -110,11 +110,11 @@ void *cache_get(cache_t *cache, cachekey_t key);
*
* \return Error indication.
*/
-result_t cache_put(cache_t *cache,
- cachekey_t key,
- void *data,
- size_t length,
- void **inserted);
+result_t cache_put(cache_t *cache,
+ cachekey_t key,
+ void *data,
+ size_t length,
+ void **inserted);
/**
* Print cache statistics to stdout.
diff --git a/include/datastruct/hash.h b/include/datastruct/hash.h
index e7ace3be..02794d2d 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
@@ -75,13 +75,13 @@ hash_destroy_value_t hash_no_destroy_value;
*
* \return Error indication.
*/
-result_t hash_create(const void *default_value,
- int nbins,
- hash_fn_t *fn,
- hash_compare_t *compare,
- hash_destroy_key_t *destroy_key,
- hash_destroy_value_t *destroy_value,
- T **hash);
+result_t hash_create(const void *default_value,
+ int nbins,
+ hash_fn_t *fn,
+ hash_compare_t *compare,
+ hash_destroy_key_t *destroy_key,
+ hash_destroy_value_t *destroy_value,
+ T **hash);
/**
* Destroy a hash.
@@ -106,8 +106,8 @@ const void *hash_lookup(T *hash, const void *key);
* Insert the specified key:value pair into the hash.
*
* The hash takes ownership of the key and value pointers. It will call the
- * destroy functions passed to hash_create when the keys and values are to
- * be destroyed.
+ * destroy functions passed to hash_create when the keys and values are to be
+ * destroyed.
*
* \param hash Hash.
* \param key Key to insert.
@@ -152,8 +152,8 @@ typedef int (hash_walk_callback_t)(const void *key,
* \param cb Callback routine.
* \param opaque Opaque pointer to pass to callback routine.
*
- * \return Error indication.
- * \retval result_OK If the walk completed successfully.
+ * \return Error indication. \retval result_OK If the walk completed
+ * successfully.
*/
result_t hash_walk(const T *hash, hash_walk_callback_t *cb, void *opaque);
@@ -168,9 +168,8 @@ result_t hash_walk(const T *hash, hash_walk_callback_t *cb, void *opaque);
* \param[out] key Pointer to receive key.
* \param[out] value Pointer to receive value.
*
- * \return Error indication.
- * \retval result_OK If an element was found.
- * \retval result_HASH_END If no elements remain.
+ * \return Error indication. \retval result_OK If an element was found.
+ * \retval result_HASH_END If no elements remain.
*/
result_t hash_walk_continuation(T *hash,
int continuation,
diff --git a/include/datastruct/hlist.h b/include/datastruct/hlist.h
index a8ffb6b6..5b73388c 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 ff8cdf84..ebdb986c 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 609833a2..a2c160cf 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
@@ -77,11 +77,11 @@ typedef unsigned int ntree_walk_flags_t;
typedef result_t (ntree_walk_fn_t)(T *t, void *opaque);
/* max_depth of 0 means 'walk all', 1..N just walk level 1..N */
-result_t ntree_walk(T *t,
- ntree_walk_flags_t flags,
- int max_depth,
- ntree_walk_fn_t *fn,
- void *opaque);
+result_t ntree_walk(T *t,
+ ntree_walk_flags_t flags,
+ int max_depth,
+ ntree_walk_fn_t *fn,
+ void *opaque);
/* ----------------------------------------------------------------------- */
diff --git a/include/datastruct/vector.h b/include/datastruct/vector.h
index 82755fa3..4a4b9c66 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
@@ -10,7 +10,7 @@
* index.
*
* \warning If the vector is altered then pointers into the vector may be
- * invalidated (should the block move when reallocated).
+ * invalidated (should the block move when reallocated).
*/
#ifndef DATASTRUCT_VECTOR_H
@@ -155,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.
@@ -163,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/bitmap-set.h b/include/framebuf/bitmap-set.h
index 809ea432..33631177 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 0863eee2..34d15095 100644
--- a/include/framebuf/bitmap.h
+++ b/include/framebuf/bitmap.h
@@ -1,16 +1,21 @@
-/* bitmap.h -- bitmap image type */
+/* framebuf/bitmap.h -- bitmap image type */
#ifndef FRAMEBUF_BITMAP_H
#define FRAMEBUF_BITMAP_H
+#include
+
#include "base/result.h"
#include "framebuf/colour.h"
+#include "framebuf/pattern.h"
#include "framebuf/pixelfmt.h"
#include "framebuf/span.h"
+#include "geom/box.h"
+#include "geom/size.h"
/** Common bitmap structure members (used for screens too). */
#define bitmap_COMMON_MEMBERS \
- int width, height; /**< Width and height of the bitmap in pixels. */ \
+ size2d_t size; /**< Width and height of the bitmap in pixels. */ \
pixelfmt_t format; /**< Pixel format of the bitmap. */ \
int rowbytes; /**< Number of bytes per row of the bitmap. */ \
colour_t *palette; /**< Palette of the bitmap, or NULL. */ \
@@ -32,8 +37,7 @@ bitmap_t;
* Initialise a previously allocated bitmap structure.
*
* \param[in] bm Bitmap to initialise.
- * \param[in] width Width of the bitmap in pixels.
- * \param[in] height Height of the bitmap in pixels.
+ * \param[in] size Width and height of the bitmap in pixels.
* \param[in] fmt Pixel format of the bitmap.
* \param[in] rowbytes Number of bytes per row of the bitmap.
* \param[in] palette Palette of the bitmap, or NULL.
@@ -41,13 +45,25 @@ bitmap_t;
* \return \ref result_OK on success, or appropriate result code otherwise.
*/
result_t bitmap_init(bitmap_t *bm,
- int width,
- int height,
+ size2d_t size,
pixelfmt_t fmt,
int rowbytes,
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.
*
@@ -69,6 +85,29 @@ void bitmap_clear(bitmap_t *bm, colour_t colour);
// bm->base = base;
//}
+/**
+ * Fill a rectangle of the bitmap with a repeating 8x8 pattern.
+ *
+ * A plain pattern paints every pixel in the area, set bits taking
+ * `pattern->fg` and clear bits `pattern->bg`. A stencil pattern (one with
+ * `pattern_FLAG_STENCIL`) paints only the set-bit pixels, leaving the rest
+ * untouched. The tile is phased against `pattern->origin`, so abutting fills
+ * with the same origin line up.
+ *
+ * Supported for 8bpp and 32bpp formats only; other formats return \ref
+ * result_NOT_SUPPORTED.
+ *
+ * \param[in] bm Bitmap to fill.
+ * \param[in] area Rectangle to fill, clipped to the bitmap bounds. NULL
+ * fills the whole bitmap.
+ * \param[in] pattern Pattern to fill with.
+ * \return \ref result_OK on success, \ref result_NOT_SUPPORTED for an
+ * unsupported pixel format.
+ */
+result_t bitmap_fill_pattern(bitmap_t *bm,
+ const box_t *area,
+ const pattern_t *pattern);
+
/**
* Load a PNG image into the given bitmap.
*
@@ -88,7 +127,8 @@ result_t bitmap_load_png(bitmap_t *bm, const char *filename);
result_t bitmap_save_png(const bitmap_t *bm, const char *filename);
/**
- * Convert the given bitmap into a different pixel format, allocating a new bitmap structure for the result.
+ * Convert the given bitmap into a different pixel format, allocating a new
+ * bitmap structure for the result.
*
* \param[in] bm Bitmap to convert.
* \param[in] newfmt New pixel format.
diff --git a/include/framebuf/bmfont.h b/include/framebuf/bmfont.h
index cf41a2b5..1153533c 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
@@ -22,6 +22,38 @@ typedef int bmfont_width_t; /* in pixels */
*/
result_t bmfont_create(const char *png, bmfont_t **bmfont);
+/**
+ * Callback for bmfont_enumerate(), invoked once per font found.
+ *
+ * \param[in] name The font's leafname with the ".png" extension stripped,
+ * e.g. "Trinity.Medium" for "Trinity.Medium.png". Borrowed;
+ * copy it if it must outlive the call.
+ * \param[in] path The full path that would be passed to bmfont_create() to
+ * load this font. Borrowed.
+ * \param[in] opaque The pointer passed to bmfont_enumerate().
+ * \return \ref result_OK to continue, \ref result_STOP_WALK to stop early
+ * (bmfont_enumerate() then also returns \ref result_OK), or any
+ * other code to abort with that code.
+ */
+typedef result_t (bmfont_enumerate_fn)(const char *name,
+ const char *path,
+ void *opaque);
+
+/**
+ * Enumerate the bitmap fonts in a directory: every ".png" file in \p dir is
+ * reported to \p fn (non-recursive, order unspecified).
+ *
+ * \param[in] dir Directory to scan.
+ * \param[in] fn Called once per font; see bmfont_enumerate_fn.
+ * \param[in] opaque Passed through to \p fn.
+ * \return \ref result_OK on success (including a stop-walk), \ref
+ * result_FILE_NOT_FOUND if \p dir cannot be opened, \ref
+ * result_NULL_ARG, or a code propagated from \p fn.
+ */
+result_t bmfont_enumerate(const char *dir,
+ bmfont_enumerate_fn *fn,
+ void *opaque);
+
/**
* Destroy a bitmap font.
*
@@ -33,11 +65,21 @@ void bmfont_destroy(bmfont_t *bmfont);
* Read the width and height of the specified bitmap font.
*
* \param[in] bmfont Bitmap font to query.
- * \param[out] width Width of the font in pixels.
- * \param[out] height Height of the font in pixels.
+ * \param[out] width Width of the font in pixels, or NULL if not wanted.
+ * \param[out] height Height of the font in pixels, or NULL if not wanted.
*/
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).
+ *
+ * \param[in] bmfont Bitmap font to query.
+ * \return Number of glyphs in the font.
+ */
+int bmfont_get_count(bmfont_t *bmfont);
+
/**
* Measure the width of a string drawn with the specified font.
*
diff --git a/include/framebuf/colour.h b/include/framebuf/colour.h
index 53ba312b..452f1be4 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 cd577fc0..219878ef 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 1ac2ef6a..d8e9b15f 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
@@ -11,7 +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.
@@ -115,7 +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.
@@ -133,7 +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.
@@ -153,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.
@@ -170,9 +174,11 @@ void curve_bezier_cubic_f(point_t p0,
point_t *points);
/**
- * As for \ref curve_bezier_cubic but uses forward differencing (fixed-point version).
+ * As for \ref curve_bezier_cubic but uses forward differencing (fixed-point
+ * version).
*
- * \warning This suffers from drift (the end point is not guaranteed to be reached) if `nsteps` isn't a power of 2.
+ * \warning This suffers from drift (the end point is not guaranteed to be
+ * reached) if `nsteps` isn't a power of 2.
*
* \param[in] p0 Start point.
* \param[in] p1 Control point 1.
diff --git a/include/framebuf/palettes.h b/include/framebuf/palettes.h
index 3beabdc0..41500e0b 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
@@ -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/include/framebuf/pattern.h b/include/framebuf/pattern.h
new file mode 100644
index 00000000..67c783ee
--- /dev/null
+++ b/include/framebuf/pattern.h
@@ -0,0 +1,108 @@
+/* framebuf/pattern.h -- 8x8 repeating fill pattern */
+
+#ifndef FRAMEBUF_PATTERN_H
+#define FRAMEBUF_PATTERN_H
+
+#include
+
+#include "framebuf/colour.h"
+#include "geom/point.h"
+
+/**
+ * Built-in 8x8 fill patterns. Each is a 1-bit tile: set bits take the
+ * foreground colour, clear bits the background. Pass one to
+ * `pattern_from_preset` to obtain a `pattern_t`.
+ */
+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 = 0,
+ screen_PATTERN_BAYER_LIMIT = screen_PATTERN_BAYER0 + 65,
+
+ /**
+ * A flat fill and a 50% checkerboard are just the ends and midpoint of the
+ * Bayer run, so they are aliases rather than separate tiles. `EMPTY` is
+ * the inverse of `SOLID`; `GREY50` is its own inverse.
+ */
+ screen_PATTERN_EMPTY = screen_PATTERN_BAYER0,
+ screen_PATTERN_GREY50 = screen_PATTERN_BAYER0 + 32,
+ screen_PATTERN_SOLID = screen_PATTERN_BAYER0 + 64,
+
+ /* The remaining named tiles follow the Bayer run. */
+ screen_PATTERN_HSTRIPE = screen_PATTERN_BAYER_LIMIT,
+ /**< Horizontal bars. */
+ screen_PATTERN_VSTRIPE, /**< Vertical bars. */
+ screen_PATTERN_DIAGONAL, /**< Diagonal lines. */
+ screen_PATTERN_DOTS, /**< Sparse dots. */
+ screen_PATTERN_GRID, /**< Thin grid lines. */
+ screen_PATTERN_CROSSHATCH, /**< Crossed thin lines. */
+
+ screen_PATTERN_HSTRIPE_INV, /**< Inverse of HSTRIPE. */
+ screen_PATTERN_VSTRIPE_INV, /**< Inverse of VSTRIPE. */
+ screen_PATTERN_DIAGONAL_INV, /**< Inverse of DIAGONAL. */
+ screen_PATTERN_DOTS_INV, /**< Inverse of DOTS. */
+ screen_PATTERN_GRID_INV, /**< Inverse of GRID. */
+ screen_PATTERN_CROSSHATCH_INV, /**< Inverse of CROSSHATCH. */
+
+ screen_PATTERN__LIMIT
+ /**< Count of patterns; not itself a pattern. */
+}
+screen_pattern_t;
+
+/** Flags for `pattern_t`. */
+enum
+{
+ /**
+ * Paint only where a pattern bit is set, leaving clear-bit pixels
+ * untouched (a stencil). Without this the whole area is painted, clear
+ * bits taking `bg`.
+ */
+ pattern_FLAG_STENCIL = 1u << 0
+};
+
+/**
+ * An 8x8 repeating fill pattern and how it paints. Passed to
+ * `screen_fill_pattern` and `bitmap_fill_pattern`.
+ */
+typedef struct pattern
+{
+ uint8_t bits[8]; /**< One byte per row, MSB = leftmost pixel. */
+ colour_t fg; /**< Colour for set bits. */
+ colour_t bg; /**< Colour for clear bits, unless a stencil. */
+ unsigned flags; /**< Bitwise OR of `pattern_FLAG_*`, or 0. */
+ point_t origin; /**< Tile phase: the coordinate mapping to the fill box's
+ top-left corner. Passing a scroll origin keeps the
+ pattern locked to content rather than crawling. */
+}
+pattern_t;
+
+/**
+ * Build a `pattern_t` from a built-in preset and a pair of colours. The
+ * result has no flags set and a zero origin; assign `.flags` and `.origin`
+ * afterwards if needed.
+ *
+ * \param[in] preset One of `screen_PATTERN_*`.
+ * \param[in] fg Colour for set bits.
+ * \param[in] bg Colour for clear bits.
+ * \return The pattern.
+ */
+pattern_t pattern_from_preset(screen_pattern_t preset,
+ colour_t fg,
+ colour_t bg);
+
+/**
+ * Build a stencil `pattern_t` from a caller-supplied 8x8 mask and a single
+ * colour. `pattern_FLAG_STENCIL` is set, so clear-bit pixels are left
+ * untouched; `.bg` is unused. The origin is zero.
+ *
+ * \param[in] mask Eight bytes, one per pattern row, MSB leftmost.
+ * \param[in] colour Colour for set bits.
+ * \return The pattern.
+ */
+pattern_t pattern_from_mask(const uint8_t mask[8], colour_t colour);
+
+#endif /* FRAMEBUF_PATTERN_H */
diff --git a/include/framebuf/pixelfmt.h b/include/framebuf/pixelfmt.h
index b20b2882..c8b0f638 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 4c1942a3..aa8087ff 100644
--- a/include/framebuf/screen.h
+++ b/include/framebuf/screen.h
@@ -1,9 +1,10 @@
-/* screen.h -- screen type */
+/* framebuf/screen.h -- screen type */
#ifndef FRAMEBUF_SCREEN_H
#define FRAMEBUF_SCREEN_H
#include "framebuf/bitmap.h"
+#include "framebuf/pattern.h"
#include "geom/box.h"
#include "geom/point.h"
#include "utils/fxp.h"
@@ -24,23 +25,22 @@ screen_t;
* Initialize a previously allocated screen structure.
*
* \param[in] scr Screen to initialize.
- * \param[in] width Width of screen in pixels.
- * \param[in] height Height of screen in pixels.
+ * \param[in] size Width and height of the screen in pixels.
* \param[in] fmt Pixel format of the screen.
* \param[in] rowbytes Number of bytes per row of the screen.
* \param[in] palette Palette of the screen, or NULL.
* \param[in] base Base address of the screen.
*/
void screen_init(screen_t *scr,
- int width,
- int height,
+ size2d_t size,
pixelfmt_t fmt,
int rowbytes,
colour_t *palette,
void *base);
/**
- * Initialize a previously allocated screen structure, for drawing to an existing bitmap.
+ * Initialize a previously allocated screen structure, for drawing to an
+ * existing bitmap.
*
* \param[in] scr Screen to initialize.
* \param[in] bm Bitmap to draw to.
@@ -64,7 +64,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.
@@ -72,17 +72,17 @@ void screen_draw_pixel(screen_t *scr, int x, int y, colour_t colour);
* \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] width Width of rectangle.
- * \param[in] height Height of rectangle.
+ * \param[in] size Width and height of rectangle.
* \param[in] colour Colour of rectangle.
*/
-void screen_draw_rect(screen_t *scr,
- int x, int y,
- int width, int height,
- colour_t colour);
+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.
@@ -90,16 +90,52 @@ 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,
- 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);
+
+/**
+ * Fills a horizontal run of `w` pixels starting at (`x`, `y`).
+ *
+ * A non-positive `w` draws nothing. Clipped to the screen's clip region.
+ * This is the per-row primitive `screen_fill_rect` and `screen_draw_circle`
+ * build on.
+ *
+ * \param[in] scr Screen to draw upon.
+ * \param[in] x X coordinate of the leftmost pixel of the run.
+ * \param[in] y Y coordinate of the run.
+ * \param[in] w Length of the run in pixels.
+ * \param[in] colour Colour of the run.
+ */
+void screen_fill_hline(screen_t *scr, int x, int y, int w, colour_t colour);
/**
- * 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).
+ * Fills a box with a repeating 8x8 pattern.
+ *
+ * A plain pattern paints every pixel in the box, set bits taking
+ * `pattern->fg` and clear bits `pattern->bg`. A stencil pattern (one with
+ * `pattern_FLAG_STENCIL`) paints only the set-bit pixels. The tile is phased
+ * against `pattern->origin`: that coordinate is the one that maps 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.
+ *
+ * \param[in] scr Screen to draw upon.
+ * \param[in] box Box to fill, inclusive-exclusive.
+ * \param[in] pattern Pattern to fill with.
+ */
+void screen_fill_pattern(screen_t *scr,
+ const box_t *box,
+ const pattern_t *pattern);
+
+/**
+ * Copies a bitmap onto the screen, 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.
@@ -107,23 +143,63 @@ void screen_draw_square(screen_t *scr,
* \param[in] scr Screen to draw upon.
* \param[in] x X coordinate of leftmost point to draw bitmap at.
* \param[in] y Y coordinate of topmost point to draw bitmap at.
- * \param[in] src Bitmap to draw.
+ * \param[in] src Bitmap to copy.
+ * \return \ref result_OK on success, \ref result_NOT_SUPPORTED if the
+ * screen's pixel format has no blit path.
+ */
+result_t screen_copy_bitmap(screen_t *scr,
+ int x,
+ int y,
+ const bitmap_t *src);
+
+/** Flags for `screen_copy_ninepatch`. */
+enum
+{
+ screen_NINEPATCH_NO_CENTRE = 1u << 0 /**< Leave the interior untouched. */
+};
+
+/**
+ * Copies a "9-patch" onto the screen: 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_copy_bitmap` does. No scaling is performed.
+ *
+ * \param[in] scr Screen to draw upon.
+ * \param[in] dst Destination box to fill with the frame.
+ * \param[in] src Source image, a 3x3 grid of cells.
+ * \param[in] flags Bitwise OR of `screen_NINEPATCH_*`, or 0. Pass
+ * `screen_NINEPATCH_NO_CENTRE` to draw only the border and
+ * leave the interior untouched.
+ * \return \ref result_OK on success, \ref result_NOT_SUPPORTED if the
+ * screen's pixel format has no blit path.
*/
-void screen_draw_bitmap(screen_t *scr, int x, int y, const bitmap_t *src);
+result_t screen_copy_ninepatch(screen_t *scr,
+ const box_t *dst,
+ const bitmap_t *src,
+ unsigned int flags);
/**
- * Copies a rectangular region of the screen to another position on the
- * same screen (e.g. sliding an already-rendered window's pixels to a new
- * position without asking its owner to redraw). Source and destination may
- * overlap; copying is done in the correct row order to handle that safely.
+ * Copies a rectangular region of the screen to another position on the same
+ * screen (e.g. sliding an already-rendered window's pixels to a new position
+ * without asking its owner to redraw). Source and destination may overlap;
+ * 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.
*
* 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.
+ * invalidate/redraw when it is not \ref result_OK (an unknown pixel format,
+ * or the source/destination lying wholly off-screen), 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
@@ -134,17 +210,18 @@ void screen_draw_bitmap(screen_t *scr, int x, int y, const bitmap_t *src);
* \param[in] scr Screen to copy within.
* \param[in] src Screen-space region to copy from.
* \param[in] dst Top-left of the destination.
- * \param[out] copied_dst Set to the on-screen box actually copied to (may
- * be 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).
+ * \param[out] copied_dst Set to the on-screen box actually copied to (may be
+ * smaller than intended if either end was partly
+ * off-screen). Pass NULL if not needed. Left unset if
+ * the copy was declined.
+ * \return \ref result_OK if the copy was performed, \ref
+ * result_NOT_SUPPORTED if declined (unsupported pixel format, or
+ * nothing left to copy after clipping).
*/
-int screen_copy_rect(screen_t *scr,
- const box_t *src,
- point_t dst,
- box_t *copied_dst);
+result_t screen_copy_rect(screen_t *scr,
+ const box_t *src,
+ point_t dst,
+ box_t *copied_dst);
/**
* Draws a line (Bresenham version with aliasing).
@@ -159,13 +236,114 @@ int screen_copy_rect(screen_t *scr,
* \param[in] colour Colour of line.
*/
void screen_draw_line(screen_t *scr,
- int x0, int y0, int x1, int y1,
- colour_t colour);
+ int x0,
+ int y0,
+ int x1,
+ 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 one-pixel unfilled circle outline (integer midpoint algorithm, no
+ * anti-aliasing). Clipped to the screen's clip region. A negative radius
+ * draws nothing; a zero radius draws a single pixel at the centre.
+ *
+ * \param[in] scr Screen to draw upon.
+ * \param[in] cx X coordinate of the centre.
+ * \param[in] cy Y coordinate of the centre.
+ * \param[in] r Radius in pixels.
+ * \param[in] colour Colour of the outline.
+ */
+void screen_draw_circle(screen_t *scr,
+ int cx,
+ int cy,
+ int r,
+ colour_t colour);
+
+/**
+ * Draws a solid filled disc of the given radius. Clipped to the screen's
+ * clip region. A negative radius draws nothing; a zero radius draws a single
+ * pixel at the centre.
+ *
+ * \param[in] scr Screen to draw upon.
+ * \param[in] cx X coordinate of the centre.
+ * \param[in] cy Y coordinate of the centre.
+ * \param[in] r Radius in pixels.
+ * \param[in] colour Colour of the disc.
+ */
+void screen_fill_circle(screen_t *scr,
+ int cx,
+ int cy,
+ int r,
+ 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).
*
- * Coordinates are fixed point values of type `fix8_t`. Coordinates are inclusive.
+ * Coordinates are fixed point values of type `fix8_t`. Coordinates are
+ * inclusive.
*
* \param[in] scr Screen to draw upon.
* \param[in] x0 X coordinate of first point of line.
@@ -175,13 +353,17 @@ void screen_draw_line(screen_t *scr,
* \param[in] colour Colour of line.
*/
void screen_draw_line_wu_fix8(screen_t *scr,
- fix8_t x0, fix8_t y0, fix8_t x1, fix8_t y1,
- colour_t colour);
+ fix8_t x0,
+ fix8_t y0,
+ fix8_t x1,
+ fix8_t y1,
+ colour_t colour);
/**
* Draws a line (floating point Wu version with anti-aliasing).
*
- * Coordinates are floating point values of type `float`. Coordinates are inclusive.
+ * Coordinates are floating point values of type `float`. Coordinates are
+ * inclusive.
*
* \param[in] scr Screen to draw upon.
* \param[in] x0 X coordinate of first point of line.
@@ -191,7 +373,10 @@ void screen_draw_line_wu_fix8(screen_t *scr,
* \param[in] colour Colour of rectangle.
*/
void screen_draw_line_wu_float(screen_t *scr,
- float x0, float y0, float x1, float y1,
- colour_t colour);
+ float x0,
+ float y0,
+ float x1,
+ float y1,
+ colour_t colour);
#endif /* FRAMEBUF_SCREEN_H */
diff --git a/include/framebuf/span-bgrx8888.h b/include/framebuf/span-bgrx8888.h
index e4bf23c1..f956fda9 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 f49edf53..776f7f6f 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 c110ccc9..77685f14 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
@@ -10,7 +10,8 @@
* Find an appropriate span for the specified pixel format.
*
* \param[in] fmt Required pixel format.
- * \return A span, or NULL if no span is available for the specified pixel format.
+ * \return A span, or NULL if no span is available for the specified pixel
+ * format.
*/
const span_t *spanregistry_get(pixelfmt_t fmt);
diff --git a/include/framebuf/span-rgba8888.h b/include/framebuf/span-rgba8888.h
index 4aec7fd6..bad2cc32 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 e516b636..0c12e03e 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 67d29fe1..8640c97c 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 04e93158..149de5a6 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
@@ -14,10 +14,31 @@
*/
typedef void (span_copy_t)(void *dst, const void *src, int length);
+/**
+ * Type of a "fill run of pixels" function.
+ *
+ * Writes `length` copies of a single pixel value. `dst` points at the base
+ * of the run's row and `first` is the index of the first pixel to write
+ * within that row, so sub-byte formats (e.g. P4) can address an odd nibble
+ * without the caller pre-packing. Whole-byte formats treat `dst` + `first`
+ * as an ordinary pixel pointer.
+ *
+ * \param[out] dst Base of the destination row.
+ * \param[in] first Index of the first pixel to write, from `dst`.
+ * \param[in] pixel Pixel value to write (already quantised to the format).
+ * \param[in] length Number of pixels to write.
+ */
+typedef void (span_fill_t)(void *dst,
+ int first,
+ pixelfmt_any_t pixel,
+ 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.
@@ -37,7 +58,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.
@@ -54,12 +77,14 @@ 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
{
pixelfmt_t format; /**< Pixel format this span is for. */
span_copy_t *copy; /**< Copy pixels function. */
+ span_fill_t *fill; /**< Fill run of pixels function. */
span_blendconst_t *blendconst; /**< Blend constant pixels function. */
span_blendarray_t *blendarray; /**< Blend array of pixels function. */
}
diff --git a/include/geom/box.h b/include/geom/box.h
index f0530f29..da7751c6 100644
--- a/include/geom/box.h
+++ b/include/geom/box.h
@@ -1,10 +1,12 @@
-/* box.h -- box type */
+/* geom/box.h -- box type */
#ifndef GEOM_BOX_H
#define GEOM_BOX_H
#include
+#include "geom/size.h"
+
#ifdef __cplusplus
extern "C"
{
@@ -27,12 +29,23 @@ 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 }
/** Initialises a box from a position (x,y) and a size (w,h). */
#define BOX_POS_SIZE(x, y, w, h) { (x), (y), (x) + (w), (y) + (h) }
+/**
+ * Returns the size of the box "b".
+ *
+ * \param[in] b The box to measure.
+ * \return The box's width and height.
+ */
+size2d_t box_size(const box_t *b);
+
/**
* Reset the box to an invalid state.
*
@@ -82,11 +95,14 @@ int box_intersects(const box_t *a, const box_t *b);
int box_intersection(const box_t *a, const box_t *b, box_t *c);
/**
- * Populates the box "clipped" with the sizes of the edges discarded when clipping box "b" against "a".
+ * Populates the box "clipped" with the sizes of the edges discarded when
+ * clipping box "b" against "a".
*
* \param[in] a The first box.
* \param[in] b The second box.
- * \param[out] clipped Not really a box, but one scalar per edge. Values are positive where "b" extends outside of "a", zero otherwise.
+ * \param[out] clipped Not really a box, but one scalar per edge. Values are
+ * positive where "b" extends outside of "a", zero
+ * otherwise.
*/
void box_clipped(const box_t *a, const box_t *b, box_t *clipped);
diff --git a/include/geom/layout.h b/include/geom/layout.h
index 73971a83..ded5e37e 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
@@ -61,7 +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 82b37ec5..a8cc7668 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
@@ -11,7 +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.
*
* \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 c47525b8..cc7aaa33 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
@@ -77,6 +77,37 @@ int packer_next_width(T *packer, packer_loc_t loc);
result_t packer_place_at(T *packer,
const box_t *area);
+/**
+ * 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.
+ *
+ * \param[in] packer Packer to configure.
+ * \param[in] gutter Gutter width in pixels.
+ */
+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.
+ *
+ * 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.
+ * \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'.
diff --git a/include/geom/point.h b/include/geom/point.h
index be19615c..36ee4be9 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
@@ -10,4 +10,7 @@ typedef struct point
}
point_t;
+/** Construct a point_t compound literal. */
+#define POINT(x, y) ((point_t) { (x), (y) })
+
#endif /* GEOM_POINT_H */
diff --git a/include/geom/size.h b/include/geom/size.h
new file mode 100644
index 00000000..0210cc4c
--- /dev/null
+++ b/include/geom/size.h
@@ -0,0 +1,16 @@
+/* geom/size.h -- size type */
+
+#ifndef GEOM_SIZE_H
+#define GEOM_SIZE_H
+
+/** 2D size with integer dimensions. */
+typedef struct size2d
+{
+ int w, h;
+}
+size2d_t;
+
+/** Construct a size2d_t compound literal. */
+#define SIZE2D(w, h) ((size2d_t) { (w), (h) })
+
+#endif /* GEOM_SIZE_H */
diff --git a/include/io/path.h b/include/io/path.h
index 4d92e330..0cb354f4 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
@@ -8,14 +8,17 @@
/**
* Join 'leaf' with extension 'ext' according to the host convention.
*
- * Note: Returns a pointer to an internal static buffer of length `DPTLIB_MAXPATH`.
+ * Note: Returns a pointer to an internal static buffer of length
+ * `DPTLIB_MAXPATH`.
*/
const char *path_join_leafname(const char *leaf, const char *ext);
/**
- * Join 'root' with `nbranches` directory names according to the host convention.
+ * Join 'root' with `nbranches` directory names according to the host
+ * convention.
*
- * Note: Returns a pointer to an internal static buffer of length `DPTLIB_MAXPATH`.
+ * Note: Returns a pointer to an internal static buffer of length
+ * `DPTLIB_MAXPATH`.
*/
const char *path_join_filename(const char *root, int nbranches, ...);
diff --git a/include/io/stream-mem.h b/include/io/stream-mem.h
index b30c44d1..bf48c14a 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
@@ -19,7 +19,8 @@ extern "C"
*
* \param[in] block Block of memory to create the stream from.
* \param[in] length Length of the block of memory in bytes.
- * \param[out] s Pointer to a `stream_t` pointer to store the created stream.
+ * \param[out] s Pointer to a `stream_t` pointer to store the created
+ * stream.
* \return \ref result_OK on success, or appropriate result code otherwise.
*/
result_t stream_mem_create(const unsigned char *block,
diff --git a/include/io/stream-mtfcomp.h b/include/io/stream-mtfcomp.h
index 0f0ec2f5..7d17d036 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
@@ -16,7 +16,8 @@ extern "C"
*
* \param[in] input The input stream.
* \param[in] bufsz The buffer size.
- * \param[out] s Pointer to a `stream_t` pointer to store the created stream.
+ * \param[out] s Pointer to a `stream_t` pointer to store the created
+ * stream.
* \return \ref result_OK on success, or appropriate result code otherwise.
*/
result_t stream_mtfcomp_create(stream_t *input, int bufsz, stream_t **s);
@@ -26,7 +27,8 @@ result_t stream_mtfcomp_create(stream_t *input, int bufsz, stream_t **s);
*
* \param[in] input The input stream.
* \param[in] bufsz The buffer size.
- * \param[out] s Pointer to a `stream_t` pointer to store the created stream.
+ * \param[out] s Pointer to a `stream_t` pointer to store the created
+ * stream.
* \return \ref result_OK on success, or appropriate result code otherwise.
*/
result_t stream_mtfdecomp_create(stream_t *input, int bufsz, stream_t **s);
diff --git a/include/io/stream-packbits.h b/include/io/stream-packbits.h
index 77b9af18..10b25ca6 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
@@ -16,20 +16,26 @@ extern "C"
*
* \param[in] input Input stream.
* \param[in] bufsz Buffer size in bytes (0 for a sensible default).
- * \param[out] s Pointer to a `stream_t` pointer to store the created stream.
+ * \param[out] s Pointer to a `stream_t` pointer to store the created
+ * 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.
*
* \param[in] input Input stream.
* \param[in] bufsz Buffer size in bytes (0 for a sensible default).
- * \param[out] s Pointer to a `stream_t` pointer to store the created stream.
+ * \param[out] s Pointer to a `stream_t` pointer to store the created
+ * 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-stdio.h b/include/io/stream-stdio.h
index 80e245a9..a769c70a 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
@@ -19,7 +19,8 @@ extern "C"
*
* \param[in] f File to create the stream from.
* \param[in] bufsz Buffer size in bytes (0 for a sensible default).
- * \param[out] s Pointer to a `stream_t` pointer to store the created stream.
+ * \param[out] s Pointer to a `stream_t` pointer to store the created
+ * stream.
* \return \ref result_OK on success, or appropriate result code otherwise.
*/
result_t stream_stdio_create(FILE *f, int bufsz, stream_t **s);
diff --git a/include/io/stream.h b/include/io/stream.h
index dbcb8035..ff1e353d 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 c3319030..aca2be79 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
@@ -25,7 +25,8 @@ extern testfn_t pickle_test,
/* framebuf */
extern testfn_t bmfont_test,
composite_test,
- curve_test;
+ curve_test,
+ screen_test;
/* geom */
extern testfn_t box_test,
diff --git a/include/test/txtscr.h b/include/test/txtscr.h
index 0dcf6191..40c6c6fd 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
new file mode 100644
index 00000000..8bc6f4dd
--- /dev/null
+++ b/include/text/bmtext.h
@@ -0,0 +1,90 @@
+/* text/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/include/text/txtfmt.h b/include/text/txtfmt.h
index efd19407..65867ce1 100644
--- a/include/text/txtfmt.h
+++ b/include/text/txtfmt.h
@@ -1,13 +1,12 @@
-/* txtfmt.h -- text formatting */
+/* text/txtfmt.h -- text formatting */
/**
* \file txtfmt.h
*
- * Word-wraps a string to a given character width. Wraps at character
- * widths, not measured widths, so works best for monospaced text.
+ * Word-wraps a string to a given character width. Wraps at character widths,
+ * not measured widths, so works best for monospaced text.
*
- * - Breaks at spaces.
- * - Forces a newline at \\n or \\r.
+ * - Breaks at spaces. - Forces a newline at \\n or \\r.
*/
#ifndef DATASTRUCT_TXTFMT_H
@@ -105,15 +104,15 @@ int txtfmt_get_wrapped_width(const txtfmt_t *tx);
* \return result_OK or result_BAD_ARG if index is out of range.
*/
result_t txtfmt_get_line(const txtfmt_t *tx,
- int index,
- const char **line,
- int *length);
+ int index,
+ const char **line,
+ int *length);
/* ----------------------------------------------------------------------- */
/**
- * Print the wrapped text via printf, including line numbers (for testing
- * and debugging).
+ * Print the wrapped text via printf, including line numbers (for testing and
+ * debugging).
*
* \param[in] tx Txtfmt to print.
*
diff --git a/include/utils/array.h b/include/utils/array.h
index 917dcebb..228c33b4 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
@@ -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.
@@ -92,12 +92,12 @@ void array_squeeze2(unsigned char *base,
*
* \return 0 - ok, 1 - out of memory
*/
-int array_grow(void **block,
- size_t elemsize,
- int used,
- int *allocated,
- int need,
- int minimum);
+int array_grow(void **block,
+ size_t elemsize,
+ int used,
+ int *allocated,
+ int need,
+ int minimum);
/**
* Shrink a dynamically allocated array to have no free entries.
diff --git a/include/utils/barith.h b/include/utils/barith.h
index 053ccd77..68008063 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 7405f2e2..2fed3371 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 71508c01..d3af2d89 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 4043cb9b..d5bfe9a5 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 50df4a1f..aef58a0a 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 c7f56a7a..dc41271e 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).
@@ -29,22 +29,21 @@ extern "C"
*
* The format string argument accepts the following format characters:
*
- * - 'c' to pack into 8 bits (notional char)
- * - 's' to pack into 16 bits (notional short)
- * - 'i' to pack into 32 bits (notional int)
- * - 'q' to pack into 64 bits (notional long long 'quad')
+ * - 'c' to pack into 8 bits (notional char) - 's' to pack into 16 bits
+ * (notional short) - 'i' to pack into 32 bits (notional int) - 'q' to pack
+ * into 64 bits (notional long long 'quad')
*
* Each format character may be preceded by a count.
*
*
* Examples:
*
- * n = pack(outbuf, "ccc", 1, 2, 3); ("ccc" can also be written "3c")
- * n = pack(outbuf, "2si", 0x2000, 12345, 1 << 31);
+ * n = pack(outbuf, "ccc", 1, 2, 3); ("ccc" can also be written "3c") n =
+ * 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:
*
@@ -63,8 +62,8 @@ size_t pack(unsigned char *outbuf, const char *fmt, ...);
/**
* Structure unpacking.
*
- * The arguments are unpacked from 'buf' according to the format string
- * 'fmt' using little-endian byte order.
+ * The arguments are unpacked from 'buf' according to the format string 'fmt'
+ * using little-endian byte order.
*
* \see pack for a description of the format string.
*
@@ -75,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:
*
@@ -88,15 +87,13 @@ size_t pack(unsigned char *outbuf, const char *fmt, ...);
* Additionally, unpack can specify different source and destination sizes by
* prefixing a formatting character with a source size qualifier:
*
- * - 'b' - byte
- * - 'h' - half-word
- * - 'w' - word
- * - 'd' - double word
+ * - 'b' - byte - 'h' - half-word - 'w' - word - 'd' - double word
*
- * (Note that these specifiers are all different than the formatting characters).
+ * (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:
*
@@ -109,8 +106,7 @@ size_t pack(unsigned char *outbuf, const char *fmt, ...);
*
* unpack copes with different endian formats. Prefix the string with:
*
- * - '<' to unpack little endian data
- * - '>' to unpack big endian data
+ * - '<' to unpack little endian data - '>' to unpack big endian data
*
* The default is [ought to be] platform dependent.
*
diff --git a/include/utils/primes.h b/include/utils/primes.h
index 8948abf5..1937c849 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/component/colourmenu.h b/include/wuss/component/colourmenu.h
new file mode 100644
index 00000000..2640d11d
--- /dev/null
+++ b/include/wuss/component/colourmenu.h
@@ -0,0 +1,94 @@
+/* wuss/component/colourmenu.h -- a menu of the system palette colours */
+
+/**
+ * \file colourmenu.h
+ *
+ * A shared wuss component: a pop-up menu with one row per system-palette
+ * entry, each row carrying a colour chip (see wuss_MENU_ITEM_SWATCH) -- the
+ * RISC OS Toolbox ColourMenu in miniature.
+ *
+ * A wuss_colourmenu owns a plain wuss_menu_t (see wuss/menu.h): item i is
+ * palette index i, its swatch that colour, its label a "#RRGGBB" hex string.
+ * A task opens it with wuss_menu_open and, in its wuss_EVENT_MENU_SELECT
+ * case, calls wuss_colourmenu_selected to turn the event back into a
+ * wuss_colour_t palette index.
+ *
+ * Built only when WUSS_COMPONENTS is defined (which implies WUSS_MENUS).
+ */
+
+#ifndef WUSS_COMPONENT_COLOURMENU_H
+#define WUSS_COMPONENT_COLOURMENU_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include "base/result.h"
+
+#include "wuss/menu.h"
+#include "wuss/task.h"
+#include "wuss/wuss.h"
+
+/* ----------------------------------------------------------------------- */
+
+/** Opaque handle: owns the menu tree and the hex-label strings in it. */
+typedef struct wuss_colourmenu wuss_colourmenu_t;
+
+/**
+ * Build a colour menu covering \p wuss's whole system palette: one row per
+ * entry, index order, each with its colour chip and a "#RRGGBB" label.
+ *
+ * \param[out] out Filled with the new handle on success, untouched on
+ * failure.
+ * \param[in] wuss Owner; its palette is read now (not retained). The menu
+ * outlives changes to the palette -- rebuild it after a
+ * wuss_set_palette if the rows should follow.
+ * \param[in] title Menu caption, borrowed and copied; NULL for "Colour".
+ * \return \ref result_OK, \ref result_OOM, or \ref result_NULL_ARG.
+ */
+result_t wuss_colourmenu_create(wuss_colourmenu_t **out,
+ const wuss_t *wuss,
+ const char *title);
+
+/**
+ * Free a colour menu and every string in it. Any open menu chain showing it
+ * must be closed first (wuss_menu_close). Safe to pass NULL.
+ *
+ * \param[in] doomed Handle to free, or NULL.
+ */
+void wuss_colourmenu_destroy(wuss_colourmenu_t *doomed);
+
+/**
+ * The menu to hand to wuss_menu_open. Borrowed; valid until
+ * wuss_colourmenu_destroy. NULL only if \p cm is NULL.
+ *
+ * \param[in] cm Handle.
+ * \return The menu, or NULL.
+ */
+const wuss_menu_t *wuss_colourmenu_menu(const wuss_colourmenu_t *cm);
+
+/**
+ * Resolve a wuss_EVENT_MENU_SELECT event to the picked palette index.
+ *
+ * Call from the task's wuss_EVENT_MENU_SELECT case. Returns 0 with \p ok
+ * cleared -- not a match -- unless \p ev is a MENU_SELECT whose menu is this
+ * colourmenu's own; so a task multiplexing several menus through one handle
+ * can call each resolver in turn.
+ *
+ * \param[in] cm Handle.
+ * \param[in] ev The event passed to the task's handle callback.
+ * \param[out] ok Set non-zero if \p ev was this colourmenu's, else zero. May
+ * be NULL.
+ * \return The selected palette index, or 0 if \p ev is not this colourmenu's
+ * (check \p ok to tell that from a real index 0).
+ */
+wuss_colour_t wuss_colourmenu_selected(const wuss_colourmenu_t *cm,
+ const wuss_event_t *ev,
+ int *ok);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WUSS_COMPONENT_COLOURMENU_H */
diff --git a/include/wuss/component/fontmenu.h b/include/wuss/component/fontmenu.h
new file mode 100644
index 00000000..95613dfc
--- /dev/null
+++ b/include/wuss/component/fontmenu.h
@@ -0,0 +1,119 @@
+/* wuss/component/fontmenu.h -- a menu of the available bitmap fonts */
+
+/**
+ * \file fontmenu.h
+ *
+ * A shared wuss component: a pop-up menu listing the bitmap fonts in a
+ * directory, the RISC OS Toolbox FontMenu in miniature.
+ *
+ * A wuss_fontmenu owns a plain wuss_menu_t (see wuss/menu.h) built from
+ * bmfont_enumerate: one leaf item per font, alphabetically sorted, its label
+ * the font's leafname sans ".png". A task opens it with wuss_menu_open and,
+ * in its wuss_EVENT_MENU_SELECT case, calls wuss_fontmenu_selected to turn
+ * the event back into a font name -- no indexing into menu->items by hand.
+ *
+ * Built only when WUSS_COMPONENTS is defined (which implies WUSS_MENUS).
+ */
+
+#ifndef WUSS_COMPONENT_FONTMENU_H
+#define WUSS_COMPONENT_FONTMENU_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include "base/result.h"
+
+#include "wuss/menu.h"
+#include "wuss/task.h"
+#include "wuss/wuss.h"
+
+/* ----------------------------------------------------------------------- */
+
+/** Opaque handle: owns the menu tree and the font-name strings in it,
+ * allocated through the hooks passed to wuss_fontmenu_create. */
+typedef struct wuss_fontmenu wuss_fontmenu_t;
+
+/**
+ * Build a font menu from the ".png" fonts in \p dir (see bmfont_enumerate).
+ * Items are sorted by name; \p title is the menu's caption (NULL -> "Font").
+ *
+ * \param[out] out Filled with the new handle on success, untouched on
+ * failure.
+ * \param[in] dir Directory to scan for fonts.
+ * \param[in] title Menu caption, borrowed and copied; NULL for "Font".
+ * \param[in] wuss Window manager to consult for fonts to leave out, or
+ * NULL to include every font found. Any wuss_create font
+ * slot (see \ref wuss_font_desc_t) whose class is \ref
+ * wuss_FONT_CLASS_SYSTEM and whose name matches a scanned
+ * leafname is skipped -- e.g. the symbol font wuss itself
+ * draws menu ticks and submenu arrows from, which is not
+ * meant to be picked as a text font.
+ * \param[in] alloc Allocator for every block the handle keeps, copied in;
+ * NULL selects \ref wuss_alloc (plain stdlib). Pass the
+ * same hooks given to wuss_create so the component and the
+ * window manager share a heap. The transient directory
+ * scan still uses stdlib.
+ * \return \ref result_OK, \ref result_OOM, \ref result_NULL_ARG, or \ref
+ * result_FILE_NOT_FOUND if \p dir cannot be opened. An empty
+ * directory still succeeds, yielding a menu with no items.
+ */
+result_t wuss_fontmenu_create(wuss_fontmenu_t **out,
+ const char *dir,
+ const char *title,
+ const wuss_t *wuss,
+ const wuss_alloc_t *alloc);
+
+/**
+ * Free a font menu and every string in it. Any open menu chain showing it
+ * must be closed first (wuss_menu_close). Safe to pass NULL.
+ *
+ * \param[in] doomed Handle to free, or NULL.
+ */
+void wuss_fontmenu_destroy(wuss_fontmenu_t *doomed);
+
+/**
+ * The menu to hand to wuss_menu_open. Borrowed; valid until
+ * wuss_fontmenu_destroy. NULL only if \p fm is NULL.
+ *
+ * \param[in] fm Handle.
+ * \return The menu, or NULL.
+ */
+const wuss_menu_t *wuss_fontmenu_menu(const wuss_fontmenu_t *fm);
+
+/**
+ * Resolve a wuss_EVENT_MENU_SELECT event to the picked font's name.
+ *
+ * Call from the task's wuss_EVENT_MENU_SELECT case. Returns NULL -- not a
+ * match -- unless \p ev is a MENU_SELECT whose menu is this fontmenu's own;
+ * so a task multiplexing several menus through one handle can call each
+ * fontmenu's resolver in turn.
+ *
+ * \param[in] fm Handle.
+ * \param[in] ev The event passed to the task's handle callback.
+ * \return The selected font's name (borrowed, valid until
+ * wuss_fontmenu_destroy), or NULL if \p ev is not this fontmenu's.
+ */
+const char *wuss_fontmenu_selected(const wuss_fontmenu_t *fm,
+ const wuss_event_t *ev);
+
+/**
+ * Tick the item at \p index and untick every other item, so the menu shows
+ * which font is currently in use. The menu is caller-displayed, not redrawn
+ * here -- call before wuss_menu_open (or reopen the menu) for the tick to be
+ * seen.
+ *
+ * \param[in] fm Handle; a NULL \p fm is a no-op.
+ * \param[in] index Row to tick, or -1 to untick every row (e.g. when a font
+ * outside the menu, such as the wuss system font, is in
+ * use). Out-of-range values other than -1 still untick
+ * every row.
+ */
+void wuss_fontmenu_set_ticked(wuss_fontmenu_t *fm, int index);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WUSS_COMPONENT_FONTMENU_H */
diff --git a/include/wuss/icon.h b/include/wuss/icon.h
new file mode 100644
index 00000000..94b36f27
--- /dev/null
+++ b/include/wuss/icon.h
@@ -0,0 +1,359 @@
+/* wuss/icon.h -- wuss work-area icons */
+
+/**
+ * \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.
+ *
+ * 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
+ * local_box -- so an icon scrolls with the content it sits on. Its on-screen
+ * position is (content.x0 - scroll.x + bbox), using the window's current
+ * 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_MOUSE.
+ */
+
+#ifndef WUSS_ICON_H
+#define WUSS_ICON_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include "base/result.h"
+#include "framebuf/screen.h"
+#include "geom/box.h"
+#include "geom/point.h"
+
+#include "wuss/wuss.h"
+
+/* The in-content icon subsystem is a compile-time option (CMake WUSS_ICONS).
+ * With it off the library has no wuss_icon_* symbols, so this header body is
+ * skipped. */
+#ifdef WUSS_ICONS
+
+/* ----------------------------------------------------------------------- */
+
+/* wuss_icon_t (opaque, owned by the window it is created on) is forward-declared
+ * 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.
+ */
+typedef enum wuss_icon_type
+{
+ /** 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;
+
+/** Icon appearance and behaviour flags, combinable with bitwise OR. */
+typedef enum wuss_icon_flags
+{
+ wuss_ICON_FLAGS_NONE = 0,
+ /** 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_TYPE_MENU_ENTRY: draw a small colour chip (spec.swatch) in the
+ * row's left gutter, where the tick would sit. Mutually exclusive with a
+ * selected tick -- the chip wins. Ignored by other types. */
+ wuss_ICON_FLAGS_SWATCH = 1 << 8,
+ /** Two-bit field (bits 9-10) selecting which of wuss_create's fonts draws
+ * this icon's text: 0 is the system font, 1-3 the further slots. Build it
+ * with wuss_ICON_FONT(n); read it with wuss_ICON_FONT_OF(flags). A slot
+ * that was not filled falls back to the system font. */
+ wuss_ICON_FLAGS_FONT_MASK = 3 << 9
+}
+wuss_icon_flags_t;
+
+/**
+ * Encode font slot \p n (0..3) as icon flags; OR into wuss_icon_spec::flags.
+ */
+#define wuss_ICON_FONT(n) (((n) & 3) << 9)
+
+/** Decode the font slot (0..3) from an icon's flags. */
+#define wuss_ICON_FONT_OF(f) (((f) >> 9) & 3)
+
+/**
+ * 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
+ * zero-initialise the spec.
+ */
+typedef struct wuss_icon_spec
+{
+ /** 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;
+ /** wuss_ICON_TYPE_MENU_ENTRY with wuss_ICON_FLAGS_SWATCH: the colour chip to
+ * draw in the left gutter, as an index into the system palette. Ignored
+ * unless that flag is set; ignored by all other icon types. */
+ wuss_colour_t swatch;
+ /** Appearance/behaviour flags. */
+ wuss_icon_flags_t flags;
+}
+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.
+ *
+ * \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.
+ * \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.
+ */
+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.
+ *
+ * \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).
+ */
+result_t wuss_icon_create_array(wuss_window_t *window,
+ const wuss_icon_spec_t *specs,
+ int nspecs,
+ wuss_icon_t **icons);
+
+/**
+ * Draw an icon from a spec once, retaining nothing: no allocation, no icon
+ * added to the window. For static, non-interactive content a task can redraw
+ * cheaply from its own model -- a large grid of swatches, say -- without a
+ * live icon per cell.
+ *
+ * Call only from a task's wuss_EVENT_REDRAW handler, passing that event's \c
+ * bounds and \c scroll straight through; the icon is painted through the
+ * window manager's screen with the redraw clip already in force. The spec is
+ * validated exactly as \ref wuss_icon_create validates it. The icon's \c
+ * bbox is in virtual document space, as for a created icon.
+ *
+ * \param[in] window Window being redrawn.
+ * \param[in] spec Icon description; not retained.
+ * \param[in] content The redraw event's \c bounds (full content box, screen
+ * space).
+ * \param[in] scroll The redraw event's \c scroll offset.
+ * \return \ref result_OK, or the same \ref result_WUSS_BAD_ICON / \ref
+ * result_WUSS_BAD_COLOUR \ref wuss_icon_create would return.
+ */
+result_t wuss_icon_plot(wuss_window_t *window,
+ const wuss_icon_spec_t *spec,
+ const box_t *content,
+ point_t scroll);
+
+/**
+ * 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.
+ *
+ * \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).
+ */
+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.
+ *
+ * \param[in] icon Icon to change.
+ * \param[in] hidden Non-zero to hide the icon, zero to show it.
+ */
+void wuss_icon_set_hidden(wuss_icon_t *icon, int hidden);
+
+/**
+ * Fetch an icon's bounding box, in virtual document space.
+ *
+ * \param[in] icon Icon to query.
+ * \param[out] bbox Filled in with the bounding box.
+ */
+void wuss_icon_get_bbox(const wuss_icon_t *icon, box_t *bbox);
+
+/**
+ * Fetch an icon's type.
+ *
+ * \param[in] icon Icon to query.
+ * \return The icon's type.
+ */
+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.
+ */
+const char *wuss_icon_get_text(const wuss_icon_t *icon);
+
+/**
+ * Fetch the window an icon belongs to.
+ *
+ * \param[in] icon Icon to query.
+ * \return The owning window.
+ */
+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
+}
+#endif
+
+#endif /* WUSS_ICON_H */
diff --git a/include/wuss/menu-desc.h b/include/wuss/menu-desc.h
new file mode 100644
index 00000000..d15c9a19
--- /dev/null
+++ b/include/wuss/menu-desc.h
@@ -0,0 +1,73 @@
+/* wuss/menu-desc.h -- build a wuss_menu_t tree from a descriptor string */
+
+/**
+ * \file menu-desc.h
+ *
+ * A convenience helper on top of wuss/menu.h: parse a compact
+ * PrivateEye-style descriptor string into a heap wuss_menu_t tree the caller
+ * owns, and free it again. Nothing in the core menu helper depends on this;
+ * a task that hand-assembles its wuss_menu_t arrays never needs to include it.
+ *
+ * Built only when WUSS_MENUS is defined.
+ */
+
+#ifndef WUSS_MENU_DESC_H
+#define WUSS_MENU_DESC_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include "base/result.h"
+
+#include "wuss/menu.h"
+
+/* ----------------------------------------------------------------------- */
+
+/**
+ * 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
+ * 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, "Display, Open, !Grid,
+ * ~Export, |Quit") -- "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.
+ *
+ * \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).
+ */
+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
+
+#endif /* WUSS_MENU_DESC_H */
diff --git a/include/wuss/menu.h b/include/wuss/menu.h
new file mode 100644
index 00000000..b73eddcb
--- /dev/null
+++ b/include/wuss/menu.h
@@ -0,0 +1,163 @@
+/* wuss/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"
+#include "wuss/window.h"
+
+/* ----------------------------------------------------------------------- */
+
+/** Per-item flags for a wuss_menu_item_t. OR'd together. */
+typedef enum wuss_menu_item_flags
+{
+ wuss_MENU_ITEM_NONE = 0,
+
+ /** Draw a tick at the item's left edge */
+ wuss_MENU_ITEM_TICKED = 1 << 0,
+
+ /** 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_DASHED = 1 << 1,
+
+ /** Greyed, never highlights, not selectable */
+ wuss_MENU_ITEM_DISABLED = 1 << 2,
+
+ /** Draw a colour chip of \c swatch at the item's left edge, in place of
+ * a tick */
+ wuss_MENU_ITEM_SWATCH = 1 << 3
+}
+wuss_menu_item_flags_t;
+
+/** One row of a menu. */
+typedef struct wuss_menu_item
+{
+ /** Row label; NULL is treated as "" */
+ const char *text;
+
+ /** See wuss_menu_item_flags_t */
+ wuss_menu_item_flags_t flags;
+
+ /** Non-NULL: draw a right arrow and open this menu to the right on
+ * hover */
+ const struct wuss_menu *submenu;
+
+ /** 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_window_t *window;
+
+ /** With wuss_MENU_ITEM_SWATCH: the colour chip to draw at the row's
+ * left edge, as an index into the system palette. Ignored without that
+ * flag, so a zero-initialised item is unaffected. */
+ wuss_colour_t swatch;
+}
+wuss_menu_item_t;
+
+/** A menu: an array of items the caller owns. */
+typedef struct wuss_menu
+{
+ const char *title; /**< titlebar caption; NULL treated as "" */
+ 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;
+
+/* ----------------------------------------------------------------------- */
+
+/**
+ * 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.
+ *
+ * When a leaf item is released over, a wuss_EVENT_MENU_SELECT event is
+ * delivered to \p task's handle (with window == NULL); its data.menu_select
+ * carries the (sub)menu, the item index and the release button.
+ *
+ * \param[in] task Task opening the menu; receives wuss_EVENT_MENU_SELECT.
+ * The menu windows are wuss-owned, not task's.
+ * \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[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_task_t *task,
+ const wuss_menu_t *menu,
+ point_t at,
+ 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);
+
+/**
+ * Re-tick a currently open menu level in place, for a task that keeps an
+ * ADJUST-picked menu open (see wuss_menu_open) and wants its own tick to
+ * change without rebuilding the chain: an ADJUST pick delivers
+ * wuss_EVENT_MENU_SELECT but does not close or redraw the menu, so a task
+ * that only edits its wuss_menu_item_t.flags array never sees it take effect
+ * on screen.
+ *
+ * Ticks item \p index and unticks every other item of the open level whose
+ * \c menu is \p menu (searched from \p handle's chain), then invalidates the
+ * changed rows. A no-op if \p handle is stale/closed, \p menu is not an open
+ * level of its chain, or \p index is out of range (still unticking every row
+ * in that case).
+ *
+ * \param[in] handle Chain handle from wuss_menu_open.
+ * \param[in] menu The (sub)menu level to update; matched by pointer
+ * against the description passed to wuss_menu_open or
+ * reached via a wuss_menu_item_t.submenu.
+ * \param[in] index Row to tick, or -1 to untick every row.
+ */
+void wuss_menu_set_ticked(wuss_menu_handle_t handle,
+ const wuss_menu_t *menu,
+ int index);
+
+/* ----------------------------------------------------------------------- */
+
+/* Building a wuss_menu_t tree from a compact descriptor string, and freeing
+ * it again, lives in wuss/menu-desc.h -- a convenience layer on top of this
+ * core helper. */
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WUSS_MENU_H */
diff --git a/include/wuss/task.h b/include/wuss/task.h
new file mode 100644
index 00000000..7bed6dc9
--- /dev/null
+++ b/include/wuss/task.h
@@ -0,0 +1,299 @@
+/* wuss/task.h -- wuss task API */
+
+/**
+ * \file task.h
+ *
+ * A Wuss task: a registered object that owns windows and is the single
+ * delivery target for their events, plus the events themselves.
+ *
+ * A task is a mini-instance: wuss_task_create / wuss_task_destroy rhyme with
+ * wuss_create / wuss_destroy. Every window is created against a task (see
+ * window.h), and that task's one handle callback receives all events for all
+ * its windows, as well as the app-wide IDLE / PALETTE / MENU_SELECT
+ * notifications.
+ */
+
+#ifndef WUSS_TASK_H
+#define WUSS_TASK_H
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+#include "base/result.h"
+#include "geom/box.h"
+#include "geom/point.h"
+#include "framebuf/screen.h"
+
+#include "wuss/wuss.h"
+#include "wuss/icon.h"
+
+/* ----------------------------------------------------------------------- */
+
+/**
+ * The master event kind. Every event delivered to any recipient carries one
+ * of these values; the per-recipient views below are strict subsets of it
+ * with the same integer values, so no translation happens at dispatch.
+ */
+typedef enum wuss_event_kind
+{
+ /** Part of a window's content needs repaint. */
+ wuss_EVENT_REDRAW,
+ /** Button down/up/move over window content. */
+ wuss_EVENT_MOUSE,
+ /** A work-area button icon was clicked or hovered (window view); or, in
+ * the task view, a future shared/dock element -- reserved, nothing
+ * emits it yet. */
+ wuss_EVENT_ICON,
+ /** Mouse wheel used over a window's content. */
+ wuss_EVENT_SCROLL,
+ /** A visible window moved or resized. */
+ wuss_EVENT_OPEN,
+ /** A hidden window is about to become visible; a non-OK return vetoes
+ * the reveal. */
+ wuss_EVENT_PRE_SHOW,
+ /** A window has become visible (hidden->visible transition only, not at
+ * create). */
+ wuss_EVENT_SHOW,
+ /** A window is about to close via wuss_window_try_close; a non-OK return
+ * vetoes it. Never fired by wuss_window_close. */
+ wuss_EVENT_PRE_CLOSE,
+ /** A window has closed after a successful wuss_window_try_close. Never
+ * fired by wuss_window_close. */
+ wuss_EVENT_CLOSE,
+ /** Wuss has finished its pending work; once per task per wuss_idle. */
+ wuss_EVENT_IDLE,
+ /** The task is shutting down, via wuss_task_destroy; its windows are
+ * still alive. */
+ wuss_EVENT_QUIT,
+ /** System palette changed, via wuss_set_palette; once per task. Recache
+ * any wuss_nearest_colour selections. */
+ wuss_EVENT_PALETTE,
+ /** A leaf menu item was picked; delivered to the task that opened the
+ * menu. */
+ wuss_EVENT_MENU_SELECT
+}
+wuss_event_kind_t;
+
+/**
+ * The subset of wuss_event_kind_t a window's handle callback can receive.
+ * Same integer values as the master enum; listed separately for
+ * documentation and the debug-only dispatch assert.
+ *
+ * Members: wuss_EVENT_REDRAW, wuss_EVENT_MOUSE, wuss_EVENT_ICON,
+ * wuss_EVENT_SCROLL, wuss_EVENT_OPEN, wuss_EVENT_PRE_SHOW, wuss_EVENT_SHOW,
+ * wuss_EVENT_PRE_CLOSE, wuss_EVENT_CLOSE.
+ */
+typedef wuss_event_kind_t wuss_window_event_kind_t;
+
+/**
+ * The subset of wuss_event_kind_t a task's handle callback can receive with
+ * no window (window == NULL): the app-wide notifications.
+ *
+ * Members: wuss_EVENT_IDLE, wuss_EVENT_QUIT, wuss_EVENT_PALETTE,
+ * wuss_EVENT_MENU_SELECT, wuss_EVENT_ICON (reserved for a future shared/dock
+ * element; nothing emits it yet).
+ */
+typedef wuss_event_kind_t wuss_task_event_kind_t;
+
+/**
+ * An event delivered to a handle callback. Only the union member matching \c
+ * kind is valid; several kinds carry no data.
+ */
+typedef struct wuss_event
+{
+ wuss_event_kind_t kind;
+ union
+ {
+ /** wuss_EVENT_REDRAW: called with scr->clip already set to the
+ * on-screen, clipped content area. bounds and scroll are exactly what
+ * wuss_window_get_content_bounds/wuss_window_get_scroll would return,
+ * passed through so tasks don't need to call back into Wuss on every
+ * redraw. */
+ struct
+ {
+ screen_t *scr;
+
+ /**
+ * The region that actually needs repainting, screen space; a subset of
+ * bounds. Tasks should only touch pixels within this box.
+ */
+ const box_t *content;
+
+ /**
+ * The window's full (unclipped) content-area box, screen space, as per
+ * wuss_window_get_content_bounds; for converting screen position to
+ * document position.
+ */
+ const box_t *bounds;
+
+ /** Current scroll offset, as per wuss_window_get_scroll. */
+ point_t scroll;
+ }
+ redraw;
+
+ /** wuss_EVENT_MOUSE: point is in virtual content space -- the window's
+ * scroll offset has already been added, so the task must not add it
+ * again. With a scroll offset of (0,0) this is the same as window-local
+ * content coordinates, where the content area's top-left is (0,0).
+ * button is meaningful for DOWN/UP, and is a set of wuss_button_t
+ * flags, so test it with '&' rather than comparing for equality. */
+ struct
+ {
+ wuss_mouse_action_t action;
+ point_t point;
+ wuss_button_t button;
+ }
+ mouse;
+
+ /** wuss_EVENT_ICON: delivered instead of wuss_EVENT_MOUSE while the
+ * pointer is inside a wuss_ICON_TYPE_BUTTON icon's bounding box.
+ * Label, hidden and disabled icons never raise this; those clicks
+ * fall through as wuss_EVENT_MOUSE. action is DOWN/UP/MOVE; button
+ * is a set of wuss_button_t flags, so test it with '&' rather than
+ * comparing for equality. In the task view (window == NULL) this is
+ * reserved for a future shared/dock element and is never currently
+ * emitted. */
+ struct
+ {
+ wuss_icon_t *icon;
+ wuss_mouse_action_t action;
+ wuss_button_t button;
+ }
+ icon;
+
+ /** wuss_EVENT_SCROLL: point is window-local content coordinates, as
+ * per mouse. delta's sign and units are as passed to wuss_scroll. */
+ struct
+ {
+ point_t point;
+ int delta;
+ }
+ scroll;
+
+ /** wuss_EVENT_MENU_SELECT: delivered to the task that called
+ * wuss_menu_open. menu is the (sub)menu the item belongs to; index is
+ * its position in menu->items; button is the wuss_button_t flags for the
+ * release -- ADJUST keeps the chain open, SELECT closes it; test with
+ * '&'. */
+ struct
+ {
+ const struct wuss_menu *menu;
+ int index;
+ wuss_button_t button;
+ }
+ menu_select;
+
+ /* wuss_EVENT_OPEN, wuss_EVENT_PRE_SHOW, wuss_EVENT_SHOW,
+ * wuss_EVENT_PRE_CLOSE, wuss_EVENT_CLOSE, wuss_EVENT_IDLE,
+ * wuss_EVENT_QUIT and wuss_EVENT_PALETTE carry no data. */
+ }
+ data;
+}
+wuss_event_t;
+
+/**
+ * A window's event callback: receives the wuss_window_event_kind_t subset.
+ *
+ * \param[in] window The window receiving the event.
+ * \param[in] event The event; see wuss_event_t.
+ * \param[in] task_data As passed to wuss_task_create.
+ * \return \ref result_OK on success, else an appropriate result code. For
+ * wuss_EVENT_PRE_SHOW / wuss_EVENT_PRE_CLOSE a non-OK return vetoes
+ * the transition.
+ */
+typedef result_t (wuss_window_fn_t)(wuss_window_t *window,
+ const wuss_event_t *event,
+ void *task_data);
+
+/**
+ * A task's event callback: receives the wuss_task_event_kind_t subset,
+ * always with window == NULL.
+ *
+ * \param[in] window Always NULL for task-view events; the parameter is
+ * kept so a single handle can serve both views.
+ * \param[in] event The event; see wuss_event_t.
+ * \param[in] task_data As passed to wuss_task_create.
+ * \return \ref result_OK on success, else an appropriate result code.
+ */
+typedef result_t (wuss_task_fn_t)(wuss_window_t *window,
+ const wuss_event_t *event,
+ void *task_data);
+
+/**
+ * Task creation descriptor. handle serves both the window-view and task-view
+ * events for every window the task owns.
+ */
+typedef struct wuss_task_desc
+{
+ /**
+ * Event callback, or NULL for a task that receives no events (its windows
+ * still get their background filled per wuss_window_create's bg). The one
+ * callback is invoked for both wuss_window_fn_t-shaped and
+ * wuss_task_fn_t-shaped events; the two typedefs have the same signature.
+ */
+ wuss_window_fn_t *handle;
+
+ /** Opaque pointer passed back to handle. */
+ void *task_data;
+
+ /** Optional name, for debugging/tracing; borrowed, not copied. */
+ const char *name;
+}
+wuss_task_desc_t;
+
+/**
+ * Register a task on a window manager.
+ *
+ * The task is appended to the manager's task list; app-wide notifications
+ * (wuss_idle, wuss_set_palette) are delivered to tasks in registration
+ * order.
+ *
+ * \param[in] wuss Window manager.
+ * \param[in] desc Task descriptor; copied in (name is borrowed).
+ * \param[out] task Newly registered task.
+ * \return \ref result_OK on success, \ref result_OOM if the task node could
+ * not be allocated.
+ */
+result_t wuss_task_create(wuss_t *wuss,
+ const wuss_task_desc_t *desc,
+ wuss_task_t **task);
+
+/**
+ * Unregister and free a task.
+ *
+ * Fires one wuss_EVENT_QUIT to the task's handle (its windows are still
+ * alive), then force-closes every window the task owns via wuss_window_close
+ * (no wuss_EVENT_PRE_CLOSE / wuss_EVENT_CLOSE), unlinks the task from the
+ * manager and frees it.
+ *
+ * \param[in] doomed Task to destroy. NULL is a no-op.
+ */
+void wuss_task_destroy(wuss_task_t *doomed);
+
+/**
+ * Opt a task into self-destruct once its last window closes.
+ *
+ * With autoclose set, the moment a task's window list becomes empty (whether
+ * via wuss_window_close or the wuss_window_try_close close path) the task
+ * behaves as if wuss_task_destroy had been called: one wuss_EVENT_QUIT is
+ * delivered, then the task is unlinked and freed. Free task_data from the
+ * wuss_EVENT_QUIT case, not wuss_EVENT_CLOSE -- a closing window would
+ * otherwise leave the task registered and still receiving wuss_idle /
+ * wuss_set_palette broadcasts with a stale task_data.
+ *
+ * Suited to fire-and-forget tasks that own exactly the windows they spawn.
+ * Leave it off for tasks that outlive their windows (e.g. one owning a pool
+ * of transient menu windows).
+ *
+ * \param[in] task Task to configure. NULL is a no-op.
+ * \param[in] on Non-zero to enable autoclose, zero to disable.
+ */
+void wuss_task_set_autoclose(wuss_task_t *task, int on);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* WUSS_TASK_H */
diff --git a/include/wuss/window.h b/include/wuss/window.h
index 255564d9..f251fd8f 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
@@ -18,173 +18,136 @@ extern "C"
#include "base/result.h"
#include "geom/box.h"
#include "geom/point.h"
+#include "geom/size.h"
#include "framebuf/screen.h"
+#include "wuss/task.h"
#include "wuss/wuss.h"
/* ----------------------------------------------------------------------- */
-/** 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. */
- wuss_EVENT_REDRAW, /**< Part of the window's content needs repainting. */
- wuss_EVENT_OPEN, /**< Window moved or resized. */
- wuss_EVENT_CLOSE, /**< Close icon clicked; Wuss takes no action itself. */
- wuss_EVENT_MOUSE, /**< Button down/up over the window's content. */
- wuss_EVENT_SCROLL, /**< Mouse wheel used over the window's content. */
- wuss_EVENT_QUIT /**< Task shutting down, via wuss_task_stop. */
-}
-wuss_event_kind_t;
-
-/**
- * An event delivered to a task's handle callback. Only the union member
- * matching \c kind is valid.
- */
-typedef struct wuss_event
-{
- wuss_event_kind_t kind;
- union
- {
- /** wuss_EVENT_REDRAW: called with scr->clip already set to the
- * on-screen, clipped content area. bounds and scroll are exactly what
- * wuss_window_get_content_bounds/wuss_window_get_scroll would return,
- * passed through so tasks don't need to call back into Wuss on every
- * redraw. */
- struct
- {
- screen_t *scr;
- const box_t *content; /**< The region that actually needs repainting, screen space; a subset of bounds. Tasks should only touch pixels within this box. */
- const box_t *bounds; /**< The window's full (unclipped) content-area box, screen space, as per wuss_window_get_content_bounds; for converting screen position to document position. */
- point_t scroll; /**< Current scroll offset, as per wuss_window_get_scroll. */
- }
- redraw;
-
- /** wuss_EVENT_MOUSE: point is window-local content coordinates (the
- * content area's top-left is (0,0)). button is meaningful for
- * DOWN/UP. */
- struct
- {
- wuss_mouse_action_t action;
- point_t point;
- wuss_button_t button;
- }
- mouse;
-
- /** wuss_EVENT_SCROLL: point is window-local content coordinates, as
- * per mouse. delta's sign and units are as passed to wuss_scroll. */
- struct
- {
- point_t point;
- int delta;
- }
- scroll;
-
- /* wuss_EVENT_IDLE, wuss_EVENT_CLOSE, wuss_EVENT_QUIT and
- * wuss_EVENT_OPEN carry no data. */
- }
- data;
-}
-wuss_event_t;
-
-/**
- * Task event callback.
- *
- * \param[in] window The window receiving the event.
- * \param[in] event The event; see wuss_event_t.
- * \param[in] task_data As passed to wuss_window_create.
- * \return \ref result_OK on success, else an appropriate result code.
- */
-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. */
-typedef struct wuss_task
-{
- wuss_event_fn_t *handle; /**< NULL => task receives no events; Wuss still fills the content background per bg. */
- void *task_data;
- wuss_colour_t bg; /**< Content background, filled by Wuss before redraw is called, or wuss_NO_BACKGROUND for the task to draw its own background (avoids a redundant fill behind an opaque task). */
-}
-wuss_task_t;
-
-/**
- * Build a wuss_task_t from its fields.
- *
- * \param[in] handle Event callback, or NULL for a task that receives no events.
- * \param[in] task_data Opaque pointer passed back to the callback.
- * \param[in] bg Content background, or wuss_NO_BACKGROUND.
- * \return The populated task.
- */
-wuss_task_t wuss_task_start(wuss_event_fn_t *handle,
- void *task_data,
- wuss_colour_t bg);
-
-/**
- * Notify a window's task that it is shutting down, via wuss_EVENT_QUIT.
- * Not called automatically by wuss_window_close; call it first if the
- * task needs notice before its window is torn down.
- *
- * \param[in] window Window whose task should be stopped.
- * \return \ref result_OK on success, else the result returned by the
- * task's handle callback.
- */
-result_t wuss_task_stop(wuss_window_t *window);
-
-/**
- * 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.
- *
- * \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.
- */
-result_t wuss_idle(wuss_t *wuss);
-
/**
* 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
+ * 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 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] task Content delegate. Copied in. May be NULL for a window with no content handling.
- * \param[in] doc_width Virtual document width, for the horizontal scrollbar's sausage proportion; pass content's own width for a window with nothing to scroll.
- * \param[in] doc_height Virtual document height, for the vertical scrollbar's sausage proportion; pass content's own height for a window with nothing to scroll.
- * \param[out] window Newly created window. Becomes the topmost window.
+ * \param[in] task Owning task; the window is created on task's window
+ * manager and delivers all its events to task's handle.
+ * Immutable once set.
+ * \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] 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.
+ * \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.
+ * \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
- * task->bg is out of range for the palette, or another
+ * 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,
+result_t wuss_window_create(wuss_task_t *task,
const box_t *content,
const char *title,
wuss_window_flags_t flags,
- const wuss_task_t *task,
- int doc_width,
- int doc_height,
+ wuss_backdrop_t bg,
+ size2d_t doc,
+ size2d_t min_doc,
wuss_window_t **window);
/**
- * Destroy a window.
+ * Create a window, letting Wuss choose its position.
*
- * \param[in] doomed Window to destroy.
+ * 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.
+ *
+ * \param[in] task Owning task, as wuss_window_create.
+ * \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.
+ * \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.
+ */
+result_t wuss_window_create_placed(wuss_task_t *task,
+ size2d_t size,
+ const char *title,
+ wuss_window_flags_t flags,
+ wuss_backdrop_t bg,
+ size2d_t doc,
+ size2d_t min_doc,
+ wuss_window_t **window);
+
+/**
+ * Destroy a window. The forced, unvetoable teardown: fires no
+ * wuss_EVENT_PRE_CLOSE / wuss_EVENT_CLOSE. Used by wuss_task_destroy, tests
+ * and error paths. For the user close-icon path, which the task can veto,
+ * see wuss_window_try_close.
+ *
+ * \param[in] doomed Window to destroy. NULL is a no-op.
*/
void wuss_window_close(wuss_window_t *doomed);
+/**
+ * Attempt to close a window, giving its task a chance to veto.
+ *
+ * Fires wuss_EVENT_PRE_CLOSE to the task; a non-OK return vetoes the close,
+ * the window stays open and that result is returned. Otherwise fires
+ * wuss_EVENT_CLOSE (while the window is still alive) and then destroys the
+ * window as per wuss_window_close.
+ *
+ * This is the path the titlebar close icon takes.
+ *
+ * \param[in] window Window to close. NULL is a no-op returning \ref
+ * result_OK.
+ * \return \ref result_OK if the window was closed (or was NULL), else the
+ * non-OK result the task returned from wuss_EVENT_PRE_CLOSE.
+ */
+result_t wuss_window_try_close(wuss_window_t *window);
+
/**
* Move a window, preserving its size.
*
@@ -193,16 +156,38 @@ 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.
+ *
+ * Showing a hidden window fires wuss_EVENT_PRE_SHOW to the task first: a
+ * non-OK return vetoes the reveal, the window stays hidden and that result
+ * is returned. On a successful reveal wuss_EVENT_SHOW follows. Hiding a
+ * visible window, and any call that is a no-op (already in the requested
+ * state), fires nothing and returns \ref result_OK.
+ *
+ * \param[in] window Window to show or hide.
+ * \param[in] hidden Non-zero to hide, zero to show.
+ * \return \ref result_OK on success or a no-op, else the non-OK result the
+ * task returned from wuss_EVENT_PRE_SHOW.
+ */
+result_t wuss_window_set_hidden(wuss_window_t *window, int hidden);
+
/**
* Resize a window's content area, preserving its top-left position.
*
* \param[in] window Window to resize.
- * \param[in] width New content width.
- * \param[in] height New content height.
- * \return \ref result_OK on success, \ref result_WUSS_TOO_SMALL if width or
- * height is not positive.
+ * \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.
*/
-result_t wuss_window_resize(wuss_window_t *window, int width, int height);
+result_t wuss_window_resize(wuss_window_t *window, size2d_t size);
/**
* Move a window to one end of the z-order.
@@ -236,28 +221,40 @@ void wuss_window_get_content_bounds(const wuss_window_t *window,
/**
* 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).
+ * 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] 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.
*/
void wuss_window_invalidate(wuss_window_t *window, const box_t *local_box);
/** Mark a window's whole content area as dirty. Shorthand for
* wuss_window_invalidate(window, NULL). */
-#define wuss_window_invalidate_all(window) wuss_window_invalidate((window), NULL)
+#define wuss_window_invalidate_all(window) \
+ wuss_window_invalidate((window), NULL)
+
+/**
+ * Mark a window's whole virtual document extent as dirty, not just the part
+ * currently on screen. wuss_window_invalidate_all only covers the visible
+ * content rectangle, which is enough when the document is no bigger than the
+ * window; use this when a change touches the whole document (a colour or
+ * theme swap, say) so every scroll position repaints correctly. The extent
+ * is clipped to what is actually visible before anything is queued.
+ *
+ * \param[in] window Window whose whole document changed.
+ */
+void wuss_window_invalidate_extent(wuss_window_t *window);
/**
* 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.
+ * 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.
@@ -273,17 +270,19 @@ 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 c8add3aa..b7728d65 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
@@ -16,15 +16,26 @@ extern "C"
{
#endif
+#include
+
#include "base/result.h"
#include "framebuf/bmfont.h"
+#include "framebuf/screen.h"
#include "geom/box.h"
#include "geom/point.h"
+#include "geom/size.h"
/* ----------------------------------------------------------------------- */
-#define result_WUSS_TOO_SMALL (result_BASE_WUSS + 0) /* Window/resize dimensions too small */
-#define result_WUSS_BAD_COLOUR (result_BASE_WUSS + 1) /* A palette index was out of range */
+/** Window/resize dimensions too small. */
+#define result_WUSS_TOO_SMALL (result_BASE_WUSS + 0)
+/** 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).
+ */
+#define result_WUSS_BAD_ICON (result_BASE_WUSS + 2)
/* ----------------------------------------------------------------------- */
@@ -34,15 +45,48 @@ typedef struct wuss wuss_t;
/** A window. Full API is in window.h. */
typedef struct wuss_window wuss_window_t;
+/** A registered task: owns windows, receives their events. Full API is in
+ * task.h. */
+typedef struct wuss_task wuss_task_t;
+
+/** A work-area icon. Full API is in icon.h, and is compiled only when the
+ * 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) and by the wuss shared
+ * components. \c malloc, \c realloc and \c free must behave like their C
+ * library namesakes -- 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.
+ *
+ * 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.
*/
typedef enum wuss_button
{
- wuss_BUTTON_SELECT,
- wuss_BUTTON_MENU,
- wuss_BUTTON_ADJUST
+ wuss_BUTTON_NONE = 0,
+ wuss_BUTTON_ADJUST = 1 << 0,
+ wuss_BUTTON_MENU = 1 << 1,
+ wuss_BUTTON_SELECT = 1 << 2
}
wuss_button_t;
@@ -55,47 +99,147 @@ typedef enum wuss_mouse_action
}
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;
+/**
+ * An index into a wuss_t's system palette (see wuss_create). Not a colour_t.
+ */
+typedef unsigned char wuss_colour_t;
-/** Sentinel for wuss_task_t::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)
+/**
+ * Symbolic wuss_colour_t values. A raw wuss_colour_t is a system-palette
+ * index, 0..wuss_COLOUR_SYMBOLIC-1 (so a palette may hold up to 128 real
+ * entries). Values from wuss_COLOUR_SYMBOLIC up are not palette indices but
+ * roles the window manager resolves to a concrete index against the live
+ * palette (and, for the wuss_COLOUR_*_ chrome roles, the live wuss_config):
+ * the named colours pick the nearest system-palette entry to the RGB the
+ * name implies, the chrome roles echo the matching wuss_config_t field.
+ * Accepted anywhere a wuss_colour_t is: config furniture/bevel/accent/
+ * backdrop, window backgrounds (see wuss_window_create), icon specs.
+ * wuss_NO_BACKGROUND is not symbolic and always passes through unchanged.
+ */
+#define wuss_COLOUR_SYMBOLIC ((wuss_colour_t) 128)
+
+/* Named colours: nearest system-palette entry to the named RGB. */
+#define wuss_COLOUR_BLACK (wuss_COLOUR_SYMBOLIC + 0)
+#define wuss_COLOUR_WHITE (wuss_COLOUR_SYMBOLIC + 1)
+#define wuss_COLOUR_RED (wuss_COLOUR_SYMBOLIC + 2)
+#define wuss_COLOUR_GREEN (wuss_COLOUR_SYMBOLIC + 3)
+#define wuss_COLOUR_BLUE (wuss_COLOUR_SYMBOLIC + 4)
+#define wuss_COLOUR_YELLOW (wuss_COLOUR_SYMBOLIC + 5)
+#define wuss_COLOUR_CYAN (wuss_COLOUR_SYMBOLIC + 6)
+#define wuss_COLOUR_MAGENTA (wuss_COLOUR_SYMBOLIC + 7)
+#define wuss_COLOUR_GREY (wuss_COLOUR_SYMBOLIC + 8)
+
+/* Chrome roles: echo the matching wuss_config_t field, resolved to a
+ * concrete index -- e.g. wuss_COLOUR_TITLE_BG is furniture.title.bg,
+ * wuss_COLOUR_BUTTON_HILIGHT is bevel.light, wuss_COLOUR_BUTTON_SHADOW is
+ * bevel.dark, wuss_COLOUR_BACKDROP is backdrop.colour. */
+#define wuss_COLOUR_TITLE_BG (wuss_COLOUR_SYMBOLIC + 16)
+#define wuss_COLOUR_TITLE_FG (wuss_COLOUR_SYMBOLIC + 17)
+#define wuss_COLOUR_BUTTON_HILIGHT (wuss_COLOUR_SYMBOLIC + 18)
+#define wuss_COLOUR_BUTTON_SHADOW (wuss_COLOUR_SYMBOLIC + 19)
+#define wuss_COLOUR_ACCENT_BG (wuss_COLOUR_SYMBOLIC + 20)
+#define wuss_COLOUR_ACCENT_FG (wuss_COLOUR_SYMBOLIC + 21)
+#define wuss_COLOUR_BACKDROP (wuss_COLOUR_SYMBOLIC + 22)
+
/** Furniture chrome colours, one entry per class of furniture. Title is
* the only two-tone class (fill + text); the rest are drawn as a single
- * flat colour. Each value is an index into the system palette (see
+ * 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
{
- wuss_colour_t bg; /**< Titlebar fill. */
- wuss_colour_t fg; /**< Titlebar text. */
+ wuss_colour_t bg; /**< Titlebar fill. */
+ wuss_colour_t fg; /**< Titlebar text. */
}
title;
- wuss_colour_t back; /**< Send-to-back icon. */
- wuss_colour_t close; /**< Close icon. */
- wuss_colour_t toggle; /**< Toggle-size icon. */
- wuss_colour_t resize; /**< Resize icon. */
- wuss_colour_t arrows; /**< Scrollbar arrows. */
- wuss_colour_t wells; /**< Scrollbar wells. */
- wuss_colour_t sausages; /**< Scrollbar sausages. */
+ wuss_colour_t back; /**< Send-to-back icon. */
+ wuss_colour_t close; /**< Close icon. */
+ wuss_colour_t toggle; /**< Toggle-size icon. */
+ wuss_colour_t resize; /**< Resize icon. */
+ struct
+ {
+ wuss_colour_t arrows; /**< Scrollbar arrows. */
+ wuss_colour_t wells; /**< Scrollbar wells. */
+ wuss_colour_t sausages; /**< Scrollbar sausages. */
+ }
+ scroll;
}
-wuss_palette_t;
+wuss_furniture_palette_t;
-/** Per-window appearance flags, combinable with bitwise OR. */
+/**
+ * Per-window appearance flags, combinable with bitwise OR.
+ *
+ * \note When the library is built with the WUSS_FURNITURE CMake option off,
+ * every window is chromeless regardless of these flags and the
+ * wuss_WINDOW_NO_* bits are ignored.
+ */
typedef enum wuss_window_flags
{
- wuss_WINDOW_NONE = 0, /**< Default: every furniture region drawn. */
- wuss_WINDOW_NO_TITLEBAR = 1 << 0, /**< No titlebar; content fills the full visible area, and no drag handle exists. */
- wuss_WINDOW_NO_OUTLINE = 1 << 1, /**< No 1px border drawn around the visible area. */
- wuss_WINDOW_NO_CLOSE = 1 << 2, /**< No close icon in the titlebar. Ignored if flags includes wuss_WINDOW_NO_TITLEBAR. */
- wuss_WINDOW_NO_BACK = 1 << 3, /**< No send-to-back icon in the titlebar. Ignored if flags includes wuss_WINDOW_NO_TITLEBAR. */
- wuss_WINDOW_NO_TOGGLE_SIZE = 1 << 4, /**< No toggle-size icon in the titlebar. Ignored if flags includes wuss_WINDOW_NO_TITLEBAR. */
- wuss_WINDOW_NO_VSCROLL = 1 << 5, /**< No vertical scrollbar on the right edge. */
- wuss_WINDOW_NO_HSCROLL = 1 << 6, /**< No horizontal scrollbar on the bottom edge. */
- wuss_WINDOW_NO_RESIZE = 1 << 7, /**< No resize icon in the bottom-right corner. */
- wuss_WINDOW_NO_TOGGLE_BLIT = 1 << 8 /**< 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). */
+ /** Default: every furniture region drawn. */
+ wuss_WINDOW_NONE = 0,
+
+ /**
+ * No titlebar; content fills the full visible area, and no drag handle
+ * exists.
+ */
+ wuss_WINDOW_NO_TITLEBAR = 1 << 0,
+
+ /** No 1px border drawn around the visible area. */
+ wuss_WINDOW_NO_OUTLINE = 1 << 1,
+
+ /**
+ * No close icon in the titlebar. Ignored if flags includes
+ * wuss_WINDOW_NO_TITLEBAR.
+ */
+ wuss_WINDOW_NO_CLOSE = 1 << 2,
+
+ /**
+ * No send-to-back icon in the titlebar. Ignored if flags includes
+ * wuss_WINDOW_NO_TITLEBAR.
+ */
+ wuss_WINDOW_NO_BACK = 1 << 3,
+
+ /**
+ * No toggle-size icon in the titlebar. Ignored if flags includes
+ * wuss_WINDOW_NO_TITLEBAR.
+ */
+ wuss_WINDOW_NO_TOGGLE_SIZE = 1 << 4,
+
+ /** No vertical scrollbar on the right edge. */
+ wuss_WINDOW_NO_VSCROLL = 1 << 5,
+
+ /** No horizontal scrollbar on the bottom edge. */
+ wuss_WINDOW_NO_HSCROLL = 1 << 6,
+
+ /** No resize icon in the bottom-right corner. */
+ wuss_WINDOW_NO_RESIZE = 1 << 7,
+
+ /**
+ * 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).
+ */
+ 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;
@@ -108,33 +252,218 @@ typedef enum wuss_zorder
}
wuss_zorder_t;
-/** Optional creation-time configuration. */
+/**
+ * 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 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
{
- int titlebar_height; /**< Titlebar height in pixels, or 0 to derive from font metrics (or a built-in fallback if no font). */
- wuss_palette_t palette; /**< Furniture chrome colours. */
+ /**
+ * 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;
+
+ /** Furniture chrome colours. Ignored when WUSS_FURNITURE is off. */
+ wuss_furniture_palette_t furniture;
+
+ /**
+ * 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
+ {
+ wuss_colour_t light; /**< Top/left bevel edge. */
+ wuss_colour_t dark; /**< Bottom/right bevel edge. */
+ }
+ 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;
}
wuss_config_t;
+/** Most fonts wuss_create will take, and the range an icon's font-select
+ * flags can name (see wuss_ICON_FONT). */
+#define wuss_MAX_FONTS 4
+
+/**
+ * What a font passed to wuss_create is for. Lets a shared component such as
+ * a font-picker menu tell a decorative/chrome font apart from one meant to
+ * be offered as a user-selectable text face.
+ */
+typedef enum wuss_font_class
+{
+ wuss_FONT_CLASS_NONE, /**< An ordinary text font. */
+
+ /**
+ * Chrome/decoration only, e.g. the symbol font menu ticks and submenu
+ * arrows are drawn from (see \ref WUSS_SYMBOL_FONT). Not meant to be
+ * offered as a text face.
+ */
+ wuss_FONT_CLASS_SYSTEM
+}
+wuss_font_class_t;
+
+/**
+ * One font slot passed to wuss_create: the font itself, what it is for, and
+ * the name a caller-side picker (e.g. wuss_fontmenu) should know it by.
+ */
+typedef struct wuss_font_desc
+{
+ /** Font handle. Not owned; must outlive the wuss_t. */
+ bmfont_t *font;
+
+ wuss_font_class_t font_class; /**< What the font is for. */
+
+ /**
+ * Borrowed; the font's leafname sans ".png", for a picker to match
+ * against. NULL if the slot has no name (e.g. a NONE-class font that no
+ * picker needs to skip).
+ */
+ const char *name;
+}
+wuss_font_desc_t;
+
/**
* Create a window manager.
*
- * \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 default palette.
- * \param[in] npalette Number of entries in palette. Ignored if palette is NULL.
+ * \param[in] scr Screen to draw windows onto. Not owned; must outlive
+ * the wuss_t.
+ * \param[in] fonts Up to \ref wuss_MAX_FONTS font slots, copied into the
+ * wuss_t (the descriptors, not the fonts -- the fonts
+ * are not owned and must outlive it). Slot 0 is the
+ * system font, used for titlebar labels and any icon
+ * that does not select another. NULL, or nfonts 0,
+ * leaves titlebars unlabelled.
+ * \param[in] nfonts Number of entries in \p fonts, 0..\ref
+ * wuss_MAX_FONTS; more than that is an error.
+ * \param[in] palette System palette, copied in, or NULL to use a built-in
+ * default palette.
+ * \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 appropriate result code.
+ * \return \ref result_OK on success, \ref result_BAD_ARG if \p nfonts is
+ * negative or exceeds \ref wuss_MAX_FONTS, \ref
+ * result_WUSS_BAD_COLOUR if any of config's palette entries are out
+ * of range for the palette, or another appropriate result code.
+ */
+result_t wuss_create(screen_t *scr,
+ const wuss_font_desc_t *fonts,
+ int nfonts,
+ const colour_t *palette,
+ int npalette,
+ const wuss_config_t *config,
+ 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 once to
+ * every registered task (in registration order, window == NULL) 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
+ * (iteration still continues past it).
+ */
+result_t wuss_set_palette(wuss_t *wuss,
+ const colour_t *palette,
+ int npalette);
+
+/**
+ * Replace the desktop backdrop partway through a session.
+ *
+ * Validates \p backdrop against the current palette (as wuss_create does its
+ * config->backdrop), copies it in over the existing one, then invalidates
+ * the whole screen so the next wuss_redraw / wuss_redraw_dirty repaints it
+ * behind every window. Tasks are not notified. The caller is still
+ * responsible for the next redraw.
+ *
+ * \param[in] wuss Window manager.
+ * \param[in] backdrop New backdrop, copied in. Set its colour to
+ * wuss_NO_BACKGROUND for no backdrop.
+ * \return \ref result_OK on success, \ref result_WUSS_BAD_COLOUR if a colour
+ * index in \p backdrop is out of range (the backdrop is left
+ * unchanged).
*/
-result_t wuss_create(screen_t *scr,
- bmfont_t *font,
- const colour_t *palette,
- int npalette,
- const wuss_config_t *config,
- wuss_t **wuss);
+result_t wuss_set_backdrop(wuss_t *wuss, const wuss_backdrop_t *backdrop);
/**
* Destroy a window manager, and any windows still open on it.
@@ -144,21 +473,88 @@ result_t wuss_create(screen_t *scr,
void wuss_destroy(wuss_t *doomed);
/**
- * Redraw every window, back-to-front.
+ * Fetch the system font (see wuss_create), for tasks to draw their own
+ * content in the same face as window titlebars. Equivalent to
+ * wuss_get_font_n(wuss, 0).
*
* \param[in] wuss Window manager.
- * \return \ref result_OK on success, or the last non-OK result returned by
- * a task's redraw callback (drawing continues past a failing
- * window rather than stopping).
+ * \return System font, or NULL if none was given to wuss_create.
+ */
+bmfont_t *wuss_get_font(const wuss_t *wuss);
+
+/**
+ * Fetch one of the fonts passed to wuss_create by slot.
+ *
+ * \param[in] wuss Window manager.
+ * \param[in] index Font slot, 0..\ref wuss_MAX_FONTS - 1.
+ * \return The font in that slot, or NULL if the slot is out of range or was
+ * not filled.
+ */
+bmfont_t *wuss_get_font_n(const wuss_t *wuss, int index);
+
+/**
+ * Fetch the class of one of the fonts passed to wuss_create by slot (see
+ * \ref wuss_font_desc_t).
+ *
+ * \param[in] wuss Window manager.
+ * \param[in] index Font slot, 0..\ref wuss_MAX_FONTS - 1.
+ * \return The slot's class, or \ref wuss_FONT_CLASS_NONE if the slot is out
+ * of range or was not filled.
+ */
+wuss_font_class_t wuss_get_font_class_n(const wuss_t *wuss, int index);
+
+/**
+ * Fetch the name of one of the fonts passed to wuss_create by slot (see \ref
+ * wuss_font_desc_t).
+ *
+ * \param[in] wuss Window manager.
+ * \param[in] index Font slot, 0..\ref wuss_MAX_FONTS - 1.
+ * \return The slot's name (borrowed, valid until wuss_destroy), or NULL if
+ * the slot is out of range, was not filled, or was given no name.
+ */
+const char *wuss_get_font_name_n(const wuss_t *wuss, int index);
+
+/**
+ * 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);
+
+/**
+ * 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.
+ *
+ * \param[in] wuss Window manager.
+ * \return \ref result_OK on success, or the last non-OK result returned by a
+ * task's redraw callback (drawing continues past a failing window
+ * rather than stopping).
*/
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.
+ * 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.
@@ -170,11 +566,12 @@ 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.
+ * 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.
- * \return \ref result_OK on success, or the last non-OK result returned by
- * a task's redraw callback.
+ * \return \ref result_OK on success, or the last non-OK result returned by a
+ * task's redraw callback.
*/
result_t wuss_redraw_dirty(wuss_t *wuss);
@@ -191,14 +588,15 @@ 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). 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);
@@ -206,56 +604,75 @@ 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.
+ * 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.
* \param[in] button Button pressed or released.
* \param[in] action wuss_MOUSE_DOWN or wuss_MOUSE_UP.
- * \param[out] hit Window under the pointer (or being dragged), or NULL if none. May be NULL if not needed.
- * \return \ref result_OK, or a result code returned by the task's mouse callback.
+ * \param[out] hit Window under the pointer (or being dragged), or NULL if
+ * none. May be NULL if not needed.
+ * \return \ref result_OK, or a result code returned by the task's mouse
+ * callback.
*/
-result_t wuss_mouse_click(wuss_t *wuss,
- point_t p,
- wuss_button_t button,
- wuss_mouse_action_t action,
- wuss_window_t **hit);
+result_t wuss_mouse_click(wuss_t *wuss,
+ point_t p,
+ wuss_button_t button,
+ wuss_mouse_action_t action,
+ 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.
+ * 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.
- * \param[out] hit Window under the pointer (or being dragged), or NULL if none. May be NULL if not needed.
- * \return \ref result_OK, or a result code returned by the task's mouse callback.
+ * \param[out] hit Window under the pointer (or being dragged), or NULL if
+ * none. May be NULL if not needed.
+ * \return \ref result_OK, or a result code returned by the task's mouse
+ * callback.
*/
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.
+ * 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.
- * \return \ref result_OK, or a result code returned by the task's scroll callback.
+ * \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.
*/
result_t wuss_scroll(wuss_t *wuss,
point_t p,
int delta,
wuss_window_t **hit);
+/**
+ * Broadcast a wuss_EVENT_IDLE event once to every registered task, in
+ * registration order (window == NULL). Intended to be called once per
+ * main-loop iteration, after other pending input has been handled, so tasks
+ * can drive their own animation/timers -- iterating their own window lists
+ * -- without the caller stepping each one individually.
+ *
+ * \param[in] wuss Window manager whose tasks should go idle.
+ * \return \ref result_OK on success, else the first non-OK result returned
+ * by a task's handle callback (iteration still continues past it).
+ */
+result_t wuss_idle(wuss_t *wuss);
+
#ifdef __cplusplus
}
#endif
diff --git a/libraries/base/result/result-string.c b/libraries/base/result/result-string.c
new file mode 100644
index 00000000..a24343b2
--- /dev/null
+++ b/libraries/base/result/result-string.c
@@ -0,0 +1,84 @@
+/* result-string.c -- map result_t codes to human-readable strings */
+
+#include "base/result.h"
+
+#include "databases/filename-db.h"
+#include "databases/pickle.h"
+#include "databases/tag-db.h"
+#include "datastruct/atom.h"
+#include "datastruct/bitfifo.h"
+#include "datastruct/hash.h"
+#include "geom/layout.h"
+#include "geom/packer.h"
+#include "io/stream.h"
+#include "wuss/wuss.h"
+
+const char *result_string(result_t err)
+{
+ switch (err)
+ {
+ case result_OK: return "OK";
+ case result_OOM: return "Out of memory";
+ case result_FILE_NOT_FOUND: return "File not found";
+ case result_BAD_ARG: return "Bad argument";
+ case result_BUFFER_OVERFLOW: return "Buffer overflow";
+ case result_STOP_WALK: return "Walk cancelled";
+ case result_PARSE_ERROR: return "Parse error";
+ case result_TOO_BIG: return "Value too big";
+ case result_NOT_IMPLEMENTED: return "Not implemented";
+ case result_NOT_FOUND: return "Not found";
+ case result_EXISTS: return "Already exists";
+ case result_CLASHES: return "Key clashes with existing one";
+ case result_NULL_ARG: return "NULL argument";
+ case result_NOT_SUPPORTED: return "Not supported";
+ case result_INCOMPATIBLE: return "Incompatible argument";
+ case result_FOPEN_FAILED: return "fopen() failed";
+
+ case result_STREAM_BAD_SEEK: return "Stream: bad seek";
+ case result_STREAM_CANT_SEEK: return "Stream: cannot seek";
+ case result_STREAM_UNKNOWN_OP: return "Stream: unknown operation";
+
+ case result_ATOM_SET_EMPTY: return "Atom: set is empty";
+ case result_ATOM_NAME_EXISTS: return "Atom: name already exists";
+ case result_ATOM_OUT_OF_RANGE: return "Atom: index out of range";
+
+ case result_HASH_END: return "Hash: end of iteration";
+ case result_HASH_BAD_CONT: return "Hash: invalid continuation value";
+
+ case result_PICKLE_END: return "Pickle: end of data";
+ case result_PICKLE_SKIP: return "Pickle: skip entry";
+ case result_PICKLE_INCOMPATIBLE: return "Pickle: incompatible format";
+ case result_PICKLE_COULDNT_OPEN_FILE: return "Pickle: could not open file";
+ case result_PICKLE_SYNTAX_ERROR: return "Pickle: syntax error";
+
+ case result_TAGDB_INCOMPATIBLE: return "TagDB: incompatible format";
+ case result_TAGDB_COULDNT_OPEN_FILE: return "TagDB: could not open file";
+ case result_TAGDB_SYNTAX_ERROR: return "TagDB: syntax error";
+ case result_TAGDB_UNKNOWN_ID: return "TagDB: unknown id";
+ case result_TAGDB_BUFF_OVERFLOW: return "TagDB: buffer overflow";
+ case result_TAGDB_UNKNOWN_TAG: return "TagDB: unknown tag";
+
+ case result_FILENAMEDB_INCOMPATIBLE: return "FilenameDB: incompatible format";
+ case result_FILENAMEDB_COULDNT_OPEN_FILE: return "FilenameDB: could not open file";
+ case result_FILENAMEDB_SYNTAX_ERROR: return "FilenameDB: syntax error";
+ case result_FILENAMEDB_BUFF_OVERFLOW: return "FilenameDB: buffer overflow";
+
+ case result_TEST_PASSED: return "Test passed";
+ case result_TEST_FAILED: return "Test failed";
+
+ case result_PACKER_DIDNT_FIT: return "Packer: did not fit";
+ case result_PACKER_EMPTY: return "Packer: empty";
+
+ case result_LAYOUT_BUFFER_FULL: return "Layout: buffer full";
+
+ case result_BITFIFO_EMPTY: return "BitFIFO: empty";
+ case result_BITFIFO_FULL: return "BitFIFO: full";
+ case result_BITFIFO_INSUFFICIENT: return "BitFIFO: insufficient bits";
+
+ case result_WUSS_TOO_SMALL: return "Wuss: dimensions too small";
+ case result_WUSS_BAD_COLOUR: return "Wuss: palette index out of range";
+ case result_WUSS_BAD_ICON: return "Wuss: malformed icon spec";
+
+ default: return "Unknown error";
+ }
+}
diff --git a/libraries/databases/digest-db/digest-db.c b/libraries/databases/digest-db/digest-db.c
index c09e0e9e..ed22e84b 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
#include
diff --git a/libraries/databases/filename-db/filename-db.c b/libraries/databases/filename-db/filename-db.c
index abe5202c..9ba41f73 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 a517f4ef..e4553874 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
#include
diff --git a/libraries/databases/pickle/hash-reader.c b/libraries/databases/pickle/hash-reader.c
index d878c83c..e1ee74b5 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
#include
diff --git a/libraries/databases/pickle/hash-writer.c b/libraries/databases/pickle/hash-writer.c
index 2949dcfb..3b892f71 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
#include
diff --git a/libraries/databases/pickle/pickle.c b/libraries/databases/pickle/pickle.c
index 55bcf06b..eadb172f 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
#include
diff --git a/libraries/databases/pickle/test/pickle-test.c b/libraries/databases/pickle/test/pickle-test.c
index 5e15e8a8..8b38f707 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
#include
@@ -40,7 +41,10 @@ static char *my_strdup(const char *s)
/* ----------------------------------------------------------------------- */
-static result_t test1_format_key(const void *key, char *buf, size_t len, void *opaque)
+static result_t test1_format_key(const void *key,
+ char *buf,
+ size_t len,
+ void *opaque)
{
NOT_USED(len);
NOT_USED(opaque);
@@ -50,7 +54,10 @@ static result_t test1_format_key(const void *key, char *buf, size_t len, void *o
return result_OK;
}
-static result_t test1_format_value(const void *key, char *buf, size_t len, void *opaque)
+static result_t test1_format_value(const void *key,
+ char *buf,
+ size_t len,
+ void *opaque)
{
NOT_USED(len);
NOT_USED(opaque);
@@ -320,7 +327,10 @@ cheese_value_t;
/* ----------------------------------------------------------------------- */
-static result_t cheese_format_key(const void *vkey, char *buf, size_t len, void *opaque)
+static result_t cheese_format_key(const void *vkey,
+ char *buf,
+ size_t len,
+ void *opaque)
{
const cheese_key_t *key = vkey;
@@ -332,13 +342,16 @@ static result_t cheese_format_key(const void *vkey, char *buf, size_t len, void
return result_OK;
}
-static result_t cheese_format_value(const void *vvalue, char *buf, size_t len, void *opaque)
+static result_t cheese_format_value(const void *vvalue,
+ char *buf,
+ size_t len,
+ void *opaque)
{
const cheese_value_t *value = vvalue;
NOT_USED(opaque);
- sprintf(buf,
+ snprintf(buf, len,
"%s %s %s %s %s %d",
cheese_country_to_string(value->country),
cheese_region_to_string(value->region),
diff --git a/libraries/databases/pickle/unpickle.c b/libraries/databases/pickle/unpickle.c
index fce030be..a0c2be20 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
#include
diff --git a/libraries/databases/tag-db/tag-db.c b/libraries/databases/tag-db/tag-db.c
index 5397b361..95865d17 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)
@@ -461,8 +461,7 @@ result_t tagdb_add(tagdb_t *db, const unsigned char *name, tagdb_tag_t *ptag)
void *newarr;
n = power2gt(db->c_allocated);
- if (n < 8)
- n = 8;
+ n = MAX(n, 8);
newarr = realloc(db->counts, n * sizeof(*db->counts));
if (newarr == NULL)
diff --git a/libraries/databases/tag-db/test/tag-db-test.c b/libraries/databases/tag-db/test/tag-db-test.c
index 076b1191..8e7a777f 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
#include
diff --git a/libraries/datastruct/atom/create.c b/libraries/datastruct/atom/create.c
index 90d51b6b..1a61b68c 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
diff --git a/libraries/datastruct/atom/delete-block.c b/libraries/datastruct/atom/delete-block.c
index 1a9ca722..6ededecc 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
diff --git a/libraries/datastruct/atom/delete.c b/libraries/datastruct/atom/delete.c
index a7be93f9..a6735de5 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
#include
diff --git a/libraries/datastruct/atom/destroy.c b/libraries/datastruct/atom/destroy.c
index 3dcf6470..493587c4 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
diff --git a/libraries/datastruct/atom/for-block.c b/libraries/datastruct/atom/for-block.c
index c67db912..c8f27f42 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
#include
diff --git a/libraries/datastruct/atom/get.c b/libraries/datastruct/atom/get.c
index ef63a7f0..6b423075 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
#include
diff --git a/libraries/datastruct/atom/impl.h b/libraries/datastruct/atom/impl.h
index 35c7e13c..12373308 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 58663d69..f8a3b23a 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
#include
@@ -8,6 +8,7 @@
#include "fortify/fortify.h"
#endif
+#include "base/utils.h"
#include "utils/array.h"
#include "utils/barith.h"
@@ -53,8 +54,7 @@ result_t atom_ensure_loc_space(atom_set_t *s)
newallocated = s->l_used + 1;
/* start with at least this many entries */
- if (newallocated < LOCPTRMINSZ)
- newallocated = LOCPTRMINSZ;
+ newallocated = MAX(newallocated, LOCPTRMINSZ);
/* subtract 1 to make it greater than or equal */
newallocated = power2gt(newallocated - 1);
@@ -120,8 +120,7 @@ result_t atom_ensure_blk_space(atom_set_t *s, size_t length)
newallocated = s->b_used + 1;
/* start with at least this many entries */
- if (newallocated < BLKPTRMINSZ)
- newallocated = BLKPTRMINSZ;
+ newallocated = MAX(newallocated, BLKPTRMINSZ);
/* subtract 1 to make it greater than or equal */
newallocated = power2gt(newallocated - 1);
diff --git a/libraries/datastruct/atom/set.c b/libraries/datastruct/atom/set.c
index 0b702451..68d1a8d9 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
#include
diff --git a/libraries/datastruct/atom/test/atom-test.c b/libraries/datastruct/atom/test/atom-test.c
index 0fbabed5..78743dea 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
#include
diff --git a/libraries/datastruct/bitarr/count.c b/libraries/datastruct/bitarr/count.c
index 475562c9..b3a89652 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
diff --git a/libraries/datastruct/bitarr/test/bitarr-test.c b/libraries/datastruct/bitarr/test/bitarr-test.c
index 91a566bb..e8403c05 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
#include
diff --git a/libraries/datastruct/bitfifo/bitfifo.c b/libraries/datastruct/bitfifo/bitfifo.c
index cd0e6b13..ba215515 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
#include
diff --git a/libraries/datastruct/bitfifo/test/bitfifo-test.c b/libraries/datastruct/bitfifo/test/bitfifo-test.c
index d46bf447..d346539e 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
#include
diff --git a/libraries/datastruct/bitvec/and.c b/libraries/datastruct/bitvec/and.c
index b291e995..d6eceab4 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
diff --git a/libraries/datastruct/bitvec/clear-all.c b/libraries/datastruct/bitvec/clear-all.c
index f17259ed..8b24e7ea 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
diff --git a/libraries/datastruct/bitvec/clear.c b/libraries/datastruct/bitvec/clear.c
index 166b232a..982ac63b 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 51e548aa..6834dc26 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 56f2b454..459797c7 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
#include
diff --git a/libraries/datastruct/bitvec/destroy.c b/libraries/datastruct/bitvec/destroy.c
index ece3d5c3..70a1eb09 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
diff --git a/libraries/datastruct/bitvec/ensure.c b/libraries/datastruct/bitvec/ensure.c
index 62adab48..02f74c0c 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
#include
diff --git a/libraries/datastruct/bitvec/eq.c b/libraries/datastruct/bitvec/eq.c
index b7e4d500..3e852c1d 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
diff --git a/libraries/datastruct/bitvec/get.c b/libraries/datastruct/bitvec/get.c
index e2b270ef..a548b47c 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 e94f1fb6..7e312de6 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 401d6748..3c484190 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
diff --git a/libraries/datastruct/bitvec/next.c b/libraries/datastruct/bitvec/next.c
index 67c540b3..0bb2d992 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 5724ef37..cc06de1e 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
diff --git a/libraries/datastruct/bitvec/set-all.c b/libraries/datastruct/bitvec/set-all.c
index d913afa0..368aa710 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
diff --git a/libraries/datastruct/bitvec/set.c b/libraries/datastruct/bitvec/set.c
index 2cefd9b4..58c4a699 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 97165452..fb708dd3 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
#include
diff --git a/libraries/datastruct/bitvec/toggle.c b/libraries/datastruct/bitvec/toggle.c
index 94a29bc4..4105b9bb 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 77a08af3..d911b8be 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
#include
@@ -7,6 +7,7 @@
#include
#include
+#include "base/utils.h"
#include "utils/barith.h"
#include "datastruct/cache.h"
@@ -521,8 +522,7 @@ static void purge(cache_t *c, entry_t *purgeent, int entbin)
}
/* firstfree points to the lowest block */
- if (free < c->firstfree)
- c->firstfree = free;
+ c->firstfree = MIN(c->firstfree, free);
}
cache_check(c, 0);
@@ -573,11 +573,11 @@ static entry_t *evict(cache_t *c)
return evictee;
}
-result_t cache_put(cache_t *c,
- cachekey_t key,
- void *data,
- size_t length,
- void **inserted)
+result_t cache_put(cache_t *c,
+ cachekey_t key,
+ void *data,
+ size_t length,
+ void **inserted)
{
const size_t quantum = sizeof(free_t);
diff --git a/libraries/datastruct/cache/test/cache-test.c b/libraries/datastruct/cache/test/cache-test.c
index 4125faa8..e4fbaaa8 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
#include
@@ -183,7 +184,7 @@ static result_t cache_test_put(cache_t *cache, int maxkey)
for (i = 0; i < maxkey; i++)
{
- sprintf(data, "(%d)", i);
+ snprintf(data, sizeof(data), "(%d)", i);
cache_put(cache, i, data, strlen(data) + 1, NULL);
}
@@ -197,7 +198,7 @@ static result_t cache_test_put(cache_t *cache, int maxkey)
{
char *cached;
- int len = sprintf(data, "(%d)", i);
+ int len = snprintf(data, sizeof(data), "(%d)", i);
cached = (char *) cache_get(cache, i);
if (cached)
{
@@ -215,7 +216,7 @@ static result_t cache_test_put(cache_t *cache, int maxkey)
for (i = 0; i < maxkey; i++)
{
- sprintf(data, "(%d)", i);
+ snprintf(data, sizeof(data), "(%d)", i);
cache_put(cache, i, data, strlen(data) + 1, NULL);
}
diff --git a/libraries/datastruct/hash/count.c b/libraries/datastruct/hash/count.c
index 0d24af29..02fbe318 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 f5db0315..eb2387f4 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
#include
@@ -74,13 +74,13 @@ void hash_no_destroy_value(void *string)
/* ----------------------------------------------------------------------- */
-result_t hash_create(const void *default_value,
- int nbins,
- hash_fn_t *fn,
- hash_compare_t *compare,
- hash_destroy_key_t *destroy_key,
- hash_destroy_value_t *destroy_value,
- hash_t **ph)
+result_t hash_create(const void *default_value,
+ int nbins,
+ hash_fn_t *fn,
+ hash_compare_t *compare,
+ hash_destroy_key_t *destroy_key,
+ hash_destroy_value_t *destroy_value,
+ hash_t **ph)
{
hash_t *h;
hash_node_t **bins;
diff --git a/libraries/datastruct/hash/destroy.c b/libraries/datastruct/hash/destroy.c
index 8aa8252a..2c06ca98 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
diff --git a/libraries/datastruct/hash/impl.h b/libraries/datastruct/hash/impl.h
index 808a7901..9403e832 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 df661758..0644aee1 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
diff --git a/libraries/datastruct/hash/lookup-node.c b/libraries/datastruct/hash/lookup-node.c
index 6b6c4357..42ee1445 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
diff --git a/libraries/datastruct/hash/lookup.c b/libraries/datastruct/hash/lookup.c
index 8f665037..3671c38b 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
diff --git a/libraries/datastruct/hash/remove.c b/libraries/datastruct/hash/remove.c
index a3a1ce8a..8222d6ae 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
diff --git a/libraries/datastruct/hash/test/hash-test.c b/libraries/datastruct/hash/test/hash-test.c
index 7d807b29..c896d5d3 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
#include
diff --git a/libraries/datastruct/hash/walk-cont.c b/libraries/datastruct/hash/walk-cont.c
index 4fa822fd..db3b44c6 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
#include
diff --git a/libraries/datastruct/hash/walk.c b/libraries/datastruct/hash/walk.c
index 6388c627..d876d441 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
diff --git a/libraries/datastruct/hlist/append.c b/libraries/datastruct/hlist/append.c
index 601242b5..3bcfbd50 100644
--- a/libraries/datastruct/hlist/append.c
+++ b/libraries/datastruct/hlist/append.c
@@ -1,3 +1,4 @@
+/* datastruct/hlist/append.c */
#include
#include
diff --git a/libraries/datastruct/hlist/copy.c b/libraries/datastruct/hlist/copy.c
index 49fdd4cc..12240d40 100644
--- a/libraries/datastruct/hlist/copy.c
+++ b/libraries/datastruct/hlist/copy.c
@@ -1,3 +1,4 @@
+/* datastruct/hlist/copy.c */
#include
#include
diff --git a/libraries/datastruct/hlist/free.c b/libraries/datastruct/hlist/free.c
index 5823b503..3e8dfc38 100644
--- a/libraries/datastruct/hlist/free.c
+++ b/libraries/datastruct/hlist/free.c
@@ -1,3 +1,4 @@
+/* datastruct/hlist/free.c */
#include
#include
diff --git a/libraries/datastruct/hlist/impl.h b/libraries/datastruct/hlist/impl.h
index 32b30fa7..b3e533d8 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 b52c1615..41535767 100644
--- a/libraries/datastruct/hlist/length.c
+++ b/libraries/datastruct/hlist/length.c
@@ -1,3 +1,4 @@
+/* datastruct/hlist/length.c */
#include
#include
diff --git a/libraries/datastruct/hlist/list.c b/libraries/datastruct/hlist/list.c
index a8178dff..07cd0ffb 100644
--- a/libraries/datastruct/hlist/list.c
+++ b/libraries/datastruct/hlist/list.c
@@ -1,3 +1,4 @@
+/* datastruct/hlist/list.c */
#include
#include
diff --git a/libraries/datastruct/hlist/map.c b/libraries/datastruct/hlist/map.c
index 183567b0..d0a3acd7 100644
--- a/libraries/datastruct/hlist/map.c
+++ b/libraries/datastruct/hlist/map.c
@@ -1,3 +1,4 @@
+/* datastruct/hlist/map.c */
#include
#include
diff --git a/libraries/datastruct/hlist/pop.c b/libraries/datastruct/hlist/pop.c
index 03a09025..9d3d3e4a 100644
--- a/libraries/datastruct/hlist/pop.c
+++ b/libraries/datastruct/hlist/pop.c
@@ -1,3 +1,4 @@
+/* datastruct/hlist/pop.c */
#include
#include
diff --git a/libraries/datastruct/hlist/push.c b/libraries/datastruct/hlist/push.c
index fefe1cd2..2e5add1b 100644
--- a/libraries/datastruct/hlist/push.c
+++ b/libraries/datastruct/hlist/push.c
@@ -1,3 +1,4 @@
+/* datastruct/hlist/push.c */
#include
#include
diff --git a/libraries/datastruct/hlist/reverse.c b/libraries/datastruct/hlist/reverse.c
index 49467f1f..31647c87 100644
--- a/libraries/datastruct/hlist/reverse.c
+++ b/libraries/datastruct/hlist/reverse.c
@@ -1,3 +1,4 @@
+/* datastruct/hlist/reverse.c */
#include
#include
diff --git a/libraries/datastruct/hlist/to-array.c b/libraries/datastruct/hlist/to-array.c
index 26da4b1f..be907e24 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
#include
diff --git a/libraries/datastruct/list/add-head.c b/libraries/datastruct/list/add-head.c
index f7e1d6ba..59b9f6c7 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 bb602371..b500ba57 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 7db71f05..e283cb87 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 c9cfcac3..d91c25df 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 532a7223..bf73e24f 100644
--- a/libraries/datastruct/list/remove.c
+++ b/libraries/datastruct/list/remove.c
@@ -1,3 +1,4 @@
+/* datastruct/list/remove.c */
#include
diff --git a/libraries/datastruct/list/test/list-test.c b/libraries/datastruct/list/test/list-test.c
index a6e959cd..ed2964ec 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
diff --git a/libraries/datastruct/list/walk.c b/libraries/datastruct/list/walk.c
index 37c7ed9a..bbbee8c2 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 6214491d..3870dad9 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
diff --git a/libraries/datastruct/ntree/delete.c b/libraries/datastruct/ntree/delete.c
index 8730160b..3c640ad7 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
diff --git a/libraries/datastruct/ntree/depth.c b/libraries/datastruct/ntree/depth.c
index e64d4e37..491e4d2f 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 8bcbf63a..2e896803 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
diff --git a/libraries/datastruct/ntree/free.c b/libraries/datastruct/ntree/free.c
index 06b345eb..06b0323c 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
#include
diff --git a/libraries/datastruct/ntree/get-data.c b/libraries/datastruct/ntree/get-data.c
index 07647d9e..f842bdeb 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 dec9802e..21c6ecc8 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 6ef8f7fb..94d78899 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
#include
diff --git a/libraries/datastruct/ntree/insert-before.c b/libraries/datastruct/ntree/insert-before.c
index ae44f4aa..0a2ccc49 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
#include
@@ -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/datastruct/ntree/insert.c b/libraries/datastruct/ntree/insert.c
index 26e90199..2bb87f1d 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
diff --git a/libraries/datastruct/ntree/last-child.c b/libraries/datastruct/ntree/last-child.c
index 5dd9a037..800eac4f 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 6735c76c..ea5343d1 100644
--- a/libraries/datastruct/ntree/max-height.c
+++ b/libraries/datastruct/ntree/max-height.c
@@ -1,5 +1,6 @@
-/* max-height.c -- n-ary tree */
+/* datastruct/ntree/max-height.c -- n-ary tree */
+#include "base/utils.h"
#include "datastruct/ntree.h"
#include "impl.h"
@@ -18,9 +19,8 @@ int ntree_max_height(ntree_t *t)
{
int h;
- h = ntree_max_height(child);
- if (h > max)
- max = h;
+ h = ntree_max_height(child);
+ max = MAX(max, h);
}
return max + 1;
diff --git a/libraries/datastruct/ntree/n-nodes.c b/libraries/datastruct/ntree/n-nodes.c
index 76a18124..80189c9a 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 24aeb2b4..fa1dd1ad 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
diff --git a/libraries/datastruct/ntree/next-sibling.c b/libraries/datastruct/ntree/next-sibling.c
index 57d3d73e..617c234f 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
diff --git a/libraries/datastruct/ntree/nth-child.c b/libraries/datastruct/ntree/nth-child.c
index feef026f..cf11b395 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 a947a76d..ed756cf2 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
diff --git a/libraries/datastruct/ntree/prepend.c b/libraries/datastruct/ntree/prepend.c
index 42d0a912..e5820a9a 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
diff --git a/libraries/datastruct/ntree/prev-sibling.c b/libraries/datastruct/ntree/prev-sibling.c
index 48526b5a..04866359 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
diff --git a/libraries/datastruct/ntree/set-data.c b/libraries/datastruct/ntree/set-data.c
index 611b418c..533a6696 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 63279894..9aa5417b 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
#include
@@ -119,10 +120,10 @@ static result_t concat(ntree_t *t, void *opaque)
return result_OK;
}
-static result_t tree_to_string(ntree_t *t,
- ntree_walk_flags_t flags,
- int depth,
- char *buf)
+static result_t tree_to_string(ntree_t *t,
+ ntree_walk_flags_t flags,
+ int depth,
+ char *buf)
{
result_t err;
concat_data_t concat_data;
@@ -137,9 +138,9 @@ static result_t tree_to_string(ntree_t *t,
return err;
}
-static result_t walk_test(ntree_t *t,
- ntree_walk_flags_t flags,
- const char *expected[])
+static result_t walk_test(ntree_t *t,
+ ntree_walk_flags_t flags,
+ const char *expected[])
{
result_t err = result_OK;
int i;
diff --git a/libraries/datastruct/ntree/unlink.c b/libraries/datastruct/ntree/unlink.c
index c9df723c..94838fd6 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
#include
diff --git a/libraries/datastruct/ntree/walk.c b/libraries/datastruct/ntree/walk.c
index e9e4e219..22320720 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
#include
diff --git a/libraries/datastruct/vector/clear.c b/libraries/datastruct/vector/clear.c
index 0345e94a..da5096a7 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
diff --git a/libraries/datastruct/vector/create.c b/libraries/datastruct/vector/create.c
index bdc03a38..ab861228 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
diff --git a/libraries/datastruct/vector/destroy.c b/libraries/datastruct/vector/destroy.c
index b4fce1a0..1cf7a36f 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
diff --git a/libraries/datastruct/vector/ensure.c b/libraries/datastruct/vector/ensure.c
index 29bcf67b..39b44e80 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
diff --git a/libraries/datastruct/vector/get.c b/libraries/datastruct/vector/get.c
index 817ddc1b..b6d995a9 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
#include
diff --git a/libraries/datastruct/vector/impl.h b/libraries/datastruct/vector/impl.h
index 7ffc5935..143a0fcb 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 f171d2e8..5a09f628 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
#include
diff --git a/libraries/datastruct/vector/length.c b/libraries/datastruct/vector/length.c
index 87e33c8d..29a17a21 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
diff --git a/libraries/datastruct/vector/set-length.c b/libraries/datastruct/vector/set-length.c
index 3e9a0d84..4d20b92d 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
diff --git a/libraries/datastruct/vector/set-width.c b/libraries/datastruct/vector/set-width.c
index 7a222e9e..26d4fd61 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
diff --git a/libraries/datastruct/vector/set.c b/libraries/datastruct/vector/set.c
index 7f5e5c94..0a2a39db 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
#include
diff --git a/libraries/datastruct/vector/test/vector-test.c b/libraries/datastruct/vector/test/vector-test.c
index 2b7ec064..48fba96c 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
diff --git a/libraries/datastruct/vector/width.c b/libraries/datastruct/vector/width.c
index 24930510..4d3081b5 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
diff --git a/libraries/framebuf/bitmap/bitmap.c b/libraries/framebuf/bitmap/bitmap.c
index 1d6354a9..139d70ae 100644
--- a/libraries/framebuf/bitmap/bitmap.c
+++ b/libraries/framebuf/bitmap/bitmap.c
@@ -1,4 +1,4 @@
-/* bitmap.c */
+/* framebuf/bitmap/bitmap.c */
#include
#include
@@ -9,44 +9,58 @@
#include "framebuf/span-registry.h"
result_t bitmap_init(bitmap_t *bm,
- int width,
- int height,
+ size2d_t size,
pixelfmt_t fmt,
int rowbytes,
const colour_t *palette,
void *base)
{
- int log2bpp;
-
assert(bm);
- bm->width = width;
- bm->height = height;
+ bm->size = size;
bm->format = fmt;
bm->rowbytes = rowbytes;
bm->palette = NULL;
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;
}
@@ -77,7 +91,7 @@ void bitmap_clear(bitmap_t *bm, colour_t colour)
case 2: px *= 0x11; break;
case 3: px *= 0x01; break;
}
- memset(bm->base, px, bm->rowbytes * bm->height);
+ memset(bm->base, px, bm->rowbytes * bm->size.h);
break;
case 5: /* 32bpp - pixels are ints */
@@ -87,18 +101,18 @@ void bitmap_clear(bitmap_t *bm, colour_t colour)
pixelfmt_any32_t tmp2 = tmp1 ^ (tmp1 >> 8);
if (tmp2 == 0)
{
- memset(bm->base, px, bm->rowbytes * bm->height);
+ memset(bm->base, px, bm->rowbytes * bm->size.h);
}
else
{
pixelfmt_any32_t *pixels;
pixels = bm->base;
- for (y = 0; y < bm->height; y++)
+ for (y = 0; y < bm->size.h; y++)
{
- for (x = 0; x < bm->width; x++)
+ for (x = 0; x < bm->size.w; x++)
*pixels++ = px;
- pixels += bm->rowbytes / sizeof(*pixels) - bm->width;
+ pixels += bm->rowbytes / sizeof(*pixels) - bm->size.w;
}
}
}
@@ -126,7 +140,7 @@ static result_t bmconv_p4_to_bgrx8888(const bitmap_t *src, bitmap_t **pdst)
for (i = 0; i < 16; i++)
map[i] = colour_to_pixel(src->palette, 16, src->palette[i], pixelfmt_bgrx8888);
- outpixels = malloc(src->width * sizeof(pixelfmt_bgrx8888_t) * src->height); // rowbytes rounding needed?
+ outpixels = malloc(src->size.w * sizeof(pixelfmt_bgrx8888_t) * src->size.h); // rowbytes rounding needed?
if (outpixels == NULL)
return result_OOM;
@@ -138,18 +152,18 @@ static result_t bmconv_p4_to_bgrx8888(const bitmap_t *src, bitmap_t **pdst)
}
rc = bitmap_init(dst,
- src->width, src->height,
+ src->size,
pixelfmt_bgrx8888,
- src->width * sizeof(pixelfmt_bgrx8888_t),
+ src->size.w * sizeof(pixelfmt_bgrx8888_t),
NULL,
outpixels);
if (rc)
return rc;
inpixels = src->base;
- for (y = 0; y < src->height; y++)
+ for (y = 0; y < src->size.h; y++)
{
- for (x = 0; x < src->width / 8; x++)
+ for (x = 0; x < src->size.w / 8; x++)
{
pixelfmt_p4_t in = *inpixels++; // fetches 8 pixels
// 0xABCDEFGH is 8 4bpp pixels shown H,G,F,E,D,C,B,A
@@ -169,7 +183,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/bitmap/fill-pattern.c b/libraries/framebuf/bitmap/fill-pattern.c
new file mode 100644
index 00000000..f30ea2ea
--- /dev/null
+++ b/libraries/framebuf/bitmap/fill-pattern.c
@@ -0,0 +1,101 @@
+/* framebuf/bitmap/fill-pattern.c -- fill a bitmap rectangle with an 8x8 pattern */
+
+#include
+#include
+#include
+
+#include "geom/box.h"
+
+#include "framebuf/pattern.h"
+
+#include "framebuf/bitmap.h"
+
+result_t bitmap_fill_pattern(bitmap_t *bm,
+ const box_t *area,
+ const pattern_t *pattern)
+{
+ int log2bpp;
+ int stencil;
+ pixelfmt_any_t fg;
+ pixelfmt_any_t bg;
+ box_t full;
+ box_t clip;
+ int xphase, yphase;
+ int x, y;
+
+ assert(bm);
+ assert(pattern);
+
+ log2bpp = pixelfmt_log2bpp(bm->format);
+ if (log2bpp != 3 && log2bpp != 5)
+ return result_NOT_SUPPORTED;
+
+ full.x0 = 0;
+ full.y0 = 0;
+ full.x1 = bm->size.w;
+ full.y1 = bm->size.h;
+
+ if (area == NULL)
+ {
+ clip = full;
+ }
+ else if (!box_intersection(area, &full, &clip))
+ {
+ return result_OK; /* nothing to fill */
+ }
+
+ stencil = (pattern->flags & pattern_FLAG_STENCIL) != 0;
+
+ fg = colour_to_pixel(bm->palette,
+ bm->palette ? 1 << (1 << log2bpp) : 0,
+ pattern->fg,
+ bm->format);
+ bg = colour_to_pixel(bm->palette,
+ bm->palette ? 1 << (1 << log2bpp) : 0,
+ pattern->bg,
+ bm->format);
+
+ /* The tile phase: the coordinate in "origin" is the one that maps to the
+ * pattern's leftmost/topmost bit. */
+ xphase = pattern->origin.x;
+ yphase = pattern->origin.y;
+
+ if (log2bpp == 3) /* 8bpp */
+ {
+ uint8_t *base;
+ uint8_t *row;
+
+ base = bm->base;
+ for (y = clip.y0; y < clip.y1; y++)
+ {
+ uint8_t bits = pattern->bits[(y - yphase) & 7];
+
+ row = base + (size_t) y * bm->rowbytes;
+ for (x = clip.x0; x < clip.x1; x++)
+ if (bits & (0x80 >> ((x - xphase) & 7)))
+ row[x] = (uint8_t) fg;
+ else if (!stencil)
+ row[x] = (uint8_t) bg;
+ }
+ }
+ else /* 32bpp */
+ {
+ uint8_t *base;
+ pixelfmt_any32_t *row;
+
+ base = bm->base;
+ for (y = clip.y0; y < clip.y1; y++)
+ {
+ uint8_t bits = pattern->bits[(y - yphase) & 7];
+
+ row = (pixelfmt_any32_t *) (base + (size_t) y * bm->rowbytes);
+ for (x = clip.x0; x < clip.x1; x++)
+ if (bits & (0x80 >> ((x - xphase) & 7)))
+ row[x] = fg;
+ else if (!stencil)
+ row[x] = bg;
+ }
+ }
+
+ return result_OK;
+}
diff --git a/libraries/framebuf/bitmap/load.c b/libraries/framebuf/bitmap/load.c
index 8177e37f..85f3be70 100644
--- a/libraries/framebuf/bitmap/load.c
+++ b/libraries/framebuf/bitmap/load.c
@@ -1,4 +1,4 @@
-/* load.c */
+/* framebuf/bitmap/load.c */
#include
#include
@@ -124,8 +124,7 @@ result_t bitmap_load_png(bitmap_t *bm, const char *filename)
png_read_image(png_ptr, row_pointers);
- bitmap_init(bm,
- pngwidth, pngheight,
+ bitmap_init(bm, SIZE2D(pngwidth, pngheight),
bm_fmt,
bm_rowbytes,
NULL, /* no palette */
diff --git a/libraries/framebuf/bitmap/save.c b/libraries/framebuf/bitmap/save.c
index 9b627c14..4de7ee23 100755
--- a/libraries/framebuf/bitmap/save.c
+++ b/libraries/framebuf/bitmap/save.c
@@ -1,4 +1,4 @@
-/* save.c */
+/* framebuf/bitmap/save.c */
#include
#include
@@ -67,7 +67,7 @@ result_t bitmap_save_png(const bitmap_t *bm, const char *filename)
png_init_io(png_ptr, fp);
png_set_IHDR(png_ptr, info_ptr,
- bm->width, bm->height,
+ bm->size.w, bm->size.h,
8,
fmt,
PNG_INTERLACE_NONE,
@@ -81,7 +81,7 @@ result_t bitmap_save_png(const bitmap_t *bm, const char *filename)
// png_set_filler(png_ptr, 0, PNG_FILLER_AFTER);
// png_set_packing for <8bpp images
- outrow = malloc(bytespp * bm->width * sizeof(png_byte));
+ outrow = malloc(bytespp * bm->size.w * sizeof(png_byte));
if (outrow == NULL)
{
rc = result_OOM;
@@ -90,14 +90,14 @@ result_t bitmap_save_png(const bitmap_t *bm, const char *filename)
inrow = bm->base;
- for (y = 0; y < bm->height; y++)
+ for (y = 0; y < bm->size.h; y++)
{
png_bytep pout = &outrow[0];
switch (bm->format)
{
case pixelfmt_bgrx8888:
- for (x = 0; x < bm->width; x++)
+ for (x = 0; x < bm->size.w; x++)
{
pixelfmt_xxxa8888_t in = *inrow++;
*pout++ = PIXELFMT_xxRx8888(in);
@@ -107,7 +107,7 @@ result_t bitmap_save_png(const bitmap_t *bm, const char *filename)
break;
case pixelfmt_bgra8888:
- for (x = 0; x < bm->width; x++)
+ for (x = 0; x < bm->size.w; x++)
{
pixelfmt_xxxa8888_t in = *inrow++;
*pout++ = PIXELFMT_xxRx8888(in);
diff --git a/libraries/framebuf/bmfont/bmfont.c b/libraries/framebuf/bmfont/bmfont.c
index 16ff74da..7a16ce51 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
*
@@ -17,6 +17,7 @@
#include
+#include "base/debug.h"
#include "base/utils.h"
#include "framebuf/bmfont.h"
#include "utils/array.h"
@@ -29,8 +30,6 @@
/* Input font PNGs must have 32 characters per row */
#define CHARS_PER_ROW (32)
-#undef BMFONT_DEBUG
-
/* -------------------------------------------------------------------------- */
/* Pixel values */
@@ -92,16 +91,85 @@ static int count_adw(unsigned char tab[256], pixelfmt_any_t adw_px)
tab[(adw_px >> 24) & 0xFF];
}
+/* -------------------------------------------------------------------------- */
+
+/** True if row \p y of the 2bpp image holds at least one pixel of \p value.
+ * Pixels are packed four to a byte, most significant pair first. */
+static int row_has_pixel(const unsigned char *pixels,
+ size_t rowbytes,
+ png_uint_32 y,
+ png_uint_32 width,
+ int value)
+{
+ const unsigned char *row = pixels + rowbytes * y;
+ png_uint_32 x;
+
+ for (x = 0; x < width; x++)
+ {
+ int px = (row[x >> 2] >> (6 - 2 * (x & 3))) & 3;
+ if (px == value)
+ return 1;
+ }
+
+ return 0;
+}
+
+/**
+ * Detect the grid cell height from the decoded pixels.
+ *
+ * The only unknown in the format is the cell height: 32 cells per row, each
+ * an advance-width strip row on top of the glyph rows. A strip row carries
+ * PIXEL_WIDTH_IDX pixels and no glyph ink (PIXEL_FG_IDX); the glyph rows
+ * carry no PIXEL_WIDTH_IDX. A candidate height is correct iff it divides the
+ * image and every row it implies matches its role. Returns 0 if nothing fits.
+ */
+static int detect_gridheight(const unsigned char *pixels,
+ size_t rowbytes,
+ png_uint_32 width,
+ png_uint_32 height)
+{
+ int gridwidth = (int) (width / CHARS_PER_ROW);
+ int gh;
+
+ /* a cell is at least two rows (one strip, one glyph) and no taller than
+ * a generous multiple of its width */
+ for (gh = 2; gh <= gridwidth * 3 && (png_uint_32) gh <= height; gh++)
+ {
+ png_uint_32 y;
+ int ok = 1;
+
+ if ((height % (png_uint_32) gh) != 0)
+ continue;
+
+ for (y = 0; y < height && ok; y++)
+ {
+ int has_adw = row_has_pixel(pixels, rowbytes, y, width, PIXEL_WIDTH_IDX);
+ int has_ink = row_has_pixel(pixels, rowbytes, y, width, PIXEL_FG_IDX);
+
+ if ((y % (png_uint_32) gh) == 0)
+ ok = has_adw && !has_ink; /* strip row */
+ else
+ ok = !has_adw; /* glyph row */
+ }
+
+ if (ok)
+ return gh;
+ }
+
+ return 0;
+}
+
/** Verify the font format and build the advance width table. */
-static result_t extract_advance_widths(bmfont_t *bmfont,
- void *voidpixels,
- png_uint_32 imgwidth,
- png_uint_32 imgheight,
- size_t rowbytes)
+static result_t extract_advance_widths(bmfont_t *bmfont,
+ void *voidpixels,
+ png_uint_32 imgwidth,
+ png_uint_32 imgheight,
+ size_t rowbytes)
{
result_t rc = result_OK;
unsigned char adwtab[256];
unsigned int *pixels;
+ unsigned int *pixels_end;
int bitsperchar;
unsigned int mask;
png_uint_32 y;
@@ -115,6 +183,7 @@ static result_t extract_advance_widths(bmfont_t *bmfont,
build_adw_tab(adwtab, PIXEL_WIDTH_IDX);
pixels = (unsigned int *) voidpixels;
+ pixels_end = pixels + (rowbytes * imgheight) / sizeof(*pixels);
bitsperchar = bmfont->charwidth * 2; /* 2 because 2bpp */
mask = 0xFFFFFFFFu << (32 - bitsperchar);
@@ -131,6 +200,8 @@ static result_t extract_advance_widths(bmfont_t *bmfont,
/* maintain two words, a current and a pending so we've always got enough
* bits ready */
+ if (pixels >= pixels_end)
+ return result_PARSE_ERROR; /* malformed grid: row starts past the buffer */
currbits = rev_l(*pixels++);
ncurrbits = 32; /* bits available in currbits */
nextbits = 0;
@@ -144,7 +215,9 @@ static result_t extract_advance_widths(bmfont_t *bmfont,
if (nnextbits == 0) /* refill if needed */
{
- nextbits = rev_l(*pixels++); // TODO: Check for end of buffer
+ if (pixels >= pixels_end)
+ return result_PARSE_ERROR; /* malformed grid: row overruns the buffer */
+ nextbits = rev_l(*pixels++);
nnextbits = 32;
}
@@ -158,7 +231,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,
@@ -174,8 +247,11 @@ static result_t extract_advance_widths(bmfont_t *bmfont,
pixels += ((bmfont->gridheight - 1) * rowbytes) / sizeof(*pixels);
- assert(pixels >= (unsigned int *) voidpixels);
- assert(pixels <= (unsigned int *) voidpixels + (rowbytes * imgheight / 4));
+ /* a malformed grid can walk pixels outside the decoded image; refuse it
+ * rather than read past the buffer on the next row */
+ if (pixels < (unsigned int *) voidpixels ||
+ pixels > (unsigned int *) voidpixels + (rowbytes * imgheight / 4))
+ return result_PARSE_ERROR;
}
return rc;
@@ -198,11 +274,11 @@ static void build_repack_tab(unsigned char tab[256], int idx)
((((i >> 6) & 3) == idx) << 3);
}
-static result_t extract_glyphs(bmfont_t *bmfont,
- void *voidpixels,
- png_uint_32 imgwidth,
- png_uint_32 imgheight,
- size_t rowbytes)
+static result_t extract_glyphs(bmfont_t *bmfont,
+ void *voidpixels,
+ png_uint_32 imgwidth,
+ png_uint_32 imgheight,
+ size_t rowbytes)
{
result_t rc = result_OK;
unsigned char repacktab[256];
@@ -361,12 +437,14 @@ result_t bmfont_create(const char *png, bmfont_t **pbmfont)
fp = fopen(png, "rb");
if (fp == NULL)
{
+ logf_error("bmfont_create: cannot open \"%s\"", png);
rc = result_FILE_NOT_FOUND;
goto cleanup;
}
if (!file_is_png(fp))
{
+ logf_error("bmfont_create: \"%s\" is not a PNG", png);
rc = result_BAD_ARG;
goto cleanup;
}
@@ -397,67 +475,19 @@ result_t bmfont_create(const char *png, bmfont_t **pbmfont)
png_get_IHDR(png_ptr, info_ptr,
&pngwidth, &pngheight, &pngbitdepth, &pngcolourtype,
NULL, NULL, NULL);
-#ifdef BMFONT_DEBUG
- fprintf(stderr, "bmfont load png: w=%d h=%d bit_depth=%d colour_type=%d\n",
- (int) width, (int) height, bit_depth, colour_type);
-#endif
+ logf_info("bmfont load png: w=%d h=%d bit_depth=%d colour_type=%d",
+ (int) pngwidth, (int) pngheight, pngbitdepth, pngcolourtype);
/* we need a 2bpp paletted PNG */
if (pngbitdepth != 2 || pngcolourtype != PNG_COLOR_TYPE_PALETTE)
{
-#ifdef BMFONT_DEBUG
- fprintf(stderr, "Incompatible PNG format: depth=%d, coltype=%d\n",
- bit_depth, colour_type);
-#endif
+ logf_error("bmfont: incompatible PNG format: depth=%d, coltype=%d",
+ pngbitdepth, pngcolourtype);
rc = result_INCOMPATIBLE;
goto cleanup;
}
- /* work out the font size */
-
- int gridwidth = pngwidth / CHARS_PER_ROW;
- int gridheight = 0; /* to be determined */
-
- {
- int h;
-
- /* starting with gridwidth, increment until we find a height which evenly divides */
- for (h = gridwidth; h < gridwidth * 3; h++)
- if ((pngheight % h) == 0)
- {
- gridheight = h;
- break;
- }
-
- if (gridheight == 0)
- {
-#ifdef BMFONT_DEBUG
- fprintf(stderr, "Can't determine font height\n");
-#endif
- rc = result_BAD_ARG;
- goto cleanup;
- }
- }
-
- bmfont = calloc(1, sizeof(*bmfont));
- if (bmfont == NULL)
- {
- rc = result_OOM;
- goto cleanup;
- }
-
- bmfont->gridwidth = gridwidth; /* width of glyphs in grid */
- bmfont->gridheight = gridheight; /* height of glyphs in grid */
- bmfont->charwidth = gridwidth; /* currently same as gridwidth */
- bmfont->charheight = gridheight - 1; /* -1 to account for advance width pixel row */
- bmfont->totalchars = CHARS_PER_ROW * pngheight / gridheight;
- bmfont->glyphrowbytes = (gridwidth + 7) / 8; /* byte width of stored glyphs (1 or 2) */
-
-#ifdef BMFONT_DEBUG
- fprintf(stderr, "bmfont load png: charwidth=%d charheight=%d totalchars=%d glyphrowbytes=%d\n",
- bmfont->charwidth, bmfont->charheight,
- bmfont->totalchars, bmfont->glyphrowbytes);
-#endif
+ /* decode the image; the size detector needs the pixels */
{
size_t pngrowbytes;
@@ -477,8 +507,44 @@ result_t bmfont_create(const char *png, bmfont_t **pbmfont)
row_pointers[h] = pixels + pngrowbytes * h;
png_read_image(png_ptr, row_pointers);
+ }
+
+ /* work out the font size */
- rc = extract_advance_widths(bmfont, pixels, pngwidth, pngheight, pngrowbytes);
+ {
+ size_t pngrowbytes = png_get_rowbytes(png_ptr, info_ptr);
+ int gridwidth = pngwidth / CHARS_PER_ROW;
+ int gridheight = detect_gridheight(pixels, pngrowbytes,
+ pngwidth, pngheight);
+
+ if (gridheight == 0)
+ {
+ logf_error("bmfont: can't determine font height");
+ rc = result_BAD_ARG;
+ goto cleanup;
+ }
+
+ bmfont = calloc(1, sizeof(*bmfont));
+ if (bmfont == NULL)
+ {
+ rc = result_OOM;
+ goto cleanup;
+ }
+
+ bmfont->gridwidth = gridwidth; /* width of glyphs in grid */
+ bmfont->gridheight = gridheight; /* height of glyphs in grid */
+ bmfont->charwidth = gridwidth; /* currently same as gridwidth */
+ bmfont->charheight = gridheight - 1; /* -1 for the advance width row */
+ bmfont->totalchars = CHARS_PER_ROW * pngheight / gridheight;
+ bmfont->glyphrowbytes = (gridwidth + 7) / 8; /* stored glyph row: 1 or 2 */
+
+ logf_info("bmfont load png: charwidth=%d charheight=%d totalchars=%d "
+ "glyphrowbytes=%d",
+ bmfont->charwidth, bmfont->charheight,
+ bmfont->totalchars, bmfont->glyphrowbytes);
+
+ rc = extract_advance_widths(bmfont, pixels, pngwidth, pngheight,
+ pngrowbytes);
if (rc)
goto cleanup;
@@ -515,8 +581,15 @@ void bmfont_destroy(bmfont_t *bmfont)
void bmfont_get_info(bmfont_t *bmfont, int *width, int *height)
{
- *width = bmfont->charwidth;
- *height = bmfont->charheight;
+ if (width)
+ *width = bmfont->charwidth;
+ if (height)
+ *height = bmfont->charheight;
+}
+
+int bmfont_get_count(bmfont_t *bmfont)
+{
+ return bmfont->totalchars;
}
result_t bmfont_measure(bmfont_t *bmfont,
@@ -1241,7 +1314,7 @@ result_t bmfont_draw(bmfont_t *bmfont,
}
}
- *end_pos = (point_t) { x, pos->y };
+ *end_pos = POINT(x, pos->y);
}
return result_OK;
diff --git a/libraries/framebuf/bmfont/enumerate.c b/libraries/framebuf/bmfont/enumerate.c
new file mode 100644
index 00000000..dd34f3a2
--- /dev/null
+++ b/libraries/framebuf/bmfont/enumerate.c
@@ -0,0 +1,208 @@
+/* framebuf/bmfont/enumerate.c -- list the bitmap fonts in a directory */
+
+#include
+#include
+#include
+
+#include "base/result.h"
+#include "framebuf/bmfont.h"
+
+/* ----------------------------------------------------------------------- */
+
+#define BMFONT_EXT ".png"
+#define BMFONT_EXT_LEN 4
+
+#ifdef TARGET_RISCOS
+
+/* SharedCLibrary (-mlibscl) has no support, so on RISC OS we
+ * enumerate via OSLib's OS_GBPB 9 wrapper instead of opendir/readdir. */
+
+#include "oslib/osgbpb.h"
+#include "oslib/os.h"
+
+result_t bmfont_enumerate(const char *dir,
+ bmfont_enumerate_fn *fn,
+ void *opaque)
+{
+ result_t rc;
+ os_error *err;
+ int context;
+ int read;
+ char buffer[256];
+ const char *leaf;
+ size_t leaflen;
+ char name[256];
+ char path[512];
+
+ if (dir == NULL || fn == NULL)
+ return result_NULL_ARG;
+
+ rc = result_OK;
+ context = 0;
+
+ for (;;)
+ {
+ err = xosgbpb_dir_entries(dir,
+ (osgbpb_string_list *) buffer,
+ 1,
+ context,
+ sizeof(buffer),
+ "*",
+ &read,
+ &context);
+ if (err != NULL)
+ {
+ rc = result_FILE_NOT_FOUND;
+ break;
+ }
+
+ if (read > 0)
+ {
+ leaf = buffer;
+ leaflen = strlen(leaf);
+
+ if (leaflen > BMFONT_EXT_LEN &&
+ leaflen - BMFONT_EXT_LEN < sizeof(name) &&
+ strcmp(leaf + leaflen - BMFONT_EXT_LEN, BMFONT_EXT) == 0)
+ {
+ memcpy(name, leaf, leaflen - BMFONT_EXT_LEN);
+ name[leaflen - BMFONT_EXT_LEN] = '\0';
+
+ snprintf(path, sizeof(path), "%s.%s", dir, leaf);
+
+ rc = fn(name, path, opaque);
+ if (rc == result_STOP_WALK)
+ {
+ rc = result_OK;
+ break;
+ }
+ if (rc != result_OK)
+ break;
+ }
+ }
+
+ if (context == -1)
+ break;
+ }
+
+ return rc;
+}
+
+#elif defined(_MSC_VER) /* !TARGET_RISCOS */
+
+/* MSVC has no ; enumerate via the Win32 FindFirstFile family
+ * instead of opendir/readdir. */
+
+#include
+
+result_t bmfont_enumerate(const char *dir,
+ bmfont_enumerate_fn *fn,
+ void *opaque)
+{
+ result_t rc;
+ WIN32_FIND_DATAA fd;
+ HANDLE h;
+ char pattern[512];
+ const char *leaf;
+ size_t leaflen;
+ char name[256];
+ char path[512];
+
+ if (dir == NULL || fn == NULL)
+ return result_NULL_ARG;
+
+ snprintf(pattern, sizeof(pattern), "%s\\*", dir);
+
+ h = FindFirstFileA(pattern, &fd);
+ if (h == INVALID_HANDLE_VALUE)
+ return result_FILE_NOT_FOUND;
+
+ rc = result_OK;
+
+ do
+ {
+ leaf = fd.cFileName;
+ leaflen = strlen(leaf);
+
+ if (leaflen <= BMFONT_EXT_LEN || leaflen - BMFONT_EXT_LEN >= sizeof(name))
+ continue;
+ if (strcmp(leaf + leaflen - BMFONT_EXT_LEN, BMFONT_EXT) != 0)
+ continue;
+
+ memcpy(name, leaf, leaflen - BMFONT_EXT_LEN);
+ name[leaflen - BMFONT_EXT_LEN] = '\0';
+
+ snprintf(path, sizeof(path), "%s/%s", dir, leaf);
+
+ rc = fn(name, path, opaque);
+ if (rc == result_STOP_WALK)
+ {
+ rc = result_OK;
+ break;
+ }
+ if (rc != result_OK)
+ break;
+ }
+ while (FindNextFileA(h, &fd));
+
+ FindClose(h);
+
+ return rc;
+}
+
+#else /* !TARGET_RISCOS, !_MSC_VER */
+
+#include
+
+result_t bmfont_enumerate(const char *dir,
+ bmfont_enumerate_fn *fn,
+ void *opaque)
+{
+ result_t rc;
+ DIR *dp;
+ struct dirent *de;
+ const char *leaf;
+ size_t leaflen;
+ char name[256];
+ char path[512];
+
+ if (dir == NULL || fn == NULL)
+ return result_NULL_ARG;
+
+ dp = opendir(dir);
+ if (dp == NULL)
+ return result_FILE_NOT_FOUND;
+
+ rc = result_OK;
+
+ while ((de = readdir(dp)) != NULL)
+ {
+ leaf = de->d_name;
+ leaflen = strlen(leaf);
+
+ if (leaflen <= BMFONT_EXT_LEN || leaflen - BMFONT_EXT_LEN >= sizeof(name))
+ continue;
+ if (strcmp(leaf + leaflen - BMFONT_EXT_LEN, BMFONT_EXT) != 0)
+ continue;
+
+ memcpy(name, leaf, leaflen - BMFONT_EXT_LEN);
+ name[leaflen - BMFONT_EXT_LEN] = '\0';
+
+ snprintf(path, sizeof(path), "%s/%s", dir, leaf);
+
+ rc = fn(name, path, opaque);
+ if (rc == result_STOP_WALK)
+ {
+ rc = result_OK;
+ break;
+ }
+ if (rc != result_OK)
+ break;
+ }
+
+ closedir(dp);
+
+ return rc;
+}
+
+#endif /* TARGET_RISCOS */
diff --git a/libraries/framebuf/bmfont/test/bmfont-test.c b/libraries/framebuf/bmfont/test/bmfont-test.c
index f1f2f6a9..e937c354 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
#include
@@ -183,17 +183,28 @@ bmtestline_t;
/* ----------------------------------------------------------------------- */
-#define MAXFONTS 7
+#define MAXFONTS 8
static bmtestfont_t bmfonts[MAXFONTS] =
{
- { "daydream-font", NULL },
- { "gliderrider-font", NULL },
- { "tiny-font", NULL },
- { "henry-font", NULL },
- { "tall-font", NULL },
- { "ms-sans-serif", NULL },
- { "digits-font", NULL }
+ { "Daydream", NULL },
+ { "GliderRider", NULL },
+ { "Tiny", NULL },
+ { "Henry", NULL },
+ { "CookeTall", NULL },
+ { "MS Sans Serif", NULL },
+ { "Digits-Regular", NULL },
+ { "Digits-Bold", NULL }
+};
+
+/* Symbols.png isn't Latin text, so it's excluded from bmfonts[] above (used
+ * to draw lorem_ipsum in the clipping/layout tests) but must still show up
+ * in the enumerate test, which just walks the fixture directory. */
+#define MAXFONTS_ENUM 9
+
+static const char *bmfonts_enum_extra[MAXFONTS_ENUM - MAXFONTS] =
+{
+ "Symbols"
};
/* ----------------------------------------------------------------------- */
@@ -715,6 +726,124 @@ static result_t bmfont_interactive_test(bmfontteststate_t *state)
return result_TEST_PASSED;
}
+/* ----------------------------------------------------------------------- */
+
+typedef struct bmfont_enum_check
+{
+ int found[MAXFONTS]; /* parallel to bmfonts[]; set when that name is seen */
+ int found_extra[MAXFONTS_ENUM - MAXFONTS]; /* parallel to bmfonts_enum_extra[] */
+ int total; /* every callback, including unrecognised names */
+ int stop_after; /* >0: return result_STOP_WALK once total reaches it */
+}
+bmfont_enum_check_t;
+
+static result_t bmfont_enum_cb(const char *name,
+ const char *path,
+ void *opaque)
+{
+ bmfont_enum_check_t *chk = opaque;
+ int i;
+
+ assert(name);
+ assert(path);
+
+ chk->total++;
+
+ for (i = 0; i < MAXFONTS; i++)
+ if (strcmp(name, bmfonts[i].filename) == 0)
+ chk->found[i] = 1;
+
+ for (i = 0; i < MAXFONTS_ENUM - MAXFONTS; i++)
+ if (strcmp(name, bmfonts_enum_extra[i]) == 0)
+ chk->found_extra[i] = 1;
+
+ if (chk->stop_after > 0 && chk->total >= chk->stop_after)
+ return result_STOP_WALK;
+
+ return result_OK;
+}
+
+static result_t bmfont_enumerate_test(const char *resources)
+{
+ const char *dir;
+ bmfont_enum_check_t chk;
+ result_t rc;
+ int i;
+
+ dir = path_join_filename(resources, 2, "resources", "bmfonts");
+
+ /* full walk: every fixture font is reported exactly once */
+ memset(&chk, 0, sizeof(chk));
+ rc = bmfont_enumerate(dir, bmfont_enum_cb, &chk);
+ if (rc != result_OK)
+ {
+ fprintf(stderr, "bmfont_enumerate: unexpected rc %x\n", rc);
+ return result_TEST_FAILED;
+ }
+ if (chk.total != MAXFONTS_ENUM)
+ {
+ fprintf(stderr, "bmfont_enumerate: saw %d entries, expected %d\n",
+ chk.total, MAXFONTS_ENUM);
+ return result_TEST_FAILED;
+ }
+ for (i = 0; i < MAXFONTS_ENUM - MAXFONTS; i++)
+ {
+ if (!chk.found_extra[i])
+ {
+ fprintf(stderr, "bmfont_enumerate: missing font %s\n",
+ bmfonts_enum_extra[i]);
+ return result_TEST_FAILED;
+ }
+ }
+ for (i = 0; i < MAXFONTS; i++)
+ {
+ if (!chk.found[i])
+ {
+ fprintf(stderr, "bmfont_enumerate: missing font %s\n",
+ bmfonts[i].filename);
+ return result_TEST_FAILED;
+ }
+ }
+
+ /* stop-walk: callback returns result_STOP_WALK, enumerate still returns OK */
+ memset(&chk, 0, sizeof(chk));
+ chk.stop_after = 3;
+ rc = bmfont_enumerate(dir, bmfont_enum_cb, &chk);
+ if (rc != result_OK)
+ {
+ fprintf(stderr, "bmfont_enumerate stop-walk: rc %x\n", rc);
+ return result_TEST_FAILED;
+ }
+ if (chk.total != 3)
+ {
+ fprintf(stderr, "bmfont_enumerate stop-walk: ran %d times, expected 3\n",
+ chk.total);
+ return result_TEST_FAILED;
+ }
+
+ /* missing directory */
+ memset(&chk, 0, sizeof(chk));
+ rc = bmfont_enumerate("no/such/dir/here", bmfont_enum_cb, &chk);
+ if (rc != result_FILE_NOT_FOUND)
+ {
+ fprintf(stderr, "bmfont_enumerate bad dir: rc %x, expected %x\n",
+ rc, result_FILE_NOT_FOUND);
+ return result_TEST_FAILED;
+ }
+
+ /* NULL arguments */
+ if (bmfont_enumerate(NULL, bmfont_enum_cb, &chk) != result_NULL_ARG ||
+ bmfont_enumerate(dir, NULL, &chk) != result_NULL_ARG)
+ {
+ fprintf(stderr, "bmfont_enumerate: NULL arg not rejected\n");
+ return result_TEST_FAILED;
+ }
+
+ return result_TEST_PASSED;
+}
+
+/* ----------------------------------------------------------------------- */
+
result_t bmfont_test_one_format(const char *resources,
int scr_width,
int scr_height,
@@ -753,9 +882,7 @@ result_t bmfont_test_one_format(const char *resources,
goto Failure;
}
- bitmap_init(&state.bm,
- state.scr_width,
- state.scr_height,
+ bitmap_init(&state.bm, SIZE2D(state.scr_width, state.scr_height),
scr_fmt,
scr_rowbytes,
state.palette,
@@ -840,6 +967,10 @@ result_t bmfont_test(const char *resources)
result_t rc;
int i;
+ rc = bmfont_enumerate_test(resources);
+ if (rc != result_TEST_PASSED)
+ return rc;
+
for (i = 0; i < NELEMS(tab); i++)
{
rc = bmfont_test_one_format(resources,
diff --git a/libraries/framebuf/colour/colour.c b/libraries/framebuf/colour/colour.c
index 216eab11..e7b58b86 100644
--- a/libraries/framebuf/colour/colour.c
+++ b/libraries/framebuf/colour/colour.c
@@ -1,4 +1,4 @@
-/* colour.c */
+/* framebuf/colour/colour.c */
#include
#include
@@ -63,9 +63,9 @@ static unsigned int closest_palette_entry(const colour_t *palette,
dr = ent_r - req_r;
dg = ent_g - req_g;
db = ent_b - req_b;
- curdist = ((dr * dr) * red_weight +
- (dg * dg) * green_weight +
- (db * db) * blue_weight) >> 16;
+ curdist = ((unsigned int) (dr * dr) * red_weight +
+ (unsigned int) (dg * dg) * green_weight +
+ (unsigned int) (db * db) * blue_weight) >> 16;
if (curdist < dist)
{
dist = curdist;
diff --git a/libraries/framebuf/composite/composite.c b/libraries/framebuf/composite/composite.c
index cd5f4fbe..b2859cd0 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.
*
@@ -936,8 +936,8 @@ static void composite_xxxa8888(composite_rule_t rule,
srcscan = src->base;
dstscan = dst->base;
- width = src->width;
- height = src->height;
+ width = src->size.w;
+ height = src->size.h;
rowbytes = src->rowbytes / sizeof(pixelfmt_xxxa8888_t);
while (height--)
@@ -959,8 +959,8 @@ result_t composite(composite_rule_t rule,
if (src == NULL || dst == NULL)
return result_NULL_ARG;
- if (src->width != dst->width ||
- src->height != dst->height ||
+ if (src->size.w != dst->size.w ||
+ src->size.h != dst->size.h ||
src->format != dst->format)
return result_BAD_ARG;
diff --git a/libraries/framebuf/composite/test/composite-test.c b/libraries/framebuf/composite/test/composite-test.c
index 4577a4b9..eac2cdc8 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
#include
@@ -34,7 +34,7 @@ static result_t bitmap_clone_by_size(bitmap_t *cloned, const bitmap_t *src)
assert(cloned);
assert(src);
- pixelbytes = src->height * src->rowbytes;
+ pixelbytes = src->size.h * src->rowbytes;
pixels = malloc(pixelbytes);
if (pixels == NULL)
return result_OOM;
@@ -52,13 +52,13 @@ static result_t bitmap_clone_pixels(bitmap_t *dst, const bitmap_t *src)
assert(dst);
assert(src);
- if (dst->width != src->width ||
- dst->height != src->height ||
+ if (dst->size.w != src->size.w ||
+ dst->size.h != src->size.h ||
dst->format != src->format ||
dst->rowbytes != src->rowbytes)
return result_INCOMPATIBLE;
- memcpy(dst->base, src->base, src->height * src->rowbytes);
+ memcpy(dst->base, src->base, src->size.h * src->rowbytes);
return result_OK;
}
@@ -73,7 +73,7 @@ static result_t bitmap_plot(const bitmap_t *src, bitmap_t *dst, int x, int y)
return result_INCOMPATIBLE;
sp += x + y * dst->rowbytes / 4;
- for (h = 0; h < src->height; h++)
+ for (h = 0; h < src->size.h; h++)
{
memcpy(sp, dp, src->rowbytes);
sp += dst->rowbytes / 4;
@@ -99,9 +99,9 @@ static result_t bitmap_convert_inplace(bitmap_t *bm, pixelfmt_t new_fmt)
pixelfmt_rgbx8888_t *p = bm->base;
int x,y;
- for (y = 0; y < bm->height; y++)
+ for (y = 0; y < bm->size.h; y++)
{
- for (x = 0; x < bm->width; x++)
+ for (x = 0; x < bm->size.w; x++)
{
pixelfmt_rgbx8888_t px = *p;
*p++ = PIXELFMT_MAKE_BGRA8888(PIXELFMT_Bxxx8888(px),
@@ -131,9 +131,9 @@ static result_t bitmap_convert_inplace(bitmap_t *bm, pixelfmt_t new_fmt)
pixelfmt_rgba8888_t *p = bm->base;
int x,y;
- for (y = 0; y < bm->height; y++)
+ for (y = 0; y < bm->size.h; y++)
{
- for (x = 0; x < bm->width; x++)
+ for (x = 0; x < bm->size.w; x++)
{
pixelfmt_rgba8888_t px = *p;
*p++ = PIXELFMT_MAKE_BGRA8888(PIXELFMT_Bxxx8888(px),
@@ -177,7 +177,7 @@ static result_t load_test_png(bitmap_t *bm,
if (rc)
return rc;
- if (bm->width != SMALLWIDTH || bm->height != SMALLHEIGHT || bm->format != FORMAT)
+ if (bm->size.w != SMALLWIDTH || bm->size.h != SMALLHEIGHT || bm->format != FORMAT)
{
fprintf(stderr, "load_test_png: wrong width, height or format\n");
free(bm->base);
@@ -210,7 +210,7 @@ result_t composite_test(const char *resources)
goto Failure;
}
- bitmap_init(&bigbitmap, WIDTH, HEIGHT, FORMAT, scr_rowbytes, NULL, bigpixels);
+ bitmap_init(&bigbitmap, SIZE2D(WIDTH, HEIGHT), FORMAT, scr_rowbytes, NULL, bigpixels);
rc = load_test_png(&bm[0], resources, "A"); /* source */
if (rc)
diff --git a/libraries/framebuf/curve/curve.c b/libraries/framebuf/curve/curve.c
index b3ce5fcb..dc74c5ef 100644
--- a/libraries/framebuf/curve/curve.c
+++ b/libraries/framebuf/curve/curve.c
@@ -1,4 +1,4 @@
-/* bezier.c */
+/* framebuf/curve/curve.c */
// TODO
//
@@ -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++)
{
diff --git a/libraries/framebuf/curve/test/curve-test.c b/libraries/framebuf/curve/test/curve-test.c
index 6349a728..a78021ed 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
#include
@@ -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)
@@ -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
@@ -804,9 +804,7 @@ result_t curve_test_one_format(const char *resources,
goto Failure;
}
- bitmap_init(&state.bm,
- state.scr_width,
- state.scr_height,
+ bitmap_init(&state.bm, SIZE2D(state.scr_width, state.scr_height),
scr_fmt,
scr_rowbytes,
state.palette,
diff --git a/libraries/framebuf/palettes/palettes.c b/libraries/framebuf/palettes/palettes.c
index 46816b45..ac3b2586 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"
@@ -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/framebuf/pattern/pattern.c b/libraries/framebuf/pattern/pattern.c
new file mode 100644
index 00000000..42634b9d
--- /dev/null
+++ b/libraries/framebuf/pattern/pattern.c
@@ -0,0 +1,139 @@
+/* framebuf/pattern/pattern.c -- 8x8 fill pattern constructors and preset table */
+
+#include
+#include
+#include
+#include
+
+#include "framebuf/colour.h"
+
+#include "framebuf/pattern.h"
+
+/* One byte per row, MSB = leftmost pixel.
+ *
+ * The 65 Bayer levels come first so a caller can index by coverage level as
+ * screen_PATTERN_BAYER0 + level. BAYER0/BAYER32/BAYER64 double as the EMPTY,
+ * GREY50 and SOLID patterns (see the enum), so those have no rows of their
+ * own. The named non-Bayer tiles and their inverses follow. */
+static const uint8_t patterns[screen_PATTERN__LIMIT][8] =
+{
+ /* 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. */
+ { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* BAYER0 */
+ { 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 */
+
+ /* Named non-Bayer tiles. */
+ { 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00 }, /* HSTRIPE */
+ { 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA }, /* VSTRIPE */
+ { 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 */
+
+ /* Inverses of the six above (bitwise NOT of each row). */
+ { 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF }, /* HSTRIPE_INV */
+ { 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55, 0x55 }, /* VSTRIPE_INV */
+ { 0x77, 0xBB, 0xDD, 0xEE, 0x77, 0xBB, 0xDD, 0xEE }, /* DIAGONAL_INV */
+ { 0x77, 0xFF, 0xDD, 0xFF, 0x77, 0xFF, 0xDD, 0xFF }, /* DOTS_INV */
+ { 0x00, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F }, /* GRID_INV */
+ { 0x7F, 0xBE, 0xDD, 0xEB, 0xF7, 0xEB, 0xDD, 0xBE } /* CROSSHATCH_INV */
+};
+
+/* 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];
+
+pattern_t pattern_from_preset(screen_pattern_t preset,
+ colour_t fg,
+ colour_t bg)
+{
+ pattern_t pat;
+
+ assert(preset >= 0 && preset < screen_PATTERN__LIMIT);
+
+ memset(&pat, 0, sizeof pat);
+ memcpy(pat.bits, patterns[preset], sizeof pat.bits);
+ pat.fg = fg;
+ pat.bg = bg;
+
+ return pat;
+}
+
+pattern_t pattern_from_mask(const uint8_t mask[8], colour_t colour)
+{
+ pattern_t pat;
+
+ assert(mask);
+
+ memset(&pat, 0, sizeof pat);
+ memcpy(pat.bits, mask, sizeof pat.bits);
+ pat.fg = colour;
+ pat.flags = pattern_FLAG_STENCIL;
+
+ return pat;
+}
diff --git a/libraries/framebuf/pixelfmt/log2bpp.c b/libraries/framebuf/pixelfmt/log2bpp.c
index dbc78a38..1d593519 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
diff --git a/libraries/framebuf/screen/screen-copy-ninepatch.c b/libraries/framebuf/screen/screen-copy-ninepatch.c
new file mode 100644
index 00000000..be1c6b76
--- /dev/null
+++ b/libraries/framebuf/screen/screen-copy-ninepatch.c
@@ -0,0 +1,181 @@
+/* framebuf/screen/screen-copy-ninepatch.c -- 9-patch bitmap drawing */
+
+#include
+
+#include "base/utils.h"
+#include "geom/box.h"
+#include "geom/size.h"
+
+#include "framebuf/bitmap.h"
+#include "framebuf/pixelfmt.h"
+#include "framebuf/screen.h"
+
+/* ----------------------------------------------------------------------- */
+
+/* Build a bitmap_t view onto one cell (col,row) of a 3x3 grid within "src".
+ * The view shares "src"'s rowbytes, so screen_copy_bitmap walks the parent
+ * image correctly despite the narrower size. */
+static void ninepatch_cell(bitmap_t *cell,
+ const bitmap_t *src,
+ int col,
+ int row,
+ int pw,
+ int ph)
+{
+ int log2bpp;
+ int bpp;
+
+ /* Byte stride per pixel. Sub-byte formats have no meaningful cell offset. */
+ log2bpp = pixelfmt_log2bpp(src->format);
+ assert(log2bpp >= 3);
+ bpp = 1 << (log2bpp - 3);
+
+ *cell = *src;
+ cell->size = SIZE2D(pw, ph);
+ cell->base = (unsigned char *) src->base
+ + row * ph * src->rowbytes
+ + col * pw * bpp;
+}
+
+/* Tile "cell" across "area" (a screen-space box), with the screen clip set to
+ * the intersection of "area" and "saved". The draw origin starts at
+ * (area->x0, area->y0) and steps by (stepx, stepy); a zero step means a single
+ * row or column. Overhang past "area" is removed by the clip. */
+static void tile_area(screen_t *scr,
+ const box_t *saved,
+ const box_t *area,
+ const bitmap_t *cell,
+ int stepx,
+ int stepy)
+{
+ box_t clip;
+
+ if (box_is_empty(saved))
+ clip = *area;
+ else if (box_intersection(saved, area, &clip))
+ return; /* nothing visible */
+
+ if (box_is_empty(&clip))
+ return;
+
+ scr->clip = clip;
+
+ {
+ int oy;
+ int ox;
+
+ for (oy = area->y0; oy < area->y1;
+ oy += (stepy > 0) ? stepy : (area->y1 - oy))
+ {
+ for (ox = area->x0; ox < area->x1;
+ ox += (stepx > 0) ? stepx : (area->x1 - ox))
+ screen_copy_bitmap(scr, ox, oy, cell);
+ }
+ }
+}
+
+/* ----------------------------------------------------------------------- */
+
+result_t screen_copy_ninepatch(screen_t *scr,
+ const box_t *dst,
+ const bitmap_t *src,
+ unsigned int flags)
+{
+ box_t saved;
+ box_t orig_clip;
+ int pw, ph;
+ int midx, midy;
+ int lx, rx, ty, by;
+ bitmap_t cell;
+
+ assert(src->size.w > 0 && src->size.w % 3 == 0);
+ assert(src->size.h > 0 && src->size.h % 3 == 0);
+
+ if (box_is_empty(dst))
+ return result_OK;
+
+ pw = src->size.w / 3;
+ ph = src->size.h / 3;
+
+ /* Corner column/row boundaries in the destination. When "dst" is narrower or
+ * shorter than two patches the near and far corners would overlap, so each
+ * boundary is clamped to the destination midpoint: the near corner gets the
+ * near half, the far corner the far half, and the edge/centre runs between
+ * them collapse to nothing. */
+ midx = (dst->x0 + dst->x1) / 2;
+ midy = (dst->y0 + dst->y1) / 2;
+ lx = MIN(dst->x0 + pw, midx);
+ rx = MAX(dst->x1 - pw, midx);
+ ty = MIN(dst->y0 + ph, midy);
+ by = MAX(dst->y1 - ph, midy);
+
+ /* Fold "dst" into the saved clip once, so every tile_area call is bounded by
+ * the destination rectangle as well as the caller's clip. An empty caller
+ * clip means "no clipping", so in that case the bound is "dst" alone. */
+ orig_clip = scr->clip;
+ if (box_is_empty(&orig_clip))
+ saved = *dst;
+ else if (box_intersection(&orig_clip, dst, &saved))
+ return result_OK; /* dst entirely outside the clip */
+
+ /* Corners. */
+ {
+ box_t b;
+
+ ninepatch_cell(&cell, src, 0, 0, pw, ph);
+ b.x0 = dst->x0; b.y0 = dst->y0; b.x1 = lx; b.y1 = ty;
+ tile_area(scr, &saved, &b, &cell, 0, 0);
+
+ ninepatch_cell(&cell, src, 2, 0, pw, ph);
+ b.x0 = rx; b.y0 = dst->y0; b.x1 = dst->x1; b.y1 = ty;
+ tile_area(scr, &saved, &b, &cell, 0, 0);
+
+ ninepatch_cell(&cell, src, 0, 2, pw, ph);
+ b.x0 = dst->x0; b.y0 = by; b.x1 = lx; b.y1 = dst->y1;
+ tile_area(scr, &saved, &b, &cell, 0, 0);
+
+ ninepatch_cell(&cell, src, 2, 2, pw, ph);
+ b.x0 = rx; b.y0 = by; b.x1 = dst->x1; b.y1 = dst->y1;
+ tile_area(scr, &saved, &b, &cell, 0, 0);
+ }
+
+ /* Edges. */
+ if (rx > lx)
+ {
+ box_t b;
+
+ ninepatch_cell(&cell, src, 1, 0, pw, ph);
+ b.x0 = lx; b.y0 = dst->y0; b.x1 = rx; b.y1 = ty;
+ tile_area(scr, &saved, &b, &cell, pw, 0);
+
+ ninepatch_cell(&cell, src, 1, 2, pw, ph);
+ b.x0 = lx; b.y0 = by; b.x1 = rx; b.y1 = dst->y1;
+ tile_area(scr, &saved, &b, &cell, pw, 0);
+ }
+ if (by > ty)
+ {
+ box_t b;
+
+ ninepatch_cell(&cell, src, 0, 1, pw, ph);
+ b.x0 = dst->x0; b.y0 = ty; b.x1 = lx; b.y1 = by;
+ tile_area(scr, &saved, &b, &cell, 0, ph);
+
+ ninepatch_cell(&cell, src, 2, 1, pw, ph);
+ b.x0 = rx; b.y0 = ty; b.x1 = dst->x1; b.y1 = by;
+ tile_area(scr, &saved, &b, &cell, 0, ph);
+ }
+
+ /* Centre. */
+ if (rx > lx && by > ty && !(flags & screen_NINEPATCH_NO_CENTRE))
+ {
+ box_t b;
+
+ ninepatch_cell(&cell, src, 1, 1, pw, ph);
+ b.x0 = lx; b.y0 = ty; b.x1 = rx; b.y1 = by;
+ tile_area(scr, &saved, &b, &cell, pw, ph);
+ }
+
+ scr->clip = orig_clip;
+
+ return result_OK;
+}
diff --git a/libraries/framebuf/screen/screen-copy-rect.c b/libraries/framebuf/screen/screen-copy-rect.c
index 87bd4284..36eed1a7 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
@@ -13,8 +13,13 @@
* columns within each row by the same rule applied to "dx" -- the standard
* two-axis blit-direction trick, so every pixel is read before anything
* that could overwrite it is written. */
-static int screen_copy_rect_p4(screen_t *scr, const box_t *s, const box_t *d,
- int width, int height, int dx, int dy)
+static result_t screen_copy_rect_p4(screen_t *scr,
+ const box_t *s,
+ const box_t *d,
+ int width,
+ int height,
+ int dx,
+ int dy)
{
unsigned char *base;
int rowbytes;
@@ -54,28 +59,67 @@ static int screen_copy_rect_p4(screen_t *scr, const box_t *s, const box_t *d,
}
}
- return 1;
+ return result_OK;
}
-int screen_copy_rect(screen_t *scr, const box_t *src, point_t dst, box_t *copied_dst)
+/* Whole-byte formats (8/16/32bpp): a row of "width" pixels is "width * bpp"
+ * contiguous bytes, so each row moves in one memmove. Rows can alias across
+ * each other (never within a row) when the vertical shift is smaller than the
+ * copied height, so walk in the direction that always reads a row before it
+ * is overwritten. */
+static result_t screen_copy_rect_bytes(screen_t *scr,
+ const box_t *s,
+ const box_t *d,
+ int width,
+ int height,
+ int dy,
+ int bpp)
{
- box_t clip_box, s, d, d_clipped;
- int dx, dy, width, height, bpp;
unsigned char *base;
int rowbytes;
+ int row;
+
+ base = scr->base;
+ rowbytes = scr->rowbytes;
+
+ if (dy > 0)
+ {
+ for (row = height - 1; row >= 0; row--)
+ memmove(base + (size_t) (d->y0 + row) * rowbytes + (size_t) d->x0 * bpp,
+ base + (size_t) (s->y0 + row) * rowbytes + (size_t) s->x0 * bpp,
+ (size_t) width * bpp);
+ }
+ else
+ {
+ for (row = 0; row < height; row++)
+ memmove(base + (size_t) (d->y0 + row) * rowbytes + (size_t) d->x0 * bpp,
+ base + (size_t) (s->y0 + row) * rowbytes + (size_t) s->x0 * bpp,
+ (size_t) width * bpp);
+ }
+
+ return result_OK;
+}
+
+result_t screen_copy_rect(screen_t *scr,
+ const box_t *src,
+ point_t dst,
+ box_t *copied_dst)
+{
+ box_t clip_box, s, d, d_clipped;
+ int dx, dy, width, height, bpp;
if (screen_get_clip(scr, &clip_box))
- return 0; /* invalid clipped screen */
+ return result_NOT_SUPPORTED; /* invalid clipped screen */
dx = dst.x - src->x0;
dy = dst.y - src->y0;
if (box_intersection(&clip_box, src, &s))
- return 0; /* source entirely off-screen */
+ return result_NOT_SUPPORTED; /* source entirely off-screen */
box_translated(&s, dx, dy, &d);
if (box_intersection(&clip_box, &d, &d_clipped))
- return 0; /* destination entirely off-screen */
+ return result_NOT_SUPPORTED; /* destination entirely off-screen */
/* keep only the part of "s" whose translated position also survived
* clipping, so source and destination stay the same size */
@@ -85,7 +129,7 @@ int screen_copy_rect(screen_t *scr, const box_t *src, point_t dst, box_t *copied
s.y1 += d_clipped.y1 - d.y1;
if (box_is_empty(&s))
- return 0;
+ return result_NOT_SUPPORTED;
width = s.x1 - s.x0;
height = s.y1 - s.y0;
@@ -111,34 +155,8 @@ int screen_copy_rect(screen_t *scr, const box_t *src, point_t dst, box_t *copied
default:
/* ponytail: unknown/future pixel format; callers must fall back to a
* normal invalidate/redraw there */
- return 0;
- }
-
- base = scr->base;
- rowbytes = scr->rowbytes;
-
- /* Rows can alias across each other (never within a row, since a row's
- * bytes never overlap another row's) when the vertical shift is smaller
- * than the copied height, so walk in the direction that always reads a
- * row before it's overwritten. */
- if (dy > 0)
- {
- int row;
-
- for (row = height - 1; row >= 0; row--)
- memmove(base + (size_t) (d_clipped.y0 + row) * rowbytes + (size_t) d_clipped.x0 * bpp,
- base + (size_t) (s.y0 + row) * rowbytes + (size_t) s.x0 * bpp,
- (size_t) width * bpp);
- }
- else
- {
- int row;
-
- for (row = 0; row < height; row++)
- memmove(base + (size_t) (d_clipped.y0 + row) * rowbytes + (size_t) d_clipped.x0 * bpp,
- base + (size_t) (s.y0 + row) * rowbytes + (size_t) s.x0 * bpp,
- (size_t) width * bpp);
+ return result_NOT_SUPPORTED;
}
- return 1;
+ return screen_copy_rect_bytes(scr, &s, &d_clipped, width, height, dy, bpp);
}
diff --git a/libraries/framebuf/screen/screen-draw-circle.c b/libraries/framebuf/screen/screen-draw-circle.c
new file mode 100644
index 00000000..a9737266
--- /dev/null
+++ b/libraries/framebuf/screen/screen-draw-circle.c
@@ -0,0 +1,108 @@
+/* framebuf/screen/screen-draw-circle.c -- circle outline and fill */
+
+#include
+
+#include "base/utils.h"
+#include "geom/box.h"
+
+#include "framebuf/screen.h"
+
+/* One octant of a midpoint circle, mirrored to the other seven. screen_set_pixel
+ * clips each plot, so nothing here needs its own bounds check. */
+void screen_draw_circle(screen_t *scr,
+ int cx,
+ int cy,
+ int r,
+ colour_t colour)
+{
+ int x, y, err;
+
+ if (r < 0)
+ return;
+
+ if (r == 0)
+ {
+ screen_set_pixel(scr, cx, cy, colour);
+ return;
+ }
+
+ x = r;
+ y = 0;
+ err = 1 - r;
+
+ while (x >= y)
+ {
+ screen_set_pixel(scr, cx + x, cy + y, colour);
+ screen_set_pixel(scr, cx - x, cy + y, colour);
+ screen_set_pixel(scr, cx + x, cy - y, colour);
+ screen_set_pixel(scr, cx - x, cy - y, colour);
+ screen_set_pixel(scr, cx + y, cy + x, colour);
+ screen_set_pixel(scr, cx - y, cy + x, colour);
+ screen_set_pixel(scr, cx + y, cy - x, colour);
+ screen_set_pixel(scr, cx - y, cy - x, colour);
+
+ y++;
+ if (err < 0)
+ {
+ err += 2 * y + 1;
+ }
+ else
+ {
+ x--;
+ err += 2 * (y - x) + 1;
+ }
+ }
+}
+
+/* Same octant stepping as the outline, but each step emits a pair of solid
+ * horizontal runs (via screen_fill_hline, which clips) rather than eight
+ * points. Runs from the two octant families cover every scanline of the
+ * disc exactly once. */
+void screen_fill_circle(screen_t *scr,
+ int cx,
+ int cy,
+ int r,
+ colour_t colour)
+{
+ int x, y, err;
+
+ if (r < 0)
+ return;
+
+ if (r == 0)
+ {
+ screen_set_pixel(scr, cx, cy, colour);
+ return;
+ }
+
+ x = r;
+ y = 0;
+ err = 1 - r;
+
+ while (x >= y)
+ {
+ /* the wide pair: rows cy +/- y, spanning -x..+x */
+ screen_fill_hline(scr, cx - x, cy + y, 2 * x + 1, colour);
+ if (y != 0)
+ screen_fill_hline(scr, cx - x, cy - y, 2 * x + 1, colour);
+
+ /* the tall pair: rows cy +/- x, spanning -y..+y; skip while it would
+ * fall inside the wide pair's rows to avoid overdraw */
+ if (x != y)
+ {
+ screen_fill_hline(scr, cx - y, cy + x, 2 * y + 1, colour);
+ screen_fill_hline(scr, cx - y, cy - x, 2 * y + 1, colour);
+ }
+
+ y++;
+ if (err < 0)
+ {
+ err += 2 * y + 1;
+ }
+ else
+ {
+ x--;
+ err += 2 * (y - x) + 1;
+ }
+ }
+}
diff --git a/libraries/framebuf/screen/screen-draw.c b/libraries/framebuf/screen/screen-draw.c
index 686ee99f..4c5e30ac 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
#include
@@ -15,12 +15,68 @@
#include "framebuf/screen.h"
-/* Number of pixels converted per span call in screen_draw_bitmap(). Bounds
+/* Number of pixels converted per span call in screen_copy_bitmap(). Bounds
* the size of its stack scratch buffers so arbitrarily wide bitmaps don't
* 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)
+/* Each helper writes the single pixel (x, y), already known to be inside the
+ * clip, with the colour previously resolved to "pxl". */
+
+static void screen_set_pixel_p4(screen_t *scr,
+ int x,
+ int y,
+ pixelfmt_any_t pxl)
+{
+ unsigned char *scrp;
+ int shift;
+
+ scrp = (unsigned char *) scr->base + y * scr->rowbytes + (x >> 1);
+ shift = (x & 1) * 4;
+
+ *scrp = (unsigned char) ((*scrp & ~(0xF << shift)) | ((pxl & 0xF) << shift));
+}
+
+static void screen_set_pixel_8(screen_t *scr,
+ int x,
+ int y,
+ pixelfmt_any_t pxl)
+{
+ pixelfmt_any8_t *scrp;
+
+ scrp = scr->base;
+ scrp += y * scr->rowbytes / sizeof(*scrp) + x;
+
+ *scrp = (pixelfmt_any8_t) pxl;
+}
+
+static void screen_set_pixel_16(screen_t *scr,
+ int x,
+ int y,
+ pixelfmt_any_t pxl)
+{
+ pixelfmt_any16_t *scrp;
+
+ scrp = scr->base;
+ scrp += y * scr->rowbytes / sizeof(*scrp) + x;
+
+ *scrp = pxl;
+}
+
+static void screen_set_pixel_32(screen_t *scr,
+ int x,
+ int y,
+ pixelfmt_any_t pxl)
+{
+ pixelfmt_any32_t *scrp;
+
+ scrp = scr->base;
+ scrp += y * scr->rowbytes / sizeof(*scrp) + x;
+
+ *scrp = pxl;
+}
+
+void screen_set_pixel(screen_t *scr, int x, int y, colour_t colour)
{
box_t clip;
pixelfmt_any_t pxl;
@@ -33,60 +89,61 @@ void screen_draw_pixel(screen_t *scr, int x, int y, colour_t colour)
colour, scr->format);
switch (pixelfmt_log2bpp(scr->format))
{
- case 2:
- {
- unsigned char *scrp;
- int shift;
-
- scrp = (unsigned char *) scr->base + y * scr->rowbytes + (x >> 1);
- shift = (x & 1) * 4;
+ case 2: screen_set_pixel_p4(scr, x, y, pxl); break;
+ case 3: screen_set_pixel_8(scr, x, y, pxl); break;
+ case 4: screen_set_pixel_16(scr, x, y, pxl); break;
+ case 5: screen_set_pixel_32(scr, x, y, pxl); break;
- *scrp = (unsigned char) ((*scrp & ~(0xF << shift)) | ((pxl & 0xF) << shift));
- }
+ default:
+ assert(!"Unimplemented pixel format");
break;
+ }
+}
- case 3:
- {
- pixelfmt_any8_t *scrp;
-
- scrp = scr->base;
- scrp += y * scr->rowbytes / sizeof(*scrp) + x;
+/* Each helper alpha-blends "colour" at "alpha" into the single pixel (x, y),
+ * already known to be inside the clip. */
- *scrp = (pixelfmt_any8_t) pxl;
- }
- break;
+static void screen_blend_pixel_p4(screen_t *scr,
+ int x,
+ int y,
+ colour_t colour,
+ int alpha)
+{
+ unsigned char *scrp;
+ int shift;
+ unsigned char idx, out;
- case 4:
- {
- pixelfmt_any16_t *scrp;
+ scrp = (unsigned char *) scr->base + y * scr->rowbytes + (x >> 1);
+ shift = (x & 1) * 4;
+ idx = (*scrp >> shift) & 0xF;
- scrp = scr->base;
- scrp += y * scr->rowbytes / sizeof(*scrp) + x;
+ scr->span->blendconst(&out, &idx, &colour, 1, alpha, scr->palette);
- *scrp = pxl;
- }
- break;
+ *scrp = (unsigned char) ((*scrp & ~(0xF << shift)) | ((out & 0xF) << shift));
+}
- case 5:
- {
- pixelfmt_any32_t *scrp;
+static void screen_blend_pixel_32(screen_t *scr,
+ int x,
+ int y,
+ colour_t colour,
+ int alpha)
+{
+ pixelfmt_any32_t *scrp;
+ pixelfmt_any_t colpx;
- scrp = scr->base;
- scrp += y * scr->rowbytes / sizeof(*scrp) + x;
+ colpx = colour_to_pixel(NULL, 0, colour, scr->format);
- *scrp = pxl;
- }
- break;
+ scrp = scr->base;
+ scrp += y * scr->rowbytes / sizeof(*scrp) + x;
- default:
- assert(!"Unimplemented pixel format");
- break;
- }
+ scr->span->blendconst(scrp, scrp, &colpx, 1, alpha, NULL);
}
static void screen_blend_pixel(screen_t *scr,
- int x, int y,
- colour_t colour, int alpha)
+ int x,
+ int y,
+ colour_t colour,
+ int alpha)
{
box_t clip;
@@ -98,35 +155,8 @@ static void screen_blend_pixel(screen_t *scr,
switch (pixelfmt_log2bpp(scr->format))
{
- case 2:
- {
- unsigned char *scrp;
- int shift;
- unsigned char idx, out;
-
- scrp = (unsigned char *) scr->base + y * scr->rowbytes + (x >> 1);
- shift = (x & 1) * 4;
- idx = (*scrp >> shift) & 0xF;
-
- scr->span->blendconst(&out, &idx, &colour, 1, alpha, scr->palette);
-
- *scrp = (unsigned char) ((*scrp & ~(0xF << shift)) | ((out & 0xF) << shift));
- }
- break;
-
- case 5:
- {
- pixelfmt_any32_t *scrp;
- pixelfmt_any_t colpx;
-
- colpx = colour_to_pixel(NULL, 0, colour, scr->format);
-
- scrp = scr->base;
- scrp += y * scr->rowbytes / sizeof(*scrp) + x;
-
- scr->span->blendconst(scrp, scrp, &colpx, 1, alpha, NULL);
- }
- break;
+ case 2: screen_blend_pixel_p4(scr, x, y, colour, alpha); break;
+ case 5: screen_blend_pixel_32(scr, x, y, colour, alpha); break;
default:
assert(!"Unimplemented pixel format");
@@ -136,109 +166,175 @@ static void screen_blend_pixel(screen_t *scr,
/* ----------------------------------------------------------------------- */
-void screen_draw_rect(screen_t *scr,
- int x, int y,
- int width, int height,
- colour_t colour)
+void screen_fill_rect(screen_t *scr,
+ int x,
+ int y,
+ size2d_t size,
+ colour_t colour)
{
- box_t clip_box;
- box_t rect_box;
- box_t draw_box;
- int clipped_width, clipped_height;
- pixelfmt_any_t fmt;
+ box_t clip_box;
+ box_t rect_box;
+ box_t draw_box;
+ int yy;
if (screen_get_clip(scr, &clip_box))
return; /* invalid clipped screen */
rect_box.x0 = x;
rect_box.y0 = y;
- rect_box.x1 = x + width;
- rect_box.y1 = y + height;
+ rect_box.x1 = x + size.w;
+ rect_box.y1 = y + size.h;
if (box_intersection(&clip_box, &rect_box, &draw_box))
return;
- clipped_width = draw_box.x1 - draw_box.x0;
- clipped_height = draw_box.y1 - draw_box.y0;
+ /* Each row is already clipped, so hand the pre-clipped span straight to
+ * screen_fill_hline. */
+ for (yy = draw_box.y0; yy < draw_box.y1; yy++)
+ screen_fill_hline(scr, draw_box.x0, yy, draw_box.x1 - draw_box.x0, 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);
+}
- fmt = colour_to_pixel(scr->palette,
- (scr->format == pixelfmt_p4) ? 16 : 0,
- colour, scr->format);
- switch (pixelfmt_log2bpp(scr->format))
+/* ----------------------------------------------------------------------- */
+
+/* Blit "src" onto the paletted screen, its top-left at (x, y), clipped to
+ * "draw_box". No linear channel bits to blend, so this falls back to
+ * alpha-tested transfer (skip fully transparent, else nearest palette match)
+ * rather than true blending, matching screen_set_pixel's case 2. */
+static void screen_copy_bitmap_p4(screen_t *scr,
+ int x,
+ int y,
+ const bitmap_t *src,
+ const box_t *draw_box,
+ int has_alpha)
+{
+ const unsigned char *srcrow;
+ unsigned char *dstbase;
+ int clipped_width, clipped_height;
+ int yy;
+
+ clipped_width = draw_box->x1 - draw_box->x0;
+ clipped_height = draw_box->y1 - draw_box->y0;
+
+ srcrow = (const unsigned char *) src->base + (draw_box->y0 - y) * src->rowbytes;
+ dstbase = scr->base;
+
+ for (yy = 0; yy < clipped_height; yy++)
{
- case 2:
+ const pixelfmt_rgba8888_t *srcpx;
+ unsigned char *rowp;
+ int xx;
+
+ srcpx = (const pixelfmt_rgba8888_t *) srcrow + (draw_box->x0 - x);
+ rowp = dstbase + (draw_box->y0 + yy) * scr->rowbytes;
+
+ for (xx = 0; xx < clipped_width; xx++)
{
- unsigned char *rowp;
- int yy, xx;
+ colour_t c;
+ int dstx;
+ unsigned char *scrp;
+ int shift;
+ pixelfmt_any_t pxl;
- rowp = (unsigned char *) scr->base + draw_box.y0 * scr->rowbytes;
- for (yy = 0; yy < clipped_height; yy++)
- {
- for (xx = 0; xx < clipped_width; xx++)
- {
- int x;
- unsigned char *scrp;
- int shift;
-
- x = draw_box.x0 + xx;
- scrp = rowp + (x >> 1);
- shift = (x & 1) * 4;
-
- *scrp = (unsigned char) ((*scrp & ~(0xF << shift)) | ((fmt & 0xF) << shift));
- }
- rowp += scr->rowbytes;
- }
+ c.primary = srcpx[xx];
+ if (has_alpha && colour_get_alpha(&c) == 0)
+ continue; /* fully transparent: leave background alone */
+
+ dstx = draw_box->x0 + xx;
+ scrp = rowp + (dstx >> 1);
+ shift = (dstx & 1) * 4;
+ pxl = colour_to_pixel(scr->palette, 16, c, scr->format);
+
+ *scrp = (unsigned char) ((*scrp & ~(0xF << shift)) | ((pxl & 0xF) << shift));
}
- break;
- case 5:
+ srcrow += src->rowbytes;
+ }
+}
+
+/* Blit "src" onto the 32bpp screen, its top-left at (x, y), clipped to
+ * "draw_box", alpha-blending row spans through the span registry. */
+static void screen_copy_bitmap_32(screen_t *scr,
+ int x,
+ int y,
+ const bitmap_t *src,
+ const box_t *draw_box,
+ int has_alpha)
+{
+ pixelfmt_any32_t colbuf[BITMAP_BLIT_CHUNK];
+ unsigned char alphabuf[BITMAP_BLIT_CHUNK];
+ const unsigned char *srcrow;
+ pixelfmt_any32_t *dstrow;
+ int clipped_width, clipped_height;
+ int yy;
+
+ clipped_width = draw_box->x1 - draw_box->x0;
+ clipped_height = draw_box->y1 - draw_box->y0;
+
+ srcrow = (const unsigned char *) src->base + (draw_box->y0 - y) * src->rowbytes;
+ dstrow = scr->base;
+ dstrow += draw_box->y0 * scr->rowbytes / (int) sizeof(*dstrow) + draw_box->x0;
+
+ for (yy = 0; yy < clipped_height; yy++)
+ {
+ const pixelfmt_rgba8888_t *srcpx;
+ pixelfmt_any32_t *dstpx;
+ int remaining;
+
+ srcpx = (const pixelfmt_rgba8888_t *) srcrow + (draw_box->x0 - x);
+ dstpx = dstrow;
+ remaining = clipped_width;
+
+ while (remaining > 0)
{
- pixelfmt_any32_t *scrp;
- int w;
+ int chunk, i;
- scrp = scr->base;
- scrp += draw_box.y0 * scr->rowbytes / sizeof(*scrp) + draw_box.x0;
- while (clipped_height--)
+ chunk = MIN(remaining, BITMAP_BLIT_CHUNK);
+
+ for (i = 0; i < chunk; i++)
{
- for (w = clipped_width; w > 0; w--)
- *scrp++ = fmt;
- scrp += scr->rowbytes / sizeof(*scrp) - clipped_width;
+ colour_t c;
+
+ c.primary = srcpx[i];
+ colbuf[i] = colour_to_pixel(scr->palette, 0, c, scr->format);
+ alphabuf[i] = has_alpha ? colour_get_alpha(&c) : PIXELFMT_OPAQUE;
}
+
+ scr->span->blendarray(dstpx, dstpx, colbuf, chunk, alphabuf);
+
+ srcpx += chunk;
+ dstpx += chunk;
+ remaining -= chunk;
}
- break;
- default:
- assert(!"Unimplemented pixel format");
- break;
+ srcrow += src->rowbytes;
+ dstrow += scr->rowbytes / (int) sizeof(*dstrow);
}
}
-void screen_draw_square(screen_t *scr, int x, int y, int size, colour_t colour)
-{
- screen_draw_rect(scr, x, y, size, size, colour);
-}
-
-/* ----------------------------------------------------------------------- */
-
-void screen_draw_bitmap(screen_t *scr, int x, int y, const bitmap_t *src)
+result_t screen_copy_bitmap(screen_t *scr, int x, int y, const bitmap_t *src)
{
box_t clip_box;
box_t src_box;
box_t draw_box;
- int clipped_width, clipped_height;
int has_alpha;
if (screen_get_clip(scr, &clip_box))
- return; /* invalid clipped screen */
+ return result_OK; /* invalid clipped screen: nothing to draw */
src_box.x0 = x;
src_box.y0 = y;
- src_box.x1 = x + src->width;
- src_box.y1 = y + src->height;
+ src_box.x1 = x + src->size.w;
+ src_box.y1 = y + src->size.h;
if (box_intersection(&clip_box, &src_box, &draw_box))
- return; /* nothing visible */
-
- clipped_width = draw_box.x1 - draw_box.x0;
- clipped_height = draw_box.y1 - draw_box.y0;
+ return result_OK; /* nothing visible */
/* Source pixels loaded from PNG are always laid out R,G,B,A/X byte order
* (see bitmap_load_png()), the same layout colour_t::primary uses, so
@@ -247,126 +343,180 @@ void screen_draw_bitmap(screen_t *scr, int x, int y, const bitmap_t *src)
switch (pixelfmt_log2bpp(scr->format))
{
- case 2:
- {
- /* 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. */
- const unsigned char *srcrow;
- unsigned char *dstbase;
- int yy;
+ case 2: screen_copy_bitmap_p4(scr, x, y, src, &draw_box, has_alpha); break;
+ case 5: screen_copy_bitmap_32(scr, x, y, src, &draw_box, has_alpha); break;
- srcrow = (const unsigned char *) src->base + (draw_box.y0 - y) * src->rowbytes;
- dstbase = scr->base;
+ default:
+ assert(!"Unimplemented pixel format");
+ return result_NOT_SUPPORTED;
+ }
- for (yy = 0; yy < clipped_height; yy++)
- {
- const pixelfmt_rgba8888_t *srcpx;
- unsigned char *rowp;
- int xx;
-
- srcpx = (const pixelfmt_rgba8888_t *) srcrow + (draw_box.x0 - x);
- rowp = dstbase + (draw_box.y0 + yy) * scr->rowbytes;
-
- for (xx = 0; xx < clipped_width; xx++)
- {
- colour_t c;
- int dstx;
- unsigned char *scrp;
- int shift;
- pixelfmt_any_t pxl;
-
- c.primary = srcpx[xx];
- if (has_alpha && colour_get_alpha(&c) == 0)
- continue; /* fully transparent: leave background alone */
-
- dstx = draw_box.x0 + xx;
- scrp = rowp + (dstx >> 1);
- shift = (dstx & 1) * 4;
- pxl = colour_to_pixel(scr->palette, 16, c, scr->format);
-
- *scrp = (unsigned char) ((*scrp & ~(0xF << shift)) | ((pxl & 0xF) << shift));
- }
-
- srcrow += src->rowbytes;
- }
- }
- break;
+ return result_OK;
+}
- case 5:
- {
- pixelfmt_any32_t colbuf[BITMAP_BLIT_CHUNK];
- unsigned char alphabuf[BITMAP_BLIT_CHUNK];
- const unsigned char *srcrow;
- pixelfmt_any32_t *dstrow;
- int yy;
+/* ----------------------------------------------------------------------- */
- srcrow = (const unsigned char *) src->base + (draw_box.y0 - y) * src->rowbytes;
- dstrow = scr->base;
- dstrow += draw_box.y0 * scr->rowbytes / (int) sizeof(*dstrow) + draw_box.x0;
+/* Build the box covering the whole screen, ignoring the current clip
+ * rectangle. Unlike the clip rectangle this is invariant across redraws, so
+ * clipping a line's endpoints against it yields the same result every time.
+ */
+static void screen_get_bounds(const screen_t *scr, box_t *bounds)
+{
+ bounds->x0 = 0;
+ bounds->y0 = 0;
+ bounds->x1 = scr->size.w;
+ bounds->y1 = scr->size.h;
+}
- for (yy = 0; yy < clipped_height; yy++)
- {
- const pixelfmt_rgba8888_t *srcpx;
- pixelfmt_any32_t *dstpx;
- int remaining;
+void screen_draw_line(screen_t *scr,
+ int x0,
+ int y0,
+ int x1,
+ int y1,
+ 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;
- srcpx = (const pixelfmt_rgba8888_t *) srcrow + (draw_box.x0 - x);
- dstpx = dstrow;
- remaining = clipped_width;
+ if (screen_get_clip(scr, &clip_box))
+ return; /* invalid clipped screen */
- while (remaining > 0)
- {
- int chunk, i;
+ /* Reject only: the clipped-back endpoints are discarded, since feeding
+ * them into the stepping maths below would make the pixels chosen depend
+ * on which clip rectangle we happened to be called with. */
+ rx0 = x0;
+ ry0 = y0;
+ rx1 = x1;
+ ry1 = y1;
+ if (line_clip(&clip_box, &rx0, &ry0, &rx1, &ry1) == 0)
+ return;
- chunk = (remaining > BITMAP_BLIT_CHUNK) ? BITMAP_BLIT_CHUNK : remaining;
+ /* Bound the number of steps taken. Safe to feed into the stepping maths
+ * as the screen bounds never vary between calls. Cannot reject: the clip
+ * box is always a subset of the screen bounds and it just accepted. */
+ screen_get_bounds(scr, &bounds);
+ (void) line_clip(&bounds, &x0, &y0, &x1, &y1);
- for (i = 0; i < chunk; i++)
- {
- colour_t c;
+ dx = x1 - x0;
+ adx = abs(dx);
+ sx = SGN(dx);
- c.primary = srcpx[i];
- colbuf[i] = colour_to_pixel(scr->palette, 0, c, scr->format);
- alphabuf[i] = has_alpha ? colour_get_alpha(&c) : PIXELFMT_OPAQUE;
- }
+ dy = y1 - y0;
+ ady = -abs(dy);
+ sy = SGN(dy);
- scr->span->blendarray(dstpx, dstpx, colbuf, chunk, alphabuf);
+ error = adx + ady;
- srcpx += chunk;
- dstpx += chunk;
- remaining -= chunk;
- }
+ for (;;)
+ {
+ screen_set_pixel(scr, x0, y0, colour);
- srcrow += src->rowbytes;
- dstrow += scr->rowbytes / (int) sizeof(*dstrow);
- }
- }
- break;
+ if (x0 == x1 && y0 == y1)
+ break;
- default:
- assert(!"Unimplemented pixel format");
- 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_lines(screen_t *scr,
+ const point_t *points,
+ int npoints,
+ colour_t colour)
+{
+ int i;
-void screen_draw_line(screen_t *scr,
- int x0, int y0, int x1, int y1,
- colour_t colour)
+ 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,
+ 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;
+ off = MAX(off, 0);
+ period = on + off;
if (screen_get_clip(scr, &clip_box))
return; /* invalid clipped screen */
- if (line_clip(&clip_box, &x0, &y0, &x1, &y1) == 0)
+ 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);
@@ -376,10 +526,14 @@ void screen_draw_line(screen_t *scr,
sy = SGN(dy);
error = adx + ady;
+ phase = 0;
for (;;)
{
- screen_draw_pixel(scr, x0, y0, colour);
+ if (phase < on)
+ screen_set_pixel(scr, x0, y0, colour);
+ if (++phase >= period)
+ phase = 0;
if (x0 == x1 && y0 == y1)
break;
@@ -401,10 +555,15 @@ void screen_draw_line(screen_t *scr,
}
void screen_draw_line_wu_fix8(screen_t *scr,
- fix8_t x0_f8, fix8_t y0_f8, fix8_t x1_f8, fix8_t y1_f8,
- colour_t colour)
+ fix8_t x0_f8,
+ fix8_t y0_f8,
+ fix8_t x1_f8,
+ fix8_t y1_f8,
+ colour_t colour)
{
box_t clip_box_f8;
+ box_t bounds_f8;
+ fix8_t rx0_f8, ry0_f8, rx1_f8, ry1_f8;
fix8_t dx_f8, dy_f8;
int steep_b; /* a bool */
fix16_t grad_f16;
@@ -423,9 +582,20 @@ void screen_draw_line_wu_fix8(screen_t *scr,
/* scale up screen clip box to match the coordinate type */
box_scalelog2(&clip_box_f8, FIX8_SHIFT);
- if (line_clip(&clip_box_f8, &x0_f8, &y0_f8, &x1_f8, &y1_f8) == 0)
+ /* Reject only: see screen_draw_line() for why the clipped-back endpoints
+ * are discarded rather than used. */
+ rx0_f8 = x0_f8;
+ ry0_f8 = y0_f8;
+ rx1_f8 = x1_f8;
+ ry1_f8 = y1_f8;
+ if (line_clip(&clip_box_f8, &rx0_f8, &ry0_f8, &rx1_f8, &ry1_f8) == 0)
return;
+ /* Bound the number of steps taken, using the invariant screen bounds. */
+ screen_get_bounds(scr, &bounds_f8);
+ box_scalelog2(&bounds_f8, FIX8_SHIFT);
+ (void) line_clip(&bounds_f8, &x0_f8, &y0_f8, &x1_f8, &y1_f8);
+
dx_f8 = x1_f8 - x0_f8;
dy_f8 = y1_f8 - y0_f8;
@@ -443,18 +613,20 @@ void screen_draw_line_wu_fix8(screen_t *scr,
SWAP(y0_f8, y1_f8);
}
- grad_f16 = (dx_f8 == 0) ? FIX16_ONE : FIX16_ONE * dy_f8 / dx_f8;
+ /* 64-bit intermediates: FIX16_ONE * dy_f8 and grad_f16 * dx overflow int. */
+ grad_f16 = (dx_f8 == 0) ? FIX16_ONE : (fix16_t) ((long long) FIX16_ONE * dy_f8 / dx_f8);
/* start point */
xend_i = FIX8_ROUND_TO_INT(x0_f8);
- yend_f8 = y0_f8 + grad_f16 * (INT_TO_FIX8(xend_i) - x0_f8) / FIX16_ONE;
+ yend_f8 = y0_f8 + (fix8_t) ((long long) grad_f16 * (INT_TO_FIX8(xend_i) - x0_f8) / FIX16_ONE);
xgap_f8 = INT_TO_FIX8(xend_i) + FIX8_ONE / 2 - x0_f8;
assert(xgap_f8 >= 0 && xgap_f8 <= FIX8_ONE);
ix0_i = xend_i;
iy0_i = FIX8_FLOOR_TO_INT(yend_f8);
- alpha1_i = (255 * (INT_TO_FIX8(iy0_i) + FIX8_ONE - yend_f8) * xgap_f8 / FIX8_ONE) / FIX8_ONE;
- alpha2_i = (255 * -(INT_TO_FIX8(iy0_i) - yend_f8) * xgap_f8 / FIX8_ONE) / FIX8_ONE;
+ /* iy0_i may be negative; use multiply not INT_TO_FIX8's left shift. */
+ alpha1_i = (255 * (iy0_i * FIX8_ONE + FIX8_ONE - yend_f8) * xgap_f8 / FIX8_ONE) / FIX8_ONE;
+ alpha2_i = (255 * -(iy0_i * FIX8_ONE - yend_f8) * xgap_f8 / FIX8_ONE) / FIX8_ONE;
if (steep_b)
{
screen_blend_pixel(scr, iy0_i, ix0_i, colour, alpha1_i);
@@ -466,18 +638,20 @@ void screen_draw_line_wu_fix8(screen_t *scr,
screen_blend_pixel(scr, ix0_i, iy0_i + 1, colour, alpha2_i);
}
- yf_f8 = ((yend_f8 << (FIX16_SHIFT - FIX8_SHIFT)) + grad_f16) >> (FIX16_SHIFT - FIX8_SHIFT);
+ /* yend_f8 may be negative; form the fix16 sum by multiply (left-shifting a
+ * negative is UB) then arithmetic-shift back down. */
+ yf_f8 = (yend_f8 * (FIX16_ONE / FIX8_ONE) + grad_f16) >> (FIX16_SHIFT - FIX8_SHIFT);
/* end point */
xend_i = FIX8_ROUND_TO_INT(x1_f8);
- yend_f8 = y1_f8 + grad_f16 * (INT_TO_FIX8(xend_i) - x1_f8) / FIX16_ONE;
+ yend_f8 = y1_f8 + (fix8_t) ((long long) grad_f16 * (INT_TO_FIX8(xend_i) - x1_f8) / FIX16_ONE);
xgap_f8 = x1_f8 + FIX8_ONE / 2 - INT_TO_FIX8(xend_i);
assert(xgap_f8 >= 0 && xgap_f8 < FIX8_ONE);
ix1_i = xend_i;
iy1_i = FIX8_FLOOR_TO_INT(yend_f8);
- alpha1_i = (255 * (INT_TO_FIX8(iy1_i) + FIX8_ONE - yend_f8) * xgap_f8 / FIX8_ONE) / FIX8_ONE;
- alpha2_i = (255 * -(INT_TO_FIX8(iy1_i) - yend_f8) * xgap_f8 / FIX8_ONE) / FIX8_ONE;
+ alpha1_i = (255 * (iy1_i * FIX8_ONE + FIX8_ONE - yend_f8) * xgap_f8 / FIX8_ONE) / FIX8_ONE;
+ alpha2_i = (255 * -(iy1_i * FIX8_ONE - yend_f8) * xgap_f8 / FIX8_ONE) / FIX8_ONE;
if (steep_b)
{
screen_blend_pixel(scr, iy1_i, ix1_i, colour, alpha1_i);
@@ -494,8 +668,8 @@ void screen_draw_line_wu_fix8(screen_t *scr,
for (x_i = ix0_i + 1; x_i < ix1_i; x_i++)
{
y_i = FIX8_FLOOR_TO_INT(yf_f8);
- alpha1_i = (255 * (INT_TO_FIX8(y_i) + FIX8_ONE - yf_f8)) / FIX8_ONE;
- alpha2_i = (255 * -(INT_TO_FIX8(y_i) - yf_f8)) / FIX8_ONE;
+ alpha1_i = (255 * (y_i * FIX8_ONE + FIX8_ONE - yf_f8)) / FIX8_ONE;
+ alpha2_i = (255 * -(y_i * FIX8_ONE - yf_f8)) / FIX8_ONE;
if (steep_b)
{
screen_blend_pixel(scr, y_i, x_i, colour, alpha1_i);
@@ -506,7 +680,7 @@ void screen_draw_line_wu_fix8(screen_t *scr,
screen_blend_pixel(scr, x_i, y_i, colour, alpha1_i);
screen_blend_pixel(scr, x_i, y_i + 1, colour, alpha2_i);
}
- yf_f8 = ((yf_f8 << (FIX16_SHIFT - FIX8_SHIFT)) + grad_f16) >> (FIX16_SHIFT - FIX8_SHIFT);
+ yf_f8 = (yf_f8 * (FIX16_ONE / FIX8_ONE) + grad_f16) >> (FIX16_SHIFT - FIX8_SHIFT);
}
}
@@ -517,10 +691,14 @@ static int my_lroundf(float x)
}
void screen_draw_line_wu_float(screen_t *scr,
- float fx0, float fy0, float fx1, float fy1,
- colour_t colour)
+ float fx0,
+ float fy0,
+ float fx1,
+ float fy1,
+ colour_t colour)
{
box_t clip_box;
+ box_t bounds;
int x0, y0, x1, y1;
float dx, dy;
int steep; /* bool */
@@ -532,13 +710,15 @@ void screen_draw_line_wu_float(screen_t *scr,
int alpha1, alpha2;
float yf;
int ix1, iy1;
+ int xlo, xhi;
+ int xstart, xstop;
int x, y;
if (screen_get_clip(scr, &clip_box))
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;
@@ -546,6 +726,8 @@ void screen_draw_line_wu_float(screen_t *scr,
if (line_clip(&clip_box, &x0, &y0, &x1, &y1) == 0)
return;
+ screen_get_bounds(scr, &bounds);
+
dx = fx1 - fx0;
dy = fy1 - fy0;
@@ -611,7 +793,18 @@ void screen_draw_line_wu_float(screen_t *scr,
/* mid points */
- for (x = ix0 + 1; x < ix1; x++)
+ /* Bound the loop to the screen. Skipped steps are fast-forwarded through
+ * the gradient in closed form, so the pixels drawn stay a function of the
+ * true endpoints alone: the screen bounds, unlike the clip box, are the
+ * same on every call. */
+ xlo = steep ? bounds.y0 : bounds.x0;
+ xhi = steep ? bounds.y1 : bounds.x1;
+ xstart = MAX(ix0 + 1, xlo - 1);
+ xstop = MIN(ix1, xhi + 1);
+
+ yf += grad * (float) (xstart - (ix0 + 1));
+
+ for (x = xstart; x < xstop; x++)
{
y = floorf(yf);
alpha1 = 255.0f * (y + 1.0f - yf);
diff --git a/libraries/framebuf/screen/screen-fill-hline.c b/libraries/framebuf/screen/screen-fill-hline.c
new file mode 100644
index 00000000..45403dac
--- /dev/null
+++ b/libraries/framebuf/screen/screen-fill-hline.c
@@ -0,0 +1,50 @@
+/* framebuf/screen/screen-fill-hline.c -- horizontal run fill */
+
+#include
+#include
+
+#include "framebuf/colour.h"
+
+#include "framebuf/screen.h"
+
+/* ----------------------------------------------------------------------- */
+
+void screen_fill_hline(screen_t *scr, int x, int y, int w, colour_t colour)
+{
+ box_t clip_box;
+ box_t run_box;
+ box_t draw_box;
+ int clipped_width;
+ pixelfmt_any_t fmt;
+ unsigned char *rowp;
+
+ if (w <= 0)
+ return;
+
+ if (screen_get_clip(scr, &clip_box))
+ return; /* invalid clipped screen */
+
+ run_box.x0 = x;
+ run_box.y0 = y;
+ run_box.x1 = x + w;
+ run_box.y1 = y + 1;
+ if (box_intersection(&clip_box, &run_box, &draw_box))
+ return;
+
+ clipped_width = draw_box.x1 - draw_box.x0;
+
+ fmt = colour_to_pixel(scr->palette,
+ (scr->format == pixelfmt_p4) ? 16 : 0,
+ colour, scr->format);
+
+ /* the per-format run fill lives in the span table; "first" lets it address
+ * an odd P4 nibble so this needn't pack. scr->span is NULL for a pixelfmt_t
+ * with no span-registry entry -- the assert catches that in debug builds,
+ * but a release build must still no-op rather than dereference NULL. */
+ assert(scr->span && scr->span->fill);
+ if (scr->span == NULL)
+ return;
+
+ rowp = (unsigned char *) scr->base + draw_box.y0 * scr->rowbytes;
+ scr->span->fill(rowp, draw_box.x0, fmt, clipped_width);
+}
diff --git a/libraries/framebuf/screen/screen-fill-pattern.c b/libraries/framebuf/screen/screen-fill-pattern.c
new file mode 100644
index 00000000..6d83bd89
--- /dev/null
+++ b/libraries/framebuf/screen/screen-fill-pattern.c
@@ -0,0 +1,165 @@
+/* framebuf/screen/screen-fill-pattern.c -- fill a box with a repeating 8x8 pattern */
+
+#include
+#include
+
+#include "base/utils.h"
+#include "framebuf/colour.h"
+#include "framebuf/pattern.h"
+#include "framebuf/pixelfmt.h"
+#include "geom/box.h"
+
+#include "framebuf/screen.h"
+
+/* runs[row][col] is the pattern's expanded colour for tile row "row" at
+ * screen column (draw_box.x0 + col), i.e. already phase-shifted for x. The
+ * scanline helpers below just index it; stencil paths re-test the pattern
+ * bit per pixel rather than using the run. */
+typedef pixelfmt_any_t pattern_runs_t[8][8];
+
+static void screen_fill_pattern_p4(screen_t *scr,
+ const pattern_t *pattern,
+ const box_t *draw_box,
+ int stencil,
+ const pattern_runs_t runs)
+{
+ unsigned char *rowp;
+ int row, col, x, y;
+
+ rowp = (unsigned char *) scr->base + draw_box->y0 * scr->rowbytes;
+ for (y = draw_box->y0; y < draw_box->y1; y++)
+ {
+ const pixelfmt_any_t *run;
+ uint8_t bits;
+
+ row = (y - pattern->origin.y) & 7;
+ run = runs[row];
+ bits = pattern->bits[row];
+ col = 0;
+ for (x = draw_box->x0; x < draw_box->x1; x++)
+ {
+ if (!stencil ||
+ (bits & (0x80u >> ((x - pattern->origin.x) & 7))))
+ {
+ unsigned char *scrp;
+ int shift;
+
+ scrp = rowp + (x >> 1);
+ shift = (x & 1) * 4;
+
+ *scrp = (unsigned char) ((*scrp & ~(0xF << shift)) |
+ ((run[col] & 0xF) << shift));
+ }
+ col = (col + 1) & 7;
+ }
+ rowp += scr->rowbytes;
+ }
+}
+
+static void screen_fill_pattern_32(screen_t *scr,
+ const pattern_t *pattern,
+ const box_t *draw_box,
+ int stencil,
+ pixelfmt_any_t fg_fmt,
+ const pattern_runs_t runs)
+{
+ unsigned char *rowp;
+ int row, col, x, y;
+
+ rowp = (unsigned char *) scr->base + draw_box->y0 * scr->rowbytes;
+ for (y = draw_box->y0; y < draw_box->y1; y++)
+ {
+ const pixelfmt_any_t *run;
+ uint8_t bits;
+ pixelfmt_any32_t *scrp;
+ int w;
+ int n;
+
+ row = (y - pattern->origin.y) & 7;
+ run = runs[row];
+ bits = pattern->bits[row];
+ scrp = (pixelfmt_any32_t *) rowp + draw_box->x0;
+ w = draw_box->x1 - draw_box->x0;
+
+ if (!stencil)
+ {
+ /* leading partial tile up to an 8-pixel boundary, then whole runs */
+ col = 0;
+ while (w > 0)
+ {
+ n = MIN(8 - col, w);
+ memcpy(scrp, run + col, (size_t) n * sizeof(*scrp));
+ scrp += n;
+ w -= n;
+ col = 0;
+ }
+ }
+ else
+ {
+ for (x = draw_box->x0; x < draw_box->x1; x++)
+ {
+ if (bits & (0x80u >> ((x - pattern->origin.x) & 7)))
+ *scrp = fg_fmt;
+ scrp++;
+ }
+ }
+ rowp += scr->rowbytes;
+ }
+}
+
+void screen_fill_pattern(screen_t *scr,
+ const box_t *box,
+ const pattern_t *pattern)
+{
+ box_t clip_box;
+ box_t draw_box;
+ int stencil;
+ pixelfmt_any_t fg_fmt, bg_fmt;
+ pattern_runs_t runs; /* one expanded colour run per tile row */
+ int xphase;
+ int row, col;
+
+ assert(scr);
+ assert(pattern);
+
+ if (screen_get_clip(scr, &clip_box))
+ return; /* invalid clipped screen */
+
+ if (box_intersection(&clip_box, box, &draw_box))
+ return; /* nothing visible */
+
+ stencil = (pattern->flags & pattern_FLAG_STENCIL) != 0;
+
+ fg_fmt = colour_to_pixel(scr->palette,
+ (scr->format == pixelfmt_p4) ? 16 : 0,
+ pattern->fg, scr->format);
+ bg_fmt = colour_to_pixel(scr->palette,
+ (scr->format == pixelfmt_p4) ? 16 : 0,
+ pattern->bg, scr->format);
+
+ xphase = ((draw_box.x0 - pattern->origin.x) & 7);
+ for (row = 0; row < 8; row++)
+ {
+ uint8_t bits;
+
+ bits = pattern->bits[row];
+ for (col = 0; col < 8; col++)
+ runs[row][col] =
+ (bits & (0x80u >> ((xphase + col) & 7))) ? fg_fmt : bg_fmt;
+ }
+
+ switch (pixelfmt_log2bpp(scr->format))
+ {
+ case 2:
+ screen_fill_pattern_p4(scr, pattern, &draw_box, stencil, runs);
+ break;
+
+ case 5:
+ screen_fill_pattern_32(scr, pattern, &draw_box, stencil, fg_fmt, runs);
+ break;
+
+ default:
+ assert(!"Unimplemented pixel format");
+ break;
+ }
+}
diff --git a/libraries/framebuf/screen/screen.c b/libraries/framebuf/screen/screen.c
index 578ba073..7cac1f59 100644
--- a/libraries/framebuf/screen/screen.c
+++ b/libraries/framebuf/screen/screen.c
@@ -1,4 +1,4 @@
-/* screen.c */
+/* framebuf/screen/screen.c */
#include
#include
@@ -15,8 +15,7 @@
#include "framebuf/screen.h"
void screen_init(screen_t *scr,
- int width,
- int height,
+ size2d_t size,
pixelfmt_t fmt,
int rowbytes,
colour_t *palette,
@@ -24,8 +23,7 @@ void screen_init(screen_t *scr,
{
assert(scr);
- scr->width = width;
- scr->height = height;
+ scr->size = size;
scr->format = fmt;
scr->rowbytes = rowbytes;
scr->palette = palette; // FIXME: This doesn't clone the palette, whereas bitmap_init()'s equivalent does.
@@ -48,8 +46,8 @@ int screen_get_clip(const screen_t *scr, box_t *clip)
{
clip->x0 = 0;
clip->y0 = 0;
- clip->x1 = scr->width;
- clip->y1 = scr->height;
+ clip->x1 = scr->size.w;
+ clip->y1 = scr->size.h;
if (box_is_empty(&scr->clip))
return 0; /* not empty */
diff --git a/libraries/framebuf/screen/test/screen-test.c b/libraries/framebuf/screen/test/screen-test.c
new file mode 100644
index 00000000..7fec322c
--- /dev/null
+++ b/libraries/framebuf/screen/test/screen-test.c
@@ -0,0 +1,776 @@
+/* framebuf/screen/test/screen-test.c -- test screen drawing */
+
+#include
+#include
+
+#include "base/result.h"
+#include "base/utils.h"
+#include "framebuf/bitmap.h"
+#include "framebuf/colour.h"
+#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"
+
+/* ----------------------------------------------------------------------- */
+
+#define WIDTH 64
+#define HEIGHT 64
+
+#define BACKGROUND 0xFF000000
+
+typedef struct testscreen
+{
+ screen_t scr;
+ pixelfmt_bgrx8888_t pixels[WIDTH * HEIGHT];
+}
+testscreen_t;
+
+typedef enum linekind
+{
+ linekind_INT,
+ linekind_WU_FIX8,
+ linekind_WU_FLOAT
+}
+linekind_t;
+
+typedef struct linetest
+{
+ int x0, y0, x1, y1;
+}
+linetest_t;
+
+/* Rectangles which, taken together, cover the whole canvas without overlap.
+ * Three separate partitions: vertical strips, horizontal strips, and an
+ * irregular split, mirroring the pieces wuss__clip_to_visible() generates. */
+typedef struct partition
+{
+ int nboxes;
+ box_t boxes[6];
+}
+partition_t;
+
+static const partition_t partitions[] =
+{
+ { 3, { { 0, 0, 20, 64 }, { 20, 0, 41, 64 }, { 41, 0, 64, 64 } } },
+ { 3, { { 0, 0, 64, 13 }, { 0, 13, 64, 47 }, { 0, 47, 64, 64 } } },
+ { 5, { { 0, 0, 64, 17 }, { 0, 17, 9, 64 }, { 9, 17, 33, 40 },
+ { 33, 17, 64, 40 }, { 9, 40, 64, 64 } } }
+};
+
+static const linetest_t lines[] =
+{
+ { 4, 32, 60, 32 }, /* horizontal */
+ { 32, 4, 32, 60 }, /* vertical */
+ { 4, 4, 60, 60 }, /* 45 degrees */
+ { 60, 60, 4, 4 }, /* 45 degrees, reversed */
+ { 2, 10, 62, 30 }, /* shallow */
+ { 62, 30, 2, 10 }, /* shallow, reversed */
+ { 10, 2, 30, 62 }, /* steep */
+ { 30, 62, 10, 2 }, /* steep, reversed */
+ { 4, 13, 60, 13 }, /* lands on a partition boundary */
+ { 20, 0, 20, 64 }, /* lands on a partition boundary */
+ { -30, 20, 90, 44 }, /* partially outside */
+ { 20, -40, 44, 100 }, /* partially outside */
+ { -20, -20, -5, -5 } /* wholly outside */
+};
+
+/* ----------------------------------------------------------------------- */
+
+static void testscreen_init(testscreen_t *ts)
+{
+ int i;
+
+ for (i = 0; i < WIDTH * HEIGHT; i++)
+ ts->pixels[i] = BACKGROUND;
+
+ screen_init(&ts->scr, SIZE2D(WIDTH, HEIGHT),
+ pixelfmt_bgrx8888,
+ WIDTH * (int) sizeof(ts->pixels[0]),
+ NULL,
+ ts->pixels);
+}
+
+static void draw(screen_t *scr,
+ linekind_t kind,
+ const linetest_t *line,
+ colour_t colour)
+{
+ switch (kind)
+ {
+ case linekind_INT:
+ screen_draw_line(scr, line->x0, line->y0, line->x1, line->y1, colour);
+ break;
+
+ case linekind_WU_FIX8:
+ /* multiply, not INT_TO_FIX8: coords may be negative (left shift is UB). */
+ screen_draw_line_wu_fix8(scr,
+ line->x0 * FIX8_ONE, line->y0 * FIX8_ONE,
+ line->x1 * FIX8_ONE, line->y1 * FIX8_ONE,
+ colour);
+ break;
+
+ case linekind_WU_FLOAT:
+ screen_draw_line_wu_float(scr,
+ (float) line->x0, (float) line->y0,
+ (float) line->x1, (float) line->y1,
+ colour);
+ break;
+ }
+}
+
+/* ----------------------------------------------------------------------- */
+
+/* The same logical line drawn in one go, and drawn once per piece of a
+ * partition of the canvas, must produce identical pixels. */
+static result_t test_clip_invariance(void)
+{
+ static testscreen_t reference;
+ static testscreen_t pieced;
+
+ colour_t colour;
+ size_t l, p, k;
+ int b;
+
+ colour = colour_rgb(255, 255, 255);
+
+ for (k = 0; k < 3; k++)
+ for (l = 0; l < NELEMS(lines); l++)
+ {
+ testscreen_init(&reference);
+ draw(&reference.scr, (linekind_t) k, &lines[l], colour);
+
+ for (p = 0; p < NELEMS(partitions); p++)
+ {
+ testscreen_init(&pieced);
+
+ for (b = 0; b < partitions[p].nboxes; b++)
+ {
+ pieced.scr.clip = partitions[p].boxes[b];
+ draw(&pieced.scr, (linekind_t) k, &lines[l], colour);
+ }
+
+ if (memcmp(reference.pixels, pieced.pixels, sizeof(reference.pixels)))
+ {
+ printf("screen: clip invariance failed for line %zu, "
+ "rasterizer %zu, partition %zu\n", l, k, p);
+ return result_TEST_FAILED;
+ }
+ }
+ }
+
+ return result_TEST_PASSED;
+}
+
+/* Guard against the test above passing vacuously because clipping stopped
+ * happening at all: pixels outside the clip box must stay untouched. */
+static result_t test_clipping_still_happens(void)
+{
+ static const box_t clip = { 0, 0, 64, 20 };
+
+ static testscreen_t ts;
+
+ colour_t colour;
+ size_t k;
+ int x, y;
+
+ colour = colour_rgb(255, 255, 255);
+
+ for (k = 0; k < 3; k++)
+ {
+ const linetest_t line = { 4, 4, 60, 60 }; /* crosses the clip boundary */
+
+ testscreen_init(&ts);
+ ts.scr.clip = clip;
+ draw(&ts.scr, (linekind_t) k, &line, colour);
+
+ for (y = 0; y < HEIGHT; y++)
+ for (x = 0; x < WIDTH; x++)
+ {
+ if (box_contains_point(&clip, x, y))
+ continue;
+
+ if (ts.pixels[y * WIDTH + x] != BACKGROUND)
+ {
+ printf("screen: pixel (%d,%d) drawn outside the clip box "
+ "by rasterizer %zu\n", x, y, k);
+ return result_TEST_FAILED;
+ }
+ }
+ }
+
+ return result_TEST_PASSED;
+}
+
+/* ----------------------------------------------------------------------- */
+
+/* Wu fix8 lines with large and off-screen endpoints must not trip
+ * UndefinedBehaviorSanitizer: the gradient maths once overflowed 32-bit int
+ * (FIX16_ONE * dy_f8) and left-shifted negative pixel coordinates. */
+static result_t test_wu_fix8_extreme_coords(void)
+{
+ /* fix8: value * 256, written out to avoid left-shifting negatives here too. */
+ static const fix8_t endpoints[][4] =
+ {
+ { -1000 * 256, 32 * 256, 2000 * 256, 33 * 256 },
+ { 32 * 256, -1000 * 256, 31 * 256, 2000 * 256 },
+ { -5000 * 256, -5000 * 256, 5000 * 256, 5000 * 256 },
+ { -32000 * 256, 10 * 256, 32000 * 256, 50 * 256 }
+ };
+
+ static testscreen_t ts;
+
+ colour_t colour;
+ size_t i;
+
+ colour = colour_rgb(255, 255, 255);
+
+ for (i = 0; i < NELEMS(endpoints); i++)
+ {
+ testscreen_init(&ts);
+ screen_draw_line_wu_fix8(&ts.scr,
+ endpoints[i][0], endpoints[i][1],
+ endpoints[i][2], endpoints[i][3],
+ colour);
+ }
+
+ return result_TEST_PASSED;
+}
+
+/* ----------------------------------------------------------------------- */
+
+/* 9x9 source: each 3x3 cell a distinct solid colour, indexed [row][col]. */
+#define NP_SRC 9
+#define NP_CELL 3
+
+static const int np_rgb[3][3][3] =
+{
+ { { 255, 0, 0 }, { 255, 255, 0 }, { 0, 255, 0 } },
+ { { 0, 255, 255 }, { 128, 128, 128 }, { 0, 0, 255 } },
+ { { 255, 0, 255 }, { 255, 255, 255 }, { 64, 64, 64 } }
+};
+
+/* Encode an rgb colour to a screen pixel the same way the draw path does. */
+static pixelfmt_bgrx8888_t np_encode(testscreen_t *ts, int r, int g, int b)
+{
+ testscreen_init(ts);
+ screen_set_pixel(&ts->scr, 0, 0, colour_rgb(r, g, b));
+ return ts->pixels[0];
+}
+
+static void np_make_src(bitmap_t *src, pixelfmt_rgba8888_t *buf)
+{
+ int cx, cy, x, y;
+
+ for (cy = 0; cy < 3; cy++)
+ for (cx = 0; cx < 3; cx++)
+ {
+ colour_t c;
+
+ c = colour_rgb(np_rgb[cy][cx][0], np_rgb[cy][cx][1], np_rgb[cy][cx][2]);
+
+ for (y = 0; y < NP_CELL; y++)
+ for (x = 0; x < NP_CELL; x++)
+ buf[(cy * NP_CELL + y) * NP_SRC + (cx * NP_CELL + x)] = c.primary;
+ }
+
+ bitmap_init(src, SIZE2D(NP_SRC, NP_SRC), pixelfmt_rgba8888,
+ NP_SRC * (int) sizeof(buf[0]), NULL, buf);
+}
+
+static int np_at(testscreen_t *ts, int x, int y)
+{
+ return (int) ts->pixels[y * WIDTH + x];
+}
+
+static result_t test_ninepatch(void)
+{
+ static testscreen_t ts;
+ static testscreen_t enc;
+ static pixelfmt_rgba8888_t srcbuf[NP_SRC * NP_SRC];
+
+ bitmap_t src;
+ box_t dst = { 5, 5, 45, 45 };
+ int exp[3][3];
+ int cx, cy;
+
+ np_make_src(&src, srcbuf);
+
+ for (cy = 0; cy < 3; cy++)
+ for (cx = 0; cx < 3; cx++)
+ exp[cy][cx] = (int) np_encode(&enc,
+ np_rgb[cy][cx][0],
+ np_rgb[cy][cx][1],
+ np_rgb[cy][cx][2]);
+
+ /* Normal case. */
+ testscreen_init(&ts);
+ screen_copy_ninepatch(&ts.scr, &dst, &src, 0);
+
+ /* Corners: the 3x3 block at each destination corner is that corner colour. */
+ if (np_at(&ts, 5, 5) != exp[0][0] || np_at(&ts, 7, 7) != exp[0][0] ||
+ np_at(&ts, 44, 5) != exp[0][2] || np_at(&ts, 42, 7) != exp[0][2] ||
+ np_at(&ts, 5, 44) != exp[2][0] || np_at(&ts, 7, 42) != exp[2][0] ||
+ np_at(&ts, 44, 44) != exp[2][2] || np_at(&ts, 42, 42) != exp[2][2])
+ {
+ printf("screen: ninepatch corner mismatch\n");
+ return result_TEST_FAILED;
+ }
+
+ /* Mid-edge and interior. */
+ if (np_at(&ts, 25, 6) != exp[0][1] || /* top edge */
+ np_at(&ts, 25, 43) != exp[2][1] || /* bottom edge */
+ np_at(&ts, 6, 25) != exp[1][0] || /* left edge */
+ np_at(&ts, 43, 25) != exp[1][2] || /* right edge */
+ np_at(&ts, 25, 25) != exp[1][1]) /* centre */
+ {
+ printf("screen: ninepatch edge/centre mismatch\n");
+ return result_TEST_FAILED;
+ }
+
+ /* Clipping: a pixel just outside dst stays background. */
+ if (np_at(&ts, 4, 4) != (int) (pixelfmt_bgrx8888_t) BACKGROUND ||
+ np_at(&ts, 45, 45) != (int) (pixelfmt_bgrx8888_t) BACKGROUND)
+ {
+ printf("screen: ninepatch drew outside dst\n");
+ return result_TEST_FAILED;
+ }
+
+ /* Clip composition: restrict to the left half, the right half is untouched. */
+ testscreen_init(&ts);
+ ts.scr.clip = (box_t) { 0, 0, 25, 64 };
+ screen_copy_ninepatch(&ts.scr, &dst, &src, 0);
+ if (np_at(&ts, 6, 25) != exp[1][0] ||
+ np_at(&ts, 30, 25) != (int) (pixelfmt_bgrx8888_t) BACKGROUND)
+ {
+ printf("screen: ninepatch ignored the screen clip\n");
+ return result_TEST_FAILED;
+ }
+ /* The clip is restored on return. */
+ if (!box_is_empty(&ts.scr.clip) &&
+ (ts.scr.clip.x0 != 0 || ts.scr.clip.x1 != 25))
+ {
+ printf("screen: ninepatch did not restore the clip\n");
+ return result_TEST_FAILED;
+ }
+
+ /* Degenerate: dst exactly two cells each way -> only corners, no centre. */
+ testscreen_init(&ts);
+ {
+ box_t small = { 10, 10, 10 + 2 * NP_CELL, 10 + 2 * NP_CELL };
+
+ screen_copy_ninepatch(&ts.scr, &small, &src, 0);
+ if (np_at(&ts, 10, 10) != exp[0][0] ||
+ np_at(&ts, 15, 15) != exp[2][2] ||
+ np_at(&ts, 12, 12) == exp[1][1]) /* centre colour must NOT appear */
+ {
+ printf("screen: ninepatch degenerate case wrong\n");
+ return result_TEST_FAILED;
+ }
+ }
+
+ /* NO_CENTRE: border drawn, interior stays background. */
+ testscreen_init(&ts);
+ screen_copy_ninepatch(&ts.scr, &dst, &src, screen_NINEPATCH_NO_CENTRE);
+ if (np_at(&ts, 5, 5) != exp[0][0] || /* corner still drawn */
+ np_at(&ts, 25, 6) != exp[0][1] || /* edge still drawn */
+ np_at(&ts, 25, 25) != (int) (pixelfmt_bgrx8888_t) BACKGROUND) /* centre skipped */
+ {
+ printf("screen: ninepatch NO_CENTRE wrong\n");
+ return result_TEST_FAILED;
+ }
+
+ return result_TEST_PASSED;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static result_t test_fill_pattern(void)
+{
+ static testscreen_t ts;
+ static testscreen_t enc;
+
+ box_t box = { 8, 8, 24, 24 };
+ int fg, bg;
+
+ fg = (int) np_encode(&enc, 255, 0, 0);
+ bg = (int) np_encode(&enc, 0, 0, 255);
+
+ /* GREY50 is 0xAA,0x55,... : at origin (0,0) pixel (x,y) is fg when
+ * ((x ^ y) & 1) == 0. */
+ testscreen_init(&ts);
+ {
+ pattern_t pat = pattern_from_preset(screen_PATTERN_GREY50,
+ colour_rgb(255, 0, 0),
+ colour_rgb(0, 0, 255));
+ screen_fill_pattern(&ts.scr, &box, &pat);
+ }
+
+ if (np_at(&ts, 8, 8) != fg || /* (0,0) phase -> set bit */
+ np_at(&ts, 9, 8) != bg ||
+ np_at(&ts, 8, 9) != bg ||
+ np_at(&ts, 9, 9) != fg)
+ {
+ printf("screen: fill_pattern GREY50 wrong at origin 0\n");
+ return result_TEST_FAILED;
+ }
+
+ /* Outside the box stays background. */
+ if (np_at(&ts, 7, 7) != (int) (pixelfmt_bgrx8888_t) BACKGROUND ||
+ np_at(&ts, 24, 24) != (int) (pixelfmt_bgrx8888_t) BACKGROUND)
+ {
+ printf("screen: fill_pattern drew outside the box\n");
+ return result_TEST_FAILED;
+ }
+
+ /* Shift the origin by one in x: every pixel's phase flips, so the same
+ * screen coordinate takes the other colour. */
+ testscreen_init(&ts);
+ {
+ pattern_t pat = pattern_from_preset(screen_PATTERN_GREY50,
+ colour_rgb(255, 0, 0),
+ colour_rgb(0, 0, 255));
+ pat.origin = POINT(1, 0);
+ screen_fill_pattern(&ts.scr, &box, &pat);
+ }
+ if (np_at(&ts, 8, 8) != bg || np_at(&ts, 9, 8) != fg)
+ {
+ printf("screen: fill_pattern ignored origin phase\n");
+ return result_TEST_FAILED;
+ }
+
+ /* Honours the screen clip. */
+ testscreen_init(&ts);
+ ts.scr.clip = (box_t) { 0, 0, 16, 64 };
+ {
+ pattern_t pat = pattern_from_preset(screen_PATTERN_SOLID,
+ colour_rgb(255, 0, 0),
+ colour_rgb(0, 0, 255));
+ screen_fill_pattern(&ts.scr, &box, &pat);
+ }
+ if (np_at(&ts, 10, 10) != fg ||
+ np_at(&ts, 20, 10) != (int) (pixelfmt_bgrx8888_t) BACKGROUND)
+ {
+ printf("screen: fill_pattern ignored the screen clip\n");
+ return result_TEST_FAILED;
+ }
+
+ return result_TEST_PASSED;
+}
+
+/* ----------------------------------------------------------------------- */
+
+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;
+}
+
+/* ----------------------------------------------------------------------- */
+
+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;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static result_t test_draw_circle(void)
+{
+ static testscreen_t ts;
+ static testscreen_t enc;
+
+ int fg, bg;
+
+ fg = (int) np_encode(&enc, 0, 255, 0);
+ bg = (int) (pixelfmt_bgrx8888_t) BACKGROUND;
+
+ /* r=10 about (32,32): the four axis points lie on the circle, the centre
+ * and a point just inside the rim do not. */
+ testscreen_init(&ts);
+ screen_draw_circle(&ts.scr, 32, 32, 10, colour_rgb(0, 255, 0));
+
+ if (np_at(&ts, 42, 32) != fg || np_at(&ts, 22, 32) != fg ||
+ np_at(&ts, 32, 42) != fg || np_at(&ts, 32, 22) != fg)
+ {
+ printf("screen: draw_circle missing an axis point\n");
+ return result_TEST_FAILED;
+ }
+ if (np_at(&ts, 32, 32) != bg || np_at(&ts, 38, 32) != bg)
+ {
+ printf("screen: draw_circle filled its interior\n");
+ return result_TEST_FAILED;
+ }
+
+ /* r=0 draws the centre pixel only. */
+ testscreen_init(&ts);
+ screen_draw_circle(&ts.scr, 5, 5, 0, colour_rgb(0, 255, 0));
+ if (np_at(&ts, 5, 5) != fg || np_at(&ts, 6, 5) != bg)
+ {
+ printf("screen: draw_circle r=0 wrong\n");
+ return result_TEST_FAILED;
+ }
+
+ /* Honours the screen clip: right half of the circle is masked off. */
+ testscreen_init(&ts);
+ ts.scr.clip = (box_t) { 0, 0, 32, 64 };
+ screen_draw_circle(&ts.scr, 32, 32, 10, colour_rgb(0, 255, 0));
+ if (np_at(&ts, 22, 32) != fg || np_at(&ts, 42, 32) != bg)
+ {
+ printf("screen: draw_circle ignored the screen clip\n");
+ return result_TEST_FAILED;
+ }
+
+ return result_TEST_PASSED;
+}
+
+/* ----------------------------------------------------------------------- */
+
+static result_t test_fill_circle(void)
+{
+ static testscreen_t ts;
+ static testscreen_t enc;
+
+ int fg, bg;
+
+ fg = (int) np_encode(&enc, 0, 255, 0);
+ bg = (int) (pixelfmt_bgrx8888_t) BACKGROUND;
+
+ /* r=10 about (32,32): centre, rim and axis extremes lit; one pixel beyond
+ * the rim is background. */
+ testscreen_init(&ts);
+ screen_fill_circle(&ts.scr, 32, 32, 10, colour_rgb(0, 255, 0));
+
+ if (np_at(&ts, 32, 32) != fg || /* centre */
+ np_at(&ts, 38, 32) != fg || /* interior */
+ np_at(&ts, 42, 32) != fg || np_at(&ts, 22, 32) != fg ||
+ np_at(&ts, 32, 42) != fg || np_at(&ts, 32, 22) != fg)
+ {
+ printf("screen: fill_circle left a hole\n");
+ return result_TEST_FAILED;
+ }
+ if (np_at(&ts, 43, 32) != bg || np_at(&ts, 32, 43) != bg)
+ {
+ printf("screen: fill_circle spilled past the rim\n");
+ return result_TEST_FAILED;
+ }
+
+ /* Honours the screen clip. */
+ testscreen_init(&ts);
+ ts.scr.clip = (box_t) { 0, 0, 32, 64 };
+ screen_fill_circle(&ts.scr, 32, 32, 10, colour_rgb(0, 255, 0));
+ if (np_at(&ts, 28, 32) != fg || np_at(&ts, 36, 32) != bg)
+ {
+ printf("screen: fill_circle 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);
+
+ static const screentestfn tests[] =
+ {
+ test_clip_invariance,
+ test_clipping_still_happens,
+ test_wu_fix8_extreme_coords,
+ test_ninepatch,
+ test_fill_pattern,
+ test_dashed_line,
+ test_draw_lines,
+ test_draw_rect,
+ test_draw_circle,
+ test_fill_circle
+ };
+
+ result_t rc;
+ size_t i;
+ int nfailures;
+
+ NOT_USED(resources);
+
+ nfailures = 0;
+ for (i = 0; i < NELEMS(tests); i++)
+ {
+ rc = tests[i]();
+ if (rc != result_TEST_PASSED)
+ nfailures++;
+ }
+
+ return (nfailures == 0) ? result_TEST_PASSED : result_TEST_FAILED;
+}
diff --git a/libraries/framebuf/span-registry/get.c b/libraries/framebuf/span-registry/get.c
index b68e5501..52f99df7 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
diff --git a/libraries/framebuf/span-registry/regdata.h b/libraries/framebuf/span-registry/regdata.h
index aae1c27f..37b3fe15 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 0306fed6..9d34b5f1 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 9f9f2f04..5d2e06be 100644
--- a/libraries/framebuf/span/all8888.c
+++ b/libraries/framebuf/span/all8888.c
@@ -1,4 +1,4 @@
-/* all8888.c */
+/* framebuf/span/all8888.c */
#include
@@ -11,3 +11,15 @@ void span_all8888_copy(void *dst, const void *src, int length)
memcpy(dst, src, length * 4);
}
+
+void span_all8888_fill(void *dst,
+ int first,
+ pixelfmt_any_t pixel,
+ int length)
+{
+ pixelfmt_any32_t *p;
+
+ p = (pixelfmt_any32_t *) dst + first;
+ while (length--)
+ *p++ = pixel;
+}
diff --git a/libraries/framebuf/span/all8888.h b/libraries/framebuf/span/all8888.h
index f5b285b4..6984c05c 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
@@ -6,5 +6,6 @@
#include "framebuf/span.h"
span_copy_t span_all8888_copy;
+span_fill_t span_all8888_fill;
#endif /* SPAN_ALL8888_H */
diff --git a/libraries/framebuf/span/bgrx8888.c b/libraries/framebuf/span/bgrx8888.c
index 25aaea02..da51fc13 100644
--- a/libraries/framebuf/span/bgrx8888.c
+++ b/libraries/framebuf/span/bgrx8888.c
@@ -1,4 +1,4 @@
-/* bgrx8888.c */
+/* framebuf/span/bgrx8888.c */
#include
@@ -24,6 +24,7 @@ const span_t span_bgrx8888 =
{
pixelfmt_bgrx8888,
span_all8888_copy,
+ span_all8888_fill,
span_bgrx8888_blendconst,
span_bgrx8888_blendarray,
};
diff --git a/libraries/framebuf/span/p4.c b/libraries/framebuf/span/p4.c
index eb401a8d..e612bce2 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
@@ -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,
@@ -67,10 +67,38 @@ static void span_p4_blendconst(void *vdst,
}
}
+/* unlike span_p4_blendconst above, this works directly on packed screen
+ * bytes: "dst" is the row base, "first" the nibble (pixel) index into it, so
+ * odd start columns and odd lengths are handled without the caller packing.
+ * "pixel" is a palette index in its low nibble. */
+static void span_p4_fill(void *vdst,
+ int first,
+ pixelfmt_any_t pixel,
+ int length)
+{
+ unsigned char *base;
+ unsigned char nib;
+ int x;
+
+ base = vdst;
+ nib = (unsigned char) (pixel & 0xF);
+
+ for (x = first; x < first + length; x++)
+ {
+ unsigned char *p;
+ int shift;
+
+ p = base + (x >> 1);
+ shift = (x & 1) * 4;
+ *p = (unsigned char) ((*p & ~(0xF << shift)) | (nib << shift));
+ }
+}
+
const span_t span_p4 =
{
pixelfmt_p4,
NULL, /* copy: unneeded so far (nibble packing makes a generic array copy awkward) */
+ span_p4_fill,
span_p4_blendconst,
NULL, /* blendarray: unneeded so far */
};
diff --git a/libraries/framebuf/span/rgbx8888.c b/libraries/framebuf/span/rgbx8888.c
index 01424443..1b6358ad 100644
--- a/libraries/framebuf/span/rgbx8888.c
+++ b/libraries/framebuf/span/rgbx8888.c
@@ -1,4 +1,4 @@
-/* rgbx8888.c */
+/* framebuf/span/rgbx8888.c */
#include
@@ -24,6 +24,7 @@ const span_t span_rgbx8888 =
{
pixelfmt_rgbx8888,
span_all8888_copy,
+ span_all8888_fill,
span_rgbx8888_blendconst,
span_rgbx8888_blendarray,
};
diff --git a/libraries/framebuf/span/xbgr8888.c b/libraries/framebuf/span/xbgr8888.c
index 1dc85442..b64897e0 100644
--- a/libraries/framebuf/span/xbgr8888.c
+++ b/libraries/framebuf/span/xbgr8888.c
@@ -1,4 +1,4 @@
-/* xbgr8888.c */
+/* framebuf/span/xbgr8888.c */
#include
@@ -24,6 +24,7 @@ const span_t span_xbgr8888 =
{
pixelfmt_xbgr8888,
span_all8888_copy,
+ span_all8888_fill,
span_xbgr8888_blendconst,
span_xbgr8888_blendarray,
};
diff --git a/libraries/geom/box/clipped.c b/libraries/geom/box/clipped.c
index 0348ba90..fc575f6b 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 868ac77e..83b206b7 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 b13ebf97..650f225c 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 203c5a5c..f787ec9d 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 39005c06..9d9f390c 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
diff --git a/libraries/geom/box/grow.c b/libraries/geom/box/grow.c
index dfbcf6d7..e07aee9a 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 4d1bdf7a..d8af0083 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 c9e6b686..e4360008 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 8fcaedb2..57d5f14e 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 0a785b2f..88286d20 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
diff --git a/libraries/geom/box/round.c b/libraries/geom/box/round.c
index 6979b8a5..e9838983 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 89ed7bf1..66ee929d 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
new file mode 100644
index 00000000..4a4d7710
--- /dev/null
+++ b/libraries/geom/box/size.c
@@ -0,0 +1,14 @@
+/* geom/box/size.c -- return the size of the specified box */
+
+#include "geom/box.h"
+#include "geom/size.h"
+
+size2d_t box_size(const box_t *box)
+{
+ size2d_t size;
+
+ size.w = box->x1 - box->x0;
+ size.h = box->y1 - box->y0;
+
+ return size;
+}
diff --git a/libraries/geom/box/test/box-test.c b/libraries/geom/box/test/box-test.c
index efd8c69f..9b474abd 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
#include
diff --git a/libraries/geom/box/translated.c b/libraries/geom/box/translated.c
index f8d2d195..c84d56d4 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 36b431f5..9944ffe9 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 39081501..2c23fe1a 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
diff --git a/libraries/geom/layout/test/layout-test.c b/libraries/geom/layout/test/layout-test.c
index b1db8331..cad978ff 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
#include
diff --git a/libraries/geom/line/line.c b/libraries/geom/line/line.c
index c35898a7..96370dd3 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"
@@ -13,6 +13,60 @@ typedef unsigned int outcode_t;
#define outcode_BOTTOM (1u << 2)
#define outcode_TOP (1u << 3)
+/* Compute (a * b) / c, truncating toward zero, without overflow and
+ * without using a 64-bit or floating-point intermediate. The caller must
+ * guarantee the true result fits in an int -- quotient bits at or above
+ * bit 31 are dropped silently. Holds here: every argument is a screen
+ * coordinate or a difference of two. */
+static int muldiv(int a, int b, int c)
+{
+ unsigned int a_lo, a_hi, b_lo, b_hi;
+ unsigned int lo_lo, hi_lo, lo_hi, hi_hi;
+ unsigned int cross, cross_carry, lo_carry;
+ unsigned int hi, lo;
+ unsigned int ua, ub, uc;
+ unsigned int rem, quot, bit;
+ int neg;
+ int i;
+
+ neg = 0;
+ if (a < 0) { neg = !neg; ua = 0u - (unsigned int) a; } else ua = (unsigned int) a;
+ if (b < 0) { neg = !neg; ub = 0u - (unsigned int) b; } else ub = (unsigned int) b;
+ if (c < 0) { neg = !neg; uc = 0u - (unsigned int) c; } else uc = (unsigned int) c;
+
+ /* widen ua * ub into a 64-bit result held as two 32-bit halves */
+ a_lo = ua & 0xFFFFu; a_hi = ua >> 16;
+ b_lo = ub & 0xFFFFu; b_hi = ub >> 16;
+
+ lo_lo = a_lo * b_lo;
+ hi_lo = a_hi * b_lo;
+ lo_hi = a_lo * b_hi;
+ hi_hi = a_hi * b_hi;
+
+ cross = hi_lo + lo_hi;
+ cross_carry = (cross < hi_lo) ? (1u << 16) : 0u;
+ lo = lo_lo + (cross << 16);
+ lo_carry = (lo < lo_lo) ? 1u : 0u;
+ hi = hi_hi + (cross >> 16) + cross_carry + lo_carry;
+
+ /* long-divide the 64-bit (hi:lo) dividend by uc, one bit at a time */
+ rem = 0;
+ quot = 0;
+ for (i = 63; i >= 0; i--)
+ {
+ bit = (i >= 32) ? ((hi >> (i - 32)) & 1u) : ((lo >> i) & 1u);
+ rem = (rem << 1) | bit;
+ if (rem >= uc)
+ {
+ rem -= uc;
+ if (i < 32)
+ quot |= (1u << i);
+ }
+ }
+
+ return neg ? -(int) quot : (int) quot;
+}
+
static INLINE outcode_t compute_outcode(const box_t *clip, int x, int y)
{
outcode_t code;
@@ -74,29 +128,29 @@ int line_clip(const box_t *clip,
}
else
{
- oc = oc1 > oc0 ? oc1 : oc0;
+ oc = MAX(oc1, oc0);
w = x1 - x0;
h = y1 - y0;
if (oc & outcode_TOP)
{
- x = x0 + w * (clip->y1 - 1 - y0) / h;
+ x = x0 + muldiv(w, clip->y1 - 1 - y0, h);
y = clip->y1 - 1;
}
else if (oc & outcode_BOTTOM)
{
- x = x0 + w * (clip->y0 - y0) / h;
+ x = x0 + muldiv(w, clip->y0 - y0, h);
y = clip->y0;
}
else if (oc & outcode_RIGHT)
{
x = clip->x1 - 1;
- y = y0 + h * (clip->x1 - 1 - x0) / w;
+ y = y0 + muldiv(h, clip->x1 - 1 - x0, w);
}
else if (oc & outcode_LEFT)
{
x = clip->x0;
- y = y0 + h * (clip->x0 - x0) / w;
+ y = y0 + muldiv(h, clip->x0 - x0, w);
}
if (oc == oc0)
diff --git a/libraries/geom/packer/impl.h b/libraries/geom/packer/impl.h
index 2b3312a5..119ce176 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
@@ -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
@@ -34,6 +40,9 @@ struct packer
packer_sortdir_t order; /* order to which we have sorted */
int sorted; /* a bool */
+ int gutter; /* px reserved past each placed box's
+ inner edges by packer_place_by; 0 = none */
+
box_t consumed_area; /* total consumed area */
};
diff --git a/libraries/geom/packer/packer.c b/libraries/geom/packer/packer.c
index 59c19fe7..18cf8928 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
#include
@@ -11,6 +11,7 @@
#include "base/result.h"
#include "base/debug.h"
+#include "base/utils.h"
#include "geom/box.h"
#include "utils/array.h"
@@ -40,12 +41,18 @@ 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;
p->order = packer_SORT_TOP_LEFT; /* any will do */
p->sorted = 1;
+ p->gutter = 0;
+
p->consumed_area.x0 = INT_MAX;
p->consumed_area.y0 = INT_MAX;
p->consumed_area.x1 = INT_MIN;
@@ -59,6 +66,7 @@ void packer_destroy(packer_t *doomed)
if (doomed == NULL)
return;
+ free(doomed->placed);
free(doomed->areas);
free(doomed);
}
@@ -394,9 +402,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 */
@@ -405,7 +416,88 @@ 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;
+
+ /* 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);
+}
+
+void packer_set_gutter(packer_t *packer, int gutter)
+{
+ packer->gutter = MAX(gutter, 0);
}
result_t packer_place_by(packer_t *packer,
@@ -416,6 +508,8 @@ result_t packer_place_by(packer_t *packer,
{
result_t err;
const box_t *b;
+ int g, fw, fh;
+ box_t consume;
if (pos)
*pos = NULL;
@@ -423,11 +517,17 @@ result_t packer_place_by(packer_t *packer,
if (w == 0 || h == 0)
return result_PACKER_EMPTY;
+ /* look for a free area big enough for the box plus the gutter strip it
+ * reserves along its two inner edges */
+ g = packer->gutter;
+ fw = w + g;
+ fh = h + g;
+
for (b = packer_start(packer, (packer_sortdir_t) loc);
b;
b = packer_next(packer))
{
- if (box_could_hold(b, w, h))
+ if (box_could_hold(b, fw, fh))
{
logf_debug("packer_place_by: %s", "fits");
break;
@@ -440,18 +540,24 @@ result_t packer_place_by(packer_t *packer,
return result_PACKER_DIDNT_FIT;
}
+ /* the box sits flush in the chosen corner of the free area; the gutter is
+ * reserved on its two edges that face away from that corner */
switch (loc)
{
case packer_LOC_TOP_LEFT:
case packer_LOC_BOTTOM_LEFT:
packer->placed_area.x0 = b->x0;
packer->placed_area.x1 = b->x0 + w;
+ consume.x0 = b->x0;
+ consume.x1 = b->x0 + fw; /* box + gutter to the right */
break;
case packer_LOC_TOP_RIGHT:
case packer_LOC_BOTTOM_RIGHT:
packer->placed_area.x0 = b->x1 - w;
packer->placed_area.x1 = b->x1;
+ consume.x0 = b->x1 - fw; /* box + gutter to the left */
+ consume.x1 = b->x1;
break;
default:
@@ -464,19 +570,27 @@ result_t packer_place_by(packer_t *packer,
case packer_LOC_TOP_RIGHT:
packer->placed_area.y0 = b->y1 - h;
packer->placed_area.y1 = b->y1;
+ consume.y0 = b->y1 - fh; /* box + gutter below */
+ consume.y1 = b->y1;
break;
case packer_LOC_BOTTOM_LEFT:
case packer_LOC_BOTTOM_RIGHT:
packer->placed_area.y0 = b->y0;
packer->placed_area.y1 = b->y0 + h;
+ consume.y0 = b->y0;
+ consume.y1 = b->y0 + fh; /* box + gutter above */
break;
default:
break;
}
- err = remove_area(packer, &packer->placed_area);
+ err = remove_area(packer, &consume);
+ if (err)
+ return err;
+
+ err = note_placed(packer, &consume);
if (err)
return err;
@@ -539,6 +653,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 ba744b0b..ac0015bc 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
#include
@@ -399,6 +400,233 @@ static int test2(void)
return 1;
}
+/* packer_release: a slot handed back becomes available again. */
+static int test3(void)
+{
+ static const box_t pagedims = { 0, 0, 100, 100 };
+
+ packer_t *packer;
+ const box_t *a, *b, *c;
+ box_t freed;
+ result_t err;
+
+ printf("test3: packer_release\n");
+
+ packer = packer_create(&pagedims);
+ if (packer == NULL)
+ return 1;
+
+ /* fill the page with four 50x50 quads, top-left order */
+ err = packer_place_by(packer, packer_LOC_TOP_LEFT, 50, 50, &a);
+ err |= packer_place_by(packer, packer_LOC_TOP_LEFT, 50, 50, &b);
+ err |= packer_place_by(packer, packer_LOC_TOP_LEFT, 50, 50, &c);
+ err |= packer_place_by(packer, packer_LOC_TOP_LEFT, 50, 50, NULL);
+ if (err)
+ goto failure;
+
+ /* page is now full: a fifth 50x50 must not fit */
+ if (packer_place_by(packer, packer_LOC_TOP_LEFT, 50, 50, NULL)
+ != result_PACKER_DIDNT_FIT)
+ {
+ printf("test3: expected DIDNT_FIT while full\n");
+ goto failure;
+ }
+
+ /* release the top-left quad, then a 50x50 must fit again, in that slot */
+ freed.x0 = 0; freed.y0 = 0; freed.x1 = 50; freed.y1 = 50;
+ err = packer_release(packer, &freed);
+ if (err)
+ goto failure;
+
+ err = packer_place_by(packer, packer_LOC_TOP_LEFT, 50, 50, &a);
+ if (err)
+ {
+ printf("test3: placement after release failed (%d)\n", err);
+ goto failure;
+ }
+ if (a->x0 != 0 || a->y0 != 0 || a->x1 != 50 || a->y1 != 50)
+ {
+ printf("test3: reused slot <%d,%d-%d,%d>, wanted <0,0-50,50>\n",
+ a->x0, a->y0, a->x1, a->y1);
+ goto failure;
+ }
+
+ /* a box wholly outside the margins is rejected */
+ freed.x0 = 200; freed.y0 = 200; freed.x1 = 250; freed.y1 = 250;
+ if (packer_release(packer, &freed) != result_PACKER_EMPTY)
+ {
+ printf("test3: out-of-bounds release not rejected\n");
+ goto failure;
+ }
+
+ packer_destroy(packer);
+
+
+ /* gutter: a column just wide enough for one box plus its gutter forces the
+ * second placement to stack above the first, gutter between them */
+ {
+ static const box_t coldims = { 0, 0, 30, 200 };
+
+ box_t first;
+
+ printf("test3: packer_set_gutter\n");
+
+ packer = packer_create(&coldims);
+ if (packer == NULL)
+ return 1;
+
+ packer_set_gutter(packer, 10);
+
+ /* pos points at the packer's single result buffer, so copy the first
+ * placement out before the second overwrites it */
+ err = packer_place_by(packer, packer_LOC_BOTTOM_LEFT, 20, 20, &a);
+ if (err)
+ goto failure;
+ first = *a;
+
+ err = packer_place_by(packer, packer_LOC_BOTTOM_LEFT, 20, 20, &b);
+ if (err)
+ goto failure;
+
+ /* boxes are still 20x20 (the gutter is not added to the result)... */
+ if (first.x1 - first.x0 != 20 || first.y1 - first.y0 != 20 ||
+ b->x1 - b->x0 != 20 || b->y1 - b->y0 != 20)
+ {
+ printf("test3: gutter inflated the placed box\n");
+ goto failure;
+ }
+ /* ...but the second sits a full gutter above the first, not flush */
+ if (b->y0 - first.y1 != 10)
+ {
+ printf("test3: gap between placements was %d, wanted 10\n",
+ b->y0 - first.y1);
+ goto failure;
+ }
+ }
+
+ packer_destroy(packer);
+ 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);
+ return 1;
+}
+
result_t packer_test(const char *resources)
{
result_t err;
@@ -413,6 +641,14 @@ result_t packer_test(const char *resources)
if (err)
goto failure;
+ err = test3();
+ if (err)
+ goto failure;
+
+ err = test4();
+ if (err)
+ goto failure;
+
return result_TEST_PASSED;
diff --git a/libraries/io/path/path.c b/libraries/io/path/path.c
index d1bb657c..42acdc1c 100644
--- a/libraries/io/path/path.c
+++ b/libraries/io/path/path.c
@@ -1,4 +1,4 @@
-/* path.c */
+/* io/path/path.c */
#include
#include
diff --git a/libraries/io/stream/stream-mem.c b/libraries/io/stream/stream-mem.c
index c490504f..81ac0c0f 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
#include
diff --git a/libraries/io/stream/stream-mtfcomp.c b/libraries/io/stream/stream-mtfcomp.c
index 3c0ea8b4..e8971594 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
#include
diff --git a/libraries/io/stream/stream-packbitscomp.c b/libraries/io/stream/stream-packbitscomp.c
index e8dab061..3cb529a9 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
#include
@@ -44,7 +44,9 @@ typedef struct stream_packbitscomp
}
stream_packbitscomp_t;
-static result_t stream_packbitscomp_op(stream_t *s, stream_opcode_t op, void *arg)
+static result_t stream_packbitscomp_op(stream_t *s,
+ stream_opcode_t op,
+ void *arg)
{
NOT_USED(s);
NOT_USED(op);
diff --git a/libraries/io/stream/stream-packbitsdecomp.c b/libraries/io/stream/stream-packbitsdecomp.c
index d4a75004..3dde4a9b 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
#include
@@ -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/io/stream/stream-stdio.c b/libraries/io/stream/stream-stdio.c
index 8d6074bb..8ed97dcf 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
#include
diff --git a/libraries/io/stream/stream.c b/libraries/io/stream/stream.c
index 6101315f..b28966f6 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
#include
diff --git a/libraries/io/stream/test/stream-test.c b/libraries/io/stream/test/stream-test.c
index d100a674..696dc19d 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
#include
diff --git a/libraries/test/txtscr/txtscr.c b/libraries/test/txtscr/txtscr.c
index 2a0c5377..d5670868 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
#include
diff --git a/libraries/text/bmtext/draw.c b/libraries/text/bmtext/draw.c
new file mode 100644
index 00000000..d64a8de7
--- /dev/null
+++ b/libraries/text/bmtext/draw.c
@@ -0,0 +1,32 @@
+/* text/bmtext/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_height;
+ point_t pos;
+ int i;
+
+ bmfont_get_info(font, NULL, &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 00000000..b6f30b67
--- /dev/null
+++ b/libraries/text/bmtext/layout.c
@@ -0,0 +1,53 @@
+/* text/bmtext/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/text/txtfmt/create.c b/libraries/text/txtfmt/create.c
index 19f2999a..47516ba7 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
#include
diff --git a/libraries/text/txtfmt/destroy.c b/libraries/text/txtfmt/destroy.c
index b176e220..aaed4e33 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
diff --git a/libraries/text/txtfmt/get-length.c b/libraries/text/txtfmt/get-length.c
index 21e0a231..b3e2fd68 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 90023946..247bd8f7 100644
--- a/libraries/text/txtfmt/get-line.c
+++ b/libraries/text/txtfmt/get-line.c
@@ -1,13 +1,13 @@
-/* get-line.c -- txtfmt - text formatting */
+/* text/txtfmt/get-line.c -- text formatting */
#include "text/txtfmt.h"
#include "impl.h"
result_t txtfmt_get_line(const txtfmt_t *tx,
- int index,
- const char **line,
- int *length)
+ int index,
+ const char **line,
+ int *length)
{
if (index < 0 || index >= tx->nspans)
return result_BAD_ARG;
diff --git a/libraries/text/txtfmt/get-nlines.c b/libraries/text/txtfmt/get-nlines.c
index 03c0ba6a..c9bc3417 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 06f1d8ce..2b104cd6 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 7a4ced4d..3725bfa3 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 a6a898e0..13ef73de 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
diff --git a/libraries/text/txtfmt/test/txtfmt-test.c b/libraries/text/txtfmt/test/txtfmt-test.c
index 9cc0edbd..a1f28b5f 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
diff --git a/libraries/text/txtfmt/wrap.c b/libraries/text/txtfmt/wrap.c
index dc7632e3..37316673 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
#include
@@ -7,6 +7,7 @@
#include "fortify/fortify.h"
#endif
+#include "base/utils.h"
#include "text/txtfmt.h"
#include "impl.h"
@@ -24,9 +25,7 @@ static result_t emit_line(txtfmt_t *tx, int start, int length)
/* doubling strategy */
- n = tx->allocated * 2;
- if (n < StartAt)
- n = StartAt;
+ n = MAX(tx->allocated * 2, StartAt);
newspans = realloc(tx->spans, sizeof(*tx->spans) * n);
if (newspans == NULL)
@@ -39,8 +38,7 @@ static result_t emit_line(txtfmt_t *tx, int start, int length)
tx->spans[i].start = start;
tx->spans[i].length = length;
- if (length > tx->wrapped_width)
- tx->wrapped_width = length;
+ tx->wrapped_width = MAX(tx->wrapped_width, length);
tx->nspans = ++i;
diff --git a/libraries/utils/array/delelem.c b/libraries/utils/array/delelem.c
index 35493cc9..5f5d55ea 100644
--- a/libraries/utils/array/delelem.c
+++ b/libraries/utils/array/delelem.c
@@ -1,3 +1,4 @@
+/* utils/array/delelem.c */
#include
#include