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())