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 [![Build status](https://github.com/dpt/DPTLib/actions/workflows/ci.yml/badge.svg)](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: -![Henry Font](../resources/bmfonts/henry-font.png) +![Henry Font](../resources/bmfonts/henry.png) or this: -![Digits Font](../resources/bmfonts/digits-font.png) +![Digits Font](../resources/bmfonts/Digits-Regular.png) or even this: -![Tiny Font](../resources/bmfonts/tiny-font.png) +![Tiny Font](../resources/bmfonts/tiny.png) ...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 diff --git a/libraries/utils/array/delelems.c b/libraries/utils/array/delelems.c index aac00640..2b3ad7fc 100644 --- a/libraries/utils/array/delelems.c +++ b/libraries/utils/array/delelems.c @@ -1,3 +1,4 @@ +/* utils/array/delelems.c */ #include #include diff --git a/libraries/utils/array/grow.c b/libraries/utils/array/grow.c index 267f81d3..f49d6771 100644 --- a/libraries/utils/array/grow.c +++ b/libraries/utils/array/grow.c @@ -1,4 +1,4 @@ -/* grow.c -- grow an array */ +/* utils/array/grow.c -- grow an array */ #include #include @@ -7,16 +7,17 @@ #include "fortify/fortify.h" #endif +#include "base/utils.h" #include "utils/array.h" #include "utils/barith.h" /* used, need, minimum - specified as number of elements (not bytes) */ -int array_grow(void **pblock, - size_t elemsize, - int used, - int *pallocated, - int need, - int minimum) +int array_grow(void **pblock, + size_t elemsize, + int used, + int *pallocated, + int need, + int minimum) { int to_allocate; void *block; @@ -29,9 +30,7 @@ int array_grow(void **pblock, assert(minimum > 0); need += used; - - if (need < minimum) - need = minimum; + need = MAX(need, minimum); if (need <= *pallocated) return 0; /* block has enough spare space */ diff --git a/libraries/utils/array/shrink.c b/libraries/utils/array/shrink.c index 0b8de40d..98828cd3 100644 --- a/libraries/utils/array/shrink.c +++ b/libraries/utils/array/shrink.c @@ -1,4 +1,4 @@ -/* shrink.c -- shrink wrap an array */ +/* utils/array/shrink.c -- shrink wrap an array */ #include #include diff --git a/libraries/utils/array/squeeze.c b/libraries/utils/array/squeeze.c index eef61de2..d4a71937 100644 --- a/libraries/utils/array/squeeze.c +++ b/libraries/utils/array/squeeze.c @@ -1,3 +1,4 @@ +/* utils/array/squeeze.c */ #include #include diff --git a/libraries/utils/array/stretch.c b/libraries/utils/array/stretch.c index b1064005..eac7377a 100644 --- a/libraries/utils/array/stretch.c +++ b/libraries/utils/array/stretch.c @@ -1,3 +1,4 @@ +/* utils/array/stretch.c */ #include #include diff --git a/libraries/utils/array/test/array-test.c b/libraries/utils/array/test/array-test.c index 537ee49f..c9e998fd 100644 --- a/libraries/utils/array/test/array-test.c +++ b/libraries/utils/array/test/array-test.c @@ -1,3 +1,4 @@ +/* utils/array/test/array-test.c */ #include #include diff --git a/libraries/utils/barith/barith.c b/libraries/utils/barith/barith.c index 07e95d44..711a3a17 100644 --- a/libraries/utils/barith/barith.c +++ b/libraries/utils/barith/barith.c @@ -1,4 +1,4 @@ -/* barith.c -- define all of barith */ +/* utils/barith/barith.c -- define all of barith */ #define BARITH_INLINE diff --git a/libraries/utils/bsearch/bsearch-impl.h b/libraries/utils/bsearch/bsearch-impl.h index 914142e3..6ee9ec54 100644 --- a/libraries/utils/bsearch/bsearch-impl.h +++ b/libraries/utils/bsearch/bsearch-impl.h @@ -1,4 +1,4 @@ -/* bsearch-impl.h -- binary searching arrays */ +/* utils/bsearch/bsearch-impl.h -- binary searching arrays */ #if !defined(TYPE) || !defined(NAME) #error TYPE and NAME must be defined. diff --git a/libraries/utils/bsearch/bsearch-int.c b/libraries/utils/bsearch/bsearch-int.c index eb0549ce..93b3fc07 100644 --- a/libraries/utils/bsearch/bsearch-int.c +++ b/libraries/utils/bsearch/bsearch-int.c @@ -1,4 +1,4 @@ -/* bsearch-int.c -- binary searching arrays */ +/* utils/bsearch/bsearch-int.c -- binary searching arrays */ #define TYPE int #define NAME bsearch_int diff --git a/libraries/utils/bsearch/bsearch-short.c b/libraries/utils/bsearch/bsearch-short.c index 528ca7d7..aa7c22c4 100644 --- a/libraries/utils/bsearch/bsearch-short.c +++ b/libraries/utils/bsearch/bsearch-short.c @@ -1,4 +1,4 @@ -/* bsearch-short.c -- binary searching arrays */ +/* utils/bsearch/bsearch-short.c -- binary searching arrays */ #define TYPE short #define NAME bsearch_short diff --git a/libraries/utils/bsearch/bsearch-uint.c b/libraries/utils/bsearch/bsearch-uint.c index 725cc581..1b73bd9b 100644 --- a/libraries/utils/bsearch/bsearch-uint.c +++ b/libraries/utils/bsearch/bsearch-uint.c @@ -1,4 +1,4 @@ -/* bsearch-uint.c -- binary searching arrays */ +/* utils/bsearch/bsearch-uint.c -- binary searching arrays */ #define TYPE unsigned int #define NAME bsearch_uint diff --git a/libraries/utils/bsearch/bsearch-ushort.c b/libraries/utils/bsearch/bsearch-ushort.c index 0cdc9304..eb30e4b8 100644 --- a/libraries/utils/bsearch/bsearch-ushort.c +++ b/libraries/utils/bsearch/bsearch-ushort.c @@ -1,4 +1,4 @@ -/* bsearch-ushort.c -- binary searching arrays */ +/* utils/bsearch/bsearch-ushort.c -- binary searching arrays */ #define TYPE unsigned short #define NAME bsearch_ushort diff --git a/libraries/utils/bsearch/test/bsearch-test.c b/libraries/utils/bsearch/test/bsearch-test.c index f2798af9..cbe6b7db 100644 --- a/libraries/utils/bsearch/test/bsearch-test.c +++ b/libraries/utils/bsearch/test/bsearch-test.c @@ -1,3 +1,4 @@ +/* utils/bsearch/test/bsearch-test.c */ #include #include diff --git a/libraries/utils/bytesex/rev-l-block.c b/libraries/utils/bytesex/rev-l-block.c index dd652f0d..887fa50f 100644 --- a/libraries/utils/bytesex/rev-l-block.c +++ b/libraries/utils/bytesex/rev-l-block.c @@ -1,4 +1,4 @@ -/* rev-l-block.c -- reversing bytesex */ +/* utils/bytesex/rev-l-block.c -- reversing bytesex */ #include diff --git a/libraries/utils/bytesex/rev-l-m.c b/libraries/utils/bytesex/rev-l-m.c index 3c9befb4..822fc495 100644 --- a/libraries/utils/bytesex/rev-l-m.c +++ b/libraries/utils/bytesex/rev-l-m.c @@ -1,4 +1,4 @@ -/* rev-l-m.c -- reversing bytesex */ +/* utils/bytesex/rev-l-m.c -- reversing bytesex */ #include "utils/bytesex.h" diff --git a/libraries/utils/bytesex/rev-l.c b/libraries/utils/bytesex/rev-l.c index 9124cf4e..39367bc6 100644 --- a/libraries/utils/bytesex/rev-l.c +++ b/libraries/utils/bytesex/rev-l.c @@ -1,4 +1,4 @@ -/* rev-l.c -- reversing bytesex */ +/* utils/bytesex/rev-l.c -- reversing bytesex */ #include "utils/bytesex.h" diff --git a/libraries/utils/bytesex/rev-s-block.c b/libraries/utils/bytesex/rev-s-block.c index 118fa580..cd24f235 100644 --- a/libraries/utils/bytesex/rev-s-block.c +++ b/libraries/utils/bytesex/rev-s-block.c @@ -1,4 +1,4 @@ -/* rev-s-block.c -- reversing bytesex */ +/* utils/bytesex/rev-s-block.c -- reversing bytesex */ #include diff --git a/libraries/utils/bytesex/rev-s-m.c b/libraries/utils/bytesex/rev-s-m.c index d5bf7e46..8b181e3d 100644 --- a/libraries/utils/bytesex/rev-s-m.c +++ b/libraries/utils/bytesex/rev-s-m.c @@ -1,4 +1,4 @@ -/* rev-s-m.c -- reversing bytesex */ +/* utils/bytesex/rev-s-m.c -- reversing bytesex */ #include "utils/bytesex.h" diff --git a/libraries/utils/bytesex/rev-s-pair-m.c b/libraries/utils/bytesex/rev-s-pair-m.c index c4f0327d..23104771 100644 --- a/libraries/utils/bytesex/rev-s-pair-m.c +++ b/libraries/utils/bytesex/rev-s-pair-m.c @@ -1,4 +1,4 @@ -/* rev-s-pair-m.c -- reversing bytesex */ +/* utils/bytesex/rev-s-pair-m.c -- reversing bytesex */ #include "utils/bytesex.h" diff --git a/libraries/utils/bytesex/rev-s-pair.c b/libraries/utils/bytesex/rev-s-pair.c index b251a683..c98cab4d 100644 --- a/libraries/utils/bytesex/rev-s-pair.c +++ b/libraries/utils/bytesex/rev-s-pair.c @@ -1,4 +1,4 @@ -/* rev-s-pair.c -- reversing bytesex */ +/* utils/bytesex/rev-s-pair.c -- reversing bytesex */ #include "utils/bytesex.h" diff --git a/libraries/utils/bytesex/rev-s.c b/libraries/utils/bytesex/rev-s.c index 392e910f..646e0da0 100644 --- a/libraries/utils/bytesex/rev-s.c +++ b/libraries/utils/bytesex/rev-s.c @@ -1,4 +1,4 @@ -/* rev-s.c -- reversing bytesex */ +/* utils/bytesex/rev-s.c -- reversing bytesex */ #include "utils/bytesex.h" diff --git a/libraries/utils/bytesex/util.h b/libraries/utils/bytesex/util.h index f41523e9..c6b153a3 100644 --- a/libraries/utils/bytesex/util.h +++ b/libraries/utils/bytesex/util.h @@ -1,4 +1,4 @@ -/* util.h -- utils for reversing bytesex */ +/* utils/bytesex/util.h -- utils for reversing bytesex */ #include diff --git a/libraries/utils/fxp/smull-fxp16.c b/libraries/utils/fxp/smull-fxp16.c index 6c4509ab..44ecd19d 100644 --- a/libraries/utils/fxp/smull-fxp16.c +++ b/libraries/utils/fxp/smull-fxp16.c @@ -1,4 +1,4 @@ -/* smull-fxp16.c -- fixed point helpers */ +/* utils/fxp/smull-fxp16.c -- fixed point helpers */ #include "utils/fxp.h" diff --git a/libraries/utils/fxp/umull-fxp16.c b/libraries/utils/fxp/umull-fxp16.c index e15a846c..30377304 100644 --- a/libraries/utils/fxp/umull-fxp16.c +++ b/libraries/utils/fxp/umull-fxp16.c @@ -1,4 +1,4 @@ -/* umull-fxp16.c -- fixed point helpers */ +/* utils/fxp/umull-fxp16.c -- fixed point helpers */ #include "utils/fxp.h" diff --git a/libraries/utils/maths/degs-to-rads.c b/libraries/utils/maths/degs-to-rads.c index 891ffe26..1e1f0f92 100644 --- a/libraries/utils/maths/degs-to-rads.c +++ b/libraries/utils/maths/degs-to-rads.c @@ -1,4 +1,4 @@ -/* degs-to-rads.c -- degrees to radians */ +/* utils/maths/degs-to-rads.c -- degrees to radians */ #include "utils/maths.h" diff --git a/libraries/utils/maths/gcd.c b/libraries/utils/maths/gcd.c index 9216dbe7..f4e2d531 100644 --- a/libraries/utils/maths/gcd.c +++ b/libraries/utils/maths/gcd.c @@ -1,4 +1,4 @@ -/* gcd.c -- greatest common divisor */ +/* utils/maths/gcd.c -- greatest common divisor */ #include "utils/maths.h" diff --git a/libraries/utils/pack/pack.c b/libraries/utils/pack/pack.c index aacce624..26dd7bbf 100644 --- a/libraries/utils/pack/pack.c +++ b/libraries/utils/pack/pack.c @@ -1,4 +1,4 @@ -/* pack.c -- structure packing */ +/* utils/pack/pack.c -- structure packing */ #include #include diff --git a/libraries/utils/pack/test/pack-test.c b/libraries/utils/pack/test/pack-test.c index bed208b3..64d7e04a 100644 --- a/libraries/utils/pack/test/pack-test.c +++ b/libraries/utils/pack/test/pack-test.c @@ -1,4 +1,4 @@ -/* pack-test.c -- tests for pack/unpack */ +/* utils/pack/test/pack-test.c -- tests for pack/unpack */ #include #include diff --git a/libraries/utils/pack/unpack.c b/libraries/utils/pack/unpack.c index b0251590..3ffec99a 100644 --- a/libraries/utils/pack/unpack.c +++ b/libraries/utils/pack/unpack.c @@ -1,4 +1,4 @@ -/* unpack.c -- structure unpacking */ +/* utils/pack/unpack.c -- structure unpacking */ #include #include @@ -244,7 +244,7 @@ static size_t name(const unsigned char *buf, const char *fmt, va_list args) \ a = va_arg(args, uint32_t *); \ while (n--) \ { \ - *a++ = (uint32_t) (bp[w0] | (bp[w1] << 8) | (bp[w2] << 16) | (bp[w3] << 24)); \ + *a++ = (uint32_t) ((uint32_t) bp[w0] | ((uint32_t) bp[w1] << 8) | ((uint32_t) bp[w2] << 16) | ((uint32_t) bp[w3] << 24)); \ bp += 4; \ } \ } \ @@ -257,7 +257,7 @@ static size_t name(const unsigned char *buf, const char *fmt, va_list args) \ a = va_arg(args, uint64_t *); \ while (n--) \ { \ - *a++ = (uint32_t) (bp[w0] | (bp[w1] << 8) | (bp[w2] << 16) | (bp[w3] << 24)); \ + *a++ = (uint32_t) ((uint32_t) bp[w0] | ((uint32_t) bp[w1] << 8) | ((uint32_t) bp[w2] << 16) | ((uint32_t) bp[w3] << 24)); \ bp += 4; \ } \ } \ @@ -270,7 +270,7 @@ static size_t name(const unsigned char *buf, const char *fmt, va_list args) \ a = va_arg(args, int64_t *); \ while (n--) \ { \ - *a++ = (int32_t) (bp[w0] | (bp[w1] << 8) | (bp[w2] << 16) | (bp[w3] << 24)); \ + *a++ = (int32_t) ((uint32_t) bp[w0] | ((uint32_t) bp[w1] << 8) | ((uint32_t) bp[w2] << 16) | ((uint32_t) bp[w3] << 24)); \ bp += 4; \ } \ } \ @@ -473,7 +473,7 @@ static size_t name(const unsigned char *buf, const char *fmt, va_list args) \ while (n--) \ { \ pI = va_arg(args, uint32_t *); \ - *pI = (uint32_t) (bp[w0] | (bp[w1] << 8) | (bp[w2] << 16) | (bp[w3] << 24)); \ + *pI = (uint32_t) ((uint32_t) bp[w0] | ((uint32_t) bp[w1] << 8) | ((uint32_t) bp[w2] << 16) | ((uint32_t) bp[w3] << 24)); \ bp += 4; \ } \ break; \ @@ -482,7 +482,7 @@ static size_t name(const unsigned char *buf, const char *fmt, va_list args) \ while (n--) \ { \ pQ = va_arg(args, uint64_t *); \ - *pQ = (uint32_t) (bp[w0] | (bp[w1] << 8) | (bp[w2] << 16) | (bp[w3] << 24)); \ + *pQ = (uint32_t) ((uint32_t) bp[w0] | ((uint32_t) bp[w1] << 8) | ((uint32_t) bp[w2] << 16) | ((uint32_t) bp[w3] << 24)); \ bp += 4; \ } \ break; \ @@ -491,7 +491,7 @@ static size_t name(const unsigned char *buf, const char *fmt, va_list args) \ while (n--) \ { \ pq = va_arg(args, int64_t *); \ - *pq = (int32_t) (bp[w0] | (bp[w1] << 8) | (bp[w2] << 16) | (bp[w3] << 24)); \ + *pq = (int32_t) ((uint32_t) bp[w0] | ((uint32_t) bp[w1] << 8) | ((uint32_t) bp[w2] << 16) | ((uint32_t) bp[w3] << 24)); \ bp += 4; \ } \ break; \ diff --git a/libraries/utils/primes/primes.c b/libraries/utils/primes/primes.c index b39fcd64..9d25d619 100644 --- a/libraries/utils/primes/primes.c +++ b/libraries/utils/primes/primes.c @@ -1,4 +1,4 @@ -/* primes.c -- cache of prime numbers */ +/* utils/primes/primes.c -- cache of prime numbers */ #include "base/utils.h" diff --git a/libraries/wuss/component/colourmenu.c b/libraries/wuss/component/colourmenu.c new file mode 100644 index 00000000..faa77554 --- /dev/null +++ b/libraries/wuss/component/colourmenu.c @@ -0,0 +1,177 @@ +/* wuss/component/colourmenu.c -- a menu of the system palette colours */ + +#include +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/result.h" +#include "framebuf/colour.h" +#include "framebuf/pixelfmt.h" + +#include "wuss/menu.h" +#include "wuss/task.h" +#include "wuss/wuss.h" + +#include "wuss/component/colourmenu.h" + +#include "../core/impl.h" + +/* ----------------------------------------------------------------------- */ + +/* ponytail: flat list, one item per palette entry, index order. No grouping + * of near colours, no "recent" section. wuss_colourmenu_selected is then a + * single pointer compare plus the item index. */ +struct wuss_colourmenu +{ + wuss_alloc_t alloc; /* copied hooks; the wuss_t itself is not retained */ + wuss_menu_t *menu; /* owned; every block via alloc, see menu_free */ +}; + +/* Free a menu built here -- text, items, title, node -- through the same + * hooks it was built with, so wuss_menu_destroy (plain free) must not be + * used. Tolerates NULL text for partial unwinding. */ +static void menu_free(const wuss_alloc_t *a, wuss_menu_t *m) +{ + int i; + + if (m == NULL) + return; + + for (i = 0; i < m->nitems; i++) + a->free((void *) m->items[i].text); /* discard const */ + a->free((void *) m->items); /* discard const */ + a->free((void *) m->title); /* discard const */ + a->free(m); +} + +result_t wuss_colourmenu_create(wuss_colourmenu_t **out, + const wuss_t *wuss, + const char *title) +{ + const wuss_alloc_t *a; + wuss_colourmenu_t *cm; + wuss_menu_t *m; + wuss_menu_item_t *items; + int n; + int i; + + items = NULL; + + if (out == NULL || wuss == NULL) + return result_NULL_ARG; + + a = &wuss->alloc; + n = wuss->npalette; + + cm = a->malloc(sizeof(*cm)); + if (cm == NULL) + return result_OOM; + cm->alloc = *a; + cm->menu = NULL; + a = &cm->alloc; /* use the copy from here on -- outlives the wuss_t */ + + m = a->malloc(sizeof(*m)); + if (m == NULL) + { + a->free(cm); + return result_OOM; + } + m->title = NULL; + m->items = NULL; + m->nitems = 0; + + if (n > 0) + { + items = a->malloc((size_t) n * sizeof(*items)); + if (items == NULL) + { + menu_free(a, m); + a->free(cm); + return result_OOM; + } + memset(items, 0, (size_t) n * sizeof(*items)); + m->items = items; + m->nitems = n; /* items zeroed: menu_free's NULL-text loop is safe now */ + } + + m->title = wuss__alloc_strdup(a, title ? title : "Colour"); + if (m->title == NULL) + { + menu_free(a, m); + a->free(cm); + return result_OOM; + } + + for (i = 0; i < n; i++) + { + pixelfmt_rgba8888_t px; + char label[8]; + + px = wuss->palette[i].primary; + snprintf(label, sizeof(label), "#%02X%02X%02X", + PIXELFMT_Rxxx8888(px), + PIXELFMT_xGxx8888(px), + PIXELFMT_xxBx8888(px)); + + items[i].text = wuss__alloc_strdup(a, label); + if (items[i].text == NULL) + { + menu_free(a, m); + a->free(cm); + return result_OOM; + } + + items[i].flags = wuss_MENU_ITEM_SWATCH; + items[i].swatch = (wuss_colour_t) i; + } + + cm->menu = m; + *out = cm; + return result_OK; +} + +void wuss_colourmenu_destroy(wuss_colourmenu_t *doomed) +{ + wuss_alloc_t alloc; + + if (doomed == NULL) + return; + + alloc = doomed->alloc; + menu_free(&alloc, doomed->menu); + alloc.free(doomed); +} + +const wuss_menu_t *wuss_colourmenu_menu(const wuss_colourmenu_t *cm) +{ + return cm ? cm->menu : NULL; +} + +wuss_colour_t wuss_colourmenu_selected(const wuss_colourmenu_t *cm, + const wuss_event_t *ev, + int *ok) +{ + int index; + + if (ok != NULL) + *ok = 0; + + if (cm == NULL || ev == NULL) + return 0; + if (ev->kind != wuss_EVENT_MENU_SELECT) + return 0; + if (ev->data.menu_select.menu != cm->menu) + return 0; + + index = ev->data.menu_select.index; + if (index < 0 || index >= cm->menu->nitems) + return 0; + + if (ok != NULL) + *ok = 1; + return cm->menu->items[index].swatch; +} diff --git a/libraries/wuss/component/fontmenu.c b/libraries/wuss/component/fontmenu.c new file mode 100644 index 00000000..8bf3f14e --- /dev/null +++ b/libraries/wuss/component/fontmenu.c @@ -0,0 +1,300 @@ +/* wuss/component/fontmenu.c -- a menu of the available bitmap fonts */ + +#include +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/result.h" +#include "framebuf/bmfont.h" + +#include "wuss/menu.h" +#include "wuss/task.h" +#include "wuss/wuss.h" + +#include "wuss/component/fontmenu.h" + +#include "../core/impl.h" + +/* ----------------------------------------------------------------------- */ + +/* ponytail: flat list, one item per font, no family/weight grouping into + * submenus. wuss_fontmenu_selected then needs only a single pointer compare. + * Add a grouping pass (and widen the identity check to the submenus) if a + * font directory ever gets big enough to want it. */ +struct wuss_fontmenu +{ + wuss_alloc_t alloc; /* copied hooks; every block below goes through these */ + wuss_menu_t *menu; /* owned; freed by menu_free, not wuss_menu_destroy */ +}; + +/* Free a menu built here -- text, items, title, node -- through the same + * hooks it was built with. Tolerates NULL text for partial unwinding. */ +static void menu_free(const wuss_alloc_t *a, wuss_menu_t *m) +{ + int i; + + if (m == NULL) + return; + + for (i = 0; i < m->nitems; i++) + a->free((void *) m->items[i].text); /* discard const */ + a->free((void *) m->items); /* discard const */ + a->free((void *) m->title); /* discard const */ + a->free(m); +} + +/* ----------------------------------------------------------------------- */ + +/* Growable list of name copies, filled by the enumerate callback. Transient: + * freed before create returns. Routed through the caller's hooks so even the + * scratch never touches raw stdlib. */ +typedef struct namelist +{ + const wuss_alloc_t *alloc; /* borrowed; the resolved hooks */ + const wuss_t *wuss; /* borrowed; consulted to skip SYSTEM fonts, or + * NULL to skip nothing */ + char **names; + int n; + int cap; + int oom; /* a strdup/grow failed; stop and unwind */ +} +namelist_t; + +/* True if any wuss_create font slot names \p name and is classed SYSTEM -- + * chrome-only, not meant to be offered as a text font. */ +static int is_system_font_name(const wuss_t *wuss, const char *name) +{ + int i; + + if (wuss == NULL) + return 0; + + for (i = 0; i < wuss_MAX_FONTS; i++) + { + const char *slot_name = wuss_get_font_name_n(wuss, i); + + if (slot_name != NULL && strcmp(slot_name, name) == 0 && + wuss_get_font_class_n(wuss, i) == wuss_FONT_CLASS_SYSTEM) + return 1; + } + + return 0; +} + +static result_t collect_name(const char *name, + const char *path, + void *opaque) +{ + namelist_t *nl = opaque; + char *copy; + + (void) path; + + if (is_system_font_name(nl->wuss, name)) + return result_OK; + + if (wuss__array_grow(nl->alloc, (void **) &nl->names, sizeof(*nl->names), + nl->n, &nl->cap, 1, 8)) + { + nl->oom = 1; + return result_OOM; + } + + copy = wuss__alloc_strdup(nl->alloc, name); + if (copy == NULL) + { + nl->oom = 1; + return result_OOM; + } + + nl->names[nl->n++] = copy; + return result_OK; +} + +static void namelist_free(namelist_t *nl) +{ + int i; + + for (i = 0; i < nl->n; i++) + nl->alloc->free(nl->names[i]); + nl->alloc->free(nl->names); + nl->names = NULL; + nl->n = nl->cap = 0; +} + +static int name_cmp(const void *a, const void *b) +{ + return strcmp(*(const char *const *) a, *(const char *const *) b); +} + +/* ----------------------------------------------------------------------- */ + +/* Build a flat wuss_menu_t through \p a, copying each names[i] in (the + * scratch list keeps its own copies). On failure everything taken is freed + * through \p a and *out is untouched. */ +static result_t build_menu(const wuss_alloc_t *a, + char **names, + int nnames, + const char *title, + wuss_menu_t **out) +{ + wuss_menu_t *m; + wuss_menu_item_t *items; + int i; + + items = NULL; + + m = a->malloc(sizeof(*m)); + if (m == NULL) + return result_OOM; + m->title = NULL; + m->items = NULL; + m->nitems = 0; + + if (nnames > 0) + { + items = a->malloc((size_t) nnames * sizeof(*items)); + if (items == NULL) + { + menu_free(a, m); + return result_OOM; + } + memset(items, 0, (size_t) nnames * sizeof(*items)); + m->items = items; + m->nitems = nnames; /* items zeroed: menu_free's NULL-text loop is safe */ + } + + m->title = wuss__alloc_strdup(a, title ? title : "Font"); + if (m->title == NULL) + { + menu_free(a, m); + return result_OOM; + } + + for (i = 0; i < nnames; i++) + { + items[i].text = wuss__alloc_strdup(a, names[i]); + if (items[i].text == NULL) + { + menu_free(a, m); + return result_OOM; + } + items[i].flags = wuss_MENU_ITEM_NONE; + items[i].submenu = NULL; + items[i].window = NULL; + } + + *out = m; + return result_OK; +} + +/* ----------------------------------------------------------------------- */ + +result_t wuss_fontmenu_create(wuss_fontmenu_t **out, + const char *dir, + const char *title, + const wuss_t *wuss, + const wuss_alloc_t *alloc) +{ + namelist_t nl; + wuss_fontmenu_t *fm; + result_t rc; + + if (out == NULL || dir == NULL) + return result_NULL_ARG; + + if (alloc == NULL) + alloc = &wuss_alloc; + + memset(&nl, 0, sizeof(nl)); + nl.alloc = alloc; + nl.wuss = wuss; + + rc = bmfont_enumerate(dir, collect_name, &nl); + if (rc != result_OK) + { + namelist_free(&nl); + return nl.oom ? result_OOM : rc; + } + + if (nl.n > 1) + qsort(nl.names, (size_t) nl.n, sizeof(nl.names[0]), name_cmp); + + fm = alloc->malloc(sizeof(*fm)); + if (fm == NULL) + { + namelist_free(&nl); + return result_OOM; + } + fm->alloc = *alloc; + fm->menu = NULL; + + rc = build_menu(&fm->alloc, nl.names, nl.n, title, &fm->menu); + namelist_free(&nl); /* build_menu copied what it needed */ + if (rc != result_OK) + { + alloc->free(fm); + return rc; + } + + *out = fm; + return result_OK; +} + +void wuss_fontmenu_destroy(wuss_fontmenu_t *doomed) +{ + wuss_alloc_t alloc; + + if (doomed == NULL) + return; + + alloc = doomed->alloc; + menu_free(&alloc, doomed->menu); + alloc.free(doomed); +} + +const wuss_menu_t *wuss_fontmenu_menu(const wuss_fontmenu_t *fm) +{ + return fm ? fm->menu : NULL; +} + +const char *wuss_fontmenu_selected(const wuss_fontmenu_t *fm, + const wuss_event_t *ev) +{ + int index; + + if (fm == NULL || ev == NULL) + return NULL; + if (ev->kind != wuss_EVENT_MENU_SELECT) + return NULL; + if (ev->data.menu_select.menu != fm->menu) + return NULL; + + index = ev->data.menu_select.index; + if (index < 0 || index >= fm->menu->nitems) + return NULL; + + return fm->menu->items[index].text; +} + +void wuss_fontmenu_set_ticked(wuss_fontmenu_t *fm, int index) +{ + wuss_menu_item_t *items; + int i; + + if (fm == NULL) + return; + + /* fm->menu is ours to mutate; only wuss_fontmenu_menu hands it out const */ + items = (wuss_menu_item_t *) fm->menu->items; + + for (i = 0; i < fm->menu->nitems; i++) + if (i == index) + items[i].flags |= wuss_MENU_ITEM_TICKED; + else + items[i].flags &= ~(wuss_menu_item_flags_t) wuss_MENU_ITEM_TICKED; +} diff --git a/libraries/wuss/core/backdrop.c b/libraries/wuss/core/backdrop.c new file mode 100644 index 00000000..a078f3c7 --- /dev/null +++ b/libraries/wuss/core/backdrop.c @@ -0,0 +1,51 @@ +/* wuss/backdrop.c -- shared backdrop validation and fill */ + +#include "framebuf/screen.h" + +#include "impl.h" + +result_t wuss__validate_backdrop(const wuss_t *wuss, + const wuss_backdrop_t *backdrop) +{ + if (backdrop->colour == wuss_NO_BACKGROUND) + return result_OK; + + if (backdrop->colour < 0 || backdrop->colour >= wuss->npalette) + return result_WUSS_BAD_COLOUR; + + if (backdrop->pattern < 0 || backdrop->pattern >= screen_PATTERN__LIMIT) + return result_WUSS_BAD_COLOUR; + + if (backdrop->pattern != screen_PATTERN_SOLID && + (backdrop->pattern_bg < 0 || backdrop->pattern_bg >= wuss->npalette)) + return result_WUSS_BAD_COLOUR; + + return result_OK; +} + +void wuss__fill_backdrop(screen_t *scr, + const colour_t *palette, + const wuss_backdrop_t *backdrop, + const box_t *area, + int origin_x, + int origin_y) +{ + if (backdrop->colour == wuss_NO_BACKGROUND) + return; + + if (backdrop->pattern == screen_PATTERN_SOLID) + { + screen_fill_rect(scr, area->x0, area->y0, box_size(area), + palette[backdrop->colour]); + } + else + { + pattern_t pat; + + pat = pattern_from_preset(backdrop->pattern, + palette[backdrop->colour], + palette[backdrop->pattern_bg]); + pat.origin = POINT(origin_x, origin_y); + screen_fill_pattern(scr, area, &pat); + } +} diff --git a/libraries/wuss/core/create.c b/libraries/wuss/core/create.c new file mode 100644 index 00000000..b5ed44b5 --- /dev/null +++ b/libraries/wuss/core/create.c @@ -0,0 +1,300 @@ +/* wuss/create.c -- wuss - minimal window manager */ + +#include +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/utils.h" + +#include "impl.h" + +/* The default allocator: plain stdlib. wuss_create copies this in when its + * alloc argument is NULL. */ +const wuss_alloc_t wuss_alloc = +{ + malloc, realloc, free +}; + +/* Built-in fallback palette when the caller passes NULL: black and white. + * wuss makes no other assumptions about palette contents or length. */ +static const colour_t wuss__default_palette[] = +{ + { 0xFF000000 }, /* 0: black */ + { 0xFFFFFFFF } /* 1: white */ +}; + +#if defined(WUSS_FURNITURE) || defined(WUSS_ICONS) +/* Range-check the bevel colours and the backdrop against the palette. Shared + * by the furniture and icons-only paths so the accepted range, the error code + * and the freed-pointer set stay in one place. */ +static result_t validate_bevel_backdrop(const wuss_t *w, + wuss_colour_t blight, + wuss_colour_t bdark, + wuss_colour_t abg, + wuss_colour_t afg) +{ + if (blight < 0 || blight >= w->npalette || + bdark < 0 || bdark >= w->npalette || + abg < 0 || abg >= w->npalette || + afg < 0 || afg >= w->npalette || + wuss__validate_backdrop(w, &w->backdrop) != result_OK) + return result_WUSS_BAD_COLOUR; + + return result_OK; +} +#endif + +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) +{ + bmfont_t *font; + wuss_alloc_t al; + wuss_t *w; +#ifdef WUSS_FURNITURE + wuss_furniture_palette_t pal; + wuss_colour_t bg, fg; +#endif +#if defined(WUSS_FURNITURE) || defined(WUSS_ICONS) + wuss_colour_t blight, bdark; + wuss_colour_t abg, afg; +#endif +#ifdef WUSS_FURNITURE + int font_height; +#endif + + assert(scr != NULL); + assert(wuss != NULL); + + if (nfonts < 0 || nfonts > wuss_MAX_FONTS || (nfonts > 0 && fonts == NULL)) + return result_BAD_ARG; + + font = (nfonts > 0) ? fonts[0].font : NULL; + + al = (alloc != NULL) ? *alloc : wuss_alloc; + + w = al.malloc(sizeof(*w)); + if (w == NULL) + return result_OOM; + w->alloc = al; + + if (palette == NULL) + { + palette = wuss__default_palette; + npalette = NELEMS(wuss__default_palette); + } + else if (npalette <= 0) + { + wuss__free(w, w); + return result_BAD_ARG; + } + + w->palette = wuss__malloc(w, npalette * sizeof(*w->palette)); + if (w->palette == NULL) + { + wuss__free(w, w); + return result_OOM; + } + memcpy(w->palette, palette, npalette * sizeof(*w->palette)); + w->npalette = npalette; + + /* First pass: white/black and the named symbolic colours (BLACK..GREY). + * The chrome-role slots are still the out-of-range sentinel here -- filled + * once the config's own colours below are resolved and stored. This lets + * wuss__resolve_colour handle a config that uses a named symbolic. */ + wuss__rebuild_palettecache(w); + + if (config != NULL) + { + w->backdrop = config->backdrop; + w->backdrop.colour = wuss__resolve_colour(w, w->backdrop.colour); + w->backdrop.pattern_bg = wuss__resolve_colour(w, w->backdrop.pattern_bg); + } + else + { + w->backdrop.colour = wuss_NO_BACKGROUND; + w->backdrop.pattern = screen_PATTERN_SOLID; + w->backdrop.pattern_bg = wuss_NO_BACKGROUND; + } + +#ifdef WUSS_FURNITURE + if (config != NULL) + { + pal = config->furniture; + pal.title.bg = wuss__resolve_colour(w, pal.title.bg); + pal.title.fg = wuss__resolve_colour(w, pal.title.fg); + pal.back = wuss__resolve_colour(w, pal.back); + pal.close = wuss__resolve_colour(w, pal.close); + pal.toggle = wuss__resolve_colour(w, pal.toggle); + pal.resize = wuss__resolve_colour(w, pal.resize); + pal.scroll.arrows = wuss__resolve_colour(w, pal.scroll.arrows); + pal.scroll.wells = wuss__resolve_colour(w, pal.scroll.wells); + pal.scroll.sausages = wuss__resolve_colour(w, pal.scroll.sausages); + blight = wuss__resolve_colour(w, config->bevel.light); + bdark = wuss__resolve_colour(w, config->bevel.dark); + abg = wuss__resolve_colour(w, config->accent.bg); + afg = wuss__resolve_colour(w, config->accent.fg); + } + else + { + bg = 0; + fg = (w->npalette > 1) ? 1 : 0; + + pal.title.bg = bg; + pal.title.fg = fg; + pal.back = fg; + pal.close = fg; + pal.toggle = fg; + pal.resize = bg; + pal.scroll.arrows = bg; + pal.scroll.wells = bg; + pal.scroll.sausages = fg; + + blight = 0; + bdark = 0; + abg = bg; /* default action button: the titlebar colours */ + afg = fg; + } + + if (pal.title.bg < 0 || pal.title.bg >= w->npalette || + pal.title.fg < 0 || pal.title.fg >= w->npalette || + pal.back < 0 || pal.back >= w->npalette || + pal.close < 0 || pal.close >= w->npalette || + pal.toggle < 0 || pal.toggle >= w->npalette || + pal.resize < 0 || pal.resize >= w->npalette || + pal.scroll.arrows < 0 || pal.scroll.arrows >= w->npalette || + pal.scroll.wells < 0 || pal.scroll.wells >= w->npalette || + pal.scroll.sausages < 0 || pal.scroll.sausages >= w->npalette || + validate_bevel_backdrop(w, blight, bdark, abg, afg) != result_OK) + { + wuss__free(w, w->palette); + wuss__free(w, w); + return result_WUSS_BAD_COLOUR; + } + + w->furniture_colours = pal; + w->bevel_light = blight; + w->bevel_dark = bdark; + w->accent_bg = abg; + w->accent_fg = afg; + + if (config != NULL && config->titlebar_height > 0) + { + w->titlebar_height = config->titlebar_height; + } + else if (font != NULL) + { + /* titles draw in the bold weight when one was supplied; size the + * titlebar to whichever weight is taller */ + bmfont_get_info(font, NULL, &font_height); + if (nfonts > 1 && fonts[1].font != NULL) + { + int titleh; + + bmfont_get_info(fonts[1].font, NULL, &titleh); + font_height = MAX(font_height, titleh); + } + w->titlebar_height = font_height + 4; + } + else + { + w->titlebar_height = WUSS_DEFAULT_TITLEBAR_HEIGHT; + } +#else /* !WUSS_FURNITURE */ +#ifdef WUSS_ICONS + if (config != NULL) + { + blight = wuss__resolve_colour(w, config->bevel.light); + bdark = wuss__resolve_colour(w, config->bevel.dark); + abg = wuss__resolve_colour(w, config->accent.bg); + afg = wuss__resolve_colour(w, config->accent.fg); + } + else + { + blight = 0; + bdark = 0; + abg = 0; + afg = (w->npalette > 1) ? 1 : 0; + } + if (validate_bevel_backdrop(w, blight, bdark, abg, afg) != result_OK) + { + wuss__free(w, w->palette); + wuss__free(w, w); + return result_WUSS_BAD_COLOUR; + } + w->bevel_light = blight; + w->bevel_dark = bdark; + w->accent_bg = abg; + w->accent_fg = afg; +#else + if (wuss__validate_backdrop(w, &w->backdrop) != result_OK) + { + wuss__free(w, w->palette); + wuss__free(w, w); + return result_WUSS_BAD_COLOUR; + } +#endif +#endif /* WUSS_FURNITURE */ + + /* Second pass: the chrome colours are stored and concrete now, so fill in + * the chrome-role symbolic slots (wuss_COLOUR_TITLE_BG etc.). */ + wuss__rebuild_palettecache(w); + + w->scr = scr; + { + int i; + + for (i = 0; i < nfonts; i++) + { + w->fonts[i] = fonts[i].font; + w->font_classes[i] = fonts[i].font_class; + w->font_names[i] = fonts[i].name; + } + for (; i < wuss_MAX_FONTS; i++) + { + w->fonts[i] = NULL; + w->font_classes[i] = wuss_FONT_CLASS_NONE; + w->font_names[i] = NULL; + } + w->nfonts = nfonts; + } +#ifdef WUSS_FURNITURE + w->furniture.dragging = NULL; + w->furniture.drag.x = 0; + w->furniture.drag.y = 0; + w->furniture_ops = &wuss__furniture_default_ops; +#endif +#ifdef WUSS_ICONS + w->pressed_icon = NULL; + w->hover_icon = NULL; +#endif +#ifdef WUSS_MENUS + w->menu_chain = NULL; + w->menu_task = NULL; + w->menu_eat_up = 0; +#endif + + w->ndirty = 0; + + w->layout = NULL; + w->cascade.x = 0; + w->cascade.y = 0; + w->pointer.x = 0; + w->pointer.y = 0; + + list_init(&w->z_order); + list_init(&w->tasks); + + *wuss = w; + + return result_OK; +} diff --git a/libraries/wuss/core/destroy.c b/libraries/wuss/core/destroy.c new file mode 100644 index 00000000..b8582de6 --- /dev/null +++ b/libraries/wuss/core/destroy.c @@ -0,0 +1,56 @@ +/* wuss/destroy.c -- wuss - minimal window manager */ + +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "impl.h" + +void wuss_destroy(wuss_t *doomed) +{ + list_t *e; + + if (doomed == NULL) + return; + +#ifdef WUSS_MENUS + /* Drop any open menu chain first: its windows are freed by the z_order + * sweep below, but the chain nodes and their icon-handle arrays are not. */ + wuss_menu_close(doomed->menu_chain); +#endif + + e = doomed->z_order.next; + while (e != NULL) + { + list_t *next; + + next = e->next; +#ifdef WUSS_ICONS + wuss__icons_free(wuss__window_from_link(e)); +#endif + wuss__free(doomed, e); + e = next; + } + + /* Windows are gone; QUIT then free any tasks the caller left registered, + * so a task's client-owned task_data (freed only from its QUIT handler, + * per the task_data-ownership contract) is not leaked. */ + e = doomed->tasks.next; + while (e != NULL) + { + list_t *next; + wuss_event_t event; + + next = e->next; + event.kind = wuss_EVENT_QUIT; + (void) wuss__deliver(wuss__task_from_link(e), NULL, &event); + wuss__free(doomed, e); + e = next; + } + + packer_destroy(doomed->layout); + wuss__free(doomed, doomed->palette); + wuss__free(doomed, doomed); /* reads doomed->alloc.free before freeing */ +} diff --git a/libraries/wuss/core/get-font.c b/libraries/wuss/core/get-font.c new file mode 100644 index 00000000..8e2f75ac --- /dev/null +++ b/libraries/wuss/core/get-font.c @@ -0,0 +1,42 @@ +/* wuss/core/get-font.c -- look up a registered font slot by index */ + +#include + +#include "impl.h" + +bmfont_t *wuss_get_font(const wuss_t *wuss) +{ + assert(wuss != NULL); + + return wuss->fonts[0]; +} + +bmfont_t *wuss_get_font_n(const wuss_t *wuss, int index) +{ + assert(wuss != NULL); + + if (index < 0 || index >= wuss_MAX_FONTS) + return NULL; + + return wuss->fonts[index]; +} + +wuss_font_class_t wuss_get_font_class_n(const wuss_t *wuss, int index) +{ + assert(wuss != NULL); + + if (index < 0 || index >= wuss_MAX_FONTS) + return wuss_FONT_CLASS_NONE; + + return wuss->font_classes[index]; +} + +const char *wuss_get_font_name_n(const wuss_t *wuss, int index) +{ + assert(wuss != NULL); + + if (index < 0 || index >= wuss_MAX_FONTS) + return NULL; + + return wuss->font_names[index]; +} diff --git a/libraries/wuss/core/get-pointer.c b/libraries/wuss/core/get-pointer.c new file mode 100644 index 00000000..db0b6d45 --- /dev/null +++ b/libraries/wuss/core/get-pointer.c @@ -0,0 +1,12 @@ +/* wuss/get-pointer.c -- wuss - minimal window manager */ + +#include + +#include "impl.h" + +point_t wuss_get_pointer(const wuss_t *wuss) +{ + assert(wuss != NULL); + + return wuss->pointer; +} diff --git a/libraries/wuss/core/idle.c b/libraries/wuss/core/idle.c new file mode 100644 index 00000000..a27b1ef2 --- /dev/null +++ b/libraries/wuss/core/idle.c @@ -0,0 +1,31 @@ +/* wuss/idle.c -- wuss - minimal window manager */ + +#include "impl.h" + +result_t wuss_idle(wuss_t *wuss) +{ + wuss_event_t event; + result_t rc; + list_t *e; + + event.kind = wuss_EVENT_IDLE; + + rc = result_OK; + for (e = wuss->tasks.next; e != NULL; ) + { + wuss_task_t *task; + list_t *next; + result_t crc; + + task = (wuss_task_t *) e; + next = e->next; /* an autoclose task may free its own node in the handler */ + + crc = wuss__deliver(task, NULL, &event); + if (crc != result_OK && rc == result_OK) + rc = crc; + + e = next; + } + + return rc; +} diff --git a/libraries/wuss/core/impl.h b/libraries/wuss/core/impl.h new file mode 100644 index 00000000..33ea2100 --- /dev/null +++ b/libraries/wuss/core/impl.h @@ -0,0 +1,689 @@ +/* wuss/impl.h -- wuss - minimal window manager */ + +#ifndef IMPL_H +#define IMPL_H + +#include +#include + +#include "base/debug.h" +#include "base/utils.h" +#include "datastruct/list.h" +#include "geom/box.h" +#include "geom/packer.h" +#include "geom/point.h" +#include "geom/size.h" +#include "framebuf/screen.h" +#include "framebuf/bmfont.h" +#include "utils/barith.h" + +#include "wuss/wuss.h" +#include "wuss/window.h" +#include "wuss/task.h" + +#ifdef WUSS_FURNITURE +#include "../furniture.h" +#endif +#ifdef WUSS_ICONS +#include "../icon.h" +#endif +#ifdef WUSS_MENUS +#include "../menu.h" +#endif + +#define WUSS_TITLE_MAX 63 +#define WUSS_DEFAULT_TITLEBAR_HEIGHT 20 + +#define WUSS_MAX_DIRTY 16 /* dirty regions tracked before further invalidations get merged into the last entry */ + +/* ponytail: fixed cap; if hit, remaining pieces are carried through + * unclipped against further occluders rather than growing storage -- safe, + * just some avoidable redraw work, never wrong */ +#define WUSS_MAX_INVALIDATE_PIECES 32 + +#define WUSS_PLACE_GUTTER 6 /* px left between windows auto-placed by wuss_window_create_placed */ + +#define WUSS_BUTTON_INSET 3 /* shared by close/back/toggle/resize furniture buttons and scrollbar breadth */ + +#ifdef WUSS_ICONS +#define WUSS_FRAME_CAPTION_INSET 8 /* x offset of a wuss_ICON_TYPE_FRAME caption from the frame's left edge */ +#define WUSS_FRAME_CAPTION_PAD 2 /* gap left in the frame's top edge either side of the caption */ + +/* Font slot (see wuss_create's fonts[]) consulted for menu decoration glyphs: + * the selection tick and the submenu arrow. When the slot is empty those are + * drawn as vector strokes instead. A font in this slot is expected to carry a + * checkmark bitmap in its WUSS_GLYPH_TICK cell and an arrowhead in its + * WUSS_GLYPH_SUBMENU cell; the ASCII defaults let an ordinary bmfont stand in. */ +#define WUSS_SYMBOL_FONT 2 +#ifndef WUSS_GLYPH_TICK +#define WUSS_GLYPH_TICK '*' +#endif +#ifndef WUSS_GLYPH_SUBMENU +#define WUSS_GLYPH_SUBMENU '>' +#endif +#endif +#define WUSS_MIN_CONTENT 20 /* resize-drag floor: content can never be squeezed smaller than this */ +#define WUSS_SCROLL_INSET 2 /* sausage cross-axis margin from its well's edges, purely cosmetic */ +#define WUSS_DIVIDER_PX 1 /* interior rule between the content area and the furniture on its right/bottom */ + +/* Internal per-window state, distinct from the public wuss_window_flags_t + * appearance flags a caller sets at creation -- room to grow without + * widening struct wuss_window by an int per flag. */ +typedef enum wuss_window_state +{ + wuss_WINDOW_STATE_NONE = 0, + wuss_WINDOW_STATE_TOGGLED = 1 << 0 /* currently at TOGGLE_SIZE's "full" size */ +} +wuss_window_state_t; + +/* Internal wuss_task::flags bits. */ +enum +{ + wuss_TASK__AUTOCLOSE = 1u << 0, /* self-destruct once the last window closes */ + wuss_TASK__REAPING = 1u << 1 /* teardown in progress; suppress autoclose */ +}; + +/* A registered task. Owns a list of windows (linked through each window's + * second embedded list_t, task_link) and is the single delivery target for + * their events plus the app-wide IDLE/PALETTE/MENU_SELECT notifications. + * Tasks are chained through wuss::tasks in registration order. */ +struct wuss_task +{ + list_t link; /* anchor node in wuss::tasks; must be first */ + wuss_t *wuss; + wuss_window_fn_t *handle; /* nullable: no events delivered */ + void *task_data; + const char *name; /* borrowed, may be NULL */ + list_t windows; /* anchor; nodes are window->task_link */ + unsigned int flags; /* wuss_TASK__* */ +}; + +struct wuss +{ + screen_t *scr; + bmfont_t *fonts[wuss_MAX_FONTS]; /* slot 0 is the system + * font; unset slots NULL; not owned */ + wuss_font_class_t font_classes[wuss_MAX_FONTS]; /* parallel to + * fonts[]; NONE for an unset slot */ + const char *font_names[wuss_MAX_FONTS]; /* parallel to + * fonts[]; borrowed; NULL if unset + * or given no name */ + int nfonts; /* entries filled from wuss_create */ + wuss_alloc_t alloc; /* malloc/realloc/free hooks, copied in + * by wuss_create; used for every heap + * block this wuss_t owns */ + colour_t *palette; /* owned */ + int npalette; + /* Concrete 0..npalette-1 index for each symbolic wuss_colour_t, indexed by + * (c - wuss_COLOUR_SYMBOLIC). Unused slots hold wuss_COLOUR_SYMBOLIC so a + * stray symbolic still fails a range check. Rebuilt by + * wuss__rebuild_palettecache on create and every palette/config change. */ + wuss_colour_t palettecache[128]; +#ifdef WUSS_FURNITURE + wuss_furniture_palette_t furniture_colours; +#endif +#if defined(WUSS_FURNITURE) || defined(WUSS_ICONS) + wuss_colour_t bevel_light; /* work-area button top/left edge */ + wuss_colour_t bevel_dark; /* work-area button bottom/right edge */ + wuss_colour_t accent_bg; /* default action button fill */ + wuss_colour_t accent_fg; /* default action button text */ +#endif + wuss_backdrop_t backdrop; /* colour==wuss_NO_BACKGROUND: none */ +#ifdef WUSS_FURNITURE + int titlebar_height; +#endif + list_t z_order; /* anchor; head = topmost window */ + list_t tasks; /* anchor; registered tasks, in + * wuss_task_create order */ +#ifdef WUSS_FURNITURE + struct wuss__furniture furniture; /* drag state */ + const wuss__furniture_ops_t *furniture_ops; /* core->furniture dispatch; + * wuss_create defaults it to + * wuss__furniture_default_ops */ +#endif + box_t dirty[WUSS_MAX_DIRTY]; /* accumulated by wuss_invalidate; reset by a redraw */ + int ndirty; + packer_t *layout; /* owned; occupied screen area for + * wuss_window_create_placed, lazily + * created on first auto-placement */ + point_t cascade; /* next cascade offset, used once the + * layout packer has no room left */ + point_t pointer; /* last pointer position, screen + * space, from any mouse click/move */ +#ifdef WUSS_ICONS + wuss_icon_t *pressed_icon; /* button icon held down, NULL when + * idle; released on any MOUSE_UP + * even if a new window now covers + * its owner */ + wuss_icon_t *hover_icon; /* icon the pointer is currently + * over, NULL when none; drives + * hover-highlight repaint of + * menu-entry icons */ +#endif +#ifdef WUSS_MENUS + struct wuss__menu *menu_chain; /* head (root) of the open pop-up + * menu chain, NULL when none open; + * consulted by mouse-click.c to + * dismiss on a click-away */ + wuss_task_t *menu_task; /* internal task owning every + * borderless menu window; created + * lazily on first wuss_menu_open, + * freed by wuss_destroy's task + * sweep */ + int menu_eat_up; /* a menu opened on the last + * MOUSE_DOWN; swallow its matching + * MOUSE_UP so the release does not + * immediately pick row 0 */ +#endif +}; + +struct wuss_window +{ + list_t link; /* z-order chain; must be first member */ + list_t task_link; /* chain through the owning task's window + * list (wuss_task::windows) */ + wuss_t *wuss; + box_t visible; /* full on-screen footprint: content expanded + * outward by any titlebar/outline furniture */ + wuss_task_t *task; /* owning task, set at create, immutable; never + * NULL */ + wuss_backdrop_t bg; /* content background; colour==wuss_NO_BACKGROUND: none */ + wuss_window_flags_t flags; + point_t scroll; /* offset into virtual content space of the + * content box's top-left; see wuss_window_set_scroll */ + size2d_t doc; /* virtual document extent, set at creation */ + size2d_t min_doc; /* resize floor, set at creation; see + * wuss__min_content */ +#ifdef WUSS_FURNITURE + wuss_window_state_t state; /* see wuss_window_state_t */ +#endif + box_t packed; /* region wuss_window_create_placed took + * out of wuss->layout (footprint + gutter), + * to give back on close/move; empty if not + * auto-placed or already released */ +#ifdef WUSS_FURNITURE + box_t pre_toggle; /* visible bounds to restore on the next toggle */ + char title[WUSS_TITLE_MAX + 1]; +#endif +#ifdef WUSS_ICONS + wuss_icon_t **icons; /* owned; array of owned icon pointers */ + int nicons; + int cap_icons; +#endif +}; + +/* Thin wrappers over the furniture_ops dispatch so core call sites that only + * poke furniture for a redraw don't each need their own #ifdef WUSS_FURNITURE + * guard -- they compile to nothing in a no-furniture build. The routing hooks + * (hit_test, toggle_size, drag_*) stay unwrapped: their call sites sit inside + * furniture-only logic that is already whole-block guarded. Named "chrome" + * rather than "furniture" to steer clear of the wuss__furniture_* dispatch + * targets these forward to. */ +#ifdef WUSS_FURNITURE +static inline void wuss__chrome_draw(wuss_t *wuss, + wuss_window_t *window, + const box_t *full) +{ + wuss->furniture_ops->draw(wuss, window, full); +} + +static inline void wuss__chrome_repaint(wuss_window_t *window) +{ + window->wuss->furniture_ops->invalidate(window); +} + +static inline void wuss__chrome_repaint_for(wuss_window_t *window, + const box_t *visible) +{ + window->wuss->furniture_ops->invalidate_for(window, visible); +} +#else +static inline void wuss__chrome_draw(wuss_t *wuss, + wuss_window_t *window, + const box_t *full) +{ + (void) wuss; + (void) window; + (void) full; +} + +static inline void wuss__chrome_repaint(wuss_window_t *window) +{ + (void) window; +} + +static inline void wuss__chrome_repaint_for(wuss_window_t *window, + const box_t *visible) +{ + (void) window; + (void) visible; +} +#endif + +wuss_window_t *wuss__window_at(wuss_t *wuss, point_t p); + +/* Rebuild wuss->palettecache (white, black and the symbolic[] table) from + * the current palette and the stored chrome colours. Call after the palette + * or any chrome colour changes; the chrome fields must already be concrete + * indices (resolve config through wuss__resolve_colour before storing). */ +void wuss__rebuild_palettecache(wuss_t *wuss); + +/* Concrete 0..npalette-1 palette index for any wuss_colour_t: a symbolic + * value (>= wuss_COLOUR_SYMBOLIC, bar wuss_NO_BACKGROUND) via the cache, + * everything else -- raw indices, wuss_NO_BACKGROUND -- unchanged. */ +static inline wuss_colour_t wuss__resolve_colour(const wuss_t *wuss, + wuss_colour_t c) +{ + if (c >= wuss_COLOUR_SYMBOLIC && c != wuss_NO_BACKGROUND) + return wuss->palettecache[c - wuss_COLOUR_SYMBOLIC]; + return c; +} + +/* Allocation through a wuss_t's configured hooks (see struct wuss::alloc). + * Every heap block a wuss_t owns -- windows, icons, icon-pointer arrays, menu + * nodes -- goes through these so a caller can supply its own allocator. */ +static inline void *wuss__malloc(const wuss_t *wuss, size_t size) +{ + return wuss->alloc.malloc(size); +} + +static inline void *wuss__realloc(const wuss_t *wuss, void *ptr, size_t size) +{ + return wuss->alloc.realloc(ptr, size); +} + +static inline void wuss__free(const wuss_t *wuss, void *ptr) +{ + wuss->alloc.free(ptr); +} + +/* Duplicate a NUL-terminated string through an allocator's malloc hook + * (strdup is POSIX, not C99, so it is not assumed). Returns NULL on OOM or a + * NULL s. Takes wuss_alloc_t, not wuss_t, so the shared components (which + * keep a copy of the hooks, not the wuss_t) can use it too. */ +static inline char *wuss__alloc_strdup(const wuss_alloc_t *a, const char *s) +{ + size_t len; + char *copy; + + if (s == NULL) + return NULL; + + len = strlen(s) + 1; + copy = a->malloc(len); + if (copy != NULL) + memcpy(copy, s, len); + return copy; +} + +/* array_grow (utils/array.h) done through an allocator's realloc hook: the + * next-power-of-two doubling, same element/return semantics (0 ok, 1 OOM). + * used/need/minimum are element counts. See libraries/utils/array/grow.c. */ +static inline int wuss__array_grow(const wuss_alloc_t *a, + void **block, + size_t elemsize, + int used, + int *allocated, + int need, + int minimum) +{ + int to_allocate; + void *grown; + + need += used; + need = MAX(need, minimum); + if (need <= *allocated) + return 0; + + to_allocate = power2gt(need - 1); + + grown = a->realloc(*block, elemsize * (size_t) to_allocate); + if (grown == NULL) + return 1; + + *block = grown; + *allocated = to_allocate; + return 0; +} + +/* Range-check a backdrop spec against the palette: its colour, and -- when a + * non-solid pattern is set -- its pattern index and background colour. + * Returns result_OK or result_WUSS_BAD_COLOUR. */ +result_t wuss__validate_backdrop(const wuss_t *wuss, + const wuss_backdrop_t *backdrop); + +/* Paint "backdrop" into "area" on "scr": a flat fill in the SOLID case, + * otherwise the 8x8 pattern tiled in colour over pattern_bg, phased against + * (origin_x, origin_y). No-op when colour is wuss_NO_BACKGROUND. Caller sets + * scr->clip. */ +void wuss__fill_backdrop(screen_t *scr, + const colour_t *palette, + const wuss_backdrop_t *backdrop, + const box_t *area, + int origin_x, + int origin_y); + +/* clamp "desired" to the window's scrollable range; step the current offset + * by "delta" and apply it. Core (furniture-independent) -- used by the wheel + * and, when built, the scrollbar furniture. */ +point_t wuss__scroll_clamp(const wuss_window_t *window, point_t desired); +void wuss__scroll_step(wuss_window_t *window, point_t delta); + +#ifdef WUSS_FURNITURE +void wuss__titlebar_box(const wuss_window_t *window, box_t *out); +void wuss__close_box(const wuss_window_t *window, box_t *out); +void wuss__content_box(const wuss_window_t *window, box_t *out); +#else +/* No furniture: the content area is the whole visible footprint. */ +static inline void wuss__content_box(const wuss_window_t *window, box_t *out) +{ + *out = window->visible; +} +#endif +void wuss__invalidate_clipped(wuss_window_t *window, + const box_t *box); +void wuss__invalidate_minus(wuss_t *wuss, + const box_t *whole, + const box_t *keep); +void wuss__invalidate_uncovered(wuss_window_t *window); + +/* Clip "box" (screen space) down to the parts not already covered by + * windows above "window" in the z-order, writing the surviving pieces to + * "out" (capacity WUSS_MAX_INVALIDATE_PIECES) and returning their count. */ +int wuss__clip_to_visible(wuss_window_t *window, + const box_t *box, + box_t *out); + +/* Subtract each of "cuts" (an array of "ncuts" boxes) from "whole", writing + * the surviving pieces to "out" (capacity WUSS_MAX_INVALIDATE_PIECES) and + * returning their count. */ +int wuss__subtract_boxes(const box_t *whole, + const box_t *cuts, + int ncuts, + box_t *out); + +/* Given "n" single-rect blits, each moving "clean[i]" to "dest[i]", find an + * order in which no blit's destination overwrites a still-unread source of a + * later blit. Writes the piece indices to "order" (capacity + * WUSS_MAX_INVALIDATE_PIECES) and returns non-zero on success; returns zero + * when the overlap graph has a cycle and no safe order exists. */ +int wuss__order_pieces(const box_t *clean, + const box_t *dest, + int n, + int *order); + +/* Slide the pixels of "src" (nsrc pieces the caller has already carved clear + * of occluders and stale regions) by (dx, dy), clipping each destination + * against the screen and the windows above "window", clobber-ordered so no + * blit steps on another's source; if "clip" is non-NULL scr->clip is pinned + * to it for the copy (and restored after), else the clip is left alone. On + * success + * fills "copied" (capacity WUSS_MAX_INVALIDATE_PIECES) with the pieces + * actually blitted, sets "*ncopied" and returns 1 -- the caller repaints + * whatever "copied" misses. Returns 0 (with "*ncopied" zeroed) when there is + * no safe fast path, and the caller must fall back to a full invalidate. */ +int wuss__blit_pieces(wuss_window_t *window, + const box_t *src, + int nsrc, + int dx, + int dy, + const box_t *clip, + box_t *copied, + int *ncopied); + +/* Recover the owning wuss_window_t from a node in a task's window list + * (window->task_link). task_link is not the first member, so a plain cast + * won't do. */ +#define wuss__window_from_task_link(node) \ + ((wuss_window_t *) ((char *) (node) - offsetof(struct wuss_window, task_link))) + +/* Recover the owning wuss_window_t from a node in the z-order chain + * (window->link). link is the first member, so this is just a cast -- but + * naming it keeps every z-order walk honest about depending on that. */ +#define wuss__window_from_link(node) \ + ((wuss_window_t *) (void *) (node)) + +/* Recover the owning wuss_task_t from a node in the task chain (task->link). + * link is the first member, so this is just a cast -- but naming it keeps + * every task walk honest about depending on that. */ +#define wuss__task_from_link(node) \ + ((wuss_task_t *) (void *) (node)) + +/* The single dispatch chokepoint. Delivers "ev" to "task"'s handle, passing + * "win_or_null" as the window (NULL for task-view events). No-op (returns + * result_OK) when task is NULL or has no handle. A debug build also asserts + * ev->kind is valid for the recipient view. Every emit site routes through + * here. */ +result_t wuss__deliver(wuss_task_t *task, + wuss_window_t *win_or_null, + const wuss_event_t *ev); + +/* Notify a window's task that it has been moved or resized, via + * wuss_EVENT_OPEN; the return value is discarded, matching how furniture + * drawing and other in-line notifications are treated. */ +static inline void wuss__notify_open(wuss_window_t *window) +{ + wuss_event_t event; + + event.kind = wuss_EVENT_OPEN; + (void) wuss__deliver(window->task, window, &event); +} + +static inline int wuss__size_ok(int width, int height) +{ + return width > 0 && height > 0; +} + +/* Give an auto-placed window's slot back to the layout packer and stop + * tracking it, so a later close/move/resize doesn't release it twice. A + * no-op for windows that were never auto-placed (empty "packed"). */ +static inline void wuss__release_packed(wuss_window_t *window) +{ + if (box_is_empty(&window->packed)) + return; + + (void) packer_release(window->wuss->layout, &window->packed); + box_reset(&window->packed); +} + +/* The floor a resize-drag or toggle-size will shrink a window's content to: + * the client's min_doc where it set one, but never below WUSS_MIN_CONTENT (a + * window must stay big enough to grab) nor above the window's own doc extent + * (a window can't be forced larger than the document it shows). */ +static inline void wuss__min_content(const wuss_window_t *window, + size2d_t *min) +{ + min->w = CLAMP(window->min_doc.w, WUSS_MIN_CONTENT, MAX(window->doc.w, + WUSS_MIN_CONTENT)); + min->h = CLAMP(window->min_doc.h, WUSS_MIN_CONTENT, MAX(window->doc.h, + WUSS_MIN_CONTENT)); +} + +#ifdef WUSS_FURNITURE +static inline int wuss__titlebar_height_for(const wuss_t *wuss, + wuss_window_flags_t flags) +{ + return (flags & wuss_WINDOW_NO_TITLEBAR) ? 0 : wuss->titlebar_height; +} + +static inline int wuss__titlebar_height(const wuss_window_t *window) +{ + return wuss__titlebar_height_for(window->wuss, window->flags); +} + +static inline int wuss__window_toggled(const wuss_window_t *window) +{ + return (window->state & wuss_WINDOW_STATE_TOGGLED) != 0; +} + +static inline void wuss__window_set_toggled(wuss_window_t *window, + int toggled) +{ + if (toggled) + window->state |= wuss_WINDOW_STATE_TOGGLED; + else + window->state &= (wuss_window_state_t) ~wuss_WINDOW_STATE_TOGGLED; +} + +static inline int wuss__outline_px_for(wuss_window_flags_t flags) +{ + return (flags & wuss_WINDOW_NO_OUTLINE) ? 0 : 1; +} + +static inline int wuss__outline_px(const wuss_window_t *window) +{ + return wuss__outline_px_for(window->flags); +} + +/* ponytail: falls back to wuss's own titlebar height when the window has + * none, so NO_TITLEBAR windows that still opt into scrollbars/resize match + * their titled siblings instead of a hardcoded size; the hardcoded default + * is only a last-resort floor if even that isn't positive */ +static inline int wuss__button_size_for(const wuss_t *wuss, + wuss_window_flags_t flags) +{ + int size; + + size = wuss__titlebar_height_for(wuss, flags) - 2 * WUSS_BUTTON_INSET; + if (size > 0) + return size; + + size = wuss->titlebar_height - 2 * WUSS_BUTTON_INSET; + + return (size > 0) ? size : WUSS_DEFAULT_TITLEBAR_HEIGHT - 2 * WUSS_BUTTON_INSET; +} + +static inline int wuss__button_size(const wuss_window_t *window) +{ + return wuss__button_size_for(window->wuss, window->flags); +} + +/* how much of a content box's width/height is furniture (scrollbars, the + * resize icon's corner), reserved outside the content area rather than + * carved out of it -- shared by wuss__content_box (subtracts it back off + * visible) and window creation/resize (add it to visible up front) so the + * two stay consistent with each other */ +static inline void wuss__furniture_carve_for(wuss_window_flags_t flags, + int button_size, + point_t *carve) +{ + carve->x = (flags & wuss_WINDOW_NO_VSCROLL) ? 0 : button_size; + carve->y = (flags & wuss_WINDOW_NO_HSCROLL) ? 0 : button_size; + + if (!(flags & wuss_WINDOW_NO_RESIZE) && + (flags & wuss_WINDOW_NO_VSCROLL) && + (flags & wuss_WINDOW_NO_HSCROLL)) + { + carve->x = button_size; + carve->y = button_size; + } + + /* where furniture abuts the content area, a rule divides the two */ + if (carve->x > 0) + carve->x += WUSS_DIVIDER_PX; + if (carve->y > 0) + carve->y += WUSS_DIVIDER_PX; +} +#else /* !WUSS_FURNITURE */ +/* No furniture: every geometry helper collapses to "no chrome", so the core + * window create/move/resize maths still compiles and yields visible == + * content. */ +static inline int wuss__titlebar_height_for(const wuss_t *wuss, + wuss_window_flags_t flags) +{ + (void) wuss; (void) flags; + return 0; +} + +static inline int wuss__titlebar_height(const wuss_window_t *window) +{ + (void) window; + return 0; +} + +static inline int wuss__outline_px_for(wuss_window_flags_t flags) +{ + (void) flags; + return 0; +} + +static inline int wuss__outline_px(const wuss_window_t *window) +{ + (void) window; + return 0; +} + +static inline int wuss__button_size_for(const wuss_t *wuss, + wuss_window_flags_t flags) +{ + (void) wuss; (void) flags; + return 0; +} + +static inline int wuss__button_size(const wuss_window_t *window) +{ + (void) window; + return 0; +} + +static inline void wuss__furniture_carve_for(wuss_window_flags_t flags, + int button_size, + point_t *carve) +{ + (void) flags; (void) button_size; + carve->x = 0; + carve->y = 0; +} +#endif /* WUSS_FURNITURE */ + +/* Largest content width/height whose visible box (content + outline + + * titlebar + scrollbar/resize carve) still fits the screen from the + * window's current top-left. Never returns below WUSS_MIN_CONTENT: a + * window jammed hard against the far edge stays grabbable even though it + * then overhangs. Shared by wuss_window_create and wuss_window_resize so + * no path can produce a window larger than the screen. */ +static inline void wuss__max_content_on_screen(const wuss_window_t *window, + size2d_t *max) +{ + int outline_px, titlebar_height; + point_t carve; + + outline_px = wuss__outline_px(window); + titlebar_height = wuss__titlebar_height(window); + wuss__furniture_carve_for(window->flags, wuss__button_size(window), &carve); + + max->w = window->wuss->scr->size.w - window->visible.x0 + - 2 * outline_px - carve.x; + max->h = window->wuss->scr->size.h - window->visible.y0 + - 2 * outline_px - titlebar_height - carve.y; + + max->w = MAX(max->w, WUSS_MIN_CONTENT); + max->h = MAX(max->h, WUSS_MIN_CONTENT); +} + +/* Largest content width/height whose visible box fits the screen when the + * window may be repositioned -- i.e. the whole screen less chrome, with no + * allowance for the window's current top-left. Toggle-size uses this and + * then nudges the top-left toward the origin by the minimum needed to fit. + * Floored at WUSS_MIN_CONTENT like wuss__max_content_on_screen. + * ponytail: a named helper (~6 lines) to stay parallel with its sibling + * above rather than open-coded into toggle-action.c. */ +static inline void wuss__max_content_anywhere_on_screen(const wuss_window_t *window, + size2d_t *max) +{ + int outline_px, titlebar_height; + point_t carve; + + outline_px = wuss__outline_px(window); + titlebar_height = wuss__titlebar_height(window); + wuss__furniture_carve_for(window->flags, wuss__button_size(window), &carve); + + max->w = window->wuss->scr->size.w - 2 * outline_px - carve.x; + max->h = window->wuss->scr->size.h + - 2 * outline_px - titlebar_height - carve.y; + + max->w = MAX(max->w, WUSS_MIN_CONTENT); + max->h = MAX(max->h, WUSS_MIN_CONTENT); +} + +#endif /* IMPL_H */ diff --git a/libraries/wuss/invalidate.c b/libraries/wuss/core/invalidate.c similarity index 89% rename from libraries/wuss/invalidate.c rename to libraries/wuss/core/invalidate.c index 1bca4190..d1a8535d 100644 --- a/libraries/wuss/invalidate.c +++ b/libraries/wuss/core/invalidate.c @@ -1,4 +1,4 @@ -/* invalidate.c -- wuss - minimal window manager */ +/* wuss/invalidate.c -- wuss - minimal window manager */ #include @@ -68,11 +68,18 @@ result_t wuss_invalidate(wuss_t *wuss, const box_t *box) while (changed); if (wuss->ndirty < WUSS_MAX_DIRTY) + { wuss->dirty[wuss->ndirty++] = cur; + } else + { /* ponytail: array full, fold into the last entry rather than growing * storage; over-approximates that entry's area but stays correct */ + logf_info("wuss_invalidate: %d dirty regions, coalescing " + "(%d,%d)-(%d,%d) into the last entry", + WUSS_MAX_DIRTY, cur.x0, cur.y0, cur.x1, cur.y1); box_union(&wuss->dirty[WUSS_MAX_DIRTY - 1], &cur, &wuss->dirty[WUSS_MAX_DIRTY - 1]); + } return result_OK; } diff --git a/libraries/wuss/core/mouse-click.c b/libraries/wuss/core/mouse-click.c new file mode 100644 index 00000000..108c1307 --- /dev/null +++ b/libraries/wuss/core/mouse-click.c @@ -0,0 +1,312 @@ +/* wuss/mouse-click.c -- wuss - minimal window manager */ + +#include "impl.h" + +result_t wuss_mouse_click(wuss_t *wuss, + point_t p, + wuss_button_t button, + wuss_mouse_action_t action, + wuss_window_t **hit) +{ + wuss_window_t *win; + wuss_event_t event; + int x, y; + + x = p.x; + y = p.y; + + wuss->pointer = p; + +#ifdef WUSS_ICONS + /* Release a held button icon on a MOUSE_UP that will NOT reach it through the + * normal window hit-test: the up may land on a window that opened over the + * icon's owner on MOUSE_DOWN. When the up does land back on the pressed icon, + * leave it held so the icon-hit path below completes the click (latch + * radio/option state, toggle a menu tick). */ + if (action == wuss_MOUSE_UP && wuss->pressed_icon != NULL) + { + wuss_icon_t *pressed = wuss->pressed_icon; + wuss_window_t *presswin = pressed->window; + box_t content; + point_t doc_point; + int still_over; + + wuss__content_box(presswin, &content); + doc_point.x = x - content.x0 + presswin->scroll.x; + doc_point.y = y - content.y0 + presswin->scroll.y; + + still_over = (wuss__window_at(wuss, p) == presswin && + wuss__icon_hit_test(presswin, doc_point) == pressed); + + if (!still_over) + { + wuss->pressed_icon = NULL; + if (wuss__icon_pressed(pressed)) + { + wuss__icon_set_state(pressed, wuss_ICON_STATE_PRESSED, 0); + wuss__icon_invalidate(pressed); + } + } + } +#endif + +#ifdef WUSS_FURNITURE + if (action == wuss_MOUSE_UP && wuss->furniture.dragging != NULL) + { + win = wuss->furniture.dragging; + if (hit != NULL) + *hit = win; + wuss->furniture.dragging = NULL; + wuss->furniture.drag_kind = wuss_FURNITURE_DRAG_NONE; + + return result_OK; + } +#endif + + win = wuss__window_at(wuss, p); + if (hit != NULL) + *hit = win; + +#ifdef WUSS_MENUS + /* The MOUSE_UP that follows the MENU press which opened the chain is spent: + * without this it would land on the new menu's row 0 and pick it. */ + if (action == wuss_MOUSE_UP && wuss->menu_eat_up) + { + wuss->menu_eat_up = 0; + return result_OK; + } + + /* Any fresh press ends the eat-up window: the release it pairs with is a + * real click, not the tail of the menu-opening press. */ + if (action == wuss_MOUSE_DOWN) + wuss->menu_eat_up = 0; + + /* A press outside every window in the open menu chain dismisses the chain + * and is spent doing so. Presses inside the chain fall through to the menu + * window's own delegate. */ + if (action == wuss_MOUSE_DOWN && wuss__menu_click_outside(wuss, win)) + return result_OK; +#endif + + if (win == NULL) + return result_OK; + +#ifdef WUSS_FURNITURE + { + wuss_furniture_region_t region; + + region = wuss->furniture_ops->hit_test(win, POINT(x, y)); + + if (region == wuss_FURNITURE_CLOSE && + action == wuss_MOUSE_DOWN && + (button & wuss_BUTTON_SELECT)) + { + /* User close-icon path: routes through try_close, so the task gets + * PRE_CLOSE (may veto) then CLOSE and, if not vetoed, wuss tears the + * window down. A veto's non-OK return propagates to the caller. */ + return wuss_window_try_close(win); + } + + if (region == wuss_FURNITURE_BACK && action == wuss_MOUSE_DOWN) + { + if (button & wuss_BUTTON_SELECT) + wuss_window_restack(win, wuss_ZORDER_BACK); + else if (button & wuss_BUTTON_ADJUST) + wuss_window_restack(win, wuss_ZORDER_FRONT); + return result_OK; + } + + if (region == wuss_FURNITURE_TOGGLE_SIZE || + region == wuss_FURNITURE_VSCROLL_UP || + region == wuss_FURNITURE_VSCROLL_DOWN || + region == wuss_FURNITURE_HSCROLL_LEFT || + region == wuss_FURNITURE_HSCROLL_RIGHT) + { + if (action == wuss_MOUSE_DOWN && + (button & (wuss_BUTTON_SELECT | wuss_BUTTON_ADJUST))) + { + /* Adjust-clicking a scroll arrow steps the opposite way to the arrow + * it points, so one arrow can be worked in both directions without + * moving the pointer. Toggle-size stays Select-only. */ + int step; + + /* Select wins a Select+Adjust chord, so a chord never scrolls + * backwards unexpectedly. */ + step = (button & wuss_BUTTON_SELECT) ? WUSS_SCROLL_STEP + : -WUSS_SCROLL_STEP; + + switch (region) + { + case wuss_FURNITURE_TOGGLE_SIZE: + if (button & wuss_BUTTON_SELECT) + wuss->furniture_ops->toggle_size(win); + break; + case wuss_FURNITURE_VSCROLL_UP: + wuss__scroll_step(win, POINT(0, -step)); + break; + case wuss_FURNITURE_VSCROLL_DOWN: + wuss__scroll_step(win, POINT(0, step)); + break; + case wuss_FURNITURE_HSCROLL_LEFT: + wuss__scroll_step(win, POINT(-step, 0)); + break; + case wuss_FURNITURE_HSCROLL_RIGHT: + wuss__scroll_step(win, POINT(step, 0)); + break; + default: + break; + } + } + return result_OK; + } + + if (region == wuss_FURNITURE_CLOSE || region == wuss_FURNITURE_TITLE) + { + if (action == wuss_MOUSE_DOWN && + (button & (wuss_BUTTON_SELECT | wuss_BUTTON_ADJUST))) + { + box_t content; + + if (button & wuss_BUTTON_SELECT) + wuss_window_restack(win, wuss_ZORDER_FRONT); + + wuss__content_box(win, &content); + wuss->furniture.dragging = win; + wuss->furniture.drag_kind = wuss_FURNITURE_DRAG_MOVE; + wuss->furniture.drag.x = x - content.x0; + wuss->furniture.drag.y = y - content.y0; + } + return result_OK; + } + + if (region == wuss_FURNITURE_RESIZE || + region == wuss_FURNITURE_VSCROLL_WELL || + region == wuss_FURNITURE_HSCROLL_WELL) + { + if (action == wuss_MOUSE_DOWN && + (button & (wuss_BUTTON_SELECT | wuss_BUTTON_ADJUST))) + { + point_t scroll; + + /* Only resize raises the window; dragging a scrollbar well must not + * reorder the stack. */ + if ((button & wuss_BUTTON_SELECT) && region == wuss_FURNITURE_RESIZE) + wuss_window_restack(win, wuss_ZORDER_FRONT); + + /* A click landing in the well itself (not on the sausage) pages the + * content one visible extent towards the click, RISC OS style, keeping + * one step of overlap. The drag state is still armed below so a press + * that then moves onto the sausage keeps working. */ + if (region == wuss_FURNITURE_VSCROLL_WELL || + region == wuss_FURNITURE_HSCROLL_WELL) + { + box_t sausage; + box_t content; + int page; + + wuss__content_box(win, &content); + + if (region == wuss_FURNITURE_VSCROLL_WELL) + { + wuss__vscroll_sausage_box(win, &sausage); + page = (content.y1 - content.y0) - WUSS_SCROLL_STEP; + page = MAX(page, 1); + if (y < sausage.y0) + wuss__scroll_step(win, POINT(0, -page)); + else if (y > sausage.y1) + wuss__scroll_step(win, POINT(0, page)); + } + else + { + wuss__hscroll_sausage_box(win, &sausage); + page = (content.x1 - content.x0) - WUSS_SCROLL_STEP; + page = MAX(page, 1); + if (x < sausage.x0) + wuss__scroll_step(win, POINT(-page, 0)); + else if (x > sausage.x1) + wuss__scroll_step(win, POINT(page, 0)); + } + } + + wuss_window_get_scroll(win, &scroll); + + wuss->furniture.dragging = win; + wuss->furniture.drag_kind = wuss__furniture_drag_kind(region); + wuss->furniture.drag.x = x; + wuss->furniture.drag.y = y; + wuss->furniture.drag_scroll_start = (region == wuss_FURNITURE_VSCROLL_WELL) ? scroll.y : scroll.x; + + /* Resize needs the pointer's offset from the content box's current + * bottom-right corner, so the point grabbed on the resize icon stays + * under the pointer as it moves, rather than that corner jumping to + * meet the pointer on the very first move. */ + if (region == wuss_FURNITURE_RESIZE) + { + box_t content; + wuss__content_box(win, &content); + wuss->furniture.drag_offset.x = x - content.x1; + wuss->furniture.drag_offset.y = y - content.y1; + } + } + return result_OK; + } + } +#endif /* WUSS_FURNITURE */ + + if (win->task->handle != NULL) + { + box_t content; + point_t doc_point; + + wuss__content_box(win, &content); + doc_point.x = x - content.x0 + win->scroll.x; + doc_point.y = y - content.y0 + win->scroll.y; + +#ifdef WUSS_ICONS + { + wuss_icon_t *icon; + + icon = wuss__icon_hit_test(win, doc_point); + if (icon != NULL) + { + if (action == wuss_MOUSE_DOWN && + (button & (wuss_BUTTON_SELECT | wuss_BUTTON_ADJUST))) + { + wuss__icon_set_state(icon, wuss_ICON_STATE_PRESSED, 1); + wuss->pressed_icon = icon; + wuss__icon_invalidate(icon); + } + else if (action == wuss_MOUSE_UP && wuss__icon_pressed(icon)) + { + wuss__icon_set_state(icon, wuss_ICON_STATE_PRESSED, 0); + wuss->pressed_icon = NULL; + wuss__icon_invalidate(icon); + + /* a completed click latches radio/option state before the task is + * told, so the wuss_EVENT_ICON handler sees the new value */ + if (icon->type == wuss_ICON_TYPE_OPTION) + wuss__icon_select(icon, !wuss__icon_selected(icon)); + else if (icon->type == wuss_ICON_TYPE_RADIO) + wuss__icon_select(icon, + (button & wuss_BUTTON_ADJUST) ? !wuss__icon_selected(icon) + : 1); + } + + event.kind = wuss_EVENT_ICON; + event.data.icon.icon = icon; + event.data.icon.action = action; + event.data.icon.button = button; + return wuss__deliver(win->task, win, &event); + } + } +#endif + + event.kind = wuss_EVENT_MOUSE; + event.data.mouse.action = action; + event.data.mouse.point = doc_point; + event.data.mouse.button = button; + return wuss__deliver(win->task, win, &event); + } + + return result_OK; +} diff --git a/libraries/wuss/core/mouse-move.c b/libraries/wuss/core/mouse-move.c new file mode 100644 index 00000000..98675359 --- /dev/null +++ b/libraries/wuss/core/mouse-move.c @@ -0,0 +1,130 @@ +/* wuss/mouse-move.c -- wuss - minimal window manager */ + +#include "impl.h" + +result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit) +{ + wuss_window_t *win; + int x, y; + + x = p.x; + y = p.y; + + wuss->pointer = p; + +#ifdef WUSS_FURNITURE + if (wuss->furniture.dragging != NULL) + { + win = wuss->furniture.dragging; + if (hit != NULL) + *hit = win; + + switch (wuss->furniture.drag_kind) + { + case wuss_FURNITURE_DRAG_RESIZE: + wuss->furniture_ops->drag_resize(win, POINT(x, y)); + break; + + case wuss_FURNITURE_DRAG_VSCROLL_SAUSAGE: + wuss->furniture_ops->drag_sausage(win, y - wuss->furniture.drag.y, wuss->furniture.drag_scroll_start, 0); + break; + + case wuss_FURNITURE_DRAG_HSCROLL_SAUSAGE: + wuss->furniture_ops->drag_sausage(win, x - wuss->furniture.drag.x, wuss->furniture.drag_scroll_start, 1); + break; + + case wuss_FURNITURE_DRAG_MOVE: + default: + wuss_window_move(win, POINT(x - wuss->furniture.drag.x, y - wuss->furniture.drag.y)); + break; + } + + return result_OK; + } +#endif + + win = wuss__window_at(wuss, p); + if (hit != NULL) + *hit = win; + + if (win == NULL) + { +#ifdef WUSS_ICONS + wuss__icon_set_hover(wuss, NULL); +#endif + return result_OK; + } + +#ifdef WUSS_FURNITURE + if (wuss->furniture_ops->hit_test(win, POINT(x, y)) != wuss_FURNITURE_CONTENT) + { +#ifdef WUSS_ICONS + wuss__icon_set_hover(wuss, NULL); +#endif + return result_OK; + } +#endif + + if (win->task->handle == NULL) + { +#ifdef WUSS_ICONS + wuss__icon_set_hover(wuss, NULL); +#endif + return result_OK; + } + + { + box_t content; + point_t doc_point; + wuss_event_t event; + + wuss__content_box(win, &content); + doc_point.x = x - content.x0 + win->scroll.x; + doc_point.y = y - content.y0 + win->scroll.y; + +#ifdef WUSS_ICONS + { + wuss_icon_t *icon; + int k; + + icon = wuss__icon_hit_test(win, doc_point); + + wuss__icon_set_hover(wuss, icon); + + /* Clear the pressed state of any button the pointer has left. This does + * not re-press a button on drag-back-in, and does not track which mouse + * button is held -- wuss keeps no persistent "button down over content" + * state. */ + for (k = 0; k < win->nicons; k++) + { + wuss_icon_t *it = win->icons[k]; + + if (wuss__icon_pressed(it) && it != icon) + { + wuss__icon_set_state(it, wuss_ICON_STATE_PRESSED, 0); + if (wuss->pressed_icon == it) + wuss->pressed_icon = NULL; + wuss__icon_invalidate(it); + } + } + + if (icon != NULL) + { + event.kind = wuss_EVENT_ICON; + event.data.icon.icon = icon; + event.data.icon.action = wuss_MOUSE_MOVE; + event.data.icon.button = wuss_BUTTON_SELECT; + return wuss__deliver(win->task, win, &event); + } + } +#endif + + event.kind = wuss_EVENT_MOUSE; + event.data.mouse.action = wuss_MOUSE_MOVE; + event.data.mouse.point = doc_point; + event.data.mouse.button = wuss_BUTTON_SELECT; + return wuss__deliver(win->task, win, &event); + } + + return result_OK; +} diff --git a/libraries/wuss/core/nearest-colour.c b/libraries/wuss/core/nearest-colour.c new file mode 100644 index 00000000..39fe3800 --- /dev/null +++ b/libraries/wuss/core/nearest-colour.c @@ -0,0 +1,40 @@ +/* wuss/nearest-colour.c -- wuss - minimal window manager */ + +#include + +#include "framebuf/pixelfmt.h" + +#include "impl.h" + +wuss_colour_t wuss_nearest_colour(const wuss_t *wuss, int r, int g, int b) +{ + unsigned long best_d; + wuss_colour_t best_i; + int i; + + assert(wuss != NULL); + assert(wuss->npalette > 0); + + best_d = ~0UL; + best_i = 0; + for (i = 0; i < wuss->npalette; i++) + { + unsigned int px; + long dr, dg, db; + unsigned long d; + + /* rgba8888 is 0xAABBGGRR (see pixelfmt.h). */ + px = wuss->palette[i].primary; + dr = r - (long) ( px & 0xFF); + dg = g - (long) ((px >> 8) & 0xFF); + db = b - (long) ((px >> 16) & 0xFF); + d = (unsigned long) (dr * dr + dg * dg + db * db); + if (d < best_d) + { + best_d = d; + best_i = i; + } + } + + return best_i; +} diff --git a/libraries/wuss/core/rebuild-palettecache.c b/libraries/wuss/core/rebuild-palettecache.c new file mode 100644 index 00000000..a2350fa1 --- /dev/null +++ b/libraries/wuss/core/rebuild-palettecache.c @@ -0,0 +1,58 @@ +/* wuss/rebuild-palettecache.c -- resolve symbolic colours for the current palette */ + +#include + +#include "framebuf/colour.h" + +#include "impl.h" + +/* RGB each named symbolic colour resolves to, in wuss_COLOUR_BLACK.. order. */ +static const unsigned char wuss__named_rgb[][3] = +{ + { 0x00, 0x00, 0x00 }, /* wuss_COLOUR_BLACK */ + { 0xFF, 0xFF, 0xFF }, /* wuss_COLOUR_WHITE */ + { 0xFF, 0x00, 0x00 }, /* wuss_COLOUR_RED */ + { 0x00, 0xFF, 0x00 }, /* wuss_COLOUR_GREEN */ + { 0x00, 0x00, 0xFF }, /* wuss_COLOUR_BLUE */ + { 0xFF, 0xFF, 0x00 }, /* wuss_COLOUR_YELLOW */ + { 0x00, 0xFF, 0xFF }, /* wuss_COLOUR_CYAN */ + { 0xFF, 0x00, 0xFF }, /* wuss_COLOUR_MAGENTA */ + { 0x80, 0x80, 0x80 } /* wuss_COLOUR_GREY */ +}; + +void wuss__rebuild_palettecache(wuss_t *wuss) +{ + wuss_colour_t *cache; + size_t i; + + cache = wuss->palettecache; + + /* Default every slot to wuss_COLOUR_SYMBOLIC: always >= npalette (palettes + * hold at most 128 real entries) so an unmapped symbolic still fails every + * range check, and -- unlike wuss_NO_BACKGROUND -- it is not mistaken for + * "no background" by wuss__validate_backdrop. */ + for (i = 0; i < NELEMS(wuss->palettecache); i++) + cache[i] = wuss_COLOUR_SYMBOLIC; + + for (i = 0; i < NELEMS(wuss__named_rgb); i++) + cache[(wuss_COLOUR_BLACK - wuss_COLOUR_SYMBOLIC) + i] = + wuss_nearest_colour(wuss, + wuss__named_rgb[i][0], + wuss__named_rgb[i][1], + wuss__named_rgb[i][2]); + +#ifdef WUSS_FURNITURE + cache[wuss_COLOUR_TITLE_BG - wuss_COLOUR_SYMBOLIC] = + wuss->furniture_colours.title.bg; + cache[wuss_COLOUR_TITLE_FG - wuss_COLOUR_SYMBOLIC] = + wuss->furniture_colours.title.fg; +#endif +#if defined(WUSS_FURNITURE) || defined(WUSS_ICONS) + cache[wuss_COLOUR_BUTTON_HILIGHT - wuss_COLOUR_SYMBOLIC] = wuss->bevel_light; + cache[wuss_COLOUR_BUTTON_SHADOW - wuss_COLOUR_SYMBOLIC] = wuss->bevel_dark; + cache[wuss_COLOUR_ACCENT_BG - wuss_COLOUR_SYMBOLIC] = wuss->accent_bg; + cache[wuss_COLOUR_ACCENT_FG - wuss_COLOUR_SYMBOLIC] = wuss->accent_fg; +#endif + if (wuss->backdrop.colour != wuss_NO_BACKGROUND) + cache[wuss_COLOUR_BACKDROP - wuss_COLOUR_SYMBOLIC] = wuss->backdrop.colour; +} diff --git a/libraries/wuss/core/redraw.c b/libraries/wuss/core/redraw.c new file mode 100644 index 00000000..fe6d31aa --- /dev/null +++ b/libraries/wuss/core/redraw.c @@ -0,0 +1,197 @@ +/* wuss/redraw.c -- wuss - minimal window manager */ + +#include "impl.h" + +/* Fill "area" with the desktop backdrop, except for whatever part of it is + * covered by a wuss_NO_BACKGROUND window's content -- that task owns those + * pixels outright, so a desktop-coloured pre-fill there would flash (or, + * for a task that doesn't repaint every pixel every time, permanently + * show) backdrop instead of whatever was already on screen. */ +static void fill_backdrop_excluding_content(wuss_t *wuss, const box_t *area) +{ + box_t cuts[WUSS_MAX_INVALIDATE_PIECES]; + box_t pieces[WUSS_MAX_INVALIDATE_PIECES]; + int ncuts, npieces, i; + list_t *e; + + ncuts = 0; + for (e = wuss->z_order.next; + e != NULL && ncuts < WUSS_MAX_INVALIDATE_PIECES; + e = e->next) + { + wuss_window_t *win; + box_t content, clipped; + + win = wuss__window_from_link(e); + if (win->flags & wuss_WINDOW_HIDDEN) + continue; + if (win->bg.colour != wuss_NO_BACKGROUND) + continue; + + wuss__content_box(win, &content); + if (box_intersection(&content, area, &clipped)) + continue; /* no overlap with the area being filled */ + + cuts[ncuts++] = clipped; + } + + npieces = wuss__subtract_boxes(area, cuts, ncuts, pieces); + + for (i = 0; i < npieces; i++) + wuss__fill_backdrop(wuss->scr, wuss->palette, &wuss->backdrop, + &pieces[i], 0, 0); +} + +static void redraw_window(wuss_t *wuss, + wuss_window_t *win, + const box_t *full, + result_t *rc) +{ + box_t clipped; + box_t visible_clipped; + box_t content; + box_t pieces[WUSS_MAX_INVALIDATE_PIECES]; + int npieces, i; + + if (win->flags & wuss_WINDOW_HIDDEN) + return; /* not drawn while hidden */ + + if (box_intersection(&win->visible, full, &visible_clipped)) + return; /* offscreen */ + + /* clip chrome to whatever part of the window isn't hidden behind a + * higher window, same reasoning as the content clip below -- otherwise + * a window redrawing under an overlap would paint its furniture back + * over whatever is covering it */ + npieces = wuss__clip_to_visible(win, &visible_clipped, pieces); + for (i = 0; i < npieces; i++) + { + wuss->scr->clip = pieces[i]; + wuss__chrome_draw(wuss, win, &pieces[i]); + } + + wuss__content_box(win, &content); + if (box_intersection(&content, full, &clipped)) + return; + + /* skip (or shrink to) whatever part of "clipped" isn't hidden behind a + * higher window -- otherwise every dirty rect raised by a window on top + * (e.g. a moving ball) would also repaint whatever it's covering below, + * however expensive that redraw is, for pixels nobody will ever see */ + npieces = wuss__clip_to_visible(win, &clipped, pieces); + + for (i = 0; i < npieces; i++) + { + wuss->scr->clip = pieces[i]; + + /* phase any pattern against the scroll origin so it locks to the + * content rather than crawling as the window scrolls */ + wuss__fill_backdrop(wuss->scr, wuss->palette, &win->bg, &content, + content.x0 - win->scroll.x, + content.y0 - win->scroll.y); + + { + wuss_event_t event; + result_t crc; + + event.kind = wuss_EVENT_REDRAW; + event.data.redraw.scr = wuss->scr; + event.data.redraw.content = &pieces[i]; + event.data.redraw.bounds = &content; + event.data.redraw.scroll = win->scroll; + crc = wuss__deliver(win->task, win, &event); + if (crc != result_OK) + *rc = crc; + } + +#ifdef WUSS_ICONS + { + int k; + + /* draw in array order so later-created icons paint on top, matching + * wuss__icon_hit_test's reverse scan */ + for (k = 0; k < win->nicons; k++) + wuss__icon_draw(wuss, win->icons[k], &content, win->scroll); + } +#endif + } +} + +/* draws back-to-front (list head = topmost window drawn last) without + * recursing per window, so stack use stays flat regardless of window count; + * ponytail: O(n^2) walk, fine while window counts stay small, switch to an + * array/vector pass if that stops being true */ +static void redraw_from(wuss_t *wuss, + list_t *head, + const box_t *full, + result_t *rc) +{ + const list_t *stop; + + stop = NULL; + for (;;) + { + list_t *e, *last; + + last = NULL; + for (e = head; e != stop; e = e->next) + last = e; + + if (last == NULL) + break; + + redraw_window(wuss, wuss__window_from_link(last), full, rc); + stop = last; + } +} + +result_t wuss_redraw(wuss_t *wuss) +{ + box_t full; + result_t rc; + + full.x0 = 0; + full.y0 = 0; + full.x1 = wuss->scr->size.w; + full.y1 = wuss->scr->size.h; + + wuss->scr->clip = full; + fill_backdrop_excluding_content(wuss, &full); + + rc = result_OK; + redraw_from(wuss, wuss->z_order.next, &full, &rc); + + /* redraw_window narrows wuss->scr->clip to whatever it last painted; + * reset it so anything drawing after this redraw (not least + * screen_copy_rect, used for window-drag blitting) sees the whole + * screen rather than that leftover sliver. */ + box_reset(&wuss->scr->clip); + + wuss->ndirty = 0; + + return rc; +} + +result_t wuss_redraw_dirty(wuss_t *wuss) +{ + result_t rc; + int i; + + if (wuss->ndirty == 0) + return result_OK; + + rc = result_OK; + for (i = 0; i < wuss->ndirty; i++) + { + wuss->scr->clip = wuss->dirty[i]; + fill_backdrop_excluding_content(wuss, &wuss->dirty[i]); + + redraw_from(wuss, wuss->z_order.next, &wuss->dirty[i], &rc); + } + + box_reset(&wuss->scr->clip); /* see wuss_redraw's comment on the same call */ + + wuss->ndirty = 0; + + return rc; +} diff --git a/libraries/wuss/core/scroll-step.c b/libraries/wuss/core/scroll-step.c new file mode 100644 index 00000000..23b653b4 --- /dev/null +++ b/libraries/wuss/core/scroll-step.c @@ -0,0 +1,43 @@ +/* wuss/scroll-step.c -- clamp and apply a scroll offset */ + +#include "impl.h" + +point_t wuss__scroll_clamp(const wuss_window_t *window, point_t desired) +{ + box_t content; + int max_x, max_y; + + wuss__content_box(window, &content); + + max_x = window->doc.w - (content.x1 - content.x0); + max_y = window->doc.h - (content.y1 - content.y0); + max_x = MAX(max_x, 0); + max_y = MAX(max_y, 0); + + desired.x = CLAMP(desired.x, 0, max_x); + desired.y = CLAMP(desired.y, 0, max_y); + + return desired; +} + +void wuss__scroll_step(wuss_window_t *window, point_t delta) +{ + point_t scroll; + + /* A window that declared an axis non-scrollable (e.g. a pop-up menu) has no + * scrollbar to clamp against and its geometry assumes scroll == 0, so a wheel + * turn over it must not move the content -- doing so corrupts the display. */ + if (window->flags & wuss_WINDOW_NO_HSCROLL) + delta.x = 0; + if (window->flags & wuss_WINDOW_NO_VSCROLL) + delta.y = 0; + if (delta.x == 0 && delta.y == 0) + return; + + wuss_window_get_scroll(window, &scroll); + scroll.x += delta.x; + scroll.y += delta.y; + scroll = wuss__scroll_clamp(window, scroll); + + wuss_window_set_scroll(window, scroll); +} diff --git a/libraries/wuss/core/scroll.c b/libraries/wuss/core/scroll.c new file mode 100644 index 00000000..45dff36d --- /dev/null +++ b/libraries/wuss/core/scroll.c @@ -0,0 +1,72 @@ +/* wuss/scroll.c -- wuss - minimal window manager */ + +#include "impl.h" + +/* Scrolling moves content under a stationary pointer, so the icon the pointer + * now sits over may differ from before. Re-resolve the hovered icon so a + * highlighted wuss_ICON_TYPE_MENU_ENTRY does not keep its highlight after + * scrolling out from under the pointer. */ +static void wuss__scroll_rehover(wuss_t *wuss, + wuss_window_t *win, + point_t screen_point) +{ +#ifdef WUSS_ICONS + box_t content; + point_t doc_point; + + wuss__content_box(win, &content); + doc_point.x = screen_point.x - content.x0 + win->scroll.x; + doc_point.y = screen_point.y - content.y0 + win->scroll.y; + + wuss__icon_set_hover(wuss, wuss__icon_hit_test(win, doc_point)); +#else + (void) wuss; + (void) win; + (void) screen_point; +#endif +} + +result_t wuss_scroll(wuss_t *wuss, point_t p, int delta, wuss_window_t **hit) +{ + wuss_window_t *win; + int x, y; + + x = p.x; + y = p.y; + + win = wuss__window_at(wuss, p); + if (hit != NULL) + *hit = win; + + if (win == NULL) + return result_OK; + +#ifdef WUSS_FURNITURE + { + box_t titlebar; + + wuss__titlebar_box(win, &titlebar); + if (box_contains_point(&titlebar, x, y)) + return result_OK; + } +#endif + + { + box_t content; + wuss_event_t event; + + /* Note the pointer position before scrolling, so the event describes the + * content the pointer was over when the wheel turned, not the content the + * step below brings under it. */ + wuss__content_box(win, &content); + event.kind = wuss_EVENT_SCROLL; + event.data.scroll.point.x = x - content.x0 + win->scroll.x; + event.data.scroll.point.y = y - content.y0 + win->scroll.y; + event.data.scroll.delta = delta; + + wuss__scroll_step(win, POINT(0, delta)); + wuss__scroll_rehover(wuss, win, POINT(x, y)); + + return wuss__deliver(win->task, win, &event); + } +} diff --git a/libraries/wuss/core/set-backdrop.c b/libraries/wuss/core/set-backdrop.c new file mode 100644 index 00000000..d098467a --- /dev/null +++ b/libraries/wuss/core/set-backdrop.c @@ -0,0 +1,38 @@ +/* wuss/set-backdrop.c -- swap the desktop backdrop mid-session */ + +#include + +#include "geom/box.h" + +#include "impl.h" + +result_t wuss_set_backdrop(wuss_t *wuss, const wuss_backdrop_t *backdrop) +{ + box_t screen; + result_t rc; + wuss_backdrop_t resolved; + + assert(wuss != NULL); + assert(backdrop != NULL); + + resolved = *backdrop; + resolved.colour = wuss__resolve_colour(wuss, resolved.colour); + resolved.pattern_bg = wuss__resolve_colour(wuss, resolved.pattern_bg); + + rc = wuss__validate_backdrop(wuss, &resolved); + if (rc != result_OK) + return rc; + + wuss->backdrop = resolved; + + /* wuss_COLOUR_BACKDROP now follows the new colour. */ + wuss__rebuild_palettecache(wuss); + + screen.x0 = 0; + screen.y0 = 0; + screen.x1 = wuss->scr->size.w; + screen.y1 = wuss->scr->size.h; + wuss_invalidate(wuss, &screen); + + return result_OK; +} diff --git a/libraries/wuss/core/set-palette.c b/libraries/wuss/core/set-palette.c new file mode 100644 index 00000000..e4599909 --- /dev/null +++ b/libraries/wuss/core/set-palette.c @@ -0,0 +1,57 @@ +/* wuss/set-palette.c -- swap the system palette mid-session */ + +#include +#include + +#include "geom/box.h" + +#include "impl.h" + +result_t wuss_set_palette(wuss_t *wuss, + const colour_t *palette, + int npalette) +{ + wuss_event_t event; + box_t screen; + result_t rc; + list_t *e; + + assert(wuss != NULL); + assert(palette != NULL); + + /* Same length only: keeps every stored furniture/bevel/backdrop colour + * index in range, and matches the fixed-size framebuffer palette on a + * paletted screen. */ + if (npalette != wuss->npalette) + return result_BAD_ARG; + + memcpy(wuss->palette, palette, npalette * sizeof(*wuss->palette)); + + wuss__rebuild_palettecache(wuss); + + rc = result_OK; + event.kind = wuss_EVENT_PALETTE; + for (e = wuss->tasks.next; e != NULL; ) + { + wuss_task_t *task; + list_t *next; + result_t crc; + + task = (wuss_task_t *) e; + next = e->next; /* an autoclose task may free its own node in the handler */ + + crc = wuss__deliver(task, NULL, &event); + if (crc != result_OK && rc == result_OK) + rc = crc; + + e = next; + } + + screen.x0 = 0; + screen.y0 = 0; + screen.x1 = wuss->scr->size.w; + screen.y1 = wuss->scr->size.h; + wuss_invalidate(wuss, &screen); + + return rc; +} diff --git a/libraries/wuss/core/task/create.c b/libraries/wuss/core/task/create.c new file mode 100644 index 00000000..a88cbf37 --- /dev/null +++ b/libraries/wuss/core/task/create.c @@ -0,0 +1,40 @@ +/* wuss/task/create.c -- register a task on a window manager */ + +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "wuss/task.h" + +#include "../impl.h" + +result_t wuss_task_create(wuss_t *wuss, + const wuss_task_desc_t *desc, + wuss_task_t **task) +{ + wuss_task_t *t; + + assert(wuss != NULL); + assert(desc != NULL); + assert(task != NULL); + + t = wuss__malloc(wuss, sizeof(*t)); + if (t == NULL) + return result_OOM; + + t->wuss = wuss; + t->handle = desc->handle; + t->task_data = desc->task_data; + t->name = desc->name; + t->flags = 0; + list_init(&t->windows); + + list_add_to_tail(&wuss->tasks, &t->link); + + *task = t; + + return result_OK; +} diff --git a/libraries/wuss/core/task/deliver.c b/libraries/wuss/core/task/deliver.c new file mode 100644 index 00000000..6be99e25 --- /dev/null +++ b/libraries/wuss/core/task/deliver.c @@ -0,0 +1,54 @@ +/* wuss/task/deliver.c -- the single event-dispatch chokepoint */ + +#include +#include + +#include "wuss/task.h" + +#include "../impl.h" + +#ifndef NDEBUG +/* Is "kind" valid for delivery to a window (win != NULL) or to a task + * (win == NULL)? The two sets are the wuss_window_event_kind_t / + * wuss_task_event_kind_t views documented in task.h. */ +static int wuss__kind_ok_for(wuss_event_kind_t kind, int have_window) +{ + switch (kind) + { + case wuss_EVENT_REDRAW: + case wuss_EVENT_MOUSE: + case wuss_EVENT_SCROLL: + case wuss_EVENT_OPEN: + case wuss_EVENT_PRE_SHOW: + case wuss_EVENT_SHOW: + case wuss_EVENT_PRE_CLOSE: + case wuss_EVENT_CLOSE: + return have_window; + + case wuss_EVENT_IDLE: + case wuss_EVENT_QUIT: + case wuss_EVENT_PALETTE: + case wuss_EVENT_MENU_SELECT: + return !have_window; + + case wuss_EVENT_ICON: + return 1; /* window view: real icon; task view: reserved shared element */ + } + + return 0; +} +#endif + +result_t wuss__deliver(wuss_task_t *task, + wuss_window_t *win_or_null, + const wuss_event_t *ev) +{ + if (task == NULL || task->handle == NULL) + return result_OK; + +#ifndef NDEBUG + assert(wuss__kind_ok_for(ev->kind, win_or_null != NULL)); +#endif + + return task->handle(win_or_null, ev, task->task_data); +} diff --git a/libraries/wuss/core/task/destroy.c b/libraries/wuss/core/task/destroy.c new file mode 100644 index 00000000..74c7760f --- /dev/null +++ b/libraries/wuss/core/task/destroy.c @@ -0,0 +1,49 @@ +/* wuss/task/destroy.c -- unregister and free a task */ + +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "wuss/task.h" + +#include "../impl.h" + +void wuss_task_destroy(wuss_task_t *doomed) +{ + wuss_event_t event; + wuss_t *wuss; + list_t *e; + + if (doomed == NULL) + return; + + wuss = doomed->wuss; + + /* Mark the teardown so wuss_window_close's autoclose path doesn't also + * fire QUIT and free the node from under us. */ + doomed->flags |= wuss_TASK__REAPING; + +#ifdef WUSS_MENUS + /* If this task opened the live menu chain, close it now: MENU_SELECT is + * delivered to the chain's owner, and leaving the chain open would leave + * its owner (and any pending pick flash's owner) pointing at `doomed` + * after it is freed below. */ + if (wuss->menu_chain != NULL && wuss->menu_chain->owner == doomed) + wuss_menu_close(wuss->menu_chain); +#endif + + /* One QUIT while every window is still alive. */ + event.kind = wuss_EVENT_QUIT; + (void) wuss__deliver(doomed, NULL, &event); + + /* Force-close every window the task owns, in task-list order. No + * PRE_CLOSE/CLOSE -- wuss_window_close is the unvetoable teardown. Each + * close unlinks the window's task_link, so re-read the head each time. */ + while ((e = doomed->windows.next) != NULL) + wuss_window_close(wuss__window_from_task_link(e)); + + list_remove(&wuss->tasks, &doomed->link); + wuss__free(wuss, doomed); +} diff --git a/libraries/wuss/core/task/set-autoclose.c b/libraries/wuss/core/task/set-autoclose.c new file mode 100644 index 00000000..d546ac29 --- /dev/null +++ b/libraries/wuss/core/task/set-autoclose.c @@ -0,0 +1,18 @@ +/* wuss/task/set-autoclose.c -- opt a task into self-destruct on last close */ + +#include + +#include "wuss/task.h" + +#include "../impl.h" + +void wuss_task_set_autoclose(wuss_task_t *task, int on) +{ + if (task == NULL) + return; + + if (on) + task->flags |= wuss_TASK__AUTOCLOSE; + else + task->flags &= ~wuss_TASK__AUTOCLOSE; +} diff --git a/libraries/wuss/window/at.c b/libraries/wuss/core/window/at.c similarity index 64% rename from libraries/wuss/window/at.c rename to libraries/wuss/core/window/at.c index 963f428c..8574538a 100644 --- a/libraries/wuss/window/at.c +++ b/libraries/wuss/core/window/at.c @@ -1,4 +1,4 @@ -/* at.c -- wuss - minimal window manager */ +/* wuss/window/at.c -- wuss - minimal window manager */ #include "../impl.h" @@ -10,7 +10,9 @@ wuss_window_t *wuss__window_at(wuss_t *wuss, point_t p) { wuss_window_t *win; - win = (wuss_window_t *) e; + win = wuss__window_from_link(e); + if (win->flags & wuss_WINDOW_HIDDEN) + continue; if (box_contains_point(&win->visible, p.x, p.y)) return win; } diff --git a/libraries/wuss/core/window/close.c b/libraries/wuss/core/window/close.c new file mode 100644 index 00000000..5563468e --- /dev/null +++ b/libraries/wuss/core/window/close.c @@ -0,0 +1,59 @@ +/* wuss/window/close.c -- wuss - minimal window manager */ + +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "../impl.h" + +void wuss_window_close(wuss_window_t *doomed) +{ + wuss_task_t *task; + wuss_event_t event; + wuss_t *wuss; + + if (doomed == NULL) + return; + + wuss = doomed->wuss; + task = doomed->task; +#ifdef WUSS_FURNITURE + if (wuss->furniture.dragging == doomed) + wuss->furniture.dragging = NULL; +#endif +#ifdef WUSS_ICONS + if (wuss->pressed_icon != NULL && wuss->pressed_icon->window == doomed) + wuss->pressed_icon = NULL; + if (wuss->hover_icon != NULL && wuss->hover_icon->window == doomed) + wuss->hover_icon = NULL; +#endif + + wuss__release_packed(doomed); + + wuss__invalidate_clipped(doomed, &doomed->visible); + + list_remove(&wuss->z_order, &doomed->link); + list_remove(&task->windows, &doomed->task_link); + +#ifdef WUSS_ICONS + wuss__icons_free(doomed); +#endif + + wuss__free(wuss, doomed); + + /* An autoclose task self-destructs once it loses its last window: QUIT + * (so the handler can free task_data), then unlink and free the node. + * Suppressed while wuss_task_destroy is already tearing the task down. */ + if ((task->flags & wuss_TASK__AUTOCLOSE) && + !(task->flags & wuss_TASK__REAPING) && + task->windows.next == NULL) + { + event.kind = wuss_EVENT_QUIT; + (void) wuss__deliver(task, NULL, &event); + + list_remove(&wuss->tasks, &task->link); + wuss__free(wuss, task); + } +} diff --git a/libraries/wuss/core/window/create-placed.c b/libraries/wuss/core/window/create-placed.c new file mode 100644 index 00000000..f3484177 --- /dev/null +++ b/libraries/wuss/core/window/create-placed.c @@ -0,0 +1,175 @@ +/* wuss/window/create-placed.c -- window creation with wuss-chosen position */ + +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "geom/box.h" +#include "geom/packer.h" + +#include "../impl.h" + +/* Footprint padding around a content area of the given flags: the outline on + * every edge, the titlebar on top, and the scrollbar/resize carve on the + * right and bottom. Matches wuss_window_create's own visible-box maths so an + * auto-placed slot ends up exactly the size the window will occupy. */ +static void footprint_pad(const wuss_t *wuss, + wuss_window_flags_t flags, + int *left, + int *top, + int *right, + int *bottom) +{ + int outline_px, titlebar_height; + point_t carve; + + outline_px = wuss__outline_px_for(flags); + titlebar_height = wuss__titlebar_height_for(wuss, flags); + wuss__furniture_carve_for(flags, wuss__button_size_for(wuss, flags), &carve); + + *left = outline_px; + *top = outline_px + titlebar_height; + *right = outline_px + carve.x; + *bottom = outline_px + carve.y; +} + +/* Pick the next cascade position for a window of the given footprint size, + * once the layout packer has no room. Steps down/right by a titlebar each + * call, wrapping back to the top-left when the step would push the footprint + * off the screen. A footprint that is itself larger than the screen can never + * fit; it is pinned at the top-left and the cascade counter is not advanced, + * so it does not wedge every later window at the origin too. */ +static void next_cascade(wuss_t *wuss, int fw, int fh, point_t *pos) +{ + int scr_w, scr_h, step; + + scr_w = wuss->scr->size.w; + scr_h = wuss->scr->size.h; +#ifdef WUSS_FURNITURE + step = wuss->titlebar_height; +#else + step = 0; +#endif + if (step <= 0) + step = WUSS_DEFAULT_TITLEBAR_HEIGHT; + + if (fw > scr_w || fh > scr_h) + { + pos->x = 0; + pos->y = 0; + return; + } + + if (wuss->cascade.x + fw > scr_w || wuss->cascade.y + fh > scr_h) + { + wuss->cascade.x = 0; + wuss->cascade.y = 0; + } + + *pos = wuss->cascade; + + wuss->cascade.x += step; + wuss->cascade.y += step; +} + +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) +{ + wuss_t *wuss; + result_t rc; + int left, top, right, bottom; + int fw, fh; + box_t screen, content, consumed; + point_t origin; + const box_t *slot; + int tracked; + + assert(task != NULL); + assert(window != NULL); + + wuss = task->wuss; + + if (!wuss__size_ok(size.w, size.h)) + return result_WUSS_TOO_SMALL; + + if (wuss->layout == NULL) + { + static const box_t margins = { + WUSS_PLACE_GUTTER, WUSS_PLACE_GUTTER, WUSS_PLACE_GUTTER, WUSS_PLACE_GUTTER + }; + + screen.x0 = 0; + screen.y0 = 0; + screen.x1 = wuss->scr->size.w; + screen.y1 = wuss->scr->size.h; + + wuss->layout = packer_create(&screen); + if (wuss->layout == NULL) + return result_OOM; + + packer_set_margins(wuss->layout, &margins); + packer_set_gutter(wuss->layout, WUSS_PLACE_GUTTER); + } + + footprint_pad(wuss, flags, &left, &top, &right, &bottom); + fw = left + size.w + right; + fh = top + size.h + bottom; + + /* packer's Y axis is reversed. bottom left here gives top left packing. */ + rc = packer_place_by(wuss->layout, packer_LOC_BOTTOM_LEFT, fw, fh, &slot); + if (rc == result_OK) + { + origin.x = slot->x0; + origin.y = slot->y0; + tracked = 1; + } + else if (rc == result_PACKER_DIDNT_FIT) + { + next_cascade(wuss, fw, fh, &origin); + tracked = 0; + } + else + { + return rc; + } + + /* content box = footprint origin plus the top/left furniture padding */ + content.x0 = origin.x + left; + content.y0 = origin.y + top; + content.x1 = content.x0 + size.w; + content.y1 = content.y0 + size.h; + + /* what packer_place_by actually took out of the free list: the footprint + * plus the gutter strip on its inner edges (right and, in packer space, + * top -- see packer_LOC_BOTTOM_LEFT). Releasing exactly this on close / + * move keeps the gutter from leaking away over a session. */ + if (tracked) + { + consumed.x0 = slot->x0; + consumed.y0 = slot->y0; + consumed.x1 = slot->x1 + WUSS_PLACE_GUTTER; + consumed.y1 = slot->y1 + WUSS_PLACE_GUTTER; + } + + rc = wuss_window_create(task, &content, title, flags, bg, + doc, min_doc, window); + if (rc != result_OK) + { + if (tracked) + (void) packer_release(wuss->layout, &consumed); + return rc; + } + + if (tracked) + (*window)->packed = consumed; + + return result_OK; +} diff --git a/libraries/wuss/window/create.c b/libraries/wuss/core/window/create.c similarity index 51% rename from libraries/wuss/window/create.c rename to libraries/wuss/core/window/create.c index fa3cdc03..a48ed7ba 100644 --- a/libraries/wuss/window/create.c +++ b/libraries/wuss/core/window/create.c @@ -1,4 +1,4 @@ -/* create.c -- wuss - minimal window manager */ +/* wuss/window/create.c -- wuss - minimal window manager */ #include #include @@ -10,36 +10,39 @@ #include "../impl.h" -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) { + wuss_t *wuss; wuss_window_t *win; int width, height, outline_px, titlebar_height; int scr_width, scr_height, dx, dy; point_t carve; - assert(wuss != NULL); + assert(task != NULL); assert(content != NULL); assert(window != NULL); + wuss = task->wuss; + width = content->x1 - content->x0; height = content->y1 - content->y0; if (!wuss__size_ok(width, height)) return result_WUSS_TOO_SMALL; - win = malloc(sizeof(*win)); + win = wuss__malloc(wuss, sizeof(*win)); if (win == NULL) return result_OOM; outline_px = wuss__outline_px_for(flags); titlebar_height = wuss__titlebar_height_for(wuss, flags); - wuss__furniture_carve_for(flags, wuss__icon_size_for(wuss, flags), &carve); + wuss__furniture_carve_for(flags, wuss__button_size_for(wuss, flags), &carve); win->wuss = wuss; win->visible.x0 = content->x0 - outline_px; @@ -50,8 +53,8 @@ result_t wuss_window_create(wuss_t *wuss, /* nudge back on-screen so the titlebar/close icon stay reachable; a * window bigger than the screen keeps its top-left (titlebar) edge * on-screen rather than being centred or left alone */ - scr_width = wuss->scr->width; - scr_height = wuss->scr->height; + scr_width = wuss->scr->size.w; + scr_height = wuss->scr->size.h; dx = 0; if (win->visible.x0 < 0) @@ -69,30 +72,53 @@ result_t wuss_window_create(wuss_t *wuss, if (win->visible.y0 + dy < 0) dy = -win->visible.y0; - win->visible.x0 += dx; win->visible.x1 += dx; - win->visible.y0 += dy; win->visible.y1 += dy; + box_translated(&win->visible, dx, dy, &win->visible); win->flags = flags; + + /* clamp so the window can never be born larger than the screen; done + * after the on-screen nudge above so it measures from the final + * top-left. only the bottom-right corner moves. */ + { + size2d_t max; + + wuss__max_content_on_screen(win, &max); + if (win->visible.x1 - win->visible.x0 - 2 * outline_px - carve.x > max.w) + win->visible.x1 = win->visible.x0 + 2 * outline_px + carve.x + max.w; + if (win->visible.y1 - win->visible.y0 - 2 * outline_px + - titlebar_height - carve.y > max.h) + win->visible.y1 = win->visible.y0 + 2 * outline_px + titlebar_height + + carve.y + max.h; + } + win->scroll.x = 0; win->scroll.y = 0; - win->doc_width = doc_width; - win->doc_height = doc_height; - win->toggled = 0; + win->doc = doc; + win->min_doc = min_doc; +#ifdef WUSS_FURNITURE + win->state = wuss_WINDOW_STATE_NONE; +#endif +#ifdef WUSS_ICONS + win->icons = NULL; + win->nicons = 0; + win->cap_icons = 0; +#endif - if (task != NULL) - win->task = *task; - else - memset(&win->task, 0, sizeof(win->task)); + box_reset(&win->packed); /* wuss_window_create_placed fills this in after */ + + win->task = task; - if (task == NULL) - win->task.bg = wuss_NO_BACKGROUND; - else if (task->bg != wuss_NO_BACKGROUND && - (task->bg < 0 || task->bg >= wuss->npalette)) + bg.colour = wuss__resolve_colour(wuss, bg.colour); + bg.pattern_bg = wuss__resolve_colour(wuss, bg.pattern_bg); + + if (wuss__validate_backdrop(wuss, &bg) != result_OK) { - free(win); + wuss__free(wuss, win); return result_WUSS_BAD_COLOUR; } + win->bg = bg; +#ifdef WUSS_FURNITURE if (title != NULL) { strncpy(win->title, title, WUSS_TITLE_MAX); @@ -102,10 +128,15 @@ result_t wuss_window_create(wuss_t *wuss, { win->title[0] = '\0'; } +#else + (void) title; +#endif list_add_to_head(&wuss->z_order, &win->link); + list_add_to_tail(&task->windows, &win->task_link); - wuss_invalidate(wuss, &win->visible); + if (!(flags & wuss_WINDOW_HIDDEN)) + wuss_invalidate(wuss, &win->visible); *window = win; diff --git a/libraries/wuss/core/window/get-content-bounds.c b/libraries/wuss/core/window/get-content-bounds.c new file mode 100644 index 00000000..4a8cbab5 --- /dev/null +++ b/libraries/wuss/core/window/get-content-bounds.c @@ -0,0 +1,9 @@ +/* wuss/window/get-content-bounds.c -- wuss - minimal window manager */ + +#include "../impl.h" + +void wuss_window_get_content_bounds(const wuss_window_t *window, + box_t *content) +{ + wuss__content_box(window, content); +} diff --git a/libraries/wuss/window/get-scroll.c b/libraries/wuss/core/window/get-scroll.c similarity index 65% rename from libraries/wuss/window/get-scroll.c rename to libraries/wuss/core/window/get-scroll.c index 18d09d8d..b2345194 100644 --- a/libraries/wuss/window/get-scroll.c +++ b/libraries/wuss/core/window/get-scroll.c @@ -1,4 +1,4 @@ -/* get-scroll.c -- wuss - minimal window manager */ +/* wuss/window/get-scroll.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/core/window/get-visible-bounds.c b/libraries/wuss/core/window/get-visible-bounds.c new file mode 100644 index 00000000..d305ad4e --- /dev/null +++ b/libraries/wuss/core/window/get-visible-bounds.c @@ -0,0 +1,9 @@ +/* wuss/window/get-visible-bounds.c -- wuss - minimal window manager */ + +#include "../impl.h" + +void wuss_window_get_visible_bounds(const wuss_window_t *window, + box_t *visible) +{ + *visible = window->visible; +} diff --git a/libraries/wuss/core/window/invalidate.c b/libraries/wuss/core/window/invalidate.c new file mode 100644 index 00000000..d6b7032a --- /dev/null +++ b/libraries/wuss/core/window/invalidate.c @@ -0,0 +1,400 @@ +/* wuss/window/invalidate.c -- wuss - minimal window manager */ + +#include + +#include "../impl.h" + +/* Windows are treated as fully opaque over their whole visible footprint + * (titlebar/outline included) for occlusion purposes, matching how + * redraw_from repaints them: a task that leaves gaps via + * wuss_NO_BACKGROUND won't get those gaps refreshed by this either. */ + +/* Append "piece" minus its intersection "cut" with an occluder to "out", + * as up to four non-overlapping bands. */ +static void box_subtract_into(const box_t *piece, + const box_t *cut, + box_t *out, + int *pnout) +{ + if (cut->y0 > piece->y0 && *pnout < WUSS_MAX_INVALIDATE_PIECES) + { + out[*pnout].x0 = piece->x0; out[*pnout].y0 = piece->y0; + out[*pnout].x1 = piece->x1; out[*pnout].y1 = cut->y0; + (*pnout)++; + } + if (cut->y1 < piece->y1 && *pnout < WUSS_MAX_INVALIDATE_PIECES) + { + out[*pnout].x0 = piece->x0; out[*pnout].y0 = cut->y1; + out[*pnout].x1 = piece->x1; out[*pnout].y1 = piece->y1; + (*pnout)++; + } + if (cut->x0 > piece->x0 && *pnout < WUSS_MAX_INVALIDATE_PIECES) + { + out[*pnout].x0 = piece->x0; out[*pnout].y0 = cut->y0; + out[*pnout].x1 = cut->x0; out[*pnout].y1 = cut->y1; + (*pnout)++; + } + if (cut->x1 < piece->x1 && *pnout < WUSS_MAX_INVALIDATE_PIECES) + { + out[*pnout].x0 = cut->x1; out[*pnout].y0 = cut->y0; + out[*pnout].x1 = piece->x1; out[*pnout].y1 = cut->y1; + (*pnout)++; + } +} + +/* Clip "box" (screen space) down to the parts not already covered by + * windows above "window" in the z-order, writing the surviving pieces to + * "out" (capacity WUSS_MAX_INVALIDATE_PIECES) and returning their count. */ +int wuss__clip_to_visible(wuss_window_t *window, + const box_t *box, + box_t *out) +{ + box_t scratch[WUSS_MAX_INVALIDATE_PIECES]; + box_t *cur, *nxt, *tmp; + int ncur; + list_t *e; + + cur = out; + nxt = scratch; + cur[0] = *box; + ncur = 1; + + for (e = window->wuss->z_order.next; e != &window->link; e = e->next) + { + wuss_window_t *occluder; + int nnext, p; + + occluder = wuss__window_from_link(e); + if (occluder->flags & wuss_WINDOW_HIDDEN) + continue; /* a hidden window occludes nothing */ + nnext = 0; + + for (p = 0; p < ncur; p++) + { + box_t cut; + + if (box_intersection(&occluder->visible, &cur[p], &cut)) + { + if (nnext < WUSS_MAX_INVALIDATE_PIECES) + nxt[nnext++] = cur[p]; /* no overlap: piece survives untouched */ + } + else + { + box_subtract_into(&cur[p], &cut, nxt, &nnext); + } + } + + ncur = nnext; + tmp = cur; + cur = nxt; + nxt = tmp; + + if (ncur == 0) + break; + } + + if (cur != out) + memcpy(out, cur, ncur * sizeof(*out)); + + return ncur; +} + +/* Subtract each of "cuts" (an array of "ncuts" boxes) from "whole", writing + * the surviving pieces to "out" (capacity WUSS_MAX_INVALIDATE_PIECES) and + * returning their count. */ +int wuss__subtract_boxes(const box_t *whole, + const box_t *cuts, + int ncuts, + box_t *out) +{ + box_t scratch[WUSS_MAX_INVALIDATE_PIECES]; + box_t *cur, *nxt, *tmp; + int ncur, i; + + cur = out; + nxt = scratch; + cur[0] = *whole; + ncur = 1; + + for (i = 0; i < ncuts; i++) + { + int p, nnext; + + nnext = 0; + + for (p = 0; p < ncur; p++) + { + box_t cut; + + if (box_intersection(&cuts[i], &cur[p], &cut)) + { + if (nnext < WUSS_MAX_INVALIDATE_PIECES) + nxt[nnext++] = cur[p]; /* no overlap: piece survives untouched */ + } + else + { + box_subtract_into(&cur[p], &cut, nxt, &nnext); + } + } + + ncur = nnext; + tmp = cur; + cur = nxt; + nxt = tmp; + + if (ncur == 0) + break; + } + + if (cur != out) + memcpy(out, cur, ncur * sizeof(*out)); + + return ncur; +} + +/* Sequential single-rect blits (each a self-consistent memmove) can still + * corrupt each other when one piece's destination lands on another piece's + * still-unread source -- but that only actually matters if no blit order + * avoids it. Build the "must happen before" graph (piece j before piece i + * whenever dest[i] would overwrite clean[j]'s still-unread source) and + * topologically sort it: any window with more than one occluder-carved + * piece near a shared edge -- e.g. two bands split by a corner occluder -- + * routinely has one such pairwise overlap without there being a genuine + * cycle, and rejecting those outright regressed plain corner-occlusion + * drags into full fallback redraws. Only an actual cycle (i must precede j + * and j must precede i) has no safe order and needs the fallback. */ +int wuss__order_pieces(const box_t *clean, + const box_t *dest, + int n, + int *order) +{ + int adj[WUSS_MAX_INVALIDATE_PIECES][WUSS_MAX_INVALIDATE_PIECES]; + int indeg[WUSS_MAX_INVALIDATE_PIECES]; + int queue[WUSS_MAX_INVALIDATE_PIECES]; + int i, j, head, tail, nout, u; + + for (i = 0; i < n; i++) + indeg[i] = 0; + for (j = 0; j < n; j++) + for (i = 0; i < n; i++) + adj[j][i] = 0; + + for (i = 0; i < n; i++) + for (j = 0; j < n; j++) + if (i != j && !adj[j][i] && box_intersects(&dest[i], &clean[j])) + { + adj[j][i] = 1; /* j must be blitted before i */ + indeg[i]++; + } + + tail = 0; + for (i = 0; i < n; i++) + if (indeg[i] == 0) + queue[tail++] = i; + + head = nout = 0; + while (head < tail) + { + u = queue[head++]; + order[nout++] = u; + + for (i = 0; i < n; i++) + if (adj[u][i] && --indeg[i] == 0) + queue[tail++] = i; + } + + return nout == n; +} + +/* Slide the pixels of "src" (nsrc clean pieces -- the caller has already + * carved them clear of occluders and any stale regions) by (dx, dy), + * clipping every destination against the screen and against the windows + * above "window" so the blit never paints over an occluder. Blits are + * clobber-ordered (wuss__order_pieces) so no piece overwrites another's + * still-unread source. If "clip" is non-NULL scr->clip is pinned to it for + * the duration (and restored after) -- pass it when the destinations must + * not spill past some box (set-scroll pins the content box); pass NULL to + * leave the clip alone and let screen_copy_rect self-clip to the screen. + * + * On success the pieces actually copied are written to "copied" (capacity + * WUSS_MAX_INVALIDATE_PIECES), "*ncopied" is set, and 1 is returned -- the + * caller then repaints whatever "copied" did not cover. Returns 0, with + * "*ncopied" zeroed, when there is no safe fast path (nothing clean, the + * piece budget overflowed, no clobber-free order exists, or screen_copy_rect + * declined -- e.g. a paletted screen with no blit path): the caller must + * fall back to invalidating its whole dirty region. A piece copied only + * partially (its far edge slid off-screen) still has the uncovered remainder + * invalidated here, since that ground was already clipped clear of every + * occluder. A caller that already invalidates its whole affected region + * against "copied" afterwards just sees those strips folded into the dirty + * list twice, which is harmless. */ +int wuss__blit_pieces(wuss_window_t *window, + const box_t *src, + int nsrc, + int dx, + int dy, + const box_t *clip, + box_t *copied, + int *ncopied) +{ + box_t blit_src[WUSS_MAX_INVALIDATE_PIECES]; + box_t blit_dest[WUSS_MAX_INVALIDATE_PIECES]; + int order[WUSS_MAX_INVALIDATE_PIECES]; + int nblit, overflow, i, j, idx; + box_t saved_clip; + + *ncopied = 0; + + nblit = 0; + overflow = 0; + for (i = 0; i < nsrc && !overflow; i++) + { + box_t want, vis[WUSS_MAX_INVALIDATE_PIECES]; + int nvis; + + box_translated(&src[i], dx, dy, &want); + nvis = wuss__clip_to_visible(window, &want, vis); + for (j = 0; j < nvis; j++) + { + if (nblit == WUSS_MAX_INVALIDATE_PIECES) + { + overflow = 1; + break; + } + blit_dest[nblit] = vis[j]; + box_translated(&vis[j], -dx, -dy, &blit_src[nblit]); + nblit++; + } + } + + if (nsrc == 0 || overflow || + !wuss__order_pieces(blit_src, blit_dest, nblit, order)) + return 0; + + if (clip != NULL) + { + saved_clip = window->wuss->scr->clip; + window->wuss->scr->clip = *clip; + } + + for (i = 0; i < nblit; i++) + { + box_t got; + + idx = order[i]; + if (screen_copy_rect(window->wuss->scr, &blit_src[idx], + POINT(blit_dest[idx].x0, blit_dest[idx].y0), + &got) != result_OK) + { + if (clip != NULL) + window->wuss->scr->clip = saved_clip; + *ncopied = 0; + return 0; + } + + /* "got" can be smaller than blit_dest[idx] if part slid off-screen: the + * uncovered remainder has no source pixels and needs a real repaint. + * Safe raw -- blit_dest[idx] was already clipped clear of occluders. */ + wuss__invalidate_minus(window->wuss, &blit_dest[idx], &got); + + if (*ncopied < WUSS_MAX_INVALIDATE_PIECES) + copied[(*ncopied)++] = got; + } + + if (clip != NULL) + window->wuss->scr->clip = saved_clip; + + return 1; +} + +/* Invalidate the parts of "window"'s footprint that are hidden behind other + * windows at the current z-order -- the rest of its footprint is already + * showing its own correct pixels, so redrawing that too would just be + * wasted work. Bring-to-front calls this before reordering, so "hidden" + * means "about to be uncovered"; send-to-back calls it after reordering, so + * "hidden" means "just became covered" -- either way, invalidating exactly + * these parts is enough for the next redraw to leave the screen correct. */ +void wuss__invalidate_uncovered(wuss_window_t *window) +{ + box_t visible[WUSS_MAX_INVALIDATE_PIECES]; + box_t hidden[WUSS_MAX_INVALIDATE_PIECES]; + int nvisible, nhidden, i; + + nvisible = wuss__clip_to_visible(window, &window->visible, visible); + nhidden = wuss__subtract_boxes(&window->visible, visible, nvisible, hidden); + + for (i = 0; i < nhidden; i++) + wuss_invalidate(window->wuss, &hidden[i]); +} + +/* Invalidate "box" (screen space), clipped against windows above "window" + * in the z-order: the shared primitive behind wuss_window_invalidate and + * every other window-owned invalidation (move, resize, background change, + * destroy) that would otherwise redraw parts nothing can see change. */ +void wuss__invalidate_clipped(wuss_window_t *window, const box_t *box) +{ + box_t pieces[WUSS_MAX_INVALIDATE_PIECES]; + int npieces, i; + + npieces = wuss__clip_to_visible(window, box, pieces); + + for (i = 0; i < npieces; i++) + wuss_invalidate(window->wuss, &pieces[i]); +} + +/* Invalidate the part of "whole" not already covered by "keep" -- used + * after a blit has slid a window's pixels from "whole" to "keep", so only + * the vacated sliver still needs an actual repaint. */ +void wuss__invalidate_minus(wuss_t *wuss, + const box_t *whole, + const box_t *keep) +{ + box_t pieces[WUSS_MAX_INVALIDATE_PIECES]; + box_t cut; + int npieces, i; + + if (box_intersection(keep, whole, &cut)) + { + wuss_invalidate(wuss, whole); /* no overlap: all of "whole" is vacated */ + return; + } + + npieces = 0; + box_subtract_into(whole, &cut, pieces, &npieces); + + for (i = 0; i < npieces; i++) + wuss_invalidate(wuss, &pieces[i]); +} + +void wuss_window_invalidate(wuss_window_t *window, const box_t *local_box) +{ + box_t screen_box, content, whole; + + wuss__content_box(window, &content); + + if (local_box == NULL) + { + whole.x0 = 0; + whole.y0 = 0; + whole.x1 = content.x1 - content.x0; + whole.y1 = content.y1 - content.y0; + local_box = &whole; + } + + box_translated(local_box, content.x0 - window->scroll.x, + content.y0 - window->scroll.y, &screen_box); + + wuss__invalidate_clipped(window, &screen_box); +} + +void wuss_window_invalidate_extent(wuss_window_t *window) +{ + box_t extent; + + extent.x0 = 0; + extent.y0 = 0; + extent.x1 = window->doc.w; + extent.y1 = window->doc.h; + + wuss_window_invalidate(window, &extent); +} diff --git a/libraries/wuss/core/window/move.c b/libraries/wuss/core/window/move.c new file mode 100644 index 00000000..9f6bf379 --- /dev/null +++ b/libraries/wuss/core/window/move.c @@ -0,0 +1,122 @@ +/* wuss/window/move.c -- wuss - minimal window manager */ + +#include "../impl.h" + +/* p is the window's content top-left; the furniture offset (outline plus + * any titlebar) is constant for a given window, so the footprint just + * follows it */ +void wuss_window_move(wuss_window_t *window, point_t p) +{ + box_t clean[WUSS_MAX_INVALIDATE_PIECES]; + box_t full_dest[WUSS_MAX_INVALIDATE_PIECES]; + box_t copied[WUSS_MAX_INVALIDATE_PIECES]; + int width, height, outline_px, titlebar_height; + int dx, dy, nclean, ncopied, i; + box_t before, dirty; + + /* a manual move desyncs the window from its layout-packer slot; hand the + * slot back and stop tracking this window's position */ + wuss__release_packed(window); + + width = window->visible.x1 - window->visible.x0; + height = window->visible.y1 - window->visible.y0; + outline_px = wuss__outline_px(window); + titlebar_height = wuss__titlebar_height(window); + before = window->visible; + + /* a hidden window has nothing on screen to slide and must paint nothing; + * just translate its footprint so it is in place when shown again */ + if (window->flags & wuss_WINDOW_HIDDEN) + { + window->visible.x0 = p.x - outline_px; + window->visible.y0 = p.y - outline_px - titlebar_height; + window->visible.x1 = window->visible.x0 + width; + window->visible.y1 = window->visible.y0 + height; + wuss__notify_open(window); + return; + } + + /* The clean (non-occluded) pieces of "before" are genuinely this + * window's own rendering; whatever isn't clean is hidden behind some + * other window and has no valid pixels of this window's content to + * slide. Computed against the current z-order, before the move. */ + nclean = wuss__clip_to_visible(window, &before, clean); + + window->visible.x0 = p.x - outline_px; + window->visible.y0 = p.y - outline_px - titlebar_height; + window->visible.x1 = window->visible.x0 + width; + window->visible.y1 = window->visible.y0 + height; + + wuss__notify_open(window); + + dx = window->visible.x0 - before.x0; + dy = window->visible.y0 - before.y0; + + for (i = 0; i < nclean; i++) + box_translated(&clean[i], dx, dy, &full_dest[i]); + + /* Slide the clean pieces by (dx, dy); wuss__blit_pieces clips each + * destination clear of the windows above this one -- an occluder there + * hasn't moved, so its pixels are already correct and pasting stale ones + * over them would just have to be repainted straight back. It also + * repairs any piece that slid partly off-screen. */ + if (nclean > 0 && + wuss__blit_pieces(window, clean, nclean, dx, dy, NULL, copied, &ncopied)) + { + box_t hidden[WUSS_MAX_INVALIDATE_PIECES]; + int nhidden; + + /* Each clean piece is, by construction, clear of any occluder at its + * old position, so the vacated sliver left behind by sliding it to its + * full (untrimmed) new position is safe to invalidate raw, without + * re-checking occlusion -- regardless of whether every pixel of that + * new position actually got a blit above: the part that landed under an + * occluder was skipped there, but the old position is vacated either + * way. + * + * The sliver is clean[i] minus *every* clean piece's destination, not + * just its own: with an occluder biting a corner out of "before", one + * clean piece can slide onto ground another clean piece just vacated + * (e.g. a full-width bottom band vacated straight into the destination + * of the right-side band on a downward drag). That overlap already got + * valid pixels from the other piece's blit, so invalidating it would + * just repaint good pixels. */ + for (i = 0; i < nclean; i++) + { + box_t sliver[WUSS_MAX_INVALIDATE_PIECES]; + int nsliver, s; + + /* ponytail: nclean is a handful, so this can't approach the + * WUSS_MAX_INVALIDATE_PIECES cap; if that ever changes, a dropped + * piece here means a missed repaint (visible corruption), not just + * wasted work -- revisit then. */ + nsliver = wuss__subtract_boxes(&clean[i], full_dest, nclean, sliver); + for (s = 0; s < nsliver; s++) + wuss_invalidate(window->wuss, &sliver[s]); + } + + /* Whatever of "before" wasn't clean has no valid source pixels: its + * translated destination needs a genuine repaint, clipped against + * whatever's above this window there now. */ + nhidden = wuss__subtract_boxes(&before, clean, nclean, hidden); + for (i = 0; i < nhidden; i++) + { + box_t hidden_dest; + + box_translated(&hidden[i], dx, dy, &hidden_dest); + wuss__invalidate_clipped(window, &hidden_dest); + } + } + else + { + /* Nothing of "before" was clean, splitting overflowed the piece budget, + * the pieces would have clobbered each other, or the blit was declined: + * fall back to a normal clipped redraw of the whole moved footprint. */ + logf_warning("wuss_window_move: blit fast path failed, repainting the " + "whole footprint (nclean=%d)", nclean); + box_union(&before, &window->visible, &dirty); + wuss__invalidate_clipped(window, &dirty); + } + + (void) ncopied; /* move.c repairs via the sliver logic above, not "copied" */ +} diff --git a/libraries/wuss/core/window/resize.c b/libraries/wuss/core/window/resize.c new file mode 100644 index 00000000..a3d26e10 --- /dev/null +++ b/libraries/wuss/core/window/resize.c @@ -0,0 +1,160 @@ +/* wuss/window/resize.c -- wuss - minimal window manager */ + +#include "base/utils.h" +#include "geom/box.h" + +#include "../impl.h" + +/* Invalidate the part of "a" not covered by "b", given both share the same + * top-left corner (true of a window's visible box before/after a resize, as + * only the bottom-right corner moves): the difference splits into exactly + * two non-overlapping rectangles, each clipped against occluders before + * queueing. */ +static void invalidate_grown_or_shrunk(wuss_window_t *window, + const box_t *a, + const box_t *b) +{ + box_t piece; + + if (a->x1 > b->x1) + { + piece.x0 = b->x1; piece.y0 = a->y0; + piece.x1 = a->x1; piece.y1 = a->y1; + wuss__invalidate_clipped(window, &piece); + } + + if (a->y1 > b->y1) + { + piece.x0 = a->x0; piece.y0 = b->y1; + piece.x1 = MIN(a->x1, b->x1); piece.y1 = a->y1; + wuss__invalidate_clipped(window, &piece); + } +} + +result_t wuss_window_resize(wuss_window_t *window, size2d_t size) +{ + int outline_px, titlebar_height; + box_t before, before_content; + point_t carve; + point_t old_scroll; + point_t clamped; + + if (!wuss__size_ok(size.w, size.h)) + return result_WUSS_TOO_SMALL; + + /* a manual resize desyncs the window from its layout-packer slot */ + wuss__release_packed(window); + + /* The requested content size is honoured verbatim: a window may end up + * overhanging the screen edge (same as one dragged there, or one whose + * far corner is off-screen when toggle-size floors it at WUSS_MIN_CONTENT). + * Only wuss_window_create still caps at the screen. */ + + outline_px = wuss__outline_px(window); + titlebar_height = wuss__titlebar_height(window); + before = window->visible; + old_scroll = window->scroll; + wuss__content_box(window, &before_content); + wuss__furniture_carve_for(window->flags, wuss__button_size(window), &carve); + + window->visible.x1 = window->visible.x0 + size.w + 2 * outline_px + carve.x; + window->visible.y1 = window->visible.y0 + size.h + titlebar_height + 2 * outline_px + carve.y; + + wuss__notify_open(window); + + /* Enlarging the viewport (or growing it past the doc extent) can scroll + * content that no longer exists into view; pull the offset back so only + * the document extent is ever shown. The content pixels already on screen + * are still this window's own rendering, just at the old scroll offset -- + * so slide them by the scroll delta and only the newly-exposed strip(s) + * need a real repaint. The valid source is the old content box minus + * whatever windows above it were covering (those areas hold occluder + * pixels, not this window's), so blit it piece by piece; the clip bounds + * every destination to the new content box, discarding anything sliding + * off the old box's far edge rather than dragging stale pixels in. If the + * screen format can't blit, fall back to repainting the whole content + * box. Either way the grown/shrunk-sliver logic below still assumes an + * unchanged interior, which now holds. */ + clamped = wuss__scroll_clamp(window, window->scroll); + if (clamped.x != window->scroll.x || clamped.y != window->scroll.y) + { + box_t content; + box_t src[WUSS_MAX_INVALIDATE_PIECES]; + box_t copied[WUSS_MAX_INVALIDATE_PIECES]; + box_t dirty[WUSS_MAX_INVALIDATE_PIECES]; + int dx, dy, nsrc, ncopied, ndirty, i; + + dx = clamped.x - old_scroll.x; + dy = clamped.y - old_scroll.y; + window->scroll = clamped; + wuss__content_box(window, &content); + + nsrc = wuss__clip_to_visible(window, &before_content, src); + ncopied = 0; + window->wuss->scr->clip = content; + for (i = 0; i < nsrc; i++) + { + box_t got; + + if (screen_copy_rect(window->wuss->scr, &src[i], + POINT(src[i].x0 - dx, src[i].y0 - dy), + &got) == result_OK && + ncopied < WUSS_MAX_INVALIDATE_PIECES) + copied[ncopied++] = got; + } + + if (ncopied > 0) + { + /* Repaint the content box minus what the blit reused, each survivor + * clipped to this window's visible area: parts that were behind an + * occluder still show the occluder's own correct pixels, so leaving + * them out keeps the resize from redrawing the occluding window. */ + ndirty = wuss__subtract_boxes(&content, copied, ncopied, dirty); + for (i = 0; i < ndirty; i++) + wuss__invalidate_clipped(window, &dirty[i]); + } + else + { + /* the scroll-reclamp blit copied nothing (paletted screen, or every + * piece off-screen): repaint the whole content box */ + logf_warning("wuss_window_resize: scroll-reclamp blit copied nothing, " + "repainting the whole content box (nsrc=%d)", nsrc); + wuss__invalidate_clipped(window, &content); + } + wuss__chrome_repaint(window); + } + + if (window->flags & wuss_WINDOW_NO_RESIZE_BLIT) + { + /* This task's content isn't just anchored positions plus furniture -- + * it lays itself out across the whole window (e.g. a palette swatch + * grid), so the "unchanged interior" assumption below doesn't hold: + * every pixel of the new footprint needs redrawing, not just the + * grown/shrunk sliver. */ + box_t dirty; + + box_union(&before, &window->visible, &dirty); + wuss__invalidate_clipped(window, &dirty); + } + else + { + /* The top-left corner is fixed, so any pixels within both the old and + * new footprint are unchanged and don't need repainting -- only the + * shrunk-away sliver (revealing whatever is now behind it) and the + * grown-into sliver (this window's own previously-undrawn content) do. */ + invalidate_grown_or_shrunk(window, &before, &window->visible); + invalidate_grown_or_shrunk(window, &window->visible, &before); + + /* Growing strands old furniture (e.g. the old outline/scrollbar edge) + * inside what's now interior content -- a region both calls above treat + * as already-valid and so never repaint. Force its old position dirty + * too. Shrinking needs no such help: old furniture positions only ever + * land outside the new, smaller box, already covered above. */ + if (window->visible.x1 - window->visible.x0 > before.x1 - before.x0 || + window->visible.y1 - window->visible.y0 > before.y1 - before.y0) + wuss__chrome_repaint_for(window, &before); + wuss__chrome_repaint(window); + } + + return result_OK; +} diff --git a/libraries/wuss/window/restack.c b/libraries/wuss/core/window/restack.c similarity index 93% rename from libraries/wuss/window/restack.c rename to libraries/wuss/core/window/restack.c index 5192bdfb..d4d7ee9e 100644 --- a/libraries/wuss/window/restack.c +++ b/libraries/wuss/core/window/restack.c @@ -1,4 +1,4 @@ -/* restack.c -- wuss - minimal window manager */ +/* wuss/window/restack.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/core/window/set-background.c b/libraries/wuss/core/window/set-background.c new file mode 100644 index 00000000..1d1d5337 --- /dev/null +++ b/libraries/wuss/core/window/set-background.c @@ -0,0 +1,22 @@ +/* wuss/window/set-background.c -- wuss - minimal window manager */ + +#include "../impl.h" + +result_t wuss_window_set_background(wuss_window_t *window, + wuss_backdrop_t bg) +{ + box_t content; + + bg.colour = wuss__resolve_colour(window->wuss, bg.colour); + bg.pattern_bg = wuss__resolve_colour(window->wuss, bg.pattern_bg); + + if (wuss__validate_backdrop(window->wuss, &bg) != result_OK) + return result_WUSS_BAD_COLOUR; + + window->bg = bg; + + wuss__content_box(window, &content); + wuss__invalidate_clipped(window, &content); + + return result_OK; +} diff --git a/libraries/wuss/core/window/set-hidden.c b/libraries/wuss/core/window/set-hidden.c new file mode 100644 index 00000000..373c2477 --- /dev/null +++ b/libraries/wuss/core/window/set-hidden.c @@ -0,0 +1,41 @@ +/* wuss/window/set-hidden.c -- wuss - minimal window manager */ + +#include + +#include "wuss/task.h" + +#include "../impl.h" + +result_t wuss_window_set_hidden(wuss_window_t *window, int hidden) +{ + wuss_event_t event; + result_t rc; + int was_hidden; + + was_hidden = (window->flags & wuss_WINDOW_HIDDEN) != 0; + if (!was_hidden == !hidden) + return result_OK; /* no change */ + + if (hidden) + { + /* still on screen: repaint its footprint now, then mark it gone */ + wuss_invalidate(window->wuss, &window->visible); + window->flags |= wuss_WINDOW_HIDDEN; + return result_OK; + } + + /* hidden -> visible: PRE_SHOW may veto, leaving the window hidden. */ + event.kind = wuss_EVENT_PRE_SHOW; + rc = wuss__deliver(window->task, window, &event); + if (rc != result_OK) + return rc; + + /* mark it back, repaint its footprint so it appears, then SHOW */ + window->flags &= (wuss_window_flags_t) ~wuss_WINDOW_HIDDEN; + wuss_invalidate(window->wuss, &window->visible); + + event.kind = wuss_EVENT_SHOW; + (void) wuss__deliver(window->task, window, &event); + + return result_OK; +} diff --git a/libraries/wuss/core/window/set-scroll.c b/libraries/wuss/core/window/set-scroll.c new file mode 100644 index 00000000..4717e7cf --- /dev/null +++ b/libraries/wuss/core/window/set-scroll.c @@ -0,0 +1,87 @@ +/* wuss/window/set-scroll.c -- wuss - minimal window manager */ + +#include "../impl.h" + +void wuss_window_set_scroll(wuss_window_t *window, point_t p) +{ + box_t content; + box_t stale[WUSS_MAX_DIRTY]; + box_t clean[WUSS_MAX_INVALIDATE_PIECES]; + box_t src[WUSS_MAX_INVALIDATE_PIECES]; + box_t copied[WUSS_MAX_INVALIDATE_PIECES]; + box_t dirty[WUSS_MAX_INVALIDATE_PIECES]; + int dx, dy, nstale, nclean, nsrc, ncopied, ndirty, i, overflow; + + dx = p.x - window->scroll.x; + dy = p.y - window->scroll.y; + if (dx == 0 && dy == 0) + return; + + window->scroll = p; + + wuss__content_box(window, &content); + + /* Any content-box area already sitting in the dirty list holds stale pixels + * (e.g. the strip exposed by an earlier scroll this frame, not yet + * redrawn): a wheel spin can deliver several scrolls before wuss_redraw_dirty + * runs, and the blit below is a framebuffer memmove -- feeding it a stale + * source would smear that stale content into the interior. Collect those + * regions now, before the furniture invalidate adds its own unrelated + * rects, and exclude them from the blit source further down. */ + nstale = 0; + for (i = 0; i < window->wuss->ndirty; i++) + if (box_intersects(&window->wuss->dirty[i], &content)) + stale[nstale++] = window->wuss->dirty[i]; + + /* the scrollbar sausage position depends on scroll, so its well needs + * redrawing too -- content invalidation alone never touches it */ + wuss__chrome_repaint(window); + + /* The valid blit source is the content box minus whatever windows above it + * were covering (those areas hold occluder pixels, not this window's), + * minus the stale regions collected above. wuss__blit_pieces then clips + * each destination against the occluders too. Each subtract can split a + * piece into up to four bands, so this can overflow the piece budget on a + * badly fragmented window -- treat that as "no safe fast path". */ + nclean = wuss__clip_to_visible(window, &content, clean); + nsrc = 0; + overflow = 0; + for (i = 0; i < nclean; i++) + { + box_t kept[WUSS_MAX_INVALIDATE_PIECES]; + int nkept, k; + + nkept = wuss__subtract_boxes(&clean[i], stale, nstale, kept); + for (k = 0; k < nkept; k++) + { + if (nsrc == WUSS_MAX_INVALIDATE_PIECES) + { + overflow = 1; + break; + } + src[nsrc++] = kept[k]; + } + if (overflow) + break; + } + + if (!overflow && + wuss__blit_pieces(window, src, nsrc, -dx, -dy, &content, + copied, &ncopied)) + { + /* Repaint the content box minus what the blit reused, each survivor + * clipped to this window's visible area: parts that were behind an + * occluder still show the occluder's own correct pixels, so leaving + * them out keeps the scroll from redrawing the occluding window. */ + ndirty = wuss__subtract_boxes(&content, copied, ncopied, dirty); + for (i = 0; i < ndirty; i++) + wuss__invalidate_clipped(window, &dirty[i]); + return; + } + + /* fast path unavailable (piece budget overflowed, or wuss__blit_pieces + * found no safe blit): repaint the whole content box */ + logf_warning("wuss_window_set_scroll: blit fast path failed, repainting the " + "whole content box (overflow=%d)", overflow); + wuss__invalidate_clipped(window, &content); +} diff --git a/libraries/wuss/core/window/try-close.c b/libraries/wuss/core/window/try-close.c new file mode 100644 index 00000000..d8a14a4f --- /dev/null +++ b/libraries/wuss/core/window/try-close.c @@ -0,0 +1,31 @@ +/* wuss/window/try-close.c -- veto-checked window close */ + +#include + +#include "wuss/task.h" + +#include "../impl.h" + +result_t wuss_window_try_close(wuss_window_t *window) +{ + wuss_event_t event; + result_t rc; + + if (window == NULL) + return result_OK; + + /* PRE_CLOSE: a non-OK return from the task vetoes the close; the window + * stays open and no CLOSE is sent. */ + event.kind = wuss_EVENT_PRE_CLOSE; + rc = wuss__deliver(window->task, window, &event); + if (rc != result_OK) + return rc; + + /* CLOSE while the window is still alive, then the unvetoable teardown. */ + event.kind = wuss_EVENT_CLOSE; + (void) wuss__deliver(window->task, window, &event); + + wuss_window_close(window); + + return result_OK; +} diff --git a/libraries/wuss/create.c b/libraries/wuss/create.c deleted file mode 100644 index 424d6e5a..00000000 --- a/libraries/wuss/create.c +++ /dev/null @@ -1,138 +0,0 @@ -/* create.c -- wuss - minimal window manager */ - -#include -#include -#include - -#ifdef FORTIFY -#include "fortify/fortify.h" -#endif - -#include "base/utils.h" -#include "framebuf/palettes.h" - -#include "impl.h" - -result_t wuss_create(screen_t *scr, - bmfont_t *font, - const colour_t *palette, - int npalette, - const wuss_config_t *config, - wuss_t **wuss) -{ - wuss_t *w; - wuss_palette_t pal; - wuss_colour_t bg, fg; - int font_height; - int font_width; - - assert(scr != NULL); - assert(wuss != NULL); - - w = malloc(sizeof(*w)); - if (w == NULL) - return result_OOM; - - if (palette != NULL) - { - if (npalette <= 0) - { - free(w); - return result_BAD_ARG; - } - - w->palette = malloc(npalette * sizeof(*w->palette)); - if (w->palette == NULL) - { - free(w); - return result_OOM; - } - memcpy(w->palette, palette, npalette * sizeof(*w->palette)); - w->npalette = npalette; - } - else - { - w->palette = malloc(palette_PICO8__LENGTH * sizeof(*w->palette)); - if (w->palette == NULL) - { - free(w); - return result_OOM; - } - define_pico8_palette(w->palette); - w->npalette = palette_PICO8__LENGTH; - } - - if (config != NULL) - { - pal = config->palette; - } - else - { - if (palette == NULL) - { - bg = palette_PICO8_DARK_BLUE; - fg = palette_PICO8_WHITE; - } - else - { - bg = 0; - fg = (w->npalette > 1) ? 1 : 0; - } - - pal.title.bg = bg; - pal.title.fg = fg; - pal.back = fg; - pal.close = fg; - pal.toggle = fg; - pal.resize = bg; - pal.arrows = bg; - pal.wells = bg; - pal.sausages = fg; - } - - if (pal.title.bg < 0 || pal.title.bg >= w->npalette || - pal.title.fg < 0 || pal.title.fg >= w->npalette || - pal.back < 0 || pal.back >= w->npalette || - pal.close < 0 || pal.close >= w->npalette || - pal.toggle < 0 || pal.toggle >= w->npalette || - pal.resize < 0 || pal.resize >= w->npalette || - pal.arrows < 0 || pal.arrows >= w->npalette || - pal.wells < 0 || pal.wells >= w->npalette || - pal.sausages < 0 || pal.sausages >= w->npalette) - { - free(w->palette); - free(w); - return result_WUSS_BAD_COLOUR; - } - - w->furniture_colours = pal; - - if (config != NULL && config->titlebar_height > 0) - { - w->titlebar_height = config->titlebar_height; - } - else if (font != NULL) - { - bmfont_get_info(font, &font_width, &font_height); - NOT_USED(font_width); - w->titlebar_height = font_height + 4; - } - else - { - w->titlebar_height = WUSS_DEFAULT_TITLEBAR_HEIGHT; - } - - w->scr = scr; - w->font = font; - w->furniture.dragging = NULL; - w->furniture.drag.x = 0; - w->furniture.drag.y = 0; - - w->ndirty = 0; - - list_init(&w->z_order); - - *wuss = w; - - return result_OK; -} diff --git a/libraries/wuss/destroy.c b/libraries/wuss/destroy.c deleted file mode 100644 index c546af82..00000000 --- a/libraries/wuss/destroy.c +++ /dev/null @@ -1,30 +0,0 @@ -/* destroy.c -- wuss - minimal window manager */ - -#include - -#ifdef FORTIFY -#include "fortify/fortify.h" -#endif - -#include "impl.h" - -void wuss_destroy(wuss_t *doomed) -{ - list_t *e; - - if (doomed == NULL) - return; - - e = doomed->z_order.next; - while (e != NULL) - { - list_t *next; - - next = e->next; - free(e); - e = next; - } - - free(doomed->palette); - free(doomed); -} diff --git a/libraries/wuss/furniture.h b/libraries/wuss/furniture.h index ddc707b3..0bf378b9 100644 --- a/libraries/wuss/furniture.h +++ b/libraries/wuss/furniture.h @@ -1,4 +1,4 @@ -/* furniture.h -- wuss - minimal window manager */ +/* wuss/furniture.h -- wuss - minimal window manager */ #ifndef WUSS_FURNITURE_IMPL_H #define WUSS_FURNITURE_IMPL_H @@ -8,8 +8,9 @@ #include "wuss/wuss.h" -#define WUSS_MIN_SAUSAGE 6 /* scrollbar sausage never shrinks below this, however small the content fraction */ -#define WUSS_SCROLL_STEP 20 /* pixels stepped per scrollbar arrow click */ +#define WUSS_MIN_SAUSAGE 6 /* scrollbar sausage never shrinks below this, however small the content fraction */ +#define WUSS_SCROLL_END_GAP 2 /* sausage along-axis margin from its well's ends, purely cosmetic */ +#define WUSS_SCROLL_STEP 20 /* pixels stepped per scrollbar arrow click */ /* Which region of a window's border (or its content) a point falls in. */ typedef enum wuss_furniture_region @@ -46,9 +47,14 @@ struct wuss__furniture { wuss_window_t *dragging; /* NULL when idle */ wuss_furniture_drag_kind_t drag_kind; - point_t drag; /* MOVE: pointer offset within content; - * RESIZE: unused, recomputed each move; - * *_SAUSAGE: pointer position at drag start */ + point_t drag; /* *_SAUSAGE: pointer position at drag start; + * MOVE: pointer offset within content */ + point_t drag_offset; /* RESIZE: pointer offset from the + * content box's bottom-right corner + * at drag start, so the grab point + * stays under the pointer instead + * of the window's edge snapping to + * it on the first move */ int drag_scroll_start; /* *_SAUSAGE: scroll.x/scroll.y at drag start */ }; @@ -76,7 +82,33 @@ void wuss__furniture_draw(wuss_t *wuss, wuss_window_t *window, const box_t *full); void wuss__furniture_invalidate(wuss_window_t *window); -void wuss__furniture_invalidate_for(wuss_window_t *window, const box_t *visible); +void wuss__furniture_invalidate_for(wuss_window_t *window, + const box_t *visible); + +/* Dispatch table the wuss core reaches window furniture through: everything + * the core (z-order, mouse routing, redraw, window lifecycle) calls that + * lives in the furniture .c files. A caller could point wuss::furniture_ops at its + * own table to replace the furniture wholesale; wuss_create defaults it to + * wuss__furniture_default_ops. The geometry helpers in impl.h + * (wuss__titlebar_box, wuss__content_box, wuss__furniture_carve_for, the + * titlebar-height/button-size inlines) are a compile-time seam between the + * furniture and no-furniture builds and stay out of this table. */ +typedef struct wuss__furniture_ops +{ + wuss_furniture_region_t (*hit_test)(const wuss_window_t *window, point_t p); + void (*draw)(wuss_t *wuss, wuss_window_t *window, const box_t *full); + void (*invalidate)(wuss_window_t *window); + void (*invalidate_for)(wuss_window_t *window, const box_t *visible); + void (*toggle_size)(wuss_window_t *window); + void (*drag_resize)(wuss_window_t *window, point_t p); + void (*drag_sausage)(wuss_window_t *window, + int delta_px, + int scroll_start, + int horizontal); +} +wuss__furniture_ops_t; + +extern const wuss__furniture_ops_t wuss__furniture_default_ops; /* geometry: titlebar icons */ void wuss__back_box(const wuss_window_t *window, box_t *out); @@ -99,8 +131,6 @@ int wuss__hscroll_well_px(const wuss_window_t *window); /* actions */ void wuss__furniture_toggle_size(wuss_window_t *window); -void wuss__furniture_scroll_step(wuss_window_t *window, point_t delta); -point_t wuss__scroll_clamp(const wuss_window_t *window, point_t desired); void wuss__furniture_drag_resize(wuss_window_t *window, point_t p); void wuss__furniture_drag_sausage(wuss_window_t *window, int delta_px, diff --git a/libraries/wuss/furniture/back-box.c b/libraries/wuss/furniture/back-box.c index f1ada608..3998a785 100644 --- a/libraries/wuss/furniture/back-box.c +++ b/libraries/wuss/furniture/back-box.c @@ -1,6 +1,6 @@ -/* back-box.c -- wuss - minimal window manager */ +/* wuss/furniture/back-box.c -- wuss - minimal window manager */ -#include "../impl.h" +#include "../core/impl.h" void wuss__back_box(const wuss_window_t *window, box_t *out) { @@ -9,8 +9,8 @@ void wuss__back_box(const wuss_window_t *window, box_t *out) wuss__titlebar_box(window, &titlebar); - inset = WUSS_ICON_INSET; - size = wuss__icon_size(window); + inset = WUSS_BUTTON_INSET; + size = wuss__button_size(window); out->x0 = titlebar.x0 + inset; out->y0 = titlebar.y0 + inset; diff --git a/libraries/wuss/furniture/close-box.c b/libraries/wuss/furniture/close-box.c index c927f8c5..e7c19c8e 100644 --- a/libraries/wuss/furniture/close-box.c +++ b/libraries/wuss/furniture/close-box.c @@ -1,6 +1,6 @@ -/* close-box.c -- wuss - minimal window manager */ +/* wuss/furniture/close-box.c -- wuss - minimal window manager */ -#include "../impl.h" +#include "../core/impl.h" void wuss__close_box(const wuss_window_t *window, box_t *out) { @@ -9,8 +9,8 @@ void wuss__close_box(const wuss_window_t *window, box_t *out) wuss__titlebar_box(window, &titlebar); - inset = WUSS_ICON_INSET; - size = wuss__icon_size(window); + inset = WUSS_BUTTON_INSET; + size = wuss__button_size(window); out->x0 = titlebar.x0 + inset; if (!(window->flags & wuss_WINDOW_NO_BACK)) diff --git a/libraries/wuss/furniture/content-box.c b/libraries/wuss/furniture/content-box.c index 0f8081e6..45675d84 100644 --- a/libraries/wuss/furniture/content-box.c +++ b/libraries/wuss/furniture/content-box.c @@ -1,6 +1,6 @@ -/* content-box.c -- wuss - minimal window manager */ +/* wuss/furniture/content-box.c -- wuss - minimal window manager */ -#include "../impl.h" +#include "../core/impl.h" void wuss__content_box(const wuss_window_t *window, box_t *out) { @@ -14,7 +14,7 @@ void wuss__content_box(const wuss_window_t *window, box_t *out) out->x1 = window->visible.x1 - outline_px; out->y1 = window->visible.y1 - outline_px; - wuss__furniture_carve_for(window->flags, wuss__icon_size(window), &carve); + wuss__furniture_carve_for(window->flags, wuss__button_size(window), &carve); out->x1 -= carve.x; out->y1 -= carve.y; } diff --git a/libraries/wuss/furniture/drag-resize.c b/libraries/wuss/furniture/drag-resize.c index 965ff557..00acdc0a 100644 --- a/libraries/wuss/furniture/drag-resize.c +++ b/libraries/wuss/furniture/drag-resize.c @@ -1,20 +1,25 @@ -/* drag-resize.c -- wuss - minimal window manager */ +/* wuss/furniture/drag-resize.c -- wuss - minimal window manager */ #include "base/utils.h" -#include "../impl.h" +#include "../core/impl.h" void wuss__furniture_drag_resize(wuss_window_t *window, point_t p) { - box_t content; - int width, height; + box_t content; + size2d_t min; + int width, height; wuss__content_box(window, &content); + wuss__min_content(window, &min); - width = p.x - content.x0; - height = p.y - content.y0; - width = CLAMP(width, WUSS_MIN_CONTENT, window->doc_width); - height = CLAMP(height, WUSS_MIN_CONTENT, window->doc_height); + /* Subtract the offset recorded at drag start so the point originally + * grabbed on the resize icon stays under the pointer, rather than the + * window's edge snapping to meet the pointer on the first move. */ + width = p.x - window->wuss->furniture.drag_offset.x - content.x0; + height = p.y - window->wuss->furniture.drag_offset.y - content.y0; + width = CLAMP(width, min.w, MAX(window->doc.w, min.w)); + height = CLAMP(height, min.h, MAX(window->doc.h, min.h)); - wuss_window_resize(window, width, height); + wuss_window_resize(window, SIZE2D(width, height)); } diff --git a/libraries/wuss/furniture/draw.c b/libraries/wuss/furniture/draw.c index bf0c2102..38d73fe4 100644 --- a/libraries/wuss/furniture/draw.c +++ b/libraries/wuss/furniture/draw.c @@ -1,19 +1,44 @@ -/* draw.c -- wuss - minimal window manager */ +/* wuss/furniture/draw.c -- wuss - minimal window manager */ #include #include "base/utils.h" #include "geom/point.h" -#include "../impl.h" +#include "../core/impl.h" + +/* Paint one furniture rectangle "b" in "colour", clipped to the part of it + * that falls inside "full" (the redraw region). A no-op when "b" is wholly + * outside "full". Pins scr->clip to the clipped rect -- the caller's next + * draw is expected to set its own clip. */ +static void fill_furniture_rect(wuss_t *wuss, + const box_t *b, + const box_t *full, + colour_t colour) +{ + box_t clipped; + + if (box_intersection(b, full, &clipped)) + return; /* wholly outside the redraw region */ + + wuss->scr->clip = clipped; + screen_fill_rect(wuss->scr, b->x0, b->y0, box_size(b), colour); +} void wuss__furniture_draw(wuss_t *wuss, wuss_window_t *window, const box_t *full) { - box_t visible_clipped; - box_t clipped; - box_t titlebar; + box_t visible_clipped; + box_t clipped; + box_t titlebar; + bmfont_t *titlefont; + + /* window titles are drawn in the bold weight (font slot 1) when one was + * supplied, falling back to the system font */ + titlefont = (wuss->nfonts > 1 && wuss->fonts[1] != NULL) + ? wuss->fonts[1] + : wuss->fonts[0]; if (box_intersection(&window->visible, full, &visible_clipped)) return; /* offscreen */ @@ -22,16 +47,16 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&titlebar, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, - titlebar.x0, titlebar.y0, - titlebar.x1 - titlebar.x0, titlebar.y1 - titlebar.y0, + screen_fill_rect(wuss->scr, + titlebar.x0, titlebar.y0, box_size(&titlebar), wuss->palette[wuss->furniture_colours.title.bg]); - if (wuss->font != NULL && window->title[0] != '\0') + if (titlefont != NULL && window->title[0] != '\0') { point_t pos; int text_x0, text_x1, titlelen, split_point; bmfont_width_t width; + box_t text_box, text_clip; text_x0 = titlebar.x0 + 2; if (!(window->flags & wuss_WINDOW_NO_CLOSE)) @@ -58,16 +83,26 @@ void wuss__furniture_draw(wuss_t *wuss, text_x1 = toggle.x0 - 2; } - if (text_x1 > text_x0) + /* A title too wide for its slot mustn't bleed into a neighbouring + * icon: clip drawing to the slot itself, not just the whole titlebar, + * so a too-long title is cut off cleanly rather than overdrawing + * whatever furniture the current redraw didn't happen to touch. */ + text_box.x0 = text_x0; + text_box.y0 = titlebar.y0; + text_box.x1 = text_x1; + text_box.y1 = titlebar.y1; + if (text_x1 > text_x0 && !box_intersection(&text_box, &clipped, &text_clip)) { titlelen = (int) strlen(window->title); - bmfont_measure(wuss->font, window->title, titlelen, text_x1 - text_x0, &split_point, &width); + bmfont_measure(titlefont, window->title, titlelen, text_x1 - text_x0, &split_point, &width); pos.x = (split_point < titlelen) ? text_x0 : text_x0 + MAX(0, ((text_x1 - text_x0) - width) / 2); pos.y = titlebar.y0 + 2; - bmfont_draw(wuss->font, wuss->scr, window->title, titlelen, + wuss->scr->clip = text_clip; + bmfont_draw(titlefont, wuss->scr, window->title, titlelen, wuss->palette[wuss->furniture_colours.title.fg], wuss->palette[wuss->furniture_colours.title.bg], &pos, NULL); + wuss->scr->clip = clipped; } } @@ -76,10 +111,8 @@ void wuss__furniture_draw(wuss_t *wuss, box_t close; wuss__close_box(window, &close); - screen_draw_rect(wuss->scr, - close.x0, close.y0, - close.x1 - close.x0, close.y1 - close.y0, - wuss->palette[wuss->furniture_colours.close]); + fill_furniture_rect(wuss, &close, full, + wuss->palette[wuss->furniture_colours.close]); } if (!(window->flags & wuss_WINDOW_NO_BACK)) @@ -87,10 +120,8 @@ void wuss__furniture_draw(wuss_t *wuss, box_t back; wuss__back_box(window, &back); - screen_draw_rect(wuss->scr, - back.x0, back.y0, - back.x1 - back.x0, back.y1 - back.y0, - wuss->palette[wuss->furniture_colours.back]); + fill_furniture_rect(wuss, &back, full, + wuss->palette[wuss->furniture_colours.back]); } if (!(window->flags & wuss_WINDOW_NO_TOGGLE_SIZE)) @@ -98,10 +129,8 @@ void wuss__furniture_draw(wuss_t *wuss, box_t toggle; wuss__toggle_box(window, &toggle); - screen_draw_rect(wuss->scr, - toggle.x0, toggle.y0, - toggle.x1 - toggle.x0, toggle.y1 - toggle.y0, - wuss->palette[wuss->furniture_colours.toggle]); + fill_furniture_rect(wuss, &toggle, full, + wuss->palette[wuss->furniture_colours.toggle]); } } @@ -110,14 +139,8 @@ void wuss__furniture_draw(wuss_t *wuss, box_t resize; wuss__resize_box(window, &resize); - if (!box_intersection(&resize, full, &clipped)) - { - wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, - resize.x0, resize.y0, - resize.x1 - resize.x0, resize.y1 - resize.y0, - wuss->palette[wuss->furniture_colours.resize]); - } + fill_furniture_rect(wuss, &resize, full, + wuss->palette[wuss->furniture_colours.resize]); } if (!(window->flags & wuss_WINDOW_NO_VSCROLL)) @@ -125,36 +148,20 @@ void wuss__furniture_draw(wuss_t *wuss, box_t up, down, well, sausage; wuss__vscroll_up_box(window, &up); - if (!box_intersection(&up, full, &clipped)) - { - wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, up.x0, up.y0, up.x1 - up.x0, up.y1 - up.y0, - wuss->palette[wuss->furniture_colours.arrows]); - } + fill_furniture_rect(wuss, &up, full, + wuss->palette[wuss->furniture_colours.scroll.arrows]); wuss__vscroll_down_box(window, &down); - if (!box_intersection(&down, full, &clipped)) - { - wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, down.x0, down.y0, down.x1 - down.x0, down.y1 - down.y0, - wuss->palette[wuss->furniture_colours.arrows]); - } + fill_furniture_rect(wuss, &down, full, + wuss->palette[wuss->furniture_colours.scroll.arrows]); wuss__vscroll_well_box(window, &well); - if (!box_intersection(&well, full, &clipped)) - { - wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, well.x0, well.y0, well.x1 - well.x0, well.y1 - well.y0, - wuss->palette[wuss->furniture_colours.wells]); - } + fill_furniture_rect(wuss, &well, full, + wuss->palette[wuss->furniture_colours.scroll.wells]); wuss__vscroll_sausage_box(window, &sausage); - if (!box_intersection(&sausage, full, &clipped)) - { - wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, sausage.x0, sausage.y0, sausage.x1 - sausage.x0, sausage.y1 - sausage.y0, - wuss->palette[wuss->furniture_colours.sausages]); - } + fill_furniture_rect(wuss, &sausage, full, + wuss->palette[wuss->furniture_colours.scroll.sausages]); } if (!(window->flags & wuss_WINDOW_NO_HSCROLL)) @@ -162,35 +169,49 @@ void wuss__furniture_draw(wuss_t *wuss, box_t left, right, well, sausage; wuss__hscroll_left_box(window, &left); - if (!box_intersection(&left, full, &clipped)) - { - wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, left.x0, left.y0, left.x1 - left.x0, left.y1 - left.y0, - wuss->palette[wuss->furniture_colours.arrows]); - } + fill_furniture_rect(wuss, &left, full, + wuss->palette[wuss->furniture_colours.scroll.arrows]); wuss__hscroll_right_box(window, &right); - if (!box_intersection(&right, full, &clipped)) - { - wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, right.x0, right.y0, right.x1 - right.x0, right.y1 - right.y0, - wuss->palette[wuss->furniture_colours.arrows]); - } + fill_furniture_rect(wuss, &right, full, + wuss->palette[wuss->furniture_colours.scroll.arrows]); wuss__hscroll_well_box(window, &well); - if (!box_intersection(&well, full, &clipped)) + fill_furniture_rect(wuss, &well, full, + wuss->palette[wuss->furniture_colours.scroll.wells]); + + wuss__hscroll_sausage_box(window, &sausage); + fill_furniture_rect(wuss, &sausage, full, + wuss->palette[wuss->furniture_colours.scroll.sausages]); + } + + { + box_t content, rule; + point_t carve; + + /* Interior rules: where furniture is carved off the content area's right + * or bottom edge, the last pixel of the carve is a dividing line. */ + wuss__content_box(window, &content); + wuss__furniture_carve_for(window->flags, wuss__button_size(window), &carve); + + if (carve.x > 0) { - wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, well.x0, well.y0, well.x1 - well.x0, well.y1 - well.y0, - wuss->palette[wuss->furniture_colours.wells]); + rule.x0 = content.x1; + rule.x1 = content.x1 + WUSS_DIVIDER_PX; + rule.y0 = content.y0; + rule.y1 = content.y1; + fill_furniture_rect(wuss, &rule, full, + wuss->palette[wuss->furniture_colours.title.bg]); } - wuss__hscroll_sausage_box(window, &sausage); - if (!box_intersection(&sausage, full, &clipped)) + if (carve.y > 0) { - wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, sausage.x0, sausage.y0, sausage.x1 - sausage.x0, sausage.y1 - sausage.y0, - wuss->palette[wuss->furniture_colours.sausages]); + rule.x0 = content.x0; + rule.x1 = content.x1 + ((carve.x > 0) ? WUSS_DIVIDER_PX : 0); /* meet the vertical rule at the corner */ + rule.y0 = content.y1; + rule.y1 = content.y1 + WUSS_DIVIDER_PX; + fill_furniture_rect(wuss, &rule, full, + wuss->palette[wuss->furniture_colours.title.bg]); } } @@ -204,9 +225,9 @@ void wuss__furniture_draw(wuss_t *wuss, border = wuss->palette[wuss->furniture_colours.title.bg]; /* no dedicated outline class; matches titlebar chrome */ wuss->scr->clip = visible_clipped; - screen_draw_rect(wuss->scr, window->visible.x0, window->visible.y0, width, 1, border); - screen_draw_rect(wuss->scr, window->visible.x0, window->visible.y1 - 1, width, 1, border); - screen_draw_rect(wuss->scr, window->visible.x0, window->visible.y0, 1, height, border); - screen_draw_rect(wuss->scr, window->visible.x1 - 1, window->visible.y0, 1, height, border); + screen_fill_rect(wuss->scr, window->visible.x0, window->visible.y0, SIZE2D(width, 1), border); + screen_fill_rect(wuss->scr, window->visible.x0, window->visible.y1 - 1, SIZE2D(width, 1), border); + screen_fill_rect(wuss->scr, window->visible.x0, window->visible.y0, SIZE2D(1, height), border); + screen_fill_rect(wuss->scr, window->visible.x1 - 1, window->visible.y0, SIZE2D(1, height), border); } } diff --git a/libraries/wuss/furniture/hit-test.c b/libraries/wuss/furniture/hit-test.c index d6e0705b..b2439005 100644 --- a/libraries/wuss/furniture/hit-test.c +++ b/libraries/wuss/furniture/hit-test.c @@ -1,6 +1,6 @@ -/* hit-test.c -- wuss - minimal window manager */ +/* wuss/furniture/hit-test.c -- wuss - minimal window manager */ -#include "../impl.h" +#include "../core/impl.h" wuss_furniture_region_t wuss__furniture_hit_test(const wuss_window_t *window, point_t p) diff --git a/libraries/wuss/furniture/hscroll-box.c b/libraries/wuss/furniture/hscroll-box.c deleted file mode 100644 index d25155ea..00000000 --- a/libraries/wuss/furniture/hscroll-box.c +++ /dev/null @@ -1,99 +0,0 @@ -/* hscroll-box.c -- wuss - minimal window manager */ - -#include "../impl.h" - -static void hscroll_row(const wuss_window_t *window, box_t *out) -{ - int outline_px, size; - - outline_px = wuss__outline_px(window); - size = wuss__icon_size(window); - - out->y1 = window->visible.y1 - outline_px; - out->y0 = out->y1 - size; - - out->x0 = window->visible.x0 + outline_px; - out->x1 = window->visible.x1 - outline_px - size; /* stop left of the resize corner */ -} - -void wuss__hscroll_left_box(const wuss_window_t *window, box_t *out) -{ - box_t row; - int size; - - hscroll_row(window, &row); - size = wuss__icon_size(window); - - out->y0 = row.y0; - out->y1 = row.y1; - out->x0 = row.x0; - out->x1 = row.x0 + size; -} - -void wuss__hscroll_right_box(const wuss_window_t *window, box_t *out) -{ - box_t row; - int size; - - hscroll_row(window, &row); - size = wuss__icon_size(window); - - out->y0 = row.y0; - out->y1 = row.y1; - out->x1 = row.x1; - out->x0 = row.x1 - size; -} - -void wuss__hscroll_well_box(const wuss_window_t *window, box_t *out) -{ - box_t row; - int size; - - hscroll_row(window, &row); - size = wuss__icon_size(window); - - out->y0 = row.y0; - out->y1 = row.y1; - out->x0 = row.x0 + size; - out->x1 = row.x1 - size; -} - -int wuss__hscroll_well_px(const wuss_window_t *window) -{ - box_t well; - - wuss__hscroll_well_box(window, &well); - - return well.x1 - well.x0; -} - -void wuss__hscroll_sausage_box(const wuss_window_t *window, box_t *out) -{ - box_t well, content; - int well_px, content_size, doc_size, sausage_px, sausage_x0; - - wuss__hscroll_well_box(window, &well); - wuss__content_box(window, &content); - - well_px = well.x1 - well.x0; - content_size = content.x1 - content.x0; - doc_size = window->doc_width; - if (doc_size < content_size) - doc_size = content_size; - - sausage_px = (doc_size > 0) ? well_px * content_size / doc_size : well_px; - if (sausage_px < WUSS_MIN_SAUSAGE) - sausage_px = WUSS_MIN_SAUSAGE; - if (sausage_px > well_px) - sausage_px = well_px; - - if (doc_size > content_size && well_px > sausage_px) - sausage_x0 = well.x0 + (well_px - sausage_px) * window->scroll.x / (doc_size - content_size); - else - sausage_x0 = well.x0; - - out->y0 = well.y0; - out->y1 = well.y1; - out->x0 = sausage_x0; - out->x1 = sausage_x0 + sausage_px; -} diff --git a/libraries/wuss/furniture/invalidate.c b/libraries/wuss/furniture/invalidate.c index e7385d9e..51c03d53 100644 --- a/libraries/wuss/furniture/invalidate.c +++ b/libraries/wuss/furniture/invalidate.c @@ -1,17 +1,23 @@ -/* invalidate.c -- wuss - minimal window manager */ +/* wuss/furniture/invalidate.c -- wuss - minimal window manager */ -#include "../impl.h" +#include "../core/impl.h" /* Same as wuss__furniture_invalidate, but against an arbitrary box rather * than window->visible -- lets a caller that's just resized the window * (toggle-size) also mark the *old* furniture strips dirty, since a blit * that reused the old pixels leaves stale titlebar/scrollbar/outline * pixels sitting wherever those strips used to be. */ -void wuss__furniture_invalidate_for(wuss_window_t *window, const box_t *visible) +void wuss__furniture_invalidate_for(wuss_window_t *window, + const box_t *visible) { - int outline_px; + int outline_px; + point_t carve; outline_px = wuss__outline_px(window); + /* Ask for the same carve the layout uses rather than reading the + * scrollbar flags directly: a window with both scrollbars off but resize + * on still reserves both strips, for the resize icon and the rules. */ + wuss__furniture_carve_for(window->flags, wuss__button_size(window), &carve); if (!(window->flags & wuss_WINDOW_NO_TITLEBAR)) { @@ -24,23 +30,23 @@ void wuss__furniture_invalidate_for(wuss_window_t *window, const box_t *visible) wuss__invalidate_clipped(window, &titlebar); } - if (!(window->flags & wuss_WINDOW_NO_VSCROLL)) + if (carve.x > 0) { box_t column; column.x1 = visible->x1 - outline_px; - column.x0 = column.x1 - wuss__icon_size(window); + column.x0 = column.x1 - carve.x; /* includes the interior rule */ column.y0 = visible->y0; column.y1 = visible->y1; wuss__invalidate_clipped(window, &column); } - if (!(window->flags & wuss_WINDOW_NO_HSCROLL)) + if (carve.y > 0) { box_t row; row.y1 = visible->y1 - outline_px; - row.y0 = row.y1 - wuss__icon_size(window); + row.y0 = row.y1 - carve.y; /* includes the interior rule */ row.x0 = visible->x0; row.x1 = visible->x1; wuss__invalidate_clipped(window, &row); diff --git a/libraries/wuss/furniture/ops.c b/libraries/wuss/furniture/ops.c new file mode 100644 index 00000000..081d788b --- /dev/null +++ b/libraries/wuss/furniture/ops.c @@ -0,0 +1,16 @@ +/* wuss/furniture/ops.c -- wuss - core-to-furniture dispatch table */ + +#include "../core/impl.h" + +/* The built-in furniture: wuss_create points every window's furniture_ops + * here unless a caller overrides it. */ +const wuss__furniture_ops_t wuss__furniture_default_ops = +{ + wuss__furniture_hit_test, + wuss__furniture_draw, + wuss__furniture_invalidate, + wuss__furniture_invalidate_for, + wuss__furniture_toggle_size, + wuss__furniture_drag_resize, + wuss__furniture_drag_sausage +}; diff --git a/libraries/wuss/furniture/resize-box.c b/libraries/wuss/furniture/resize-box.c index 0a9707b9..a55aba86 100644 --- a/libraries/wuss/furniture/resize-box.c +++ b/libraries/wuss/furniture/resize-box.c @@ -1,13 +1,13 @@ -/* resize-box.c -- wuss - minimal window manager */ +/* wuss/furniture/resize-box.c -- wuss - minimal window manager */ -#include "../impl.h" +#include "../core/impl.h" void wuss__resize_box(const wuss_window_t *window, box_t *out) { int outline_px, size; outline_px = wuss__outline_px(window); - size = wuss__icon_size(window); + size = wuss__button_size(window); out->x1 = window->visible.x1 - outline_px; out->x0 = out->x1 - size; diff --git a/libraries/wuss/furniture/scroll-action.c b/libraries/wuss/furniture/scroll-action.c index 9dbeaf7a..be92019a 100644 --- a/libraries/wuss/furniture/scroll-action.c +++ b/libraries/wuss/furniture/scroll-action.c @@ -1,44 +1,6 @@ -/* scroll-action.c -- wuss - minimal window manager */ +/* wuss/furniture/scroll-action.c -- wuss - minimal window manager */ -#include "../impl.h" - -point_t wuss__scroll_clamp(const wuss_window_t *window, point_t desired) -{ - box_t content; - int max_x, max_y; - - wuss__content_box(window, &content); - - max_x = window->doc_width - (content.x1 - content.x0); - max_y = window->doc_height - (content.y1 - content.y0); - if (max_x < 0) - max_x = 0; - if (max_y < 0) - max_y = 0; - - if (desired.x < 0) - desired.x = 0; - else if (desired.x > max_x) - desired.x = max_x; - if (desired.y < 0) - desired.y = 0; - else if (desired.y > max_y) - desired.y = max_y; - - return desired; -} - -void wuss__furniture_scroll_step(wuss_window_t *window, point_t delta) -{ - point_t scroll; - - wuss_window_get_scroll(window, &scroll); - scroll.x += delta.x; - scroll.y += delta.y; - scroll = wuss__scroll_clamp(window, scroll); - - wuss_window_set_scroll(window, scroll); -} +#include "../core/impl.h" void wuss__furniture_drag_sausage(wuss_window_t *window, int delta_px, @@ -46,7 +8,7 @@ void wuss__furniture_drag_sausage(wuss_window_t *window, int horizontal) { box_t content; - int well_px, content_size, doc_size, max_scroll, new_scroll; + int well_px, track_px, end_gap, content_size, doc_size, max_scroll, new_scroll; wuss__content_box(window, &content); @@ -54,31 +16,31 @@ void wuss__furniture_drag_sausage(wuss_window_t *window, { well_px = wuss__hscroll_well_px(window); content_size = content.x1 - content.x0; - doc_size = window->doc_width; + doc_size = window->doc.w; } else { well_px = wuss__vscroll_well_px(window); content_size = content.y1 - content.y0; - doc_size = window->doc_height; + doc_size = window->doc.h; } - max_scroll = doc_size - content_size; - if (max_scroll < 0) - max_scroll = 0; + /* Sausage travels over the well minus the cosmetic end gaps; must match + * wuss__*scroll_sausage_box so drag tracks the pointer 1:1. */ + end_gap = (well_px > 2 * WUSS_SCROLL_END_GAP + WUSS_MIN_SAUSAGE) ? WUSS_SCROLL_END_GAP : 0; + track_px = well_px - 2 * end_gap; - if (well_px <= 0 || doc_size <= content_size) + max_scroll = MAX(doc_size - content_size, 0); + + if (track_px <= 0 || doc_size <= content_size) new_scroll = 0; else - new_scroll = scroll_start + delta_px * doc_size / well_px; + new_scroll = scroll_start + delta_px * doc_size / track_px; - if (new_scroll < 0) - new_scroll = 0; - else if (new_scroll > max_scroll) - new_scroll = max_scroll; + new_scroll = CLAMP(new_scroll, 0, max_scroll); if (horizontal) - wuss_window_set_scroll(window, (point_t) { new_scroll, window->scroll.y }); + wuss_window_set_scroll(window, POINT(new_scroll, window->scroll.y)); else - wuss_window_set_scroll(window, (point_t) { window->scroll.x, new_scroll }); + wuss_window_set_scroll(window, POINT(window->scroll.x, new_scroll)); } diff --git a/libraries/wuss/furniture/scroll-box.c b/libraries/wuss/furniture/scroll-box.c new file mode 100644 index 00000000..42aaf39b --- /dev/null +++ b/libraries/wuss/furniture/scroll-box.c @@ -0,0 +1,238 @@ +/* wuss/furniture/scroll-box.c -- wuss - minimal window manager */ + +/* The vertical and horizontal scrollbars are the same layout transposed: a + * fixed-breadth strip down the right / along the bottom of the visible box, + * an arrow at each end and the well between them, with a proportional sausage + * in the well. Everything here is written once against a "horizontal" flag; + * the ten public wuss__vscroll_ / wuss__hscroll_ entry points are thin + * wrappers that pin the flag. */ + +#include "../core/impl.h" + +/* The scrollbar strip: full breadth, running between the titlebar (v) or left + * outline (h) and the resize corner. "long" axis is y when vertical, x when + * horizontal. */ +static void scroll_strip(const wuss_window_t *window, + int horizontal, + box_t *out) +{ + box_t titlebar; + int outline_px, size; + + outline_px = wuss__outline_px(window); + size = wuss__button_size(window); + + if (horizontal) + { + out->y1 = window->visible.y1 - outline_px; + out->y0 = out->y1 - size; + out->x0 = window->visible.x0 + outline_px; + out->x1 = window->visible.x1 - outline_px - size; /* clear of resize corner */ + } + else + { + out->x1 = window->visible.x1 - outline_px; + out->x0 = out->x1 - size; + wuss__titlebar_box(window, &titlebar); + out->y0 = titlebar.y1; + out->y1 = window->visible.y1 - outline_px - size; /* clear of resize corner */ + } +} + +/* One "size"-long slice off an end of "strip" along its long axis: the near + * end (up / left arrow) when far_end is 0, the far end (down / right arrow) + * when far_end is 1. The cross axis spans the whole strip. */ +static void scroll_end_slice(const box_t *strip, + int horizontal, + int far_end, + int size, + box_t *out) +{ + *out = *strip; + + if (horizontal) + { + if (far_end) + out->x0 = strip->x1 - size; + else + out->x1 = strip->x0 + size; + } + else + { + if (far_end) + out->y0 = strip->y1 - size; + else + out->y1 = strip->y0 + size; + } +} + +/* The well: "strip" with a "size"-long arrow slice removed from each end. */ +static void scroll_middle_slice(const box_t *strip, + int horizontal, + int size, + box_t *out) +{ + *out = *strip; + + if (horizontal) + { + out->x0 = strip->x0 + size; + out->x1 = strip->x1 - size; + } + else + { + out->y0 = strip->y0 + size; + out->y1 = strip->y1 - size; + } +} + +/* The well as a standalone call: strip, then middle slice, without the + * caller threading "size" through. */ +static void scroll_well(const wuss_window_t *window, + int horizontal, + box_t *out) +{ + box_t strip; + + scroll_strip(window, horizontal, &strip); + scroll_middle_slice(&strip, horizontal, wuss__button_size(window), out); +} + +/* The sausage: a proportional handle in the well, offset by the scroll + * position. Must track wuss__furniture_drag_sausage's maths so a drag follows + * the pointer 1:1. */ +static void scroll_sausage(const wuss_window_t *window, + int horizontal, + box_t *out) +{ + box_t well, content; + int well_px, track_px, end_gap, content_size, doc_size; + int sausage_px, sausage_lo, inset, scroll; + + scroll_well(window, horizontal, &well); + wuss__content_box(window, &content); + + if (horizontal) + { + well_px = well.x1 - well.x0; + content_size = content.x1 - content.x0; + doc_size = window->doc.w; + } + else + { + well_px = well.y1 - well.y0; + content_size = content.y1 - content.y0; + doc_size = window->doc.h; + } + + /* Leave a cosmetic gap at each end of the well; drop it if the well is + * too short to spare two gaps plus a minimum sausage. */ + end_gap = (well_px > 2 * WUSS_SCROLL_END_GAP + WUSS_MIN_SAUSAGE) ? WUSS_SCROLL_END_GAP : 0; + track_px = well_px - 2 * end_gap; + + doc_size = MAX(doc_size, content_size); + + sausage_px = (doc_size > 0) ? track_px * content_size / doc_size : track_px; + sausage_px = CLAMP(sausage_px, WUSS_MIN_SAUSAGE, track_px); + + scroll = horizontal ? window->scroll.x : window->scroll.y; + + if (doc_size > content_size && track_px > sausage_px) + sausage_lo = end_gap + (track_px - sausage_px) * scroll / (doc_size - content_size); + else + sausage_lo = end_gap; + + if (horizontal) + { + /* Inset the sausage from the well's long edges, but only while that + * leaves something to draw: a tiny titlebar font can make the well + * narrower than two insets, which would invert the box. */ + inset = (well.y1 - well.y0 > 2 * WUSS_SCROLL_INSET) ? WUSS_SCROLL_INSET : 0; + out->y0 = well.y0 + inset; + out->y1 = well.y1 - inset; + out->x0 = well.x0 + sausage_lo; + out->x1 = out->x0 + sausage_px; + } + else + { + inset = (well.x1 - well.x0 > 2 * WUSS_SCROLL_INSET) ? WUSS_SCROLL_INSET : 0; + out->x0 = well.x0 + inset; + out->x1 = well.x1 - inset; + out->y0 = well.y0 + sausage_lo; + out->y1 = out->y0 + sausage_px; + } +} + +/* ----- vertical scrollbar ------------------------------------------------ */ + +void wuss__vscroll_up_box(const wuss_window_t *window, box_t *out) +{ + box_t strip; + + scroll_strip(window, 0, &strip); + scroll_end_slice(&strip, 0, 0, wuss__button_size(window), out); +} + +void wuss__vscroll_down_box(const wuss_window_t *window, box_t *out) +{ + box_t strip; + + scroll_strip(window, 0, &strip); + scroll_end_slice(&strip, 0, 1, wuss__button_size(window), out); +} + +void wuss__vscroll_well_box(const wuss_window_t *window, box_t *out) +{ + scroll_well(window, 0, out); +} + +int wuss__vscroll_well_px(const wuss_window_t *window) +{ + box_t well; + + wuss__vscroll_well_box(window, &well); + + return well.y1 - well.y0; +} + +void wuss__vscroll_sausage_box(const wuss_window_t *window, box_t *out) +{ + scroll_sausage(window, 0, out); +} + +/* ----- horizontal scrollbar -------------------------------------------- */ + +void wuss__hscroll_left_box(const wuss_window_t *window, box_t *out) +{ + box_t strip; + + scroll_strip(window, 1, &strip); + scroll_end_slice(&strip, 1, 0, wuss__button_size(window), out); +} + +void wuss__hscroll_right_box(const wuss_window_t *window, box_t *out) +{ + box_t strip; + + scroll_strip(window, 1, &strip); + scroll_end_slice(&strip, 1, 1, wuss__button_size(window), out); +} + +void wuss__hscroll_well_box(const wuss_window_t *window, box_t *out) +{ + scroll_well(window, 1, out); +} + +int wuss__hscroll_well_px(const wuss_window_t *window) +{ + box_t well; + + wuss__hscroll_well_box(window, &well); + + return well.x1 - well.x0; +} + +void wuss__hscroll_sausage_box(const wuss_window_t *window, box_t *out) +{ + scroll_sausage(window, 1, out); +} diff --git a/libraries/wuss/furniture/titlebar-box.c b/libraries/wuss/furniture/titlebar-box.c index 52a9dade..6a620b6c 100644 --- a/libraries/wuss/furniture/titlebar-box.c +++ b/libraries/wuss/furniture/titlebar-box.c @@ -1,6 +1,6 @@ -/* titlebar-box.c -- wuss - minimal window manager */ +/* wuss/furniture/titlebar-box.c -- wuss - minimal window manager */ -#include "../impl.h" +#include "../core/impl.h" void wuss__titlebar_box(const wuss_window_t *window, box_t *out) { diff --git a/libraries/wuss/furniture/toggle-action.c b/libraries/wuss/furniture/toggle-action.c index 376d59f6..d7ee6572 100644 --- a/libraries/wuss/furniture/toggle-action.c +++ b/libraries/wuss/furniture/toggle-action.c @@ -1,60 +1,73 @@ -/* toggle-action.c -- wuss - minimal window manager */ +/* wuss/furniture/toggle-action.c -- wuss - minimal window manager */ #include "base/utils.h" -#include "../impl.h" +#include "../core/impl.h" void wuss__furniture_toggle_size(wuss_window_t *window) { - box_t before, new_visible, dirty, copied; + box_t before, before_content, new_visible, dirty, copied, blit_src, blit_dst; + int dx, dy, blitted, scroll_reclamped; before = window->visible; + wuss__content_box(window, &before_content); + scroll_reclamped = 0; - if (window->toggled) + if (wuss__window_toggled(window)) { new_visible = window->pre_toggle; } else { - int outline_px, titlebar_height, available_width, available_height, width, height; - point_t carve; + int outline_px, titlebar_height, target_w, target_h, vis_w, vis_h, x0, y0; + point_t carve; + size2d_t min, screen_max; outline_px = wuss__outline_px(window); titlebar_height = wuss__titlebar_height(window); - wuss__furniture_carve_for(window->flags, wuss__icon_size(window), &carve); - - /* bounded by what's actually left of the screen from the window's - * current top-left, not the screen's full width/height -- otherwise a - * window not already at the origin grows straight off the edge. Also - * account for scrollbar/resize-icon furniture (carve), which sits - * outside the content area same as create.c/resize.c do, or the window - * ends up carve.x/carve.y short of the doc size it's meant to reach. */ - available_width = window->wuss->scr->width - window->visible.x0 - 2 * outline_px - carve.x; - available_height = window->wuss->scr->height - window->visible.y0 - 2 * outline_px - titlebar_height - carve.y; - - /* a window dragged far enough off-screen leaves no room at all to the - * screen edge, so the above can go negative -- floor it like - * drag-resize.c does, or new_visible ends up with x1/y1 less than - * x0/y0 (an inverted box), which is never hit-testable again - * (box_contains_point can't match x1doc_width, available_width); - height = MIN(window->doc_height, available_height); + wuss__furniture_carve_for(window->flags, wuss__button_size(window), &carve); + + /* Toggle grows the window to the whole screen (not just the space left + * from its current top-left), capped per axis at the window's own doc + * extent -- a doc of 0 on an axis means "no cap", fill the screen. + * screen_max already accounts for outline/titlebar/scrollbar furniture + * sitting outside the content area. */ + wuss__max_content_anywhere_on_screen(window, &screen_max); + + target_w = window->doc.w ? MIN(window->doc.w, screen_max.w) : screen_max.w; + target_h = window->doc.h ? MIN(window->doc.h, screen_max.h) : screen_max.h; + + /* a window with a tiny doc still needs to end up big enough to grab; + * floor the target like drag-resize.c does, or the toggled box could be + * smaller than WUSS_MIN_CONTENT and awkward to un-toggle. */ + wuss__min_content(window, &min); + target_w = MAX(target_w, min.w); + target_h = MAX(target_h, min.h); + + vis_w = target_w + 2 * outline_px + carve.x; + vis_h = target_h + titlebar_height + 2 * outline_px + carve.y; + + /* nudge the top-left toward the origin by the minimum needed to fit the + * grown box on screen; a window that already fits stays put. */ + x0 = window->visible.x0; + y0 = window->visible.y0; + if (x0 + vis_w > window->wuss->scr->size.w) + x0 = window->wuss->scr->size.w - vis_w; + if (y0 + vis_h > window->wuss->scr->size.h) + y0 = window->wuss->scr->size.h - vis_h; + x0 = MAX(x0, 0); + y0 = MAX(y0, 0); window->pre_toggle = window->visible; - new_visible.x0 = window->visible.x0; - new_visible.y0 = window->visible.y0; - new_visible.x1 = new_visible.x0 + width + 2 * outline_px + carve.x; - new_visible.y1 = new_visible.y0 + height + titlebar_height + 2 * outline_px + carve.y; + new_visible.x0 = x0; + new_visible.y0 = y0; + new_visible.x1 = x0 + vis_w; + new_visible.y1 = y0 + vis_h; } window->visible = new_visible; - window->toggled = !window->toggled; + wuss__window_set_toggled(window, !wuss__window_toggled(window)); { point_t new_scroll; @@ -77,42 +90,69 @@ void wuss__furniture_toggle_size(wuss_window_t *window) window->scroll = new_scroll; wuss__content_box(window, &content); wuss__invalidate_clipped(window, &content); + scroll_reclamped = 1; } } - if (!(window->flags & wuss_WINDOW_NO_TOGGLE_BLIT) && - window->wuss->z_order.next == &window->link && - screen_copy_rect(window->wuss->scr, &before, - (point_t) { before.x0, before.y0 }, &copied)) + /* Only the *content* pixels are anchored-position rendering that can just + * be slid; the furniture (titlebar, outline, scrollbars) reflows to the + * window's new size and position, so it must never be part of the blit -- + * blitting it would drag a stale titlebar/scrollbar strip into what is + * now interior content. Blit the old content box to where it now sits + * (shifted by the top-left delta; dx==dy==0 for a pure grow-in-place), + * then repaint the newly-exposed content, the vacated region and all the + * furniture. Topmost only (nothing above it, so no occluder pixels to + * preserve or step on) and only when the screen format can blit. Skipped + * when the toggle re-clamped scroll: the whole new content box is already + * invalidated above at the corrected offset. */ + dx = window->visible.x0 - before.x0; + dy = window->visible.y0 - before.y0; + blitted = 0; + + if (!scroll_reclamped && + !(window->flags & wuss_WINDOW_NO_RESIZE_BLIT) && + window->wuss->z_order.next == &window->link) { - /* Topmost, and the screen format supports the blit: the window's - * top-left never moves for a toggle, so re-blitting "before" onto - * itself is a no-op that just confirms which of its pixels are still - * on-screen -- only whatever's newly exposed (grown) or newly vacated - * (shrunk) relative to that needs an actual repaint. */ - wuss__invalidate_minus(window->wuss, &before, &window->visible); - wuss__invalidate_minus(window->wuss, &window->visible, &copied); - - /* Unlike a move, a toggle changes the window's size, so furniture that - * lays itself out relative to that size (titlebar icons anchored to its - * right edge, scrollbar well/sausage proportions, the resize corner) - * reflows even where the blit reused valid content pixels -- e.g. the - * old toggle icon location is now mid-titlebar, not redrawn by either - * invalidate_minus above since it falls inside both "before" and the - * new "visible". Force it dirty regardless of the blit. */ - if (window->visible.x1 - window->visible.x0 > before.x1 - before.x0 || - window->visible.y1 - window->visible.y0 > before.y1 - before.y0) - /* Growing strands old furniture (e.g. the old vscroll column) inside - * what's now interior content, a region the blit above treats as - * already-valid and so never repaints -- force its old position dirty - * too. Shrinking needs no such help: old furniture positions only - * ever land outside the new, smaller box, already covered by the - * invalidate_minus vacated-region call above. */ - wuss__furniture_invalidate_for(window, &before); + box_t new_content, shifted; + + wuss__content_box(window, &new_content); + + /* the part of the old content box that, slid by (dx,dy), lands within + * the new content box: its pixels are the valid source for that overlap */ + box_translated(&new_content, -dx, -dy, &shifted); + if (box_intersection(&before_content, &shifted, &blit_src) == 0 && + !box_is_empty(&blit_src)) + { + box_translated(&blit_src, dx, dy, &blit_dst); + if (screen_copy_rect(window->wuss->scr, &blit_src, + POINT(blit_dst.x0, blit_dst.y0), + &copied) == result_OK) + blitted = 1; + } + } + + if (blitted) + { + box_t new_content; + + wuss__content_box(window, &new_content); + + /* the old footprint minus the content pixels the blit left valid at + * their new home: the vacated region plus every furniture strip. */ + wuss__invalidate_minus(window->wuss, &before, &copied); + /* the new content box minus what the blit filled: newly-exposed content. */ + wuss__invalidate_minus(window->wuss, &new_content, &copied); + /* furniture always reflows -- repaint it at the new position outright. */ wuss__furniture_invalidate(window); } else { + /* No blit: the window is not topmost, its content self-lays-out + * (NO_RESIZE_BLIT), the old and new content boxes do not overlap, or + * screen_copy_rect declined. Repaint the whole union of old and new + * footprint. */ + logf_info("wuss furniture toggle: no blit fast path, repainting the whole " + "old+new footprint"); box_union(&before, &window->visible, &dirty); wuss__invalidate_clipped(window, &dirty); } diff --git a/libraries/wuss/furniture/toggle-box.c b/libraries/wuss/furniture/toggle-box.c index 8f7ee7d3..326ecf67 100644 --- a/libraries/wuss/furniture/toggle-box.c +++ b/libraries/wuss/furniture/toggle-box.c @@ -1,6 +1,6 @@ -/* toggle-box.c -- wuss - minimal window manager */ +/* wuss/furniture/toggle-box.c -- wuss - minimal window manager */ -#include "../impl.h" +#include "../core/impl.h" void wuss__toggle_box(const wuss_window_t *window, box_t *out) { @@ -9,8 +9,8 @@ void wuss__toggle_box(const wuss_window_t *window, box_t *out) wuss__titlebar_box(window, &titlebar); - inset = WUSS_ICON_INSET; - size = wuss__icon_size(window); + inset = WUSS_BUTTON_INSET; + size = wuss__button_size(window); out->x1 = titlebar.x1 - inset; out->x0 = out->x1 - size; diff --git a/libraries/wuss/furniture/vscroll-box.c b/libraries/wuss/furniture/vscroll-box.c deleted file mode 100644 index ae5f9d0b..00000000 --- a/libraries/wuss/furniture/vscroll-box.c +++ /dev/null @@ -1,101 +0,0 @@ -/* vscroll-box.c -- wuss - minimal window manager */ - -#include "../impl.h" - -static void vscroll_column(const wuss_window_t *window, box_t *out) -{ - box_t titlebar; - int outline_px, size; - - outline_px = wuss__outline_px(window); - size = wuss__icon_size(window); - - out->x1 = window->visible.x1 - outline_px; - out->x0 = out->x1 - size; - - wuss__titlebar_box(window, &titlebar); - out->y0 = titlebar.y1; - out->y1 = window->visible.y1 - outline_px - size; /* stop above the resize corner */ -} - -void wuss__vscroll_up_box(const wuss_window_t *window, box_t *out) -{ - box_t column; - int size; - - vscroll_column(window, &column); - size = wuss__icon_size(window); - - out->x0 = column.x0; - out->x1 = column.x1; - out->y0 = column.y0; - out->y1 = column.y0 + size; -} - -void wuss__vscroll_down_box(const wuss_window_t *window, box_t *out) -{ - box_t column; - int size; - - vscroll_column(window, &column); - size = wuss__icon_size(window); - - out->x0 = column.x0; - out->x1 = column.x1; - out->y1 = column.y1; - out->y0 = column.y1 - size; -} - -void wuss__vscroll_well_box(const wuss_window_t *window, box_t *out) -{ - box_t column; - int size; - - vscroll_column(window, &column); - size = wuss__icon_size(window); - - out->x0 = column.x0; - out->x1 = column.x1; - out->y0 = column.y0 + size; - out->y1 = column.y1 - size; -} - -int wuss__vscroll_well_px(const wuss_window_t *window) -{ - box_t well; - - wuss__vscroll_well_box(window, &well); - - return well.y1 - well.y0; -} - -void wuss__vscroll_sausage_box(const wuss_window_t *window, box_t *out) -{ - box_t well, content; - int well_px, content_size, doc_size, sausage_px, sausage_y0; - - wuss__vscroll_well_box(window, &well); - wuss__content_box(window, &content); - - well_px = well.y1 - well.y0; - content_size = content.y1 - content.y0; - doc_size = window->doc_height; - if (doc_size < content_size) - doc_size = content_size; - - sausage_px = (doc_size > 0) ? well_px * content_size / doc_size : well_px; - if (sausage_px < WUSS_MIN_SAUSAGE) - sausage_px = WUSS_MIN_SAUSAGE; - if (sausage_px > well_px) - sausage_px = well_px; - - if (doc_size > content_size && well_px > sausage_px) - sausage_y0 = well.y0 + (well_px - sausage_px) * window->scroll.y / (doc_size - content_size); - else - sausage_y0 = well.y0; - - out->x0 = well.x0; - out->x1 = well.x1; - out->y0 = sausage_y0; - out->y1 = sausage_y0 + sausage_px; -} diff --git a/libraries/wuss/helpers/create-from-desc.c b/libraries/wuss/helpers/create-from-desc.c new file mode 100644 index 00000000..85ac41d7 --- /dev/null +++ b/libraries/wuss/helpers/create-from-desc.c @@ -0,0 +1,516 @@ +/* wuss/helpers/create-from-desc.c -- build a menu tree from a descriptor string */ + +/* The token machine (Token / Parser / getopt / getname / getnext / isdelim) + * and the explicit menu stack are ported from PrivateEye's + * libs/appengine/wimp/menu/create-from-desc.c. What changed for wuss: the + * output is a heap wuss_menu_t tree rather than a wimp_menu, there is no title + * row (the first token of a { } block is parsed then discarded, so descriptor + * strings stay source-compatible), and a '>' submenu is deep-copied from the + * const wuss_menu_t * vararg so the whole result tree is owned uniformly and + * freed by one wuss_menu_destroy. */ + +#include +#include +#include +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/result.h" +#include "utils/array.h" + +#include "wuss/menu-desc.h" + +/* ----------------------------------------------------------------------- */ + +enum { WUSS_MENU_DESC_DEPTH = 8 }; /* nested { } limit, as the Wimp original */ +enum { WUSS_MENU_DESC_TEXT = 64 }; /* per-token expansion buffer */ + +/* ----------------------------------------------------------------------- */ + +typedef enum { Name, Sep, DashSep, Push, Pop, End, Error } Token; + +enum { Tick = 1 << 0, Shade = 1 << 1, SubMenu = 1 << 2 }; + +typedef struct +{ + const char *p; + const char *start, *end; /* end exclusive */ + unsigned int flags; +} +Parser; + +static void getopt_(Parser *parser) +{ + const char *p; + int c; + + parser->flags = 0; + p = parser->p; + + for (;;) + { + c = *p; + if (c == '!') parser->flags |= Tick; + else if (c == '>') parser->flags |= SubMenu; + else if (c == '~') parser->flags |= Shade; + else { parser->p = p; return; } + p++; + } +} + +static int isdelim(int c) +{ + return c == ',' || c == '|' || c == '{' || c == '}' || c == '\0'; +} + +static void getname(Parser *parser) +{ + const char *p; + + getopt_(parser); + p = parser->p; + parser->start = p; + + while (!isdelim(*p)) + p++; + + parser->p = p; + + /* trim trailing whitespace so "Foo { " yields the name "Foo", not "Foo " */ + while (p > parser->start && isspace((unsigned char) p[-1])) + p--; + + parser->end = p; +} + +static Token getnext(Parser *parser) +{ + while (isspace((unsigned char) *parser->p)) + parser->p++; + + if (!isdelim(*parser->p)) + { + getname(parser); + if (parser->start == parser->end) + return Error; + return Name; + } + + switch (*parser->p++) + { + case ',': return Sep; + case '|': return DashSep; + case '{': return Push; + case '}': return Pop; + } + + assert(parser->p[-1] == '\0'); + return End; +} + +/* ----------------------------------------------------------------------- */ + +void wuss_menu_destroy(wuss_menu_t *menu) +{ + int i; + + if (menu == NULL) + return; + + for (i = 0; i < menu->nitems; i++) + { + free((void *) menu->items[i].text); /* discard const */ + wuss_menu_destroy((wuss_menu_t *) menu->items[i].submenu); + } + free((void *) menu->title); /* discard const */ + free((void *) menu->items); /* discard const */ + free(menu); +} + +/* Deep-copy a caller-supplied menu tree so a '>' submenu can be owned by, and + * freed with, the result of wuss_menu_create_from_desc. */ +static result_t menu_deep_copy(const wuss_menu_t *src, wuss_menu_t **out) +{ + wuss_menu_t *m; + wuss_menu_item_t *items; + int i; + result_t rc; + + m = malloc(sizeof(*m)); + if (m == NULL) + return result_OOM; + + items = (src->nitems > 0) + ? calloc((size_t) src->nitems, sizeof(*items)) + : NULL; + if (src->nitems > 0 && items == NULL) + { + free(m); + return result_OOM; + } + + m->title = src->title ? strdup(src->title) : NULL; + if (src->title != NULL && m->title == NULL) + { + free(items); + free(m); + return result_OOM; + } + + m->items = items; + m->nitems = src->nitems; + + for (i = 0; i < src->nitems; i++) + { + items[i].text = strdup(src->items[i].text ? src->items[i].text : ""); + items[i].flags = src->items[i].flags; + items[i].submenu = NULL; + items[i].window = src->items[i].window; /* borrowed, as in the source */ + items[i].swatch = src->items[i].swatch; + + if (items[i].text == NULL) + { + m->nitems = i; /* only [0, i) are populated -- keeps destroy correct */ + wuss_menu_destroy(m); + return result_OOM; + } + + if (src->items[i].submenu != NULL) + { + wuss_menu_t *child; + + rc = menu_deep_copy(src->items[i].submenu, &child); + if (rc != result_OK) + { + m->nitems = i + 1; + wuss_menu_destroy(m); + return rc; + } + items[i].submenu = child; + } + } + + *out = m; + return result_OK; +} + +/* ----------------------------------------------------------------------- */ + +/* A menu under construction: a growable wuss_menu_item_t array plus the + * pending '|' dash flag owed to the next-added item. */ +typedef struct +{ + wuss_menu_item_t *items; + int n; + int cap; + int pending_dash; +} +Building; + +static result_t build_emit(Building *b, + const char *text, + unsigned int flags, + wuss_menu_t *submenu) +{ + wuss_menu_item_t *it; + char *copy; + + if (array_grow((void **) &b->items, sizeof(*b->items), + b->n, &b->cap, 1, 4)) + return result_OOM; + + copy = strdup(text ? text : ""); + if (copy == NULL) + return result_OOM; + + it = &b->items[b->n++]; + it->text = copy; + it->flags = flags; + it->submenu = submenu; + it->window = NULL; /* array_grow leaves these uninitialised otherwise */ + it->swatch = wuss_NO_BACKGROUND; + return result_OK; +} + +static result_t build_add(Building *b, + const char *text, + unsigned int opt, + wuss_menu_t *submenu) +{ + unsigned int flags; + + flags = wuss_MENU_ITEM_NONE; + + /* '|' before this item draws a dashed rule above it: set DASHED on the item + * itself, which stays an ordinary interactive row. */ + if (b->pending_dash) + { + b->pending_dash = 0; + flags |= wuss_MENU_ITEM_DASHED; + } + + if (opt & Tick) + flags |= wuss_MENU_ITEM_TICKED; + if (opt & Shade) + flags |= wuss_MENU_ITEM_DISABLED; + + return build_emit(b, text, flags, submenu); +} + +/* Seal a Building into a heap wuss_menu_t, transferring its item array. */ +static wuss_menu_t *build_seal(Building *b) +{ + wuss_menu_t *m; + + m = malloc(sizeof(*m)); + if (m == NULL) + return NULL; + + /* ponytail: the descriptor's { } title token is still discarded, so a + * parser-built menu has no caption. Wire it through Building if that + * changes. */ + m->title = NULL; + m->items = b->items; + m->nitems = b->n; + + b->items = NULL; + b->n = b->cap = 0; + + return m; +} + +/* Free an unsealed Building (error path): everything in it is ours. */ +static void build_discard(Building *b) +{ + int i; + + for (i = 0; i < b->n; i++) + { + free((void *) b->items[i].text); /* discard const */ + wuss_menu_destroy((wuss_menu_t *) b->items[i].submenu); + } + free(b->items); + b->items = NULL; + b->n = b->cap = 0; +} + +/* ----------------------------------------------------------------------- */ + +result_t wuss_menu_create_from_desc(wuss_menu_t **out, const char *desc, ...) +{ + Parser parser; + Building stack[WUSS_MENU_DESC_DEPTH]; + int need_title[WUSS_MENU_DESC_DEPTH]; + char *titles[WUSS_MENU_DESC_DEPTH]; + int sp; + va_list ap; + result_t rc; + char text[WUSS_MENU_DESC_TEXT]; + int i; + + assert(out != NULL); + assert(desc != NULL); + + parser.p = desc; + + for (i = 0; i < WUSS_MENU_DESC_DEPTH; i++) + { + stack[i].items = NULL; + stack[i].n = stack[i].cap = stack[i].pending_dash = 0; + need_title[i] = 0; + titles[i] = NULL; + } + + /* the descriptor's first token is the root menu's title, exactly as the + * first token inside a { } is that submenu's -- so the same strings port + * from PrivateEye unchanged */ + need_title[0] = 1; + + sp = 0; + rc = result_OK; + + va_start(ap, desc); + + for (;;) + { + Token tok; + + tok = getnext(&parser); + + if (tok == End) + break; + + if (tok == Error) + { + rc = result_BAD_ARG; + break; + } + + if (tok == Sep) + continue; + + if (tok == DashSep) + { + stack[sp].pending_dash = 1; + continue; + } + + if (tok == Push) + { + if (sp + 1 >= WUSS_MENU_DESC_DEPTH) + { + rc = result_BAD_ARG; /* too deep */ + break; + } + sp++; + need_title[sp] = 1; /* first token inside { } is a title, discard it */ + continue; + } + + if (tok == Pop) + { + wuss_menu_t *sub; + + if (sp == 0) + { + rc = result_BAD_ARG; /* } without { */ + break; + } + + sub = build_seal(&stack[sp]); + if (sub == NULL) + { + rc = result_OOM; + break; + } + sp--; + + if (stack[sp].n == 0) + { + wuss_menu_destroy(sub); + rc = result_BAD_ARG; /* { } with no preceding item */ + break; + } + stack[sp].items[stack[sp].n - 1].submenu = sub; + continue; + } + + /* tok == Name */ + { + char *q; + const char *s; + unsigned int opt; + int overflow; + + opt = parser.flags; + q = text; + overflow = 0; + + for (s = parser.start; s != parser.end; s++) + { + if (*s == '%' && (s + 1) != parser.end && s[1] == 's') + { + const char *arg; + size_t len; + + arg = va_arg(ap, const char *); + len = strlen(arg); + if ((size_t) (q - text) + len >= sizeof(text)) + { + overflow = 1; + break; + } + memcpy(q, arg, len); + q += len; + s++; + } + else + { + if ((size_t) (q - text) + 1 >= sizeof(text)) + { + overflow = 1; + break; + } + *q++ = *s; + } + } + + if (overflow) + { + rc = result_BAD_ARG; + break; + } + *q = '\0'; + + if (need_title[sp]) + { + /* the menu's title token -- captured, not emitted as an item; kept + * only for the root (sp 0), submenus get no caption */ + need_title[sp] = 0; + if (sp == 0) + { + titles[0] = strdup(text); + if (titles[0] == NULL) + { + rc = result_OOM; + break; + } + } + continue; + } + + { + wuss_menu_t *submenu; + + submenu = NULL; + if (opt & SubMenu) + { + const wuss_menu_t *borrowed; + + borrowed = va_arg(ap, const wuss_menu_t *); + rc = menu_deep_copy(borrowed, &submenu); + if (rc != result_OK) + break; + } + + rc = build_add(&stack[sp], text, opt, submenu); + if (rc != result_OK) + { + wuss_menu_destroy(submenu); + break; + } + } + } + } + + va_end(ap); + + if (rc == result_OK && sp != 0) + rc = result_BAD_ARG; /* unclosed { */ + + if (rc != result_OK) + { + for (i = 0; i < WUSS_MENU_DESC_DEPTH; i++) + { + build_discard(&stack[i]); + free(titles[i]); + } + return rc; + } + + *out = build_seal(&stack[0]); + if (*out == NULL) + { + build_discard(&stack[0]); + free(titles[0]); + return result_OOM; + } + + (*out)->title = titles[0]; /* ownership transferred; NULL if none given */ + + return result_OK; +} diff --git a/libraries/wuss/icon.h b/libraries/wuss/icon.h new file mode 100644 index 00000000..ff67acc7 --- /dev/null +++ b/libraries/wuss/icon.h @@ -0,0 +1,116 @@ +/* wuss/icon.h -- work-area icons, internal */ + +#ifndef WUSS_ICON_IMPL_H +#define WUSS_ICON_IMPL_H + +#include "geom/box.h" +#include "geom/point.h" + +#include "framebuf/screen.h" + +#include "wuss/wuss.h" +#include "wuss/icon.h" + +/* Per-instance transient state, kept as bitflags with wuss__icon_* accessors + * (mirrors wuss_window_state_t) so more can be added without growing the + * struct. Not part of the public appearance API. */ +typedef enum wuss_icon_state +{ + wuss_ICON_STATE_NONE = 0, + wuss_ICON_STATE_PRESSED = 1 << 0, /* button: held with the pointer inside */ + wuss_ICON_STATE_SELECTED = 1 << 1, /* radio/option: latched on; menu entry: + * draw a tick */ + wuss_ICON_STATE_HOVERED = 1 << 2 /* menu entry: pointer is over it */ +} +wuss_icon_state_t; + +struct wuss_icon +{ + wuss_window_t *window; /* owner; back-pointer for invalidate/get_window */ + box_t bbox; /* virtual document space */ + wuss_icon_type_t type; + char *text; /* owned; never NULL ("" instead) */ + wuss_colour_t fg, bg; + screen_pattern_t pattern; /* wuss_ICON_TYPE_PATTERN tile; 0 otherwise */ + const bitmap_t *bitmap; /* wuss_ICON_TYPE_BITMAP image; borrowed, or NULL */ + int group; /* radio: exclusive-selection group; 0 = none */ + wuss_colour_t swatch; /* menu entry + FLAGS_SWATCH: left-gutter chip + * colour; wuss_NO_BACKGROUND otherwise */ + wuss_icon_flags_t flags; + wuss_icon_state_t state; +}; + +static inline int wuss__icon_pressed(const wuss_icon_t *icon) +{ + return (icon->state & wuss_ICON_STATE_PRESSED) != 0; +} + +static inline int wuss__icon_selected(const wuss_icon_t *icon) +{ + return (icon->state & wuss_ICON_STATE_SELECTED) != 0; +} + +static inline int wuss__icon_hovered(const wuss_icon_t *icon) +{ + return (icon->state & wuss_ICON_STATE_HOVERED) != 0; +} + +static inline void wuss__icon_set_state(wuss_icon_t *icon, + wuss_icon_state_t bit, + int on) +{ + if (on) + icon->state |= bit; + else + icon->state &= (wuss_icon_state_t) ~bit; +} + +/* Set icon->selected, invalidating it. For a radio with a non-zero group, + * selecting it also clears every other selected radio on the same window with + * that group. Ignored for types with no latched state. */ +void wuss__icon_select(wuss_icon_t *icon, int selected); + +/* Make "icon" (may be NULL) the hovered icon: clears the hovered flag on the + * previous wuss->hover_icon and sets it on the new one, invalidating whichever + * of the two changed. A no-op if nothing changed. */ +void wuss__icon_set_hover(wuss_t *wuss, wuss_icon_t *icon); + +/* Map an icon bbox (virtual document space) into screen space: + * screen = content.x0 - scroll.x + bbox. wuss__icon_draw paints through this + * so hit-testing and invalidation cannot drift from what is drawn. */ +void wuss__icon_box_to_screen(const box_t *content, + point_t scroll, + const box_t *bbox, + box_t *out); + +/* Invalidate exactly this icon's bbox, via wuss_window_invalidate, so a + * set_text / pressed-state / hide change repaints just the icon. */ +void wuss__icon_invalidate(const wuss_icon_t *icon); + +/* Validate a spec against the palette and fill "out" with a detached icon (no + * window, no owned text -- out->text is aliased to spec->text or ""). Shared + * by wuss_icon_create and wuss_icon_plot. Returns result_WUSS_BAD_ICON / + * result_WUSS_BAD_COLOUR as wuss_icon_create documents, else result_OK. */ +result_t wuss__icon_from_spec(const wuss_t *wuss, + const wuss_icon_spec_t *spec, + wuss_icon_t *out); + +/* Draw one icon. Called from redraw_window with wuss->scr->clip already set to + * the surviving content piece and the background already filled. "content" is + * the window's full (unclipped) content box, screen space; "scroll" is + * window->scroll. */ +void wuss__icon_draw(wuss_t *wuss, + const wuss_icon_t *icon, + const box_t *content, + point_t scroll); + +/* Hit-test every visible, enabled button icon of "window" against a point given + * in virtual document space. Returns the topmost (last-created wins) match, or + * NULL. Label, hidden and disabled icons are skipped. */ +wuss_icon_t *wuss__icon_hit_test(wuss_window_t *window, point_t doc_point); + +/* Free a window's whole icon store (text + nodes + array). Teardown only: does + * not invalidate or swap-remove. */ +void wuss__icons_free(wuss_window_t *window); + +#endif /* WUSS_ICON_IMPL_H */ diff --git a/libraries/wuss/icon/create-array.c b/libraries/wuss/icon/create-array.c new file mode 100644 index 00000000..879b4104 --- /dev/null +++ b/libraries/wuss/icon/create-array.c @@ -0,0 +1,43 @@ +/* wuss/icon/create-array.c -- create several work-area icons at once */ + +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "../core/impl.h" + +result_t wuss_icon_create_array(wuss_window_t *window, + const wuss_icon_spec_t *specs, + int nspecs, + wuss_icon_t **icons) +{ + result_t rc; + int i; + int j; + + assert(window != NULL); + assert(specs != NULL || nspecs == 0); + + for (i = 0; i < nspecs; i++) + { + wuss_icon_t *it; + + rc = wuss_icon_create(window, &specs[i], &it); + if (rc != result_OK) + { + /* all-or-nothing: unwind the icons this call already created. They are + * the last (i) entries on the window's icon list, newest last. */ + for (j = 0; j < i; j++) + wuss_icon_delete(window->icons[window->nicons - 1]); + return rc; + } + + if (icons != NULL) + icons[i] = it; + } + + return result_OK; +} diff --git a/libraries/wuss/icon/create.c b/libraries/wuss/icon/create.c new file mode 100644 index 00000000..aeac0f57 --- /dev/null +++ b/libraries/wuss/icon/create.c @@ -0,0 +1,70 @@ +/* wuss/icon/create.c -- create a work-area icon */ + +#include +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "../core/impl.h" + +result_t wuss_icon_create(wuss_window_t *window, + const wuss_icon_spec_t *spec, + wuss_icon_t **icon) +{ + wuss_t *w; + wuss_icon_t *it; + wuss_icon_t **grown; + wuss_icon_t scratch; + const char *src; + size_t len; + int newcap; + result_t rc; + + assert(window != NULL); + assert(spec != NULL); + + w = window->wuss; + + rc = wuss__icon_from_spec(w, spec, &scratch); + if (rc != result_OK) + return rc; + + if (window->nicons == window->cap_icons) + { + newcap = (window->cap_icons == 0) ? 4 : window->cap_icons * 2; + grown = wuss__realloc(w, window->icons, newcap * sizeof(*window->icons)); + if (grown == NULL) + return result_OOM; + window->icons = grown; + window->cap_icons = newcap; + } + + it = wuss__malloc(w, sizeof(*it)); + if (it == NULL) + return result_OOM; + + *it = scratch; /* scratch.text aliases spec->text; replaced with an owned copy below */ + it->window = window; + + src = (spec->text != NULL) ? spec->text : ""; + len = strlen(src); + it->text = wuss__malloc(w, len + 1); + if (it->text == NULL) + { + wuss__free(w, it); + return result_OOM; + } + memcpy(it->text, src, len + 1); + + window->icons[window->nicons++] = it; + + wuss__icon_invalidate(it); + + if (icon != NULL) + *icon = it; + + return result_OK; +} diff --git a/libraries/wuss/icon/delete.c b/libraries/wuss/icon/delete.c new file mode 100644 index 00000000..cc3f1be1 --- /dev/null +++ b/libraries/wuss/icon/delete.c @@ -0,0 +1,39 @@ +/* wuss/icon/delete.c -- destroy a work-area icon */ + +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "../core/impl.h" + +void wuss_icon_delete(wuss_icon_t *icon) +{ + wuss_window_t *window; + int i; + + if (icon == NULL) + return; + + window = icon->window; + + if (window->wuss->pressed_icon == icon) + window->wuss->pressed_icon = NULL; + if (window->wuss->hover_icon == icon) + window->wuss->hover_icon = NULL; + + wuss__icon_invalidate(icon); + + for (i = 0; i < window->nicons; i++) + { + if (window->icons[i] == icon) + { + window->icons[i] = window->icons[--window->nicons]; + break; + } + } + + wuss__free(window->wuss, icon->text); + wuss__free(window->wuss, icon); +} diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c new file mode 100644 index 00000000..c37850ff --- /dev/null +++ b/libraries/wuss/icon/draw.c @@ -0,0 +1,553 @@ +/* wuss/icon/draw.c -- draw a work-area icon */ + +#include + +#include "base/utils.h" +#include "geom/box.h" +#include "geom/point.h" +#include "geom/size.h" +#include "framebuf/bmfont.h" +#include "framebuf/screen.h" + +#include "../core/impl.h" + +/* ----------------------------------------------------------------------- */ + +/* Shared per-draw state, resolved once by wuss__icon_draw and handed to each + * type's draw helper. "b" is the icon's screen box; "fg" is its resolved + * foreground; "font" is the icon's selected weight (falling back to slot 0); + * "font_height" is 0 and "have_font" is 0 when there is no font or no text to + * draw. */ +typedef struct icon_draw_ctx +{ + wuss_t *wuss; + screen_t *scr; + const wuss_icon_t *icon; + bmfont_t *font; + box_t b; + colour_t fg; + int font_height; + int have_font; +} +icon_draw_ctx_t; + +/* ----------------------------------------------------------------------- */ + +static void icon_bevel(screen_t *scr, + const box_t *b, + colour_t fill, + colour_t light, + colour_t dark) +{ + screen_fill_rect(scr, b->x0, b->y0, + SIZE2D(b->x1 - b->x0, b->y1 - b->y0), fill); + + screen_draw_line(scr, b->x0, b->y0, b->x1 - 1, b->y0, light); + screen_draw_line(scr, b->x0, b->y0, b->x0, b->y1 - 1, light); + screen_draw_line(scr, b->x0, b->y1 - 1, b->x1 - 1, b->y1 - 1, dark); + screen_draw_line(scr, b->x1 - 1, b->y0, b->x1 - 1, b->y1 - 1, dark); +} + +/* Resolve the ground an icon's text/glyph blends against: an explicit icon bg, + * else the window's dominant backdrop colour (a pattern fill blends against its + * clear-bit colour, not its foreground), else "fallback" so bmfont still has a + * blend colour. */ +static colour_t icon_blend_ground(const icon_draw_ctx_t *c, + colour_t fallback) +{ + const wuss_icon_t *icon = c->icon; + + if (icon->bg != wuss_NO_BACKGROUND) + return c->wuss->palette[icon->bg]; + + if (icon->window->bg.colour != wuss_NO_BACKGROUND) + return (icon->window->bg.pattern != screen_PATTERN_SOLID) + ? c->wuss->palette[icon->window->bg.pattern_bg] + : c->wuss->palette[icon->window->bg.colour]; + + return fallback; +} + +/* Draw one menu-decoration glyph (a tick or a submenu arrow) from the symbol + * font slot, centred on "centre", blending "ink" over "ground". Returns 1 when + * the glyph was drawn, 0 when the slot is empty or the font lacks that cell -- + * the caller then falls back to its vector rendering. */ +static int wuss__draw_symbol_glyph(const wuss_t *wuss, + screen_t *scr, + point_t centre, + char glyph, + colour_t ink, + colour_t ground) +{ + bmfont_t *font; + point_t pos; + int font_width, font_height; + + font = wuss->fonts[WUSS_SYMBOL_FONT]; + if (font == NULL) + return 0; + + if ((unsigned char) glyph < ' ' || + (unsigned char) glyph - ' ' >= bmfont_get_count(font)) + return 0; + + bmfont_get_info(font, &font_width, &font_height); + pos.x = centre.x - font_width / 2; + pos.y = centre.y - font_height / 2; + bmfont_draw(font, scr, &glyph, 1, ink, ground, &pos, NULL); + return 1; +} + +/* ----------------------------------------------------------------------- */ + +static void wuss__icon_draw_pattern(const icon_draw_ctx_t *c, + const box_t *content, + point_t scroll) +{ + const wuss_icon_t *icon = c->icon; + colour_t pat_fg; + + /* disabled: fold the pattern into its own ground so it reads as greyed, + * mirroring the button's fg-swap */ + pat_fg = (icon->flags & wuss_ICON_FLAGS_DISABLED) + ? c->wuss->palette[icon->bg] + : c->fg; + + { + pattern_t pat; + + pat = pattern_from_preset(icon->pattern, + pat_fg, c->wuss->palette[icon->bg]); + pat.origin = POINT(content->x0 - scroll.x, content->y0 - scroll.y); + screen_fill_pattern(c->scr, &c->b, &pat); + } +} + +/* ----------------------------------------------------------------------- */ + +static void wuss__icon_draw_label(const icon_draw_ctx_t *c) +{ + const wuss_icon_t *icon = c->icon; + const box_t *b = &c->b; + colour_t bg; + point_t pos; + int interior_w, split_point; + bmfont_width_t width; + + if (icon->bg != wuss_NO_BACKGROUND) + { + bg = c->wuss->palette[icon->bg]; + screen_fill_rect(c->scr, b->x0, b->y0, + SIZE2D(b->x1 - b->x0, b->y1 - b->y0), bg); + } + else + { + bg = icon_blend_ground(c, c->fg); + } + + if (!c->have_font) + return; + + interior_w = MAX((b->x1 - b->x0) - 2, 1); + + bmfont_measure(c->font, icon->text, (int) strlen(icon->text), + interior_w, &split_point, &width); + + if (icon->flags & wuss_ICON_FLAGS_JUSTIFY_CENTRE) + pos.x = b->x0 + ((b->x1 - b->x0) - width) / 2; + else if (icon->flags & wuss_ICON_FLAGS_JUSTIFY_RIGHT) + pos.x = b->x1 - 1 - width; + else + pos.x = b->x0 + 1; + pos.y = b->y0 + (b->y1 - b->y0 - c->font_height) / 2; + + bmfont_draw(c->font, c->scr, icon->text, (int) strlen(icon->text), + c->fg, bg, &pos, NULL); +} + +/* ----------------------------------------------------------------------- */ + +static void wuss__icon_draw_frame(const icon_draw_ctx_t *c) +{ + const wuss_icon_t *icon = c->icon; + const box_t *b = &c->b; + colour_t bg; + int cap_w, cap_x, gap_x0, gap_x1, mid_y; + + bg = icon_blend_ground(c, c->fg); + + cap_w = 0; + if (c->have_font) + { + int split_point; + bmfont_width_t width; + + bmfont_measure(c->font, icon->text, (int) strlen(icon->text), + (b->x1 - b->x0) - WUSS_FRAME_CAPTION_INSET * 2, + &split_point, &width); + cap_w = width; + } + + mid_y = b->y0 + c->font_height / 2; + + /* left, right and bottom edges are unbroken */ + screen_draw_line(c->scr, b->x0, mid_y, b->x0, b->y1 - 1, c->fg); + screen_draw_line(c->scr, b->x1 - 1, mid_y, b->x1 - 1, b->y1 - 1, c->fg); + screen_draw_line(c->scr, b->x0, b->y1 - 1, b->x1 - 1, b->y1 - 1, c->fg); + + /* the top edge is broken around the caption */ + cap_x = b->x0 + WUSS_FRAME_CAPTION_INSET; + gap_x0 = cap_x - WUSS_FRAME_CAPTION_PAD; + gap_x1 = cap_x + cap_w + WUSS_FRAME_CAPTION_PAD; + if (gap_x0 > b->x0) + screen_draw_line(c->scr, b->x0, mid_y, gap_x0, mid_y, c->fg); + if (gap_x1 < b->x1 - 1) + screen_draw_line(c->scr, gap_x1, mid_y, b->x1 - 1, mid_y, c->fg); + + if (c->have_font && cap_w > 0) + { + point_t pos; + + pos.x = cap_x; + pos.y = b->y0; + bmfont_draw(c->font, c->scr, icon->text, (int) strlen(icon->text), + c->fg, bg, &pos, NULL); + } +} + +/* ----------------------------------------------------------------------- */ + +static void wuss__icon_draw_button(const icon_draw_ctx_t *c) +{ + const wuss_icon_t *icon = c->icon; + const box_t *b = &c->b; + colour_t light, dark, base, label; + int pressed, is_default; + + pressed = wuss__icon_pressed(icon); + is_default = (icon->flags & wuss_ICON_FLAGS_DEFAULT) != 0; + + if (is_default) + { + /* default action button: a flat accent-filled rectangle inside a + * one-pixel accent-text border, distinct from the bevelled ordinary + * buttons around it */ + base = c->wuss->palette[c->wuss->accent_bg]; + light = c->wuss->palette[c->wuss->accent_fg]; + dark = light; + label = c->wuss->palette[c->wuss->accent_fg]; + } + else + { + base = c->wuss->palette[icon->bg]; + light = c->wuss->palette[c->wuss->bevel_light]; + dark = c->wuss->palette[c->wuss->bevel_dark]; + label = c->fg; + } + + if (icon->flags & wuss_ICON_FLAGS_DISABLED) + label = c->wuss->palette[c->wuss->bevel_dark]; /* greyed: sink toward dark */ + + if (pressed) + icon_bevel(c->scr, b, base, dark, light); + else + icon_bevel(c->scr, b, base, light, dark); + + if (c->have_font) + { + point_t pos; + int interior_w, split_point; + bmfont_width_t width; + + interior_w = MAX((b->x1 - b->x0) - 2, 1); + + bmfont_measure(c->font, icon->text, (int) strlen(icon->text), + interior_w, &split_point, &width); + + pos.x = b->x0 + ((b->x1 - b->x0) - width) / 2; + pos.y = b->y0 + (b->y1 - b->y0 - c->font_height) / 2; + if (pressed) + { + pos.x += 1; + pos.y += 1; + } + + bmfont_draw(c->font, c->scr, icon->text, (int) strlen(icon->text), + label, base, &pos, NULL); + } +} + +/* ----------------------------------------------------------------------- */ + +/* wuss_ICON_TYPE_RADIO and wuss_ICON_TYPE_OPTION: a font-height square glyph at + * the left, vertically centred, with the label to its right. RADIO draws a + * square ring with a solid centre when selected; OPTION draws a box with a tick + * when selected. */ +static void wuss__icon_draw_radio_option(const icon_draw_ctx_t *c) +{ + const wuss_icon_t *icon = c->icon; + const box_t *b = &c->b; + colour_t glyph, bg; + box_t g; + int gsz, gy, tx; + + gsz = CLAMP(c->font_height, 8, b->y1 - b->y0); + gy = b->y0 + (b->y1 - b->y0 - gsz) / 2; + + g.x0 = b->x0; + g.y0 = gy; + g.x1 = b->x0 + gsz; + g.y1 = gy + gsz; + + glyph = (icon->flags & wuss_ICON_FLAGS_DISABLED) + ? c->wuss->palette[c->wuss->bevel_dark] + : c->fg; + + bg = icon_blend_ground(c, glyph); + + if (icon->bg != wuss_NO_BACKGROUND) + screen_fill_rect(c->scr, b->x0, b->y0, + SIZE2D(b->x1 - b->x0, b->y1 - b->y0), bg); + + if (icon->type == wuss_ICON_TYPE_RADIO) + { + /* a square ring (no circle primitive); a solid centre when selected */ + screen_draw_line(c->scr, g.x0 + 2, g.y0, g.x1 - 3, g.y0, glyph); + screen_draw_line(c->scr, g.x0 + 2, g.y1 - 1, g.x1 - 3, g.y1 - 1, glyph); + screen_draw_line(c->scr, g.x0, g.y0 + 2, g.x0, g.y1 - 3, glyph); + screen_draw_line(c->scr, g.x1 - 1, g.y0 + 2, g.x1 - 1, g.y1 - 3, glyph); + if (wuss__icon_selected(icon)) + screen_fill_rect(c->scr, g.x0 + 3, g.y0 + 3, + SIZE2D(gsz - 6, gsz - 6), glyph); + } + else + { + /* a box; a tick (two strokes) when selected */ + screen_draw_rect(c->scr, g.x0, g.y0, + SIZE2D(g.x1 - g.x0, g.y1 - g.y0), glyph); + if (wuss__icon_selected(icon)) + { + screen_draw_line(c->scr, g.x0 + 2, g.y0 + gsz / 2, + g.x0 + gsz / 2 - 1, g.y1 - 3, glyph); + screen_draw_line(c->scr, g.x0 + gsz / 2 - 1, g.y1 - 3, + g.x1 - 3, g.y0 + 2, glyph); + } + } + + if (c->have_font) + { + point_t pos; + int interior_w, split_point; + bmfont_width_t width; + + tx = g.x1 + 4; + interior_w = MAX((b->x1 - tx) - 1, 1); + + bmfont_measure(c->font, icon->text, (int) strlen(icon->text), + interior_w, &split_point, &width); + NOT_USED(width); + + pos.x = tx; + pos.y = b->y0 + (b->y1 - b->y0 - c->font_height) / 2; + bmfont_draw(c->font, c->scr, icon->text, (int) strlen(icon->text), + glyph, bg, &pos, NULL); + } +} + +/* ----------------------------------------------------------------------- */ + +static void wuss__icon_draw_bitmap(const icon_draw_ctx_t *c) +{ + const box_t *b = &c->b; + screen_t clipped; + + if (c->icon->bitmap == NULL) + return; + + /* screen_copy_bitmap clips to scr->clip and does not scale, so narrow the + * clip to the icon box (intersected with whatever redraw already set) and + * blit at the box's top-left */ + clipped = *c->scr; + if (box_intersection(&c->scr->clip, b, &clipped.clip)) + return; + + screen_copy_bitmap(&clipped, b->x0, b->y0, c->icon->bitmap); +} + +/* ----------------------------------------------------------------------- */ + +static void wuss__icon_draw_menu_entry(const icon_draw_ctx_t *c) +{ + const wuss_icon_t *icon = c->icon; + const box_t *b = &c->b; + colour_t ink, ground, tmp; + int disabled, highlit, pad; + + disabled = (icon->flags & wuss_ICON_FLAGS_DISABLED) != 0; + highlit = wuss__icon_hovered(icon) && !disabled; + pad = 4; + + /* resolve the row's own ink over its own/inherited ground */ + ground = icon_blend_ground(c, c->fg); + ink = disabled ? c->wuss->palette[c->wuss->bevel_dark] : c->fg; + + /* highlight simply swaps the row's fg/bg */ + if (highlit) + { + tmp = ink; + ink = ground; + ground = tmp; + } + + if (highlit || icon->bg != wuss_NO_BACKGROUND) + screen_fill_rect(c->scr, b->x0, b->y0, + SIZE2D(b->x1 - b->x0, b->y1 - b->y0), ground); + + /* left-edge colour chip (wins over the tick) or tick when selected */ + if ((icon->flags & wuss_ICON_FLAGS_SWATCH) && + icon->swatch != wuss_NO_BACKGROUND) + { + int cx, cy, h; + + h = MAX(c->font_height, 8); + cx = b->x0 + pad; + cy = b->y0 + (b->y1 - b->y0 - h) / 2; + screen_fill_rect(c->scr, cx, cy, SIZE2D(h, h), + c->wuss->palette[icon->swatch]); + screen_draw_rect(c->scr, cx, cy, SIZE2D(h, h), ink); /* 1px border */ + } + else if (wuss__icon_selected(icon)) + { + int cx, cy, h; + point_t centre; + + h = MAX(c->font_height, 8); + cx = b->x0 + pad; + cy = b->y0 + (b->y1 - b->y0 - h) / 2; + centre.x = cx + h / 2; + centre.y = cy + h / 2; + if (!wuss__draw_symbol_glyph(c->wuss, c->scr, centre, + WUSS_GLYPH_TICK, ink, ground)) + { + screen_draw_line(c->scr, cx, cy + h / 2, cx + h / 2 - 1, cy + h - 2, ink); + screen_draw_line(c->scr, cx + h / 2 - 1, cy + h - 2, cx + h - 2, cy, ink); + } + } + + /* right-edge arrow for a submenu entry */ + if (icon->flags & wuss_ICON_FLAGS_SUBMENU) + { + int ax, ay, r, dy; + point_t centre; + + r = (c->font_height >= 8) ? c->font_height / 3 : 3; + ax = b->x1 - 1 - pad - r; + ay = b->y0 + (b->y1 - b->y0) / 2; + centre.x = ax + r / 2; + centre.y = ay; + if (!wuss__draw_symbol_glyph(c->wuss, c->scr, centre, + WUSS_GLYPH_SUBMENU, ink, ground)) + { + for (dy = -r; dy <= r; dy++) + screen_draw_line(c->scr, ax, ay + dy, + ax + (r - (dy < 0 ? -dy : dy)), ay + dy, ink); + } + } + + if (c->have_font && icon->text != NULL && icon->text[0] != '\0') + { + point_t pos; + + pos.x = b->x0 + pad + c->font_height; /* leave room for a tick */ + pos.y = b->y0 + (b->y1 - b->y0 - c->font_height) / 2; + bmfont_draw(c->font, c->scr, icon->text, (int) strlen(icon->text), + ink, ground, &pos, NULL); + } +} + +/* ----------------------------------------------------------------------- */ + +/* wuss_ICON_TYPE_RULE: a dashed rule centred in the box, drawn in the icon's + * fg over whatever ground it inherits. Inert -- see wuss__icon_hit_test. */ +static void wuss__icon_draw_rule(const icon_draw_ctx_t *c) +{ + const box_t *b = &c->b; + int mid_y; + + mid_y = b->y0 + (b->y1 - b->y0) / 2; + screen_draw_dashed_line(c->scr, + b->x0, mid_y, b->x1 - 1, mid_y, + 2, 2, c->fg); +} + +/* ----------------------------------------------------------------------- */ + +void wuss__icon_draw(wuss_t *wuss, + const wuss_icon_t *icon, + const box_t *content, + point_t scroll) +{ + icon_draw_ctx_t c; + int fontidx; + + if (icon->flags & wuss_ICON_FLAGS_HIDDEN) + return; + + wuss__icon_box_to_screen(content, scroll, &icon->bbox, &c.b); + + if (box_is_empty(&c.b)) + return; + + c.wuss = wuss; + c.scr = wuss->scr; + c.icon = icon; + c.fg = wuss->palette[icon->fg]; + + /* pick the icon's requested weight; fall back to the system font */ + fontidx = wuss_ICON_FONT_OF(icon->flags); + c.font = wuss->fonts[fontidx]; + if (c.font == NULL) + c.font = wuss->fonts[0]; + + c.have_font = (c.font != NULL && icon->text[0] != '\0'); + if (c.have_font) + bmfont_get_info(c.font, NULL, &c.font_height); + else + c.font_height = 0; + + switch (icon->type) + { + case wuss_ICON_TYPE_PATTERN: + wuss__icon_draw_pattern(&c, content, scroll); + break; + + case wuss_ICON_TYPE_LABEL: + wuss__icon_draw_label(&c); + break; + + case wuss_ICON_TYPE_FRAME: + wuss__icon_draw_frame(&c); + break; + + case wuss_ICON_TYPE_BUTTON: + wuss__icon_draw_button(&c); + break; + + case wuss_ICON_TYPE_RADIO: + case wuss_ICON_TYPE_OPTION: + wuss__icon_draw_radio_option(&c); + break; + + case wuss_ICON_TYPE_BITMAP: + wuss__icon_draw_bitmap(&c); + break; + + case wuss_ICON_TYPE_MENU_ENTRY: + wuss__icon_draw_menu_entry(&c); + break; + + case wuss_ICON_TYPE_RULE: + wuss__icon_draw_rule(&c); + break; + } +} diff --git a/libraries/wuss/icon/free.c b/libraries/wuss/icon/free.c new file mode 100644 index 00000000..cb4c5e08 --- /dev/null +++ b/libraries/wuss/icon/free.c @@ -0,0 +1,28 @@ +/* wuss/icon/free.c -- free a window's whole icon store */ + +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "../core/impl.h" + +void wuss__icons_free(wuss_window_t *window) +{ + wuss_t *w; + int i; + + w = window->wuss; + + for (i = 0; i < window->nicons; i++) + { + wuss__free(w, window->icons[i]->text); + wuss__free(w, window->icons[i]); + } + + wuss__free(w, window->icons); + window->icons = NULL; + window->nicons = 0; + window->cap_icons = 0; +} diff --git a/libraries/wuss/icon/from-spec.c b/libraries/wuss/icon/from-spec.c new file mode 100644 index 00000000..46d817ef --- /dev/null +++ b/libraries/wuss/icon/from-spec.c @@ -0,0 +1,84 @@ +/* wuss/icon/from-spec.c -- validate an icon spec into a detached icon */ + +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "../core/impl.h" + +result_t wuss__icon_from_spec(const wuss_t *w, + const wuss_icon_spec_t *spec, + wuss_icon_t *out) +{ + wuss_colour_t fg, bg, swatch; + int has_swatch; + + assert(w != NULL); + assert(spec != NULL); + assert(out != NULL); + + fg = wuss__resolve_colour(w, spec->fg); + bg = wuss__resolve_colour(w, spec->bg); + + has_swatch = (spec->type == wuss_ICON_TYPE_MENU_ENTRY) && + (spec->flags & wuss_ICON_FLAGS_SWATCH); + swatch = has_swatch ? wuss__resolve_colour(w, spec->swatch) + : wuss_NO_BACKGROUND; + + switch (spec->type) + { + case wuss_ICON_TYPE_LABEL: + case wuss_ICON_TYPE_BUTTON: + case wuss_ICON_TYPE_PATTERN: + case wuss_ICON_TYPE_FRAME: + case wuss_ICON_TYPE_RADIO: + case wuss_ICON_TYPE_OPTION: + case wuss_ICON_TYPE_BITMAP: + case wuss_ICON_TYPE_MENU_ENTRY: + case wuss_ICON_TYPE_RULE: + break; + + default: + return result_WUSS_BAD_ICON; + } + + if ((spec->type == wuss_ICON_TYPE_BUTTON || + spec->type == wuss_ICON_TYPE_PATTERN) && + bg == wuss_NO_BACKGROUND) + return result_WUSS_BAD_ICON; + + if (spec->type == wuss_ICON_TYPE_BITMAP && spec->bitmap == NULL) + return result_WUSS_BAD_ICON; + + if (spec->type == wuss_ICON_TYPE_PATTERN && + (spec->pattern < 0 || spec->pattern >= screen_PATTERN__LIMIT)) + return result_WUSS_BAD_ICON; + + if (fg < 0 || fg >= w->npalette) + return result_WUSS_BAD_COLOUR; + + if (bg != wuss_NO_BACKGROUND && + (bg < 0 || bg >= w->npalette)) + return result_WUSS_BAD_COLOUR; + + if (has_swatch && (swatch < 0 || swatch >= w->npalette)) + return result_WUSS_BAD_COLOUR; + + out->window = NULL; + out->bbox = spec->bbox; + out->type = spec->type; + out->text = (char *) ((spec->text != NULL) ? spec->text : ""); + out->fg = fg; + out->bg = bg; + out->pattern = (spec->type == wuss_ICON_TYPE_PATTERN) ? spec->pattern + : screen_PATTERN_SOLID; + out->bitmap = (spec->type == wuss_ICON_TYPE_BITMAP) ? spec->bitmap : NULL; + out->group = (spec->type == wuss_ICON_TYPE_RADIO) ? spec->group : 0; + out->swatch = swatch; + out->flags = spec->flags; + out->state = wuss_ICON_STATE_NONE; + + return result_OK; +} diff --git a/libraries/wuss/icon/get-bbox.c b/libraries/wuss/icon/get-bbox.c new file mode 100644 index 00000000..99b42c1d --- /dev/null +++ b/libraries/wuss/icon/get-bbox.c @@ -0,0 +1,8 @@ +/* wuss/icon/get-bbox.c -- read a work-area icon's bounding box */ + +#include "../core/impl.h" + +void wuss_icon_get_bbox(const wuss_icon_t *icon, box_t *bbox) +{ + *bbox = icon->bbox; +} diff --git a/libraries/wuss/icon/get-selected.c b/libraries/wuss/icon/get-selected.c new file mode 100644 index 00000000..dffcb792 --- /dev/null +++ b/libraries/wuss/icon/get-selected.c @@ -0,0 +1,12 @@ +/* wuss/icon/get-selected.c -- query a radio/option icon's latched state */ + +#include + +#include "../core/impl.h" + +int wuss_icon_get_selected(const wuss_icon_t *icon) +{ + assert(icon != NULL); + + return wuss__icon_selected(icon); +} diff --git a/libraries/wuss/icon/get-text.c b/libraries/wuss/icon/get-text.c new file mode 100644 index 00000000..b3f8de33 --- /dev/null +++ b/libraries/wuss/icon/get-text.c @@ -0,0 +1,8 @@ +/* wuss/icon/get-text.c -- read a work-area icon's label */ + +#include "../core/impl.h" + +const char *wuss_icon_get_text(const wuss_icon_t *icon) +{ + return icon->text; +} diff --git a/libraries/wuss/icon/get-type.c b/libraries/wuss/icon/get-type.c new file mode 100644 index 00000000..f988044e --- /dev/null +++ b/libraries/wuss/icon/get-type.c @@ -0,0 +1,8 @@ +/* wuss/icon/get-type.c -- read a work-area icon's type */ + +#include "../core/impl.h" + +wuss_icon_type_t wuss_icon_get_type(const wuss_icon_t *icon) +{ + return icon->type; +} diff --git a/libraries/wuss/icon/get-window.c b/libraries/wuss/icon/get-window.c new file mode 100644 index 00000000..a5e41e6d --- /dev/null +++ b/libraries/wuss/icon/get-window.c @@ -0,0 +1,8 @@ +/* wuss/icon/get-window.c -- read a work-area icon's owning window */ + +#include "../core/impl.h" + +wuss_window_t *wuss_icon_get_window(const wuss_icon_t *icon) +{ + return icon->window; +} diff --git a/libraries/wuss/icon/hit-test.c b/libraries/wuss/icon/hit-test.c new file mode 100644 index 00000000..d734b9c1 --- /dev/null +++ b/libraries/wuss/icon/hit-test.c @@ -0,0 +1,40 @@ +/* wuss/icon/hit-test.c -- work-area icon hit testing */ + +#include "geom/box.h" + +#include "../core/impl.h" + +wuss_icon_t *wuss__icon_hit_test(wuss_window_t *window, point_t doc_point) +{ + wuss_icon_t *it; + int i; + + /* last created wins, matching the draw order in redraw_window */ + for (i = window->nicons - 1; i >= 0; i--) + { + it = window->icons[i]; + + /* a bitmap icon is interactive only when it asks to be; RULE and the + * static types are always inert; the other types always click */ + if (it->type == wuss_ICON_TYPE_BITMAP) + { + if (!(it->flags & wuss_ICON_FLAGS_INTERACTIVE)) + continue; + } + else if (it->type != wuss_ICON_TYPE_BUTTON && + it->type != wuss_ICON_TYPE_RADIO && + it->type != wuss_ICON_TYPE_OPTION && + it->type != wuss_ICON_TYPE_MENU_ENTRY) + { + continue; + } + + if (it->flags & (wuss_ICON_FLAGS_HIDDEN | wuss_ICON_FLAGS_DISABLED)) + continue; + + if (box_contains_point(&it->bbox, doc_point.x, doc_point.y)) + return it; + } + + return NULL; +} diff --git a/libraries/wuss/icon/invalidate.c b/libraries/wuss/icon/invalidate.c new file mode 100644 index 00000000..156c5747 --- /dev/null +++ b/libraries/wuss/icon/invalidate.c @@ -0,0 +1,10 @@ +/* wuss/icon/invalidate.c -- mark a work-area icon's bbox dirty */ + +#include "../core/impl.h" + +void wuss__icon_invalidate(const wuss_icon_t *icon) +{ + /* the icon's bbox is already in the window-local (pre-scroll) coordinates + * wuss_window_invalidate expects */ + wuss_window_invalidate(icon->window, &icon->bbox); +} diff --git a/libraries/wuss/icon/plot.c b/libraries/wuss/icon/plot.c new file mode 100644 index 00000000..a305e968 --- /dev/null +++ b/libraries/wuss/icon/plot.c @@ -0,0 +1,32 @@ +/* wuss/icon/plot.c -- draw an icon from a spec without retaining it */ + +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "../core/impl.h" + +result_t wuss_icon_plot(wuss_window_t *window, + const wuss_icon_spec_t *spec, + const box_t *content, + point_t scroll) +{ + wuss_icon_t scratch; + result_t rc; + + assert(window != NULL); + assert(spec != NULL); + assert(content != NULL); + + rc = wuss__icon_from_spec(window->wuss, spec, &scratch); + if (rc != result_OK) + return rc; + + scratch.window = window; + + wuss__icon_draw(window->wuss, &scratch, content, scroll); + + return result_OK; +} diff --git a/libraries/wuss/icon/screen-box.c b/libraries/wuss/icon/screen-box.c new file mode 100644 index 00000000..74a9035e --- /dev/null +++ b/libraries/wuss/icon/screen-box.c @@ -0,0 +1,18 @@ +/* wuss/icon/screen-box.c -- work-area icon bbox to screen space */ + +#include "geom/box.h" +#include "geom/point.h" + +#include "../core/impl.h" + +/* Map an icon bbox (virtual document space) into screen space, given the + * owning window's content box and scroll offset. wuss__icon_draw uses this + * for the box it paints into; keeping the transform here means hit-testing + * and invalidation cannot drift from what is drawn. */ +void wuss__icon_box_to_screen(const box_t *content, + point_t scroll, + const box_t *bbox, + box_t *out) +{ + box_translated(bbox, content->x0 - scroll.x, content->y0 - scroll.y, out); +} diff --git a/libraries/wuss/icon/set-hidden.c b/libraries/wuss/icon/set-hidden.c new file mode 100644 index 00000000..996c2fdc --- /dev/null +++ b/libraries/wuss/icon/set-hidden.c @@ -0,0 +1,13 @@ +/* wuss/icon/set-hidden.c -- show or hide a work-area icon */ + +#include "../core/impl.h" + +void wuss_icon_set_hidden(wuss_icon_t *icon, int hidden) +{ + if (hidden) + icon->flags |= wuss_ICON_FLAGS_HIDDEN; + else + icon->flags &= (wuss_icon_flags_t) ~wuss_ICON_FLAGS_HIDDEN; + + wuss__icon_invalidate(icon); +} diff --git a/libraries/wuss/icon/set-hover.c b/libraries/wuss/icon/set-hover.c new file mode 100644 index 00000000..1c55d1fd --- /dev/null +++ b/libraries/wuss/icon/set-hover.c @@ -0,0 +1,38 @@ +/* wuss/icon/set-hover.c -- track which work-area icon the pointer is over */ + +#include + +#include "../core/impl.h" + +/* Only wuss_ICON_TYPE_MENU_ENTRY changes appearance on hover (see + * wuss__icon_draw_menu_entry). Buttons and every other type ignore the hover state, + * so redrawing them on pointer enter/leave is wasted work. */ +static int wuss__icon_hover_visible(const wuss_icon_t *icon) +{ + return icon->type == wuss_ICON_TYPE_MENU_ENTRY; +} + +void wuss__icon_set_hover(wuss_t *wuss, wuss_icon_t *icon) +{ + wuss_icon_t *prev; + + prev = wuss->hover_icon; + if (prev == icon) + return; + + if (prev != NULL) + { + wuss__icon_set_state(prev, wuss_ICON_STATE_HOVERED, 0); + if (wuss__icon_hover_visible(prev)) + wuss__icon_invalidate(prev); + } + + wuss->hover_icon = icon; + + if (icon != NULL) + { + wuss__icon_set_state(icon, wuss_ICON_STATE_HOVERED, 1); + if (wuss__icon_hover_visible(icon)) + wuss__icon_invalidate(icon); + } +} diff --git a/libraries/wuss/icon/set-selected.c b/libraries/wuss/icon/set-selected.c new file mode 100644 index 00000000..712aee57 --- /dev/null +++ b/libraries/wuss/icon/set-selected.c @@ -0,0 +1,52 @@ +/* wuss/icon/set-selected.c -- latch a radio/option icon's selected state */ + +#include + +#include "../core/impl.h" + +void wuss__icon_select(wuss_icon_t *icon, int selected) +{ + wuss_window_t *window; + wuss_icon_t *other; + int i; + + assert(icon != NULL); + + if (icon->type != wuss_ICON_TYPE_RADIO && + icon->type != wuss_ICON_TYPE_OPTION && + icon->type != wuss_ICON_TYPE_MENU_ENTRY) + return; + + selected = selected ? 1 : 0; + + /* selecting a grouped radio clears its siblings first */ + if (selected && + icon->type == wuss_ICON_TYPE_RADIO && + icon->group != 0) + { + window = icon->window; + for (i = 0; i < window->nicons; i++) + { + other = window->icons[i]; + if (other == icon) + continue; + if (other->type != wuss_ICON_TYPE_RADIO || other->group != icon->group) + continue; + if (!wuss__icon_selected(other)) + continue; + wuss__icon_set_state(other, wuss_ICON_STATE_SELECTED, 0); + wuss__icon_invalidate(other); + } + } + + if (wuss__icon_selected(icon) == selected) + return; + + wuss__icon_set_state(icon, wuss_ICON_STATE_SELECTED, selected); + wuss__icon_invalidate(icon); +} + +void wuss_icon_set_selected(wuss_icon_t *icon, int selected) +{ + wuss__icon_select(icon, selected); +} diff --git a/libraries/wuss/icon/set-text.c b/libraries/wuss/icon/set-text.c new file mode 100644 index 00000000..35c8fcce --- /dev/null +++ b/libraries/wuss/icon/set-text.c @@ -0,0 +1,34 @@ +/* wuss/icon/set-text.c -- replace a work-area icon's label */ + +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "../core/impl.h" + +result_t wuss_icon_set_text(wuss_icon_t *icon, const char *text) +{ + wuss_t *w; + const char *src; + char *dup; + size_t len; + + w = icon->window->wuss; + src = (text != NULL) ? text : ""; + len = strlen(src); + + dup = wuss__malloc(w, len + 1); + if (dup == NULL) + return result_OOM; + memcpy(dup, src, len + 1); + + wuss__free(w, icon->text); + icon->text = dup; + + wuss__icon_invalidate(icon); + + return result_OK; +} diff --git a/libraries/wuss/idle.c b/libraries/wuss/idle.c deleted file mode 100644 index c8de3625..00000000 --- a/libraries/wuss/idle.c +++ /dev/null @@ -1,29 +0,0 @@ -/* idle.c -- wuss - minimal window manager */ - -#include "impl.h" - -result_t wuss_idle(wuss_t *wuss) -{ - wuss_event_t event; - result_t rc; - list_t *e; - - event.kind = wuss_EVENT_IDLE; - - rc = result_OK; - for (e = wuss->z_order.next; e != NULL; e = e->next) - { - wuss_window_t *win; - result_t crc; - - win = (wuss_window_t *) e; - if (win->task.handle == NULL) - continue; - - crc = win->task.handle(win, &event, win->task.task_data); - if (crc != result_OK) - rc = crc; - } - - return rc; -} diff --git a/libraries/wuss/impl.h b/libraries/wuss/impl.h deleted file mode 100644 index 531766f6..00000000 --- a/libraries/wuss/impl.h +++ /dev/null @@ -1,155 +0,0 @@ -/* impl.h -- wuss - minimal window manager */ - -#ifndef IMPL_H -#define IMPL_H - -#include "datastruct/list.h" -#include "geom/box.h" -#include "framebuf/screen.h" -#include "framebuf/bmfont.h" - -#include "wuss/wuss.h" -#include "wuss/window.h" - -#include "furniture.h" - -#define WUSS_TITLE_MAX 63 -#define WUSS_DEFAULT_TITLEBAR_HEIGHT 20 - -#define WUSS_MAX_DIRTY 16 /* dirty regions tracked before further invalidations get merged into the last entry */ - -/* ponytail: fixed cap; if hit, remaining pieces are carried through - * unclipped against further occluders rather than growing storage -- safe, - * just some avoidable redraw work, never wrong */ -#define WUSS_MAX_INVALIDATE_PIECES 32 - -#define WUSS_ICON_INSET 3 /* shared by close/back/toggle/resize icons and scrollbar breadth */ -#define WUSS_MIN_CONTENT 20 /* resize-drag floor: content can never be squeezed smaller than this */ - -struct wuss -{ - screen_t *scr; - bmfont_t *font; /* nullable, not owned */ - colour_t *palette; /* owned */ - int npalette; - wuss_palette_t furniture_colours; - int titlebar_height; - list_t z_order; /* anchor; head = topmost window */ - struct wuss__furniture furniture; - box_t dirty[WUSS_MAX_DIRTY]; /* accumulated by wuss_invalidate; reset by a redraw */ - int ndirty; -}; - -struct wuss_window -{ - list_t link; /* must be first member */ - wuss_t *wuss; - box_t visible; /* full on-screen footprint: content expanded - * outward by any titlebar/outline furniture */ - wuss_task_t task; - wuss_window_flags_t flags; - point_t scroll; /* offset into virtual content space of the - * content box's top-left; see wuss_window_set_scroll */ - int doc_width, doc_height; /* virtual document extent, set at creation */ - int toggled; /* currently at TOGGLE_SIZE's "full" size? */ - box_t pre_toggle; /* visible bounds to restore on the next toggle */ - char title[WUSS_TITLE_MAX + 1]; -}; - -wuss_window_t *wuss__window_at(wuss_t *wuss, point_t p); -void wuss__titlebar_box(const wuss_window_t *window, box_t *out); -void wuss__close_box(const wuss_window_t *window, box_t *out); -void wuss__content_box(const wuss_window_t *window, box_t *out); -void wuss__invalidate_clipped(wuss_window_t *window, - const box_t *box); -void wuss__invalidate_minus(wuss_t *wuss, - const box_t *whole, - const box_t *keep); -void wuss__invalidate_uncovered(wuss_window_t *window); - -/* Clip "box" (screen space) down to the parts not already covered by - * windows above "window" in the z-order, writing the surviving pieces to - * "out" (capacity WUSS_MAX_INVALIDATE_PIECES) and returning their count. */ -int wuss__clip_to_visible(wuss_window_t *window, - const box_t *box, - box_t *out); - -/* Notify a window's task that it has been moved or resized, via - * wuss_EVENT_OPEN; the return value is discarded, matching how furniture - * drawing and other in-line notifications are treated. */ -static inline void wuss__notify_open(wuss_window_t *window) -{ - wuss_event_t event; - - if (window->task.handle == NULL) - return; - - event.kind = wuss_EVENT_OPEN; - (void) window->task.handle(window, &event, window->task.task_data); -} - -static inline int wuss__size_ok(int width, int height) -{ - return width > 0 && height > 0; -} - -static inline int wuss__titlebar_height_for(const wuss_t *wuss, - wuss_window_flags_t flags) -{ - return (flags & wuss_WINDOW_NO_TITLEBAR) ? 0 : wuss->titlebar_height; -} - -static inline int wuss__titlebar_height(const wuss_window_t *window) -{ - return wuss__titlebar_height_for(window->wuss, window->flags); -} - -static inline int wuss__outline_px_for(wuss_window_flags_t flags) -{ - return (flags & wuss_WINDOW_NO_OUTLINE) ? 0 : 1; -} - -static inline int wuss__outline_px(const wuss_window_t *window) -{ - return wuss__outline_px_for(window->flags); -} - -/* ponytail: falls back to the default titlebar height when the window has - * none, so NO_TITLEBAR windows that still opt into scrollbars/resize get a - * sane breadth rather than a negative one */ -static inline int wuss__icon_size_for(const wuss_t *wuss, wuss_window_flags_t flags) -{ - int size; - - size = wuss__titlebar_height_for(wuss, flags) - 2 * WUSS_ICON_INSET; - - return (size > 0) ? size : WUSS_DEFAULT_TITLEBAR_HEIGHT - 2 * WUSS_ICON_INSET; -} - -static inline int wuss__icon_size(const wuss_window_t *window) -{ - return wuss__icon_size_for(window->wuss, window->flags); -} - -/* how much of a content box's width/height is furniture (scrollbars, the - * resize icon's corner), reserved outside the content area rather than - * carved out of it -- shared by wuss__content_box (subtracts it back off - * visible) and window creation/resize (add it to visible up front) so the - * two stay consistent with each other */ -static inline void wuss__furniture_carve_for(wuss_window_flags_t flags, - int icon_size, - point_t *carve) -{ - carve->x = (flags & wuss_WINDOW_NO_VSCROLL) ? 0 : icon_size; - carve->y = (flags & wuss_WINDOW_NO_HSCROLL) ? 0 : icon_size; - - if (!(flags & wuss_WINDOW_NO_RESIZE) && - (flags & wuss_WINDOW_NO_VSCROLL) && - (flags & wuss_WINDOW_NO_HSCROLL)) - { - carve->x = icon_size; - carve->y = icon_size; - } -} - -#endif /* IMPL_H */ diff --git a/libraries/wuss/menu.h b/libraries/wuss/menu.h new file mode 100644 index 00000000..482b392e --- /dev/null +++ b/libraries/wuss/menu.h @@ -0,0 +1,60 @@ +/* wuss/menu.h -- wuss pop-up menu helper, internal */ + +#ifndef WUSS_MENU_IMPL_H +#define WUSS_MENU_IMPL_H + +#include "geom/point.h" + +#include "wuss/wuss.h" +#include "wuss/menu.h" + +/* One open level of a menu chain: its window and per-item icon handles, plus a + * link to the level it opened from. The head (root) is wuss->menu_chain; each + * child points at its parent so wuss_menu_close can walk leaf-to-root. */ +struct wuss__menu +{ + wuss_t *wuss; + wuss_task_t *owner; /* task that opened the chain (from + * wuss_menu_open); MENU_SELECT is + * delivered here. Only meaningful on the + * root; children copy it for convenience */ + wuss_window_t *window; /* the borderless menu window; for a + * borrowed-window level (see borrowed) the + * caller's own window instead */ + const wuss_menu_t *menu; /* borrowed source description; NULL for a + * borrowed-window level */ + wuss_icon_t **icons; /* owned array, menu->nitems entries, in + * item order; NULL for a borrowed-window + * level */ + struct wuss__menu *parent; /* level this one opened from, NULL at root */ + struct wuss__menu *child; /* open submenu level, NULL when none */ + int open_index; /* item index whose submenu `child` is, + * -1 when no child open */ + int borrowed; /* 1: `window` is a caller-owned window + * shown in place of a submenu -- hide it + * rather than close it on teardown */ + + /* SELECT-pick flash: the picked row's highlight is toggled a few times off + * wuss_EVENT_IDLE before the chain closes and MENU_SELECT is delivered. + * Set on the picked level; frames == 0 means no flash in progress. The + * owner/menu/index/button are captured because the chain (and this node) + * is freed before the deferred notification goes out. */ + struct + { + int frames; /* IDLE frames left in the flash */ + int index; /* item index being flashed */ + int keep_open; /* 1 (ADJUST pick): flash but do not tear + * the chain down before MENU_SELECT */ + wuss_task_t *owner; /* captured MENU_SELECT target */ + const wuss_menu_t *menu; /* captured menu for the notification */ + wuss_button_t button; /* captured release button */ + } + flash; +}; + +/* Called from wuss_mouse_click on a MOUSE_DOWN before the window hit-test: if a + * menu chain is open and `hit` is not one of its windows, close the whole chain + * and return 1 (the click is spent on dismissal). Returns 0 otherwise. */ +int wuss__menu_click_outside(wuss_t *wuss, const wuss_window_t *hit); + +#endif /* WUSS_MENU_IMPL_H */ diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c new file mode 100644 index 00000000..f5256697 --- /dev/null +++ b/libraries/wuss/menu/menu.c @@ -0,0 +1,854 @@ +/* wuss/menu/menu.c -- wuss pop-up menu helper */ + +#include +#include +#include +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/result.h" +#include "base/utils.h" +#include "framebuf/bmfont.h" +#include "geom/box.h" +#include "geom/point.h" +#include "geom/size.h" + +#include "wuss/icon.h" +#include "wuss/task.h" +#include "wuss/window.h" +#include "wuss/wuss.h" + +#include "../core/impl.h" + +/* Row padding above/below the glyph, and the gutters left for the tick (left) + * and the submenu arrow (right). All in pixels. */ +#define WUSS_MENU_ROW_PAD 4 +#define WUSS_MENU_TICK_W 14 +#define WUSS_MENU_ARROW_W 14 +#define WUSS_MENU_TEXT_PAD 6 +#define WUSS_MENU_SUBMENU_OVERLAP 2 /* px a submenu overlaps its parent's right edge */ + +/* SELECT-pick flash: toggle the row highlight every WUSS_MENU_FLASH_PERIOD + * IDLE frames, for WUSS_MENU_FLASH_FRAMES frames total. At ~60 Hz that is + * three quick on/off blinks in about 0.2s -- the RISC OS feel. */ +#define WUSS_MENU_FLASH_FRAMES 12 +#define WUSS_MENU_FLASH_PERIOD 2 + +/* ----------------------------------------------------------------------- */ + +static result_t wuss__menu_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data); + +static result_t wuss__menu_spawn(wuss_t *wuss, + wuss_task_t *owner, + const wuss_menu_t *menu, + point_t at, + struct wuss__menu *parent, + struct wuss__menu **out); + +static void wuss__menu_flash_step(struct wuss__menu *self); +static void wuss__menu_flash_finish(struct wuss__menu *self); + +static int wuss__pointer_over_row_arrow(wuss_window_t *window, + const wuss_icon_t *icon); + +/* The internal task that owns every borderless menu window, created on the + * first wuss_menu_open of a session. */ +static wuss_task_t *wuss__menu_task(wuss_t *wuss) +{ + wuss_task_desc_t desc; + + if (wuss->menu_task != NULL) + return wuss->menu_task; + + desc.handle = wuss__menu_handle; + desc.task_data = wuss; /* the ICON path walks the chain by window; the IDLE + * path (window == NULL) needs the wuss_t directly */ + desc.name = "wuss:menu"; + + if (wuss_task_create(wuss, &desc, &wuss->menu_task) != result_OK) + return NULL; + + return wuss->menu_task; +} + +/* Find the open chain node whose window is `window` (the menu window whose + * delegate fired), or NULL. Borrowed-window levels never fire this handler + * (their windows keep the caller's task), so a match is always a real menu + * level with icons. */ +static struct wuss__menu *wuss__menu_node_for(wuss_t *wuss, + const wuss_window_t *window) +{ + struct wuss__menu *node; + + for (node = wuss->menu_chain; node != NULL; node = node->child) + if (node->window == window) + return node; + + return NULL; +} + +/* Close `node` and everything it opened, leaf-first. The caller clears any + * parent's child pointer. */ +static void wuss__menu_close_from(struct wuss__menu *node) +{ + if (node == NULL) + return; + + wuss__menu_close_from(node->child); + node->child = NULL; + + /* A pending pick flash still owes its owner a MENU_SELECT; the chain is + * about to be freed out from under it (the caller unlinks it from + * wuss->menu_chain first), so delivering the notification here -- rather + * than via wuss__menu_flash_finish, whose own teardown would race this + * one -- is the only chance the pick has of reaching its owner. */ + if (node->flash.frames > 0) + { + wuss_event_t sel; + + node->flash.frames = 0; + sel.kind = wuss_EVENT_MENU_SELECT; + sel.data.menu_select.menu = node->flash.menu; + sel.data.menu_select.index = node->flash.index; + sel.data.menu_select.button = node->flash.button; + (void) wuss__deliver(node->flash.owner, NULL, &sel); + } + + { + wuss_t *w; + + w = node->wuss; + if (node->borrowed) + wuss_window_set_hidden(node->window, 1); /* caller's window: hide, keep */ + else + wuss_window_close(node->window); + wuss__free(w, node->icons); /* NULL for a borrowed level */ + wuss__free(w, node); + } +} + +/* Compute where a submenu (or a borrowed window standing in for one) opens + * off the row `icon` in parent level `self`: content top-left in screen + * space, the child's own titlebar then sitting above it so the two rows line + * up. Mirrors the maths in wuss__menu_handle's MOUSE_MOVE branch. */ +static point_t wuss__submenu_anchor(struct wuss__menu *self, + const wuss_icon_t *icon) +{ + box_t wb; + box_t ib; + point_t scroll; + point_t at; + + wuss_window_get_content_bounds(self->window, &wb); + wuss_icon_get_bbox(icon, &ib); + wuss_window_get_scroll(self->window, &scroll); + + at.x = wb.x1 - WUSS_MENU_SUBMENU_OVERLAP; + at.y = wb.y0 + ib.y0 - scroll.y; + return at; +} + +/* Open item `index`'s borrowed window as level `self->child`: position it + * where a submenu would appear, un-hide it, bring it to the front. A + * PRE_SHOW veto from the window's own task leaves the row unopened. */ +static result_t wuss__menu_open_window(struct wuss__menu *self, int index) +{ + struct wuss__menu *node; + wuss_window_t *win; + point_t at; + point_t prev; + + win = self->menu->items[index].window; + prev = POINT(win->visible.x0, win->visible.y0); + + node = wuss__malloc(self->wuss, sizeof(*node)); + if (node == NULL) + return result_OOM; + + node->wuss = self->wuss; + node->owner = self->owner; + node->window = win; + node->menu = NULL; + node->icons = NULL; + node->parent = self; + node->child = NULL; + node->open_index = -1; + node->borrowed = 1; + memset(&node->flash, 0, sizeof(node->flash)); + + at = wuss__submenu_anchor(self, self->icons[index]); + wuss_window_move(win, at); + if (wuss_window_set_hidden(win, 0) != result_OK) + { + wuss_window_move(win, prev); /* vetoed: undo the anchor move */ + wuss__free(self->wuss, node); /* row stays unopened */ + return result_OK; + } + wuss_window_restack(win, wuss_ZORDER_FRONT); + + self->child = node; + return result_OK; +} + +/* ----------------------------------------------------------------------- */ + +/* The menu window's task delegate. Shared across every borderless menu + * window; the firing level is located by its window. */ +static result_t wuss__menu_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) +{ + struct wuss__menu *self; + const wuss_icon_t *icon; + const wuss_menu_item_t *item; + wuss_t *wuss; + int index; + int i; + + /* IDLE arrives with window == NULL; task_data is the wuss_t (see + * wuss__menu_task). Drive any running SELECT-pick flash off it. */ + if (event->kind == wuss_EVENT_IDLE) + { + struct wuss__menu *node; + + wuss = task_data; + for (node = wuss->menu_chain; node != NULL; node = node->child) + if (!node->borrowed && node->flash.frames > 0) + { + wuss__menu_flash_step(node); + break; /* node may be freed; the chain is gone if the flash ended */ + } + return result_OK; + } + + if (event->kind != wuss_EVENT_ICON) + return result_OK; + + wuss = window->wuss; + self = wuss__menu_node_for(wuss, window); + if (self == NULL) + return result_OK; + + icon = event->data.icon.icon; + index = -1; + for (i = 0; i < self->menu->nitems; i++) + { + if (self->icons[i] == icon) + { + index = i; + break; + } + } + if (index < 0) + return result_OK; + + item = &self->menu->items[index]; + + if (item->flags & wuss_MENU_ITEM_DISABLED) + return result_OK; + + if (event->data.icon.action == wuss_MOUSE_MOVE) + { + point_t at; + int has_child_row; + int on_arrow; + + has_child_row = (item->submenu != NULL || item->window != NULL); + on_arrow = has_child_row + && wuss__pointer_over_row_arrow(self->window, icon); + + /* A submenu opens only while the pointer is over its row's arrow. Any + * other move that re-enters this level -- onto a different row, or onto + * this row's text off the arrow -- closes the child it opened. */ + if (self->child != NULL && !(self->open_index == index && on_arrow)) + { + wuss__menu_close_from(self->child); + self->child = NULL; + self->open_index = -1; + } + + if (self->child != NULL) + return result_OK; /* still on the open row's arrow: leave it up */ + + if (!on_arrow) + return result_OK; + + /* A borrowed window opens where a submenu would; a submenu spawns as a + * fresh menu level. Either lines its row 0 up with this row -- the + * child's own titlebar sits above `at`. */ + if (item->window != NULL) + { + if (wuss__menu_open_window(self, index) == result_OK && self->child != NULL) + self->open_index = index; + return result_OK; + } + + at = wuss__submenu_anchor(self, icon); + if (wuss__menu_spawn(self->wuss, self->owner, item->submenu, at, self, + &self->child) == result_OK) + self->open_index = index; + + return result_OK; + } + + if (event->data.icon.action == wuss_MOUSE_UP) + { + wuss_button_t button; + wuss_task_t *owner; + const wuss_menu_t *menu; + + button = event->data.icon.button; + + if (item->submenu != NULL || item->window != NULL) + return result_OK; /* a submenu/window row opens on hover, not a pick */ + + /* owner (the task that opened the chain) is copied onto every level */ + owner = self->owner; + menu = self->menu; + + /* Flash the picked row, then deliver MENU_SELECT from the IDLE handler. + * SELECT tears the whole chain down first; ADJUST keeps it open so the + * row can be re-picked. Capture everything the notification needs now -- + * a SELECT flash frees `self` when it ends. A re-pick while a flash on + * this level is still running (fast clicks) finishes that flash first so + * its row is cleared and its MENU_SELECT is not lost; if that previous + * pick was a SELECT the finish frees the whole chain (including `self`), + * so this pick is spent on it and we must not touch `self` again. */ + { + wuss_icon_t *row; + + if (self->flash.frames > 0) + { + wuss__menu_flash_finish(self); + + /* the finish's own SELECT tears the chain down when it wasn't a + * keep_open pick, and even a keep_open pick's MENU_SELECT delivery + * may re-enter and close the chain from the client side (e.g. the + * owner calling wuss_menu_close()) -- either way `self` is gone the + * moment the chain no longer starts at it */ + if (wuss__menu_node_for(wuss, window) != self) + return result_OK; /* `self` and the chain are gone */ + } + + row = self->icons[index]; + + self->flash.frames = WUSS_MENU_FLASH_FRAMES; + self->flash.index = index; + self->flash.owner = owner; + self->flash.menu = menu; + self->flash.button = button; + self->flash.keep_open = (button & wuss_BUTTON_ADJUST) != 0; + + /* first blink now: the row is already highlit under the pointer, so + * drop the highlight this frame for an immediate visible change */ + wuss__icon_set_state(row, wuss_ICON_STATE_HOVERED, 0); + wuss__icon_invalidate(row); + } + return result_OK; + } + + return result_OK; +} + +/* True if the wuss pointer currently sits over icon `icon` in `window`. */ +static int wuss__pointer_over_icon(wuss_window_t *window, + const wuss_icon_t *icon) +{ + point_t p; + box_t content; + point_t doc; + + p = wuss_get_pointer(window->wuss); + wuss__content_box(window, &content); + doc.x = p.x - content.x0 + window->scroll.x; + doc.y = p.y - content.y0 + window->scroll.y; + + return wuss__icon_hit_test(window, doc) == icon; +} + +/* True if the wuss pointer sits over the submenu-arrow gutter of row `icon` + * in `window`: the pointer is within the row vertically and inside the + * rightmost WUSS_MENU_ARROW_W pixels of it. A submenu opens only from here, + * so re-entering the parent anywhere else closes the child. */ +static int wuss__pointer_over_row_arrow(wuss_window_t *window, + const wuss_icon_t *icon) +{ + point_t p; + box_t content; + box_t bbox; + point_t doc; + + p = wuss_get_pointer(window->wuss); + wuss__content_box(window, &content); + doc.x = p.x - content.x0 + window->scroll.x; + doc.y = p.y - content.y0 + window->scroll.y; + + wuss_icon_get_bbox(icon, &bbox); + + return doc.y >= bbox.y0 && doc.y < bbox.y1 + && doc.x >= bbox.x1 - WUSS_MENU_ARROW_W && doc.x < bbox.x1; +} + +/* End the pick flash on `self` now: leave the flashed row un-highlit unless the + * pointer is still over it (on an ADJUST re-pick no MOUSE_MOVE follows to bring + * the highlight back), then deliver the MENU_SELECT the flash stands in for -- + * tearing the chain down first unless the pick was an ADJUST (keep_open). On a + * non-keep_open return `self` has been freed. Callers guard against calling + * this with no flash armed. */ +static void wuss__menu_flash_finish(struct wuss__menu *self) +{ + struct wuss__menu *root; + wuss_event_t sel; + wuss_icon_t *row = self->icons[self->flash.index]; + wuss_task_t *owner = self->flash.owner; + const wuss_menu_t *menu = self->flash.menu; + int index = self->flash.index; + wuss_button_t button = self->flash.button; + int keep_open = self->flash.keep_open; + int on; + + self->flash.frames = 0; + + on = keep_open && wuss__pointer_over_icon(self->window, row); + wuss__icon_set_state(row, wuss_ICON_STATE_HOVERED, on); + wuss__icon_invalidate(row); + + if (!keep_open) + { + root = self; + while (root->parent != NULL) + root = root->parent; + + root->wuss->menu_chain = NULL; + wuss__menu_close_from(root); /* frees `self` */ + } + + sel.kind = wuss_EVENT_MENU_SELECT; + sel.data.menu_select.menu = menu; + sel.data.menu_select.index = index; + sel.data.menu_select.button = button; + (void) wuss__deliver(owner, NULL, &sel); +} + +/* Advance a running pick flash by one IDLE frame; hand off to + * wuss__menu_flash_finish when it runs out. `self` must be a real + * (non-borrowed) level. */ +static void wuss__menu_flash_step(struct wuss__menu *self) +{ + wuss_icon_t *row; + + if (self->flash.frames <= 0) + return; + + row = self->icons[self->flash.index]; + + self->flash.frames--; + + if (self->flash.frames == 0) + { + wuss__menu_flash_finish(self); /* delivers MENU_SELECT; may free `self` */ + return; + } + + /* toggle the highlight at each period boundary; the pick frame already did + * the first (off) blink, so phase here starts the row coming back on */ + if (self->flash.frames % WUSS_MENU_FLASH_PERIOD == 0) + { + int on = ((self->flash.frames / WUSS_MENU_FLASH_PERIOD) & 1) == 0; + + wuss__icon_set_state(row, wuss_ICON_STATE_HOVERED, on); + wuss__icon_invalidate(row); + } +} + +/* ----------------------------------------------------------------------- */ + +/* Measure the menu, create its borderless window and one MENU_ENTRY icon per + * item, and hang the node off *out. */ +static result_t wuss__menu_spawn(wuss_t *wuss, + wuss_task_t *owner, + const wuss_menu_t *menu, + point_t at, + struct wuss__menu *parent, + struct wuss__menu **out) +{ + struct wuss__menu *node; + wuss_task_t *menu_task; + wuss_icon_spec_t *specs; + wuss_icon_t **made; + result_t rc; + int fh; + int pitch; + int sep_h; + int y; + int widest; + int width, height; + int doc_h; + int max_h; + int i; + int ndashed; + int nspecs; + int s; + int outline_px; + int titlebar_height; + point_t carve; + wuss_window_flags_t menu_flags; + size2d_t doc; + size2d_t min_doc; + box_t content; + + assert(wuss != NULL); + assert(menu != NULL); + assert(wuss->fonts[0] != NULL); + + if (menu->nitems <= 0) + return result_WUSS_BAD_ICON; + + menu_task = wuss__menu_task(wuss); + if (menu_task == NULL) + return result_OOM; + + menu_flags = wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK + | wuss_WINDOW_NO_TOGGLE_SIZE + | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL + | wuss_WINDOW_NO_RESIZE; + + outline_px = wuss__outline_px_for(menu_flags); + titlebar_height = wuss__titlebar_height_for(wuss, menu_flags); + + bmfont_get_info(wuss->fonts[0], NULL, &fh); + pitch = fh + 2 * WUSS_MENU_ROW_PAD; + sep_h = 2 * WUSS_MENU_ROW_PAD; + + ndashed = 0; + for (i = 0; i < menu->nitems; i++) + if (menu->items[i].flags & wuss_MENU_ITEM_DASHED) + ndashed++; + nspecs = menu->nitems + ndashed; + + widest = 0; + for (i = 0; i < menu->nitems; i++) + { + const char *text; + int len; + int split; + bmfont_width_t w; + + text = menu->items[i].text ? menu->items[i].text : ""; + len = (int) strlen(text); + if (len == 0) + continue; /* empty label (bare rule row); nothing to measure */ + if (bmfont_measure(wuss->fonts[0], text, len, + INT_MAX, &split, &w) == result_OK && (int) w > widest) + widest = (int) w; + } + + width = WUSS_MENU_TICK_W + widest + WUSS_MENU_TEXT_PAD + WUSS_MENU_ARROW_W; + + /* every item is a full row now; a dashed item also gets a sep_h rule above. + * doc_h is the whole menu; `height` is what the window actually shows. When + * the menu is taller than the space the screen leaves for it, cap `height` + * and give the window a real vertical scrollbar -- the icon draw and hit + * test already honour window->scroll, so the rows just move under it. */ + doc_h = menu->nitems * pitch + ndashed * sep_h; + + max_h = wuss->scr->size.h - 2 * outline_px - titlebar_height; + + if (doc_h > max_h) + { + height = max_h; + menu_flags &= (wuss_window_flags_t) ~wuss_WINDOW_NO_VSCROLL; + } + else + { + height = doc_h; + } + + wuss__furniture_carve_for(menu_flags, wuss__button_size_for(wuss, menu_flags), + &carve); + + /* Nudge onto the screen where it can be, but keep the top-left on screen. + * `at` is the content top-left; the window's visible box also spans the + * outline on all four sides, the titlebar above and (for a scrolling menu) + * the scrollbar carve on the right, so clamp against that whole extent -- + * otherwise wuss_window_create's own on-screen size clamp trims the content + * and the last row (e.g. a trailing "Quit") is cropped. */ + if (at.x + width + outline_px + carve.x > wuss->scr->size.w) + at.x = wuss->scr->size.w - width - outline_px - carve.x; + if (at.y + height + outline_px > wuss->scr->size.h) + at.y = wuss->scr->size.h - height - outline_px; + if (at.x - outline_px < 0) + at.x = outline_px; + if (at.y - outline_px - titlebar_height < 0) + at.y = outline_px + titlebar_height; + + node = wuss__malloc(wuss, sizeof(*node)); + if (node == NULL) + return result_OOM; + + node->icons = wuss__malloc(wuss, (size_t) menu->nitems * sizeof(*node->icons)); + specs = wuss__malloc(wuss, (size_t) nspecs * sizeof(*specs)); + made = wuss__malloc(wuss, (size_t) nspecs * sizeof(*made)); + if (node->icons == NULL || specs == NULL || made == NULL) + { + wuss__free(wuss, made); + wuss__free(wuss, specs); + wuss__free(wuss, node->icons); + wuss__free(wuss, node); + return result_OOM; + } + memset(node->icons, 0, (size_t) menu->nitems * sizeof(*node->icons)); + + node->wuss = wuss; + node->owner = owner; + node->window = NULL; + node->menu = menu; + node->parent = parent; + node->child = NULL; + node->open_index = -1; + node->borrowed = 0; + memset(&node->flash, 0, sizeof(node->flash)); + + /* One MENU_ENTRY icon per item, in item order, preceded by an inert + * wuss_ICON_TYPE_RULE icon for each dashed item. specs[] therefore holds + * nspecs entries; s walks it while i walks the items. */ + y = 0; + s = 0; + for (i = 0; i < menu->nitems; i++) + { + const wuss_menu_item_t *item; + wuss_icon_flags_t flags; + + item = &menu->items[i]; + flags = wuss_ICON_FLAGS_NONE; + + if (item->flags & wuss_MENU_ITEM_DASHED) + { + flags |= wuss_ICON_FLAGS_SEPARATOR; + + specs[s].type = wuss_ICON_TYPE_RULE; + specs[s].bbox.x0 = 0; + specs[s].bbox.y0 = y; + specs[s].bbox.x1 = width; + specs[s].bbox.y1 = y + sep_h; + specs[s].text = ""; + specs[s].fg = wuss_COLOUR_BLACK; + specs[s].bg = wuss_NO_BACKGROUND; + specs[s].swatch = wuss_NO_BACKGROUND; + specs[s].flags = wuss_ICON_FLAGS_NONE; + s++; + y += sep_h; + } + + if (item->flags & wuss_MENU_ITEM_DISABLED) + flags |= wuss_ICON_FLAGS_DISABLED; + if (item->submenu != NULL || item->window != NULL) + flags |= wuss_ICON_FLAGS_SUBMENU; + if (item->flags & wuss_MENU_ITEM_SWATCH) + flags |= wuss_ICON_FLAGS_SWATCH; + + specs[s].type = wuss_ICON_TYPE_MENU_ENTRY; + specs[s].bbox.x0 = 0; + specs[s].bbox.y0 = y; + specs[s].bbox.x1 = width; + specs[s].bbox.y1 = y + pitch; + specs[s].text = item->text ? item->text : ""; + specs[s].fg = wuss_COLOUR_BLACK; + specs[s].bg = wuss_NO_BACKGROUND; + specs[s].swatch = (item->flags & wuss_MENU_ITEM_SWATCH) ? item->swatch + : wuss_NO_BACKGROUND; + specs[s].flags = flags; + s++; + + y += pitch; + } + + /* doc spans the whole menu so the window can scroll it; min_doc is what the + * window shows, so its own resize floor never exceeds what fits. */ + doc = SIZE2D(width, doc_h); + min_doc = SIZE2D(width, height); + + /* `at` is the content top-left, already clamped on screen above. Create the + * window there directly -- creating it elsewhere and wuss_window_move-ing it + * afterwards would blit its not-yet-rendered pixels and leave the titlebar + * unpainted. */ + content.x0 = at.x; + content.y0 = at.y; + content.x1 = at.x + width; + content.y1 = at.y + height; + + rc = wuss_window_create(menu_task, &content, + menu->title ? menu->title : "", + menu_flags, + wuss_BACKDROP_COLOUR(wuss_COLOUR_WHITE), + doc, min_doc, &node->window); + if (rc != result_OK) + { + wuss__free(wuss, made); + wuss__free(wuss, specs); + wuss__free(wuss, node->icons); + wuss__free(wuss, node); + return rc; + } + + rc = wuss_icon_create_array(node->window, specs, nspecs, made); + wuss__free(wuss, specs); + if (rc != result_OK) + { + wuss__free(wuss, made); + wuss_window_close(node->window); + wuss__free(wuss, node->icons); + wuss__free(wuss, node); + return rc; + } + + /* keep only the entry handles, item-indexed; the rule icons stay owned by + * the window and need no further handling. Walk made[] with the same + * rule-then-entry interleave as the specs loop. */ + s = 0; + for (i = 0; i < menu->nitems; i++) + { + if (menu->items[i].flags & wuss_MENU_ITEM_DASHED) + s++; + node->icons[i] = made[s]; + s++; + + if (menu->items[i].flags & wuss_MENU_ITEM_TICKED) + wuss_icon_set_selected(node->icons[i], 1); + } + wuss__free(wuss, made); + + *out = node; + return result_OK; +} + +/* ----------------------------------------------------------------------- */ + +result_t wuss_menu_open(wuss_task_t *task, + const wuss_menu_t *menu, + point_t at, + wuss_menu_handle_t *out) +{ + struct wuss__menu *root; + wuss_t *wuss; + result_t rc; + + assert(task != NULL); + assert(menu != NULL); + + wuss = task->wuss; + + if (wuss->menu_chain != NULL) + { + wuss__menu_close_from(wuss->menu_chain); + wuss->menu_chain = NULL; + } + + /* RISC OS convention: the pointer opens the menu sitting a little inside its + * first item, not on the top-left corner. Shift the content top-left up and + * left so `at` (the pointer) lands over row 0. */ + at.x -= WUSS_MENU_TICK_W; + at.y -= WUSS_MENU_ROW_PAD; + + rc = wuss__menu_spawn(wuss, task, menu, at, NULL, &root); + if (rc != result_OK) + return rc; + + wuss->menu_chain = root; + + /* A menu is opened from a task's MOUSE_DOWN handler; the matching MOUSE_UP is + * still to come and would land on the fresh menu's row 0. Mark it to be + * eaten. wuss_mouse_click clears this on the next MOUSE_UP whether or not it + * hit the menu. */ + wuss->menu_eat_up = 1; + + if (out != NULL) + *out = root; + return result_OK; +} + +void wuss_menu_close(wuss_menu_handle_t handle) +{ + struct wuss__menu *root; + + if (handle == NULL) + return; + + root = handle; + while (root->parent != NULL) + root = root->parent; + + if (root->wuss->menu_chain != root) + return; /* stale handle */ + + root->wuss->menu_chain = NULL; + wuss__menu_close_from(root); +} + +int wuss_menu_is_open(wuss_menu_handle_t handle) +{ + struct wuss__menu *root; + + if (handle == NULL) + return 0; + + root = handle; + while (root->parent != NULL) + root = root->parent; + + return root->wuss->menu_chain == root; +} + +void wuss_menu_set_ticked(wuss_menu_handle_t handle, + const wuss_menu_t *menu, + int index) +{ + struct wuss__menu *root; + struct wuss__menu *node; + int i; + + if (handle == NULL || menu == NULL) + return; + + root = handle; + while (root->parent != NULL) + root = root->parent; + + if (root->wuss->menu_chain != root) + return; /* stale handle */ + + for (node = root; node != NULL; node = node->child) + if (node->menu == menu) + break; + if (node == NULL) + return; /* menu is not a level of this chain */ + + for (i = 0; i < menu->nitems; i++) + wuss_icon_set_selected(node->icons[i], i == index); +} + +/* ----------------------------------------------------------------------- */ + +int wuss__menu_click_outside(wuss_t *wuss, const wuss_window_t *hit) +{ + struct wuss__menu *node; + + if (wuss->menu_chain == NULL) + return 0; + + for (node = wuss->menu_chain; node != NULL; node = node->child) + { + if (node->window == hit) + return 0; + } + + wuss__menu_close_from(wuss->menu_chain); + wuss->menu_chain = NULL; + return 1; +} diff --git a/libraries/wuss/mouse-click.c b/libraries/wuss/mouse-click.c deleted file mode 100644 index 88ff9c58..00000000 --- a/libraries/wuss/mouse-click.c +++ /dev/null @@ -1,145 +0,0 @@ -/* mouse-click.c -- wuss - minimal window manager */ - -#include "impl.h" - -result_t wuss_mouse_click(wuss_t *wuss, - point_t p, - wuss_button_t button, - wuss_mouse_action_t action, - wuss_window_t **hit) -{ - wuss_window_t *win; - wuss_furniture_region_t region; - wuss_event_t event; - int x, y; - - x = p.x; - y = p.y; - - if (action == wuss_MOUSE_UP && wuss->furniture.dragging != NULL) - { - win = wuss->furniture.dragging; - if (hit != NULL) - *hit = win; - wuss->furniture.dragging = NULL; - wuss->furniture.drag_kind = wuss_FURNITURE_DRAG_NONE; - - return result_OK; - } - - win = wuss__window_at(wuss, p); - if (hit != NULL) - *hit = win; - - if (win == NULL) - return result_OK; - - region = wuss__furniture_hit_test(win, (point_t) { x, y }); - - if (region == wuss_FURNITURE_CLOSE && - action == wuss_MOUSE_DOWN && - button == wuss_BUTTON_SELECT) - { - if (win->task.handle == NULL) - return result_OK; - - event.kind = wuss_EVENT_CLOSE; - return win->task.handle(win, &event, win->task.task_data); - } - - if (region == wuss_FURNITURE_BACK && action == wuss_MOUSE_DOWN) - { - if (button == wuss_BUTTON_SELECT) - wuss_window_restack(win, wuss_ZORDER_BACK); - else if (button == wuss_BUTTON_ADJUST) - wuss_window_restack(win, wuss_ZORDER_FRONT); - return result_OK; - } - - if (region == wuss_FURNITURE_TOGGLE_SIZE || - region == wuss_FURNITURE_VSCROLL_UP || - region == wuss_FURNITURE_VSCROLL_DOWN || - region == wuss_FURNITURE_HSCROLL_LEFT || - region == wuss_FURNITURE_HSCROLL_RIGHT) - { - if (action == wuss_MOUSE_DOWN && button == wuss_BUTTON_SELECT) - { - switch (region) - { - case wuss_FURNITURE_TOGGLE_SIZE: - wuss__furniture_toggle_size(win); - break; - case wuss_FURNITURE_VSCROLL_UP: - wuss__furniture_scroll_step(win, (point_t) { 0, -WUSS_SCROLL_STEP }); - break; - case wuss_FURNITURE_VSCROLL_DOWN: - wuss__furniture_scroll_step(win, (point_t) { 0, WUSS_SCROLL_STEP }); - break; - case wuss_FURNITURE_HSCROLL_LEFT: - wuss__furniture_scroll_step(win, (point_t) { -WUSS_SCROLL_STEP, 0 }); - break; - case wuss_FURNITURE_HSCROLL_RIGHT: - wuss__furniture_scroll_step(win, (point_t) { WUSS_SCROLL_STEP, 0 }); - break; - default: - break; - } - } - return result_OK; - } - - if (region == wuss_FURNITURE_CLOSE || region == wuss_FURNITURE_TITLE) - { - if (action == wuss_MOUSE_DOWN) - { - box_t content; - - if (button == wuss_BUTTON_SELECT) - wuss_window_restack(win, wuss_ZORDER_FRONT); - - wuss__content_box(win, &content); - wuss->furniture.dragging = win; - wuss->furniture.drag_kind = wuss_FURNITURE_DRAG_MOVE; - wuss->furniture.drag.x = x - content.x0; - wuss->furniture.drag.y = y - content.y0; - } - return result_OK; - } - - if (region == wuss_FURNITURE_RESIZE || - region == wuss_FURNITURE_VSCROLL_WELL || - region == wuss_FURNITURE_HSCROLL_WELL) - { - if (action == wuss_MOUSE_DOWN) - { - point_t scroll; - - if (button == wuss_BUTTON_SELECT) - wuss_window_restack(win, wuss_ZORDER_FRONT); - - wuss_window_get_scroll(win, &scroll); - - wuss->furniture.dragging = win; - wuss->furniture.drag_kind = wuss__furniture_drag_kind(region); - wuss->furniture.drag.x = x; - wuss->furniture.drag.y = y; - wuss->furniture.drag_scroll_start = (region == wuss_FURNITURE_VSCROLL_WELL) ? scroll.y : scroll.x; - } - return result_OK; - } - - if (win->task.handle != NULL) - { - box_t content; - - wuss__content_box(win, &content); - event.kind = wuss_EVENT_MOUSE; - event.data.mouse.action = action; - event.data.mouse.point.x = x - content.x0 + win->scroll.x; - event.data.mouse.point.y = y - content.y0 + win->scroll.y; - event.data.mouse.button = button; - return win->task.handle(win, &event, win->task.task_data); - } - - return result_OK; -} diff --git a/libraries/wuss/mouse-move.c b/libraries/wuss/mouse-move.c deleted file mode 100644 index 859bd5c5..00000000 --- a/libraries/wuss/mouse-move.c +++ /dev/null @@ -1,67 +0,0 @@ -/* mouse-move.c -- wuss - minimal window manager */ - -#include "impl.h" - -result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit) -{ - wuss_window_t *win; - int x, y; - - x = p.x; - y = p.y; - - if (wuss->furniture.dragging != NULL) - { - win = wuss->furniture.dragging; - if (hit != NULL) - *hit = win; - - switch (wuss->furniture.drag_kind) - { - case wuss_FURNITURE_DRAG_RESIZE: - wuss__furniture_drag_resize(win, (point_t) { x, y }); - break; - - case wuss_FURNITURE_DRAG_VSCROLL_SAUSAGE: - wuss__furniture_drag_sausage(win, y - wuss->furniture.drag.y, wuss->furniture.drag_scroll_start, 0); - break; - - case wuss_FURNITURE_DRAG_HSCROLL_SAUSAGE: - wuss__furniture_drag_sausage(win, x - wuss->furniture.drag.x, wuss->furniture.drag_scroll_start, 1); - break; - - case wuss_FURNITURE_DRAG_MOVE: - default: - wuss_window_move(win, (point_t) { x - wuss->furniture.drag.x, y - wuss->furniture.drag.y }); - break; - } - - return result_OK; - } - - win = wuss__window_at(wuss, p); - if (hit != NULL) - *hit = win; - - if (win == NULL) - return result_OK; - - if (wuss__furniture_hit_test(win, (point_t) { x, y }) != wuss_FURNITURE_CONTENT) - return result_OK; - - if (win->task.handle != NULL) - { - box_t content; - wuss_event_t event; - - wuss__content_box(win, &content); - event.kind = wuss_EVENT_MOUSE; - event.data.mouse.action = wuss_MOUSE_MOVE; - event.data.mouse.point.x = x - content.x0 + win->scroll.x; - event.data.mouse.point.y = y - content.y0 + win->scroll.y; - event.data.mouse.button = wuss_BUTTON_SELECT; - return win->task.handle(win, &event, win->task.task_data); - } - - return result_OK; -} diff --git a/libraries/wuss/redraw.c b/libraries/wuss/redraw.c deleted file mode 100644 index 574aec77..00000000 --- a/libraries/wuss/redraw.c +++ /dev/null @@ -1,127 +0,0 @@ -/* redraw.c -- wuss - minimal window manager */ - -#include "impl.h" - -static void redraw_window(wuss_t *wuss, - wuss_window_t *win, - const box_t *full, - result_t *rc) -{ - box_t clipped; - box_t visible_clipped; - box_t content; - box_t pieces[WUSS_MAX_INVALIDATE_PIECES]; - int npieces, i; - - if (box_intersection(&win->visible, full, &visible_clipped)) - return; /* offscreen */ - - wuss__furniture_draw(wuss, win, full); - - wuss__content_box(win, &content); - if (box_intersection(&content, full, &clipped)) - return; - - /* skip (or shrink to) whatever part of "clipped" isn't hidden behind a - * higher window -- otherwise every dirty rect raised by a window on top - * (e.g. a moving ball) would also repaint whatever it's covering below, - * however expensive that redraw is, for pixels nobody will ever see */ - npieces = wuss__clip_to_visible(win, &clipped, pieces); - - for (i = 0; i < npieces; i++) - { - wuss->scr->clip = pieces[i]; - - if (win->task.bg != wuss_NO_BACKGROUND) - screen_draw_rect(wuss->scr, - content.x0, content.y0, - content.x1 - content.x0, content.y1 - content.y0, - wuss->palette[win->task.bg]); - - if (win->task.handle != NULL) - { - wuss_event_t event; - result_t crc; - - event.kind = wuss_EVENT_REDRAW; - event.data.redraw.scr = wuss->scr; - event.data.redraw.content = &pieces[i]; - event.data.redraw.bounds = &content; - event.data.redraw.scroll = win->scroll; - crc = win->task.handle(win, &event, win->task.task_data); - if (crc != result_OK) - *rc = crc; - } - } -} - -/* draws back-to-front (list head = topmost window drawn last) without - * recursing per window, so stack use stays flat regardless of window count; - * ponytail: O(n^2) walk, fine while window counts stay small, switch to an - * array/vector pass if that stops being true */ -static void redraw_from(wuss_t *wuss, - list_t *head, - const box_t *full, - result_t *rc) -{ - const list_t *stop; - - stop = NULL; - for (;;) - { - list_t *e, *last; - - last = NULL; - for (e = head; e != stop; e = e->next) - last = e; - - if (last == NULL) - break; - - redraw_window(wuss, (wuss_window_t *) last, full, rc); - stop = last; - } -} - -result_t wuss_redraw(wuss_t *wuss) -{ - box_t full; - result_t rc; - - full.x0 = 0; - full.y0 = 0; - full.x1 = wuss->scr->width; - full.y1 = wuss->scr->height; - - rc = result_OK; - redraw_from(wuss, wuss->z_order.next, &full, &rc); - - /* redraw_window narrows wuss->scr->clip to whatever it last painted; - * reset it so anything drawing after this redraw (not least - * screen_copy_rect, used for window-drag blitting) sees the whole - * screen rather than that leftover sliver. */ - box_reset(&wuss->scr->clip); - - wuss->ndirty = 0; - - return rc; -} - -result_t wuss_redraw_dirty(wuss_t *wuss) -{ - result_t rc; - int i; - - if (wuss->ndirty == 0) - return result_OK; - - rc = result_OK; - for (i = 0; i < wuss->ndirty; i++) - redraw_from(wuss, wuss->z_order.next, &wuss->dirty[i], &rc); - - box_reset(&wuss->scr->clip); /* see wuss_redraw's comment on the same call */ - - wuss->ndirty = 0; - - return rc; -} diff --git a/libraries/wuss/scroll.c b/libraries/wuss/scroll.c deleted file mode 100644 index 3900d7b8..00000000 --- a/libraries/wuss/scroll.c +++ /dev/null @@ -1,41 +0,0 @@ -/* scroll.c -- wuss - minimal window manager */ - -#include "impl.h" - -result_t wuss_scroll(wuss_t *wuss, point_t p, int delta, wuss_window_t **hit) -{ - wuss_window_t *win; - box_t titlebar; - int x, y; - - x = p.x; - y = p.y; - - win = wuss__window_at(wuss, p); - if (hit != NULL) - *hit = win; - - if (win == NULL) - return result_OK; - - wuss__titlebar_box(win, &titlebar); - if (box_contains_point(&titlebar, x, y)) - return result_OK; - - wuss__furniture_scroll_step(win, (point_t) { 0, delta }); - - if (win->task.handle != NULL) - { - box_t content; - wuss_event_t event; - - wuss__content_box(win, &content); - event.kind = wuss_EVENT_SCROLL; - event.data.scroll.point.x = x - content.x0 + win->scroll.x; - event.data.scroll.point.y = y - content.y0 + win->scroll.y; - event.data.scroll.delta = delta; - return win->task.handle(win, &event, win->task.task_data); - } - - return result_OK; -} diff --git a/libraries/wuss/task-start.c b/libraries/wuss/task-start.c deleted file mode 100644 index c7000704..00000000 --- a/libraries/wuss/task-start.c +++ /dev/null @@ -1,18 +0,0 @@ -/* task-start.c -- wuss - minimal window manager */ - -#include - -#include "wuss/window.h" - -wuss_task_t wuss_task_start(wuss_event_fn_t *handle, - void *task_data, - wuss_colour_t bg) -{ - wuss_task_t task; - - task.handle = handle; - task.task_data = task_data; - task.bg = bg; - - return task; -} diff --git a/libraries/wuss/task-stop.c b/libraries/wuss/task-stop.c deleted file mode 100644 index 2945819f..00000000 --- a/libraries/wuss/task-stop.c +++ /dev/null @@ -1,15 +0,0 @@ -/* task-stop.c -- wuss - minimal window manager */ - -#include "impl.h" - -result_t wuss_task_stop(wuss_window_t *window) -{ - wuss_event_t event; - - if (window->task.handle == NULL) - return result_OK; - - event.kind = wuss_EVENT_QUIT; - - return window->task.handle(window, &event, window->task.task_data); -} diff --git a/libraries/wuss/test/tasks/ball.c b/libraries/wuss/test/tasks/ball.c index 581f4940..ce73ddd2 100644 --- a/libraries/wuss/test/tasks/ball.c +++ b/libraries/wuss/test/tasks/ball.c @@ -1,6 +1,8 @@ -/* ball.c -- wuss test - bouncing ball task */ +/* wuss/test/tasks/ball.c -- bouncing ball task */ -#ifdef USE_SDL +#ifdef WUSS_APP + +#include #ifdef FORTIFY #include "fortify/fortify.h" @@ -8,41 +10,87 @@ #include "base/utils.h" #include "framebuf/palettes.h" +#include "framebuf/screen.h" #include "geom/box.h" #include "ball.h" -result_t ball_create(wuss_t *wuss, const colour_t *palette, ball_task_t *task) +#define BALL_BASE_RADIUS 8 /* +/-50% at spawn -> 4..12 */ + +/* a fresh radius in [BALL_BASE_RADIUS/2, BALL_BASE_RADIUS*3/2] */ +static int ball_random_radius(void) +{ + return BALL_BASE_RADIUS / 2 + rand() % (BALL_BASE_RADIUS + 1); +} + +/* a fresh fully-opaque colour, red channel is at least 0x40 so it stays + * visible against the red background */ +static colour_t ball_random_colour(void) +{ + int i; + + i = 0x40 + rand() % 0xC0; + return colour_rgb(0xFF, i, i); +} + +/* Invalidation box (virtual content space, as ball positions are held) + * covering a ball -- or its swept range -- whose centre spans + * [vx0,vx1] x [vy0,vy1]. wuss_window_invalidate maps this to the screen and + * applies scroll, so this must not. The circle occupies [c-radius,c+radius] + * inclusive, i.e. 2*radius+1 pixels; box_t is half-open so x1/y1 get the + * extra 1. */ +static box_t ball_local_box(int vx0, int vy0, int vx1, int vy1, int radius) { - wuss_task_t delegate; - box_t box; + box_t local; - task->bg = palette[palette_PICO8_RED]; - task->ball = palette[palette_PICO8_WHITE]; + local.x0 = vx0 - radius; + local.y0 = vy0 - radius; + local.x1 = vx1 + radius + 1; + local.y1 = vy1 + radius + 1; + + return local; +} + +result_t ball_create(wuss_t *wuss, ball_task_t *task) +{ + wuss_task_t *delegate; + wuss_task_desc_t delegate_desc; + result_t rc; + + task->bg = colour_rgb(0xFF, 0x00, 0x00); task->nballs = 1; task->balls[0].x = 50; task->balls[0].y = 50; task->balls[0].dx = 3; task->balls[0].dy = 2; - task->balls[0].radius = 8; - - delegate = wuss_task_start(ball_handle, task, wuss_NO_BACKGROUND); /* ball_redraw paints its own background every frame */ - box = (box_t) BOX_POS_SIZE(20, 20, 200, 160); - - return wuss_window_create(wuss, - &box, - "Bouncing Ball", - wuss_WINDOW_NONE, - &delegate, - box.x1 - box.x0, - box.y1 - box.y0, - &task->window); -} - -void ball_destroy(ball_task_t *task) -{ - wuss_window_close(task->window); + task->balls[0].radius = ball_random_radius(); + task->balls[0].colour = ball_random_colour(); + + /* ball_redraw paints its own background every frame */ + delegate_desc.handle = ball_handle; + delegate_desc.task_data = task; + delegate_desc.name = "ball"; + rc = wuss_task_create(wuss, &delegate_desc, &delegate); + if (rc != result_OK) + { + free(task); /* nothing registered yet; the spawner will not free it */ + return rc; + } + wuss_task_set_autoclose(delegate, 1); + + rc = wuss_window_create_placed(delegate, + SIZE2D(200, 160), + "Bouncing Ball", + wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(200, 160), + SIZE2D(0, 0), + &task->window); + if (rc != result_OK) + wuss_task_destroy(delegate); /* unregister; its QUIT frees the task block */ + + return rc; } static result_t ball_redraw(const wuss_event_t *event, void *task_data) @@ -50,7 +98,8 @@ static result_t ball_redraw(const wuss_event_t *event, void *task_data) ball_task_t *bc; screen_t *scr; const box_t *content, *bounds; - int i, sx, sy; + int sx, sy; + int i; bc = task_data; @@ -60,9 +109,7 @@ static result_t ball_redraw(const wuss_event_t *event, void *task_data) sx = event->data.redraw.scroll.x; sy = event->data.redraw.scroll.y; - screen_draw_rect(scr, content->x0, content->y0, - content->x1 - content->x0, - content->y1 - content->y0, + screen_fill_rect(scr, content->x0, content->y0, box_size(content), bc->bg); for (i = 0; i < bc->nballs; i++) @@ -71,8 +118,10 @@ static result_t ball_redraw(const wuss_event_t *event, void *task_data) b = &bc->balls[i]; - screen_draw_rect(scr, bounds->x0 - sx + b->x - b->radius, bounds->y0 - sy + b->y - b->radius, - b->radius * 2, b->radius * 2, bc->ball); + screen_fill_circle(scr, bounds->x0 - sx + b->x, + bounds->y0 - sy + b->y, + b->radius, + b->colour); } return result_OK; @@ -87,48 +136,43 @@ static result_t ball_mouse(wuss_window_t *window, { ball_task_t *bc; box_t local; - point_t scroll; bc = task_data; + NOT_USED(window); + if (action != wuss_MOUSE_DOWN) return result_OK; - wuss_window_get_scroll(window, &scroll); - - if (button == wuss_BUTTON_SELECT) - { - ball_t *b; - - if (bc->nballs >= BALL_MAX) - return result_OK; - - b = &bc->balls[bc->nballs++]; - b->x = x + scroll.x; - b->y = y + scroll.y; - b->dx = (bc->nballs & 1) ? 3 : -3; - b->dy = (bc->nballs & 2) ? 2 : -2; - b->radius = 8; - - local.x0 = b->x - b->radius - scroll.x; - local.y0 = b->y - b->radius - scroll.y; - local.x1 = b->x + b->radius - scroll.x; - local.y1 = b->y + b->radius - scroll.y; - wuss_window_invalidate(bc->window, &local); - } - else if (button == wuss_BUTTON_ADJUST) + if (button & (wuss_BUTTON_SELECT | wuss_BUTTON_ADJUST)) { ball_t *b; - - if (bc->nballs <= 1) - return result_OK; /* keep at least one ball on screen */ - - b = &bc->balls[--bc->nballs]; - - local.x0 = b->x - b->radius - scroll.x; - local.y0 = b->y - b->radius - scroll.y; - local.x1 = b->x + b->radius - scroll.x; - local.y1 = b->y + b->radius - scroll.y; + + if (button & wuss_BUTTON_SELECT) + { + if (bc->nballs >= BALL_MAX) + return result_OK; + + /* x,y already arrive in virtual content space, as ball positions are + * held; only the invalidation boxes below need the scroll offset taking + * back off to reach window-local coordinates. */ + b = &bc->balls[bc->nballs++]; + b->x = x; + b->y = y; + b->dx = (bc->nballs & 1) ? 3 : -3; + b->dy = (bc->nballs & 2) ? 2 : -2; + b->radius = ball_random_radius(); + b->colour = ball_random_colour(); + } + else if (button & wuss_BUTTON_ADJUST) + { + if (bc->nballs <= 1) + return result_OK; /* keep at least one ball on screen */ + + b = &bc->balls[--bc->nballs]; + } + + local = ball_local_box(b->x, b->y, b->x, b->y, b->radius); wuss_window_invalidate(bc->window, &local); } @@ -169,10 +213,9 @@ static result_t ball_idle(void *task_data) if (b->y - b->radius < scroll.y) { b->y = scroll.y + b->radius; b->dy = -b->dy; } else if (b->y + b->radius > scroll.y + height){ b->y = scroll.y + height - b->radius; b->dy = -b->dy; } - local.x0 = MIN(old_x, b->x) - b->radius - scroll.x; - local.y0 = MIN(old_y, b->y) - b->radius - scroll.y; - local.x1 = MAX(old_x, b->x) + b->radius - scroll.x; - local.y1 = MAX(old_y, b->y) + b->radius - scroll.y; + local = ball_local_box(MIN(old_x, b->x), MIN(old_y, b->y), + MAX(old_x, b->x), MAX(old_y, b->y), + b->radius); wuss_window_invalidate(bc->window, &local); } @@ -180,7 +223,7 @@ static result_t ball_idle(void *task_data) return result_OK; } -result_t ball_handle(wuss_window_t *window, +result_t ball_handle(wuss_window_t *window, const wuss_event_t *event, void *task_data) { @@ -200,9 +243,8 @@ result_t ball_handle(wuss_window_t *window, case wuss_EVENT_IDLE: return ball_idle(task_data); - case wuss_EVENT_CLOSE: - wuss_window_close(window); - bc->window = NULL; + case wuss_EVENT_QUIT: + free(bc); /* task_data was calloc'd per instance by the spawner */ return result_OK; default: @@ -210,4 +252,4 @@ result_t ball_handle(wuss_window_t *window, } } -#endif /* USE_SDL */ +#endif /* WUSS_APP */ diff --git a/libraries/wuss/test/tasks/ball.h b/libraries/wuss/test/tasks/ball.h index 3d17ab1e..a1b8d9f2 100644 --- a/libraries/wuss/test/tasks/ball.h +++ b/libraries/wuss/test/tasks/ball.h @@ -1,9 +1,9 @@ -/* ball.h -- wuss test - bouncing ball task */ +/* wuss/test/tasks/ball.h -- bouncing ball task */ #ifndef TASKS_BALL_H #define TASKS_BALL_H -#ifdef USE_SDL +#ifdef WUSS_APP #include "framebuf/colour.h" #include "wuss/window.h" @@ -13,9 +13,10 @@ /* one bouncing ball, centre in local content coords */ typedef struct ball { - int x, y; - int dx, dy; - int radius; + int x, y; + int dx, dy; + int radius; + colour_t colour; } ball_t; @@ -26,20 +27,18 @@ ball_t; typedef struct ball_task { wuss_window_t *window; - colour_t bg, ball; + colour_t bg; ball_t balls[BALL_MAX]; int nballs; } ball_task_t; -wuss_event_fn_t ball_handle; +wuss_window_fn_t ball_handle; -/* create the bouncing-ball window against the given wuss instance */ -result_t ball_create(wuss_t *wuss, const colour_t *palette, ball_task_t *task); +/* create the bouncing-ball window against the given wuss instance; "task" is a + * per-instance block owned by the window and freed when it closes */ +result_t ball_create(wuss_t *wuss, ball_task_t *task); -/* destroy the bouncing-ball window created by ball_create */ -void ball_destroy(ball_task_t *task); - -#endif /* USE_SDL */ +#endif /* WUSS_APP */ #endif /* TASKS_BALL_H */ diff --git a/libraries/wuss/test/tasks/blank.c b/libraries/wuss/test/tasks/blank.c index a976a1ad..9986416a 100644 --- a/libraries/wuss/test/tasks/blank.c +++ b/libraries/wuss/test/tasks/blank.c @@ -1,6 +1,8 @@ -/* blank.c -- wuss test - colour-cycling task */ +/* wuss/test/tasks/blank.c -- colour-cycling task */ -#ifdef USE_SDL +#ifdef WUSS_APP + +#include #ifdef FORTIFY #include "fortify/fortify.h" @@ -15,31 +17,40 @@ #define BLANK_CYCLE_FRAMES 30 /* colour advances every half-second at 60fps */ -result_t blank_create(wuss_t *wuss, int npalette, blank_task_t *task) +result_t blank_create(wuss_t *wuss, blank_task_t *task) { - wuss_task_t delegate; - box_t box; + wuss_task_t *delegate; + wuss_task_desc_t delegate_desc; + result_t rc; - task->npalette = npalette; - task->index = palette_PICO8_GREEN; + task->npalette = 16; // TODO: Read max palette index from wuss + task->index = 0; task->frame_count = 0; - delegate = wuss_task_start(blank_handle, task, palette_PICO8_GREEN); /* wuss fills the content area itself */ - box = (box_t) BOX_POS_SIZE(260, 60, 200, 160); - - return wuss_window_create(wuss, - &box, - NULL, - wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE, - &delegate, - box.x1 - box.x0, - box.y1 - box.y0, - &task->window); -} + /* wuss fills the content area itself */ + delegate_desc.handle = blank_handle; + delegate_desc.task_data = task; + delegate_desc.name = "blank"; + rc = wuss_task_create(wuss, &delegate_desc, &delegate); + if (rc != result_OK) + { + free(task); /* nothing registered yet; the spawner will not free it */ + return rc; + } + wuss_task_set_autoclose(delegate, 1); + + rc = wuss_window_create_placed(delegate, + SIZE2D(200, 160), + NULL, + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE, + wuss_BACKDROP_COLOUR(task->index), + SIZE2D(200, 160), + SIZE2D(0, 0), + &task->window); + if (rc != result_OK) + wuss_task_destroy(delegate); /* unregister; its QUIT frees the task block */ -void blank_destroy(blank_task_t *task) -{ - wuss_window_close(task->window); + return rc; } static result_t blank_idle(void *task_data) @@ -55,18 +66,23 @@ static result_t blank_idle(void *task_data) bc->frame_count = 0; bc->index = (bc->index + 1) % bc->npalette; - rc = wuss_window_set_background(bc->window, bc->index); + rc = wuss_window_set_background(bc->window, + wuss_BACKDROP_COLOUR(bc->index)); if (rc != result_OK) logf_warning("blank_idle: wuss_window_set_background(%d) failed", bc->index); return rc; } -result_t blank_handle(wuss_window_t *window, +result_t blank_handle(wuss_window_t *window, const wuss_event_t *event, void *task_data) { - NOT_USED(window); + if (event->kind == wuss_EVENT_QUIT) + { + free(task_data); /* calloc'd per instance by the spawner */ + return result_OK; + } if (event->kind != wuss_EVENT_IDLE) return result_OK; @@ -74,4 +90,4 @@ result_t blank_handle(wuss_window_t *window, return blank_idle(task_data); } -#endif /* USE_SDL */ +#endif /* WUSS_APP */ diff --git a/libraries/wuss/test/tasks/blank.h b/libraries/wuss/test/tasks/blank.h index a2586304..34ea5459 100644 --- a/libraries/wuss/test/tasks/blank.h +++ b/libraries/wuss/test/tasks/blank.h @@ -1,11 +1,12 @@ -/* blank.h -- wuss test - colour-cycling task */ +/* wuss/test/tasks/blank.h -- colour-cycling task */ #ifndef TASKS_BLANK_H #define TASKS_BLANK_H -#ifdef USE_SDL +#ifdef WUSS_APP #include "wuss/window.h" +#include "wuss/task.h" #include "wuss/wuss.h" /* window C's task: no redraw callback at all, relying entirely on wuss's @@ -20,14 +21,12 @@ typedef struct blank_task } blank_task_t; -wuss_event_fn_t blank_handle; +wuss_window_fn_t blank_handle; /* create the colour-cycling blank window against the given wuss instance */ -result_t blank_create(wuss_t *wuss, int npalette, blank_task_t *task); +result_t blank_create(wuss_t *wuss, blank_task_t *task); -/* destroy the colour-cycling window created by blank_create */ -void blank_destroy(blank_task_t *task); -#endif /* USE_SDL */ +#endif /* WUSS_APP */ #endif /* TASKS_BLANK_H */ diff --git a/libraries/wuss/test/tasks/chars.c b/libraries/wuss/test/tasks/chars.c new file mode 100644 index 00000000..1db82b14 --- /dev/null +++ b/libraries/wuss/test/tasks/chars.c @@ -0,0 +1,314 @@ +/* wuss/test/tasks/chars.c -- bitmap font glyph grid task with a font picker */ + +#ifdef WUSS_APP + +#include +#include +#include + +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/utils.h" +#include "framebuf/palettes.h" +#include "geom/box.h" +#include "geom/point.h" +#include "io/path.h" +#include "wuss/menu.h" + +#include "chars.h" + +#define CHARS_COLS 16 +#define CHARS_ROWS 16 +#define CHARS_PAD 1 + +/* keep the resources root the task was created with; the picker loads a font + * on demand by leafname and chars_create does not stash it elsewhere. + * ponytail: one demo, one instance at a time -- a file-scope copy is fine. */ +static char chars_resources[256]; + +/* ----------------------------------------------------------------------- */ + +/* content size for the grid at the given font's cell metrics. Each cell + * stacks the index (drawn in the wuss system font) above the glyph. */ +static size2d_t chars_window_size(chars_task_t *task, bmfont_t *font) +{ + int fw, fh, ifw, ifh, cell_w, cell_h; + + bmfont_get_info(font, &fw, &fh); + bmfont_get_info(wuss_get_font(task->wuss), &ifw, &ifh); + cell_w = MAX(fw, ifw * 3) + CHARS_PAD * 2; + cell_h = ifh + fh + CHARS_PAD * 3; + return SIZE2D(cell_w * CHARS_COLS, cell_h * CHARS_ROWS); +} + +/* load fonts[idx] if not already in hand; returns it or NULL on failure. + * name is the menu label for that row -- the font's leafname sans ".png". */ +static bmfont_t *chars_load_font(chars_task_t *task, + int idx, + const char *name) +{ + const char *leaf; + const char *filename; + bmfont_t *font; + result_t rc; + + if (task->fonts[idx] != NULL) + return task->fonts[idx]; + + leaf = path_join_leafname(name, "png"); + filename = path_join_filename(chars_resources, 3, "resources", "bmfonts", + leaf); + + rc = bmfont_create(filename, &font); + if (rc != result_OK) + return NULL; + + task->fonts[idx] = font; + return font; +} + +/* switch the grid to the font at menu row idx (label name), resizing to suit */ +static result_t chars_set_font(chars_task_t *task, int idx, const char *name) +{ + bmfont_t *font; + + if (idx < 0 || idx >= task->nfonts || idx == task->current) + return result_OK; + + font = chars_load_font(task, idx, name); + if (font == NULL) + return result_OK; /* leave the current font in place */ + + task->font = font; + task->current = idx; + + wuss_window_resize(task->window, chars_window_size(task, font)); + wuss_window_invalidate_all(task->window); + return result_OK; +} + +static result_t chars_open_menu(chars_task_t *task) +{ + /* the current font's row is ticked; task->current is -1 for the wuss + * system font, which ticks nothing */ + wuss_fontmenu_set_ticked(task->fontmenu, task->current); + return wuss_menu_open(task->delegate, + wuss_fontmenu_menu(task->fontmenu), + wuss_get_pointer(task->wuss), &task->menu_handle); +} + +/* ----------------------------------------------------------------------- */ + +result_t chars_create(wuss_t *wuss, + const char *resources, + chars_task_t *task) +{ + wuss_task_t *delegate; + wuss_task_desc_t delegate_desc; + result_t rc; + bmfont_t *font; + const char *bmfonts_dir; + const wuss_menu_t *menu; + + font = wuss_get_font(wuss); + if (font == NULL) + { + task->window = NULL; + return result_OK; + } + + strncpy(chars_resources, resources, sizeof(chars_resources) - 1); + chars_resources[sizeof(chars_resources) - 1] = '\0'; + + task->wuss = wuss; + task->font = font; + task->current = -1; /* the wuss system font is none of the picker's */ + task->menu_handle = NULL; + task->fg = colour_rgb(0x00, 0x00, 0x00); + task->mg = colour_rgb(0xBB, 0xBB, 0xBB); + task->bg = colour_rgb(0xFF, 0xFF, 0xFF); + + /* the picker: every ".png" font under resources/bmfonts, sorted, less any + * SYSTEM-class font (e.g. the one wuss draws menu ticks/arrows from) */ + bmfonts_dir = path_join_filename(chars_resources, 2, "resources", "bmfonts"); + rc = wuss_fontmenu_create(&task->fontmenu, bmfonts_dir, "Font", wuss, NULL); + if (rc != result_OK) + { + free(task); /* nothing registered yet; the spawner will not free it */ + return rc; + } + + menu = wuss_fontmenu_menu(task->fontmenu); + task->nfonts = menu->nitems; + task->fonts = calloc((size_t) task->nfonts, sizeof(*task->fonts)); + if (task->nfonts > 0 && task->fonts == NULL) + { + wuss_fontmenu_destroy(task->fontmenu); + free(task); + return result_OOM; + } + + /* chars_redraw paints every cell itself */ + delegate_desc.handle = chars_handle; + delegate_desc.task_data = task; + delegate_desc.name = "chars"; + rc = wuss_task_create(wuss, &delegate_desc, &delegate); + if (rc != result_OK) + { + /* nothing registered yet; the spawner will not free it */ + wuss_fontmenu_destroy(task->fontmenu); + free(task->fonts); + free(task); + return rc; + } + wuss_task_set_autoclose(delegate, 1); + task->delegate = delegate; + + rc = wuss_window_create_placed(delegate, + chars_window_size(task, font), + "Chars", + wuss_WINDOW_NO_HSCROLL, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + chars_window_size(task, font), + SIZE2D(64, 64), + &task->window); + if (rc != result_OK) + wuss_task_destroy(delegate); /* unregister; its QUIT frees the task block */ + + return rc; +} + +static result_t chars_redraw(const wuss_event_t *event, void *task_data) +{ + chars_task_t *cc; + screen_t *scr; + bmfont_t *sysfont; + const box_t *bounds; + int font_width, font_height, sysfont_width, sysfont_height; + int cell_w, cell_h; + int first, count; + int i, sx, sy; + + cc = task_data; + + scr = event->data.redraw.scr; + bounds = event->data.redraw.bounds; + sx = event->data.redraw.scroll.x; + sy = event->data.redraw.scroll.y; + + sysfont = wuss_get_font(cc->wuss); + + bmfont_get_info(cc->font, &font_width, &font_height); + bmfont_get_info(sysfont, &sysfont_width, &sysfont_height); + cell_w = MAX(font_width, sysfont_width * 3) + CHARS_PAD * 2; + cell_h = sysfont_height + font_height + CHARS_PAD * 3; + + first = ' '; /* bmfont glyphs are laid out contiguously starting here */ + count = bmfont_get_count(cc->font); + /* bmfont indexes its glyph table off a plain char, so a byte value above + * CHAR_MAX would index negatively on a signed-char platform. Never draw + * one, however many glyphs the font claims. */ + if (first + count > CHAR_MAX + 1) + count = CHAR_MAX + 1 - first; + + for (i = 0; i < CHARS_COLS * CHARS_ROWS; i++) + { + int col, row, x, y; + char ch; + char label[4]; + point_t pos; + + col = i % CHARS_COLS; + row = i / CHARS_COLS; + x = bounds->x0 - sx + col * cell_w; + y = bounds->y0 - sy + row * cell_h; + + screen_fill_rect(scr, x, y, SIZE2D(cell_w, cell_h), cc->bg); + screen_draw_line(scr, x, y, x + cell_w - 1, y, cc->mg); + screen_draw_line(scr, x, y, x, y + cell_h - 1, cc->mg); + + snprintf(label, 4, "%d", i); + pos.x = x + CHARS_PAD; + pos.y = y + CHARS_PAD; + bmfont_draw(sysfont, scr, label, (int) strlen(label), cc->mg, cc->bg, + &pos, NULL); + + if (i < first || i >= first + count) + continue; /* no glyph for this byte value: leave the cell blank */ + + ch = (char) i; + pos.x = x + CHARS_PAD; + pos.y = y + CHARS_PAD * 2 + sysfont_height; + bmfont_draw(cc->font, scr, &ch, 1, cc->fg, cc->bg, &pos, NULL); + } + + return result_OK; +} + +result_t chars_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) +{ + chars_task_t *cc = task_data; + + NOT_USED(window); + + switch (event->kind) + { + case wuss_EVENT_QUIT: + { + int i; + + for (i = 0; i < cc->nfonts; i++) + if (cc->fonts[i] != NULL) + bmfont_destroy(cc->fonts[i]); + free(cc->fonts); + wuss_fontmenu_destroy(cc->fontmenu); + free(cc); /* calloc'd per instance by the spawner */ + } + return result_OK; + + case wuss_EVENT_MOUSE: + if (event->data.mouse.action == wuss_MOUSE_DOWN && + (event->data.mouse.button & wuss_BUTTON_MENU)) + return chars_open_menu(cc); + return result_OK; + + case wuss_EVENT_MENU_SELECT: + { + const char *name; + result_t rc; + + name = wuss_fontmenu_selected(cc->fontmenu, event); + if (name == NULL) + return result_OK; + + rc = chars_set_font(cc, event->data.menu_select.index, name); + + if (event->data.menu_select.button & wuss_BUTTON_ADJUST) + /* ADJUST keeps the chain open without rebuilding it, so the + * fresh-open tick set in chars_open_menu is now stale on screen; + * retick the still-open chain in place to match cc->current */ + wuss_menu_set_ticked(cc->menu_handle, + wuss_fontmenu_menu(cc->fontmenu), cc->current); + else + /* SELECT has already closed and freed the chain by the time this + * event arrives; the handle is stale, don't touch it */ + cc->menu_handle = NULL; + + return rc; + } + + case wuss_EVENT_REDRAW: + return chars_redraw(event, task_data); + + default: + return result_OK; + } +} + +#endif /* WUSS_APP */ diff --git a/libraries/wuss/test/tasks/chars.h b/libraries/wuss/test/tasks/chars.h new file mode 100644 index 00000000..9fa940fa --- /dev/null +++ b/libraries/wuss/test/tasks/chars.h @@ -0,0 +1,47 @@ +/* wuss/test/tasks/chars.h -- system font glyph grid task */ + +#ifndef TASKS_CHARS_H +#define TASKS_CHARS_H + +#ifdef WUSS_APP + +#include "framebuf/bmfont.h" +#include "framebuf/colour.h" +#include "wuss/component/fontmenu.h" +#include "wuss/task.h" +#include "wuss/window.h" + +/* displays every glyph (0-255) of a bitmap font as a 32x8 grid, so the whole + * font can be eyeballed at a glance. A MENU click on the window opens a + * picker -- a wuss_fontmenu over resources/bmfonts -- that swaps the font in + * place. */ +typedef struct chars_task +{ + wuss_window_t *window; + wuss_task_t *delegate; /* the wuss task backing this window */ + wuss_t *wuss; /* for wuss_get_pointer when opening the + * menu */ + wuss_fontmenu_t *fontmenu; /* the font picker; owns the menu and + * names */ + wuss_menu_handle_t menu_handle; /* the open chain, to re-tick it live on + * an ADJUST pick that keeps it open */ + bmfont_t *font; /* currently shown; == fonts[current] */ + bmfont_t **fonts; /* one slot per menu item, lazily loaded */ + int nfonts; /* length of fonts[]; == menu item count */ + int current; /* index into fonts[], or -1 for sysfont */ + colour_t fg, mg, bg; +} +chars_task_t; + +wuss_window_fn_t chars_handle; + +/* create the glyph-grid window against the given wuss instance; does + * nothing and returns result_OK if wuss has no system font. resources is the + * root the font picker loads bmfonts from (resources/bmfonts/.png). */ +result_t chars_create(wuss_t *wuss, + const char *resources, + chars_task_t *task); + +#endif /* WUSS_APP */ + +#endif /* TASKS_CHARS_H */ diff --git a/libraries/wuss/test/tasks/checker.c b/libraries/wuss/test/tasks/checker.c index 0578ce53..2d284fd2 100644 --- a/libraries/wuss/test/tasks/checker.c +++ b/libraries/wuss/test/tasks/checker.c @@ -1,6 +1,8 @@ -/* checker.c -- wuss test - checkerboard task */ +/* wuss/test/tasks/checker.c -- checkerboard task */ -#ifdef USE_SDL +#ifdef WUSS_APP + +#include #ifdef FORTIFY #include "fortify/fortify.h" @@ -16,58 +18,63 @@ #define CHECKER_BAND_MIN 1 #define CHECKER_BAND_MAX 32 -result_t checker_create(wuss_t *wuss, - const colour_t *palette, - checker_task_t *task) +result_t checker_create(wuss_t*wuss, checker_task_t *task) { - wuss_task_t delegate; - box_t box; + wuss_task_t *delegate; + wuss_task_desc_t delegate_desc; result_t rc; - task->black = palette[palette_PICO8_BLACK]; - task->white = palette[palette_PICO8_WHITE]; + task->black = colour_rgb(0x00, 0x00, 0x00); + task->white = colour_rgb(0xFF, 0xFF, 0xFF); task->pattern = checker_PATTERN_CHECKERBOARD; task->pattern2 = checker_PATTERN_VERTICAL; task->band = CHECKER_BAND_DEFAULT; task->band2 = CHECKER_BAND_DEFAULT; - delegate = wuss_task_start(checker_handle, task, wuss_NO_BACKGROUND); /* checker_redraw paints every pixel itself */ - box = (box_t) BOX_POS_SIZE(440, 300, 160, 160); - - rc = wuss_window_create(wuss, - &box, - "Checker 1", - wuss_WINDOW_NONE, - &delegate, - box.x1 - box.x0, - box.y1 - box.y0, - &task->window); + /* checker_redraw paints every pixel itself */ + delegate_desc.handle = checker_handle; + delegate_desc.task_data = task; + delegate_desc.name = "checker"; + rc = wuss_task_create(wuss, &delegate_desc, &delegate); if (rc != result_OK) + { + free(task); /* nothing registered yet; the spawner will not free it */ return rc; + } - box = (box_t) BOX_POS_SIZE(440, 10, 160, 160); + rc = wuss_window_create_placed(delegate, + SIZE2D(160, 160), + "Checker 1", + wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(160, 160), + SIZE2D(0, 0), + &task->window); + if (rc != result_OK) + { + wuss_task_destroy(delegate); /* unregister; its QUIT frees the task block */ + return rc; + } - rc = wuss_window_create(wuss, - &box, - "Checker 2", - wuss_WINDOW_NONE, - &delegate, - box.x1 - box.x0, - box.y1 - box.y0, - &task->window2); + rc = wuss_window_create_placed(delegate, + SIZE2D(160, 160), + "Checker 2", + wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(160, 160), + SIZE2D(0, 0), + &task->window2); if (rc != result_OK) { - wuss_window_close(task->window); + wuss_task_destroy(delegate); /* closes "Checker 1", QUIT frees the block */ return rc; } - return result_OK; -} + /* both windows up: from here, closing the last one reaps the task and its + * wuss_EVENT_QUIT frees task_data */ + wuss_task_set_autoclose(delegate, 1); -void checker_destroy(checker_task_t *task) -{ - wuss_window_close(task->window); - wuss_window_close(task->window2); + return result_OK; } static result_t checker_redraw(wuss_window_t *window, @@ -106,7 +113,7 @@ static result_t checker_redraw(wuss_window_t *window, default: band = lx / band_px + ly / band_px; break; /* CHECKERBOARD */ } - screen_draw_pixel(scr, x, y, (band & 1) ? cc->black : cc->white); + screen_set_pixel(scr, x, y, (band & 1) ? cc->black : cc->white); } } @@ -128,7 +135,9 @@ static result_t checker_mouse(wuss_window_t *window, void *task_data) return result_OK; } -static result_t checker_scroll(wuss_window_t *window, int delta, void *task_data) +static result_t checker_scroll(wuss_window_t *window, + int delta, + void *task_data) { checker_task_t *cc; int *band; @@ -144,7 +153,7 @@ static result_t checker_scroll(wuss_window_t *window, int delta, void *task_data return result_OK; } -result_t checker_handle(wuss_window_t *window, +result_t checker_handle(wuss_window_t *window, const wuss_event_t *event, void *task_data) { @@ -158,7 +167,8 @@ result_t checker_handle(wuss_window_t *window, return checker_redraw(window, event, task_data); case wuss_EVENT_MOUSE: - if (event->data.mouse.action != wuss_MOUSE_DOWN) + if (event->data.mouse.action != wuss_MOUSE_DOWN || + !(event->data.mouse.button & wuss_BUTTON_SELECT)) return result_OK; return checker_mouse(window, task_data); @@ -166,16 +176,21 @@ result_t checker_handle(wuss_window_t *window, return checker_scroll(window, event->data.scroll.delta, task_data); case wuss_EVENT_CLOSE: - wuss_window_close(window); if (window == cc->window2) cc->window2 = NULL; else cc->window = NULL; return result_OK; + case wuss_EVENT_QUIT: + /* one calloc'd block backs both windows; the task autocloses once the + * second window goes, so free it here */ + free(cc); + return result_OK; + default: return result_OK; } } -#endif /* USE_SDL */ +#endif /* WUSS_APP */ diff --git a/libraries/wuss/test/tasks/checker.h b/libraries/wuss/test/tasks/checker.h index 4df752a8..63f7d4b2 100644 --- a/libraries/wuss/test/tasks/checker.h +++ b/libraries/wuss/test/tasks/checker.h @@ -1,9 +1,9 @@ -/* checker.h -- wuss test - checkerboard task */ +/* wuss/test/tasks/checker.h -- checkerboard task */ #ifndef TASKS_CHECKER_H #define TASKS_CHECKER_H -#ifdef USE_SDL +#ifdef WUSS_APP #include "framebuf/colour.h" #include "wuss/window.h" @@ -30,16 +30,11 @@ typedef struct checker_task } checker_task_t; -wuss_event_fn_t checker_handle; +wuss_window_fn_t checker_handle; /* create the two checkerboard windows against the given wuss instance */ -result_t checker_create(wuss_t *wuss, - const colour_t *palette, - checker_task_t *task); +result_t checker_create(wuss_t *wuss, checker_task_t *task); -/* destroy the checkerboard windows created by checker_create */ -void checker_destroy(checker_task_t *task); - -#endif /* USE_SDL */ +#endif /* WUSS_APP */ #endif /* TASKS_CHECKER_H */ diff --git a/libraries/wuss/test/tasks/clock.c b/libraries/wuss/test/tasks/clock.c new file mode 100644 index 00000000..bf8f475f --- /dev/null +++ b/libraries/wuss/test/tasks/clock.c @@ -0,0 +1,256 @@ +/* wuss/test/tasks/clock.c -- analogue clock task */ + +#ifdef WUSS_APP + +#include +#include +#include +#include + +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/utils.h" +#include "framebuf/bmfont.h" +#include "framebuf/screen.h" +#include "geom/box.h" +#include "geom/point.h" +#include "utils/fxp.h" + +#include "clock.h" + +#ifndef M_PI +#define M_PI 3.14159265358979323846 +#endif + +#define CLOCK_FACE_FRACTION 0.92 /* bezel radius as a fraction of half the smaller side */ +#define CLOCK_HOUR_TICK 0.10 /* hour-tick length, fraction of face radius */ +#define CLOCK_MINUTE_TICK 0.05 /* minute-tick length, fraction of face radius */ +#define CLOCK_NUMERAL_RING 0.75 /* numeral-centre radius, fraction of face radius */ +#define CLOCK_HOUR_HAND 0.50 /* hand lengths, fraction of face radius */ +#define CLOCK_MINUTE_HAND 0.68 +#define CLOCK_SECOND_HAND 0.80 + +/* a screen-space point in fix8_t, for screen_draw_line_wu_fix8 */ +typedef struct fix8_point { fix8_t x, y; } fix8_point_t; + +/* point on a circle of radius r about (cx,cy); angle 0 points straight up + * (12 o'clock) and increases clockwise, matching a clock face */ +static fix8_point_t clock_polar(double cx, double cy, double r, double angle) +{ + fix8_point_t p; + + p.x = FLOAT_TO_FIX8(cx + r * sin(angle)); + p.y = FLOAT_TO_FIX8(cy - r * cos(angle)); + + return p; +} + +static void clock_draw_hand(screen_t *scr, + double cx, + double cy, + double r, + double angle, + colour_t colour) +{ + fix8_point_t tip; + + tip = clock_polar(cx, cy, r, angle); + screen_draw_line_wu_fix8(scr, + FLOAT_TO_FIX8(cx), FLOAT_TO_FIX8(cy), + tip.x, tip.y, + colour); +} + +/* draw text centred on (x,y), background transparent so it sits on the face */ +static void clock_draw_centred(bmfont_t *font, + screen_t *scr, + const char *text, + double x, + double y, + colour_t fg) +{ + bmfont_width_t width; + point_t pos; + int len, fh; + + len = (int) strlen(text); + bmfont_measure(font, text, len, INT_MAX, NULL, &width); + bmfont_get_info(font, NULL, &fh); + + pos.x = (int) (x - width / 2.0); + pos.y = (int) (y - fh / 2.0); + bmfont_draw(font, scr, text, len, fg, colour_rgba(0, 0, 0, 0), &pos, NULL); +} + +static result_t clock_create_window(wuss_t *wuss, + clock_task_t *task, + wuss_task_t *delegate) +{ + return wuss_window_create_placed(delegate, + SIZE2D(160, 160), + "Clock", + wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(160, 160), + SIZE2D(0, 0), + &task->window); +} + +result_t clock_create(wuss_t *wuss, bmfont_t *font, clock_task_t *task) +{ + wuss_task_t *delegate; + wuss_task_desc_t delegate_desc; + result_t rc; + + task->font = font; + task->bg = colour_rgb(0x1D, 0x2B, 0x53); + task->bezel = colour_rgb(0xFF, 0xF1, 0xE8); + task->hand = colour_rgb(0xFF, 0xF1, 0xE8); + task->second_hand = colour_rgb(0xFF, 0x00, 0x4D); + task->show_second = true; + + /* clock_redraw paints its own background every frame */ + delegate_desc.handle = clock_handle; + delegate_desc.task_data = task; + delegate_desc.name = "clock"; + rc = wuss_task_create(wuss, &delegate_desc, &delegate); + if (rc != result_OK) + { + free(task); /* nothing registered yet; the spawner will not free it */ + return rc; + } + wuss_task_set_autoclose(delegate, 1); + + rc = clock_create_window(wuss, task, delegate); + if (rc != result_OK) + wuss_task_destroy(delegate); /* unregister; its QUIT frees the task block */ + + return rc; +} + +static result_t clock_redraw(const wuss_event_t *event, void *task_data) +{ + clock_task_t *cc; + screen_t *scr; + const box_t *content, *bounds; + time_t now; + struct tm *lt; + double cx, cy, r; + double hour_angle, minute_angle, second_angle; + int i, sx, sy; + + cc = task_data; + + scr = event->data.redraw.scr; + content = event->data.redraw.content; + bounds = event->data.redraw.bounds; + sx = event->data.redraw.scroll.x; + sy = event->data.redraw.scroll.y; + + screen_fill_rect(scr, + content->x0, + content->y0, box_size(content), + cc->bg); + + cx = bounds->x0 - sx + (bounds->x1 - bounds->x0) / 2.0; + cy = bounds->y0 - sy + (bounds->y1 - bounds->y0) / 2.0; + r = MIN(bounds->x1 - bounds->x0, bounds->y1 - bounds->y0) + / 2.0 * CLOCK_FACE_FRACTION; + + /* bezel */ + screen_draw_circle(scr, (int) (cx + 0.5), (int) (cy + 0.5), (int) (r + 0.5), + cc->bezel); + + /* 60 minute ticks, every fifth one an hour tick drawn longer */ + for (i = 0; i < 60; i++) + { + double angle, inner; + fix8_point_t a, b; + + angle = i * 2.0 * M_PI / 60.0; + inner = (i % 5 == 0) ? (1.0 - CLOCK_HOUR_TICK) : (1.0 - CLOCK_MINUTE_TICK); + a = clock_polar(cx, cy, r * inner, angle); + b = clock_polar(cx, cy, r, angle); + screen_draw_line_wu_fix8(scr, a.x, a.y, b.x, b.y, cc->bezel); + } + + /* the 1..12 numerals, centred on a ring inside the hour ticks */ + for (i = 1; i <= 12; i++) + { + double angle; + fix8_point_t at; + char buf[3]; + + angle = i * 2.0 * M_PI / 12.0; + at = clock_polar(cx, cy, r * CLOCK_NUMERAL_RING, angle); + snprintf(buf, sizeof(buf), "%d", i); + clock_draw_centred(cc->font, scr, buf, + FIX8_ROUND_TO_INT(at.x), FIX8_ROUND_TO_INT(at.y), + cc->bezel); + } + + now = time(NULL); + lt = localtime(&now); + + second_angle = lt->tm_sec * 2.0 * M_PI / 60.0; + minute_angle = (lt->tm_min + lt->tm_sec / 60.0) * 2.0 * M_PI / 60.0; + hour_angle = ((lt->tm_hour % 12) + lt->tm_min / 60.0) * 2.0 * M_PI / 12.0; + + clock_draw_hand(scr, cx, cy, r * CLOCK_HOUR_HAND, hour_angle, cc->hand); + clock_draw_hand(scr, cx, cy, r * CLOCK_MINUTE_HAND, minute_angle, cc->hand); + if (cc->show_second) + clock_draw_hand(scr, cx, cy, r * CLOCK_SECOND_HAND, second_angle, + cc->second_hand); + + return result_OK; +} + +static result_t clock_mouse(clock_task_t *cc, wuss_button_t button) +{ + if (button & wuss_BUTTON_SELECT) + { + cc->show_second = !cc->show_second; + wuss_window_invalidate_all(cc->window); + } + + return result_OK; +} + +result_t clock_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) +{ + clock_task_t *cc; + + cc = task_data; + + NOT_USED(window); + + switch (event->kind) + { + case wuss_EVENT_REDRAW: + return clock_redraw(event, task_data); + + case wuss_EVENT_MOUSE: + if (event->data.mouse.action != wuss_MOUSE_DOWN) + return result_OK; + return clock_mouse(cc, event->data.mouse.button); + + case wuss_EVENT_IDLE: + wuss_window_invalidate_all(cc->window); + return result_OK; + + case wuss_EVENT_QUIT: + free(cc); /* task_data was calloc'd per instance by the spawner */ + return result_OK; + + default: + return result_OK; + } +} + +#endif /* WUSS_APP */ diff --git a/libraries/wuss/test/tasks/clock.h b/libraries/wuss/test/tasks/clock.h new file mode 100644 index 00000000..8fa72414 --- /dev/null +++ b/libraries/wuss/test/tasks/clock.h @@ -0,0 +1,34 @@ +/* wuss/test/tasks/clock.h -- analogue clock task */ + +#ifndef TASKS_CLOCK_H +#define TASKS_CLOCK_H + +#ifdef WUSS_APP + +#include + +#include "framebuf/bmfont.h" +#include "framebuf/colour.h" +#include "wuss/window.h" + +/* a round-faced analogue clock: a tick-marked bezel with 1..12 numerals and + * hour, minute and second hands read from the system clock and advanced on + * every idle tick. A Select click shows or hides the second hand. */ +typedef struct clock_task +{ + wuss_window_t *window; + bmfont_t *font; /* borrowed; the numerals are drawn with it */ + colour_t bg, bezel, hand, second_hand; + bool show_second; +} +clock_task_t; + +wuss_window_fn_t clock_handle; + +/* create the clock window against the given wuss instance, lettering the + * face with the given (caller-owned) font */ +result_t clock_create(wuss_t *wuss, bmfont_t *font, clock_task_t *task); + +#endif /* WUSS_APP */ + +#endif /* TASKS_CLOCK_H */ diff --git a/libraries/wuss/test/tasks/curve.c b/libraries/wuss/test/tasks/curve.c index 2f25b1c4..38e7b34a 100644 --- a/libraries/wuss/test/tasks/curve.c +++ b/libraries/wuss/test/tasks/curve.c @@ -1,6 +1,8 @@ -/* curve.c -- wuss test - draggable Bezier curve task */ +/* wuss/test/tasks/curve.c -- draggable Bezier curve task */ -#ifdef USE_SDL +#ifdef WUSS_APP + +#include #include @@ -21,40 +23,47 @@ #define CURVE_SEGMENTS_MIN 4 #define CURVE_SEGMENTS_MAX 128 -result_t curve_create(wuss_t *wuss, - const colour_t *palette, - curve_task_t *task) +result_t curve_create(wuss_t *wuss, curve_task_t *task) { - wuss_task_t delegate; - box_t box; + wuss_task_t *delegate; + wuss_task_desc_t delegate_desc; + result_t rc; - task->bg = palette[palette_PICO8_WHITE]; - task->line = palette[palette_PICO8_BLACK]; - task->blob = palette[palette_PICO8_RED]; + task->bg = colour_rgb(0xFF, 0xFF, 0xFF); + task->line = colour_rgb(0x00, 0x00, 0x00); + task->blob = colour_rgb(0xFF, 0x00, 0x00); task->nsegments = CURVE_SEGMENTS_DEFAULT; task->dragging = -1; - task->points[0] = (point_t) { 10, 10 }; - task->points[1] = (point_t) { 10, 140 }; - task->points[2] = (point_t) { 210, 10 }; - task->points[3] = (point_t) { 210, 140 }; - - delegate = wuss_task_start(curve_handle, task, wuss_NO_BACKGROUND); /* curve_redraw paints its own background */ - box = (box_t) BOX_POS_SIZE(20, 260, 220, 160); - - return wuss_window_create(wuss, - &box, - "Curve", - wuss_WINDOW_NONE, - &delegate, - box.x1 - box.x0, - box.y1 - box.y0, - &task->window); -} - -void curve_destroy(curve_task_t *task) -{ - wuss_window_close(task->window); + task->points[0] = POINT(10, 10); + task->points[1] = POINT(10, 140); + task->points[2] = POINT(210, 10); + task->points[3] = POINT(210, 140); + + /* curve_redraw paints its own background */ + delegate_desc.handle = curve_handle; + delegate_desc.task_data = task; + delegate_desc.name = "curve"; + rc = wuss_task_create(wuss, &delegate_desc, &delegate); + if (rc != result_OK) + { + free(task); /* nothing registered yet; the spawner will not free it */ + return rc; + } + wuss_task_set_autoclose(delegate, 1); + + rc = wuss_window_create_placed(delegate, + SIZE2D(220, 160), + "Curve", + wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(220, 160), + SIZE2D(0, 0), + &task->window); + if (rc != result_OK) + wuss_task_destroy(delegate); /* unregister; its QUIT frees the task block */ + + return rc; } static int blob_hit(const point_t *p, int x, int y) @@ -79,8 +88,7 @@ static result_t curve_redraw(const wuss_event_t *event, curve_task_t *task) sx = event->data.redraw.scroll.x; sy = event->data.redraw.scroll.y; - screen_draw_rect(scr, content->x0, content->y0, - content->x1 - content->x0, content->y1 - content->y0, + screen_fill_rect(scr, content->x0, content->y0, box_size(content), task->bg); prev = task->points[0]; @@ -103,28 +111,29 @@ static result_t curve_redraw(const wuss_event_t *event, curve_task_t *task) { cur = task->points[i]; cur.x += bounds->x0 - sx; cur.y += bounds->y0 - sy; - screen_draw_square(scr, cur.x - half, cur.y - half, CURVE_BLOBSZ, task->blob); + screen_fill_square(scr, cur.x - half, cur.y - half, CURVE_BLOBSZ, task->blob); } return result_OK; } -static result_t curve_mouse(curve_task_t *task, - wuss_mouse_action_t action, - int x, - int y, - wuss_window_t *window) +static result_t curve_mouse(curve_task_t *task, + wuss_mouse_action_t action, + int x, + int y, + wuss_button_t button, + wuss_window_t *window) { - int i; - point_t scroll; + int i; - wuss_window_get_scroll(window, &scroll); - x += scroll.x; - y += scroll.y; + /* x,y already arrive in virtual content space: wuss_mouse_click/move add the + * window's scroll offset before delivering the event. */ switch (action) { case wuss_MOUSE_DOWN: + if (!(button & wuss_BUTTON_SELECT)) + break; for (i = 0; i < CURVE_NCONTROLPTS; i++) { if (blob_hit(&task->points[i], x, y)) @@ -178,14 +187,14 @@ result_t curve_handle(wuss_window_t *window, case wuss_EVENT_MOUSE: return curve_mouse(task, event->data.mouse.action, - event->data.mouse.point.x, event->data.mouse.point.y, window); + event->data.mouse.point.x, event->data.mouse.point.y, + event->data.mouse.button, window); case wuss_EVENT_SCROLL: return curve_scroll(task, event->data.scroll.delta, window); - case wuss_EVENT_CLOSE: - wuss_window_close(window); - task->window = NULL; + case wuss_EVENT_QUIT: + free(task); /* task_data was calloc'd per instance by the spawner */ return result_OK; default: @@ -193,4 +202,4 @@ result_t curve_handle(wuss_window_t *window, } } -#endif /* USE_SDL */ +#endif /* WUSS_APP */ diff --git a/libraries/wuss/test/tasks/curve.h b/libraries/wuss/test/tasks/curve.h index 2a411ffe..92e842aa 100644 --- a/libraries/wuss/test/tasks/curve.h +++ b/libraries/wuss/test/tasks/curve.h @@ -1,9 +1,9 @@ -/* curve.h -- wuss test - draggable Bezier curve task */ +/* wuss/test/tasks/curve.h -- draggable Bezier curve task */ #ifndef TASKS_CURVE_H #define TASKS_CURVE_H -#ifdef USE_SDL +#ifdef WUSS_APP #include "framebuf/colour.h" #include "geom/point.h" @@ -23,16 +23,11 @@ typedef struct curve_task } curve_task_t; -wuss_event_fn_t curve_handle; +wuss_window_fn_t curve_handle; /* create the curve window against the given wuss instance */ -result_t curve_create(wuss_t *wuss, - const colour_t *palette, - curve_task_t *task); +result_t curve_create(wuss_t *wuss, curve_task_t *task); -/* destroy the curve window created by curve_create */ -void curve_destroy(curve_task_t *task); - -#endif /* USE_SDL */ +#endif /* WUSS_APP */ #endif /* TASKS_CURVE_H */ diff --git a/libraries/wuss/test/tasks/gradient.c b/libraries/wuss/test/tasks/gradient.c index 949558f8..4dd1e52b 100644 --- a/libraries/wuss/test/tasks/gradient.c +++ b/libraries/wuss/test/tasks/gradient.c @@ -1,6 +1,8 @@ -/* gradient.c -- wuss test - gradient fill task */ +/* wuss/test/tasks/gradient.c -- gradient fill task */ -#ifdef USE_SDL +#ifdef WUSS_APP + +#include #ifdef FORTIFY #include "fortify/fortify.h" @@ -33,25 +35,34 @@ static int dither(int v, int x, int y) result_t gradient_create(wuss_t *wuss, gradient_task_t *task) { - wuss_task_t delegate; - box_t box; - - delegate = wuss_task_start(gradient_handle, task, wuss_NO_BACKGROUND); /* gradient_redraw paints every pixel itself */ - box = (box_t) BOX_POS_SIZE(620, 300, GRADIENT_OPEN_WIDTH, GRADIENT_OPEN_HEIGHT); - - return wuss_window_create(wuss, - &box, - "Gradient", - wuss_WINDOW_NONE, - &delegate, - GRADIENT_DOC_WIDTH, - GRADIENT_DOC_HEIGHT, - &task->window); -} - -void gradient_destroy(gradient_task_t *task) -{ - wuss_window_close(task->window); + wuss_task_t *delegate; + wuss_task_desc_t delegate_desc; + result_t rc; + + /* gradient_redraw paints every pixel itself */ + delegate_desc.handle = gradient_handle; + delegate_desc.task_data = task; + delegate_desc.name = "gradient"; + rc = wuss_task_create(wuss, &delegate_desc, &delegate); + if (rc != result_OK) + { + free(task); /* nothing registered yet; the spawner will not free it */ + return rc; + } + wuss_task_set_autoclose(delegate, 1); + + rc = wuss_window_create_placed(delegate, + SIZE2D(GRADIENT_OPEN_WIDTH, GRADIENT_OPEN_HEIGHT), + "Gradient", + wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(GRADIENT_DOC_WIDTH, GRADIENT_DOC_HEIGHT), + SIZE2D(0, 0), + &task->window); + if (rc != result_OK) + wuss_task_destroy(delegate); /* unregister; its QUIT frees the task block */ + + return rc; } static result_t gradient_redraw(const wuss_event_t *event, void *task_data) @@ -75,7 +86,7 @@ static result_t gradient_redraw(const wuss_event_t *event, void *task_data) lx = x - bounds->x0 + sx; ly = y - bounds->y0 + sy; - screen_draw_pixel(scr, x, y, + screen_set_pixel(scr, x, y, colour_rgb(dither(lx * 255 / GRADIENT_DOC_WIDTH, lx, ly), dither(ly * 255 / GRADIENT_DOC_HEIGHT, lx, ly), dither(255 - (lx + ly) * 255 / (GRADIENT_DOC_WIDTH + GRADIENT_DOC_HEIGHT), lx, ly))); @@ -98,9 +109,8 @@ result_t gradient_handle(wuss_window_t *window, case wuss_EVENT_REDRAW: return gradient_redraw(event, task_data); - case wuss_EVENT_CLOSE: - wuss_window_close(window); - gc->window = NULL; + case wuss_EVENT_QUIT: + free(gc); /* task_data was calloc'd per instance by the spawner */ return result_OK; default: @@ -108,4 +118,4 @@ result_t gradient_handle(wuss_window_t *window, } } -#endif /* USE_SDL */ +#endif /* WUSS_APP */ diff --git a/libraries/wuss/test/tasks/gradient.h b/libraries/wuss/test/tasks/gradient.h index a37ff22a..0ad2c658 100644 --- a/libraries/wuss/test/tasks/gradient.h +++ b/libraries/wuss/test/tasks/gradient.h @@ -1,9 +1,9 @@ -/* gradient.h -- wuss test - gradient fill task */ +/* wuss/test/tasks/gradient.h -- gradient fill task */ #ifndef TASKS_GRADIENT_H #define TASKS_GRADIENT_H -#ifdef USE_SDL +#ifdef WUSS_APP #include "wuss/window.h" @@ -16,14 +16,12 @@ typedef struct gradient_task } gradient_task_t; -wuss_event_fn_t gradient_handle; +wuss_window_fn_t gradient_handle; /* create the gradient window against the given wuss instance */ result_t gradient_create(wuss_t *wuss, gradient_task_t *task); -/* destroy the gradient window created by gradient_create */ -void gradient_destroy(gradient_task_t *task); -#endif /* USE_SDL */ +#endif /* WUSS_APP */ #endif /* TASKS_GRADIENT_H */ diff --git a/libraries/wuss/test/tasks/icons.c b/libraries/wuss/test/tasks/icons.c new file mode 100644 index 00000000..149eb00e --- /dev/null +++ b/libraries/wuss/test/tasks/icons.c @@ -0,0 +1,619 @@ +/* wuss/test/tasks/icons.c -- work-area icons task */ + +#ifdef WUSS_APP +#include "framebuf/palettes.h" + +#include +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "framebuf/bitmap.h" +#include "framebuf/palettes.h" +#include "framebuf/pattern.h" +#include "framebuf/screen.h" +#include "geom/box.h" +#include "geom/point.h" +#include "geom/size.h" +#include "io/path.h" + +#include "icons.h" + +#define ICONS_DOC_W 260 +#define ICONS_DOC_H 900 /* taller than the window, so scrolling is exercised */ +#define ICONS_MARGIN 28 /* left edge of everything except frame captions */ +#define ICONS_ROW 20 /* vertical pitch between stacked simple icons */ + +/* Maximum spec count across every group icons_create can lay out; the sprite + * pair is the only optional part (skipped when the PNG fails to load). Kept + * as one enum so a miscount between this and the groups below fails loudly + * (an array bound, not a silent overrun) rather than corrupting the heap. */ +enum +{ + ICONS_N_INTRO = 4, /* heading, counter button, counter label, scrolled-away button */ + ICONS_N_BUTTONS = 4, /* frame + normal + default + disabled button */ + ICONS_N_RADIOS = 8, /* frame + 3 radios + option + state label + 2 justified labels */ + ICONS_N_BITMAPS = 3, /* frame + decorative + interactive bitmap */ + ICONS_N_PATTERN = 2, /* frame + one PATTERN swatch */ + ICONS_N_MENU = 7, /* plain, ticked, swatch, submenu, disabled, rule, separator entry */ + ICONS_NSPECS = ICONS_N_INTRO + ICONS_N_BUTTONS + ICONS_N_RADIOS + + ICONS_N_BITMAPS + ICONS_N_PATTERN + ICONS_N_MENU +}; + +/* Running state threaded through the icons_add_* helpers: where to write the + * next spec, and how far down the document the next group should start. */ +typedef struct icons_layout +{ + wuss_icon_spec_t *specs; + int n; /* specs[0..n) are filled in */ + int y; /* document y of the next group */ + wuss_colour_t black; + wuss_colour_t grey5; + wuss_colour_t grey6; + wuss_colour_t red; /* menu swatch demo colour */ +} +icons_layout_t; + +/* ----------------------------------------------------------------------- */ + +/* [0] a heading, [1] the button that bumps the counter (shown as a default + * action button, so it carries the accent styling), [2] the counter label + * beside it, [3] a button far down the document, to prove icons scroll and + * stay clickable. Returns the indices of [1] and [2] via button/counter. */ +static void icons_add_intro(icons_layout_t *lay, int *button, int *counter) +{ + wuss_icon_spec_t *s; + + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN, lay->y, 220, 14); + s->type = wuss_ICON_TYPE_LABEL; + s->text = "Work-area icons:"; + s->fg = lay->black; + s->bg = wuss_NO_BACKGROUND; + s->flags = wuss_ICON_FONT(1); + lay->n++; + lay->y += 30; + + *button = lay->n; + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN, lay->y, 80, 22); + s->type = wuss_ICON_TYPE_BUTTON; + s->text = "Press me"; + s->fg = lay->black; + s->bg = lay->grey6; + s->flags = wuss_ICON_FLAGS_DEFAULT; + lay->n++; + + *counter = lay->n; + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN + 92, lay->y, 120, 22); + s->type = wuss_ICON_TYPE_LABEL; + s->text = "0"; + s->fg = lay->black; + s->bg = wuss_NO_BACKGROUND; + lay->n++; + lay->y += 46; + + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN, 780, 90, 52); + s->type = wuss_ICON_TYPE_BUTTON; + s->text = "Scrolled"; + s->fg = lay->black; + s->bg = lay->grey6; + lay->n++; +} + +/* A grouping frame captioned "Buttons", with a normal, a default (accent) and + * a disabled button side by side inside it. */ +static void icons_add_buttons(icons_layout_t *lay) +{ + wuss_icon_spec_t *s; + int top; + + top = lay->y; + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN, top, 200, 56); + s->type = wuss_ICON_TYPE_FRAME; + s->text = "Buttons"; + s->fg = lay->black; + s->bg = wuss_NO_BACKGROUND; + lay->n++; + + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN + 10, top + 22, 56, 22); + s->type = wuss_ICON_TYPE_BUTTON; + s->text = "Normal"; + s->fg = lay->black; + s->bg = lay->grey6; + lay->n++; + + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN + 74, top + 22, 56, 22); + s->type = wuss_ICON_TYPE_BUTTON; + s->text = "Default"; + s->fg = lay->black; + s->bg = lay->grey6; + s->flags = wuss_ICON_FLAGS_DEFAULT; + lay->n++; + + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN + 138, top + 22, 56, 22); + s->type = wuss_ICON_TYPE_BUTTON; + s->text = "Disabled"; + s->fg = lay->black; + s->bg = lay->grey6; + s->flags = wuss_ICON_FLAGS_DISABLED; + lay->n++; + + lay->y = top + 70; +} + +/* A grouping frame captioned "Radios & options", with two justified labels, + * three radios sharing group 1, a standalone option and a label echoing + * whichever control last changed. Returns the indices of the option and the + * echo label via opt/state. */ +static void icons_add_radios(icons_layout_t *lay, int *opt, int *state) +{ + wuss_icon_spec_t *s; + int top; + int r; + + top = lay->y; + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN, top, 200, 170); + s->type = wuss_ICON_TYPE_FRAME; + s->text = "Radios & options"; + s->fg = lay->black; + s->bg = wuss_NO_BACKGROUND; + lay->n++; + + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN + 10, top + 20, 180, 14); + s->type = wuss_ICON_TYPE_LABEL; + s->text = "right"; + s->fg = lay->black; + s->bg = wuss_NO_BACKGROUND; + s->flags = wuss_ICON_FLAGS_JUSTIFY_RIGHT; + lay->n++; + + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN + 10, top + 40, 180, 14); + s->type = wuss_ICON_TYPE_LABEL; + s->text = "centre"; + s->fg = lay->black; + s->bg = wuss_NO_BACKGROUND; + s->flags = wuss_ICON_FLAGS_JUSTIFY_CENTRE; + lay->n++; + + for (r = 0; r < 3; r++) + { + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN + 10, top + 66 + r * 20, 180, 16); + s->type = wuss_ICON_TYPE_RADIO; + s->text = (r == 0) ? "Red" : (r == 1) ? "Green" : "Blue"; + s->fg = lay->black; + s->bg = wuss_NO_BACKGROUND; + s->group = 1; + lay->n++; + } + + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN + 10, top + 130, 180, 16); + s->type = wuss_ICON_TYPE_OPTION; + s->text = "Wireframe"; + s->fg = lay->black; + s->bg = wuss_NO_BACKGROUND; + *opt = lay->n; + lay->n++; + + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN + 10, top + 150, 180, 14); + s->type = wuss_ICON_TYPE_LABEL; + s->text = "(no selection)"; + s->fg = lay->black; + s->bg = wuss_NO_BACKGROUND; + *state = lay->n; + lay->n++; + + lay->y = top + 190; +} + +/* A grouping frame captioned "Bitmaps", holding a decorative sprite and (to + * its right) an interactive one that bumps the counter -- only laid out if + * the sprite loaded. Returns the interactive icon's index via *hotspot, or + * leaves it unset if sprite is NULL. */ +static void icons_add_bitmaps(icons_layout_t *lay, + const bitmap_t *sprite, + int *hotspot) +{ + wuss_icon_spec_t *s; + int top; + + if (sprite == NULL) + return; + + top = lay->y; + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN, top, 200, + sprite->size.h + 30); + s->type = wuss_ICON_TYPE_FRAME; + s->text = "Bitmaps"; + s->fg = lay->black; + s->bg = wuss_NO_BACKGROUND; + lay->n++; + + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN + 10, top + 20, + sprite->size.w, sprite->size.h); + s->type = wuss_ICON_TYPE_BITMAP; + s->bitmap = sprite; + lay->n++; + + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN + 20 + sprite->size.w, top + 20, + sprite->size.w, sprite->size.h); + s->type = wuss_ICON_TYPE_BITMAP; + s->bitmap = sprite; + s->flags = wuss_ICON_FLAGS_INTERACTIVE; + *hotspot = lay->n; + lay->n++; + + lay->y = top + sprite->size.h + 50; +} + +/* A grouping frame captioned "Pattern", holding one PATTERN-filled swatch. */ +static void icons_add_pattern(icons_layout_t *lay) +{ + wuss_icon_spec_t *s; + int top; + + top = lay->y; + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN, top, 200, 60); + s->type = wuss_ICON_TYPE_FRAME; + s->text = "Pattern"; + s->fg = lay->black; + s->bg = wuss_NO_BACKGROUND; + lay->n++; + + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN + 10, top + 20, 180, 26); + s->type = wuss_ICON_TYPE_PATTERN; + s->fg = lay->black; + s->bg = lay->grey6; + s->pattern = screen_PATTERN_DIAGONAL; + lay->n++; + + lay->y = top + 76; +} + +/* A menu-entry strip: plain, ticked, a swatch entry, a submenu entry, a + * disabled entry, then a dashed rule and a SEPARATOR-flagged entry below it. + * Hover the pointer over any live entry to see the highlight track; the rule + * stays inert. Returns the "Show grid" index (started ticked) via *ticked. */ +static void icons_add_menu(icons_layout_t *lay, int *ticked) +{ + wuss_icon_spec_t *s; + int top; + + top = lay->y; + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN, top, 180, 16); + s->type = wuss_ICON_TYPE_MENU_ENTRY; + s->text = "Open"; + s->fg = lay->black; + s->bg = wuss_NO_BACKGROUND; + lay->n++; + + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN, top + ICONS_ROW, 180, 16); + s->type = wuss_ICON_TYPE_MENU_ENTRY; + s->text = "Show grid"; + s->fg = lay->black; + s->bg = wuss_NO_BACKGROUND; + *ticked = lay->n; + lay->n++; + + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN, top + ICONS_ROW * 2, 180, 16); + s->type = wuss_ICON_TYPE_MENU_ENTRY; + s->text = "Layer colour"; + s->fg = lay->black; + s->bg = wuss_NO_BACKGROUND; + s->swatch = lay->red; + s->flags = wuss_ICON_FLAGS_SWATCH; + lay->n++; + + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN, top + ICONS_ROW * 3, 180, 16); + s->type = wuss_ICON_TYPE_MENU_ENTRY; + s->text = "Export"; + s->fg = lay->black; + s->bg = wuss_NO_BACKGROUND; + s->flags = wuss_ICON_FLAGS_SUBMENU; + lay->n++; + + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN, top + ICONS_ROW * 4, 180, 16); + s->type = wuss_ICON_TYPE_MENU_ENTRY; + s->text = "Disabled"; + s->fg = lay->black; + s->bg = wuss_NO_BACKGROUND; + s->flags = wuss_ICON_FLAGS_DISABLED; + lay->n++; + + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN, top + ICONS_ROW * 5, 180, 10); + s->type = wuss_ICON_TYPE_RULE; + s->fg = lay->black; + s->bg = wuss_NO_BACKGROUND; + lay->n++; + + s = &lay->specs[lay->n]; + s->bbox = (box_t) BOX_POS_SIZE(ICONS_MARGIN, top + ICONS_ROW * 6, 180, 16); + s->type = wuss_ICON_TYPE_MENU_ENTRY; + s->text = "Quit"; + s->fg = lay->black; + s->bg = wuss_NO_BACKGROUND; + s->flags = wuss_ICON_FLAGS_SEPARATOR; + lay->n++; + + lay->y = top + ICONS_ROW * 7 + 10; +} + +/* ----------------------------------------------------------------------- */ + +result_t icons_create(wuss_t *wuss, + bmfont_t *font, + const char *resources, + icons_task_t *task) +{ + wuss_task_t *delegate; + wuss_task_desc_t delegate_desc; + wuss_icon_spec_t specs[ICONS_NSPECS]; + wuss_icon_t *made[ICONS_NSPECS]; + icons_layout_t lay; + const char *sprite_path; + int i_button, i_counter, i_opt, i_state, i_hotspot, i_ticked; + result_t rc; + + task->font = font; + task->label = colour_rgb(0x00, 0x00, 0x00); + task->paper = colour_rgb(0xDD, 0xDD, 0xDD); /* the window bg, below */ + task->window = NULL; + task->button = NULL; + task->counter = NULL; + task->count = 0; + task->opt = NULL; + task->state = NULL; + task->has_sprite = 0; + task->hotspot = NULL; + + sprite_path = path_join_filename(resources, 3, "resources", "wuss", + path_join_leafname("ninepatch", "png")); + if (bitmap_load_png(&task->sprite, sprite_path) == result_OK) + task->has_sprite = 1; + + delegate_desc.handle = icons_handle; + delegate_desc.task_data = task; + delegate_desc.name = "icons"; + rc = wuss_task_create(wuss, &delegate_desc, &delegate); + if (rc != result_OK) + { + if (task->has_sprite) + free(task->sprite.base); + free(task); /* nothing registered yet; the spawner will not free it */ + return rc; + } + + memset(&lay, 0, sizeof(lay)); + lay.black = wuss_nearest_colour(wuss, 0x00, 0x00, 0x00); + lay.grey5 = wuss_nearest_colour(wuss, 0xBB, 0xBB, 0xBB); + lay.grey6 = wuss_nearest_colour(wuss, 0xDD, 0xDD, 0xDD); + lay.red = wuss_nearest_colour(wuss, 0xCC, 0x33, 0x33); + + rc = wuss_window_create_placed(delegate, + SIZE2D(ICONS_DOC_W, 200), + "Icons", + wuss_WINDOW_NONE, + wuss_BACKDROP_PATTERN(lay.grey5, + screen_PATTERN_CROSSHATCH, + lay.grey6), + SIZE2D(ICONS_DOC_W, ICONS_DOC_H), + SIZE2D(0, 0), + &task->window); + if (rc != result_OK) + { + wuss_task_destroy(delegate); /* QUIT frees the task block and its sprite */ + return rc; + } + + memset(specs, 0, sizeof(specs)); + lay.specs = specs; + lay.n = 0; + lay.y = 28; /* icons sit past the ruler gutter -- see ICONS_GUTTER-ish + * axis labels drawn by icons_redraw, which keep the top/ + * left strip to themselves */ + + i_hotspot = -1; + + icons_add_intro(&lay, &i_button, &i_counter); + icons_add_buttons(&lay); + icons_add_radios(&lay, &i_opt, &i_state); + icons_add_bitmaps(&lay, task->has_sprite ? &task->sprite : NULL, &i_hotspot); + icons_add_pattern(&lay); + icons_add_menu(&lay, &i_ticked); + + rc = wuss_icon_create_array(task->window, specs, lay.n, made); + if (rc != result_OK) + goto failure; + + task->button = made[i_button]; + task->counter = made[i_counter]; + task->opt = made[i_opt]; + task->state = made[i_state]; + if (i_hotspot >= 0) + task->hotspot = made[i_hotspot]; + wuss_icon_set_selected(made[i_ticked], 1); /* "Show grid" starts ticked */ + + /* fully built: from here a last-window close reaps the task and its + * wuss_EVENT_QUIT frees task_data */ + wuss_task_set_autoclose(delegate, 1); + + return result_OK; + +failure: + wuss_task_destroy(delegate); /* closes the window; QUIT frees block + sprite */ + return rc; +} + +#define ICONS_GRID 16 /* document-space pitch of the backdrop grid */ +#define ICONS_AXIS_LABEL 64 /* label every Nth grid line along each axis */ + +/* Screen coordinate of the first grid line at or after lo. Grid lines sit at + * document multiples of ICONS_GRID; the screen coordinate of document d is + * origin - scroll + d, so lines fall on screen coordinates congruent to + * (origin - scroll) modulo ICONS_GRID. */ +static int icons_grid_first(int origin, int scroll, int lo) +{ + int phase; + + phase = (origin - scroll) % ICONS_GRID; + if (phase < 0) + phase += ICONS_GRID; + + return lo - ((lo - phase) % ICONS_GRID + ICONS_GRID) % ICONS_GRID; +} + +static result_t icons_redraw(const wuss_event_t *event, void *task_data) +{ + icons_task_t *tcx; + screen_t *scr; + const box_t *content; + const box_t *bounds; + point_t scroll; + point_t pos; + char buf[16]; + int ox; /* screen x of document x=0 */ + int oy; /* screen y of document y=0 */ + int doc; + int x; + int y; + + tcx = task_data; + + scr = event->data.redraw.scr; + content = event->data.redraw.content; + bounds = event->data.redraw.bounds; + scroll = event->data.redraw.scroll; + + ox = bounds->x0 - scroll.x; + oy = bounds->y0 - scroll.y; + + /* Everything this task paints is anchored to the document, not the window, + * so it scrolls rigidly with the content -- which is what Wuss's scroll + * blit assumes. Nothing here is pinned to a window edge. Every draw is + * clipped to the dirty rectangle (content) so partial redraws stay cheap. */ + + /* the faint crosshatch backdrop is now the window's own bg (a + * wuss_backdrop_t pattern fill), painted by Wuss before this event and + * phase-locked to the scroll origin -- nothing to draw here */ + + /* x-axis ruler: document x printed just below the y=0 line, at each + * labelled grid column. Scrolls with the document like the grid. */ + for (x = icons_grid_first(bounds->x0, scroll.x, content->x0); + x < content->x1; + x += ICONS_GRID) + { + doc = x - ox; + if (doc <= 0 || doc % ICONS_AXIS_LABEL != 0) + continue; + + snprintf(buf, sizeof(buf), "%d", doc); + pos = POINT(x + 2, oy + 2); + bmfont_draw(tcx->font, scr, buf, (int) strlen(buf), + tcx->label, tcx->paper, &pos, NULL); + } + + /* y-axis ruler: document y printed just right of the x=0 line, at each + * labelled grid row. */ + for (y = icons_grid_first(bounds->y0, scroll.y, content->y0); + y < content->y1; + y += ICONS_GRID) + { + doc = y - oy; + if (doc <= 0 || doc % ICONS_AXIS_LABEL != 0) + continue; + + snprintf(buf, sizeof(buf), "%d", doc); + pos = POINT(ox + 2, y + 2); + bmfont_draw(tcx->font, scr, buf, (int) strlen(buf), + tcx->label, tcx->paper, &pos, NULL); + } + + return result_OK; +} + +static result_t icons_icon(const wuss_event_t *event, void *task_data) +{ + icons_task_t *tcx; + wuss_icon_t *icon; + char buf[48]; + + tcx = task_data; + icon = event->data.icon.icon; + + /* a radio/option latches on MOUSE_UP -- report the state then */ + if (event->data.icon.action == wuss_MOUSE_UP && + (wuss_icon_get_type(icon) == wuss_ICON_TYPE_RADIO || + wuss_icon_get_type(icon) == wuss_ICON_TYPE_OPTION)) + { + snprintf(buf, sizeof(buf), "%s: %s%s", + wuss_icon_get_text(icon), + wuss_icon_get_selected(icon) ? "on" : "off", + (icon == tcx->opt) ? "" : " (radio)"); + return wuss_icon_set_text(tcx->state, buf); + } + + if (event->data.icon.action != wuss_MOUSE_DOWN) + return result_OK; + if (icon != tcx->button && icon != tcx->hotspot) + return result_OK; + + tcx->count++; + snprintf(buf, sizeof(buf), "%d", tcx->count); + + return wuss_icon_set_text(tcx->counter, buf); +} + +result_t icons_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) +{ + icons_task_t *tcx; + + tcx = task_data; + + switch (event->kind) + { + case wuss_EVENT_REDRAW: + return icons_redraw(event, task_data); + + case wuss_EVENT_ICON: + return icons_icon(event, task_data); + + case wuss_EVENT_QUIT: + if (tcx->has_sprite) + free(tcx->sprite.base); + free(tcx); /* calloc'd per instance by the spawner */ + return result_OK; + + default: + return result_OK; + } +} + +#endif /* WUSS_APP */ diff --git a/libraries/wuss/test/tasks/icons.h b/libraries/wuss/test/tasks/icons.h new file mode 100644 index 00000000..775a2d2d --- /dev/null +++ b/libraries/wuss/test/tasks/icons.h @@ -0,0 +1,45 @@ +/* wuss/test/tasks/icons.h -- work-area icons task */ + +#ifndef TASKS_ICONS_H +#define TASKS_ICONS_H + +#ifdef WUSS_APP + +#include "framebuf/bitmap.h" +#include "framebuf/bmfont.h" +#include "framebuf/colour.h" +#include "wuss/icon.h" +#include "wuss/window.h" + +/* demonstrates wuss-managed work-area icons: labelled groups covering every + * icon type (buttons, radios/options, bitmaps, a pattern swatch, a menu-entry + * strip) plus a button placed far down the document to show icons scroll with + * the content and stay clickable */ +typedef struct icons_task +{ + wuss_window_t *window; + bmfont_t *font; + colour_t label; /* axis coordinate text */ + colour_t paper; /* window bg, for bmfont_draw glyph blending */ + wuss_icon_t *button; /* "Press me" */ + wuss_icon_t *counter; /* label showing hit count */ + int count; + wuss_icon_t *opt; /* standalone option button */ + wuss_icon_t *state; /* label echoing radio/option selection */ + bitmap_t sprite; /* borrowed by the two BITMAP icons; freed on close */ + int has_sprite; + wuss_icon_t *hotspot; /* the interactive BITMAP icon */ +} +icons_task_t; + +wuss_window_fn_t icons_handle; + +/* create the icons window against the given wuss instance */ +result_t icons_create(wuss_t *wuss, + bmfont_t *font, + const char *resources, + icons_task_t *task); + +#endif /* WUSS_APP */ + +#endif /* TASKS_ICONS_H */ diff --git a/libraries/wuss/test/tasks/image.c b/libraries/wuss/test/tasks/image.c index 63a5f333..c096be84 100644 --- a/libraries/wuss/test/tasks/image.c +++ b/libraries/wuss/test/tasks/image.c @@ -1,6 +1,6 @@ -/* image.c -- wuss test - static bitmap image task */ +/* wuss/test/tasks/image.c -- static bitmap image task */ -#ifdef USE_SDL +#ifdef WUSS_APP #include @@ -11,45 +11,66 @@ #include "base/utils.h" #include "framebuf/palettes.h" #include "geom/box.h" -#include "io/path.h" #include "image.h" -result_t image_create(wuss_t *wuss, - const colour_t *palette, - const char *resources, - image_task_t *task) +#define BORDER 16 + +result_t image_create(wuss_t *wuss, + const char *path, + const char *background_path, + image_task_t *task) { - const char *leafname; - const char *filename; - wuss_task_t delegate; - box_t box; + wuss_task_t *delegate; + wuss_task_desc_t delegate_desc; result_t rc; + size2d_t sz; - leafname = path_join_leafname("jessica", "png"); - filename = path_join_filename(resources, 3, "resources", "images", leafname); - rc = bitmap_load_png(&task->bitmap, filename); + rc = bitmap_load_png(&task->bitmap, path); if (rc != result_OK) + { + free(task); /* nothing registered yet; the spawner will not free it */ return rc; + } - delegate = wuss_task_start(image_handle, task, palette_PICO8_BLACK); /* shows through the image's transparent pixels */ - /* shorter than the bitmap so there's something to scroll through */ - box = (box_t) BOX_POS_SIZE(370, 10, task->bitmap.width, task->bitmap.height * 2 / 3); - - return wuss_window_create(wuss, - &box, - "Image", - wuss_WINDOW_NONE, - &delegate, - task->bitmap.width, - task->bitmap.height, - &task->window); -} + rc = bitmap_load_png(&task->ninepatch, background_path); + if (rc != result_OK) + { + free(task->bitmap.base); + free(task); /* nothing registered yet; the spawner will not free it */ + return rc; + } -void image_destroy(image_task_t *task) -{ - wuss_window_close(task->window); - free(task->bitmap.base); + /* shows through the image's transparent pixels */ + delegate_desc.handle = image_handle; + delegate_desc.task_data = task; + delegate_desc.name = "image"; + rc = wuss_task_create(wuss, &delegate_desc, &delegate); + if (rc != result_OK) + { + free(task->bitmap.base); + free(task->ninepatch.base); + free(task); /* nothing registered yet; the spawner will not free it */ + return rc; + } + wuss_task_set_autoclose(delegate, 1); + + sz.w = task->bitmap.size.w + BORDER * 2; + sz.h = task->bitmap.size.h + BORDER * 2; + + rc = wuss_window_create_placed(delegate, + /* shorter than the bitmap so there's something to scroll through */ + SIZE2D(sz.w, sz.h * 2 / 3), + "Image", + wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(palette_PICO8_PINK), + sz, + SIZE2D(32, 32), + &task->window); + if (rc != result_OK) + wuss_task_destroy(delegate); /* QUIT frees the two bitmaps and the block */ + + return rc; } static result_t image_redraw(const wuss_event_t *event, void *task_data) @@ -58,6 +79,8 @@ static result_t image_redraw(const wuss_event_t *event, void *task_data) screen_t *scr; const box_t *bounds; int sx, sy; + int bx, by; + box_t behind; ic = task_data; @@ -65,13 +88,23 @@ static result_t image_redraw(const wuss_event_t *event, void *task_data) bounds = event->data.redraw.bounds; sx = event->data.redraw.scroll.x; sy = event->data.redraw.scroll.y; + bx = bounds->x0 - sx + BORDER; + by = bounds->y0 - sy + BORDER; + +#define NINEPATCHSZ 9 + + behind.x0 = bx - NINEPATCHSZ; + behind.y0 = by - NINEPATCHSZ; + behind.x1 = behind.x0 + ic->bitmap.size.w + NINEPATCHSZ * 2; + behind.y1 = behind.y0 + ic->bitmap.size.h + NINEPATCHSZ * 2; + screen_copy_ninepatch(scr, &behind, &ic->ninepatch, 0); - screen_draw_bitmap(scr, bounds->x0 - sx, bounds->y0 - sy, &ic->bitmap); + screen_copy_bitmap(scr, bx, by, &ic->bitmap); return result_OK; } -result_t image_handle(wuss_window_t *window, +result_t image_handle(wuss_window_t *window, const wuss_event_t *event, void *task_data) { @@ -84,9 +117,10 @@ result_t image_handle(wuss_window_t *window, case wuss_EVENT_REDRAW: return image_redraw(event, task_data); - case wuss_EVENT_CLOSE: - wuss_window_close(window); - ic->window = NULL; + case wuss_EVENT_QUIT: + free(ic->bitmap.base); + free(ic->ninepatch.base); + free(ic); /* task_data was calloc'd per instance by the spawner */ return result_OK; default: @@ -94,4 +128,4 @@ result_t image_handle(wuss_window_t *window, } } -#endif /* USE_SDL */ +#endif /* WUSS_APP */ diff --git a/libraries/wuss/test/tasks/image.h b/libraries/wuss/test/tasks/image.h index c17aa0f5..df56c28c 100644 --- a/libraries/wuss/test/tasks/image.h +++ b/libraries/wuss/test/tasks/image.h @@ -1,9 +1,9 @@ -/* image.h -- wuss test - static bitmap image task */ +/* wuss/test/tasks/image.h -- static bitmap image task */ #ifndef TASKS_IMAGE_H #define TASKS_IMAGE_H -#ifdef USE_SDL +#ifdef WUSS_APP #include "framebuf/bitmap.h" #include "wuss/window.h" @@ -14,22 +14,20 @@ typedef struct image_task { wuss_window_t *window; - bitmap_t bitmap; /* owned: base freed by the caller when done */ + bitmap_t bitmap; /* owned: base freed by the caller when done */ + bitmap_t ninepatch; /* owned: 9-patch tiled behind the main image */ } image_task_t; -wuss_event_fn_t image_handle; +wuss_window_fn_t image_handle; -/* load the image and create its window against the given wuss instance; - * resources is the DPTLib repo root, for locating the bundled PNG */ -result_t image_create(wuss_t *wuss, - const colour_t *palette, - const char *resources, - image_task_t *task); +/* load the PNG at path (and the 9-patch PNG at background_path, drawn tiled + * behind it) and create its window against the given wuss instance */ +result_t image_create(wuss_t *wuss, + const char *path, + const char *background_path, + image_task_t *task); -/* destroy the window and free the bitmap loaded by image_create */ -void image_destroy(image_task_t *task); - -#endif /* USE_SDL */ +#endif /* WUSS_APP */ #endif /* TASKS_IMAGE_H */ diff --git a/libraries/wuss/test/tasks/launcher.c b/libraries/wuss/test/tasks/launcher.c deleted file mode 100644 index 16fcf4a9..00000000 --- a/libraries/wuss/test/tasks/launcher.c +++ /dev/null @@ -1,144 +0,0 @@ -/* launcher.c -- wuss test - clickable list of names that spawn other tasks */ - -#ifdef USE_SDL - -#include - -#ifdef FORTIFY -#include "fortify/fortify.h" -#endif - -#include "base/utils.h" -#include "framebuf/palettes.h" -#include "geom/box.h" -#include "geom/point.h" - -#include "launcher.h" - -#define LAUNCHER_ROW_HEIGHT 20 -#define LAUNCHER_PAD 4 -#define LAUNCHER_WIDTH 140 - -result_t launcher_create(wuss_t *wuss, - launcher_entry_t *entries, - int nentries, - bmfont_t *font, - const colour_t *palette, - launcher_task_t *task) -{ - wuss_task_t delegate; - box_t box; - - task->entries = entries; - task->nentries = nentries; - task->font = font; - task->fg = palette[palette_PICO8_BLACK]; - task->bg = palette[palette_PICO8_WHITE]; - task->running_fg = palette[palette_PICO8_LIGHT_GREY]; - - delegate = wuss_task_start(launcher_handle, task, wuss_NO_BACKGROUND); /* launcher_redraw paints its own background */ - box = (box_t) BOX_POS_SIZE(10, 10, LAUNCHER_WIDTH, LAUNCHER_PAD * 2 + nentries * LAUNCHER_ROW_HEIGHT); - - return wuss_window_create(wuss, - &box, - "Launcher", - wuss_WINDOW_NO_CLOSE, - &delegate, - box.x1 - box.x0, - box.y1 - box.y0, - &task->window); -} - -void launcher_destroy(launcher_task_t *task) -{ - wuss_window_close(task->window); -} - -static result_t launcher_redraw(const wuss_event_t *event, void *task_data) -{ - launcher_task_t *lc; - screen_t *scr; - const box_t *content, *bounds; - int i, font_width, font_height, sy; - point_t pos; - const launcher_entry_t *entry; - - lc = task_data; - - scr = event->data.redraw.scr; - content = event->data.redraw.content; - bounds = event->data.redraw.bounds; - sy = event->data.redraw.scroll.y; - - screen_draw_rect(scr, content->x0, content->y0, - content->x1 - content->x0, content->y1 - content->y0, - lc->bg); - - bmfont_get_info(lc->font, &font_width, &font_height); - NOT_USED(font_width); - - for (i = 0; i < lc->nentries; i++) - { - entry = &lc->entries[i]; - - pos.x = bounds->x0 + LAUNCHER_PAD; - pos.y = bounds->y0 - sy + LAUNCHER_PAD + i * LAUNCHER_ROW_HEIGHT + (LAUNCHER_ROW_HEIGHT - font_height) / 2; - - bmfont_draw(lc->font, scr, entry->name, (int) strlen(entry->name), - entry->running ? lc->running_fg : lc->fg, lc->bg, &pos, NULL); - } - - return result_OK; -} - -static result_t launcher_mouse(wuss_window_t *window, int y, void *task_data) -{ - launcher_task_t *lc; - int i; - point_t scroll; - launcher_entry_t *entry; - result_t rc; - - lc = task_data; - - wuss_window_get_scroll(window, &scroll); - NOT_USED(scroll.x); - - i = (y + scroll.y - LAUNCHER_PAD) / LAUNCHER_ROW_HEIGHT; - if (i < 0 || i >= lc->nentries) - return result_OK; - - entry = &lc->entries[i]; - if (entry->running) - return result_OK; - - rc = entry->spawn(); - if (rc == result_OK) - { - entry->running = true; - wuss_window_invalidate_all(window); - } - - return rc; -} - -result_t launcher_handle(wuss_window_t *window, - const wuss_event_t *event, - void *task_data) -{ - switch (event->kind) - { - case wuss_EVENT_REDRAW: - return launcher_redraw(event, task_data); - - case wuss_EVENT_MOUSE: - if (event->data.mouse.action != wuss_MOUSE_DOWN) - return result_OK; - return launcher_mouse(window, event->data.mouse.point.y, task_data); - - default: - return result_OK; - } -} - -#endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/launcher.h b/libraries/wuss/test/tasks/launcher.h deleted file mode 100644 index 05ebd89c..00000000 --- a/libraries/wuss/test/tasks/launcher.h +++ /dev/null @@ -1,54 +0,0 @@ -/* launcher.h -- wuss test - clickable list of names that spawn other tasks */ - -#ifndef TASKS_LAUNCHER_H -#define TASKS_LAUNCHER_H - -#ifdef USE_SDL - -#include - -#include "framebuf/bmfont.h" -#include "framebuf/colour.h" -#include "wuss/window.h" - -typedef result_t (*launcher_spawn_fn_t)(void); -typedef void (*launcher_destroy_fn_t)(void); - -/* one clickable row; "running" is set once spawned and never cleared, so a - * second click is a no-op -- relaunching a task after its window closes - * needs the test restarted */ -typedef struct launcher_entry -{ - const char *name; - launcher_spawn_fn_t spawn; - launcher_destroy_fn_t destroy; - bool running; -} -launcher_entry_t; - -typedef struct launcher_task -{ - launcher_entry_t *entries; /* owned by the caller, must outlive the window */ - int nentries; - bmfont_t *font; - colour_t fg, bg, running_fg; - wuss_window_t *window; -} -launcher_task_t; - -wuss_event_fn_t launcher_handle; - -/* create a window listing "entries"; clicking a row calls its spawn - * function once */ -result_t launcher_create(wuss_t *wuss, - launcher_entry_t *entries, - int nentries, - bmfont_t *font, - const colour_t *palette, - launcher_task_t *task); - -void launcher_destroy(launcher_task_t *task); - -#endif /* USE_SDL */ - -#endif /* TASKS_LAUNCHER_H */ diff --git a/libraries/wuss/test/tasks/lissajous.c b/libraries/wuss/test/tasks/lissajous.c new file mode 100644 index 00000000..ee5ab115 --- /dev/null +++ b/libraries/wuss/test/tasks/lissajous.c @@ -0,0 +1,180 @@ +/* wuss/test/tasks/lissajous.c -- Lissajous figure task */ + +#ifdef WUSS_APP + +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/utils.h" +#include "framebuf/palettes.h" +#include "geom/box.h" + +#include "lissajous.h" + +/* frequency pairs cycled by a Select click */ +static const int lissajous_freqs[][2] = +{ + { 3, 2 }, { 5, 4 }, { 3, 4 }, { 5, 6 }, { 1, 2 }, { 7, 4 } +}; + +result_t lissajous_create(wuss_t *wuss, lissajous_task_t *task) +{ + wuss_task_t *delegate; + wuss_task_desc_t delegate_desc; + result_t rc; + + task->bg = colour_rgb(0x00, 0x00, 0x00); + task->fg = colour_rgb(0x00, 0xFF, 0x00); + task->freq_index = 0; + task->a = lissajous_freqs[0][0]; + task->b = lissajous_freqs[0][1]; + task->phase = 0.0; + task->drift = 0.01; + + /* redraw paints its own background every frame */ + delegate_desc.handle = lissajous_handle; + delegate_desc.task_data = task; + delegate_desc.name = "lissajous"; + rc = wuss_task_create(wuss, &delegate_desc, &delegate); + if (rc != result_OK) + { + free(task); /* nothing registered yet; the spawner will not free it */ + return rc; + } + wuss_task_set_autoclose(delegate, 1); + + rc = wuss_window_create_placed(delegate, + SIZE2D(220, 220), + "Lissajous", + wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(220, 220), + SIZE2D(0, 0), + &task->window); + if (rc != result_OK) + wuss_task_destroy(delegate); /* unregister; its QUIT frees the task block */ + + return rc; +} + +static result_t lissajous_redraw(const wuss_event_t *event, void *task_data) +{ + lissajous_task_t *lc; + screen_t *scr; + const box_t *content, *bounds; + int sx, sy; + int width, height, cx, cy, rx, ry; + int i; + + lc = task_data; + + scr = event->data.redraw.scr; + content = event->data.redraw.content; + bounds = event->data.redraw.bounds; + sx = event->data.redraw.scroll.x; + sy = event->data.redraw.scroll.y; + + screen_fill_rect(scr, content->x0, content->y0, box_size(content), lc->bg); + + width = bounds->x1 - bounds->x0; + height = bounds->y1 - bounds->y0; + cx = bounds->x0 - sx + width / 2; + cy = bounds->y0 - sy + height / 2; + rx = width / 2 - 10; + ry = height / 2 - 10; + + for (i = 0; i < LISSAJOUS_POINTS; i++) + { + double t; + int px, py; + + t = (double) i / LISSAJOUS_POINTS * 2.0 * M_PI; + px = cx + (int) (rx * sin(lc->a * t + lc->phase)); + py = cy + (int) (ry * sin(lc->b * t)); + + screen_set_pixel(scr, px, py, lc->fg); + } + + return result_OK; +} + +static result_t lissajous_mouse(wuss_window_t *window, + wuss_mouse_action_t action, + wuss_button_t button, + void *task_data) +{ + lissajous_task_t *lc; + + NOT_USED(window); + + lc = task_data; + + if (action != wuss_MOUSE_DOWN) + return result_OK; + + if (button & wuss_BUTTON_SELECT) + { + lc->freq_index = (lc->freq_index + 1) % (int) NELEMS(lissajous_freqs); + lc->a = lissajous_freqs[lc->freq_index][0]; + lc->b = lissajous_freqs[lc->freq_index][1]; + wuss_window_invalidate_all(lc->window); /* whole figure changes */ + } + else if (button & wuss_BUTTON_ADJUST) + { + lc->drift = -lc->drift; + } + + return result_OK; +} + +static result_t lissajous_idle(void *task_data) +{ + lissajous_task_t *lc; + + lc = task_data; + + lc->phase += lc->drift; + if (lc->phase > 2.0 * M_PI) + lc->phase -= 2.0 * M_PI; + else if (lc->phase < 0.0) + lc->phase += 2.0 * M_PI; + + wuss_window_invalidate_all(lc->window); /* ponytail: repaint whole content; figure fills it and is cheap */ + + return result_OK; +} + +result_t lissajous_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) +{ + lissajous_task_t *lc; + + lc = task_data; + + switch (event->kind) + { + case wuss_EVENT_REDRAW: + return lissajous_redraw(event, task_data); + + case wuss_EVENT_MOUSE: + return lissajous_mouse(window, event->data.mouse.action, + event->data.mouse.button, task_data); + + case wuss_EVENT_IDLE: + return lissajous_idle(task_data); + + case wuss_EVENT_QUIT: + free(lc); /* task_data was calloc'd per instance by the spawner */ + return result_OK; + + default: + return result_OK; + } +} + +#endif /* WUSS_APP */ diff --git a/libraries/wuss/test/tasks/lissajous.h b/libraries/wuss/test/tasks/lissajous.h new file mode 100644 index 00000000..2fd194e7 --- /dev/null +++ b/libraries/wuss/test/tasks/lissajous.h @@ -0,0 +1,35 @@ +/* wuss/test/tasks/lissajous.h -- Lissajous figure task */ + +#ifndef TASKS_LISSAJOUS_H +#define TASKS_LISSAJOUS_H + +#ifdef WUSS_APP + +#include "framebuf/colour.h" +#include "wuss/window.h" + +#define LISSAJOUS_POINTS 512 /* samples plotted along the curve */ + +/* window task: a Lissajous figure x=sin(a*t+phase), y=sin(b*t) plotted as a + * ring of dots. The phase drifts each idle tick so the figure slowly morphs. + * Select cycles the frequency pair (a,b); Adjust reverses the drift. */ +typedef struct lissajous_task +{ + wuss_window_t *window; + colour_t bg, fg; + int a, b; /* frequency ratio */ + double phase; + double drift; + int freq_index; +} +lissajous_task_t; + +wuss_window_fn_t lissajous_handle; + +/* create the Lissajous window against the given wuss instance; "task" is a + * per-instance block owned by the window and freed when it closes */ +result_t lissajous_create(wuss_t *wuss, lissajous_task_t *task); + +#endif /* WUSS_APP */ + +#endif /* TASKS_LISSAJOUS_H */ diff --git a/libraries/wuss/test/tasks/palette.c b/libraries/wuss/test/tasks/palette.c index cd1b1794..57690879 100644 --- a/libraries/wuss/test/tasks/palette.c +++ b/libraries/wuss/test/tasks/palette.c @@ -1,6 +1,8 @@ -/* palette.c -- wuss test - desktop palette swatch grid task */ +/* wuss/test/tasks/palette.c -- desktop palette swatch grid task */ -#ifdef USE_SDL +#ifdef WUSS_APP + +#include #ifdef FORTIFY #include "fortify/fortify.h" @@ -17,28 +19,37 @@ result_t palette_create(wuss_t *wuss, int npalette, palette_task_t *task) { - wuss_task_t delegate; - box_t box; + wuss_task_t *delegate; + wuss_task_desc_t delegate_desc; + result_t rc; task->palette = palette; task->npalette = npalette; - delegate = wuss_task_start(palette_handle, task, palette_PICO8_BLACK); /* backdrop for any rounding gap around the grid */ - box = (box_t) BOX_POS_SIZE(380, 260, 100, 100); - - return wuss_window_create(wuss, - &box, - "Palette", - wuss_WINDOW_NO_TOGGLE_BLIT, /* swatch grid is laid out across the whole window, so a resize must redraw all of it, not just the newly (un)covered edge */ - &delegate, - box.x1 - box.x0, - box.y1 - box.y0, - &task->window); -} - -void palette_destroy(palette_task_t *task) -{ - wuss_window_close(task->window); + /* backdrop for any rounding gap around the grid */ + delegate_desc.handle = palette_handle; + delegate_desc.task_data = task; + delegate_desc.name = "palette"; + rc = wuss_task_create(wuss, &delegate_desc, &delegate); + if (rc != result_OK) + { + free(task); /* nothing registered yet; the spawner will not free it */ + return rc; + } + wuss_task_set_autoclose(delegate, 1); + + rc = wuss_window_create_placed(delegate, + SIZE2D(100, 100), + "Palette", + wuss_WINDOW_NO_RESIZE_BLIT, /* swatch grid is laid out across the whole window, so a resize must redraw all of it, not just the newly (un)covered edge */ + wuss_BACKDROP_COLOUR(palette_PICO8_BLACK), + SIZE2D(100, 100), + SIZE2D(0, 0), + &task->window); + if (rc != result_OK) + wuss_task_destroy(delegate); /* unregister; its QUIT frees the task block */ + + return rc; } static result_t palette_redraw(const wuss_event_t *event, void *task_data) @@ -77,20 +88,19 @@ static result_t palette_redraw(const wuss_event_t *event, void *task_data) x = bounds->x0 - sx + col * cell_w; y = bounds->y0 - sy + row * cell_h; - screen_draw_rect(scr, x, y, cell_w, cell_h, pc->palette[i]); + screen_fill_rect(scr, x, y, SIZE2D(cell_w, cell_h), pc->palette[i]); } return result_OK; } -result_t palette_handle(wuss_window_t *window, +result_t palette_handle(wuss_window_t *window, const wuss_event_t *event, void *task_data) { - if (event->kind == wuss_EVENT_CLOSE) + if (event->kind == wuss_EVENT_QUIT) { - wuss_window_close(window); - ((palette_task_t *) task_data)->window = NULL; + free(task_data); /* calloc'd per instance by the spawner */ return result_OK; } @@ -100,4 +110,4 @@ result_t palette_handle(wuss_window_t *window, return palette_redraw(event, task_data); } -#endif /* USE_SDL */ +#endif /* WUSS_APP */ diff --git a/libraries/wuss/test/tasks/palette.h b/libraries/wuss/test/tasks/palette.h index 8f7445b1..f30a6a6d 100644 --- a/libraries/wuss/test/tasks/palette.h +++ b/libraries/wuss/test/tasks/palette.h @@ -1,9 +1,9 @@ -/* palette.h -- wuss test - desktop palette swatch grid task */ +/* wuss/test/tasks/palette.h -- desktop palette swatch grid task */ #ifndef TASKS_PALETTE_H #define TASKS_PALETTE_H -#ifdef USE_SDL +#ifdef WUSS_APP #include "framebuf/colour.h" #include "wuss/window.h" @@ -18,7 +18,7 @@ typedef struct palette_task } palette_task_t; -wuss_event_fn_t palette_handle; +wuss_window_fn_t palette_handle; /* create the palette-swatch-grid window against the given wuss instance */ result_t palette_create(wuss_t *wuss, @@ -26,9 +26,7 @@ result_t palette_create(wuss_t *wuss, int npalette, palette_task_t *task); -/* destroy the palette-swatch-grid window created by palette_create */ -void palette_destroy(palette_task_t *task); -#endif /* USE_SDL */ +#endif /* WUSS_APP */ #endif /* TASKS_PALETTE_H */ diff --git a/libraries/wuss/test/tasks/porter-duff.c b/libraries/wuss/test/tasks/porter-duff.c new file mode 100644 index 00000000..6d970581 --- /dev/null +++ b/libraries/wuss/test/tasks/porter-duff.c @@ -0,0 +1,423 @@ +/* wuss/test/tasks/porter-duff.c -- animated Porter-Duff compositing task */ + +#ifdef WUSS_APP + +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/utils.h" +#include "framebuf/palettes.h" +#include "framebuf/pixelfmt.h" +#include "geom/box.h" +#include "geom/point.h" +#include "io/path.h" + +#include "porter-duff.h" + +#define PD_SIZE (256) /* the demo images are 256x256 */ +#define PD_LABEL_HEIGHT (20) /* strip below the pane, for the rule name */ +#define PD_CHECKER_BAND (8) /* checkerboard square size, in pixels */ +#define PD_FORMAT (pixelfmt_bgra8888) + +#define PD_FRAMES_DEFAULT (120) /* ~2s per rule at 60 main-loop passes/sec */ +#define PD_FRAMES_MIN (10) +#define PD_FRAMES_MAX (600) + +/* ----------------------------------------------------------------------- */ + +/* These three are lifted from libraries/framebuf/composite/test/composite-test.c, + * where they're static helpers rather than library functions: their memory + * management is raw malloc and the original carries a FIXME saying as much, so + * they're copied rather than promoted to framebuf. */ + +static result_t bitmap_clone_by_size(bitmap_t *cloned, const bitmap_t *src) +{ + size_t pixelbytes; + void *pixels; + + pixelbytes = src->size.h * src->rowbytes; + pixels = malloc(pixelbytes); + if (pixels == NULL) + return result_OOM; + + *cloned = *src; + cloned->base = pixels; + + return result_OK; +} + +static result_t bitmap_clone_pixels(bitmap_t *dst, const bitmap_t *src) +{ + 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->size.h * src->rowbytes); + + return result_OK; +} + +/* Only the arms reachable from bitmap_load_png's output are implemented. */ +static result_t bitmap_convert_inplace(bitmap_t *bm, pixelfmt_t new_fmt) +{ + pixelfmt_any_t *p; + int x, y; + + if (new_fmt != pixelfmt_bgra8888) + return result_NOT_IMPLEMENTED; + + switch (bm->format) + { + case pixelfmt_bgra8888: + return result_OK; + + case pixelfmt_rgbx8888: + p = bm->base; + for (y = 0; y < bm->size.h; y++) + for (x = 0; x < bm->size.w; x++) + { + pixelfmt_rgbx8888_t px = *p; + /* the x byte isn't a real alpha channel, so force it opaque */ + *p++ = PIXELFMT_MAKE_BGRA8888(PIXELFMT_Bxxx8888(px), + PIXELFMT_xGxx8888(px), + PIXELFMT_xxRx8888(px), + 0xFF); + } + bm->format = pixelfmt_bgra8888; + return result_OK; + + case pixelfmt_rgba8888: + p = bm->base; + for (y = 0; y < bm->size.h; y++) + for (x = 0; x < bm->size.w; x++) + { + pixelfmt_rgba8888_t px = *p; + *p++ = PIXELFMT_MAKE_BGRA8888(PIXELFMT_Bxxx8888(px), + PIXELFMT_xGxx8888(px), + PIXELFMT_xxRx8888(px), + PIXELFMT_xxxA8888(px)); + } + bm->format = pixelfmt_bgra8888; + return result_OK; + + default: + return result_NOT_IMPLEMENTED; + } +} + +/* ----------------------------------------------------------------------- */ + +static const char *const rule_names[composite_RULE__LIMIT] = +{ + "CLEAR", + "SRC", + "DST", + "SRC OVER", + "DST OVER", + "SRC IN", + "DST IN", + "SRC OUT", + "DST OUT", + "SRC ATOP", + "DST ATOP", + "XOR" +}; + +static result_t load_demo_png(bitmap_t *bm, + const char *resources, + const char *leafname) +{ + const char *leafname_ext; + const char *filename; + result_t rc; + + leafname_ext = path_join_leafname(leafname, "png"); + filename = path_join_filename(resources, 3, + "resources", "composite", leafname_ext); + + rc = bitmap_load_png(bm, filename); + if (rc != result_OK) + return rc; + + rc = bitmap_convert_inplace(bm, PD_FORMAT); + if (rc != result_OK) + { + free(bm->base); + return rc; + } + + return result_OK; +} + +/* ----------------------------------------------------------------------- */ + +result_t porter_duff_create(wuss_t *wuss, + const colour_t *palette, + bmfont_t *font, + const char *resources, + porter_duff_task_t *task) +{ + wuss_task_t *delegate; + wuss_task_desc_t delegate_desc; + result_t rc; + + task->font = font; + task->rule = composite_RULE_CLEAR; + task->frame = 0; + task->frames_per_rule = PD_FRAMES_DEFAULT; + task->light = palette[palette_PICO8_LIGHT_GREY]; + task->dark = palette[palette_PICO8_DARK_GREY]; + task->fg = palette[palette_PICO8_WHITE]; + task->bg = palette[palette_PICO8_BLACK]; + + rc = load_demo_png(&task->a, resources, "A"); + if (rc != result_OK) + return rc; + + rc = load_demo_png(&task->b, resources, "B"); + if (rc != result_OK) + goto free_a; + + rc = bitmap_clone_by_size(&task->src, &task->a); + if (rc != result_OK) + goto free_b; + + rc = bitmap_clone_by_size(&task->dst, &task->b); + if (rc != result_OK) + goto free_src; + + /* porter_duff_redraw paints every pixel itself */ + delegate_desc.handle = porter_duff_handle; + delegate_desc.task_data = task; + delegate_desc.name = "porter-duff"; + rc = wuss_task_create(wuss, &delegate_desc, &delegate); + if (rc != result_OK) + goto free_dst; /* nothing registered yet; the spawner will not free task */ + wuss_task_set_autoclose(delegate, 1); + + rc = wuss_window_create_placed(delegate, + SIZE2D(PD_SIZE, PD_SIZE + PD_LABEL_HEIGHT), + "Porter-Duff", + wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(PD_SIZE, PD_SIZE + PD_LABEL_HEIGHT), + SIZE2D(0, 0), + &task->window); + if (rc != result_OK) + { + wuss_task_destroy(delegate); /* QUIT frees the four bitmaps and task */ + return rc; + } + + return result_OK; + +free_dst: + free(task->dst.base); +free_src: + free(task->src.base); +free_b: + free(task->b.base); +free_a: + free(task->a.base); + free(task); /* no task was registered on any goto here; spawner won't free */ + + return rc; +} + +/* ----------------------------------------------------------------------- */ + +/* Triangle ramp: 0 at the start of the rule's turn, 255 at its midpoint, back + * to 0 at its end. */ +static int porter_duff_ramp(const porter_duff_task_t *pd) +{ + int half; + + half = pd->frames_per_rule / 2; + if (half <= 0) + return 255; + + if (pd->frame < half) + return pd->frame * 255 / half; + else + return MAX(0, (pd->frames_per_rule - pd->frame) * 255 / half); +} + +/* Copy "a" into "src", scaling its alpha channel by "ramp" (0..255). The + * library's compositing works on non-premultiplied values, so the colour + * components are left alone. */ +static void porter_duff_ramp_src(porter_duff_task_t *pd, int ramp) +{ + pixelfmt_any_t *sp; + pixelfmt_any_t *dp; + int x, y; + + sp = pd->a.base; + dp = pd->src.base; + + for (y = 0; y < pd->a.size.h; y++) + for (x = 0; x < pd->a.size.w; x++) + { + pixelfmt_any_t px = *sp++; + unsigned int alpha; + + alpha = PIXELFMT_xxxA8888(px) * ramp / 255; + *dp++ = (px & ~PIXELFMT_xxxA8888_MASK) | + ((pixelfmt_any_t) alpha << PIXELFMT_xxxA8888_SHIFT); + } +} + +static void porter_duff_draw_checkerboard(const porter_duff_task_t *pd, + screen_t *scr, + const box_t *content, + const box_t *bounds) +{ + int x, y, lx, ly, band; + + for (y = content->y0; y < content->y1; y++) + for (x = content->x0; x < content->x1; x++) + { + lx = x - bounds->x0; + ly = y - bounds->y0; + band = lx / PD_CHECKER_BAND + ly / PD_CHECKER_BAND; + + screen_set_pixel(scr, x, y, (band & 1) ? pd->dark : pd->light); + } +} + +static result_t porter_duff_redraw(const wuss_event_t *event, + void *task_data) +{ + porter_duff_task_t *pd; + screen_t *scr; + const box_t *content, *bounds; + const char *name; + point_t pos; + result_t rc; + + pd = task_data; + + scr = event->data.redraw.scr; + content = event->data.redraw.content; + bounds = event->data.redraw.bounds; + + porter_duff_draw_checkerboard(pd, scr, content, bounds); + + /* ponytail: the whole 256x256 pane is recomposited on every redraw -- two + * full-image memcpys plus two full-image passes. Fine for one window in a + * test harness; if the main loop ever gets tight, cache the composited + * bitmap and rebuild it only when the ramp value or rule actually changes. */ + rc = bitmap_clone_pixels(&pd->dst, &pd->b); + if (rc != result_OK) + return rc; + + porter_duff_ramp_src(pd, porter_duff_ramp(pd)); + + rc = composite(pd->rule, &pd->src, &pd->dst); + if (rc != result_OK) + return rc; + + screen_copy_bitmap(scr, bounds->x0, bounds->y0, &pd->dst); + + name = rule_names[pd->rule]; + pos.x = bounds->x0 + 2; + pos.y = bounds->y0 + PD_SIZE + 2; + + return bmfont_draw(pd->font, scr, name, (int) strlen(name), + pd->fg, pd->bg, &pos, NULL); +} + +static result_t porter_duff_idle(void *task_data) +{ + porter_duff_task_t *pd; + + pd = task_data; + + if (++pd->frame >= pd->frames_per_rule) + { + pd->frame = 0; + pd->rule = (pd->rule + 1) % composite_RULE__LIMIT; + } + + /* the ramp changes every frame, so the whole pane is stale every frame */ + wuss_window_invalidate_all(pd->window); + + return result_OK; +} + +static result_t porter_duff_mouse(wuss_window_t *window, void *task_data) +{ + porter_duff_task_t *pd; + + pd = task_data; + + pd->rule = (pd->rule + 1) % composite_RULE__LIMIT; + pd->frame = 0; + + wuss_window_invalidate_all(window); + + return result_OK; +} + +static result_t porter_duff_scroll(wuss_window_t *window, + int delta, + void *task_data) +{ + porter_duff_task_t *pd; + + pd = task_data; + + pd->frames_per_rule += delta * 10; + pd->frames_per_rule = CLAMP(pd->frames_per_rule, + PD_FRAMES_MIN, PD_FRAMES_MAX); + pd->frame = MIN(pd->frame, pd->frames_per_rule); + + wuss_window_invalidate_all(window); + + return result_OK; +} + +result_t porter_duff_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) +{ + porter_duff_task_t *pd; + + pd = task_data; + + switch (event->kind) + { + case wuss_EVENT_REDRAW: + return porter_duff_redraw(event, task_data); + + case wuss_EVENT_MOUSE: + if (event->data.mouse.action != wuss_MOUSE_DOWN || + !(event->data.mouse.button & wuss_BUTTON_SELECT)) + return result_OK; + return porter_duff_mouse(window, task_data); + + case wuss_EVENT_SCROLL: + return porter_duff_scroll(window, event->data.scroll.delta, task_data); + + case wuss_EVENT_IDLE: + return porter_duff_idle(task_data); + + case wuss_EVENT_QUIT: + free(pd->dst.base); + free(pd->src.base); + free(pd->b.base); + free(pd->a.base); + free(pd); /* calloc'd per instance by the spawner */ + return result_OK; + + default: + return result_OK; + } +} + +#endif /* WUSS_APP */ diff --git a/libraries/wuss/test/tasks/porter-duff.h b/libraries/wuss/test/tasks/porter-duff.h new file mode 100644 index 00000000..c617aa2b --- /dev/null +++ b/libraries/wuss/test/tasks/porter-duff.h @@ -0,0 +1,48 @@ +/* wuss/test/tasks/porter-duff.h -- animated Porter-Duff compositing task */ + +#ifndef TASKS_PORTER_DUFF_H +#define TASKS_PORTER_DUFF_H + +#ifdef WUSS_APP + +#include "framebuf/bitmap.h" +#include "framebuf/bmfont.h" +#include "framebuf/composite.h" +#include "wuss/window.h" + +/* window's task: the two composite demo images blended under a cycling + * Porter-Duff rule, over an alpha checkerboard. The source image's alpha is + * ramped up and back down across each rule's turn, so every operator is seen + * across its full range */ +typedef struct porter_duff_task +{ + wuss_window_t *window; + bmfont_t *font; + bitmap_t a; /* owned: pristine source, BGRA */ + bitmap_t b; /* owned: pristine destination, BGRA */ + bitmap_t src; /* owned: scratch, rebuilt each frame */ + bitmap_t dst; /* owned: scratch, rebuilt each frame */ + composite_rule_t rule; + int frame; /* frames elapsed in the current rule */ + int frames_per_rule; + colour_t light; /* checkerboard */ + colour_t dark; /* checkerboard */ + colour_t fg; /* rule name label */ + colour_t bg; /* rule name label */ +} +porter_duff_task_t; + +wuss_window_fn_t porter_duff_handle; + +/* load the two demo images and create the window against the given wuss + * instance; resources is the DPTLib repo root, for locating the bundled PNGs */ +result_t porter_duff_create(wuss_t *wuss, + const colour_t *palette, + bmfont_t *font, + const char *resources, + porter_duff_task_t *task); + + +#endif /* WUSS_APP */ + +#endif /* TASKS_PORTER_DUFF_H */ diff --git a/libraries/wuss/test/tasks/sofa.c b/libraries/wuss/test/tasks/sofa.c index 7db04833..b827ca2f 100644 --- a/libraries/wuss/test/tasks/sofa.c +++ b/libraries/wuss/test/tasks/sofa.c @@ -1,6 +1,8 @@ -/* sofa.c -- wuss test - rotating wireframe sofa and spaceship task */ +/* wuss/test/tasks/sofa.c -- rotating wireframe sofa and spaceship task */ -#ifdef USE_SDL +#ifdef WUSS_APP + +#include #include @@ -15,12 +17,15 @@ #include "sofa.h" +#define SOFA_VERTEX_DOT 2 /* side, px, of the white marker square drawn at each vertex */ + #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #define SOFA_TILT -0.5 /* static camera tilt, radians, so the seat is visible from above */ #define SOFA_SPIN_PER_FRAME 0.02 /* radians/frame at 60fps, one turn every ~5s */ +#define SOFA_ROTATIONS_PER_MODEL 2 /* auto-cycle to the next model after this many full turns */ #define SOFA_CAMERA_DIST 4.0 /* perspective divisor: bigger = flatter */ #define SOFA_UNIT_FRACTION 0.35 /* fraction of min(width,height) per model unit, at zoom 1.0 */ #define SOFA_ZOOM_MIN 0.2 @@ -36,15 +41,15 @@ typedef struct box3 { double x0, y0, z0, x1, y1, z1; } box3_t; /* the sofa: seat, backrest and two arms, in model units (roughly -1..1) */ static const box3_t sofa_parts[] = { - { -0.8, -0.3, -0.6, 0.8, 0.1, 0.6 }, /* seat */ - { -1.0, -0.3, 0.35, 1.0, 0.9, 0.6 }, /* backrest */ - { -1.0, -0.3, -0.6, -0.8, 0.5, 0.6 }, /* left arm */ - { 0.8, -0.3, -0.6, 1.0, 0.5, 0.6 }, /* right arm */ + { -0.8, -0.3, -0.5, 0.8, 0.1, 0.5 }, /* seat */ + { -1.0, -0.3, 0.5, 1.0, 0.9, 0.8 }, /* backrest */ + { -1.0, -0.3, -0.5, -0.8, 0.5, 0.5 }, /* left arm */ + { 0.8, -0.3, -0.5, 1.0, 0.5, 0.5 }, /* right arm */ }; /* cube corner edges, indexing box3_corners' bit-numbered corners (bit0=x, * bit1=y, bit2=z; each pair differs in exactly one bit) */ -static const int cube_edges[12][2] = +static const int box_cube_edges[12][2] = { { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, { 0, 2 }, { 1, 3 }, { 4, 6 }, { 5, 7 }, @@ -76,6 +81,213 @@ static const int ship_edges[18][2] = { 5, 8 }, { 8, 9 }, { 9, 5 }, /* right wing */ }; +/* a Cobra Mk. 3 from Elite */ +static const vec3_t cobra_vertices[28] = +{ + { 32/100.0, 0/100.0, 76/100.0 }, + { -32/100.0, 0/100.0, 76/100.0 }, + { 0/100.0, 26/100.0, 24/100.0 }, + {-120/100.0, -3/100.0, -8/100.0 }, + { 120/100.0, -3/100.0, -8/100.0 }, + { -88/100.0, 16/100.0, -40/100.0 }, + { 88/100.0, 16/100.0, -40/100.0 }, + { 128/100.0, -8/100.0, -40/100.0 }, + {-128/100.0, -8/100.0, -40/100.0 }, + { 0/100.0, 26/100.0, -40/100.0 }, + { -32/100.0, -24/100.0, -40/100.0 }, + { 32/100.0, -24/100.0, -40/100.0 }, + { -36/100.0, 8/100.0, -40/100.0 }, + { -8/100.0, 12/100.0, -40/100.0 }, + { 8/100.0, 12/100.0, -40/100.0 }, + { 36/100.0, 8/100.0, -40/100.0 }, + { 36/100.0, -12/100.0, -40/100.0 }, + { 8/100.0, -16/100.0, -40/100.0 }, + { -8/100.0, -16/100.0, -40/100.0 }, + { -36/100.0, -12/100.0, -40/100.0 }, + { 0/100.0, 0/100.0, 76/100.0 }, + { 0/100.0, 0/100.0, 90/100.0 }, + { -80/100.0, -6/100.0, -40/100.0 }, + { -80/100.0, 6/100.0, -40/100.0 }, + { -88/100.0, 0/100.0, -40/100.0 }, + { 80/100.0, 6/100.0, -40/100.0 }, + { 88/100.0, 0/100.0, -40/100.0 }, + { 80/100.0, -6/100.0, -40/100.0 }, +}; + +static const int cobra_edges[38][2] = +{ + { 0, 1 }, + { 0, 4 }, + { 1, 3 }, + { 3, 8 }, + { 4, 7 }, + { 6, 7 }, + { 6, 9 }, + { 5, 9 }, + { 5, 8 }, + { 2, 5 }, + { 2, 6 }, + { 3, 5 }, + { 4, 6 }, + { 1, 2 }, + { 0, 2 }, + { 8, 10 }, + { 10, 11 }, + { 7, 11 }, + { 1, 10 }, + { 0, 11 }, + { 1, 5 }, + { 0, 6 }, + { 20, 21 }, + { 12, 13 }, + { 18, 19 }, + { 14, 15 }, + { 16, 17 }, + { 15, 16 }, + { 14, 17 }, + { 13, 18 }, + { 12, 19 }, + { 2, 9 }, + { 22, 24 }, + { 23, 24 }, + { 22, 23 }, + { 25, 26 }, + { 26, 27 }, + { 25, 27 }, +}; + +/* the five Platonic solids, vertices normalised to unit circumradius */ + +static const vec3_t tetra_vertices[4] = +{ + { 0.577350269189626, 0.577350269189626, 0.577350269189626 }, + { 0.577350269189626, -0.577350269189626, -0.577350269189626 }, + { -0.577350269189626, 0.577350269189626, -0.577350269189626 }, + { -0.577350269189626, -0.577350269189626, 0.577350269189626 }, +}; +static const int tetra_edges[6][2] = +{ + { 0, 1 }, { 0, 2 }, { 0, 3 }, { 1, 2 }, + { 1, 3 }, { 2, 3 }, +}; + +static const vec3_t cube_vertices[8] = +{ + { 0.577350269189626, 0.577350269189626, 0.577350269189626 }, + { 0.577350269189626, 0.577350269189626, -0.577350269189626 }, + { 0.577350269189626, -0.577350269189626, 0.577350269189626 }, + { 0.577350269189626, -0.577350269189626, -0.577350269189626 }, + { -0.577350269189626, 0.577350269189626, 0.577350269189626 }, + { -0.577350269189626, 0.577350269189626, -0.577350269189626 }, + { -0.577350269189626, -0.577350269189626, 0.577350269189626 }, + { -0.577350269189626, -0.577350269189626, -0.577350269189626 }, +}; +static const int cube_edges[12][2] = +{ + { 0, 1 }, { 0, 2 }, { 0, 4 }, { 1, 3 }, + { 1, 5 }, { 2, 3 }, { 2, 6 }, { 3, 7 }, + { 4, 5 }, { 4, 6 }, { 5, 7 }, { 6, 7 }, +}; + +static const vec3_t octa_vertices[6] = +{ + { 1, 0, 0 }, + { -1, 0, 0 }, + { 0, 1, 0 }, + { 0, -1, 0 }, + { 0, 0, 1 }, + { 0, 0, -1 }, +}; +static const int octa_edges[12][2] = +{ + { 0, 2 }, { 0, 3 }, { 0, 4 }, { 0, 5 }, + { 1, 2 }, { 1, 3 }, { 1, 4 }, { 1, 5 }, + { 2, 4 }, { 2, 5 }, { 3, 4 }, { 3, 5 }, +}; + +static const vec3_t icosa_vertices[12] = +{ + { 0, 0.525731112119134, 0.85065080835204 }, + { 0, 0.525731112119134, -0.85065080835204 }, + { 0, -0.525731112119134, 0.85065080835204 }, + { 0, -0.525731112119134, -0.85065080835204 }, + { 0.525731112119134, 0.85065080835204, 0 }, + { 0.525731112119134, -0.85065080835204, 0 }, + { -0.525731112119134, 0.85065080835204, 0 }, + { -0.525731112119134, -0.85065080835204, 0 }, + { 0.85065080835204, 0, 0.525731112119134 }, + { 0.85065080835204, 0, -0.525731112119134 }, + { -0.85065080835204, 0, 0.525731112119134 }, + { -0.85065080835204, 0, -0.525731112119134 }, +}; +static const int icosa_edges[30][2] = +{ + { 0, 2 }, { 0, 4 }, { 0, 6 }, { 0, 8 }, + { 0, 10 }, { 1, 3 }, { 1, 4 }, { 1, 6 }, + { 1, 9 }, { 1, 11 }, { 2, 5 }, { 2, 7 }, + { 2, 8 }, { 2, 10 }, { 3, 5 }, { 3, 7 }, + { 3, 9 }, { 3, 11 }, { 4, 6 }, { 4, 8 }, + { 4, 9 }, { 5, 7 }, { 5, 8 }, { 5, 9 }, + { 6, 10 }, { 6, 11 }, { 7, 10 }, { 7, 11 }, + { 8, 9 }, { 10, 11 }, +}; + +static const vec3_t dodeca_vertices[20] = +{ + { 0.577350269189626, 0.577350269189626, 0.577350269189626 }, + { 0.577350269189626, 0.577350269189626, -0.577350269189626 }, + { 0.577350269189626, -0.577350269189626, 0.577350269189626 }, + { 0.577350269189626, -0.577350269189626, -0.577350269189626 }, + { -0.577350269189626, 0.577350269189626, 0.577350269189626 }, + { -0.577350269189626, 0.577350269189626, -0.577350269189626 }, + { -0.577350269189626, -0.577350269189626, 0.577350269189626 }, + { -0.577350269189626, -0.577350269189626, -0.577350269189626 }, + { 0, 0.35682208977309, 0.934172358962716 }, + { 0, 0.35682208977309, -0.934172358962716 }, + { 0, -0.35682208977309, 0.934172358962716 }, + { 0, -0.35682208977309, -0.934172358962716 }, + { 0.35682208977309, 0.934172358962716, 0 }, + { 0.35682208977309, -0.934172358962716, 0 }, + { -0.35682208977309, 0.934172358962716, 0 }, + { -0.35682208977309, -0.934172358962716, 0 }, + { 0.934172358962716, 0, 0.35682208977309 }, + { 0.934172358962716, 0, -0.35682208977309 }, + { -0.934172358962716, 0, 0.35682208977309 }, + { -0.934172358962716, 0, -0.35682208977309 }, +}; +static const int dodeca_edges[30][2] = +{ + { 0, 8 }, { 0, 12 }, { 0, 16 }, { 1, 9 }, + { 1, 12 }, { 1, 17 }, { 2, 10 }, { 2, 13 }, + { 2, 16 }, { 3, 11 }, { 3, 13 }, { 3, 17 }, + { 4, 8 }, { 4, 14 }, { 4, 18 }, { 5, 9 }, + { 5, 14 }, { 5, 19 }, { 6, 10 }, { 6, 15 }, + { 6, 18 }, { 7, 11 }, { 7, 15 }, { 7, 19 }, + { 8, 10 }, { 9, 11 }, { 12, 14 }, { 13, 15 }, + { 16, 17 }, { 18, 19 }, +}; + +/* a wireframe model: a vertex array plus an edge index array */ +typedef struct wireframe +{ + const vec3_t *vertices; + int nvertices; + const int (*edges)[2]; + int nedges; +} +wireframe_t; + +#define WIREFRAME(v, e) { v, NELEMS(v), e, NELEMS(e) } + +static const wireframe_t polyhedra[] = +{ + WIREFRAME(tetra_vertices, tetra_edges), + WIREFRAME(cube_vertices, cube_edges), + WIREFRAME(octa_vertices, octa_edges), + WIREFRAME(icosa_vertices, icosa_edges), + WIREFRAME(dodeca_vertices, dodeca_edges), +}; + static void box3_corners(const box3_t *box, vec3_t out[8]) { int i; @@ -122,34 +334,65 @@ static fix8_point_t project(vec3_t v, int cx, int cy, double unit) return p; } -result_t sofa_create(wuss_t *wuss, const colour_t *palette, sofa_task_t *task) +/* a marker square, SOFA_VERTEX_DOT on a side, centred on each projected + * vertex; drawn after the wireframe so the dots sit on top of the edges */ +static void draw_vertex_dots(screen_t *scr, + const fix8_point_t *screen, + int nvertices, + colour_t colour) { - wuss_task_t delegate; - box_t box; + int half; + int i; - task->bg = palette[palette_PICO8_DARK_PURPLE]; - task->line = palette[palette_PICO8_ORANGE]; - task->angle = 0.0; - task->zoom = 1.0; - task->spinning = true; - task->shape = sofa_SHAPE_SOFA; + half = SOFA_VERTEX_DOT / 2; - delegate = wuss_task_start(sofa_handle, task, wuss_NO_BACKGROUND); /* sofa_redraw paints its own background every frame */ - box = (box_t) BOX_POS_SIZE(250, 260, 180, 160); - - return wuss_window_create(wuss, - &box, - "Sofa", - wuss_WINDOW_NONE, - &delegate, - box.x1 - box.x0, - box.y1 - box.y0, - &task->window); + for (i = 0; i < nvertices; i++) + screen_fill_square(scr, + FIX8_ROUND_TO_INT(screen[i].x) - half, + FIX8_ROUND_TO_INT(screen[i].y) - half, + SOFA_VERTEX_DOT, + colour); } -void sofa_destroy(sofa_task_t *task) +result_t sofa_create(wuss_t*wuss, sofa_task_t*task) { - wuss_window_close(task->window); + wuss_task_t *delegate; + wuss_task_desc_t delegate_desc; + result_t rc; + + task->bg = colour_rgb(0x7E, 0x25, 0x53); + task->line = colour_rgb(0xFF, 0xA3, 0x00); + task->dot = colour_rgb(0xFF, 0xFF, 0xFF); + task->angle = 0.0; + task->zoom = 1.0; + task->spinning = true; + task->shape = sofa_SHAPE_SOFA; + task->turns = 0; + + /* sofa_redraw paints its own background every frame */ + delegate_desc.handle = sofa_handle; + delegate_desc.task_data = task; + delegate_desc.name = "sofa"; + rc = wuss_task_create(wuss, &delegate_desc, &delegate); + if (rc != result_OK) + { + free(task); /* nothing registered yet; the spawner will not free it */ + return rc; + } + wuss_task_set_autoclose(delegate, 1); + + rc = wuss_window_create_placed(delegate, + SIZE2D(180, 160), + "Sofa", + wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(180, 160), + SIZE2D(0, 0), + &task->window); + if (rc != result_OK) + wuss_task_destroy(delegate); /* unregister; its QUIT frees the task block */ + + return rc; } static result_t sofa_redraw(const wuss_event_t *event, void *task_data) @@ -169,11 +412,9 @@ static result_t sofa_redraw(const wuss_event_t *event, void *task_data) sx = event->data.redraw.scroll.x; sy = event->data.redraw.scroll.y; - screen_draw_rect(scr, + screen_fill_rect(scr, content->x0, - content->y0, - content->x1 - content->x0, - content->y1 - content->y0, + content->y0, box_size(content), sc->bg); cx = bounds->x0 - sx + (bounds->x1 - bounds->x0) / 2; @@ -196,13 +437,15 @@ static result_t sofa_redraw(const wuss_event_t *event, void *task_data) { const fix8_point_t *a, *b; - a = &screen[cube_edges[i][0]]; - b = &screen[cube_edges[i][1]]; + a = &screen[box_cube_edges[i][0]]; + b = &screen[box_cube_edges[i][1]]; screen_draw_line_wu_fix8(scr, a->x, a->y, b->x, b->y, sc->line); } + + draw_vertex_dots(scr, screen, 8, sc->dot); } } - else + else if (sc->shape == sofa_SHAPE_SHIP) { fix8_point_t screen[NELEMS(ship_vertices)]; int i; @@ -218,23 +461,69 @@ static result_t sofa_redraw(const wuss_event_t *event, void *task_data) b = &screen[ship_edges[i][1]]; screen_draw_line_wu_fix8(scr, a->x, a->y, b->x, b->y, sc->line); } + + draw_vertex_dots(scr, screen, (int) NELEMS(ship_vertices), sc->dot); + } + else if (sc->shape == sofa_SHAPE_COBRA) + { + fix8_point_t screen[NELEMS(cobra_vertices)]; + int i; + + for (i = 0; i < (int) NELEMS(cobra_vertices); i++) + screen[i] = project(rotate_xy(cobra_vertices[i], SOFA_TILT, sc->angle), cx, cy, unit); + + for (i = 0; i < (int) NELEMS(cobra_edges); i++) + { + const fix8_point_t *a, *b; + + a = &screen[cobra_edges[i][0]]; + b = &screen[cobra_edges[i][1]]; + screen_draw_line_wu_fix8(scr, a->x, a->y, b->x, b->y, sc->line); + } + + draw_vertex_dots(scr, screen, (int) NELEMS(cobra_vertices), sc->dot); + } + else + { + const wireframe_t *wf; + fix8_point_t screen[NELEMS(dodeca_vertices)]; /* largest solid */ + int i; + + wf = &polyhedra[sc->shape - sofa_SHAPE_TETRAHEDRON]; + + for (i = 0; i < wf->nvertices; i++) + screen[i] = project(rotate_xy(wf->vertices[i], SOFA_TILT, sc->angle), cx, cy, unit); + + for (i = 0; i < wf->nedges; i++) + { + const fix8_point_t *a, *b; + + a = &screen[wf->edges[i][0]]; + b = &screen[wf->edges[i][1]]; + screen_draw_line_wu_fix8(scr, a->x, a->y, b->x, b->y, sc->line); + } + + draw_vertex_dots(scr, screen, wf->nvertices, sc->dot); } return result_OK; } -static result_t sofa_mouse(wuss_window_t *window, wuss_button_t button, void *task_data) +static result_t sofa_mouse(wuss_window_t *window, + wuss_button_t button, + void *task_data) { sofa_task_t *sc; sc = task_data; - if (button == wuss_BUTTON_ADJUST) + if (button & wuss_BUTTON_ADJUST) { - sc->shape = (sc->shape == sofa_SHAPE_SOFA) ? sofa_SHAPE_SHIP : sofa_SHAPE_SOFA; + sc->shape = (sc->shape + 1) % sofa_SHAPE__LIMIT; + sc->turns = 0; wuss_window_invalidate_all(window); } - else + else if (button & wuss_BUTTON_SELECT) { sc->spinning = !sc->spinning; } @@ -242,7 +531,9 @@ static result_t sofa_mouse(wuss_window_t *window, wuss_button_t button, void *ta return result_OK; } -static result_t sofa_scroll(wuss_window_t *window, int delta, void *task_data) +static result_t sofa_scroll(wuss_window_t *window, + int delta, + void *task_data) { sofa_task_t *sc; @@ -267,14 +558,21 @@ static result_t sofa_idle(void *task_data) task->angle += SOFA_SPIN_PER_FRAME; if (task->angle > 2.0 * M_PI) + { task->angle -= 2.0 * M_PI; + if (++task->turns >= SOFA_ROTATIONS_PER_MODEL) + { + task->turns = 0; + task->shape = (task->shape + 1) % sofa_SHAPE__LIMIT; + } + } wuss_window_invalidate_all(task->window); return result_OK; } -result_t sofa_handle(wuss_window_t *window, +result_t sofa_handle(wuss_window_t *window, const wuss_event_t *event, void *task_data) { @@ -298,9 +596,8 @@ result_t sofa_handle(wuss_window_t *window, case wuss_EVENT_IDLE: return sofa_idle(task_data); - case wuss_EVENT_CLOSE: - wuss_window_close(window); - sc->window = NULL; + case wuss_EVENT_QUIT: + free(sc); /* task_data was calloc'd per instance by the spawner */ return result_OK; default: @@ -308,4 +605,4 @@ result_t sofa_handle(wuss_window_t *window, } } -#endif /* USE_SDL */ +#endif /* WUSS_APP */ diff --git a/libraries/wuss/test/tasks/sofa.h b/libraries/wuss/test/tasks/sofa.h index 20ace4aa..732d5dc5 100644 --- a/libraries/wuss/test/tasks/sofa.h +++ b/libraries/wuss/test/tasks/sofa.h @@ -1,9 +1,9 @@ -/* sofa.h -- wuss test - rotating wireframe sofa task */ +/* wuss/test/tasks/sofa.h -- rotating wireframe sofa task */ #ifndef TASKS_SOFA_H #define TASKS_SOFA_H -#ifdef USE_SDL +#ifdef WUSS_APP #include @@ -14,32 +14,37 @@ typedef enum sofa_shape { sofa_SHAPE_SOFA, - sofa_SHAPE_SHIP + sofa_SHAPE_SHIP, + sofa_SHAPE_COBRA, + sofa_SHAPE_TETRAHEDRON, + sofa_SHAPE_CUBE, + sofa_SHAPE_OCTAHEDRON, + sofa_SHAPE_ICOSAHEDRON, + sofa_SHAPE_DODECAHEDRON, + sofa_SHAPE__LIMIT } sofa_shape_t; -/* a wireframe sofa (seat, backrest, two arms) or a spaceship, +/* a wireframe sofa (seat, backrest, two arms), spaceship or Platonic solid, * spinning about its vertical axis; a Select click pauses/resumes the spin, * an Adjust click cycles the model */ typedef struct sofa_task { wuss_window_t *window; - colour_t bg, line; + colour_t bg, line, dot; double angle; double zoom; /* scroll-adjustable */ bool spinning; sofa_shape_t shape; + int turns; /* completed rotations of the current model */ } sofa_task_t; -wuss_event_fn_t sofa_handle; +wuss_window_fn_t sofa_handle; /* create the sofa window against the given wuss instance */ -result_t sofa_create(wuss_t *wuss, const colour_t *palette, sofa_task_t *task); +result_t sofa_create(wuss_t*wuss, sofa_task_t*task); -/* destroy the sofa window created by sofa_create */ -void sofa_destroy(sofa_task_t *task); - -#endif /* USE_SDL */ +#endif /* WUSS_APP */ #endif /* TASKS_SOFA_H */ diff --git a/libraries/wuss/test/tasks/swatches.c b/libraries/wuss/test/tasks/swatches.c new file mode 100644 index 00000000..cefd1b1d --- /dev/null +++ b/libraries/wuss/test/tasks/swatches.c @@ -0,0 +1,211 @@ +/* wuss/test/tasks/swatches.c -- fill-pattern swatch grid task */ + +#ifdef WUSS_APP + +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/utils.h" +#include "framebuf/pattern.h" +#include "geom/box.h" +#include "geom/point.h" +#include "geom/size.h" +#include "wuss/icon.h" +#include "wuss/menu.h" + +#include "swatches.h" + +/* ponytail: the app builds wuss with a 16-entry palette (apps/wuss/main.c) + * and wuss exposes no count. Hardcode it; add a wuss_palette_count() only if + * another palette size ever ships. */ +#define SWATCHES_NCOLOURS 16 +#define SWATCHES_CELL 16 /* px; cells butt together, no pitch */ + +#define SWATCHES_DOC_W (SWATCHES_NCOLOURS * SWATCHES_CELL) +#define SWATCHES_DOC_H (screen_PATTERN__LIMIT * SWATCHES_CELL) + +#define SWATCHES_PAPER_RGB 0xFF, 0xFF, 0xFF + +/* Redraw: plot one PATTERN swatch per (pattern, colour) pair -- row = + * pattern, column = palette index used as the pattern's ink over task->paper + * (white until a colour is picked from the pop-up menu). No icons are + * retained; wuss_icon_plot resolves the palette live, so a palette or paper + * change just redraws. */ +static result_t swatches_redraw(swatches_task_t *task, + const wuss_event_t *event) +{ + wuss_icon_spec_t spec; + const box_t *bounds; + point_t scroll; + int pat, col; + result_t rc; + + bounds = event->data.redraw.bounds; + scroll = event->data.redraw.scroll; + + memset(&spec, 0, sizeof(spec)); + spec.type = wuss_ICON_TYPE_PATTERN; + spec.bg = task->paper; + + for (pat = 0; pat < screen_PATTERN__LIMIT; pat++) + { + for (col = 0; col < SWATCHES_NCOLOURS; col++) + { + spec.bbox = (box_t) BOX_POS_SIZE(col * SWATCHES_CELL, + pat * SWATCHES_CELL, + SWATCHES_CELL, SWATCHES_CELL); + spec.fg = (wuss_colour_t) col; + spec.pattern = (screen_pattern_t) pat; + + rc = wuss_icon_plot(task->window, &spec, bounds, scroll); + if (rc != result_OK) + return rc; + } + } + + return result_OK; +} + +result_t swatches_create(wuss_t *wuss, swatches_task_t *task) +{ + wuss_task_t *delegate; + wuss_task_desc_t delegate_desc; + result_t rc; + + task->wuss = wuss; + task->window = NULL; + task->task = NULL; + task->colourmenu = NULL; + task->paper = wuss_nearest_colour(wuss, SWATCHES_PAPER_RGB); + + delegate_desc.handle = swatches_handle; + delegate_desc.task_data = task; + delegate_desc.name = "swatches"; + rc = wuss_task_create(wuss, &delegate_desc, &delegate); + if (rc != result_OK) + { + free(task); /* nothing registered yet; the spawner will not free it */ + return rc; + } + task->task = delegate; + + rc = wuss_colourmenu_create(&task->colourmenu, wuss, "Colour"); + if (rc != result_OK) + { + wuss_task_destroy(delegate); + return rc; + } + + rc = wuss_window_create_placed(delegate, + SIZE2D(SWATCHES_DOC_W, 140), + "Swatches", + wuss_WINDOW_NO_RESIZE_BLIT, /* grid spans the whole window; a resize redraws all of it */ + wuss_BACKDROP_COLOUR(wuss_nearest_colour(wuss, 0xDD, 0xDD, 0xDD)), + SIZE2D(SWATCHES_DOC_W, SWATCHES_DOC_H), + SIZE2D(0, 0), + &task->window); + if (rc != result_OK) + { + wuss_colourmenu_destroy(task->colourmenu); + wuss_task_destroy(delegate); /* unregister; its QUIT frees the task block */ + return rc; + } + + /* fully built: from here a last-window close reaps the task and its + * wuss_EVENT_QUIT frees task_data */ + wuss_task_set_autoclose(delegate, 1); + + return result_OK; +} + +/* A SELECT click on a cell makes that cell -- its fill pattern, its palette + * colour as the ink, the current paper -- the desktop backdrop. A MENU click + * pops the colour menu; its pick becomes the paper. The grid has no retained + * icons, so the cell is found by arithmetic on the click point (virtual + * content space). */ +static result_t swatches_click(swatches_task_t *task, + const wuss_event_t *event) +{ + wuss_backdrop_t backdrop; + point_t pt; + int pat, col; + + if (event->data.mouse.action != wuss_MOUSE_DOWN) + return result_OK; + + if (event->data.mouse.button & wuss_BUTTON_MENU) + return wuss_menu_open(task->task, + wuss_colourmenu_menu(task->colourmenu), + wuss_get_pointer(task->wuss), NULL); + + if (!(event->data.mouse.button & wuss_BUTTON_SELECT)) + return result_OK; + + pt = event->data.mouse.point; + col = pt.x / SWATCHES_CELL; + pat = pt.y / SWATCHES_CELL; + + if (pt.x < 0 || pt.y < 0 || + col >= SWATCHES_NCOLOURS || pat >= screen_PATTERN__LIMIT) + return result_OK; + + backdrop = (wuss_backdrop_t) wuss_BACKDROP_PATTERN((wuss_colour_t) col, + (screen_pattern_t) pat, + task->paper); + return wuss_set_backdrop(task->wuss, &backdrop); +} + +/* A pick from the colour menu this task opened: the chosen palette index + * becomes the paper the patterns mix over. */ +static result_t swatches_menu_select(swatches_task_t *task, + const wuss_event_t *event) +{ + wuss_colour_t picked; + int mine; + + picked = wuss_colourmenu_selected(task->colourmenu, event, &mine); + if (!mine) + return result_OK; + + task->paper = picked; + + /* every cell shares the new paper: dirty the whole document, not just the + * visible rectangle wuss_window_invalidate_all covers */ + wuss_window_invalidate_extent(task->window); + return result_OK; +} + +result_t swatches_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) +{ + swatches_task_t *task = task_data; + + NOT_USED(window); + + switch (event->kind) + { + case wuss_EVENT_REDRAW: + return swatches_redraw(task, event); + + case wuss_EVENT_MOUSE: + return swatches_click(task, event); + + case wuss_EVENT_MENU_SELECT: + return swatches_menu_select(task, event); + + case wuss_EVENT_QUIT: + wuss_colourmenu_destroy(task->colourmenu); + free(task_data); /* calloc'd per instance by the spawner */ + return result_OK; + + default: + return result_OK; + } +} + +#endif /* WUSS_APP */ diff --git a/libraries/wuss/test/tasks/swatches.h b/libraries/wuss/test/tasks/swatches.h new file mode 100644 index 00000000..71a6877c --- /dev/null +++ b/libraries/wuss/test/tasks/swatches.h @@ -0,0 +1,39 @@ +/* wuss/test/tasks/swatches.h -- fill-pattern swatch grid task */ + +#ifndef TASKS_SWATCHES_H +#define TASKS_SWATCHES_H + +#ifdef WUSS_APP + +/* ponytail: no #ifdef WUSS_COMPONENTS guard -- it is PUBLIC on DPTLib and ON + * by default, so the wuss app always has the colourmenu component. */ +#include "wuss/component/colourmenu.h" +#include "wuss/task.h" +#include "wuss/window.h" + +/* Grid of every (fill pattern, palette colour) pair: one row per built-in + * screen fill pattern, one column per system-palette entry, each cell a + * PATTERN icon packed edge to edge. The document is taller than the window + * so the grid scrolls through it. A MENU-button click pops a wuss_colourmenu; + * the picked colour becomes the paper the patterns mix over (white until + * then). */ + +typedef struct swatches_task +{ + wuss_t *wuss; + wuss_window_t *window; + wuss_task_t *task; /* delegate; opens the colour menu */ + wuss_colourmenu_t *colourmenu; + wuss_colour_t paper; /* pattern bg; the "mixing" colour */ +} +swatches_task_t; + +wuss_window_fn_t swatches_handle; + +/* create the swatches window against the given wuss instance */ +result_t swatches_create(wuss_t *wuss, swatches_task_t *task); + + +#endif /* WUSS_APP */ + +#endif /* TASKS_SWATCHES_H */ diff --git a/libraries/wuss/test/tasks/text.c b/libraries/wuss/test/tasks/text.c index 0202eabf..c296a052 100644 --- a/libraries/wuss/test/tasks/text.c +++ b/libraries/wuss/test/tasks/text.c @@ -1,8 +1,9 @@ -/* text.c -- wuss test - static paragraph task */ +/* wuss/test/tasks/text.c -- static paragraph task with a font picker */ -#ifdef USE_SDL +#ifdef WUSS_APP + +#include -#include #include #include @@ -15,6 +16,9 @@ #include "framebuf/palettes.h" #include "geom/box.h" #include "geom/point.h" +#include "io/path.h" +#include "text/bmtext.h" +#include "wuss/menu.h" #include "text.h" @@ -23,102 +27,170 @@ #endif static const char paragraph[] = - "Lorem ipsum dolor sit amet, consectetur adipiscing elit, " - "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; +"Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec mattis luctus libero. Donec imperdiet, velit quis venenatis iaculis, metus libero cursus ligula, egestas sagittis dui diam in mi."; + +/* ----------------------------------------------------------------------- */ -result_t text_create(wuss_t *wuss, - const colour_t *palette, - bmfont_t *font, - text_task_t *task) +/* load fonts[idx] if not already in hand; returns it or NULL on failure. + * name is the menu label for that row -- the font's leafname sans ".png". */ +static bmfont_t *text_load_font(text_task_t *task, + const char *resources, + int idx, + const char *name) { - wuss_task_t delegate; - box_t box; + const char *leaf; + const char *filename; + bmfont_t *font; result_t rc; - task->font = font; - task->bg = palette[palette_PICO8_BLUE]; /* matches the "Lorem Ipsum" window's bg, for bmfont_draw's glyph blending */ - task->fg = palette[palette_PICO8_WHITE]; - task->frame_count = 0; - task->resizing = true; + if (task->fonts[idx] != NULL) + return task->fonts[idx]; - delegate = wuss_task_start(text_handle, task, palette_PICO8_BLUE); - box = (box_t) BOX_POS_SIZE(120, 100, 220, 180); + leaf = path_join_leafname(name, "png"); + filename = path_join_filename(resources, 3, "resources", "bmfonts", leaf); - task->base_width = box.x1 - box.x0; - task->base_height = box.y1 - box.y0; - - rc = wuss_window_create(wuss, - &box, - "Lorem Ipsum", - wuss_WINDOW_NONE, - &delegate, - box.x1 - box.x0, - box.y1 - box.y0, - &task->window); + rc = bmfont_create(filename, &font); + if (rc != result_OK) + return NULL; - return rc; + task->fonts[idx] = font; + return font; } -void text_destroy(text_task_t *task) +/* switch the paragraph to the font at menu row idx (label name) */ +static result_t text_set_font(text_task_t *task, int idx, const char *name) { - wuss_window_close(task->window); + bmfont_t *font; + + if (idx < 0 || idx >= task->nfonts || idx == task->current) + return result_OK; + + font = text_load_font(task, task->resources, idx, name); + if (font == NULL) + return result_OK; /* leave the current font in place */ + + task->font = font; + task->current = idx; + + wuss_window_invalidate_all(task->window); + return result_OK; } -static result_t text_redraw(const wuss_event_t *event, void *task_data) +static result_t text_open_menu(text_task_t *task) { - text_task_t *tcx; - screen_t *scr; - const box_t *bounds; - int font_width, font_height, sy; - const char *string; - int stringlen; - point_t pos; + return wuss_menu_open(task->delegate, + wuss_fontmenu_menu(task->fontmenu), + wuss_get_pointer(task->wuss), NULL); +} - tcx = task_data; +/* ----------------------------------------------------------------------- */ - scr = event->data.redraw.scr; - bounds = event->data.redraw.bounds; - sy = event->data.redraw.scroll.y; +result_t text_create(wuss_t *wuss, + const char *resources, + text_task_t *task) +{ + wuss_task_t *delegate; + wuss_task_desc_t delegate_desc; + const char *bmfonts_dir; + const wuss_menu_t *menu; + size2d_t sz; + result_t rc; + + task->wuss = wuss; + task->font = wuss_get_font(wuss); + task->current = -1; /* the wuss system font is none of the picker's */ + task->bg = colour_rgb(0xFF, 0xFF, 0xFF); + task->fg = colour_rgb(0x00, 0x00, 0x00); + task->frame_count = 0; + task->resizing = true; - bmfont_get_info(tcx->font, &font_width, &font_height); + strncpy(task->resources, resources, sizeof(task->resources) - 1); + task->resources[sizeof(task->resources) - 1] = '\0'; - string = paragraph; - stringlen = (int) strlen(paragraph); + /* the picker: every ".png" font under resources/bmfonts, sorted, less any + * SYSTEM-class font (e.g. the one wuss draws menu ticks/arrows from) */ + bmfonts_dir = path_join_filename(task->resources, 2, "resources", "bmfonts"); + rc = wuss_fontmenu_create(&task->fontmenu, bmfonts_dir, "Font", wuss, NULL); + if (rc != result_OK) + { + free(task); /* nothing registered yet; the spawner will not free it */ + return rc; + } - pos.x = bounds->x0 + 4; - pos.y = bounds->y0 - sy + 4; + menu = wuss_fontmenu_menu(task->fontmenu); + task->nfonts = menu->nitems; + task->fonts = calloc((size_t) task->nfonts, sizeof(*task->fonts)); + if (task->nfonts > 0 && task->fonts == NULL) + { + wuss_fontmenu_destroy(task->fontmenu); + free(task); + return result_OOM; + } - while (stringlen > 0 && pos.y + font_height <= bounds->y1) + delegate_desc.handle = text_handle; + delegate_desc.task_data = task; + delegate_desc.name = "text"; + rc = wuss_task_create(wuss, &delegate_desc, &delegate); + if (rc != result_OK) { - int absolute_break, friendly_break; - bmfont_width_t width; + wuss_fontmenu_destroy(task->fontmenu); + free(task->fonts); + free(task); /* nothing registered yet; the spawner will not free it */ + return rc; + } + wuss_task_set_autoclose(delegate, 1); + task->delegate = delegate; + + sz = SIZE2D(220, 180); + task->base_width = sz.w; + task->base_height = sz.h; + + rc = wuss_window_create_placed(delegate, + sz, + "Lorem Ipsum", + wuss_WINDOW_NO_RESIZE_BLIT, /* paragraph reflows across the whole window, so a resize must redraw all of it, not just the newly (un)covered edge */ + wuss_BACKDROP_COLOUR(wuss_nearest_colour(wuss, 0xFF, 0xFF, 0xFF)), + sz, + SIZE2D(0, 0), + &task->window); + if (rc != result_OK) + wuss_task_destroy(delegate); /* unregister; its QUIT frees the task block */ - bmfont_measure(tcx->font, string, stringlen, bounds->x1 - 4 - pos.x, &absolute_break, &width); + return rc; +} - 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 */ - } +#define INSET 4 +#define LEADING 2 +#define MAX_LINES 64 /* paragraph is short and fixed; overflow is dropped */ - bmfont_draw(tcx->font, scr, string, friendly_break, tcx->fg, tcx->bg, &pos, NULL); +static result_t text_redraw(const wuss_event_t *event, void *task_data) +{ + text_task_t *tcx; + screen_t *scr; + const box_t *bounds; + int sx, sy; + bmtext_line_t lines[MAX_LINES]; + int nlines; + point_t origin; - string += friendly_break; - stringlen -= friendly_break; - while (stringlen > 0 && isspace((unsigned char) *string)) - { - string++; - stringlen--; - } + tcx = task_data; - pos.x = bounds->x0 + 4; - pos.y += font_height + 2; - } + scr = event->data.redraw.scr; + bounds = event->data.redraw.bounds; + sx = event->data.redraw.scroll.x; + sy = event->data.redraw.scroll.y; + + nlines = bmtext_layout(tcx->font, + paragraph, + (int) strlen(paragraph), + (bounds->x1 - INSET) - (bounds->x0 + INSET), + lines, + MAX_LINES); + + origin.x = bounds->x0 - sx + INSET; + origin.y = bounds->y0 - sy + INSET; + + bmtext_draw(tcx->font, scr, lines, nlines, tcx->fg, tcx->bg, LEADING, origin); return result_OK; } @@ -155,14 +227,14 @@ static result_t text_idle(void *task_data) angle = tcx->frame_count * (2.0 * M_PI / TEXT_RESIZE_PERIOD_FRAMES); width = tcx->base_width + (int) (TEXT_RESIZE_AMPLITUDE * sin(angle)); - rc = wuss_window_resize(tcx->window, width, height); + rc = wuss_window_resize(tcx->window, SIZE2D(width, height)); if (rc != result_OK) logf_warning("text_idle: wuss_window_resize(%d, %d) failed", width, height); return rc; } -result_t text_handle(wuss_window_t *window, +result_t text_handle(wuss_window_t *window, const wuss_event_t *event, void *task_data) { @@ -170,19 +242,43 @@ result_t text_handle(wuss_window_t *window, tcx = task_data; + NOT_USED(window); + switch (event->kind) { case wuss_EVENT_REDRAW: return text_redraw(event, task_data); case wuss_EVENT_MOUSE: - if (event->data.mouse.action != wuss_MOUSE_DOWN) + if (event->data.mouse.action == wuss_MOUSE_DOWN && + (event->data.mouse.button & wuss_BUTTON_MENU)) + return text_open_menu(tcx); + if (event->data.mouse.action != wuss_MOUSE_DOWN || + !(event->data.mouse.button & wuss_BUTTON_SELECT)) return result_OK; return text_mouse(task_data); - case wuss_EVENT_CLOSE: - wuss_window_close(window); - tcx->window = NULL; + case wuss_EVENT_MENU_SELECT: + { + const char *name; + + name = wuss_fontmenu_selected(tcx->fontmenu, event); + if (name != NULL) + return text_set_font(tcx, event->data.menu_select.index, name); + } + return result_OK; + + case wuss_EVENT_QUIT: + { + int i; + + for (i = 0; i < tcx->nfonts; i++) + if (tcx->fonts[i] != NULL) + bmfont_destroy(tcx->fonts[i]); + free(tcx->fonts); + wuss_fontmenu_destroy(tcx->fontmenu); + free(tcx); /* task_data was calloc'd per instance by the spawner */ + } return result_OK; case wuss_EVENT_IDLE: @@ -193,4 +289,4 @@ result_t text_handle(wuss_window_t *window, } } -#endif /* USE_SDL */ +#endif /* WUSS_APP */ diff --git a/libraries/wuss/test/tasks/text.h b/libraries/wuss/test/tasks/text.h index cf1dd7fe..f0c6ae36 100644 --- a/libraries/wuss/test/tasks/text.h +++ b/libraries/wuss/test/tasks/text.h @@ -1,42 +1,51 @@ -/* text.h -- wuss test - static paragraph task */ +/* wuss/test/tasks/text.h -- static paragraph task */ #ifndef TASKS_TEXT_H #define TASKS_TEXT_H -#ifdef USE_SDL +#ifdef WUSS_APP #include #include "framebuf/bmfont.h" #include "framebuf/colour.h" +#include "wuss/component/fontmenu.h" +#include "wuss/task.h" #include "wuss/window.h" /* window B's task: flows a fixed paragraph of placeholder text over its - * wuss-filled background, one line per bmfont_draw call */ + * wuss-filled background, one line per bmfont_draw call. A MENU click on the + * window opens a picker -- a wuss_fontmenu over resources/bmfonts -- that + * swaps the paragraph font in place. */ typedef struct text_task { - wuss_window_t *window; - bmfont_t *font; - colour_t bg, fg; - int base_width; /* content width when the window was created */ - int base_height; /* content height when the window was created */ - int frame_count; - bool resizing; /* toggled by a content click; text_step only - * resizes the window while this is true */ + wuss_window_t *window; + wuss_task_t *delegate; /* the wuss task backing this window */ + wuss_t *wuss; /* for wuss_get_pointer when opening the menu */ + wuss_fontmenu_t *fontmenu; /* the font picker; owns the menu and names */ + bmfont_t *font; /* currently shown; fonts[current] or sysfont */ + bmfont_t **fonts; /* one slot per menu item, lazily loaded */ + int nfonts; /* length of fonts[]; == menu item count */ + int current; /* index into fonts[], or -1 for sysfont */ + colour_t bg, fg; + char resources[256]; /* root the picker loads bmfonts from */ + int base_width; /* content width when the window was made */ + int base_height; /* content height when the window was made */ + int frame_count; + bool resizing; /* toggled by a content click; text_step only + * resizes the window while this is true */ } text_task_t; -wuss_event_fn_t text_handle; +wuss_window_fn_t text_handle; -/* create the paragraph-of-text window against the given wuss instance */ +/* create the paragraph-of-text window against the given wuss instance. + * resources is the root the font picker loads bmfonts from + * (resources/bmfonts/.png). */ result_t text_create(wuss_t *wuss, - const colour_t *palette, - bmfont_t *font, + const char *resources, text_task_t *task); -/* destroy the paragraph-of-text window created by text_create */ -void text_destroy(text_task_t *task); - -#endif /* USE_SDL */ +#endif /* WUSS_APP */ #endif /* TASKS_TEXT_H */ diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index 97a7ac2b..c19152b1 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -1,5 +1,6 @@ -/* wuss-test.c -- wuss - minimal window manager */ +/* wuss/test/wuss-test.c -- wuss - minimal window manager */ +#include #include #include #include @@ -19,493 +20,324 @@ #include "geom/box.h" #include "io/path.h" #include "wuss/wuss.h" +#include "wuss/task.h" #include "wuss/window.h" +#ifdef WUSS_MENUS +#include "wuss/menu.h" +#include "wuss/menu-desc.h" +#endif +#ifdef WUSS_COMPONENTS +#include "wuss/component/fontmenu.h" +#include "wuss/component/colourmenu.h" +#endif + +/* white-box: the menu-flash test drives picks through the icon layer and + * reads back struct wuss__menu / struct wuss_icon state directly */ +#if defined(WUSS_MENUS) && defined(WUSS_ICONS) +#include "../core/impl.h" +#include "../icon.h" +#endif #include "test/all-tests.h" /* ----------------------------------------------------------------------- */ -#ifdef USE_SDL - -#include - -#include "tasks/ball.h" -#include "tasks/blank.h" -#include "tasks/checker.h" -#include "tasks/curve.h" -#include "tasks/gradient.h" -#include "tasks/image.h" -#include "tasks/launcher.h" -#include "tasks/palette.h" -#include "tasks/sofa.h" -#include "tasks/text.h" - -/* Screen pixel format for the interactive test: 1 = 32bpp pixelfmt_bgrx8888 - * (feeds SDL directly, no per-frame conversion); 0 = pixelfmt_p4 paletted - * (exercises screen_copy_rect's nibble-packed blit path instead). */ -#define WUSS_TEST_32BPP 0 - -/* the launcher's spawn callbacks take no arguments, so the pieces they need - * are stashed here instead; wuss_interactive_test runs at most once per - * process, so file-scope statics are as good as a context struct */ -static wuss_t *g_wuss; -static const colour_t *g_palette; -static int g_npalette; -static const char *g_resources; -static bmfont_t *g_daydream_font; - -static ball_task_t g_ball_task; -static text_task_t g_text_task; -static blank_task_t g_blank_task; -static palette_task_t g_palette_task; -static image_task_t g_image_task; -static checker_task_t g_checker_task; -static curve_task_t g_curve_task; -static sofa_task_t g_sofa_task; -static gradient_task_t g_gradient_task; - -static result_t spawn_ball(void) { return ball_create(g_wuss, g_palette, &g_ball_task); } -static result_t spawn_text(void) { return text_create(g_wuss, g_palette, g_daydream_font, &g_text_task); } -static result_t spawn_blank(void) { return blank_create(g_wuss, g_npalette, &g_blank_task); } -static result_t spawn_palette(void) { return palette_create(g_wuss, g_palette, g_npalette, &g_palette_task); } -static result_t spawn_image(void) { return image_create(g_wuss, g_palette, g_resources, &g_image_task); } -static result_t spawn_checker(void) { return checker_create(g_wuss, g_palette, &g_checker_task); } -static result_t spawn_curve(void) { return curve_create(g_wuss, g_palette, &g_curve_task); } -static result_t spawn_sofa(void) { return sofa_create(g_wuss, g_palette, &g_sofa_task); } -static result_t spawn_gradient(void) { return gradient_create(g_wuss, &g_gradient_task); } - -static void destroy_ball(void) { ball_destroy(&g_ball_task); } -static void destroy_text(void) { text_destroy(&g_text_task); } -static void destroy_blank(void) { blank_destroy(&g_blank_task); } -static void destroy_palette(void) { palette_destroy(&g_palette_task); } -static void destroy_image(void) { image_destroy(&g_image_task); } -static void destroy_checker(void) { checker_destroy(&g_checker_task); } -static void destroy_curve(void) { curve_destroy(&g_curve_task); } -static void destroy_sofa(void) { sofa_destroy(&g_sofa_task); } -static void destroy_gradient(void) { gradient_destroy(&g_gradient_task); } - -static launcher_entry_t g_launcher_entries[] = -{ - { "Ball", spawn_ball, destroy_ball, false }, - { "Text", spawn_text, destroy_text, false }, - { "Blank", spawn_blank, destroy_blank, false }, - { "Palette", spawn_palette, destroy_palette, false }, - { "Image", spawn_image, destroy_image, false }, - { "Checker", spawn_checker, destroy_checker, false }, - { "Curve", spawn_curve, destroy_curve, false }, - { "Sofa", spawn_sofa, destroy_sofa, false }, - { "Gradient", spawn_gradient, destroy_gradient, false } -}; - -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; +/* The SDL interactive driver that used to live here (spawn_* callbacks, the + * launcher table and the run_wuss loop) is now the standalone `wuss` app + * in apps/wuss/main.c. This file is the Wuss unit test only. */ - 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); +typedef struct test_task +{ + int redraw_count; + int mouse_count; + wuss_mouse_action_t last_action; + int last_x, last_y; + wuss_button_t last_button; + int last_scroll_x, last_scroll_y; + int close_count; + int pre_close_count; + int show_count; + int pre_show_count; + int stop_count; + int open_count; + int palette_count; + int idle_count; + int veto_pre_close; + int veto_pre_show; + int menu_select_count; + int last_menu_index; } +test_task_t; -/* click windows to bring to front, drag titlebars to move, resize the - * SDL window to see the Wuss screen scale; F2 doubles the SDL window size, - * Shift-F2 halves it; F3 redraws the whole screen one pixel at a time, to - * catch tasks whose drawing routines misbehave under a 1x1 clip; Q or - * close to quit */ -static result_t wuss_interactive_test(const char *resources) +static result_t test_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) { - const int scr_width = 640; - const int scr_height = 480; -#if WUSS_TEST_32BPP - const int rowbytes = scr_width * 4; /* pixelfmt_bgrx8888: 4 bytes/pixel */ -#else - const int rowbytes = scr_width / 2; /* pixelfmt_p4: 2 pixels/byte */ -#endif + test_task_t *tc; - result_t rc; - const char *leafname; - const char *filename; - bmfont_t *font; - bmfont_t *daydream_font; - void *pixels; - bitmap_t bm; -#if !WUSS_TEST_32BPP - bitmap_t *disp; -#endif - screen_t scr; - colour_t palette[16]; - wuss_t *wuss; - launcher_task_t launcher_task; - SDL_Window *window; - SDL_Renderer *renderer; - SDL_Texture *texture; - bool quit; - bool garbage_pending; - bool pixel_stress_pending; - int i; - - define_pico8_palette(palette); - - leafname = path_join_leafname("digits-font", "png"); - filename = path_join_filename(resources, 3, "resources", "bmfonts", leafname); - rc = bmfont_create(filename, &font); - if (rc != result_OK) - goto Failure; + NOT_USED(window); - leafname = path_join_leafname("daydream-font", "png"); - filename = path_join_filename(resources, 3, "resources", "bmfonts", leafname); - rc = bmfont_create(filename, &daydream_font); - if (rc != result_OK) - goto Failure; + tc = task_data; - pixels = malloc(rowbytes * scr_height); - if (pixels == NULL) - goto Failure; + switch (event->kind) + { + case wuss_EVENT_REDRAW: + tc->redraw_count++; + break; -#if WUSS_TEST_32BPP - rc = bitmap_init(&bm, scr_width, scr_height, pixelfmt_bgrx8888, rowbytes, palette, pixels); -#else - rc = bitmap_init(&bm, scr_width, scr_height, pixelfmt_p4, rowbytes, palette, pixels); -#endif - if (rc != result_OK) - goto Failure; + case wuss_EVENT_MOUSE: + tc->mouse_count++; + tc->last_action = event->data.mouse.action; + tc->last_x = event->data.mouse.point.x; + tc->last_y = event->data.mouse.point.y; + tc->last_button = event->data.mouse.button; + break; - bitmap_clear(&bm, palette[palette_PICO8_WHITE]); + case wuss_EVENT_SCROLL: + tc->last_scroll_x = event->data.scroll.point.x; + tc->last_scroll_y = event->data.scroll.point.y; + break; - screen_for_bitmap(&scr, &bm); + case wuss_EVENT_CLOSE: + tc->close_count++; + break; - if (!SDL_Init(SDL_INIT_VIDEO)) - { - fprintf(stderr, "Error: SDL_Init: %s\n", SDL_GetError()); - goto Failure; - } + case wuss_EVENT_PRE_CLOSE: + tc->pre_close_count++; + if (tc->veto_pre_close) + return result_BAD_ARG; /* any non-OK return vetoes the close */ + break; - window = SDL_CreateWindow("DPTLib Wuss Test", scr_width, scr_height, 0); - if (window == NULL) - { - fprintf(stderr, "Error: SDL_CreateWindow: %s\n", SDL_GetError()); - goto Failure; - } + case wuss_EVENT_SHOW: + tc->show_count++; + break; - renderer = SDL_CreateRenderer(window, NULL); - if (renderer == NULL) - { - fprintf(stderr, "Error: SDL_CreateRenderer: %s\n", SDL_GetError()); - goto Failure; - } + case wuss_EVENT_PRE_SHOW: + tc->pre_show_count++; + if (tc->veto_pre_show) + return result_BAD_ARG; /* any non-OK return vetoes the reveal */ + break; - texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, - SDL_TEXTUREACCESS_STREAMING, - scr_width, scr_height); - if (texture == NULL) - { - fprintf(stderr, "Error: SDL_CreateTexture: %s\n", SDL_GetError()); - goto Failure; - } + case wuss_EVENT_MENU_SELECT: + tc->menu_select_count++; + tc->last_menu_index = event->data.menu_select.index; + break; - SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_NONE); - SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); /* keep pixels crisp when F2 scales the window up */ + case wuss_EVENT_QUIT: + tc->stop_count++; + break; - { - wuss_config_t config; + case wuss_EVENT_OPEN: + tc->open_count++; + break; + + case wuss_EVENT_PALETTE: + tc->palette_count++; + break; - config.titlebar_height = 0; - config.palette.title.bg = palette_PICO8_DARK_BLUE; - config.palette.title.fg = palette_PICO8_WHITE; - config.palette.back = palette_PICO8_GREEN; - config.palette.close = palette_PICO8_RED; - config.palette.toggle = palette_PICO8_ORANGE; - config.palette.resize = palette_PICO8_LAVENDER; - config.palette.arrows = palette_PICO8_BLUE; - config.palette.wells = palette_PICO8_DARK_BLUE; - config.palette.sausages = palette_PICO8_LIGHT_GREY; + case wuss_EVENT_IDLE: + tc->idle_count++; + break; - rc = wuss_create(&scr, font, palette, NELEMS(palette), &config, &wuss); - if (rc != result_OK) - goto Failure; + default: + break; } - g_wuss = wuss; - g_palette = palette; - g_npalette = NELEMS(palette); - g_resources = resources; - g_daydream_font = daydream_font; + return result_OK; +} - rc = launcher_create(wuss, g_launcher_entries, NELEMS(g_launcher_entries), font, palette, &launcher_task); - if (rc != result_OK) - goto Failure; +/* An IDLE handler that closes a window the first time it is broadcast to. On + * an autoclose task with that as its only window, the close reaps the task + * and frees its list node from inside the wuss_idle / wuss_set_palette walk; + * the test checks the walk survives it and still reaches the tasks behind. */ +static wuss_window_t *g_close_on_idle_win; - quit = false; - garbage_pending = false; - pixel_stress_pending = false; +static result_t close_on_idle_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) +{ + test_task_t *tc; - wuss_redraw(wuss); + NOT_USED(window); - while (!quit) - { - SDL_Event event; + tc = task_data; - while (SDL_PollEvent(&event)) + if (event->kind == wuss_EVENT_IDLE) + { + tc->idle_count++; + if (g_close_on_idle_win != NULL) { - switch (event.type) - { - case SDL_EVENT_QUIT: - quit = true; - break; - - case SDL_EVENT_KEY_UP: - if (event.key.key == SDLK_Q) - quit = true; - else if (event.key.key == SDLK_F1) - garbage_pending = true; - else if (event.key.key == SDLK_F3) - pixel_stress_pending = true; - else if (event.key.key == SDLK_F2) - { - int w, h; - - SDL_GetWindowSize(window, &w, &h); - if (event.key.mod & SDL_KMOD_SHIFT) - SDL_SetWindowSize(window, w / 2, h / 2); - else - SDL_SetWindowSize(window, w * 2, h * 2); - } - break; - - case SDL_EVENT_MOUSE_BUTTON_DOWN: - { - int x, y; - - sdl_pos_to_scr(window, scr_width, scr_height, event.button.x, event.button.y, &x, &y); - wuss_mouse_click(wuss, (point_t) { x, y }, sdl_button_to_wuss(event.button.button), wuss_MOUSE_DOWN, NULL); - } - break; - - case SDL_EVENT_MOUSE_BUTTON_UP: - { - int x, y; - - sdl_pos_to_scr(window, scr_width, scr_height, event.button.x, event.button.y, &x, &y); - wuss_mouse_click(wuss, (point_t) { x, y }, sdl_button_to_wuss(event.button.button), wuss_MOUSE_UP, NULL); - } - break; - - case SDL_EVENT_MOUSE_MOTION: - { - int x, y; - - sdl_pos_to_scr(window, scr_width, scr_height, event.motion.x, event.motion.y, &x, &y); - wuss_mouse_move(wuss, (point_t) { x, y }, NULL); - } - break; - - case SDL_EVENT_MOUSE_WHEEL: - { - int x, y; - - sdl_pos_to_scr(window, scr_width, scr_height, event.wheel.mouse_x, event.wheel.mouse_y, &x, &y); - wuss_scroll(wuss, (point_t) { x, y }, (int) event.wheel.y, NULL); - } - break; - - default: - break; - } - } + wuss_window_t *doomed; - wuss_idle(wuss); - - if (garbage_pending) - { - /* full-screen corruption, drawn over whatever's already on screen - * (windows included) and left untouched: nothing here is invalidated, - * so it stays put until something actually redraws over it, e.g. the - * ball's own small per-frame dirty rect eating a trail through it, or - * a window being dragged across it */ - unsigned char *p; - size_t n; - size_t i; - - p = pixels; - n = (size_t) rowbytes * scr_height; - for (i = 0; i < n; i++) - p[i] = (unsigned char) rand(); - - garbage_pending = false; + doomed = g_close_on_idle_win; + g_close_on_idle_win = NULL; + wuss_window_close(doomed); /* reaps this task mid-walk */ } - else if (pixel_stress_pending) - { - /* redraw the whole screen one pixel at a time: each wuss_redraw_dirty - * call is flushed before the next pixel is invalidated, so no two - * pixels are ever coalesced into one redraw. A task whose drawing - * routine assumes it always gets handed a multi-pixel/aligned clip - * (rather than trusting scr->clip) will visibly misdraw here even - * though it looks fine under normal, larger dirty regions. */ - int x, y; - - for (y = 0; y < scr_height; y++) - { - for (x = 0; x < scr_width; x++) - { - box_t px; - - px.x0 = x; px.y0 = y; - px.x1 = x + 1; px.y1 = y + 1; - - wuss_invalidate(wuss, &px); - wuss_redraw_dirty(wuss); - } - } + return result_OK; + } - pixel_stress_pending = false; - } - else - { - int ndirty, i; + if (event->kind == wuss_EVENT_QUIT) + tc->stop_count++; - ndirty = wuss_get_dirty_count(wuss); - for (i = 0; i < ndirty; i++) - { - box_t dirty; + return result_OK; +} - wuss_get_dirty(wuss, i, &dirty); - scr.clip = dirty; - screen_draw_rect(&scr, dirty.x0, dirty.y0, - dirty.x1 - dirty.x0, dirty.y1 - dirty.y0, - palette[palette_PICO8_WHITE]); - } +/* A redraw handler that paints its content as one-pixel horizontal lines + * whose colour encodes the document-space Y of each row, so a test can read + * the framebuffer back and tell not just whose pixels are where but whether + * a blit slid them by the right amount. task_data points to a colour_t used + * only as a base hue (its blue channel is replaced per row). NULL task_data + * means flood with a single fixed colour instead. */ +static result_t paint_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) +{ + const box_t *clip; + const box_t *bounds; + point_t scroll; + int y, doc_y; - /* narrowed above per dirty rect for the flash; screen_copy_rect (used - * for window-drag blitting) reads this clip too, so it must not leak - * into the next frame narrower than the whole screen */ - box_reset(&scr.clip); + NOT_USED(window); - wuss_redraw_dirty(wuss); - } + if (event->kind != wuss_EVENT_REDRAW) + return result_OK; -#if WUSS_TEST_32BPP - SDL_UpdateTexture(texture, NULL, bm.base, bm.rowbytes); /* bm is already bgrx8888: no conversion needed */ -#else - rc = bitmap_convert(&bm, pixelfmt_bgrx8888, &disp); - if (rc == result_OK) - { - SDL_UpdateTexture(texture, NULL, disp->base, disp->rowbytes); - free(disp->base); - free(disp); - } -#endif + clip = event->data.redraw.content; + bounds = event->data.redraw.bounds; + scroll = event->data.redraw.scroll; - SDL_RenderTexture(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); + if (task_data == NULL) + { + screen_fill_rect(event->data.redraw.scr, clip->x0, clip->y0, + SIZE2D(clip->x1 - clip->x0, clip->y1 - clip->y0), + colour_rgb(0xcc, 0xdd, 0xee)); + return result_OK; + } - SDL_Delay(1000 / 60); + for (y = clip->y0; y < clip->y1; y++) + { + doc_y = y - bounds->y0 + scroll.y; + screen_fill_rect(event->data.redraw.scr, clip->x0, y, + SIZE2D(clip->x1 - clip->x0, 1), + colour_rgb(0x20, 0x40, doc_y & 0xff)); } - for (i = 0; i < NELEMS(g_launcher_entries); i++) - if (g_launcher_entries[i].running) - g_launcher_entries[i].destroy(); - launcher_destroy(&launcher_task); + return result_OK; +} - wuss_destroy(wuss); - bmfont_destroy(font); - bmfont_destroy(daydream_font); +/* ----------------------------------------------------------------------- */ - SDL_DestroyTexture(texture); - SDL_DestroyRenderer(renderer); - SDL_DestroyWindow(window); - SDL_Quit(); +/* Most tests declare their test_task_t on the block stack. A registered + * task outlives that block (wuss only sweeps at wuss_destroy), so once the + * block exits the task's task_data dangles. Broadcast events (PALETTE, + * IDLE) walk every registered task and would read that freed stack. + * + * mk_task records every task it makes; reap_test_tasks destroys them all. + * Call reap_test_tasks() before any broadcast test and before + * wuss_destroy, so no stale stack is ever delivered to. */ +#define MK_TASK_MAX 64 +static wuss_task_t *mk_task_reg[MK_TASK_MAX]; +static int mk_task_count; + +/* Register a task in one call. Returns NULL on OOM; callers goto Failure. */ +static wuss_task_t *mk_task(wuss_t *wuss, + wuss_window_fn_t *handle, + void *task_data) +{ + wuss_task_desc_t desc; + wuss_task_t *task; - free(pixels); + desc.handle = handle; + desc.task_data = task_data; + desc.name = "wuss-test"; - return result_TEST_PASSED; + if (wuss_task_create(wuss, &desc, &task) != result_OK) + return NULL; + if (mk_task_count < MK_TASK_MAX) + mk_task_reg[mk_task_count++] = task; -Failure: + return task; +} - printf("wuss_interactive_test: failed (rc=0x%X)\n", rc); +/* Destroy every task mk_task created and forget them. Each + * wuss_task_destroy closes that task's windows and fires one QUIT. */ +static void reap_test_tasks(void) +{ + int i; - return result_TEST_FAILED; + for (i = 0; i < mk_task_count; i++) + wuss_task_destroy(mk_task_reg[i]); + mk_task_count = 0; } -#endif /* USE_SDL */ - /* ----------------------------------------------------------------------- */ -typedef struct test_task +#if defined(WUSS_MENUS) && defined(WUSS_ICONS) +/* Click menu row `row` of the open chain level `level` with `button`: a + * MOUSE_DOWN then MOUSE_UP at the row's on-screen centre, exactly as the + * real event pump would deliver them. */ +static void flash_pick_row(wuss_t *wuss, + struct wuss__menu *level, + int row, + wuss_button_t button) { - int redraw_count; - int mouse_count; - wuss_mouse_action_t last_action; - int last_x, last_y; - wuss_button_t last_button; - int close_count; - int stop_count; - int open_count; + box_t content; + box_t bbox; + box_t screen_box; + point_t scroll; + point_t at; + + wuss_window_get_content_bounds(level->window, &content); + wuss_window_get_scroll(level->window, &scroll); + wuss_icon_get_bbox(level->icons[row], &bbox); + wuss__icon_box_to_screen(&content, scroll, &bbox, &screen_box); + + at.x = (screen_box.x0 + screen_box.x1) / 2; + at.y = (screen_box.y0 + screen_box.y1) / 2; + + wuss_mouse_click(wuss, at, button, wuss_MOUSE_DOWN, NULL); + wuss_mouse_click(wuss, at, button, wuss_MOUSE_UP, NULL); } -test_task_t; - -static result_t test_handle(wuss_window_t *window, - const wuss_event_t *event, - void *task_data) -{ - test_task_t *tc; - - NOT_USED(window); - - tc = task_data; +#endif - switch (event->kind) - { - case wuss_EVENT_REDRAW: - tc->redraw_count++; - break; +/* ----------------------------------------------------------------------- */ - case wuss_EVENT_MOUSE: - tc->mouse_count++; - tc->last_action = event->data.mouse.action; - tc->last_x = event->data.mouse.point.x; - tc->last_y = event->data.mouse.point.y; - tc->last_button = event->data.mouse.button; - break; +/* Total area covered by the dirty list, counting overlapped pixels once. + * Summing each region's area instead would double-count wherever two + * invalidations overlap, which they legitimately do. */ +static int dirty_union_area(wuss_t *wuss, const box_t *bounds) +{ + static unsigned char covered[512 * 512]; - case wuss_EVENT_CLOSE: - tc->close_count++; - break; + box_t region; + int w, h, i, x, y, area; - case wuss_EVENT_QUIT: - tc->stop_count++; - break; + w = bounds->x1 - bounds->x0; + h = bounds->y1 - bounds->y0; + if (w <= 0 || h <= 0 || w > 512 || h > 512) + return -1; - case wuss_EVENT_OPEN: - tc->open_count++; - break; + memset(covered, 0, (size_t) w * h); - default: - break; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + for (y = MAX(region.y0, bounds->y0); y < MIN(region.y1, bounds->y1); y++) + for (x = MAX(region.x0, bounds->x0); x < MIN(region.x1, bounds->x1); x++) + covered[(y - bounds->y0) * w + (x - bounds->x0)] = 1; } - return result_OK; + area = 0; + for (i = 0; i < w * h; i++) + area += covered[i]; + + return area; } -/* ----------------------------------------------------------------------- */ +#if defined(WUSS_FURNITURE) && defined(WUSS_ICONS) result_t wuss_test(const char *resources) { @@ -517,8 +349,8 @@ result_t wuss_test(const char *resources) wuss_t *wuss; wuss_config_t bad_config; wuss_t *bad_wuss; - test_task_t tc_a, tc_b, tc_c, tc_d; - wuss_task_t delegate_a, delegate_b, delegate_c, delegate_d; + test_task_t tc_a, tc_b, tc_c, tc_d; + wuss_task_t *delegate_a, *delegate_b, *delegate_c, *delegate_d; box_t box_a, box_b, box_c, box_d; wuss_window_t *win_a, *win_b, *win_c, *win_d; wuss_window_t *hit; @@ -527,14 +359,16 @@ result_t wuss_test(const char *resources) int width, height; const colour_t custom_palette[2] = { 0, 0 }; - NOT_USED(resources); +#if !(defined(WUSS_MENUS) && defined(WUSS_ICONS)) + NOT_USED(resources); /* only the menu-flash test reads it */ +#endif rowbytes = 200 * 4; pixels = malloc(rowbytes * 200); if (pixels == NULL) goto Failure; - rc = bitmap_init(&bm, 200, 200, pixelfmt_bgrx8888, rowbytes, NULL, pixels); + rc = bitmap_init(&bm, SIZE2D(200, 200), pixelfmt_bgrx8888, rowbytes, NULL, pixels); if (rc != result_OK) goto Failure; @@ -542,17 +376,17 @@ result_t wuss_test(const char *resources) printf("test: wuss_create with bad titlebar colour index\n"); - bad_config.titlebar_height = 0; - bad_config.palette.title.bg = 999; - bad_config.palette.title.fg = 0; - bad_config.palette.back = 0; - bad_config.palette.close = 0; - bad_config.palette.toggle = 0; - bad_config.palette.resize = 0; - bad_config.palette.arrows = 0; - bad_config.palette.wells = 0; - bad_config.palette.sausages = 0; - rc = wuss_create(&scr, NULL, NULL, 0, &bad_config, &bad_wuss); + bad_config.titlebar_height = 0; + bad_config.furniture.title.bg = 100; /* in range as a byte, but well past any test palette and below wuss_COLOUR_SYMBOLIC */ + bad_config.furniture.title.fg = 0; + bad_config.furniture.back = 0; + bad_config.furniture.close = 0; + bad_config.furniture.toggle = 0; + bad_config.furniture.resize = 0; + bad_config.furniture.scroll.arrows = 0; + bad_config.furniture.scroll.wells = 0; + bad_config.furniture.scroll.sausages = 0; + rc = wuss_create(&scr, NULL, 0, NULL, 0, &bad_config, NULL, &bad_wuss); if (rc != result_WUSS_BAD_COLOUR) goto Failure; @@ -561,7 +395,7 @@ result_t wuss_test(const char *resources) { wuss_t *custom_wuss; - rc = wuss_create(&scr, NULL, custom_palette, 2, NULL, &custom_wuss); + rc = wuss_create(&scr, NULL, 0, custom_palette, 2, NULL, NULL, &custom_wuss); if (rc != result_OK) goto Failure; wuss_destroy(custom_wuss); @@ -569,72 +403,130 @@ result_t wuss_test(const char *resources) printf("test: wuss_create with default palette\n"); - rc = wuss_create(&scr, NULL, NULL, 0, NULL, &wuss); + rc = wuss_create(&scr, NULL, 0, NULL, 0, NULL, NULL, &wuss); if (rc != result_OK) goto Failure; + printf("test: symbolic wuss_colour_t resolves against palette and config\n"); + + { + /* red, green, blue, white, black (colour_t primary is 0xAABBGGRR) -- + * enough for the named symbolics to land on distinct, checkable + * indices. */ + static const colour_t sympal[5] = + { + { 0xFF0000FF }, { 0xFF00FF00 }, { 0xFFFF0000 }, + { 0xFFFFFFFF }, { 0xFF000000 } + }; + wuss_config_t symcfg; + wuss_t *symw; + wuss_window_t *symwin; + wuss_task_t *symdel; + box_t symbox; + + memset(&symcfg, 0, sizeof(symcfg)); + symcfg.furniture.title.bg = wuss_COLOUR_BLUE; /* -> index 2 */ + symcfg.furniture.title.fg = wuss_COLOUR_WHITE; /* -> index 3 */ + symcfg.backdrop = wuss_BACKDROP_COLOUR(wuss_COLOUR_GREEN); /* -> 1 */ + + rc = wuss_create(&scr, NULL, 0, sympal, 5, &symcfg, NULL, &symw); + if (rc != result_OK) + goto Failure; + + /* named colours resolve to nearest-palette-entry; raw indices and + * wuss_NO_BACKGROUND pass straight through; chrome roles echo the + * resolved config. */ + if (wuss__resolve_colour(symw, wuss_COLOUR_RED) != 0 || + wuss__resolve_colour(symw, wuss_COLOUR_GREEN) != 1 || + wuss__resolve_colour(symw, wuss_COLOUR_BLUE) != 2 || + wuss__resolve_colour(symw, wuss_COLOUR_WHITE) != 3 || + wuss__resolve_colour(symw, wuss_COLOUR_BLACK) != 4 || + wuss__resolve_colour(symw, 2) != 2 || + wuss__resolve_colour(symw, wuss_NO_BACKGROUND) != wuss_NO_BACKGROUND || + wuss__resolve_colour(symw, wuss_COLOUR_TITLE_BG) != 2 || + wuss__resolve_colour(symw, wuss_COLOUR_TITLE_FG) != 3 || + wuss__resolve_colour(symw, wuss_COLOUR_BACKDROP) != 1) + { + wuss_destroy(symw); + goto Failure; + } + + /* a symbolic colour is accepted (and stored resolved) where a client + * passes a wuss_colour_t. */ + symdel = mk_task(symw, NULL, NULL); + if (symdel == NULL) { wuss_destroy(symw); goto Failure; } + symbox.x0 = 0; symbox.y0 = 0; symbox.x1 = 80; symbox.y1 = 80; + rc = wuss_window_create(symdel, &symbox, "sym", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_COLOUR_RED), + SIZE2D(80, 80), SIZE2D(80, 80), &symwin); + if (rc != result_OK) { wuss_destroy(symw); goto Failure; } + if (symwin->bg.colour != 0) /* wuss_COLOUR_RED -> palette index 0 */ + { + wuss_destroy(symw); + goto Failure; + } + + wuss_destroy(symw); /* sweeps symdel too */ + mk_task_count = 0; /* drop the now-stale registry entry */ + } + printf("test: window_create too small\n"); box_a.x0 = 0; box_a.y0 = 0; box_a.x1 = 100; box_a.y1 = 0; /* zero-height content is invalid regardless of furniture */ - rc = wuss_window_create(wuss, + rc = wuss_window_create(mk_task(wuss, NULL, NULL), &box_a, "toosmall", wuss_WINDOW_NONE, - NULL, - box_a.x1 - box_a.x0, - box_a.y1 - box_a.y0, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_a), + SIZE2D(0, 0), &win_a); if (rc != result_WUSS_TOO_SMALL) goto Failure; printf("test: create overlapping windows A and B\n"); - tc_a.redraw_count = 0; - tc_a.mouse_count = 0; - tc_a.open_count = 0; - delegate_a.handle = test_handle; - delegate_a.task_data = &tc_a; - delegate_a.bg = wuss_NO_BACKGROUND; + memset(&tc_a, 0, sizeof(tc_a)); + delegate_a = mk_task(wuss, test_handle, &tc_a); + if (delegate_a == NULL) goto Failure; box_a.x0 = 0; box_a.y0 = 0; box_a.x1 = 100; box_a.y1 = 100; - rc = wuss_window_create(wuss, + rc = wuss_window_create(delegate_a, &box_a, "A", wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - &delegate_a, - box_a.x1 - box_a.x0, - box_a.y1 - box_a.y0, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_a), + SIZE2D(0, 0), &win_a); if (rc != result_OK) goto Failure; - tc_b.redraw_count = 0; - tc_b.mouse_count = 0; - delegate_b.handle = test_handle; - delegate_b.task_data = &tc_b; - delegate_b.bg = wuss_NO_BACKGROUND; + memset(&tc_b, 0, sizeof(tc_b)); + delegate_b = mk_task(wuss, test_handle, &tc_b); + if (delegate_b == NULL) goto Failure; box_b.x0 = 50; box_b.y0 = 50; box_b.x1 = 150; box_b.y1 = 150; - rc = wuss_window_create(wuss, + rc = wuss_window_create(delegate_b, &box_b, "B", wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - &delegate_b, - box_b.x1 - box_b.x0, - box_b.y1 - box_b.y0, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_b), + SIZE2D(0, 0), &win_b); if (rc != result_OK) goto Failure; @@ -703,7 +595,7 @@ result_t wuss_test(const char *resources) printf("test: z-order hit test and local coordinate translation (B on top)\n"); tc_b.mouse_count = 0; - rc = wuss_mouse_click(wuss, (point_t) { 75, 75 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); + rc = wuss_mouse_click(wuss, POINT(75, 75), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); if (rc != result_OK) goto Failure; if (hit != win_b) @@ -713,46 +605,53 @@ result_t wuss_test(const char *resources) if (tc_b.last_action != wuss_MOUSE_DOWN || tc_b.last_x != 25 || tc_b.last_y != 25) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { 75, 75 }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(75, 75), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; - printf("test: clicking a window's close icon sends wuss_EVENT_CLOSE, not a drag\n"); + printf("test: close icon runs wuss_window_try_close; a PRE_CLOSE veto keeps the window\n"); - tc_a.close_count = 0; - rc = wuss_mouse_click(wuss, (point_t) { 6, 11 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* A's close icon */ - if (rc != result_OK) - goto Failure; + tc_a.pre_close_count = 0; + tc_a.close_count = 0; + tc_a.veto_pre_close = 1; + rc = wuss_mouse_click(wuss, POINT(6, 11), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* A's close icon */ + if (rc != result_BAD_ARG) + goto Failure; /* the veto's non-OK return propagates out of the click */ if (hit != win_a) goto Failure; - if (tc_a.close_count != 1) - goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { 31, 36 }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); /* if the close click had started a drag, this would move A */ + rc = wuss_mouse_click(wuss, POINT(31, 36), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); /* if the close click had started a drag, this would move A */ if (rc != result_OK) goto Failure; - wuss_window_get_visible_bounds(win_a, &visible); + if (tc_a.pre_close_count != 1) + goto Failure; /* the close icon asked the task first */ + if (tc_a.close_count != 0) + goto Failure; /* vetoed: no CLOSE, window not torn down */ + + wuss_window_get_visible_bounds(win_a, &visible); /* still alive */ if (visible.x0 != 0 || visible.y0 != 0) goto Failure; /* unmoved: no drag was started by the close click */ + tc_a.veto_pre_close = 0; + printf("test: click-to-front changes subsequent overlap hits\n"); tc_a.mouse_count = 0; - rc = wuss_mouse_click(wuss, (point_t) { 31, 11 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* A's titlebar, above its content, clear of the close icon */ + rc = wuss_mouse_click(wuss, POINT(31, 11), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* A's titlebar, above its content, clear of the close icon */ if (rc != result_OK) goto Failure; if (hit != win_a) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { 31, 11 }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(31, 11), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; printf("test: content click does not change z-order\n"); tc_b.mouse_count = 0; - rc = wuss_mouse_click(wuss, (point_t) { 120, 120 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* B's content, only within B */ + rc = wuss_mouse_click(wuss, POINT(120, 120), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* B's content, only within B */ if (rc != result_OK) goto Failure; if (hit != win_b) @@ -760,12 +659,12 @@ result_t wuss_test(const char *resources) if (tc_b.mouse_count != 1) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { 120, 120 }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(120, 120), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; tc_a.mouse_count = 0; - rc = wuss_mouse_click(wuss, (point_t) { 75, 75 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* A still topmost: B's content click above didn't bring it to front */ + rc = wuss_mouse_click(wuss, POINT(75, 75), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* A still topmost: B's content click above didn't bring it to front */ if (rc != result_OK) goto Failure; if (hit != win_a) @@ -773,14 +672,14 @@ result_t wuss_test(const char *resources) if (tc_a.mouse_count != 1 || tc_a.last_x != 74 || tc_a.last_y != 54) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { 75, 75 }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(75, 75), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; printf("test: titlebar click starts a drag, not delivered as content\n"); tc_a.mouse_count = 0; - rc = wuss_mouse_click(wuss, (point_t) { 31, 11 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* A's titlebar, A already topmost, clear of the close icon */ + rc = wuss_mouse_click(wuss, POINT(31, 11), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* A's titlebar, A already topmost, clear of the close icon */ if (rc != result_OK) goto Failure; if (hit != win_a) @@ -796,7 +695,7 @@ result_t wuss_test(const char *resources) before_a = tc_a.redraw_count; before_b = tc_b.redraw_count; - rc = wuss_mouse_move(wuss, (point_t) { 31, 36 }, &hit); + rc = wuss_mouse_move(wuss, POINT(31, 36), &hit); if (rc != result_OK) goto Failure; if (hit != win_a) @@ -821,7 +720,7 @@ result_t wuss_test(const char *resources) printf("test: mouse-up ends the drag\n"); - rc = wuss_mouse_click(wuss, (point_t) { 31, 36 }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(31, 36), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; if (hit != win_a) @@ -829,13 +728,13 @@ result_t wuss_test(const char *resources) printf("test: Adjust-drag moves a window without bringing it to front\n"); - rc = wuss_mouse_click(wuss, (point_t) { 140, 35 }, wuss_BUTTON_ADJUST, wuss_MOUSE_DOWN, &hit); /* B's titlebar, clear of A */ + rc = wuss_mouse_click(wuss, POINT(140, 35), wuss_BUTTON_ADJUST, wuss_MOUSE_DOWN, &hit); /* B's titlebar, clear of A */ if (rc != result_OK) goto Failure; if (hit != win_b) goto Failure; - rc = wuss_mouse_move(wuss, (point_t) { 145, 60 }, &hit); + rc = wuss_mouse_move(wuss, POINT(145, 60), &hit); if (rc != result_OK) goto Failure; if (hit != win_b) @@ -845,23 +744,23 @@ result_t wuss_test(const char *resources) if (visible.x0 != 54 || visible.y0 != 54) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { 145, 60 }, wuss_BUTTON_ADJUST, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(145, 60), wuss_BUTTON_ADJUST, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; if (hit != win_b) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { 75, 75 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* within both A and B; A still topmost */ + rc = wuss_mouse_click(wuss, POINT(75, 75), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* within both A and B; A still topmost */ if (rc != result_OK) goto Failure; if (hit != win_a) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { 75, 75 }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(75, 75), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; - rc = wuss_mouse_move(wuss, (point_t) { 200, 200 }, &hit); /* off all windows, drag must have ended */ + rc = wuss_mouse_move(wuss, POINT(200, 200), &hit); /* off all windows, drag must have ended */ if (rc != result_OK) goto Failure; if (hit != NULL) @@ -876,13 +775,13 @@ result_t wuss_test(const char *resources) printf("test: window_resize valid and too-small cases\n"); - rc = wuss_window_resize(win_a, 50, 0); /* zero-height content is invalid */ + rc = wuss_window_resize(win_a, SIZE2D(50, 0)); /* zero-height content is invalid */ if (rc != result_WUSS_TOO_SMALL) goto Failure; if (tc_a.open_count != 1) goto Failure; /* rejected resize: no wuss_EVENT_OPEN */ - rc = wuss_window_resize(win_a, 50, 50); + rc = wuss_window_resize(win_a, SIZE2D(50, 50)); if (rc != result_OK) goto Failure; if (tc_a.open_count != 2) @@ -902,25 +801,78 @@ result_t wuss_test(const char *resources) if (width != 52 || height != 72) goto Failure; + printf("test: window_resize honours the requested size even past the " + "screen edge\n"); + + /* screen is 200x200; win_a's visible box sits at (0,25) with a 1px + * outline all round and a 20px titlebar. asking for a 500x500 content + * area is honoured verbatim; the visible box simply overhangs the + * screen (width 500+2, height 500+2+20). */ + rc = wuss_window_resize(win_a, SIZE2D(500, 500)); + if (rc != result_OK) + goto Failure; + wuss_window_get_content_bounds(win_a, &content); + if (content.x1 - content.x0 != 500 || content.y1 - content.y0 != 500) + goto Failure; + wuss_window_get_visible_bounds(win_a, &visible); + if (visible.x1 != 502 || visible.y1 != 547) + goto Failure; + + /* a smaller request is likewise honoured verbatim */ + rc = wuss_window_resize(win_a, SIZE2D(60, 40)); + if (rc != result_OK) + goto Failure; + wuss_window_get_content_bounds(win_a, &content); + if (content.x1 - content.x0 != 60 || content.y1 - content.y0 != 40) + goto Failure; + + /* restore for the tests that follow */ + rc = wuss_window_resize(win_a, SIZE2D(50, 50)); + if (rc != result_OK) + goto Failure; + + printf("test: window_create can never make a window bigger than the " + "screen\n"); + + { + box_t box_big; + wuss_window_t *win_big; + + /* content box asks for 10,10..400,400; the on-screen nudge pulls the + * visible top-left (10-1 outline) back to (0,0), then the clamp caps + * the content at 200 - 2*1 - 20(titlebar) tall, 200 - 2*1 wide. */ + box_big.x0 = 10; box_big.y0 = 10; box_big.x1 = 400; box_big.y1 = 400; + rc = wuss_window_create(mk_task(wuss, NULL, NULL), &box_big, "BIG", wuss_WINDOW_NO_VSCROLL | + wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(400, 400), SIZE2D(0, 0), &win_big); + if (rc != result_OK) + goto Failure; + wuss_window_get_visible_bounds(win_big, &visible); + if (visible.x0 != 0 || visible.y0 != 0 || + visible.x1 != 200 || visible.y1 != 200) + goto Failure; + wuss_window_close(win_big); + } + printf("test: title-less, no-outline window has no furniture, so visible == content\n"); tc_d.redraw_count = 0; tc_d.mouse_count = 0; - delegate_d.handle = test_handle; - delegate_d.task_data = &tc_d; - delegate_d.bg = wuss_NO_BACKGROUND; + delegate_d = mk_task(wuss, test_handle, &tc_d); + if (delegate_d == NULL) goto Failure; box_d.x0 = 0; box_d.y0 = 160; box_d.x1 = 30; box_d.y1 = 175; /* shorter than the 20px titlebar_height, still valid: no titlebar to fit */ - rc = wuss_window_create(wuss, + rc = wuss_window_create(delegate_d, &box_d, "ignored", wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - &delegate_d, - box_d.x1 - box_d.x0, - box_d.y1 - box_d.y0, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_d), + SIZE2D(0, 0), &win_d); if (rc != result_OK) goto Failure; @@ -932,7 +884,7 @@ result_t wuss_test(const char *resources) printf("test: click within a title-less window's top edge is delivered as content, not a drag\n"); - rc = wuss_mouse_click(wuss, (point_t) { 5, 165 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); + rc = wuss_mouse_click(wuss, POINT(5, 165), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); if (rc != result_OK) goto Failure; if (hit != win_d) @@ -940,79 +892,77 @@ result_t wuss_test(const char *resources) if (tc_d.mouse_count != 1 || tc_d.last_action != wuss_MOUSE_DOWN || tc_d.last_x != 5 || tc_d.last_y != 5) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { 5, 165 }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(5, 165), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; printf("test: content click on a title-less window does not change z-order\n"); { - test_task_t tc_e, tc_f; - wuss_task_t delegate_e, delegate_f; + static test_task_t tc_e, tc_f; + wuss_task_t *delegate_e, *delegate_f; box_t box_e, box_f; wuss_window_t *win_e, *win_f; tc_e.redraw_count = 0; tc_e.mouse_count = 0; - delegate_e.handle = test_handle; - delegate_e.task_data = &tc_e; - delegate_e.bg = wuss_NO_BACKGROUND; + delegate_e = mk_task(wuss, test_handle, &tc_e); + if (delegate_e == NULL) goto Failure; box_e.x0 = 100; box_e.y0 = 0; box_e.x1 = 150; box_e.y1 = 50; - rc = wuss_window_create(wuss, + rc = wuss_window_create(delegate_e, &box_e, NULL, wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - &delegate_e, - box_e.x1 - box_e.x0, - box_e.y1 - box_e.y0, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_e), + SIZE2D(0, 0), &win_e); if (rc != result_OK) goto Failure; tc_f.redraw_count = 0; tc_f.mouse_count = 0; - delegate_f.handle = test_handle; - delegate_f.task_data = &tc_f; - delegate_f.bg = wuss_NO_BACKGROUND; + delegate_f = mk_task(wuss, test_handle, &tc_f); + if (delegate_f == NULL) goto Failure; box_f.x0 = 130; box_f.y0 = 20; box_f.x1 = 180; box_f.y1 = 70; - rc = wuss_window_create(wuss, + rc = wuss_window_create(delegate_f, &box_f, NULL, wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - &delegate_f, - box_f.x1 - box_f.x0, - box_f.y1 - box_f.y0, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_f), + SIZE2D(0, 0), &win_f); if (rc != result_OK) goto Failure; /* F was created after E, so F is topmost; clicking E's exposed content * (outside the overlap) is delivered to E but must not raise it */ - rc = wuss_mouse_click(wuss, (point_t) { 110, 10 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* within E only */ + rc = wuss_mouse_click(wuss, POINT(110, 10), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* within E only */ if (rc != result_OK) goto Failure; if (hit != win_e) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { 110, 10 }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(110, 10), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { 135, 25 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* overlap: F still on top */ + rc = wuss_mouse_click(wuss, POINT(135, 25), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* overlap: F still on top */ if (rc != result_OK) goto Failure; if (hit != win_f) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { 135, 25 }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(135, 25), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; @@ -1022,11 +972,11 @@ result_t wuss_test(const char *resources) printf("test: wuss_window_set_background\n"); - rc = wuss_window_set_background(win_d, 999); + rc = wuss_window_set_background(win_d, wuss_BACKDROP_COLOUR(100)); if (rc != result_WUSS_BAD_COLOUR) goto Failure; - rc = wuss_window_set_background(win_d, palette_PICO8_ORANGE); + rc = wuss_window_set_background(win_d, wuss_BACKDROP_COLOUR(1)); if (rc != result_OK) goto Failure; @@ -1035,50 +985,48 @@ result_t wuss_test(const char *resources) printf("test: moving/resizing a window entirely behind an occluder has no visible effect\n"); { - test_task_t tc_h, tc_g; - wuss_task_t delegate_h, delegate_g; + static test_task_t tc_h, tc_g; + wuss_task_t *delegate_h, *delegate_g; box_t box_h, box_g; wuss_window_t *win_h, *win_g; - int before_h, before_g; + int before_h, before_g; tc_h.redraw_count = 0; tc_h.mouse_count = 0; - delegate_h.handle = test_handle; - delegate_h.task_data = &tc_h; - delegate_h.bg = wuss_NO_BACKGROUND; + delegate_h = mk_task(wuss, test_handle, &tc_h); + if (delegate_h == NULL) goto Failure; box_h.x0 = 10; box_h.y0 = 10; box_h.x1 = 30; box_h.y1 = 30; - rc = wuss_window_create(wuss, + rc = wuss_window_create(delegate_h, &box_h, NULL, wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - &delegate_h, - box_h.x1 - box_h.x0, - box_h.y1 - box_h.y0, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_h), + SIZE2D(0, 0), &win_h); if (rc != result_OK) goto Failure; tc_g.redraw_count = 0; tc_g.mouse_count = 0; - delegate_g.handle = test_handle; - delegate_g.task_data = &tc_g; - delegate_g.bg = wuss_NO_BACKGROUND; + delegate_g = mk_task(wuss, test_handle, &tc_g); + if (delegate_g == NULL) goto Failure; box_g.x0 = 0; box_g.y0 = 0; box_g.x1 = 150; box_g.y1 = 150; /* G is created after H, so G is topmost and fully covers H */ - rc = wuss_window_create(wuss, + rc = wuss_window_create(delegate_g, &box_g, NULL, wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - &delegate_g, - box_g.x1 - box_g.x0, - box_g.y1 - box_g.y0, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_g), + SIZE2D(0, 0), &win_g); if (rc != result_OK) goto Failure; @@ -1090,7 +1038,7 @@ result_t wuss_test(const char *resources) before_h = tc_h.redraw_count; before_g = tc_g.redraw_count; - wuss_window_move(win_h, (point_t) { 60, 60 }); /* still entirely within G's footprint */ + wuss_window_move(win_h, POINT(60, 60)); /* still entirely within G's footprint */ if (wuss_get_dirty_count(wuss) != 0) goto Failure; @@ -1100,7 +1048,7 @@ result_t wuss_test(const char *resources) if (tc_h.redraw_count != before_h || tc_g.redraw_count != before_g) goto Failure; /* nothing visible changed: no redraw of either window */ - rc = wuss_window_resize(win_h, 25, 25); /* still entirely within G's footprint */ + rc = wuss_window_resize(win_h, SIZE2D(25, 25)); /* still entirely within G's footprint */ if (rc != result_OK) goto Failure; if (wuss_get_dirty_count(wuss) != 0) @@ -1119,49 +1067,47 @@ result_t wuss_test(const char *resources) printf("test: bring-to-front only invalidates the newly-uncovered part\n"); { - test_task_t tc_i, tc_j; - wuss_task_t delegate_i, delegate_j; + static test_task_t tc_i, tc_j; + wuss_task_t *delegate_i, *delegate_j; box_t box_i, box_j, dirty; wuss_window_t *win_i, *win_j; tc_i.redraw_count = 0; tc_i.mouse_count = 0; - delegate_i.handle = test_handle; - delegate_i.task_data = &tc_i; - delegate_i.bg = wuss_NO_BACKGROUND; + delegate_i = mk_task(wuss, test_handle, &tc_i); + if (delegate_i == NULL) goto Failure; box_i.x0 = 0; box_i.y0 = 0; box_i.x1 = 100; box_i.y1 = 100; - rc = wuss_window_create(wuss, + rc = wuss_window_create(delegate_i, &box_i, NULL, wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - &delegate_i, - box_i.x1 - box_i.x0, - box_i.y1 - box_i.y0, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_i), + SIZE2D(0, 0), &win_i); if (rc != result_OK) goto Failure; tc_j.redraw_count = 0; tc_j.mouse_count = 0; - delegate_j.handle = test_handle; - delegate_j.task_data = &tc_j; - delegate_j.bg = wuss_NO_BACKGROUND; + delegate_j = mk_task(wuss, test_handle, &tc_j); + if (delegate_j == NULL) goto Failure; box_j.x0 = 50; box_j.y0 = 0; box_j.x1 = 150; box_j.y1 = 100; /* J created after I, so J is topmost, covering I's right half */ - rc = wuss_window_create(wuss, + rc = wuss_window_create(delegate_j, &box_j, NULL, wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - &delegate_j, - box_j.x1 - box_j.x0, - box_j.y1 - box_j.y0, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_j), + SIZE2D(0, 0), &win_j); if (rc != result_OK) goto Failure; @@ -1190,29 +1136,28 @@ result_t wuss_test(const char *resources) printf("test: dragging off-screen and back on repaints the reappearing edge\n"); { - test_task_t tc_m; - wuss_task_t delegate_m; + static test_task_t tc_m; + wuss_task_t *delegate_m; box_t box_m; wuss_window_t *win_m; int before_m; tc_m.redraw_count = 0; tc_m.mouse_count = 0; - delegate_m.handle = test_handle; - delegate_m.task_data = &tc_m; - delegate_m.bg = wuss_NO_BACKGROUND; + delegate_m = mk_task(wuss, test_handle, &tc_m); + if (delegate_m == NULL) goto Failure; box_m.x0 = 10; box_m.y0 = 10; box_m.x1 = 60; box_m.y1 = 60; /* 50x50, fully on-screen, topmost (created last) */ - rc = wuss_window_create(wuss, + rc = wuss_window_create(delegate_m, &box_m, NULL, wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, - &delegate_m, - box_m.x1 - box_m.x0, - box_m.y1 - box_m.y0, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_m), + SIZE2D(0, 0), &win_m); if (rc != result_OK) goto Failure; @@ -1221,14 +1166,14 @@ result_t wuss_test(const char *resources) if (rc != result_OK) goto Failure; - wuss_window_move(win_m, (point_t) { -40, 10 }); /* slide left until half of M is off the left edge */ + wuss_window_move(win_m, POINT(-40, 10)); /* slide left until half of M is off the left edge */ rc = wuss_redraw_dirty(wuss); /* flush the vacated-sliver repaint from this move */ if (rc != result_OK) goto Failure; before_m = tc_m.redraw_count; - wuss_window_move(win_m, (point_t) { 10, 10 }); /* slide back: the part that re-enters the screen was + wuss_window_move(win_m, POINT(10, 10)); /* slide back: the part that re-enters the screen was * never blitted (its source pixels were off-screen), * so it must be a real task redraw, not a blit */ if (wuss_get_dirty_count(wuss) == 0) @@ -1246,45 +1191,43 @@ result_t wuss_test(const char *resources) printf("test: Adjust-click on a window's back icon brings it to front\n"); { - test_task_t tc_g, tc_h; - wuss_task_t delegate_g, delegate_h; + static test_task_t tc_g, tc_h; + wuss_task_t *delegate_g, *delegate_h; box_t box_g, box_h; wuss_window_t *win_g, *win_h; tc_h.redraw_count = 0; tc_h.mouse_count = 0; - delegate_h.handle = test_handle; - delegate_h.task_data = &tc_h; - delegate_h.bg = wuss_NO_BACKGROUND; + delegate_h = mk_task(wuss, test_handle, &tc_h); + if (delegate_h == NULL) goto Failure; box_h.x0 = 130; box_h.y0 = 50; box_h.x1 = 190; box_h.y1 = 100; - rc = wuss_window_create(wuss, + rc = wuss_window_create(delegate_h, &box_h, "H", wuss_WINDOW_NONE, - &delegate_h, - box_h.x1 - box_h.x0, - box_h.y1 - box_h.y0, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_h), + SIZE2D(0, 0), &win_h); if (rc != result_OK) goto Failure; tc_g.redraw_count = 0; tc_g.mouse_count = 0; - delegate_g.handle = test_handle; - delegate_g.task_data = &tc_g; - delegate_g.bg = wuss_NO_BACKGROUND; + delegate_g = mk_task(wuss, test_handle, &tc_g); + if (delegate_g == NULL) goto Failure; box_g.x0 = 110; box_g.y0 = 30; box_g.x1 = 160; box_g.y1 = 80; - rc = wuss_window_create(wuss, + rc = wuss_window_create(delegate_g, &box_g, "G", wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL, - &delegate_g, - box_g.x1 - box_g.x0, - box_g.y1 - box_g.y0, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_g), + SIZE2D(0, 0), &win_g); if (rc != result_OK) goto Failure; @@ -1293,53 +1236,53 @@ result_t wuss_test(const char *resources) * corner) never falls under H, so it stays clickable either way */ wuss_window_get_visible_bounds(win_g, &visible); - rc = wuss_mouse_click(wuss, (point_t) { 145, 65 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* overlap of G and H */ + rc = wuss_mouse_click(wuss, POINT(145, 65), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* overlap of G and H */ if (rc != result_OK) goto Failure; if (hit != win_g) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { 145, 65 }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(145, 65), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { visible.x0 + 5, visible.y0 + 5 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* G's back icon */ + rc = wuss_mouse_click(wuss, POINT(visible.x0 + 5, visible.y0 + 5), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* G's back icon */ if (rc != result_OK) goto Failure; if (hit != win_g) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { visible.x0 + 5, visible.y0 + 5 }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(visible.x0 + 5, visible.y0 + 5), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { 145, 65 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* overlap: H now on top */ + rc = wuss_mouse_click(wuss, POINT(145, 65), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* overlap: H now on top */ if (rc != result_OK) goto Failure; if (hit != win_h) /* G was sent to back */ goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { 145, 65 }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(145, 65), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { visible.x0 + 5, visible.y0 + 5 }, wuss_BUTTON_ADJUST, wuss_MOUSE_DOWN, &hit); /* Adjust-click G's back icon */ + rc = wuss_mouse_click(wuss, POINT(visible.x0 + 5, visible.y0 + 5), wuss_BUTTON_ADJUST, wuss_MOUSE_DOWN, &hit); /* Adjust-click G's back icon */ if (rc != result_OK) goto Failure; if (hit != win_g) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { visible.x0 + 5, visible.y0 + 5 }, wuss_BUTTON_ADJUST, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(visible.x0 + 5, visible.y0 + 5), wuss_BUTTON_ADJUST, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { 145, 65 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* overlap: G back on top */ + rc = wuss_mouse_click(wuss, POINT(145, 65), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* overlap: G back on top */ if (rc != result_OK) goto Failure; if (hit != win_g) /* Adjust-click on the back icon brought G back to front */ goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { 145, 65 }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(145, 65), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; @@ -1347,17 +1290,17 @@ result_t wuss_test(const char *resources) wuss_window_get_content_bounds(win_g, &content); /* G's doc_width/doc_height are 50x50, same as its initial content size */ - rc = wuss_mouse_click(wuss, (point_t) { visible.x1 - 3, visible.y1 - 3 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* G's resize icon */ + rc = wuss_mouse_click(wuss, POINT(visible.x1 - 3, visible.y1 - 3), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* G's resize icon */ if (rc != result_OK) goto Failure; if (hit != win_g) goto Failure; - rc = wuss_mouse_move(wuss, (point_t) { content.x0 + 50, content.y0 + 50 }, &hit); /* drag to exactly the doc extent */ + rc = wuss_mouse_move(wuss, POINT(content.x0 + 50, content.y0 + 50), &hit); /* drag to exactly the doc extent */ if (rc != result_OK) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { content.x0 + 50, content.y0 + 50 }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(content.x0 + 50, content.y0 + 50), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; @@ -1365,15 +1308,15 @@ result_t wuss_test(const char *resources) width = content.x1 - content.x0; height = content.y1 - content.y0; - rc = wuss_mouse_click(wuss, (point_t) { visible.x1 - 3, visible.y1 - 3 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* G's resize icon again */ + rc = wuss_mouse_click(wuss, POINT(visible.x1 - 3, visible.y1 - 3), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* G's resize icon again */ if (rc != result_OK) goto Failure; - rc = wuss_mouse_move(wuss, (point_t) { content.x0 + 500, content.y0 + 500 }, &hit); /* drag far past the doc extent */ + rc = wuss_mouse_move(wuss, POINT(content.x0 + 500, content.y0 + 500), &hit); /* drag far past the doc extent */ if (rc != result_OK) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { content.x0 + 500, content.y0 + 500 }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(content.x0 + 500, content.y0 + 500), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; @@ -1385,44 +1328,90 @@ result_t wuss_test(const char *resources) wuss_window_close(win_h); } - printf("test: toggle-size blits rather than redrawing the whole window\n"); + printf("test: drag-resize stops at min_doc, not just WUSS_MIN_CONTENT\n"); { - test_task_t tc_t; - wuss_task_t delegate_t; - box_t box_t_win, before, after, titlebar, toggle; - wuss_window_t *win_t; - int outline_px, titlebar_height, inset, icon; - int i, dirty_area, full_area, cx, cy, old_icon_x, old_icon_y, found; - int interior_x, interior_y, interior_dirty; - int old_vscroll_x, old_vscroll_y, old_vscroll_found; + static test_task_t tc_m; + wuss_task_t *delegate_m; + box_t box_m, content, visible; + wuss_window_t *win_m; - tc_t.redraw_count = 0; - tc_t.mouse_count = 0; - delegate_t.handle = test_handle; - delegate_t.task_data = &tc_t; - delegate_t.bg = wuss_NO_BACKGROUND; + tc_m.redraw_count = 0; + tc_m.mouse_count = 0; + delegate_m = mk_task(wuss, test_handle, &tc_m); + if (delegate_m == NULL) goto Failure; - box_t_win.x0 = 10; box_t_win.y0 = 10; - box_t_win.x1 = 50; box_t_win.y1 = 50; /* 40x40 content, room to grow to a 200x200 doc */ - rc = wuss_window_create(wuss, &box_t_win, "T", wuss_WINDOW_NONE, - &delegate_t, 200, 200, &win_t); + box_m.x0 = 10; box_m.y0 = 10; + box_m.x1 = 210; box_m.y1 = 210; /* 200x200 content, floored at 80x60 */ + rc = wuss_window_create(delegate_m, &box_m, "M", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(200, 200), SIZE2D(80, 60), + &win_m); if (rc != result_OK) goto Failure; - rc = wuss_redraw_dirty(wuss); /* flush the create's own invalidate */ + wuss_window_get_visible_bounds(win_m, &visible); + wuss_window_get_content_bounds(win_m, &content); + + rc = wuss_mouse_click(wuss, POINT(visible.x1 - 3, visible.y1 - 3), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* M's resize icon */ if (rc != result_OK) goto Failure; + if (hit != win_m) + goto Failure; + + rc = wuss_mouse_move(wuss, POINT(content.x0 + 5, content.y0 + 5), &hit); /* drag far inside min_doc */ + if (rc != result_OK) + goto Failure; + + rc = wuss_mouse_click(wuss, POINT(content.x0 + 5, content.y0 + 5), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_m, &content); + if (content.x1 - content.x0 != 80 || content.y1 - content.y0 != 60) + goto Failure; /* floored at min_doc, not squeezed down to WUSS_MIN_CONTENT */ + + wuss_window_close(win_m); + } + + printf("test: toggle-size blits rather than redrawing the whole window\n"); + + { + static test_task_t tc_t; + wuss_task_t *delegate_t; + box_t box_t_win, before, after, titlebar, toggle; + wuss_window_t *win_t; + int outline_px, titlebar_height, inset, icon; + int i, dirty_area, full_area, cx, cy, old_icon_x, old_icon_y, found; + int interior_x, interior_y, interior_dirty; + int old_vscroll_x, old_vscroll_y, old_vscroll_found; + + tc_t.redraw_count = 0; + tc_t.mouse_count = 0; + delegate_t = mk_task(wuss, test_handle, &tc_t); + if (delegate_t == NULL) goto Failure; + + box_t_win.x0 = 10; box_t_win.y0 = 10; + box_t_win.x1 = 50; box_t_win.y1 = 50; /* 40x40 content, room to grow to a 150x150 doc without the toggled box needing to be repositioned off (10,10) -- this test is about the in-place grow blit */ + rc = wuss_window_create(delegate_t, &box_t_win, "T", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(150, 150), SIZE2D(0, 0), &win_t); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush the create's own invalidate */ + if (rc != result_OK) + goto Failure; + + /* toggle icon: top-right of the titlebar, inset by 3px, sized 20 - 2*3 + * (default titlebar height 20, WUSS_BUTTON_INSET 3), matching + * wuss__toggle_box's formula -- mirrored here since the test only sees + * the public API */ + outline_px = 1; + titlebar_height = 20; + inset = 3; + icon = titlebar_height - 2 * inset; - /* toggle icon: top-right of the titlebar, inset by 3px, sized 20 - 2*3 - * (default titlebar height 20, WUSS_ICON_INSET 3), matching - * wuss__toggle_box's formula -- mirrored here since the test only sees - * the public API */ - outline_px = 1; - titlebar_height = 20; - inset = 3; - icon = titlebar_height - 2 * inset; - wuss_window_get_visible_bounds(win_t, &before); titlebar.x0 = before.x0 + outline_px; titlebar.x1 = before.x1 - outline_px; @@ -1453,12 +1442,12 @@ result_t wuss_test(const char *resources) old_vscroll_x = before.x1 - outline_px - icon / 2; old_vscroll_y = interior_y; - rc = wuss_mouse_click(wuss, (point_t) { cx, cy }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* T's toggle-size icon: grow */ + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* T's toggle-size icon: grow */ if (rc != result_OK) goto Failure; if (hit != win_t) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { cx, cy }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; @@ -1500,9 +1489,11 @@ result_t wuss_test(const char *resources) wuss_window_get_visible_bounds(win_t, &after); if (after.x1 > 200 || after.y1 > 200) - goto Failure; /* maximizing must stay on-screen: bounded by what's left - * of the screen from T's own x0/y0 (10,10), not by the - * screen's full width/height as if T were at the origin */ + goto Failure; /* toggling must leave the window fully on-screen */ + if (after.x0 != before.x0 || after.y0 != before.y0) + goto Failure; /* a 150x150 doc toggled from (10,10) fits without moving, + * so the top-left stays put and the in-place grow blit + * (asserted below) applies */ rc = wuss_redraw_dirty(wuss); if (rc != result_OK) @@ -1520,12 +1511,12 @@ result_t wuss_test(const char *resources) cx = (toggle.x0 + toggle.x1) / 2; cy = (toggle.y0 + toggle.y1) / 2; - rc = wuss_mouse_click(wuss, (point_t) { cx, cy }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* T's toggle-size icon: shrink */ + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* T's toggle-size icon: shrink */ if (rc != result_OK) goto Failure; if (hit != win_t) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { cx, cy }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; @@ -1555,8 +1546,8 @@ result_t wuss_test(const char *resources) printf("test: toggle-size that forces a scroll re-clamp invalidates the content it's about to redraw at the new offset, not just the blit's edge sliver\n"); { - test_task_t tc_r; - wuss_task_t delegate_r; + static test_task_t tc_r; + wuss_task_t *delegate_r; box_t box_r, before, titlebar, toggle; wuss_window_t *win_r; int outline_px, titlebar_height, inset, icon; @@ -1564,14 +1555,14 @@ result_t wuss_test(const char *resources) tc_r.redraw_count = 0; tc_r.mouse_count = 0; - delegate_r.handle = test_handle; - delegate_r.task_data = &tc_r; - delegate_r.bg = wuss_NO_BACKGROUND; + delegate_r = mk_task(wuss, test_handle, &tc_r); + if (delegate_r == NULL) goto Failure; box_r.x0 = 10; box_r.y0 = 10; box_r.x1 = 50; box_r.y1 = 50; /* 40x40 content; doc bigger than that, so it starts scrollable */ - rc = wuss_window_create(wuss, &box_r, "R", wuss_WINDOW_NONE, - &delegate_r, 70, 70, &win_r); + rc = wuss_window_create(delegate_r, &box_r, "R", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(70, 70), SIZE2D(0, 0), &win_r); if (rc != result_OK) goto Failure; @@ -1579,7 +1570,7 @@ result_t wuss_test(const char *resources) if (rc != result_OK) goto Failure; - wuss_window_set_scroll(win_r, (point_t) { 0, 15 }); /* within range: max_y = 70 - 40 = 30 */ + wuss_window_set_scroll(win_r, POINT(0, 15)); /* within range: max_y = 70 - 40 = 30 */ rc = wuss_redraw_dirty(wuss); /* flush the scroll's own invalidate */ if (rc != result_OK) @@ -1611,12 +1602,12 @@ result_t wuss_test(const char *resources) interior_x = (before.x0 + outline_px + before.x1 - outline_px - icon) / 2; interior_y = (before.y0 + outline_px + titlebar_height + before.y1 - outline_px - icon) / 2; - rc = wuss_mouse_click(wuss, (point_t) { cx, cy }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* R's toggle-size icon: grow past doc_height, forcing a scroll re-clamp */ + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* R's toggle-size icon: grow past doc_height, forcing a scroll re-clamp */ if (rc != result_OK) goto Failure; if (hit != win_r) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { cx, cy }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; @@ -1649,11 +1640,492 @@ result_t wuss_test(const char *resources) wuss_window_close(win_r); } - printf("test: wuss_WINDOW_NO_TOGGLE_BLIT redraws the whole window instead of blitting\n"); + printf("test: wuss_window_resize on a topmost window at max scroll blits the still-valid content rather than redrawing it all\n"); + + { + /* Unlike the toggle-size path above, a direct wuss_window_resize (the + * interactive drag-resize path) runs with the screen still showing this + * window's content at the old geometry and old scroll offset. When the + * grow forces a scroll re-clamp, the overlap of the old and new content + * boxes is genuine on-screen content that just needs sliding by the + * scroll delta -- only the newly-exposed strip needs a real repaint. */ + static test_task_t tc_d; + wuss_task_t *delegate_d; + box_t box_d, content_before; + wuss_window_t *win_d; + point_t scroll_d; + int i; + int top_x, top_y, mid_x, mid_y, bottom_x, bottom_y; + int top_dirty, mid_dirty, bottom_dirty; + + tc_d.redraw_count = 0; + tc_d.mouse_count = 0; + delegate_d = mk_task(wuss, test_handle, &tc_d); + if (delegate_d == NULL) goto Failure; + + box_d.x0 = 10; box_d.y0 = 10; + box_d.x1 = 90; box_d.y1 = 90; /* 80x80 content; doc taller, so it starts scrollable */ + rc = wuss_window_create(delegate_d, &box_d, "D", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(80, 140), SIZE2D(0, 0), &win_d); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush the create's own invalidate */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_d, &content_before); + + /* scroll to the bottom: max_y = doc.h (140) - content height (80) = 60 */ + wuss_window_set_scroll(win_d, POINT(0, 60)); + rc = wuss_redraw_dirty(wuss); /* flush the scroll's own invalidate */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_scroll(win_d, &scroll_d); + if (scroll_d.y != 60) + goto Failure; + + /* Three probe points at the same x, well clear of the scrollbar column. + * The re-clamp drops scroll.y from 60 to 0, so on-screen content slides + * DOWN by 60px: the top ~60px of the content box is newly revealed and + * must be repainted, everything below it was already on screen and must + * be reused by the blit. */ + top_x = content_before.x0 + 8; + top_y = content_before.y0 + 8; /* < 60px down: in the revealed strip */ + mid_x = content_before.x0 + 8; + mid_y = content_before.y0 + 70; /* > 60px down: reused */ + bottom_x = content_before.x0 + 8; + bottom_y = content_before.y1 - 8; /* near old viewport bottom: reused */ + + /* grow the content taller than the doc: content height goes to >= 140, + * so max_y drops to 0 and scroll.y is clamped back from 60 to 0 */ + rc = wuss_window_resize(win_d, SIZE2D(80, 160)); + if (rc != result_OK) + goto Failure; + + wuss_window_get_scroll(win_d, &scroll_d); + if (scroll_d.y != 0) + goto Failure; /* the grow must have re-clamped the offset */ + + if (wuss_get_dirty_count(wuss) == 0) + goto Failure; + + top_dirty = 0; + mid_dirty = 0; + bottom_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + box_t region; + + wuss_get_dirty(wuss, i, ®ion); + if (box_contains_point(®ion, top_x, top_y)) + top_dirty = 1; + if (box_contains_point(®ion, mid_x, mid_y)) + mid_dirty = 1; + if (box_contains_point(®ion, bottom_x, bottom_y)) + bottom_dirty = 1; + } + + if (!top_dirty) + goto Failure; /* the newly-revealed strip must be repainted */ + + if (mid_dirty || bottom_dirty) + goto Failure; /* content that stayed on screen must be reused by the + * blit-and-shift, not redrawn -- a full content-box + * invalidate has regressed the optimisation */ + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_d); + } + + printf("test: wuss_window_resize on a non-topmost window blits the un-occluded content and never dirties the occluder\n"); + + { + /* Another window covers the right half of D's content. The blit source + * is the old content box MINUS what the occluder covered, so the + * un-occluded left half is slid into place; the right half still shows + * the occluder's own correct pixels, so it must stay clean -- dirtying + * it would redraw the occluding window for nothing. Only the un-occluded + * newly-revealed top strip needs a repaint. */ + static test_task_t tc_d, tc_o; + wuss_task_t *delegate_d, *delegate_o; + box_t box_d, box_o, content_before; + wuss_window_t *win_d, *win_o; + point_t scroll_d; + int i, split_x; + int occluded_x, occluded_y, kept_x, kept_y, strip_x, strip_y; + int occluded_dirty, kept_dirty, strip_dirty; + + tc_d.redraw_count = 0; tc_d.mouse_count = 0; + tc_o.redraw_count = 0; tc_o.mouse_count = 0; + delegate_d = mk_task(wuss, test_handle, &tc_d); + if (delegate_d == NULL) goto Failure; + delegate_o = mk_task(wuss, test_handle, &tc_o); + if (delegate_o == NULL) goto Failure; + + box_d.x0 = 10; box_d.y0 = 10; + box_d.x1 = 110; box_d.y1 = 90; /* 100x80 content; doc taller, so scrollable */ + rc = wuss_window_create(delegate_d, &box_d, "D", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(100, 140), SIZE2D(0, 0), &win_d); + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_d, &content_before); + split_x = (content_before.x0 + content_before.x1) / 2; + + /* occluder covering the right half of D's content box and beyond */ + box_o.x0 = split_x; box_o.y0 = content_before.y0 - 5; + box_o.x1 = content_before.x1 + 40; box_o.y1 = content_before.y1 + 40; + rc = wuss_window_create(delegate_o, &box_o, "O", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(box_o.x1 - box_o.x0, box_o.y1 - box_o.y0), + SIZE2D(0, 0), &win_o); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush both creates */ + if (rc != result_OK) + goto Failure; + + wuss_window_set_scroll(win_d, POINT(0, 60)); /* max_y = 140 - 80 = 60 */ + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_get_scroll(win_d, &scroll_d); + if (scroll_d.y != 60) + goto Failure; + + /* occluded: under O -- must stay clean. kept: un-occluded, below the + * ~60px revealed strip -- reused by the blit, must stay clean. strip: + * un-occluded, in the newly-revealed top band -- must be repainted. */ + occluded_x = split_x + 8; + occluded_y = content_before.y1 - 8; + kept_x = content_before.x0 + 8; + kept_y = content_before.y1 - 8; + strip_x = content_before.x0 + 8; + strip_y = content_before.y0 + 8; + + rc = wuss_window_resize(win_d, SIZE2D(100, 160)); /* grow past doc height */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_scroll(win_d, &scroll_d); + if (scroll_d.y != 0) + goto Failure; + + if (wuss_get_dirty_count(wuss) == 0) + goto Failure; + + occluded_dirty = 0; + kept_dirty = 0; + strip_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + box_t region; + + wuss_get_dirty(wuss, i, ®ion); + if (box_contains_point(®ion, occluded_x, occluded_y)) + occluded_dirty = 1; + if (box_contains_point(®ion, kept_x, kept_y)) + kept_dirty = 1; + if (box_contains_point(®ion, strip_x, strip_y)) + strip_dirty = 1; + } + + if (occluded_dirty) + goto Failure; /* under the occluder: its pixels are already correct, + * dirtying this would redraw the occluding window */ + + if (kept_dirty) + goto Failure; /* un-occluded content must be reused by the blit */ + + if (!strip_dirty) + goto Failure; /* the un-occluded newly-revealed strip must be repainted */ + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_o); + wuss_window_close(win_d); + } + + printf("test: wuss_window_set_scroll on a non-topmost window blits the un-occluded content and never dirties the occluder\n"); + + { + /* A window covered on its right half by another still blits its + * un-occluded left half on a programmatic scroll. The right half shows + * the occluder's own correct pixels, so it must stay clean -- dirtying + * it would redraw the occluding window. Only the un-occluded + * newly-exposed edge strip needs a repaint. */ + static test_task_t tc_s, tc_o; + wuss_task_t *delegate_s, *delegate_o; + box_t box_s, box_o, content; + wuss_window_t *win_s, *win_o; + int i, split_x; + int occluded_x, occluded_y, kept_x, kept_y, strip_x, strip_y; + int occluded_dirty, kept_dirty, strip_dirty; + + tc_s.redraw_count = 0; tc_s.mouse_count = 0; + tc_o.redraw_count = 0; tc_o.mouse_count = 0; + delegate_s = mk_task(wuss, test_handle, &tc_s); + if (delegate_s == NULL) goto Failure; + delegate_o = mk_task(wuss, test_handle, &tc_o); + if (delegate_o == NULL) goto Failure; + + box_s.x0 = 10; box_s.y0 = 10; + box_s.x1 = 110; box_s.y1 = 90; /* 100x80 content; doc taller, so scrollable */ + rc = wuss_window_create(delegate_s, &box_s, "S", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(100, 200), SIZE2D(0, 0), &win_s); + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_s, &content); + split_x = (content.x0 + content.x1) / 2; + + box_o.x0 = split_x; box_o.y0 = content.y0 - 5; + box_o.x1 = content.x1 + 40; box_o.y1 = content.y1 + 40; + rc = wuss_window_create(delegate_o, &box_o, "O", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(box_o.x1 - box_o.x0, box_o.y1 - box_o.y0), + SIZE2D(0, 0), &win_o); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush both creates */ + if (rc != result_OK) + goto Failure; + + /* kept: un-occluded, near the bottom -- content there just slides up by + * the scroll delta, so the blit must reuse it. occluded: under O -- must + * stay clean. strip: un-occluded, in the newly-exposed bottom band -- + * must be repainted. */ + kept_x = content.x0 + 8; + kept_y = content.y0 + 8; + occluded_x = split_x + 8; + occluded_y = content.y1 - 20; + strip_x = content.x0 + 8; + strip_y = content.y1 - 6; + + wuss_window_set_scroll(win_s, POINT(0, 12)); + + if (wuss_get_dirty_count(wuss) == 0) + goto Failure; + + kept_dirty = 0; + occluded_dirty = 0; + strip_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + box_t region; + + wuss_get_dirty(wuss, i, ®ion); + if (box_contains_point(®ion, kept_x, kept_y)) + kept_dirty = 1; + if (box_contains_point(®ion, occluded_x, occluded_y)) + occluded_dirty = 1; + if (box_contains_point(®ion, strip_x, strip_y)) + strip_dirty = 1; + } + + if (occluded_dirty) + goto Failure; /* under the occluder: its pixels are already correct, + * dirtying this would redraw the occluding window */ + + if (kept_dirty) + goto Failure; /* un-occluded content must be reused by the blit */ + + if (!strip_dirty) + goto Failure; /* the un-occluded newly-exposed strip must be repainted */ + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_o); + wuss_window_close(win_s); + } + + printf("test: wuss_window_set_scroll never blits a scrolled window's content over a mid-content occluder\n"); + + { + /* Regression: a small window O sits in the middle of a larger scrollable + * window M's content, covering neither M's top nor bottom edge. A + * programmatic vertical scroll of M takes a blit source slice from below + * O and shifts it up by the scroll delta, so its destination overlaps O. + * screen_copy_rect only clips to the content box, so without the fix it + * paints M's content over O and marks that area "copied" -- excluding it + * from the repaint set, so O's pixels stay overpainted with M's colour. + * With the fix each destination is clipped against the occluders and only + * the un-occluded sub-pieces are blitted, leaving O's own pixels intact. + * Read the framebuffer behind O to check. */ + colour_t cm; + uint32_t fb_m, fb_o; + static test_task_t tc_m, tc_o; + wuss_task_t *delegate_m, *delegate_o; + box_t box_m, box_o, content; + wuss_window_t *win_m, *win_o; + int occ_x, occ_y, mid_x, mid_y, delta; + + cm = colour_rgb(0x11, 0x22, 0x33); + tc_m.redraw_count = 0; tc_m.mouse_count = 0; + tc_o.redraw_count = 0; tc_o.mouse_count = 0; + delegate_m = mk_task(wuss, paint_handle, &cm); + if (delegate_m == NULL) goto Failure; + delegate_o = mk_task(wuss, paint_handle, NULL); + if (delegate_o == NULL) goto Failure; + + box_m.x0 = 10; box_m.y0 = 10; + box_m.x1 = 110; box_m.y1 = 110; /* 100x100 content; doc taller, so scrollable */ + rc = wuss_window_create(delegate_m, &box_m, "M", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(100, 300), SIZE2D(0, 0), &win_m); + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_m, &content); + + /* occluder spanning M's full content width, a band in the vertical + * middle -- so wuss__clip_to_visible splits M's blit source into a top + * and a bottom band with nothing behind O. Scrolling down slides the + * bottom band up; part of its destination lands behind O, yet no dirty + * repaint region touches O, so without the fix O stays overpainted. */ + box_o.x0 = content.x0 - 5; box_o.y0 = content.y0 + 40; + box_o.x1 = content.x1 + 5; box_o.y1 = content.y0 + 70; + rc = wuss_window_create(delegate_o, &box_o, "O", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(box_o.x1 - box_o.x0, box_o.y1 - box_o.y0), + SIZE2D(0, 0), &win_o); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush both creates: M then O paint */ + if (rc != result_OK) + goto Failure; + + /* sample the framebuffer: occ_* sits behind O, mid_* sits in M's content + * clear of O -- record what each colour actually renders as */ + occ_x = (box_o.x0 + box_o.x1) / 2; + occ_y = box_o.y1 - 3; /* near O's bottom: the bottom band's blit + * destination reaches up to here */ + mid_x = content.x0 + 5; + mid_y = content.y0 + 5; + fb_o = ((const uint32_t *) pixels)[occ_y * 200 + occ_x]; + fb_m = ((const uint32_t *) pixels)[mid_y * 200 + mid_x]; + if (fb_o == fb_m) + goto Failure; /* the two windows must render distinguishable pixels */ + + delta = 20; + wuss_window_set_scroll(win_m, POINT(0, delta)); + rc = wuss_redraw_dirty(wuss); /* apply the scroll's blit + any repaint */ + if (rc != result_OK) + goto Failure; + + /* the pixel behind O must still be O's, not M's content blitted over it */ + if (((const uint32_t *) pixels)[occ_y * 200 + occ_x] != fb_o) + goto Failure; + + wuss_window_close(win_o); + wuss_window_close(win_m); + } + + printf("test: wuss_window_set_scroll orders its blit sub-pieces so one never clobbers another's source\n"); + + { + /* Regression: a small window O floats in the middle of a larger + * scrollable window M, with a gap all round it. wuss__clip_to_visible + * carves M's blittable content into bands around O; a vertical scroll + * shifts each band by the same delta, and one band's shifted + * destination lands on another band's still-unread source. Blitting the + * bands in clip-emit order corrupts M's own content -- pixels near O's + * bottom edge end up double-shifted. The blit must be ordered (or fall + * back). paint_handle paints M as one-pixel rows whose blue channel + * encodes document Y, so a mis-shifted row is detectable exactly. */ + colour_t cm; + static test_task_t tc_m, tc_o; + wuss_task_t *delegate_m, *delegate_o; + box_t box_m, box_o, content, ovis; + wuss_window_t *win_m, *win_o; + int gx, gy, delta, bad; + + cm = colour_rgb(0x11, 0x22, 0x33); + tc_m.redraw_count = 0; tc_m.mouse_count = 0; + tc_o.redraw_count = 0; tc_o.mouse_count = 0; + delegate_m = mk_task(wuss, paint_handle, &cm); + if (delegate_m == NULL) goto Failure; + delegate_o = mk_task(wuss, paint_handle, NULL); + if (delegate_o == NULL) goto Failure; + + box_m.x0 = 10; box_m.y0 = 10; + box_m.x1 = 110; box_m.y1 = 110; /* 100x100 content; doc taller, scrollable */ + rc = wuss_window_create(delegate_m, &box_m, "M", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(100, 300), SIZE2D(0, 0), &win_m); + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_m, &content); + + /* O strictly inside M's content, gap on every side */ + box_o.x0 = content.x0 + 25; box_o.y0 = content.y0 + 25; + box_o.x1 = content.x0 + 75; box_o.y1 = content.y0 + 65; + rc = wuss_window_create(delegate_o, &box_o, "O", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(box_o.x1 - box_o.x0, box_o.y1 - box_o.y0), + SIZE2D(0, 0), &win_o); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush both creates */ + if (rc != result_OK) + goto Failure; + + /* probe the gap column just right of O: pure M content, must slide up by + * exactly the scroll delta with no discontinuity */ + wuss_window_get_visible_bounds(win_o, &ovis); + gx = (ovis.x1 + content.x1) / 2; + delta = 20; + + wuss_window_set_scroll(win_m, POINT(0, delta)); + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + /* every row in the gap column, across O's vertical span, must show the + * document Y that scrolling put there: doc_y = (screen_y - content.y0) + + * delta, low byte carried in blue by paint_handle */ + bad = 0; + for (gy = ovis.y0 + 2; gy < ovis.y1 - 2; gy++) + { + uint32_t px, blue; + + if (gy < content.y0 || gy >= content.y1) + continue; + px = ((const uint32_t *) pixels)[gy * 200 + gx]; + blue = px & 0xff; + if (blue != (uint32_t) ((gy - content.y0 + delta) & 0xff)) + bad = 1; + } + if (bad) + goto Failure; /* a band blit clobbered another band's source */ + + wuss_window_close(win_o); + wuss_window_close(win_m); + } + + printf("test: wuss_WINDOW_NO_RESIZE_BLIT redraws the whole window instead of blitting\n"); { - test_task_t tc_nb; - wuss_task_t delegate_nb; + static test_task_t tc_nb; + wuss_task_t *delegate_nb; box_t box_nb, before, titlebar, toggle; wuss_window_t *win_nb; int outline_px, titlebar_height, inset, icon; @@ -1661,14 +2133,14 @@ result_t wuss_test(const char *resources) tc_nb.redraw_count = 0; tc_nb.mouse_count = 0; - delegate_nb.handle = test_handle; - delegate_nb.task_data = &tc_nb; - delegate_nb.bg = wuss_NO_BACKGROUND; + delegate_nb = mk_task(wuss, test_handle, &tc_nb); + if (delegate_nb == NULL) goto Failure; box_nb.x0 = 10; box_nb.y0 = 10; box_nb.x1 = 50; box_nb.y1 = 50; /* 40x40 content, room to grow to a 200x200 doc */ - rc = wuss_window_create(wuss, &box_nb, "NB", wuss_WINDOW_NO_TOGGLE_BLIT, - &delegate_nb, 200, 200, &win_nb); + rc = wuss_window_create(delegate_nb, &box_nb, "NB", wuss_WINDOW_NO_RESIZE_BLIT, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(200, 200), SIZE2D(0, 0), &win_nb); if (rc != result_OK) goto Failure; @@ -1698,12 +2170,12 @@ result_t wuss_test(const char *resources) interior_x = (before.x0 + outline_px + before.x1 - outline_px - icon) / 2; interior_y = (before.y0 + outline_px + titlebar_height + before.y1 - outline_px - icon) / 2; - rc = wuss_mouse_click(wuss, (point_t) { cx, cy }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* NB's toggle-size icon: grow */ + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* NB's toggle-size icon: grow */ if (rc != result_OK) goto Failure; if (hit != win_nb) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { cx, cy }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; @@ -1717,7 +2189,7 @@ result_t wuss_test(const char *resources) interior_dirty = 1; } if (!interior_dirty) - goto Failure; /* wuss_WINDOW_NO_TOGGLE_BLIT must skip the blit path + goto Failure; /* wuss_WINDOW_NO_RESIZE_BLIT must skip the blit path * entirely, so even an interior pixel the blit would * otherwise have preserved comes out dirty */ @@ -1731,8 +2203,8 @@ result_t wuss_test(const char *resources) printf("test: toggle-size maximize accounts for scrollbar furniture, not just outline/titlebar\n"); { - test_task_t tc_u; - wuss_task_t delegate_u; + static test_task_t tc_u; + wuss_task_t *delegate_u; box_t box_u, ub, titlebar, toggle; wuss_window_t *win_u; int outline_px, titlebar_height, inset, icon; @@ -1740,14 +2212,14 @@ result_t wuss_test(const char *resources) tc_u.redraw_count = 0; tc_u.mouse_count = 0; - delegate_u.handle = test_handle; - delegate_u.task_data = &tc_u; - delegate_u.bg = wuss_NO_BACKGROUND; + delegate_u = mk_task(wuss, test_handle, &tc_u); + if (delegate_u == NULL) goto Failure; box_u.x0 = 80; box_u.y0 = 80; box_u.x1 = 120; box_u.y1 = 120; /* 40x40 content */ - rc = wuss_window_create(wuss, &box_u, "U", wuss_WINDOW_NONE, /* scrollbars on: carve.x/y = icon size */ - &delegate_u, 70, 70, &win_u); /* doc size well within the 200x200 screen: growth is doc-limited, not screen-limited */ + rc = wuss_window_create(mk_task(wuss, NULL, NULL), &box_u, "U", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(70, 70), SIZE2D(0, 0), &win_u); /* doc size well within the 200x200 screen: growth is doc-limited, not screen-limited */ if (rc != result_OK) goto Failure; @@ -1771,12 +2243,12 @@ result_t wuss_test(const char *resources) cx = (toggle.x0 + toggle.x1) / 2; cy = (toggle.y0 + toggle.y1) / 2; - rc = wuss_mouse_click(wuss, (point_t) { cx, cy }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* U's toggle-size icon: grow to doc size */ + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* U's toggle-size icon: grow to doc size */ if (rc != result_OK) goto Failure; if (hit != win_u) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { cx, cy }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; @@ -1804,12 +2276,12 @@ result_t wuss_test(const char *resources) cx = (toggle.x0 + toggle.x1) / 2; cy = (toggle.y0 + toggle.y1) / 2; - rc = wuss_mouse_click(wuss, (point_t) { cx, cy }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* U's toggle-size icon: shrink back */ + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* U's toggle-size icon: shrink back */ if (rc != result_OK) goto Failure; if (hit != win_u) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { cx, cy }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; @@ -1829,18 +2301,20 @@ result_t wuss_test(const char *resources) printf("test: toggle-size maximize stays a valid box for a window dragged off the right/bottom edge\n"); { - test_task_t tc_v; - wuss_task_t delegate_v; + static test_task_t tc_v; + wuss_task_t *delegate_v; box_t box_v, vb, titlebar, toggle; wuss_window_t *win_v; int outline_px, titlebar_height, inset, icon; int cx, cy; - tc_v.redraw_count = 0; - tc_v.mouse_count = 0; - delegate_v.handle = test_handle; - delegate_v.task_data = &tc_v; - delegate_v.bg = wuss_NO_BACKGROUND; + memset(&tc_v, 0, sizeof(tc_v)); + tc_v.veto_pre_close = 1; /* this test pokes furniture with a window in a + * deliberately broken (inverted) box; a stray + * hit on the close icon must not tear win_v down + * mid-test */ + delegate_v = mk_task(wuss, test_handle, &tc_v); + if (delegate_v == NULL) goto Failure; box_v.x0 = 10; box_v.y0 = 10; box_v.x1 = 70; box_v.y1 = 70; /* 60x60 content -- wide enough titlebar that @@ -1853,8 +2327,9 @@ result_t wuss_test(const char *resources) * unrelated to toggle-size specifically). * Doc big enough that maximize is * screen-limited, not doc-limited. */ - rc = wuss_window_create(wuss, &box_v, "V", wuss_WINDOW_NONE, - &delegate_v, 200, 200, &win_v); + rc = wuss_window_create(delegate_v, &box_v, "V", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(200, 200), SIZE2D(0, 0), &win_v); if (rc != result_OK) goto Failure; @@ -1867,7 +2342,7 @@ result_t wuss_test(const char *resources) * above): titlebar drag calls wuss_window_move with no clamping. From * here, "available space to the screen edge" (scr_width - visible.x0 - * furniture) goes negative, further than furniture alone can absorb. */ - wuss_window_move(win_v, (point_t) { 310, 310 }); + wuss_window_move(win_v, POINT(310, 310)); outline_px = 1; titlebar_height = 20; @@ -1885,33 +2360,38 @@ result_t wuss_test(const char *resources) cx = (toggle.x0 + toggle.x1) / 2; cy = (toggle.y0 + toggle.y1) / 2; - rc = wuss_mouse_click(wuss, (point_t) { cx, cy }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* V's toggle-size icon: maximize while off-screen */ + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* V's toggle-size icon: maximize while off-screen */ if (rc != result_OK) goto Failure; if (hit != win_v) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { cx, cy }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); if (rc != result_OK) goto Failure; wuss_window_get_visible_bounds(win_v, &vb); if (vb.x1 <= vb.x0 || vb.y1 <= vb.y0) - goto Failure; /* screen-limited width/height went negative (x0 is - * further right than the screen edge plus furniture - * can make room for), producing an inverted box -- + goto Failure; /* a naive "space from x0 to the screen edge" sum goes + * negative from off-screen, producing an inverted box -- * un-hit-testable forever after, since box_contains_point - * can never match x0>x1: the window is stuck, unclickable, - * unclosable. Must floor at WUSS_MIN_CONTENT like - * drag-resize.c does, even if that leaves the maximized - * window hanging off the visible screen. */ + * can never match x0>x1: the window would be stuck, + * unclickable, unclosable */ + if (vb.x0 < 0 || vb.y0 < 0 || vb.x1 > 200 || vb.y1 > 200) + goto Failure; /* toggle repositions toward the origin by the minimum + * needed, so even a window dragged right off the screen + * comes back fully on-screen when maximized */ rc = wuss_redraw_dirty(wuss); if (rc != result_OK) goto Failure; - /* toggle back: shrink. Confirm the window is still reachable at all -- - * this alone would already fail (rc != result_OK from a wrong hit) if - * the icon had become permanently unhittable above. */ + /* toggle back: shrink. With the window clipped this narrow its toggle + * icon overlaps close's box (see the box_v comment above), so the click + * meant for toggle may land on close instead; hit-test checks close + * first. That routes through wuss_window_try_close, and this task's + * PRE_CLOSE veto (set at task creation) both keeps the window alive and + * makes the click return the veto's non-OK -- accept that here. The + * point of the check is line-below: the box must still be valid. */ wuss_window_get_visible_bounds(win_v, &vb); titlebar.x0 = vb.x0 + outline_px; titlebar.x1 = vb.x1 - outline_px; @@ -1923,13 +2403,13 @@ result_t wuss_test(const char *resources) cx = (toggle.x0 + toggle.x1) / 2; cy = (toggle.y0 + toggle.y1) / 2; - rc = wuss_mouse_click(wuss, (point_t) { cx, cy }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* V's toggle-size icon: shrink back */ - if (rc != result_OK) + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* V's toggle-size icon: shrink back */ + if (rc != result_OK && rc != result_BAD_ARG) goto Failure; if (hit != win_v) goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { cx, cy }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); - if (rc != result_OK) + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + if (rc != result_OK && rc != result_BAD_ARG) goto Failure; wuss_window_get_visible_bounds(win_v, &vb); @@ -1943,63 +2423,1806 @@ result_t wuss_test(const char *resources) wuss_window_close(win_v); } - printf("test: destroy mid-drag then move doesn't crash\n"); - - tc_c.redraw_count = 0; - tc_c.mouse_count = 0; - delegate_c.handle = test_handle; - delegate_c.task_data = &tc_c; - delegate_c.bg = wuss_NO_BACKGROUND; + printf("test: a toggle-size that repositions the window blits the overlapping content rather than repainting it all, and restores the exact pre-toggle box\n"); - box_c.x0 = 0; - box_c.y0 = 0; - box_c.x1 = 40; - box_c.y1 = 40; - rc = wuss_window_create(wuss, - &box_c, - "C", - wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | - wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | - wuss_WINDOW_NO_RESIZE, - &delegate_c, - box_c.x1 - box_c.x0, - box_c.y1 - box_c.y0, - &win_c); - if (rc != result_OK) - goto Failure; + { + wuss_task_t *delegate_p; + box_t box_p, before, after, titlebar, toggle; + wuss_window_t *win_p; + int outline_px, titlebar_height, inset, icon, cx, cy; + int i, dx, dy, kept_x, kept_y, kept_dirty; + int ghost_x, ghost_y, ghost_dirty; + + delegate_p = mk_task(wuss, NULL, NULL); + if (delegate_p == NULL) goto Failure; + + box_p.x0 = 110; box_p.y0 = 110; + box_p.x1 = 190; box_p.y1 = 190; /* 80x80 content, well down the bottom-right + * of the 200x200 screen -- big enough to + * have a real interior the blit can keep; + * doc big enough that maximize is + * screen-limited so the window must move */ + rc = wuss_window_create(delegate_p, &box_p, "P", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(400, 400), SIZE2D(0, 0), &win_p); + if (rc != result_OK) + goto Failure; - rc = wuss_mouse_click(wuss, (point_t) { 31, 11 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* C's titlebar, above its content, clear of the close icon */ - if (rc != result_OK) - goto Failure; - if (hit != win_c) - goto Failure; + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; - wuss_window_close(win_c); + outline_px = 1; + titlebar_height = 20; + inset = 3; + icon = titlebar_height - 2 * inset; - rc = wuss_mouse_move(wuss, (point_t) { 20, 20 }, &hit); - if (rc != result_OK) - goto Failure; + wuss_window_get_visible_bounds(win_p, &before); + titlebar.x1 = before.x1 - outline_px; + titlebar.y0 = before.y0 + outline_px; + toggle.x1 = titlebar.x1 - inset; + toggle.x0 = toggle.x1 - icon; + toggle.y0 = titlebar.y0 + inset; + toggle.y1 = toggle.y0 + icon; + cx = (toggle.x0 + toggle.x1) / 2; + cy = (toggle.y0 + toggle.y1) / 2; - printf("test: wuss_task_stop sends wuss_EVENT_QUIT to each window's task\n"); + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); + if (rc != result_OK || hit != win_p) + goto Failure; + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + if (rc != result_OK) + goto Failure; - tc_a.stop_count = 0; - tc_b.stop_count = 0; - wuss_task_stop(win_a); - wuss_task_stop(win_b); - wuss_window_close(win_a); - wuss_window_close(win_b); - if (tc_a.stop_count != 1 || tc_b.stop_count != 1) - goto Failure; + wuss_window_get_visible_bounds(win_p, &after); + if (after.x0 >= before.x0 || after.y0 >= before.y0) + goto Failure; /* the window had to move toward the origin to fit its + * maximized box on screen */ + if (after.x0 < 0 || after.y0 < 0 || after.x1 > 200 || after.y1 > 200) + goto Failure; /* ...but only as far as needed, and it stays on-screen */ + if (after.x1 - after.x0 < 190 || after.y1 - after.y0 < 190) + goto Failure; /* it genuinely fills (nearly) the whole screen */ + + dx = after.x0 - before.x0; + dy = after.y0 - before.y0; + + /* a point in the old content interior, clear of outline/titlebar/ + * scrollbar furniture on every side: after the toggle's shift by + * (dx,dy) it lands in the new content interior, and the blit must have + * carried its pixels there -- so it must NOT be in the dirty list. + * (This window starts screen-clamped hard against the bottom-right, so + * the maximized footprint is a superset of the old one -- there is no + * vacated backdrop strip to check, only the preserved interior.) */ + kept_x = (before.x0 + outline_px + before.x1 - outline_px - icon) / 2 + dx; + kept_y = (before.y0 + outline_px + titlebar_height + + before.y1 - outline_px - icon) / 2 + dy; + + /* where the OLD vertical scrollbar column lands once shifted by (dx,dy): + * a whole-footprint blit would drag that stale scrollbar strip here, + * into what is now interior content. The blit must be content-only, so + * this point must be in the dirty list to be repainted as content. */ + ghost_x = before.x1 - outline_px - icon / 2 + dx; + ghost_y = (before.y0 + outline_px + titlebar_height + + before.y1 - outline_px) / 2 + dy; + + kept_dirty = 0; + ghost_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + box_t region; - wuss_destroy(wuss); + wuss_get_dirty(wuss, i, ®ion); + if (box_contains_point(®ion, kept_x, kept_y)) + kept_dirty = 1; + if (box_contains_point(®ion, ghost_x, ghost_y)) + ghost_dirty = 1; + } + if (kept_dirty) + goto Failure; /* the shifted interior content was blitted, not repainted */ + if (!ghost_dirty) + goto Failure; /* the old scrollbar's shifted position must repaint as + * content -- the blit must not have carried furniture */ - free(pixels); + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; -#ifdef USE_SDL - rc = wuss_interactive_test(resources); - if (rc != result_TEST_PASSED) + /* toggle back: the icon moved with the grown/repositioned titlebar */ + wuss_window_get_visible_bounds(win_p, &after); + titlebar.x1 = after.x1 - outline_px; + titlebar.y0 = after.y0 + outline_px; + toggle.x1 = titlebar.x1 - inset; + toggle.x0 = toggle.x1 - icon; + toggle.y0 = titlebar.y0 + inset; + toggle.y1 = toggle.y0 + icon; + cx = (toggle.x0 + toggle.x1) / 2; + cy = (toggle.y0 + toggle.y1) / 2; + + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); + if (rc != result_OK || hit != win_p) + goto Failure; + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_p, &after); + if (after.x0 != before.x0 || after.y0 != before.y0 || + after.x1 != before.x1 || after.y1 != before.y1) + goto Failure; /* restore returns the exact pre-toggle box, position too */ + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_p); + } + + printf("test: toggle-size fills the screen for a window with no document extent (doc == 0), and per-axis where only one axis has a doc\n"); + + { + wuss_task_t *delegate_z; + box_t box_z, before, after, titlebar, toggle; + wuss_window_t *win_z; + int outline_px, titlebar_height, inset, icon, cx, cy, axis; + + outline_px = 1; + titlebar_height = 20; + inset = 3; + icon = titlebar_height - 2 * inset; + + /* axis 0: doc == (0,0) -> both axes fill the screen. + * axis 1: doc == (60,0) -> width caps at ~60, height fills the screen. */ + for (axis = 0; axis < 2; axis++) + { + delegate_z = mk_task(wuss, NULL, NULL); + if (delegate_z == NULL) goto Failure; + + box_z.x0 = 20; box_z.y0 = 20; + box_z.x1 = 60; box_z.y1 = 60; /* 40x40 content */ + rc = wuss_window_create(delegate_z, &box_z, "Z", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + axis == 0 ? SIZE2D(0, 0) : SIZE2D(60, 0), + SIZE2D(0, 0), &win_z); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_z, &before); + titlebar.x1 = before.x1 - outline_px; + titlebar.y0 = before.y0 + outline_px; + toggle.x1 = titlebar.x1 - inset; + toggle.x0 = toggle.x1 - icon; + toggle.y0 = titlebar.y0 + inset; + toggle.y1 = toggle.y0 + icon; + cx = (toggle.x0 + toggle.x1) / 2; + cy = (toggle.y0 + toggle.y1) / 2; + + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); + if (rc != result_OK || hit != win_z) + goto Failure; + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_z, &content); + width = content.x1 - content.x0; + height = content.y1 - content.y0; + + if (height < 150) + goto Failure; /* the y axis has no doc cap in either case -> fills the + * screen, not stuck at the 40px starting height */ + if (axis == 0 && width < 150) + goto Failure; /* doc == 0 -> the x axis fills the screen too */ + if (axis == 1 && width != 60) + goto Failure; /* doc.w == 60 -> the x axis caps at the doc extent */ + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + /* toggle back restores the starting box */ + wuss_window_get_visible_bounds(win_z, &after); + titlebar.x1 = after.x1 - outline_px; + titlebar.y0 = after.y0 + outline_px; + toggle.x1 = titlebar.x1 - inset; + toggle.x0 = toggle.x1 - icon; + toggle.y0 = titlebar.y0 + inset; + toggle.y1 = toggle.y0 + icon; + cx = (toggle.x0 + toggle.x1) / 2; + cy = (toggle.y0 + toggle.y1) / 2; + + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); + if (rc != result_OK || hit != win_z) + goto Failure; + rc = wuss_mouse_click(wuss, POINT(cx, cy), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_z, &after); + if (after.x0 != before.x0 || after.y0 != before.y0 || + after.x1 != before.x1 || after.y1 != before.y1) + goto Failure; + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_z); + } + } + + printf("test: dragging a back-most window with nothing above it still blits\n"); + + { + static test_task_t tc_k, tc_l; + wuss_task_t *delegate_k, *delegate_l; + box_t box_k, box_l; + wuss_window_t *win_k, *win_l; + int before_k, before_l; + + tc_k.redraw_count = 0; + tc_k.mouse_count = 0; + delegate_k = mk_task(wuss, test_handle, &tc_k); + if (delegate_k == NULL) goto Failure; + + box_k.x0 = 0; box_k.y0 = 140; /* clear of the still-open A/B windows above */ + box_k.x1 = 50; box_k.y1 = 175; + rc = wuss_window_create(delegate_k, + &box_k, + "K", + wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_k), + SIZE2D(0, 0), + &win_k); + if (rc != result_OK) + goto Failure; + + tc_l.redraw_count = 0; + tc_l.mouse_count = 0; + delegate_l = mk_task(wuss, test_handle, &tc_l); + if (delegate_l == NULL) goto Failure; + + box_l.x0 = 120; box_l.y0 = 140; /* well clear of K, so never overlaps it */ + box_l.x1 = 170; box_l.y1 = 175; + rc = wuss_window_create(delegate_l, + &box_l, + "L", + wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_l), + SIZE2D(0, 0), + &win_l); + if (rc != result_OK) + goto Failure; + + wuss_window_restack(win_k, wuss_ZORDER_BACK); /* K is no longer topmost, but L never overlaps it */ + + rc = wuss_redraw_dirty(wuss); /* flush the restack's own dirty region first */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_k, &visible); + + rc = wuss_mouse_click(wuss, POINT(visible.x0 + 31, visible.y0 + 11), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* K's titlebar, clear of the close icon */ + if (rc != result_OK) + goto Failure; + if (hit != win_k) + goto Failure; + + before_k = tc_k.redraw_count; + before_l = tc_l.redraw_count; + rc = wuss_mouse_move(wuss, POINT(visible.x0 + 45, visible.y0 + 21), &hit); + if (rc != result_OK) + goto Failure; + if (hit != win_k) + goto Failure; + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + if (tc_k.redraw_count != before_k || tc_l.redraw_count != before_l) + goto Failure; /* blitted, not redrawn: nothing above K overlapped its old + * footprint, so the move fast path must still apply even + * though K isn't topmost */ + + rc = wuss_mouse_click(wuss, POINT(visible.x0 + 45, visible.y0 + 21), wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_k); + wuss_window_close(win_l); + } + + printf("test: resizing a window only invalidates the grown/shrunk sliver\n"); + + { + static test_task_t tc_m2; + wuss_task_t *delegate_m2; + box_t box_m2, before2, after2, region; + wuss_window_t *win_m2; + int i, dirty_area, full_area, interior_x, interior_y, interior_dirty; + + tc_m2.redraw_count = 0; + tc_m2.mouse_count = 0; + delegate_m2 = mk_task(wuss, test_handle, &tc_m2); + if (delegate_m2 == NULL) goto Failure; + + box_m2.x0 = 0; box_m2.y0 = 0; + box_m2.x1 = 40; box_m2.y1 = 40; + rc = wuss_window_create(delegate_m2, + &box_m2, + "M2", + wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_m2), + SIZE2D(0, 0), + &win_m2); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush the creation invalidation first */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_m2, &before2); + interior_x = before2.x0 + 2; /* inside the untouched left edge */ + interior_y = before2.y0 + 25; /* below the titlebar, in plain content */ + + rc = wuss_window_resize(win_m2, SIZE2D(80, 80)); /* grow */ + if (rc != result_OK) + goto Failure; + + if (wuss_get_dirty_count(wuss) == 0) + goto Failure; + + interior_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (box_contains_point(®ion, interior_x, interior_y)) + interior_dirty = 1; + } + if (interior_dirty) + goto Failure; /* untouched top-left corner, unchanged by growing bottom-right */ + + wuss_window_get_visible_bounds(win_m2, &after2); + full_area = (after2.x1 - after2.x0) * (after2.y1 - after2.y0); + dirty_area = dirty_union_area(wuss, &after2); + if (dirty_area < 0 || dirty_area >= full_area) + goto Failure; /* must be less than a full redraw of the grown footprint */ + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + before2 = after2; + + rc = wuss_window_resize(win_m2, SIZE2D(40, 40)); /* shrink back */ + if (rc != result_OK) + goto Failure; + + if (wuss_get_dirty_count(wuss) == 0) + goto Failure; + + interior_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (box_contains_point(®ion, interior_x, interior_y)) + interior_dirty = 1; + } + if (interior_dirty) + goto Failure; /* still untouched: the corner that remains after shrinking */ + + full_area = (before2.x1 - before2.x0) * (before2.y1 - before2.y0); + dirty_area = dirty_union_area(wuss, &before2); + if (dirty_area < 0 || dirty_area >= full_area) + goto Failure; + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_m2); + } + + printf("test: resizing a wuss_WINDOW_NO_RESIZE_BLIT window redraws it fully\n"); + + { + static test_task_t tc_nb2; + wuss_task_t *delegate_nb2; + box_t box_nb2, before3, after3, region; + wuss_window_t *win_nb2; + int i, dirty_area, full_area; + + tc_nb2.redraw_count = 0; + tc_nb2.mouse_count = 0; + delegate_nb2 = mk_task(wuss, test_handle, &tc_nb2); + if (delegate_nb2 == NULL) goto Failure; + + box_nb2.x0 = 0; box_nb2.y0 = 0; + box_nb2.x1 = 40; box_nb2.y1 = 40; + rc = wuss_window_create(delegate_nb2, &box_nb2, "NB2", wuss_WINDOW_NO_RESIZE_BLIT, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_nb2), + SIZE2D(0, 0), + &win_nb2); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush the creation invalidation first */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_nb2, &before3); + + rc = wuss_window_resize(win_nb2, SIZE2D(80, 80)); /* grow */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_nb2, &after3); + full_area = (after3.x1 - after3.x0) * (after3.y1 - after3.y0); + dirty_area = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + dirty_area += (region.x1 - region.x0) * (region.y1 - region.y0); + } + if (dirty_area < full_area) + goto Failure; /* NO_RESIZE_BLIT must fully redraw, not just the sliver */ + + wuss_window_close(win_nb2); + } + + printf("test: dragging a clear window onto an occluder leaves the occluder untouched\n"); + + { + static test_task_t tc_n, tc_o; + wuss_task_t *delegate_n, *delegate_o; + box_t box_n, box_o, visible_o, exposed, occluded, region; + wuss_window_t *win_n, *win_o; + int i, exposed_dirty, occluded_dirty; + + tc_n.redraw_count = 0; + tc_n.mouse_count = 0; + delegate_n = mk_task(wuss, test_handle, &tc_n); + if (delegate_n == NULL) goto Failure; + + box_n.x0 = 0; box_n.y0 = 140; /* clear of any occluder to start */ + box_n.x1 = 60; box_n.y1 = 170; + rc = wuss_window_create(delegate_n, &box_n, "N", + wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_n), + SIZE2D(0, 0), + &win_n); + if (rc != result_OK) + goto Failure; + + tc_o.redraw_count = 0; + tc_o.mouse_count = 0; + delegate_o = mk_task(wuss, test_handle, &tc_o); + if (delegate_o == NULL) goto Failure; + + box_o.x0 = 90; box_o.y0 = 140; /* N will be dragged partly on top of O */ + box_o.x1 = 130; box_o.y1 = 180; + rc = wuss_window_create(delegate_o, &box_o, "O", + wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_o), + SIZE2D(0, 0), + &win_o); + if (rc != result_OK) + goto Failure; + + /* O is created after N, so O is topmost -- N's destination footprint + * will overlap an occluder above it in z-order. */ + + rc = wuss_redraw_dirty(wuss); /* flush both creations first */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_n, &visible); + + /* Move N far enough right that its new footprint lands partly under O + * (which stays wholly untouched), while its old footprint started + * entirely clear of O. */ + wuss_window_move(win_n, POINT(visible.x0 + 80, visible.y0 + 10)); + + wuss_window_get_visible_bounds(win_n, &visible); + wuss_window_get_visible_bounds(win_o, &visible_o); + exposed.x0 = visible.x0; exposed.y0 = visible.y0; + exposed.x1 = visible_o.x0; exposed.y1 = visible.y1; /* N's part left of O */ + box_intersection(&visible, &visible_o, &occluded); /* N's part under O */ + + /* The part of N's new footprint that lands under O must NOT be queued + * dirty -- O hasn't moved, so its pixels are already correct there, and + * the move blit must have skipped blitting into that area rather than + * pasting N's stale pixels over it and forcing a repair. */ + occluded_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (box_intersects(®ion, &occluded)) + occluded_dirty = 1; + } + if (occluded_dirty) + goto Failure; + + /* The exposed part of N's new footprint, not under any occluder, must + * NOT be queued dirty -- it was already moved there correctly by the + * blit, so redrawing it too would be exactly the "repaint what could + * have been left in place" waste this fast path exists to avoid. */ + exposed_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (box_intersects(®ion, &exposed)) + exposed_dirty = 1; + } + if (exposed_dirty) + goto Failure; + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_n); + wuss_window_close(win_o); + } + + printf("test: moving a partly-occluded window blits its clean part and only repaints the occluded part\n"); + + { + static test_task_t tc_a, tc_b; + wuss_task_t *delegate_a, *delegate_b; + box_t box_a, box_b, visible_a, visible_b_before; + box_t clean_new, hidden_new, region; + wuss_window_t *win_a, *win_b; + int i, dx, clean_dirty, hidden_dirty; + + tc_b.redraw_count = 0; + tc_b.mouse_count = 0; + delegate_b = mk_task(wuss, test_handle, &tc_b); + if (delegate_b == NULL) goto Failure; + + box_b.x0 = 20; box_b.y0 = 10; /* left half will sit under A */ + box_b.x1 = 80; box_b.y1 = 50; + rc = wuss_window_create(delegate_b, &box_b, "B", + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_b), + SIZE2D(0, 0), + &win_b); + if (rc != result_OK) + goto Failure; + + tc_a.redraw_count = 0; + tc_a.mouse_count = 0; + delegate_a = mk_task(wuss, test_handle, &tc_a); + if (delegate_a == NULL) goto Failure; + + box_a.x0 = 0; box_a.y0 = 0; /* created after B, so A is topmost */ + box_a.x1 = 40; box_a.y1 = 100; + rc = wuss_window_create(delegate_a, &box_a, "A", + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_a), + SIZE2D(0, 0), + &win_a); + if (rc != result_OK) + goto Failure; + + /* B's old footprint (x:20-80,y:10-50) is split by A (x:0-40) into a + * hidden strip (x:20-40, under A) and a clean strip (x:40-80, exposed). */ + + rc = wuss_redraw_dirty(wuss); /* flush both creations first */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_b, &visible_b_before); + wuss_window_get_visible_bounds(win_a, &visible_a); + + /* Move B far enough right that its whole new footprint clears A. */ + dx = 60; + wuss_window_move(win_b, POINT(visible_b_before.x0 + dx, + visible_b_before.y0)); + + /* The clean strip (previously exposed, genuinely B's own pixels) lands + * at its translated destination and must have been blitted there, not + * repainted. */ + clean_new.x0 = 40 + dx; clean_new.y0 = 10; + clean_new.x1 = 80 + dx; clean_new.y1 = 50; + + /* The hidden strip (previously under A, never B's valid rendering) has + * no valid source pixels, so its translated destination must be a real + * repaint. */ + hidden_new.x0 = 20 + dx; hidden_new.y0 = 10; + hidden_new.x1 = 40 + dx; hidden_new.y1 = 50; + + hidden_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (box_intersects(®ion, &hidden_new)) + hidden_dirty = 1; + } + if (!hidden_dirty) + goto Failure; + + clean_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (box_intersects(®ion, &clean_new)) + clean_dirty = 1; + } + if (clean_dirty) + goto Failure; + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_b); + wuss_window_close(win_a); + } + + printf("test: moving a window whose occluded piece was never blitted doesn't redraw the occluder\n"); + + { + static test_task_t tc_a, tc_b; + wuss_task_t *delegate_a, *delegate_b; + box_t box_a, box_b, visible_a, visible_b_before; + box_t region; + wuss_window_t *win_a, *win_b; + int i, occluder_dirty; + + tc_b.redraw_count = 0; + tc_b.mouse_count = 0; + delegate_b = mk_task(wuss, test_handle, &tc_b); + if (delegate_b == NULL) goto Failure; + + box_b.x0 = 0; box_b.y0 = 0; /* right part sits under A throughout */ + box_b.x1 = 60; box_b.y1 = 40; + rc = wuss_window_create(delegate_b, &box_b, "B", + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_b), + SIZE2D(0, 0), + &win_b); + if (rc != result_OK) + goto Failure; + + tc_a.redraw_count = 0; + tc_a.mouse_count = 0; + delegate_a = mk_task(wuss, test_handle, &tc_a); + if (delegate_a == NULL) goto Failure; + + box_a.x0 = 40; box_a.y0 = 0; /* created after B, so A is topmost */ + box_a.x1 = 100; box_a.y1 = 40; + rc = wuss_window_create(delegate_a, &box_a, "A", + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_a), + SIZE2D(0, 0), + &win_a); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush both creations first */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_b, &visible_b_before); + wuss_window_get_visible_bounds(win_a, &visible_a); + + /* Move B straight down: its clean piece (x:0-40) and hidden piece + * (x:40-60, under A) both stay clear of / under A exactly as before -- + * nothing about A's own pixels is ever touched by the blit, so A must + * not be forced to redraw. */ + wuss_window_move(win_b, POINT(visible_b_before.x0, + visible_b_before.y0 + 5)); + + occluder_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (box_intersects(®ion, &visible_a)) + occluder_dirty = 1; + } + if (occluder_dirty) + goto Failure; + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_b); + wuss_window_close(win_a); + } + + printf("test: moving a window split by a mid-band occluder past the gap between bands blits both bands in a safe order\n"); + + { + static test_task_t tc_a, tc_b; + wuss_task_t *delegate_a, *delegate_b; + box_t box_a, box_b, visible_b_before; + box_t occluded_overlap, hidden_new, region; + wuss_window_t *win_a, *win_b; + int i, occluded_dirty, hidden_dirty; + + tc_b.redraw_count = 0; + tc_b.mouse_count = 0; + delegate_b = mk_task(wuss, test_handle, &tc_b); + if (delegate_b == NULL) goto Failure; + + box_b.x0 = 0; box_b.y0 = 0; /* middle band sits under A */ + box_b.x1 = 60; box_b.y1 = 60; + rc = wuss_window_create(delegate_b, &box_b, "B", + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_b), + SIZE2D(0, 0), + &win_b); + if (rc != result_OK) + goto Failure; + + tc_a.redraw_count = 0; + tc_a.mouse_count = 0; + delegate_a = mk_task(wuss, test_handle, &tc_a); + if (delegate_a == NULL) goto Failure; + + box_a.x0 = 0; box_a.y0 = 20; /* created after B, so A is topmost */ + box_a.x1 = 60; box_a.y1 = 40; + rc = wuss_window_create(delegate_a, &box_a, "A", + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_a), + SIZE2D(0, 0), + &win_a); + if (rc != result_OK) + goto Failure; + + /* B's old footprint (y:0-60) is split by A (y:20-40) into a top clean + * band (y:0-20), a hidden middle band (y:20-40) and a bottom clean band + * (y:40-60), each spanning the full width. */ + + rc = wuss_redraw_dirty(wuss); /* flush both creations first */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_b, &visible_b_before); + + /* Move B down by 25px, past the 20px gap between the two clean bands: + * the top band's destination (y:25-45) lands on the bottom band's + * still-unread old source (y:40-60). Blitted in the other order -- + * bottom band first (its destination y:65-85 doesn't touch the top + * band's source), then the top band -- both blits are safe, so this + * is not a genuine clobber cycle (translating disjoint pieces by the + * same offset never produces one: any conflict is consistently + * oriented by the direction of the move). The top band's destination + * overlap with A (y:25-40) is skipped by the blit entirely (A hasn't + * moved, its pixels there are already correct), so it must stay clean; + * the translated hidden band (y:45-65, never had valid pixels) still + * needs forcing dirty for a real repaint. */ + wuss_window_move(win_b, POINT(visible_b_before.x0, + visible_b_before.y0 + 25)); + + occluded_overlap.x0 = 0; occluded_overlap.y0 = 25; + occluded_overlap.x1 = 60; occluded_overlap.y1 = 40; + hidden_new.x0 = 0; hidden_new.y0 = 45; + hidden_new.x1 = 60; hidden_new.y1 = 65; + + occluded_dirty = hidden_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (box_intersects(®ion, &occluded_overlap)) + occluded_dirty = 1; + if (box_contains_box(&hidden_new, ®ion)) + hidden_dirty = 1; + } + if (occluded_dirty || !hidden_dirty) + goto Failure; + + /* The blit must have actually happened, not fallen back: the part of + * B's new footprint that's clear of A and not the hidden band (e.g. + * the bottom band's new position, y:65-85) must not be dirtied. */ + { + box_t clean_after, dirty_check; + + clean_after.x0 = 0; clean_after.y0 = 65; + clean_after.x1 = 60; clean_after.y1 = 85; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (!box_intersection(®ion, &clean_after, &dirty_check)) + goto Failure; + } + } + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_b); + wuss_window_close(win_a); + } + + printf("test: dragging a window deeper under a corner occluder blits both L-shaped pieces in a safe order\n"); + + { + static test_task_t tc_a, tc_b; + wuss_task_t *delegate_a, *delegate_b; + box_t box_a, box_b, visible_b_before, visible_a, region; + wuss_window_t *win_a, *win_b; + int i; + + tc_b.redraw_count = 0; + tc_b.mouse_count = 0; + delegate_b = mk_task(wuss, test_handle, &tc_b); + if (delegate_b == NULL) goto Failure; + + box_b.x0 = 80; box_b.y0 = 80; /* corner already under A */ + box_b.x1 = 140; box_b.y1 = 140; + rc = wuss_window_create(delegate_b, &box_b, "B", + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_b), + SIZE2D(0, 0), + &win_b); + if (rc != result_OK) + goto Failure; + + tc_a.redraw_count = 0; + tc_a.mouse_count = 0; + delegate_a = mk_task(wuss, test_handle, &tc_a); + if (delegate_a == NULL) goto Failure; + + box_a.x0 = 0; box_a.y0 = 0; /* created after B, so A is topmost */ + box_a.x1 = 100; box_a.y1 = 100; + rc = wuss_window_create(delegate_a, &box_a, "A", + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_a), + SIZE2D(0, 0), + &win_a); + if (rc != result_OK) + goto Failure; + + /* B's old footprint (80,80)-(140,140) overlaps A (0,0)-(100,100) in its + * corner (80,80)-(100,100); the rest of B is split into an L-shaped + * clean region of two pieces, one of whose destination lands on the + * other's still-unread source -- but blitting the other piece first + * avoids that entirely, so this must NOT fall back to a full clipped + * redraw (that was the "Adjust drag behind a corner fully redraws the + * window" regression). Dragging B up-left by (-15,-15) also grows the + * overlap with A without ever fully hiding or fully clearing it -- the + * blit skips the part of each piece's destination that now lands under + * A, so A's own rendering there is never touched and needs no repair. */ + + rc = wuss_redraw_dirty(wuss); /* flush both creations first */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_b, &visible_b_before); + wuss_window_get_visible_bounds(win_a, &visible_a); + + wuss_window_move(win_b, POINT(visible_b_before.x0 - 15, + visible_b_before.y0 - 15)); + + /* The blit must have actually happened, not fallen back: B's own + * footprint (outside A) must not be dirtied wholesale. */ + { + box_t visible_b_after, whole_footprint, dirty_area_box; + int dirty_area, footprint_area; + + wuss_window_get_visible_bounds(win_b, &visible_b_after); + box_union(&visible_b_before, &visible_b_after, &whole_footprint); + + dirty_area = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (!box_intersection(®ion, &whole_footprint, &dirty_area_box)) + dirty_area += (dirty_area_box.x1 - dirty_area_box.x0) * + (dirty_area_box.y1 - dirty_area_box.y0); + } + footprint_area = (whole_footprint.x1 - whole_footprint.x0) * + (whole_footprint.y1 - whole_footprint.y0); + if (dirty_area >= footprint_area) + goto Failure; /* fell back to a full redraw instead of blitting */ + } + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_b); + wuss_window_close(win_a); + } + + printf("test: destroy mid-drag then move doesn't crash\n"); + + tc_c.redraw_count = 0; + tc_c.mouse_count = 0; + delegate_c = mk_task(wuss, test_handle, &tc_c); + if (delegate_c == NULL) goto Failure; + + box_c.x0 = 0; + box_c.y0 = 0; + box_c.x1 = 40; + box_c.y1 = 40; + rc = wuss_window_create(delegate_c, + &box_c, + "C", + wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_c), + SIZE2D(0, 0), + &win_c); + if (rc != result_OK) goto Failure; -#endif + + rc = wuss_mouse_click(wuss, POINT(31, 11), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* C's titlebar, above its content, clear of the close icon */ + if (rc != result_OK) + goto Failure; + if (hit != win_c) + goto Failure; + + wuss_window_close(win_c); + + rc = wuss_mouse_move(wuss, POINT(20, 20), &hit); + if (rc != result_OK) + goto Failure; + + printf("test: mouse and scroll events arrive in virtual content space, with the scroll offset applied exactly once\n"); + + { + static test_task_t tc_s = { 0 }; + wuss_task_t *delegate_s; + box_t box_s, content_s; + wuss_window_t *win_s; + point_t scroll; + + delegate_s = mk_task(wuss, test_handle, &tc_s); + if (delegate_s == NULL) goto Failure; + + box_s.x0 = 10; box_s.y0 = 10; + box_s.x1 = 60; box_s.y1 = 60; /* 50x50 content onto a 200x200 doc: room to scroll */ + rc = wuss_window_create(delegate_s, &box_s, "S", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(200, 200), SIZE2D(0, 0), &win_s); + if (rc != result_OK) + goto Failure; + + scroll.x = 30; + scroll.y = 40; + wuss_window_set_scroll(win_s, scroll); + wuss_window_get_scroll(win_s, &scroll); /* read back in case it clamped */ + + wuss_window_get_content_bounds(win_s, &content_s); + + rc = wuss_mouse_click(wuss, + POINT(content_s.x0 + 5, content_s.y0 + 7), + wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); + if (rc != result_OK) + goto Failure; + if (hit != win_s) + goto Failure; + if (tc_s.last_x != 5 + scroll.x || tc_s.last_y != 7 + scroll.y) + goto Failure; /* a task adding the scroll offset itself would double-count it */ + + rc = wuss_mouse_click(wuss, + POINT(content_s.x0 + 5, content_s.y0 + 7), + wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + if (rc != result_OK) + goto Failure; + + rc = wuss_mouse_move(wuss, + POINT(content_s.x0 + 11, content_s.y0 + 13), + &hit); + if (rc != result_OK) + goto Failure; + if (tc_s.last_x != 11 + scroll.x || tc_s.last_y != 13 + scroll.y) + goto Failure; + + tc_s.last_scroll_x = -1; + tc_s.last_scroll_y = -1; + rc = wuss_scroll(wuss, + POINT(content_s.x0 + 3, content_s.y0 + 4), + 1, &hit); + if (rc != result_OK) + goto Failure; + if (hit != win_s) + goto Failure; + if (tc_s.last_scroll_x != 3 + scroll.x || tc_s.last_scroll_y != 4 + scroll.y) + goto Failure; + + wuss_window_close(win_s); + } + + printf("test: content bounds survive furniture, including the interior rules\n"); + + { + /* Furniture -- outline, titlebar, scrollbars and the rules dividing the + * content from them -- is added outside the requested content box, never + * carved out of it, so what the caller asks for is what it gets, both at + * creation and after a resize. */ + static test_task_t tc_r; + wuss_task_t *delegate_r; + box_t box_r, content_r; + wuss_window_t *win_r; + + memset(&tc_r, 0, sizeof(tc_r)); + delegate_r = mk_task(wuss, test_handle, &tc_r); + if (delegate_r == NULL) goto Failure; + + box_r.x0 = 20; + box_r.y0 = 30; + box_r.x1 = 120; + box_r.y1 = 110; + rc = wuss_window_create(delegate_r, + &box_r, + "rules", + wuss_WINDOW_NONE, /* all furniture present */ + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(400, 400), + SIZE2D(0, 0), + &win_r); + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_r, &content_r); + if (content_r.x1 - content_r.x0 != box_r.x1 - box_r.x0 || + content_r.y1 - content_r.y0 != box_r.y1 - box_r.y0) + goto Failure; + + rc = wuss_window_resize(win_r, SIZE2D(61, 47)); + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_r, &content_r); + if (content_r.x1 - content_r.x0 != 61 || content_r.y1 - content_r.y0 != 47) + goto Failure; + + wuss_window_close(win_r); + } + + printf("test: wuss_window_create_placed tiles windows and reclaims a closed slot\n"); + + { + /* Windows created without a position are packed towards the top-left and + * must not overlap; closing one frees its slot for the next create. */ + static test_task_t tc_p[4]; + wuss_task_t *delegate_p; + wuss_window_t *win_p[4]; + box_t vis[4], probe; + int k, m; + + memset(tc_p, 0, sizeof(tc_p)); + delegate_p = mk_task(wuss, test_handle, &tc_p[0]); + if (delegate_p == NULL) goto Failure; + + for (k = 0; k < 4; k++) + { + rc = wuss_window_create_placed(delegate_p, + SIZE2D(40, 30), + "P", + wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(40, 30), + SIZE2D(0, 0), + &win_p[k]); + if (rc != result_OK) + goto Failure; + wuss_window_get_visible_bounds(win_p[k], &vis[k]); + } + + for (k = 0; k < 4; k++) + for (m = k + 1; m < 4; m++) + if (box_intersects(&vis[k], &vis[m])) + goto Failure; /* auto-placed windows overlapped */ + + /* free the second window's slot, then a new placed window should land + * back in it rather than being pushed past the others */ + probe = vis[1]; + wuss_window_close(win_p[1]); + + rc = wuss_window_create_placed(delegate_p, + SIZE2D(40, 30), + "P", + wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + SIZE2D(40, 30), + SIZE2D(0, 0), + &win_p[1]); + if (rc != result_OK) + goto Failure; + wuss_window_get_visible_bounds(win_p[1], &vis[1]); + if (vis[1].x0 != probe.x0 || vis[1].y0 != probe.y0 || + vis[1].x1 != probe.x1 || vis[1].y1 != probe.y1) + goto Failure; /* freed slot not reused */ + + /* a manual move releases the slot: closing afterwards must not + * double-release (would corrupt the packer's free list) */ + wuss_window_move(win_p[0], POINT(200, 200)); + + for (k = 0; k < 4; k++) + wuss_window_close(win_p[k]); + } + + printf("test: wuss_set_palette and wuss_idle broadcast once per registered task\n"); + { + /* the wuss under test was made with a 2-entry palette (see top of this + * function); a fresh 2-entry palette must broadcast to every task, a + * wrong-length one must be refused without broadcasting */ + static const colour_t swapped[2] = { { 0xFF202020 }, { 0xFFE0E0E0 } }; + static const colour_t too_long[3] = + { { 0xFF000000 }, { 0xFF808080 }, { 0xFFFFFFFF } }; + + static test_task_t tc_pa, tc_pb; + wuss_task_t *delegate_pa, *delegate_pb; + + /* every earlier block-local test_task_t is out of scope now; drop the + * tasks that still point at it so the broadcast counts are exactly the + * two made here */ + reap_test_tasks(); + + memset(&tc_pa, 0, sizeof(tc_pa)); + memset(&tc_pb, 0, sizeof(tc_pb)); + delegate_pa = mk_task(wuss, test_handle, &tc_pa); + delegate_pb = mk_task(wuss, test_handle, &tc_pb); + if (delegate_pa == NULL || delegate_pb == NULL) + goto Failure; + + rc = wuss_set_palette(wuss, swapped, NELEMS(swapped)); + if (rc != result_OK) + goto Failure; + if (tc_pa.palette_count != 1 || tc_pb.palette_count != 1) + goto Failure; /* one PALETTE per registered task */ + + rc = wuss_set_palette(wuss, too_long, NELEMS(too_long)); + if (rc != result_BAD_ARG) + goto Failure; + if (tc_pa.palette_count != 1 || tc_pb.palette_count != 1) + goto Failure; /* rejected call must not have broadcast */ + + /* wuss_idle broadcasts a single wuss_EVENT_IDLE to each task too; a + * task with no window still gets it (delivery is per registered task, + * not per window) */ + rc = wuss_idle(wuss); + if (rc != result_OK) + goto Failure; + if (tc_pa.idle_count != 1 || tc_pb.idle_count != 1) + goto Failure; + + reap_test_tasks(); /* tc_pa/tc_pb die with this block too */ + rc = result_OK; + } + + printf("test: wuss_window_set_hidden fires PRE_SHOW; a veto keeps the window hidden\n"); + { + static test_task_t tc_ps; + wuss_task_t *delegate_ps; + box_t box_ps; + wuss_window_t *win_ps; + + memset(&tc_ps, 0, sizeof(tc_ps)); + delegate_ps = mk_task(wuss, test_handle, &tc_ps); + if (delegate_ps == NULL) + goto Failure; + + box_ps.x0 = 0; box_ps.y0 = 0; + box_ps.x1 = 60; box_ps.y1 = 60; + rc = wuss_window_create(delegate_ps, &box_ps, "PS", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_ps), SIZE2D(0, 0), &win_ps); + if (rc != result_OK) + goto Failure; + + /* hide it: no event, and it must not catch the pointer any more */ + rc = wuss_window_set_hidden(win_ps, 1); + if (rc != result_OK || tc_ps.pre_show_count != 0 || tc_ps.show_count != 0) + goto Failure; + rc = wuss_mouse_click(wuss, POINT(10, 10), wuss_BUTTON_SELECT, + wuss_MOUSE_DOWN, &hit); + if (rc != result_OK || hit == win_ps) + goto Failure; /* hidden window is not hit-tested */ + (void) wuss_mouse_click(wuss, POINT(10, 10), wuss_BUTTON_SELECT, + wuss_MOUSE_UP, &hit); + + /* vetoed reveal: PRE_SHOW fires, returns the veto rc, no SHOW, window + * stays hidden */ + tc_ps.veto_pre_show = 1; + rc = wuss_window_set_hidden(win_ps, 0); + if (rc != result_BAD_ARG) + goto Failure; + if (tc_ps.pre_show_count != 1 || tc_ps.show_count != 0) + goto Failure; + rc = wuss_mouse_click(wuss, POINT(10, 10), wuss_BUTTON_SELECT, + wuss_MOUSE_DOWN, &hit); + if (rc != result_OK || hit == win_ps) + goto Failure; /* still hidden */ + (void) wuss_mouse_click(wuss, POINT(10, 10), wuss_BUTTON_SELECT, + wuss_MOUSE_UP, &hit); + + /* allow it: PRE_SHOW then SHOW, and now it catches the pointer */ + tc_ps.veto_pre_show = 0; + rc = wuss_window_set_hidden(win_ps, 0); + if (rc != result_OK) + goto Failure; + if (tc_ps.pre_show_count != 2 || tc_ps.show_count != 1) + goto Failure; + rc = wuss_mouse_click(wuss, POINT(10, 10), wuss_BUTTON_SELECT, + wuss_MOUSE_DOWN, &hit); + if (rc != result_OK || hit != win_ps) + goto Failure; + (void) wuss_mouse_click(wuss, POINT(10, 10), wuss_BUTTON_SELECT, + wuss_MOUSE_UP, &hit); + + wuss_task_destroy(delegate_ps); + mk_task_count = 0; /* delegate_ps is gone; drop the stale registry entry */ + rc = result_OK; + } + + printf("test: wuss_task_destroy sends one wuss_EVENT_QUIT and closes the task's windows\n"); + { + static test_task_t tc_q; + wuss_task_t *delegate_q; + box_t box_q; + wuss_window_t *win_q1, *win_q2; + + memset(&tc_q, 0, sizeof(tc_q)); + delegate_q = mk_task(wuss, test_handle, &tc_q); + if (delegate_q == NULL) goto Failure; + + box_q.x0 = 0; box_q.y0 = 0; + box_q.x1 = 40; box_q.y1 = 40; + rc = wuss_window_create(delegate_q, &box_q, "Q1", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_q), SIZE2D(0, 0), &win_q1); + if (rc != result_OK) + goto Failure; + rc = wuss_window_create(delegate_q, &box_q, "Q2", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_q), SIZE2D(0, 0), &win_q2); + if (rc != result_OK) + goto Failure; + NOT_USED(win_q1); + NOT_USED(win_q2); + + /* one QUIT for the task, regardless of how many windows it owns */ + wuss_task_destroy(delegate_q); + if (tc_q.stop_count != 1) + goto Failure; + + mk_task_count = 0; /* delegate_q is gone; drop the stale registry entry */ + } + + printf("test: an autoclose task self-destructs when its last window closes and stops receiving broadcasts\n"); + { + static test_task_t tc_ac; + wuss_task_t *delegate_ac; + box_t box_ac; + wuss_window_t *win_ac1, *win_ac2; + + memset(&tc_ac, 0, sizeof(tc_ac)); + delegate_ac = mk_task(wuss, test_handle, &tc_ac); + if (delegate_ac == NULL) goto Failure; + wuss_task_set_autoclose(delegate_ac, 1); + + box_ac.x0 = 0; box_ac.y0 = 0; + box_ac.x1 = 40; box_ac.y1 = 40; + rc = wuss_window_create(delegate_ac, &box_ac, "AC1", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_ac), SIZE2D(0, 0), &win_ac1); + if (rc != result_OK) + goto Failure; + rc = wuss_window_create(delegate_ac, &box_ac, "AC2", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_ac), SIZE2D(0, 0), &win_ac2); + if (rc != result_OK) + goto Failure; + + /* first close: task still owns a window, so no reap and no QUIT */ + wuss_window_close(win_ac1); + if (tc_ac.stop_count != 0) + goto Failure; + + rc = wuss_idle(wuss); + if (rc != result_OK) + goto Failure; + if (tc_ac.idle_count != 1) /* still registered, still broadcast to */ + goto Failure; + + /* last close: reap fires one QUIT and unregisters the task */ + wuss_window_close(win_ac2); + if (tc_ac.stop_count != 1) + goto Failure; + + rc = wuss_idle(wuss); + if (rc != result_OK) + goto Failure; + if (tc_ac.idle_count != 1) /* gone: no further broadcast */ + goto Failure; + + mk_task_count = 0; /* delegate_ac reaped itself; drop the stale entry */ + } + + printf("test: a task reaping itself from inside wuss_idle does not derail the task walk\n"); + { + static test_task_t tc_w1, tc_w2, tc_w3; + wuss_task_t *delegate_w1, *delegate_w2, *delegate_w3; + box_t box_w; + wuss_window_t *win_w1, *win_w3; + + memset(&tc_w1, 0, sizeof(tc_w1)); + memset(&tc_w2, 0, sizeof(tc_w2)); + memset(&tc_w3, 0, sizeof(tc_w3)); + + box_w.x0 = 0; box_w.y0 = 0; + box_w.x1 = 40; box_w.y1 = 40; + + /* w1 first, w2 (self-reaping, autoclose) in the middle, w3 last: the + * walk must deliver to w1, survive w2 freeing its own node, and still + * reach w3. */ + delegate_w1 = mk_task(wuss, test_handle, &tc_w1); + if (delegate_w1 == NULL) goto Failure; + wuss_task_set_autoclose(delegate_w1, 1); + rc = wuss_window_create(delegate_w1, &box_w, "W1", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_w), SIZE2D(0, 0), &win_w1); + if (rc != result_OK) + goto Failure; + + delegate_w2 = mk_task(wuss, close_on_idle_handle, &tc_w2); + if (delegate_w2 == NULL) goto Failure; + wuss_task_set_autoclose(delegate_w2, 1); + rc = wuss_window_create(delegate_w2, &box_w, "W2", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_w), SIZE2D(0, 0), + &g_close_on_idle_win); + if (rc != result_OK) + goto Failure; + + delegate_w3 = mk_task(wuss, test_handle, &tc_w3); + if (delegate_w3 == NULL) goto Failure; + wuss_task_set_autoclose(delegate_w3, 1); + rc = wuss_window_create(delegate_w3, &box_w, "W3", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + box_size(&box_w), SIZE2D(0, 0), &win_w3); + if (rc != result_OK) + goto Failure; + + rc = wuss_idle(wuss); + if (rc != result_OK) + goto Failure; + if (tc_w1.idle_count != 1) /* reached before the reap */ + goto Failure; + if (tc_w2.stop_count != 1) /* self-reaped mid-walk */ + goto Failure; + if (tc_w3.idle_count != 1) /* walk still reached the task behind w2 */ + goto Failure; + + /* w2 is gone; a second idle must skip it cleanly */ + rc = wuss_idle(wuss); + if (rc != result_OK) + goto Failure; + if (tc_w2.idle_count != 1 || tc_w3.idle_count != 2) + goto Failure; + + wuss_window_close(win_w1); /* autoclose: reaps w1 */ + wuss_window_close(win_w3); /* autoclose: reaps w3 */ + mk_task_count = 0; /* all three tasks reaped themselves; drop stale entries */ + } + +#ifdef WUSS_MENUS + printf("test: wuss_menu_create_from_desc parses a descriptor tree\n"); + { + static const wuss_menu_item_t borrowed_items[] = + { + { "Info", wuss_MENU_ITEM_NONE, NULL }, + { "About", wuss_MENU_ITEM_NONE, NULL } + }; + static const wuss_menu_t borrowed = + { + "Help", borrowed_items, NELEMS(borrowed_items) + }; + wuss_menu_t *m; + const wuss_menu_t *sub; + + /* "Root" is the root caption (first token), then items App, + * File{ %s title discarded, Grid(!), Export(~), |Quit }, Help(> borrowed), + * with "File" substituted for both %s. '|Quit' sets DASHED on Quit itself, + * so the submenu holds 3 rows. */ + rc = wuss_menu_create_from_desc(&m, + "Root, App, %s { %s, !Grid, ~Export, |Quit }, >Help", + "File", "File", &borrowed); + if (rc != result_OK) + goto Failure; + + if (m->nitems != 3) goto MenuFail; + if (m->title == NULL || strcmp(m->title, "Root") != 0) goto MenuFail; + if (strcmp(m->items[0].text, "App") != 0) goto MenuFail; + if (m->items[0].submenu != NULL) goto MenuFail; + + /* items[1] "File" carries the { } submenu; its first token was the title + * and is not emitted; '|Quit' sets DASHED on the Quit row itself, which + * keeps its label */ + if (strcmp(m->items[1].text, "File") != 0) goto MenuFail; + sub = m->items[1].submenu; + if (sub == NULL || sub->nitems != 3) goto MenuFail; + if (strcmp(sub->items[0].text, "Grid") != 0) goto MenuFail; + if (!(sub->items[0].flags & wuss_MENU_ITEM_TICKED)) goto MenuFail; + if (!(sub->items[1].flags & wuss_MENU_ITEM_DISABLED)) goto MenuFail; + if (strcmp(sub->items[2].text, "Quit") != 0) goto MenuFail; + if (!(sub->items[2].flags & wuss_MENU_ITEM_DASHED)) goto MenuFail; + + /* items[2] "Help" got a deep copy of the borrowed menu */ + if (strcmp(m->items[2].text, "Help") != 0) goto MenuFail; + sub = m->items[2].submenu; + if (sub == NULL || sub == &borrowed) goto MenuFail; + if (sub->nitems != 2) goto MenuFail; + if (strcmp(sub->items[1].text, "About") != 0) goto MenuFail; + + wuss_menu_destroy(m); + + /* malformed: unbalanced brace */ + rc = wuss_menu_create_from_desc(&m, "T, A { B, C"); + if (rc != result_BAD_ARG) + goto MenuFail; + + rc = result_OK; + goto MenuOK; + +MenuFail: + printf("wuss_test: menu-desc check failed\n"); + return result_TEST_FAILED; +MenuOK: ; + } + +#ifdef WUSS_COMPONENTS + printf("test: wuss_fontmenu lists the bmfonts dir and resolves a pick\n"); + { + const char *dir; + wuss_fontmenu_t *fm; + const wuss_menu_t *fmm; + wuss_event_t ev; + const char *name; + int i; + + dir = path_join_filename(resources, 2, "resources", "bmfonts"); + + rc = wuss_fontmenu_create(&fm, dir, "Font", NULL, NULL); + if (rc != result_OK) + goto FontMenuFail; + + fmm = wuss_fontmenu_menu(fm); + if (fmm == NULL) goto FontMenuFail; + if (fmm->title == NULL || strcmp(fmm->title, "Font") != 0) + goto FontMenuFail; + if (fmm->nitems < 2) goto FontMenuFail; + + /* items sorted ascending, every row a plain leaf */ + for (i = 0; i < fmm->nitems; i++) + { + if (fmm->items[i].text == NULL) goto FontMenuFail; + if (fmm->items[i].submenu != NULL) goto FontMenuFail; + if (i > 0 && strcmp(fmm->items[i - 1].text, fmm->items[i].text) >= 0) + goto FontMenuFail; + } + + /* a MENU_SELECT for this menu resolves to the row's label */ + ev.kind = wuss_EVENT_MENU_SELECT; + ev.data.menu_select.menu = fmm; + ev.data.menu_select.index = 1; + ev.data.menu_select.button = wuss_BUTTON_SELECT; + name = wuss_fontmenu_selected(fm, &ev); + if (name == NULL || strcmp(name, fmm->items[1].text) != 0) + goto FontMenuFail; + + /* wrong event kind, foreign menu and out-of-range index all decline */ + ev.kind = wuss_EVENT_IDLE; + if (wuss_fontmenu_selected(fm, &ev) != NULL) goto FontMenuFail; + ev.kind = wuss_EVENT_MENU_SELECT; + ev.data.menu_select.menu = NULL; + if (wuss_fontmenu_selected(fm, &ev) != NULL) goto FontMenuFail; + ev.data.menu_select.menu = fmm; + ev.data.menu_select.index = fmm->nitems; + if (wuss_fontmenu_selected(fm, &ev) != NULL) goto FontMenuFail; + + wuss_fontmenu_destroy(fm); + + /* a SYSTEM-class wuss font is dropped from the menu; NONE-class and + * unnamed slots are not */ + { + const char *sysfontfile; + bmfont_t *sysfont; + wuss_font_desc_t sysdescs[2]; + wuss_t *syswuss; + + sysfontfile = path_join_filename(resources, 3, "resources", "bmfonts", + path_join_leafname("Symbols", "png")); + rc = bmfont_create(sysfontfile, &sysfont); + if (rc != result_OK) goto FontMenuFail; + + sysdescs[0].font = sysfont; + sysdescs[0].font_class = wuss_FONT_CLASS_SYSTEM; + sysdescs[0].name = "Symbols"; + sysdescs[1].font = sysfont; + sysdescs[1].font_class = wuss_FONT_CLASS_NONE; + sysdescs[1].name = "Tiny"; /* NONE class: not skipped though named */ + + rc = wuss_create(&scr, sysdescs, 2, NULL, 0, NULL, NULL, &syswuss); + if (rc != result_OK) + { + bmfont_destroy(sysfont); + goto FontMenuFail; + } + + /* dir points into path_join_filename's shared scratch buffer, which + * sysfontfile's path_join_filename call above just overwrote -- + * re-derive it rather than reuse the now-stale pointer */ + dir = path_join_filename(resources, 2, "resources", "bmfonts"); + + rc = wuss_fontmenu_create(&fm, dir, "Font", syswuss, NULL); + wuss_destroy(syswuss); + bmfont_destroy(sysfont); + if (rc != result_OK) goto FontMenuFail; + + fmm = wuss_fontmenu_menu(fm); + if (fmm == NULL) goto FontMenuFail; + for (i = 0; i < fmm->nitems; i++) + if (strcmp(fmm->items[i].text, "Symbols") == 0) goto FontMenuFail; + for (i = 0; i < fmm->nitems; i++) + if (strcmp(fmm->items[i].text, "Tiny") == 0) + break; + if (i == fmm->nitems) goto FontMenuFail; + + wuss_fontmenu_destroy(fm); + } + + /* missing directory is surfaced, not swallowed */ + rc = wuss_fontmenu_create(&fm, "no/such/dir/here", NULL, NULL, NULL); + if (rc != result_FILE_NOT_FOUND) goto FontMenuFail; + + rc = result_OK; + goto FontMenuOK; + +FontMenuFail: + printf("wuss_test: fontmenu check failed\n"); + return result_TEST_FAILED; +FontMenuOK: ; + } + + printf("test: wuss_colourmenu covers the palette and resolves a pick\n"); + { + wuss_colourmenu_t *cm; + const wuss_menu_t *cmm; + wuss_event_t ev; + wuss_colour_t picked; + int ok; + int i; + + rc = wuss_colourmenu_create(&cm, wuss, "Colour"); + if (rc != result_OK) + goto ColourMenuFail; + + cmm = wuss_colourmenu_menu(cm); + if (cmm == NULL) goto ColourMenuFail; + if (cmm->title == NULL || strcmp(cmm->title, "Colour") != 0) + goto ColourMenuFail; + if (cmm->nitems < 2) goto ColourMenuFail; + + /* one swatch row per palette index, in order */ + for (i = 0; i < cmm->nitems; i++) + { + if (cmm->items[i].text == NULL) goto ColourMenuFail; + if ((cmm->items[i].flags & wuss_MENU_ITEM_SWATCH) == 0) + goto ColourMenuFail; + if (cmm->items[i].swatch != (wuss_colour_t) i) goto ColourMenuFail; + } + + /* a MENU_SELECT for this menu resolves to the row's palette index */ + ev.kind = wuss_EVENT_MENU_SELECT; + ev.data.menu_select.menu = cmm; + ev.data.menu_select.index = 1; + ev.data.menu_select.button = wuss_BUTTON_SELECT; + ok = -1; + picked = wuss_colourmenu_selected(cm, &ev, &ok); + if (!ok || picked != (wuss_colour_t) 1) goto ColourMenuFail; + + /* wrong event kind, foreign menu and out-of-range index all decline */ + ev.kind = wuss_EVENT_IDLE; + if (wuss_colourmenu_selected(cm, &ev, &ok) != 0 || ok) + goto ColourMenuFail; + ev.kind = wuss_EVENT_MENU_SELECT; + ev.data.menu_select.menu = NULL; + if (wuss_colourmenu_selected(cm, &ev, &ok) != 0 || ok) + goto ColourMenuFail; + ev.data.menu_select.menu = cmm; + ev.data.menu_select.index = cmm->nitems; + if (wuss_colourmenu_selected(cm, &ev, &ok) != 0 || ok) + goto ColourMenuFail; + + wuss_colourmenu_destroy(cm); + + if (wuss_colourmenu_create(&cm, NULL, "Colour") != result_NULL_ARG) + goto ColourMenuFail; + + rc = result_OK; + goto ColourMenuOK; + +ColourMenuFail: + printf("wuss_test: colourmenu check failed\n"); + return result_TEST_FAILED; +ColourMenuOK: ; + } +#endif /* WUSS_COMPONENTS */ + +#ifdef WUSS_ICONS + printf("test: menu pick flashes then delivers MENU_SELECT; fast ADJUST " + "re-picks do not lose a click or leave a row inverted\n"); + { + static const wuss_menu_item_t flash_items[] = + { + { "One", wuss_MENU_ITEM_NONE, NULL }, + { "Two", wuss_MENU_ITEM_NONE, NULL }, + { "Three", wuss_MENU_ITEM_NONE, NULL } + }; + static const wuss_menu_t flash_menu = + { + "Pick", flash_items, NELEMS(flash_items) + }; + + const char *fontfile; + bmfont_t *font = NULL; + wuss_font_desc_t fdesc; + screen_t fscr; + bitmap_t fbm; + void *fpixels; + wuss_t *fwuss; + test_task_t ftc; + wuss_task_t *fowner; + struct wuss__menu *chain; + int i; + + /* a menu needs a font for its row metrics; the core wuss above was made + * without one */ + fontfile = path_join_filename(resources, 3, "resources", "bmfonts", + path_join_leafname("Tiny", "png")); + rc = bmfont_create(fontfile, &font); + if (rc != result_OK) + { + printf("wuss_test: flash test could not load %s\n", fontfile); + goto Failure; + } + + fpixels = malloc((size_t) rowbytes * 200); + if (fpixels == NULL) { rc = result_OOM; goto FlashFail; } + rc = bitmap_init(&fbm, SIZE2D(200, 200), pixelfmt_bgrx8888, rowbytes, + NULL, fpixels); + if (rc != result_OK) goto FlashFailFree; + screen_for_bitmap(&fscr, &fbm); + + fdesc.font = font; + fdesc.font_class = wuss_FONT_CLASS_NONE; + fdesc.name = NULL; + rc = wuss_create(&fscr, &fdesc, 1, NULL, 0, NULL, NULL, &fwuss); + if (rc != result_OK) goto FlashFailFree; + + memset(&ftc, 0, sizeof(ftc)); + fowner = mk_task(fwuss, test_handle, &ftc); + if (fowner == NULL) { rc = result_OOM; goto FlashDestroy; } + + /* --- a plain SELECT pick: flash runs, then one MENU_SELECT --- */ + rc = wuss_menu_open(fowner, &flash_menu, POINT(40, 40), NULL); + if (rc != result_OK) goto FlashDestroy; + + chain = fwuss->menu_chain; + if (chain == NULL || chain->menu != &flash_menu) goto FlashCheckFail; + + flash_pick_row(fwuss, chain, 1, wuss_BUTTON_SELECT); + + if (ftc.menu_select_count != 0) goto FlashCheckFail; /* deferred, not yet */ + if (chain->flash.frames <= 0) goto FlashCheckFail; /* flash was armed */ + + for (i = 0; i < 64; i++) /* longer than any plausible flash */ + wuss_idle(fwuss); + + if (ftc.menu_select_count != 1) goto FlashCheckFail; + if (ftc.last_menu_index != 1) goto FlashCheckFail; + if (fwuss->menu_chain != NULL) goto FlashCheckFail; /* SELECT tore it down */ + + /* --- fast ADJUST re-picks: row 0, then row 2 mid-flash --- */ + rc = wuss_menu_open(fowner, &flash_menu, POINT(40, 40), NULL); + if (rc != result_OK) goto FlashDestroy; + chain = fwuss->menu_chain; + ftc.menu_select_count = 0; + + flash_pick_row(fwuss, chain, 0, wuss_BUTTON_ADJUST); + wuss_idle(fwuss); + wuss_idle(fwuss); /* a couple of flash frames, nowhere near done */ + flash_pick_row(fwuss, chain, 2, wuss_BUTTON_ADJUST); + + /* the pre-empted row 0 pick must have been delivered, not dropped */ + if (ftc.menu_select_count != 1) goto FlashCheckFail; + if (ftc.last_menu_index != 0) goto FlashCheckFail; + /* and row 0 must not be left highlit */ + if (wuss__icon_hovered(chain->icons[0])) goto FlashCheckFail; + + for (i = 0; i < 64; i++) /* longer than any plausible flash */ + wuss_idle(fwuss); + + if (ftc.menu_select_count != 2) goto FlashCheckFail; + if (ftc.last_menu_index != 2) goto FlashCheckFail; + /* ADJUST keeps the chain open. The flash ends un-highlit, but the pointer + * is still parked on row 2 (flash_pick_row left it there) so that row -- + * and only that row -- comes back highlit. */ + if (fwuss->menu_chain == NULL) goto FlashCheckFail; + for (i = 0; i < flash_menu.nitems; i++) + if (wuss__icon_hovered(fwuss->menu_chain->icons[i]) != (i == 2)) + goto FlashCheckFail; + + rc = result_OK; + goto FlashDestroy; + +FlashCheckFail: + printf("wuss_test: menu flash check failed " + "(select_count=%d last_index=%d chain=%p)\n", + ftc.menu_select_count, ftc.last_menu_index, + (void *) fwuss->menu_chain); + rc = result_TEST_FAILED; + +FlashDestroy: + reap_test_tasks(); + wuss_destroy(fwuss); +FlashFailFree: + free(fpixels); +FlashFail: + bmfont_destroy(font); + if (rc != result_OK) + return result_TEST_FAILED; + } +#endif /* WUSS_ICONS */ +#endif /* WUSS_MENUS */ + + wuss_destroy(wuss); + + free(pixels); return result_TEST_PASSED; @@ -2010,3 +4233,231 @@ result_t wuss_test(const char *resources) return result_TEST_FAILED; } + +#else /* !(WUSS_FURNITURE && WUSS_ICONS) */ + +/* Compact core test for builds with furniture and/or icons compiled out. + * Exercises the chromeless window path: create, move, z-order, invalidate, + * redraw and programmatic scroll. No titlebar, no scrollbars, no icons. */ +result_t wuss_test(const char *resources) +{ + result_t rc; + int rowbytes; + void *pixels; + bitmap_t bm; + screen_t scr; + wuss_t *wuss; + test_task_t tc_a, tc_b; + wuss_task_t *delegate_a, *delegate_b; + box_t box_a, box_b, content; + wuss_window_t *win_a, *win_b, *hit; + point_t scroll; + + /* Force a chromeless window even in a build that still has furniture, so the + * assertions below (content box == visible box) hold in every config. */ + const wuss_window_flags_t chromeless = + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_CLOSE | + wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | + wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE; + + NOT_USED(resources); + + rowbytes = 200 * 4; + pixels = malloc((size_t) rowbytes * 200); + if (pixels == NULL) + goto Failure; + + rc = bitmap_init(&bm, SIZE2D(200, 200), pixelfmt_bgrx8888, rowbytes, NULL, + pixels); + if (rc != result_OK) + goto Failure; + + screen_for_bitmap(&scr, &bm); + + printf("test: wuss_create (core)\n"); + + rc = wuss_create(&scr, NULL, 0, NULL, 0, NULL, NULL, &wuss); + if (rc != result_OK) + goto Failure; + + printf("test: window_create too small\n"); + + box_a.x0 = 0; box_a.y0 = 0; box_a.x1 = 100; box_a.y1 = 0; + rc = wuss_window_create(mk_task(wuss, NULL, NULL), &box_a, "toosmall", wuss_WINDOW_NONE, + wuss_NO_BACKGROUND, box_size(&box_a), + SIZE2D(0, 0), &win_a); + if (rc != result_WUSS_TOO_SMALL) + goto Failure; + + printf("test: create overlapping windows A and B\n"); + + memset(&tc_a, 0, sizeof(tc_a)); + memset(&tc_b, 0, sizeof(tc_b)); + delegate_a = mk_task(wuss, test_handle, &tc_a); + if (delegate_a == NULL) goto Failure; + delegate_b = mk_task(wuss, test_handle, &tc_b); + if (delegate_b == NULL) goto Failure; + + box_a.x0 = 0; box_a.y0 = 0; box_a.x1 = 100; box_a.y1 = 100; + rc = wuss_window_create(delegate_a, &box_a, "A", chromeless, + wuss_NO_BACKGROUND, SIZE2D(400, 400), + SIZE2D(0, 0), &win_a); + if (rc != result_OK) + goto Failure; + + box_b.x0 = 50; box_b.y0 = 50; box_b.x1 = 150; box_b.y1 = 150; + rc = wuss_window_create(delegate_b, &box_b, "B", chromeless, + wuss_NO_BACKGROUND, SIZE2D(400, 400), + SIZE2D(0, 0), &win_b); + if (rc != result_OK) + goto Failure; + + /* With furniture off the content box is the visible box verbatim. */ + wuss_window_get_content_bounds(win_a, &content); + if (content.x0 != 0 || content.y0 != 0 || + content.x1 != 100 || content.y1 != 100) + goto Failure; + + printf("test: window_resize honours the requested size even past the " + "screen edge\n"); + + /* screen is 200x200; win_a sits at (0,0), chromeless. asking for a + * 500x500 content area is honoured verbatim. */ + rc = wuss_window_resize(win_a, SIZE2D(500, 500)); + if (rc != result_OK) + goto Failure; + wuss_window_get_content_bounds(win_a, &content); + if (content.x1 - content.x0 != 500 || content.y1 - content.y0 != 500) + goto Failure; + + /* a smaller request is likewise honoured verbatim */ + rc = wuss_window_resize(win_a, SIZE2D(120, 90)); + if (rc != result_OK) + goto Failure; + wuss_window_get_content_bounds(win_a, &content); + if (content.x1 - content.x0 != 120 || content.y1 - content.y0 != 90) + goto Failure; + + /* an offset top-left does not clip the size either */ + wuss_window_move(win_a, POINT(60, 40)); + rc = wuss_window_resize(win_a, SIZE2D(500, 500)); + if (rc != result_OK) + goto Failure; + wuss_window_get_content_bounds(win_a, &content); + if (content.x1 - content.x0 != 500 || content.y1 - content.y0 != 500) + goto Failure; + wuss_window_move(win_a, POINT(0, 0)); + rc = wuss_window_resize(win_a, SIZE2D(100, 100)); + if (rc != result_OK) + goto Failure; + + printf("test: window_create can never make a window bigger than the " + "screen\n"); + + { + box_t box_big; + wuss_window_t *win_big; + + /* chromeless and the on-screen nudge drags the top-left back to (0,0), + * so the clamp caps content at the full 200x200 screen */ + box_big.x0 = 10; box_big.y0 = 10; box_big.x1 = 400; box_big.y1 = 400; + rc = wuss_window_create(mk_task(wuss, NULL, NULL), &box_big, "BIG", chromeless, + wuss_NO_BACKGROUND, SIZE2D(400, 400), + SIZE2D(0, 0), &win_big); + if (rc != result_OK) + goto Failure; + wuss_window_get_content_bounds(win_big, &content); + if (content.x1 - content.x0 != 200 || content.y1 - content.y0 != 200) + goto Failure; + wuss_window_close(win_big); + } + + printf("test: redraw delivers REDRAW events\n"); + + wuss_redraw(wuss); + if (tc_a.redraw_count == 0 || tc_b.redraw_count == 0) + goto Failure; + + printf("test: z-order - click routes to topmost window\n"); + + /* B was created last so it is on top over the overlap at (75,75). */ + rc = wuss_mouse_click(wuss, POINT(75, 75), wuss_BUTTON_SELECT, + wuss_MOUSE_DOWN, &hit); + if (rc != result_OK || hit != win_b) + goto Failure; + if (tc_b.mouse_count != 1 || tc_a.mouse_count != 0) + goto Failure; + + wuss_window_restack(win_a, wuss_ZORDER_FRONT); + rc = wuss_mouse_click(wuss, POINT(75, 75), wuss_BUTTON_SELECT, + wuss_MOUSE_DOWN, &hit); + if (rc != result_OK || hit != win_a) + goto Failure; + if (tc_a.mouse_count != 1) + goto Failure; + + printf("test: mouse point is in document coordinates\n"); + + if (tc_a.last_x != 75 || tc_a.last_y != 75) + goto Failure; + + printf("test: window_move\n"); + + wuss_window_move(win_a, POINT(20, 20)); + wuss_window_get_content_bounds(win_a, &content); + if (content.x0 != 20 || content.y0 != 20) + goto Failure; + + printf("test: programmatic scroll offsets document coordinates\n"); + + wuss_window_set_scroll(win_a, POINT(10, 5)); + wuss_window_get_scroll(win_a, &scroll); + if (scroll.x != 10 || scroll.y != 5) + goto Failure; + + rc = wuss_mouse_click(wuss, POINT(20, 20), wuss_BUTTON_SELECT, + wuss_MOUSE_DOWN, &hit); + if (rc != result_OK || hit != win_a) + goto Failure; + if (tc_a.last_x != 10 || tc_a.last_y != 5) + goto Failure; + + printf("test: wheel scroll delivers SCROLL event and moves offset\n"); + + wuss_scroll(wuss, POINT(20, 20), 8, &hit); + if (hit != win_a) + goto Failure; + wuss_window_get_scroll(win_a, &scroll); + if (scroll.y != 13) + goto Failure; + + printf("test: invalidate marks dirty region\n"); + + wuss_window_invalidate(win_a, NULL); + if (wuss_get_dirty_count(wuss) == 0) + goto Failure; + wuss_redraw(wuss); + if (wuss_get_dirty_count(wuss) != 0) + goto Failure; + + printf("test: window_close delivers CLOSE and drops the window\n"); + + wuss_window_close(win_b); + rc = wuss_mouse_click(wuss, POINT(140, 140), wuss_BUTTON_SELECT, + wuss_MOUSE_DOWN, &hit); + if (rc != result_OK || hit != NULL) + goto Failure; + + wuss_destroy(wuss); + free(pixels); + + return result_TEST_PASSED; + +Failure: + + printf("wuss_test: failed\n"); + + return result_TEST_FAILED; +} + +#endif /* WUSS_FURNITURE && WUSS_ICONS */ diff --git a/libraries/wuss/window/close.c b/libraries/wuss/window/close.c deleted file mode 100644 index 252158cd..00000000 --- a/libraries/wuss/window/close.c +++ /dev/null @@ -1,27 +0,0 @@ -/* close.c -- wuss - minimal window manager */ - -#include - -#ifdef FORTIFY -#include "fortify/fortify.h" -#endif - -#include "../impl.h" - -void wuss_window_close(wuss_window_t *doomed) -{ - wuss_t *wuss; - - if (doomed == NULL) - return; - - wuss = doomed->wuss; - if (wuss->furniture.dragging == doomed) - wuss->furniture.dragging = NULL; - - wuss__invalidate_clipped(doomed, &doomed->visible); - - list_remove(&wuss->z_order, &doomed->link); - - free(doomed); -} diff --git a/libraries/wuss/window/get-content-bounds.c b/libraries/wuss/window/get-content-bounds.c deleted file mode 100644 index be2187d7..00000000 --- a/libraries/wuss/window/get-content-bounds.c +++ /dev/null @@ -1,8 +0,0 @@ -/* get-content-bounds.c -- wuss - minimal window manager */ - -#include "../impl.h" - -void wuss_window_get_content_bounds(const wuss_window_t *window, box_t *content) -{ - wuss__content_box(window, content); -} diff --git a/libraries/wuss/window/get-visible-bounds.c b/libraries/wuss/window/get-visible-bounds.c deleted file mode 100644 index b33f3bed..00000000 --- a/libraries/wuss/window/get-visible-bounds.c +++ /dev/null @@ -1,8 +0,0 @@ -/* get-visible-bounds.c -- wuss - minimal window manager */ - -#include "../impl.h" - -void wuss_window_get_visible_bounds(const wuss_window_t *window, box_t *visible) -{ - *visible = window->visible; -} diff --git a/libraries/wuss/window/invalidate.c b/libraries/wuss/window/invalidate.c deleted file mode 100644 index 9ed97811..00000000 --- a/libraries/wuss/window/invalidate.c +++ /dev/null @@ -1,229 +0,0 @@ -/* invalidate.c -- wuss - minimal window manager */ - -#include - -#include "../impl.h" - -/* Windows are treated as fully opaque over their whole visible footprint - * (titlebar/outline included) for occlusion purposes, matching how - * redraw_from repaints them: a task that leaves gaps via - * wuss_NO_BACKGROUND won't get those gaps refreshed by this either. */ - -/* Append "piece" minus its intersection "cut" with an occluder to "out", - * as up to four non-overlapping bands. */ -static void box_subtract_into(const box_t *piece, - const box_t *cut, - box_t *out, - int *pnout) -{ - if (cut->y0 > piece->y0 && *pnout < WUSS_MAX_INVALIDATE_PIECES) - { - out[*pnout].x0 = piece->x0; out[*pnout].y0 = piece->y0; - out[*pnout].x1 = piece->x1; out[*pnout].y1 = cut->y0; - (*pnout)++; - } - if (cut->y1 < piece->y1 && *pnout < WUSS_MAX_INVALIDATE_PIECES) - { - out[*pnout].x0 = piece->x0; out[*pnout].y0 = cut->y1; - out[*pnout].x1 = piece->x1; out[*pnout].y1 = piece->y1; - (*pnout)++; - } - if (cut->x0 > piece->x0 && *pnout < WUSS_MAX_INVALIDATE_PIECES) - { - out[*pnout].x0 = piece->x0; out[*pnout].y0 = cut->y0; - out[*pnout].x1 = cut->x0; out[*pnout].y1 = cut->y1; - (*pnout)++; - } - if (cut->x1 < piece->x1 && *pnout < WUSS_MAX_INVALIDATE_PIECES) - { - out[*pnout].x0 = cut->x1; out[*pnout].y0 = cut->y0; - out[*pnout].x1 = piece->x1; out[*pnout].y1 = cut->y1; - (*pnout)++; - } -} - -/* Clip "box" (screen space) down to the parts not already covered by - * windows above "window" in the z-order, writing the surviving pieces to - * "out" (capacity WUSS_MAX_INVALIDATE_PIECES) and returning their count. */ -int wuss__clip_to_visible(wuss_window_t *window, const box_t *box, box_t *out) -{ - box_t scratch[WUSS_MAX_INVALIDATE_PIECES]; - box_t *cur, *nxt, *tmp; - int ncur; - list_t *e; - - cur = out; - nxt = scratch; - cur[0] = *box; - ncur = 1; - - for (e = window->wuss->z_order.next; e != &window->link; e = e->next) - { - wuss_window_t *occluder; - int nnext, p; - - occluder = (wuss_window_t *) e; - nnext = 0; - - for (p = 0; p < ncur; p++) - { - box_t cut; - - if (box_intersection(&occluder->visible, &cur[p], &cut)) - { - if (nnext < WUSS_MAX_INVALIDATE_PIECES) - nxt[nnext++] = cur[p]; /* no overlap: piece survives untouched */ - } - else - { - box_subtract_into(&cur[p], &cut, nxt, &nnext); - } - } - - ncur = nnext; - tmp = cur; - cur = nxt; - nxt = tmp; - - if (ncur == 0) - break; - } - - if (cur != out) - memcpy(out, cur, ncur * sizeof(*out)); - - return ncur; -} - -/* Subtract each of "cuts" (an array of "ncuts" boxes) from "whole", writing - * the surviving pieces to "out" (capacity WUSS_MAX_INVALIDATE_PIECES) and - * returning their count. */ -static int subtract_boxes(const box_t *whole, - const box_t *cuts, - int ncuts, - box_t *out) -{ - box_t scratch[WUSS_MAX_INVALIDATE_PIECES]; - box_t *cur, *nxt, *tmp; - int ncur, i; - - cur = out; - nxt = scratch; - cur[0] = *whole; - ncur = 1; - - for (i = 0; i < ncuts; i++) - { - int p, nnext; - - nnext = 0; - - for (p = 0; p < ncur; p++) - { - box_t cut; - - if (box_intersection(&cuts[i], &cur[p], &cut)) - { - if (nnext < WUSS_MAX_INVALIDATE_PIECES) - nxt[nnext++] = cur[p]; /* no overlap: piece survives untouched */ - } - else - { - box_subtract_into(&cur[p], &cut, nxt, &nnext); - } - } - - ncur = nnext; - tmp = cur; - cur = nxt; - nxt = tmp; - - if (ncur == 0) - break; - } - - if (cur != out) - memcpy(out, cur, ncur * sizeof(*out)); - - return ncur; -} - -/* Invalidate the parts of "window"'s footprint that are hidden behind other - * windows at the current z-order -- the rest of its footprint is already - * showing its own correct pixels, so redrawing that too would just be - * wasted work. Bring-to-front calls this before reordering, so "hidden" - * means "about to be uncovered"; send-to-back calls it after reordering, so - * "hidden" means "just became covered" -- either way, invalidating exactly - * these parts is enough for the next redraw to leave the screen correct. */ -void wuss__invalidate_uncovered(wuss_window_t *window) -{ - box_t visible[WUSS_MAX_INVALIDATE_PIECES]; - box_t hidden[WUSS_MAX_INVALIDATE_PIECES]; - int nvisible, nhidden, i; - - nvisible = wuss__clip_to_visible(window, &window->visible, visible); - nhidden = subtract_boxes(&window->visible, visible, nvisible, hidden); - - for (i = 0; i < nhidden; i++) - wuss_invalidate(window->wuss, &hidden[i]); -} - -/* Invalidate "box" (screen space), clipped against windows above "window" - * in the z-order: the shared primitive behind wuss_window_invalidate and - * every other window-owned invalidation (move, resize, background change, - * destroy) that would otherwise redraw parts nothing can see change. */ -void wuss__invalidate_clipped(wuss_window_t *window, const box_t *box) -{ - box_t pieces[WUSS_MAX_INVALIDATE_PIECES]; - int npieces, i; - - npieces = wuss__clip_to_visible(window, box, pieces); - - for (i = 0; i < npieces; i++) - wuss_invalidate(window->wuss, &pieces[i]); -} - -/* Invalidate the part of "whole" not already covered by "keep" -- used - * after a blit has slid a window's pixels from "whole" to "keep", so only - * the vacated sliver still needs an actual repaint. */ -void wuss__invalidate_minus(wuss_t *wuss, const box_t *whole, const box_t *keep) -{ - box_t pieces[WUSS_MAX_INVALIDATE_PIECES]; - box_t cut; - int npieces, i; - - if (box_intersection(keep, whole, &cut)) - { - wuss_invalidate(wuss, whole); /* no overlap: all of "whole" is vacated */ - return; - } - - npieces = 0; - box_subtract_into(whole, &cut, pieces, &npieces); - - for (i = 0; i < npieces; i++) - wuss_invalidate(wuss, &pieces[i]); -} - -void wuss_window_invalidate(wuss_window_t *window, const box_t *local_box) -{ - box_t screen_box, content, whole; - - wuss__content_box(window, &content); - - if (local_box == NULL) - { - whole.x0 = 0; - whole.y0 = 0; - whole.x1 = content.x1 - content.x0; - whole.y1 = content.y1 - content.y0; - local_box = &whole; - } - - screen_box.x0 = content.x0 - window->scroll.x + local_box->x0; - screen_box.y0 = content.y0 - window->scroll.y + local_box->y0; - screen_box.x1 = content.x0 - window->scroll.x + local_box->x1; - screen_box.y1 = content.y0 - window->scroll.y + local_box->y1; - - wuss__invalidate_clipped(window, &screen_box); -} diff --git a/libraries/wuss/window/move.c b/libraries/wuss/window/move.c deleted file mode 100644 index 3b594240..00000000 --- a/libraries/wuss/window/move.c +++ /dev/null @@ -1,51 +0,0 @@ -/* move.c -- wuss - minimal window manager */ - -#include "../impl.h" - -/* p is the window's content top-left; the furniture offset (outline plus - * any titlebar) is constant for a given window, so the footprint just - * follows it */ -void wuss_window_move(wuss_window_t *window, point_t p) -{ - int width, height, outline_px, titlebar_height; - box_t before, dirty, copied; - - width = window->visible.x1 - window->visible.x0; - height = window->visible.y1 - window->visible.y0; - outline_px = wuss__outline_px(window); - titlebar_height = wuss__titlebar_height(window); - before = window->visible; - - window->visible.x0 = p.x - outline_px; - window->visible.y0 = p.y - outline_px - titlebar_height; - window->visible.x1 = window->visible.x0 + width; - window->visible.y1 = window->visible.y0 + height; - - wuss__notify_open(window); - - if (window->wuss->z_order.next == &window->link && - screen_copy_rect(window->wuss->scr, &before, - (point_t) { window->visible.x0, window->visible.y0 }, &copied)) - { - /* Topmost, and the screen format supports the blit: every pixel of - * "before" is genuinely this window's own rendering (nothing above it - * to have punched holes in it), so sliding those pixels to the new - * position is exactly as correct as asking the task to redraw there, - * but far cheaper -- only the vacated sliver behind the old position - * still needs an actual repaint. */ - wuss__invalidate_minus(window->wuss, &before, &window->visible); - - /* "copied" can be smaller than the new footprint if either end of the - * move was partly off-screen (e.g. dragging back on-screen from - * off-screen): the leftover part has no valid source pixels behind it, - * so it needs a real repaint too, not just the vacated sliver above. */ - wuss__invalidate_minus(window->wuss, &window->visible, &copied); - } - else - { - /* Not topmost, or the blit was declined (e.g. paletted screen): fall - * back to a normal clipped redraw of the whole moved footprint. */ - box_union(&before, &window->visible, &dirty); - wuss__invalidate_clipped(window, &dirty); - } -} diff --git a/libraries/wuss/window/resize.c b/libraries/wuss/window/resize.c deleted file mode 100644 index 9a7f48c1..00000000 --- a/libraries/wuss/window/resize.c +++ /dev/null @@ -1,28 +0,0 @@ -/* resize.c -- wuss - minimal window manager */ - -#include "../impl.h" - -result_t wuss_window_resize(wuss_window_t *window, int width, int height) -{ - int outline_px, titlebar_height; - box_t before, dirty; - point_t carve; - - if (!wuss__size_ok(width, height)) - return result_WUSS_TOO_SMALL; - - outline_px = wuss__outline_px(window); - titlebar_height = wuss__titlebar_height(window); - before = window->visible; - wuss__furniture_carve_for(window->flags, wuss__icon_size(window), &carve); - - window->visible.x1 = window->visible.x0 + width + 2 * outline_px + carve.x; - window->visible.y1 = window->visible.y0 + height + titlebar_height + 2 * outline_px + carve.y; - - wuss__notify_open(window); - - box_union(&before, &window->visible, &dirty); - wuss__invalidate_clipped(window, &dirty); - - return result_OK; -} diff --git a/libraries/wuss/window/set-background.c b/libraries/wuss/window/set-background.c deleted file mode 100644 index 92ee232f..00000000 --- a/libraries/wuss/window/set-background.c +++ /dev/null @@ -1,18 +0,0 @@ -/* set-background.c -- wuss - minimal window manager */ - -#include "../impl.h" - -result_t wuss_window_set_background(wuss_window_t *window, wuss_colour_t bg) -{ - box_t content; - - if (bg != wuss_NO_BACKGROUND && (bg < 0 || bg >= window->wuss->npalette)) - return result_WUSS_BAD_COLOUR; - - window->task.bg = bg; - - wuss__content_box(window, &content); - wuss__invalidate_clipped(window, &content); - - return result_OK; -} diff --git a/libraries/wuss/window/set-scroll.c b/libraries/wuss/window/set-scroll.c deleted file mode 100644 index 1543289e..00000000 --- a/libraries/wuss/window/set-scroll.c +++ /dev/null @@ -1,39 +0,0 @@ -/* set-scroll.c -- wuss - minimal window manager */ - -#include "../impl.h" - -void wuss_window_set_scroll(wuss_window_t *window, point_t p) -{ - box_t content, copied; - int dx, dy; - - dx = p.x - window->scroll.x; - dy = p.y - window->scroll.y; - if (dx == 0 && dy == 0) - return; - - window->scroll = p; - - wuss__content_box(window, &content); - - /* the scrollbar sausage position depends on scroll, so its well needs - * redrawing too -- content invalidation alone never touches it */ - wuss__furniture_invalidate(window); - - if (window->wuss->z_order.next == &window->link) - { - /* Topmost, so every pixel in "content" is genuinely this window's own - * rendering: slide it by the scroll delta (clipped to the viewport, so - * source and destination both stay inside "content") and only the - * newly-exposed edge strip(s) need an actual repaint. */ - window->wuss->scr->clip = content; - if (screen_copy_rect(window->wuss->scr, &content, - (point_t) { content.x0 - dx, content.y0 - dy }, &copied)) - { - wuss__invalidate_minus(window->wuss, &content, &copied); - return; - } - } - - wuss__invalidate_clipped(window, &content); -} diff --git a/resources/bmfonts/tall-font.png b/resources/bmfonts/CookeTall.png similarity index 100% rename from resources/bmfonts/tall-font.png rename to resources/bmfonts/CookeTall.png diff --git a/resources/bmfonts/daydream-font.png b/resources/bmfonts/Daydream.png similarity index 100% rename from resources/bmfonts/daydream-font.png rename to resources/bmfonts/Daydream.png diff --git a/resources/bmfonts/Digits-Bold.png b/resources/bmfonts/Digits-Bold.png new file mode 100644 index 00000000..bc0a318d Binary files /dev/null and b/resources/bmfonts/Digits-Bold.png differ diff --git a/resources/bmfonts/Digits-Regular.png b/resources/bmfonts/Digits-Regular.png new file mode 100755 index 00000000..ecad722f Binary files /dev/null and b/resources/bmfonts/Digits-Regular.png differ diff --git a/resources/bmfonts/gliderrider-font.png b/resources/bmfonts/GliderRider.png similarity index 100% rename from resources/bmfonts/gliderrider-font.png rename to resources/bmfonts/GliderRider.png diff --git a/resources/bmfonts/henry-font.png b/resources/bmfonts/Henry.png similarity index 100% rename from resources/bmfonts/henry-font.png rename to resources/bmfonts/Henry.png diff --git a/resources/bmfonts/MS Sans Serif.png b/resources/bmfonts/MS Sans Serif.png new file mode 100644 index 00000000..969ba1e6 Binary files /dev/null and b/resources/bmfonts/MS Sans Serif.png differ diff --git a/resources/bmfonts/Symbols.png b/resources/bmfonts/Symbols.png new file mode 100644 index 00000000..f4c8a636 Binary files /dev/null and b/resources/bmfonts/Symbols.png differ diff --git a/resources/bmfonts/Tiny.png b/resources/bmfonts/Tiny.png new file mode 100644 index 00000000..dc140588 Binary files /dev/null and b/resources/bmfonts/Tiny.png differ diff --git a/resources/bmfonts/digits-font.png b/resources/bmfonts/digits-font.png deleted file mode 100644 index 4111a428..00000000 Binary files a/resources/bmfonts/digits-font.png and /dev/null differ diff --git a/resources/bmfonts/ms-sans-serif.png b/resources/bmfonts/ms-sans-serif.png deleted file mode 100644 index 9ec9d04b..00000000 Binary files a/resources/bmfonts/ms-sans-serif.png and /dev/null differ diff --git a/resources/bmfonts/tiny-font.png b/resources/bmfonts/tiny-font.png deleted file mode 100644 index b2d7770a..00000000 Binary files a/resources/bmfonts/tiny-font.png and /dev/null differ diff --git a/resources/wuss/ninepatch.aseprite b/resources/wuss/ninepatch.aseprite new file mode 100644 index 00000000..0399756c Binary files /dev/null and b/resources/wuss/ninepatch.aseprite differ diff --git a/resources/wuss/ninepatch.png b/resources/wuss/ninepatch.png new file mode 100644 index 00000000..304e2af1 Binary files /dev/null and b/resources/wuss/ninepatch.png differ diff --git a/tools/test_wrap_doxygen.py b/tools/test_wrap_doxygen.py new file mode 100644 index 00000000..f252a2e7 --- /dev/null +++ b/tools/test_wrap_doxygen.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +"""Smallest possible smoke test for wrap_doxygen.py. Run directly: +python3 tools/test_wrap_doxygen.py +""" +from wrap_doxygen import process + +src = '''/** + * A short line. + * + * \\param[in] thing This description is deliberately long enough that it has to wrap across more than one line to fit. + * \\return Also deliberately long enough that this return description needs to wrap across two lines at least. + */ +int f(int thing); + +/** This single-line comment is deliberately long enough to need promoting to a multi-line block comment. */ +int g(void); +'''.splitlines() + +out, overflow = process(src, width=77) + +assert overflow == [], overflow +assert all(len(l) <= 77 for l in out), [l for l in out if len(l) > 77] +assert out[0] == '/**' +assert out[1] == ' * A short line.' +# continuation lines must keep the "*" one column right of "/**", with a +# space after it -- regression check for the "* text" (no leading space) +# alignment bug this script originally had. +cont = [l for l in out if 'wrap across more than' in l] +assert cont and cont[0][:3] == ' * ', repr(cont) +assert 'int g(void);' in out +assert out.count('/**') == 2 # original block + the promoted single-liner + +# idempotent: reformatting already-reformatted text changes nothing +out2, overflow2 = process(out, width=77) +assert out2 == out +assert overflow2 == [] + +print('ok') diff --git a/tools/test_wrap_protos.py b/tools/test_wrap_protos.py new file mode 100644 index 00000000..c09d3eaf --- /dev/null +++ b/tools/test_wrap_protos.py @@ -0,0 +1,191 @@ +#!/usr/bin/env python3 +"""assert-based self-check for wrap_protos.py""" +from wrap_protos import process, split_top_level + +W = 77 + + +def one(src): + out, changed = process(src, W) + return out if changed else None + + +def test_short_untouched(): + assert one("int f(int a, int b);\n") is None + + +def test_void_untouched(): + long = "static int " + "x" * 70 + "(void);\n" + assert one(long) is None + + +def test_basic_alignment(): + src = ("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)\n") + assert one(src) == ( + "static int screen_copy_rect_p4(screen_t *scr,\n" + " const box_t *s,\n" + " const box_t *d,\n" + " int width,\n" + " int height,\n" + " int dx,\n" + " int dy)\n" + ) + + +def test_idempotent(): + src = ("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)\n") + first = one(src) + assert process(first, W)[1] is False, "second pass should be a no-op" + + +def test_rewrap_bad_alignment(): + # joined form is >77, and it arrives already (badly) wrapped + src = ( + "static int screen_copy_rectangle_p4(screen_t *scr,\n" + " const box_t *s, const box_t *d,\n" + " int width, int height, int dx, int dy)\n" + ) + assert one(src) == ( + "static int screen_copy_rectangle_p4(screen_t *scr,\n" + " const box_t *s,\n" + " const box_t *d,\n" + " int width,\n" + " int height,\n" + " int dx,\n" + " int dy)\n" + ) + + +def test_short_multiline_untouched(): + # already multi-line but joins to <77 -- leave the hand grouping alone + src = ( + "void draw_rect(screen_t *scr,\n" + " int x, int y,\n" + " colour_t colour);\n" + ) + assert one(src) is None + + +def test_semicolon_tail_kept(): + src = ("extern void some_really_long_function_name_here(int first_argument, " + "int second_argument, int third_arg);\n") + got = one(src) + assert got.rstrip().endswith(");") + assert got.count("\n") == 3 + + +def test_brace_tail_spaced(): + src = ("static long another_long_one_that_is_over_the_limit(char *buffer, " + "unsigned long length, int flags) {\n") + assert one(src).rstrip().endswith(") {") + + +def test_func_pointer_param_aligned(): + src = ("int register_a_callback_with_a_longish_name(void *ctx, " + "int (*cb)(void *, int), unsigned long some_flags)\n") + got = one(src) + assert got == ( + "int register_a_callback_with_a_longish_name(void *ctx,\n" + " int (*cb)(void *, int),\n" + " unsigned long some_flags)\n" + ), got + + +def test_unnamed_params_aligned(): + src = ("int a_function_with_unnamed_parameters_that_is_long(const char *, " + "unsigned long, int);\n") + got = one(src) + assert got == ( + "int a_function_with_unnamed_parameters_that_is_long(const char *,\n" + " unsigned long,\n" + " int);\n" + ), got + + +def test_split_top_level_respects_nesting(): + assert split_top_level("int a, void (*f)(int, int), char *b") == \ + ["int a", "void (*f)(int, int)", "char *b"] + + +def test_variadic_bails(): + src = ("int a_printf_like_function_with_a_long_name(const char *fmt, " + "int count, ...)\n") + assert one(src) is None + + +def test_call_statement_not_touched(): + src = (" array_squeeze(v->base, v->used, v->width, width_argument_here); " + "// a call, way over 77 columns of course yes indeed\n") + assert one(src) is None + + +def test_bare_call_not_touched(): + src = (" some_function_call_that_is_quite_long(argument_one, argument_two, " + "argument_three, argument_four);\n") + assert one(src) is None + + +def test_if_statement_not_touched(): + src = (" if (some_long_condition_function(a, b) && another_condition(c, d) " + "&& yet_another_one(e))\n") + assert one(src) is None + + +def test_method_call_not_touched(): + src = (" obj->do_something_with_a_really_long_method_name(first_arg, " + "second_arg, third_arg, fourth);\n") + assert one(src) is None + + +def test_array_param_aligned(): + src = ("static result_t walk_test(ntree_t *t, ntree_walk_flags_t flags, " + "const char *expected[])\n{\n") + got = one(src) + assert got == ( + "static result_t walk_test(ntree_t *t,\n" + " ntree_walk_flags_t flags,\n" + " const char *expected[])\n" + "{\n" + ), got + # declarators all begin at the same column + lines = got.splitlines() + assert lines[0].index('*t') + 1 == lines[1].rindex('flags') \ + == lines[2].index('*expected') + 1 + + +def test_return_statement_not_touched(): + src = (" return atom_set(db->tags, db->counts[tag].index, " + "(const unsigned char *) name, strlen((char *) name) + 1);\n") + assert one(src) is None + + +def test_sizeof_call_not_touched(): + src = (" return some_allocator_function_with_a_long_name(sizeof(*p), " + "count_of_things, alignment_bytes);\n") + assert one(src) is None + + +def test_comment_line_not_touched(): + src = ( + "/** Mark it dirty. Shorthand for\n" + " * some_function_with_a_long_name(window, NULL, 0, extra_argument). */\n" + "#define M(w) some_function_with_a_long_name((w), NULL, 0, 0)\n" + ) + assert one(src) is None + + +def test_definition_still_wraps(): + src = ("result_t vector_set_width_with_a_longer_name(vector_t *v, " + "size_t width, int flags, int more)\n{\n") + got = one(src) + assert got is not None and got.splitlines()[0].endswith("(vector_t *v,") + + +if __name__ == '__main__': + for name, fn in sorted(globals().items()): + if name.startswith('test_'): + fn() + print(f'ok {name}') + print('all passed') diff --git a/tools/wrap_doxygen.py b/tools/wrap_doxygen.py new file mode 100644 index 00000000..4c6b2e8a --- /dev/null +++ b/tools/wrap_doxygen.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +"""wrap_doxygen.py -- rewrap Doxygen /** ... */ block comments to a column +limit, greedy-fill, continuation lines aligned under the description text +(matching the \\param/\\return style already used across DPTLib headers). + +Scope (ponytail: lazy on purpose): + - Rewraps multi-line /** ... */ blocks: \\param/\\return/\\brief/\\file/etc + tag lines, and plain prose lines, each as one reflow-able paragraph. + - Promotes an overlong single-line /** ... */ comment into a multi-line + block. + - Leaves /**< ... */ trailing member comments and non-comment code lines + alone even if too long -- fixing those means moving the comment above + the declaration and touching struct/enum layout, a structural call this + script doesn't make. Still reported so a human can look. +""" +import argparse +import re +import sys +import textwrap + +OPEN_RE = re.compile(r'^(\s*)/\*\*\s*$') +CLOSE_RE = re.compile(r'^(\s*)\*/\s*$') +BLANK_RE = re.compile(r'^\s*\*\s*$') +SINGLE_LINE_RE = re.compile(r'^(\s*)/\*\*\s+(.*\S)\s+\*/\s*$') +PARAM_RE = re.compile(r'^(\s*\*\s*\\param(?:\[[^\]]*\])?\s+\S+\s+)(.*)$') +TAG_RE = re.compile( + r'^(\s*\*\s*\\(?:return|brief|file|note|warning|pre|post|throws?)\b\s*)(.*)$') +PROSE_RE = re.compile(r'^(\s*\*\s+)(\S.*)$') + + +def cont_prefix(prefix1, indent): + return indent + '*' + ' ' * (len(prefix1) - len(indent) - 1) + + +def fill(prefix1, words, width, indent): + text = ' '.join(w for w in words if w) + cont = cont_prefix(prefix1, indent) + lines = textwrap.wrap(text, width=width, initial_indent=prefix1, + subsequent_indent=cont, break_long_words=False, + break_on_hyphens=False) + return lines or [prefix1.rstrip()] + + +def wrap_single_line_comment(indent, text, width): + body_width = width - len(indent) - 3 # " * " prefix + lines = textwrap.wrap(text, width=body_width, break_long_words=False, + break_on_hyphens=False) + out = [indent + '/**'] + out += [indent + ' * ' + l for l in lines] + out.append(indent + ' */') + return out + + +def process(lines, width): + out = [] + overflow = [] + i = 0 + n = len(lines) + while i < n: + line = lines[i].rstrip('\n') + + m = SINGLE_LINE_RE.match(line) + if m and '/**<' not in line and len(line) > width: + indent, text = m.groups() + out.extend(wrap_single_line_comment(indent, text, width)) + i += 1 + continue + + m = OPEN_RE.match(line) + if m: + out.append(line) + indent = m.group(1) + ' ' # body lines' "*" sits one col right of "/**" + i += 1 + pending = None # (prefix1, [words...]) + + def flush(): + if pending is not None: + out.extend(fill(pending[0], pending[1], width, indent)) + + while i < n: + body = lines[i].rstrip('\n') + + mclose = CLOSE_RE.match(body) + if mclose: + flush() + pending = None + out.append(body) + i += 1 + break + + if BLANK_RE.match(body): + flush() + pending = None + out.append(body) + i += 1 + continue + + mtag = PARAM_RE.match(body) or TAG_RE.match(body) + if mtag: + flush() + pending = (mtag.group(1), [mtag.group(2)]) + i += 1 + continue + + mprose = PROSE_RE.match(body) + if mprose: + if pending is None: + pending = (mprose.group(1), [mprose.group(2)]) + else: + pending[1].append(mprose.group(2)) + i += 1 + continue + + # Doesn't look like a normal comment-body line -- pass through. + flush() + pending = None + out.append(body) + i += 1 + else: + flush() + continue + + if len(line) > width: + overflow.append((i + 1, line)) + out.append(line) + i += 1 + + return out, overflow + + +def main(): + ap = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + ap.add_argument('files', nargs='+') + ap.add_argument('--width', type=int, default=77) + ap.add_argument('--check', action='store_true', + help="don't write; exit 1 if any file would change") + args = ap.parse_args() + + changed = False + for path in args.files: + with open(path, encoding='utf-8') as f: + original = f.readlines() + new_lines, overflow = process(original, args.width) + new_text = '\n'.join(new_lines) + '\n' + old_text = ''.join(original) + + if new_text != old_text: + changed = True + if args.check: + print(f'{path}: would reformat') + else: + with open(path, 'w', encoding='utf-8') as f: + f.write(new_text) + print(f'{path}: reformatted') + for lineno, text in overflow: + print(f'{path}:{lineno}: still over {args.width} cols ' + f'(needs manual restructuring): {text.strip()}', + file=sys.stderr) + + if args.check and changed: + return 1 + return 0 + + +if __name__ == '__main__': + sys.exit(main()) diff --git a/tools/wrap_protos.py b/tools/wrap_protos.py new file mode 100644 index 00000000..2787af3a --- /dev/null +++ b/tools/wrap_protos.py @@ -0,0 +1,328 @@ +#!/usr/bin/env python3 +"""wrap_protos.py -- rewrap C function prototypes/definitions that exceed a +column limit onto one parameter per line, with the parameter names aligned +in a column (the style already used across DPTLib, e.g. screen-copy-rect.c): + + static int screen_copy_rect_p4(screen_t *scr, + const box_t *s, + int width) + +Scope: + - Rewraps a header/definition whose parameter list ends in ")" then an + optional ";" or "{". Handles both a single overlong physical line and an + already-wrapped multi-line prototype (the list is joined, then re-split + and realigned) -- so re-running is idempotent and fixes bad alignment. + - Splits the parameter list on top-level commas only, so function-pointer + params and (T)(args) casts survive the split. + - Aligns the declarator (name, or the "(*name)" of a function pointer) + into one column; "*"s are pulled against the name. Unnamed params align + on their trailing token. void / empty / variadic-only lists are left + as-is (nothing to align). + - Only rewrites when the original spans >1 line OR exceeds the width. +""" +import argparse +import re +import sys + +# Match " (" ... ")" , across newlines. Non-greedy head stops +# at the first "(", the args group is everything up to the final ")". +# +# The head is deliberately strict -- " (" with +# nothing else -- so a bare call statement like "foo(a, b);" or "p->fn(x)" +# never matches. It still needs the is_prototype_head() checks below (>=2 +# space-separated words, a plausible return type, real param syntax). +DECL_RE = re.compile( + r'^(?P[ \t]*)' + r'(?P[A-Za-z_]\w*(?:[ \t]+(?:[A-Za-z_]\w*|\*+))*[ \t]*' + r'\*?[ \t]*[A-Za-z_]\w*\()' + r'(?P.*?)' + r'\)[ \t]*(?P[;{]?)[ \t]*$', + re.DOTALL) +# statement-start guard: the previous non-blank line must end like this for the +# candidate to be at the start of a statement (not mid-expression / mid-call). +STMT_END_RE = re.compile(r'(?:[;{}]|\*/|\)|,|^\s*#.*|^\s*/[/*].*)\s*$') +QUALIFIERS = {'static', 'extern', 'inline', '_Noreturn', 'auto', 'register', + '__inline', '__inline__', '__forceinline'} +# C keywords that can't appear in a function decl/def head -- their presence +# means the line is a statement (return/if/while/...), not a prototype. +STMT_KW = {'return', 'if', 'else', 'while', 'for', 'do', 'switch', 'case', + 'default', 'goto', 'break', 'continue', 'sizeof', 'typedef', + 'typeof', '__typeof__', 'defined'} +IDENT_TAIL_RE = re.compile(r'([A-Za-z_]\w*)\s*$') +# C type keywords -- a param made only of these (+ * and whitespace) is unnamed +TYPE_KW = {'void', 'char', 'short', 'int', 'long', 'float', 'double', + 'signed', 'unsigned', 'const', 'volatile', 'struct', 'union', + 'enum', '_Bool', 'size_t', 'ssize_t', 'ptrdiff_t', 'wchar_t'} +# function pointer / array-of-fn-pointer param: "TYPE (*name)(...)" / "(*name[])" +FNPTR_RE = re.compile(r'^(?P.*?)\(\s*\*\s*(?P\w*)\s*(?P(?:\[\s*\w*\s*\])*)\)\s*(?P\(.*\))\s*$', re.DOTALL) + + +def is_prototype_head(indent, head, args): + """Reject anything that isn't a real top-level function decl/def head. + + Guards against call statements ("foo(a,b);", "obj->method(x)"), macro + invocations, and control-flow ("if (...)"). Conservative: a false + negative just means a line is left unwrapped.""" + h = head[:-1].strip() # drop trailing "(" + if any(c in h for c in '->.=[]"\'+-/%<>&|!~?:'): + return False + words = h.split() + if len(words) < 2: # need at least " " + return False + name = words[-1].lstrip('*') + if not name.isidentifier() or name in QUALIFIERS: + return False + # every leading word is a type-ish token (keyword, identifier, or stars) + # and none is a statement keyword + for w in words: + bare = w.strip('*') + if bare in STMT_KW: + return False + for w in words[:-1]: + if w.strip('*') and not (w.strip('*').isidentifier()): + return False + # args must look like a parameter list: empty, "void", or comma-separated + # items that each contain a type token (an identifier or a "*"). + a = args.strip() + if a in ('', 'void'): + return True + for part in split_top_level(a): + p = part.strip() + if not p: + return False + if p == '...': + continue + if not re.search(r'[A-Za-z_]', p): # no type at all -> not a param + return False + if p[0] in '"\'' or '=' in p: # string arg / default -> a call + return False + return True + + +def split_top_level(args): + """Split on commas not nested in () or [].""" + parts, depth, start = [], 0, 0 + for i, c in enumerate(args): + if c in '([': + depth += 1 + elif c in ')]': + depth -= 1 + elif c == ',' and depth == 0: + parts.append(args[start:i]) + start = i + 1 + parts.append(args[start:]) + return [' '.join(p.split()) for p in parts] + + +def parse_param(param): + """Return (lhs, stars, decl) so that "lhs" + pad + stars + decl reflows the + param with "decl" (the name, or "(*name)(...)") in an aligned column. + + Returns None for void / "..." (nothing meaningful to align).""" + p = param.strip() + if p in ('', 'void', '...'): + return None + + m = FNPTR_RE.match(p) + if m: + ret = m.group('ret').rstrip() + stars = '' + while ret.endswith('*'): + stars += '*' + ret = ret[:-1].rstrip() + decl = f"(*{m.group('name')}{m.group('arr')}){m.group('rest')}" + return ret, stars, decl + + if '(' in p: # some other parenthesised form -- leave whole + return p, '', '' + + # peel a trailing array suffix ("[]", "[N]", "[N][M]") off the declarator + arr = '' + mt = re.search(r'((?:\[[^\]]*\])+)\s*$', p) + if mt: + arr = mt.group(1) + p = p[:mt.start()].rstrip() + + bare = {w for w in p.replace('*', ' ').split()} + m = IDENT_TAIL_RE.search(p) + if m and p[:m.start()].strip() and not bare <= TYPE_KW: # "TYPE name" + head = p[:m.start()].rstrip() + name = m.group(1) + else: # unnamed: "TYPE" / "TYPE *" + head, name = p, '' + stars = '' + while head.endswith('*'): + stars += '*' + head = head[:-1].rstrip() + return head, stars, name + arr + + +def reflow_parts(indent, head, args, tail): + params = split_top_level(args.strip()) + if len(params) == 1 and params[0].strip() in ('', 'void'): + return None + + parsed = [parse_param(p) for p in params] + if any(p is None for p in parsed): # void mixed in, or "..." -- bail + return None + + # DPTLib style (see screen-copy-rect.c, cache.c): the "*" sits immediately + # before the declarator, declarators line up in one column, and a "**" + # param's extra star hangs one place to the left. So the name column is + # one past the longest "type + space + stars". + name_col = max(len(l) + 1 + len(s) for l, s, _ in parsed) + cont = ' ' * len(indent + head) + close = ') ' + tail if tail == '{' else ')' + tail + + out = [] + for i, (lhs, stars, decl) in enumerate(parsed): + # right-align "stars + decl" so decl starts at name_col + tail_txt = stars + decl + piece = (lhs + ' ' * (name_col - len(stars) - len(lhs)) + tail_txt).rstrip() + prefix = (indent + head) if i == 0 else cont + sep = ',' if i < len(parsed) - 1 else close + out.append(prefix + piece + sep) + return '\n'.join(out) + '\n' + + +def _balanced(s): + """True if () and [] are balanced and never go negative in s.""" + depth = 0 + for c in s: + if c in '([': + depth += 1 + elif c in ')]': + depth -= 1 + if depth < 0: + return False + return depth == 0 + + +def _strip_comments(s): + """Blank out /* ... */ and // ... content so scanning ignores it. + Assumes s starts outside a comment.""" + out = [] + k = 0 + while k < len(s): + if s[k:k+2] == '//': + break + if s[k:k+2] == '/*': + end = s.find('*/', k + 2) + if end < 0: + return ''.join(out), True # unterminated -> rest is comment + out.append(' ') + out.append(' ' * (end + 2 - k - 2)) + k = end + 2 + continue + out.append(s[k]) + k += 1 + return ''.join(out), False + + +def process(text, width): + out = [] + changed = False + lines = text.splitlines(keepends=True) + prev_nonblank = '' # last source line already emitted + in_comment = False + i = 0 + while i < len(lines): + if in_comment: + out.append(lines[i]) + if '*/' in lines[i]: + in_comment = False + rest, cont = _strip_comments(lines[i].split('*/', 1)[1]) + in_comment = cont + i += 1 + continue + _, opened = _strip_comments(lines[i]) + if opened: # this line opens a block comment + out.append(lines[i]) + in_comment = True + i += 1 + continue + # a comment-continuation line (" * ...") never starts a prototype + if lines[i].lstrip().startswith('*'): + out.append(lines[i]) + i += 1 + continue + # Grow a candidate span up to the first line that closes the parens and + # ends in ) / ); / {. Stop early if a line ends in ; or { without + # balancing -- that means this was never a prototype. + j = i + span = '' + m = None + while j < len(lines) and j - i < 40: + span += lines[j] + body = span.rstrip() + if _balanced(body) and body.endswith((')', ');', ') {', '){')): + m = DECL_RE.match(body) + break + if body.endswith((';', '{', '}')) and _balanced(body): + break # closed, but not as a prototype + j += 1 + + ok = False + if m: + at_stmt_start = (prev_nonblank == '' + or STMT_END_RE.search(prev_nonblank)) + head_first = m.group('head').split()[0] + # a definition head may sit on its own line after "static\n" etc. + ok = (at_stmt_start + and is_prototype_head(m.group('indent'), m.group('head'), + m.group('args'))) + # reject macro-style ALL-CAPS "return type" + if head_first.isupper() and head_first not in TYPE_KW: + ok = False + + if ok: + one_line = (m.group('indent') + m.group('head') + + ', '.join(split_top_level(m.group('args').strip())) + + ')' + (' ' + m.group('tail') if m.group('tail') == '{' + else m.group('tail'))) + if len(one_line) > width: + new = reflow_parts(m.group('indent'), m.group('head'), + m.group('args'), m.group('tail')) + if new is not None and new != span: + out.append(new) + changed = True + prev_nonblank = new.rstrip('\n').rsplit('\n', 1)[-1] + i = j + 1 + continue + + out.append(lines[i]) + if lines[i].strip(): + prev_nonblank = lines[i].rstrip('\n') + i += 1 + return ''.join(out), changed + + +def main(): + ap = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument('files', nargs='+') + ap.add_argument('-w', '--width', type=int, default=77) + ap.add_argument('-n', '--dry-run', action='store_true', + help='report files that would change, write nothing') + args = ap.parse_args() + + rc = 0 + for path in args.files: + with open(path) as f: + text = f.read() + new, changed = process(text, args.width) + if not changed: + continue + if args.dry_run: + print(f'would rewrap: {path}') + rc = 1 + else: + with open(path, 'w') as f: + f.write(new) + print(f'rewrapped: {path}') + return rc + + +if __name__ == '__main__': + sys.exit(main())