diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..cc9b7fb8 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,88 @@ +# 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 + +- `wuss/icon.h` — work-area icons: static labels and clickable bevelled + buttons drawn inside a window's content area. Icon boxes are in virtual + document space, so they scroll with the content. Wuss hit-tests buttons + before the content task sees a click and delivers them as `wuss_EVENT_ICON`; + labels and hidden or disabled icons fall through as `wuss_EVENT_MOUSE`. +- `wuss_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. +- `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_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. + +### Fixed + +- 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. +- 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 ad68b665..26c28807 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,32 +8,49 @@ 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. + +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,9 +62,7 @@ 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. @@ -70,7 +85,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 f9b0db89..a322d526 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,8 @@ endif(CCACHE_FOUND) option(USE_FORTIFY "Use Fortify" OFF) option(USE_ASAN "Build with AddressSanitizer and UndefinedBehaviorSanitizer" OFF) option(DPTLIB_IMAGES_READ_ONLY "Remove libpng write support" OFF) +option(WUSS_FURNITURE "Build the wuss window-furniture subsystem" ON) +option(WUSS_ICONS "Build the wuss in-content icon subsystem" ON) # Referencing CMAKE_TOOLCHAIN_FILE avoids a warning on rebuilds. if(NOT ${CMAKE_TOOLCHAIN_FILE} STREQUAL "") @@ -116,6 +118,10 @@ set(PUBLIC_HEADERS include/wuss/window.h include/wuss/wuss.h) +if(WUSS_ICONS) + list(APPEND PUBLIC_HEADERS include/wuss/icon.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}") @@ -234,6 +240,8 @@ set(FRAMEBUF_SOURCES libraries/framebuf/screen/screen.c libraries/framebuf/screen/screen-copy-rect.c libraries/framebuf/screen/screen-draw.c + libraries/framebuf/screen/screen-draw-ninepatch.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 @@ -317,24 +325,9 @@ set(UTILS_SOURCES libraries/utils/pack/unpack.c libraries/utils/primes/primes.c) -set(WUSS_SOURCES +set(WUSS_CORE_SOURCES libraries/wuss/create.c libraries/wuss/destroy.c - 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/resize-box.c - libraries/wuss/furniture/scroll-action.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/get-font.c libraries/wuss/idle.c libraries/wuss/impl.h @@ -343,10 +336,12 @@ set(WUSS_SOURCES libraries/wuss/mouse-move.c libraries/wuss/redraw.c libraries/wuss/scroll.c + libraries/wuss/scroll-step.c libraries/wuss/task/start.c libraries/wuss/task/stop.c libraries/wuss/window/at.c libraries/wuss/window/create.c + libraries/wuss/window/create-placed.c libraries/wuss/window/close.c libraries/wuss/window/get-content-bounds.c libraries/wuss/window/get-scroll.c @@ -358,6 +353,48 @@ set(WUSS_SOURCES libraries/wuss/window/set-background.c libraries/wuss/window/set-scroll.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/resize-box.c + libraries/wuss/furniture/scroll-action.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) + +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/get-bbox.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/screen-box.c + libraries/wuss/icon/set-hidden.c + libraries/wuss/icon/set-text.c + libraries/wuss/icon.h) + +set(WUSS_SOURCES ${WUSS_CORE_SOURCES}) +if(WUSS_FURNITURE) + list(APPEND WUSS_SOURCES ${WUSS_FURNITURE_SOURCES}) +endif() +if(WUSS_ICONS) + list(APPEND WUSS_SOURCES ${WUSS_ICON_SOURCES}) +endif() + set(ALL_SOURCES ${PUBLIC_HEADERS} ${DATABASE_SOURCES} @@ -408,6 +445,13 @@ if(DPTLIB_IMAGES_READ_ONLY) target_compile_definitions(DPTLib PRIVATE DPTLIB_IMAGES_READ_ONLY) endif() +if(WUSS_FURNITURE) + target_compile_definitions(DPTLib PUBLIC WUSS_FURNITURE) +endif() +if(WUSS_ICONS) + target_compile_definitions(DPTLib PUBLIC WUSS_ICONS) +endif() + if(NOT TARGET_RISCOS) set(CMAKE_FIND_FRAMEWORK NEVER) find_package(PNG REQUIRED MODULE) @@ -550,12 +594,20 @@ if(BUILD_TESTS) 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/porter-duff.c libraries/wuss/test/tasks/sofa.c libraries/wuss/test/tasks/text.c libraries/wuss/test/wuss-test.c) + # The icon-driven launcher and the icons task only build when both wuss + # subsystems are present; the SDL interactive driver needs them too. + if(WUSS_FURNITURE AND WUSS_ICONS) + list(APPEND TEST_SOURCES + libraries/wuss/test/tasks/icons.c + libraries/wuss/test/tasks/launcher.c) + endif() + source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${TEST_SOURCES}) # Avoid a warning from CMake @@ -579,6 +631,10 @@ if(BUILD_TESTS) endif() if(BUILD_SDL_TESTS) + if(NOT (WUSS_FURNITURE AND WUSS_ICONS)) + message(FATAL_ERROR "BUILD_SDL_TESTS requires WUSS_FURNITURE and WUSS_ICONS") + endif() + find_package(SDL3 REQUIRED) find_package(SDL3_image REQUIRED) diff --git a/README.md b/README.md index 187fac83..2f17af0d 100644 --- a/README.md +++ b/README.md @@ -91,7 +91,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/docs/wuss.md b/docs/windowing/wuss.md similarity index 50% rename from docs/wuss.md rename to docs/windowing/wuss.md index 1d91fc58..0871dd5c 100644 --- a/docs/wuss.md +++ b/docs/windowing/wuss.md @@ -32,13 +32,15 @@ Create a window with a content bounding box, optional title, appearance flags, a result_t wuss_window_create(wuss_t *wuss, const box_t *content, const char *title, wuss_window_flags_t flags, wuss_colour_t bg, const wuss_task_t *task, - size2d_t doc, + size2d_t doc, size2d_t min_doc, wuss_window_t **window); ``` `bg` is filled in by wuss before each redraw event, or `wuss_NO_BACKGROUND` for the task to draw its own background (avoids a redundant fill behind an opaque task); changeable later via `wuss_window_set_background`. -`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. Set once at creation, immutable thereafter. +`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. @@ -60,12 +62,12 @@ wuss_task_t; - `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_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 icon in the bottom-right corner. +- `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. @@ -81,6 +83,7 @@ typedef enum wuss_event_kind { wuss_EVENT_REDRAW, wuss_EVENT_MOUSE, + wuss_EVENT_ICON, wuss_EVENT_SCROLL } wuss_event_kind_t; @@ -91,8 +94,9 @@ typedef struct wuss_event 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; + 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; } @@ -108,6 +112,8 @@ For `wuss_EVENT_REDRAW`, `event->data.redraw.scr` is called with `scr->clip` alr 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. @@ -122,11 +128,67 @@ Each window carries a scroll offset, `(0, 0)` by default: the point in the task' ## 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/include/datastruct/bitfifo.h b/include/datastruct/bitfifo.h index 509b6560..f7ec8e50 100644 --- a/include/datastruct/bitfifo.h +++ b/include/datastruct/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/cache.h b/include/datastruct/cache.h index a7983c82..164a18e6 100644 --- a/include/datastruct/cache.h +++ b/include/datastruct/cache.h @@ -109,11 +109,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 4d5b881f..75067575 100644 --- a/include/datastruct/hash.h +++ b/include/datastruct/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. diff --git a/include/datastruct/ntree.h b/include/datastruct/ntree.h index 609833a2..9e7d54af 100644 --- a/include/datastruct/ntree.h +++ b/include/datastruct/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/framebuf/screen.h b/include/framebuf/screen.h index 41988ede..309f205b 100644 --- a/include/framebuf/screen.h +++ b/include/framebuf/screen.h @@ -75,9 +75,10 @@ void screen_draw_pixel(screen_t *scr, int x, int y, colour_t colour); * \param[in] colour Colour of rectangle. */ void screen_draw_rect(screen_t *scr, - int x, int y, - size2d_t size, - colour_t colour); + int x, + int y, + size2d_t size, + colour_t colour); /** * Special case of `screen_draw_rect`. @@ -93,6 +94,50 @@ void screen_draw_square(screen_t *scr, int size, colour_t colour); +/** + * Built-in 8x8 fill patterns for `screen_fill_pattern`. Each is a 1-bit tile: + * set bits take the foreground colour, clear bits the background. + */ +typedef enum screen_pattern +{ + screen_PATTERN_SOLID = 0, /**< Every pixel foreground. */ + screen_PATTERN_GREY50, /**< 50% checkerboard. */ + screen_PATTERN_HSTRIPE, /**< 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__LIMIT /**< Count of patterns; not itself a pattern. */ +} +screen_pattern_t; + +/** + * Fills a box with a repeating 8x8 two-colour pattern. + * + * The tile is phased against (`origin_x`, `origin_y`): that coordinate is the + * one that would map to the box's top-left corner. Passing the caller's own + * scroll origin keeps the pattern locked to content as the box moves, rather + * than crawling with it. + * + * 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. + * \param[in] origin_x X coordinate mapping to `box->x0` for tile phase. + * \param[in] origin_y Y coordinate mapping to `box->y0` for tile phase. + * \param[in] fg Colour for set pattern bits. + * \param[in] bg Colour for clear pattern bits. + */ +void screen_fill_pattern(screen_t *scr, + const box_t *box, + screen_pattern_t pattern, + int origin_x, + int origin_y, + colour_t fg, + colour_t bg); + /** * Draws a bitmap, alpha-blending it against the screen where the bitmap has an * alpha channel. On paletted screens, which have no linear channel bits to @@ -108,6 +153,38 @@ void screen_draw_square(screen_t *scr, */ void screen_draw_bitmap(screen_t *scr, int x, int y, const bitmap_t *src); +/** Flags for `screen_draw_ninepatch`. */ +enum +{ + screen_NINEPATCH_NO_CENTRE = 1u << 0 /**< Leave the interior untouched. */ +}; + +/** + * Draws a "9-patch": a resizable frame built from a source image that is a 3x3 + * grid of equal cells. The source width and height must each be a positive + * multiple of 3; the cell size is a third of each. Given a destination box, the + * four corner cells are drawn at their natural size in the destination corners, + * the four edge cells are tiled along the destination edges, and the centre + * cell is tiled across the interior. + * + * If the destination is narrower or shorter than two cells the opposing corners + * overlap and each is clipped to its own half; the edges and centre are then + * omitted. Drawing is clipped to both the destination box and the screen's clip + * region, which is restored on return. Cells are blended exactly as + * `screen_draw_bitmap` does. No scaling is performed. + * + * \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. + */ +void screen_draw_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 @@ -156,8 +233,11 @@ 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 line (fixed-point Wu version with anti-aliasing). @@ -173,8 +253,11 @@ 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). @@ -190,7 +273,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/geom/packer.h b/include/geom/packer.h index ea966075..184f7d4f 100644 --- a/include/geom/packer.h +++ b/include/geom/packer.h @@ -77,6 +77,35 @@ 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. + * + * Adjacent released areas are not coalesced, so a single placement that would + * span two separately-released areas will not fit until the gap between them is + * also freed; placing a box that fits wholly within one free area is + * unaffected. packer_get_consumed_area is not narrowed by a release. + * + * \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..a1360842 100644 --- a/include/geom/point.h +++ b/include/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 index 202f474b..5d66d4c0 100644 --- a/include/geom/size.h +++ b/include/geom/size.h @@ -10,4 +10,7 @@ typedef struct size2d } size2d_t; +/** Construct a size2d_t compound literal. */ +#define SIZE2D(w, h) ((size2d_t) { (w), (h) }) + #endif /* GEOM_SIZE_H */ diff --git a/include/text/txtfmt.h b/include/text/txtfmt.h index ab520886..dc087c06 100644 --- a/include/text/txtfmt.h +++ b/include/text/txtfmt.h @@ -104,9 +104,9 @@ 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); /* ----------------------------------------------------------------------- */ diff --git a/include/utils/array.h b/include/utils/array.h index eb5072b7..26ae20df 100644 --- a/include/utils/array.h +++ b/include/utils/array.h @@ -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/wuss/icon.h b/include/wuss/icon.h new file mode 100644 index 00000000..be9eaa18 --- /dev/null +++ b/include/wuss/icon.h @@ -0,0 +1,215 @@ +/* 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 "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 +{ + wuss_ICON_TYPE_LABEL = 0, /**< Static text drawn with the window manager's + * font. Not interactive: clicks fall through to + * the task as wuss_EVENT_MOUSE. */ + wuss_ICON_TYPE_BUTTON, /**< Bevelled rectangle with a centred text label + * and pressed-state visual feedback; clicks and + * hovers are delivered to the task as + * wuss_EVENT_ICON. */ + wuss_ICON_TYPE_PATTERN /**< Bounding box filled with a repeating two-colour + * 8x8 tile (spec.pattern) in fg/bg, phased to + * document space so it scrolls rigidly with + * content. Not interactive: clicks fall through + * to the task as wuss_EVENT_MOUSE. text is + * ignored. */ +} +wuss_icon_type_t; + +/** Icon appearance and behaviour flags, combinable with bitwise OR. */ +typedef enum wuss_icon_flags +{ + wuss_ICON_FLAGS_NONE = 0, + wuss_ICON_FLAGS_HIDDEN = 1 << 0, /**< Not drawn, not hit-tested. */ + wuss_ICON_FLAGS_DISABLED = 1 << 1 /**< Drawn greyed; clicks fall through to + * the task as wuss_EVENT_MOUSE rather + * than raising wuss_EVENT_ICON. */ +} +wuss_icon_flags_t; + +/** + * 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 +{ + box_t bbox; /**< Bounding box, virtual document space, + * inclusive-exclusive. */ + wuss_icon_type_t type; /**< Icon type. */ + const char *text; /**< NUL-terminated label; copied. NULL means "". */ + wuss_colour_t fg; /**< Text colour, as an index into the system + * palette. */ + wuss_colour_t bg; /**< Fill/bevel base colour, as an index into the + * system palette. A label may pass + * wuss_NO_BACKGROUND for text with no fill; a + * button or pattern icon must pass a real + * index. */ + screen_pattern_t pattern; /**< Tile for wuss_ICON_TYPE_PATTERN; ignored by + * other types. Zero (screen_PATTERN_SOLID) is a + * safe default for zero-initialised specs. */ + wuss_icon_flags_t flags; /**< Appearance/behaviour 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 or a button + * spec has no fill colour. + */ +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); + +/** + * 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); + +#endif /* WUSS_ICONS */ + +#ifdef __cplusplus +} +#endif + +#endif /* WUSS_ICON_H */ diff --git a/include/wuss/task.h b/include/wuss/task.h index b3cd3942..02b65a4a 100644 --- a/include/wuss/task.h +++ b/include/wuss/task.h @@ -3,8 +3,8 @@ /** * \file task.h * - * A Wuss task: the content delegate a window hands its drawing and input - * events to, and the events themselves. + * A Wuss task: the content delegate a window hands its drawing and input events + * to, and the events themselves. */ #ifndef WUSS_TASK_H @@ -21,6 +21,7 @@ extern "C" #include "framebuf/screen.h" #include "wuss/wuss.h" +#include "wuss/icon.h" /* ----------------------------------------------------------------------- */ @@ -30,8 +31,9 @@ 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_CLOSE, /**< Close button clicked; Wuss takes no action itself. */ wuss_EVENT_MOUSE, /**< Button down/up over the window's content. */ + wuss_EVENT_ICON, /**< A work-area button icon was clicked or hovered. */ wuss_EVENT_SCROLL, /**< Mouse wheel used over the window's content. */ wuss_EVENT_QUIT /**< Task shutting down, via wuss_task_stop. */ } @@ -77,7 +79,8 @@ typedef struct wuss_event * 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. */ + * 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; @@ -86,6 +89,20 @@ typedef struct wuss_event } 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. */ + 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 diff --git a/include/wuss/window.h b/include/wuss/window.h index 60762c05..efdc7464 100644 --- a/include/wuss/window.h +++ b/include/wuss/window.h @@ -39,26 +39,29 @@ extern "C" * 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] bg Content background, filled by Wuss before each redraw, - * or wuss_NO_BACKGROUND for the task to draw its own - * background (avoids a redundant fill behind an opaque - * task). Changeable later via - * wuss_window_set_background. - * \param[in] task Content delegate. Copied in. May be NULL for a window - * with no content handling. - * \param[in] doc Virtual document extent, for the scrollbars' sausage - * proportions; pass content's own width and height for a - * window with nothing to scroll. - * \param[out] window Newly created window. Becomes the topmost window. + * \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] bg Content background, filled by Wuss before each redraw, or + * wuss_NO_BACKGROUND for the task to draw its own + * background (avoids a redundant fill behind an opaque + * task). Changeable later via wuss_window_set_background. + * \param[in] task Content delegate. Copied in. May be NULL for a window + * with no content handling. + * \param[in] doc Virtual document extent, for the scrollbars' sausage + * proportions; pass content's own width and height for a + * window with nothing to scroll. Also the ceiling a + * resize-drag or toggle-size will grow the content area to. + * \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 bg is * out of range for the palette, or another appropriate result code. @@ -70,8 +73,48 @@ result_t wuss_window_create(wuss_t *wuss, wuss_colour_t bg, const wuss_task_t *task, size2d_t doc, + size2d_t min_doc, wuss_window_t **window); +/** + * Create a window, letting Wuss choose its position. + * + * As wuss_window_create, but instead of a content box you pass just the content + * size; Wuss places the window (furniture included) in the first free screen + * region, packed towards the top-left, tracking occupied area across calls so + * successive auto-placed windows tile rather than stack. When no region is + * large enough the window is cascaded from the previous placement, stepping by + * a titlebar height and wrapping at the screen edge. + * + * The chosen slot is returned to the pool when the window is closed, or when it + * is first moved or resized via wuss_window_move / wuss_window_resize (after + * which Wuss no longer tracks its position). A window dragged by its titlebar + * counts as moved. + * + * \param[in] wuss Window manager to create the window on. + * \param[in] size Requested content-area size. Width and height must both + * be positive. + * \param[in] 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] task Content delegate, 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_t *wuss, + size2d_t size, + const char *title, + wuss_window_flags_t flags, + wuss_colour_t bg, + const wuss_task_t *task, + size2d_t doc, + size2d_t min_doc, + wuss_window_t **window); + /** * Destroy a window. * @@ -132,10 +175,10 @@ void wuss_window_get_content_bounds(const wuss_window_t *window, * 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); diff --git a/include/wuss/wuss.h b/include/wuss/wuss.h index 9c0f1f7c..de8d74bc 100644 --- a/include/wuss/wuss.h +++ b/include/wuss/wuss.h @@ -28,6 +28,10 @@ extern "C" #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) /* ----------------------------------------------------------------------- */ @@ -37,15 +41,25 @@ typedef struct wuss wuss_t; /** A window. Full API is in window.h. */ typedef struct wuss_window wuss_window_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; + /** * 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; @@ -70,20 +84,21 @@ typedef int wuss_colour_t; /** 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 { 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 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. */ @@ -94,7 +109,13 @@ typedef struct wuss_palette } wuss_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 { /** Default: every furniture region drawn. */ @@ -155,18 +176,37 @@ typedef enum wuss_zorder } wuss_zorder_t; -/** Optional creation-time configuration. */ +/** + * Optional creation-time configuration. + * + * \note titlebar_height and palette are ignored when the library is built with + * WUSS_FURNITURE off; bevel is ignored when built with both + * WUSS_FURNITURE and WUSS_ICONS off. backdrop is always honoured. + */ typedef struct wuss_config { /** * Titlebar height in pixels, or 0 to derive from font metrics (or a built-in - * fallback if no font). + * fallback if no font). Ignored when WUSS_FURNITURE is off. */ int titlebar_height; - /** Furniture chrome colours. */ + /** Furniture chrome colours. Ignored when WUSS_FURNITURE is off. */ wuss_palette_t palette; + /** + * Bevelled work-area button edge shades, as indices into the system palette: + * light on the top/left edges, dark on the bottom/right (swapped when the + * button is pressed). Both default to the titlebar fill colour when config is + * NULL. Ignored when both WUSS_FURNITURE and WUSS_ICONS are off. + */ + struct + { + wuss_colour_t light; /**< Top/left bevel edge. */ + wuss_colour_t dark; /**< Bottom/right bevel edge. */ + } + bevel; + /** * Desktop background colour, painted behind windows on every redraw, or * wuss_NO_BACKGROUND to leave the background untouched (the caller must then @@ -193,12 +233,12 @@ wuss_config_t; * config's palette entries are out of range for the palette, or another * appropriate result code. */ -result_t wuss_create(screen_t *scr, - bmfont_t *font, - const colour_t *palette, - int npalette, - const wuss_config_t *config, - wuss_t **wuss); +result_t wuss_create(screen_t *scr, + bmfont_t *font, + const colour_t *palette, + int npalette, + const wuss_config_t *config, + wuss_t **wuss); /** * Destroy a window manager, and any windows still open on it. @@ -297,11 +337,11 @@ void wuss_get_dirty(const wuss_t *wuss, int index, box_t *out); * \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 diff --git a/libraries/databases/pickle/test/pickle-test.c b/libraries/databases/pickle/test/pickle-test.c index a72a268b..59ab3981 100644 --- a/libraries/databases/pickle/test/pickle-test.c +++ b/libraries/databases/pickle/test/pickle-test.c @@ -40,7 +40,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 +53,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 +326,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,7 +341,10 @@ 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; diff --git a/libraries/datastruct/cache/cache.c b/libraries/datastruct/cache/cache.c index 77a08af3..1a8c1cc1 100644 --- a/libraries/datastruct/cache/cache.c +++ b/libraries/datastruct/cache/cache.c @@ -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/hash/create.c b/libraries/datastruct/hash/create.c index f5db0315..70f23800 100644 --- a/libraries/datastruct/hash/create.c +++ b/libraries/datastruct/hash/create.c @@ -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/ntree/test/ntree-test.c b/libraries/datastruct/ntree/test/ntree-test.c index 63279894..8a9c8950 100644 --- a/libraries/datastruct/ntree/test/ntree-test.c +++ b/libraries/datastruct/ntree/test/ntree-test.c @@ -119,10 +119,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 +137,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/framebuf/bitmap/load.c b/libraries/framebuf/bitmap/load.c index 609a349e..b4b3fbaa 100644 --- a/libraries/framebuf/bitmap/load.c +++ b/libraries/framebuf/bitmap/load.c @@ -124,7 +124,7 @@ result_t bitmap_load_png(bitmap_t *bm, const char *filename) png_read_image(png_ptr, row_pointers); - bitmap_init(bm, (size2d_t) { pngwidth, pngheight }, + bitmap_init(bm, SIZE2D(pngwidth, pngheight), bm_fmt, bm_rowbytes, NULL, /* no palette */ diff --git a/libraries/framebuf/bmfont/bmfont.c b/libraries/framebuf/bmfont/bmfont.c index 5dd01fa4..00d3b87c 100644 --- a/libraries/framebuf/bmfont/bmfont.c +++ b/libraries/framebuf/bmfont/bmfont.c @@ -93,11 +93,11 @@ static int count_adw(unsigned char tab[256], pixelfmt_any_t adw_px) } /** 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]; @@ -198,11 +198,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]; @@ -515,8 +515,10 @@ 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) @@ -1246,7 +1248,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/test/bmfont-test.c b/libraries/framebuf/bmfont/test/bmfont-test.c index f46302dd..c9caed05 100644 --- a/libraries/framebuf/bmfont/test/bmfont-test.c +++ b/libraries/framebuf/bmfont/test/bmfont-test.c @@ -753,7 +753,7 @@ result_t bmfont_test_one_format(const char *resources, goto Failure; } - bitmap_init(&state.bm, (size2d_t) { 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/composite/test/composite-test.c b/libraries/framebuf/composite/test/composite-test.c index 58d9bb45..fdb0fe95 100644 --- a/libraries/framebuf/composite/test/composite-test.c +++ b/libraries/framebuf/composite/test/composite-test.c @@ -210,7 +210,7 @@ result_t composite_test(const char *resources) goto Failure; } - bitmap_init(&bigbitmap, (size2d_t) { 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/test/curve-test.c b/libraries/framebuf/curve/test/curve-test.c index 31e83507..5cca0837 100644 --- a/libraries/framebuf/curve/test/curve-test.c +++ b/libraries/framebuf/curve/test/curve-test.c @@ -804,7 +804,7 @@ result_t curve_test_one_format(const char *resources, goto Failure; } - bitmap_init(&state.bm, (size2d_t) { 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/screen/screen-copy-rect.c b/libraries/framebuf/screen/screen-copy-rect.c index 87bd4284..5ae5231e 100644 --- a/libraries/framebuf/screen/screen-copy-rect.c +++ b/libraries/framebuf/screen/screen-copy-rect.c @@ -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 int 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; @@ -57,7 +62,10 @@ static int screen_copy_rect_p4(screen_t *scr, const box_t *s, const box_t *d, return 1; } -int screen_copy_rect(screen_t *scr, const box_t *src, point_t dst, box_t *copied_dst) +int 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; diff --git a/libraries/framebuf/screen/screen-draw-ninepatch.c b/libraries/framebuf/screen/screen-draw-ninepatch.c new file mode 100644 index 00000000..b0b7a795 --- /dev/null +++ b/libraries/framebuf/screen/screen-draw-ninepatch.c @@ -0,0 +1,178 @@ +/* screen-draw-ninepatch.c -- 9-patch bitmap drawing */ + +#include + +#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_draw_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_draw_bitmap(scr, ox, oy, cell); + } + } +} + +/* ----------------------------------------------------------------------- */ + +void screen_draw_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; + + 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 = dst->x0 + pw; if (lx > midx) lx = midx; + rx = dst->x1 - pw; if (rx < midx) rx = midx; + ty = dst->y0 + ph; if (ty > midy) ty = midy; + by = dst->y1 - ph; if (by < midy) by = 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; /* 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; +} diff --git a/libraries/framebuf/screen/screen-draw.c b/libraries/framebuf/screen/screen-draw.c index afff1d28..26523abe 100644 --- a/libraries/framebuf/screen/screen-draw.c +++ b/libraries/framebuf/screen/screen-draw.c @@ -85,8 +85,10 @@ void screen_draw_pixel(screen_t *scr, int x, int y, colour_t colour) } 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; @@ -137,9 +139,10 @@ static void screen_blend_pixel(screen_t *scr, /* ----------------------------------------------------------------------- */ void screen_draw_rect(screen_t *scr, - int x, int y, - size2d_t size, - colour_t colour) + int x, + int y, + size2d_t size, + colour_t colour) { box_t clip_box; box_t rect_box; @@ -214,7 +217,7 @@ void screen_draw_rect(screen_t *scr, void screen_draw_square(screen_t *scr, int x, int y, int size, colour_t colour) { - screen_draw_rect(scr, x, y, (size2d_t) { size, size }, colour); + screen_draw_rect(scr, x, y, SIZE2D(size, size), colour); } /* ----------------------------------------------------------------------- */ @@ -364,8 +367,11 @@ static void screen_get_bounds(const screen_t *scr, box_t *bounds) } 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) { box_t clip_box; box_t bounds; @@ -428,8 +434,11 @@ 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; @@ -483,18 +492,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); @@ -506,18 +517,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); @@ -534,8 +547,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); @@ -546,7 +559,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); } } @@ -557,8 +570,11 @@ 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; diff --git a/libraries/framebuf/screen/screen-fill-pattern.c b/libraries/framebuf/screen/screen-fill-pattern.c new file mode 100644 index 00000000..d0613b6b --- /dev/null +++ b/libraries/framebuf/screen/screen-fill-pattern.c @@ -0,0 +1,138 @@ +/* screen-fill-pattern.c -- fill a box with a repeating 8x8 two-colour pattern */ + +#include +#include + +#include "framebuf/colour.h" +#include "framebuf/pixelfmt.h" +#include "geom/box.h" + +#include "framebuf/screen.h" + +/* One byte per row, MSB = leftmost pixel. */ +static const unsigned char patterns[screen_PATTERN__LIMIT][8] = +{ + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, /* SOLID */ + { 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55 }, /* GREY50 */ + { 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 */ +}; + +void screen_fill_pattern(screen_t *scr, + const box_t *box, + screen_pattern_t pattern, + int origin_x, + int origin_y, + colour_t fg, + colour_t bg) +{ + const unsigned char *tile; + box_t clip_box; + box_t draw_box; + pixelfmt_any_t fg_fmt, bg_fmt; + pixelfmt_any_t runs[8][8]; /* one expanded colour run per tile row */ + int xphase; + int row, col, x, y; + + assert(pattern >= 0 && pattern < screen_PATTERN__LIMIT); + if (pattern < 0 || pattern >= screen_PATTERN__LIMIT) + return; + + tile = patterns[pattern]; + + if (screen_get_clip(scr, &clip_box)) + return; /* invalid clipped screen */ + + if (box_intersection(&clip_box, box, &draw_box)) + return; /* nothing visible */ + + fg_fmt = colour_to_pixel(scr->palette, + (scr->format == pixelfmt_p4) ? 16 : 0, + fg, scr->format); + bg_fmt = colour_to_pixel(scr->palette, + (scr->format == pixelfmt_p4) ? 16 : 0, + bg, scr->format); + + /* The tile is 8x8 and repeats, so there are only eight distinct pixel rows. + * Expand each to a colour run once here, already phase-shifted for x, then + * the scanline loops just index runs[row][col] with no per-pixel bit test. */ + xphase = ((draw_box.x0 - origin_x) & 7); + for (row = 0; row < 8; row++) + { + unsigned char bits; + + bits = tile[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: + { + unsigned char *rowp; + const pixelfmt_any_t *run; + unsigned char *scrp; + int shift; + + rowp = (unsigned char *) scr->base + draw_box.y0 * scr->rowbytes; + for (y = draw_box.y0; y < draw_box.y1; y++) + { + run = runs[(y - origin_y) & 7]; + col = 0; + for (x = draw_box.x0; x < draw_box.x1; x++) + { + 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; + } + } + break; + + case 5: + { + unsigned char *rowp; + const pixelfmt_any_t *run; + pixelfmt_any32_t *scrp; + int w; + int n; + + rowp = (unsigned char *) scr->base + draw_box.y0 * scr->rowbytes; + for (y = draw_box.y0; y < draw_box.y1; y++) + { + run = runs[(y - origin_y) & 7]; + scrp = (pixelfmt_any32_t *) rowp + draw_box.x0; + w = draw_box.x1 - draw_box.x0; + + /* leading partial tile up to an 8-pixel boundary, then whole runs */ + col = 0; + while (w > 0) + { + n = 8 - col; + if (n > w) + n = w; + memcpy(scrp, run + col, (size_t) n * sizeof(*scrp)); + scrp += n; + w -= n; + col = 0; + } + rowp += scr->rowbytes; + } + } + break; + + default: + assert(!"Unimplemented pixel format"); + break; + } +} diff --git a/libraries/framebuf/screen/test/screen-test.c b/libraries/framebuf/screen/test/screen-test.c index 5b59876f..2d14a006 100644 --- a/libraries/framebuf/screen/test/screen-test.c +++ b/libraries/framebuf/screen/test/screen-test.c @@ -5,6 +5,7 @@ #include "base/result.h" #include "base/utils.h" +#include "framebuf/bitmap.h" #include "framebuf/colour.h" #include "framebuf/pixelfmt.h" #include "framebuf/screen.h" @@ -85,15 +86,17 @@ static void testscreen_init(testscreen_t *ts) for (i = 0; i < WIDTH * HEIGHT; i++) ts->pixels[i] = BACKGROUND; - screen_init(&ts->scr, (size2d_t) { WIDTH, HEIGHT }, + 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) +static void draw(screen_t *scr, + linekind_t kind, + const linetest_t *line, + colour_t colour) { switch (kind) { @@ -102,9 +105,10 @@ static void draw(screen_t *scr, linekind_t kind, const linetest_t *line, break; case linekind_WU_FIX8: + /* multiply, not INT_TO_FIX8: coords may be negative (left shift is UB). */ screen_draw_line_wu_fix8(scr, - INT_TO_FIX8(line->x0), INT_TO_FIX8(line->y0), - INT_TO_FIX8(line->x1), INT_TO_FIX8(line->y1), + line->x0 * FIX8_ONE, line->y0 * FIX8_ONE, + line->x1 * FIX8_ONE, line->y1 * FIX8_ONE, colour); break; @@ -202,6 +206,249 @@ static result_t test_clipping_still_happens(void) /* ----------------------------------------------------------------------- */ +/* 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_draw_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_draw_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_draw_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_draw_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_draw_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); + screen_fill_pattern(&ts.scr, &box, screen_PATTERN_GREY50, 0, 0, + colour_rgb(255, 0, 0), colour_rgb(0, 0, 255)); + + 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); + screen_fill_pattern(&ts.scr, &box, screen_PATTERN_GREY50, 1, 0, + colour_rgb(255, 0, 0), colour_rgb(0, 0, 255)); + 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 }; + screen_fill_pattern(&ts.scr, &box, screen_PATTERN_SOLID, 0, 0, + colour_rgb(255, 0, 0), colour_rgb(0, 0, 255)); + 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; +} + +/* ----------------------------------------------------------------------- */ + result_t screen_test(const char *resources) { typedef result_t (*screentestfn)(void); @@ -209,7 +456,10 @@ result_t screen_test(const char *resources) static const screentestfn tests[] = { test_clip_invariance, - test_clipping_still_happens + test_clipping_still_happens, + test_wu_fix8_extreme_coords, + test_ninepatch, + test_fill_pattern }; result_t rc; diff --git a/libraries/geom/packer/impl.h b/libraries/geom/packer/impl.h index 2b3312a5..f8beff00 100644 --- a/libraries/geom/packer/impl.h +++ b/libraries/geom/packer/impl.h @@ -34,6 +34,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..a542c5e2 100644 --- a/libraries/geom/packer/packer.c +++ b/libraries/geom/packer/packer.c @@ -46,6 +46,8 @@ packer_t *packer_create(const box_t *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; @@ -408,6 +410,26 @@ result_t packer_place_at(packer_t *packer, const box_t *area) return remove_area(packer, &b); } +result_t packer_release(packer_t *packer, const box_t *area) +{ + box_t b; + + (void) box_intersection(&packer->margins, area, &b); + + if (box_is_empty(&b)) + return result_PACKER_EMPTY; + + /* ponytail: no coalescing with neighbouring free areas -- a released + * fragment is usable on its own, which is all whole-box placement needs; + * tile-style reuse spanning two released areas would need a merge pass */ + return add_area(packer, &b); +} + +void packer_set_gutter(packer_t *packer, int gutter) +{ + packer->gutter = (gutter > 0) ? gutter : 0; +} + result_t packer_place_by(packer_t *packer, packer_loc_t loc, int w, @@ -416,6 +438,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 +447,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 +470,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 +500,23 @@ 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; diff --git a/libraries/geom/packer/test/packer-test.c b/libraries/geom/packer/test/packer-test.c index ba744b0b..523c4f50 100644 --- a/libraries/geom/packer/test/packer-test.c +++ b/libraries/geom/packer/test/packer-test.c @@ -399,6 +399,120 @@ 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; +} + result_t packer_test(const char *resources) { result_t err; @@ -413,6 +527,10 @@ result_t packer_test(const char *resources) if (err) goto failure; + err = test3(); + if (err) + goto failure; + return result_TEST_PASSED; diff --git a/libraries/io/stream/stream-packbitscomp.c b/libraries/io/stream/stream-packbitscomp.c index e8dab061..a9630c43 100644 --- a/libraries/io/stream/stream-packbitscomp.c +++ b/libraries/io/stream/stream-packbitscomp.c @@ -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/text/txtfmt/get-line.c b/libraries/text/txtfmt/get-line.c index 90023946..4be628fb 100644 --- a/libraries/text/txtfmt/get-line.c +++ b/libraries/text/txtfmt/get-line.c @@ -5,9 +5,9 @@ #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/utils/array/grow.c b/libraries/utils/array/grow.c index 267f81d3..42952902 100644 --- a/libraries/utils/array/grow.c +++ b/libraries/utils/array/grow.c @@ -11,12 +11,12 @@ #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; diff --git a/libraries/wuss/create.c b/libraries/wuss/create.c index 82eaca5c..086dd587 100644 --- a/libraries/wuss/create.c +++ b/libraries/wuss/create.c @@ -13,18 +13,43 @@ #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) +#if defined(WUSS_FURNITURE) || defined(WUSS_ICONS) +/* Range-check the bevel colours and (when set) the backdrop against the + * palette. Shared by the furniture and icons-only paths so the accepted + * range, the error code and the freed-pointer set stay in one place. */ +static result_t validate_bevel_backdrop(const wuss_t *w, + wuss_colour_t blight, + wuss_colour_t bdark) +{ + if (blight < 0 || blight >= w->npalette || + bdark < 0 || bdark >= w->npalette || + (w->backdrop != wuss_NO_BACKGROUND && + (w->backdrop < 0 || w->backdrop >= w->npalette))) + return result_WUSS_BAD_COLOUR; + + return result_OK; +} +#endif + +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; +#ifdef WUSS_FURNITURE wuss_palette_t pal; wuss_colour_t bg, fg; +#endif +#if defined(WUSS_FURNITURE) || defined(WUSS_ICONS) + wuss_colour_t blight, bdark; +#endif +#ifdef WUSS_FURNITURE int font_height; int font_width; +#endif assert(scr != NULL); assert(wuss != NULL); @@ -62,9 +87,12 @@ result_t wuss_create(screen_t *scr, w->npalette = palette_PICO8__LENGTH; } +#ifdef WUSS_FURNITURE if (config != NULL) { pal = config->palette; + blight = config->bevel.light; + bdark = config->bevel.dark; w->backdrop = config->backdrop; } else @@ -91,6 +119,9 @@ result_t wuss_create(screen_t *scr, pal.scroll.arrows = bg; pal.scroll.wells = bg; pal.scroll.sausages = fg; + + blight = 0; + bdark = 0; } if (pal.title.bg < 0 || pal.title.bg >= w->npalette || @@ -102,8 +133,7 @@ result_t wuss_create(screen_t *scr, pal.scroll.arrows < 0 || pal.scroll.arrows >= w->npalette || pal.scroll.wells < 0 || pal.scroll.wells >= w->npalette || pal.scroll.sausages < 0 || pal.scroll.sausages >= w->npalette || - (w->backdrop != wuss_NO_BACKGROUND && - (w->backdrop < 0 || w->backdrop >= w->npalette))) + validate_bevel_backdrop(w, blight, bdark) != result_OK) { free(w->palette); free(w); @@ -111,6 +141,8 @@ result_t wuss_create(screen_t *scr, } w->furniture_colours = pal; + w->bevel_light = blight; + w->bevel_dark = bdark; if (config != NULL && config->titlebar_height > 0) { @@ -126,15 +158,56 @@ result_t wuss_create(screen_t *scr, { w->titlebar_height = WUSS_DEFAULT_TITLEBAR_HEIGHT; } +#else /* !WUSS_FURNITURE */ + w->backdrop = (config != NULL) ? config->backdrop : wuss_NO_BACKGROUND; + +#ifdef WUSS_ICONS + if (config != NULL) + { + blight = config->bevel.light; + bdark = config->bevel.dark; + } + else + { + blight = 0; + bdark = 0; + } + if (validate_bevel_backdrop(w, blight, bdark) != result_OK) + { + free(w->palette); + free(w); + return result_WUSS_BAD_COLOUR; + } + w->bevel_light = blight; + w->bevel_dark = bdark; +#else + if (w->backdrop != wuss_NO_BACKGROUND && + (w->backdrop < 0 || w->backdrop >= w->npalette)) + { + free(w->palette); + free(w); + return result_WUSS_BAD_COLOUR; + } +#endif +#endif /* WUSS_FURNITURE */ w->scr = scr; w->font = font; +#ifdef WUSS_FURNITURE w->furniture.dragging = NULL; w->furniture.drag.x = 0; w->furniture.drag.y = 0; +#endif +#ifdef WUSS_ICONS + w->pressed_icon = NULL; +#endif w->ndirty = 0; + w->layout = NULL; + w->cascade.x = 0; + w->cascade.y = 0; + list_init(&w->z_order); *wuss = w; diff --git a/libraries/wuss/destroy.c b/libraries/wuss/destroy.c index c546af82..372fd517 100644 --- a/libraries/wuss/destroy.c +++ b/libraries/wuss/destroy.c @@ -21,10 +21,14 @@ void wuss_destroy(wuss_t *doomed) list_t *next; next = e->next; +#ifdef WUSS_ICONS + wuss__icons_free((wuss_window_t *) e); +#endif free(e); e = next; } + packer_destroy(doomed->layout); free(doomed->palette); free(doomed); } diff --git a/libraries/wuss/furniture.h b/libraries/wuss/furniture.h index ddc707b3..4b5b7bd8 100644 --- a/libraries/wuss/furniture.h +++ b/libraries/wuss/furniture.h @@ -46,9 +46,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 +81,8 @@ 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); /* geometry: titlebar icons */ void wuss__back_box(const wuss_window_t *window, box_t *out); @@ -99,8 +105,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..deab1238 100644 --- a/libraries/wuss/furniture/back-box.c +++ b/libraries/wuss/furniture/back-box.c @@ -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..424af7c4 100644 --- a/libraries/wuss/furniture/close-box.c +++ b/libraries/wuss/furniture/close-box.c @@ -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..72f379eb 100644 --- a/libraries/wuss/furniture/content-box.c +++ b/libraries/wuss/furniture/content-box.c @@ -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 19995ec4..9a92a3a3 100644 --- a/libraries/wuss/furniture/drag-resize.c +++ b/libraries/wuss/furniture/drag-resize.c @@ -6,15 +6,20 @@ 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.w); - height = CLAMP(height, WUSS_MIN_CONTENT, window->doc.h); + /* 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, (size2d_t) { width, height }); + wuss_window_resize(window, SIZE2D(width, height)); } diff --git a/libraries/wuss/furniture/draw.c b/libraries/wuss/furniture/draw.c index bc4dc31b..a30cb44d 100644 --- a/libraries/wuss/furniture/draw.c +++ b/libraries/wuss/furniture/draw.c @@ -207,7 +207,7 @@ void wuss__furniture_draw(wuss_t *wuss, /* 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__icon_size(window), &carve); + wuss__furniture_carve_for(window->flags, wuss__button_size(window), &carve); if (carve.x > 0) { @@ -248,9 +248,9 @@ void wuss__furniture_draw(wuss_t *wuss, border = wuss->palette[wuss->furniture_colours.title.bg]; /* no dedicated outline class; matches titlebar chrome */ wuss->scr->clip = visible_clipped; - screen_draw_rect(wuss->scr, window->visible.x0, window->visible.y0, (size2d_t) { width, 1 }, border); - screen_draw_rect(wuss->scr, window->visible.x0, window->visible.y1 - 1, (size2d_t) { width, 1 }, border); - screen_draw_rect(wuss->scr, window->visible.x0, window->visible.y0, (size2d_t) { 1, height }, border); - screen_draw_rect(wuss->scr, window->visible.x1 - 1, window->visible.y0, (size2d_t) { 1, height }, border); + screen_draw_rect(wuss->scr, window->visible.x0, window->visible.y0, SIZE2D(width, 1), border); + screen_draw_rect(wuss->scr, window->visible.x0, window->visible.y1 - 1, SIZE2D(width, 1), border); + screen_draw_rect(wuss->scr, window->visible.x0, window->visible.y0, SIZE2D(1, height), border); + screen_draw_rect(wuss->scr, window->visible.x1 - 1, window->visible.y0, SIZE2D(1, height), border); } } diff --git a/libraries/wuss/furniture/hscroll-box.c b/libraries/wuss/furniture/hscroll-box.c index 763071e7..31bbdc9b 100644 --- a/libraries/wuss/furniture/hscroll-box.c +++ b/libraries/wuss/furniture/hscroll-box.c @@ -7,7 +7,7 @@ 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); + size = wuss__button_size(window); out->y1 = window->visible.y1 - outline_px; out->y0 = out->y1 - size; @@ -22,7 +22,7 @@ void wuss__hscroll_left_box(const wuss_window_t *window, box_t *out) int size; hscroll_row(window, &row); - size = wuss__icon_size(window); + size = wuss__button_size(window); out->y0 = row.y0; out->y1 = row.y1; @@ -36,7 +36,7 @@ void wuss__hscroll_right_box(const wuss_window_t *window, box_t *out) int size; hscroll_row(window, &row); - size = wuss__icon_size(window); + size = wuss__button_size(window); out->y0 = row.y0; out->y1 = row.y1; @@ -50,7 +50,7 @@ void wuss__hscroll_well_box(const wuss_window_t *window, box_t *out) int size; hscroll_row(window, &row); - size = wuss__icon_size(window); + size = wuss__button_size(window); out->y0 = row.y0; out->y1 = row.y1; diff --git a/libraries/wuss/furniture/invalidate.c b/libraries/wuss/furniture/invalidate.c index bd044e3f..dc2e3ba7 100644 --- a/libraries/wuss/furniture/invalidate.c +++ b/libraries/wuss/furniture/invalidate.c @@ -16,7 +16,7 @@ void wuss__furniture_invalidate_for(wuss_window_t *window, const box_t *visible) /* 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__icon_size(window), &carve); + wuss__furniture_carve_for(window->flags, wuss__button_size(window), &carve); if (!(window->flags & wuss_WINDOW_NO_TITLEBAR)) { diff --git a/libraries/wuss/furniture/resize-box.c b/libraries/wuss/furniture/resize-box.c index 0a9707b9..143ab98c 100644 --- a/libraries/wuss/furniture/resize-box.c +++ b/libraries/wuss/furniture/resize-box.c @@ -7,7 +7,7 @@ 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 9b9edf54..6ac7a498 100644 --- a/libraries/wuss/furniture/scroll-action.c +++ b/libraries/wuss/furniture/scroll-action.c @@ -2,44 +2,6 @@ #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); - 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); -} - void wuss__furniture_drag_sausage(wuss_window_t *window, int delta_px, int scroll_start, @@ -78,7 +40,7 @@ void wuss__furniture_drag_sausage(wuss_window_t *window, new_scroll = 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/toggle-action.c b/libraries/wuss/furniture/toggle-action.c index 4203f778..6a330d6f 100644 --- a/libraries/wuss/furniture/toggle-action.c +++ b/libraries/wuss/furniture/toggle-action.c @@ -16,12 +16,13 @@ void wuss__furniture_toggle_size(wuss_window_t *window) } else { - int outline_px, titlebar_height, available_width, available_height, width, height; - point_t carve; + int outline_px, titlebar_height, available_width, available_height, width, height; + point_t carve; + size2d_t min; outline_px = wuss__outline_px(window); titlebar_height = wuss__titlebar_height(window); - wuss__furniture_carve_for(window->flags, wuss__icon_size(window), &carve); + wuss__furniture_carve_for(window->flags, wuss__button_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 @@ -38,9 +39,10 @@ void wuss__furniture_toggle_size(wuss_window_t *window) * x0/y0 (an inverted box), which is never hit-testable again * (box_contains_point can't match x1doc.w, available_width); height = MIN(window->doc.h, available_height); @@ -83,7 +85,7 @@ void wuss__furniture_toggle_size(wuss_window_t *window) if (!(window->flags & wuss_WINDOW_NO_RESIZE_BLIT) && window->wuss->z_order.next == &window->link && screen_copy_rect(window->wuss->scr, &before, - (point_t) { before.x0, before.y0 }, &copied)) + POINT(before.x0, before.y0), &copied)) { /* Topmost, and the screen format supports the blit: the window's * top-left never moves for a toggle, so re-blitting "before" onto diff --git a/libraries/wuss/furniture/toggle-box.c b/libraries/wuss/furniture/toggle-box.c index 8f7ee7d3..4d9cf3f4 100644 --- a/libraries/wuss/furniture/toggle-box.c +++ b/libraries/wuss/furniture/toggle-box.c @@ -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 index 58e84f12..ba873db6 100644 --- a/libraries/wuss/furniture/vscroll-box.c +++ b/libraries/wuss/furniture/vscroll-box.c @@ -8,7 +8,7 @@ static void vscroll_column(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; @@ -24,7 +24,7 @@ void wuss__vscroll_up_box(const wuss_window_t *window, box_t *out) int size; vscroll_column(window, &column); - size = wuss__icon_size(window); + size = wuss__button_size(window); out->x0 = column.x0; out->x1 = column.x1; @@ -38,7 +38,7 @@ void wuss__vscroll_down_box(const wuss_window_t *window, box_t *out) int size; vscroll_column(window, &column); - size = wuss__icon_size(window); + size = wuss__button_size(window); out->x0 = column.x0; out->x1 = column.x1; @@ -52,7 +52,7 @@ void wuss__vscroll_well_box(const wuss_window_t *window, box_t *out) int size; vscroll_column(window, &column); - size = wuss__icon_size(window); + size = wuss__button_size(window); out->x0 = column.x0; out->x1 = column.x1; diff --git a/libraries/wuss/icon.h b/libraries/wuss/icon.h new file mode 100644 index 00000000..13c2c67f --- /dev/null +++ b/libraries/wuss/icon.h @@ -0,0 +1,57 @@ +/* icon.h -- wuss - 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" + +struct wuss_icon +{ + wuss_window_t *window; /* owner; back-pointer for invalidate/get_window */ + box_t bbox; /* virtual document space */ + wuss_icon_type_t type; + char *text; /* owned; never NULL ("" instead) */ + wuss_colour_t fg; + wuss_colour_t bg; + screen_pattern_t pattern; /* wuss_ICON_TYPE_PATTERN tile; 0 otherwise */ + wuss_icon_flags_t flags; + int pressed; /* button: 1 while held with the pointer inside */ +}; + +/* 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); + +/* 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..5da88935 --- /dev/null +++ b/libraries/wuss/icon/create-array.c @@ -0,0 +1,43 @@ +/* create-array.c -- wuss - create several work-area icons at once */ + +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "../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..86686b56 --- /dev/null +++ b/libraries/wuss/icon/create.c @@ -0,0 +1,92 @@ +/* create.c -- wuss - create a work-area icon */ + +#include +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "../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; + const char *src; + size_t len; + int newcap; + + assert(window != NULL); + assert(spec != NULL); + + w = window->wuss; + + if (spec->type != wuss_ICON_TYPE_LABEL && + spec->type != wuss_ICON_TYPE_BUTTON && + spec->type != wuss_ICON_TYPE_PATTERN) + return result_WUSS_BAD_ICON; + + if ((spec->type == wuss_ICON_TYPE_BUTTON || + spec->type == wuss_ICON_TYPE_PATTERN) && + spec->bg == wuss_NO_BACKGROUND) + 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 (spec->fg < 0 || spec->fg >= w->npalette) + return result_WUSS_BAD_COLOUR; + + if (spec->bg != wuss_NO_BACKGROUND && + (spec->bg < 0 || spec->bg >= w->npalette)) + return result_WUSS_BAD_COLOUR; + + if (window->nicons == window->cap_icons) + { + newcap = (window->cap_icons == 0) ? 4 : window->cap_icons * 2; + grown = realloc(window->icons, newcap * sizeof(*window->icons)); + if (grown == NULL) + return result_OOM; + window->icons = grown; + window->cap_icons = newcap; + } + + it = malloc(sizeof(*it)); + if (it == NULL) + return result_OOM; + + src = (spec->text != NULL) ? spec->text : ""; + len = strlen(src); + it->text = malloc(len + 1); + if (it->text == NULL) + { + free(it); + return result_OOM; + } + memcpy(it->text, src, len + 1); + + it->window = window; + it->bbox = spec->bbox; + it->type = spec->type; + it->fg = spec->fg; + it->bg = spec->bg; + it->pattern = (spec->type == wuss_ICON_TYPE_PATTERN) ? spec->pattern + : screen_PATTERN_SOLID; + it->flags = spec->flags; + it->pressed = 0; + + 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..5f5932b7 --- /dev/null +++ b/libraries/wuss/icon/delete.c @@ -0,0 +1,37 @@ +/* delete.c -- wuss - destroy a work-area icon */ + +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "../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; + + wuss__icon_invalidate(icon); + + for (i = 0; i < window->nicons; i++) + { + if (window->icons[i] == icon) + { + window->icons[i] = window->icons[--window->nicons]; + break; + } + } + + free(icon->text); + free(icon); +} diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c new file mode 100644 index 00000000..2a06be2b --- /dev/null +++ b/libraries/wuss/icon/draw.c @@ -0,0 +1,157 @@ +/* draw.c -- wuss - 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 "../impl.h" + +/* ----------------------------------------------------------------------- */ + +static void icon_bevel(screen_t *scr, + const box_t *b, + colour_t fill, + colour_t light, + colour_t dark) +{ + screen_draw_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); +} + +/* ----------------------------------------------------------------------- */ + +void wuss__icon_draw(wuss_t *wuss, + const wuss_icon_t *icon, + const box_t *content, + point_t scroll) +{ + screen_t *scr; + box_t b; + colour_t fg; + int font_width, font_height; + int have_font; + + if (icon->flags & wuss_ICON_FLAGS_HIDDEN) + return; + + scr = wuss->scr; + + wuss__icon_box_to_screen(content, scroll, &icon->bbox, &b); + + if (b.x1 <= b.x0 || b.y1 <= b.y0) + return; + + fg = wuss->palette[icon->fg]; + + have_font = (wuss->font != NULL && icon->text[0] != '\0'); + if (have_font) + bmfont_get_info(wuss->font, &font_width, &font_height); + else + font_width = font_height = 0; + NOT_USED(font_width); + + switch (icon->type) + { + case wuss_ICON_TYPE_PATTERN: + { + colour_t pat_fg; + + /* disabled: fold the pattern into its own ground so it reads as greyed, + * mirroring the button's fg-swap */ + pat_fg = (icon->flags & wuss_ICON_FLAGS_DISABLED) + ? wuss->palette[icon->bg] + : fg; + + screen_fill_pattern(scr, &b, icon->pattern, + content->x0 - scroll.x, content->y0 - scroll.y, + pat_fg, wuss->palette[icon->bg]); + } + break; + + case wuss_ICON_TYPE_LABEL: + { + colour_t bg; + + if (icon->bg != wuss_NO_BACKGROUND) + { + bg = wuss->palette[icon->bg]; + screen_draw_rect(scr, b.x0, b.y0, + SIZE2D(b.x1 - b.x0, b.y1 - b.y0), bg); + } + else if (icon->window->bg != wuss_NO_BACKGROUND) + { + bg = wuss->palette[icon->window->bg]; + } + else + { + bg = fg; /* bmfont needs a blend colour; nothing better to offer */ + } + + if (have_font) + { + point_t pos; + + pos.x = b.x0 + 1; + pos.y = b.y0 + (b.y1 - b.y0 - font_height) / 2; + bmfont_draw(wuss->font, scr, icon->text, (int) strlen(icon->text), + fg, bg, &pos, NULL); + } + } + break; + + case wuss_ICON_TYPE_BUTTON: + { + colour_t light, dark, base; + int pressed; + + base = wuss->palette[icon->bg]; + light = wuss->palette[wuss->bevel_light]; + dark = wuss->palette[wuss->bevel_dark]; + pressed = icon->pressed; + + if (icon->flags & wuss_ICON_FLAGS_DISABLED) + fg = dark; /* greyed: label sinks toward the dark bevel shade */ + + if (pressed) + icon_bevel(scr, &b, base, dark, light); + else + icon_bevel(scr, &b, base, light, dark); + + if (have_font) + { + point_t pos; + int interior_w, split_point; + bmfont_width_t width; + + interior_w = (b.x1 - b.x0) - 2; + if (interior_w < 1) + interior_w = 1; + + bmfont_measure(wuss->font, icon->text, (int) strlen(icon->text), + interior_w, &split_point, &width); + + pos.x = b.x0 + ((b.x1 - b.x0) - width) / 2; + pos.y = b.y0 + (b.y1 - b.y0 - font_height) / 2; + if (pressed) + { + pos.x += 1; + pos.y += 1; + } + + bmfont_draw(wuss->font, scr, icon->text, (int) strlen(icon->text), + fg, base, &pos, NULL); + } + } + break; + } +} diff --git a/libraries/wuss/icon/free.c b/libraries/wuss/icon/free.c new file mode 100644 index 00000000..d7cbac7e --- /dev/null +++ b/libraries/wuss/icon/free.c @@ -0,0 +1,25 @@ +/* free.c -- wuss - free a window's whole icon store */ + +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "../impl.h" + +void wuss__icons_free(wuss_window_t *window) +{ + int i; + + for (i = 0; i < window->nicons; i++) + { + free(window->icons[i]->text); + free(window->icons[i]); + } + + free(window->icons); + window->icons = NULL; + window->nicons = 0; + window->cap_icons = 0; +} diff --git a/libraries/wuss/icon/get-bbox.c b/libraries/wuss/icon/get-bbox.c new file mode 100644 index 00000000..55bdbd8d --- /dev/null +++ b/libraries/wuss/icon/get-bbox.c @@ -0,0 +1,8 @@ +/* get-bbox.c -- wuss - read a work-area icon's bounding box */ + +#include "../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-text.c b/libraries/wuss/icon/get-text.c new file mode 100644 index 00000000..2978cdb7 --- /dev/null +++ b/libraries/wuss/icon/get-text.c @@ -0,0 +1,8 @@ +/* get-text.c -- wuss - read a work-area icon's label */ + +#include "../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..84b34091 --- /dev/null +++ b/libraries/wuss/icon/get-type.c @@ -0,0 +1,8 @@ +/* get-type.c -- wuss - read a work-area icon's type */ + +#include "../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..9921af6d --- /dev/null +++ b/libraries/wuss/icon/get-window.c @@ -0,0 +1,8 @@ +/* get-window.c -- wuss - read a work-area icon's owning window */ + +#include "../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..d7579e21 --- /dev/null +++ b/libraries/wuss/icon/hit-test.c @@ -0,0 +1,28 @@ +/* hit-test.c -- wuss - work-area icon hit testing */ + +#include "geom/box.h" + +#include "../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]; + + if (it->type != wuss_ICON_TYPE_BUTTON) + 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..91c9e47f --- /dev/null +++ b/libraries/wuss/icon/invalidate.c @@ -0,0 +1,10 @@ +/* invalidate.c -- wuss - mark a work-area icon's bbox dirty */ + +#include "../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/screen-box.c b/libraries/wuss/icon/screen-box.c new file mode 100644 index 00000000..59e4e4e0 --- /dev/null +++ b/libraries/wuss/icon/screen-box.c @@ -0,0 +1,21 @@ +/* screen-box.c -- wuss - work-area icon bbox to screen space */ + +#include "geom/box.h" +#include "geom/point.h" + +#include "../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) +{ + out->x0 = content->x0 - scroll.x + bbox->x0; + out->y0 = content->y0 - scroll.y + bbox->y0; + out->x1 = content->x0 - scroll.x + bbox->x1; + out->y1 = content->y0 - scroll.y + bbox->y1; +} diff --git a/libraries/wuss/icon/set-hidden.c b/libraries/wuss/icon/set-hidden.c new file mode 100644 index 00000000..c76e35a8 --- /dev/null +++ b/libraries/wuss/icon/set-hidden.c @@ -0,0 +1,13 @@ +/* set-hidden.c -- wuss - show or hide a work-area icon */ + +#include "../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-text.c b/libraries/wuss/icon/set-text.c new file mode 100644 index 00000000..3a5eca25 --- /dev/null +++ b/libraries/wuss/icon/set-text.c @@ -0,0 +1,32 @@ +/* set-text.c -- wuss - replace a work-area icon's label */ + +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "../impl.h" + +result_t wuss_icon_set_text(wuss_icon_t *icon, const char *text) +{ + const char *src; + char *dup; + size_t len; + + src = (text != NULL) ? text : ""; + len = strlen(src); + + dup = malloc(len + 1); + if (dup == NULL) + return result_OOM; + memcpy(dup, src, len + 1); + + free(icon->text); + icon->text = dup; + + wuss__icon_invalidate(icon); + + return result_OK; +} diff --git a/libraries/wuss/impl.h b/libraries/wuss/impl.h index 18425751..fa8f52da 100644 --- a/libraries/wuss/impl.h +++ b/libraries/wuss/impl.h @@ -3,8 +3,11 @@ #ifndef IMPL_H #define IMPL_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" @@ -12,7 +15,12 @@ #include "wuss/wuss.h" #include "wuss/window.h" +#ifdef WUSS_FURNITURE #include "furniture.h" +#endif +#ifdef WUSS_ICONS +#include "icon.h" +#endif #define WUSS_TITLE_MAX 63 #define WUSS_DEFAULT_TITLEBAR_HEIGHT 20 @@ -24,7 +32,9 @@ * 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_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 */ #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 */ @@ -45,13 +55,34 @@ struct wuss bmfont_t *font; /* nullable, not owned */ colour_t *palette; /* owned */ int npalette; +#ifdef WUSS_FURNITURE wuss_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 */ +#endif wuss_colour_t backdrop; /* wuss_NO_BACKGROUND for none */ +#ifdef WUSS_FURNITURE int titlebar_height; +#endif list_t z_order; /* anchor; head = topmost window */ +#ifdef WUSS_FURNITURE struct wuss__furniture furniture; +#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 */ +#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 */ +#endif }; struct wuss_window @@ -66,15 +97,45 @@ struct wuss_window 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 }; wuss_window_t *wuss__window_at(wuss_t *wuss, point_t p); + +/* 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, @@ -116,7 +177,32 @@ 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, +/* 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; @@ -154,22 +240,23 @@ static inline int wuss__outline_px(const wuss_window_t *window) * 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__icon_size_for(const wuss_t *wuss, wuss_window_flags_t flags) +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_ICON_INSET; + size = wuss__titlebar_height_for(wuss, flags) - 2 * WUSS_BUTTON_INSET; if (size > 0) return size; - size = wuss->titlebar_height - 2 * WUSS_ICON_INSET; + size = wuss->titlebar_height - 2 * WUSS_BUTTON_INSET; - return (size > 0) ? size : WUSS_DEFAULT_TITLEBAR_HEIGHT - 2 * WUSS_ICON_INSET; + return (size > 0) ? size : WUSS_DEFAULT_TITLEBAR_HEIGHT - 2 * WUSS_BUTTON_INSET; } -static inline int wuss__icon_size(const wuss_window_t *window) +static inline int wuss__button_size(const wuss_window_t *window) { - return wuss__icon_size_for(window->wuss, window->flags); + return wuss__button_size_for(window->wuss, window->flags); } /* how much of a content box's width/height is furniture (scrollbars, the @@ -178,18 +265,18 @@ static inline int wuss__icon_size(const wuss_window_t *window) * 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, + int button_size, point_t *carve) { - carve->x = (flags & wuss_WINDOW_NO_VSCROLL) ? 0 : icon_size; - carve->y = (flags & wuss_WINDOW_NO_HSCROLL) ? 0 : icon_size; + 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 = icon_size; - carve->y = icon_size; + carve->x = button_size; + carve->y = button_size; } /* where furniture abuts the content area, a rule divides the two */ @@ -198,5 +285,56 @@ static inline void wuss__furniture_carve_for(wuss_window_flags_t flags, 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 */ #endif /* IMPL_H */ diff --git a/libraries/wuss/mouse-click.c b/libraries/wuss/mouse-click.c index 88ff9c58..8028deb7 100644 --- a/libraries/wuss/mouse-click.c +++ b/libraries/wuss/mouse-click.c @@ -2,20 +2,37 @@ #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) +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; +#ifdef WUSS_ICONS + /* Release a held button icon on any MOUSE_UP, before the hit-test picks a + * window: the up may land on a window that opened over the icon's owner on + * MOUSE_DOWN, so wuss__window_at would never reach the pressed icon. */ + if (action == wuss_MOUSE_UP && wuss->pressed_icon != NULL) + { + wuss_icon_t *pressed = wuss->pressed_icon; + + wuss->pressed_icon = NULL; + if (pressed->pressed) + { + pressed->pressed = 0; + wuss__icon_invalidate(pressed); + } + } +#endif + +#ifdef WUSS_FURNITURE if (action == wuss_MOUSE_UP && wuss->furniture.dragging != NULL) { win = wuss->furniture.dragging; @@ -26,6 +43,7 @@ result_t wuss_mouse_click(wuss_t *wuss, return result_OK; } +#endif win = wuss__window_at(wuss, p); if (hit != NULL) @@ -34,110 +52,175 @@ result_t wuss_mouse_click(wuss_t *wuss, 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) +#ifdef WUSS_FURNITURE { - if (win->task.handle == NULL) - return result_OK; - - event.kind = wuss_EVENT_CLOSE; - return win->task.handle(win, &event, win->task.task_data); - } + wuss_furniture_region_t region; - 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; - } + region = wuss__furniture_hit_test(win, POINT(x, y)); - 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) + if (region == wuss_FURNITURE_CLOSE && + 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; - } + if (win->task.handle == NULL) + return result_OK; + + event.kind = wuss_EVENT_CLOSE; + return win->task.handle(win, &event, win->task.task_data); } - return result_OK; - } - if (region == wuss_FURNITURE_CLOSE || region == wuss_FURNITURE_TITLE) - { - if (action == wuss_MOUSE_DOWN) + if (region == wuss_FURNITURE_BACK && action == wuss_MOUSE_DOWN) { - box_t content; - - if (button == wuss_BUTTON_SELECT) + 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; + } - 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; + 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_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; } - return result_OK; - } - if (region == wuss_FURNITURE_RESIZE || - region == wuss_FURNITURE_VSCROLL_WELL || - region == wuss_FURNITURE_HSCROLL_WELL) - { - if (action == wuss_MOUSE_DOWN) + if (region == wuss_FURNITURE_CLOSE || region == wuss_FURNITURE_TITLE) { - point_t scroll; + if (action == wuss_MOUSE_DOWN) + { + box_t content; - if (button == wuss_BUTTON_SELECT) - wuss_window_restack(win, wuss_ZORDER_FRONT); + if (button & wuss_BUTTON_SELECT) + wuss_window_restack(win, wuss_ZORDER_FRONT); - wuss_window_get_scroll(win, &scroll); + 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; + } - 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; + if (region == wuss_FURNITURE_RESIZE || + region == wuss_FURNITURE_VSCROLL_WELL || + region == wuss_FURNITURE_HSCROLL_WELL) + { + if (action == wuss_MOUSE_DOWN) + { + 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); + + 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; } - return result_OK; } +#endif /* WUSS_FURNITURE */ if (win->task.handle != NULL) { - box_t content; + box_t content; + point_t doc_point; 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; + 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))) + { + icon->pressed = 1; + wuss->pressed_icon = icon; + wuss__icon_invalidate(icon); + } + else if (action == wuss_MOUSE_UP && icon->pressed) + { + icon->pressed = 0; + wuss->pressed_icon = NULL; + wuss__icon_invalidate(icon); + } + + event.kind = wuss_EVENT_ICON; + event.data.icon.icon = icon; + event.data.icon.action = action; + event.data.icon.button = button; + return win->task.handle(win, &event, win->task.task_data); + } + } +#endif + + event.kind = wuss_EVENT_MOUSE; + event.data.mouse.action = action; + event.data.mouse.point = doc_point; + event.data.mouse.button = button; return win->task.handle(win, &event, win->task.task_data); } diff --git a/libraries/wuss/mouse-move.c b/libraries/wuss/mouse-move.c index 859bd5c5..e07144ee 100644 --- a/libraries/wuss/mouse-move.c +++ b/libraries/wuss/mouse-move.c @@ -10,6 +10,7 @@ result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit) x = p.x; y = p.y; +#ifdef WUSS_FURNITURE if (wuss->furniture.dragging != NULL) { win = wuss->furniture.dragging; @@ -19,7 +20,7 @@ result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit) switch (wuss->furniture.drag_kind) { case wuss_FURNITURE_DRAG_RESIZE: - wuss__furniture_drag_resize(win, (point_t) { x, y }); + wuss__furniture_drag_resize(win, POINT(x, y)); break; case wuss_FURNITURE_DRAG_VSCROLL_SAUSAGE: @@ -32,12 +33,13 @@ result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit) case wuss_FURNITURE_DRAG_MOVE: default: - wuss_window_move(win, (point_t) { x - wuss->furniture.drag.x, y - wuss->furniture.drag.y }); + 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) @@ -46,20 +48,60 @@ result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit) if (win == NULL) return result_OK; - if (wuss__furniture_hit_test(win, (point_t) { x, y }) != wuss_FURNITURE_CONTENT) +#ifdef WUSS_FURNITURE + if (wuss__furniture_hit_test(win, POINT(x, y)) != wuss_FURNITURE_CONTENT) return result_OK; +#endif if (win->task.handle != NULL) { box_t content; + point_t doc_point; 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; + 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); + + /* 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 (it->pressed && it != icon) + { + it->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 win->task.handle(win, &event, win->task.task_data); + } + } +#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 win->task.handle(win, &event, win->task.task_data); } diff --git a/libraries/wuss/redraw.c b/libraries/wuss/redraw.c index e3f403a3..d86e3567 100644 --- a/libraries/wuss/redraw.c +++ b/libraries/wuss/redraw.c @@ -16,7 +16,9 @@ static void redraw_window(wuss_t *wuss, if (box_intersection(&win->visible, full, &visible_clipped)) return; /* offscreen */ +#ifdef WUSS_FURNITURE wuss__furniture_draw(wuss, win, full); +#endif wuss__content_box(win, &content); if (box_intersection(&content, full, &clipped)) @@ -51,6 +53,17 @@ static void redraw_window(wuss_t *wuss, 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 } } @@ -128,7 +141,7 @@ result_t wuss_redraw_dirty(wuss_t *wuss) { wuss->scr->clip = wuss->dirty[i]; screen_draw_rect(wuss->scr, - wuss->dirty[i].x0, wuss->dirty[i].y0, (size2d_t) { wuss->dirty[i].x1 - wuss->dirty[i].x0, wuss->dirty[i].y1 - wuss->dirty[i].y0 }, + wuss->dirty[i].x0, wuss->dirty[i].y0, SIZE2D(wuss->dirty[i].x1 - wuss->dirty[i].x0, wuss->dirty[i].y1 - wuss->dirty[i].y0), wuss->palette[wuss->backdrop]); } diff --git a/libraries/wuss/scroll-step.c b/libraries/wuss/scroll-step.c new file mode 100644 index 00000000..c69766e8 --- /dev/null +++ b/libraries/wuss/scroll-step.c @@ -0,0 +1,41 @@ +/* scroll-step.c -- wuss - 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); + 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__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); +} diff --git a/libraries/wuss/scroll.c b/libraries/wuss/scroll.c index f4d0979a..bcabbb43 100644 --- a/libraries/wuss/scroll.c +++ b/libraries/wuss/scroll.c @@ -5,7 +5,6 @@ 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; @@ -18,9 +17,15 @@ result_t wuss_scroll(wuss_t *wuss, point_t p, int delta, wuss_window_t **hit) if (win == NULL) return result_OK; - wuss__titlebar_box(win, &titlebar); - if (box_contains_point(&titlebar, x, y)) - 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 if (win->task.handle != NULL) { @@ -36,12 +41,12 @@ result_t wuss_scroll(wuss_t *wuss, point_t p, int delta, wuss_window_t **hit) event.data.scroll.point.y = y - content.y0 + win->scroll.y; event.data.scroll.delta = delta; - wuss__furniture_scroll_step(win, (point_t) { 0, delta }); + wuss__scroll_step(win, POINT(0, delta)); return win->task.handle(win, &event, win->task.task_data); } - wuss__furniture_scroll_step(win, (point_t) { 0, delta }); + wuss__scroll_step(win, POINT(0, delta)); return result_OK; } diff --git a/libraries/wuss/test/tasks/ball.c b/libraries/wuss/test/tasks/ball.c index d0af7601..2ee7a424 100644 --- a/libraries/wuss/test/tasks/ball.c +++ b/libraries/wuss/test/tasks/ball.c @@ -2,6 +2,8 @@ #ifdef USE_SDL +#include + #ifdef FORTIFY #include "fortify/fortify.h" #endif @@ -15,7 +17,6 @@ result_t ball_create(wuss_t *wuss, const colour_t *palette, ball_task_t *task) { wuss_task_t delegate; - box_t box; task->bg = palette[palette_PICO8_RED]; task->ball = palette[palette_PICO8_WHITE]; @@ -28,23 +29,19 @@ result_t ball_create(wuss_t *wuss, const colour_t *palette, ball_task_t *task) task->balls[0].radius = 8; delegate = wuss_task_start(ball_handle, task); /* 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, - wuss_NO_BACKGROUND, - &delegate, - box_size(&box), - &task->window); -} -void ball_destroy(ball_task_t *task) -{ - wuss_window_close(task->window); + return wuss_window_create_placed(wuss, + SIZE2D(200, 160), + "Bouncing Ball", + wuss_WINDOW_NONE, + wuss_NO_BACKGROUND, + &delegate, + SIZE2D(200, 160), + SIZE2D(0, 0), + &task->window); } + static result_t ball_redraw(const wuss_event_t *event, void *task_data) { ball_task_t *bc; @@ -69,7 +66,7 @@ static result_t ball_redraw(const wuss_event_t *event, void *task_data) b = &bc->balls[i]; - screen_draw_rect(scr, bounds->x0 - sx + b->x - b->radius, bounds->y0 - sy + b->y - b->radius, (size2d_t) { b->radius * 2, b->radius * 2 }, bc->ball); + screen_draw_rect(scr, bounds->x0 - sx + b->x - b->radius, bounds->y0 - sy + b->y - b->radius, SIZE2D(b->radius * 2, b->radius * 2), bc->ball); } return result_OK; @@ -93,7 +90,7 @@ static result_t ball_mouse(wuss_window_t *window, wuss_window_get_scroll(window, &scroll); - if (button == wuss_BUTTON_SELECT) + if (button & wuss_BUTTON_SELECT) { ball_t *b; @@ -116,7 +113,7 @@ static result_t ball_mouse(wuss_window_t *window, local.y1 = b->y + b->radius - scroll.y; wuss_window_invalidate(bc->window, &local); } - else if (button == wuss_BUTTON_ADJUST) + else if (button & wuss_BUTTON_ADJUST) { ball_t *b; @@ -180,7 +177,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) { @@ -202,7 +199,7 @@ result_t ball_handle(wuss_window_t *window, case wuss_EVENT_CLOSE: wuss_window_close(window); - bc->window = NULL; + free(bc); /* task_data was calloc'd per instance by the spawner */ return result_OK; default: diff --git a/libraries/wuss/test/tasks/ball.h b/libraries/wuss/test/tasks/ball.h index 3d17ab1e..f42fb579 100644 --- a/libraries/wuss/test/tasks/ball.h +++ b/libraries/wuss/test/tasks/ball.h @@ -34,12 +34,10 @@ ball_task_t; wuss_event_fn_t ball_handle; -/* create the bouncing-ball window against the given wuss instance */ +/* create the bouncing-ball window against the given wuss instance; "task" is a + * per-instance block owned by the window and freed when it closes */ result_t ball_create(wuss_t *wuss, const colour_t *palette, ball_task_t *task); -/* destroy the bouncing-ball window created by ball_create */ -void ball_destroy(ball_task_t *task); - #endif /* USE_SDL */ #endif /* TASKS_BALL_H */ diff --git a/libraries/wuss/test/tasks/blank.c b/libraries/wuss/test/tasks/blank.c index 39f3b709..603bec2d 100644 --- a/libraries/wuss/test/tasks/blank.c +++ b/libraries/wuss/test/tasks/blank.c @@ -2,6 +2,8 @@ #ifdef USE_SDL +#include + #ifdef FORTIFY #include "fortify/fortify.h" #endif @@ -18,28 +20,22 @@ result_t blank_create(wuss_t *wuss, int npalette, blank_task_t *task) { wuss_task_t delegate; - box_t box; task->npalette = npalette; task->index = palette_PICO8_GREEN; task->frame_count = 0; delegate = wuss_task_start(blank_handle, task); /* 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, - palette_PICO8_GREEN, - &delegate, - box_size(&box), - &task->window); -} -void blank_destroy(blank_task_t *task) -{ - wuss_window_close(task->window); + return wuss_window_create_placed(wuss, + SIZE2D(200, 160), + NULL, + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE, + palette_PICO8_GREEN, + &delegate, + SIZE2D(200, 160), + SIZE2D(0, 0), + &task->window); } static result_t blank_idle(void *task_data) @@ -62,11 +58,16 @@ static result_t blank_idle(void *task_data) 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_CLOSE) + { + wuss_window_close(window); + free(task_data); /* calloc'd per instance by the spawner */ + return result_OK; + } if (event->kind != wuss_EVENT_IDLE) return result_OK; diff --git a/libraries/wuss/test/tasks/blank.h b/libraries/wuss/test/tasks/blank.h index a2586304..b1ffd74f 100644 --- a/libraries/wuss/test/tasks/blank.h +++ b/libraries/wuss/test/tasks/blank.h @@ -25,8 +25,6 @@ wuss_event_fn_t blank_handle; /* create the colour-cycling blank window against the given wuss instance */ result_t blank_create(wuss_t *wuss, int npalette, blank_task_t *task); -/* destroy the colour-cycling window created by blank_create */ -void blank_destroy(blank_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/chars.c b/libraries/wuss/test/tasks/chars.c index 16c25168..1680d005 100644 --- a/libraries/wuss/test/tasks/chars.c +++ b/libraries/wuss/test/tasks/chars.c @@ -2,6 +2,8 @@ #ifdef USE_SDL +#include + #include #ifdef FORTIFY @@ -24,7 +26,7 @@ result_t chars_create(wuss_t *wuss, chars_task_t *task) { wuss_task_t delegate; - box_t box; + size2d_t sz; bmfont_t *font; int font_width, font_height, cell_w, cell_h; @@ -44,25 +46,20 @@ result_t chars_create(wuss_t *wuss, task->bg = palette[palette_PICO8_WHITE]; delegate = wuss_task_start(chars_handle, task); /* chars_redraw paints every cell itself */ - box = (box_t) BOX_POS_SIZE(500, 100, cell_w * CHARS_COLS, cell_h * CHARS_ROWS); - - return wuss_window_create(wuss, - &box, - "Chars", - wuss_WINDOW_NO_RESIZE | - wuss_WINDOW_NO_TOGGLE_SIZE | - wuss_WINDOW_NO_VSCROLL | - wuss_WINDOW_NO_HSCROLL, - wuss_NO_BACKGROUND, - &delegate, - box_size(&box), - &task->window); -} - -void chars_destroy(chars_task_t *task) -{ - if (task->window != NULL) - wuss_window_close(task->window); + sz = SIZE2D(cell_w * CHARS_COLS, cell_h * CHARS_ROWS); + + return wuss_window_create_placed(wuss, + sz, + "Chars", + wuss_WINDOW_NO_RESIZE | + wuss_WINDOW_NO_TOGGLE_SIZE | + wuss_WINDOW_NO_VSCROLL | + wuss_WINDOW_NO_HSCROLL, + wuss_NO_BACKGROUND, + &delegate, + sz, + SIZE2D(0, 0), + &task->window); } static result_t chars_redraw(const wuss_event_t *event, void *task_data) @@ -104,7 +101,7 @@ static result_t chars_redraw(const wuss_event_t *event, void *task_data) x = bounds->x0 - sx + col * cell_w; y = bounds->y0 - sy + row * cell_h; - screen_draw_rect(scr, x, y, (size2d_t) { cell_w, cell_h }, cc->bg); + screen_draw_rect(scr, x, y, SIZE2D(cell_w, cell_h), cc->bg); if (i < first || i >= first + count) continue; /* no glyph for this byte value: leave the cell blank */ @@ -120,12 +117,12 @@ static result_t chars_redraw(const wuss_event_t *event, void *task_data) result_t chars_handle(wuss_window_t *window, const wuss_event_t *event, - void *task_data) + void *task_data) { if (event->kind == wuss_EVENT_CLOSE) { wuss_window_close(window); - ((chars_task_t *) task_data)->window = NULL; + free(task_data); /* calloc'd per instance by the spawner */ return result_OK; } diff --git a/libraries/wuss/test/tasks/chars.h b/libraries/wuss/test/tasks/chars.h index 80495809..5df4199c 100644 --- a/libraries/wuss/test/tasks/chars.h +++ b/libraries/wuss/test/tasks/chars.h @@ -27,8 +27,6 @@ result_t chars_create(wuss_t *wuss, const colour_t *palette, chars_task_t *task); -/* destroy the glyph-grid window created by chars_create, if any */ -void chars_destroy(chars_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/checker.c b/libraries/wuss/test/tasks/checker.c index 7639a535..cecbbc48 100644 --- a/libraries/wuss/test/tasks/checker.c +++ b/libraries/wuss/test/tasks/checker.c @@ -2,6 +2,8 @@ #ifdef USE_SDL +#include + #ifdef FORTIFY #include "fortify/fortify.h" #endif @@ -21,7 +23,6 @@ result_t checker_create(wuss_t *wuss, checker_task_t *task) { wuss_task_t delegate; - box_t box; result_t rc; task->black = palette[palette_PICO8_BLACK]; @@ -32,29 +33,28 @@ result_t checker_create(wuss_t *wuss, task->band2 = CHECKER_BAND_DEFAULT; delegate = wuss_task_start(checker_handle, task); /* 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, - wuss_NO_BACKGROUND, - &delegate, - box_size(&box), - &task->window); + + rc = wuss_window_create_placed(wuss, + SIZE2D(160, 160), + "Checker 1", + wuss_WINDOW_NONE, + wuss_NO_BACKGROUND, + &delegate, + SIZE2D(160, 160), + SIZE2D(0, 0), + &task->window); if (rc != result_OK) return rc; - box = (box_t) BOX_POS_SIZE(440, 10, 160, 160); - - rc = wuss_window_create(wuss, - &box, - "Checker 2", - wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, - &delegate, - box_size(&box), - &task->window2); + rc = wuss_window_create_placed(wuss, + SIZE2D(160, 160), + "Checker 2", + wuss_WINDOW_NONE, + wuss_NO_BACKGROUND, + &delegate, + SIZE2D(160, 160), + SIZE2D(0, 0), + &task->window2); if (rc != result_OK) { wuss_window_close(task->window); @@ -64,12 +64,6 @@ result_t checker_create(wuss_t *wuss, return result_OK; } -void checker_destroy(checker_task_t *task) -{ - wuss_window_close(task->window); - wuss_window_close(task->window2); -} - static result_t checker_redraw(wuss_window_t *window, const wuss_event_t *event, void *task_data) @@ -128,7 +122,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 +140,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) { @@ -171,6 +167,9 @@ result_t checker_handle(wuss_window_t *window, cc->window2 = NULL; else cc->window = NULL; + /* one calloc'd block backs both windows; free it once both are gone */ + if (cc->window == NULL && cc->window2 == NULL) + free(cc); return result_OK; default: diff --git a/libraries/wuss/test/tasks/checker.h b/libraries/wuss/test/tasks/checker.h index 4df752a8..ca3983ce 100644 --- a/libraries/wuss/test/tasks/checker.h +++ b/libraries/wuss/test/tasks/checker.h @@ -37,8 +37,6 @@ result_t checker_create(wuss_t *wuss, const colour_t *palette, checker_task_t *task); -/* destroy the checkerboard windows created by checker_create */ -void checker_destroy(checker_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/curve.c b/libraries/wuss/test/tasks/curve.c index b19b09e3..09456f0c 100644 --- a/libraries/wuss/test/tasks/curve.c +++ b/libraries/wuss/test/tasks/curve.c @@ -2,6 +2,8 @@ #ifdef USE_SDL +#include + #include #ifdef FORTIFY @@ -26,7 +28,6 @@ result_t curve_create(wuss_t *wuss, curve_task_t *task) { wuss_task_t delegate; - box_t box; task->bg = palette[palette_PICO8_WHITE]; task->line = palette[palette_PICO8_BLACK]; @@ -34,27 +35,22 @@ result_t curve_create(wuss_t *wuss, 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 }; + task->points[0] = POINT(10, 10); + task->points[1] = POINT(10, 140); + task->points[2] = POINT(210, 10); + task->points[3] = POINT(210, 140); delegate = wuss_task_start(curve_handle, task); /* 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, - wuss_NO_BACKGROUND, - &delegate, - box_size(&box), - &task->window); -} -void curve_destroy(curve_task_t *task) -{ - wuss_window_close(task->window); + return wuss_window_create_placed(wuss, + SIZE2D(220, 160), + "Curve", + wuss_WINDOW_NONE, + wuss_NO_BACKGROUND, + &delegate, + SIZE2D(220, 160), + SIZE2D(0, 0), + &task->window); } static int blob_hit(const point_t *p, int x, int y) @@ -108,11 +104,11 @@ static result_t curve_redraw(const wuss_event_t *event, curve_task_t *task) 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_window_t *window) { int i; @@ -182,7 +178,7 @@ result_t curve_handle(wuss_window_t *window, case wuss_EVENT_CLOSE: wuss_window_close(window); - task->window = NULL; + free(task); /* task_data was calloc'd per instance by the spawner */ return result_OK; default: diff --git a/libraries/wuss/test/tasks/curve.h b/libraries/wuss/test/tasks/curve.h index 2a411ffe..de7a2e80 100644 --- a/libraries/wuss/test/tasks/curve.h +++ b/libraries/wuss/test/tasks/curve.h @@ -30,8 +30,6 @@ result_t curve_create(wuss_t *wuss, const colour_t *palette, curve_task_t *task); -/* destroy the curve window created by curve_create */ -void curve_destroy(curve_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/gradient.c b/libraries/wuss/test/tasks/gradient.c index 78f0c479..0602325c 100644 --- a/libraries/wuss/test/tasks/gradient.c +++ b/libraries/wuss/test/tasks/gradient.c @@ -2,6 +2,8 @@ #ifdef USE_SDL +#include + #ifdef FORTIFY #include "fortify/fortify.h" #endif @@ -34,24 +36,18 @@ 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); /* 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, - wuss_NO_BACKGROUND, - &delegate, - (size2d_t) { GRADIENT_DOC_WIDTH, GRADIENT_DOC_HEIGHT }, - &task->window); -} -void gradient_destroy(gradient_task_t *task) -{ - wuss_window_close(task->window); + return wuss_window_create_placed(wuss, + SIZE2D(GRADIENT_OPEN_WIDTH, GRADIENT_OPEN_HEIGHT), + "Gradient", + wuss_WINDOW_NONE, + wuss_NO_BACKGROUND, + &delegate, + SIZE2D(GRADIENT_DOC_WIDTH, GRADIENT_DOC_HEIGHT), + SIZE2D(0, 0), + &task->window); } static result_t gradient_redraw(const wuss_event_t *event, void *task_data) @@ -100,7 +96,7 @@ result_t gradient_handle(wuss_window_t *window, case wuss_EVENT_CLOSE: wuss_window_close(window); - gc->window = NULL; + free(gc); /* task_data was calloc'd per instance by the spawner */ return result_OK; default: diff --git a/libraries/wuss/test/tasks/gradient.h b/libraries/wuss/test/tasks/gradient.h index a37ff22a..8f720073 100644 --- a/libraries/wuss/test/tasks/gradient.h +++ b/libraries/wuss/test/tasks/gradient.h @@ -21,8 +21,6 @@ wuss_event_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 */ diff --git a/libraries/wuss/test/tasks/icons.c b/libraries/wuss/test/tasks/icons.c new file mode 100644 index 00000000..a9953354 --- /dev/null +++ b/libraries/wuss/test/tasks/icons.c @@ -0,0 +1,258 @@ +/* icons.c -- wuss test - work-area icons task */ + +#ifdef USE_SDL +#include "framebuf/palettes.h" + +#include +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "framebuf/palettes.h" +#include "framebuf/screen.h" +#include "geom/box.h" +#include "geom/point.h" +#include "geom/size.h" + +#include "icons.h" + +#define ICONS_DOC_W 220 +#define ICONS_DOC_H 520 /* taller than the window, so scrolling is exercised */ + +result_t icons_create(wuss_t *wuss, + const colour_t *palette, + bmfont_t *font, + icons_task_t *task) +{ + /* [0..3] plus one swatch per built-in pattern */ + wuss_task_t delegate; + wuss_icon_spec_t specs[4 + screen_PATTERN__LIMIT]; + wuss_icon_t *made[4 + screen_PATTERN__LIMIT]; + int p; + result_t rc; + + task->font = font; + task->ink = palette[palette_PICO8_LAVENDER]; + task->label = palette[palette_PICO8_DARK_BLUE]; + task->paper = palette[palette_PICO8_LIGHT_GREY]; /* the window bg, below */ + task->window = NULL; + task->button = NULL; + task->counter = NULL; + task->count = 0; + + delegate = wuss_task_start(icons_handle, task); + + rc = wuss_window_create_placed(wuss, + SIZE2D(ICONS_DOC_W, 160), + "Icons", + wuss_WINDOW_NONE, + palette_PICO8_LIGHT_GREY, + &delegate, + SIZE2D(ICONS_DOC_W, ICONS_DOC_H), + SIZE2D(0, 0), + &task->window); + if (rc != result_OK) + return rc; + + memset(specs, 0, sizeof(specs)); + + /* icons sit past the ruler gutter (see ICONS_GUTTER in icons_redraw) so the + * axis labels have the top/left strip to themselves */ + + /* [0] a heading label */ + specs[0].bbox = (box_t) BOX_POS_SIZE(28, 28, 180, 14); + specs[0].type = wuss_ICON_TYPE_LABEL; + specs[0].text = "Work-area icons:"; + specs[0].fg = palette_PICO8_DARK_BLUE; + specs[0].bg = wuss_NO_BACKGROUND; + + /* [1] the button that bumps the counter */ + specs[1].bbox = (box_t) BOX_POS_SIZE(28, 50, 80, 22); + specs[1].type = wuss_ICON_TYPE_BUTTON; + specs[1].text = "Press me"; + specs[1].fg = palette_PICO8_BLACK; + specs[1].bg = palette_PICO8_LIGHT_GREY; + + /* [2] the counter label beside it */ + specs[2].bbox = (box_t) BOX_POS_SIZE(120, 50, 120, 22); + specs[2].type = wuss_ICON_TYPE_LABEL; + specs[2].text = "0"; + specs[2].fg = palette_PICO8_DARK_BLUE; + specs[2].bg = wuss_NO_BACKGROUND; + + /* [3] a button far down the document, to prove icons scroll and stay clickable */ + specs[3].bbox = (box_t) BOX_POS_SIZE(28, 460, 90, 52); + specs[3].type = wuss_ICON_TYPE_BUTTON; + specs[3].text = "Scrolled"; + specs[3].fg = palette_PICO8_BLACK; + specs[3].bg = palette_PICO8_LIGHT_GREY; + + /* [4..] one swatch per built-in pattern, in a column down the document so + * they scroll through the window and stay phase-locked while doing so */ + for (p = 0; p < screen_PATTERN__LIMIT; p++) + { + specs[4 + p].bbox = (box_t) BOX_POS_SIZE(28, 90 + p * 44, 90, 36); + specs[4 + p].type = wuss_ICON_TYPE_PATTERN; + specs[4 + p].fg = palette_PICO8_DARK_BLUE; + specs[4 + p].bg = palette_PICO8_LIGHT_GREY; + specs[4 + p].pattern = (screen_pattern_t) p; + } + + rc = wuss_icon_create_array(task->window, specs, + 4 + screen_PATTERN__LIMIT, made); + if (rc != result_OK) + goto failure; + + task->button = made[1]; + task->counter = made[2]; + + return result_OK; + +failure: + wuss_window_close(task->window); + task->window = NULL; + 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. */ + + /* a faint grid across the whole work area */ + for (x = icons_grid_first(bounds->x0, scroll.x, content->x0); + x < content->x1; + x += ICONS_GRID) + screen_draw_line(scr, x, content->y0, x, content->y1 - 1, tcx->ink); + + for (y = icons_grid_first(bounds->y0, scroll.y, content->y0); + y < content->y1; + y += ICONS_GRID) + screen_draw_line(scr, content->x0, y, content->x1 - 1, y, tcx->ink); + + /* 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; + char buf[16]; + + tcx = task_data; + + if (event->data.icon.action != wuss_MOUSE_DOWN) + return result_OK; + if (event->data.icon.icon != tcx->button) + 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_CLOSE: + wuss_window_close(window); + free(tcx); /* calloc'd per instance by the spawner */ + return result_OK; + + default: + return result_OK; + } +} + +#endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/icons.h b/libraries/wuss/test/tasks/icons.h new file mode 100644 index 00000000..aebf0ea2 --- /dev/null +++ b/libraries/wuss/test/tasks/icons.h @@ -0,0 +1,40 @@ +/* icons.h -- wuss test - work-area icons task */ + +#ifndef TASKS_ICONS_H +#define TASKS_ICONS_H + +#ifdef USE_SDL + +#include "framebuf/bmfont.h" +#include "framebuf/colour.h" +#include "wuss/icon.h" +#include "wuss/window.h" + +/* demonstrates wuss-managed work-area icons: a couple of labels, a button that + * bumps a counter, and a second 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 ink; /* grid lines */ + colour_t label; /* axis coordinate text */ + colour_t paper; /* window bg, for bmfont_draw glyph blending */ + wuss_icon_t *button; /* "Press me" */ + wuss_icon_t *counter; /* label showing hit count */ + int count; +} +icons_task_t; + +wuss_event_fn_t icons_handle; + +/* create the icons window against the given wuss instance */ +result_t icons_create(wuss_t *wuss, + const colour_t *palette, + bmfont_t *font, + icons_task_t *task); + + +#endif /* USE_SDL */ + +#endif /* TASKS_ICONS_H */ diff --git a/libraries/wuss/test/tasks/image.c b/libraries/wuss/test/tasks/image.c index d9e55207..943836e1 100644 --- a/libraries/wuss/test/tasks/image.c +++ b/libraries/wuss/test/tasks/image.c @@ -11,45 +11,47 @@ #include "base/utils.h" #include "framebuf/palettes.h" #include "geom/box.h" -#include "io/path.h" #include "image.h" +#define BORDER 16 + result_t image_create(wuss_t *wuss, const colour_t *palette, - const char *resources, + const char *path, + const char *background_path, image_task_t *task) { - const char *leafname; - const char *filename; wuss_task_t delegate; - box_t box; 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) return rc; - delegate = wuss_task_start(image_handle, task); /* 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.size.w, task->bitmap.size.h * 2 / 3); - - return wuss_window_create(wuss, - &box, - "Image", - wuss_WINDOW_NONE, - palette_PICO8_BLACK, - &delegate, - (size2d_t) { task->bitmap.size.w, task->bitmap.size.h }, - &task->window); -} + rc = bitmap_load_png(&task->ninepatch, background_path); + if (rc != result_OK) + { + free(task->bitmap.base); + return rc; + } -void image_destroy(image_task_t *task) -{ - wuss_window_close(task->window); - free(task->bitmap.base); + delegate = wuss_task_start(image_handle, task); /* shows through the image's transparent pixels */ + + sz.w = task->bitmap.size.w + BORDER * 2; + sz.h = task->bitmap.size.h + BORDER * 2; + + return wuss_window_create_placed(wuss, + /* shorter than the bitmap so there's something to scroll through */ + SIZE2D(sz.w, sz.h * 2 / 3), + "Image", + wuss_WINDOW_NONE, + palette_PICO8_PINK, + &delegate, + sz, + SIZE2D(32, 32), + &task->window); } static result_t image_redraw(const wuss_event_t *event, void *task_data) @@ -58,6 +60,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 +69,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_draw_ninepatch(scr, &behind, &ic->ninepatch, 0); - screen_draw_bitmap(scr, bounds->x0 - sx, bounds->y0 - sy, &ic->bitmap); + screen_draw_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) { @@ -86,7 +100,9 @@ result_t image_handle(wuss_window_t *window, case wuss_EVENT_CLOSE: wuss_window_close(window); - ic->window = NULL; + free(ic->bitmap.base); + free(ic->ninepatch.base); + free(ic); /* task_data was calloc'd per instance by the spawner */ return result_OK; default: diff --git a/libraries/wuss/test/tasks/image.h b/libraries/wuss/test/tasks/image.h index c17aa0f5..f62eb928 100644 --- a/libraries/wuss/test/tasks/image.h +++ b/libraries/wuss/test/tasks/image.h @@ -14,21 +14,21 @@ 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; -/* load the image and create its window against the given wuss instance; - * resources is the DPTLib repo root, for locating the bundled PNG */ +/* load the PNG at path (and the 9-patch PNG at background_path, drawn tiled + * behind it) and create its window against the given wuss instance */ result_t image_create(wuss_t *wuss, const colour_t *palette, - const char *resources, + 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 */ diff --git a/libraries/wuss/test/tasks/launcher.c b/libraries/wuss/test/tasks/launcher.c index 32188120..d01b565e 100644 --- a/libraries/wuss/test/tasks/launcher.c +++ b/libraries/wuss/test/tasks/launcher.c @@ -2,6 +2,7 @@ #ifdef USE_SDL +#include #include #ifdef FORTIFY @@ -11,128 +12,102 @@ #include "base/utils.h" #include "framebuf/palettes.h" #include "geom/box.h" -#include "geom/point.h" + +#include "wuss/icon.h" #include "launcher.h" -#define LAUNCHER_ROW_HEIGHT 20 +#define LAUNCHER_ROW_HEIGHT 22 #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); /* 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, - wuss_NO_BACKGROUND, - &delegate, - box_size(&box), - &task->window); -} - -void launcher_destroy(launcher_task_t *task) -{ - wuss_window_close(task->window); -} +#define LAUNCHER_WIDTH 160 -static result_t launcher_redraw(const wuss_event_t *event, void *task_data) +result_t launcher_create(wuss_t *wuss, + const launcher_entry_t *entries, + int nentries, + launcher_task_t *task) { - launcher_task_t *lc; - screen_t *scr; - const box_t *content, *bounds; - int i, font_width, font_height, sx, 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; - sx = event->data.redraw.scroll.x; - sy = event->data.redraw.scroll.y; - - screen_draw_rect(scr, content->x0, content->y0, box_size(content), - lc->bg); - - bmfont_get_info(lc->font, &font_width, &font_height); - NOT_USED(font_width); - - for (i = 0; i < lc->nentries; i++) + wuss_task_t delegate; + wuss_icon_spec_t specs[LAUNCHER_MAX_ENTRIES]; + size2d_t sz; + int i; + result_t rc; + + assert(nentries <= LAUNCHER_MAX_ENTRIES); + + task->entries = entries; + task->nentries = nentries; + task->window = NULL; + memset(task->icons, 0, sizeof(task->icons)); + + delegate = wuss_task_start(launcher_handle, task); + sz = SIZE2D(LAUNCHER_WIDTH, + LAUNCHER_PAD * 2 + nentries * LAUNCHER_ROW_HEIGHT); + + rc = wuss_window_create_placed(wuss, + sz, + "Launcher", + wuss_WINDOW_NO_CLOSE, + palette_PICO8_LIGHT_GREY, + &delegate, + sz, + SIZE2D(0, 0), + &task->window); + if (rc != result_OK) + return rc; + + memset(specs, 0, sizeof(specs)); + + for (i = 0; i < nentries; i++) { - entry = &lc->entries[i]; - - pos.x = bounds->x0 - sx + LAUNCHER_PAD; - pos.y = bounds->y0 - sy + LAUNCHER_PAD + i * LAUNCHER_ROW_HEIGHT + (LAUNCHER_ROW_HEIGHT - font_height) / 2; + specs[i].bbox = (box_t) BOX_POS_SIZE(LAUNCHER_PAD, + LAUNCHER_PAD + i * LAUNCHER_ROW_HEIGHT, + LAUNCHER_WIDTH - LAUNCHER_PAD * 2, + LAUNCHER_ROW_HEIGHT - 2); + specs[i].type = wuss_ICON_TYPE_BUTTON; + specs[i].text = entries[i].name; + specs[i].fg = palette_PICO8_BLACK; + specs[i].bg = palette_PICO8_LIGHT_GREY; + } - bmfont_draw(lc->font, scr, entry->name, (int) strlen(entry->name), - entry->running ? lc->running_fg : lc->fg, lc->bg, &pos, NULL); + rc = wuss_icon_create_array(task->window, specs, nentries, task->icons); + if (rc != result_OK) + { + wuss_window_close(task->window); + task->window = NULL; + return rc; } return result_OK; } -static result_t launcher_mouse(wuss_window_t *window, int y, void *task_data) +void launcher_destroy(launcher_task_t *task) { - launcher_task_t *lc; - int i; - launcher_entry_t *entry; - result_t rc; - - lc = task_data; - - /* y already arrives in virtual content space, which is how the rows are - * laid out, so the scroll offset must not be added again here. */ - i = (y - LAUNCHER_PAD) / LAUNCHER_ROW_HEIGHT; - if (i < 0 || i >= lc->nentries) - return result_OK; + wuss_window_close(task->window); +} - entry = &lc->entries[i]; - if (entry->running) - return result_OK; +static result_t launcher_icon(launcher_task_t *lc, const wuss_icon_t *icon) +{ + int i; - rc = entry->spawn(); - if (rc == result_OK) - { - entry->running = true; - wuss_window_invalidate_all(window); - } + for (i = 0; i < lc->nentries; i++) + if (lc->icons[i] == icon) + return lc->entries[i].spawn(); - return rc; + return result_OK; } result_t launcher_handle(wuss_window_t *window, const wuss_event_t *event, void *task_data) { + NOT_USED(window); + switch (event->kind) { - case wuss_EVENT_REDRAW: - return launcher_redraw(event, task_data); - - case wuss_EVENT_MOUSE: - if (event->data.mouse.action != wuss_MOUSE_DOWN) + case wuss_EVENT_ICON: + if (event->data.icon.action != wuss_MOUSE_DOWN) return result_OK; - return launcher_mouse(window, event->data.mouse.point.y, task_data); + return launcher_icon(task_data, event->data.icon.icon); default: return result_OK; diff --git a/libraries/wuss/test/tasks/launcher.h b/libraries/wuss/test/tasks/launcher.h index 05ebd89c..573f3e07 100644 --- a/libraries/wuss/test/tasks/launcher.h +++ b/libraries/wuss/test/tasks/launcher.h @@ -7,45 +7,40 @@ #include -#include "framebuf/bmfont.h" -#include "framebuf/colour.h" +#include "wuss/icon.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 */ +/* one clickable row */ typedef struct launcher_entry { - const char *name; - launcher_spawn_fn_t spawn; - launcher_destroy_fn_t destroy; - bool running; + const char *name; + launcher_spawn_fn_t spawn; } launcher_entry_t; +/* each row is a work-area button icon; clicking one spawns a fresh instance + * of its task, so a row may be clicked any number of times */ +#define LAUNCHER_MAX_ENTRIES 32 + typedef struct launcher_task { - 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; + const launcher_entry_t *entries; /* owned by the caller, must outlive the window */ + int nentries; + wuss_icon_t *icons[LAUNCHER_MAX_ENTRIES]; + wuss_window_t *window; } launcher_task_t; wuss_event_fn_t launcher_handle; -/* create a window 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); +/* create a window of button icons, one per entry; each click on a button + * calls its spawn function */ +result_t launcher_create(wuss_t *wuss, + const launcher_entry_t *entries, + int nentries, + launcher_task_t *task); void launcher_destroy(launcher_task_t *task); diff --git a/libraries/wuss/test/tasks/palette.c b/libraries/wuss/test/tasks/palette.c index 4e44965a..dfcc2eee 100644 --- a/libraries/wuss/test/tasks/palette.c +++ b/libraries/wuss/test/tasks/palette.c @@ -2,6 +2,8 @@ #ifdef USE_SDL +#include + #ifdef FORTIFY #include "fortify/fortify.h" #endif @@ -18,27 +20,21 @@ result_t palette_create(wuss_t *wuss, palette_task_t *task) { wuss_task_t delegate; - box_t box; task->palette = palette; task->npalette = npalette; delegate = wuss_task_start(palette_handle, task); /* 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_RESIZE_BLIT, /* swatch grid is laid out across the whole window, so a resize must redraw all of it, not just the newly (un)covered edge */ - palette_PICO8_BLACK, - &delegate, - box_size(&box), - &task->window); -} -void palette_destroy(palette_task_t *task) -{ - wuss_window_close(task->window); + return wuss_window_create_placed(wuss, + SIZE2D(100, 100), + "Palette", + wuss_WINDOW_NO_RESIZE_BLIT, /* swatch grid is laid out across the whole window, so a resize must redraw all of it, not just the newly (un)covered edge */ + palette_PICO8_BLACK, + &delegate, + SIZE2D(100, 100), + SIZE2D(0, 0), + &task->window); } static result_t palette_redraw(const wuss_event_t *event, void *task_data) @@ -77,20 +73,20 @@ static result_t palette_redraw(const wuss_event_t *event, void *task_data) x = bounds->x0 - sx + col * cell_w; y = bounds->y0 - sy + row * cell_h; - screen_draw_rect(scr, x, y, (size2d_t) { cell_w, cell_h }, pc->palette[i]); + screen_draw_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) { wuss_window_close(window); - ((palette_task_t *) task_data)->window = NULL; + free(task_data); /* calloc'd per instance by the spawner */ return result_OK; } diff --git a/libraries/wuss/test/tasks/palette.h b/libraries/wuss/test/tasks/palette.h index 8f7445b1..8463df9a 100644 --- a/libraries/wuss/test/tasks/palette.h +++ b/libraries/wuss/test/tasks/palette.h @@ -26,8 +26,6 @@ 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 */ diff --git a/libraries/wuss/test/tasks/porter-duff.c b/libraries/wuss/test/tasks/porter-duff.c new file mode 100644 index 00000000..14fc1dc4 --- /dev/null +++ b/libraries/wuss/test/tasks/porter-duff.c @@ -0,0 +1,411 @@ +/* porter-duff.c -- wuss test - animated Porter-Duff compositing task */ + +#ifdef USE_SDL + +#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; + 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; + + delegate = wuss_task_start(porter_duff_handle, task); /* porter_duff_redraw paints every pixel itself */ + + rc = wuss_window_create_placed(wuss, + SIZE2D(PD_SIZE, PD_SIZE + PD_LABEL_HEIGHT), + "Porter-Duff", + wuss_WINDOW_NONE, + wuss_NO_BACKGROUND, + &delegate, + SIZE2D(PD_SIZE, PD_SIZE + PD_LABEL_HEIGHT), + SIZE2D(0, 0), + &task->window); + if (rc != result_OK) + goto free_dst; + + 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); + + 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_draw_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_draw_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) + 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_CLOSE: + wuss_window_close(window); + 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 /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/porter-duff.h b/libraries/wuss/test/tasks/porter-duff.h new file mode 100644 index 00000000..3a7f210e --- /dev/null +++ b/libraries/wuss/test/tasks/porter-duff.h @@ -0,0 +1,48 @@ +/* porter-duff.h -- wuss test - animated Porter-Duff compositing task */ + +#ifndef TASKS_PORTER_DUFF_H +#define TASKS_PORTER_DUFF_H + +#ifdef USE_SDL + +#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_event_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 /* USE_SDL */ + +#endif /* TASKS_PORTER_DUFF_H */ diff --git a/libraries/wuss/test/tasks/sofa.c b/libraries/wuss/test/tasks/sofa.c index 300c89da..28cef250 100644 --- a/libraries/wuss/test/tasks/sofa.c +++ b/libraries/wuss/test/tasks/sofa.c @@ -2,6 +2,8 @@ #ifdef USE_SDL +#include + #include #ifdef FORTIFY @@ -15,6 +17,8 @@ #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 @@ -77,6 +81,81 @@ 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] = @@ -255,13 +334,33 @@ static fix8_point_t project(vec3_t v, int cx, int cy, double unit) return p; } +/* 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) +{ + int half; + int i; + + half = SOFA_VERTEX_DOT / 2; + + for (i = 0; i < nvertices; i++) + screen_draw_square(scr, + FIX8_ROUND_TO_INT(screen[i].x) - half, + FIX8_ROUND_TO_INT(screen[i].y) - half, + SOFA_VERTEX_DOT, + colour); +} + result_t sofa_create(wuss_t *wuss, const colour_t *palette, sofa_task_t *task) { wuss_task_t delegate; - box_t box; task->bg = palette[palette_PICO8_DARK_PURPLE]; task->line = palette[palette_PICO8_ORANGE]; + task->dot = palette[palette_PICO8_WHITE]; task->angle = 0.0; task->zoom = 1.0; task->spinning = true; @@ -269,21 +368,16 @@ result_t sofa_create(wuss_t *wuss, const colour_t *palette, sofa_task_t *task) task->turns = 0; delegate = wuss_task_start(sofa_handle, task); /* 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, - wuss_NO_BACKGROUND, - &delegate, - box_size(&box), - &task->window); -} -void sofa_destroy(sofa_task_t *task) -{ - wuss_window_close(task->window); + return wuss_window_create_placed(wuss, + SIZE2D(180, 160), + "Sofa", + wuss_WINDOW_NONE, + wuss_NO_BACKGROUND, + &delegate, + SIZE2D(180, 160), + SIZE2D(0, 0), + &task->window); } static result_t sofa_redraw(const wuss_event_t *event, void *task_data) @@ -332,6 +426,8 @@ static result_t sofa_redraw(const wuss_event_t *event, void *task_data) 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 if (sc->shape == sofa_SHAPE_SHIP) @@ -350,6 +446,27 @@ 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 { @@ -370,18 +487,22 @@ static result_t sofa_redraw(const wuss_event_t *event, void *task_data) 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 + 1) % sofa_SHAPE__LIMIT; sc->turns = 0; @@ -434,7 +555,7 @@ static result_t sofa_idle(void *task_data) 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) { @@ -460,7 +581,7 @@ result_t sofa_handle(wuss_window_t *window, case wuss_EVENT_CLOSE: wuss_window_close(window); - sc->window = NULL; + free(sc); /* task_data was calloc'd per instance by the spawner */ return result_OK; default: diff --git a/libraries/wuss/test/tasks/sofa.h b/libraries/wuss/test/tasks/sofa.h index a092ecaf..1a8c9ffd 100644 --- a/libraries/wuss/test/tasks/sofa.h +++ b/libraries/wuss/test/tasks/sofa.h @@ -15,6 +15,7 @@ typedef enum sofa_shape { sofa_SHAPE_SOFA, sofa_SHAPE_SHIP, + sofa_SHAPE_COBRA, sofa_SHAPE_TETRAHEDRON, sofa_SHAPE_CUBE, sofa_SHAPE_OCTAHEDRON, @@ -30,7 +31,7 @@ sofa_shape_t; 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; @@ -44,8 +45,6 @@ wuss_event_fn_t sofa_handle; /* create the sofa window against the given wuss instance */ result_t sofa_create(wuss_t *wuss, const colour_t *palette, sofa_task_t *task); -/* destroy the sofa window created by sofa_create */ -void sofa_destroy(sofa_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/text.c b/libraries/wuss/test/tasks/text.c index 629bea22..aa094435 100644 --- a/libraries/wuss/test/tasks/text.c +++ b/libraries/wuss/test/tasks/text.c @@ -2,6 +2,8 @@ #ifdef USE_SDL +#include + #include #include #include @@ -32,7 +34,7 @@ result_t text_create(wuss_t *wuss, text_task_t *task) { wuss_task_t delegate; - box_t box; + size2d_t sz; result_t rc; task->font = font; @@ -42,28 +44,24 @@ result_t text_create(wuss_t *wuss, task->resizing = true; delegate = wuss_task_start(text_handle, task); - box = (box_t) BOX_POS_SIZE(120, 100, 220, 180); + sz = SIZE2D(220, 180); - task->base_width = box.x1 - box.x0; - task->base_height = box.y1 - box.y0; + task->base_width = sz.w; + task->base_height = sz.h; - rc = wuss_window_create(wuss, - &box, - "Lorem Ipsum", - wuss_WINDOW_NO_RESIZE_BLIT, /* paragraph reflows across the whole window, so a resize must redraw all of it, not just the newly (un)covered edge */ - palette_PICO8_BLUE, - &delegate, - box_size(&box), - &task->window); + rc = wuss_window_create_placed(wuss, + sz, + "Lorem Ipsum", + wuss_WINDOW_NO_RESIZE_BLIT, /* paragraph reflows across the whole window, so a resize must redraw all of it, not just the newly (un)covered edge */ + palette_PICO8_BLUE, + &delegate, + sz, + SIZE2D(0, 0), + &task->window); return rc; } -void text_destroy(text_task_t *task) -{ - wuss_window_close(task->window); -} - static result_t text_redraw(const wuss_event_t *event, void *task_data) { text_task_t *tcx; @@ -155,14 +153,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, (size2d_t) { 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) { @@ -182,7 +180,7 @@ result_t text_handle(wuss_window_t *window, case wuss_EVENT_CLOSE: wuss_window_close(window); - tcx->window = NULL; + free(tcx); /* task_data was calloc'd per instance by the spawner */ return result_OK; case wuss_EVENT_IDLE: diff --git a/libraries/wuss/test/tasks/text.h b/libraries/wuss/test/tasks/text.h index cf1dd7fe..d7a985af 100644 --- a/libraries/wuss/test/tasks/text.h +++ b/libraries/wuss/test/tasks/text.h @@ -34,8 +34,6 @@ result_t text_create(wuss_t *wuss, bmfont_t *font, text_task_t *task); -/* destroy the paragraph-of-text window created by text_create */ -void text_destroy(text_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index 0a744361..6017f6f4 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -35,9 +35,11 @@ #include "tasks/checker.h" #include "tasks/curve.h" #include "tasks/gradient.h" +#include "tasks/icons.h" #include "tasks/image.h" #include "tasks/launcher.h" #include "tasks/palette.h" +#include "tasks/porter-duff.h" #include "tasks/sofa.h" #include "tasks/text.h" @@ -49,57 +51,165 @@ /* 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 chars_task_t g_chars_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_chars(void) { return chars_create(g_wuss, g_palette, &g_chars_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_chars(void) { chars_destroy(&g_chars_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[] = +static wuss_t *g_wuss; +static const colour_t *g_palette; +static int g_npalette; +static const char *g_resources; +static bmfont_t *g_daydream_font; + +/* Each spawn allocates a fresh per-instance task block so a task may run in + * several windows at once; the block is owned by its window and freed by the + * task's wuss_EVENT_CLOSE handler. If create fails, or (for the font-less + * chars task) returns OK without opening a window, the block is freed here -- + * otherwise it would leak. */ + +static result_t spawn_ball(void) { - { "Ball", spawn_ball, destroy_ball, false }, - { "Text", spawn_text, destroy_text, false }, - { "Blank", spawn_blank, destroy_blank, false }, - { "Chars", spawn_chars, destroy_chars, 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 } + ball_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = ball_create(g_wuss, g_palette, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_text(void) +{ + text_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = text_create(g_wuss, g_palette, g_daydream_font, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_blank(void) +{ + blank_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = blank_create(g_wuss, g_npalette, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_chars(void) +{ + chars_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = chars_create(g_wuss, g_palette, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_palette(void) +{ + palette_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = palette_create(g_wuss, g_palette, g_npalette, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_image(void) +{ + image_task_t *t; + const char *leafname; + const char *filename; + char buf[DPTLIB_MAXPATH]; + const char *ninepatch; + result_t rc; + + t = calloc(1, sizeof(*t)); + if (t == NULL) return result_OOM; + + leafname = path_join_leafname("jessica", "png"); + filename = path_join_filename(g_resources, 3, "resources", "images", leafname); + strcpy(buf, filename); + ninepatch = path_join_filename(g_resources, 3, "resources", "wuss", + path_join_leafname("9tile", "png")); + + rc = image_create(g_wuss, g_palette, buf, ninepatch, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_checker(void) +{ + checker_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = checker_create(g_wuss, g_palette, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_curve(void) +{ + curve_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = curve_create(g_wuss, g_palette, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_sofa(void) +{ + sofa_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = sofa_create(g_wuss, g_palette, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_gradient(void) +{ + gradient_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = gradient_create(g_wuss, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_icons(void) +{ + icons_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = icons_create(g_wuss, g_palette, g_daydream_font, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_porter_duff(void) +{ + porter_duff_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = porter_duff_create(g_wuss, g_palette, g_daydream_font, g_resources, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static const launcher_entry_t g_launcher_entries[] = +{ + { "Ball", spawn_ball }, + { "Text", spawn_text }, + { "Blank", spawn_blank }, + { "Chars", spawn_chars }, + { "Palette", spawn_palette }, + { "Image", spawn_image }, + { "Checker", spawn_checker }, + { "Curve", spawn_curve }, + { "Sofa", spawn_sofa }, + { "Gradient", spawn_gradient }, + { "Icons", spawn_icons }, + { "Porter-Duff", spawn_porter_duff } }; static wuss_button_t sdl_button_to_wuss(Uint8 button) @@ -186,9 +296,9 @@ static result_t wuss_interactive_test(const char *resources) goto Failure; #if WUSS_TEST_32BPP - rc = bitmap_init(&bm, (size2d_t) { scr_width, scr_height }, pixelfmt_bgrx8888, rowbytes, palette, pixels); + rc = bitmap_init(&bm, SIZE2D(scr_width, scr_height), pixelfmt_bgrx8888, rowbytes, palette, pixels); #else - rc = bitmap_init(&bm, (size2d_t) { scr_width, scr_height }, pixelfmt_p4, rowbytes, palette, pixels); + rc = bitmap_init(&bm, SIZE2D(scr_width, scr_height), pixelfmt_p4, rowbytes, palette, pixels); #endif if (rc != result_OK) goto Failure; @@ -242,6 +352,8 @@ static result_t wuss_interactive_test(const char *resources) config.palette.scroll.arrows = palette_PICO8_BLUE; config.palette.scroll.wells = palette_PICO8_DARK_BLUE; config.palette.scroll.sausages = palette_PICO8_LIGHT_GREY; + config.bevel.light = palette_PICO8_WHITE; + config.bevel.dark = palette_PICO8_DARK_GREY; config.backdrop = palette_PICO8_LIGHT_GREY; rc = wuss_create(&scr, font, palette, NELEMS(palette), &config, &wuss); @@ -255,7 +367,7 @@ static result_t wuss_interactive_test(const char *resources) g_resources = resources; g_daydream_font = daydream_font; - rc = launcher_create(wuss, g_launcher_entries, NELEMS(g_launcher_entries), font, palette, &launcher_task); + rc = launcher_create(wuss, g_launcher_entries, NELEMS(g_launcher_entries), &launcher_task); if (rc != result_OK) goto Failure; @@ -303,7 +415,7 @@ static result_t wuss_interactive_test(const char *resources) 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); + wuss_mouse_click(wuss, POINT(x, y), sdl_button_to_wuss(event.button.button), wuss_MOUSE_DOWN, NULL); } break; @@ -312,7 +424,7 @@ static result_t wuss_interactive_test(const char *resources) 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); + wuss_mouse_click(wuss, POINT(x, y), sdl_button_to_wuss(event.button.button), wuss_MOUSE_UP, NULL); } break; @@ -321,7 +433,7 @@ static result_t wuss_interactive_test(const char *resources) 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); + wuss_mouse_move(wuss, POINT(x, y), NULL); } break; @@ -330,7 +442,7 @@ static result_t wuss_interactive_test(const char *resources) 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); + wuss_scroll(wuss, POINT(x, y), (int) event.wheel.y, NULL); } break; @@ -408,9 +520,10 @@ static result_t wuss_interactive_test(const char *resources) SDL_Delay(1000 / 60); } - for (i = 0; i < NELEMS(g_launcher_entries); i++) - if (g_launcher_entries[i].running) - g_launcher_entries[i].destroy(); + /* ponytail: wuss_destroy() below frees every still-open window but not the + * per-instance task block hung off it, so any task window left open at quit + * leaks its block. Harmless at process exit; add a wuss close callback if a + * task ever needs deterministic teardown. */ launcher_destroy(&launcher_task); wuss_destroy(wuss); @@ -452,7 +565,7 @@ typedef struct test_task } test_task_t; -static result_t test_handle(wuss_window_t *window, +static result_t test_handle(wuss_window_t *window, const wuss_event_t *event, void *task_data) { @@ -534,6 +647,8 @@ static int dirty_union_area(wuss_t *wuss, const box_t *bounds) return area; } +#if defined(WUSS_FURNITURE) && defined(WUSS_ICONS) + result_t wuss_test(const char *resources) { result_t rc; @@ -544,8 +659,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; @@ -561,7 +676,7 @@ result_t wuss_test(const char *resources) if (pixels == NULL) goto Failure; - rc = bitmap_init(&bm, (size2d_t) { 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; @@ -613,6 +728,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, NULL, box_size(&box_a), + SIZE2D(0, 0), &win_a); if (rc != result_WUSS_TOO_SMALL) goto Failure; @@ -622,7 +738,7 @@ result_t wuss_test(const char *resources) tc_a.redraw_count = 0; tc_a.mouse_count = 0; tc_a.open_count = 0; - delegate_a.handle = test_handle; + delegate_a.handle = test_handle; delegate_a.task_data = &tc_a; box_a.x0 = 0; @@ -638,13 +754,14 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_a, 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.handle = test_handle; delegate_b.task_data = &tc_b; box_b.x0 = 50; @@ -660,6 +777,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_b, box_size(&box_b), + SIZE2D(0, 0), &win_b); if (rc != result_OK) goto Failure; @@ -728,7 +846,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) @@ -738,14 +856,14 @@ 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"); 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 */ + rc = wuss_mouse_click(wuss, POINT(6, 11), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* A's close icon */ if (rc != result_OK) goto Failure; if (hit != win_a) @@ -753,7 +871,7 @@ result_t wuss_test(const char *resources) 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; @@ -764,20 +882,20 @@ result_t wuss_test(const char *resources) 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) @@ -785,12 +903,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) @@ -798,14 +916,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) @@ -821,7 +939,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) @@ -846,7 +964,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) @@ -854,13 +972,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) @@ -870,23 +988,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) @@ -901,13 +1019,13 @@ result_t wuss_test(const char *resources) printf("test: window_resize valid and too-small cases\n"); - rc = wuss_window_resize(win_a, (size2d_t) { 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, (size2d_t) { 50, 50 }); + rc = wuss_window_resize(win_a, SIZE2D(50, 50)); if (rc != result_OK) goto Failure; if (tc_a.open_count != 2) @@ -945,6 +1063,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_d, box_size(&box_d), + SIZE2D(0, 0), &win_d); if (rc != result_OK) goto Failure; @@ -956,7 +1075,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) @@ -964,21 +1083,21 @@ 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; + 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.handle = test_handle; delegate_e.task_data = &tc_e; box_e.x0 = 100; box_e.y0 = 0; @@ -992,13 +1111,14 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_e, 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.handle = test_handle; delegate_f.task_data = &tc_f; box_f.x0 = 130; box_f.y0 = 20; @@ -1012,29 +1132,30 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_f, 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; @@ -1057,15 +1178,15 @@ 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; + 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.handle = test_handle; delegate_h.task_data = &tc_h; box_h.x0 = 10; box_h.y0 = 10; @@ -1079,13 +1200,14 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_h, 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.handle = test_handle; delegate_g.task_data = &tc_g; box_g.x0 = 0; box_g.y0 = 0; @@ -1099,6 +1221,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_g, box_size(&box_g), + SIZE2D(0, 0), &win_g); if (rc != result_OK) goto Failure; @@ -1110,7 +1233,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; @@ -1120,7 +1243,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, (size2d_t) { 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) @@ -1139,14 +1262,14 @@ 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; + 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.handle = test_handle; delegate_i.task_data = &tc_i; box_i.x0 = 0; box_i.y0 = 0; @@ -1160,13 +1283,14 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_i, 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.handle = test_handle; delegate_j.task_data = &tc_j; box_j.x0 = 50; box_j.y0 = 0; @@ -1180,6 +1304,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_j, box_size(&box_j), + SIZE2D(0, 0), &win_j); if (rc != result_OK) goto Failure; @@ -1208,15 +1333,15 @@ 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; + 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.handle = test_handle; delegate_m.task_data = &tc_m; box_m.x0 = 10; box_m.y0 = 10; @@ -1230,6 +1355,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_m, box_size(&box_m), + SIZE2D(0, 0), &win_m); if (rc != result_OK) goto Failure; @@ -1238,14 +1364,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) @@ -1282,6 +1408,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_h, box_size(&box_h), + SIZE2D(0, 0), &win_h); if (rc != result_OK) goto Failure; @@ -1300,6 +1427,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_g, box_size(&box_g), + SIZE2D(0, 0), &win_g); if (rc != result_OK) goto Failure; @@ -1308,53 +1436,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; @@ -1362,17 +1490,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; @@ -1380,15 +1508,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; @@ -1400,6 +1528,53 @@ result_t wuss_test(const char *resources) wuss_window_close(win_h); } + printf("test: drag-resize stops at min_doc, not just WUSS_MIN_CONTENT\n"); + + { + test_task_t tc_m; + wuss_task_t delegate_m; + box_t box_m, content, visible; + wuss_window_t *win_m; + + tc_m.redraw_count = 0; + tc_m.mouse_count = 0; + delegate_m.handle = test_handle; + delegate_m.task_data = &tc_m; + + box_m.x0 = 10; box_m.y0 = 10; + box_m.x1 = 210; box_m.y1 = 210; /* 200x200 content, floored at 80x60 */ + rc = wuss_window_create(wuss, &box_m, "M", wuss_WINDOW_NONE, + wuss_NO_BACKGROUND, + &delegate_m, + SIZE2D(200, 200), SIZE2D(80, 60), + &win_m); + if (rc != result_OK) + goto Failure; + + 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"); { @@ -1422,7 +1597,7 @@ result_t wuss_test(const char *resources) rc = wuss_window_create(wuss, &box_t_win, "T", wuss_WINDOW_NONE, wuss_NO_BACKGROUND, &delegate_t, - (size2d_t) { 200, 200 }, &win_t); + SIZE2D(200, 200), SIZE2D(0, 0), &win_t); if (rc != result_OK) goto Failure; @@ -1431,7 +1606,7 @@ result_t wuss_test(const char *resources) goto Failure; /* toggle icon: top-right of the titlebar, inset by 3px, sized 20 - 2*3 - * (default titlebar height 20, WUSS_ICON_INSET 3), matching + * (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; @@ -1469,12 +1644,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; @@ -1536,12 +1711,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; @@ -1588,7 +1763,7 @@ result_t wuss_test(const char *resources) rc = wuss_window_create(wuss, &box_r, "R", wuss_WINDOW_NONE, wuss_NO_BACKGROUND, &delegate_r, - (size2d_t) { 70, 70 }, &win_r); + SIZE2D(70, 70), SIZE2D(0, 0), &win_r); if (rc != result_OK) goto Failure; @@ -1596,7 +1771,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) @@ -1628,12 +1803,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; @@ -1686,7 +1861,7 @@ result_t wuss_test(const char *resources) rc = wuss_window_create(wuss, &box_nb, "NB", wuss_WINDOW_NO_RESIZE_BLIT, wuss_NO_BACKGROUND, &delegate_nb, - (size2d_t) { 200, 200 }, &win_nb); + SIZE2D(200, 200), SIZE2D(0, 0), &win_nb); if (rc != result_OK) goto Failure; @@ -1716,12 +1891,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; @@ -1765,7 +1940,7 @@ result_t wuss_test(const char *resources) box_u.x1 = 120; box_u.y1 = 120; /* 40x40 content */ rc = wuss_window_create(wuss, &box_u, "U", wuss_WINDOW_NONE, wuss_NO_BACKGROUND, /* scrollbars on: carve.x/y = icon size */ &delegate_u, - (size2d_t) { 70, 70 }, &win_u); /* doc size well within the 200x200 screen: growth is doc-limited, not screen-limited */ + 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; @@ -1789,12 +1964,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; @@ -1822,12 +1997,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; @@ -1873,7 +2048,7 @@ result_t wuss_test(const char *resources) rc = wuss_window_create(wuss, &box_v, "V", wuss_WINDOW_NONE, wuss_NO_BACKGROUND, &delegate_v, - (size2d_t) { 200, 200 }, &win_v); + SIZE2D(200, 200), SIZE2D(0, 0), &win_v); if (rc != result_OK) goto Failure; @@ -1886,7 +2061,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; @@ -1904,12 +2079,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); /* 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; @@ -1942,12 +2117,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); /* V's toggle-size icon: shrink back */ + 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) 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; @@ -1987,6 +2162,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_k, box_size(&box_k), + SIZE2D(0, 0), &win_k); if (rc != result_OK) goto Failure; @@ -2007,6 +2183,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_l, box_size(&box_l), + SIZE2D(0, 0), &win_l); if (rc != result_OK) goto Failure; @@ -2019,7 +2196,7 @@ result_t wuss_test(const char *resources) wuss_window_get_visible_bounds(win_k, &visible); - rc = wuss_mouse_click(wuss, (point_t) { visible.x0 + 31, visible.y0 + 11 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* K's titlebar, clear of the close icon */ + 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) @@ -2027,7 +2204,7 @@ result_t wuss_test(const char *resources) before_k = tc_k.redraw_count; before_l = tc_l.redraw_count; - rc = wuss_mouse_move(wuss, (point_t) { visible.x0 + 45, visible.y0 + 21 }, &hit); + rc = wuss_mouse_move(wuss, POINT(visible.x0 + 45, visible.y0 + 21), &hit); if (rc != result_OK) goto Failure; if (hit != win_k) @@ -2041,7 +2218,7 @@ result_t wuss_test(const char *resources) * footprint, so the move fast path must still apply even * though K isn't topmost */ - rc = wuss_mouse_click(wuss, (point_t) { visible.x0 + 45, visible.y0 + 21 }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + 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; @@ -2073,6 +2250,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_m2, box_size(&box_m2), + SIZE2D(0, 0), &win_m2); if (rc != result_OK) goto Failure; @@ -2085,7 +2263,7 @@ result_t wuss_test(const char *resources) 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_t) { 80, 80 }); /* grow */ + rc = wuss_window_resize(win_m2, SIZE2D(80, 80)); /* grow */ if (rc != result_OK) goto Failure; @@ -2114,7 +2292,7 @@ result_t wuss_test(const char *resources) before2 = after2; - rc = wuss_window_resize(win_m2, (size2d_t) { 40, 40 }); /* shrink back */ + rc = wuss_window_resize(win_m2, SIZE2D(40, 40)); /* shrink back */ if (rc != result_OK) goto Failure; @@ -2163,6 +2341,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_nb2, box_size(&box_nb2), + SIZE2D(0, 0), &win_nb2); if (rc != result_OK) goto Failure; @@ -2173,7 +2352,7 @@ result_t wuss_test(const char *resources) wuss_window_get_visible_bounds(win_nb2, &before3); - rc = wuss_window_resize(win_nb2, (size2d_t) { 80, 80 }); /* grow */ + rc = wuss_window_resize(win_nb2, SIZE2D(80, 80)); /* grow */ if (rc != result_OK) goto Failure; @@ -2214,6 +2393,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_n, box_size(&box_n), + SIZE2D(0, 0), &win_n); if (rc != result_OK) goto Failure; @@ -2232,6 +2412,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_o, box_size(&box_o), + SIZE2D(0, 0), &win_o); if (rc != result_OK) goto Failure; @@ -2248,7 +2429,7 @@ result_t wuss_test(const char *resources) /* 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_t) { visible.x0 + 80, visible.y0 + 10 }); + 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); @@ -2316,6 +2497,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_b, box_size(&box_b), + SIZE2D(0, 0), &win_b); if (rc != result_OK) goto Failure; @@ -2334,6 +2516,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_a, box_size(&box_a), + SIZE2D(0, 0), &win_a); if (rc != result_OK) goto Failure; @@ -2350,8 +2533,8 @@ result_t wuss_test(const char *resources) /* Move B far enough right that its whole new footprint clears A. */ dx = 60; - wuss_window_move(win_b, (point_t) { visible_b_before.x0 + dx, - visible_b_before.y0 }); + 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 @@ -2417,6 +2600,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_b, box_size(&box_b), + SIZE2D(0, 0), &win_b); if (rc != result_OK) goto Failure; @@ -2435,6 +2619,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_a, box_size(&box_a), + SIZE2D(0, 0), &win_a); if (rc != result_OK) goto Failure; @@ -2450,8 +2635,8 @@ result_t wuss_test(const char *resources) * (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_t) { visible_b_before.x0, - visible_b_before.y0 + 5 }); + 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++) @@ -2495,6 +2680,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_b, box_size(&box_b), + SIZE2D(0, 0), &win_b); if (rc != result_OK) goto Failure; @@ -2513,6 +2699,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_a, box_size(&box_a), + SIZE2D(0, 0), &win_a); if (rc != result_OK) goto Failure; @@ -2539,8 +2726,8 @@ result_t wuss_test(const char *resources) * 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_t) { visible_b_before.x0, - visible_b_before.y0 + 25 }); + 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; @@ -2606,6 +2793,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_b, box_size(&box_b), + SIZE2D(0, 0), &win_b); if (rc != result_OK) goto Failure; @@ -2624,6 +2812,7 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_a, box_size(&box_a), + SIZE2D(0, 0), &win_a); if (rc != result_OK) goto Failure; @@ -2646,8 +2835,8 @@ result_t wuss_test(const char *resources) 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_t) { visible_b_before.x0 - 15, - visible_b_before.y0 - 15 }); + 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. */ @@ -2700,11 +2889,12 @@ result_t wuss_test(const char *resources) wuss_NO_BACKGROUND, &delegate_c, box_size(&box_c), + SIZE2D(0, 0), &win_c); 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 */ + 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) @@ -2712,7 +2902,7 @@ result_t wuss_test(const char *resources) wuss_window_close(win_c); - rc = wuss_mouse_move(wuss, (point_t) { 20, 20 }, &hit); + rc = wuss_mouse_move(wuss, POINT(20, 20), &hit); if (rc != result_OK) goto Failure; @@ -2733,7 +2923,7 @@ result_t wuss_test(const char *resources) rc = wuss_window_create(wuss, &box_s, "S", wuss_WINDOW_NONE, wuss_NO_BACKGROUND, &delegate_s, - (size2d_t) { 200, 200 }, &win_s); + SIZE2D(200, 200), SIZE2D(0, 0), &win_s); if (rc != result_OK) goto Failure; @@ -2745,7 +2935,7 @@ result_t wuss_test(const char *resources) wuss_window_get_content_bounds(win_s, &content_s); rc = wuss_mouse_click(wuss, - (point_t) { content_s.x0 + 5, content_s.y0 + 7 }, + POINT(content_s.x0 + 5, content_s.y0 + 7), wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); if (rc != result_OK) goto Failure; @@ -2755,13 +2945,13 @@ result_t wuss_test(const char *resources) goto Failure; /* a task adding the scroll offset itself would double-count it */ rc = wuss_mouse_click(wuss, - (point_t) { content_s.x0 + 5, content_s.y0 + 7 }, + 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_t) { content_s.x0 + 11, content_s.y0 + 13 }, + POINT(content_s.x0 + 11, content_s.y0 + 13), &hit); if (rc != result_OK) goto Failure; @@ -2771,7 +2961,7 @@ result_t wuss_test(const char *resources) tc_s.last_scroll_x = -1; tc_s.last_scroll_y = -1; rc = wuss_scroll(wuss, - (point_t) { content_s.x0 + 3, content_s.y0 + 4 }, + POINT(content_s.x0 + 3, content_s.y0 + 4), 1, &hit); if (rc != result_OK) goto Failure; @@ -2809,7 +2999,8 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NONE, /* all furniture present */ wuss_NO_BACKGROUND, &delegate_r, - (size2d_t) { 400, 400 }, + SIZE2D(400, 400), + SIZE2D(0, 0), &win_r); if (rc != result_OK) goto Failure; @@ -2819,7 +3010,7 @@ result_t wuss_test(const char *resources) content_r.y1 - content_r.y0 != box_r.y1 - box_r.y0) goto Failure; - rc = wuss_window_resize(win_r, (size2d_t) { 61, 47 }); + rc = wuss_window_resize(win_r, SIZE2D(61, 47)); if (rc != result_OK) goto Failure; @@ -2830,6 +3021,71 @@ result_t wuss_test(const char *resources) 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. */ + 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.handle = test_handle; + delegate_p.task_data = &tc_p[0]; + + for (k = 0; k < 4; k++) + { + rc = wuss_window_create_placed(wuss, + SIZE2D(40, 30), + "P", + wuss_WINDOW_NONE, + wuss_NO_BACKGROUND, + &delegate_p, + 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(wuss, + SIZE2D(40, 30), + "P", + wuss_WINDOW_NONE, + wuss_NO_BACKGROUND, + &delegate_p, + 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_task_stop sends wuss_EVENT_QUIT to each window's task\n"); tc_a.stop_count = 0; @@ -2860,3 +3116,175 @@ 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, NULL, 0, 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(wuss, &box_a, "toosmall", wuss_WINDOW_NONE, + wuss_NO_BACKGROUND, NULL, 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.handle = test_handle; delegate_a.task_data = &tc_a; + delegate_b.handle = test_handle; delegate_b.task_data = &tc_b; + + box_a.x0 = 0; box_a.y0 = 0; box_a.x1 = 100; box_a.y1 = 100; + rc = wuss_window_create(wuss, &box_a, "A", chromeless, + wuss_NO_BACKGROUND, &delegate_a, 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(wuss, &box_b, "B", chromeless, + wuss_NO_BACKGROUND, &delegate_b, 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: 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 index 252158cd..570425d0 100644 --- a/libraries/wuss/window/close.c +++ b/libraries/wuss/window/close.c @@ -16,12 +16,24 @@ void wuss_window_close(wuss_window_t *doomed) return; wuss = doomed->wuss; +#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; +#endif + + wuss__release_packed(doomed); wuss__invalidate_clipped(doomed, &doomed->visible); list_remove(&wuss->z_order, &doomed->link); +#ifdef WUSS_ICONS + wuss__icons_free(doomed); +#endif + free(doomed); } diff --git a/libraries/wuss/window/create-placed.c b/libraries/wuss/window/create-placed.c new file mode 100644 index 00000000..6088a289 --- /dev/null +++ b/libraries/wuss/window/create-placed.c @@ -0,0 +1,173 @@ +/* create-placed.c -- wuss - 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_t *wuss, + size2d_t size, + const char *title, + wuss_window_flags_t flags, + wuss_colour_t bg, + const wuss_task_t *task, + size2d_t doc, + size2d_t min_doc, + wuss_window_t **window) +{ + 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(wuss != NULL); + assert(window != NULL); + + 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(wuss, &content, title, flags, bg, task, + 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/window/create.c index 591da41e..11bb445b 100644 --- a/libraries/wuss/window/create.c +++ b/libraries/wuss/window/create.c @@ -17,6 +17,7 @@ result_t wuss_window_create(wuss_t *wuss, wuss_colour_t bg, const wuss_task_t *task, size2d_t doc, + size2d_t min_doc, wuss_window_t **window) { wuss_window_t *win; @@ -39,7 +40,7 @@ result_t wuss_window_create(wuss_t *wuss, 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; @@ -76,7 +77,17 @@ result_t wuss_window_create(wuss_t *wuss, win->scroll.x = 0; win->scroll.y = 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 + + box_reset(&win->packed); /* wuss_window_create_placed fills this in after */ if (task != NULL) win->task = *task; @@ -90,6 +101,7 @@ result_t wuss_window_create(wuss_t *wuss, } win->bg = bg; +#ifdef WUSS_FURNITURE if (title != NULL) { strncpy(win->title, title, WUSS_TITLE_MAX); @@ -99,6 +111,9 @@ 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); diff --git a/libraries/wuss/window/move.c b/libraries/wuss/window/move.c index ef0ee7d0..a23eaa17 100644 --- a/libraries/wuss/window/move.c +++ b/libraries/wuss/window/move.c @@ -25,8 +25,10 @@ static void translate_box(const box_t *box, int dx, int dy, box_t *out) * cycle, and rejecting those outright regressed plain corner-occlusion * drags into full fallback redraws. Only an actual cycle (i must precede j * and j must precede i) has no safe order and needs the fallback. */ -static int wuss__order_pieces(const box_t *clean, const box_t *dest, int n, - int *order) +static int wuss__order_pieces(const box_t *clean, + const box_t *dest, + int n, + int *order) { int adj[WUSS_MAX_INVALIDATE_PIECES][WUSS_MAX_INVALIDATE_PIECES]; int indeg[WUSS_MAX_INVALIDATE_PIECES]; @@ -78,6 +80,10 @@ void wuss_window_move(wuss_window_t *window, point_t p) box_t before, dirty, copied; int blit_failed; + /* 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); @@ -145,13 +151,15 @@ void wuss_window_move(wuss_window_t *window, point_t p) idx = order[i]; if (!screen_copy_rect(window->wuss->scr, &blit_src[idx], - (point_t) { blit_dest[idx].x0, blit_dest[idx].y0 }, + POINT(blit_dest[idx].x0, blit_dest[idx].y0), &copied)) { - /* The screen format doesn't support the blit at all (e.g. paletted): - * this fails identically for every piece, so it fails on the first - * one, before any blit has happened. Bail out; the caller's fallback - * full invalidate repairs the window either way. */ + /* screen_copy_rect refused this piece: either the screen format has no + * blit path (e.g. paletted -- fails on the first piece, before anything + * has moved) or this piece's source/dest lies off-screen, which can + * happen part-way through after earlier pieces have already blitted. + * Either way, bail: the pieces done so far are self-consistent and the + * caller's fallback full invalidate repaints the whole union. */ blit_failed = 1; break; } @@ -175,9 +183,28 @@ void wuss_window_move(wuss_window_t *window, point_t p) * 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. */ + * 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++) - wuss__invalidate_minus(window->wuss, &clean[i], &full_dest[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 diff --git a/libraries/wuss/window/resize.c b/libraries/wuss/window/resize.c index 46e6e7ff..bcaca693 100644 --- a/libraries/wuss/window/resize.c +++ b/libraries/wuss/window/resize.c @@ -39,10 +39,13 @@ result_t wuss_window_resize(wuss_window_t *window, size2d_t size) 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); + 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); + 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; @@ -75,10 +78,12 @@ result_t wuss_window_resize(wuss_window_t *window, size2d_t size) * 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. */ +#ifdef WUSS_FURNITURE if (window->visible.x1 - window->visible.x0 > before.x1 - before.x0 || window->visible.y1 - window->visible.y0 > before.y1 - before.y0) wuss__furniture_invalidate_for(window, &before); wuss__furniture_invalidate(window); +#endif } return result_OK; diff --git a/libraries/wuss/window/set-scroll.c b/libraries/wuss/window/set-scroll.c index 1543289e..e1371013 100644 --- a/libraries/wuss/window/set-scroll.c +++ b/libraries/wuss/window/set-scroll.c @@ -16,9 +16,11 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p) wuss__content_box(window, &content); +#ifdef WUSS_FURNITURE /* the scrollbar sausage position depends on scroll, so its well needs * redrawing too -- content invalidation alone never touches it */ wuss__furniture_invalidate(window); +#endif if (window->wuss->z_order.next == &window->link) { @@ -28,7 +30,7 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p) * 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)) + POINT(content.x0 - dx, content.y0 - dy), &copied)) { wuss__invalidate_minus(window->wuss, &content, &copied); return; diff --git a/resources/wuss/9tile.png b/resources/wuss/9tile.png new file mode 100644 index 00000000..304e2af1 Binary files /dev/null and b/resources/wuss/9tile.png differ diff --git a/tools/test_wrap_protos.py b/tools/test_wrap_protos.py new file mode 100644 index 00000000..abef8405 --- /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 = 80 + + +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 >80, 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 <80 -- 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 80 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_protos.py b/tools/wrap_protos.py new file mode 100644 index 00000000..03191596 --- /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=80) + 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())