diff --git a/CLAUDE.md b/CLAUDE.md index 5f18cf77..ad68b665 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -51,6 +51,8 @@ Success prints `++ Tests completed in Ns: N of N tests passed.` New source/header files must be added by hand to the relevant `set(..._SOURCES ...)` list and, for public headers, to `PUBLIC_HEADERS`, in `CMakeLists.txt` — there is no globbing. +**Internal naming (see `wuss`).** Functions declared in a private `impl.h` and not part of the public header use a double-underscore prefix, e.g. `wuss__titlebar_height_for`; internal-only enum constants still use the module's normal single-underscore style (e.g. `wuss_WINDOW_STATE_TOGGLED`), matching public enums. Per-instance internal state that isn't part of the public appearance API (e.g. `struct wuss_window`'s toggled/maximised state) is kept as a bitflags enum with `wuss__window_*` accessor helpers rather than loose `int`/`bool` fields, leaving room to add flags without growing the struct. + **Error handling.** No exceptions; functions return `result_t` (`include/base/result.h`). Each module reserves a `result_BASE_` offset block and defines its own `result__*` codes starting from that base. Common generic codes (`result_OK`, `result_OOM`, `result_BAD_ARG`, etc.) live at `result_BASE_GENERIC`. Callers typically check `rc != result_OK` and propagate. **Debug/logging.** `include/base/debug.h` provides `logf_info/warning/error/fatal/abort`, plus `check(err)` (log-and-`goto failure`) and `sentinel` (unreachable-code marker), both of which are compiled out entirely when `NDEBUG` is set — don't rely on their side effects in release builds. diff --git a/CMakeLists.txt b/CMakeLists.txt index 50283d94..f9b0db89 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,7 +14,7 @@ endif() list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules/) -project(DPTLib VERSION 0.5.0 DESCRIPTION "DPT's C Library" LANGUAGES C) +project(DPTLib VERSION 0.6.0 DESCRIPTION "DPT's C Library" LANGUAGES C) # The values set in the toolchain file aren't available until this point. if(TARGET_RISCOS) @@ -28,6 +28,7 @@ if(CCACHE_FOUND) endif(CCACHE_FOUND) option(USE_FORTIFY "Use Fortify" OFF) +option(USE_ASAN "Build with AddressSanitizer and UndefinedBehaviorSanitizer" OFF) option(DPTLIB_IMAGES_READ_ONLY "Remove libpng write support" OFF) # Referencing CMAKE_TOOLCHAIN_FILE avoids a warning on rebuilds. @@ -40,7 +41,7 @@ add_subdirectory(libraries/fortify) add_library(DPTLib) set_target_properties(DPTLib PROPERTIES - VERSION 0.5.0 + VERSION 0.6.0 DESCRIPTION "DPT's Portable C Library" C_STANDARD 99 PREFIX "" # remove 'lib' prefix @@ -93,6 +94,7 @@ set(PUBLIC_HEADERS include/geom/line.h include/geom/packer.h include/geom/point.h + include/geom/size.h include/io/path.h include/io/stream-mem.h include/io/stream-mtfcomp.h @@ -110,6 +112,7 @@ set(PUBLIC_HEADERS include/utils/maths.h include/utils/pack.h include/utils/primes.h + include/wuss/task.h include/wuss/window.h include/wuss/wuss.h) @@ -253,6 +256,7 @@ set(GEOM_SOURCES libraries/geom/box/reset.c libraries/geom/box/round.c libraries/geom/box/round4.c + libraries/geom/box/size.c libraries/geom/box/translated.c libraries/geom/box/union.c libraries/geom/layout/layout.c @@ -331,6 +335,7 @@ set(WUSS_SOURCES 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 libraries/wuss/invalidate.c @@ -338,8 +343,8 @@ set(WUSS_SOURCES libraries/wuss/mouse-move.c libraries/wuss/redraw.c libraries/wuss/scroll.c - libraries/wuss/task-start.c - libraries/wuss/task-stop.c + libraries/wuss/task/start.c + libraries/wuss/task/stop.c libraries/wuss/window/at.c libraries/wuss/window/create.c libraries/wuss/window/close.c @@ -394,6 +399,11 @@ if(USE_FORTIFY) target_link_libraries(DPTLib PUBLIC Fortify) endif() +if(USE_ASAN) + target_compile_options(DPTLib PUBLIC -fsanitize=address,undefined -g -O0) + target_link_options(DPTLib PUBLIC -fsanitize=address,undefined) +endif() + if(DPTLIB_IMAGES_READ_ONLY) target_compile_definitions(DPTLib PRIVATE DPTLIB_IMAGES_READ_ONLY) endif() @@ -523,6 +533,7 @@ if(BUILD_TESTS) libraries/framebuf/bmfont/test/bmfont-test.c libraries/framebuf/composite/test/composite-test.c libraries/framebuf/curve/test/curve-test.c + libraries/framebuf/screen/test/screen-test.c libraries/geom/box/test/box-test.c libraries/geom/layout/test/layout-test.c libraries/geom/packer/test/packer-test.c @@ -534,6 +545,7 @@ if(BUILD_TESTS) libraries/datastruct/vector/test/vector-test.c libraries/wuss/test/tasks/ball.c libraries/wuss/test/tasks/blank.c + libraries/wuss/test/tasks/chars.c libraries/wuss/test/tasks/checker.c libraries/wuss/test/tasks/curve.c libraries/wuss/test/tasks/gradient.c diff --git a/README.md b/README.md index e42b101b..187fac83 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # DPTLib -version 0.5.0 +version 0.6.0 [![Build status](https://github.com/dpt/DPTLib/actions/workflows/ci.yml/badge.svg)](https://github.com/dpt/DPTLib/actions) diff --git a/apps/test/main.c b/apps/test/main.c index 9080cd2f..435b3b82 100644 --- a/apps/test/main.c +++ b/apps/test/main.c @@ -54,6 +54,7 @@ static const test_t tests[] = { "bmfont", bmfont_test }, { "composite", composite_test }, { "curve", curve_test }, + { "screen", screen_test }, { "box", box_test }, { "layout", layout_test }, diff --git a/docs/framebuf/bmfont.md b/docs/framebuf/bmfont.md index 19b5c2cd..2a9b94b3 100644 --- a/docs/framebuf/bmfont.md +++ b/docs/framebuf/bmfont.md @@ -2,15 +2,15 @@ "bmfont" is a sub-library of DPTLib for drawing proportionally spaced bitmap fonts. It reads font definitions from PNG files like this: -![Henry Font](../resources/bmfonts/henry-font.png) +![Henry Font](../resources/bmfonts/henry.png) or this: -![Digits Font](../resources/bmfonts/digits-font.png) +![Digits Font](../resources/bmfonts/digits.png) or even this: -![Tiny Font](../resources/bmfonts/tiny-font.png) +![Tiny Font](../resources/bmfonts/tiny.png) ...which have the glyphs laid out in a grid, with extra lines inserted, that define the advance widths. diff --git a/docs/wuss.md b/docs/wuss.md index 19ba7571..1d91fc58 100644 --- a/docs/wuss.md +++ b/docs/wuss.md @@ -20,38 +20,40 @@ result_t wuss_create(screen_t *scr, wuss_t **wuss); ``` -`font` and `palette` may both be NULL, for unlabelled titlebars and a built-in default palette respectively. `config` may be NULL for default titlebar height/colours. +`font` and `palette` may both be NULL, for unlabelled titlebars and a built-in default palette respectively. `config` may be NULL for default titlebar height/colours. `config->backdrop` sets a desktop background colour painted behind windows on every redraw, or `wuss_NO_BACKGROUND` (the default when `config` is NULL) to leave the background untouched and require the caller to repaint it itself. `wuss_get_font` reads back the font passed in (or NULL), for a task that wants to draw its own content in the same face as titlebars. Destroy with `wuss_destroy`, which also destroys any windows still open on it. ## Windows -Create a window with a content bounding box, optional title, appearance flags and a task delegate: +Create a window with a content bounding box, optional title, appearance flags, a content background and a task delegate: ```C result_t wuss_window_create(wuss_t *wuss, const box_t *content, const char *title, - wuss_window_flags_t flags, const wuss_task_t *task, - int doc_width, int doc_height, + wuss_window_flags_t flags, wuss_colour_t bg, + const wuss_task_t *task, + size2d_t doc, wuss_window_t **window); ``` -`doc_width`/`doc_height` are the virtual document extent behind the horizontal/vertical scrollbars' sausage proportion; pass `content`'s own width/height for a window with nothing to scroll. Set once at creation, immutable thereafter. +`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. Furniture is additional to `content`, not carved out of it: the window's content area always ends up exactly the box requested, and its on-screen footprint (`wuss_window_get_visible_bounds`) is `content` expanded outward by whatever furniture flags request — a titlebar above, and/or a 1px outline around all four sides. -`wuss_task_t` holds the task's event callback and its content background: +`wuss_task_t` holds the task's event callback: ```C typedef struct wuss_task { wuss_event_fn_t *handle; /* NULL => task receives no events */ void *task_data; - wuss_colour_t bg; /* filled by wuss before a redraw event, or wuss_NO_BACKGROUND */ } wuss_task_t; ``` -`wuss_task_make` builds one from `handle`/`task_data`/`bg`. +`wuss_task_start` builds one from `handle`/`task_data`. `flags` combines, by bitwise OR: @@ -64,8 +66,9 @@ wuss_task_t; - `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_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` apply regardless. +`wuss_WINDOW_NO_CLOSE`/`NO_BACK`/`NO_TOGGLE_SIZE` are ignored if `flags` includes `wuss_WINDOW_NO_TITLEBAR`; `NO_VSCROLL`/`NO_HSCROLL`/`NO_RESIZE`/`NO_RESIZE_BLIT` apply regardless. All furniture actions (back, toggle-size, resize-drag, scrollbar arrow/thumb) are handled entirely within Wuss via `wuss_mouse_click`/`wuss_mouse_move` — no new client events. @@ -111,19 +114,19 @@ Feed mouse events in with `wuss_mouse_click` (action `wuss_MOUSE_DOWN` or `wuss_ ## Scrolling -Each window carries a scroll offset, `(0, 0)` by default: the point in the task's virtual content space that appears at the content area's top-left. `wuss_window_set_scroll(window, x, y)` moves it (invalidating the content area so the next redraw picks it up); `wuss_window_get_scroll` reads it back. `wuss_scroll` applies this offset itself as Wuss's default scroll action, clamped to `doc_width`/`doc_height` (set at window creation), before also delivering `wuss_EVENT_SCROLL` to the task if it has a handle: +Each window carries a scroll offset, `(0, 0)` by default: the point in the task's virtual content space that appears at the content area's top-left. `wuss_window_set_scroll(window, x, y)` moves it (invalidating the content area so the next redraw picks it up); `wuss_window_get_scroll` reads it back. `wuss_scroll` applies this offset itself as Wuss's default scroll action, clamped to `doc` (set at window creation), before also delivering `wuss_EVENT_SCROLL` to the task if it has a handle: - window-local `x`/`y` delivered in mouse/scroll events (and expected in `wuss_window_invalidate`'s `local_box`) are in virtual content space, i.e. already shifted by the scroll offset. - a redraw event's `content` is still the on-screen (unscrolled) content box; a task reads the offset itself via `wuss_window_get_scroll` to work out which part of its content to paint there. ## Redrawing -- `wuss_redraw` repaints every window, back-to-front, unconditionally. +- `wuss_redraw` repaints every window, back-to-front, unconditionally, having first painted the configured backdrop colour (see Setup) behind them, if any. - `wuss_invalidate` / `wuss_window_invalidate` mark a screen-space or window-local region dirty; window management calls these automatically for its own changes, but a task must call one of them itself whenever its content changes on its own (e.g. an animation), passing the union of the old and new areas that need repainting. -- `wuss_redraw_dirty` repaints only the accumulated dirty region, then clears it. Wuss only repaints windows, not the background between/behind them, so a caller whose invalidation can expose background (e.g. after a window move) should clear that region itself first. -- `wuss_get_dirty` fetches the current accumulated dirty region without redrawing. +- `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. ## Limitations - No menus: `wuss_BUTTON_MENU` is defined and routed like any other button, but Wuss has no built-in menu widget. -- No overlapping-window damage tracking finer than each window's own bounding box. diff --git a/include/databases/digest-db.h b/include/databases/digest-db.h index b771ea41..e91eaca2 100644 --- a/include/databases/digest-db.h +++ b/include/databases/digest-db.h @@ -5,9 +5,9 @@ * * Digest database. * - * digestdb is a wrapper around an atom set specifically for holding - * (128-bit) digests. It is used by tagdb and filenamedb to share the cost of - * storing digest values by replacing 128-bit digests with smaller indices. + * digestdb is a wrapper around an atom set specifically for holding (128-bit) + * digests. It is used by tagdb and filenamedb to share the cost of storing + * digest values by replacing 128-bit digests with smaller indices. */ #ifndef DATABASES_DIGEST_DB_H diff --git a/include/databases/filename-db.h b/include/databases/filename-db.h index 86d78ea1..1a68715a 100644 --- a/include/databases/filename-db.h +++ b/include/databases/filename-db.h @@ -5,8 +5,8 @@ * * Filename database. * - * The filenamedb is an associative array which maps keys (such as digests) - * to filenames. The data is stored on disc. + * The filenamedb is an associative array which maps keys (such as digests) to + * filenames. The data is stored on disc. * * PrivateEye uses this to find out where an image lives given its digest. */ diff --git a/include/databases/pickle.h b/include/databases/pickle.h index 2dde0bd9..eaa48b19 100644 --- a/include/databases/pickle.h +++ b/include/databases/pickle.h @@ -6,12 +6,10 @@ * Storage of associative arrays. * * Its name borrowed from Python, this module provides pickle_pickle() and - * pickle_unpickle() which respectively serialise or deserialise an - * associative array to a file of the form: + * pickle_unpickle() which respectively serialise or deserialise an associative + * array to a file of the form: * - * # - * - * (zero or more) + * # (zero or more) * * When serialising, keys and values are read from through an abstract * pickle_reader_methods interface. They are then transformed into savable @@ -242,9 +240,9 @@ pickle_unformat_methods_t; /* ----------------------------------------------------------------------- */ /** - * Serialise associative array 'assocarr' to the file 'filename'. Interpret - * the contents of the associative array using the methods in 'reader'. - * Format the keys and values for storage using the methods in 'format'. + * Serialise associative array 'assocarr' to the file 'filename'. Interpret the + * contents of the associative array using the methods in 'reader'. Format the + * keys and values for storage using the methods in 'format'. * * \param[in] filename Filename to save to. * \param[in] assocarr Associative array to pickle. @@ -261,9 +259,9 @@ result_t pickle_pickle(const char *filename, void *opaque); /** - * Populate associative array 'assocarr' from the file 'filename'. Insert - * into the associative array using the methods in 'writer'. Parse - * the keys and values from storage using the methods in 'unformat'. + * Populate associative array 'assocarr' from the file 'filename'. Insert into + * the associative array using the methods in 'writer'. Parse the keys and + * values from storage using the methods in 'unformat'. * * \param[in] filename Filename to read from. * \param[in] assocarr Associative array to pickle. diff --git a/include/datastruct/atom.h b/include/datastruct/atom.h index d17c98b7..3d5b9e9f 100644 --- a/include/datastruct/atom.h +++ b/include/datastruct/atom.h @@ -6,16 +6,15 @@ * Indexed data block store. * * Atoms are indices assigned to blocks of stored data. Identical data blocks - * are assigned the same atom. Atoms belonging to the same set can be - * directly compared avoiding the need to memcmp, strcmp, or otherwise - * linearly compare the contents of the respective data blocks. + * are assigned the same atom. Atoms belonging to the same set can be directly + * compared avoiding the need to memcmp, strcmp, or otherwise linearly compare + * the contents of the respective data blocks. * - * Data blocks can be retrieved by quoting an atom to atom_get. This returns - * a pointer to the data block along with its length. + * Data blocks can be retrieved by quoting an atom to atom_get. This returns a + * pointer to the data block along with its length. * - * To avoid heap overhead, atoms are stored in a series of fixed-size pools - * of memory. The size of the pools may be specified when the set is first - * created. + * To avoid heap overhead, atoms are stored in a series of fixed-size pools of + * memory. The size of the pools may be specified when the set is first created. */ #ifndef DATASTRUCT_ATOM_H @@ -57,15 +56,13 @@ atom_set_t *atom_create(void); /** * Create a new atom set using the specified data sizes. * - * \param locpoolsz Size of a location pool, or zero for the default. - * Set this to the number of entries you typically expect to - * store. - * \param blkpoolsz Size of a block pool, or zero for the default. - * No inserted data block may be larger than this. - * Increasing this value will use fewer individual block - * pools, reducing heap overhead, at the expense of - * potentially greater wasted space should the block pool - * remain not fully allocated. + * \param locpoolsz Size of a location pool, or zero for the default. Set this + * to the number of entries you typically expect to store. + * \param blkpoolsz Size of a block pool, or zero for the default. No inserted + * data block may be larger than this. Increasing this value + * will use fewer individual block pools, reducing heap + * overhead, at the expense of potentially greater wasted space + * should the block pool remain not fully allocated. * * \return New atom set, or NULL if out of memory. */ @@ -117,8 +114,8 @@ void atom_delete(atom_set_t *set, atom_t atom); * * \param set Atom set. * \param atom Atom to retrieve. - * \param[out] length Length of data block, in bytes. - * NULL if length is not required. + * \param[out] length Length of data block, in bytes. NULL if length is not + * required. * * \return Data block. */ @@ -159,8 +156,8 @@ atom_t atom_for_block(atom_set_t *set, /** * Delete an existing atom specified by data block. * - * This is a convenience function equivalent to: - * atom_delete(set, atom_for_block(set, block, length)). + * This is a convenience function equivalent to: atom_delete(set, + * atom_for_block(set, block, length)). * * \param set Atom set. * \param block Data block to retrieve. diff --git a/include/datastruct/bitarr.h b/include/datastruct/bitarr.h index 33a81b55..56b082ff 100644 --- a/include/datastruct/bitarr.h +++ b/include/datastruct/bitarr.h @@ -5,9 +5,9 @@ * * Arrays of bits. * - * Bit arrays are an array of bits. They are of a fixed length and allocated - * by the client. The bit array library provides functions to manipulate bit - * arrays but not allocate them. + * Bit arrays are an array of bits. They are of a fixed length and allocated by + * the client. The bit array library provides functions to manipulate bit arrays + * but not allocate them. * * \see Bit Vector for manipulating a dynamically allocated bit array. * @@ -54,8 +54,7 @@ typedef unsigned int bitarr_elem_t; #define BITARR_MASK (BITARR_BITS - 1) /** - * Return the number of elements required to store the specified number of - * bits. + * Return the number of elements required to store the specified number of bits. */ #define BITARR_ELEMS(nbits) ((nbits + BITARR_BITS - 1) >> BITARR_SHIFT) @@ -63,15 +62,15 @@ typedef unsigned int bitarr_elem_t; * Declare a bit array with the specified number of bits. * * This uses the OSLib trick of declaring a macro to define a type and also - * declaring an 'equivalent' struct. In practice they're not actually - * equivalent as the former, bitarr_ARRAY, is an anonymous struct of a given - * length and the bitarr_t is a struct containing an array of 'UNKNOWN' - * length, which is actually defined as 1. + * declaring an 'equivalent' struct. In practice they're not actually equivalent + * as the former, bitarr_ARRAY, is an anonymous struct of a given length and the + * bitarr_t is a struct containing an array of 'UNKNOWN' length, which is + * actually defined as 1. * * This works out all right, letting us declare bit arrays by specifying the - * number of bits we require, but we end up needing to cast out declared - * entries to (bitarr_t *) when calling a 'real' function, e.g. bitarr_count - * which looks awkward. + * number of bits we require, but we end up needing to cast out declared entries + * to (bitarr_t *) when calling a 'real' function, e.g. bitarr_count which looks + * awkward. */ #define bitarr_ARRAY(nbits) \ struct { \ @@ -114,8 +113,7 @@ struct bitarr do { BITARR_OP(arr, bit, |=); } while (0) /** - * Clear a single bit. - * */ + * Clear a single bit. */ #define bitarr_clear(arr, bit) \ do { BITARR_OP(arr, bit, &= ~); } while (0) diff --git a/include/datastruct/cache.h b/include/datastruct/cache.h index 1041d9fa..a7983c82 100644 --- a/include/datastruct/cache.h +++ b/include/datastruct/cache.h @@ -46,8 +46,8 @@ typedef unsigned int cachekey_t; /** * Create a cache. * - * \param[in] config Pointer to cache parameters, or NULL for default - * cache parameters. + * \param[in] config Pointer to cache parameters, or NULL for default cache + * parameters. * \param[in] length Byte length of the cache to allocate. * \param[out] cache Returned pointer to the created cache. * @@ -67,8 +67,8 @@ void cache_destroy(cache_t *doomed); /** * Create a cache in a supplied block of memory. * - * \param[in] config Pointer to cache parameters, or NULL for default - * cache parameters. + * \param[in] config Pointer to cache parameters, or NULL for default cache + * parameters. * \param[in] block Pointer to memory to use. * \param[in] length Byte length of block. * \param[out] cache Returned pointer to the created cache. @@ -104,9 +104,8 @@ void *cache_get(cache_t *cache, cachekey_t key); * \param[in] key Key. * \param[in] data Pointer to data to store. * \param[in] length Length of data. - * \param[out] inserted Returned pointer to the inserted data, or NULL if - * not wanted. This valid until the next cache_put - * operation. + * \param[out] inserted Returned pointer to the inserted data, or NULL if not + * wanted. This valid until the next cache_put operation. * * \return Error indication. */ diff --git a/include/datastruct/hash.h b/include/datastruct/hash.h index e7ace3be..4d5b881f 100644 --- a/include/datastruct/hash.h +++ b/include/datastruct/hash.h @@ -5,8 +5,8 @@ * * Hash is an associative array. * - * The interface presently forces you to malloc all keys, and values passed - * in, yourself. + * The interface presently forces you to malloc all keys, and values passed in, + * yourself. */ #ifndef DATASTRUCT_HASH_H @@ -106,8 +106,8 @@ const void *hash_lookup(T *hash, const void *key); * Insert the specified key:value pair into the hash. * * The hash takes ownership of the key and value pointers. It will call the - * destroy functions passed to hash_create when the keys and values are to - * be destroyed. + * destroy functions passed to hash_create when the keys and values are to be + * destroyed. * * \param hash Hash. * \param key Key to insert. @@ -152,8 +152,8 @@ typedef int (hash_walk_callback_t)(const void *key, * \param cb Callback routine. * \param opaque Opaque pointer to pass to callback routine. * - * \return Error indication. - * \retval result_OK If the walk completed successfully. + * \return Error indication. \retval result_OK If the walk completed + * successfully. */ result_t hash_walk(const T *hash, hash_walk_callback_t *cb, void *opaque); @@ -168,9 +168,8 @@ result_t hash_walk(const T *hash, hash_walk_callback_t *cb, void *opaque); * \param[out] key Pointer to receive key. * \param[out] value Pointer to receive value. * - * \return Error indication. - * \retval result_OK If an element was found. - * \retval result_HASH_END If no elements remain. + * \return Error indication. \retval result_OK If an element was found. + * \retval result_HASH_END If no elements remain. */ result_t hash_walk_continuation(T *hash, int continuation, diff --git a/include/datastruct/vector.h b/include/datastruct/vector.h index 82755fa3..2ed1752a 100644 --- a/include/datastruct/vector.h +++ b/include/datastruct/vector.h @@ -3,14 +3,13 @@ /** * \file vector.h * - * Vector is an abstracted array which can be resized by both length and - * element width. + * Vector is an abstracted array which can be resized by both length and element + * width. * - * Elements are of a fixed size, stored contiguously and are addressed by - * index. + * Elements are of a fixed size, stored contiguously and are addressed by index. * * \warning If the vector is altered then pointers into the vector may be - * invalidated (should the block move when reallocated). + * invalidated (should the block move when reallocated). */ #ifndef DATASTRUCT_VECTOR_H @@ -83,8 +82,8 @@ result_t vector_set_length(vector_t *vector, unsigned int length); /* ----------------------------------------------------------------------- */ /** - * Reserve space for at least the specified number of elements in the - * specified vector. + * Reserve space for at least the specified number of elements in the specified + * vector. * * \param[in] vector Vector to change. * \param[in] need Required length. @@ -107,8 +106,8 @@ size_t vector_width(const vector_t *vector); /** * Change the byte width of element stored in the specified vector. * - * If the element width is reduced then any extra bytes are lost. If - * increased, then zeroes are inserted. + * If the element width is reduced then any extra bytes are lost. If increased, + * then zeroes are inserted. * * \param[in] vector Vector to change. * \param[in] width New element width. diff --git a/include/framebuf/bitmap.h b/include/framebuf/bitmap.h index 0863eee2..59305c41 100644 --- a/include/framebuf/bitmap.h +++ b/include/framebuf/bitmap.h @@ -7,10 +7,11 @@ #include "framebuf/colour.h" #include "framebuf/pixelfmt.h" #include "framebuf/span.h" +#include "geom/size.h" /** Common bitmap structure members (used for screens too). */ #define bitmap_COMMON_MEMBERS \ - int width, height; /**< Width and height of the bitmap in pixels. */ \ + size2d_t size; /**< Width and height of the bitmap in pixels. */ \ pixelfmt_t format; /**< Pixel format of the bitmap. */ \ int rowbytes; /**< Number of bytes per row of the bitmap. */ \ colour_t *palette; /**< Palette of the bitmap, or NULL. */ \ @@ -32,8 +33,7 @@ bitmap_t; * Initialise a previously allocated bitmap structure. * * \param[in] bm Bitmap to initialise. - * \param[in] width Width of the bitmap in pixels. - * \param[in] height Height of the bitmap in pixels. + * \param[in] size Width and height of the bitmap in pixels. * \param[in] fmt Pixel format of the bitmap. * \param[in] rowbytes Number of bytes per row of the bitmap. * \param[in] palette Palette of the bitmap, or NULL. @@ -41,8 +41,7 @@ bitmap_t; * \return \ref result_OK on success, or appropriate result code otherwise. */ result_t bitmap_init(bitmap_t *bm, - int width, - int height, + size2d_t size, pixelfmt_t fmt, int rowbytes, const colour_t *palette, @@ -88,7 +87,8 @@ result_t bitmap_load_png(bitmap_t *bm, const char *filename); result_t bitmap_save_png(const bitmap_t *bm, const char *filename); /** - * Convert the given bitmap into a different pixel format, allocating a new bitmap structure for the result. + * Convert the given bitmap into a different pixel format, allocating a new + * bitmap structure for the result. * * \param[in] bm Bitmap to convert. * \param[in] newfmt New pixel format. diff --git a/include/framebuf/bmfont.h b/include/framebuf/bmfont.h index cf41a2b5..e46cd9f4 100644 --- a/include/framebuf/bmfont.h +++ b/include/framebuf/bmfont.h @@ -38,6 +38,16 @@ void bmfont_destroy(bmfont_t *bmfont); */ void bmfont_get_info(bmfont_t *bmfont, int *width, int *height); +/** + * Read the number of glyphs in the specified bitmap font. Glyphs are laid out + * contiguously starting at ' ' (space, 0x20), so a char c has a glyph iff c >= + * ' ' and c < ' ' + bmfont_get_count(bmfont). + * + * \param[in] bmfont Bitmap font to query. + * \return Number of glyphs in the font. + */ +int bmfont_get_count(bmfont_t *bmfont); + /** * Measure the width of a string drawn with the specified font. * diff --git a/include/framebuf/curve.h b/include/framebuf/curve.h index 1ac2ef6a..ecc5dc63 100644 --- a/include/framebuf/curve.h +++ b/include/framebuf/curve.h @@ -11,7 +11,8 @@ /** * Return the point on the line defined by points `p0` and `p1` at time `t`. * - * This is a straight linear interpolation between the two points: there is no curvature. + * This is a straight linear interpolation between the two points: there is no + * curvature. * * \param[in] p0 Start point. * \param[in] p1 End point. @@ -115,7 +116,8 @@ point_t curve_bezier_point_on_cubic_r(point_t p0, fix16_t t); /** - * As for \ref curve_bezier_point_on_quartic but is written in terms of cubics (and in turn of quads). + * As for \ref curve_bezier_point_on_quartic but is written in terms of cubics + * (and in turn of quads). * * \param[in] p0 Start point. * \param[in] p1 Control point 1. @@ -133,7 +135,8 @@ point_t curve_bezier_point_on_quartic_r(point_t p0, fix16_t t); /** - * As for \ref curve_bezier_point_on_quintic but is written in terms of quartics (and in turn of cubics, etc.). + * As for \ref curve_bezier_point_on_quintic but is written in terms of quartics + * (and in turn of cubics, etc.). * * \param[in] p0 Start point. * \param[in] p1 Control point 1. @@ -170,9 +173,11 @@ void curve_bezier_cubic_f(point_t p0, point_t *points); /** - * As for \ref curve_bezier_cubic but uses forward differencing (fixed-point version). + * As for \ref curve_bezier_cubic but uses forward differencing (fixed-point + * version). * - * \warning This suffers from drift (the end point is not guaranteed to be reached) if `nsteps` isn't a power of 2. + * \warning This suffers from drift (the end point is not guaranteed to be + * reached) if `nsteps` isn't a power of 2. * * \param[in] p0 Start point. * \param[in] p1 Control point 1. diff --git a/include/framebuf/screen.h b/include/framebuf/screen.h index 4c1942a3..41988ede 100644 --- a/include/framebuf/screen.h +++ b/include/framebuf/screen.h @@ -24,23 +24,22 @@ screen_t; * Initialize a previously allocated screen structure. * * \param[in] scr Screen to initialize. - * \param[in] width Width of screen in pixels. - * \param[in] height Height of screen in pixels. + * \param[in] size Width and height of the screen in pixels. * \param[in] fmt Pixel format of the screen. * \param[in] rowbytes Number of bytes per row of the screen. * \param[in] palette Palette of the screen, or NULL. * \param[in] base Base address of the screen. */ void screen_init(screen_t *scr, - int width, - int height, + size2d_t size, pixelfmt_t fmt, int rowbytes, colour_t *palette, void *base); /** - * Initialize a previously allocated screen structure, for drawing to an existing bitmap. + * Initialize a previously allocated screen structure, for drawing to an + * existing bitmap. * * \param[in] scr Screen to initialize. * \param[in] bm Bitmap to draw to. @@ -72,13 +71,12 @@ void screen_draw_pixel(screen_t *scr, int x, int y, colour_t colour); * \param[in] scr Screen to draw upon. * \param[in] x X coordinate of leftmost point of rectangle. * \param[in] y Y coordinate of topmost point of rectangle. - * \param[in] width Width of rectangle. - * \param[in] height Height of rectangle. + * \param[in] size Width and height of rectangle. * \param[in] colour Colour of rectangle. */ void screen_draw_rect(screen_t *scr, int x, int y, - int width, int height, + size2d_t size, colour_t colour); /** @@ -96,13 +94,12 @@ void screen_draw_square(screen_t *scr, colour_t colour); /** - * Draws a bitmap, alpha-blending it against the screen where the bitmap - * has an alpha channel. On paletted screens, which have no linear channel - * bits to blend, this falls back to alpha-tested transparency instead - * (drawn at full strength, or not at all). + * Draws a bitmap, alpha-blending it against the screen where the bitmap has an + * alpha channel. On paletted screens, which have no linear channel bits to + * blend, this falls back to alpha-tested transparency instead (drawn at full + * strength, or not at all). * - * The bitmap is clipped to the screen's clip region. No scaling is - * performed. + * The bitmap is clipped to the screen's clip region. No scaling is performed. * * \param[in] scr Screen to draw upon. * \param[in] x X coordinate of leftmost point to draw bitmap at. @@ -112,34 +109,34 @@ void screen_draw_square(screen_t *scr, void screen_draw_bitmap(screen_t *scr, int x, int y, const bitmap_t *src); /** - * Copies a rectangular region of the screen to another position on the - * same screen (e.g. sliding an already-rendered window's pixels to a new - * position without asking its owner to redraw). Source and destination may - * overlap; copying is done in the correct row order to handle that safely. + * Copies a rectangular region of the screen to another position on the same + * screen (e.g. sliding an already-rendered window's pixels to a new position + * without asking its owner to redraw). Source and destination may overlap; + * copying is done in the correct row order to handle that safely. * * Both the source and destination are clipped to the screen's clip region, - * shrinking together so the copied area always maps source pixel to - * destination pixel 1:1. + * shrinking together so the copied area always maps source pixel to destination + * pixel 1:1. * * Callers must check the return value and fall back to a normal - * invalidate/redraw when it's false (e.g. out of memory, or an unknown - * pixel format), since a declined copy leaves the destination untouched. + * invalidate/redraw when it's false (e.g. out of memory, or an unknown pixel + * format), since a declined copy leaves the destination untouched. * * If "src" or the intended destination falls partly off-screen, the actual - * copied area shrinks to what both ends have in common on-screen: callers - * must invalidate whatever part of their intended (unclipped) destination - * falls outside "copied_dst", since it has no valid source pixels to have - * been copied from and so is left untouched, not merely stale. + * copied area shrinks to what both ends have in common on-screen: callers must + * invalidate whatever part of their intended (unclipped) destination falls + * outside "copied_dst", since it has no valid source pixels to have been copied + * from and so is left untouched, not merely stale. * * \param[in] scr Screen to copy within. * \param[in] src Screen-space region to copy from. * \param[in] dst Top-left of the destination. - * \param[out] copied_dst Set to the on-screen box actually copied to (may - * be smaller than intended if either end was - * partly off-screen). Pass NULL if not needed. - * Left unset if the copy was declined. - * \return True if the copy was performed, false if declined (unsupported - * pixel format). + * \param[out] copied_dst Set to the on-screen box actually copied to (may be + * smaller than intended if either end was partly + * off-screen). Pass NULL if not needed. Left unset if + * the copy was declined. + * \return True if the copy was performed, false if declined (unsupported pixel + * format). */ int screen_copy_rect(screen_t *scr, const box_t *src, @@ -165,7 +162,8 @@ void screen_draw_line(screen_t *scr, /** * Draws a line (fixed-point Wu version with anti-aliasing). * - * Coordinates are fixed point values of type `fix8_t`. Coordinates are inclusive. + * Coordinates are fixed point values of type `fix8_t`. Coordinates are + * inclusive. * * \param[in] scr Screen to draw upon. * \param[in] x0 X coordinate of first point of line. @@ -181,7 +179,8 @@ void screen_draw_line_wu_fix8(screen_t *scr, /** * Draws a line (floating point Wu version with anti-aliasing). * - * Coordinates are floating point values of type `float`. Coordinates are inclusive. + * Coordinates are floating point values of type `float`. Coordinates are + * inclusive. * * \param[in] scr Screen to draw upon. * \param[in] x0 X coordinate of first point of line. diff --git a/include/framebuf/span-registry.h b/include/framebuf/span-registry.h index c110ccc9..b1eb34ff 100644 --- a/include/framebuf/span-registry.h +++ b/include/framebuf/span-registry.h @@ -10,7 +10,8 @@ * Find an appropriate span for the specified pixel format. * * \param[in] fmt Required pixel format. - * \return A span, or NULL if no span is available for the specified pixel format. + * \return A span, or NULL if no span is available for the specified pixel + * format. */ const span_t *spanregistry_get(pixelfmt_t fmt); diff --git a/include/framebuf/span.h b/include/framebuf/span.h index 04e93158..724ff622 100644 --- a/include/framebuf/span.h +++ b/include/framebuf/span.h @@ -17,15 +17,17 @@ typedef void (span_copy_t)(void *dst, const void *src, int length); /** * Type of a "blend constant pixels" function. * - * This will blend the respective source pixels by the specified constant alpha value, writing the results to the destination buffer (like Porter-Duff Source Over Destination). + * This will blend the respective source pixels by the specified constant alpha + * value, writing the results to the destination buffer (like Porter-Duff Source + * Over Destination). * * \param[out] dst Destination pixels. * \param[in] src1 Source pixels 1. * \param[in] src2 Source pixels 2. * \param[in] length Length of pixels to blend. * \param[in] alpha Constant alpha value (0..255). - * \param[in] context Format-specific extra data (e.g. a palette for an - * indexed format); ignored where not needed, pass NULL. + * \param[in] context Format-specific extra data (e.g. a palette for an indexed + * format); ignored where not needed, pass NULL. */ typedef void (span_blendconst_t)(void *dst, const void *src1, @@ -37,7 +39,9 @@ typedef void (span_blendconst_t)(void *dst, /** * Type of a "blend array of pixels" function. * - * This will blend the respective source pixels by the specified alpha values, writing the results to the destination buffer (like Porter-Duff Source Over Destination). + * This will blend the respective source pixels by the specified alpha values, + * writing the results to the destination buffer (like Porter-Duff Source Over + * Destination). * * \param[out] dst Destination pixels. * \param[in] src1 Source pixels 1. @@ -54,7 +58,8 @@ typedef void (span_blendarray_t)(void *dst, /** * Defines a span. * - * A span is a group of functions that combine runs of pixels. They are keyed by pixel format. + * A span is a group of functions that combine runs of pixels. They are keyed by + * pixel format. */ typedef struct span { diff --git a/include/geom/box.h b/include/geom/box.h index f0530f29..a2fd0736 100644 --- a/include/geom/box.h +++ b/include/geom/box.h @@ -5,6 +5,8 @@ #include +#include "geom/size.h" + #ifdef __cplusplus extern "C" { @@ -27,17 +29,28 @@ typedef os_box box_t; #endif -/** Initialises a box to an invalid state that will still produce a valid result when intersected with. */ +/** + * Initialises a box to an invalid state that will still produce a valid result + * when intersected with. + */ #define BOX_INIT { INT_MAX, INT_MAX, INT_MIN, INT_MIN } /** Initialises a box from a position (x,y) and a size (w,h). */ #define BOX_POS_SIZE(x, y, w, h) { (x), (y), (x) + (w), (y) + (h) } +/** + * Returns the size of the box "b". + * + * \param[in] b The box to measure. + * \return The box's width and height. + */ +size2d_t box_size(const box_t *b); + /** * Reset the box to an invalid state. * - * This sets x0,y0 to INT_MAX and the x1,y1 to INT_MIN. This is an invalid - * box but will still produce a valid result when intersected with. + * This sets x0,y0 to INT_MAX and the x1,y1 to INT_MIN. This is an invalid box + * but will still produce a valid result when intersected with. * * \param[in] b The box to reset. */ @@ -82,11 +95,14 @@ int box_intersects(const box_t *a, const box_t *b); int box_intersection(const box_t *a, const box_t *b, box_t *c); /** - * Populates the box "clipped" with the sizes of the edges discarded when clipping box "b" against "a". + * Populates the box "clipped" with the sizes of the edges discarded when + * clipping box "b" against "a". * * \param[in] a The first box. * \param[in] b The second box. - * \param[out] clipped Not really a box, but one scalar per edge. Values are positive where "b" extends outside of "a", zero otherwise. + * \param[out] clipped Not really a box, but one scalar per edge. Values are + * positive where "b" extends outside of "a", zero + * otherwise. */ void box_clipped(const box_t *a, const box_t *b, box_t *clipped); diff --git a/include/geom/layout.h b/include/geom/layout.h index 73971a83..131c8768 100644 --- a/include/geom/layout.h +++ b/include/geom/layout.h @@ -61,7 +61,8 @@ layout_spec_t; * \param[in] nelements Number of layout elements given. * \param[out] boxes An array of boxes to be populated. * \param[in] nboxes Number of boxes available. - * \return \ref result_OK on success, result_LAYOUT_BUFFER_FULL if too few boxes were supplied, or appropriate result code otherwise. + * \return \ref result_OK on success, result_LAYOUT_BUFFER_FULL if too few boxes + * were supplied, or appropriate result code otherwise. */ result_t layout_place(const layout_spec_t *spec, const layout_element_t *elements, diff --git a/include/geom/line.h b/include/geom/line.h index 82b37ec5..c57849d8 100644 --- a/include/geom/line.h +++ b/include/geom/line.h @@ -11,7 +11,14 @@ extern "C" #include "geom/box.h" /** - * Clips the line (x0,y0)-(x1,y1) by box `clip` and returns the clipped points in `x0` and co. + * Clips the line (x0,y0)-(x1,y1) by box `clip` and returns the clipped points + * in `x0` and co. + * + * The returned points are rounded to the nearest integer position on the clip + * boundary, so they vary with the clip box given. Callers which step along the + * line incrementally (Bresenham, Wu, etc.) must not seed their error terms from + * them if the pixels drawn are to be independent of the clip box passed in; use + * the return value to reject and step from the original endpoints. * * \param[in] clip Rectangular clip region. * \param[in,out] x0 X coordinate of first point of line (modified). diff --git a/include/geom/packer.h b/include/geom/packer.h index c47525b8..ea966075 100644 --- a/include/geom/packer.h +++ b/include/geom/packer.h @@ -78,8 +78,8 @@ result_t packer_place_at(T *packer, const box_t *area); /** - * Places a box of dimensions (w,h) in the next free area determined by - * location 'loc'. + * Places a box of dimensions (w,h) in the next free area determined by location + * 'loc'. * * \param[in] packer Packer to place box. * \param[in] loc Direction to search from for the next available area. diff --git a/include/geom/size.h b/include/geom/size.h new file mode 100644 index 00000000..202f474b --- /dev/null +++ b/include/geom/size.h @@ -0,0 +1,13 @@ +/* size.h -- size type */ + +#ifndef GEOM_SIZE_H +#define GEOM_SIZE_H + +/** 2D size with integer dimensions. */ +typedef struct size2d +{ + int w, h; +} +size2d_t; + +#endif /* GEOM_SIZE_H */ diff --git a/include/io/path.h b/include/io/path.h index 4d92e330..8a12df5c 100644 --- a/include/io/path.h +++ b/include/io/path.h @@ -8,14 +8,17 @@ /** * Join 'leaf' with extension 'ext' according to the host convention. * - * Note: Returns a pointer to an internal static buffer of length `DPTLIB_MAXPATH`. + * Note: Returns a pointer to an internal static buffer of length + * `DPTLIB_MAXPATH`. */ const char *path_join_leafname(const char *leaf, const char *ext); /** - * Join 'root' with `nbranches` directory names according to the host convention. + * Join 'root' with `nbranches` directory names according to the host + * convention. * - * Note: Returns a pointer to an internal static buffer of length `DPTLIB_MAXPATH`. + * Note: Returns a pointer to an internal static buffer of length + * `DPTLIB_MAXPATH`. */ const char *path_join_filename(const char *root, int nbranches, ...); diff --git a/include/io/stream-mem.h b/include/io/stream-mem.h index b30c44d1..599f973e 100644 --- a/include/io/stream-mem.h +++ b/include/io/stream-mem.h @@ -19,7 +19,8 @@ extern "C" * * \param[in] block Block of memory to create the stream from. * \param[in] length Length of the block of memory in bytes. - * \param[out] s Pointer to a `stream_t` pointer to store the created stream. + * \param[out] s Pointer to a `stream_t` pointer to store the created + * stream. * \return \ref result_OK on success, or appropriate result code otherwise. */ result_t stream_mem_create(const unsigned char *block, diff --git a/include/io/stream-mtfcomp.h b/include/io/stream-mtfcomp.h index 0f0ec2f5..ffe7f6de 100644 --- a/include/io/stream-mtfcomp.h +++ b/include/io/stream-mtfcomp.h @@ -16,7 +16,8 @@ extern "C" * * \param[in] input The input stream. * \param[in] bufsz The buffer size. - * \param[out] s Pointer to a `stream_t` pointer to store the created stream. + * \param[out] s Pointer to a `stream_t` pointer to store the created + * stream. * \return \ref result_OK on success, or appropriate result code otherwise. */ result_t stream_mtfcomp_create(stream_t *input, int bufsz, stream_t **s); @@ -26,7 +27,8 @@ result_t stream_mtfcomp_create(stream_t *input, int bufsz, stream_t **s); * * \param[in] input The input stream. * \param[in] bufsz The buffer size. - * \param[out] s Pointer to a `stream_t` pointer to store the created stream. + * \param[out] s Pointer to a `stream_t` pointer to store the created + * stream. * \return \ref result_OK on success, or appropriate result code otherwise. */ result_t stream_mtfdecomp_create(stream_t *input, int bufsz, stream_t **s); diff --git a/include/io/stream-packbits.h b/include/io/stream-packbits.h index 77b9af18..836066bb 100644 --- a/include/io/stream-packbits.h +++ b/include/io/stream-packbits.h @@ -16,7 +16,8 @@ extern "C" * * \param[in] input Input stream. * \param[in] bufsz Buffer size in bytes (0 for a sensible default). - * \param[out] s Pointer to a `stream_t` pointer to store the created stream. + * \param[out] s Pointer to a `stream_t` pointer to store the created + * stream. * \return \ref result_OK on success, or appropriate result code otherwise. */ result_t stream_packbitscomp_create(stream_t *input, int bufsz, stream_t **s); @@ -26,7 +27,8 @@ result_t stream_packbitscomp_create(stream_t *input, int bufsz, stream_t **s); * * \param[in] input Input stream. * \param[in] bufsz Buffer size in bytes (0 for a sensible default). - * \param[out] s Pointer to a `stream_t` pointer to store the created stream. + * \param[out] s Pointer to a `stream_t` pointer to store the created + * stream. * \return \ref result_OK on success, or appropriate result code otherwise. */ result_t stream_packbitsdecomp_create(stream_t *input, int bufsz, stream_t **s); diff --git a/include/io/stream-stdio.h b/include/io/stream-stdio.h index 80e245a9..71bcd4f9 100644 --- a/include/io/stream-stdio.h +++ b/include/io/stream-stdio.h @@ -19,7 +19,8 @@ extern "C" * * \param[in] f File to create the stream from. * \param[in] bufsz Buffer size in bytes (0 for a sensible default). - * \param[out] s Pointer to a `stream_t` pointer to store the created stream. + * \param[out] s Pointer to a `stream_t` pointer to store the created + * stream. * \return \ref result_OK on success, or appropriate result code otherwise. */ result_t stream_stdio_create(FILE *f, int bufsz, stream_t **s); diff --git a/include/io/stream.h b/include/io/stream.h index dbcb8035..7e676fd4 100644 --- a/include/io/stream.h +++ b/include/io/stream.h @@ -3,8 +3,7 @@ /** * \file Stream (interface). * - * A stream is a generic interface which can be used to wrap sources of - * bytes. + * A stream is a generic interface which can be used to wrap sources of bytes. * * Single byte and block operations are supported. Byte access is efficient: * implemented as a macro. diff --git a/include/test/all-tests.h b/include/test/all-tests.h index c3319030..9986c3a7 100644 --- a/include/test/all-tests.h +++ b/include/test/all-tests.h @@ -25,7 +25,8 @@ extern testfn_t pickle_test, /* framebuf */ extern testfn_t bmfont_test, composite_test, - curve_test; + curve_test, + screen_test; /* geom */ extern testfn_t box_test, diff --git a/include/text/txtfmt.h b/include/text/txtfmt.h index efd19407..ab520886 100644 --- a/include/text/txtfmt.h +++ b/include/text/txtfmt.h @@ -3,11 +3,10 @@ /** * \file txtfmt.h * - * Word-wraps a string to a given character width. Wraps at character - * widths, not measured widths, so works best for monospaced text. + * Word-wraps a string to a given character width. Wraps at character widths, + * not measured widths, so works best for monospaced text. * - * - Breaks at spaces. - * - Forces a newline at \\n or \\r. + * - Breaks at spaces. - Forces a newline at \\n or \\r. */ #ifndef DATASTRUCT_TXTFMT_H @@ -81,8 +80,8 @@ int txtfmt_get_nlines(const txtfmt_t *tx); /** * Returns the wrapped width of a txtfmt. * - * e.g. If you wrap some text containing a word 10 characters long it'll - * never get any thinner than 10. + * e.g. If you wrap some text containing a word 10 characters long it'll never + * get any thinner than 10. * * \param[in] tx Txtfmt to query. * @@ -93,8 +92,8 @@ int txtfmt_get_wrapped_width(const txtfmt_t *tx); /** * Retrieve a line produced by the last wrap. * - * The returned pointer refers into the txtfmt's own copy of the string and - * is valid until the next call to txtfmt_wrap or txtfmt_destroy. It is not + * The returned pointer refers into the txtfmt's own copy of the string and is + * valid until the next call to txtfmt_wrap or txtfmt_destroy. It is not * NUL-terminated: use the returned length. * * \param[in] tx Txtfmt to query. @@ -112,8 +111,8 @@ result_t txtfmt_get_line(const txtfmt_t *tx, /* ----------------------------------------------------------------------- */ /** - * Print the wrapped text via printf, including line numbers (for testing - * and debugging). + * Print the wrapped text via printf, including line numbers (for testing and + * debugging). * * \param[in] tx Txtfmt to print. * diff --git a/include/utils/array.h b/include/utils/array.h index 917dcebb..eb5072b7 100644 --- a/include/utils/array.h +++ b/include/utils/array.h @@ -19,8 +19,8 @@ extern "C" /* ----------------------------------------------------------------------- */ /** - * Signifies an array of unknown length, e.g. when used as the size of an - * array which is the final member of a struct. + * Signifies an array of unknown length, e.g. when used as the size of an array + * which is the final member of a struct. */ #define UNKNOWN 1 @@ -80,8 +80,8 @@ void array_squeeze2(unsigned char *base, * * Presently the growth strategy is doubling. * - * 'block' can be NULL to perform an initial alloc. - * Start with used == allocated == 0. + * 'block' can be NULL to perform an initial alloc. Start with used == allocated + * == 0. * * \param block Pointer to pointer to block. Updated on success. * \param elemsize Element size in bytes. diff --git a/include/utils/pack.h b/include/utils/pack.h index c7f56a7a..43bf89a2 100644 --- a/include/utils/pack.h +++ b/include/utils/pack.h @@ -5,9 +5,9 @@ * * Structure packing and unpacking routines. * - * Inspired by printf, scanf and the Python 'struct' module these allow a - * string composed of formatting character to specify how data should be - * marshalled into memory. + * Inspired by printf, scanf and the Python 'struct' module these allow a string + * composed of formatting character to specify how data should be marshalled + * into memory. */ #ifndef UTILS_PACK_H @@ -29,22 +29,21 @@ extern "C" * * The format string argument accepts the following format characters: * - * - 'c' to pack into 8 bits (notional char) - * - 's' to pack into 16 bits (notional short) - * - 'i' to pack into 32 bits (notional int) - * - 'q' to pack into 64 bits (notional long long 'quad') + * - 'c' to pack into 8 bits (notional char) - 's' to pack into 16 bits + * (notional short) - 'i' to pack into 32 bits (notional int) - 'q' to pack + * into 64 bits (notional long long 'quad') * * Each format character may be preceded by a count. * * * Examples: * - * n = pack(outbuf, "ccc", 1, 2, 3); ("ccc" can also be written "3c") - * n = pack(outbuf, "2si", 0x2000, 12345, 1 << 31); + * n = pack(outbuf, "ccc", 1, 2, 3); ("ccc" can also be written "3c") n = + * pack(outbuf, "2si", 0x2000, 12345, 1 << 31); * * - * Using '*' instead of a count invokes array mode: the next argument is - * used as an array length and the next after that as an array base pointer. + * Using '*' instead of a count invokes array mode: the next argument is used as + * an array length and the next after that as an array base pointer. * * Example: * @@ -63,8 +62,8 @@ size_t pack(unsigned char *outbuf, const char *fmt, ...); /** * Structure unpacking. * - * The arguments are unpacked from 'buf' according to the format string - * 'fmt' using little-endian byte order. + * The arguments are unpacked from 'buf' according to the format string 'fmt' + * using little-endian byte order. * * \see pack for a description of the format string. * @@ -75,8 +74,8 @@ size_t pack(unsigned char *outbuf, const char *fmt, ...); * Retrieves three characters and an int from 'inbuf'. * * - * Using '*' instead of a count invokes array mode: the next argument is - * used as an array length and the next after that as an array base pointer. + * Using '*' instead of a count invokes array mode: the next argument is used as + * an array length and the next after that as an array base pointer. * * Example: * @@ -88,12 +87,10 @@ size_t pack(unsigned char *outbuf, const char *fmt, ...); * Additionally, unpack can specify different source and destination sizes by * prefixing a formatting character with a source size qualifier: * - * - 'b' - byte - * - 'h' - half-word - * - 'w' - word - * - 'd' - double word + * - 'b' - byte - 'h' - half-word - 'w' - word - 'd' - double word * - * (Note that these specifiers are all different than the formatting characters). + * (Note that these specifiers are all different than the formatting + * characters). * * With these qualifers, sign becomes important. You can write CSIQ for unsigned * arguments, or csiq for signed arguments. @@ -109,8 +106,7 @@ size_t pack(unsigned char *outbuf, const char *fmt, ...); * * unpack copes with different endian formats. Prefix the string with: * - * - '<' to unpack little endian data - * - '>' to unpack big endian data + * - '<' to unpack little endian data - '>' to unpack big endian data * * The default is [ought to be] platform dependent. * diff --git a/include/wuss/task.h b/include/wuss/task.h new file mode 100644 index 00000000..b3cd3942 --- /dev/null +++ b/include/wuss/task.h @@ -0,0 +1,156 @@ +/* task.h -- wuss task API */ + +/** + * \file task.h + * + * A Wuss task: the content delegate a window hands its drawing and input + * events to, and the events themselves. + */ + +#ifndef WUSS_TASK_H +#define WUSS_TASK_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "base/result.h" +#include "geom/box.h" +#include "geom/point.h" +#include "framebuf/screen.h" + +#include "wuss/wuss.h" + +/* ----------------------------------------------------------------------- */ + +/** Which kind of event a wuss_event_t carries; more will be added over time. */ +typedef enum wuss_event_kind +{ + wuss_EVENT_IDLE, /**< Wuss has finished its pending tasks. */ + wuss_EVENT_REDRAW, /**< Part of the window's content needs repainting. */ + wuss_EVENT_OPEN, /**< Window moved or resized. */ + wuss_EVENT_CLOSE, /**< Close icon clicked; Wuss takes no action itself. */ + wuss_EVENT_MOUSE, /**< Button down/up over the window's content. */ + wuss_EVENT_SCROLL, /**< Mouse wheel used over the window's content. */ + wuss_EVENT_QUIT /**< Task shutting down, via wuss_task_stop. */ +} +wuss_event_kind_t; + +/** + * An event delivered to a task's handle callback. Only the union member + * matching \c kind is valid. + */ +typedef struct wuss_event +{ + wuss_event_kind_t kind; + union + { + /** wuss_EVENT_REDRAW: called with scr->clip already set to the + * on-screen, clipped content area. bounds and scroll are exactly what + * wuss_window_get_content_bounds/wuss_window_get_scroll would return, + * passed through so tasks don't need to call back into Wuss on every + * redraw. */ + struct + { + screen_t *scr; + + /** + * The region that actually needs repainting, screen space; a subset of + * bounds. Tasks should only touch pixels within this box. + */ + const box_t *content; + + /** + * The window's full (unclipped) content-area box, screen space, as per + * wuss_window_get_content_bounds; for converting screen position to + * document position. + */ + const box_t *bounds; + + /** Current scroll offset, as per wuss_window_get_scroll. */ + point_t scroll; + } + redraw; + + /** wuss_EVENT_MOUSE: point is in virtual content space -- the window's + * scroll offset has already been added, so the task must not add it + * again. With a scroll offset of (0,0) this is the same as window-local + * content coordinates, where the content area's top-left is (0,0). + * button is meaningful for DOWN/UP. */ + struct + { + wuss_mouse_action_t action; + point_t point; + wuss_button_t button; + } + mouse; + + /** wuss_EVENT_SCROLL: point is window-local content coordinates, as + * per mouse. delta's sign and units are as passed to wuss_scroll. */ + struct + { + point_t point; + int delta; + } + scroll; + + /* wuss_EVENT_IDLE, wuss_EVENT_CLOSE, wuss_EVENT_QUIT and + * wuss_EVENT_OPEN carry no data. */ + } + data; +} +wuss_event_t; + +/** + * Task event callback. + * + * \param[in] window The window receiving the event. + * \param[in] event The event; see wuss_event_t. + * \param[in] task_data As passed to wuss_window_create. + * \return \ref result_OK on success, else an appropriate result code. + */ +typedef result_t (wuss_event_fn_t)(wuss_window_t *window, + const wuss_event_t *event, + void *task_data); + +/** A window's content delegate. Copied by value into the window at creation. */ +typedef struct wuss_task +{ + /** + * NULL => task receives no events; Wuss still fills the content background + * per wuss_window_create's bg. + */ + wuss_event_fn_t *handle; + + void *task_data; +} +wuss_task_t; + +/** + * Build a wuss_task_t from its fields. + * + * \param[in] handle Event callback, or NULL for a task that receives no + * events. + * \param[in] task_data Opaque pointer passed back to the callback. + * \return The populated task. + */ +wuss_task_t wuss_task_start(wuss_event_fn_t *handle, + void *task_data); + +/** + * Notify a window's task that it is shutting down, via wuss_EVENT_QUIT. Not + * called automatically by wuss_window_close; call it first if the task needs + * notice before its window is torn down. + * + * \param[in] window Window whose task should be stopped. + * \return \ref result_OK on success, else the result returned by the task's + * handle callback. + */ +result_t wuss_task_stop(wuss_window_t *window); + +#ifdef __cplusplus +} +#endif + +#endif /* WUSS_TASK_H */ diff --git a/include/wuss/window.h b/include/wuss/window.h index 255564d9..60762c05 100644 --- a/include/wuss/window.h +++ b/include/wuss/window.h @@ -3,8 +3,8 @@ /** * \file window.h * - * A Wuss window: creation, destruction, positioning, sizing and task - * delegation of content drawing and mouse handling. + * A Wuss window: creation, destruction, positioning, sizing and task delegation + * of content drawing and mouse handling. */ #ifndef WUSS_WINDOW_H @@ -18,164 +18,58 @@ extern "C" #include "base/result.h" #include "geom/box.h" #include "geom/point.h" +#include "geom/size.h" #include "framebuf/screen.h" +#include "wuss/task.h" #include "wuss/wuss.h" /* ----------------------------------------------------------------------- */ -/** Which kind of event a wuss_event_t carries; more will be added over time. */ -typedef enum wuss_event_kind -{ - wuss_EVENT_IDLE, /**< Wuss has finished its pending tasks. */ - wuss_EVENT_REDRAW, /**< Part of the window's content needs repainting. */ - wuss_EVENT_OPEN, /**< Window moved or resized. */ - wuss_EVENT_CLOSE, /**< Close icon clicked; Wuss takes no action itself. */ - wuss_EVENT_MOUSE, /**< Button down/up over the window's content. */ - wuss_EVENT_SCROLL, /**< Mouse wheel used over the window's content. */ - wuss_EVENT_QUIT /**< Task shutting down, via wuss_task_stop. */ -} -wuss_event_kind_t; - -/** - * An event delivered to a task's handle callback. Only the union member - * matching \c kind is valid. - */ -typedef struct wuss_event -{ - wuss_event_kind_t kind; - union - { - /** wuss_EVENT_REDRAW: called with scr->clip already set to the - * on-screen, clipped content area. bounds and scroll are exactly what - * wuss_window_get_content_bounds/wuss_window_get_scroll would return, - * passed through so tasks don't need to call back into Wuss on every - * redraw. */ - struct - { - screen_t *scr; - const box_t *content; /**< The region that actually needs repainting, screen space; a subset of bounds. Tasks should only touch pixels within this box. */ - const box_t *bounds; /**< The window's full (unclipped) content-area box, screen space, as per wuss_window_get_content_bounds; for converting screen position to document position. */ - point_t scroll; /**< Current scroll offset, as per wuss_window_get_scroll. */ - } - redraw; - - /** wuss_EVENT_MOUSE: point is window-local content coordinates (the - * content area's top-left is (0,0)). button is meaningful for - * DOWN/UP. */ - struct - { - wuss_mouse_action_t action; - point_t point; - wuss_button_t button; - } - mouse; - - /** wuss_EVENT_SCROLL: point is window-local content coordinates, as - * per mouse. delta's sign and units are as passed to wuss_scroll. */ - struct - { - point_t point; - int delta; - } - scroll; - - /* wuss_EVENT_IDLE, wuss_EVENT_CLOSE, wuss_EVENT_QUIT and - * wuss_EVENT_OPEN carry no data. */ - } - data; -} -wuss_event_t; - -/** - * Task event callback. - * - * \param[in] window The window receiving the event. - * \param[in] event The event; see wuss_event_t. - * \param[in] task_data As passed to wuss_window_create. - * \return \ref result_OK on success, else an appropriate result code. - */ -typedef result_t (wuss_event_fn_t)(wuss_window_t *window, - const wuss_event_t *event, - void *task_data); - -/** A window's content delegate. Copied by value into the window at creation. */ -typedef struct wuss_task -{ - wuss_event_fn_t *handle; /**< NULL => task receives no events; Wuss still fills the content background per bg. */ - void *task_data; - wuss_colour_t bg; /**< Content background, filled by Wuss before redraw is called, or wuss_NO_BACKGROUND for the task to draw its own background (avoids a redundant fill behind an opaque task). */ -} -wuss_task_t; - -/** - * Build a wuss_task_t from its fields. - * - * \param[in] handle Event callback, or NULL for a task that receives no events. - * \param[in] task_data Opaque pointer passed back to the callback. - * \param[in] bg Content background, or wuss_NO_BACKGROUND. - * \return The populated task. - */ -wuss_task_t wuss_task_start(wuss_event_fn_t *handle, - void *task_data, - wuss_colour_t bg); - -/** - * Notify a window's task that it is shutting down, via wuss_EVENT_QUIT. - * Not called automatically by wuss_window_close; call it first if the - * task needs notice before its window is torn down. - * - * \param[in] window Window whose task should be stopped. - * \return \ref result_OK on success, else the result returned by the - * task's handle callback. - */ -result_t wuss_task_stop(wuss_window_t *window); - -/** - * Broadcast a wuss_EVENT_IDLE event to every window's task, in z-order. - * Intended to be called once per main-loop iteration, after other pending - * input has been handled, so tasks can drive their own animation/timers - * without the caller stepping each one individually. - * - * \param[in] wuss Window manager whose windows' tasks should go idle. - * \return \ref result_OK on success, else the first non-OK result returned - * by a task's handle callback. - */ -result_t wuss_idle(wuss_t *wuss); - /** * Create a window. * - * Furniture (titlebar/outline) is added outside \p content, not carved out - * of it: before clamping, the window's content area is exactly \p content, - * and its on-screen footprint (see wuss_window_get_visible_bounds) is - * \p content expanded outward by whatever furniture flags request. If that - * footprint would then fall off the top or left edge of the screen, the - * window (content included) is nudged right/down just enough to bring it - * flush with the edge, so the titlebar/close icon stay reachable; a window - * wider or taller than the screen keeps its top-left corner on-screen - * instead. The bottom/right edges are not clamped. + * Furniture (titlebar/outline) is added outside \p content, not carved out of + * it: before clamping, the window's content area is exactly \p content, and its + * on-screen footprint (see wuss_window_get_visible_bounds) is \p content + * expanded outward by whatever furniture flags request. If that footprint would + * then fall off the top or left edge of the screen, the window (content + * included) is nudged right/down just enough to bring it flush with the edge, + * so the titlebar/close icon stay reachable; a window wider or taller than the + * screen keeps its top-left corner on-screen instead. The bottom/right edges + * are not clamped. * * \param[in] wuss Window manager to create the window on. - * \param[in] content Requested content-area bounds, screen space. Copied in. - * \param[in] title Titlebar label, or NULL for none. Copied in, truncated if too long. Ignored if flags includes wuss_WINDOW_NO_TITLEBAR. - * \param[in] flags Appearance flags, e.g. wuss_WINDOW_NO_TITLEBAR / wuss_WINDOW_NO_OUTLINE, OR'd together, or wuss_WINDOW_NONE for the default furniture. - * \param[in] task Content delegate. Copied in. May be NULL for a window with no content handling. - * \param[in] doc_width Virtual document width, for the horizontal scrollbar's sausage proportion; pass content's own width for a window with nothing to scroll. - * \param[in] doc_height Virtual document height, for the vertical scrollbar's sausage proportion; pass content's own height for a window with nothing to scroll. + * \param[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. * \return \ref result_OK on success, \ref result_WUSS_TOO_SMALL if content's - * width or height is not positive, \ref result_WUSS_BAD_COLOUR if - * task->bg is out of range for the palette, or another - * appropriate result code. + * 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. */ 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, - int doc_width, - int doc_height, + size2d_t doc, wuss_window_t **window); /** @@ -197,12 +91,11 @@ void wuss_window_move(wuss_window_t *window, point_t p); * Resize a window's content area, preserving its top-left position. * * \param[in] window Window to resize. - * \param[in] width New content width. - * \param[in] height New content height. - * \return \ref result_OK on success, \ref result_WUSS_TOO_SMALL if width or - * height is not positive. + * \param[in] size New content size. + * \return \ref result_OK on success, \ref result_WUSS_TOO_SMALL if size's width + * or height is not positive. */ -result_t wuss_window_resize(wuss_window_t *window, int width, int height); +result_t wuss_window_resize(wuss_window_t *window, size2d_t size); /** * Move a window to one end of the z-order. @@ -223,8 +116,8 @@ void wuss_window_get_visible_bounds(const wuss_window_t *window, box_t *visible); /** - * Fetch a window's current content-area bounds, screen space (as requested - * at creation, or adjusted by a subsequent move/resize; excludes any + * Fetch a window's current content-area bounds, screen space (as requested at + * creation, or adjusted by a subsequent move/resize; excludes any * titlebar/outline furniture). Useful for computing invalidation regions * outside of a redraw callback. * @@ -235,29 +128,28 @@ void wuss_window_get_content_bounds(const wuss_window_t *window, box_t *content); /** - * Mark a region of a window's content as dirty, for the next - * wuss_redraw_dirty call. Content changes are opaque to Wuss, so tasks - * must call this themselves (e.g. the union of an animated element's old - * and new positions). + * Mark a region of a window's content as dirty, for the next wuss_redraw_dirty + * call. Content changes are opaque to Wuss, so tasks must call this themselves + * (e.g. the union of an animated element's old and new positions). * * \param[in] window Window whose content changed. - * \param[in] local_box Region, in window-local content coordinates (as - * passed to the task's mouse callback), or NULL to - * mark the whole content area dirty. + * \param[in] local_box Region, in window-local content coordinates (as passed + * to the task's mouse callback), or NULL to mark the + * whole content area dirty. */ void wuss_window_invalidate(wuss_window_t *window, const box_t *local_box); /** Mark a window's whole content area as dirty. Shorthand for * wuss_window_invalidate(window, NULL). */ -#define wuss_window_invalidate_all(window) wuss_window_invalidate((window), NULL) +#define wuss_window_invalidate_all(window) \ + wuss_window_invalidate((window), NULL) /** - * Set a window's scroll offset: the point in virtual content space that - * appears at the content area's top-left. Larger offsets bring later - * content into view. Invalidates the content area so the next redraw picks - * up the new offset; the task's redraw callback is responsible for using - * the offset (via wuss_window_get_scroll) to draw the right portion of its - * content. + * Set a window's scroll offset: the point in virtual content space that appears + * at the content area's top-left. Larger offsets bring later content into view. + * Invalidates the content area so the next redraw picks up the new offset; the + * task's redraw callback is responsible for using the offset (via + * wuss_window_get_scroll) to draw the right portion of its content. * * \param[in] window Window to scroll. * \param[in] p New scroll offset. @@ -278,10 +170,10 @@ void wuss_window_get_scroll(const wuss_window_t *window, point_t *p); * * \param[in] window Window to change. * \param[in] bg New content background, as an index into the system - * palette, or wuss_NO_BACKGROUND to hand background - * painting back to the task. - * \return \ref result_OK on success, \ref result_WUSS_BAD_COLOUR if bg is - * out of range for the palette. + * palette, or wuss_NO_BACKGROUND to hand background painting + * back to the task. + * \return \ref result_OK on success, \ref result_WUSS_BAD_COLOUR if bg is out + * of range for the palette. */ result_t wuss_window_set_background(wuss_window_t *window, wuss_colour_t bg); diff --git a/include/wuss/wuss.h b/include/wuss/wuss.h index c8add3aa..9c0f1f7c 100644 --- a/include/wuss/wuss.h +++ b/include/wuss/wuss.h @@ -20,11 +20,14 @@ extern "C" #include "framebuf/bmfont.h" #include "geom/box.h" #include "geom/point.h" +#include "geom/size.h" /* ----------------------------------------------------------------------- */ -#define result_WUSS_TOO_SMALL (result_BASE_WUSS + 0) /* Window/resize dimensions too small */ -#define result_WUSS_BAD_COLOUR (result_BASE_WUSS + 1) /* A palette index was out of range */ +/** Window/resize dimensions too small. */ +#define result_WUSS_TOO_SMALL (result_BASE_WUSS + 0) +/** A palette index was out of range. */ +#define result_WUSS_BAD_COLOUR (result_BASE_WUSS + 1) /* ----------------------------------------------------------------------- */ @@ -55,10 +58,14 @@ typedef enum wuss_mouse_action } wuss_mouse_action_t; -/** An index into a wuss_t's system palette (see wuss_create). Not a colour_t. */ +/** + * An index into a wuss_t's system palette (see wuss_create). Not a colour_t. + */ typedef int wuss_colour_t; -/** Sentinel for wuss_task_t::bg meaning "no automatic background fill". */ +/** + * Sentinel for wuss_window_create's bg meaning "no automatic background fill". + */ #define wuss_NO_BACKGROUND ((wuss_colour_t) -1) /** Furniture chrome colours, one entry per class of furniture. Title is @@ -77,25 +84,65 @@ typedef struct wuss_palette wuss_colour_t close; /**< Close icon. */ wuss_colour_t toggle; /**< Toggle-size icon. */ wuss_colour_t resize; /**< Resize icon. */ - wuss_colour_t arrows; /**< Scrollbar arrows. */ - wuss_colour_t wells; /**< Scrollbar wells. */ - wuss_colour_t sausages; /**< Scrollbar sausages. */ + struct + { + wuss_colour_t arrows; /**< Scrollbar arrows. */ + wuss_colour_t wells; /**< Scrollbar wells. */ + wuss_colour_t sausages; /**< Scrollbar sausages. */ + } + scroll; } wuss_palette_t; /** Per-window appearance flags, combinable with bitwise OR. */ typedef enum wuss_window_flags { - wuss_WINDOW_NONE = 0, /**< Default: every furniture region drawn. */ - wuss_WINDOW_NO_TITLEBAR = 1 << 0, /**< No titlebar; content fills the full visible area, and no drag handle exists. */ - wuss_WINDOW_NO_OUTLINE = 1 << 1, /**< No 1px border drawn around the visible area. */ - wuss_WINDOW_NO_CLOSE = 1 << 2, /**< No close icon in the titlebar. Ignored if flags includes wuss_WINDOW_NO_TITLEBAR. */ - wuss_WINDOW_NO_BACK = 1 << 3, /**< No send-to-back icon in the titlebar. Ignored if flags includes wuss_WINDOW_NO_TITLEBAR. */ - wuss_WINDOW_NO_TOGGLE_SIZE = 1 << 4, /**< No toggle-size icon in the titlebar. Ignored if flags includes wuss_WINDOW_NO_TITLEBAR. */ - wuss_WINDOW_NO_VSCROLL = 1 << 5, /**< No vertical scrollbar on the right edge. */ - wuss_WINDOW_NO_HSCROLL = 1 << 6, /**< No horizontal scrollbar on the bottom edge. */ - wuss_WINDOW_NO_RESIZE = 1 << 7, /**< No resize icon in the bottom-right corner. */ - wuss_WINDOW_NO_TOGGLE_BLIT = 1 << 8 /**< Toggle-size always fully redraws the window's content instead of blitting the preserved region -- for a task whose rendering depends on the window's size in ways redraw can't patch incrementally (e.g. a palette that lays itself out across the whole window). */ + /** Default: every furniture region drawn. */ + wuss_WINDOW_NONE = 0, + + /** + * No titlebar; content fills the full visible area, and no drag handle + * exists. + */ + wuss_WINDOW_NO_TITLEBAR = 1 << 0, + + /** No 1px border drawn around the visible area. */ + wuss_WINDOW_NO_OUTLINE = 1 << 1, + + /** + * No close icon in the titlebar. Ignored if flags includes + * wuss_WINDOW_NO_TITLEBAR. + */ + wuss_WINDOW_NO_CLOSE = 1 << 2, + + /** + * No send-to-back icon in the titlebar. Ignored if flags includes + * wuss_WINDOW_NO_TITLEBAR. + */ + wuss_WINDOW_NO_BACK = 1 << 3, + + /** + * No toggle-size icon in the titlebar. Ignored if flags includes + * wuss_WINDOW_NO_TITLEBAR. + */ + wuss_WINDOW_NO_TOGGLE_SIZE = 1 << 4, + + /** No vertical scrollbar on the right edge. */ + wuss_WINDOW_NO_VSCROLL = 1 << 5, + + /** No horizontal scrollbar on the bottom edge. */ + wuss_WINDOW_NO_HSCROLL = 1 << 6, + + /** No resize icon in the bottom-right corner. */ + wuss_WINDOW_NO_RESIZE = 1 << 7, + + /** + * A resize (drag or toggle-size) always fully redraws the window's content + * instead of blitting the preserved region -- for a task whose rendering + * depends on the window's size in ways redraw can't patch incrementally (e.g. + * a palette that lays itself out across the whole window). + */ + wuss_WINDOW_NO_RESIZE_BLIT = 1 << 8 } wuss_window_flags_t; @@ -111,23 +158,40 @@ wuss_zorder_t; /** Optional creation-time configuration. */ typedef struct wuss_config { - int titlebar_height; /**< Titlebar height in pixels, or 0 to derive from font metrics (or a built-in fallback if no font). */ - wuss_palette_t palette; /**< Furniture chrome colours. */ + /** + * Titlebar height in pixels, or 0 to derive from font metrics (or a built-in + * fallback if no font). + */ + int titlebar_height; + + /** Furniture chrome colours. */ + wuss_palette_t palette; + + /** + * Desktop background colour, painted behind windows on every redraw, or + * wuss_NO_BACKGROUND to leave the background untouched (the caller must then + * repaint it itself before wuss_redraw / wuss_redraw_dirty). + */ + wuss_colour_t backdrop; } wuss_config_t; /** * Create a window manager. * - * \param[in] scr Screen to draw windows onto. Not owned; must outlive the wuss_t. - * \param[in] font Font used to draw titlebar labels, or NULL to draw titlebars unlabelled. Not owned. - * \param[in] palette System palette, copied in, or NULL to use a built-in default palette. - * \param[in] npalette Number of entries in palette. Ignored if palette is NULL. + * \param[in] scr Screen to draw windows onto. Not owned; must outlive the + * wuss_t. + * \param[in] font Font used to draw titlebar labels, or NULL to draw + * titlebars unlabelled. Not owned. + * \param[in] palette System palette, copied in, or NULL to use a built-in + * default palette. + * \param[in] npalette Number of entries in palette. Ignored if palette is + * NULL. * \param[in] config Creation-time configuration, or NULL for defaults. * \param[out] wuss Newly created window manager. * \return \ref result_OK on success, \ref result_WUSS_BAD_COLOUR if any of - * config's palette entries are out of range for the palette, or - * another appropriate result code. + * config's palette entries are out of range for the palette, or another + * appropriate result code. */ result_t wuss_create(screen_t *scr, bmfont_t *font, @@ -144,21 +208,30 @@ result_t wuss_create(screen_t *scr, void wuss_destroy(wuss_t *doomed); /** - * Redraw every window, back-to-front. + * Fetch the system font (see wuss_create), for tasks to draw their own content + * in the same face as window titlebars. * * \param[in] wuss Window manager. - * \return \ref result_OK on success, or the last non-OK result returned by - * a task's redraw callback (drawing continues past a failing - * window rather than stopping). + * \return System font, or NULL if none was given to wuss_create. + */ +bmfont_t *wuss_get_font(const wuss_t *wuss); + +/** + * Redraw every window, back-to-front, having first painted the configured + * backdrop colour (see wuss_config_t::backdrop) behind them, if any. + * + * \param[in] wuss Window manager. + * \return \ref result_OK on success, or the last non-OK result returned by a + * task's redraw callback (drawing continues past a failing window + * rather than stopping). */ result_t wuss_redraw(wuss_t *wuss); /** - * Mark a screen-space region dirty. Window creation, destruction, move, - * resize and bring-to-front invalidate their own affected regions - * automatically; tasks must call this themselves when their content - * changes (e.g. an animation), passing the union of the old and new - * screen-space areas that need repainting. + * Mark a screen-space region dirty. Window creation, destruction, move, resize + * and bring-to-front invalidate their own affected regions automatically; tasks + * must call this themselves when their content changes (e.g. an animation), + * passing the union of the old and new screen-space areas that need repainting. * * \param[in] wuss Window manager. * \param[in] box Screen-space region to mark dirty. @@ -169,21 +242,22 @@ result_t wuss_invalidate(wuss_t *wuss, const box_t *box); /** * Redraw only the region accumulated by wuss_invalidate calls (and any * automatic invalidation from window management) since the last redraw, - * back-to-front, then clear the dirty region. Does nothing if nothing is - * dirty. + * back-to-front, then clear the dirty region. Does nothing if nothing is dirty. + * Each dirty region has the configured backdrop colour (see + * wuss_config_t::backdrop) painted into it first, if any. * * \param[in] wuss Window manager. - * \return \ref result_OK on success, or the last non-OK result returned by - * a task's redraw callback. + * \return \ref result_OK on success, or the last non-OK result returned by a + * task's redraw callback. */ result_t wuss_redraw_dirty(wuss_t *wuss); /** * Fetch the number of dirty regions currently accumulated (see * wuss_invalidate). Regions are self-coalescing: an invalidation already - * covered by an existing region is discarded, and one sharing a complete - * edge with an existing region extends it in place, so this stays small - * under most usage. + * covered by an existing region is discarded, and one sharing a complete edge + * with an existing region extends it in place, so this stays small under most + * usage. * * \param[in] wuss Window manager. * \return Number of dirty regions, 0 if nothing is dirty. @@ -191,34 +265,37 @@ result_t wuss_redraw_dirty(wuss_t *wuss); int wuss_get_dirty_count(const wuss_t *wuss); /** - * Fetch one of the current accumulated dirty regions (see - * wuss_invalidate). Wuss only repaints windows, not background - * between/behind them, so a caller whose invalidations can expose - * background (e.g. after a window move) should clear these regions itself - * before calling wuss_redraw_dirty. + * Fetch one of the current accumulated dirty regions (see wuss_invalidate). If + * no backdrop colour was configured (see wuss_config_t::backdrop), wuss only + * repaints windows, not background between/behind them, so a caller whose + * invalidations can expose background (e.g. after a window move) should clear + * these regions itself before calling wuss_redraw_dirty. * * \param[in] wuss Window manager. - * \param[in] index Index of the region to fetch, 0 to wuss_get_dirty_count() - 1. + * \param[in] index Index of the region to fetch, 0 to wuss_get_dirty_count() - + * 1. * \param[out] out Filled in with the dirty region. */ void wuss_get_dirty(const wuss_t *wuss, int index, box_t *out); /** * Deliver a mouse-down or mouse-up event (action must be wuss_MOUSE_DOWN or - * wuss_MOUSE_UP). Hit-tests the topmost window at (x,y). On a down, a - * titlebar click brings the window to front if button is Select (Adjust - * and Menu leave the z-order unchanged) and starts a drag; on an up, an - * in-progress drag is ended instead of hit-testing (an Adjust click with - * no move in between sends the window to the back rather than dragging - * it). A click on the window's content never changes the z-order and is - * delivered to the task in window-local content coordinates. + * wuss_MOUSE_UP). Hit-tests the topmost window at (x,y). On a down, a titlebar + * click brings the window to front if button is Select (Adjust and Menu leave + * the z-order unchanged) and starts a drag; on an up, an in-progress drag is + * ended instead of hit-testing (an Adjust click with no move in between sends + * the window to the back rather than dragging it). A click on the window's + * content never changes the z-order and is delivered to the task in + * window-local content coordinates. * * \param[in] wuss Window manager. * \param[in] p Screen coordinate. * \param[in] button Button pressed or released. * \param[in] action wuss_MOUSE_DOWN or wuss_MOUSE_UP. - * \param[out] hit Window under the pointer (or being dragged), or NULL if none. May be NULL if not needed. - * \return \ref result_OK, or a result code returned by the task's mouse callback. + * \param[out] hit Window under the pointer (or being dragged), or NULL if + * none. May be NULL if not needed. + * \return \ref result_OK, or a result code returned by the task's mouse + * callback. */ result_t wuss_mouse_click(wuss_t *wuss, point_t p, @@ -227,35 +304,51 @@ result_t wuss_mouse_click(wuss_t *wuss, wuss_window_t **hit); /** - * Deliver a mouse-move event. Updates the dragged window's position if a - * drag is active (invalidating the affected region; call wuss_redraw_dirty - * to actually repaint it), otherwise hit-tests and delivers to the - * window's task as per wuss_mouse_click. + * Deliver a mouse-move event. Updates the dragged window's position if a drag + * is active (invalidating the affected region; call wuss_redraw_dirty to + * actually repaint it), otherwise hit-tests and delivers to the window's task + * as per wuss_mouse_click. * * \param[in] wuss Window manager. * \param[in] p Screen coordinate. - * \param[out] hit Window under the pointer (or being dragged), or NULL if none. May be NULL if not needed. - * \return \ref result_OK, or a result code returned by the task's mouse callback. + * \param[out] hit Window under the pointer (or being dragged), or NULL if + * none. May be NULL if not needed. + * \return \ref result_OK, or a result code returned by the task's mouse + * callback. */ result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit); /** * Deliver a scroll event. Hit-tests the topmost window at p as per - * wuss_mouse_click, and delivers to the window's task in window-local - * content coordinates; dropped if the hit window has no scroll callback, - * or the pointer is over its titlebar. + * wuss_mouse_click, and delivers to the window's task in window-local content + * coordinates; dropped if the hit window has no scroll callback, or the pointer + * is over its titlebar. * * \param[in] wuss Window manager. * \param[in] p Screen coordinate. * \param[in] delta Scroll amount; sign and units are caller-defined. - * \param[out] hit Window under the pointer, or NULL if none. May be NULL if not needed. - * \return \ref result_OK, or a result code returned by the task's scroll callback. + * \param[out] hit Window under the pointer, or NULL if none. May be NULL if + * not needed. + * \return \ref result_OK, or a result code returned by the task's scroll + * callback. */ result_t wuss_scroll(wuss_t *wuss, point_t p, int delta, wuss_window_t **hit); +/** + * Broadcast a wuss_EVENT_IDLE event to every window's task, in z-order. + * Intended to be called once per main-loop iteration, after other pending input + * has been handled, so tasks can drive their own animation/timers without the + * caller stepping each one individually. + * + * \param[in] wuss Window manager whose windows' tasks should go idle. + * \return \ref result_OK on success, else the first non-OK result returned by a + * task's handle callback. + */ +result_t wuss_idle(wuss_t *wuss); + #ifdef __cplusplus } #endif diff --git a/libraries/databases/pickle/test/pickle-test.c b/libraries/databases/pickle/test/pickle-test.c index 5e15e8a8..a72a268b 100644 --- a/libraries/databases/pickle/test/pickle-test.c +++ b/libraries/databases/pickle/test/pickle-test.c @@ -338,7 +338,7 @@ static result_t cheese_format_value(const void *vvalue, char *buf, size_t len, v NOT_USED(opaque); - sprintf(buf, + snprintf(buf, len, "%s %s %s %s %s %d", cheese_country_to_string(value->country), cheese_region_to_string(value->region), diff --git a/libraries/datastruct/cache/test/cache-test.c b/libraries/datastruct/cache/test/cache-test.c index 4125faa8..c1704c90 100644 --- a/libraries/datastruct/cache/test/cache-test.c +++ b/libraries/datastruct/cache/test/cache-test.c @@ -183,7 +183,7 @@ static result_t cache_test_put(cache_t *cache, int maxkey) for (i = 0; i < maxkey; i++) { - sprintf(data, "(%d)", i); + snprintf(data, sizeof(data), "(%d)", i); cache_put(cache, i, data, strlen(data) + 1, NULL); } @@ -197,7 +197,7 @@ static result_t cache_test_put(cache_t *cache, int maxkey) { char *cached; - int len = sprintf(data, "(%d)", i); + int len = snprintf(data, sizeof(data), "(%d)", i); cached = (char *) cache_get(cache, i); if (cached) { @@ -215,7 +215,7 @@ static result_t cache_test_put(cache_t *cache, int maxkey) for (i = 0; i < maxkey; i++) { - sprintf(data, "(%d)", i); + snprintf(data, sizeof(data), "(%d)", i); cache_put(cache, i, data, strlen(data) + 1, NULL); } diff --git a/libraries/framebuf/bitmap/bitmap.c b/libraries/framebuf/bitmap/bitmap.c index 1d6354a9..af839a25 100644 --- a/libraries/framebuf/bitmap/bitmap.c +++ b/libraries/framebuf/bitmap/bitmap.c @@ -9,8 +9,7 @@ #include "framebuf/span-registry.h" result_t bitmap_init(bitmap_t *bm, - int width, - int height, + size2d_t size, pixelfmt_t fmt, int rowbytes, const colour_t *palette, @@ -20,8 +19,7 @@ result_t bitmap_init(bitmap_t *bm, assert(bm); - bm->width = width; - bm->height = height; + bm->size = size; bm->format = fmt; bm->rowbytes = rowbytes; bm->palette = NULL; @@ -77,7 +75,7 @@ void bitmap_clear(bitmap_t *bm, colour_t colour) case 2: px *= 0x11; break; case 3: px *= 0x01; break; } - memset(bm->base, px, bm->rowbytes * bm->height); + memset(bm->base, px, bm->rowbytes * bm->size.h); break; case 5: /* 32bpp - pixels are ints */ @@ -87,18 +85,18 @@ void bitmap_clear(bitmap_t *bm, colour_t colour) pixelfmt_any32_t tmp2 = tmp1 ^ (tmp1 >> 8); if (tmp2 == 0) { - memset(bm->base, px, bm->rowbytes * bm->height); + memset(bm->base, px, bm->rowbytes * bm->size.h); } else { pixelfmt_any32_t *pixels; pixels = bm->base; - for (y = 0; y < bm->height; y++) + for (y = 0; y < bm->size.h; y++) { - for (x = 0; x < bm->width; x++) + for (x = 0; x < bm->size.w; x++) *pixels++ = px; - pixels += bm->rowbytes / sizeof(*pixels) - bm->width; + pixels += bm->rowbytes / sizeof(*pixels) - bm->size.w; } } } @@ -126,7 +124,7 @@ static result_t bmconv_p4_to_bgrx8888(const bitmap_t *src, bitmap_t **pdst) for (i = 0; i < 16; i++) map[i] = colour_to_pixel(src->palette, 16, src->palette[i], pixelfmt_bgrx8888); - outpixels = malloc(src->width * sizeof(pixelfmt_bgrx8888_t) * src->height); // rowbytes rounding needed? + outpixels = malloc(src->size.w * sizeof(pixelfmt_bgrx8888_t) * src->size.h); // rowbytes rounding needed? if (outpixels == NULL) return result_OOM; @@ -138,18 +136,18 @@ static result_t bmconv_p4_to_bgrx8888(const bitmap_t *src, bitmap_t **pdst) } rc = bitmap_init(dst, - src->width, src->height, + src->size, pixelfmt_bgrx8888, - src->width * sizeof(pixelfmt_bgrx8888_t), + src->size.w * sizeof(pixelfmt_bgrx8888_t), NULL, outpixels); if (rc) return rc; inpixels = src->base; - for (y = 0; y < src->height; y++) + for (y = 0; y < src->size.h; y++) { - for (x = 0; x < src->width / 8; x++) + for (x = 0; x < src->size.w / 8; x++) { pixelfmt_p4_t in = *inpixels++; // fetches 8 pixels // 0xABCDEFGH is 8 4bpp pixels shown H,G,F,E,D,C,B,A diff --git a/libraries/framebuf/bitmap/load.c b/libraries/framebuf/bitmap/load.c index 8177e37f..609a349e 100644 --- a/libraries/framebuf/bitmap/load.c +++ b/libraries/framebuf/bitmap/load.c @@ -124,8 +124,7 @@ result_t bitmap_load_png(bitmap_t *bm, const char *filename) png_read_image(png_ptr, row_pointers); - bitmap_init(bm, - pngwidth, pngheight, + bitmap_init(bm, (size2d_t) { pngwidth, pngheight }, bm_fmt, bm_rowbytes, NULL, /* no palette */ diff --git a/libraries/framebuf/bitmap/save.c b/libraries/framebuf/bitmap/save.c index 9b627c14..7be936e5 100755 --- a/libraries/framebuf/bitmap/save.c +++ b/libraries/framebuf/bitmap/save.c @@ -67,7 +67,7 @@ result_t bitmap_save_png(const bitmap_t *bm, const char *filename) png_init_io(png_ptr, fp); png_set_IHDR(png_ptr, info_ptr, - bm->width, bm->height, + bm->size.w, bm->size.h, 8, fmt, PNG_INTERLACE_NONE, @@ -81,7 +81,7 @@ result_t bitmap_save_png(const bitmap_t *bm, const char *filename) // png_set_filler(png_ptr, 0, PNG_FILLER_AFTER); // png_set_packing for <8bpp images - outrow = malloc(bytespp * bm->width * sizeof(png_byte)); + outrow = malloc(bytespp * bm->size.w * sizeof(png_byte)); if (outrow == NULL) { rc = result_OOM; @@ -90,14 +90,14 @@ result_t bitmap_save_png(const bitmap_t *bm, const char *filename) inrow = bm->base; - for (y = 0; y < bm->height; y++) + for (y = 0; y < bm->size.h; y++) { png_bytep pout = &outrow[0]; switch (bm->format) { case pixelfmt_bgrx8888: - for (x = 0; x < bm->width; x++) + for (x = 0; x < bm->size.w; x++) { pixelfmt_xxxa8888_t in = *inrow++; *pout++ = PIXELFMT_xxRx8888(in); @@ -107,7 +107,7 @@ result_t bitmap_save_png(const bitmap_t *bm, const char *filename) break; case pixelfmt_bgra8888: - for (x = 0; x < bm->width; x++) + for (x = 0; x < bm->size.w; x++) { pixelfmt_xxxa8888_t in = *inrow++; *pout++ = PIXELFMT_xxRx8888(in); diff --git a/libraries/framebuf/bmfont/bmfont.c b/libraries/framebuf/bmfont/bmfont.c index 16ff74da..5dd01fa4 100644 --- a/libraries/framebuf/bmfont/bmfont.c +++ b/libraries/framebuf/bmfont/bmfont.c @@ -519,6 +519,11 @@ void bmfont_get_info(bmfont_t *bmfont, int *width, int *height) *height = bmfont->charheight; } +int bmfont_get_count(bmfont_t *bmfont) +{ + return bmfont->totalchars; +} + result_t bmfont_measure(bmfont_t *bmfont, const char *text, int textlen, diff --git a/libraries/framebuf/bmfont/test/bmfont-test.c b/libraries/framebuf/bmfont/test/bmfont-test.c index f1f2f6a9..f46302dd 100644 --- a/libraries/framebuf/bmfont/test/bmfont-test.c +++ b/libraries/framebuf/bmfont/test/bmfont-test.c @@ -187,13 +187,13 @@ bmtestline_t; static bmtestfont_t bmfonts[MAXFONTS] = { - { "daydream-font", NULL }, - { "gliderrider-font", NULL }, - { "tiny-font", NULL }, - { "henry-font", NULL }, - { "tall-font", NULL }, + { "daydream", NULL }, + { "gliderrider", NULL }, + { "tiny", NULL }, + { "henry", NULL }, + { "tall", NULL }, { "ms-sans-serif", NULL }, - { "digits-font", NULL } + { "digits", NULL } }; /* ----------------------------------------------------------------------- */ @@ -753,9 +753,7 @@ result_t bmfont_test_one_format(const char *resources, goto Failure; } - bitmap_init(&state.bm, - state.scr_width, - state.scr_height, + bitmap_init(&state.bm, (size2d_t) { state.scr_width, state.scr_height }, scr_fmt, scr_rowbytes, state.palette, diff --git a/libraries/framebuf/colour/colour.c b/libraries/framebuf/colour/colour.c index 216eab11..1b77838f 100644 --- a/libraries/framebuf/colour/colour.c +++ b/libraries/framebuf/colour/colour.c @@ -63,9 +63,9 @@ static unsigned int closest_palette_entry(const colour_t *palette, dr = ent_r - req_r; dg = ent_g - req_g; db = ent_b - req_b; - curdist = ((dr * dr) * red_weight + - (dg * dg) * green_weight + - (db * db) * blue_weight) >> 16; + curdist = ((unsigned int) (dr * dr) * red_weight + + (unsigned int) (dg * dg) * green_weight + + (unsigned int) (db * db) * blue_weight) >> 16; if (curdist < dist) { dist = curdist; diff --git a/libraries/framebuf/composite/composite.c b/libraries/framebuf/composite/composite.c index cd5f4fbe..3aa29acb 100644 --- a/libraries/framebuf/composite/composite.c +++ b/libraries/framebuf/composite/composite.c @@ -936,8 +936,8 @@ static void composite_xxxa8888(composite_rule_t rule, srcscan = src->base; dstscan = dst->base; - width = src->width; - height = src->height; + width = src->size.w; + height = src->size.h; rowbytes = src->rowbytes / sizeof(pixelfmt_xxxa8888_t); while (height--) @@ -959,8 +959,8 @@ result_t composite(composite_rule_t rule, if (src == NULL || dst == NULL) return result_NULL_ARG; - if (src->width != dst->width || - src->height != dst->height || + if (src->size.w != dst->size.w || + src->size.h != dst->size.h || src->format != dst->format) return result_BAD_ARG; diff --git a/libraries/framebuf/composite/test/composite-test.c b/libraries/framebuf/composite/test/composite-test.c index 4577a4b9..58d9bb45 100644 --- a/libraries/framebuf/composite/test/composite-test.c +++ b/libraries/framebuf/composite/test/composite-test.c @@ -34,7 +34,7 @@ static result_t bitmap_clone_by_size(bitmap_t *cloned, const bitmap_t *src) assert(cloned); assert(src); - pixelbytes = src->height * src->rowbytes; + pixelbytes = src->size.h * src->rowbytes; pixels = malloc(pixelbytes); if (pixels == NULL) return result_OOM; @@ -52,13 +52,13 @@ static result_t bitmap_clone_pixels(bitmap_t *dst, const bitmap_t *src) assert(dst); assert(src); - if (dst->width != src->width || - dst->height != src->height || + if (dst->size.w != src->size.w || + dst->size.h != src->size.h || dst->format != src->format || dst->rowbytes != src->rowbytes) return result_INCOMPATIBLE; - memcpy(dst->base, src->base, src->height * src->rowbytes); + memcpy(dst->base, src->base, src->size.h * src->rowbytes); return result_OK; } @@ -73,7 +73,7 @@ static result_t bitmap_plot(const bitmap_t *src, bitmap_t *dst, int x, int y) return result_INCOMPATIBLE; sp += x + y * dst->rowbytes / 4; - for (h = 0; h < src->height; h++) + for (h = 0; h < src->size.h; h++) { memcpy(sp, dp, src->rowbytes); sp += dst->rowbytes / 4; @@ -99,9 +99,9 @@ static result_t bitmap_convert_inplace(bitmap_t *bm, pixelfmt_t new_fmt) pixelfmt_rgbx8888_t *p = bm->base; int x,y; - for (y = 0; y < bm->height; y++) + for (y = 0; y < bm->size.h; y++) { - for (x = 0; x < bm->width; x++) + for (x = 0; x < bm->size.w; x++) { pixelfmt_rgbx8888_t px = *p; *p++ = PIXELFMT_MAKE_BGRA8888(PIXELFMT_Bxxx8888(px), @@ -131,9 +131,9 @@ static result_t bitmap_convert_inplace(bitmap_t *bm, pixelfmt_t new_fmt) pixelfmt_rgba8888_t *p = bm->base; int x,y; - for (y = 0; y < bm->height; y++) + for (y = 0; y < bm->size.h; y++) { - for (x = 0; x < bm->width; x++) + for (x = 0; x < bm->size.w; x++) { pixelfmt_rgba8888_t px = *p; *p++ = PIXELFMT_MAKE_BGRA8888(PIXELFMT_Bxxx8888(px), @@ -177,7 +177,7 @@ static result_t load_test_png(bitmap_t *bm, if (rc) return rc; - if (bm->width != SMALLWIDTH || bm->height != SMALLHEIGHT || bm->format != FORMAT) + if (bm->size.w != SMALLWIDTH || bm->size.h != SMALLHEIGHT || bm->format != FORMAT) { fprintf(stderr, "load_test_png: wrong width, height or format\n"); free(bm->base); @@ -210,7 +210,7 @@ result_t composite_test(const char *resources) goto Failure; } - bitmap_init(&bigbitmap, WIDTH, HEIGHT, FORMAT, scr_rowbytes, NULL, bigpixels); + bitmap_init(&bigbitmap, (size2d_t) { 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 6349a728..31e83507 100644 --- a/libraries/framebuf/curve/test/curve-test.c +++ b/libraries/framebuf/curve/test/curve-test.c @@ -804,9 +804,7 @@ result_t curve_test_one_format(const char *resources, goto Failure; } - bitmap_init(&state.bm, - state.scr_width, - state.scr_height, + bitmap_init(&state.bm, (size2d_t) { state.scr_width, state.scr_height }, scr_fmt, scr_rowbytes, state.palette, diff --git a/libraries/framebuf/screen/screen-draw.c b/libraries/framebuf/screen/screen-draw.c index 686ee99f..afff1d28 100644 --- a/libraries/framebuf/screen/screen-draw.c +++ b/libraries/framebuf/screen/screen-draw.c @@ -138,7 +138,7 @@ static void screen_blend_pixel(screen_t *scr, void screen_draw_rect(screen_t *scr, int x, int y, - int width, int height, + size2d_t size, colour_t colour) { box_t clip_box; @@ -152,8 +152,8 @@ void screen_draw_rect(screen_t *scr, rect_box.x0 = x; rect_box.y0 = y; - rect_box.x1 = x + width; - rect_box.y1 = y + height; + rect_box.x1 = x + size.w; + rect_box.y1 = y + size.h; if (box_intersection(&clip_box, &rect_box, &draw_box)) return; @@ -214,7 +214,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, size, size, colour); + screen_draw_rect(scr, x, y, (size2d_t) { size, size }, colour); } /* ----------------------------------------------------------------------- */ @@ -232,8 +232,8 @@ void screen_draw_bitmap(screen_t *scr, int x, int y, const bitmap_t *src) src_box.x0 = x; src_box.y0 = y; - src_box.x1 = x + src->width; - src_box.y1 = y + src->height; + src_box.x1 = x + src->size.w; + src_box.y1 = y + src->size.h; if (box_intersection(&clip_box, &src_box, &draw_box)) return; /* nothing visible */ @@ -351,11 +351,25 @@ void screen_draw_bitmap(screen_t *scr, int x, int y, const bitmap_t *src) /* ----------------------------------------------------------------------- */ +/* Build the box covering the whole screen, ignoring the current clip + * rectangle. Unlike the clip rectangle this is invariant across redraws, so + * clipping a line's endpoints against it yields the same result every time. + */ +static void screen_get_bounds(const screen_t *scr, box_t *bounds) +{ + bounds->x0 = 0; + bounds->y0 = 0; + bounds->x1 = scr->size.w; + bounds->y1 = scr->size.h; +} + void screen_draw_line(screen_t *scr, int x0, int y0, int x1, int y1, colour_t colour) { box_t clip_box; + box_t bounds; + int rx0, ry0, rx1, ry1; int dx, dy; int adx, ady; int sx, sy; @@ -364,9 +378,22 @@ void screen_draw_line(screen_t *scr, if (screen_get_clip(scr, &clip_box)) return; /* invalid clipped screen */ - if (line_clip(&clip_box, &x0, &y0, &x1, &y1) == 0) + /* Reject only: the clipped-back endpoints are discarded, since feeding + * them into the stepping maths below would make the pixels chosen depend + * on which clip rectangle we happened to be called with. */ + rx0 = x0; + ry0 = y0; + rx1 = x1; + ry1 = y1; + if (line_clip(&clip_box, &rx0, &ry0, &rx1, &ry1) == 0) return; + /* Bound the number of steps taken. Safe to feed into the stepping maths + * as the screen bounds never vary between calls. Cannot reject: the clip + * box is always a subset of the screen bounds and it just accepted. */ + screen_get_bounds(scr, &bounds); + (void) line_clip(&bounds, &x0, &y0, &x1, &y1); + dx = x1 - x0; adx = abs(dx); sx = SGN(dx); @@ -405,6 +432,8 @@ void screen_draw_line_wu_fix8(screen_t *scr, colour_t colour) { box_t clip_box_f8; + box_t bounds_f8; + fix8_t rx0_f8, ry0_f8, rx1_f8, ry1_f8; fix8_t dx_f8, dy_f8; int steep_b; /* a bool */ fix16_t grad_f16; @@ -423,9 +452,20 @@ void screen_draw_line_wu_fix8(screen_t *scr, /* scale up screen clip box to match the coordinate type */ box_scalelog2(&clip_box_f8, FIX8_SHIFT); - if (line_clip(&clip_box_f8, &x0_f8, &y0_f8, &x1_f8, &y1_f8) == 0) + /* Reject only: see screen_draw_line() for why the clipped-back endpoints + * are discarded rather than used. */ + rx0_f8 = x0_f8; + ry0_f8 = y0_f8; + rx1_f8 = x1_f8; + ry1_f8 = y1_f8; + if (line_clip(&clip_box_f8, &rx0_f8, &ry0_f8, &rx1_f8, &ry1_f8) == 0) return; + /* Bound the number of steps taken, using the invariant screen bounds. */ + screen_get_bounds(scr, &bounds_f8); + box_scalelog2(&bounds_f8, FIX8_SHIFT); + (void) line_clip(&bounds_f8, &x0_f8, &y0_f8, &x1_f8, &y1_f8); + dx_f8 = x1_f8 - x0_f8; dy_f8 = y1_f8 - y0_f8; @@ -521,6 +561,7 @@ void screen_draw_line_wu_float(screen_t *scr, colour_t colour) { box_t clip_box; + box_t bounds; int x0, y0, x1, y1; float dx, dy; int steep; /* bool */ @@ -532,6 +573,8 @@ void screen_draw_line_wu_float(screen_t *scr, int alpha1, alpha2; float yf; int ix1, iy1; + int xlo, xhi; + int xstart, xstop; int x, y; if (screen_get_clip(scr, &clip_box)) @@ -546,6 +589,8 @@ void screen_draw_line_wu_float(screen_t *scr, if (line_clip(&clip_box, &x0, &y0, &x1, &y1) == 0) return; + screen_get_bounds(scr, &bounds); + dx = fx1 - fx0; dy = fy1 - fy0; @@ -611,7 +656,18 @@ void screen_draw_line_wu_float(screen_t *scr, /* mid points */ - for (x = ix0 + 1; x < ix1; x++) + /* Bound the loop to the screen. Skipped steps are fast-forwarded through + * the gradient in closed form, so the pixels drawn stay a function of the + * true endpoints alone: the screen bounds, unlike the clip box, are the + * same on every call. */ + xlo = steep ? bounds.y0 : bounds.x0; + xhi = steep ? bounds.y1 : bounds.x1; + xstart = MAX(ix0 + 1, xlo - 1); + xstop = MIN(ix1, xhi + 1); + + yf += grad * (float) (xstart - (ix0 + 1)); + + for (x = xstart; x < xstop; x++) { y = floorf(yf); alpha1 = 255.0f * (y + 1.0f - yf); diff --git a/libraries/framebuf/screen/screen.c b/libraries/framebuf/screen/screen.c index 578ba073..e15fafdc 100644 --- a/libraries/framebuf/screen/screen.c +++ b/libraries/framebuf/screen/screen.c @@ -15,8 +15,7 @@ #include "framebuf/screen.h" void screen_init(screen_t *scr, - int width, - int height, + size2d_t size, pixelfmt_t fmt, int rowbytes, colour_t *palette, @@ -24,8 +23,7 @@ void screen_init(screen_t *scr, { assert(scr); - scr->width = width; - scr->height = height; + scr->size = size; scr->format = fmt; scr->rowbytes = rowbytes; scr->palette = palette; // FIXME: This doesn't clone the palette, whereas bitmap_init()'s equivalent does. @@ -48,8 +46,8 @@ int screen_get_clip(const screen_t *scr, box_t *clip) { clip->x0 = 0; clip->y0 = 0; - clip->x1 = scr->width; - clip->y1 = scr->height; + clip->x1 = scr->size.w; + clip->y1 = scr->size.h; if (box_is_empty(&scr->clip)) return 0; /* not empty */ diff --git a/libraries/framebuf/screen/test/screen-test.c b/libraries/framebuf/screen/test/screen-test.c new file mode 100644 index 00000000..5b59876f --- /dev/null +++ b/libraries/framebuf/screen/test/screen-test.c @@ -0,0 +1,230 @@ +/* screen-test.c -- test screen drawing */ + +#include +#include + +#include "base/result.h" +#include "base/utils.h" +#include "framebuf/colour.h" +#include "framebuf/pixelfmt.h" +#include "framebuf/screen.h" +#include "geom/box.h" +#include "utils/fxp.h" + +#include "test/all-tests.h" + +/* ----------------------------------------------------------------------- */ + +#define WIDTH 64 +#define HEIGHT 64 + +#define BACKGROUND 0xFF000000 + +typedef struct testscreen +{ + screen_t scr; + pixelfmt_bgrx8888_t pixels[WIDTH * HEIGHT]; +} +testscreen_t; + +typedef enum linekind +{ + linekind_INT, + linekind_WU_FIX8, + linekind_WU_FLOAT +} +linekind_t; + +typedef struct linetest +{ + int x0, y0, x1, y1; +} +linetest_t; + +/* Rectangles which, taken together, cover the whole canvas without overlap. + * Three separate partitions: vertical strips, horizontal strips, and an + * irregular split, mirroring the pieces wuss__clip_to_visible() generates. */ +typedef struct partition +{ + int nboxes; + box_t boxes[6]; +} +partition_t; + +static const partition_t partitions[] = +{ + { 3, { { 0, 0, 20, 64 }, { 20, 0, 41, 64 }, { 41, 0, 64, 64 } } }, + { 3, { { 0, 0, 64, 13 }, { 0, 13, 64, 47 }, { 0, 47, 64, 64 } } }, + { 5, { { 0, 0, 64, 17 }, { 0, 17, 9, 64 }, { 9, 17, 33, 40 }, + { 33, 17, 64, 40 }, { 9, 40, 64, 64 } } } +}; + +static const linetest_t lines[] = +{ + { 4, 32, 60, 32 }, /* horizontal */ + { 32, 4, 32, 60 }, /* vertical */ + { 4, 4, 60, 60 }, /* 45 degrees */ + { 60, 60, 4, 4 }, /* 45 degrees, reversed */ + { 2, 10, 62, 30 }, /* shallow */ + { 62, 30, 2, 10 }, /* shallow, reversed */ + { 10, 2, 30, 62 }, /* steep */ + { 30, 62, 10, 2 }, /* steep, reversed */ + { 4, 13, 60, 13 }, /* lands on a partition boundary */ + { 20, 0, 20, 64 }, /* lands on a partition boundary */ + { -30, 20, 90, 44 }, /* partially outside */ + { 20, -40, 44, 100 }, /* partially outside */ + { -20, -20, -5, -5 } /* wholly outside */ +}; + +/* ----------------------------------------------------------------------- */ + +static void testscreen_init(testscreen_t *ts) +{ + int i; + + for (i = 0; i < WIDTH * HEIGHT; i++) + ts->pixels[i] = BACKGROUND; + + screen_init(&ts->scr, (size2d_t) { WIDTH, HEIGHT }, + pixelfmt_bgrx8888, + WIDTH * (int) sizeof(ts->pixels[0]), + NULL, + ts->pixels); +} + +static void draw(screen_t *scr, linekind_t kind, const linetest_t *line, + colour_t colour) +{ + switch (kind) + { + case linekind_INT: + screen_draw_line(scr, line->x0, line->y0, line->x1, line->y1, colour); + break; + + case linekind_WU_FIX8: + 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), + colour); + break; + + case linekind_WU_FLOAT: + screen_draw_line_wu_float(scr, + (float) line->x0, (float) line->y0, + (float) line->x1, (float) line->y1, + colour); + break; + } +} + +/* ----------------------------------------------------------------------- */ + +/* The same logical line drawn in one go, and drawn once per piece of a + * partition of the canvas, must produce identical pixels. */ +static result_t test_clip_invariance(void) +{ + static testscreen_t reference; + static testscreen_t pieced; + + colour_t colour; + size_t l, p, k; + int b; + + colour = colour_rgb(255, 255, 255); + + for (k = 0; k < 3; k++) + for (l = 0; l < NELEMS(lines); l++) + { + testscreen_init(&reference); + draw(&reference.scr, (linekind_t) k, &lines[l], colour); + + for (p = 0; p < NELEMS(partitions); p++) + { + testscreen_init(&pieced); + + for (b = 0; b < partitions[p].nboxes; b++) + { + pieced.scr.clip = partitions[p].boxes[b]; + draw(&pieced.scr, (linekind_t) k, &lines[l], colour); + } + + if (memcmp(reference.pixels, pieced.pixels, sizeof(reference.pixels))) + { + printf("screen: clip invariance failed for line %zu, " + "rasterizer %zu, partition %zu\n", l, k, p); + return result_TEST_FAILED; + } + } + } + + return result_TEST_PASSED; +} + +/* Guard against the test above passing vacuously because clipping stopped + * happening at all: pixels outside the clip box must stay untouched. */ +static result_t test_clipping_still_happens(void) +{ + static const box_t clip = { 0, 0, 64, 20 }; + + static testscreen_t ts; + + colour_t colour; + size_t k; + int x, y; + + colour = colour_rgb(255, 255, 255); + + for (k = 0; k < 3; k++) + { + const linetest_t line = { 4, 4, 60, 60 }; /* crosses the clip boundary */ + + testscreen_init(&ts); + ts.scr.clip = clip; + draw(&ts.scr, (linekind_t) k, &line, colour); + + for (y = 0; y < HEIGHT; y++) + for (x = 0; x < WIDTH; x++) + { + if (box_contains_point(&clip, x, y)) + continue; + + if (ts.pixels[y * WIDTH + x] != BACKGROUND) + { + printf("screen: pixel (%d,%d) drawn outside the clip box " + "by rasterizer %zu\n", x, y, k); + return result_TEST_FAILED; + } + } + } + + return result_TEST_PASSED; +} + +/* ----------------------------------------------------------------------- */ + +result_t screen_test(const char *resources) +{ + typedef result_t (*screentestfn)(void); + + static const screentestfn tests[] = + { + test_clip_invariance, + test_clipping_still_happens + }; + + result_t rc; + size_t i; + int nfailures; + + NOT_USED(resources); + + nfailures = 0; + for (i = 0; i < NELEMS(tests); i++) + { + rc = tests[i](); + if (rc != result_TEST_PASSED) + nfailures++; + } + + return (nfailures == 0) ? result_TEST_PASSED : result_TEST_FAILED; +} diff --git a/libraries/geom/box/size.c b/libraries/geom/box/size.c new file mode 100644 index 00000000..d5b0fd28 --- /dev/null +++ b/libraries/geom/box/size.c @@ -0,0 +1,14 @@ +/* size.c -- return the size of the specified box */ + +#include "geom/box.h" +#include "geom/size.h" + +size2d_t box_size(const box_t *box) +{ + size2d_t size; + + size.w = box->x1 - box->x0; + size.h = box->y1 - box->y0; + + return size; +} diff --git a/libraries/geom/line/line.c b/libraries/geom/line/line.c index c35898a7..107b84a9 100644 --- a/libraries/geom/line/line.c +++ b/libraries/geom/line/line.c @@ -13,6 +13,60 @@ typedef unsigned int outcode_t; #define outcode_BOTTOM (1u << 2) #define outcode_TOP (1u << 3) +/* Compute (a * b) / c, truncating toward zero, without overflow and + * without using a 64-bit or floating-point intermediate. The caller must + * guarantee the true result fits in an int -- quotient bits at or above + * bit 31 are dropped silently. Holds here: every argument is a screen + * coordinate or a difference of two. */ +static int muldiv(int a, int b, int c) +{ + unsigned int a_lo, a_hi, b_lo, b_hi; + unsigned int lo_lo, hi_lo, lo_hi, hi_hi; + unsigned int cross, cross_carry, lo_carry; + unsigned int hi, lo; + unsigned int ua, ub, uc; + unsigned int rem, quot, bit; + int neg; + int i; + + neg = 0; + if (a < 0) { neg = !neg; ua = 0u - (unsigned int) a; } else ua = (unsigned int) a; + if (b < 0) { neg = !neg; ub = 0u - (unsigned int) b; } else ub = (unsigned int) b; + if (c < 0) { neg = !neg; uc = 0u - (unsigned int) c; } else uc = (unsigned int) c; + + /* widen ua * ub into a 64-bit result held as two 32-bit halves */ + a_lo = ua & 0xFFFFu; a_hi = ua >> 16; + b_lo = ub & 0xFFFFu; b_hi = ub >> 16; + + lo_lo = a_lo * b_lo; + hi_lo = a_hi * b_lo; + lo_hi = a_lo * b_hi; + hi_hi = a_hi * b_hi; + + cross = hi_lo + lo_hi; + cross_carry = (cross < hi_lo) ? (1u << 16) : 0u; + lo = lo_lo + (cross << 16); + lo_carry = (lo < lo_lo) ? 1u : 0u; + hi = hi_hi + (cross >> 16) + cross_carry + lo_carry; + + /* long-divide the 64-bit (hi:lo) dividend by uc, one bit at a time */ + rem = 0; + quot = 0; + for (i = 63; i >= 0; i--) + { + bit = (i >= 32) ? ((hi >> (i - 32)) & 1u) : ((lo >> i) & 1u); + rem = (rem << 1) | bit; + if (rem >= uc) + { + rem -= uc; + if (i < 32) + quot |= (1u << i); + } + } + + return neg ? -(int) quot : (int) quot; +} + static INLINE outcode_t compute_outcode(const box_t *clip, int x, int y) { outcode_t code; @@ -80,23 +134,23 @@ int line_clip(const box_t *clip, if (oc & outcode_TOP) { - x = x0 + w * (clip->y1 - 1 - y0) / h; + x = x0 + muldiv(w, clip->y1 - 1 - y0, h); y = clip->y1 - 1; } else if (oc & outcode_BOTTOM) { - x = x0 + w * (clip->y0 - y0) / h; + x = x0 + muldiv(w, clip->y0 - y0, h); y = clip->y0; } else if (oc & outcode_RIGHT) { x = clip->x1 - 1; - y = y0 + h * (clip->x1 - 1 - x0) / w; + y = y0 + muldiv(h, clip->x1 - 1 - x0, w); } else if (oc & outcode_LEFT) { x = clip->x0; - y = y0 + h * (clip->x0 - x0) / w; + y = y0 + muldiv(h, clip->x0 - x0, w); } if (oc == oc0) diff --git a/libraries/utils/pack/unpack.c b/libraries/utils/pack/unpack.c index b0251590..01f2911e 100644 --- a/libraries/utils/pack/unpack.c +++ b/libraries/utils/pack/unpack.c @@ -244,7 +244,7 @@ static size_t name(const unsigned char *buf, const char *fmt, va_list args) \ a = va_arg(args, uint32_t *); \ while (n--) \ { \ - *a++ = (uint32_t) (bp[w0] | (bp[w1] << 8) | (bp[w2] << 16) | (bp[w3] << 24)); \ + *a++ = (uint32_t) ((uint32_t) bp[w0] | ((uint32_t) bp[w1] << 8) | ((uint32_t) bp[w2] << 16) | ((uint32_t) bp[w3] << 24)); \ bp += 4; \ } \ } \ @@ -257,7 +257,7 @@ static size_t name(const unsigned char *buf, const char *fmt, va_list args) \ a = va_arg(args, uint64_t *); \ while (n--) \ { \ - *a++ = (uint32_t) (bp[w0] | (bp[w1] << 8) | (bp[w2] << 16) | (bp[w3] << 24)); \ + *a++ = (uint32_t) ((uint32_t) bp[w0] | ((uint32_t) bp[w1] << 8) | ((uint32_t) bp[w2] << 16) | ((uint32_t) bp[w3] << 24)); \ bp += 4; \ } \ } \ @@ -270,7 +270,7 @@ static size_t name(const unsigned char *buf, const char *fmt, va_list args) \ a = va_arg(args, int64_t *); \ while (n--) \ { \ - *a++ = (int32_t) (bp[w0] | (bp[w1] << 8) | (bp[w2] << 16) | (bp[w3] << 24)); \ + *a++ = (int32_t) ((uint32_t) bp[w0] | ((uint32_t) bp[w1] << 8) | ((uint32_t) bp[w2] << 16) | ((uint32_t) bp[w3] << 24)); \ bp += 4; \ } \ } \ @@ -473,7 +473,7 @@ static size_t name(const unsigned char *buf, const char *fmt, va_list args) \ while (n--) \ { \ pI = va_arg(args, uint32_t *); \ - *pI = (uint32_t) (bp[w0] | (bp[w1] << 8) | (bp[w2] << 16) | (bp[w3] << 24)); \ + *pI = (uint32_t) ((uint32_t) bp[w0] | ((uint32_t) bp[w1] << 8) | ((uint32_t) bp[w2] << 16) | ((uint32_t) bp[w3] << 24)); \ bp += 4; \ } \ break; \ @@ -482,7 +482,7 @@ static size_t name(const unsigned char *buf, const char *fmt, va_list args) \ while (n--) \ { \ pQ = va_arg(args, uint64_t *); \ - *pQ = (uint32_t) (bp[w0] | (bp[w1] << 8) | (bp[w2] << 16) | (bp[w3] << 24)); \ + *pQ = (uint32_t) ((uint32_t) bp[w0] | ((uint32_t) bp[w1] << 8) | ((uint32_t) bp[w2] << 16) | ((uint32_t) bp[w3] << 24)); \ bp += 4; \ } \ break; \ @@ -491,7 +491,7 @@ static size_t name(const unsigned char *buf, const char *fmt, va_list args) \ while (n--) \ { \ pq = va_arg(args, int64_t *); \ - *pq = (int32_t) (bp[w0] | (bp[w1] << 8) | (bp[w2] << 16) | (bp[w3] << 24)); \ + *pq = (int32_t) ((uint32_t) bp[w0] | ((uint32_t) bp[w1] << 8) | ((uint32_t) bp[w2] << 16) | ((uint32_t) bp[w3] << 24)); \ bp += 4; \ } \ break; \ diff --git a/libraries/wuss/create.c b/libraries/wuss/create.c index 424d6e5a..82eaca5c 100644 --- a/libraries/wuss/create.c +++ b/libraries/wuss/create.c @@ -65,9 +65,12 @@ result_t wuss_create(screen_t *scr, if (config != NULL) { pal = config->palette; + w->backdrop = config->backdrop; } else { + w->backdrop = wuss_NO_BACKGROUND; + if (palette == NULL) { bg = palette_PICO8_DARK_BLUE; @@ -79,26 +82,28 @@ result_t wuss_create(screen_t *scr, fg = (w->npalette > 1) ? 1 : 0; } - pal.title.bg = bg; - pal.title.fg = fg; - pal.back = fg; - pal.close = fg; - pal.toggle = fg; - pal.resize = bg; - pal.arrows = bg; - pal.wells = bg; - pal.sausages = fg; + pal.title.bg = bg; + pal.title.fg = fg; + pal.back = fg; + pal.close = fg; + pal.toggle = fg; + pal.resize = bg; + pal.scroll.arrows = bg; + pal.scroll.wells = bg; + pal.scroll.sausages = fg; } - if (pal.title.bg < 0 || pal.title.bg >= w->npalette || - pal.title.fg < 0 || pal.title.fg >= w->npalette || - pal.back < 0 || pal.back >= w->npalette || - pal.close < 0 || pal.close >= w->npalette || - pal.toggle < 0 || pal.toggle >= w->npalette || - pal.resize < 0 || pal.resize >= w->npalette || - pal.arrows < 0 || pal.arrows >= w->npalette || - pal.wells < 0 || pal.wells >= w->npalette || - pal.sausages < 0 || pal.sausages >= w->npalette) + if (pal.title.bg < 0 || pal.title.bg >= w->npalette || + pal.title.fg < 0 || pal.title.fg >= w->npalette || + pal.back < 0 || pal.back >= w->npalette || + pal.close < 0 || pal.close >= w->npalette || + pal.toggle < 0 || pal.toggle >= w->npalette || + pal.resize < 0 || pal.resize >= w->npalette || + pal.scroll.arrows < 0 || pal.scroll.arrows >= w->npalette || + pal.scroll.wells < 0 || pal.scroll.wells >= w->npalette || + pal.scroll.sausages < 0 || pal.scroll.sausages >= w->npalette || + (w->backdrop != wuss_NO_BACKGROUND && + (w->backdrop < 0 || w->backdrop >= w->npalette))) { free(w->palette); free(w); diff --git a/libraries/wuss/furniture/drag-resize.c b/libraries/wuss/furniture/drag-resize.c index 965ff557..19995ec4 100644 --- a/libraries/wuss/furniture/drag-resize.c +++ b/libraries/wuss/furniture/drag-resize.c @@ -13,8 +13,8 @@ void wuss__furniture_drag_resize(wuss_window_t *window, point_t p) width = p.x - content.x0; height = p.y - content.y0; - width = CLAMP(width, WUSS_MIN_CONTENT, window->doc_width); - height = CLAMP(height, WUSS_MIN_CONTENT, window->doc_height); + width = CLAMP(width, WUSS_MIN_CONTENT, window->doc.w); + height = CLAMP(height, WUSS_MIN_CONTENT, window->doc.h); - wuss_window_resize(window, width, height); + wuss_window_resize(window, (size2d_t) { width, height }); } diff --git a/libraries/wuss/furniture/draw.c b/libraries/wuss/furniture/draw.c index bf0c2102..bc4dc31b 100644 --- a/libraries/wuss/furniture/draw.c +++ b/libraries/wuss/furniture/draw.c @@ -23,8 +23,7 @@ void wuss__furniture_draw(wuss_t *wuss, { wuss->scr->clip = clipped; screen_draw_rect(wuss->scr, - titlebar.x0, titlebar.y0, - titlebar.x1 - titlebar.x0, titlebar.y1 - titlebar.y0, + titlebar.x0, titlebar.y0, box_size(&titlebar), wuss->palette[wuss->furniture_colours.title.bg]); if (wuss->font != NULL && window->title[0] != '\0') @@ -32,6 +31,7 @@ void wuss__furniture_draw(wuss_t *wuss, point_t pos; int text_x0, text_x1, titlelen, split_point; bmfont_width_t width; + box_t text_box, text_clip; text_x0 = titlebar.x0 + 2; if (!(window->flags & wuss_WINDOW_NO_CLOSE)) @@ -58,16 +58,26 @@ void wuss__furniture_draw(wuss_t *wuss, text_x1 = toggle.x0 - 2; } - if (text_x1 > text_x0) + /* A title too wide for its slot mustn't bleed into a neighbouring + * icon: clip drawing to the slot itself, not just the whole titlebar, + * so a too-long title is cut off cleanly rather than overdrawing + * whatever furniture the current redraw didn't happen to touch. */ + text_box.x0 = text_x0; + text_box.y0 = titlebar.y0; + text_box.x1 = text_x1; + text_box.y1 = titlebar.y1; + if (text_x1 > text_x0 && !box_intersection(&text_box, &clipped, &text_clip)) { titlelen = (int) strlen(window->title); bmfont_measure(wuss->font, window->title, titlelen, text_x1 - text_x0, &split_point, &width); pos.x = (split_point < titlelen) ? text_x0 : text_x0 + MAX(0, ((text_x1 - text_x0) - width) / 2); pos.y = titlebar.y0 + 2; + wuss->scr->clip = text_clip; bmfont_draw(wuss->font, wuss->scr, window->title, titlelen, wuss->palette[wuss->furniture_colours.title.fg], wuss->palette[wuss->furniture_colours.title.bg], &pos, NULL); + wuss->scr->clip = clipped; } } @@ -77,8 +87,7 @@ void wuss__furniture_draw(wuss_t *wuss, wuss__close_box(window, &close); screen_draw_rect(wuss->scr, - close.x0, close.y0, - close.x1 - close.x0, close.y1 - close.y0, + close.x0, close.y0, box_size(&close), wuss->palette[wuss->furniture_colours.close]); } @@ -88,8 +97,7 @@ void wuss__furniture_draw(wuss_t *wuss, wuss__back_box(window, &back); screen_draw_rect(wuss->scr, - back.x0, back.y0, - back.x1 - back.x0, back.y1 - back.y0, + back.x0, back.y0, box_size(&back), wuss->palette[wuss->furniture_colours.back]); } @@ -99,8 +107,7 @@ void wuss__furniture_draw(wuss_t *wuss, wuss__toggle_box(window, &toggle); screen_draw_rect(wuss->scr, - toggle.x0, toggle.y0, - toggle.x1 - toggle.x0, toggle.y1 - toggle.y0, + toggle.x0, toggle.y0, box_size(&toggle), wuss->palette[wuss->furniture_colours.toggle]); } } @@ -114,8 +121,7 @@ void wuss__furniture_draw(wuss_t *wuss, { wuss->scr->clip = clipped; screen_draw_rect(wuss->scr, - resize.x0, resize.y0, - resize.x1 - resize.x0, resize.y1 - resize.y0, + resize.x0, resize.y0, box_size(&resize), wuss->palette[wuss->furniture_colours.resize]); } } @@ -128,32 +134,32 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&up, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, up.x0, up.y0, up.x1 - up.x0, up.y1 - up.y0, - wuss->palette[wuss->furniture_colours.arrows]); + screen_draw_rect(wuss->scr, up.x0, up.y0, box_size(&up), + wuss->palette[wuss->furniture_colours.scroll.arrows]); } wuss__vscroll_down_box(window, &down); if (!box_intersection(&down, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, down.x0, down.y0, down.x1 - down.x0, down.y1 - down.y0, - wuss->palette[wuss->furniture_colours.arrows]); + screen_draw_rect(wuss->scr, down.x0, down.y0, box_size(&down), + wuss->palette[wuss->furniture_colours.scroll.arrows]); } wuss__vscroll_well_box(window, &well); if (!box_intersection(&well, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, well.x0, well.y0, well.x1 - well.x0, well.y1 - well.y0, - wuss->palette[wuss->furniture_colours.wells]); + screen_draw_rect(wuss->scr, well.x0, well.y0, box_size(&well), + wuss->palette[wuss->furniture_colours.scroll.wells]); } wuss__vscroll_sausage_box(window, &sausage); if (!box_intersection(&sausage, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, sausage.x0, sausage.y0, sausage.x1 - sausage.x0, sausage.y1 - sausage.y0, - wuss->palette[wuss->furniture_colours.sausages]); + screen_draw_rect(wuss->scr, sausage.x0, sausage.y0, box_size(&sausage), + wuss->palette[wuss->furniture_colours.scroll.sausages]); } } @@ -165,32 +171,70 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&left, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, left.x0, left.y0, left.x1 - left.x0, left.y1 - left.y0, - wuss->palette[wuss->furniture_colours.arrows]); + screen_draw_rect(wuss->scr, left.x0, left.y0, box_size(&left), + wuss->palette[wuss->furniture_colours.scroll.arrows]); } wuss__hscroll_right_box(window, &right); if (!box_intersection(&right, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, right.x0, right.y0, right.x1 - right.x0, right.y1 - right.y0, - wuss->palette[wuss->furniture_colours.arrows]); + screen_draw_rect(wuss->scr, right.x0, right.y0, box_size(&right), + wuss->palette[wuss->furniture_colours.scroll.arrows]); } wuss__hscroll_well_box(window, &well); if (!box_intersection(&well, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, well.x0, well.y0, well.x1 - well.x0, well.y1 - well.y0, - wuss->palette[wuss->furniture_colours.wells]); + screen_draw_rect(wuss->scr, well.x0, well.y0, box_size(&well), + wuss->palette[wuss->furniture_colours.scroll.wells]); } wuss__hscroll_sausage_box(window, &sausage); if (!box_intersection(&sausage, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, sausage.x0, sausage.y0, sausage.x1 - sausage.x0, sausage.y1 - sausage.y0, - wuss->palette[wuss->furniture_colours.sausages]); + screen_draw_rect(wuss->scr, sausage.x0, sausage.y0, box_size(&sausage), + wuss->palette[wuss->furniture_colours.scroll.sausages]); + } + } + + { + box_t content, rule; + point_t carve; + + /* Interior rules: where furniture is carved off the content area's right + * or bottom edge, the last pixel of the carve is a dividing line. */ + wuss__content_box(window, &content); + wuss__furniture_carve_for(window->flags, wuss__icon_size(window), &carve); + + if (carve.x > 0) + { + rule.x0 = content.x1; + rule.x1 = content.x1 + WUSS_DIVIDER_PX; + rule.y0 = content.y0; + rule.y1 = content.y1; + if (!box_intersection(&rule, full, &clipped)) + { + wuss->scr->clip = clipped; + screen_draw_rect(wuss->scr, rule.x0, rule.y0, box_size(&rule), + wuss->palette[wuss->furniture_colours.title.bg]); + } + } + + if (carve.y > 0) + { + rule.x0 = content.x0; + rule.x1 = content.x1 + ((carve.x > 0) ? WUSS_DIVIDER_PX : 0); /* meet the vertical rule at the corner */ + rule.y0 = content.y1; + rule.y1 = content.y1 + WUSS_DIVIDER_PX; + if (!box_intersection(&rule, full, &clipped)) + { + wuss->scr->clip = clipped; + screen_draw_rect(wuss->scr, rule.x0, rule.y0, box_size(&rule), + wuss->palette[wuss->furniture_colours.title.bg]); + } } } @@ -204,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, width, 1, border); - screen_draw_rect(wuss->scr, window->visible.x0, window->visible.y1 - 1, width, 1, border); - screen_draw_rect(wuss->scr, window->visible.x0, window->visible.y0, 1, height, border); - screen_draw_rect(wuss->scr, window->visible.x1 - 1, window->visible.y0, 1, height, border); + screen_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); } } diff --git a/libraries/wuss/furniture/hscroll-box.c b/libraries/wuss/furniture/hscroll-box.c index d25155ea..763071e7 100644 --- a/libraries/wuss/furniture/hscroll-box.c +++ b/libraries/wuss/furniture/hscroll-box.c @@ -70,14 +70,14 @@ int wuss__hscroll_well_px(const wuss_window_t *window) void wuss__hscroll_sausage_box(const wuss_window_t *window, box_t *out) { box_t well, content; - int well_px, content_size, doc_size, sausage_px, sausage_x0; + int well_px, content_size, doc_size, sausage_px, sausage_x0, inset; wuss__hscroll_well_box(window, &well); wuss__content_box(window, &content); well_px = well.x1 - well.x0; content_size = content.x1 - content.x0; - doc_size = window->doc_width; + doc_size = window->doc.w; if (doc_size < content_size) doc_size = content_size; @@ -92,8 +92,12 @@ void wuss__hscroll_sausage_box(const wuss_window_t *window, box_t *out) else sausage_x0 = well.x0; - out->y0 = well.y0; - out->y1 = well.y1; + /* Inset the sausage from the well's long edges, but only while that + * leaves something to draw: a tiny titlebar font can make the well + * narrower than two insets, which would invert the box. */ + inset = (well.y1 - well.y0 > 2 * WUSS_SCROLL_INSET) ? WUSS_SCROLL_INSET : 0; + out->y0 = well.y0 + inset; + out->y1 = well.y1 - inset; out->x0 = sausage_x0; out->x1 = sausage_x0 + sausage_px; } diff --git a/libraries/wuss/furniture/invalidate.c b/libraries/wuss/furniture/invalidate.c index e7385d9e..bd044e3f 100644 --- a/libraries/wuss/furniture/invalidate.c +++ b/libraries/wuss/furniture/invalidate.c @@ -9,9 +9,14 @@ * pixels sitting wherever those strips used to be. */ void wuss__furniture_invalidate_for(wuss_window_t *window, const box_t *visible) { - int outline_px; + int outline_px; + point_t carve; outline_px = wuss__outline_px(window); + /* Ask for the same carve the layout uses rather than reading the + * scrollbar flags directly: a window with both scrollbars off but resize + * on still reserves both strips, for the resize icon and the rules. */ + wuss__furniture_carve_for(window->flags, wuss__icon_size(window), &carve); if (!(window->flags & wuss_WINDOW_NO_TITLEBAR)) { @@ -24,23 +29,23 @@ void wuss__furniture_invalidate_for(wuss_window_t *window, const box_t *visible) wuss__invalidate_clipped(window, &titlebar); } - if (!(window->flags & wuss_WINDOW_NO_VSCROLL)) + if (carve.x > 0) { box_t column; column.x1 = visible->x1 - outline_px; - column.x0 = column.x1 - wuss__icon_size(window); + column.x0 = column.x1 - carve.x; /* includes the interior rule */ column.y0 = visible->y0; column.y1 = visible->y1; wuss__invalidate_clipped(window, &column); } - if (!(window->flags & wuss_WINDOW_NO_HSCROLL)) + if (carve.y > 0) { box_t row; row.y1 = visible->y1 - outline_px; - row.y0 = row.y1 - wuss__icon_size(window); + row.y0 = row.y1 - carve.y; /* includes the interior rule */ row.x0 = visible->x0; row.x1 = visible->x1; wuss__invalidate_clipped(window, &row); diff --git a/libraries/wuss/furniture/scroll-action.c b/libraries/wuss/furniture/scroll-action.c index 9dbeaf7a..9b9edf54 100644 --- a/libraries/wuss/furniture/scroll-action.c +++ b/libraries/wuss/furniture/scroll-action.c @@ -9,8 +9,8 @@ point_t wuss__scroll_clamp(const wuss_window_t *window, point_t desired) wuss__content_box(window, &content); - max_x = window->doc_width - (content.x1 - content.x0); - max_y = window->doc_height - (content.y1 - content.y0); + 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) @@ -54,13 +54,13 @@ void wuss__furniture_drag_sausage(wuss_window_t *window, { well_px = wuss__hscroll_well_px(window); content_size = content.x1 - content.x0; - doc_size = window->doc_width; + doc_size = window->doc.w; } else { well_px = wuss__vscroll_well_px(window); content_size = content.y1 - content.y0; - doc_size = window->doc_height; + doc_size = window->doc.h; } max_scroll = doc_size - content_size; diff --git a/libraries/wuss/furniture/toggle-action.c b/libraries/wuss/furniture/toggle-action.c index 376d59f6..4203f778 100644 --- a/libraries/wuss/furniture/toggle-action.c +++ b/libraries/wuss/furniture/toggle-action.c @@ -10,7 +10,7 @@ void wuss__furniture_toggle_size(wuss_window_t *window) before = window->visible; - if (window->toggled) + if (wuss__window_toggled(window)) { new_visible = window->pre_toggle; } @@ -29,8 +29,8 @@ void wuss__furniture_toggle_size(wuss_window_t *window) * account for scrollbar/resize-icon furniture (carve), which sits * outside the content area same as create.c/resize.c do, or the window * ends up carve.x/carve.y short of the doc size it's meant to reach. */ - available_width = window->wuss->scr->width - window->visible.x0 - 2 * outline_px - carve.x; - available_height = window->wuss->scr->height - window->visible.y0 - 2 * outline_px - titlebar_height - carve.y; + available_width = window->wuss->scr->size.w - window->visible.x0 - 2 * outline_px - carve.x; + available_height = window->wuss->scr->size.h - window->visible.y0 - 2 * outline_px - titlebar_height - carve.y; /* a window dragged far enough off-screen leaves no room at all to the * screen edge, so the above can go negative -- floor it like @@ -42,8 +42,8 @@ void wuss__furniture_toggle_size(wuss_window_t *window) available_width = MAX(available_width, WUSS_MIN_CONTENT); available_height = MAX(available_height, WUSS_MIN_CONTENT); - width = MIN(window->doc_width, available_width); - height = MIN(window->doc_height, available_height); + width = MIN(window->doc.w, available_width); + height = MIN(window->doc.h, available_height); window->pre_toggle = window->visible; @@ -54,7 +54,7 @@ void wuss__furniture_toggle_size(wuss_window_t *window) } window->visible = new_visible; - window->toggled = !window->toggled; + wuss__window_set_toggled(window, !wuss__window_toggled(window)); { point_t new_scroll; @@ -80,7 +80,7 @@ void wuss__furniture_toggle_size(wuss_window_t *window) } } - if (!(window->flags & wuss_WINDOW_NO_TOGGLE_BLIT) && + 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)) diff --git a/libraries/wuss/furniture/vscroll-box.c b/libraries/wuss/furniture/vscroll-box.c index ae5f9d0b..58e84f12 100644 --- a/libraries/wuss/furniture/vscroll-box.c +++ b/libraries/wuss/furniture/vscroll-box.c @@ -72,14 +72,14 @@ int wuss__vscroll_well_px(const wuss_window_t *window) void wuss__vscroll_sausage_box(const wuss_window_t *window, box_t *out) { box_t well, content; - int well_px, content_size, doc_size, sausage_px, sausage_y0; + int well_px, content_size, doc_size, sausage_px, sausage_y0, inset; wuss__vscroll_well_box(window, &well); wuss__content_box(window, &content); well_px = well.y1 - well.y0; content_size = content.y1 - content.y0; - doc_size = window->doc_height; + doc_size = window->doc.h; if (doc_size < content_size) doc_size = content_size; @@ -94,8 +94,12 @@ void wuss__vscroll_sausage_box(const wuss_window_t *window, box_t *out) else sausage_y0 = well.y0; - out->x0 = well.x0; - out->x1 = well.x1; + /* Inset the sausage from the well's long edges, but only while that + * leaves something to draw: a tiny titlebar font can make the well + * narrower than two insets, which would invert the box. */ + inset = (well.x1 - well.x0 > 2 * WUSS_SCROLL_INSET) ? WUSS_SCROLL_INSET : 0; + out->x0 = well.x0 + inset; + out->x1 = well.x1 - inset; out->y0 = sausage_y0; out->y1 = sausage_y0 + sausage_px; } diff --git a/libraries/wuss/get-font.c b/libraries/wuss/get-font.c new file mode 100644 index 00000000..206cd958 --- /dev/null +++ b/libraries/wuss/get-font.c @@ -0,0 +1,12 @@ +/* get-font.c -- wuss - minimal window manager */ + +#include + +#include "impl.h" + +bmfont_t *wuss_get_font(const wuss_t *wuss) +{ + assert(wuss != NULL); + + return wuss->font; +} diff --git a/libraries/wuss/impl.h b/libraries/wuss/impl.h index 531766f6..18425751 100644 --- a/libraries/wuss/impl.h +++ b/libraries/wuss/impl.h @@ -5,6 +5,7 @@ #include "datastruct/list.h" #include "geom/box.h" +#include "geom/size.h" #include "framebuf/screen.h" #include "framebuf/bmfont.h" @@ -25,6 +26,18 @@ #define WUSS_ICON_INSET 3 /* shared by close/back/toggle/resize icons and scrollbar breadth */ #define WUSS_MIN_CONTENT 20 /* resize-drag floor: content can never be squeezed smaller than this */ +#define WUSS_SCROLL_INSET 2 /* sausage cross-axis margin from its well's edges, purely cosmetic */ +#define WUSS_DIVIDER_PX 1 /* interior rule between the content area and the furniture on its right/bottom */ + +/* Internal per-window state, distinct from the public wuss_window_flags_t + * appearance flags a caller sets at creation -- room to grow without + * widening struct wuss_window by an int per flag. */ +typedef enum wuss_window_state +{ + wuss_WINDOW_STATE_NONE = 0, + wuss_WINDOW_STATE_TOGGLED = 1 << 0 /* currently at TOGGLE_SIZE's "full" size */ +} +wuss_window_state_t; struct wuss { @@ -33,6 +46,7 @@ struct wuss colour_t *palette; /* owned */ int npalette; wuss_palette_t furniture_colours; + wuss_colour_t backdrop; /* wuss_NO_BACKGROUND for none */ int titlebar_height; list_t z_order; /* anchor; head = topmost window */ struct wuss__furniture furniture; @@ -47,11 +61,12 @@ struct wuss_window box_t visible; /* full on-screen footprint: content expanded * outward by any titlebar/outline furniture */ wuss_task_t task; + wuss_colour_t bg; wuss_window_flags_t flags; point_t scroll; /* offset into virtual content space of the * content box's top-left; see wuss_window_set_scroll */ - int doc_width, doc_height; /* virtual document extent, set at creation */ - int toggled; /* currently at TOGGLE_SIZE's "full" size? */ + size2d_t doc; /* virtual document extent, set at creation */ + wuss_window_state_t state; /* see wuss_window_state_t */ box_t pre_toggle; /* visible bounds to restore on the next toggle */ char title[WUSS_TITLE_MAX + 1]; }; @@ -74,6 +89,14 @@ int wuss__clip_to_visible(wuss_window_t *window, const box_t *box, box_t *out); +/* Subtract each of "cuts" (an array of "ncuts" boxes) from "whole", writing + * the surviving pieces to "out" (capacity WUSS_MAX_INVALIDATE_PIECES) and + * returning their count. */ +int wuss__subtract_boxes(const box_t *whole, + const box_t *cuts, + int ncuts, + box_t *out); + /* Notify a window's task that it has been moved or resized, via * wuss_EVENT_OPEN; the return value is discarded, matching how furniture * drawing and other in-line notifications are treated. */ @@ -104,6 +127,19 @@ static inline int wuss__titlebar_height(const wuss_window_t *window) return wuss__titlebar_height_for(window->wuss, window->flags); } +static inline int wuss__window_toggled(const wuss_window_t *window) +{ + return (window->state & wuss_WINDOW_STATE_TOGGLED) != 0; +} + +static inline void wuss__window_set_toggled(wuss_window_t *window, int toggled) +{ + if (toggled) + window->state |= wuss_WINDOW_STATE_TOGGLED; + else + window->state &= (wuss_window_state_t) ~wuss_WINDOW_STATE_TOGGLED; +} + static inline int wuss__outline_px_for(wuss_window_flags_t flags) { return (flags & wuss_WINDOW_NO_OUTLINE) ? 0 : 1; @@ -114,14 +150,19 @@ static inline int wuss__outline_px(const wuss_window_t *window) return wuss__outline_px_for(window->flags); } -/* ponytail: falls back to the default titlebar height when the window has - * none, so NO_TITLEBAR windows that still opt into scrollbars/resize get a - * sane breadth rather than a negative one */ +/* ponytail: falls back to wuss's own titlebar height when the window has + * none, so NO_TITLEBAR windows that still opt into scrollbars/resize match + * their titled siblings instead of a hardcoded size; the hardcoded default + * is only a last-resort floor if even that isn't positive */ static inline int wuss__icon_size_for(const wuss_t *wuss, wuss_window_flags_t flags) { int size; size = wuss__titlebar_height_for(wuss, flags) - 2 * WUSS_ICON_INSET; + if (size > 0) + return size; + + size = wuss->titlebar_height - 2 * WUSS_ICON_INSET; return (size > 0) ? size : WUSS_DEFAULT_TITLEBAR_HEIGHT - 2 * WUSS_ICON_INSET; } @@ -150,6 +191,12 @@ static inline void wuss__furniture_carve_for(wuss_window_flags_t flags, carve->x = icon_size; carve->y = icon_size; } + + /* where furniture abuts the content area, a rule divides the two */ + if (carve->x > 0) + carve->x += WUSS_DIVIDER_PX; + if (carve->y > 0) + carve->y += WUSS_DIVIDER_PX; } #endif /* IMPL_H */ diff --git a/libraries/wuss/redraw.c b/libraries/wuss/redraw.c index 574aec77..e3f403a3 100644 --- a/libraries/wuss/redraw.c +++ b/libraries/wuss/redraw.c @@ -32,11 +32,10 @@ static void redraw_window(wuss_t *wuss, { wuss->scr->clip = pieces[i]; - if (win->task.bg != wuss_NO_BACKGROUND) + if (win->bg != wuss_NO_BACKGROUND) screen_draw_rect(wuss->scr, - content.x0, content.y0, - content.x1 - content.x0, content.y1 - content.y0, - wuss->palette[win->task.bg]); + content.x0, content.y0, box_size(&content), + wuss->palette[win->bg]); if (win->task.handle != NULL) { @@ -90,8 +89,15 @@ result_t wuss_redraw(wuss_t *wuss) full.x0 = 0; full.y0 = 0; - full.x1 = wuss->scr->width; - full.y1 = wuss->scr->height; + full.x1 = wuss->scr->size.w; + full.y1 = wuss->scr->size.h; + + if (wuss->backdrop != wuss_NO_BACKGROUND) + { + wuss->scr->clip = full; + screen_draw_rect(wuss->scr, full.x0, full.y0, box_size(&full), + wuss->palette[wuss->backdrop]); + } rc = result_OK; redraw_from(wuss, wuss->z_order.next, &full, &rc); @@ -117,7 +123,17 @@ result_t wuss_redraw_dirty(wuss_t *wuss) rc = result_OK; for (i = 0; i < wuss->ndirty; i++) + { + if (wuss->backdrop != wuss_NO_BACKGROUND) + { + wuss->scr->clip = wuss->dirty[i]; + screen_draw_rect(wuss->scr, + wuss->dirty[i].x0, wuss->dirty[i].y0, (size2d_t) { wuss->dirty[i].x1 - wuss->dirty[i].x0, wuss->dirty[i].y1 - wuss->dirty[i].y0 }, + wuss->palette[wuss->backdrop]); + } + redraw_from(wuss, wuss->z_order.next, &wuss->dirty[i], &rc); + } box_reset(&wuss->scr->clip); /* see wuss_redraw's comment on the same call */ diff --git a/libraries/wuss/scroll.c b/libraries/wuss/scroll.c index 3900d7b8..f4d0979a 100644 --- a/libraries/wuss/scroll.c +++ b/libraries/wuss/scroll.c @@ -22,20 +22,26 @@ result_t wuss_scroll(wuss_t *wuss, point_t p, int delta, wuss_window_t **hit) if (box_contains_point(&titlebar, x, y)) return result_OK; - wuss__furniture_scroll_step(win, (point_t) { 0, delta }); - if (win->task.handle != NULL) { box_t content; wuss_event_t event; + /* Note the pointer position before scrolling, so the event describes the + * content the pointer was over when the wheel turned, not the content the + * step below brings under it. */ wuss__content_box(win, &content); event.kind = wuss_EVENT_SCROLL; event.data.scroll.point.x = x - content.x0 + win->scroll.x; event.data.scroll.point.y = y - content.y0 + win->scroll.y; event.data.scroll.delta = delta; + + wuss__furniture_scroll_step(win, (point_t) { 0, delta }); + return win->task.handle(win, &event, win->task.task_data); } + wuss__furniture_scroll_step(win, (point_t) { 0, delta }); + return result_OK; } diff --git a/libraries/wuss/task-start.c b/libraries/wuss/task-start.c deleted file mode 100644 index c7000704..00000000 --- a/libraries/wuss/task-start.c +++ /dev/null @@ -1,18 +0,0 @@ -/* task-start.c -- wuss - minimal window manager */ - -#include - -#include "wuss/window.h" - -wuss_task_t wuss_task_start(wuss_event_fn_t *handle, - void *task_data, - wuss_colour_t bg) -{ - wuss_task_t task; - - task.handle = handle; - task.task_data = task_data; - task.bg = bg; - - return task; -} diff --git a/libraries/wuss/task/start.c b/libraries/wuss/task/start.c new file mode 100644 index 00000000..65eb812b --- /dev/null +++ b/libraries/wuss/task/start.c @@ -0,0 +1,16 @@ +/* start.c -- wuss - minimal window manager */ + +#include + +#include "wuss/task.h" + +wuss_task_t wuss_task_start(wuss_event_fn_t *handle, + void *task_data) +{ + wuss_task_t task; + + task.handle = handle; + task.task_data = task_data; + + return task; +} diff --git a/libraries/wuss/task-stop.c b/libraries/wuss/task/stop.c similarity index 77% rename from libraries/wuss/task-stop.c rename to libraries/wuss/task/stop.c index 2945819f..b8cbd80b 100644 --- a/libraries/wuss/task-stop.c +++ b/libraries/wuss/task/stop.c @@ -1,6 +1,6 @@ -/* task-stop.c -- wuss - minimal window manager */ +/* stop.c -- wuss - minimal window manager */ -#include "impl.h" +#include "../impl.h" result_t wuss_task_stop(wuss_window_t *window) { diff --git a/libraries/wuss/test/tasks/ball.c b/libraries/wuss/test/tasks/ball.c index 581f4940..d0af7601 100644 --- a/libraries/wuss/test/tasks/ball.c +++ b/libraries/wuss/test/tasks/ball.c @@ -27,16 +27,16 @@ result_t ball_create(wuss_t *wuss, const colour_t *palette, ball_task_t *task) task->balls[0].dy = 2; task->balls[0].radius = 8; - delegate = wuss_task_start(ball_handle, task, wuss_NO_BACKGROUND); /* ball_redraw paints its own background every frame */ + 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.x1 - box.x0, - box.y1 - box.y0, + box_size(&box), &task->window); } @@ -60,9 +60,7 @@ static result_t ball_redraw(const wuss_event_t *event, void *task_data) sx = event->data.redraw.scroll.x; sy = event->data.redraw.scroll.y; - screen_draw_rect(scr, content->x0, content->y0, - content->x1 - content->x0, - content->y1 - content->y0, + screen_draw_rect(scr, content->x0, content->y0, box_size(content), bc->bg); for (i = 0; i < bc->nballs; i++) @@ -71,8 +69,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, - 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_t) { b->radius * 2, b->radius * 2 }, bc->ball); } return result_OK; @@ -103,9 +100,12 @@ static result_t ball_mouse(wuss_window_t *window, if (bc->nballs >= BALL_MAX) return result_OK; + /* x,y already arrive in virtual content space, as ball positions are + * held; only the invalidation boxes below need the scroll offset taking + * back off to reach window-local coordinates. */ b = &bc->balls[bc->nballs++]; - b->x = x + scroll.x; - b->y = y + scroll.y; + b->x = x; + b->y = y; b->dx = (bc->nballs & 1) ? 3 : -3; b->dy = (bc->nballs & 2) ? 2 : -2; b->radius = 8; diff --git a/libraries/wuss/test/tasks/blank.c b/libraries/wuss/test/tasks/blank.c index a976a1ad..39f3b709 100644 --- a/libraries/wuss/test/tasks/blank.c +++ b/libraries/wuss/test/tasks/blank.c @@ -24,16 +24,16 @@ result_t blank_create(wuss_t *wuss, int npalette, blank_task_t *task) task->index = palette_PICO8_GREEN; task->frame_count = 0; - delegate = wuss_task_start(blank_handle, task, palette_PICO8_GREEN); /* wuss fills the content area itself */ + 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.x1 - box.x0, - box.y1 - box.y0, + box_size(&box), &task->window); } diff --git a/libraries/wuss/test/tasks/chars.c b/libraries/wuss/test/tasks/chars.c new file mode 100644 index 00000000..16c25168 --- /dev/null +++ b/libraries/wuss/test/tasks/chars.c @@ -0,0 +1,138 @@ +/* chars.c -- wuss test - system font glyph grid task */ + +#ifdef USE_SDL + +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/utils.h" +#include "framebuf/palettes.h" +#include "geom/box.h" +#include "geom/point.h" + +#include "chars.h" + +#define CHARS_COLS 32 +#define CHARS_ROWS 8 +#define CHARS_PAD 2 + +result_t chars_create(wuss_t *wuss, + const colour_t *palette, + chars_task_t *task) +{ + wuss_task_t delegate; + box_t box; + bmfont_t *font; + int font_width, font_height, cell_w, cell_h; + + font = wuss_get_font(wuss); + if (font == NULL) + { + task->window = NULL; + return result_OK; + } + + bmfont_get_info(font, &font_width, &font_height); + cell_w = font_width + CHARS_PAD * 2; + cell_h = font_height + CHARS_PAD * 2; + + task->font = font; + task->fg = palette[palette_PICO8_BLACK]; + 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); +} + +static result_t chars_redraw(const wuss_event_t *event, void *task_data) +{ + chars_task_t *cc; + screen_t *scr; + const box_t *bounds; + int font_width, font_height, cell_w, cell_h; + int first, count; + int i, sx, sy; + + cc = task_data; + + scr = event->data.redraw.scr; + bounds = event->data.redraw.bounds; + sx = event->data.redraw.scroll.x; + sy = event->data.redraw.scroll.y; + + bmfont_get_info(cc->font, &font_width, &font_height); + cell_w = font_width + CHARS_PAD * 2; + cell_h = font_height + CHARS_PAD * 2; + + first = ' '; /* bmfont glyphs are laid out contiguously starting here */ + count = bmfont_get_count(cc->font); + /* bmfont indexes its glyph table off a plain char, so a byte value above + * CHAR_MAX would index negatively on a signed-char platform. Never draw + * one, however many glyphs the font claims. */ + if (first + count > CHAR_MAX + 1) + count = CHAR_MAX + 1 - first; + + for (i = 0; i < CHARS_COLS * CHARS_ROWS; i++) + { + int col, row, x, y; + char ch; + point_t pos; + + col = i % CHARS_COLS; + row = i / CHARS_COLS; + x = bounds->x0 - sx + col * cell_w; + y = bounds->y0 - sy + row * cell_h; + + screen_draw_rect(scr, x, y, (size2d_t) { cell_w, cell_h }, cc->bg); + + if (i < first || i >= first + count) + continue; /* no glyph for this byte value: leave the cell blank */ + + ch = (char) i; + pos.x = x + CHARS_PAD; + pos.y = y + CHARS_PAD; + bmfont_draw(cc->font, scr, &ch, 1, cc->fg, cc->bg, &pos, NULL); + } + + return result_OK; +} + +result_t chars_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) +{ + if (event->kind == wuss_EVENT_CLOSE) + { + wuss_window_close(window); + ((chars_task_t *) task_data)->window = NULL; + return result_OK; + } + + if (event->kind != wuss_EVENT_REDRAW) + return result_OK; + + return chars_redraw(event, task_data); +} + +#endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/chars.h b/libraries/wuss/test/tasks/chars.h new file mode 100644 index 00000000..80495809 --- /dev/null +++ b/libraries/wuss/test/tasks/chars.h @@ -0,0 +1,35 @@ +/* chars.h -- wuss test - system font glyph grid task */ + +#ifndef TASKS_CHARS_H +#define TASKS_CHARS_H + +#ifdef USE_SDL + +#include "framebuf/bmfont.h" +#include "framebuf/colour.h" +#include "wuss/window.h" + +/* displays every glyph (0-255) of the wuss system font as a 32x8 grid, + * so the whole font can be eyeballed at a glance */ +typedef struct chars_task +{ + wuss_window_t *window; + bmfont_t *font; + colour_t fg, bg; +} +chars_task_t; + +wuss_event_fn_t chars_handle; + +/* create the glyph-grid window against the given wuss instance; does + * nothing and returns result_OK if wuss has no system font */ +result_t chars_create(wuss_t *wuss, + const colour_t *palette, + chars_task_t *task); + +/* destroy the glyph-grid window created by chars_create, if any */ +void chars_destroy(chars_task_t *task); + +#endif /* USE_SDL */ + +#endif /* TASKS_CHARS_H */ diff --git a/libraries/wuss/test/tasks/checker.c b/libraries/wuss/test/tasks/checker.c index 0578ce53..7639a535 100644 --- a/libraries/wuss/test/tasks/checker.c +++ b/libraries/wuss/test/tasks/checker.c @@ -31,16 +31,16 @@ result_t checker_create(wuss_t *wuss, task->band = CHECKER_BAND_DEFAULT; task->band2 = CHECKER_BAND_DEFAULT; - delegate = wuss_task_start(checker_handle, task, wuss_NO_BACKGROUND); /* checker_redraw paints every pixel itself */ + 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.x1 - box.x0, - box.y1 - box.y0, + box_size(&box), &task->window); if (rc != result_OK) return rc; @@ -51,9 +51,9 @@ result_t checker_create(wuss_t *wuss, &box, "Checker 2", wuss_WINDOW_NONE, + wuss_NO_BACKGROUND, &delegate, - box.x1 - box.x0, - box.y1 - box.y0, + box_size(&box), &task->window2); if (rc != result_OK) { diff --git a/libraries/wuss/test/tasks/curve.c b/libraries/wuss/test/tasks/curve.c index 2f25b1c4..b19b09e3 100644 --- a/libraries/wuss/test/tasks/curve.c +++ b/libraries/wuss/test/tasks/curve.c @@ -39,16 +39,16 @@ result_t curve_create(wuss_t *wuss, task->points[2] = (point_t) { 210, 10 }; task->points[3] = (point_t) { 210, 140 }; - delegate = wuss_task_start(curve_handle, task, wuss_NO_BACKGROUND); /* curve_redraw paints its own background */ + 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.x1 - box.x0, - box.y1 - box.y0, + box_size(&box), &task->window); } @@ -79,8 +79,7 @@ static result_t curve_redraw(const wuss_event_t *event, curve_task_t *task) sx = event->data.redraw.scroll.x; sy = event->data.redraw.scroll.y; - screen_draw_rect(scr, content->x0, content->y0, - content->x1 - content->x0, content->y1 - content->y0, + screen_draw_rect(scr, content->x0, content->y0, box_size(content), task->bg); prev = task->points[0]; @@ -115,12 +114,10 @@ static result_t curve_mouse(curve_task_t *task, int y, wuss_window_t *window) { - int i; - point_t scroll; + int i; - wuss_window_get_scroll(window, &scroll); - x += scroll.x; - y += scroll.y; + /* x,y already arrive in virtual content space: wuss_mouse_click/move add the + * window's scroll offset before delivering the event. */ switch (action) { diff --git a/libraries/wuss/test/tasks/gradient.c b/libraries/wuss/test/tasks/gradient.c index 949558f8..78f0c479 100644 --- a/libraries/wuss/test/tasks/gradient.c +++ b/libraries/wuss/test/tasks/gradient.c @@ -36,16 +36,16 @@ result_t gradient_create(wuss_t *wuss, gradient_task_t *task) wuss_task_t delegate; box_t box; - delegate = wuss_task_start(gradient_handle, task, wuss_NO_BACKGROUND); /* gradient_redraw paints every pixel itself */ + 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, - GRADIENT_DOC_WIDTH, - GRADIENT_DOC_HEIGHT, + (size2d_t) { GRADIENT_DOC_WIDTH, GRADIENT_DOC_HEIGHT }, &task->window); } diff --git a/libraries/wuss/test/tasks/image.c b/libraries/wuss/test/tasks/image.c index 63a5f333..d9e55207 100644 --- a/libraries/wuss/test/tasks/image.c +++ b/libraries/wuss/test/tasks/image.c @@ -32,17 +32,17 @@ result_t image_create(wuss_t *wuss, if (rc != result_OK) return rc; - delegate = wuss_task_start(image_handle, task, palette_PICO8_BLACK); /* shows through the image's transparent pixels */ + 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.width, task->bitmap.height * 2 / 3); + 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, - task->bitmap.width, - task->bitmap.height, + (size2d_t) { task->bitmap.size.w, task->bitmap.size.h }, &task->window); } diff --git a/libraries/wuss/test/tasks/launcher.c b/libraries/wuss/test/tasks/launcher.c index 16fcf4a9..32188120 100644 --- a/libraries/wuss/test/tasks/launcher.c +++ b/libraries/wuss/test/tasks/launcher.c @@ -36,16 +36,16 @@ result_t launcher_create(wuss_t *wuss, task->bg = palette[palette_PICO8_WHITE]; task->running_fg = palette[palette_PICO8_LIGHT_GREY]; - delegate = wuss_task_start(launcher_handle, task, wuss_NO_BACKGROUND); /* launcher_redraw paints its own background */ + 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.x1 - box.x0, - box.y1 - box.y0, + box_size(&box), &task->window); } @@ -59,7 +59,7 @@ static result_t launcher_redraw(const wuss_event_t *event, void *task_data) launcher_task_t *lc; screen_t *scr; const box_t *content, *bounds; - int i, font_width, font_height, sy; + int i, font_width, font_height, sx, sy; point_t pos; const launcher_entry_t *entry; @@ -68,10 +68,10 @@ static result_t launcher_redraw(const wuss_event_t *event, void *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, - content->x1 - content->x0, content->y1 - content->y0, + screen_draw_rect(scr, content->x0, content->y0, box_size(content), lc->bg); bmfont_get_info(lc->font, &font_width, &font_height); @@ -81,7 +81,7 @@ static result_t launcher_redraw(const wuss_event_t *event, void *task_data) { entry = &lc->entries[i]; - pos.x = bounds->x0 + LAUNCHER_PAD; + pos.x = bounds->x0 - sx + LAUNCHER_PAD; pos.y = bounds->y0 - sy + LAUNCHER_PAD + i * LAUNCHER_ROW_HEIGHT + (LAUNCHER_ROW_HEIGHT - font_height) / 2; bmfont_draw(lc->font, scr, entry->name, (int) strlen(entry->name), @@ -95,16 +95,14 @@ static result_t launcher_mouse(wuss_window_t *window, int y, void *task_data) { launcher_task_t *lc; int i; - point_t scroll; launcher_entry_t *entry; result_t rc; lc = task_data; - wuss_window_get_scroll(window, &scroll); - NOT_USED(scroll.x); - - i = (y + scroll.y - LAUNCHER_PAD) / LAUNCHER_ROW_HEIGHT; + /* 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; diff --git a/libraries/wuss/test/tasks/palette.c b/libraries/wuss/test/tasks/palette.c index cd1b1794..4e44965a 100644 --- a/libraries/wuss/test/tasks/palette.c +++ b/libraries/wuss/test/tasks/palette.c @@ -23,16 +23,16 @@ result_t palette_create(wuss_t *wuss, task->palette = palette; task->npalette = npalette; - delegate = wuss_task_start(palette_handle, task, palette_PICO8_BLACK); /* backdrop for any rounding gap around the grid */ + 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_TOGGLE_BLIT, /* swatch grid is laid out across the whole window, so a resize must redraw all of it, not just the newly (un)covered edge */ + 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.x1 - box.x0, - box.y1 - box.y0, + box_size(&box), &task->window); } @@ -77,7 +77,7 @@ static result_t palette_redraw(const wuss_event_t *event, void *task_data) x = bounds->x0 - sx + col * cell_w; y = bounds->y0 - sy + row * cell_h; - screen_draw_rect(scr, x, y, cell_w, cell_h, pc->palette[i]); + screen_draw_rect(scr, x, y, (size2d_t) { cell_w, cell_h }, pc->palette[i]); } return result_OK; diff --git a/libraries/wuss/test/tasks/sofa.c b/libraries/wuss/test/tasks/sofa.c index 7db04833..300c89da 100644 --- a/libraries/wuss/test/tasks/sofa.c +++ b/libraries/wuss/test/tasks/sofa.c @@ -21,6 +21,7 @@ #define SOFA_TILT -0.5 /* static camera tilt, radians, so the seat is visible from above */ #define SOFA_SPIN_PER_FRAME 0.02 /* radians/frame at 60fps, one turn every ~5s */ +#define SOFA_ROTATIONS_PER_MODEL 2 /* auto-cycle to the next model after this many full turns */ #define SOFA_CAMERA_DIST 4.0 /* perspective divisor: bigger = flatter */ #define SOFA_UNIT_FRACTION 0.35 /* fraction of min(width,height) per model unit, at zoom 1.0 */ #define SOFA_ZOOM_MIN 0.2 @@ -44,7 +45,7 @@ static const box3_t sofa_parts[] = /* cube corner edges, indexing box3_corners' bit-numbered corners (bit0=x, * bit1=y, bit2=z; each pair differs in exactly one bit) */ -static const int cube_edges[12][2] = +static const int box_cube_edges[12][2] = { { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 }, { 0, 2 }, { 1, 3 }, { 4, 6 }, { 5, 7 }, @@ -76,6 +77,138 @@ static const int ship_edges[18][2] = { 5, 8 }, { 8, 9 }, { 9, 5 }, /* right wing */ }; +/* the five Platonic solids, vertices normalised to unit circumradius */ + +static const vec3_t tetra_vertices[4] = +{ + { 0.577350269189626, 0.577350269189626, 0.577350269189626 }, + { 0.577350269189626, -0.577350269189626, -0.577350269189626 }, + { -0.577350269189626, 0.577350269189626, -0.577350269189626 }, + { -0.577350269189626, -0.577350269189626, 0.577350269189626 }, +}; +static const int tetra_edges[6][2] = +{ + { 0, 1 }, { 0, 2 }, { 0, 3 }, { 1, 2 }, + { 1, 3 }, { 2, 3 }, +}; + +static const vec3_t cube_vertices[8] = +{ + { 0.577350269189626, 0.577350269189626, 0.577350269189626 }, + { 0.577350269189626, 0.577350269189626, -0.577350269189626 }, + { 0.577350269189626, -0.577350269189626, 0.577350269189626 }, + { 0.577350269189626, -0.577350269189626, -0.577350269189626 }, + { -0.577350269189626, 0.577350269189626, 0.577350269189626 }, + { -0.577350269189626, 0.577350269189626, -0.577350269189626 }, + { -0.577350269189626, -0.577350269189626, 0.577350269189626 }, + { -0.577350269189626, -0.577350269189626, -0.577350269189626 }, +}; +static const int cube_edges[12][2] = +{ + { 0, 1 }, { 0, 2 }, { 0, 4 }, { 1, 3 }, + { 1, 5 }, { 2, 3 }, { 2, 6 }, { 3, 7 }, + { 4, 5 }, { 4, 6 }, { 5, 7 }, { 6, 7 }, +}; + +static const vec3_t octa_vertices[6] = +{ + { 1, 0, 0 }, + { -1, 0, 0 }, + { 0, 1, 0 }, + { 0, -1, 0 }, + { 0, 0, 1 }, + { 0, 0, -1 }, +}; +static const int octa_edges[12][2] = +{ + { 0, 2 }, { 0, 3 }, { 0, 4 }, { 0, 5 }, + { 1, 2 }, { 1, 3 }, { 1, 4 }, { 1, 5 }, + { 2, 4 }, { 2, 5 }, { 3, 4 }, { 3, 5 }, +}; + +static const vec3_t icosa_vertices[12] = +{ + { 0, 0.525731112119134, 0.85065080835204 }, + { 0, 0.525731112119134, -0.85065080835204 }, + { 0, -0.525731112119134, 0.85065080835204 }, + { 0, -0.525731112119134, -0.85065080835204 }, + { 0.525731112119134, 0.85065080835204, 0 }, + { 0.525731112119134, -0.85065080835204, 0 }, + { -0.525731112119134, 0.85065080835204, 0 }, + { -0.525731112119134, -0.85065080835204, 0 }, + { 0.85065080835204, 0, 0.525731112119134 }, + { 0.85065080835204, 0, -0.525731112119134 }, + { -0.85065080835204, 0, 0.525731112119134 }, + { -0.85065080835204, 0, -0.525731112119134 }, +}; +static const int icosa_edges[30][2] = +{ + { 0, 2 }, { 0, 4 }, { 0, 6 }, { 0, 8 }, + { 0, 10 }, { 1, 3 }, { 1, 4 }, { 1, 6 }, + { 1, 9 }, { 1, 11 }, { 2, 5 }, { 2, 7 }, + { 2, 8 }, { 2, 10 }, { 3, 5 }, { 3, 7 }, + { 3, 9 }, { 3, 11 }, { 4, 6 }, { 4, 8 }, + { 4, 9 }, { 5, 7 }, { 5, 8 }, { 5, 9 }, + { 6, 10 }, { 6, 11 }, { 7, 10 }, { 7, 11 }, + { 8, 9 }, { 10, 11 }, +}; + +static const vec3_t dodeca_vertices[20] = +{ + { 0.577350269189626, 0.577350269189626, 0.577350269189626 }, + { 0.577350269189626, 0.577350269189626, -0.577350269189626 }, + { 0.577350269189626, -0.577350269189626, 0.577350269189626 }, + { 0.577350269189626, -0.577350269189626, -0.577350269189626 }, + { -0.577350269189626, 0.577350269189626, 0.577350269189626 }, + { -0.577350269189626, 0.577350269189626, -0.577350269189626 }, + { -0.577350269189626, -0.577350269189626, 0.577350269189626 }, + { -0.577350269189626, -0.577350269189626, -0.577350269189626 }, + { 0, 0.35682208977309, 0.934172358962716 }, + { 0, 0.35682208977309, -0.934172358962716 }, + { 0, -0.35682208977309, 0.934172358962716 }, + { 0, -0.35682208977309, -0.934172358962716 }, + { 0.35682208977309, 0.934172358962716, 0 }, + { 0.35682208977309, -0.934172358962716, 0 }, + { -0.35682208977309, 0.934172358962716, 0 }, + { -0.35682208977309, -0.934172358962716, 0 }, + { 0.934172358962716, 0, 0.35682208977309 }, + { 0.934172358962716, 0, -0.35682208977309 }, + { -0.934172358962716, 0, 0.35682208977309 }, + { -0.934172358962716, 0, -0.35682208977309 }, +}; +static const int dodeca_edges[30][2] = +{ + { 0, 8 }, { 0, 12 }, { 0, 16 }, { 1, 9 }, + { 1, 12 }, { 1, 17 }, { 2, 10 }, { 2, 13 }, + { 2, 16 }, { 3, 11 }, { 3, 13 }, { 3, 17 }, + { 4, 8 }, { 4, 14 }, { 4, 18 }, { 5, 9 }, + { 5, 14 }, { 5, 19 }, { 6, 10 }, { 6, 15 }, + { 6, 18 }, { 7, 11 }, { 7, 15 }, { 7, 19 }, + { 8, 10 }, { 9, 11 }, { 12, 14 }, { 13, 15 }, + { 16, 17 }, { 18, 19 }, +}; + +/* a wireframe model: a vertex array plus an edge index array */ +typedef struct wireframe +{ + const vec3_t *vertices; + int nvertices; + const int (*edges)[2]; + int nedges; +} +wireframe_t; + +#define WIREFRAME(v, e) { v, NELEMS(v), e, NELEMS(e) } + +static const wireframe_t polyhedra[] = +{ + WIREFRAME(tetra_vertices, tetra_edges), + WIREFRAME(cube_vertices, cube_edges), + WIREFRAME(octa_vertices, octa_edges), + WIREFRAME(icosa_vertices, icosa_edges), + WIREFRAME(dodeca_vertices, dodeca_edges), +}; + static void box3_corners(const box3_t *box, vec3_t out[8]) { int i; @@ -133,17 +266,18 @@ result_t sofa_create(wuss_t *wuss, const colour_t *palette, sofa_task_t *task) task->zoom = 1.0; task->spinning = true; task->shape = sofa_SHAPE_SOFA; + task->turns = 0; - delegate = wuss_task_start(sofa_handle, task, wuss_NO_BACKGROUND); /* sofa_redraw paints its own background every frame */ + 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.x1 - box.x0, - box.y1 - box.y0, + box_size(&box), &task->window); } @@ -171,9 +305,7 @@ static result_t sofa_redraw(const wuss_event_t *event, void *task_data) screen_draw_rect(scr, content->x0, - content->y0, - content->x1 - content->x0, - content->y1 - content->y0, + content->y0, box_size(content), sc->bg); cx = bounds->x0 - sx + (bounds->x1 - bounds->x0) / 2; @@ -196,13 +328,13 @@ static result_t sofa_redraw(const wuss_event_t *event, void *task_data) { const fix8_point_t *a, *b; - a = &screen[cube_edges[i][0]]; - b = &screen[cube_edges[i][1]]; + a = &screen[box_cube_edges[i][0]]; + b = &screen[box_cube_edges[i][1]]; screen_draw_line_wu_fix8(scr, a->x, a->y, b->x, b->y, sc->line); } } } - else + else if (sc->shape == sofa_SHAPE_SHIP) { fix8_point_t screen[NELEMS(ship_vertices)]; int i; @@ -219,6 +351,26 @@ static result_t sofa_redraw(const wuss_event_t *event, void *task_data) screen_draw_line_wu_fix8(scr, a->x, a->y, b->x, b->y, sc->line); } } + else + { + const wireframe_t *wf; + fix8_point_t screen[NELEMS(dodeca_vertices)]; /* largest solid */ + int i; + + wf = &polyhedra[sc->shape - sofa_SHAPE_TETRAHEDRON]; + + for (i = 0; i < wf->nvertices; i++) + screen[i] = project(rotate_xy(wf->vertices[i], SOFA_TILT, sc->angle), cx, cy, unit); + + for (i = 0; i < wf->nedges; i++) + { + const fix8_point_t *a, *b; + + a = &screen[wf->edges[i][0]]; + b = &screen[wf->edges[i][1]]; + screen_draw_line_wu_fix8(scr, a->x, a->y, b->x, b->y, sc->line); + } + } return result_OK; } @@ -231,7 +383,8 @@ static result_t sofa_mouse(wuss_window_t *window, wuss_button_t button, void *ta if (button == wuss_BUTTON_ADJUST) { - sc->shape = (sc->shape == sofa_SHAPE_SOFA) ? sofa_SHAPE_SHIP : sofa_SHAPE_SOFA; + sc->shape = (sc->shape + 1) % sofa_SHAPE__LIMIT; + sc->turns = 0; wuss_window_invalidate_all(window); } else @@ -267,7 +420,14 @@ static result_t sofa_idle(void *task_data) task->angle += SOFA_SPIN_PER_FRAME; if (task->angle > 2.0 * M_PI) + { task->angle -= 2.0 * M_PI; + if (++task->turns >= SOFA_ROTATIONS_PER_MODEL) + { + task->turns = 0; + task->shape = (task->shape + 1) % sofa_SHAPE__LIMIT; + } + } wuss_window_invalidate_all(task->window); diff --git a/libraries/wuss/test/tasks/sofa.h b/libraries/wuss/test/tasks/sofa.h index 20ace4aa..a092ecaf 100644 --- a/libraries/wuss/test/tasks/sofa.h +++ b/libraries/wuss/test/tasks/sofa.h @@ -14,11 +14,17 @@ typedef enum sofa_shape { sofa_SHAPE_SOFA, - sofa_SHAPE_SHIP + sofa_SHAPE_SHIP, + sofa_SHAPE_TETRAHEDRON, + sofa_SHAPE_CUBE, + sofa_SHAPE_OCTAHEDRON, + sofa_SHAPE_ICOSAHEDRON, + sofa_SHAPE_DODECAHEDRON, + sofa_SHAPE__LIMIT } sofa_shape_t; -/* a wireframe sofa (seat, backrest, two arms) or a spaceship, +/* a wireframe sofa (seat, backrest, two arms), spaceship or Platonic solid, * spinning about its vertical axis; a Select click pauses/resumes the spin, * an Adjust click cycles the model */ typedef struct sofa_task @@ -29,6 +35,7 @@ typedef struct sofa_task double zoom; /* scroll-adjustable */ bool spinning; sofa_shape_t shape; + int turns; /* completed rotations of the current model */ } sofa_task_t; diff --git a/libraries/wuss/test/tasks/text.c b/libraries/wuss/test/tasks/text.c index 0202eabf..629bea22 100644 --- a/libraries/wuss/test/tasks/text.c +++ b/libraries/wuss/test/tasks/text.c @@ -41,7 +41,7 @@ result_t text_create(wuss_t *wuss, task->frame_count = 0; task->resizing = true; - delegate = wuss_task_start(text_handle, task, palette_PICO8_BLUE); + delegate = wuss_task_start(text_handle, task); box = (box_t) BOX_POS_SIZE(120, 100, 220, 180); task->base_width = box.x1 - box.x0; @@ -50,10 +50,10 @@ result_t text_create(wuss_t *wuss, rc = wuss_window_create(wuss, &box, "Lorem Ipsum", - wuss_WINDOW_NONE, + 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.x1 - box.x0, - box.y1 - box.y0, + box_size(&box), &task->window); return rc; @@ -155,7 +155,7 @@ static result_t text_idle(void *task_data) angle = tcx->frame_count * (2.0 * M_PI / TEXT_RESIZE_PERIOD_FRAMES); width = tcx->base_width + (int) (TEXT_RESIZE_AMPLITUDE * sin(angle)); - rc = wuss_window_resize(tcx->window, width, height); + rc = wuss_window_resize(tcx->window, (size2d_t) { width, height }); if (rc != result_OK) logf_warning("text_idle: wuss_window_resize(%d, %d) failed", width, height); diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index 97a7ac2b..0a744361 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -31,6 +31,7 @@ #include "tasks/ball.h" #include "tasks/blank.h" +#include "tasks/chars.h" #include "tasks/checker.h" #include "tasks/curve.h" #include "tasks/gradient.h" @@ -57,6 +58,7 @@ 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; @@ -67,6 +69,7 @@ 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); } @@ -77,6 +80,7 @@ static result_t spawn_gradient(void) { return gradient_create(g_wuss, &g_gradien 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); } @@ -89,6 +93,7 @@ static launcher_entry_t g_launcher_entries[] = { "Ball", spawn_ball, destroy_ball, false }, { "Text", spawn_text, destroy_text, false }, { "Blank", spawn_blank, destroy_blank, false }, + { "Chars", spawn_chars, destroy_chars, false }, { "Palette", spawn_palette, destroy_palette, false }, { "Image", spawn_image, destroy_image, false }, { "Checker", spawn_checker, destroy_checker, false }, @@ -164,13 +169,13 @@ static result_t wuss_interactive_test(const char *resources) define_pico8_palette(palette); - leafname = path_join_leafname("digits-font", "png"); + leafname = path_join_leafname("digits", "png"); filename = path_join_filename(resources, 3, "resources", "bmfonts", leafname); rc = bmfont_create(filename, &font); if (rc != result_OK) goto Failure; - leafname = path_join_leafname("daydream-font", "png"); + leafname = path_join_leafname("daydream", "png"); filename = path_join_filename(resources, 3, "resources", "bmfonts", leafname); rc = bmfont_create(filename, &daydream_font); if (rc != result_OK) @@ -181,9 +186,9 @@ static result_t wuss_interactive_test(const char *resources) goto Failure; #if WUSS_TEST_32BPP - rc = bitmap_init(&bm, scr_width, scr_height, pixelfmt_bgrx8888, rowbytes, palette, pixels); + rc = bitmap_init(&bm, (size2d_t) { scr_width, scr_height }, pixelfmt_bgrx8888, rowbytes, palette, pixels); #else - rc = bitmap_init(&bm, scr_width, scr_height, pixelfmt_p4, rowbytes, palette, pixels); + rc = bitmap_init(&bm, (size2d_t) { scr_width, scr_height }, pixelfmt_p4, rowbytes, palette, pixels); #endif if (rc != result_OK) goto Failure; @@ -227,16 +232,17 @@ static result_t wuss_interactive_test(const char *resources) { wuss_config_t config; - config.titlebar_height = 0; - config.palette.title.bg = palette_PICO8_DARK_BLUE; - config.palette.title.fg = palette_PICO8_WHITE; - config.palette.back = palette_PICO8_GREEN; - config.palette.close = palette_PICO8_RED; - config.palette.toggle = palette_PICO8_ORANGE; - config.palette.resize = palette_PICO8_LAVENDER; - config.palette.arrows = palette_PICO8_BLUE; - config.palette.wells = palette_PICO8_DARK_BLUE; - config.palette.sausages = palette_PICO8_LIGHT_GREY; + config.titlebar_height = 0; + config.palette.title.bg = palette_PICO8_DARK_BLUE; + config.palette.title.fg = palette_PICO8_WHITE; + config.palette.back = palette_PICO8_GREEN; + config.palette.close = palette_PICO8_RED; + config.palette.toggle = palette_PICO8_ORANGE; + config.palette.resize = palette_PICO8_LAVENDER; + config.palette.scroll.arrows = palette_PICO8_BLUE; + config.palette.scroll.wells = palette_PICO8_DARK_BLUE; + config.palette.scroll.sausages = palette_PICO8_LIGHT_GREY; + config.backdrop = palette_PICO8_LIGHT_GREY; rc = wuss_create(&scr, font, palette, NELEMS(palette), &config, &wuss); if (rc != result_OK) @@ -274,6 +280,8 @@ static result_t wuss_interactive_test(const char *resources) case SDL_EVENT_KEY_UP: if (event.key.key == SDLK_Q) quit = true; + else if (event.key.key == SDLK_F1 && (event.key.mod & SDL_KMOD_SHIFT)) + wuss_redraw(wuss); else if (event.key.key == SDLK_F1) garbage_pending = true; else if (event.key.key == SDLK_F3) @@ -379,25 +387,6 @@ static result_t wuss_interactive_test(const char *resources) } else { - int ndirty, i; - - ndirty = wuss_get_dirty_count(wuss); - for (i = 0; i < ndirty; i++) - { - box_t dirty; - - wuss_get_dirty(wuss, i, &dirty); - scr.clip = dirty; - screen_draw_rect(&scr, dirty.x0, dirty.y0, - dirty.x1 - dirty.x0, dirty.y1 - dirty.y0, - palette[palette_PICO8_WHITE]); - } - - /* narrowed above per dirty rect for the flash; screen_copy_rect (used - * for window-drag blitting) reads this clip too, so it must not leak - * into the next frame narrower than the whole screen */ - box_reset(&scr.clip); - wuss_redraw_dirty(wuss); } @@ -456,6 +445,7 @@ typedef struct test_task wuss_mouse_action_t last_action; int last_x, last_y; wuss_button_t last_button; + int last_scroll_x, last_scroll_y; int close_count; int stop_count; int open_count; @@ -486,6 +476,11 @@ static result_t test_handle(wuss_window_t *window, tc->last_button = event->data.mouse.button; break; + case wuss_EVENT_SCROLL: + tc->last_scroll_x = event->data.scroll.point.x; + tc->last_scroll_y = event->data.scroll.point.y; + break; + case wuss_EVENT_CLOSE: tc->close_count++; break; @@ -507,6 +502,38 @@ static result_t test_handle(wuss_window_t *window, /* ----------------------------------------------------------------------- */ +/* Total area covered by the dirty list, counting overlapped pixels once. + * Summing each region's area instead would double-count wherever two + * invalidations overlap, which they legitimately do. */ +static int dirty_union_area(wuss_t *wuss, const box_t *bounds) +{ + static unsigned char covered[512 * 512]; + + box_t region; + int w, h, i, x, y, area; + + w = bounds->x1 - bounds->x0; + h = bounds->y1 - bounds->y0; + if (w <= 0 || h <= 0 || w > 512 || h > 512) + return -1; + + memset(covered, 0, (size_t) w * h); + + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + for (y = MAX(region.y0, bounds->y0); y < MIN(region.y1, bounds->y1); y++) + for (x = MAX(region.x0, bounds->x0); x < MIN(region.x1, bounds->x1); x++) + covered[(y - bounds->y0) * w + (x - bounds->x0)] = 1; + } + + area = 0; + for (i = 0; i < w * h; i++) + area += covered[i]; + + return area; +} + result_t wuss_test(const char *resources) { result_t rc; @@ -534,7 +561,7 @@ result_t wuss_test(const char *resources) if (pixels == NULL) goto Failure; - rc = bitmap_init(&bm, 200, 200, pixelfmt_bgrx8888, rowbytes, NULL, pixels); + rc = bitmap_init(&bm, (size2d_t) { 200, 200 }, pixelfmt_bgrx8888, rowbytes, NULL, pixels); if (rc != result_OK) goto Failure; @@ -542,16 +569,16 @@ result_t wuss_test(const char *resources) printf("test: wuss_create with bad titlebar colour index\n"); - bad_config.titlebar_height = 0; - bad_config.palette.title.bg = 999; - bad_config.palette.title.fg = 0; - bad_config.palette.back = 0; - bad_config.palette.close = 0; - bad_config.palette.toggle = 0; - bad_config.palette.resize = 0; - bad_config.palette.arrows = 0; - bad_config.palette.wells = 0; - bad_config.palette.sausages = 0; + bad_config.titlebar_height = 0; + bad_config.palette.title.bg = 999; + bad_config.palette.title.fg = 0; + bad_config.palette.back = 0; + bad_config.palette.close = 0; + bad_config.palette.toggle = 0; + bad_config.palette.resize = 0; + bad_config.palette.scroll.arrows = 0; + bad_config.palette.scroll.wells = 0; + bad_config.palette.scroll.sausages = 0; rc = wuss_create(&scr, NULL, NULL, 0, &bad_config, &bad_wuss); if (rc != result_WUSS_BAD_COLOUR) goto Failure; @@ -583,9 +610,9 @@ result_t wuss_test(const char *resources) &box_a, "toosmall", wuss_WINDOW_NONE, + wuss_NO_BACKGROUND, NULL, - box_a.x1 - box_a.x0, - box_a.y1 - box_a.y0, + box_size(&box_a), &win_a); if (rc != result_WUSS_TOO_SMALL) goto Failure; @@ -597,7 +624,6 @@ result_t wuss_test(const char *resources) tc_a.open_count = 0; delegate_a.handle = test_handle; delegate_a.task_data = &tc_a; - delegate_a.bg = wuss_NO_BACKGROUND; box_a.x0 = 0; box_a.y0 = 0; @@ -609,9 +635,9 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, &delegate_a, - box_a.x1 - box_a.x0, - box_a.y1 - box_a.y0, + box_size(&box_a), &win_a); if (rc != result_OK) goto Failure; @@ -620,7 +646,6 @@ result_t wuss_test(const char *resources) tc_b.mouse_count = 0; delegate_b.handle = test_handle; delegate_b.task_data = &tc_b; - delegate_b.bg = wuss_NO_BACKGROUND; box_b.x0 = 50; box_b.y0 = 50; @@ -632,9 +657,9 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, &delegate_b, - box_b.x1 - box_b.x0, - box_b.y1 - box_b.y0, + box_size(&box_b), &win_b); if (rc != result_OK) goto Failure; @@ -876,13 +901,13 @@ result_t wuss_test(const char *resources) printf("test: window_resize valid and too-small cases\n"); - rc = wuss_window_resize(win_a, 50, 0); /* zero-height content is invalid */ + rc = wuss_window_resize(win_a, (size2d_t) { 50, 0 }); /* zero-height content is invalid */ if (rc != result_WUSS_TOO_SMALL) goto Failure; if (tc_a.open_count != 1) goto Failure; /* rejected resize: no wuss_EVENT_OPEN */ - rc = wuss_window_resize(win_a, 50, 50); + rc = wuss_window_resize(win_a, (size2d_t) { 50, 50 }); if (rc != result_OK) goto Failure; if (tc_a.open_count != 2) @@ -908,7 +933,6 @@ result_t wuss_test(const char *resources) tc_d.mouse_count = 0; delegate_d.handle = test_handle; delegate_d.task_data = &tc_d; - delegate_d.bg = wuss_NO_BACKGROUND; box_d.x0 = 0; box_d.y0 = 160; box_d.x1 = 30; box_d.y1 = 175; /* shorter than the 20px titlebar_height, still valid: no titlebar to fit */ @@ -918,9 +942,9 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, &delegate_d, - box_d.x1 - box_d.x0, - box_d.y1 - box_d.y0, + box_size(&box_d), &win_d); if (rc != result_OK) goto Failure; @@ -956,7 +980,6 @@ result_t wuss_test(const char *resources) tc_e.mouse_count = 0; delegate_e.handle = test_handle; delegate_e.task_data = &tc_e; - delegate_e.bg = wuss_NO_BACKGROUND; box_e.x0 = 100; box_e.y0 = 0; box_e.x1 = 150; box_e.y1 = 50; @@ -966,9 +989,9 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, &delegate_e, - box_e.x1 - box_e.x0, - box_e.y1 - box_e.y0, + box_size(&box_e), &win_e); if (rc != result_OK) goto Failure; @@ -977,7 +1000,6 @@ result_t wuss_test(const char *resources) tc_f.mouse_count = 0; delegate_f.handle = test_handle; delegate_f.task_data = &tc_f; - delegate_f.bg = wuss_NO_BACKGROUND; box_f.x0 = 130; box_f.y0 = 20; box_f.x1 = 180; box_f.y1 = 70; @@ -987,9 +1009,9 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, &delegate_f, - box_f.x1 - box_f.x0, - box_f.y1 - box_f.y0, + box_size(&box_f), &win_f); if (rc != result_OK) goto Failure; @@ -1045,7 +1067,6 @@ result_t wuss_test(const char *resources) tc_h.mouse_count = 0; delegate_h.handle = test_handle; delegate_h.task_data = &tc_h; - delegate_h.bg = wuss_NO_BACKGROUND; box_h.x0 = 10; box_h.y0 = 10; box_h.x1 = 30; box_h.y1 = 30; @@ -1055,9 +1076,9 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, &delegate_h, - box_h.x1 - box_h.x0, - box_h.y1 - box_h.y0, + box_size(&box_h), &win_h); if (rc != result_OK) goto Failure; @@ -1066,7 +1087,6 @@ result_t wuss_test(const char *resources) tc_g.mouse_count = 0; delegate_g.handle = test_handle; delegate_g.task_data = &tc_g; - delegate_g.bg = wuss_NO_BACKGROUND; box_g.x0 = 0; box_g.y0 = 0; box_g.x1 = 150; box_g.y1 = 150; /* G is created after H, so G is topmost and fully covers H */ @@ -1076,9 +1096,9 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, &delegate_g, - box_g.x1 - box_g.x0, - box_g.y1 - box_g.y0, + box_size(&box_g), &win_g); if (rc != result_OK) goto Failure; @@ -1100,7 +1120,7 @@ result_t wuss_test(const char *resources) if (tc_h.redraw_count != before_h || tc_g.redraw_count != before_g) goto Failure; /* nothing visible changed: no redraw of either window */ - rc = wuss_window_resize(win_h, 25, 25); /* still entirely within G's footprint */ + rc = wuss_window_resize(win_h, (size2d_t) { 25, 25 }); /* still entirely within G's footprint */ if (rc != result_OK) goto Failure; if (wuss_get_dirty_count(wuss) != 0) @@ -1128,7 +1148,6 @@ result_t wuss_test(const char *resources) tc_i.mouse_count = 0; delegate_i.handle = test_handle; delegate_i.task_data = &tc_i; - delegate_i.bg = wuss_NO_BACKGROUND; box_i.x0 = 0; box_i.y0 = 0; box_i.x1 = 100; box_i.y1 = 100; @@ -1138,9 +1157,9 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, &delegate_i, - box_i.x1 - box_i.x0, - box_i.y1 - box_i.y0, + box_size(&box_i), &win_i); if (rc != result_OK) goto Failure; @@ -1149,7 +1168,6 @@ result_t wuss_test(const char *resources) tc_j.mouse_count = 0; delegate_j.handle = test_handle; delegate_j.task_data = &tc_j; - delegate_j.bg = wuss_NO_BACKGROUND; box_j.x0 = 50; box_j.y0 = 0; box_j.x1 = 150; box_j.y1 = 100; /* J created after I, so J is topmost, covering I's right half */ @@ -1159,9 +1177,9 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, &delegate_j, - box_j.x1 - box_j.x0, - box_j.y1 - box_j.y0, + box_size(&box_j), &win_j); if (rc != result_OK) goto Failure; @@ -1200,7 +1218,6 @@ result_t wuss_test(const char *resources) tc_m.mouse_count = 0; delegate_m.handle = test_handle; delegate_m.task_data = &tc_m; - delegate_m.bg = wuss_NO_BACKGROUND; box_m.x0 = 10; box_m.y0 = 10; box_m.x1 = 60; box_m.y1 = 60; /* 50x50, fully on-screen, topmost (created last) */ @@ -1210,9 +1227,9 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, &delegate_m, - box_m.x1 - box_m.x0, - box_m.y1 - box_m.y0, + box_size(&box_m), &win_m); if (rc != result_OK) goto Failure; @@ -1255,7 +1272,6 @@ result_t wuss_test(const char *resources) tc_h.mouse_count = 0; delegate_h.handle = test_handle; delegate_h.task_data = &tc_h; - delegate_h.bg = wuss_NO_BACKGROUND; box_h.x0 = 130; box_h.y0 = 50; box_h.x1 = 190; box_h.y1 = 100; @@ -1263,9 +1279,9 @@ result_t wuss_test(const char *resources) &box_h, "H", wuss_WINDOW_NONE, + wuss_NO_BACKGROUND, &delegate_h, - box_h.x1 - box_h.x0, - box_h.y1 - box_h.y0, + box_size(&box_h), &win_h); if (rc != result_OK) goto Failure; @@ -1274,7 +1290,6 @@ result_t wuss_test(const char *resources) tc_g.mouse_count = 0; delegate_g.handle = test_handle; delegate_g.task_data = &tc_g; - delegate_g.bg = wuss_NO_BACKGROUND; box_g.x0 = 110; box_g.y0 = 30; box_g.x1 = 160; box_g.y1 = 80; @@ -1282,9 +1297,9 @@ result_t wuss_test(const char *resources) &box_g, "G", wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL, + wuss_NO_BACKGROUND, &delegate_g, - box_g.x1 - box_g.x0, - box_g.y1 - box_g.y0, + box_size(&box_g), &win_g); if (rc != result_OK) goto Failure; @@ -1401,12 +1416,13 @@ result_t wuss_test(const char *resources) tc_t.mouse_count = 0; delegate_t.handle = test_handle; delegate_t.task_data = &tc_t; - delegate_t.bg = wuss_NO_BACKGROUND; box_t_win.x0 = 10; box_t_win.y0 = 10; box_t_win.x1 = 50; box_t_win.y1 = 50; /* 40x40 content, room to grow to a 200x200 doc */ rc = wuss_window_create(wuss, &box_t_win, "T", wuss_WINDOW_NONE, - &delegate_t, 200, 200, &win_t); + wuss_NO_BACKGROUND, + &delegate_t, + (size2d_t) { 200, 200 }, &win_t); if (rc != result_OK) goto Failure; @@ -1566,12 +1582,13 @@ result_t wuss_test(const char *resources) tc_r.mouse_count = 0; delegate_r.handle = test_handle; delegate_r.task_data = &tc_r; - delegate_r.bg = wuss_NO_BACKGROUND; box_r.x0 = 10; box_r.y0 = 10; box_r.x1 = 50; box_r.y1 = 50; /* 40x40 content; doc bigger than that, so it starts scrollable */ rc = wuss_window_create(wuss, &box_r, "R", wuss_WINDOW_NONE, - &delegate_r, 70, 70, &win_r); + wuss_NO_BACKGROUND, + &delegate_r, + (size2d_t) { 70, 70 }, &win_r); if (rc != result_OK) goto Failure; @@ -1649,7 +1666,7 @@ result_t wuss_test(const char *resources) wuss_window_close(win_r); } - printf("test: wuss_WINDOW_NO_TOGGLE_BLIT redraws the whole window instead of blitting\n"); + printf("test: wuss_WINDOW_NO_RESIZE_BLIT redraws the whole window instead of blitting\n"); { test_task_t tc_nb; @@ -1663,12 +1680,13 @@ result_t wuss_test(const char *resources) tc_nb.mouse_count = 0; delegate_nb.handle = test_handle; delegate_nb.task_data = &tc_nb; - delegate_nb.bg = wuss_NO_BACKGROUND; box_nb.x0 = 10; box_nb.y0 = 10; box_nb.x1 = 50; box_nb.y1 = 50; /* 40x40 content, room to grow to a 200x200 doc */ - rc = wuss_window_create(wuss, &box_nb, "NB", wuss_WINDOW_NO_TOGGLE_BLIT, - &delegate_nb, 200, 200, &win_nb); + rc = wuss_window_create(wuss, &box_nb, "NB", wuss_WINDOW_NO_RESIZE_BLIT, + wuss_NO_BACKGROUND, + &delegate_nb, + (size2d_t) { 200, 200 }, &win_nb); if (rc != result_OK) goto Failure; @@ -1717,7 +1735,7 @@ result_t wuss_test(const char *resources) interior_dirty = 1; } if (!interior_dirty) - goto Failure; /* wuss_WINDOW_NO_TOGGLE_BLIT must skip the blit path + goto Failure; /* wuss_WINDOW_NO_RESIZE_BLIT must skip the blit path * entirely, so even an interior pixel the blit would * otherwise have preserved comes out dirty */ @@ -1742,12 +1760,12 @@ result_t wuss_test(const char *resources) tc_u.mouse_count = 0; delegate_u.handle = test_handle; delegate_u.task_data = &tc_u; - delegate_u.bg = wuss_NO_BACKGROUND; box_u.x0 = 80; box_u.y0 = 80; box_u.x1 = 120; box_u.y1 = 120; /* 40x40 content */ - rc = wuss_window_create(wuss, &box_u, "U", wuss_WINDOW_NONE, /* scrollbars on: carve.x/y = icon size */ - &delegate_u, 70, 70, &win_u); /* doc size well within the 200x200 screen: growth is doc-limited, not screen-limited */ + rc = wuss_window_create(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 */ if (rc != result_OK) goto Failure; @@ -1840,7 +1858,6 @@ result_t wuss_test(const char *resources) tc_v.mouse_count = 0; delegate_v.handle = test_handle; delegate_v.task_data = &tc_v; - delegate_v.bg = wuss_NO_BACKGROUND; box_v.x0 = 10; box_v.y0 = 10; box_v.x1 = 70; box_v.y1 = 70; /* 60x60 content -- wide enough titlebar that @@ -1854,7 +1871,9 @@ result_t wuss_test(const char *resources) * Doc big enough that maximize is * screen-limited, not doc-limited. */ rc = wuss_window_create(wuss, &box_v, "V", wuss_WINDOW_NONE, - &delegate_v, 200, 200, &win_v); + wuss_NO_BACKGROUND, + &delegate_v, + (size2d_t) { 200, 200 }, &win_v); if (rc != result_OK) goto Failure; @@ -1943,13 +1962,730 @@ result_t wuss_test(const char *resources) wuss_window_close(win_v); } + printf("test: dragging a back-most window with nothing above it still blits\n"); + + { + test_task_t tc_k, tc_l; + wuss_task_t delegate_k, delegate_l; + box_t box_k, box_l; + wuss_window_t *win_k, *win_l; + int before_k, before_l; + + tc_k.redraw_count = 0; + tc_k.mouse_count = 0; + delegate_k.handle = test_handle; + delegate_k.task_data = &tc_k; + + box_k.x0 = 0; box_k.y0 = 140; /* clear of the still-open A/B windows above */ + box_k.x1 = 50; box_k.y1 = 175; + rc = wuss_window_create(wuss, + &box_k, + "K", + wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, + &delegate_k, + box_size(&box_k), + &win_k); + if (rc != result_OK) + goto Failure; + + tc_l.redraw_count = 0; + tc_l.mouse_count = 0; + delegate_l.handle = test_handle; + delegate_l.task_data = &tc_l; + + box_l.x0 = 120; box_l.y0 = 140; /* well clear of K, so never overlaps it */ + box_l.x1 = 170; box_l.y1 = 175; + rc = wuss_window_create(wuss, + &box_l, + "L", + wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, + &delegate_l, + box_size(&box_l), + &win_l); + if (rc != result_OK) + goto Failure; + + wuss_window_restack(win_k, wuss_ZORDER_BACK); /* K is no longer topmost, but L never overlaps it */ + + rc = wuss_redraw_dirty(wuss); /* flush the restack's own dirty region first */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_k, &visible); + + rc = wuss_mouse_click(wuss, (point_t) { visible.x0 + 31, visible.y0 + 11 }, wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); /* K's titlebar, clear of the close icon */ + if (rc != result_OK) + goto Failure; + if (hit != win_k) + goto Failure; + + before_k = tc_k.redraw_count; + before_l = tc_l.redraw_count; + rc = wuss_mouse_move(wuss, (point_t) { visible.x0 + 45, visible.y0 + 21 }, &hit); + if (rc != result_OK) + goto Failure; + if (hit != win_k) + goto Failure; + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + if (tc_k.redraw_count != before_k || tc_l.redraw_count != before_l) + goto Failure; /* blitted, not redrawn: nothing above K overlapped its old + * footprint, so the move fast path must still apply even + * though K isn't topmost */ + + rc = wuss_mouse_click(wuss, (point_t) { visible.x0 + 45, visible.y0 + 21 }, wuss_BUTTON_SELECT, wuss_MOUSE_UP, &hit); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_k); + wuss_window_close(win_l); + } + + printf("test: resizing a window only invalidates the grown/shrunk sliver\n"); + + { + test_task_t tc_m2; + wuss_task_t delegate_m2; + box_t box_m2, before2, after2, region; + wuss_window_t *win_m2; + int i, dirty_area, full_area, interior_x, interior_y, interior_dirty; + + tc_m2.redraw_count = 0; + tc_m2.mouse_count = 0; + delegate_m2.handle = test_handle; + delegate_m2.task_data = &tc_m2; + + box_m2.x0 = 0; box_m2.y0 = 0; + box_m2.x1 = 40; box_m2.y1 = 40; + rc = wuss_window_create(wuss, + &box_m2, + "M2", + wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL, + wuss_NO_BACKGROUND, + &delegate_m2, + box_size(&box_m2), + &win_m2); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush the creation invalidation first */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_m2, &before2); + interior_x = before2.x0 + 2; /* inside the untouched left edge */ + interior_y = before2.y0 + 25; /* below the titlebar, in plain content */ + + rc = wuss_window_resize(win_m2, (size2d_t) { 80, 80 }); /* grow */ + if (rc != result_OK) + goto Failure; + + if (wuss_get_dirty_count(wuss) == 0) + goto Failure; + + interior_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (box_contains_point(®ion, interior_x, interior_y)) + interior_dirty = 1; + } + if (interior_dirty) + goto Failure; /* untouched top-left corner, unchanged by growing bottom-right */ + + wuss_window_get_visible_bounds(win_m2, &after2); + full_area = (after2.x1 - after2.x0) * (after2.y1 - after2.y0); + dirty_area = dirty_union_area(wuss, &after2); + if (dirty_area < 0 || dirty_area >= full_area) + goto Failure; /* must be less than a full redraw of the grown footprint */ + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + before2 = after2; + + rc = wuss_window_resize(win_m2, (size2d_t) { 40, 40 }); /* shrink back */ + if (rc != result_OK) + goto Failure; + + if (wuss_get_dirty_count(wuss) == 0) + goto Failure; + + interior_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (box_contains_point(®ion, interior_x, interior_y)) + interior_dirty = 1; + } + if (interior_dirty) + goto Failure; /* still untouched: the corner that remains after shrinking */ + + full_area = (before2.x1 - before2.x0) * (before2.y1 - before2.y0); + dirty_area = dirty_union_area(wuss, &before2); + if (dirty_area < 0 || dirty_area >= full_area) + goto Failure; + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_m2); + } + + printf("test: resizing a wuss_WINDOW_NO_RESIZE_BLIT window redraws it fully\n"); + + { + test_task_t tc_nb2; + wuss_task_t delegate_nb2; + box_t box_nb2, before3, after3, region; + wuss_window_t *win_nb2; + int i, dirty_area, full_area; + + tc_nb2.redraw_count = 0; + tc_nb2.mouse_count = 0; + delegate_nb2.handle = test_handle; + delegate_nb2.task_data = &tc_nb2; + + box_nb2.x0 = 0; box_nb2.y0 = 0; + box_nb2.x1 = 40; box_nb2.y1 = 40; + rc = wuss_window_create(wuss, &box_nb2, "NB2", wuss_WINDOW_NO_RESIZE_BLIT, + wuss_NO_BACKGROUND, + &delegate_nb2, + box_size(&box_nb2), + &win_nb2); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush the creation invalidation first */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_nb2, &before3); + + rc = wuss_window_resize(win_nb2, (size2d_t) { 80, 80 }); /* grow */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_nb2, &after3); + full_area = (after3.x1 - after3.x0) * (after3.y1 - after3.y0); + dirty_area = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + dirty_area += (region.x1 - region.x0) * (region.y1 - region.y0); + } + if (dirty_area < full_area) + goto Failure; /* NO_RESIZE_BLIT must fully redraw, not just the sliver */ + + wuss_window_close(win_nb2); + } + + printf("test: dragging a clear window onto an occluder leaves the occluder untouched\n"); + + { + test_task_t tc_n, tc_o; + wuss_task_t delegate_n, delegate_o; + box_t box_n, box_o, visible_o, exposed, occluded, region; + wuss_window_t *win_n, *win_o; + int i, exposed_dirty, occluded_dirty; + + tc_n.redraw_count = 0; + tc_n.mouse_count = 0; + delegate_n.handle = test_handle; + delegate_n.task_data = &tc_n; + + box_n.x0 = 0; box_n.y0 = 140; /* clear of any occluder to start */ + box_n.x1 = 60; box_n.y1 = 170; + rc = wuss_window_create(wuss, &box_n, "N", + wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, + &delegate_n, + box_size(&box_n), + &win_n); + if (rc != result_OK) + goto Failure; + + tc_o.redraw_count = 0; + tc_o.mouse_count = 0; + delegate_o.handle = test_handle; + delegate_o.task_data = &tc_o; + + box_o.x0 = 90; box_o.y0 = 140; /* N will be dragged partly on top of O */ + box_o.x1 = 130; box_o.y1 = 180; + rc = wuss_window_create(wuss, &box_o, "O", + wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, + &delegate_o, + box_size(&box_o), + &win_o); + if (rc != result_OK) + goto Failure; + + /* O is created after N, so O is topmost -- N's destination footprint + * will overlap an occluder above it in z-order. */ + + rc = wuss_redraw_dirty(wuss); /* flush both creations first */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_n, &visible); + + /* Move N far enough right that its new footprint lands partly under O + * (which stays wholly untouched), while its old footprint started + * entirely clear of O. */ + wuss_window_move(win_n, (point_t) { visible.x0 + 80, visible.y0 + 10 }); + + wuss_window_get_visible_bounds(win_n, &visible); + wuss_window_get_visible_bounds(win_o, &visible_o); + exposed.x0 = visible.x0; exposed.y0 = visible.y0; + exposed.x1 = visible_o.x0; exposed.y1 = visible.y1; /* N's part left of O */ + box_intersection(&visible, &visible_o, &occluded); /* N's part under O */ + + /* The part of N's new footprint that lands under O must NOT be queued + * dirty -- O hasn't moved, so its pixels are already correct there, and + * the move blit must have skipped blitting into that area rather than + * pasting N's stale pixels over it and forcing a repair. */ + occluded_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (box_intersects(®ion, &occluded)) + occluded_dirty = 1; + } + if (occluded_dirty) + goto Failure; + + /* The exposed part of N's new footprint, not under any occluder, must + * NOT be queued dirty -- it was already moved there correctly by the + * blit, so redrawing it too would be exactly the "repaint what could + * have been left in place" waste this fast path exists to avoid. */ + exposed_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (box_intersects(®ion, &exposed)) + exposed_dirty = 1; + } + if (exposed_dirty) + goto Failure; + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_n); + wuss_window_close(win_o); + } + + printf("test: moving a partly-occluded window blits its clean part and only repaints the occluded part\n"); + + { + test_task_t tc_a, tc_b; + wuss_task_t delegate_a, delegate_b; + box_t box_a, box_b, visible_a, visible_b_before; + box_t clean_new, hidden_new, region; + wuss_window_t *win_a, *win_b; + int i, dx, clean_dirty, hidden_dirty; + + tc_b.redraw_count = 0; + tc_b.mouse_count = 0; + delegate_b.handle = test_handle; + delegate_b.task_data = &tc_b; + + box_b.x0 = 20; box_b.y0 = 10; /* left half will sit under A */ + box_b.x1 = 80; box_b.y1 = 50; + rc = wuss_window_create(wuss, &box_b, "B", + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, + &delegate_b, + box_size(&box_b), + &win_b); + if (rc != result_OK) + goto Failure; + + tc_a.redraw_count = 0; + tc_a.mouse_count = 0; + delegate_a.handle = test_handle; + delegate_a.task_data = &tc_a; + + box_a.x0 = 0; box_a.y0 = 0; /* created after B, so A is topmost */ + box_a.x1 = 40; box_a.y1 = 100; + rc = wuss_window_create(wuss, &box_a, "A", + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, + &delegate_a, + box_size(&box_a), + &win_a); + if (rc != result_OK) + goto Failure; + + /* B's old footprint (x:20-80,y:10-50) is split by A (x:0-40) into a + * hidden strip (x:20-40, under A) and a clean strip (x:40-80, exposed). */ + + rc = wuss_redraw_dirty(wuss); /* flush both creations first */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_b, &visible_b_before); + wuss_window_get_visible_bounds(win_a, &visible_a); + + /* Move B far enough right that its whole new footprint clears A. */ + dx = 60; + wuss_window_move(win_b, (point_t) { visible_b_before.x0 + dx, + visible_b_before.y0 }); + + /* The clean strip (previously exposed, genuinely B's own pixels) lands + * at its translated destination and must have been blitted there, not + * repainted. */ + clean_new.x0 = 40 + dx; clean_new.y0 = 10; + clean_new.x1 = 80 + dx; clean_new.y1 = 50; + + /* The hidden strip (previously under A, never B's valid rendering) has + * no valid source pixels, so its translated destination must be a real + * repaint. */ + hidden_new.x0 = 20 + dx; hidden_new.y0 = 10; + hidden_new.x1 = 40 + dx; hidden_new.y1 = 50; + + hidden_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (box_intersects(®ion, &hidden_new)) + hidden_dirty = 1; + } + if (!hidden_dirty) + goto Failure; + + clean_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (box_intersects(®ion, &clean_new)) + clean_dirty = 1; + } + if (clean_dirty) + goto Failure; + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_b); + wuss_window_close(win_a); + } + + printf("test: moving a window whose occluded piece was never blitted doesn't redraw the occluder\n"); + + { + test_task_t tc_a, tc_b; + wuss_task_t delegate_a, delegate_b; + box_t box_a, box_b, visible_a, visible_b_before; + box_t region; + wuss_window_t *win_a, *win_b; + int i, occluder_dirty; + + tc_b.redraw_count = 0; + tc_b.mouse_count = 0; + delegate_b.handle = test_handle; + delegate_b.task_data = &tc_b; + + box_b.x0 = 0; box_b.y0 = 0; /* right part sits under A throughout */ + box_b.x1 = 60; box_b.y1 = 40; + rc = wuss_window_create(wuss, &box_b, "B", + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, + &delegate_b, + box_size(&box_b), + &win_b); + if (rc != result_OK) + goto Failure; + + tc_a.redraw_count = 0; + tc_a.mouse_count = 0; + delegate_a.handle = test_handle; + delegate_a.task_data = &tc_a; + + box_a.x0 = 40; box_a.y0 = 0; /* created after B, so A is topmost */ + box_a.x1 = 100; box_a.y1 = 40; + rc = wuss_window_create(wuss, &box_a, "A", + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, + &delegate_a, + box_size(&box_a), + &win_a); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush both creations first */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_b, &visible_b_before); + wuss_window_get_visible_bounds(win_a, &visible_a); + + /* Move B straight down: its clean piece (x:0-40) and hidden piece + * (x:40-60, under A) both stay clear of / under A exactly as before -- + * nothing about A's own pixels is ever touched by the blit, so A must + * not be forced to redraw. */ + wuss_window_move(win_b, (point_t) { visible_b_before.x0, + visible_b_before.y0 + 5 }); + + occluder_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (box_intersects(®ion, &visible_a)) + occluder_dirty = 1; + } + if (occluder_dirty) + goto Failure; + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_b); + wuss_window_close(win_a); + } + + printf("test: moving a window split by a mid-band occluder past the gap between bands blits both bands in a safe order\n"); + + { + test_task_t tc_a, tc_b; + wuss_task_t delegate_a, delegate_b; + box_t box_a, box_b, visible_b_before; + box_t occluded_overlap, hidden_new, region; + wuss_window_t *win_a, *win_b; + int i, occluded_dirty, hidden_dirty; + + tc_b.redraw_count = 0; + tc_b.mouse_count = 0; + delegate_b.handle = test_handle; + delegate_b.task_data = &tc_b; + + box_b.x0 = 0; box_b.y0 = 0; /* middle band sits under A */ + box_b.x1 = 60; box_b.y1 = 60; + rc = wuss_window_create(wuss, &box_b, "B", + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, + &delegate_b, + box_size(&box_b), + &win_b); + if (rc != result_OK) + goto Failure; + + tc_a.redraw_count = 0; + tc_a.mouse_count = 0; + delegate_a.handle = test_handle; + delegate_a.task_data = &tc_a; + + box_a.x0 = 0; box_a.y0 = 20; /* created after B, so A is topmost */ + box_a.x1 = 60; box_a.y1 = 40; + rc = wuss_window_create(wuss, &box_a, "A", + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, + &delegate_a, + box_size(&box_a), + &win_a); + if (rc != result_OK) + goto Failure; + + /* B's old footprint (y:0-60) is split by A (y:20-40) into a top clean + * band (y:0-20), a hidden middle band (y:20-40) and a bottom clean band + * (y:40-60), each spanning the full width. */ + + rc = wuss_redraw_dirty(wuss); /* flush both creations first */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_b, &visible_b_before); + + /* Move B down by 25px, past the 20px gap between the two clean bands: + * the top band's destination (y:25-45) lands on the bottom band's + * still-unread old source (y:40-60). Blitted in the other order -- + * bottom band first (its destination y:65-85 doesn't touch the top + * band's source), then the top band -- both blits are safe, so this + * is not a genuine clobber cycle (translating disjoint pieces by the + * same offset never produces one: any conflict is consistently + * oriented by the direction of the move). The top band's destination + * overlap with A (y:25-40) is skipped by the blit entirely (A hasn't + * moved, its pixels there are already correct), so it must stay clean; + * the translated hidden band (y:45-65, never had valid pixels) still + * needs forcing dirty for a real repaint. */ + wuss_window_move(win_b, (point_t) { visible_b_before.x0, + visible_b_before.y0 + 25 }); + + occluded_overlap.x0 = 0; occluded_overlap.y0 = 25; + occluded_overlap.x1 = 60; occluded_overlap.y1 = 40; + hidden_new.x0 = 0; hidden_new.y0 = 45; + hidden_new.x1 = 60; hidden_new.y1 = 65; + + occluded_dirty = hidden_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (box_intersects(®ion, &occluded_overlap)) + occluded_dirty = 1; + if (box_contains_box(&hidden_new, ®ion)) + hidden_dirty = 1; + } + if (occluded_dirty || !hidden_dirty) + goto Failure; + + /* The blit must have actually happened, not fallen back: the part of + * B's new footprint that's clear of A and not the hidden band (e.g. + * the bottom band's new position, y:65-85) must not be dirtied. */ + { + box_t clean_after, dirty_check; + + clean_after.x0 = 0; clean_after.y0 = 65; + clean_after.x1 = 60; clean_after.y1 = 85; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (!box_intersection(®ion, &clean_after, &dirty_check)) + goto Failure; + } + } + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_b); + wuss_window_close(win_a); + } + + printf("test: dragging a window deeper under a corner occluder blits both L-shaped pieces in a safe order\n"); + + { + test_task_t tc_a, tc_b; + wuss_task_t delegate_a, delegate_b; + box_t box_a, box_b, visible_b_before, visible_a, region; + wuss_window_t *win_a, *win_b; + int i; + + tc_b.redraw_count = 0; + tc_b.mouse_count = 0; + delegate_b.handle = test_handle; + delegate_b.task_data = &tc_b; + + box_b.x0 = 80; box_b.y0 = 80; /* corner already under A */ + box_b.x1 = 140; box_b.y1 = 140; + rc = wuss_window_create(wuss, &box_b, "B", + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, + &delegate_b, + box_size(&box_b), + &win_b); + if (rc != result_OK) + goto Failure; + + tc_a.redraw_count = 0; + tc_a.mouse_count = 0; + delegate_a.handle = test_handle; + delegate_a.task_data = &tc_a; + + box_a.x0 = 0; box_a.y0 = 0; /* created after B, so A is topmost */ + box_a.x1 = 100; box_a.y1 = 100; + rc = wuss_window_create(wuss, &box_a, "A", + wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE | + wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | + wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, + &delegate_a, + box_size(&box_a), + &win_a); + if (rc != result_OK) + goto Failure; + + /* B's old footprint (80,80)-(140,140) overlaps A (0,0)-(100,100) in its + * corner (80,80)-(100,100); the rest of B is split into an L-shaped + * clean region of two pieces, one of whose destination lands on the + * other's still-unread source -- but blitting the other piece first + * avoids that entirely, so this must NOT fall back to a full clipped + * redraw (that was the "Adjust drag behind a corner fully redraws the + * window" regression). Dragging B up-left by (-15,-15) also grows the + * overlap with A without ever fully hiding or fully clearing it -- the + * blit skips the part of each piece's destination that now lands under + * A, so A's own rendering there is never touched and needs no repair. */ + + rc = wuss_redraw_dirty(wuss); /* flush both creations first */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_visible_bounds(win_b, &visible_b_before); + wuss_window_get_visible_bounds(win_a, &visible_a); + + wuss_window_move(win_b, (point_t) { visible_b_before.x0 - 15, + visible_b_before.y0 - 15 }); + + /* The blit must have actually happened, not fallen back: B's own + * footprint (outside A) must not be dirtied wholesale. */ + { + box_t visible_b_after, whole_footprint, dirty_area_box; + int dirty_area, footprint_area; + + wuss_window_get_visible_bounds(win_b, &visible_b_after); + box_union(&visible_b_before, &visible_b_after, &whole_footprint); + + dirty_area = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + wuss_get_dirty(wuss, i, ®ion); + if (!box_intersection(®ion, &whole_footprint, &dirty_area_box)) + dirty_area += (dirty_area_box.x1 - dirty_area_box.x0) * + (dirty_area_box.y1 - dirty_area_box.y0); + } + footprint_area = (whole_footprint.x1 - whole_footprint.x0) * + (whole_footprint.y1 - whole_footprint.y0); + if (dirty_area >= footprint_area) + goto Failure; /* fell back to a full redraw instead of blitting */ + } + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_b); + wuss_window_close(win_a); + } + printf("test: destroy mid-drag then move doesn't crash\n"); tc_c.redraw_count = 0; tc_c.mouse_count = 0; delegate_c.handle = test_handle; delegate_c.task_data = &tc_c; - delegate_c.bg = wuss_NO_BACKGROUND; box_c.x0 = 0; box_c.y0 = 0; @@ -1961,9 +2697,9 @@ result_t wuss_test(const char *resources) wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, + wuss_NO_BACKGROUND, &delegate_c, - box_c.x1 - box_c.x0, - box_c.y1 - box_c.y0, + box_size(&box_c), &win_c); if (rc != result_OK) goto Failure; @@ -1980,6 +2716,120 @@ result_t wuss_test(const char *resources) if (rc != result_OK) goto Failure; + printf("test: mouse and scroll events arrive in virtual content space, with the scroll offset applied exactly once\n"); + + { + test_task_t tc_s = { 0 }; + wuss_task_t delegate_s; + box_t box_s, content_s; + wuss_window_t *win_s; + point_t scroll; + + delegate_s.handle = test_handle; + delegate_s.task_data = &tc_s; + + box_s.x0 = 10; box_s.y0 = 10; + box_s.x1 = 60; box_s.y1 = 60; /* 50x50 content onto a 200x200 doc: room to scroll */ + rc = wuss_window_create(wuss, &box_s, "S", wuss_WINDOW_NONE, + wuss_NO_BACKGROUND, + &delegate_s, + (size2d_t) { 200, 200 }, &win_s); + if (rc != result_OK) + goto Failure; + + scroll.x = 30; + scroll.y = 40; + wuss_window_set_scroll(win_s, scroll); + wuss_window_get_scroll(win_s, &scroll); /* read back in case it clamped */ + + wuss_window_get_content_bounds(win_s, &content_s); + + rc = wuss_mouse_click(wuss, + (point_t) { content_s.x0 + 5, content_s.y0 + 7 }, + wuss_BUTTON_SELECT, wuss_MOUSE_DOWN, &hit); + if (rc != result_OK) + goto Failure; + if (hit != win_s) + goto Failure; + if (tc_s.last_x != 5 + scroll.x || tc_s.last_y != 7 + scroll.y) + goto Failure; /* a task adding the scroll offset itself would double-count it */ + + rc = wuss_mouse_click(wuss, + (point_t) { 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 }, + &hit); + if (rc != result_OK) + goto Failure; + if (tc_s.last_x != 11 + scroll.x || tc_s.last_y != 13 + scroll.y) + goto Failure; + + tc_s.last_scroll_x = -1; + tc_s.last_scroll_y = -1; + rc = wuss_scroll(wuss, + (point_t) { content_s.x0 + 3, content_s.y0 + 4 }, + 1, &hit); + if (rc != result_OK) + goto Failure; + if (hit != win_s) + goto Failure; + if (tc_s.last_scroll_x != 3 + scroll.x || tc_s.last_scroll_y != 4 + scroll.y) + goto Failure; + + wuss_window_close(win_s); + } + + printf("test: content bounds survive furniture, including the interior rules\n"); + + { + /* Furniture -- outline, titlebar, scrollbars and the rules dividing the + * content from them -- is added outside the requested content box, never + * carved out of it, so what the caller asks for is what it gets, both at + * creation and after a resize. */ + test_task_t tc_r; + wuss_task_t delegate_r; + box_t box_r, content_r; + wuss_window_t *win_r; + + memset(&tc_r, 0, sizeof(tc_r)); + delegate_r.handle = test_handle; + delegate_r.task_data = &tc_r; + + box_r.x0 = 20; + box_r.y0 = 30; + box_r.x1 = 120; + box_r.y1 = 110; + rc = wuss_window_create(wuss, + &box_r, + "rules", + wuss_WINDOW_NONE, /* all furniture present */ + wuss_NO_BACKGROUND, + &delegate_r, + (size2d_t) { 400, 400 }, + &win_r); + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_r, &content_r); + if (content_r.x1 - content_r.x0 != box_r.x1 - box_r.x0 || + content_r.y1 - content_r.y0 != box_r.y1 - box_r.y0) + goto Failure; + + rc = wuss_window_resize(win_r, (size2d_t) { 61, 47 }); + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_r, &content_r); + if (content_r.x1 - content_r.x0 != 61 || content_r.y1 - content_r.y0 != 47) + goto Failure; + + wuss_window_close(win_r); + } + printf("test: wuss_task_stop sends wuss_EVENT_QUIT to each window's task\n"); tc_a.stop_count = 0; diff --git a/libraries/wuss/window/create.c b/libraries/wuss/window/create.c index fa3cdc03..591da41e 100644 --- a/libraries/wuss/window/create.c +++ b/libraries/wuss/window/create.c @@ -14,9 +14,9 @@ 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, - int doc_width, - int doc_height, + size2d_t doc, wuss_window_t **window) { wuss_window_t *win; @@ -50,8 +50,8 @@ result_t wuss_window_create(wuss_t *wuss, /* nudge back on-screen so the titlebar/close icon stay reachable; a * window bigger than the screen keeps its top-left (titlebar) edge * on-screen rather than being centred or left alone */ - scr_width = wuss->scr->width; - scr_height = wuss->scr->height; + scr_width = wuss->scr->size.w; + scr_height = wuss->scr->size.h; dx = 0; if (win->visible.x0 < 0) @@ -75,23 +75,20 @@ result_t wuss_window_create(wuss_t *wuss, win->flags = flags; win->scroll.x = 0; win->scroll.y = 0; - win->doc_width = doc_width; - win->doc_height = doc_height; - win->toggled = 0; + win->doc = doc; + win->state = wuss_WINDOW_STATE_NONE; if (task != NULL) win->task = *task; else memset(&win->task, 0, sizeof(win->task)); - if (task == NULL) - win->task.bg = wuss_NO_BACKGROUND; - else if (task->bg != wuss_NO_BACKGROUND && - (task->bg < 0 || task->bg >= wuss->npalette)) + if (bg != wuss_NO_BACKGROUND && (bg < 0 || bg >= wuss->npalette)) { free(win); return result_WUSS_BAD_COLOUR; } + win->bg = bg; if (title != NULL) { diff --git a/libraries/wuss/window/invalidate.c b/libraries/wuss/window/invalidate.c index 9ed97811..accaa784 100644 --- a/libraries/wuss/window/invalidate.c +++ b/libraries/wuss/window/invalidate.c @@ -98,10 +98,10 @@ int wuss__clip_to_visible(wuss_window_t *window, const box_t *box, box_t *out) /* Subtract each of "cuts" (an array of "ncuts" boxes) from "whole", writing * the surviving pieces to "out" (capacity WUSS_MAX_INVALIDATE_PIECES) and * returning their count. */ -static int subtract_boxes(const box_t *whole, - const box_t *cuts, - int ncuts, - box_t *out) +int wuss__subtract_boxes(const box_t *whole, + const box_t *cuts, + int ncuts, + box_t *out) { box_t scratch[WUSS_MAX_INVALIDATE_PIECES]; box_t *cur, *nxt, *tmp; @@ -162,7 +162,7 @@ void wuss__invalidate_uncovered(wuss_window_t *window) int nvisible, nhidden, i; nvisible = wuss__clip_to_visible(window, &window->visible, visible); - nhidden = subtract_boxes(&window->visible, visible, nvisible, hidden); + nhidden = wuss__subtract_boxes(&window->visible, visible, nvisible, hidden); for (i = 0; i < nhidden; i++) wuss_invalidate(window->wuss, &hidden[i]); diff --git a/libraries/wuss/window/move.c b/libraries/wuss/window/move.c index 3b594240..ef0ee7d0 100644 --- a/libraries/wuss/window/move.c +++ b/libraries/wuss/window/move.c @@ -2,13 +2,81 @@ #include "../impl.h" +/* Translate "box" by (dx, dy) into "out". */ +static void translate_box(const box_t *box, int dx, int dy, box_t *out) +{ + out->x0 = box->x0 + dx; + out->y0 = box->y0 + dy; + out->x1 = box->x1 + dx; + out->y1 = box->y1 + dy; +} + /* p is the window's content top-left; the furniture offset (outline plus * any titlebar) is constant for a given window, so the footprint just * follows it */ +/* Sequential single-rect blits (each a self-consistent memmove) can still + * corrupt each other when one piece's destination lands on another piece's + * still-unread source -- but that only actually matters if no blit order + * avoids it. Build the "must happen before" graph (piece j before piece i + * whenever dest[i] would overwrite clean[j]'s still-unread source) and + * topologically sort it: any window with more than one occluder-carved + * piece near a shared edge -- e.g. two bands split by a corner occluder -- + * routinely has one such pairwise overlap without there being a genuine + * cycle, and rejecting those outright regressed plain corner-occlusion + * drags into full fallback redraws. Only an actual cycle (i must precede j + * and j must precede i) has no safe order and needs the fallback. */ +static int wuss__order_pieces(const box_t *clean, const box_t *dest, int n, + int *order) +{ + int adj[WUSS_MAX_INVALIDATE_PIECES][WUSS_MAX_INVALIDATE_PIECES]; + int indeg[WUSS_MAX_INVALIDATE_PIECES]; + int queue[WUSS_MAX_INVALIDATE_PIECES]; + int i, j, head, tail, nout, u; + + for (i = 0; i < n; i++) + indeg[i] = 0; + for (j = 0; j < n; j++) + for (i = 0; i < n; i++) + adj[j][i] = 0; + + for (i = 0; i < n; i++) + for (j = 0; j < n; j++) + if (i != j && !adj[j][i] && box_intersects(&dest[i], &clean[j])) + { + adj[j][i] = 1; /* j must be blitted before i */ + indeg[i]++; + } + + tail = 0; + for (i = 0; i < n; i++) + if (indeg[i] == 0) + queue[tail++] = i; + + head = nout = 0; + while (head < tail) + { + u = queue[head++]; + order[nout++] = u; + + for (i = 0; i < n; i++) + if (adj[u][i] && --indeg[i] == 0) + queue[tail++] = i; + } + + return nout == n; +} + void wuss_window_move(wuss_window_t *window, point_t p) { - int width, height, outline_px, titlebar_height; - box_t before, dirty, copied; + box_t clean[WUSS_MAX_INVALIDATE_PIECES]; + box_t full_dest[WUSS_MAX_INVALIDATE_PIECES]; + box_t blit_src[WUSS_MAX_INVALIDATE_PIECES]; + box_t blit_dest[WUSS_MAX_INVALIDATE_PIECES]; + int order[WUSS_MAX_INVALIDATE_PIECES]; + int width, height, outline_px, titlebar_height; + int dx, dy, nclean, nblit, overflow, i, idx; + box_t before, dirty, copied; + int blit_failed; width = window->visible.x1 - window->visible.x0; height = window->visible.y1 - window->visible.y0; @@ -16,6 +84,12 @@ void wuss_window_move(wuss_window_t *window, point_t p) titlebar_height = wuss__titlebar_height(window); before = window->visible; + /* The clean (non-occluded) pieces of "before" are genuinely this + * window's own rendering; whatever isn't clean is hidden behind some + * other window and has no valid pixels of this window's content to + * slide. Computed against the current z-order, before the move. */ + nclean = wuss__clip_to_visible(window, &before, clean); + window->visible.x0 = p.x - outline_px; window->visible.y0 = p.y - outline_px - titlebar_height; window->visible.x1 = window->visible.x0 + width; @@ -23,28 +97,105 @@ void wuss_window_move(wuss_window_t *window, point_t p) wuss__notify_open(window); - if (window->wuss->z_order.next == &window->link && - screen_copy_rect(window->wuss->scr, &before, - (point_t) { window->visible.x0, window->visible.y0 }, &copied)) + dx = window->visible.x0 - before.x0; + dy = window->visible.y0 - before.y0; + + for (i = 0; i < nclean; i++) + translate_box(&clean[i], dx, dy, &full_dest[i]); + + /* Split each clean piece's full (untrimmed) destination down to the parts + * not already sitting under an occluder above this window there: that + * occluder hasn't moved, so its pixels are already correct, and blitting + * this window's stale pixels over them would just have to be repainted + * straight back -- cheaper to never touch them at all. Only the surviving, + * genuinely-blittable sub-pieces go on to the clobber-ordering/blit below; + * the occluded remainder needs no repair because nothing was ever pasted + * over it. */ + nblit = 0; + overflow = 0; + for (i = 0; i < nclean && !overflow; i++) + { + box_t visible_dest[WUSS_MAX_INVALIDATE_PIECES]; + int nvisible, v; + + nvisible = wuss__clip_to_visible(window, &full_dest[i], visible_dest); + + for (v = 0; v < nvisible; v++) + { + if (nblit == WUSS_MAX_INVALIDATE_PIECES) + { + overflow = 1; + break; + } + + blit_dest[nblit] = visible_dest[v]; + translate_box(&visible_dest[v], -dx, -dy, &blit_src[nblit]); + nblit++; + } + } + + /* If no ordering of these single-rect blits avoids one clobbering + * another's still-unread source, fall back rather than risk corrupting + * this window's own pixels. */ + blit_failed = nclean == 0 || overflow || + !wuss__order_pieces(blit_src, blit_dest, nblit, order); + + for (i = 0; i < nblit && !blit_failed; i++) { - /* Topmost, and the screen format supports the blit: every pixel of - * "before" is genuinely this window's own rendering (nothing above it - * to have punched holes in it), so sliding those pixels to the new - * position is exactly as correct as asking the task to redraw there, - * but far cheaper -- only the vacated sliver behind the old position - * still needs an actual repaint. */ - wuss__invalidate_minus(window->wuss, &before, &window->visible); - - /* "copied" can be smaller than the new footprint if either end of the - * move was partly off-screen (e.g. dragging back on-screen from - * off-screen): the leftover part has no valid source pixels behind it, - * so it needs a real repaint too, not just the vacated sliver above. */ - wuss__invalidate_minus(window->wuss, &window->visible, &copied); + idx = order[i]; + + if (!screen_copy_rect(window->wuss->scr, &blit_src[idx], + (point_t) { 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. */ + blit_failed = 1; + break; + } + + /* "copied" can be smaller than "blit_dest[idx]" if the move was partly + * off-screen: the leftover part has no valid source pixels behind it, + * so it needs a real repaint too. Safe to invalidate raw, without + * re-checking occlusion, since "blit_dest[idx]" (and so its "copied" + * subset) was already clipped clear of every occluder above. */ + wuss__invalidate_minus(window->wuss, &blit_dest[idx], &copied); + } + + if (nclean > 0 && !blit_failed) + { + box_t hidden[WUSS_MAX_INVALIDATE_PIECES]; + int nhidden; + + /* Each clean piece is, by construction, clear of any occluder at its + * old position, so the vacated sliver left behind by sliding it to its + * full (untrimmed) new position is safe to invalidate raw, without + * re-checking occlusion -- regardless of whether every pixel of that + * new position actually got a blit above: the part that landed under an + * occluder was skipped there, but the old position is vacated either + * way. */ + for (i = 0; i < nclean; i++) + wuss__invalidate_minus(window->wuss, &clean[i], &full_dest[i]); + + /* Whatever of "before" wasn't clean has no valid source pixels: its + * translated destination needs a genuine repaint, clipped against + * whatever's above this window there now. */ + nhidden = wuss__subtract_boxes(&before, clean, nclean, hidden); + for (i = 0; i < nhidden; i++) + { + box_t hidden_dest; + + translate_box(&hidden[i], dx, dy, &hidden_dest); + wuss__invalidate_clipped(window, &hidden_dest); + } } else { - /* Not topmost, or the blit was declined (e.g. paletted screen): fall - * back to a normal clipped redraw of the whole moved footprint. */ + /* Nothing of "before" was clean, splitting overflowed the piece budget, + * the pieces would have clobbered each other, or the blit was declined: + * fall back to a normal clipped redraw of the whole moved footprint. */ box_union(&before, &window->visible, &dirty); wuss__invalidate_clipped(window, &dirty); } diff --git a/libraries/wuss/window/resize.c b/libraries/wuss/window/resize.c index 9a7f48c1..46e6e7ff 100644 --- a/libraries/wuss/window/resize.c +++ b/libraries/wuss/window/resize.c @@ -1,14 +1,42 @@ /* resize.c -- wuss - minimal window manager */ +#include "base/utils.h" + #include "../impl.h" -result_t wuss_window_resize(wuss_window_t *window, int width, int height) +/* Invalidate the part of "a" not covered by "b", given both share the same + * top-left corner (true of a window's visible box before/after a resize, as + * only the bottom-right corner moves): the difference splits into exactly + * two non-overlapping rectangles, each clipped against occluders before + * queueing. */ +static void invalidate_grown_or_shrunk(wuss_window_t *window, + const box_t *a, + const box_t *b) +{ + box_t piece; + + if (a->x1 > b->x1) + { + piece.x0 = b->x1; piece.y0 = a->y0; + piece.x1 = a->x1; piece.y1 = a->y1; + wuss__invalidate_clipped(window, &piece); + } + + if (a->y1 > b->y1) + { + piece.x0 = a->x0; piece.y0 = b->y1; + piece.x1 = MIN(a->x1, b->x1); piece.y1 = a->y1; + wuss__invalidate_clipped(window, &piece); + } +} + +result_t wuss_window_resize(wuss_window_t *window, size2d_t size) { int outline_px, titlebar_height; - box_t before, dirty; + box_t before; point_t carve; - if (!wuss__size_ok(width, height)) + if (!wuss__size_ok(size.w, size.h)) return result_WUSS_TOO_SMALL; outline_px = wuss__outline_px(window); @@ -16,13 +44,42 @@ result_t wuss_window_resize(wuss_window_t *window, int width, int height) before = window->visible; wuss__furniture_carve_for(window->flags, wuss__icon_size(window), &carve); - window->visible.x1 = window->visible.x0 + width + 2 * outline_px + carve.x; - window->visible.y1 = window->visible.y0 + height + titlebar_height + 2 * outline_px + carve.y; + window->visible.x1 = window->visible.x0 + size.w + 2 * outline_px + carve.x; + window->visible.y1 = window->visible.y0 + size.h + titlebar_height + 2 * outline_px + carve.y; wuss__notify_open(window); - box_union(&before, &window->visible, &dirty); - wuss__invalidate_clipped(window, &dirty); + if (window->flags & wuss_WINDOW_NO_RESIZE_BLIT) + { + /* This task's content isn't just anchored positions plus furniture -- + * it lays itself out across the whole window (e.g. a palette swatch + * grid), so the "unchanged interior" assumption below doesn't hold: + * every pixel of the new footprint needs redrawing, not just the + * grown/shrunk sliver. */ + box_t dirty; + + box_union(&before, &window->visible, &dirty); + wuss__invalidate_clipped(window, &dirty); + } + else + { + /* The top-left corner is fixed, so any pixels within both the old and + * new footprint are unchanged and don't need repainting -- only the + * shrunk-away sliver (revealing whatever is now behind it) and the + * grown-into sliver (this window's own previously-undrawn content) do. */ + invalidate_grown_or_shrunk(window, &before, &window->visible); + invalidate_grown_or_shrunk(window, &window->visible, &before); + + /* Growing strands old furniture (e.g. the old outline/scrollbar edge) + * inside what's now interior content -- a region both calls above treat + * as already-valid and so never repaint. Force its old position dirty + * too. Shrinking needs no such help: old furniture positions only ever + * land outside the new, smaller box, already covered above. */ + if (window->visible.x1 - window->visible.x0 > before.x1 - before.x0 || + window->visible.y1 - window->visible.y0 > before.y1 - before.y0) + wuss__furniture_invalidate_for(window, &before); + wuss__furniture_invalidate(window); + } return result_OK; } diff --git a/libraries/wuss/window/set-background.c b/libraries/wuss/window/set-background.c index 92ee232f..8cfe7cb0 100644 --- a/libraries/wuss/window/set-background.c +++ b/libraries/wuss/window/set-background.c @@ -9,7 +9,7 @@ result_t wuss_window_set_background(wuss_window_t *window, wuss_colour_t bg) if (bg != wuss_NO_BACKGROUND && (bg < 0 || bg >= window->wuss->npalette)) return result_WUSS_BAD_COLOUR; - window->task.bg = bg; + window->bg = bg; wuss__content_box(window, &content); wuss__invalidate_clipped(window, &content); diff --git a/resources/bmfonts/daydream-font.png b/resources/bmfonts/daydream.png similarity index 100% rename from resources/bmfonts/daydream-font.png rename to resources/bmfonts/daydream.png diff --git a/resources/bmfonts/digits-font.png b/resources/bmfonts/digits-font.png deleted file mode 100644 index 4111a428..00000000 Binary files a/resources/bmfonts/digits-font.png and /dev/null differ diff --git a/resources/bmfonts/digits.png b/resources/bmfonts/digits.png new file mode 100644 index 00000000..0a51d48b Binary files /dev/null and b/resources/bmfonts/digits.png differ diff --git a/resources/bmfonts/gliderrider-font.png b/resources/bmfonts/gliderrider.png similarity index 100% rename from resources/bmfonts/gliderrider-font.png rename to resources/bmfonts/gliderrider.png diff --git a/resources/bmfonts/henry-font.png b/resources/bmfonts/henry.png similarity index 100% rename from resources/bmfonts/henry-font.png rename to resources/bmfonts/henry.png diff --git a/resources/bmfonts/tall-font.png b/resources/bmfonts/tall.png similarity index 100% rename from resources/bmfonts/tall-font.png rename to resources/bmfonts/tall.png diff --git a/resources/bmfonts/tiny-font.png b/resources/bmfonts/tiny.png similarity index 100% rename from resources/bmfonts/tiny-font.png rename to resources/bmfonts/tiny.png diff --git a/tools/test_wrap_doxygen.py b/tools/test_wrap_doxygen.py new file mode 100644 index 00000000..b0fe1a5e --- /dev/null +++ b/tools/test_wrap_doxygen.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +"""Smallest possible smoke test for wrap_doxygen.py. Run directly: +python3 tools/test_wrap_doxygen.py +""" +from wrap_doxygen import process + +src = '''/** + * A short line. + * + * \\param[in] thing This description is deliberately long enough that it has to wrap across more than one line to fit. + * \\return Also deliberately long enough that this return description needs to wrap across two lines at least. + */ +int f(int thing); + +/** This single-line comment is deliberately long enough to need promoting to a multi-line block comment. */ +int g(void); +'''.splitlines() + +out, overflow = process(src, width=80) + +assert overflow == [], overflow +assert all(len(l) <= 80 for l in out), [l for l in out if len(l) > 80] +assert out[0] == '/**' +assert out[1] == ' * A short line.' +# continuation lines must keep the "*" one column right of "/**", with a +# space after it -- regression check for the "* text" (no leading space) +# alignment bug this script originally had. +cont = [l for l in out if 'wrap across more than' in l] +assert cont and cont[0][:3] == ' * ', repr(cont) +assert 'int g(void);' in out +assert out.count('/**') == 2 # original block + the promoted single-liner + +# idempotent: reformatting already-reformatted text changes nothing +out2, overflow2 = process(out, width=80) +assert out2 == out +assert overflow2 == [] + +print('ok') diff --git a/tools/wrap_doxygen.py b/tools/wrap_doxygen.py new file mode 100644 index 00000000..f64906ba --- /dev/null +++ b/tools/wrap_doxygen.py @@ -0,0 +1,166 @@ +#!/usr/bin/env python3 +"""wrap_doxygen.py -- rewrap Doxygen /** ... */ block comments to a column +limit, greedy-fill, continuation lines aligned under the description text +(matching the \\param/\\return style already used across DPTLib headers). + +Scope (ponytail: lazy on purpose): + - Rewraps multi-line /** ... */ blocks: \\param/\\return/\\brief/\\file/etc + tag lines, and plain prose lines, each as one reflow-able paragraph. + - Promotes an overlong single-line /** ... */ comment into a multi-line + block. + - Leaves /**< ... */ trailing member comments and non-comment code lines + alone even if too long -- fixing those means moving the comment above + the declaration and touching struct/enum layout, a structural call this + script doesn't make. Still reported so a human can look. +""" +import argparse +import re +import sys +import textwrap + +OPEN_RE = re.compile(r'^(\s*)/\*\*\s*$') +CLOSE_RE = re.compile(r'^(\s*)\*/\s*$') +BLANK_RE = re.compile(r'^\s*\*\s*$') +SINGLE_LINE_RE = re.compile(r'^(\s*)/\*\*\s+(.*\S)\s+\*/\s*$') +PARAM_RE = re.compile(r'^(\s*\*\s*\\param(?:\[[^\]]*\])?\s+\S+\s+)(.*)$') +TAG_RE = re.compile( + r'^(\s*\*\s*\\(?:return|brief|file|note|warning|pre|post|throws?)\b\s*)(.*)$') +PROSE_RE = re.compile(r'^(\s*\*\s+)(\S.*)$') + + +def cont_prefix(prefix1, indent): + return indent + '*' + ' ' * (len(prefix1) - len(indent) - 1) + + +def fill(prefix1, words, width, indent): + text = ' '.join(w for w in words if w) + cont = cont_prefix(prefix1, indent) + lines = textwrap.wrap(text, width=width, initial_indent=prefix1, + subsequent_indent=cont, break_long_words=False, + break_on_hyphens=False) + return lines or [prefix1.rstrip()] + + +def wrap_single_line_comment(indent, text, width): + body_width = width - len(indent) - 3 # " * " prefix + lines = textwrap.wrap(text, width=body_width, break_long_words=False, + break_on_hyphens=False) + out = [indent + '/**'] + out += [indent + ' * ' + l for l in lines] + out.append(indent + ' */') + return out + + +def process(lines, width): + out = [] + overflow = [] + i = 0 + n = len(lines) + while i < n: + line = lines[i].rstrip('\n') + + m = SINGLE_LINE_RE.match(line) + if m and '/**<' not in line and len(line) > width: + indent, text = m.groups() + out.extend(wrap_single_line_comment(indent, text, width)) + i += 1 + continue + + m = OPEN_RE.match(line) + if m: + out.append(line) + indent = m.group(1) + ' ' # body lines' "*" sits one col right of "/**" + i += 1 + pending = None # (prefix1, [words...]) + + def flush(): + if pending is not None: + out.extend(fill(pending[0], pending[1], width, indent)) + + while i < n: + body = lines[i].rstrip('\n') + + mclose = CLOSE_RE.match(body) + if mclose: + flush() + pending = None + out.append(body) + i += 1 + break + + if BLANK_RE.match(body): + flush() + pending = None + out.append(body) + i += 1 + continue + + mtag = PARAM_RE.match(body) or TAG_RE.match(body) + if mtag: + flush() + pending = (mtag.group(1), [mtag.group(2)]) + i += 1 + continue + + mprose = PROSE_RE.match(body) + if mprose: + if pending is None: + pending = (mprose.group(1), [mprose.group(2)]) + else: + pending[1].append(mprose.group(2)) + i += 1 + continue + + # Doesn't look like a normal comment-body line -- pass through. + flush() + pending = None + out.append(body) + i += 1 + else: + flush() + continue + + if len(line) > width: + overflow.append((i + 1, line)) + out.append(line) + i += 1 + + return out, overflow + + +def main(): + ap = argparse.ArgumentParser(description=__doc__.splitlines()[0]) + ap.add_argument('files', nargs='+') + ap.add_argument('--width', type=int, default=80) + ap.add_argument('--check', action='store_true', + help="don't write; exit 1 if any file would change") + args = ap.parse_args() + + changed = False + for path in args.files: + with open(path, encoding='utf-8') as f: + original = f.readlines() + new_lines, overflow = process(original, args.width) + new_text = '\n'.join(new_lines) + '\n' + old_text = ''.join(original) + + if new_text != old_text: + changed = True + if args.check: + print(f'{path}: would reformat') + else: + with open(path, 'w', encoding='utf-8') as f: + f.write(new_text) + print(f'{path}: reformatted') + for lineno, text in overflow: + print(f'{path}:{lineno}: still over {args.width} cols ' + f'(needs manual restructuring): {text.strip()}', + file=sys.stderr) + + if args.check and changed: + return 1 + return 0 + + +if __name__ == '__main__': + sys.exit(main())