diff --git a/CHANGELOG.md b/CHANGELOG.md index cc9b7fb8..92684e80 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,14 +10,74 @@ _Unreleased_ until one is cut. ### Added -- `wuss/icon.h` — work-area icons: static labels and clickable bevelled - buttons drawn inside a window's content area. Icon boxes are in virtual - document space, so they scroll with the content. Wuss hit-tests buttons - before the content task sees a click and delivers them as `wuss_EVENT_ICON`; - labels and hidden or disabled icons fall through as `wuss_EVENT_MOUSE`. +- `wuss/menu.h` (new `WUSS_MENUS` option, implies `WUSS_ICONS`) — RISC OS-style + pop-up menus. `wuss_menu_open()` shows a caller-owned, immutable + `wuss_menu_t` (title plus an array of `wuss_menu_item_t`) as a borderless + window, nudged to stay on screen and opened under the pointer; wuss owns + layout, submenu chaining on hover and whole-chain dismissal. Per-item flags + cover ticks, disabled rows, dashed separators and submenus. + `wuss_menu_select_fn_t` reports a leaf pick — SELECT closes the chain, ADJUST + keeps it open. `wuss_menu_close()` / `wuss_menu_is_open()` manage a chain by + handle. An over-tall menu gets a real vertical scrollbar instead of being + cropped. +- `wuss_menu_create_from_desc()` / `wuss_menu_destroy()` — build a heap + `wuss_menu_t` tree from a compact descriptor string (PrivateEye's + `menu_create_from_desc` syntax: `,` between items, leading `|` for a dashed + separator, `{ ... }` submenus, `!` tick, `~` shade, `>` and `%s` varargs). + The whole tree is one owned allocation graph freed by `wuss_menu_destroy()`. +- `wuss_menu_item_t::window` — a menu row may carry a caller-owned + `wuss_window_t` instead of a `submenu` (the two are mutually exclusive). + Hovering the row shows that window where a submenu would open, using the same + anchor maths; leaving the row, a click outside, a leaf SELECT elsewhere or + `wuss_destroy()` hide it again rather than close it, so the same handle is + reused on the next hover. Create the window with `wuss_WINDOW_HIDDEN`. +- `wuss_window_set_hidden()` and the `wuss_WINDOW_HIDDEN` create flag — a hidden + window keeps its z-order slot but is not drawn, not hit-tested and occludes + nothing. `wuss_window_move()` still works on it (translate only, no blit) so + it can be parked and re-shown in position. +- `wuss_set_palette()` — swap the system palette mid-session. Copies the new + palette in, refreshes the cached nearest-black/white indices, broadcasts a + new `wuss_EVENT_PALETTE` to every window's task so it can recache + `wuss_nearest_colour()` selections, then invalidates the whole screen. + Length must match `wuss_create()`'s; a now-out-of-range furniture/bevel/ + backdrop index is rejected with the palette left unchanged. +- `wuss_nearest_colour()` — the system-palette index closest to an RGB value by + squared Euclidean distance, ties to the lower index. +- `wuss_alloc_t` and the `wuss_alloc` stdlib default — pluggable malloc/realloc/ + free hooks. `wuss_create()` takes a new `const wuss_alloc_t *` argument + (NULL selects `wuss_alloc`); every heap block a `wuss_t` owns goes through + the hooks. +- `wuss/icon.h` — work-area icons drawn inside a window's content area, in + virtual document space so they scroll with the content. Wuss hit-tests + interactive icons before the content task sees a click and delivers them as + `wuss_EVENT_ICON`; labels and hidden or disabled icons fall through as + `wuss_EVENT_MOUSE`. Icon types: `LABEL` (with `JUSTIFY_RIGHT` / `_CENTRE` + flags), bevelled `BUTTON` (`DEFAULT` flag draws it as the default action + button), `RADIO` and `OPTION` latching buttons (radios with a non-zero + `group` are mutually exclusive), `FRAME` grouping box, `BITMAP` (a + caller-owned image, hit-tested only with the `INTERACTIVE` flag), `PATTERN` + swatch, `MENU_ENTRY` and inert `RULE` rows. +- `wuss_icon_get_selected()` / `wuss_icon_set_selected()` — query and set a + radio or option icon's latched state; setting a grouped radio clears the + others in its group. No task event — the programmatic path. - `wuss_icon_create_array()` — creates a batch of icons from a spec array with all-or-nothing rollback: on the first failure any icons already created by the call are destroyed and no handles are written. +- `text/bmtext.h` — `bmtext_layout()` word-wraps a string to a pixel width in a + `bmfont_t` (measuring each candidate line, so proportional fonts wrap + correctly); `bmtext_draw()` draws the laid-out lines stacked. Layout is pure. +- `screen_draw_lines()` — connected polyline; a `screen_draw_line()` segment + between each adjacent pair. +- `screen_draw_rect()` — one-pixel unfilled rectangle outline (falls back to a + fill for a degenerate size). See _Changed_ for the fill-primitive renames. +- `screen_draw_dashed_line()` — Bresenham line with a dash-period counter. +- `screen_PATTERN_BAYER0` .. `screen_PATTERN_BAYER0 + 64` — 8x8 ordered dither, + one fill pattern per coverage level 0 (empty) to 64 (solid), indexed as + `screen_PATTERN_BAYER0 + level`. `BAYER32` == `GREY50`, `BAYER64` == `SOLID`. +- `define_wimp16_palette()` and the `palette_WIMP16_*` names — the RISC OS + desktop 16-colour palette in native Wimp index order. +- `bitmap_set_palette()` — replace a bitmap's palette in place, reusing the + existing palette buffer when it is large enough; NULL drops the palette. - `wuss_window_create_placed()` — creates a window from a content size instead of a box, letting Wuss pack it (furniture included) into the first free screen region. Successive auto-placed windows tile; placement cascades when @@ -57,6 +117,24 @@ _Unreleased_ until one is cut. ### Changed +- **Breaking:** `wuss_create()` takes a `const wuss_alloc_t *alloc` argument + after `config`; pass NULL for the stdlib allocator. +- **Breaking:** `wuss_config_t::palette` is renamed `furniture`, and its type + `wuss_palette_t` renamed `wuss_furniture_palette_t`. `wuss_colour_t` narrows + from `int` to `unsigned char`. +- **Breaking:** the framebuf draw primitives split draw/fill in their names: + `screen_draw_pixel` → `screen_set_pixel`, `screen_draw_rect` → + `screen_fill_rect`, `screen_draw_square` → `screen_fill_square`. `screen_draw_rect` + now names a one-pixel outline. +- **Breaking:** the window/desktop background is now a `wuss_backdrop_t` + (`{ colour, pattern, pattern_bg }`) instead of a bare `wuss_colour_t`: + `wuss_config_t::backdrop`, and the `bg` parameter of + `wuss_window_create()`, `wuss_window_create_placed()` and + `wuss_window_set_background()`. A non-`screen_PATTERN_SOLID` `pattern` fills + with `screen_fill_pattern()` — the desktop phased to the screen origin, a + window's content phased to its scroll origin so the pattern stays locked to + the content. `wuss_BACKDROP_COLOUR(c)` and `wuss_BACKDROP_PATTERN(c, p, b)` + build one; the flat-colour case is `wuss_BACKDROP_COLOUR(old_value)`. - **Breaking:** `wuss_button_t` values are now flags (`wuss_BUTTON_SELECT` 4, `wuss_BUTTON_MENU` 2, `wuss_BUTTON_ADJUST` 1, `wuss_BUTTON_NONE` 0) so chords such as Select+Adjust can be reported. Client code comparing a @@ -68,14 +146,32 @@ _Unreleased_ until one is cut. - Adjust-clicking a scroll arrow now steps against the direction the arrow points, so one arrow can be worked both ways without moving the pointer. Toggle-size stays Select-only. +- A window can no longer be resized larger than the screen. ### Fixed +- Opening a menu from a task's mouse-down handler no longer picks the menu's + first row on the matching mouse-up: that release is now swallowed. +- A menu chain is now closed before its `on_select` callback runs, so a + callback that opens another menu no longer fights the one being torn down. +- Menu text is drawn in the nearest-black palette entry rather than assuming + a fixed index, so menus stay legible under any system palette. +- The mouse wheel no longer scrolls a window on an axis it declared + non-scrollable. +- A MENU-button click on window furniture now has no effect, instead of being + routed to the content task. +- A window's scroll offset is re-clamped after a resize reveals content past + the document extent. +- The scrollbar well keeps a 2px gap at each end. - Dragging a scrollbar well with Select no longer raises the window; only a resize-icon grab restacks it. - `wuss_window_move()` no longer repaints already-blitted pixels when a drag past an occluded corner slides one clean piece of the window onto ground another clean piece just vacated. +- Several scroll-redraw glitches fixed: stale pixels when scroll events arrive + faster than redraws, content blitted over a mid-content occluder, blit + sub-pieces clobbering each other's source region, and repaint sets not + clipped to the visible area. The hovered icon is re-resolved after a scroll. - A work-area button held on mouse-down is now released if the click opens a window that covers the button's owner, instead of staying stuck pressed. - Resize-corner drag preserves where within the resize icon the mouse-down diff --git a/CMakeLists.txt b/CMakeLists.txt index a322d526..472fc25a 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,8 +30,9 @@ endif(CCACHE_FOUND) option(USE_FORTIFY "Use Fortify" OFF) option(USE_ASAN "Build with AddressSanitizer and UndefinedBehaviorSanitizer" OFF) option(DPTLIB_IMAGES_READ_ONLY "Remove libpng write support" OFF) -option(WUSS_FURNITURE "Build the wuss window-furniture subsystem" ON) -option(WUSS_ICONS "Build the wuss in-content icon subsystem" ON) +option(WUSS_FURNITURE "Build the Wuss window-furniture subsystem" ON) +option(WUSS_ICONS "Build the Wuss in-content icon subsystem" ON) +option(WUSS_MENUS "Build the Wuss pop-up menu helper (implies WUSS_ICONS)" ON) # Referencing CMAKE_TOOLCHAIN_FILE avoids a warning on rebuilds. if(NOT ${CMAKE_TOOLCHAIN_FILE} STREQUAL "") @@ -105,6 +106,7 @@ set(PUBLIC_HEADERS include/io/stream.h include/test/all-tests.h include/test/txtscr.h + include/text/bmtext.h include/text/txtfmt.h include/utils/array.h include/utils/barith.h @@ -121,6 +123,9 @@ set(PUBLIC_HEADERS if(WUSS_ICONS) list(APPEND PUBLIC_HEADERS include/wuss/icon.h) endif() +if(WUSS_MENUS) + list(APPEND PUBLIC_HEADERS include/wuss/menu.h) +endif() # The public headers must be set as properties of the library, not as # target_sources. The quoting is essential. @@ -285,6 +290,8 @@ set(TESTLIB_SOURCES libraries/test/txtscr/txtscr.c) set(TEXT_SOURCES + libraries/text/bmtext/draw.c + libraries/text/bmtext/layout.c libraries/text/txtfmt/create.c libraries/text/txtfmt/destroy.c libraries/text/txtfmt/get-length.c @@ -326,17 +333,21 @@ set(UTILS_SOURCES libraries/utils/primes/primes.c) set(WUSS_CORE_SOURCES + libraries/wuss/backdrop.c libraries/wuss/create.c libraries/wuss/destroy.c libraries/wuss/get-font.c + libraries/wuss/get-pointer.c libraries/wuss/idle.c libraries/wuss/impl.h libraries/wuss/invalidate.c libraries/wuss/mouse-click.c libraries/wuss/mouse-move.c + libraries/wuss/nearest-colour.c libraries/wuss/redraw.c libraries/wuss/scroll.c libraries/wuss/scroll-step.c + libraries/wuss/set-palette.c libraries/wuss/task/start.c libraries/wuss/task/stop.c libraries/wuss/window/at.c @@ -351,6 +362,7 @@ set(WUSS_CORE_SOURCES libraries/wuss/window/resize.c libraries/wuss/window/restack.c libraries/wuss/window/set-background.c + libraries/wuss/window/set-hidden.c libraries/wuss/window/set-scroll.c) set(WUSS_FURNITURE_SOURCES @@ -362,6 +374,7 @@ set(WUSS_FURNITURE_SOURCES libraries/wuss/furniture/hit-test.c libraries/wuss/furniture/hscroll-box.c libraries/wuss/furniture/invalidate.c + libraries/wuss/furniture/ops.c libraries/wuss/furniture/resize-box.c libraries/wuss/furniture/scroll-action.c libraries/wuss/furniture/titlebar-box.c @@ -377,6 +390,7 @@ set(WUSS_ICON_SOURCES libraries/wuss/icon/draw.c libraries/wuss/icon/free.c libraries/wuss/icon/get-bbox.c + libraries/wuss/icon/get-selected.c libraries/wuss/icon/get-text.c libraries/wuss/icon/get-type.c libraries/wuss/icon/get-window.c @@ -384,16 +398,29 @@ set(WUSS_ICON_SOURCES libraries/wuss/icon/invalidate.c libraries/wuss/icon/screen-box.c libraries/wuss/icon/set-hidden.c + libraries/wuss/icon/set-hover.c + libraries/wuss/icon/set-selected.c libraries/wuss/icon/set-text.c libraries/wuss/icon.h) +set(WUSS_MENU_SOURCES + libraries/wuss/menu/menu.c + libraries/wuss/menu/create-from-desc.c + libraries/wuss/menu.h) + set(WUSS_SOURCES ${WUSS_CORE_SOURCES}) if(WUSS_FURNITURE) list(APPEND WUSS_SOURCES ${WUSS_FURNITURE_SOURCES}) endif() +if(WUSS_MENUS) + set(WUSS_ICONS ON) # the menu helper needs the icon subsystem +endif() if(WUSS_ICONS) list(APPEND WUSS_SOURCES ${WUSS_ICON_SOURCES}) endif() +if(WUSS_MENUS) + list(APPEND WUSS_SOURCES ${WUSS_MENU_SOURCES}) +endif() set(ALL_SOURCES ${PUBLIC_HEADERS} @@ -451,6 +478,9 @@ endif() if(WUSS_ICONS) target_compile_definitions(DPTLib PUBLIC WUSS_ICONS) endif() +if(WUSS_MENUS) + target_compile_definitions(DPTLib PUBLIC WUSS_MENUS) +endif() if(NOT TARGET_RISCOS) set(CMAKE_FIND_FRAMEWORK NEVER) @@ -560,6 +590,7 @@ install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include option(BUILD_TESTS "Build test program" OFF) option(BUILD_SDL_TESTS "Build tests that use SDL" OFF) +option(BUILD_APPS "Build standalone apps (Wuss interactive demo)" OFF) if(BUILD_TESTS) set(TEST_SOURCES @@ -587,27 +618,8 @@ if(BUILD_TESTS) libraries/utils/bsearch/test/bsearch-test.c libraries/utils/pack/test/pack-test.c libraries/datastruct/vector/test/vector-test.c - libraries/wuss/test/tasks/ball.c - libraries/wuss/test/tasks/blank.c - libraries/wuss/test/tasks/chars.c - libraries/wuss/test/tasks/checker.c - libraries/wuss/test/tasks/curve.c - libraries/wuss/test/tasks/gradient.c - libraries/wuss/test/tasks/image.c - libraries/wuss/test/tasks/palette.c - libraries/wuss/test/tasks/porter-duff.c - libraries/wuss/test/tasks/sofa.c - libraries/wuss/test/tasks/text.c libraries/wuss/test/wuss-test.c) - # The icon-driven launcher and the icons task only build when both wuss - # subsystems are present; the SDL interactive driver needs them too. - if(WUSS_FURNITURE AND WUSS_ICONS) - list(APPEND TEST_SOURCES - libraries/wuss/test/tasks/icons.c - libraries/wuss/test/tasks/launcher.c) - endif() - source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${TEST_SOURCES}) # Avoid a warning from CMake @@ -630,19 +642,70 @@ if(BUILD_TESTS) target_link_libraries(DPTLibTest m) endif() + # Some unit tests (e.g. bmfont-test) gate an interactive SDL mode on + # USE_SDL; BUILD_SDL_TESTS links SDL3 and defines it for the test binary. if(BUILD_SDL_TESTS) - if(NOT (WUSS_FURNITURE AND WUSS_ICONS)) - message(FATAL_ERROR "BUILD_SDL_TESTS requires WUSS_FURNITURE and WUSS_ICONS") - endif() - find_package(SDL3 REQUIRED) find_package(SDL3_image REQUIRED) target_link_libraries(DPTLibTest SDL3::SDL3 SDL3_image::SDL3_image) - - target_compile_definitions(DPTLibTest PUBLIC USE_SDL) + target_compile_definitions(DPTLibTest PRIVATE USE_SDL) endif() install(TARGETS DPTLibTest RUNTIME DESTINATION ${CMAKE_INSTALL_PREFIX}) endif() + + +# Standalone apps +# + +if(BUILD_APPS) + # The Wuss interactive demo: its own executable, built from the SDL task + # modules plus apps/wuss/main.c (which was the old in-test SDL driver). + if(NOT (WUSS_FURNITURE AND WUSS_ICONS)) + message(FATAL_ERROR "BUILD_APPS requires WUSS_FURNITURE and WUSS_ICONS") + endif() + + find_package(SDL3 REQUIRED) + find_package(SDL3_image REQUIRED) + + set(WUSS_APP_SOURCES + apps/wuss/main.c + libraries/wuss/test/tasks/ball.c + libraries/wuss/test/tasks/blank.c + libraries/wuss/test/tasks/chars.c + libraries/wuss/test/tasks/checker.c + libraries/wuss/test/tasks/curve.c + libraries/wuss/test/tasks/gradient.c + libraries/wuss/test/tasks/icons.c + libraries/wuss/test/tasks/image.c + libraries/wuss/test/tasks/lissajous.c + libraries/wuss/test/tasks/palette.c + libraries/wuss/test/tasks/porter-duff.c + libraries/wuss/test/tasks/sofa.c + libraries/wuss/test/tasks/swatches.c + libraries/wuss/test/tasks/text.c) + + source_group(TREE "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${WUSS_APP_SOURCES}) + + add_executable(wuss ${WUSS_APP_SOURCES}) + + set_target_properties(wuss PROPERTIES + DESCRIPTION "DPTLib Wuss interactive demo" + C_STANDARD 99 + OUTPUT_NAME_DEBUG wuss-debug + OUTPUT_NAME_RELEASE wuss + OUTPUT_NAME_RELWITHDEBINFO wuss-relwithdebinfo + OUTPUT_NAME_MINSIZEREL wuss-minsizerel) + + target_include_directories(wuss PRIVATE libraries/wuss/test) + target_compile_definitions(wuss PRIVATE USE_SDL) + target_link_libraries(wuss DPTLib SDL3::SDL3 SDL3_image::SDL3_image) + if(NOT MSVC) + target_link_libraries(wuss m) + endif() + + install(TARGETS wuss RUNTIME + DESTINATION ${CMAKE_INSTALL_PREFIX}) +endif() diff --git a/README.md b/README.md index 2f17af0d..67c36fcb 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,11 @@ DPTLib is my platform independent C library. It contains a wide variety of funct - [`test/txtscr.h`](https://github.com/dpt/DPTLib/blob/master/include/test/txtscr.h) — text format 'screen' +### Text + +- [`text/txtfmt.h`](https://github.com/dpt/DPTLib/blob/master/include/text/txtfmt.h) — word-wrap a string to a character width (for monospaced text) +- [`text/bmtext.h`](https://github.com/dpt/DPTLib/blob/master/include/text/bmtext.h) — word-wrap and draw a paragraph to a pixel width in a `bmfont` + ### Utilities - [`utils/array.h`](https://github.com/dpt/DPTLib/blob/master/include/utils/array.h) — array utilities diff --git a/apps/test/main.c b/apps/test/main.c index 435b3b82..aaf9f21d 100644 --- a/apps/test/main.c +++ b/apps/test/main.c @@ -1,4 +1,4 @@ -// +/* test/main.c */ // main.c // DPTLibTest // diff --git a/apps/wuss/main.c b/apps/wuss/main.c new file mode 100644 index 00000000..8a2d5f7e --- /dev/null +++ b/apps/wuss/main.c @@ -0,0 +1,797 @@ +/* wuss/main.c -- Wuss - interactive minimal window manager demo */ + +#include +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/result.h" +#include "base/utils.h" +#include "framebuf/bitmap.h" +#include "framebuf/bmfont.h" +#include "framebuf/colour.h" +#include "framebuf/palettes.h" +#include "framebuf/pixelfmt.h" +#include "framebuf/screen.h" +#include "geom/box.h" +#include "io/path.h" +#include "wuss/wuss.h" +#include "wuss/window.h" +#include "wuss/menu.h" + +#include + +#include "tasks/ball.h" +#include "tasks/blank.h" +#include "tasks/chars.h" +#include "tasks/checker.h" +#include "tasks/curve.h" +#include "tasks/gradient.h" +#include "tasks/icons.h" +#include "tasks/image.h" +#include "tasks/lissajous.h" +#include "tasks/palette.h" +#include "tasks/porter-duff.h" +#include "tasks/sofa.h" +#include "tasks/swatches.h" +#include "tasks/text.h" + +/* ----------------------------------------------------------------------- */ + +/* Screen pixel format for the interactive test: 1 = 32bpp pixelfmt_bgrx8888 + * (feeds SDL directly, no per-frame conversion); 0 = pixelfmt_p4 paletted + * (exercises screen_copy_rect's nibble-packed blit path instead). */ +#define WUSS_TEST_32BPP 0 + +/* the launcher's spawn callbacks take no arguments, so the pieces they need + * are stashed here instead; run_wuss runs at most once per + * process, so a file-scope struct is as good as a passed-around context */ +static struct +{ + wuss_t *wuss; + const colour_t *palette; + int npalette; + const char *resources; + bmfont_t *daydream_font; + bool quit; /* set by the "Quit Wuss" task-menu entry */ +} +g; + +/* Each spawn allocates a fresh per-instance task block so a task may run in + * several windows at once; the block is owned by its window and freed by the + * task's wuss_EVENT_CLOSE handler. If create fails, or (for the font-less + * chars task) returns OK without opening a window, the block is freed here -- + * otherwise it would leak. */ + +static result_t spawn_ball(void) +{ + ball_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = ball_create(g.wuss, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_text(void) +{ + text_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = text_create(g.wuss, g.daydream_font, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_blank(void) +{ + blank_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = blank_create(g.wuss, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_chars(void) +{ + chars_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = chars_create(g.wuss, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_palette(void) +{ + palette_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = palette_create(g.wuss, g.palette, g.npalette, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_image(void) +{ + image_task_t *t; + const char *leafname; + const char *filename; + char buf[DPTLIB_MAXPATH]; + const char *ninepatch; + result_t rc; + + t = calloc(1, sizeof(*t)); + if (t == NULL) return result_OOM; + + leafname = path_join_leafname("jessica", "png"); + filename = path_join_filename(g.resources, 3, "resources", "images", leafname); + strcpy(buf, filename); + ninepatch = path_join_filename(g.resources, 3, "resources", "wuss", + path_join_leafname("9tile", "png")); + + rc = image_create(g.wuss, buf, ninepatch, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_checker(void) +{ + checker_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = checker_create(g.wuss, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_curve(void) +{ + curve_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = curve_create(g.wuss, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_lissajous(void) +{ + lissajous_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = lissajous_create(g.wuss, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_sofa(void) +{ + sofa_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = sofa_create(g.wuss, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_gradient(void) +{ + gradient_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = gradient_create(g.wuss, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_icons(void) +{ + icons_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = icons_create(g.wuss, g.daydream_font, g.resources, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_swatches(void) +{ + swatches_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = swatches_create(g.wuss, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +static result_t spawn_porter_duff(void) +{ + porter_duff_task_t *t = calloc(1, sizeof(*t)); + result_t rc; + if (t == NULL) return result_OOM; + rc = porter_duff_create(g.wuss, g.palette, g.daydream_font, g.resources, t); + if (rc != result_OK || t->window == NULL) { free(t); return rc; } + return result_OK; +} + +/* A static demo menu tree for the pop-up helper: a submenu, a couple of + * ticked rows and a standalone dashed rule row above the final entry. + * wuss_menu_open never mutates it. */ +static const wuss_menu_item_t g_menu_export_items[] = +{ + { "As PNG", wuss_MENU_ITEM_NONE, NULL }, + { "As JPEG", wuss_MENU_ITEM_NONE, NULL }, + { "As GIF", wuss_MENU_ITEM_DISABLED, NULL } +}; + +static const wuss_menu_t g_menu_export = +{ + "Export", g_menu_export_items, NELEMS(g_menu_export_items) +}; + +/* A caller-owned window wired as a menu item's `.window`: hovering "Details" + * shows it where a submenu would open, and moving off the row (or dismissing + * the menu) hides it again. Created once, lazily, by spawn_menu. */ +static wuss_window_t *g_menu_details_window; + +static wuss_menu_item_t g_menu_items[] = +{ + { "Open", wuss_MENU_ITEM_NONE, NULL, NULL }, + { "Show grid", wuss_MENU_ITEM_TICKED, NULL, NULL }, + { "Wireframe", wuss_MENU_ITEM_TICKED, NULL, NULL }, + { "Export", wuss_MENU_ITEM_NONE, &g_menu_export, NULL }, + { "Details", wuss_MENU_ITEM_NONE, NULL, NULL }, /* .window set in spawn_menu */ + { "Quit", wuss_MENU_ITEM_DASHED, NULL, NULL } +}; + +static const wuss_menu_t g_menu = +{ + "Display", g_menu_items, NELEMS(g_menu_items) +}; + +static void menu_selected(const wuss_menu_t *menu, + int index, + wuss_button_t button, + void *ctx) +{ + NOT_USED(button); + NOT_USED(ctx); + printf("menu: picked \"%s\"\n", + menu->items[index].text ? menu->items[index].text : "(sep)"); +} + +/* index of the "Details" row in g_menu_items */ +#define G_MENU_DETAILS_INDEX 4 + +static result_t spawn_menu(void) +{ + if (g_menu_details_window == NULL) + { + box_t content; + result_t rc; + + /* a small hidden window; wuss fills its background, no task needed */ + content.x0 = 0; + content.y0 = 0; + content.x1 = 180; + content.y1 = 120; + rc = wuss_window_create(g.wuss, &content, "Details", + wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK + | wuss_WINDOW_NO_TOGGLE_SIZE + | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL + | wuss_WINDOW_NO_RESIZE | wuss_WINDOW_HIDDEN, + wuss_BACKDROP_COLOUR(1), + NULL, + SIZE2D(180, 120), SIZE2D(0, 0), + &g_menu_details_window); + if (rc != result_OK) + return rc; + g_menu_items[G_MENU_DETAILS_INDEX].window = g_menu_details_window; + } + + return wuss_menu_open(g.wuss, &g_menu, wuss_get_pointer(g.wuss), + menu_selected, NULL, NULL); +} + +/* Same menu shape built from a descriptor string, to exercise + * wuss_menu_create_from_desc. The tree must outlive the open chain, so it is + * kept here and rebuilt (previous one freed) on each open. Freed for good in + * run_wuss's teardown. */ +static wuss_menu_t *g_menu_desc; + +static const wuss_menu_item_t g_menu_desc_export_items[] = +{ + { "As PNG", wuss_MENU_ITEM_NONE, NULL }, + { "As JPEG", wuss_MENU_ITEM_NONE, NULL }, + { "As GIF", wuss_MENU_ITEM_DISABLED, NULL } +}; + +static const wuss_menu_t g_menu_desc_export = +{ + "Export", g_menu_desc_export_items, NELEMS(g_menu_desc_export_items) +}; + +static result_t spawn_menu_desc(void) +{ + wuss_menu_t *m; + result_t rc; + + rc = wuss_menu_create_from_desc(&m, + "Display, Open, !Show grid, !Wireframe, >Export, |Quit", + &g_menu_desc_export); + if (rc != result_OK) + return rc; + + wuss_menu_destroy(g_menu_desc); + g_menu_desc = m; + + return wuss_menu_open(g.wuss, g_menu_desc, wuss_get_pointer(g.wuss), + menu_selected, NULL, NULL); +} + +/* The task launcher is a MENU-button pop-up over the backdrop rather than a + * window of buttons. g_task_items and g_task_spawn run in lock-step: picking + * item i calls g_task_spawn[i]. */ +typedef result_t (*task_spawn_fn_t)(void); + +static const wuss_menu_item_t g_task_items[] = +{ + { "Ball", wuss_MENU_ITEM_NONE, NULL }, + { "Blank", wuss_MENU_ITEM_NONE, NULL }, + { "Chars", wuss_MENU_ITEM_NONE, NULL }, + { "Checker", wuss_MENU_ITEM_NONE, NULL }, + { "Curve", wuss_MENU_ITEM_NONE, NULL }, + { "Gradient", wuss_MENU_ITEM_NONE, NULL }, + { "Icons", wuss_MENU_ITEM_NONE, NULL }, + { "Image", wuss_MENU_ITEM_NONE, NULL }, + { "Lissajous", wuss_MENU_ITEM_NONE, NULL }, + { "Palette", wuss_MENU_ITEM_NONE, NULL }, + { "Porter-Duff", wuss_MENU_ITEM_NONE, NULL }, + { "Sofa", wuss_MENU_ITEM_NONE, NULL }, + { "Swatches", wuss_MENU_ITEM_NONE, NULL }, + { "Text", wuss_MENU_ITEM_NONE, NULL }, + { "Menu", wuss_MENU_ITEM_DASHED, NULL }, + { "Menu (desc)", wuss_MENU_ITEM_NONE, NULL }, + { "Quit Wuss", wuss_MENU_ITEM_DASHED, NULL } +}; + +static result_t spawn_quit(void) +{ + g.quit = true; + return result_OK; +} + +static const task_spawn_fn_t g_task_spawn[] = +{ + spawn_ball, spawn_blank, spawn_chars, spawn_checker, spawn_curve, + spawn_gradient, spawn_icons, spawn_image, spawn_lissajous, spawn_palette, + spawn_porter_duff, spawn_sofa, spawn_swatches, spawn_text, spawn_menu, + spawn_menu_desc, spawn_quit +}; + +static const wuss_menu_t g_task_menu = +{ + "Tasks", g_task_items, NELEMS(g_task_items) +}; + +static void task_menu_selected(const wuss_menu_t *menu, + int index, + wuss_button_t button, + void *ctx) +{ + NOT_USED(menu); + NOT_USED(button); + NOT_USED(ctx); + if (index >= 0 && index < (int) NELEMS(g_task_spawn)) + (void) g_task_spawn[index](); +} + +static wuss_button_t sdl_button_to_wuss(Uint8 button) +{ + switch (button) + { + case SDL_BUTTON_MIDDLE: return wuss_BUTTON_MENU; + case SDL_BUTTON_RIGHT: return wuss_BUTTON_ADJUST; + default: return wuss_BUTTON_SELECT; + } +} + +/* Palettes F4 cycles through, in order. Each fills a colour_t[16]. */ +static void (*const g_palettes[])(colour_t *) = +{ + define_pico8_palette, + define_wimp16_palette +}; + +/* SDL delivers mouse coordinates in window space, which F2 can scale away + * from the fixed-size Wuss screen; map back down to screen space */ +static void sdl_pos_to_scr(SDL_Window *window, + int scr_width, + int scr_height, + float in_x, + float in_y, + int *out_x, + int *out_y) +{ + int win_w, win_h; + + SDL_GetWindowSize(window, &win_w, &win_h); + + *out_x = (int) (in_x * scr_width / win_w); + *out_y = (int) (in_y * scr_height / win_h); +} + +/* click windows to bring to front, drag titlebars to move, resize the + * SDL window to see the Wuss screen scale; F2 doubles the SDL window size, + * Shift-F2 halves it; F3 redraws the whole screen one pixel at a time, to + * catch tasks whose drawing routines misbehave under a 1x1 clip; F4 cycles + * the system palette live (wuss_set_palette); Q or close to quit */ +static result_t run_wuss(const char *resources) +{ + const int scr_width = 640; + const int scr_height = 480; +#if WUSS_TEST_32BPP + const int rowbytes = scr_width * 4; /* pixelfmt_bgrx8888: 4 bytes/pixel */ +#else + const int rowbytes = scr_width / 2; /* pixelfmt_p4: 2 pixels/byte */ +#endif + + result_t rc; + const char *leafname; + const char *filename; + bmfont_t *font; + bmfont_t *daydream_font; + void *pixels; + bitmap_t bm; +#if !WUSS_TEST_32BPP + bitmap_t *disp; +#endif + screen_t scr; + colour_t palette[16]; + wuss_t *wuss; + SDL_Window *window; + SDL_Renderer *renderer; + SDL_Texture *texture; + bool quit; + bool garbage_pending; + bool pixel_stress_pending; + int i; + bool use_wimp16; + int palette_index; + + { + /* "riscos16" selects the RISC OS 16-colour palette; default is PICO-8 */ + const char *palette_name = getenv("WUSS_PALETTE"); + + use_wimp16 = (palette_name != NULL && strcmp(palette_name, "wimp16") == 0); + palette_index = use_wimp16 ? 1 : 0; + g_palettes[palette_index](palette); + } + + 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", "png"); + filename = path_join_filename(resources, 3, "resources", "bmfonts", leafname); + rc = bmfont_create(filename, &daydream_font); + if (rc != result_OK) + goto Failure; + + pixels = malloc(rowbytes * scr_height); + if (pixels == NULL) + goto Failure; + +#if WUSS_TEST_32BPP + rc = bitmap_init(&bm, SIZE2D(scr_width, scr_height), pixelfmt_bgrx8888, rowbytes, palette, pixels); +#else + rc = bitmap_init(&bm, SIZE2D(scr_width, scr_height), pixelfmt_p4, rowbytes, palette, pixels); +#endif + if (rc != result_OK) + goto Failure; + + bitmap_clear(&bm, palette[palette_PICO8_WHITE]); + + screen_for_bitmap(&scr, &bm); + + if (!SDL_Init(SDL_INIT_VIDEO)) + { + fprintf(stderr, "Error: SDL_Init: %s\n", SDL_GetError()); + goto Failure; + } + + window = SDL_CreateWindow("Wuss", scr_width, scr_height, 0); + if (window == NULL) + { + fprintf(stderr, "Error: SDL_CreateWindow: %s\n", SDL_GetError()); + goto Failure; + } + + renderer = SDL_CreateRenderer(window, NULL); + if (renderer == NULL) + { + fprintf(stderr, "Error: SDL_CreateRenderer: %s\n", SDL_GetError()); + goto Failure; + } + + texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, + SDL_TEXTUREACCESS_STREAMING, + scr_width, scr_height); + if (texture == NULL) + { + fprintf(stderr, "Error: SDL_CreateTexture: %s\n", SDL_GetError()); + goto Failure; + } + + SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_NONE); + SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); /* keep pixels crisp when F2 scales the window up */ + + { + /* Furniture/bevel/accent/backdrop colour indices, one row per palette. + * Same field order as the assignments below. */ + static const wuss_colour_t chrome[2][15] = + { + /* PICO-8 */ + { palette_PICO8_DARK_BLUE, palette_PICO8_WHITE, palette_PICO8_GREEN, + palette_PICO8_RED, palette_PICO8_ORANGE, palette_PICO8_LAVENDER, + palette_PICO8_BLUE, palette_PICO8_DARK_BLUE, palette_PICO8_LIGHT_GREY, + palette_PICO8_WHITE, palette_PICO8_DARK_GREY, palette_PICO8_DARK_BLUE, + palette_PICO8_WHITE, palette_PICO8_WHITE, palette_PICO8_LIGHT_GREY }, + /* RISC OS 16-colour Wimp */ + { palette_WIMP16_GREY_75, palette_WIMP16_BLACK, palette_WIMP16_GREEN, + palette_WIMP16_RED, palette_WIMP16_ORANGE, palette_WIMP16_LIGHT_BLUE, + palette_WIMP16_GREY_50, palette_WIMP16_GREY_62, palette_WIMP16_GREY_87, + palette_WIMP16_WHITE, palette_WIMP16_GREY_50, palette_WIMP16_ORANGE, + palette_WIMP16_BLACK, palette_WIMP16_GREY_50, palette_WIMP16_GREY_37 } + }; + const wuss_colour_t *c = chrome[use_wimp16 ? 1 : 0]; + wuss_config_t config; + + config.titlebar_height = 0; + config.furniture.title.bg = c[0]; + config.furniture.title.fg = c[1]; + config.furniture.back = c[2]; + config.furniture.close = c[3]; + config.furniture.toggle = c[4]; + config.furniture.resize = c[5]; + config.furniture.scroll.arrows = c[6]; + config.furniture.scroll.wells = c[7]; + config.furniture.scroll.sausages = c[8]; + config.bevel.light = c[9]; + config.bevel.dark = c[10]; + config.accent.bg = c[11]; + config.accent.fg = c[12]; + config.backdrop.colour = c[13]; + config.backdrop.pattern = screen_PATTERN_DOTS; + config.backdrop.pattern_bg = c[14]; + + rc = wuss_create(&scr, font, palette, NELEMS(palette), &config, NULL, + &wuss); + if (rc != result_OK) + goto Failure; + } + + g.wuss = wuss; + g.palette = palette; + g.npalette = NELEMS(palette); + g.resources = resources; + g.daydream_font = daydream_font; + + quit = false; + g.quit = false; + garbage_pending = false; + pixel_stress_pending = false; + + wuss_redraw(wuss); + + while (!quit && !g.quit) + { + SDL_Event event; + + while (SDL_PollEvent(&event)) + { + switch (event.type) + { + case SDL_EVENT_QUIT: + quit = true; + break; + + case SDL_EVENT_KEY_UP: + if (event.key.key == SDLK_Q) + quit = true; + else if (event.key.key == SDLK_F1 && (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) + pixel_stress_pending = true; + else if (event.key.key == SDLK_F4) + { + /* cycle the system palette live: rebuild the palette, push it into + * the framebuffer bitmap, then tell wuss, which refreshes its own + * copy and pokes every task to recache */ + palette_index = (palette_index + 1) % (int) NELEMS(g_palettes); + g_palettes[palette_index](palette); + bitmap_set_palette(&bm, palette); + wuss_set_palette(wuss, palette, NELEMS(palette)); + } + else if (event.key.key == SDLK_F2) + { + int w, h; + + SDL_GetWindowSize(window, &w, &h); + if (event.key.mod & SDL_KMOD_SHIFT) + SDL_SetWindowSize(window, w / 2, h / 2); + else + SDL_SetWindowSize(window, w * 2, h * 2); + } + break; + + case SDL_EVENT_MOUSE_BUTTON_DOWN: + { + int x, y; + wuss_button_t button; + wuss_window_t *hit; + + sdl_pos_to_scr(window, scr_width, scr_height, event.button.x, event.button.y, &x, &y); + button = sdl_button_to_wuss(event.button.button); + wuss_mouse_click(wuss, POINT(x, y), button, wuss_MOUSE_DOWN, &hit); + + /* MENU click on bare backdrop opens the task launcher there */ + if (hit == NULL && (button & wuss_BUTTON_MENU)) + wuss_menu_open(wuss, &g_task_menu, POINT(x, y), + task_menu_selected, NULL, NULL); + } + break; + + case SDL_EVENT_MOUSE_BUTTON_UP: + { + int x, y; + + sdl_pos_to_scr(window, scr_width, scr_height, event.button.x, event.button.y, &x, &y); + wuss_mouse_click(wuss, POINT(x, y), sdl_button_to_wuss(event.button.button), wuss_MOUSE_UP, NULL); + } + break; + + case SDL_EVENT_MOUSE_MOTION: + { + int x, y; + + sdl_pos_to_scr(window, scr_width, scr_height, event.motion.x, event.motion.y, &x, &y); + wuss_mouse_move(wuss, POINT(x, y), NULL); + } + break; + + case SDL_EVENT_MOUSE_WHEEL: + { + int x, y; + + sdl_pos_to_scr(window, scr_width, scr_height, event.wheel.mouse_x, event.wheel.mouse_y, &x, &y); + wuss_scroll(wuss, POINT(x, y), (int) event.wheel.y, NULL); + } + break; + + default: + break; + } + } + + wuss_idle(wuss); + + if (garbage_pending) + { + /* full-screen corruption, drawn over whatever's already on screen + * (windows included) and left untouched: nothing here is invalidated, + * so it stays put until something actually redraws over it, e.g. the + * ball's own small per-frame dirty rect eating a trail through it, or + * a window being dragged across it */ + unsigned char *p; + size_t n; + size_t i; + + p = pixels; + n = (size_t) rowbytes * scr_height; + for (i = 0; i < n; i++) + p[i] = (unsigned char) rand(); + + garbage_pending = false; + } + else if (pixel_stress_pending) + { + /* redraw the whole screen one pixel at a time: each wuss_redraw_dirty + * call is flushed before the next pixel is invalidated, so no two + * pixels are ever coalesced into one redraw. A task whose drawing + * routine assumes it always gets handed a multi-pixel/aligned clip + * (rather than trusting scr->clip) will visibly misdraw here even + * though it looks fine under normal, larger dirty regions. */ + int x, y; + + for (y = 0; y < scr_height; y++) + { + for (x = 0; x < scr_width; x++) + { + box_t px; + + px.x0 = x; px.y0 = y; + px.x1 = x + 1; px.y1 = y + 1; + + wuss_invalidate(wuss, &px); + wuss_redraw_dirty(wuss); + } + } + + pixel_stress_pending = false; + } + else + { + wuss_redraw_dirty(wuss); + } + +#if WUSS_TEST_32BPP + SDL_UpdateTexture(texture, NULL, bm.base, bm.rowbytes); /* bm is already bgrx8888: no conversion needed */ +#else + rc = bitmap_convert(&bm, pixelfmt_bgrx8888, &disp); + if (rc == result_OK) + { + SDL_UpdateTexture(texture, NULL, disp->base, disp->rowbytes); + free(disp->base); + free(disp); + } +#endif + + SDL_RenderTexture(renderer, texture, NULL, NULL); + SDL_RenderPresent(renderer); + + SDL_Delay(1000 / 60); + } + + /* ponytail: wuss_destroy() below frees every still-open window but not the + * per-instance task block hung off it, so any task window left open at quit + * leaks its block. Harmless at process exit; add a wuss close callback if a + * task ever needs deterministic teardown. */ + wuss_menu_destroy(g_menu_desc); + + wuss_destroy(wuss); + bmfont_destroy(font); + bmfont_destroy(daydream_font); + + SDL_DestroyTexture(texture); + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + SDL_Quit(); + + free(pixels); + + return result_TEST_PASSED; + + +Failure: + + printf("run_wuss: failed (rc=0x%X)\n", rc); + + return result_TEST_FAILED; +} + +/* ----------------------------------------------------------------------- */ + +int main(int argc, char *argv[]) +{ + const char *resources = "."; + int i; + result_t rc; + + for (i = 1; i < argc; i++) + if (strcmp(argv[i], "-resources") == 0 && i + 1 < argc) + resources = argv[++i]; + + rc = run_wuss(resources); + + return rc == result_TEST_PASSED ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/docs/geom/packer.md b/docs/geom/packer.md index e9e65e03..d18002f5 100644 --- a/docs/geom/packer.md +++ b/docs/geom/packer.md @@ -64,6 +64,19 @@ result_t packer_place_at(packer_t *packer, This places an element at a specific location, ignoring margins. +## Releasing Elements + +Use `packer_release()` to hand a previously placed area back to the free pool: + +```C +result_t packer_release(packer_t *packer, + const box_t *area); +``` + +Pass the same box that `packer_place_at()` placed, or the area consumed by `packer_place_by()` (the returned footprint plus its gutter strip). The packer matches it against its record of placed boxes and rebuilds the free list from the container minus every box still live, so repeated place/release cycles reclaim the whole area exactly rather than fragmenting it into unusable slivers. `area` is clipped to the margins and copied. + +A release whose box matches no recorded placement (for example after `packer_clear()`, which drops the placement record) simply adds the area to the free pool without a rebuild. `packer_get_consumed_area()` is not narrowed by a release. + ## Layout Management Use `packer_clear()` to move past consumed areas: @@ -106,7 +119,7 @@ typedef result_t (packer_map_fn_t)(const box_t *area, void *opaque); The packer can return these specific result codes: - `result_PACKER_DIDNT_FIT` - Element couldn't be placed in available space -- `result_PACKER_EMPTY` - No elements have been placed yet +- `result_PACKER_EMPTY` - No elements have been placed yet, or a released area lay entirely outside the margins ## Cleanup diff --git a/docs/windowing/wuss.md b/docs/windowing/wuss.md index 0871dd5c..b1d0ef9d 100644 --- a/docs/windowing/wuss.md +++ b/docs/windowing/wuss.md @@ -20,7 +20,9 @@ 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. `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. +`font` and `palette` may both be NULL, for unlabelled titlebars and a built-in default palette respectively. `config` may be NULL for default titlebar height/colours. `config->backdrop` is a `wuss_backdrop_t` — a flat colour, or an 8x8 fill pattern — painted behind windows on every redraw; set its `colour` to `wuss_NO_BACKGROUND` (the default when `config` is NULL) to leave the background untouched and require the caller to repaint it itself. A non-`screen_PATTERN_SOLID` `pattern` tiles that pattern in `colour` over `pattern_bg`, phased to a fixed screen origin so it does not crawl between full and dirty-region redraws. `wuss_get_font` reads back the font passed in (or NULL), for a task that wants to draw its own content in the same face as titlebars. + +A `wuss_backdrop_t` is `{ colour, pattern, pattern_bg }`; the `wuss_BACKDROP_COLOUR(c)` and `wuss_BACKDROP_PATTERN(c, p, b)` macros build one as a compound literal. Destroy with `wuss_destroy`, which also destroys any windows still open on it. @@ -30,13 +32,13 @@ Create a window with a content bounding box, optional title, appearance flags, a ```C result_t wuss_window_create(wuss_t *wuss, const box_t *content, const char *title, - wuss_window_flags_t flags, wuss_colour_t bg, + wuss_window_flags_t flags, wuss_backdrop_t bg, const wuss_task_t *task, size2d_t doc, size2d_t min_doc, wuss_window_t **window); ``` -`bg` is 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`. +`bg` is a `wuss_backdrop_t` (flat colour or 8x8 pattern, as for `config->backdrop`), filled in by wuss before each redraw event; set its `colour` to `wuss_NO_BACKGROUND` for the task to draw its own background (avoids a redundant fill behind an opaque task). Any pattern is phased to the window's scroll origin so it stays locked to the content as the window scrolls. Changeable later via `wuss_window_set_background`, which also takes a `wuss_backdrop_t`. `doc` is the virtual document extent behind the horizontal/vertical scrollbars' sausage proportion; pass `content`'s own width/height for a window with nothing to scroll. It is also the ceiling a resize-drag or toggle-size grows the content area to. Set once at creation, immutable thereafter. @@ -93,10 +95,10 @@ typedef struct wuss_event wuss_event_kind_t kind; union { - struct { screen_t *scr; const box_t *content; } redraw; - struct { wuss_mouse_action_t action; point_t point; wuss_button_t button; } mouse; + struct { screen_t *scr; const box_t *content; } redraw; + struct { wuss_mouse_action_t action; point_t point; wuss_button_t button; } mouse; struct { wuss_icon_t *icon; wuss_mouse_action_t action; wuss_button_t button; } icon; - struct { point_t point; int delta; } scroll; + struct { point_t point; int delta; } scroll; } data; } @@ -125,6 +127,54 @@ Each window carries a scroll offset, `(0, 0)` by default: the point in the task' - window-local `x`/`y` delivered in mouse/scroll events (and expected in `wuss_window_invalidate`'s `local_box`) are in virtual content space, i.e. already shifted by the scroll offset. - a redraw event's `content` is still the on-screen (unscrolled) content box; a task reads the offset itself via `wuss_window_get_scroll` to work out which part of its content to paint there. +### How the coordinate spaces relate + +Everything a redraw callback gets is _screen space_ except `scroll`, which is a _virtual content space_ offset. + +``` +screen (0,0) --- wuss->scr, origin top-left + +-----------------------------------------------+ + | win->visible (whole on-screen footprint) | + | +-----------------------------------------+ | + | | titlebar + 1px outline (carved off) | | + | +--------------------------------+--------+ | wuss__content_box() = + | | content box = redraw.bounds | vscroll| | visible + | | (bounds.x0,y0) . | (carve)| | - outline + | | . . +-------------+ .| | | - titlebar + | | . | redraw. | this .| | | - furniture carve + | | . | content | piece .| | | + | | . | (one clip | only .| | | + | | . | piece) | .| | | + | | . +-------------+ .| | | + | | . . . . . . . . . . . . . . .| | | + | +--------------------------------+--------+ | + | | hscroll (carve) | | + | +-----------------------------------------+ | + +-----------------------------------------------+ + +virtual content space: size win->doc, its own origin (0,0). + win->scroll = which point of doc sits at bounds.x0,y0. + doc extends past the content box (right + bottom); that + overhang is exactly what scroll ranges over. +``` + +- `win->visible` — window's whole on-screen box (screen space); set by Wuss at create/move/resize/toggle. `wuss_window_get_visible_bounds`. +- content box / `redraw.bounds` — `visible` with outline, titlebar and any scrollbar/resize carve removed (`wuss__content_box`). `wuss_window_get_content_bounds`. +- `clipped` (internal) — `bounds` intersected with the dirty rect, before subtracting windows above. +- `redraw.content` — `clipped` minus whatever higher windows cover, split into non-overlapping pieces; the callback fires once per piece with `scr->clip` already set to it. Touch only pixels inside it. +- `win->scroll` / `redraw.scroll` — document-space offset; the task sets it via `wuss_window_set_scroll`, Wuss clamps it to `[0, doc - viewport]`. +- `win->doc` — virtual content extent, fixed at window create; drives the scrollbar sausage size and the scroll clamp. + +Conversions (as used in `mouse-move.c`, `scroll.c`, `scroll-step.c`): + +``` +screen -> document: doc.x = screen.x - bounds.x0 + scroll.x (same for y) +document -> screen: screen.x = bounds.x0 - scroll.x + doc.x +scroll clamp: max = doc - (bounds.x1 - bounds.x0); clamp(scroll, 0, max) +``` + +In a redraw callback: start drawing at `bounds.x0 - scroll.x`, `bounds.y0 - scroll.y`, then paint the whole content normally — the framebuffer clip (`scr->clip` = `redraw.content`) discards anything outside the piece. See `libraries/wuss/test/tasks/text.c`. + ## Redrawing - `wuss_redraw` repaints every window, back-to-front, unconditionally, having first painted the configured backdrop colour (see Setup) behind them, if any. diff --git a/include/base/debug.h b/include/base/debug.h index fd7249f8..ca74f437 100644 --- a/include/base/debug.h +++ b/include/base/debug.h @@ -1,4 +1,4 @@ -/* debug.h -- debugging and logging macros */ +/* base/debug.h -- debugging and logging macros */ #ifndef BASE_DEBUG_H #define BASE_DEBUG_H diff --git a/include/base/result.h b/include/base/result.h index 5ae5f834..d8021f31 100644 --- a/include/base/result.h +++ b/include/base/result.h @@ -1,4 +1,4 @@ -/* result.h -- generic function return values */ +/* base/result.h -- generic function return values */ #ifndef BASE_RESULT_H #define BASE_RESULT_H diff --git a/include/base/types.h b/include/base/types.h index 77570e39..f53e2f9c 100644 --- a/include/base/types.h +++ b/include/base/types.h @@ -1,4 +1,4 @@ -/* types.h -- fixed-width integer types */ +/* base/types.h -- fixed-width integer types */ #ifndef BASE_TYPES_H #define BASE_TYPES_H diff --git a/include/base/utils.h b/include/base/utils.h index 3d3cbd7a..8e6b389c 100644 --- a/include/base/utils.h +++ b/include/base/utils.h @@ -1,4 +1,4 @@ -/* utils.h -- various utilities */ +/* base/utils.h -- various utilities */ #ifndef BASE_UTILS_H #define BASE_UTILS_H diff --git a/include/databases/digest-db.h b/include/databases/digest-db.h index e91eaca2..d4002c8c 100644 --- a/include/databases/digest-db.h +++ b/include/databases/digest-db.h @@ -1,13 +1,13 @@ -/* digest-db.h -- digest database */ +/* databases/digest-db.h -- digest database */ /** * \file digest-db.h * * 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 1a68715a..fd02e3de 100644 --- a/include/databases/filename-db.h +++ b/include/databases/filename-db.h @@ -1,12 +1,12 @@ -/* filename-db.h -- filename database */ +/* databases/filename-db.h -- filename database */ /** * \file filename-db.h * * 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-reader-hash.h b/include/databases/pickle-reader-hash.h index 86c6d778..e43b3ee5 100644 --- a/include/databases/pickle-reader-hash.h +++ b/include/databases/pickle-reader-hash.h @@ -1,4 +1,4 @@ -/* pickle-reader-hash.h -- glue methods to let pickle read from hashes */ +/* databases/pickle-reader-hash.h -- glue methods to let pickle read from hashes */ #ifndef DATABASES_PICKLE_READER_HASH_H #define DATABASES_PICKLE_READER_HASH_H diff --git a/include/databases/pickle-writer-hash.h b/include/databases/pickle-writer-hash.h index 560e5f5b..e0888a17 100644 --- a/include/databases/pickle-writer-hash.h +++ b/include/databases/pickle-writer-hash.h @@ -1,4 +1,4 @@ -/* pickle-writer-hash.h -- glue methods to let pickle write to hashes */ +/* databases/pickle-writer-hash.h -- glue methods to let pickle write to hashes */ #ifndef DATABASES_PICKLE_WRITER_HASH_H #define DATABASES_PICKLE_WRITER_HASH_H diff --git a/include/databases/pickle.h b/include/databases/pickle.h index eaa48b19..0026ca7a 100644 --- a/include/databases/pickle.h +++ b/include/databases/pickle.h @@ -1,4 +1,4 @@ -/* pickle.h -- (de-)serialising associative arrays */ +/* databases/pickle.h -- (de-)serialising associative arrays */ /** * \file pickle.h @@ -6,8 +6,8 @@ * 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) * @@ -240,13 +240,14 @@ 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. - * \param[in] reader Interfaces for reading from the associative array. + * \param[in] reader Interfaces for reading from the associative + * array. * \param[in] format Interfaces for formatting the retrieved values. * \param[in] opaque Opaque pointer passed into interfaces. * @@ -259,9 +260,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/databases/tag-db.h b/include/databases/tag-db.h index 3160812c..dcbc01d7 100644 --- a/include/databases/tag-db.h +++ b/include/databases/tag-db.h @@ -1,4 +1,4 @@ -/* tag-db.h -- tag database */ +/* databases/tag-db.h -- tag database */ /** * \file tag-db.h diff --git a/include/datastruct/atom.h b/include/datastruct/atom.h index 3d5b9e9f..e4a173b1 100644 --- a/include/datastruct/atom.h +++ b/include/datastruct/atom.h @@ -1,4 +1,4 @@ -/* atom.h -- indexed data block storage */ +/* datastruct/atom.h -- indexed data block storage */ /** * \file atom.h @@ -6,15 +6,16 @@ * 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 @@ -56,13 +57,15 @@ atom_set_t *atom_create(void); /** * Create a new atom set using the specified data sizes. * - * \param locpoolsz Size of a location pool, or zero for the default. Set this - * to the number of entries you typically expect to 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. */ diff --git a/include/datastruct/bitarr.h b/include/datastruct/bitarr.h index 56b082ff..783f3d4b 100644 --- a/include/datastruct/bitarr.h +++ b/include/datastruct/bitarr.h @@ -1,13 +1,13 @@ -/* bitarr.h -- arrays of bits */ +/* datastruct/bitarr.h -- arrays of bits */ /** * \file bitarr.h * * 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,7 +54,8 @@ 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) @@ -62,15 +63,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 { \ diff --git a/include/datastruct/bitfifo.h b/include/datastruct/bitfifo.h index f7ec8e50..c73182b5 100644 --- a/include/datastruct/bitfifo.h +++ b/include/datastruct/bitfifo.h @@ -1,4 +1,4 @@ -/* bitfifo.h -- fifo which stores bits */ +/* datastruct/bitfifo.h -- fifo which stores bits */ /** * \file bitfifo.h diff --git a/include/datastruct/bitvec.h b/include/datastruct/bitvec.h index 7fdbdf1e..8d5d641f 100644 --- a/include/datastruct/bitvec.h +++ b/include/datastruct/bitvec.h @@ -1,4 +1,4 @@ -/* bitvec.h -- flexible arrays of bits */ +/* datastruct/bitvec.h -- flexible arrays of bits */ /** * \file bitvec.h diff --git a/include/datastruct/cache.h b/include/datastruct/cache.h index 164a18e6..6bbd5481 100644 --- a/include/datastruct/cache.h +++ b/include/datastruct/cache.h @@ -1,4 +1,4 @@ -/* cache.h -- generic single-block cache */ +/* datastruct/cache.h -- generic single-block cache */ /** * \file cache.h @@ -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,8 +104,9 @@ 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 75067575..02794d2d 100644 --- a/include/datastruct/hash.h +++ b/include/datastruct/hash.h @@ -1,12 +1,12 @@ -/* hash.h -- associative arrays */ +/* datastruct/hash.h -- associative arrays */ /** * \file hash.h * * 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 diff --git a/include/datastruct/hlist.h b/include/datastruct/hlist.h index a8ffb6b6..5b73388c 100644 --- a/include/datastruct/hlist.h +++ b/include/datastruct/hlist.h @@ -1,4 +1,4 @@ -/* hlist.h -- "Hanson" linked list library */ +/* datastruct/hlist.h -- "Hanson" linked list library */ /* See chapter 5 of C Interfaces and Implementations. */ diff --git a/include/datastruct/list.h b/include/datastruct/list.h index ff8cdf84..ebdb986c 100644 --- a/include/datastruct/list.h +++ b/include/datastruct/list.h @@ -1,4 +1,4 @@ -/* list.h -- linked lists */ +/* datastruct/list.h -- linked lists */ /** * \file list.h diff --git a/include/datastruct/ntree.h b/include/datastruct/ntree.h index 9e7d54af..a2c160cf 100644 --- a/include/datastruct/ntree.h +++ b/include/datastruct/ntree.h @@ -1,4 +1,4 @@ -/* ntree.h -- n-ary trees */ +/* datastruct/ntree.h -- n-ary trees */ /** * \file ntree.h diff --git a/include/datastruct/vector.h b/include/datastruct/vector.h index 2ed1752a..4a4b9c66 100644 --- a/include/datastruct/vector.h +++ b/include/datastruct/vector.h @@ -1,12 +1,13 @@ -/* vector.h -- flexible arrays */ +/* datastruct/vector.h -- flexible arrays */ /** * \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). @@ -82,8 +83,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. @@ -106,8 +107,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. @@ -154,7 +155,8 @@ void vector_set(vector_t *vector, unsigned int index, const void *value); result_t vector_insert(vector_t *vector, const void *value); /** - * Insert many elements at the end of the vector, allocating memory if required. + * Insert many elements at the end of the vector, allocating memory if + * required. * * \param[in] vector Vector to change. * \param[in] values Array of values to insert. @@ -162,7 +164,9 @@ result_t vector_insert(vector_t *vector, const void *value); * * \return result_OK or result_OOM. */ -result_t vector_insert_many(vector_t *vector, const void *values, int nvalues); +result_t vector_insert_many(vector_t *vector, + const void *values, + int nvalues); /* ----------------------------------------------------------------------- */ diff --git a/include/framebuf/bitmap-set.h b/include/framebuf/bitmap-set.h index 809ea432..33631177 100644 --- a/include/framebuf/bitmap-set.h +++ b/include/framebuf/bitmap-set.h @@ -1,4 +1,4 @@ -/* bitmap-set.h -- a set of bitmap images */ +/* framebuf/bitmap-set.h -- a set of bitmap images */ #ifndef FRAMEBUF_BITMAP_SET_H #define FRAMEBUF_BITMAP_SET_H diff --git a/include/framebuf/bitmap.h b/include/framebuf/bitmap.h index 59305c41..a11361ec 100644 --- a/include/framebuf/bitmap.h +++ b/include/framebuf/bitmap.h @@ -1,4 +1,4 @@ -/* bitmap.h -- bitmap image type */ +/* framebuf/bitmap.h -- bitmap image type */ #ifndef FRAMEBUF_BITMAP_H #define FRAMEBUF_BITMAP_H @@ -47,6 +47,19 @@ result_t bitmap_init(bitmap_t *bm, const colour_t *palette, void *base); +/** + * Replace a bitmap's palette, reusing its existing palette buffer when one + * is already allocated and it is large enough. A no-op returning \ref + * result_OK for formats with no palette (log2bpp > 3). + * + * \param[in] bm Bitmap to repalette. + * \param[in] palette New palette, copied in, or NULL to drop the bitmap's + * palette entirely. + * \return \ref result_OK on success, \ref result_OOM if a palette buffer + * could not be allocated (the bitmap keeps its old palette). + */ +result_t bitmap_set_palette(bitmap_t *bm, const colour_t *palette); + /** * Clear the given bitmap to the specified colour. * diff --git a/include/framebuf/bmfont.h b/include/framebuf/bmfont.h index e46cd9f4..417cfa17 100644 --- a/include/framebuf/bmfont.h +++ b/include/framebuf/bmfont.h @@ -1,4 +1,4 @@ -/* bmfont.h -- proportional bitmap font engine */ +/* framebuf/bmfont.h -- proportional bitmap font engine */ #ifndef DPTLIB_BMFONT_H #define DPTLIB_BMFONT_H @@ -39,9 +39,9 @@ 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). + * 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. diff --git a/include/framebuf/colour.h b/include/framebuf/colour.h index 53ba312b..452f1be4 100644 --- a/include/framebuf/colour.h +++ b/include/framebuf/colour.h @@ -1,4 +1,4 @@ -/* colour.h -- colour type */ +/* framebuf/colour.h -- colour type */ #ifndef FRAMEBUF_COLOUR_H #define FRAMEBUF_COLOUR_H diff --git a/include/framebuf/composite.h b/include/framebuf/composite.h index cd577fc0..219878ef 100644 --- a/include/framebuf/composite.h +++ b/include/framebuf/composite.h @@ -1,4 +1,4 @@ -/* composite.h -- Porter-Duff image compositing */ +/* framebuf/composite.h -- Porter-Duff image compositing */ #ifndef COMPOSITE_H #define COMPOSITE_H diff --git a/include/framebuf/curve.h b/include/framebuf/curve.h index ecc5dc63..d8e9b15f 100644 --- a/include/framebuf/curve.h +++ b/include/framebuf/curve.h @@ -1,4 +1,4 @@ -/* curve.h -- bezier calculations */ +/* framebuf/curve.h -- bezier calculations */ #ifndef DPTLIB_CURVE_H #define DPTLIB_CURVE_H @@ -11,8 +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. @@ -116,8 +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. @@ -135,8 +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. @@ -156,7 +156,8 @@ point_t curve_bezier_point_on_quintic_r(point_t p0, fix16_t t); /** - * As for \ref curve_bezier_cubic but uses forward differencing (float version). + * As for \ref curve_bezier_cubic but uses forward differencing (float + * version). * * \param[in] p0 Start point. * \param[in] p1 Control point 1. diff --git a/include/framebuf/palettes.h b/include/framebuf/palettes.h index 3beabdc0..41500e0b 100644 --- a/include/framebuf/palettes.h +++ b/include/framebuf/palettes.h @@ -1,4 +1,4 @@ -/* palettes.h -- standard palettes */ +/* framebuf/palettes.h -- standard palettes */ #ifndef PALETTES_H #define PALETTES_H @@ -25,6 +25,26 @@ #define palette_PICO8_LIGHT_PEACH (15) #define palette_PICO8__LENGTH (16) +/* RISC OS desktop (Wimp) 16-colour palette, in native Wimp index order */ + +#define palette_WIMP16_WHITE (0) +#define palette_WIMP16_GREY_87 (1) +#define palette_WIMP16_GREY_75 (2) +#define palette_WIMP16_GREY_62 (3) +#define palette_WIMP16_GREY_50 (4) +#define palette_WIMP16_GREY_37 (5) +#define palette_WIMP16_GREY_25 (6) +#define palette_WIMP16_BLACK (7) +#define palette_WIMP16_DARK_BLUE (8) +#define palette_WIMP16_YELLOW (9) +#define palette_WIMP16_GREEN (10) +#define palette_WIMP16_RED (11) +#define palette_WIMP16_CREAM (12) +#define palette_WIMP16_DARK_GREEN (13) +#define palette_WIMP16_ORANGE (14) +#define palette_WIMP16_LIGHT_BLUE (15) +#define palette_WIMP16__LENGTH (16) + /** * Define the PICO-8 palette. * @@ -32,4 +52,12 @@ */ void define_pico8_palette(colour_t palette[palette_PICO8__LENGTH]); +/** + * Define the RISC OS 16-colour desktop (Wimp) palette in native Wimp index + * order, addressed by the `palette_WIMP16_*` names. + * + * \param[out] palette RISC OS 16-colour palette. + */ +void define_wimp16_palette(colour_t palette[palette_WIMP16__LENGTH]); + #endif /* PALETTES_H */ diff --git a/include/framebuf/pixelfmt.h b/include/framebuf/pixelfmt.h index b20b2882..c8b0f638 100644 --- a/include/framebuf/pixelfmt.h +++ b/include/framebuf/pixelfmt.h @@ -1,4 +1,4 @@ -/* pixelfmt.h -- pixel formats */ +/* framebuf/pixelfmt.h -- pixel formats */ #ifndef FRAMEBUF_PIXELFMT_H #define FRAMEBUF_PIXELFMT_H diff --git a/include/framebuf/screen.h b/include/framebuf/screen.h index 309f205b..455a786e 100644 --- a/include/framebuf/screen.h +++ b/include/framebuf/screen.h @@ -1,4 +1,4 @@ -/* screen.h -- screen type */ +/* framebuf/screen.h -- screen type */ #ifndef FRAMEBUF_SCREEN_H #define FRAMEBUF_SCREEN_H @@ -63,7 +63,7 @@ int screen_get_clip(const screen_t *scr, box_t *clip); * \param[in] y Y coordinate of pixel to draw. * \param[in] colour Colour of pixel. */ -void screen_draw_pixel(screen_t *scr, int x, int y, colour_t colour); +void screen_set_pixel(screen_t *scr, int x, int y, colour_t colour); /** * Draws a solid rectangle. @@ -74,14 +74,14 @@ void screen_draw_pixel(screen_t *scr, int x, int y, colour_t colour); * \param[in] size Width and height of rectangle. * \param[in] colour Colour of rectangle. */ -void screen_draw_rect(screen_t *scr, +void screen_fill_rect(screen_t *scr, int x, int y, size2d_t size, colour_t colour); /** - * Special case of `screen_draw_rect`. + * Special case of `screen_fill_rect`. * * \param[in] scr Screen to draw upon. * \param[in] x X coordinate of leftmost point of rectangle. @@ -89,14 +89,15 @@ void screen_draw_rect(screen_t *scr, * \param[in] size Size of rectangle. * \param[in] colour Colour of rectangle. */ -void screen_draw_square(screen_t *scr, - int x, int y, - int size, - colour_t colour); +void screen_fill_square(screen_t *scr, + int x, + int y, + int size, + colour_t colour); /** - * Built-in 8x8 fill patterns for `screen_fill_pattern`. Each is a 1-bit tile: - * set bits take the foreground colour, clear bits the background. + * Built-in 8x8 fill patterns for `screen_fill_pattern`. Each is a 1-bit + * tile: set bits take the foreground colour, clear bits the background. */ typedef enum screen_pattern { @@ -108,17 +109,31 @@ typedef enum screen_pattern screen_PATTERN_DOTS, /**< Sparse dots. */ screen_PATTERN_GRID, /**< Thin grid lines. */ screen_PATTERN_CROSSHATCH, /**< Crossed thin lines. */ - screen_PATTERN__LIMIT /**< Count of patterns; not itself a pattern. */ + + /** + * 8x8 ordered (Bayer) dither, one entry per coverage level 0 (empty) to 64 + * (solid). Index by level as `screen_PATTERN_BAYER0 + level`; + * `screen_PATTERN_BAYER_LIMIT` is one past the last. + * `screen_PATTERN_BAYER0` matches nothing else here, + * `screen_PATTERN_BAYER32` equals `screen_PATTERN_GREY50` and + * `screen_PATTERN_BAYER64` equals `screen_PATTERN_SOLID`; the duplicates + * are kept so the level arithmetic stays simple. + */ + screen_PATTERN_BAYER0, + screen_PATTERN_BAYER_LIMIT = screen_PATTERN_BAYER0 + 65, + + screen_PATTERN__LIMIT = screen_PATTERN_BAYER_LIMIT + /**< Count of patterns; not itself a pattern. */ } screen_pattern_t; /** * Fills a box with a repeating 8x8 two-colour pattern. * - * The tile is phased against (`origin_x`, `origin_y`): that coordinate is the - * one that would map to the box's top-left corner. Passing the caller's own - * scroll origin keeps the pattern locked to content as the box moves, rather - * than crawling with it. + * The tile is phased against (`origin_x`, `origin_y`): that coordinate is + * the one that would map to the box's top-left corner. Passing the caller's + * own scroll origin keeps the pattern locked to content as the box moves, + * rather than crawling with it. * * Clipped to the screen's clip region. * @@ -139,12 +154,13 @@ void screen_fill_pattern(screen_t *scr, colour_t bg); /** - * Draws a bitmap, alpha-blending it against the screen where the bitmap has an - * alpha channel. On paletted screens, which have no linear channel bits to - * 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. @@ -160,18 +176,18 @@ enum }; /** - * Draws a "9-patch": a resizable frame built from a source image that is a 3x3 - * grid of equal cells. The source width and height must each be a positive - * multiple of 3; the cell size is a third of each. Given a destination box, the - * four corner cells are drawn at their natural size in the destination corners, - * the four edge cells are tiled along the destination edges, and the centre - * cell is tiled across the interior. + * Draws a "9-patch": a resizable frame built from a source image that is a + * 3x3 grid of equal cells. The source width and height must each be a + * positive multiple of 3; the cell size is a third of each. Given a + * destination box, the four corner cells are drawn at their natural size in + * the destination corners, the four edge cells are tiled along the + * destination edges, and the centre cell is tiled across the interior. * - * If the destination is narrower or shorter than two cells the opposing corners - * overlap and each is clipped to its own half; the edges and centre are then - * omitted. Drawing is clipped to both the destination box and the screen's clip - * region, which is restored on return. Cells are blended exactly as - * `screen_draw_bitmap` does. No scaling is performed. + * If the destination is narrower or shorter than two cells the opposing + * corners overlap and each is clipped to its own half; the edges and centre + * are then omitted. Drawing is clipped to both the destination box and the + * screen's clip region, which is restored on return. Cells are blended + * exactly as `screen_draw_bitmap` does. No scaling is performed. * * \param[in] scr Screen to draw upon. * \param[in] dst Destination box to fill with the frame. @@ -192,18 +208,18 @@ void screen_draw_ninepatch(screen_t *scr, * 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. * * 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. @@ -212,8 +228,8 @@ void screen_draw_ninepatch(screen_t *scr, * 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). + * \return True if the copy was performed, false if declined (unsupported + * pixel format). */ int screen_copy_rect(screen_t *scr, const box_t *src, @@ -239,6 +255,69 @@ void screen_draw_line(screen_t *scr, int y1, colour_t colour); +/** + * Draws a connected polyline through `npoints` points: a `screen_draw_line` + * segment between each adjacent pair. For a closed shape repeat the first + * point as the last. Fewer than 2 points draws nothing. + * + * Segments share their joint pixel, which is plotted by both adjacent + * segments; harmless for a solid colour. + * + * Coordinates are `int`s and inclusive. + * + * \param[in] scr Screen to draw upon. + * \param[in] points Array of `npoints` points. + * \param[in] npoints Number of points in `points`. + * \param[in] colour Colour of the polyline. + */ +void screen_draw_lines(screen_t *scr, + const point_t *points, + int npoints, + colour_t colour); + +/** + * Draws a one-pixel unfilled rectangle outline. `size` is inclusive of both + * edges, matching `screen_fill_rect`. A degenerate size (<= 1 in either + * axis) falls back to a filled `screen_fill_rect`. + * + * \param[in] scr Screen to draw upon. + * \param[in] x X coordinate of leftmost point of rectangle. + * \param[in] y Y coordinate of topmost point of rectangle. + * \param[in] size Width and height of rectangle. + * \param[in] colour Colour of the outline. + */ +void screen_draw_rect(screen_t *scr, + int x, + int y, + size2d_t size, + colour_t colour); + +/** + * Draws a stippled line: `on` pixels drawn, then `off` skipped, repeating + * along the line. Bresenham stepping, so the dash period is measured in + * steps not Euclidean distance. `on` <= 0 draws nothing; `off` <= 0 gives a + * solid line. + * + * Coordinates are `int`s and inclusive. + * + * \param[in] scr Screen to draw upon. + * \param[in] x0 X coordinate of first point of line. + * \param[in] y0 Y coordinate of first point of line. + * \param[in] x1 X coordinate of second point of line. + * \param[in] y1 Y coordinate of second point of line. + * \param[in] on Length of each dash, in steps. + * \param[in] off Gap between dashes, in steps. + * \param[in] colour Colour of line. + */ +void screen_draw_dashed_line(screen_t *scr, + int x0, + int y0, + int x1, + int y1, + int on, + int off, + colour_t colour); + /** * Draws a line (fixed-point Wu version with anti-aliasing). * diff --git a/include/framebuf/span-bgrx8888.h b/include/framebuf/span-bgrx8888.h index e4bf23c1..f956fda9 100644 --- a/include/framebuf/span-bgrx8888.h +++ b/include/framebuf/span-bgrx8888.h @@ -1,4 +1,4 @@ -/* span-bgrx8888.h -- BGRX8888 format plot methods */ +/* framebuf/span-bgrx8888.h -- BGRX8888 format plot methods */ #ifndef SPAN_BGRX8888_H #define SPAN_BGRX8888_H diff --git a/include/framebuf/span-p4.h b/include/framebuf/span-p4.h index f49edf53..776f7f6f 100644 --- a/include/framebuf/span-p4.h +++ b/include/framebuf/span-p4.h @@ -1,4 +1,4 @@ -/* span-p4.h -- P4 (4bpp paletted) format plot methods */ +/* framebuf/span-p4.h -- P4 (4bpp paletted) format plot methods */ #ifndef SPAN_P4_H #define SPAN_P4_H diff --git a/include/framebuf/span-registry.h b/include/framebuf/span-registry.h index b1eb34ff..77685f14 100644 --- a/include/framebuf/span-registry.h +++ b/include/framebuf/span-registry.h @@ -1,4 +1,4 @@ -/* span-registry.h -- registry of plotting methods */ +/* framebuf/span-registry.h -- registry of plotting methods */ #ifndef SPAN_REGISTRY_H #define SPAN_REGISTRY_H diff --git a/include/framebuf/span-rgba8888.h b/include/framebuf/span-rgba8888.h index 4aec7fd6..bad2cc32 100644 --- a/include/framebuf/span-rgba8888.h +++ b/include/framebuf/span-rgba8888.h @@ -1,4 +1,4 @@ -/* span-rgba8888.h -- RGBA8888 format plot methods */ +/* framebuf/span-rgba8888.h -- RGBA8888 format plot methods */ #ifndef SPAN_RGBA8888_H #define SPAN_RGBA8888_H diff --git a/include/framebuf/span-rgbx8888.h b/include/framebuf/span-rgbx8888.h index e516b636..0c12e03e 100644 --- a/include/framebuf/span-rgbx8888.h +++ b/include/framebuf/span-rgbx8888.h @@ -1,4 +1,4 @@ -/* span-rgbx8888.h -- RGBX8888 format plot methods */ +/* framebuf/span-rgbx8888.h -- RGBX8888 format plot methods */ #ifndef SPAN_RGBX8888_H #define SPAN_RGBX8888_H diff --git a/include/framebuf/span-xbgr8888.h b/include/framebuf/span-xbgr8888.h index 67d29fe1..8640c97c 100644 --- a/include/framebuf/span-xbgr8888.h +++ b/include/framebuf/span-xbgr8888.h @@ -1,4 +1,4 @@ -/* span-xbgr8888.h -- XBGR8888 format plot methods */ +/* framebuf/span-xbgr8888.h -- XBGR8888 format plot methods */ #ifndef SPAN_XBGR8888_H #define SPAN_XBGR8888_H diff --git a/include/framebuf/span.h b/include/framebuf/span.h index 724ff622..bda935e8 100644 --- a/include/framebuf/span.h +++ b/include/framebuf/span.h @@ -1,4 +1,4 @@ -/* span.h -- interface of plotting methods */ +/* framebuf/span.h -- interface of plotting methods */ #ifndef SPAN_H #define SPAN_H @@ -17,17 +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, @@ -39,9 +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. @@ -58,8 +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 a2fd0736..da7751c6 100644 --- a/include/geom/box.h +++ b/include/geom/box.h @@ -1,4 +1,4 @@ -/* box.h -- box type */ +/* geom/box.h -- box type */ #ifndef GEOM_BOX_H #define GEOM_BOX_H @@ -30,8 +30,8 @@ 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 } @@ -49,8 +49,8 @@ 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. */ diff --git a/include/geom/layout.h b/include/geom/layout.h index 131c8768..ded5e37e 100644 --- a/include/geom/layout.h +++ b/include/geom/layout.h @@ -1,4 +1,4 @@ -/* layout.h -- laying out elements using the packer */ +/* geom/layout.h -- laying out elements using the packer */ #ifndef GEOM_LAYOUT_H #define GEOM_LAYOUT_H @@ -61,8 +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 c57849d8..a8cc7668 100644 --- a/include/geom/line.h +++ b/include/geom/line.h @@ -1,4 +1,4 @@ -/* line.h -- lines */ +/* geom/line.h -- lines */ #ifndef GEOM_LINE_H #define GEOM_LINE_H @@ -11,14 +11,15 @@ extern "C" #include "geom/box.h" /** - * Clips the line (x0,y0)-(x1,y1) by box `clip` and returns the clipped points - * in `x0` and co. + * Clips the line (x0,y0)-(x1,y1) by box `clip` and returns the clipped + * points in `x0` and co. * - * The returned points are rounded to the nearest integer position on the clip - * boundary, so they vary with the clip box given. Callers which step along the - * line incrementally (Bresenham, Wu, etc.) must not seed their error terms from - * them if the pixels drawn are to be independent of the clip box passed in; use - * the return value to reject and step from the original endpoints. + * 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 184f7d4f..cc7aaa33 100644 --- a/include/geom/packer.h +++ b/include/geom/packer.h @@ -1,4 +1,4 @@ -/* packer.h -- box packing for layout */ +/* geom/packer.h -- box packing for layout */ #ifndef GEOM_PACKER_H #define GEOM_PACKER_H @@ -79,10 +79,10 @@ result_t packer_place_at(T *packer, /** * Sets a gutter for packer_place_by: the width in pixels of a strip it - * additionally reserves along each placed box's two edges facing away from the - * search corner, so boxes placed by location never end up flush against each - * other. Default 0 (no gutter). Negative values are treated as 0. Does not - * affect packer_place_at. + * additionally reserves along each placed box's two edges facing away from + * the search corner, so boxes placed by location never end up flush against + * each other. Default 0 (no gutter). Negative values are treated as 0. Does + * not affect packer_place_at. * * \param[in] packer Packer to configure. * \param[in] gutter Gutter width in pixels. @@ -93,22 +93,24 @@ void packer_set_gutter(T *packer, int gutter); * Returns a previously placed area to the free pool: the inverse of * packer_place_at / packer_place_by. * - * Adjacent released areas are not coalesced, so a single placement that would - * span two separately-released areas will not fit until the gap between them is - * also freed; placing a box that fits wholly within one free area is - * unaffected. packer_get_consumed_area is not narrowed by a release. + * Free areas that share a full edge are coalesced after each release, so + * repeated place/release cycles reclaim the whole page rather than + * fragmenting it. A placement spanning two free areas that only partially + * overlap (staggered edges) still will not fit until the gap between them is + * freed too. packer_get_consumed_area is not narrowed by a release. * * \param[in] packer Packer to release into. - * \param[in] area Area to release. Clipped to the packer's margins. Copied. - * \return \ref result_OK, or \ref result_PACKER_EMPTY if 'area' lies entirely - * outside the margins. + * \param[in] area Area to release. Clipped to the packer's margins. + * Copied. + * \return \ref result_OK, or \ref result_PACKER_EMPTY if 'area' lies + * entirely outside the margins. */ result_t packer_release(T *packer, const box_t *area); /** - * Places a box of dimensions (w,h) in the next free area determined by location - * 'loc'. + * 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/point.h b/include/geom/point.h index a1360842..36ee4be9 100644 --- a/include/geom/point.h +++ b/include/geom/point.h @@ -1,4 +1,4 @@ -/* point.h -- point type */ +/* geom/point.h -- point type */ #ifndef GEOM_POINT_H #define GEOM_POINT_H diff --git a/include/geom/size.h b/include/geom/size.h index 5d66d4c0..0210cc4c 100644 --- a/include/geom/size.h +++ b/include/geom/size.h @@ -1,4 +1,4 @@ -/* size.h -- size type */ +/* geom/size.h -- size type */ #ifndef GEOM_SIZE_H #define GEOM_SIZE_H diff --git a/include/io/path.h b/include/io/path.h index 8a12df5c..0cb354f4 100644 --- a/include/io/path.h +++ b/include/io/path.h @@ -1,4 +1,4 @@ -/* path.h -- filename path handling */ +/* io/path.h -- filename path handling */ #ifndef DPTLIB_PATH #define DPTLIB_PATH diff --git a/include/io/stream-mem.h b/include/io/stream-mem.h index 599f973e..bf48c14a 100644 --- a/include/io/stream-mem.h +++ b/include/io/stream-mem.h @@ -1,4 +1,4 @@ -/* stream-mem.c -- memory block IO stream implementation */ +/* io/stream-mem.h -- memory block IO stream implementation */ #ifndef STREAM_MEM_H #define STREAM_MEM_H diff --git a/include/io/stream-mtfcomp.h b/include/io/stream-mtfcomp.h index ffe7f6de..7d17d036 100644 --- a/include/io/stream-mtfcomp.h +++ b/include/io/stream-mtfcomp.h @@ -1,4 +1,4 @@ -/* stream-mtfcomp.h -- "move to front" adaptive compression stream */ +/* io/stream-mtfcomp.h -- "move to front" adaptive compression stream */ #ifndef STREAM_MTFCOMP_H #define STREAM_MTFCOMP_H diff --git a/include/io/stream-packbits.h b/include/io/stream-packbits.h index 836066bb..10b25ca6 100644 --- a/include/io/stream-packbits.h +++ b/include/io/stream-packbits.h @@ -1,4 +1,4 @@ -/* stream-packbits.h -- PackBits compression */ +/* io/stream-packbits.h -- PackBits compression */ #ifndef STREAM_PACKBITS_H #define STREAM_PACKBITS_H @@ -20,7 +20,9 @@ extern "C" * stream. * \return \ref result_OK on success, or appropriate result code otherwise. */ -result_t stream_packbitscomp_create(stream_t *input, int bufsz, stream_t **s); +result_t stream_packbitscomp_create(stream_t *input, + int bufsz, + stream_t **s); /** * Create a PackBits decompression stream. @@ -31,7 +33,9 @@ result_t stream_packbitscomp_create(stream_t *input, int bufsz, stream_t **s); * stream. * \return \ref result_OK on success, or appropriate result code otherwise. */ -result_t stream_packbitsdecomp_create(stream_t *input, int bufsz, stream_t **s); +result_t stream_packbitsdecomp_create(stream_t *input, + int bufsz, + stream_t **s); #ifdef __cplusplus } diff --git a/include/io/stream-stdio.h b/include/io/stream-stdio.h index 71bcd4f9..a769c70a 100644 --- a/include/io/stream-stdio.h +++ b/include/io/stream-stdio.h @@ -1,4 +1,4 @@ -/* stream-stdio.c -- C standard IO stream implementation */ +/* io/stream-stdio.h -- C standard IO stream implementation */ #ifndef STREAM_STDIO_H #define STREAM_STDIO_H diff --git a/include/io/stream.h b/include/io/stream.h index 7e676fd4..ff1e353d 100644 --- a/include/io/stream.h +++ b/include/io/stream.h @@ -1,9 +1,10 @@ -/* stream.h -- stream system */ +/* io/stream.h -- stream system */ /** * \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 9986c3a7..aca2be79 100644 --- a/include/test/all-tests.h +++ b/include/test/all-tests.h @@ -1,4 +1,4 @@ -/* all-tests.h */ +/* test/all-tests.h */ #ifndef TESTS_ALL_TESTS_H #define TESTS_ALL_TESTS_H diff --git a/include/test/txtscr.h b/include/test/txtscr.h index 0dcf6191..40c6c6fd 100644 --- a/include/test/txtscr.h +++ b/include/test/txtscr.h @@ -1,4 +1,4 @@ -/* txtscr.h -- text format 'screen' */ +/* test/txtscr.h -- text format 'screen' */ #ifndef TEST_TXTSCR_H #define TEST_TXTSCR_H diff --git a/include/text/bmtext.h b/include/text/bmtext.h new file mode 100644 index 00000000..8bc6f4dd --- /dev/null +++ b/include/text/bmtext.h @@ -0,0 +1,90 @@ +/* text/bmtext.h -- word-wrap and draw a paragraph in a bitmap font */ + +/** + * \file bmtext.h + * + * Splits a string into lines that each fit a given pixel width when drawn in + * a \ref bmfont_t, then draws those lines stacked. + * + * Unlike \ref txtfmt (which wraps at character counts, for monospaced text) + * this measures each candidate line with \ref bmfont_measure, so it wraps + * proportional fonts correctly. + * + * - Breaks at the last space that still fits; hard-breaks a word with no + * space in it. - Runs of whitespace at a break are swallowed. - Layout is + * pure: it touches no screen or scroll state. + */ + +#ifndef DPTLIB_BMTEXT_H +#define DPTLIB_BMTEXT_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "base/result.h" +#include "framebuf/bmfont.h" +#include "framebuf/screen.h" +#include "geom/point.h" + +/** + * One laid-out line: a pointer into the caller's string plus a length. Not + * NUL-terminated. + */ +typedef struct bmtext_line +{ + const char *str; + int len; +} +bmtext_line_t; + +/** + * Break \p string into lines that each fit \p wrap_width pixels in \p font. + * + * The line pointers refer into \p string itself, so it must outlive \p + * lines. + * + * \param[in] font Bitmap font the lines will be drawn in. + * \param[in] string Text to wrap. + * \param[in] stringlen Length of \p string in bytes. + * \param[in] wrap_width Width to wrap to, in pixels. + * \param[out] lines Filled with up to \p max lines. + * \param[in] max Capacity of \p lines. Lines past this are dropped. + * + * \return Number of lines written to \p lines. + */ +int bmtext_layout(bmfont_t *font, + const char *string, + int stringlen, + int wrap_width, + bmtext_line_t *lines, + int max); + +/** + * Draw \p nlines pre-laid-out \p lines stacked downward from \p origin, + * advancing by the font height plus \p leading pixels per line. + * + * \param[in] font Bitmap font to draw in. + * \param[in] scr Screen to draw on. + * \param[in] lines Lines from \ref bmtext_layout. + * \param[in] nlines Number of lines. + * \param[in] fg Foreground colour. + * \param[in] bg Background colour, for glyph blending. + * \param[in] leading Extra pixels between lines. + * \param[in] origin Top-left of the first line, in pixels. + */ +void bmtext_draw(bmfont_t *font, + screen_t *scr, + const bmtext_line_t *lines, + int nlines, + colour_t fg, + colour_t bg, + int leading, + point_t origin); + +#ifdef __cplusplus +} +#endif + +#endif /* DPTLIB_BMTEXT_H */ diff --git a/include/text/txtfmt.h b/include/text/txtfmt.h index dc087c06..65867ce1 100644 --- a/include/text/txtfmt.h +++ b/include/text/txtfmt.h @@ -1,4 +1,4 @@ -/* txtfmt.h -- text formatting */ +/* text/txtfmt.h -- text formatting */ /** * \file txtfmt.h @@ -80,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. * @@ -92,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. diff --git a/include/utils/array.h b/include/utils/array.h index 26ae20df..228c33b4 100644 --- a/include/utils/array.h +++ b/include/utils/array.h @@ -1,4 +1,4 @@ -/* array.h -- array utilities */ +/* utils/array.h -- array utilities */ /** * \file array.h @@ -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/barith.h b/include/utils/barith.h index 053ccd77..68008063 100644 --- a/include/utils/barith.h +++ b/include/utils/barith.h @@ -1,4 +1,4 @@ -/* barith.h -- binary arithmetic */ +/* utils/barith.h -- binary arithmetic */ #ifndef UTILS_BARITH_H #define UTILS_BARITH_H diff --git a/include/utils/bsearch.h b/include/utils/bsearch.h index 7405f2e2..2fed3371 100644 --- a/include/utils/bsearch.h +++ b/include/utils/bsearch.h @@ -1,4 +1,4 @@ -/* bsearch.h -- binary searching arrays */ +/* utils/bsearch.h -- binary searching arrays */ #ifndef UTILS_BSEARCH_H #define UTILS_BSEARCH_H diff --git a/include/utils/bytesex.h b/include/utils/bytesex.h index 71508c01..d3af2d89 100644 --- a/include/utils/bytesex.h +++ b/include/utils/bytesex.h @@ -1,4 +1,4 @@ -/* bytesex.h -- reversing bytesex */ +/* utils/bytesex.h -- reversing bytesex */ #ifndef UTILS_BYTESEX_H #define UTILS_BYTESEX_H diff --git a/include/utils/fxp.h b/include/utils/fxp.h index 4043cb9b..d5bfe9a5 100644 --- a/include/utils/fxp.h +++ b/include/utils/fxp.h @@ -1,4 +1,4 @@ -/* fxp.h -- fixed point helpers */ +/* utils/fxp.h -- fixed point helpers */ #ifndef UTILS_FXP_H #define UTILS_FXP_H diff --git a/include/utils/maths.h b/include/utils/maths.h index 50df4a1f..aef58a0a 100644 --- a/include/utils/maths.h +++ b/include/utils/maths.h @@ -1,4 +1,4 @@ -/* maths.h -- maths utils */ +/* utils/maths.h -- maths utils */ #ifndef UTILS_MATHS_H #define UTILS_MATHS_H diff --git a/include/utils/pack.h b/include/utils/pack.h index 43bf89a2..dc41271e 100644 --- a/include/utils/pack.h +++ b/include/utils/pack.h @@ -1,13 +1,13 @@ -/* pack.h -- structure packing and unpacking helpers */ +/* utils/pack.h -- structure packing and unpacking helpers */ /** * \file Pack (interface). * * 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 @@ -42,8 +42,8 @@ extern "C" * 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: * @@ -74,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: * @@ -92,8 +92,8 @@ size_t pack(unsigned char *outbuf, const char *fmt, ...); * (Note that these specifiers are all different than the formatting * characters). * - * With these qualifers, sign becomes important. You can write CSIQ for unsigned - * arguments, or csiq for signed arguments. + * With these qualifers, sign becomes important. You can write CSIQ for + * unsigned arguments, or csiq for signed arguments. * * Example: * diff --git a/include/utils/primes.h b/include/utils/primes.h index 8948abf5..1937c849 100644 --- a/include/utils/primes.h +++ b/include/utils/primes.h @@ -1,4 +1,4 @@ -/* primes.h -- cache of prime numbers */ +/* utils/primes.h -- cache of prime numbers */ #ifndef UTILS_PRIMES_H #define UTILS_PRIMES_H diff --git a/include/wuss/icon.h b/include/wuss/icon.h index be9eaa18..39636e8b 100644 --- a/include/wuss/icon.h +++ b/include/wuss/icon.h @@ -1,11 +1,11 @@ -/* icon.h -- wuss work-area icons */ +/* wuss/icon.h -- wuss work-area icons */ /** * \file icon.h * - * Work-area icons: static labels and clickable bevelled buttons that Wuss draws - * inside a window's content area and hit-tests before the content task sees a - * click. + * Work-area icons: static labels and clickable bevelled buttons that Wuss + * draws inside a window's content area and hit-tests before the content task + * sees a click. * * An icon's bounding box is given in virtual document space -- the same * coordinate space as wuss_EVENT_MOUSE's point and wuss_window_invalidate's @@ -14,9 +14,9 @@ * content bounds and scroll offset. * * Wuss fills a window's background, draws its icons, then delivers - * wuss_EVENT_REDRAW, so a task is free to paint over or around icon pixels. A - * click on a wuss_ICON_TYPE_BUTTON reaches the task as wuss_EVENT_ICON; clicks - * on a label, or on a hidden or disabled icon, fall through as + * wuss_EVENT_REDRAW, so a task is free to paint over or around icon pixels. + * A click on a wuss_ICON_TYPE_BUTTON reaches the task as wuss_EVENT_ICON; + * clicks on a label, or on a hidden or disabled icon, fall through as * wuss_EVENT_MOUSE. */ @@ -45,63 +45,136 @@ extern "C" * in wuss.h so wuss_event_t can name it regardless of this option. */ /** - * What an icon looks like and how it behaves. The enum is left open so sprite - * and editable-text icons can be added later without breaking existing specs. + * What an icon looks like and how it behaves. The enum is left open so + * sprite and editable-text icons can be added later without breaking + * existing specs. */ typedef enum wuss_icon_type { - wuss_ICON_TYPE_LABEL = 0, /**< Static text drawn with the window manager's - * font. Not interactive: clicks fall through to - * the task as wuss_EVENT_MOUSE. */ - wuss_ICON_TYPE_BUTTON, /**< Bevelled rectangle with a centred text label - * and pressed-state visual feedback; clicks and - * hovers are delivered to the task as - * wuss_EVENT_ICON. */ - wuss_ICON_TYPE_PATTERN /**< Bounding box filled with a repeating two-colour - * 8x8 tile (spec.pattern) in fg/bg, phased to - * document space so it scrolls rigidly with - * content. Not interactive: clicks fall through - * to the task as wuss_EVENT_MOUSE. text is - * ignored. */ + /** Static text drawn with the window manager's font. Not interactive: clicks + * fall through to the task as wuss_EVENT_MOUSE. */ + wuss_ICON_TYPE_LABEL = 0, + /** Bevelled rectangle with a centred text label and pressed-state visual + * feedback; clicks and hovers are delivered to the task as + * wuss_EVENT_ICON. */ + wuss_ICON_TYPE_BUTTON, + /** Bounding box filled with a repeating two-colour 8x8 tile (spec.pattern) in + * fg/bg, phased to document space so it scrolls rigidly with content. Not + * interactive: clicks fall through to the task as wuss_EVENT_MOUSE. text is + * ignored. */ + wuss_ICON_TYPE_PATTERN, + /** A grouping box: a one-pixel rectangle in fg around the bounding box, + * broken at the top-left for an optional caption (text) drawn over the + * window background. Not interactive: clicks fall through to the task as + * wuss_EVENT_MOUSE. */ + wuss_ICON_TYPE_FRAME, + /** A radio button: a small ring at the left of the bounding box, filled when + * selected, with the label (text) to its right. Interactive: a click selects + * it and clears every other selected radio sharing its non-zero group, then + * the task is told via wuss_EVENT_ICON. */ + wuss_ICON_TYPE_RADIO, + /** An option button: a small box at the left of the bounding box, ticked when + * selected, with the label (text) to its right. Interactive: a click toggles + * its own selected state (group is ignored), then the task is told via + * wuss_EVENT_ICON. */ + wuss_ICON_TYPE_OPTION, + /** A caller-owned bitmap (spec.bitmap) drawn at the top-left of the bounding + * box, alpha-blended against what is already there, clipped to the box; no + * scaling. The bitmap is borrowed, not copied, and must outlive the icon + * (unlike text). fg, bg, text and pattern are ignored. Not interactive + * unless wuss_ICON_FLAGS_INTERACTIVE is set, in which case clicks raise + * wuss_EVENT_ICON like a button. */ + wuss_ICON_TYPE_BITMAP, + /** A menu row: text left-justified across the bounding box, drawn in fg over + * the window background, or inverted (window manager's title colours) while + * the pointer is over it. An optional tick at the left edge when the icon is + * selected (see wuss_icon_set_selected), and an optional submenu arrow at + * the right edge with wuss_ICON_FLAGS_SUBMENU. wuss_ICON_FLAGS_SEPARATOR + * marks the entry as following a group boundary: the dashed rule itself is a + * separate wuss_ICON_TYPE_RULE icon laid out above the entry, not drawn by + * the entry. The entry keeps its label and stays fully interactive. + * Interactive: a click raises wuss_EVENT_ICON like a button; a disabled + * entry never highlights and its clicks fall through. */ + wuss_ICON_TYPE_MENU_ENTRY, + /** A horizontal dashed rule centred in the bounding box, drawn in fg over the + * window background. Purely decorative: never hit-tested, never highlights. + * text, bg and pattern are ignored. Used between menu rows to render the + * line a wuss_ICON_FLAGS_SEPARATOR entry sits below. */ + wuss_ICON_TYPE_RULE } wuss_icon_type_t; /** Icon appearance and behaviour flags, combinable with bitwise OR. */ typedef enum wuss_icon_flags { - wuss_ICON_FLAGS_NONE = 0, - wuss_ICON_FLAGS_HIDDEN = 1 << 0, /**< Not drawn, not hit-tested. */ - wuss_ICON_FLAGS_DISABLED = 1 << 1 /**< Drawn greyed; clicks fall through to - * the task as wuss_EVENT_MOUSE rather - * than raising wuss_EVENT_ICON. */ + wuss_ICON_FLAGS_NONE = 0, + /** Not drawn, not hit-tested. */ + wuss_ICON_FLAGS_HIDDEN = 1 << 0, + /** Drawn greyed; clicks fall through to the task as wuss_EVENT_MOUSE rather + * than raising wuss_EVENT_ICON. */ + wuss_ICON_FLAGS_DISABLED = 1 << 1, + /** wuss_ICON_TYPE_LABEL: right-align the text in the bounding box instead of + * the default left. */ + wuss_ICON_FLAGS_JUSTIFY_RIGHT = 1 << 2, + /** wuss_ICON_TYPE_LABEL: centre the text in the bounding box. Takes + * precedence over wuss_ICON_FLAGS_JUSTIFY_RIGHT. */ + wuss_ICON_FLAGS_JUSTIFY_CENTRE = 1 << 3, + /** wuss_ICON_TYPE_BUTTON: draw as a default action button, in the window + * manager's accent colours (see wuss_config_t::accent) instead of the + * ordinary bevel. Ignored by other icon types. */ + wuss_ICON_FLAGS_DEFAULT = 1 << 4, + /** wuss_ICON_TYPE_BITMAP: hit-test the icon and raise wuss_EVENT_ICON on a + * click, like a button. Without it a bitmap icon is pure decoration and + * clicks fall through as wuss_EVENT_MOUSE. Ignored by other icon types + * (interactive or not by their nature). */ + wuss_ICON_FLAGS_INTERACTIVE = 1 << 5, + /** wuss_ICON_TYPE_MENU_ENTRY: draw a right-pointing arrow at the right edge, + * marking an entry that opens a submenu. Ignored by other types. */ + wuss_ICON_FLAGS_SUBMENU = 1 << 6, + /** wuss_ICON_TYPE_MENU_ENTRY: the entry follows a group boundary. It stays a + * normal interactive row with its own label; the dashed rule above it is + * laid out and drawn as a separate wuss_ICON_TYPE_RULE icon. Ignored by + * other types. */ + wuss_ICON_FLAGS_SEPARATOR = 1 << 7 } wuss_icon_flags_t; /** - * Description of an icon at creation. Copied by value into the icon; the caller - * keeps ownership of \c text, which is copied. + * Description of an icon at creation. Copied by value into the icon; the + * caller keeps ownership of \c text, which is copied. * - * A RISC OS-style validation string is deliberately omitted for now; a later \c - * validation field would stay source-compatible for callers that + * A RISC OS-style validation string is deliberately omitted for now; a later + * \c validation field would stay source-compatible for callers that * zero-initialise the spec. */ typedef struct wuss_icon_spec { - box_t bbox; /**< Bounding box, virtual document space, - * inclusive-exclusive. */ - wuss_icon_type_t type; /**< Icon type. */ - const char *text; /**< NUL-terminated label; copied. NULL means "". */ - wuss_colour_t fg; /**< Text colour, as an index into the system - * palette. */ - wuss_colour_t bg; /**< Fill/bevel base colour, as an index into the - * system palette. A label may pass - * wuss_NO_BACKGROUND for text with no fill; a - * button or pattern icon must pass a real - * index. */ - screen_pattern_t pattern; /**< Tile for wuss_ICON_TYPE_PATTERN; ignored by - * other types. Zero (screen_PATTERN_SOLID) is a - * safe default for zero-initialised specs. */ - wuss_icon_flags_t flags; /**< Appearance/behaviour flags. */ + /** Bounding box, virtual document space, inclusive-exclusive. */ + box_t bbox; + /** Icon type. */ + wuss_icon_type_t type; + /** NUL-terminated label; copied. NULL means "". */ + const char *text; + /** Text colour, as an index into the system palette. */ + wuss_colour_t fg; + /** Fill/bevel base colour, as an index into the system palette. A label, + * frame, radio or option icon may pass wuss_NO_BACKGROUND for no fill behind + * its text/glyph; a button or pattern icon must pass a real index. */ + wuss_colour_t bg; + /** Tile for wuss_ICON_TYPE_PATTERN; ignored by other types. Zero + * (screen_PATTERN_SOLID) is a safe default for zero-initialised specs. */ + screen_pattern_t pattern; + /** wuss_ICON_TYPE_BITMAP: the image to draw. Borrowed, not copied; must + * outlive the icon. Ignored by other types; NULL (the default) is only valid + * when type is not wuss_ICON_TYPE_BITMAP. */ + const bitmap_t *bitmap; + /** wuss_ICON_TYPE_RADIO: exclusive-selection group. Selecting a radio clears + * every other selected radio on the same window with the same group. Zero + * (the default) means "no group": such a radio still toggles but never + * clears another. Ignored by all other icon types. */ + int group; + /** Appearance/behaviour flags. */ + wuss_icon_flags_t flags; } wuss_icon_spec_t; @@ -109,36 +182,38 @@ wuss_icon_spec_t; /** * Create an icon on a window. The icon is owned by the window and freed when - * the window is closed (or the window manager destroyed). Its bounding box is - * invalidated so the next redraw paints it. + * the window is closed (or the window manager destroyed). Its bounding box + * is invalidated so the next redraw paints it. * * \param[in] window Window to attach the icon to. * \param[in] spec Icon description; copied. - * \param[out] icon Filled in with the new icon handle, or NULL if the caller - * does not need it. + * \param[out] icon Filled in with the new icon handle, or NULL if the + * caller does not need it. * \return \ref result_OK on success, \ref result_OOM on allocation failure, * \ref result_WUSS_BAD_COLOUR if fg or bg is out of range for the - * palette, or \ref result_WUSS_BAD_ICON if type is unknown or a button - * spec has no fill colour. + * palette, or \ref result_WUSS_BAD_ICON if type is unknown, a button + * or pattern spec has no fill colour, or a bitmap spec has no + * bitmap. */ result_t wuss_icon_create(wuss_window_t *window, const wuss_icon_spec_t *spec, wuss_icon_t **icon); /** - * Create several icons on a window in one call, as if by \ref wuss_icon_create - * for each. Either all \c nspecs icons are created, or none are: on the first - * failure any icons already created by this call are destroyed and no handles - * are written. + * Create several icons on a window in one call, as if by \ref + * wuss_icon_create for each. Either all \c nspecs icons are created, or none + * are: on the first failure any icons already created by this call are + * destroyed and no handles are written. * * \param[in] window Window to attach the icons to. * \param[in] specs Array of \c nspecs icon descriptions; each copied. * \param[in] nspecs Number of entries in \c specs. Zero is a no-op. - * \param[out] icons Array of \c nspecs handles, filled in on success, or NULL - * if the caller does not need them. Untouched on failure. - * \return \ref result_OK on success, or the first failing \ref wuss_icon_create - * code (\ref result_OOM, \ref result_WUSS_BAD_COLOUR, \ref - * result_WUSS_BAD_ICON). + * \param[out] icons Array of \c nspecs handles, filled in on success, or + * NULL if the caller does not need them. Untouched on + * failure. + * \return \ref result_OK on success, or the first failing \ref + * wuss_icon_create code (\ref result_OOM, \ref + * result_WUSS_BAD_COLOUR, \ref result_WUSS_BAD_ICON). */ result_t wuss_icon_create_array(wuss_window_t *window, const wuss_icon_spec_t *specs, @@ -146,27 +221,27 @@ result_t wuss_icon_create_array(wuss_window_t *window, wuss_icon_t **icons); /** - * Destroy an icon, unlinking it from its window and invalidating its bounding - * box so the next redraw clears it. Safe to pass NULL. + * Destroy an icon, unlinking it from its window and invalidating its + * bounding box so the next redraw clears it. Safe to pass NULL. * * \param[in] icon Icon to destroy, or NULL. */ void wuss_icon_delete(wuss_icon_t *icon); /** - * Replace an icon's label text. The new text is copied. Invalidates the icon's - * bounding box. + * Replace an icon's label text. The new text is copied. Invalidates the + * icon's bounding box. * * \param[in] icon Icon to change. * \param[in] text New NUL-terminated label; copied. NULL means "". - * \return \ref result_OK on success, \ref result_OOM on allocation failure (the - * icon keeps its old text). + * \return \ref result_OK on success, \ref result_OOM on allocation failure + * (the icon keeps its old text). */ result_t wuss_icon_set_text(wuss_icon_t *icon, const char *text); /** - * Show or hide an icon, toggling wuss_ICON_FLAGS_HIDDEN. Invalidates the icon's - * bounding box. + * Show or hide an icon, toggling wuss_ICON_FLAGS_HIDDEN. Invalidates the + * icon's bounding box. * * \param[in] icon Icon to change. * \param[in] hidden Non-zero to hide the icon, zero to show it. @@ -193,8 +268,8 @@ wuss_icon_type_t wuss_icon_get_type(const wuss_icon_t *icon); * Fetch an icon's current label text. * * \param[in] icon Icon to query. - * \return The label, never NULL (may be ""). Owned by the icon; valid until the - * next wuss_icon_set_text or wuss_icon_delete on it. + * \return The label, never NULL (may be ""). Owned by the icon; valid until + * the next wuss_icon_set_text or wuss_icon_delete on it. */ const char *wuss_icon_get_text(const wuss_icon_t *icon); @@ -206,6 +281,28 @@ const char *wuss_icon_get_text(const wuss_icon_t *icon); */ wuss_window_t *wuss_icon_get_window(const wuss_icon_t *icon); +/** + * Fetch a radio or option icon's selected (latched) state. + * + * \param[in] icon Icon to query. + * \return Non-zero if selected, zero otherwise. Always zero for icon types + * that have no latched state. + */ +int wuss_icon_get_selected(const wuss_icon_t *icon); + +/** + * Set a radio or option icon's selected state, invalidating it so the next + * redraw repaints it. For a radio with a non-zero group, selecting it + * (passing non-zero) also clears every other selected radio on the same + * window with that group. No task event is delivered -- this is the + * programmatic path, distinct from a user click. A no-op for icon types with + * no latched state. + * + * \param[in] icon Icon to change. + * \param[in] selected Non-zero to select, zero to deselect. + */ +void wuss_icon_set_selected(wuss_icon_t *icon, int selected); + #endif /* WUSS_ICONS */ #ifdef __cplusplus diff --git a/include/wuss/menu.h b/include/wuss/menu.h new file mode 100644 index 00000000..6619b380 --- /dev/null +++ b/include/wuss/menu.h @@ -0,0 +1,174 @@ +/* wuss/menu.h -- wuss pop-up menu helper */ + +/** + * \file menu.h + * + * A thin helper for RISC OS-style pop-up menus: a menu is a borderless + * window populated with wuss_ICON_TYPE_MENU_ENTRY icons, but wuss owns the + * plumbing -- layout, placement, submenu chaining on hover and whole-chain + * dismissal on a click outside or a leaf selection. + * + * Menus are described by caller-owned, immutable wuss_menu_t / + * wuss_menu_item_t structures (which may be static). The helper never + * mutates them; a task that wants a tick to change just edits its own array + * and reopens the menu. + * + * Built only when WUSS_MENUS is defined (which implies WUSS_ICONS). + */ + +#ifndef WUSS_MENU_H +#define WUSS_MENU_H + +#ifdef __cplusplus +extern "C" +{ +#endif + +#include "base/result.h" +#include "geom/point.h" + +#include "wuss/wuss.h" +#include "wuss/window.h" + +/* ----------------------------------------------------------------------- */ + +/** Per-item flags for a wuss_menu_item_t. OR'd together. */ +typedef enum wuss_menu_item_flags +{ + wuss_MENU_ITEM_NONE = 0, + wuss_MENU_ITEM_TICKED = 1 << 0, /**< draw a tick at the item's left edge */ + wuss_MENU_ITEM_DASHED = 1 << 1, /**< draw a dashed rule above this item, + * marking a group boundary. The item is + * otherwise an ordinary row: it keeps + * its label and responds to the pointer. + * The rule is laid out and drawn + * separately and is not interactive */ + wuss_MENU_ITEM_DISABLED = 1 << 2 /**< greyed, never highlights, not + * selectable */ +} +wuss_menu_item_flags_t; + +/** One row of a menu. */ +typedef struct wuss_menu_item +{ + const char *text; /**< row label; NULL is treated as "" */ + wuss_menu_item_flags_t flags; /**< see wuss_menu_item_flags_t */ + const struct wuss_menu *submenu; /**< non-NULL: draw a right arrow and open + * this menu to the right on hover */ + wuss_window_t *window; /**< non-NULL: draw a right arrow and, on + * hover, show this caller-owned window + * where a submenu would open, hiding it + * again when the pointer leaves the row + * or the chain is dismissed. Create it + * with wuss_WINDOW_HIDDEN. Mutually + * exclusive with \c submenu. The window + * must outlive the open chain -- do not + * wuss_window_close it while its menu is + * open. */ +} +wuss_menu_item_t; + +/** A menu: an array of items the caller owns. */ +typedef struct wuss_menu +{ + const char *title; /**< titlebar caption; NULL treated as "" */ + const wuss_menu_item_t *items; + int nitems; +} +wuss_menu_t; + +/** Opaque handle to an open menu chain. */ +typedef struct wuss__menu *wuss_menu_handle_t; + +/** + * Called when the pointer is released over a selectable leaf item. + * + * \param[in] menu The menu the item belongs to (a submenu, if nested). + * \param[in] index Index of the item within \c menu->items. + * \param[in] button wuss_button_t flags for the release; test with '&'. + * ADJUST keeps the chain open, SELECT closes it. + * \param[in] ctx As passed to wuss_menu_open. + */ +typedef void (wuss_menu_select_fn_t)(const wuss_menu_t *menu, + int index, + wuss_button_t button, + void *ctx); + +/* ----------------------------------------------------------------------- */ + +/** + * Open \p menu as a pop-up at \p at (screen space), nudged to stay on + * screen. Any menu chain already open is closed first. The chain lives until + * a leaf is SELECT-picked, a click lands outside every menu window, or + * wuss_menu_close is called. + * + * \param[in] wuss Window manager. + * \param[in] menu Menu to show; borrowed, must outlive the open chain. + * \param[in] at Where to put the menu's top-left, screen space. + * \param[in] on_select Leaf-selection callback, or NULL. + * \param[in] ctx Opaque pointer passed back to \p on_select. + * \param[out] out Filled with the chain handle, or NULL if not wanted. + * \return \ref result_OK, \ref result_OOM, or a wuss_window_create code. + */ +result_t wuss_menu_open(wuss_t *wuss, + const wuss_menu_t *menu, + point_t at, + wuss_menu_select_fn_t *on_select, + void *ctx, + wuss_menu_handle_t *out); + +/** Close a menu chain and every window in it. Safe to pass a stale or NULL + * handle. */ +void wuss_menu_close(wuss_menu_handle_t handle); + +/** Non-zero while \p handle refers to a currently open chain. */ +int wuss_menu_is_open(wuss_menu_handle_t handle); + +/* ----------------------------------------------------------------------- */ + +/** + * Build a wuss_menu_t tree from a compact descriptor string. The syntax is + * lifted from PrivateEye's menu_create_from_desc. A comma separates items. + * The very first token is the root menu's titlebar caption, not an item -- + * exactly as the first token inside a '{ }' is that submenu's title -- so + * the same descriptor strings port across unchanged. Submenus keep the Wimp + * behaviour of discarding their title token; only the root's is kept. A + * leading '|' on an item draws a dashed rule above it, marking a group + * boundary; the item itself stays an ordinary interactive row. A '{ ... }' + * group after an item is that item's submenu. A per-token prefix '!' ticks + * the item, '~' shades (disables) it, and '>' attaches a submenu pulled as a + * const wuss_menu_t * from the varargs rather than from a following + * '{ }' block. A "%s" in a token substitutes the next const char * + * vararg. The '>' and "%s" varargs are consumed in the order they are + * encountered scanning left to right. + * + * Example: wuss_menu_create_from_desc(&m, "Display, Open, !Grid, + * ~Export, |Quit") -- "Display" is the caption; the menu has four + * items, with a dashed rule above "Quit". + * + * The whole tree, including copied label text, is one heap allocation graph + * owned by the caller; free it with wuss_menu_destroy. wuss_menu_open treats + * a desc-built tree exactly like a static literal. + * + * \param[out] out Filled with the root menu on success, untouched on + * failure. + * \param[in] desc Descriptor string; its first token is the root caption. + * \return \ref result_OK, \ref result_OOM, or \ref result_BAD_ARG for a + * malformed descriptor (unbalanced braces, empty token, too deep). + */ +result_t wuss_menu_create_from_desc(wuss_menu_t **out, const char *desc, ...); + +/** + * Free a tree built by wuss_menu_create_from_desc, including every submenu + * and copied label. Safe to pass NULL. Never call this on a static or + * caller-assembled wuss_menu_t. + * + * \param[in] menu Root menu returned by wuss_menu_create_from_desc, or NULL. + */ +void wuss_menu_destroy(wuss_menu_t *menu); + +#ifdef __cplusplus +} +#endif + +#endif /* WUSS_MENU_H */ diff --git a/include/wuss/task.h b/include/wuss/task.h index 02b65a4a..b41e650b 100644 --- a/include/wuss/task.h +++ b/include/wuss/task.h @@ -1,10 +1,10 @@ -/* task.h -- wuss task API */ +/* wuss/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. + * A Wuss task: the content delegate a window hands its drawing and input + * events to, and the events themselves. */ #ifndef WUSS_TASK_H @@ -25,7 +25,9 @@ extern "C" /* ----------------------------------------------------------------------- */ -/** Which kind of event a wuss_event_t carries; more will be added over time. */ +/** + * 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. */ @@ -35,7 +37,9 @@ typedef enum wuss_event_kind wuss_EVENT_MOUSE, /**< Button down/up over the window's content. */ wuss_EVENT_ICON, /**< A work-area button icon was clicked or hovered. */ wuss_EVENT_SCROLL, /**< Mouse wheel used over the window's content. */ - wuss_EVENT_QUIT /**< Task shutting down, via wuss_task_stop. */ + wuss_EVENT_QUIT, /**< Task shutting down, via wuss_task_stop. */ + wuss_EVENT_PALETTE /**< System palette changed, via wuss_set_palette; + recache any wuss_nearest_colour selections. */ } wuss_event_kind_t; @@ -112,8 +116,8 @@ typedef struct wuss_event } scroll; - /* wuss_EVENT_IDLE, wuss_EVENT_CLOSE, wuss_EVENT_QUIT and - * wuss_EVENT_OPEN carry no data. */ + /* wuss_EVENT_IDLE, wuss_EVENT_CLOSE, wuss_EVENT_QUIT, + * wuss_EVENT_OPEN and wuss_EVENT_PALETTE carry no data. */ } data; } @@ -131,7 +135,9 @@ 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. */ +/** + * A window's content delegate. Copied by value into the window at creation. + */ typedef struct wuss_task { /** diff --git a/include/wuss/window.h b/include/wuss/window.h index efdc7464..cdcca47a 100644 --- a/include/wuss/window.h +++ b/include/wuss/window.h @@ -1,10 +1,10 @@ -/* window.h -- wuss window API */ +/* wuss/window.h -- wuss window API */ /** * \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 @@ -29,48 +29,56 @@ extern "C" /** * 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 + * \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] bg Content background, filled by Wuss before each redraw: + * a flat colour or an 8x8 fill pattern (see + * wuss_backdrop_t). Set its colour to wuss_NO_BACKGROUND + * for the task to draw its own background (avoids a + * redundant fill behind an opaque task). Any pattern is + * phased to the window's scroll origin so it stays + * locked to the content. Changeable later via + * wuss_window_set_background. * \param[in] task Content delegate. Copied in. May be NULL for a window * with no content handling. * \param[in] doc Virtual document extent, for the scrollbars' sausage * proportions; pass content's own width and height for a * window with nothing to scroll. Also the ceiling a - * resize-drag or toggle-size will grow the content area to. + * resize-drag or toggle-size will grow the content area + * to. * \param[in] min_doc Minimum content size a resize-drag or toggle-size will - * shrink to. Pass (0,0) for the built-in floor. Clamped to - * doc, and to the built-in floor, so it can never make a - * window unusably small or larger than its document. + * shrink to. Pass (0,0) for the built-in floor. Clamped + * to doc, and to the built-in floor, so it can never + * make a window unusably small or larger than its + * document. * \param[out] window Newly created window. Becomes the topmost window. * \return \ref result_OK on success, \ref result_WUSS_TOO_SMALL if content's - * width or height is not positive, \ref result_WUSS_BAD_COLOUR if bg is - * out of range for the palette, or another appropriate result code. + * width or height is not positive, \ref result_WUSS_BAD_COLOUR if + * any of bg's colours are out of range for the palette, or another + * appropriate result code. */ result_t wuss_window_create(wuss_t *wuss, const box_t *content, const char *title, wuss_window_flags_t flags, - wuss_colour_t bg, + wuss_backdrop_t bg, const wuss_task_t *task, size2d_t doc, size2d_t min_doc, @@ -79,21 +87,21 @@ result_t wuss_window_create(wuss_t *wuss, /** * Create a window, letting Wuss choose its position. * - * As wuss_window_create, but instead of a content box you pass just the content - * size; Wuss places the window (furniture included) in the first free screen - * region, packed towards the top-left, tracking occupied area across calls so - * successive auto-placed windows tile rather than stack. When no region is - * large enough the window is cascaded from the previous placement, stepping by - * a titlebar height and wrapping at the screen edge. + * As wuss_window_create, but instead of a content box you pass just the + * content size; Wuss places the window (furniture included) in the first + * free screen region, packed towards the top-left, tracking occupied area + * across calls so successive auto-placed windows tile rather than stack. + * When no region is large enough the window is cascaded from the previous + * placement, stepping by a titlebar height and wrapping at the screen edge. * - * The chosen slot is returned to the pool when the window is closed, or when it - * is first moved or resized via wuss_window_move / wuss_window_resize (after - * which Wuss no longer tracks its position). A window dragged by its titlebar - * counts as moved. + * The chosen slot is returned to the pool when the window is closed, or when + * it is first moved or resized via wuss_window_move / wuss_window_resize + * (after which Wuss no longer tracks its position). A window dragged by its + * titlebar counts as moved. * * \param[in] wuss Window manager to create the window on. - * \param[in] size Requested content-area size. Width and height must both - * be positive. + * \param[in] size Requested content-area size. Width and height must + * both be positive. * \param[in] title Titlebar label, as wuss_window_create. * \param[in] flags Appearance flags, as wuss_window_create. * \param[in] bg Content background, as wuss_window_create. @@ -101,15 +109,16 @@ result_t wuss_window_create(wuss_t *wuss, * \param[in] doc Virtual document extent, as wuss_window_create. * \param[in] min_doc Minimum content size, as wuss_window_create. * \param[out] window Newly created window. Becomes the topmost window. - * \return \ref result_OK on success, \ref result_WUSS_TOO_SMALL if size's width - * or height is not positive, \ref result_OOM if the layout tracker - * could not be created, or another result code from wuss_window_create. + * \return \ref result_OK on success, \ref result_WUSS_TOO_SMALL if size's + * width or height is not positive, \ref result_OOM if the layout + * tracker could not be created, or another result code from + * wuss_window_create. */ result_t wuss_window_create_placed(wuss_t *wuss, size2d_t size, const char *title, wuss_window_flags_t flags, - wuss_colour_t bg, + wuss_backdrop_t bg, const wuss_task_t *task, size2d_t doc, size2d_t min_doc, @@ -130,13 +139,28 @@ void wuss_window_close(wuss_window_t *doomed); */ void wuss_window_move(wuss_window_t *window, point_t p); +/** + * Show or hide a window without destroying it. + * + * A hidden window keeps its z-order slot and all its state, but is not drawn + * and not hit-tested: it neither occludes lower windows nor catches the + * pointer. wuss_window_move still works while hidden, so a caller can park a + * window off to one side and re-show it in position later. Toggling + * visibility invalidates the window's footprint so the next redraw picks up + * the change. + * + * \param[in] window Window to show or hide. + * \param[in] hidden Non-zero to hide, zero to show. + */ +void wuss_window_set_hidden(wuss_window_t *window, int hidden); + /** * Resize a window's content area, preserving its top-left position. * * \param[in] window Window to resize. * \param[in] size New content size. - * \return \ref result_OK on success, \ref result_WUSS_TOO_SMALL if size's width - * or height is not positive. + * \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, size2d_t size); @@ -159,8 +183,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. * @@ -171,14 +195,15 @@ 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); @@ -188,11 +213,11 @@ void wuss_window_invalidate(wuss_window_t *window, const box_t *local_box); 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. @@ -208,17 +233,19 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p); void wuss_window_get_scroll(const wuss_window_t *window, point_t *p); /** - * Change a window's background colour, invalidating its content area so the - * next redraw picks up the new fill. + * Change a window's background, invalidating its content area so the next + * redraw picks up the new fill. * * \param[in] window Window to change. - * \param[in] bg New content background, as an index into the system - * palette, or wuss_NO_BACKGROUND to hand background painting - * back to the task. - * \return \ref result_OK on success, \ref result_WUSS_BAD_COLOUR if bg is out - * of range for the palette. + * \param[in] bg New content background: a flat colour or an 8x8 fill + * pattern (see wuss_backdrop_t). Set its colour to + * wuss_NO_BACKGROUND to hand background painting back to + * the task. + * \return \ref result_OK on success, \ref result_WUSS_BAD_COLOUR if any of + * bg's colours are out of range for the palette. */ -result_t wuss_window_set_background(wuss_window_t *window, wuss_colour_t bg); +result_t wuss_window_set_background(wuss_window_t *window, + wuss_backdrop_t bg); #ifdef __cplusplus } diff --git a/include/wuss/wuss.h b/include/wuss/wuss.h index de8d74bc..83d2758a 100644 --- a/include/wuss/wuss.h +++ b/include/wuss/wuss.h @@ -1,4 +1,4 @@ -/* wuss.h -- minimal window manager */ +/* wuss/wuss.h -- minimal window manager */ /** * \file wuss.h @@ -16,8 +16,11 @@ extern "C" { #endif +#include + #include "base/result.h" #include "framebuf/bmfont.h" +#include "framebuf/screen.h" #include "geom/box.h" #include "geom/point.h" #include "geom/size.h" @@ -29,7 +32,8 @@ extern "C" /** A palette index was out of range. */ #define result_WUSS_BAD_COLOUR (result_BASE_WUSS + 1) /** - * An icon spec was malformed (unknown type, or BUTTON without a fill colour). + * An icon spec was malformed (unknown type, or BUTTON without a fill + * colour). */ #define result_WUSS_BAD_ICON (result_BASE_WUSS + 2) @@ -45,14 +49,33 @@ typedef struct wuss_window wuss_window_t; * library is built with the WUSS_ICONS option on. */ typedef struct wuss_icon wuss_icon_t; +/** + * Allocator hooks used by a wuss_t for every heap block it owns (the + * instance itself, windows, icons, menu nodes). The three members must + * behave like the C library malloc / realloc / free -- same argument and + * return conventions, realloc(NULL, n) == malloc(n), free(NULL) a no-op. + * Passed to wuss_create and copied in; NULL there selects wuss_alloc (plain + * stdlib). + */ +typedef struct wuss_alloc +{ + void *(*malloc)(size_t size); + void *(*realloc)(void *ptr, size_t size); + void (*free)(void *ptr); +} +wuss_alloc_t; + +/** The default allocator: the C library malloc / realloc / free. */ +extern const wuss_alloc_t wuss_alloc; + /** * Mouse buttons, RISC OS-style: Select is the primary action, Adjust the * secondary action, Menu pops up a menu. * * These are flags, OR'd together, so that chords (e.g. Select and Adjust * pressed together) can be reported. The bit values match the RISC OS button - * order. Test them with '&' rather than comparing for equality, or a chord will - * match no button at all. + * order. Test them with '&' rather than comparing for equality, or a chord + * will match no button at all. */ typedef enum wuss_button { @@ -75,10 +98,11 @@ wuss_mouse_action_t; /** * An index into a wuss_t's system palette (see wuss_create). Not a colour_t. */ -typedef int wuss_colour_t; +typedef unsigned char wuss_colour_t; /** - * Sentinel for wuss_window_create's 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) @@ -87,7 +111,7 @@ typedef int wuss_colour_t; * flat colour. Ignored when the library is built with WUSS_FURNITURE off. * Each value is an index into the system palette (see * wuss_create). */ -typedef struct wuss_palette +typedef struct wuss_furniture_palette { struct { @@ -107,7 +131,7 @@ typedef struct wuss_palette } scroll; } -wuss_palette_t; +wuss_furniture_palette_t; /** * Per-window appearance flags, combinable with bitwise OR. @@ -160,10 +184,21 @@ typedef enum wuss_window_flags /** * 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). + * 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_NO_RESIZE_BLIT = 1 << 8, + + /** + * Created hidden, or hidden later via wuss_window_set_hidden: the window + * keeps its place in the z-order but is not drawn and not hit-tested, so + * it neither occludes other windows nor catches the pointer. Its position + * can still be changed with wuss_window_move while hidden, ready for when + * it is shown again. Unlike the wuss_WINDOW_NO_* bits this one is toggled + * at runtime, and it is honoured regardless of the WUSS_FURNITURE build + * option. + */ + wuss_WINDOW_HIDDEN = 1 << 9 } wuss_window_flags_t; @@ -176,29 +211,69 @@ typedef enum wuss_zorder } wuss_zorder_t; +/** + * Desktop background specification: a flat colour, or an 8x8 fill pattern. + * Used by wuss_config_t. Always honoured regardless of the WUSS_FURNITURE / + * WUSS_ICONS options. + */ +typedef struct wuss_backdrop +{ + /** + * Fill colour, or wuss_NO_BACKGROUND to leave the background untouched + * (the caller must then repaint it itself before wuss_redraw / + * wuss_redraw_dirty). When pattern is not screen_PATTERN_SOLID this is the + * pattern's foreground (set-bit) colour. + */ + wuss_colour_t colour; + + /** + * Fill pattern. screen_PATTERN_SOLID (the default) fills flat in colour; + * any other value tiles that pattern in colour over pattern_bg, phased to + * a fixed screen origin so it stays put across dirty-region redraws. + * Ignored when colour is wuss_NO_BACKGROUND. + */ + screen_pattern_t pattern; + + /** Pattern background (clear-bit) colour; used only when pattern is not + * screen_PATTERN_SOLID. */ + wuss_colour_t pattern_bg; +} +wuss_backdrop_t; + +/** A flat-colour wuss_backdrop_t (or wuss_NO_BACKGROUND for none), as a + * compound literal -- the common case where no fill pattern is wanted. */ +#define wuss_BACKDROP_COLOUR(c) \ + ((wuss_backdrop_t) { (c), screen_PATTERN_SOLID, wuss_NO_BACKGROUND }) + +/** A patterned wuss_backdrop_t: 8x8 pattern p tiled in colour c over + * background colour b. */ +#define wuss_BACKDROP_PATTERN(c, p, b) ((wuss_backdrop_t) { (c), (p), (b) }) + /** * Optional creation-time configuration. * - * \note titlebar_height and palette are ignored when the library is built with - * WUSS_FURNITURE off; bevel is ignored when built with both - * WUSS_FURNITURE and WUSS_ICONS off. backdrop is always honoured. + * \note titlebar_height and palette are ignored when the library is built + * with WUSS_FURNITURE off; bevel and accent are ignored when built + * with both WUSS_FURNITURE and WUSS_ICONS off. backdrop is always + * honoured. See the backdrop sub-struct for its own notes. */ typedef struct wuss_config { /** - * Titlebar height in pixels, or 0 to derive from font metrics (or a built-in - * fallback if no font). Ignored when WUSS_FURNITURE is off. + * Titlebar height in pixels, or 0 to derive from font metrics (or a + * built-in fallback if no font). Ignored when WUSS_FURNITURE is off. */ int titlebar_height; /** Furniture chrome colours. Ignored when WUSS_FURNITURE is off. */ - wuss_palette_t palette; + wuss_furniture_palette_t furniture; /** - * Bevelled work-area button edge shades, as indices into the system palette: - * light on the top/left edges, dark on the bottom/right (swapped when the - * button is pressed). Both default to the titlebar fill colour when config is - * NULL. Ignored when both WUSS_FURNITURE and WUSS_ICONS are off. + * Bevelled work-area button edge shades, as indices into the system + * palette: light on the top/left edges, dark on the bottom/right (swapped + * when the button is pressed). Both default to the titlebar fill colour + * when config is NULL. Ignored when both WUSS_FURNITURE and WUSS_ICONS are + * off. */ struct { @@ -208,19 +283,29 @@ typedef struct wuss_config bevel; /** - * 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). + * Fill and text colours for a default action button -- a work-area button + * icon created with wuss_ICON_FLAGS_DEFAULT, drawn to stand out from the + * ordinary bevelled buttons around it (RISC OS's "default action button"). + * Both default to the titlebar colours (bg / fg) when config is NULL. + * Ignored when both WUSS_FURNITURE and WUSS_ICONS are off. */ - wuss_colour_t backdrop; + struct + { + wuss_colour_t bg; /**< Default-button fill. */ + wuss_colour_t fg; /**< Default-button text. */ + } + accent; + + /** Desktop background, painted behind windows on every redraw. */ + wuss_backdrop_t backdrop; } wuss_config_t; /** * Create a window manager. * - * \param[in] scr Screen to draw windows onto. Not owned; must outlive the - * wuss_t. + * \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 @@ -228,18 +313,47 @@ wuss_config_t; * \param[in] npalette Number of entries in palette. Ignored if palette is * NULL. * \param[in] config Creation-time configuration, or NULL for defaults. + * \param[in] alloc Allocator hooks, copied in, or NULL for \ref + * wuss_alloc (plain stdlib). Must outlive nothing -- + * only the three function pointers are kept. * \param[out] wuss Newly created window manager. * \return \ref result_OK on success, \ref result_WUSS_BAD_COLOUR if any of - * config's palette entries are out of range for the palette, or another - * appropriate result code. + * config's palette entries are out of range for the palette, or + * another appropriate result code. */ result_t wuss_create(screen_t *scr, bmfont_t *font, const colour_t *palette, int npalette, const wuss_config_t *config, + const wuss_alloc_t *alloc, wuss_t **wuss); +/** + * Replace the system palette partway through a session. + * + * Copies \p palette in over the existing one (same semantics as + * wuss_create's palette argument), refreshes the cached nearest-black / + * nearest-white indices, broadcasts a \ref wuss_EVENT_PALETTE event to every + * window's task so they can recache any wuss_nearest_colour selections, then + * invalidates the whole screen. The caller is still responsible for the next + * wuss_redraw / wuss_redraw_dirty, and -- on a paletted screen -- for + * updating the screen bitmap's own palette to match. + * + * \param[in] wuss Window manager. + * \param[in] palette New system palette, copied in. + * \param[in] npalette Number of entries in palette. Must equal the count + * passed to wuss_create. + * \return \ref result_OK on success, \ref result_BAD_ARG if npalette does + * not match the current palette length, \ref result_WUSS_BAD_COLOUR + * if a configured furniture/bevel/backdrop colour index is now out + * of range (in which case the palette is left unchanged), else the + * first non-OK result returned by a task's handle callback. + */ +result_t wuss_set_palette(wuss_t *wuss, + const colour_t *palette, + int npalette); + /** * Destroy a window manager, and any windows still open on it. * @@ -248,14 +362,38 @@ result_t wuss_create(screen_t *scr, void wuss_destroy(wuss_t *doomed); /** - * Fetch the system font (see wuss_create), for tasks to draw their own content - * in the same face as window titlebars. + * 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 System font, or NULL if none was given to wuss_create. */ bmfont_t *wuss_get_font(const wuss_t *wuss); +/** + * The last pointer position seen by wuss_mouse_click or wuss_mouse_move, + * screen space. (0,0) until the first mouse event. Handy for opening a + * pop-up menu under the pointer from a task's icon handler, which gets no + * coordinate of its own. + * + * \param[in] wuss Window manager. + * \return Last pointer position, screen space. + */ +point_t wuss_get_pointer(const wuss_t *wuss); + +/** + * Find the system palette entry (see wuss_create) closest to an RGB value, + * by squared Euclidean distance in RGB space. Alpha is ignored. Ties keep + * the lower index. + * + * \param[in] wuss Window manager. + * \param[in] r Red component, 0..255. + * \param[in] g Green component, 0..255. + * \param[in] b Blue component, 0..255. + * \return Palette index, 0..npalette-1. + */ +wuss_colour_t wuss_nearest_colour(const wuss_t *wuss, int r, int g, int b); + /** * Redraw every window, back-to-front, having first painted the configured * backdrop colour (see wuss_config_t::backdrop) behind them, if any. @@ -268,10 +406,11 @@ bmfont_t *wuss_get_font(const wuss_t *wuss); 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. @@ -282,8 +421,8 @@ 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. - * Each dirty region has the configured backdrop colour (see + * 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. @@ -295,9 +434,9 @@ 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. @@ -305,28 +444,28 @@ 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). 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. + * 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. @@ -344,10 +483,10 @@ 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. @@ -360,15 +499,15 @@ 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. + * \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. */ @@ -379,13 +518,13 @@ result_t wuss_scroll(wuss_t *wuss, /** * 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. + * 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. + * \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); diff --git a/libraries/databases/digest-db/digest-db.c b/libraries/databases/digest-db/digest-db.c index c09e0e9e..ed22e84b 100644 --- a/libraries/databases/digest-db/digest-db.c +++ b/libraries/databases/digest-db/digest-db.c @@ -1,4 +1,4 @@ -/* digest-db.c -- digest database */ +/* databases/digest-db/digest-db.c -- digest database */ #include #include diff --git a/libraries/databases/filename-db/filename-db.c b/libraries/databases/filename-db/filename-db.c index abe5202c..9ba41f73 100644 --- a/libraries/databases/filename-db/filename-db.c +++ b/libraries/databases/filename-db/filename-db.c @@ -1,4 +1,4 @@ -/* filename-db.c -- filename database */ +/* databases/filename-db/filename-db.c -- filename database */ /* filenamedb maps md5 digests to filenames so that we can search for files * and retrieve filenames */ diff --git a/libraries/databases/pickle/delete.c b/libraries/databases/pickle/delete.c index a517f4ef..e4553874 100644 --- a/libraries/databases/pickle/delete.c +++ b/libraries/databases/pickle/delete.c @@ -1,4 +1,4 @@ -/* delete.c -- delete a pickle file */ +/* databases/pickle/delete.c -- delete a pickle file */ #include #include diff --git a/libraries/databases/pickle/hash-reader.c b/libraries/databases/pickle/hash-reader.c index d878c83c..e1ee74b5 100644 --- a/libraries/databases/pickle/hash-reader.c +++ b/libraries/databases/pickle/hash-reader.c @@ -1,4 +1,4 @@ -/* hash-reader.c -- glue methods to let pickle operate on hashes */ +/* databases/pickle/hash-reader.c -- glue methods to let pickle operate on hashes */ #include #include diff --git a/libraries/databases/pickle/hash-writer.c b/libraries/databases/pickle/hash-writer.c index 2949dcfb..3b892f71 100644 --- a/libraries/databases/pickle/hash-writer.c +++ b/libraries/databases/pickle/hash-writer.c @@ -1,4 +1,4 @@ -/* hash-writer.c -- glue methods to let pickle operate on hashes */ +/* databases/pickle/hash-writer.c -- glue methods to let pickle operate on hashes */ #include #include diff --git a/libraries/databases/pickle/pickle.c b/libraries/databases/pickle/pickle.c index 55bcf06b..eadb172f 100644 --- a/libraries/databases/pickle/pickle.c +++ b/libraries/databases/pickle/pickle.c @@ -1,4 +1,4 @@ -/* pickle.c -- serialise an associative array to file */ +/* databases/pickle/pickle.c -- serialise an associative array to file */ #include #include diff --git a/libraries/databases/pickle/test/pickle-test.c b/libraries/databases/pickle/test/pickle-test.c index 59ab3981..8b38f707 100644 --- a/libraries/databases/pickle/test/pickle-test.c +++ b/libraries/databases/pickle/test/pickle-test.c @@ -1,3 +1,4 @@ +/* databases/pickle/test/pickle-test.c */ #include #include diff --git a/libraries/databases/pickle/unpickle.c b/libraries/databases/pickle/unpickle.c index fce030be..a0c2be20 100644 --- a/libraries/databases/pickle/unpickle.c +++ b/libraries/databases/pickle/unpickle.c @@ -1,4 +1,4 @@ -/* unpickle.c -- deserialise an associative array from file */ +/* databases/pickle/unpickle.c -- deserialise an associative array from file */ #include #include diff --git a/libraries/databases/tag-db/tag-db.c b/libraries/databases/tag-db/tag-db.c index 5397b361..b6ce93ab 100644 --- a/libraries/databases/tag-db/tag-db.c +++ b/libraries/databases/tag-db/tag-db.c @@ -1,4 +1,4 @@ -/* tag-db.c -- tag database */ +/* databases/tag-db/tag-db.c -- tag database */ // TODO // cope with tags with spaces (quoted for saving and loading) diff --git a/libraries/databases/tag-db/test/tag-db-test.c b/libraries/databases/tag-db/test/tag-db-test.c index 076b1191..8e7a777f 100644 --- a/libraries/databases/tag-db/test/tag-db-test.c +++ b/libraries/databases/tag-db/test/tag-db-test.c @@ -1,3 +1,4 @@ +/* databases/tag-db/test/tag-db-test.c */ #include #include diff --git a/libraries/datastruct/atom/create.c b/libraries/datastruct/atom/create.c index 90d51b6b..1a61b68c 100644 --- a/libraries/datastruct/atom/create.c +++ b/libraries/datastruct/atom/create.c @@ -1,4 +1,4 @@ -/* create.c -- atoms */ +/* datastruct/atom/create.c -- atoms */ #include diff --git a/libraries/datastruct/atom/delete-block.c b/libraries/datastruct/atom/delete-block.c index 1a9ca722..6ededecc 100644 --- a/libraries/datastruct/atom/delete-block.c +++ b/libraries/datastruct/atom/delete-block.c @@ -1,4 +1,4 @@ -/* delete-block.c -- atoms */ +/* datastruct/atom/delete-block.c -- atoms */ #include diff --git a/libraries/datastruct/atom/delete.c b/libraries/datastruct/atom/delete.c index a7be93f9..a6735de5 100644 --- a/libraries/datastruct/atom/delete.c +++ b/libraries/datastruct/atom/delete.c @@ -1,4 +1,4 @@ -/* delete.c -- atoms */ +/* datastruct/atom/delete.c -- atoms */ #include #include diff --git a/libraries/datastruct/atom/destroy.c b/libraries/datastruct/atom/destroy.c index 3dcf6470..493587c4 100644 --- a/libraries/datastruct/atom/destroy.c +++ b/libraries/datastruct/atom/destroy.c @@ -1,4 +1,4 @@ -/* destroy.c -- atoms */ +/* datastruct/atom/destroy.c -- atoms */ #include diff --git a/libraries/datastruct/atom/for-block.c b/libraries/datastruct/atom/for-block.c index c67db912..c8f27f42 100644 --- a/libraries/datastruct/atom/for-block.c +++ b/libraries/datastruct/atom/for-block.c @@ -1,4 +1,4 @@ -/* for-block.c -- atoms */ +/* datastruct/atom/for-block.c -- atoms */ #include #include diff --git a/libraries/datastruct/atom/get.c b/libraries/datastruct/atom/get.c index ef63a7f0..6b423075 100644 --- a/libraries/datastruct/atom/get.c +++ b/libraries/datastruct/atom/get.c @@ -1,4 +1,4 @@ -/* get.c -- atoms */ +/* datastruct/atom/get.c -- atoms */ #include #include diff --git a/libraries/datastruct/atom/impl.h b/libraries/datastruct/atom/impl.h index 35c7e13c..12373308 100644 --- a/libraries/datastruct/atom/impl.h +++ b/libraries/datastruct/atom/impl.h @@ -1,4 +1,4 @@ -/* impl.h -- atoms */ +/* datastruct/atom/impl.h -- atoms */ /* An atom_set consists of a series of block pools and location pools. * diff --git a/libraries/datastruct/atom/new.c b/libraries/datastruct/atom/new.c index 58663d69..026ebebe 100644 --- a/libraries/datastruct/atom/new.c +++ b/libraries/datastruct/atom/new.c @@ -1,4 +1,4 @@ -/* new.c -- atoms */ +/* datastruct/atom/new.c -- atoms */ #include #include diff --git a/libraries/datastruct/atom/set.c b/libraries/datastruct/atom/set.c index 0b702451..68d1a8d9 100644 --- a/libraries/datastruct/atom/set.c +++ b/libraries/datastruct/atom/set.c @@ -1,4 +1,4 @@ -/* set.c -- atoms */ +/* datastruct/atom/set.c -- atoms */ #include #include diff --git a/libraries/datastruct/atom/test/atom-test.c b/libraries/datastruct/atom/test/atom-test.c index 0fbabed5..78743dea 100644 --- a/libraries/datastruct/atom/test/atom-test.c +++ b/libraries/datastruct/atom/test/atom-test.c @@ -1,3 +1,4 @@ +/* datastruct/atom/test/atom-test.c */ #include #include diff --git a/libraries/datastruct/bitarr/count.c b/libraries/datastruct/bitarr/count.c index 475562c9..b3a89652 100644 --- a/libraries/datastruct/bitarr/count.c +++ b/libraries/datastruct/bitarr/count.c @@ -1,4 +1,4 @@ -/* count.c -- arrays of bits */ +/* datastruct/bitarr/count.c -- arrays of bits */ #include diff --git a/libraries/datastruct/bitarr/test/bitarr-test.c b/libraries/datastruct/bitarr/test/bitarr-test.c index 91a566bb..e8403c05 100644 --- a/libraries/datastruct/bitarr/test/bitarr-test.c +++ b/libraries/datastruct/bitarr/test/bitarr-test.c @@ -1,3 +1,4 @@ +/* datastruct/bitarr/test/bitarr-test.c */ #include #include diff --git a/libraries/datastruct/bitfifo/bitfifo.c b/libraries/datastruct/bitfifo/bitfifo.c index cd0e6b13..ba215515 100644 --- a/libraries/datastruct/bitfifo/bitfifo.c +++ b/libraries/datastruct/bitfifo/bitfifo.c @@ -1,4 +1,4 @@ -/* bitfifo.c -- fifo which stores bits */ +/* datastruct/bitfifo/bitfifo.c -- fifo which stores bits */ #include #include diff --git a/libraries/datastruct/bitfifo/test/bitfifo-test.c b/libraries/datastruct/bitfifo/test/bitfifo-test.c index d46bf447..d346539e 100644 --- a/libraries/datastruct/bitfifo/test/bitfifo-test.c +++ b/libraries/datastruct/bitfifo/test/bitfifo-test.c @@ -1,3 +1,4 @@ +/* datastruct/bitfifo/test/bitfifo-test.c */ #include #include diff --git a/libraries/datastruct/bitvec/and.c b/libraries/datastruct/bitvec/and.c index b291e995..d6eceab4 100644 --- a/libraries/datastruct/bitvec/and.c +++ b/libraries/datastruct/bitvec/and.c @@ -1,4 +1,4 @@ -/* and.c -- bit vectors */ +/* datastruct/bitvec/and.c -- bit vectors */ #include diff --git a/libraries/datastruct/bitvec/clear-all.c b/libraries/datastruct/bitvec/clear-all.c index f17259ed..8b24e7ea 100644 --- a/libraries/datastruct/bitvec/clear-all.c +++ b/libraries/datastruct/bitvec/clear-all.c @@ -1,4 +1,4 @@ -/* clear-all.c -- bit vectors */ +/* datastruct/bitvec/clear-all.c -- bit vectors */ #include diff --git a/libraries/datastruct/bitvec/clear.c b/libraries/datastruct/bitvec/clear.c index 166b232a..982ac63b 100644 --- a/libraries/datastruct/bitvec/clear.c +++ b/libraries/datastruct/bitvec/clear.c @@ -1,4 +1,4 @@ -/* clear.c -- bit vectors */ +/* datastruct/bitvec/clear.c -- bit vectors */ #include "datastruct/bitvec.h" diff --git a/libraries/datastruct/bitvec/count.c b/libraries/datastruct/bitvec/count.c index 51e548aa..6834dc26 100644 --- a/libraries/datastruct/bitvec/count.c +++ b/libraries/datastruct/bitvec/count.c @@ -1,4 +1,4 @@ -/* count.c -- bit vectors */ +/* datastruct/bitvec/count.c -- bit vectors */ #include "utils/barith.h" #include "datastruct/bitvec.h" diff --git a/libraries/datastruct/bitvec/create.c b/libraries/datastruct/bitvec/create.c index 56f2b454..459797c7 100644 --- a/libraries/datastruct/bitvec/create.c +++ b/libraries/datastruct/bitvec/create.c @@ -1,4 +1,4 @@ -/* create.c -- bit vectors */ +/* datastruct/bitvec/create.c -- bit vectors */ #include #include diff --git a/libraries/datastruct/bitvec/destroy.c b/libraries/datastruct/bitvec/destroy.c index ece3d5c3..70a1eb09 100644 --- a/libraries/datastruct/bitvec/destroy.c +++ b/libraries/datastruct/bitvec/destroy.c @@ -1,4 +1,4 @@ -/* destroy.c -- bit vectors */ +/* datastruct/bitvec/destroy.c -- bit vectors */ #include diff --git a/libraries/datastruct/bitvec/ensure.c b/libraries/datastruct/bitvec/ensure.c index 62adab48..02f74c0c 100644 --- a/libraries/datastruct/bitvec/ensure.c +++ b/libraries/datastruct/bitvec/ensure.c @@ -1,4 +1,4 @@ -/* ensure.c -- bit vectors */ +/* datastruct/bitvec/ensure.c -- bit vectors */ #include #include diff --git a/libraries/datastruct/bitvec/eq.c b/libraries/datastruct/bitvec/eq.c index b7e4d500..3e852c1d 100644 --- a/libraries/datastruct/bitvec/eq.c +++ b/libraries/datastruct/bitvec/eq.c @@ -1,4 +1,4 @@ -/* eq.c -- bit vectors */ +/* datastruct/bitvec/eq.c -- bit vectors */ #include diff --git a/libraries/datastruct/bitvec/get.c b/libraries/datastruct/bitvec/get.c index e2b270ef..a548b47c 100644 --- a/libraries/datastruct/bitvec/get.c +++ b/libraries/datastruct/bitvec/get.c @@ -1,4 +1,4 @@ -/* get.c -- bit vectors */ +/* datastruct/bitvec/get.c -- bit vectors */ #include "datastruct/bitvec.h" diff --git a/libraries/datastruct/bitvec/impl.h b/libraries/datastruct/bitvec/impl.h index e94f1fb6..7e312de6 100644 --- a/libraries/datastruct/bitvec/impl.h +++ b/libraries/datastruct/bitvec/impl.h @@ -1,4 +1,4 @@ -/* impl.h -- bit vectors */ +/* datastruct/bitvec/impl.h -- bit vectors */ #ifndef DATASTRUCT_BITVEC_IMPL_H #define DATASTRUCT_BITVEC_IMPL_H diff --git a/libraries/datastruct/bitvec/length.c b/libraries/datastruct/bitvec/length.c index 401d6748..3c484190 100644 --- a/libraries/datastruct/bitvec/length.c +++ b/libraries/datastruct/bitvec/length.c @@ -1,4 +1,4 @@ -/* length.c -- bit vectors */ +/* datastruct/bitvec/length.c -- bit vectors */ #include diff --git a/libraries/datastruct/bitvec/next.c b/libraries/datastruct/bitvec/next.c index 67c540b3..0bb2d992 100644 --- a/libraries/datastruct/bitvec/next.c +++ b/libraries/datastruct/bitvec/next.c @@ -1,4 +1,4 @@ -/* next.c -- bit vectors */ +/* datastruct/bitvec/next.c -- bit vectors */ #include "utils/barith.h" #include "datastruct/bitvec.h" diff --git a/libraries/datastruct/bitvec/or.c b/libraries/datastruct/bitvec/or.c index 5724ef37..cc06de1e 100644 --- a/libraries/datastruct/bitvec/or.c +++ b/libraries/datastruct/bitvec/or.c @@ -1,4 +1,4 @@ -/* or.c -- bit vectors */ +/* datastruct/bitvec/or.c -- bit vectors */ #include diff --git a/libraries/datastruct/bitvec/set-all.c b/libraries/datastruct/bitvec/set-all.c index d913afa0..368aa710 100644 --- a/libraries/datastruct/bitvec/set-all.c +++ b/libraries/datastruct/bitvec/set-all.c @@ -1,4 +1,4 @@ -/* set-all.c -- bit vectors */ +/* datastruct/bitvec/set-all.c -- bit vectors */ #include diff --git a/libraries/datastruct/bitvec/set.c b/libraries/datastruct/bitvec/set.c index 2cefd9b4..58c4a699 100644 --- a/libraries/datastruct/bitvec/set.c +++ b/libraries/datastruct/bitvec/set.c @@ -1,4 +1,4 @@ -/* set.c -- bit vectors */ +/* datastruct/bitvec/set.c -- bit vectors */ #include "base/result.h" diff --git a/libraries/datastruct/bitvec/test/bitvec-test.c b/libraries/datastruct/bitvec/test/bitvec-test.c index 97165452..fb708dd3 100644 --- a/libraries/datastruct/bitvec/test/bitvec-test.c +++ b/libraries/datastruct/bitvec/test/bitvec-test.c @@ -1,3 +1,4 @@ +/* datastruct/bitvec/test/bitvec-test.c */ #include #include diff --git a/libraries/datastruct/bitvec/toggle.c b/libraries/datastruct/bitvec/toggle.c index 94a29bc4..4105b9bb 100644 --- a/libraries/datastruct/bitvec/toggle.c +++ b/libraries/datastruct/bitvec/toggle.c @@ -1,4 +1,4 @@ -/* toggle.c -- bit vectors */ +/* datastruct/bitvec/toggle.c -- bit vectors */ #include "base/result.h" diff --git a/libraries/datastruct/cache/cache.c b/libraries/datastruct/cache/cache.c index 1a8c1cc1..0b25c784 100644 --- a/libraries/datastruct/cache/cache.c +++ b/libraries/datastruct/cache/cache.c @@ -1,4 +1,4 @@ -/* cache.c -- generic single block cache */ +/* datastruct/cache/cache.c -- generic single block cache */ #include #include diff --git a/libraries/datastruct/cache/test/cache-test.c b/libraries/datastruct/cache/test/cache-test.c index c1704c90..e4fbaaa8 100644 --- a/libraries/datastruct/cache/test/cache-test.c +++ b/libraries/datastruct/cache/test/cache-test.c @@ -1,3 +1,4 @@ +/* datastruct/cache/test/cache-test.c */ #include #include diff --git a/libraries/datastruct/hash/count.c b/libraries/datastruct/hash/count.c index 0d24af29..02fbe318 100644 --- a/libraries/datastruct/hash/count.c +++ b/libraries/datastruct/hash/count.c @@ -1,4 +1,4 @@ -/* count.c -- hash */ +/* datastruct/hash/count.c -- hash */ #include "datastruct/hash.h" diff --git a/libraries/datastruct/hash/create.c b/libraries/datastruct/hash/create.c index 70f23800..eb2387f4 100644 --- a/libraries/datastruct/hash/create.c +++ b/libraries/datastruct/hash/create.c @@ -1,4 +1,4 @@ -/* create.c -- hash */ +/* datastruct/hash/create.c -- hash */ #include #include diff --git a/libraries/datastruct/hash/destroy.c b/libraries/datastruct/hash/destroy.c index 8aa8252a..2c06ca98 100644 --- a/libraries/datastruct/hash/destroy.c +++ b/libraries/datastruct/hash/destroy.c @@ -1,4 +1,4 @@ -/* destroy.c -- hash */ +/* datastruct/hash/destroy.c -- hash */ #include diff --git a/libraries/datastruct/hash/impl.h b/libraries/datastruct/hash/impl.h index 808a7901..9403e832 100644 --- a/libraries/datastruct/hash/impl.h +++ b/libraries/datastruct/hash/impl.h @@ -1,4 +1,4 @@ -/* impl.h -- hash */ +/* datastruct/hash/impl.h -- hash */ #ifndef DATASTRUCT_HASH_IMPL_H #define DATASTRUCT_HASH_IMPL_H diff --git a/libraries/datastruct/hash/insert.c b/libraries/datastruct/hash/insert.c index df661758..0644aee1 100644 --- a/libraries/datastruct/hash/insert.c +++ b/libraries/datastruct/hash/insert.c @@ -1,4 +1,4 @@ -/* insert.c -- hash */ +/* datastruct/hash/insert.c -- hash */ #include diff --git a/libraries/datastruct/hash/lookup-node.c b/libraries/datastruct/hash/lookup-node.c index 6b6c4357..42ee1445 100644 --- a/libraries/datastruct/hash/lookup-node.c +++ b/libraries/datastruct/hash/lookup-node.c @@ -1,4 +1,4 @@ -/* lookup-node.c -- hash */ +/* datastruct/hash/lookup-node.c -- hash */ #include diff --git a/libraries/datastruct/hash/lookup.c b/libraries/datastruct/hash/lookup.c index 8f665037..3671c38b 100644 --- a/libraries/datastruct/hash/lookup.c +++ b/libraries/datastruct/hash/lookup.c @@ -1,4 +1,4 @@ -/* lookup.c -- hash */ +/* datastruct/hash/lookup.c -- hash */ #include diff --git a/libraries/datastruct/hash/remove.c b/libraries/datastruct/hash/remove.c index a3a1ce8a..8222d6ae 100644 --- a/libraries/datastruct/hash/remove.c +++ b/libraries/datastruct/hash/remove.c @@ -1,4 +1,4 @@ -/* remove.c -- hash */ +/* datastruct/hash/remove.c -- hash */ #include diff --git a/libraries/datastruct/hash/test/hash-test.c b/libraries/datastruct/hash/test/hash-test.c index 7d807b29..c896d5d3 100644 --- a/libraries/datastruct/hash/test/hash-test.c +++ b/libraries/datastruct/hash/test/hash-test.c @@ -1,3 +1,4 @@ +/* datastruct/hash/test/hash-test.c */ #include #include diff --git a/libraries/datastruct/hash/walk-cont.c b/libraries/datastruct/hash/walk-cont.c index 4fa822fd..db3b44c6 100644 --- a/libraries/datastruct/hash/walk-cont.c +++ b/libraries/datastruct/hash/walk-cont.c @@ -1,4 +1,4 @@ -/* walk-cont.c -- hash */ +/* datastruct/hash/walk-cont.c -- hash */ #include #include diff --git a/libraries/datastruct/hash/walk.c b/libraries/datastruct/hash/walk.c index 6388c627..d876d441 100644 --- a/libraries/datastruct/hash/walk.c +++ b/libraries/datastruct/hash/walk.c @@ -1,4 +1,4 @@ -/* walk.c -- hash */ +/* datastruct/hash/walk.c -- hash */ #include diff --git a/libraries/datastruct/hlist/append.c b/libraries/datastruct/hlist/append.c index 601242b5..3bcfbd50 100644 --- a/libraries/datastruct/hlist/append.c +++ b/libraries/datastruct/hlist/append.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/append.c */ #include #include diff --git a/libraries/datastruct/hlist/copy.c b/libraries/datastruct/hlist/copy.c index 49fdd4cc..12240d40 100644 --- a/libraries/datastruct/hlist/copy.c +++ b/libraries/datastruct/hlist/copy.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/copy.c */ #include #include diff --git a/libraries/datastruct/hlist/free.c b/libraries/datastruct/hlist/free.c index 5823b503..3e8dfc38 100644 --- a/libraries/datastruct/hlist/free.c +++ b/libraries/datastruct/hlist/free.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/free.c */ #include #include diff --git a/libraries/datastruct/hlist/impl.h b/libraries/datastruct/hlist/impl.h index 32b30fa7..b3e533d8 100644 --- a/libraries/datastruct/hlist/impl.h +++ b/libraries/datastruct/hlist/impl.h @@ -1,3 +1,3 @@ -/* impl.h -- hlist */ +/* datastruct/hlist/impl.h -- hlist */ #define T hlist_t diff --git a/libraries/datastruct/hlist/length.c b/libraries/datastruct/hlist/length.c index b52c1615..41535767 100644 --- a/libraries/datastruct/hlist/length.c +++ b/libraries/datastruct/hlist/length.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/length.c */ #include #include diff --git a/libraries/datastruct/hlist/list.c b/libraries/datastruct/hlist/list.c index a8178dff..07cd0ffb 100644 --- a/libraries/datastruct/hlist/list.c +++ b/libraries/datastruct/hlist/list.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/list.c */ #include #include diff --git a/libraries/datastruct/hlist/map.c b/libraries/datastruct/hlist/map.c index 183567b0..d0a3acd7 100644 --- a/libraries/datastruct/hlist/map.c +++ b/libraries/datastruct/hlist/map.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/map.c */ #include #include diff --git a/libraries/datastruct/hlist/pop.c b/libraries/datastruct/hlist/pop.c index 03a09025..9d3d3e4a 100644 --- a/libraries/datastruct/hlist/pop.c +++ b/libraries/datastruct/hlist/pop.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/pop.c */ #include #include diff --git a/libraries/datastruct/hlist/push.c b/libraries/datastruct/hlist/push.c index fefe1cd2..2e5add1b 100644 --- a/libraries/datastruct/hlist/push.c +++ b/libraries/datastruct/hlist/push.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/push.c */ #include #include diff --git a/libraries/datastruct/hlist/reverse.c b/libraries/datastruct/hlist/reverse.c index 49467f1f..31647c87 100644 --- a/libraries/datastruct/hlist/reverse.c +++ b/libraries/datastruct/hlist/reverse.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/reverse.c */ #include #include diff --git a/libraries/datastruct/hlist/to-array.c b/libraries/datastruct/hlist/to-array.c index 26da4b1f..be907e24 100644 --- a/libraries/datastruct/hlist/to-array.c +++ b/libraries/datastruct/hlist/to-array.c @@ -1,3 +1,4 @@ +/* datastruct/hlist/to-array.c */ #include #include diff --git a/libraries/datastruct/list/add-head.c b/libraries/datastruct/list/add-head.c index f7e1d6ba..59b9f6c7 100644 --- a/libraries/datastruct/list/add-head.c +++ b/libraries/datastruct/list/add-head.c @@ -1,3 +1,4 @@ +/* datastruct/list/add-head.c */ #include "datastruct/list.h" diff --git a/libraries/datastruct/list/add-tail.c b/libraries/datastruct/list/add-tail.c index bb602371..b500ba57 100644 --- a/libraries/datastruct/list/add-tail.c +++ b/libraries/datastruct/list/add-tail.c @@ -1,3 +1,4 @@ +/* datastruct/list/add-tail.c */ #include "datastruct/list.h" diff --git a/libraries/datastruct/list/find.c b/libraries/datastruct/list/find.c index 7db71f05..e283cb87 100644 --- a/libraries/datastruct/list/find.c +++ b/libraries/datastruct/list/find.c @@ -1,3 +1,4 @@ +/* datastruct/list/find.c */ #include "datastruct/list.h" diff --git a/libraries/datastruct/list/init.c b/libraries/datastruct/list/init.c index c9cfcac3..d91c25df 100644 --- a/libraries/datastruct/list/init.c +++ b/libraries/datastruct/list/init.c @@ -1,3 +1,4 @@ +/* datastruct/list/init.c */ #include "datastruct/list.h" diff --git a/libraries/datastruct/list/remove.c b/libraries/datastruct/list/remove.c index 532a7223..bf73e24f 100644 --- a/libraries/datastruct/list/remove.c +++ b/libraries/datastruct/list/remove.c @@ -1,3 +1,4 @@ +/* datastruct/list/remove.c */ #include diff --git a/libraries/datastruct/list/test/list-test.c b/libraries/datastruct/list/test/list-test.c index a6e959cd..ed2964ec 100644 --- a/libraries/datastruct/list/test/list-test.c +++ b/libraries/datastruct/list/test/list-test.c @@ -1,3 +1,4 @@ +/* datastruct/list/test/list-test.c */ #include diff --git a/libraries/datastruct/list/walk.c b/libraries/datastruct/list/walk.c index 37c7ed9a..bbbee8c2 100644 --- a/libraries/datastruct/list/walk.c +++ b/libraries/datastruct/list/walk.c @@ -1,3 +1,4 @@ +/* datastruct/list/walk.c */ #include "datastruct/list.h" diff --git a/libraries/datastruct/ntree/copy.c b/libraries/datastruct/ntree/copy.c index 6214491d..3870dad9 100644 --- a/libraries/datastruct/ntree/copy.c +++ b/libraries/datastruct/ntree/copy.c @@ -1,4 +1,4 @@ -/* copy.c -- n-ary tree */ +/* datastruct/ntree/copy.c -- n-ary tree */ #include diff --git a/libraries/datastruct/ntree/delete.c b/libraries/datastruct/ntree/delete.c index 8730160b..3c640ad7 100644 --- a/libraries/datastruct/ntree/delete.c +++ b/libraries/datastruct/ntree/delete.c @@ -1,4 +1,4 @@ -/* delete.c -- n-ary tree */ +/* datastruct/ntree/delete.c -- n-ary tree */ #include diff --git a/libraries/datastruct/ntree/depth.c b/libraries/datastruct/ntree/depth.c index e64d4e37..491e4d2f 100644 --- a/libraries/datastruct/ntree/depth.c +++ b/libraries/datastruct/ntree/depth.c @@ -1,4 +1,4 @@ -/* depth.c -- n-ary tree */ +/* datastruct/ntree/depth.c -- n-ary tree */ #include "datastruct/ntree.h" diff --git a/libraries/datastruct/ntree/first-child.c b/libraries/datastruct/ntree/first-child.c index 8bcbf63a..2e896803 100644 --- a/libraries/datastruct/ntree/first-child.c +++ b/libraries/datastruct/ntree/first-child.c @@ -1,4 +1,4 @@ -/* first-child.c -- n-ary tree */ +/* datastruct/ntree/first-child.c -- n-ary tree */ #include diff --git a/libraries/datastruct/ntree/free.c b/libraries/datastruct/ntree/free.c index 06b345eb..06b0323c 100644 --- a/libraries/datastruct/ntree/free.c +++ b/libraries/datastruct/ntree/free.c @@ -1,4 +1,4 @@ -/* free.c -- n-ary tree */ +/* datastruct/ntree/free.c -- n-ary tree */ #include #include diff --git a/libraries/datastruct/ntree/get-data.c b/libraries/datastruct/ntree/get-data.c index 07647d9e..f842bdeb 100644 --- a/libraries/datastruct/ntree/get-data.c +++ b/libraries/datastruct/ntree/get-data.c @@ -1,4 +1,4 @@ -/* get-data.c -- n-ary tree */ +/* datastruct/ntree/get-data.c -- n-ary tree */ #include "datastruct/ntree.h" diff --git a/libraries/datastruct/ntree/impl.h b/libraries/datastruct/ntree/impl.h index dec9802e..21c6ecc8 100644 --- a/libraries/datastruct/ntree/impl.h +++ b/libraries/datastruct/ntree/impl.h @@ -1,4 +1,4 @@ -/* impl.h -- n-ary tree */ +/* datastruct/ntree/impl.h -- n-ary tree */ #ifndef NTREE_IMPL_H #define NTREE_IMPL_H diff --git a/libraries/datastruct/ntree/insert-after.c b/libraries/datastruct/ntree/insert-after.c index 6ef8f7fb..94d78899 100644 --- a/libraries/datastruct/ntree/insert-after.c +++ b/libraries/datastruct/ntree/insert-after.c @@ -1,4 +1,4 @@ -/* insert-after.c -- n-ary tree */ +/* datastruct/ntree/insert-after.c -- n-ary tree */ #include #include diff --git a/libraries/datastruct/ntree/insert-before.c b/libraries/datastruct/ntree/insert-before.c index ae44f4aa..0a2ccc49 100644 --- a/libraries/datastruct/ntree/insert-before.c +++ b/libraries/datastruct/ntree/insert-before.c @@ -1,4 +1,4 @@ -/* insert-before.c -- n-ary tree */ +/* datastruct/ntree/insert-before.c -- n-ary tree */ #include #include @@ -7,7 +7,9 @@ #include "impl.h" -result_t ntree_insert_before(ntree_t *parent, ntree_t *sibling, ntree_t *node) +result_t ntree_insert_before(ntree_t *parent, + ntree_t *sibling, + ntree_t *node) { assert(node->parent == NULL); diff --git a/libraries/datastruct/ntree/insert.c b/libraries/datastruct/ntree/insert.c index 26e90199..2bb87f1d 100644 --- a/libraries/datastruct/ntree/insert.c +++ b/libraries/datastruct/ntree/insert.c @@ -1,4 +1,4 @@ -/* insert.c -- n-ary tree */ +/* datastruct/ntree/insert.c -- n-ary tree */ #include diff --git a/libraries/datastruct/ntree/last-child.c b/libraries/datastruct/ntree/last-child.c index 5dd9a037..800eac4f 100644 --- a/libraries/datastruct/ntree/last-child.c +++ b/libraries/datastruct/ntree/last-child.c @@ -1,4 +1,4 @@ -/* last-child.c -- n-ary tree */ +/* datastruct/ntree/last-child.c -- n-ary tree */ #include "datastruct/ntree.h" diff --git a/libraries/datastruct/ntree/max-height.c b/libraries/datastruct/ntree/max-height.c index 6735c76c..30df91fe 100644 --- a/libraries/datastruct/ntree/max-height.c +++ b/libraries/datastruct/ntree/max-height.c @@ -1,4 +1,4 @@ -/* max-height.c -- n-ary tree */ +/* datastruct/ntree/max-height.c -- n-ary tree */ #include "datastruct/ntree.h" diff --git a/libraries/datastruct/ntree/n-nodes.c b/libraries/datastruct/ntree/n-nodes.c index 76a18124..80189c9a 100644 --- a/libraries/datastruct/ntree/n-nodes.c +++ b/libraries/datastruct/ntree/n-nodes.c @@ -1,4 +1,4 @@ -/* n-nodes.c -- n-ary tree */ +/* datastruct/ntree/n-nodes.c -- n-ary tree */ #include "base/utils.h" diff --git a/libraries/datastruct/ntree/new.c b/libraries/datastruct/ntree/new.c index 24aeb2b4..fa1dd1ad 100644 --- a/libraries/datastruct/ntree/new.c +++ b/libraries/datastruct/ntree/new.c @@ -1,4 +1,4 @@ -/* new.c -- n-ary tree */ +/* datastruct/ntree/new.c -- n-ary tree */ #include diff --git a/libraries/datastruct/ntree/next-sibling.c b/libraries/datastruct/ntree/next-sibling.c index 57d3d73e..617c234f 100644 --- a/libraries/datastruct/ntree/next-sibling.c +++ b/libraries/datastruct/ntree/next-sibling.c @@ -1,4 +1,4 @@ -/* next-sibling.c -- n-ary tree */ +/* datastruct/ntree/next-sibling.c -- n-ary tree */ #include diff --git a/libraries/datastruct/ntree/nth-child.c b/libraries/datastruct/ntree/nth-child.c index feef026f..cf11b395 100644 --- a/libraries/datastruct/ntree/nth-child.c +++ b/libraries/datastruct/ntree/nth-child.c @@ -1,4 +1,4 @@ -/* nth-child.c -- n-ary tree */ +/* datastruct/ntree/nth-child.c -- n-ary tree */ #include "datastruct/ntree.h" diff --git a/libraries/datastruct/ntree/parent.c b/libraries/datastruct/ntree/parent.c index a947a76d..ed756cf2 100644 --- a/libraries/datastruct/ntree/parent.c +++ b/libraries/datastruct/ntree/parent.c @@ -1,4 +1,4 @@ -/* parent.c -- n-ary tree */ +/* datastruct/ntree/parent.c -- n-ary tree */ #include diff --git a/libraries/datastruct/ntree/prepend.c b/libraries/datastruct/ntree/prepend.c index 42d0a912..e5820a9a 100644 --- a/libraries/datastruct/ntree/prepend.c +++ b/libraries/datastruct/ntree/prepend.c @@ -1,4 +1,4 @@ -/* prepend.c -- n-ary tree */ +/* datastruct/ntree/prepend.c -- n-ary tree */ #include diff --git a/libraries/datastruct/ntree/prev-sibling.c b/libraries/datastruct/ntree/prev-sibling.c index 48526b5a..04866359 100644 --- a/libraries/datastruct/ntree/prev-sibling.c +++ b/libraries/datastruct/ntree/prev-sibling.c @@ -1,4 +1,4 @@ -/* prev-sibling.c -- n-ary tree */ +/* datastruct/ntree/prev-sibling.c -- n-ary tree */ #include diff --git a/libraries/datastruct/ntree/set-data.c b/libraries/datastruct/ntree/set-data.c index 611b418c..533a6696 100644 --- a/libraries/datastruct/ntree/set-data.c +++ b/libraries/datastruct/ntree/set-data.c @@ -1,4 +1,4 @@ -/* set-data.c -- n-ary tree */ +/* datastruct/ntree/set-data.c -- n-ary tree */ #include "datastruct/ntree.h" diff --git a/libraries/datastruct/ntree/test/ntree-test.c b/libraries/datastruct/ntree/test/ntree-test.c index 8a9c8950..9aa5417b 100644 --- a/libraries/datastruct/ntree/test/ntree-test.c +++ b/libraries/datastruct/ntree/test/ntree-test.c @@ -1,3 +1,4 @@ +/* datastruct/ntree/test/ntree-test.c */ #include #include diff --git a/libraries/datastruct/ntree/unlink.c b/libraries/datastruct/ntree/unlink.c index c9df723c..94838fd6 100644 --- a/libraries/datastruct/ntree/unlink.c +++ b/libraries/datastruct/ntree/unlink.c @@ -1,4 +1,4 @@ -/* unlink.c -- n-ary tree */ +/* datastruct/ntree/unlink.c -- n-ary tree */ #include #include diff --git a/libraries/datastruct/ntree/walk.c b/libraries/datastruct/ntree/walk.c index e9e4e219..22320720 100644 --- a/libraries/datastruct/ntree/walk.c +++ b/libraries/datastruct/ntree/walk.c @@ -1,4 +1,4 @@ -/* walk.c -- n-ary tree */ +/* datastruct/ntree/walk.c -- n-ary tree */ #include #include diff --git a/libraries/datastruct/vector/clear.c b/libraries/datastruct/vector/clear.c index 0345e94a..da5096a7 100644 --- a/libraries/datastruct/vector/clear.c +++ b/libraries/datastruct/vector/clear.c @@ -1,4 +1,4 @@ -/* clear.c -- vector - flexible array */ +/* datastruct/vector/clear.c -- flexible array */ #include diff --git a/libraries/datastruct/vector/create.c b/libraries/datastruct/vector/create.c index bdc03a38..ab861228 100644 --- a/libraries/datastruct/vector/create.c +++ b/libraries/datastruct/vector/create.c @@ -1,4 +1,4 @@ -/* create.c -- vector - flexible array */ +/* datastruct/vector/create.c -- flexible array */ #include diff --git a/libraries/datastruct/vector/destroy.c b/libraries/datastruct/vector/destroy.c index b4fce1a0..1cf7a36f 100644 --- a/libraries/datastruct/vector/destroy.c +++ b/libraries/datastruct/vector/destroy.c @@ -1,4 +1,4 @@ -/* destroy.c -- vector - flexible array */ +/* datastruct/vector/destroy.c -- flexible array */ #include diff --git a/libraries/datastruct/vector/ensure.c b/libraries/datastruct/vector/ensure.c index 29bcf67b..39b44e80 100644 --- a/libraries/datastruct/vector/ensure.c +++ b/libraries/datastruct/vector/ensure.c @@ -1,4 +1,4 @@ -/* ensure.c -- vector - flexible array */ +/* datastruct/vector/ensure.c -- flexible array */ #include diff --git a/libraries/datastruct/vector/get.c b/libraries/datastruct/vector/get.c index 817ddc1b..b6d995a9 100644 --- a/libraries/datastruct/vector/get.c +++ b/libraries/datastruct/vector/get.c @@ -1,4 +1,4 @@ -/* get.c -- vector - flexible array */ +/* datastruct/vector/get.c -- flexible array */ #include #include diff --git a/libraries/datastruct/vector/impl.h b/libraries/datastruct/vector/impl.h index 7ffc5935..143a0fcb 100644 --- a/libraries/datastruct/vector/impl.h +++ b/libraries/datastruct/vector/impl.h @@ -1,4 +1,4 @@ -/* impl.h -- vector - flexible array */ +/* datastruct/vector/impl.h -- flexible array */ #ifndef IMPL_H #define IMPL_H diff --git a/libraries/datastruct/vector/insert.c b/libraries/datastruct/vector/insert.c index f171d2e8..5a09f628 100644 --- a/libraries/datastruct/vector/insert.c +++ b/libraries/datastruct/vector/insert.c @@ -1,4 +1,4 @@ -/* insert.c -- vector - flexible array */ +/* datastruct/vector/insert.c -- flexible array */ #include #include diff --git a/libraries/datastruct/vector/length.c b/libraries/datastruct/vector/length.c index 87e33c8d..29a17a21 100644 --- a/libraries/datastruct/vector/length.c +++ b/libraries/datastruct/vector/length.c @@ -1,4 +1,4 @@ -/* length.c -- vector - flexible array */ +/* datastruct/vector/length.c -- flexible array */ #include diff --git a/libraries/datastruct/vector/set-length.c b/libraries/datastruct/vector/set-length.c index 3e9a0d84..4d20b92d 100644 --- a/libraries/datastruct/vector/set-length.c +++ b/libraries/datastruct/vector/set-length.c @@ -1,4 +1,4 @@ -/* set-length.c -- vector - flexible array */ +/* datastruct/vector/set-length.c -- flexible array */ #include diff --git a/libraries/datastruct/vector/set-width.c b/libraries/datastruct/vector/set-width.c index 7a222e9e..26d4fd61 100644 --- a/libraries/datastruct/vector/set-width.c +++ b/libraries/datastruct/vector/set-width.c @@ -1,4 +1,4 @@ -/* set-width.c -- vector - flexible array */ +/* datastruct/vector/set-width.c -- flexible array */ #include diff --git a/libraries/datastruct/vector/set.c b/libraries/datastruct/vector/set.c index 7f5e5c94..0a2a39db 100644 --- a/libraries/datastruct/vector/set.c +++ b/libraries/datastruct/vector/set.c @@ -1,4 +1,4 @@ -/* set.c -- vector - flexible array */ +/* datastruct/vector/set.c -- flexible array */ #include #include diff --git a/libraries/datastruct/vector/test/vector-test.c b/libraries/datastruct/vector/test/vector-test.c index 2b7ec064..48fba96c 100644 --- a/libraries/datastruct/vector/test/vector-test.c +++ b/libraries/datastruct/vector/test/vector-test.c @@ -1,3 +1,4 @@ +/* datastruct/vector/test/vector-test.c */ #include diff --git a/libraries/datastruct/vector/width.c b/libraries/datastruct/vector/width.c index 24930510..4d3081b5 100644 --- a/libraries/datastruct/vector/width.c +++ b/libraries/datastruct/vector/width.c @@ -1,4 +1,4 @@ -/* width.c -- vector - flexible array */ +/* datastruct/vector/width.c -- flexible array */ #include diff --git a/libraries/framebuf/bitmap/bitmap.c b/libraries/framebuf/bitmap/bitmap.c index af839a25..139d70ae 100644 --- a/libraries/framebuf/bitmap/bitmap.c +++ b/libraries/framebuf/bitmap/bitmap.c @@ -1,4 +1,4 @@ -/* bitmap.c */ +/* framebuf/bitmap/bitmap.c */ #include #include @@ -15,8 +15,6 @@ result_t bitmap_init(bitmap_t *bm, const colour_t *palette, void *base) { - int log2bpp; - assert(bm); bm->size = size; @@ -26,25 +24,43 @@ result_t bitmap_init(bitmap_t *bm, bm->span = spanregistry_get(fmt); bm->base = base; - if (palette) - { - log2bpp = pixelfmt_log2bpp(fmt); - if (log2bpp <= 3) - { - int nentries; - colour_t *newpal; + return bitmap_set_palette(bm, palette); +} - nentries = 1 << (1 << log2bpp); - newpal = malloc(nentries * sizeof(*newpal)); - if (newpal == NULL) - return result_OOM; +result_t bitmap_set_palette(bitmap_t *bm, const colour_t *palette) +{ + int log2bpp; + int nentries; + colour_t *pal; - memcpy(newpal, palette, nentries * sizeof(*newpal)); + assert(bm); - bm->palette = newpal; - } + log2bpp = pixelfmt_log2bpp(bm->format); + if (log2bpp > 3) + return result_OK; /* no palette for this format */ + + if (palette == NULL) + { + free(bm->palette); + bm->palette = NULL; + return result_OK; } + nentries = 1 << (1 << log2bpp); + + /* the entry count is fixed by the format, so an existing buffer is always + * the right size to reuse */ + pal = bm->palette; + if (pal == NULL) + { + pal = malloc(nentries * sizeof(*pal)); + if (pal == NULL) + return result_OOM; + } + + memcpy(pal, palette, nentries * sizeof(*pal)); + bm->palette = pal; + return result_OK; } @@ -167,7 +183,9 @@ static result_t bmconv_p4_to_bgrx8888(const bitmap_t *src, bitmap_t **pdst) return rc; } -result_t bitmap_convert(const bitmap_t *src, pixelfmt_t newfmt, bitmap_t **dst) +result_t bitmap_convert(const bitmap_t *src, + pixelfmt_t newfmt, + bitmap_t **dst) { *dst = NULL; diff --git a/libraries/framebuf/bitmap/load.c b/libraries/framebuf/bitmap/load.c index b4b3fbaa..85f3be70 100644 --- a/libraries/framebuf/bitmap/load.c +++ b/libraries/framebuf/bitmap/load.c @@ -1,4 +1,4 @@ -/* load.c */ +/* framebuf/bitmap/load.c */ #include #include diff --git a/libraries/framebuf/bitmap/save.c b/libraries/framebuf/bitmap/save.c index 7be936e5..4de7ee23 100755 --- a/libraries/framebuf/bitmap/save.c +++ b/libraries/framebuf/bitmap/save.c @@ -1,4 +1,4 @@ -/* save.c */ +/* framebuf/bitmap/save.c */ #include #include diff --git a/libraries/framebuf/bmfont/bmfont.c b/libraries/framebuf/bmfont/bmfont.c index 00d3b87c..534c9996 100644 --- a/libraries/framebuf/bmfont/bmfont.c +++ b/libraries/framebuf/bmfont/bmfont.c @@ -1,4 +1,4 @@ -/* bmfont.c */ +/* framebuf/bmfont/bmfont.c */ /* TODOS / IDEAS * @@ -158,7 +158,7 @@ static result_t extract_advance_widths(bmfont_t *bmfont, assert(ncurrbits >= bitsperchar); adw_px = currbits & mask; /* extract high bits */ - currbits <<= bitsperchar; /* discard used bits */ + currbits = (bitsperchar == 32) ? 0 : currbits << bitsperchar; /* discard used bits */ ncurrbits -= bitsperchar; if (array_grow((void **) &bmfont->adw, diff --git a/libraries/framebuf/bmfont/test/bmfont-test.c b/libraries/framebuf/bmfont/test/bmfont-test.c index c9caed05..86457d8f 100644 --- a/libraries/framebuf/bmfont/test/bmfont-test.c +++ b/libraries/framebuf/bmfont/test/bmfont-test.c @@ -1,4 +1,4 @@ -/* bmfont-test.c */ +/* framebuf/bmfont/test/bmfont-test.c */ #include #include diff --git a/libraries/framebuf/colour/colour.c b/libraries/framebuf/colour/colour.c index 1b77838f..e7b58b86 100644 --- a/libraries/framebuf/colour/colour.c +++ b/libraries/framebuf/colour/colour.c @@ -1,4 +1,4 @@ -/* colour.c */ +/* framebuf/colour/colour.c */ #include #include diff --git a/libraries/framebuf/composite/composite.c b/libraries/framebuf/composite/composite.c index 3aa29acb..b2859cd0 100644 --- a/libraries/framebuf/composite/composite.c +++ b/libraries/framebuf/composite/composite.c @@ -1,4 +1,4 @@ -/* composite.c +/* framebuf/composite/composite.c * * An implementation of Porter-Duff image compositing. * diff --git a/libraries/framebuf/composite/test/composite-test.c b/libraries/framebuf/composite/test/composite-test.c index fdb0fe95..eac2cdc8 100644 --- a/libraries/framebuf/composite/test/composite-test.c +++ b/libraries/framebuf/composite/test/composite-test.c @@ -1,4 +1,4 @@ -/* composite-test.c */ +/* framebuf/composite/test/composite-test.c */ #include #include diff --git a/libraries/framebuf/curve/curve.c b/libraries/framebuf/curve/curve.c index b3ce5fcb..dc74c5ef 100644 --- a/libraries/framebuf/curve/curve.c +++ b/libraries/framebuf/curve/curve.c @@ -1,4 +1,4 @@ -/* bezier.c */ +/* framebuf/curve/curve.c */ // TODO // @@ -328,14 +328,14 @@ void curve_bezier_cubic(point_t p0, #define MULFIX16(x,y) (int) (((long long) (x) * (y)) >> FIX16_SHIFT) - cx = 3 * ((p1.x - p0.x) << FIX16_SHIFT); - cy = 3 * ((p1.y - p0.y) << FIX16_SHIFT); + cx = 3 * ((p1.x - p0.x) * FIX16_ONE); + cy = 3 * ((p1.y - p0.y) * FIX16_ONE); - bx = 3 * ((p2.x - p1.x) << FIX16_SHIFT) - cx; - by = 3 * ((p2.y - p1.y) << FIX16_SHIFT) - cy; + bx = 3 * ((p2.x - p1.x) * FIX16_ONE) - cx; + by = 3 * ((p2.y - p1.y) * FIX16_ONE) - cy; - ax = ((p3.x - p0.x) << FIX16_SHIFT) - cx - bx; - ay = ((p3.y - p0.y) << FIX16_SHIFT) - cy - by; + ax = ((p3.x - p0.x) * FIX16_ONE) - cx - bx; + ay = ((p3.y - p0.y) * FIX16_ONE) - cy - by; h = FIX16_ONE / nsteps; hh = MULFIX16(h,h); @@ -355,8 +355,8 @@ void curve_bezier_cubic(point_t p0, point = &points[0]; - curx = p0.x << FIX16_SHIFT; - cury = p0.y << FIX16_SHIFT; + curx = p0.x * FIX16_ONE; + cury = p0.y * FIX16_ONE; for (i = 0; ; i++) { diff --git a/libraries/framebuf/curve/test/curve-test.c b/libraries/framebuf/curve/test/curve-test.c index 5cca0837..a78021ed 100644 --- a/libraries/framebuf/curve/test/curve-test.c +++ b/libraries/framebuf/curve/test/curve-test.c @@ -1,4 +1,4 @@ -/* curve-test.c */ +/* framebuf/curve/test/curve-test.c */ #include #include @@ -336,7 +336,7 @@ static void draw_all_control_points(curveteststate_t *state) colour = state->palette[8 + set]; - screen_draw_square(&state->scr, b.x0, b.y0, BLOBSZ, colour); + screen_fill_square(&state->scr, b.x0, b.y0, BLOBSZ, colour); box_union(&b, &state->overalldirty, &state->overalldirty); @@ -366,8 +366,8 @@ static void draw_a_curve(curveteststate_t *state) if (state->opt.draw_endpoints) { - screen_draw_pixel(&state->scr, b.x0, b.y0, state->palette[palette_PICO8_GREEN]); - screen_draw_pixel(&state->scr, b.x1, b.y1, state->palette[palette_PICO8_RED]); + screen_set_pixel(&state->scr, b.x0, b.y0, state->palette[palette_PICO8_GREEN]); + screen_set_pixel(&state->scr, b.x1, b.y1, state->palette[palette_PICO8_RED]); } switch (state->opt.method) @@ -688,7 +688,7 @@ static result_t curve_interactive_test(curveteststate_t *state) } } #else - if (frame > 1000) + if (frame > 100) /* headless: enough frames to cover every draw path */ quit = 1; #endif diff --git a/libraries/framebuf/palettes/palettes.c b/libraries/framebuf/palettes/palettes.c index 46816b45..ac3b2586 100644 --- a/libraries/framebuf/palettes/palettes.c +++ b/libraries/framebuf/palettes/palettes.c @@ -1,4 +1,4 @@ -/* palettes.c */ +/* framebuf/palettes/palettes.c */ #include "framebuf/colour.h" @@ -23,3 +23,25 @@ void define_pico8_palette(colour_t palette[palette_PICO8__LENGTH]) palette[palette_PICO8_PINK ] = colour_rgb(0xFF, 0x77, 0xA8); palette[palette_PICO8_LIGHT_PEACH] = colour_rgb(0xFF, 0xCC, 0xAA); } + +/* The standard RISC OS desktop (Wimp) 16-colour palette in native Wimp index + * order: a greyscale ramp (0-7) then eight colours. */ +void define_wimp16_palette(colour_t palette[palette_WIMP16__LENGTH]) +{ + palette[palette_WIMP16_WHITE ] = colour_rgb(0xFF, 0xFF, 0xFF); + palette[palette_WIMP16_GREY_87 ] = colour_rgb(0xDD, 0xDD, 0xDD); + palette[palette_WIMP16_GREY_75 ] = colour_rgb(0xBB, 0xBB, 0xBB); + palette[palette_WIMP16_GREY_62 ] = colour_rgb(0x99, 0x99, 0x99); + palette[palette_WIMP16_GREY_50 ] = colour_rgb(0x77, 0x77, 0x77); + palette[palette_WIMP16_GREY_37 ] = colour_rgb(0x55, 0x55, 0x55); + palette[palette_WIMP16_GREY_25 ] = colour_rgb(0x33, 0x33, 0x33); + palette[palette_WIMP16_BLACK ] = colour_rgb(0x00, 0x00, 0x00); + palette[palette_WIMP16_DARK_BLUE ] = colour_rgb(0x00, 0x44, 0x99); + palette[palette_WIMP16_YELLOW ] = colour_rgb(0xEE, 0xEE, 0x00); + palette[palette_WIMP16_GREEN ] = colour_rgb(0x00, 0xCC, 0x00); + palette[palette_WIMP16_RED ] = colour_rgb(0xDD, 0x00, 0x00); + palette[palette_WIMP16_CREAM ] = colour_rgb(0xEE, 0xEE, 0xBB); + palette[palette_WIMP16_DARK_GREEN] = colour_rgb(0x55, 0x88, 0x00); + palette[palette_WIMP16_ORANGE ] = colour_rgb(0xFF, 0xBB, 0x00); + palette[palette_WIMP16_LIGHT_BLUE] = colour_rgb(0x00, 0xBB, 0xFF); +} diff --git a/libraries/framebuf/pixelfmt/log2bpp.c b/libraries/framebuf/pixelfmt/log2bpp.c index dbc78a38..1d593519 100644 --- a/libraries/framebuf/pixelfmt/log2bpp.c +++ b/libraries/framebuf/pixelfmt/log2bpp.c @@ -1,4 +1,4 @@ -/* log2bpp.c -- given a pixel format return its log2 bits-per-pixel size */ +/* framebuf/pixelfmt/log2bpp.c -- given a pixel format return its log2 bits-per-pixel size */ #include diff --git a/libraries/framebuf/screen/screen-copy-rect.c b/libraries/framebuf/screen/screen-copy-rect.c index 5ae5231e..46c5f559 100644 --- a/libraries/framebuf/screen/screen-copy-rect.c +++ b/libraries/framebuf/screen/screen-copy-rect.c @@ -1,4 +1,4 @@ -/* screen-copy-rect.c -- screen - same-screen rectangle copy */ +/* framebuf/screen/screen-copy-rect.c -- same-screen rectangle copy */ #include diff --git a/libraries/framebuf/screen/screen-draw-ninepatch.c b/libraries/framebuf/screen/screen-draw-ninepatch.c index b0b7a795..6dac6608 100644 --- a/libraries/framebuf/screen/screen-draw-ninepatch.c +++ b/libraries/framebuf/screen/screen-draw-ninepatch.c @@ -1,4 +1,4 @@ -/* screen-draw-ninepatch.c -- 9-patch bitmap drawing */ +/* framebuf/screen/screen-draw-ninepatch.c -- 9-patch bitmap drawing */ #include diff --git a/libraries/framebuf/screen/screen-draw.c b/libraries/framebuf/screen/screen-draw.c index 26523abe..b3409057 100644 --- a/libraries/framebuf/screen/screen-draw.c +++ b/libraries/framebuf/screen/screen-draw.c @@ -1,4 +1,4 @@ -/* screen-draw.c */ +/* framebuf/screen/screen-draw.c */ #include #include @@ -20,7 +20,7 @@ * blow the stack (relevant on RISC OS). */ #define BITMAP_BLIT_CHUNK 256 -void screen_draw_pixel(screen_t *scr, int x, int y, colour_t colour) +void screen_set_pixel(screen_t *scr, int x, int y, colour_t colour) { box_t clip; pixelfmt_any_t pxl; @@ -138,7 +138,7 @@ static void screen_blend_pixel(screen_t *scr, /* ----------------------------------------------------------------------- */ -void screen_draw_rect(screen_t *scr, +void screen_fill_rect(screen_t *scr, int x, int y, size2d_t size, @@ -215,9 +215,13 @@ void screen_draw_rect(screen_t *scr, } } -void screen_draw_square(screen_t *scr, int x, int y, int size, colour_t colour) +void screen_fill_square(screen_t *scr, + int x, + int y, + int size, + colour_t colour) { - screen_draw_rect(scr, x, y, SIZE2D(size, size), colour); + screen_fill_rect(scr, x, y, SIZE2D(size, size), colour); } /* ----------------------------------------------------------------------- */ @@ -254,7 +258,7 @@ void screen_draw_bitmap(screen_t *scr, int x, int y, const bitmap_t *src) { /* Paletted screen: no linear channel bits to blend, so fall back to * alpha-tested (skip fully transparent, else nearest palette match) - * rather than true blending, matching screen_draw_pixel's case 2. */ + * rather than true blending, matching screen_set_pixel's case 2. */ const unsigned char *srcrow; unsigned char *dstbase; int yy; @@ -412,7 +416,127 @@ void screen_draw_line(screen_t *scr, for (;;) { - screen_draw_pixel(scr, x0, y0, colour); + screen_set_pixel(scr, x0, y0, colour); + + if (x0 == x1 && y0 == y1) + break; + + e2 = 2 * error; + if (e2 >= ady) + { + if (x0 == x1) { break; } + error += ady; + x0 += sx; + } + if (e2 <= adx) + { + if (y0 == y1) { break; } + error += adx; + y0 += sy; + } + } +} + +void screen_draw_lines(screen_t *scr, + const point_t *points, + int npoints, + colour_t colour) +{ + int i; + + if (points == NULL || npoints < 2) + return; + + /* ponytail: per-segment call; joint pixels double-plot, fine for solid fill */ + for (i = 1; i < npoints; i++) + screen_draw_line(scr, + points[i - 1].x, points[i - 1].y, + points[i].x, points[i].y, + colour); +} + +void screen_draw_rect(screen_t *scr, + int x, + int y, + size2d_t size, + colour_t colour) +{ + int x1, y1; + point_t p[5]; + + if (size.w <= 1 || size.h <= 1) + { + screen_fill_rect(scr, x, y, size, colour); + return; + } + + x1 = x + size.w - 1; + y1 = y + size.h - 1; + + p[0].x = x; p[0].y = y; + p[1].x = x1; p[1].y = y; + p[2].x = x1; p[2].y = y1; + p[3].x = x; p[3].y = y1; + p[4].x = x; p[4].y = y; + + screen_draw_lines(scr, p, 5, colour); +} + +void screen_draw_dashed_line(screen_t *scr, + int x0, + int y0, + int x1, + int y1, + int on, + int off, + colour_t colour) +{ + box_t clip_box; + box_t bounds; + int rx0, ry0, rx1, ry1; + int dx, dy; + int adx, ady; + int sx, sy; + int error, e2; + int period; + int phase; + + if (on <= 0) + return; + if (off < 0) + off = 0; + period = on + off; + + if (screen_get_clip(scr, &clip_box)) + return; /* invalid clipped screen */ + + rx0 = x0; + ry0 = y0; + rx1 = x1; + ry1 = y1; + if (line_clip(&clip_box, &rx0, &ry0, &rx1, &ry1) == 0) + return; + + screen_get_bounds(scr, &bounds); + (void) line_clip(&bounds, &x0, &y0, &x1, &y1); + + dx = x1 - x0; + adx = abs(dx); + sx = SGN(dx); + + dy = y1 - y0; + ady = -abs(dy); + sy = SGN(dy); + + error = adx + ady; + phase = 0; + + for (;;) + { + if (phase < on) + screen_set_pixel(scr, x0, y0, colour); + if (++phase >= period) + phase = 0; if (x0 == x1 && y0 == y1) break; @@ -597,7 +721,7 @@ void screen_draw_line_wu_float(screen_t *scr, return; /* invalid clipped screen */ /* This discards the fractional part of the coordinates so for now just use it - * to discard lines. screen_draw_pixel() will be doing clipping too later. */ + * to discard lines. screen_set_pixel() will be doing clipping too later. */ x0 = fx0; y0 = fy0; x1 = fx1; diff --git a/libraries/framebuf/screen/screen-fill-pattern.c b/libraries/framebuf/screen/screen-fill-pattern.c index d0613b6b..5d5ed8a7 100644 --- a/libraries/framebuf/screen/screen-fill-pattern.c +++ b/libraries/framebuf/screen/screen-fill-pattern.c @@ -1,4 +1,4 @@ -/* screen-fill-pattern.c -- fill a box with a repeating 8x8 two-colour pattern */ +/* framebuf/screen/screen-fill-pattern.c -- fill a box with a repeating 8x8 two-colour pattern */ #include #include @@ -19,9 +19,85 @@ static const unsigned char patterns[screen_PATTERN__LIMIT][8] = { 0x88, 0x44, 0x22, 0x11, 0x88, 0x44, 0x22, 0x11 }, /* DIAGONAL */ { 0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00 }, /* DOTS */ { 0xFF, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80 }, /* GRID */ - { 0x80, 0x41, 0x22, 0x14, 0x08, 0x14, 0x22, 0x41 } /* CROSSHATCH */ + { 0x80, 0x41, 0x22, 0x14, 0x08, 0x14, 0x22, 0x41 }, /* CROSSHATCH */ + + /* 8x8 ordered (Bayer) dither, one row per coverage level 0..64. Level N sets + * the pixels whose threshold in the recursively-built 8x8 matrix is < N. + * BAYER32 equals GREY50 and BAYER64 equals SOLID; kept in the run so a + * caller can index by level as screen_PATTERN_BAYER0 + level. */ + { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* BAYER0 */ + { 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, /* BAYER1 */ + { 0x80, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00 }, /* BAYER2 */ + { 0x80, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00 }, /* BAYER3 */ + { 0x88, 0x00, 0x00, 0x00, 0x88, 0x00, 0x00, 0x00 }, /* BAYER4 */ + { 0x88, 0x00, 0x20, 0x00, 0x88, 0x00, 0x00, 0x00 }, /* BAYER5 */ + { 0x88, 0x00, 0x20, 0x00, 0x88, 0x00, 0x02, 0x00 }, /* BAYER6 */ + { 0x88, 0x00, 0x20, 0x00, 0x88, 0x00, 0x22, 0x00 }, /* BAYER7 */ + { 0x88, 0x00, 0x22, 0x00, 0x88, 0x00, 0x22, 0x00 }, /* BAYER8 */ + { 0x88, 0x00, 0xA2, 0x00, 0x88, 0x00, 0x22, 0x00 }, /* BAYER9 */ + { 0x88, 0x00, 0xA2, 0x00, 0x88, 0x00, 0x2A, 0x00 }, /* BAYER10 */ + { 0x88, 0x00, 0xA2, 0x00, 0x88, 0x00, 0xAA, 0x00 }, /* BAYER11 */ + { 0x88, 0x00, 0xAA, 0x00, 0x88, 0x00, 0xAA, 0x00 }, /* BAYER12 */ + { 0xA8, 0x00, 0xAA, 0x00, 0x88, 0x00, 0xAA, 0x00 }, /* BAYER13 */ + { 0xA8, 0x00, 0xAA, 0x00, 0x8A, 0x00, 0xAA, 0x00 }, /* BAYER14 */ + { 0xA8, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00 }, /* BAYER15 */ + { 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00 }, /* BAYER16 */ + { 0xAA, 0x40, 0xAA, 0x00, 0xAA, 0x00, 0xAA, 0x00 }, /* BAYER17 */ + { 0xAA, 0x40, 0xAA, 0x00, 0xAA, 0x04, 0xAA, 0x00 }, /* BAYER18 */ + { 0xAA, 0x40, 0xAA, 0x00, 0xAA, 0x44, 0xAA, 0x00 }, /* BAYER19 */ + { 0xAA, 0x44, 0xAA, 0x00, 0xAA, 0x44, 0xAA, 0x00 }, /* BAYER20 */ + { 0xAA, 0x44, 0xAA, 0x10, 0xAA, 0x44, 0xAA, 0x00 }, /* BAYER21 */ + { 0xAA, 0x44, 0xAA, 0x10, 0xAA, 0x44, 0xAA, 0x01 }, /* BAYER22 */ + { 0xAA, 0x44, 0xAA, 0x10, 0xAA, 0x44, 0xAA, 0x11 }, /* BAYER23 */ + { 0xAA, 0x44, 0xAA, 0x11, 0xAA, 0x44, 0xAA, 0x11 }, /* BAYER24 */ + { 0xAA, 0x44, 0xAA, 0x51, 0xAA, 0x44, 0xAA, 0x11 }, /* BAYER25 */ + { 0xAA, 0x44, 0xAA, 0x51, 0xAA, 0x44, 0xAA, 0x15 }, /* BAYER26 */ + { 0xAA, 0x44, 0xAA, 0x51, 0xAA, 0x44, 0xAA, 0x55 }, /* BAYER27 */ + { 0xAA, 0x44, 0xAA, 0x55, 0xAA, 0x44, 0xAA, 0x55 }, /* BAYER28 */ + { 0xAA, 0x54, 0xAA, 0x55, 0xAA, 0x44, 0xAA, 0x55 }, /* BAYER29 */ + { 0xAA, 0x54, 0xAA, 0x55, 0xAA, 0x45, 0xAA, 0x55 }, /* BAYER30 */ + { 0xAA, 0x54, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55 }, /* BAYER31 */ + { 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55 }, /* BAYER32 */ + { 0xAA, 0xD5, 0xAA, 0x55, 0xAA, 0x55, 0xAA, 0x55 }, /* BAYER33 */ + { 0xAA, 0xD5, 0xAA, 0x55, 0xAA, 0x5D, 0xAA, 0x55 }, /* BAYER34 */ + { 0xAA, 0xD5, 0xAA, 0x55, 0xAA, 0xDD, 0xAA, 0x55 }, /* BAYER35 */ + { 0xAA, 0xDD, 0xAA, 0x55, 0xAA, 0xDD, 0xAA, 0x55 }, /* BAYER36 */ + { 0xAA, 0xDD, 0xAA, 0x75, 0xAA, 0xDD, 0xAA, 0x55 }, /* BAYER37 */ + { 0xAA, 0xDD, 0xAA, 0x75, 0xAA, 0xDD, 0xAA, 0x57 }, /* BAYER38 */ + { 0xAA, 0xDD, 0xAA, 0x75, 0xAA, 0xDD, 0xAA, 0x77 }, /* BAYER39 */ + { 0xAA, 0xDD, 0xAA, 0x77, 0xAA, 0xDD, 0xAA, 0x77 }, /* BAYER40 */ + { 0xAA, 0xDD, 0xAA, 0xF7, 0xAA, 0xDD, 0xAA, 0x77 }, /* BAYER41 */ + { 0xAA, 0xDD, 0xAA, 0xF7, 0xAA, 0xDD, 0xAA, 0x7F }, /* BAYER42 */ + { 0xAA, 0xDD, 0xAA, 0xF7, 0xAA, 0xDD, 0xAA, 0xFF }, /* BAYER43 */ + { 0xAA, 0xDD, 0xAA, 0xFF, 0xAA, 0xDD, 0xAA, 0xFF }, /* BAYER44 */ + { 0xAA, 0xFD, 0xAA, 0xFF, 0xAA, 0xDD, 0xAA, 0xFF }, /* BAYER45 */ + { 0xAA, 0xFD, 0xAA, 0xFF, 0xAA, 0xDF, 0xAA, 0xFF }, /* BAYER46 */ + { 0xAA, 0xFD, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF }, /* BAYER47 */ + { 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF }, /* BAYER48 */ + { 0xEA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF, 0xAA, 0xFF }, /* BAYER49 */ + { 0xEA, 0xFF, 0xAA, 0xFF, 0xAE, 0xFF, 0xAA, 0xFF }, /* BAYER50 */ + { 0xEA, 0xFF, 0xAA, 0xFF, 0xEE, 0xFF, 0xAA, 0xFF }, /* BAYER51 */ + { 0xEE, 0xFF, 0xAA, 0xFF, 0xEE, 0xFF, 0xAA, 0xFF }, /* BAYER52 */ + { 0xEE, 0xFF, 0xBA, 0xFF, 0xEE, 0xFF, 0xAA, 0xFF }, /* BAYER53 */ + { 0xEE, 0xFF, 0xBA, 0xFF, 0xEE, 0xFF, 0xAB, 0xFF }, /* BAYER54 */ + { 0xEE, 0xFF, 0xBA, 0xFF, 0xEE, 0xFF, 0xBB, 0xFF }, /* BAYER55 */ + { 0xEE, 0xFF, 0xBB, 0xFF, 0xEE, 0xFF, 0xBB, 0xFF }, /* BAYER56 */ + { 0xEE, 0xFF, 0xFB, 0xFF, 0xEE, 0xFF, 0xBB, 0xFF }, /* BAYER57 */ + { 0xEE, 0xFF, 0xFB, 0xFF, 0xEE, 0xFF, 0xBF, 0xFF }, /* BAYER58 */ + { 0xEE, 0xFF, 0xFB, 0xFF, 0xEE, 0xFF, 0xFF, 0xFF }, /* BAYER59 */ + { 0xEE, 0xFF, 0xFF, 0xFF, 0xEE, 0xFF, 0xFF, 0xFF }, /* BAYER60 */ + { 0xFE, 0xFF, 0xFF, 0xFF, 0xEE, 0xFF, 0xFF, 0xFF }, /* BAYER61 */ + { 0xFE, 0xFF, 0xFF, 0xFF, 0xEF, 0xFF, 0xFF, 0xFF }, /* BAYER62 */ + { 0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }, /* BAYER63 */ + { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } /* BAYER64 */ }; +/* the enum leaves the 65 Bayer levels unnamed past BAYER0, so keep the table + * length pinned to it here */ +typedef char + patterns_len_matches_enum[(sizeof patterns / sizeof patterns[0]) == + screen_PATTERN__LIMIT ? 1 : -1]; + void screen_fill_pattern(screen_t *scr, const box_t *box, screen_pattern_t pattern, diff --git a/libraries/framebuf/screen/screen.c b/libraries/framebuf/screen/screen.c index e15fafdc..7cac1f59 100644 --- a/libraries/framebuf/screen/screen.c +++ b/libraries/framebuf/screen/screen.c @@ -1,4 +1,4 @@ -/* screen.c */ +/* framebuf/screen/screen.c */ #include #include diff --git a/libraries/framebuf/screen/test/screen-test.c b/libraries/framebuf/screen/test/screen-test.c index 2d14a006..2ffcdb38 100644 --- a/libraries/framebuf/screen/test/screen-test.c +++ b/libraries/framebuf/screen/test/screen-test.c @@ -1,4 +1,4 @@ -/* screen-test.c -- test screen drawing */ +/* framebuf/screen/test/screen-test.c -- test screen drawing */ #include #include @@ -10,6 +10,7 @@ #include "framebuf/pixelfmt.h" #include "framebuf/screen.h" #include "geom/box.h" +#include "geom/point.h" #include "utils/fxp.h" #include "test/all-tests.h" @@ -256,7 +257,7 @@ static const int np_rgb[3][3][3] = static pixelfmt_bgrx8888_t np_encode(testscreen_t *ts, int r, int g, int b) { testscreen_init(ts); - screen_draw_pixel(&ts->scr, 0, 0, colour_rgb(r, g, b)); + screen_set_pixel(&ts->scr, 0, 0, colour_rgb(r, g, b)); return ts->pixels[0]; } @@ -449,6 +450,188 @@ static result_t test_fill_pattern(void) /* ----------------------------------------------------------------------- */ +static result_t test_dashed_line(void) +{ + static testscreen_t ts; + static testscreen_t enc; + + int fg; + int x; + int on_count, off_count; + + fg = (int) np_encode(&enc, 255, 0, 0); + + /* Horizontal line y=10, x in [0,19], on=2 off=2: phase cycles + * 0,1 (drawn) 2,3 (skipped) starting at x=0. */ + testscreen_init(&ts); + screen_draw_dashed_line(&ts.scr, 0, 10, 19, 10, 2, 2, colour_rgb(255, 0, 0)); + + on_count = off_count = 0; + for (x = 0; x <= 19; x++) + { + int lit = (np_at(&ts, x, 10) == fg); + int want = ((x % 4) < 2); + if (lit != want) + { + printf("screen: dashed_line wrong at x=%d (lit=%d want=%d)\n", + x, lit, want); + return result_TEST_FAILED; + } + if (lit) on_count++; else off_count++; + } + if (on_count != 10 || off_count != 10) + { + printf("screen: dashed_line dash ratio off (on=%d off=%d)\n", + on_count, off_count); + return result_TEST_FAILED; + } + + /* off <= 0 gives a solid line. */ + testscreen_init(&ts); + screen_draw_dashed_line(&ts.scr, 0, 5, 9, 5, 3, 0, colour_rgb(255, 0, 0)); + for (x = 0; x <= 9; x++) + { + if (np_at(&ts, x, 5) != fg) + { + printf("screen: dashed_line with off=0 left a gap at x=%d\n", x); + return result_TEST_FAILED; + } + } + + /* on <= 0 draws nothing. */ + testscreen_init(&ts); + screen_draw_dashed_line(&ts.scr, 0, 7, 9, 7, 0, 4, colour_rgb(255, 0, 0)); + for (x = 0; x <= 9; x++) + { + if (np_at(&ts, x, 7) != (int) (pixelfmt_bgrx8888_t) BACKGROUND) + { + printf("screen: dashed_line with on=0 drew a pixel at x=%d\n", x); + return result_TEST_FAILED; + } + } + + return result_TEST_PASSED; +} + +/* ----------------------------------------------------------------------- */ + +static result_t test_draw_lines(void) +{ + static testscreen_t ts; + static testscreen_t enc; + + const point_t chain[] = { { 4, 4 }, { 20, 4 }, { 20, 20 } }; + int fg, bg; + int x, y; + + fg = (int) np_encode(&enc, 255, 0, 0); + bg = (int) (pixelfmt_bgrx8888_t) BACKGROUND; + + /* An open two-segment polyline: both segments drawn, the shared joint + * pixel lit, nothing else. */ + testscreen_init(&ts); + screen_draw_lines(&ts.scr, chain, NELEMS(chain), colour_rgb(255, 0, 0)); + + for (x = 4; x <= 20; x++) + if (np_at(&ts, x, 4) != fg) + { + printf("screen: draw_lines gap on segment 1 at x=%d\n", x); + return result_TEST_FAILED; + } + for (y = 4; y <= 20; y++) + if (np_at(&ts, 20, y) != fg) + { + printf("screen: draw_lines gap on segment 2 at y=%d\n", y); + return result_TEST_FAILED; + } + if (np_at(&ts, 5, 5) != bg || np_at(&ts, 4, 20) != bg) + { + printf("screen: draw_lines drew outside the polyline\n"); + return result_TEST_FAILED; + } + + /* Fewer than two points is a no-op. */ + testscreen_init(&ts); + screen_draw_lines(&ts.scr, chain, 1, colour_rgb(255, 0, 0)); + screen_draw_lines(&ts.scr, NULL, 5, colour_rgb(255, 0, 0)); + for (y = 0; y < HEIGHT; y++) + for (x = 0; x < WIDTH; x++) + if (np_at(&ts, x, y) != bg) + { + printf("screen: draw_lines with <2 points drew at (%d,%d)\n", x, y); + return result_TEST_FAILED; + } + + return result_TEST_PASSED; +} + +/* ----------------------------------------------------------------------- */ + +static result_t test_draw_rect(void) +{ + static testscreen_t ts; + static testscreen_t enc; + + int fg, bg; + int x, y; + + fg = (int) np_encode(&enc, 0, 255, 0); + bg = (int) (pixelfmt_bgrx8888_t) BACKGROUND; + + /* 10x8 outline at (5,5): edges lit, interior and exterior background. + * size is inclusive, so the far edges are at x=14, y=12. */ + testscreen_init(&ts); + screen_draw_rect(&ts.scr, 5, 5, SIZE2D(10, 8), colour_rgb(0, 255, 0)); + + for (x = 5; x <= 14; x++) + if (np_at(&ts, x, 5) != fg || np_at(&ts, x, 12) != fg) + { + printf("screen: draw_rect horizontal edge gap at x=%d\n", x); + return result_TEST_FAILED; + } + for (y = 5; y <= 12; y++) + if (np_at(&ts, 5, y) != fg || np_at(&ts, 14, y) != fg) + { + printf("screen: draw_rect vertical edge gap at y=%d\n", y); + return result_TEST_FAILED; + } + if (np_at(&ts, 9, 8) != bg) /* interior */ + { + printf("screen: draw_rect filled its interior\n"); + return result_TEST_FAILED; + } + if (np_at(&ts, 4, 5) != bg || np_at(&ts, 15, 5) != bg || + np_at(&ts, 5, 4) != bg || np_at(&ts, 5, 13) != bg) + { + printf("screen: draw_rect drew outside the rect\n"); + return result_TEST_FAILED; + } + + /* Degenerate size (<= 1 in an axis) falls back to a filled rect. */ + testscreen_init(&ts); + screen_draw_rect(&ts.scr, 3, 3, SIZE2D(1, 6), colour_rgb(0, 255, 0)); + for (y = 3; y <= 8; y++) + if (np_at(&ts, 3, y) != fg) + { + printf("screen: draw_rect degenerate fallback gap at y=%d\n", y); + return result_TEST_FAILED; + } + + /* Honours the screen clip. */ + testscreen_init(&ts); + ts.scr.clip = (box_t) { 0, 0, 10, 64 }; + screen_draw_rect(&ts.scr, 5, 5, SIZE2D(10, 8), colour_rgb(0, 255, 0)); + if (np_at(&ts, 5, 5) != fg || np_at(&ts, 14, 5) != bg) + { + printf("screen: draw_rect ignored the screen clip\n"); + return result_TEST_FAILED; + } + + return result_TEST_PASSED; +} + +/* ----------------------------------------------------------------------- */ + result_t screen_test(const char *resources) { typedef result_t (*screentestfn)(void); @@ -459,7 +642,10 @@ result_t screen_test(const char *resources) test_clipping_still_happens, test_wu_fix8_extreme_coords, test_ninepatch, - test_fill_pattern + test_fill_pattern, + test_dashed_line, + test_draw_lines, + test_draw_rect }; result_t rc; diff --git a/libraries/framebuf/span-registry/get.c b/libraries/framebuf/span-registry/get.c index b68e5501..52f99df7 100644 --- a/libraries/framebuf/span-registry/get.c +++ b/libraries/framebuf/span-registry/get.c @@ -1,4 +1,4 @@ -/* get.c */ +/* framebuf/span-registry/get.c */ #include diff --git a/libraries/framebuf/span-registry/regdata.h b/libraries/framebuf/span-registry/regdata.h index aae1c27f..37b3fe15 100644 --- a/libraries/framebuf/span-registry/regdata.h +++ b/libraries/framebuf/span-registry/regdata.h @@ -1,4 +1,4 @@ -/* regdata.h */ +/* framebuf/span-registry/regdata.h */ #ifndef SPAN_REGISTRY_REGDATA_H #define SPAN_REGISTRY_REGDATA_H diff --git a/libraries/framebuf/span/all8888-generic.c b/libraries/framebuf/span/all8888-generic.c index 0306fed6..9d34b5f1 100644 --- a/libraries/framebuf/span/all8888-generic.c +++ b/libraries/framebuf/span/all8888-generic.c @@ -1,4 +1,4 @@ -/* all8888-blend.c -- alpha blending common to all 8888 formats */ +/* framebuf/span/all8888-generic.c -- alpha blending common to all 8888 formats */ #include "base/utils.h" diff --git a/libraries/framebuf/span/all8888.c b/libraries/framebuf/span/all8888.c index 9f9f2f04..51922e33 100644 --- a/libraries/framebuf/span/all8888.c +++ b/libraries/framebuf/span/all8888.c @@ -1,4 +1,4 @@ -/* all8888.c */ +/* framebuf/span/all8888.c */ #include diff --git a/libraries/framebuf/span/all8888.h b/libraries/framebuf/span/all8888.h index f5b285b4..028e41c7 100644 --- a/libraries/framebuf/span/all8888.h +++ b/libraries/framebuf/span/all8888.h @@ -1,4 +1,4 @@ -/* all8888.h -- span handlers common to all 8888 formats */ +/* framebuf/span/all8888.h -- span handlers common to all 8888 formats */ #ifndef SPAN_ALL8888_H #define SPAN_ALL8888_H diff --git a/libraries/framebuf/span/bgrx8888.c b/libraries/framebuf/span/bgrx8888.c index 25aaea02..bafceaff 100644 --- a/libraries/framebuf/span/bgrx8888.c +++ b/libraries/framebuf/span/bgrx8888.c @@ -1,4 +1,4 @@ -/* bgrx8888.c */ +/* framebuf/span/bgrx8888.c */ #include diff --git a/libraries/framebuf/span/p4.c b/libraries/framebuf/span/p4.c index eb401a8d..d35e0834 100644 --- a/libraries/framebuf/span/p4.c +++ b/libraries/framebuf/span/p4.c @@ -1,4 +1,4 @@ -/* p4.c -- P4 (4bpp paletted) format plot methods */ +/* framebuf/span/p4.c -- P4 (4bpp paletted) format plot methods */ #include @@ -14,7 +14,7 @@ /* pixel values here are unpacked palette indices, one per byte (not the * two-nibbles-per-byte layout used in screen memory); packing/unpacking * into the actual screen bytes is the caller's job, as with - * screen_draw_pixel's other pixel formats. src2 is an array of colour_t, + * screen_set_pixel's other pixel formats. src2 is an array of colour_t, * not pre-quantised pixels, so the blend happens in full RGB precision * before re-quantising to the nearest palette entry. */ static void span_p4_blendconst(void *vdst, diff --git a/libraries/framebuf/span/rgbx8888.c b/libraries/framebuf/span/rgbx8888.c index 01424443..6f1efe32 100644 --- a/libraries/framebuf/span/rgbx8888.c +++ b/libraries/framebuf/span/rgbx8888.c @@ -1,4 +1,4 @@ -/* rgbx8888.c */ +/* framebuf/span/rgbx8888.c */ #include diff --git a/libraries/framebuf/span/xbgr8888.c b/libraries/framebuf/span/xbgr8888.c index 1dc85442..a2c8acde 100644 --- a/libraries/framebuf/span/xbgr8888.c +++ b/libraries/framebuf/span/xbgr8888.c @@ -1,4 +1,4 @@ -/* xbgr8888.c */ +/* framebuf/span/xbgr8888.c */ #include diff --git a/libraries/geom/box/clipped.c b/libraries/geom/box/clipped.c index 0348ba90..fc575f6b 100644 --- a/libraries/geom/box/clipped.c +++ b/libraries/geom/box/clipped.c @@ -1,4 +1,4 @@ -/* clipped.c -- calculate clipped away edge sizes */ +/* geom/box/clipped.c -- calculate clipped away edge sizes */ #include "base/utils.h" diff --git a/libraries/geom/box/contains-box.c b/libraries/geom/box/contains-box.c index 868ac77e..83b206b7 100644 --- a/libraries/geom/box/contains-box.c +++ b/libraries/geom/box/contains-box.c @@ -1,4 +1,4 @@ -/* contains-box.c -- return true if box "inside" is contained by box "outside" */ +/* geom/box/contains-box.c -- return true if box "inside" is contained by box "outside" */ #include "geom/box.h" diff --git a/libraries/geom/box/contains-point.c b/libraries/geom/box/contains-point.c index b13ebf97..650f225c 100644 --- a/libraries/geom/box/contains-point.c +++ b/libraries/geom/box/contains-point.c @@ -1,4 +1,4 @@ -/* contains-point.c -- return true if "box" contains the point (x,y) */ +/* geom/box/contains-point.c -- return true if "box" contains the point (x,y) */ #include "base/utils.h" diff --git a/libraries/geom/box/could-hold.c b/libraries/geom/box/could-hold.c index 203c5a5c..f787ec9d 100644 --- a/libraries/geom/box/could-hold.c +++ b/libraries/geom/box/could-hold.c @@ -1,4 +1,4 @@ -/* could-hold.c -- return true if "box" can hold a box of size (w,h) */ +/* geom/box/could-hold.c -- return true if "box" can hold a box of size (w,h) */ #include "geom/box.h" diff --git a/libraries/geom/box/extend.c b/libraries/geom/box/extend.c index 39005c06..9d9f390c 100644 --- a/libraries/geom/box/extend.c +++ b/libraries/geom/box/extend.c @@ -1,4 +1,4 @@ -/* extend.c -- extend box "b" to include (x,y). */ +/* geom/box/extend.c -- extend box "b" to include (x,y). */ #include diff --git a/libraries/geom/box/grow.c b/libraries/geom/box/grow.c index dfbcf6d7..e07aee9a 100644 --- a/libraries/geom/box/grow.c +++ b/libraries/geom/box/grow.c @@ -1,4 +1,4 @@ -/* grow.c -- increases the size of "box" by "change" */ +/* geom/box/grow.c -- increases the size of "box" by "change" */ #include "geom/box.h" diff --git a/libraries/geom/box/intersection.c b/libraries/geom/box/intersection.c index 4d1bdf7a..d8af0083 100644 --- a/libraries/geom/box/intersection.c +++ b/libraries/geom/box/intersection.c @@ -1,4 +1,4 @@ -/* intersection.c -- compute "c" the result of intersecting boxes "a" and "b" */ +/* geom/box/intersection.c -- compute "c" the result of intersecting boxes "a" and "b" */ #include "base/utils.h" diff --git a/libraries/geom/box/intersects.c b/libraries/geom/box/intersects.c index c9e6b686..e4360008 100644 --- a/libraries/geom/box/intersects.c +++ b/libraries/geom/box/intersects.c @@ -1,4 +1,4 @@ -/* intersects.c -- return true if box "a" overlaps box "b" */ +/* geom/box/intersects.c -- return true if box "a" overlaps box "b" */ #include "geom/box.h" diff --git a/libraries/geom/box/is-empty.c b/libraries/geom/box/is-empty.c index 8fcaedb2..57d5f14e 100644 --- a/libraries/geom/box/is-empty.c +++ b/libraries/geom/box/is-empty.c @@ -1,4 +1,4 @@ -/* is-empty.c -- return whether the specified box is empty */ +/* geom/box/is-empty.c -- return whether the specified box is empty */ #include "geom/box.h" diff --git a/libraries/geom/box/reset.c b/libraries/geom/box/reset.c index 0a785b2f..88286d20 100644 --- a/libraries/geom/box/reset.c +++ b/libraries/geom/box/reset.c @@ -1,4 +1,4 @@ -/* reset.c -- set box 'b' to be invalid */ +/* geom/box/reset.c -- set box 'b' to be invalid */ #include diff --git a/libraries/geom/box/round.c b/libraries/geom/box/round.c index 6979b8a5..e9838983 100644 --- a/libraries/geom/box/round.c +++ b/libraries/geom/box/round.c @@ -1,4 +1,4 @@ -/* round.c -- round a box's coords so they're a multiple of log2 x,y */ +/* geom/box/round.c -- round a box's coords so they're a multiple of log2 x,y */ #include "base/utils.h" diff --git a/libraries/geom/box/round4.c b/libraries/geom/box/round4.c index 89ed7bf1..66ee929d 100644 --- a/libraries/geom/box/round4.c +++ b/libraries/geom/box/round4.c @@ -1,4 +1,4 @@ -/* round4.c -- round a box's coords so they're a multiple of 4 */ +/* geom/box/round4.c -- round a box's coords so they're a multiple of 4 */ #include "geom/box.h" diff --git a/libraries/geom/box/size.c b/libraries/geom/box/size.c index d5b0fd28..4a4d7710 100644 --- a/libraries/geom/box/size.c +++ b/libraries/geom/box/size.c @@ -1,4 +1,4 @@ -/* size.c -- return the size of the specified box */ +/* geom/box/size.c -- return the size of the specified box */ #include "geom/box.h" #include "geom/size.h" diff --git a/libraries/geom/box/test/box-test.c b/libraries/geom/box/test/box-test.c index efd8c69f..9b474abd 100644 --- a/libraries/geom/box/test/box-test.c +++ b/libraries/geom/box/test/box-test.c @@ -1,3 +1,4 @@ +/* geom/box/test/box-test.c */ #include #include diff --git a/libraries/geom/box/translated.c b/libraries/geom/box/translated.c index f8d2d195..c84d56d4 100644 --- a/libraries/geom/box/translated.c +++ b/libraries/geom/box/translated.c @@ -1,4 +1,4 @@ -/* translated.c -- translate box "b" by (x,y) producing new box "t" */ +/* geom/box/translated.c -- translate box "b" by (x,y) producing new box "t" */ #include "base/utils.h" diff --git a/libraries/geom/box/union.c b/libraries/geom/box/union.c index 36b431f5..9944ffe9 100644 --- a/libraries/geom/box/union.c +++ b/libraries/geom/box/union.c @@ -1,4 +1,4 @@ -/* union.c -- return a box "c" that contains both boxes "a" and "b" */ +/* geom/box/union.c -- return a box "c" that contains both boxes "a" and "b" */ #include "base/utils.h" diff --git a/libraries/geom/layout/layout.c b/libraries/geom/layout/layout.c index 39081501..2c23fe1a 100644 --- a/libraries/geom/layout/layout.c +++ b/libraries/geom/layout/layout.c @@ -1,4 +1,4 @@ -/* layout.c -- laying out elements using the packer */ +/* geom/layout/layout.c -- laying out elements using the packer */ #include diff --git a/libraries/geom/layout/test/layout-test.c b/libraries/geom/layout/test/layout-test.c index b1db8331..cad978ff 100644 --- a/libraries/geom/layout/test/layout-test.c +++ b/libraries/geom/layout/test/layout-test.c @@ -1,3 +1,4 @@ +/* geom/layout/test/layout-test.c */ #include #include diff --git a/libraries/geom/line/line.c b/libraries/geom/line/line.c index 107b84a9..3d925bb7 100644 --- a/libraries/geom/line/line.c +++ b/libraries/geom/line/line.c @@ -1,4 +1,4 @@ -/* line.c -- Cohen-Sutherland line clipping algorithm */ +/* geom/line/line.c -- Cohen-Sutherland line clipping algorithm */ #include "base/utils.h" #include "geom/box.h" diff --git a/libraries/geom/packer/impl.h b/libraries/geom/packer/impl.h index f8beff00..119ce176 100644 --- a/libraries/geom/packer/impl.h +++ b/libraries/geom/packer/impl.h @@ -1,4 +1,4 @@ -/* impl.h -- box packing for layout */ +/* geom/packer/impl.h -- box packing for layout */ #ifndef IMPL_H #define IMPL_H @@ -21,6 +21,12 @@ struct packer int allocedareas; int usedareas; + box_t *placed; /* boxes carved out by packer_place_*, kept + so packer_release can rebuild the free + list minus the ones still live */ + int allocedplaced; + int nplaced; + box_t dims; /* page size */ box_t margins; /* page size minus margins (but not the diff --git a/libraries/geom/packer/packer.c b/libraries/geom/packer/packer.c index a542c5e2..324919fc 100644 --- a/libraries/geom/packer/packer.c +++ b/libraries/geom/packer/packer.c @@ -1,4 +1,4 @@ -/* packer.c -- box packing for layout */ +/* geom/packer/packer.c -- box packing for layout */ #include #include @@ -40,6 +40,10 @@ packer_t *packer_create(const box_t *dims) p->areas[0] = *dims; p->usedareas = 1; + p->placed = NULL; + p->allocedplaced = 0; + p->nplaced = 0; + p->dims = *dims; p->margins = *dims; @@ -61,6 +65,7 @@ void packer_destroy(packer_t *doomed) if (doomed == NULL) return; + free(doomed->placed); free(doomed->areas); free(doomed); } @@ -396,9 +401,12 @@ int packer_next_width(packer_t *packer, packer_loc_t loc) /* ----------------------------------------------------------------------- */ +static result_t note_placed(packer_t *packer, const box_t *area); + result_t packer_place_at(packer_t *packer, const box_t *area) { - box_t b; + result_t err; + box_t b; /* subtract the margins */ @@ -407,21 +415,82 @@ result_t packer_place_at(packer_t *packer, const box_t *area) if (box_is_empty(&b)) return result_PACKER_EMPTY; - return remove_area(packer, &b); + err = remove_area(packer, &b); + if (err) + return err; + + return note_placed(packer, &b); +} + +/* Rebuild the free list from scratch: the whole margin, minus every box + * currently recorded as placed. Called after a release so repeated + * place/release cycles reclaim the whole page exactly, rather than leaving + * the free list fragmented into unusable slivers. O(placed^2); 'placed' is + * the live box count, a handful in practice. */ +static result_t rebuild_free_list(packer_t *packer) +{ + result_t err; + int i; + + packer->areas[0] = packer->margins; + packer->usedareas = 1; + packer->sorted = 0; + + for (i = 0; i < packer->nplaced; i++) + { + err = remove_area(packer, &packer->placed[i]); + if (err) + return err; + } + + return result_OK; +} + +/* Record 'area' (a rect just carved out of the free list) as placed, so a + * later packer_release can rebuild the free list without it. */ +static result_t note_placed(packer_t *packer, const box_t *area) +{ + if (packer->nplaced + 1 > packer->allocedplaced) + { + int n; + box_t *p; + + n = packer->allocedplaced ? packer->allocedplaced * 2 : INITIALAREAS; + p = realloc(packer->placed, n * sizeof(*p)); + if (p == NULL) + return result_OOM; + + packer->placed = p; + packer->allocedplaced = n; + } + + packer->placed[packer->nplaced++] = *area; + + return result_OK; } result_t packer_release(packer_t *packer, const box_t *area) { box_t b; + int i; (void) box_intersection(&packer->margins, area, &b); if (box_is_empty(&b)) return result_PACKER_EMPTY; - /* ponytail: no coalescing with neighbouring free areas -- a released - * fragment is usable on its own, which is all whole-box placement needs; - * tile-style reuse spanning two released areas would need a merge pass */ + /* forget the matching placed box (exact match, else closest by origin); + * a release with no recorded placement just adds free space directly */ + for (i = 0; i < packer->nplaced; i++) + { + if (packer->placed[i].x0 == b.x0 && packer->placed[i].y0 == b.y0 && + packer->placed[i].x1 == b.x1 && packer->placed[i].y1 == b.y1) + { + packer->placed[i] = packer->placed[--packer->nplaced]; + return rebuild_free_list(packer); + } + } + return add_area(packer, &b); } @@ -520,6 +589,10 @@ result_t packer_place_by(packer_t *packer, if (err) return err; + err = note_placed(packer, &consume); + if (err) + return err; + if (pos) *pos = &packer->placed_area; @@ -579,6 +652,11 @@ result_t packer_clear(packer_t *packer, packer_cleardir_t clear) if (err) return err; + /* the cleared swathe crosses placed boxes without a matching release; drop + * the placed list so a later packer_release falls back to adding free + * space directly rather than resurrecting cleared area on a rebuild */ + packer->nplaced = 0; + return result_OK; } diff --git a/libraries/geom/packer/test/packer-test.c b/libraries/geom/packer/test/packer-test.c index 523c4f50..ac0015bc 100644 --- a/libraries/geom/packer/test/packer-test.c +++ b/libraries/geom/packer/test/packer-test.c @@ -1,3 +1,4 @@ +/* geom/packer/test/packer-test.c */ #include #include @@ -507,6 +508,119 @@ static int test3(void) return 0; +failure: + + packer_destroy(packer); + return 1; +} + +/* count_and_last: packer_map callback recording the free-area count and the + * last area visited. */ +struct scan +{ + int count; + box_t last; +}; + +static result_t count_and_last(const box_t *area, void *opaque) +{ + struct scan *s = opaque; + + s->count++; + s->last = *area; + + return result_OK; +} + +/* packer_release coalescing: filling a page with mixed sizes then releasing + * every slot must collapse the free list back to the whole page, so a + * full-page box fits again. */ +static int test4(void) +{ + static const box_t pagedims = { 0, 0, 300, 300 }; + + static const int w[6] = { 40, 55, 40, 70, 40, 50 }; + static const int h[6] = { 30, 40, 30, 35, 30, 45 }; + + packer_t *packer; + const box_t *slot; + box_t held[6]; + box_t full; + struct scan s; + result_t err; + int cycle; + int i; + + printf("test4: packer_release coalescing\n"); + + packer = packer_create(&pagedims); + if (packer == NULL) + return 1; + + packer_set_gutter(packer, 4); + + /* several open/close rounds: the free list must not drift */ + for (cycle = 0; cycle < 4; cycle++) + { + for (i = 0; i < 6; i++) + { + err = packer_place_by(packer, packer_LOC_BOTTOM_LEFT, w[i], h[i], &slot); + if (err) + { + printf("test4: cycle %d place %d failed (%d)\n", cycle, i, err); + goto failure; + } + + /* release what place_by consumed: the box plus its gutter strip */ + held[i] = *slot; + held[i].x1 = slot->x1 + 4; + held[i].y1 = slot->y1 + 4; + } + + for (i = 5; i >= 0; i--) + { + err = packer_release(packer, &held[i]); + if (err) + { + printf("test4: cycle %d release %d failed (%d)\n", cycle, i, err); + goto failure; + } + } + + /* everything is back: exactly one free area, the whole margin */ + s.count = 0; + err = packer_map(packer, count_and_last, &s); + if (err) + goto failure; + + if (s.count != 1) + { + printf("test4: cycle %d left %d free areas, wanted 1\n", cycle, s.count); + goto failure; + } + + if (s.last.x0 != 0 || s.last.y0 != 0 || + s.last.x1 != 300 || s.last.y1 != 300) + { + printf("test4: cycle %d free area <%d,%d-%d,%d>, wanted <0,0-300,300>\n", + cycle, s.last.x0, s.last.y0, s.last.x1, s.last.y1); + goto failure; + } + } + + /* and a box spanning the whole page now fits */ + full.x0 = 0; full.y0 = 0; full.x1 = 300; full.y1 = 300; + err = packer_place_at(packer, &full); + if (err) + { + printf("test4: full-page placement after reclaim failed (%d)\n", err); + goto failure; + } + + packer_destroy(packer); + return 0; + + failure: packer_destroy(packer); @@ -531,6 +645,10 @@ result_t packer_test(const char *resources) if (err) goto failure; + err = test4(); + if (err) + goto failure; + return result_TEST_PASSED; diff --git a/libraries/io/path/path.c b/libraries/io/path/path.c index d1bb657c..42acdc1c 100644 --- a/libraries/io/path/path.c +++ b/libraries/io/path/path.c @@ -1,4 +1,4 @@ -/* path.c */ +/* io/path/path.c */ #include #include diff --git a/libraries/io/stream/stream-mem.c b/libraries/io/stream/stream-mem.c index c490504f..81ac0c0f 100644 --- a/libraries/io/stream/stream-mem.c +++ b/libraries/io/stream/stream-mem.c @@ -1,4 +1,4 @@ -/* stream-mem.c -- memory block IO stream implementation */ +/* io/stream/stream-mem.c -- memory block IO stream implementation */ #include #include diff --git a/libraries/io/stream/stream-mtfcomp.c b/libraries/io/stream/stream-mtfcomp.c index 3c0ea8b4..e8971594 100644 --- a/libraries/io/stream/stream-mtfcomp.c +++ b/libraries/io/stream/stream-mtfcomp.c @@ -1,4 +1,4 @@ -/* stream-mtfcomp.c -- "Move to front" adaptive compression stream */ +/* io/stream/stream-mtfcomp.c -- "Move to front" adaptive compression stream */ #include #include diff --git a/libraries/io/stream/stream-packbitscomp.c b/libraries/io/stream/stream-packbitscomp.c index a9630c43..3cb529a9 100644 --- a/libraries/io/stream/stream-packbitscomp.c +++ b/libraries/io/stream/stream-packbitscomp.c @@ -1,4 +1,4 @@ -/* stream-packbitscomp.c -- PackBits compression */ +/* io/stream/stream-packbitscomp.c -- PackBits compression */ #include #include diff --git a/libraries/io/stream/stream-packbitsdecomp.c b/libraries/io/stream/stream-packbitsdecomp.c index d4a75004..3dde4a9b 100644 --- a/libraries/io/stream/stream-packbitsdecomp.c +++ b/libraries/io/stream/stream-packbitsdecomp.c @@ -1,4 +1,4 @@ -/* stream-packbitsdecomp.c -- PackBits decompression */ +/* io/stream/stream-packbitsdecomp.c -- PackBits decompression */ #include #include @@ -170,7 +170,9 @@ static stream_size_t stream_packbitsdecomp_fill(stream_t *s) return stream_remaining(s); } -result_t stream_packbitsdecomp_create(stream_t *input, int bufsz, stream_t **s) +result_t stream_packbitsdecomp_create(stream_t *input, + int bufsz, + stream_t **s) { stream_packbitsdecomp_t *sp; diff --git a/libraries/io/stream/stream-stdio.c b/libraries/io/stream/stream-stdio.c index 8d6074bb..8ed97dcf 100644 --- a/libraries/io/stream/stream-stdio.c +++ b/libraries/io/stream/stream-stdio.c @@ -1,4 +1,4 @@ -/* stream-stdio.c -- C standard IO stream implementation */ +/* io/stream/stream-stdio.c -- C standard IO stream implementation */ #include #include diff --git a/libraries/io/stream/stream.c b/libraries/io/stream/stream.c index 6101315f..b28966f6 100644 --- a/libraries/io/stream/stream.c +++ b/libraries/io/stream/stream.c @@ -1,4 +1,4 @@ -/* stream.c -- stream system support functions */ +/* io/stream/stream.c -- stream system support functions */ #include #include diff --git a/libraries/io/stream/test/stream-test.c b/libraries/io/stream/test/stream-test.c index d100a674..696dc19d 100644 --- a/libraries/io/stream/test/stream-test.c +++ b/libraries/io/stream/test/stream-test.c @@ -1,3 +1,4 @@ +/* io/stream/test/stream-test.c */ #include #include diff --git a/libraries/test/txtscr/txtscr.c b/libraries/test/txtscr/txtscr.c index 2a0c5377..d5670868 100644 --- a/libraries/test/txtscr/txtscr.c +++ b/libraries/test/txtscr/txtscr.c @@ -1,4 +1,4 @@ -/* txtscr.c -- text format 'screen' */ +/* test/txtscr/txtscr.c -- text format 'screen' */ #include #include diff --git a/libraries/text/bmtext/draw.c b/libraries/text/bmtext/draw.c new file mode 100644 index 00000000..1f508354 --- /dev/null +++ b/libraries/text/bmtext/draw.c @@ -0,0 +1,32 @@ +/* text/bmtext/draw.c -- draw pre-laid-out bitmap-font lines */ + +#include + +#include "framebuf/bmfont.h" +#include "framebuf/screen.h" +#include "geom/point.h" + +#include "text/bmtext.h" + +void bmtext_draw(bmfont_t *font, + screen_t *scr, + const bmtext_line_t *lines, + int nlines, + colour_t fg, + colour_t bg, + int leading, + point_t origin) +{ + int font_width, font_height; + point_t pos; + int i; + + bmfont_get_info(font, &font_width, &font_height); + + pos = origin; + for (i = 0; i < nlines; i++) + { + bmfont_draw(font, scr, lines[i].str, lines[i].len, fg, bg, &pos, NULL); + pos.y += font_height + leading; + } +} diff --git a/libraries/text/bmtext/layout.c b/libraries/text/bmtext/layout.c new file mode 100644 index 00000000..b6f30b67 --- /dev/null +++ b/libraries/text/bmtext/layout.c @@ -0,0 +1,53 @@ +/* text/bmtext/layout.c -- break a string into pixel-fitted lines */ + +#include + +#include "framebuf/bmfont.h" + +#include "text/bmtext.h" + +int bmtext_layout(bmfont_t *font, + const char *string, + int stringlen, + int wrap_width, + bmtext_line_t *lines, + int max) +{ + int nlines; + + nlines = 0; + + while (stringlen > 0 && nlines < max) + { + int absolute_break; + bmfont_width_t width; + int friendly_break; + + bmfont_measure(font, string, stringlen, wrap_width, &absolute_break, &width); + + friendly_break = absolute_break; + if (absolute_break < stringlen) + { + /* line didn't fit whole: try to break at the last space within it */ + for (friendly_break = absolute_break - 1; friendly_break > 0; friendly_break--) + if (isspace((unsigned char) string[friendly_break])) + break; + if (friendly_break <= 0) + friendly_break = absolute_break; /* no space to break at: hard break */ + } + + lines[nlines].str = string; + lines[nlines].len = friendly_break; + nlines++; + + string += friendly_break; + stringlen -= friendly_break; + while (stringlen > 0 && isspace((unsigned char) *string)) + { + string++; + stringlen--; + } + } + + return nlines; +} diff --git a/libraries/text/txtfmt/create.c b/libraries/text/txtfmt/create.c index 19f2999a..47516ba7 100644 --- a/libraries/text/txtfmt/create.c +++ b/libraries/text/txtfmt/create.c @@ -1,4 +1,4 @@ -/* create.c -- txtfmt - text formatting */ +/* text/txtfmt/create.c -- text formatting */ #include #include diff --git a/libraries/text/txtfmt/destroy.c b/libraries/text/txtfmt/destroy.c index b176e220..aaed4e33 100644 --- a/libraries/text/txtfmt/destroy.c +++ b/libraries/text/txtfmt/destroy.c @@ -1,4 +1,4 @@ -/* destroy.c -- txtfmt - text formatting */ +/* text/txtfmt/destroy.c -- text formatting */ #include diff --git a/libraries/text/txtfmt/get-length.c b/libraries/text/txtfmt/get-length.c index 21e0a231..b3e2fd68 100644 --- a/libraries/text/txtfmt/get-length.c +++ b/libraries/text/txtfmt/get-length.c @@ -1,4 +1,4 @@ -/* get-length.c -- txtfmt - text formatting */ +/* text/txtfmt/get-length.c -- text formatting */ #include "text/txtfmt.h" diff --git a/libraries/text/txtfmt/get-line.c b/libraries/text/txtfmt/get-line.c index 4be628fb..247bd8f7 100644 --- a/libraries/text/txtfmt/get-line.c +++ b/libraries/text/txtfmt/get-line.c @@ -1,4 +1,4 @@ -/* get-line.c -- txtfmt - text formatting */ +/* text/txtfmt/get-line.c -- text formatting */ #include "text/txtfmt.h" diff --git a/libraries/text/txtfmt/get-nlines.c b/libraries/text/txtfmt/get-nlines.c index 03c0ba6a..c9bc3417 100644 --- a/libraries/text/txtfmt/get-nlines.c +++ b/libraries/text/txtfmt/get-nlines.c @@ -1,4 +1,4 @@ -/* get-nlines.c -- txtfmt - text formatting */ +/* text/txtfmt/get-nlines.c -- text formatting */ #include "text/txtfmt.h" diff --git a/libraries/text/txtfmt/get-wrapped-width.c b/libraries/text/txtfmt/get-wrapped-width.c index 06f1d8ce..2b104cd6 100644 --- a/libraries/text/txtfmt/get-wrapped-width.c +++ b/libraries/text/txtfmt/get-wrapped-width.c @@ -1,4 +1,4 @@ -/* get-wrapped-width.c -- txtfmt - text formatting */ +/* text/txtfmt/get-wrapped-width.c -- text formatting */ #include "text/txtfmt.h" diff --git a/libraries/text/txtfmt/impl.h b/libraries/text/txtfmt/impl.h index 7a4ced4d..3725bfa3 100644 --- a/libraries/text/txtfmt/impl.h +++ b/libraries/text/txtfmt/impl.h @@ -1,4 +1,4 @@ -/* impl.h -- txtfmt - text formatting */ +/* text/txtfmt/impl.h -- text formatting */ #ifndef IMPL_H #define IMPL_H diff --git a/libraries/text/txtfmt/print.c b/libraries/text/txtfmt/print.c index a6a898e0..13ef73de 100644 --- a/libraries/text/txtfmt/print.c +++ b/libraries/text/txtfmt/print.c @@ -1,4 +1,4 @@ -/* print.c -- txtfmt - text formatting */ +/* text/txtfmt/print.c -- text formatting */ #include diff --git a/libraries/text/txtfmt/test/txtfmt-test.c b/libraries/text/txtfmt/test/txtfmt-test.c index 9cc0edbd..a1f28b5f 100644 --- a/libraries/text/txtfmt/test/txtfmt-test.c +++ b/libraries/text/txtfmt/test/txtfmt-test.c @@ -1,4 +1,4 @@ -/* txtfmt-test.c -- txtfmt - text formatting */ +/* text/txtfmt/test/txtfmt-test.c -- text formatting */ #include diff --git a/libraries/text/txtfmt/wrap.c b/libraries/text/txtfmt/wrap.c index dc7632e3..8d64842d 100644 --- a/libraries/text/txtfmt/wrap.c +++ b/libraries/text/txtfmt/wrap.c @@ -1,4 +1,4 @@ -/* wrap.c -- txtfmt - text formatting */ +/* text/txtfmt/wrap.c -- text formatting */ #include #include diff --git a/libraries/utils/array/delelem.c b/libraries/utils/array/delelem.c index 35493cc9..5f5d55ea 100644 --- a/libraries/utils/array/delelem.c +++ b/libraries/utils/array/delelem.c @@ -1,3 +1,4 @@ +/* utils/array/delelem.c */ #include #include diff --git a/libraries/utils/array/delelems.c b/libraries/utils/array/delelems.c index aac00640..2b3ad7fc 100644 --- a/libraries/utils/array/delelems.c +++ b/libraries/utils/array/delelems.c @@ -1,3 +1,4 @@ +/* utils/array/delelems.c */ #include #include diff --git a/libraries/utils/array/grow.c b/libraries/utils/array/grow.c index 42952902..5dbb51a6 100644 --- a/libraries/utils/array/grow.c +++ b/libraries/utils/array/grow.c @@ -1,4 +1,4 @@ -/* grow.c -- grow an array */ +/* utils/array/grow.c -- grow an array */ #include #include diff --git a/libraries/utils/array/shrink.c b/libraries/utils/array/shrink.c index 0b8de40d..98828cd3 100644 --- a/libraries/utils/array/shrink.c +++ b/libraries/utils/array/shrink.c @@ -1,4 +1,4 @@ -/* shrink.c -- shrink wrap an array */ +/* utils/array/shrink.c -- shrink wrap an array */ #include #include diff --git a/libraries/utils/array/squeeze.c b/libraries/utils/array/squeeze.c index eef61de2..d4a71937 100644 --- a/libraries/utils/array/squeeze.c +++ b/libraries/utils/array/squeeze.c @@ -1,3 +1,4 @@ +/* utils/array/squeeze.c */ #include #include diff --git a/libraries/utils/array/stretch.c b/libraries/utils/array/stretch.c index b1064005..eac7377a 100644 --- a/libraries/utils/array/stretch.c +++ b/libraries/utils/array/stretch.c @@ -1,3 +1,4 @@ +/* utils/array/stretch.c */ #include #include diff --git a/libraries/utils/array/test/array-test.c b/libraries/utils/array/test/array-test.c index 537ee49f..c9e998fd 100644 --- a/libraries/utils/array/test/array-test.c +++ b/libraries/utils/array/test/array-test.c @@ -1,3 +1,4 @@ +/* utils/array/test/array-test.c */ #include #include diff --git a/libraries/utils/barith/barith.c b/libraries/utils/barith/barith.c index 07e95d44..711a3a17 100644 --- a/libraries/utils/barith/barith.c +++ b/libraries/utils/barith/barith.c @@ -1,4 +1,4 @@ -/* barith.c -- define all of barith */ +/* utils/barith/barith.c -- define all of barith */ #define BARITH_INLINE diff --git a/libraries/utils/bsearch/bsearch-impl.h b/libraries/utils/bsearch/bsearch-impl.h index 914142e3..6ee9ec54 100644 --- a/libraries/utils/bsearch/bsearch-impl.h +++ b/libraries/utils/bsearch/bsearch-impl.h @@ -1,4 +1,4 @@ -/* bsearch-impl.h -- binary searching arrays */ +/* utils/bsearch/bsearch-impl.h -- binary searching arrays */ #if !defined(TYPE) || !defined(NAME) #error TYPE and NAME must be defined. diff --git a/libraries/utils/bsearch/bsearch-int.c b/libraries/utils/bsearch/bsearch-int.c index eb0549ce..93b3fc07 100644 --- a/libraries/utils/bsearch/bsearch-int.c +++ b/libraries/utils/bsearch/bsearch-int.c @@ -1,4 +1,4 @@ -/* bsearch-int.c -- binary searching arrays */ +/* utils/bsearch/bsearch-int.c -- binary searching arrays */ #define TYPE int #define NAME bsearch_int diff --git a/libraries/utils/bsearch/bsearch-short.c b/libraries/utils/bsearch/bsearch-short.c index 528ca7d7..aa7c22c4 100644 --- a/libraries/utils/bsearch/bsearch-short.c +++ b/libraries/utils/bsearch/bsearch-short.c @@ -1,4 +1,4 @@ -/* bsearch-short.c -- binary searching arrays */ +/* utils/bsearch/bsearch-short.c -- binary searching arrays */ #define TYPE short #define NAME bsearch_short diff --git a/libraries/utils/bsearch/bsearch-uint.c b/libraries/utils/bsearch/bsearch-uint.c index 725cc581..1b73bd9b 100644 --- a/libraries/utils/bsearch/bsearch-uint.c +++ b/libraries/utils/bsearch/bsearch-uint.c @@ -1,4 +1,4 @@ -/* bsearch-uint.c -- binary searching arrays */ +/* utils/bsearch/bsearch-uint.c -- binary searching arrays */ #define TYPE unsigned int #define NAME bsearch_uint diff --git a/libraries/utils/bsearch/bsearch-ushort.c b/libraries/utils/bsearch/bsearch-ushort.c index 0cdc9304..eb30e4b8 100644 --- a/libraries/utils/bsearch/bsearch-ushort.c +++ b/libraries/utils/bsearch/bsearch-ushort.c @@ -1,4 +1,4 @@ -/* bsearch-ushort.c -- binary searching arrays */ +/* utils/bsearch/bsearch-ushort.c -- binary searching arrays */ #define TYPE unsigned short #define NAME bsearch_ushort diff --git a/libraries/utils/bsearch/test/bsearch-test.c b/libraries/utils/bsearch/test/bsearch-test.c index f2798af9..cbe6b7db 100644 --- a/libraries/utils/bsearch/test/bsearch-test.c +++ b/libraries/utils/bsearch/test/bsearch-test.c @@ -1,3 +1,4 @@ +/* utils/bsearch/test/bsearch-test.c */ #include #include diff --git a/libraries/utils/bytesex/rev-l-block.c b/libraries/utils/bytesex/rev-l-block.c index dd652f0d..887fa50f 100644 --- a/libraries/utils/bytesex/rev-l-block.c +++ b/libraries/utils/bytesex/rev-l-block.c @@ -1,4 +1,4 @@ -/* rev-l-block.c -- reversing bytesex */ +/* utils/bytesex/rev-l-block.c -- reversing bytesex */ #include diff --git a/libraries/utils/bytesex/rev-l-m.c b/libraries/utils/bytesex/rev-l-m.c index 3c9befb4..822fc495 100644 --- a/libraries/utils/bytesex/rev-l-m.c +++ b/libraries/utils/bytesex/rev-l-m.c @@ -1,4 +1,4 @@ -/* rev-l-m.c -- reversing bytesex */ +/* utils/bytesex/rev-l-m.c -- reversing bytesex */ #include "utils/bytesex.h" diff --git a/libraries/utils/bytesex/rev-l.c b/libraries/utils/bytesex/rev-l.c index 9124cf4e..39367bc6 100644 --- a/libraries/utils/bytesex/rev-l.c +++ b/libraries/utils/bytesex/rev-l.c @@ -1,4 +1,4 @@ -/* rev-l.c -- reversing bytesex */ +/* utils/bytesex/rev-l.c -- reversing bytesex */ #include "utils/bytesex.h" diff --git a/libraries/utils/bytesex/rev-s-block.c b/libraries/utils/bytesex/rev-s-block.c index 118fa580..cd24f235 100644 --- a/libraries/utils/bytesex/rev-s-block.c +++ b/libraries/utils/bytesex/rev-s-block.c @@ -1,4 +1,4 @@ -/* rev-s-block.c -- reversing bytesex */ +/* utils/bytesex/rev-s-block.c -- reversing bytesex */ #include diff --git a/libraries/utils/bytesex/rev-s-m.c b/libraries/utils/bytesex/rev-s-m.c index d5bf7e46..8b181e3d 100644 --- a/libraries/utils/bytesex/rev-s-m.c +++ b/libraries/utils/bytesex/rev-s-m.c @@ -1,4 +1,4 @@ -/* rev-s-m.c -- reversing bytesex */ +/* utils/bytesex/rev-s-m.c -- reversing bytesex */ #include "utils/bytesex.h" diff --git a/libraries/utils/bytesex/rev-s-pair-m.c b/libraries/utils/bytesex/rev-s-pair-m.c index c4f0327d..23104771 100644 --- a/libraries/utils/bytesex/rev-s-pair-m.c +++ b/libraries/utils/bytesex/rev-s-pair-m.c @@ -1,4 +1,4 @@ -/* rev-s-pair-m.c -- reversing bytesex */ +/* utils/bytesex/rev-s-pair-m.c -- reversing bytesex */ #include "utils/bytesex.h" diff --git a/libraries/utils/bytesex/rev-s-pair.c b/libraries/utils/bytesex/rev-s-pair.c index b251a683..c98cab4d 100644 --- a/libraries/utils/bytesex/rev-s-pair.c +++ b/libraries/utils/bytesex/rev-s-pair.c @@ -1,4 +1,4 @@ -/* rev-s-pair.c -- reversing bytesex */ +/* utils/bytesex/rev-s-pair.c -- reversing bytesex */ #include "utils/bytesex.h" diff --git a/libraries/utils/bytesex/rev-s.c b/libraries/utils/bytesex/rev-s.c index 392e910f..646e0da0 100644 --- a/libraries/utils/bytesex/rev-s.c +++ b/libraries/utils/bytesex/rev-s.c @@ -1,4 +1,4 @@ -/* rev-s.c -- reversing bytesex */ +/* utils/bytesex/rev-s.c -- reversing bytesex */ #include "utils/bytesex.h" diff --git a/libraries/utils/bytesex/util.h b/libraries/utils/bytesex/util.h index f41523e9..c6b153a3 100644 --- a/libraries/utils/bytesex/util.h +++ b/libraries/utils/bytesex/util.h @@ -1,4 +1,4 @@ -/* util.h -- utils for reversing bytesex */ +/* utils/bytesex/util.h -- utils for reversing bytesex */ #include diff --git a/libraries/utils/fxp/smull-fxp16.c b/libraries/utils/fxp/smull-fxp16.c index 6c4509ab..44ecd19d 100644 --- a/libraries/utils/fxp/smull-fxp16.c +++ b/libraries/utils/fxp/smull-fxp16.c @@ -1,4 +1,4 @@ -/* smull-fxp16.c -- fixed point helpers */ +/* utils/fxp/smull-fxp16.c -- fixed point helpers */ #include "utils/fxp.h" diff --git a/libraries/utils/fxp/umull-fxp16.c b/libraries/utils/fxp/umull-fxp16.c index e15a846c..30377304 100644 --- a/libraries/utils/fxp/umull-fxp16.c +++ b/libraries/utils/fxp/umull-fxp16.c @@ -1,4 +1,4 @@ -/* umull-fxp16.c -- fixed point helpers */ +/* utils/fxp/umull-fxp16.c -- fixed point helpers */ #include "utils/fxp.h" diff --git a/libraries/utils/maths/degs-to-rads.c b/libraries/utils/maths/degs-to-rads.c index 891ffe26..1e1f0f92 100644 --- a/libraries/utils/maths/degs-to-rads.c +++ b/libraries/utils/maths/degs-to-rads.c @@ -1,4 +1,4 @@ -/* degs-to-rads.c -- degrees to radians */ +/* utils/maths/degs-to-rads.c -- degrees to radians */ #include "utils/maths.h" diff --git a/libraries/utils/maths/gcd.c b/libraries/utils/maths/gcd.c index 9216dbe7..f4e2d531 100644 --- a/libraries/utils/maths/gcd.c +++ b/libraries/utils/maths/gcd.c @@ -1,4 +1,4 @@ -/* gcd.c -- greatest common divisor */ +/* utils/maths/gcd.c -- greatest common divisor */ #include "utils/maths.h" diff --git a/libraries/utils/pack/pack.c b/libraries/utils/pack/pack.c index aacce624..26dd7bbf 100644 --- a/libraries/utils/pack/pack.c +++ b/libraries/utils/pack/pack.c @@ -1,4 +1,4 @@ -/* pack.c -- structure packing */ +/* utils/pack/pack.c -- structure packing */ #include #include diff --git a/libraries/utils/pack/test/pack-test.c b/libraries/utils/pack/test/pack-test.c index bed208b3..64d7e04a 100644 --- a/libraries/utils/pack/test/pack-test.c +++ b/libraries/utils/pack/test/pack-test.c @@ -1,4 +1,4 @@ -/* pack-test.c -- tests for pack/unpack */ +/* utils/pack/test/pack-test.c -- tests for pack/unpack */ #include #include diff --git a/libraries/utils/pack/unpack.c b/libraries/utils/pack/unpack.c index 01f2911e..3ffec99a 100644 --- a/libraries/utils/pack/unpack.c +++ b/libraries/utils/pack/unpack.c @@ -1,4 +1,4 @@ -/* unpack.c -- structure unpacking */ +/* utils/pack/unpack.c -- structure unpacking */ #include #include diff --git a/libraries/utils/primes/primes.c b/libraries/utils/primes/primes.c index b39fcd64..9d25d619 100644 --- a/libraries/utils/primes/primes.c +++ b/libraries/utils/primes/primes.c @@ -1,4 +1,4 @@ -/* primes.c -- cache of prime numbers */ +/* utils/primes/primes.c -- cache of prime numbers */ #include "base/utils.h" diff --git a/libraries/wuss/backdrop.c b/libraries/wuss/backdrop.c new file mode 100644 index 00000000..cc7071ca --- /dev/null +++ b/libraries/wuss/backdrop.c @@ -0,0 +1,43 @@ +/* wuss/backdrop.c -- shared backdrop validation and fill */ + +#include "framebuf/screen.h" + +#include "impl.h" + +result_t wuss__validate_backdrop(const wuss_t *wuss, + const wuss_backdrop_t *backdrop) +{ + if (backdrop->colour == wuss_NO_BACKGROUND) + return result_OK; + + if (backdrop->colour < 0 || backdrop->colour >= wuss->npalette) + return result_WUSS_BAD_COLOUR; + + if (backdrop->pattern < 0 || backdrop->pattern >= screen_PATTERN__LIMIT) + return result_WUSS_BAD_COLOUR; + + if (backdrop->pattern != screen_PATTERN_SOLID && + (backdrop->pattern_bg < 0 || backdrop->pattern_bg >= wuss->npalette)) + return result_WUSS_BAD_COLOUR; + + return result_OK; +} + +void wuss__fill_backdrop(screen_t *scr, + const colour_t *palette, + const wuss_backdrop_t *backdrop, + const box_t *area, + int origin_x, + int origin_y) +{ + if (backdrop->colour == wuss_NO_BACKGROUND) + return; + + if (backdrop->pattern == screen_PATTERN_SOLID) + screen_fill_rect(scr, area->x0, area->y0, box_size(area), + palette[backdrop->colour]); + else + screen_fill_pattern(scr, area, backdrop->pattern, origin_x, origin_y, + palette[backdrop->colour], + palette[backdrop->pattern_bg]); +} diff --git a/libraries/wuss/create.c b/libraries/wuss/create.c index 086dd587..d7040f95 100644 --- a/libraries/wuss/create.c +++ b/libraries/wuss/create.c @@ -1,4 +1,4 @@ -/* create.c -- wuss - minimal window manager */ +/* wuss/create.c -- wuss - minimal window manager */ #include #include @@ -9,22 +9,36 @@ #endif #include "base/utils.h" -#include "framebuf/palettes.h" #include "impl.h" +/* The default allocator: plain stdlib. wuss_create copies this in when its + * alloc argument is NULL. */ +const wuss_alloc_t wuss_alloc = { malloc, realloc, free }; + +/* Built-in fallback palette when the caller passes NULL: black and white. + * wuss makes no other assumptions about palette contents or length. */ +static const colour_t wuss__default_palette[] = +{ + { 0xFF000000 }, /* 0: black */ + { 0xFFFFFFFF } /* 1: white */ +}; + #if defined(WUSS_FURNITURE) || defined(WUSS_ICONS) -/* Range-check the bevel colours and (when set) the backdrop against the - * palette. Shared by the furniture and icons-only paths so the accepted - * range, the error code and the freed-pointer set stay in one place. */ +/* Range-check the bevel colours and the backdrop against the palette. Shared + * by the furniture and icons-only paths so the accepted range, the error code + * and the freed-pointer set stay in one place. */ static result_t validate_bevel_backdrop(const wuss_t *w, wuss_colour_t blight, - wuss_colour_t bdark) + wuss_colour_t bdark, + wuss_colour_t abg, + wuss_colour_t afg) { if (blight < 0 || blight >= w->npalette || bdark < 0 || bdark >= w->npalette || - (w->backdrop != wuss_NO_BACKGROUND && - (w->backdrop < 0 || w->backdrop >= w->npalette))) + abg < 0 || abg >= w->npalette || + afg < 0 || afg >= w->npalette || + wuss__validate_backdrop(w, &w->backdrop) != result_OK) return result_WUSS_BAD_COLOUR; return result_OK; @@ -36,15 +50,18 @@ result_t wuss_create(screen_t *scr, const colour_t *palette, int npalette, const wuss_config_t *config, + const wuss_alloc_t *alloc, wuss_t **wuss) { + wuss_alloc_t al; wuss_t *w; #ifdef WUSS_FURNITURE - wuss_palette_t pal; + wuss_furniture_palette_t pal; wuss_colour_t bg, fg; #endif #if defined(WUSS_FURNITURE) || defined(WUSS_ICONS) wuss_colour_t blight, bdark; + wuss_colour_t abg, afg; #endif #ifdef WUSS_FURNITURE int font_height; @@ -54,61 +71,62 @@ result_t wuss_create(screen_t *scr, assert(scr != NULL); assert(wuss != NULL); - w = malloc(sizeof(*w)); + al = (alloc != NULL) ? *alloc : wuss_alloc; + + w = al.malloc(sizeof(*w)); if (w == NULL) return result_OOM; + w->alloc = al; + + if (palette == NULL) + { + palette = wuss__default_palette; + npalette = NELEMS(wuss__default_palette); + } + else if (npalette <= 0) + { + wuss__free(w, w); + return result_BAD_ARG; + } - if (palette != NULL) + w->palette = wuss__malloc(w, npalette * sizeof(*w->palette)); + if (w->palette == NULL) { - if (npalette <= 0) - { - free(w); - return result_BAD_ARG; - } - - w->palette = malloc(npalette * sizeof(*w->palette)); - if (w->palette == NULL) - { - free(w); - return result_OOM; - } - memcpy(w->palette, palette, npalette * sizeof(*w->palette)); - w->npalette = npalette; + wuss__free(w, w); + return result_OOM; + } + memcpy(w->palette, palette, npalette * sizeof(*w->palette)); + w->npalette = npalette; + + /* Cache the palette indices nearest to opaque white and black, for menu + * backdrops/text and anything else wanting "paper" or "ink". */ + w->palettecache.white = wuss_nearest_colour(w, 255, 255, 255); + w->palettecache.black = wuss_nearest_colour(w, 0, 0, 0); + + if (config != NULL) + { + w->backdrop = config->backdrop; } else { - w->palette = malloc(palette_PICO8__LENGTH * sizeof(*w->palette)); - if (w->palette == NULL) - { - free(w); - return result_OOM; - } - define_pico8_palette(w->palette); - w->npalette = palette_PICO8__LENGTH; + w->backdrop.colour = wuss_NO_BACKGROUND; + w->backdrop.pattern = screen_PATTERN_SOLID; + w->backdrop.pattern_bg = wuss_NO_BACKGROUND; } #ifdef WUSS_FURNITURE if (config != NULL) { - pal = config->palette; + pal = config->furniture; blight = config->bevel.light; bdark = config->bevel.dark; - w->backdrop = config->backdrop; + abg = config->accent.bg; + afg = config->accent.fg; } else { - w->backdrop = wuss_NO_BACKGROUND; - - if (palette == NULL) - { - bg = palette_PICO8_DARK_BLUE; - fg = palette_PICO8_WHITE; - } - else - { - bg = 0; - fg = (w->npalette > 1) ? 1 : 0; - } + bg = 0; + fg = (w->npalette > 1) ? 1 : 0; pal.title.bg = bg; pal.title.fg = fg; @@ -122,6 +140,8 @@ result_t wuss_create(screen_t *scr, blight = 0; bdark = 0; + abg = bg; /* default action button: the titlebar colours */ + afg = fg; } if (pal.title.bg < 0 || pal.title.bg >= w->npalette || @@ -133,16 +153,18 @@ result_t wuss_create(screen_t *scr, pal.scroll.arrows < 0 || pal.scroll.arrows >= w->npalette || pal.scroll.wells < 0 || pal.scroll.wells >= w->npalette || pal.scroll.sausages < 0 || pal.scroll.sausages >= w->npalette || - validate_bevel_backdrop(w, blight, bdark) != result_OK) + validate_bevel_backdrop(w, blight, bdark, abg, afg) != result_OK) { - free(w->palette); - free(w); + wuss__free(w, w->palette); + wuss__free(w, w); return result_WUSS_BAD_COLOUR; } w->furniture_colours = pal; w->bevel_light = blight; w->bevel_dark = bdark; + w->accent_bg = abg; + w->accent_fg = afg; if (config != NULL && config->titlebar_height > 0) { @@ -159,33 +181,36 @@ result_t wuss_create(screen_t *scr, w->titlebar_height = WUSS_DEFAULT_TITLEBAR_HEIGHT; } #else /* !WUSS_FURNITURE */ - w->backdrop = (config != NULL) ? config->backdrop : wuss_NO_BACKGROUND; - #ifdef WUSS_ICONS if (config != NULL) { blight = config->bevel.light; bdark = config->bevel.dark; + abg = config->accent.bg; + afg = config->accent.fg; } else { blight = 0; bdark = 0; + abg = 0; + afg = (w->npalette > 1) ? 1 : 0; } - if (validate_bevel_backdrop(w, blight, bdark) != result_OK) + if (validate_bevel_backdrop(w, blight, bdark, abg, afg) != result_OK) { - free(w->palette); - free(w); + wuss__free(w, w->palette); + wuss__free(w, w); return result_WUSS_BAD_COLOUR; } w->bevel_light = blight; w->bevel_dark = bdark; + w->accent_bg = abg; + w->accent_fg = afg; #else - if (w->backdrop != wuss_NO_BACKGROUND && - (w->backdrop < 0 || w->backdrop >= w->npalette)) + if (wuss__validate_backdrop(w, &w->backdrop) != result_OK) { - free(w->palette); - free(w); + wuss__free(w, w->palette); + wuss__free(w, w); return result_WUSS_BAD_COLOUR; } #endif @@ -197,9 +222,15 @@ result_t wuss_create(screen_t *scr, w->furniture.dragging = NULL; w->furniture.drag.x = 0; w->furniture.drag.y = 0; + w->furniture_ops = &wuss__furniture_default_ops; #endif #ifdef WUSS_ICONS w->pressed_icon = NULL; + w->hover_icon = NULL; +#endif +#ifdef WUSS_MENUS + w->menu_chain = NULL; + w->menu_eat_up = 0; #endif w->ndirty = 0; @@ -207,6 +238,8 @@ result_t wuss_create(screen_t *scr, w->layout = NULL; w->cascade.x = 0; w->cascade.y = 0; + w->pointer.x = 0; + w->pointer.y = 0; list_init(&w->z_order); diff --git a/libraries/wuss/destroy.c b/libraries/wuss/destroy.c index 372fd517..dd974e56 100644 --- a/libraries/wuss/destroy.c +++ b/libraries/wuss/destroy.c @@ -1,4 +1,4 @@ -/* destroy.c -- wuss - minimal window manager */ +/* wuss/destroy.c -- wuss - minimal window manager */ #include @@ -15,6 +15,12 @@ void wuss_destroy(wuss_t *doomed) if (doomed == NULL) return; +#ifdef WUSS_MENUS + /* Drop any open menu chain first: its windows are freed by the z_order + * sweep below, but the chain nodes and their icon-handle arrays are not. */ + wuss_menu_close(doomed->menu_chain); +#endif + e = doomed->z_order.next; while (e != NULL) { @@ -24,11 +30,11 @@ void wuss_destroy(wuss_t *doomed) #ifdef WUSS_ICONS wuss__icons_free((wuss_window_t *) e); #endif - free(e); + wuss__free(doomed, e); e = next; } packer_destroy(doomed->layout); - free(doomed->palette); - free(doomed); + wuss__free(doomed, doomed->palette); + wuss__free(doomed, doomed); /* reads doomed->alloc.free before freeing */ } diff --git a/libraries/wuss/furniture.h b/libraries/wuss/furniture.h index 4b5b7bd8..0bf378b9 100644 --- a/libraries/wuss/furniture.h +++ b/libraries/wuss/furniture.h @@ -1,4 +1,4 @@ -/* furniture.h -- wuss - minimal window manager */ +/* wuss/furniture.h -- wuss - minimal window manager */ #ifndef WUSS_FURNITURE_IMPL_H #define WUSS_FURNITURE_IMPL_H @@ -8,8 +8,9 @@ #include "wuss/wuss.h" -#define WUSS_MIN_SAUSAGE 6 /* scrollbar sausage never shrinks below this, however small the content fraction */ -#define WUSS_SCROLL_STEP 20 /* pixels stepped per scrollbar arrow click */ +#define WUSS_MIN_SAUSAGE 6 /* scrollbar sausage never shrinks below this, however small the content fraction */ +#define WUSS_SCROLL_END_GAP 2 /* sausage along-axis margin from its well's ends, purely cosmetic */ +#define WUSS_SCROLL_STEP 20 /* pixels stepped per scrollbar arrow click */ /* Which region of a window's border (or its content) a point falls in. */ typedef enum wuss_furniture_region @@ -84,6 +85,31 @@ void wuss__furniture_invalidate(wuss_window_t *window); void wuss__furniture_invalidate_for(wuss_window_t *window, const box_t *visible); +/* Dispatch table the wuss core reaches window furniture through: everything + * the core (z-order, mouse routing, redraw, window lifecycle) calls that + * lives in the furniture .c files. A caller could point wuss::furniture_ops at its + * own table to replace the furniture wholesale; wuss_create defaults it to + * wuss__furniture_default_ops. The geometry helpers in impl.h + * (wuss__titlebar_box, wuss__content_box, wuss__furniture_carve_for, the + * titlebar-height/button-size inlines) are a compile-time seam between the + * furniture and no-furniture builds and stay out of this table. */ +typedef struct wuss__furniture_ops +{ + wuss_furniture_region_t (*hit_test)(const wuss_window_t *window, point_t p); + void (*draw)(wuss_t *wuss, wuss_window_t *window, const box_t *full); + void (*invalidate)(wuss_window_t *window); + void (*invalidate_for)(wuss_window_t *window, const box_t *visible); + void (*toggle_size)(wuss_window_t *window); + void (*drag_resize)(wuss_window_t *window, point_t p); + void (*drag_sausage)(wuss_window_t *window, + int delta_px, + int scroll_start, + int horizontal); +} +wuss__furniture_ops_t; + +extern const wuss__furniture_ops_t wuss__furniture_default_ops; + /* geometry: titlebar icons */ void wuss__back_box(const wuss_window_t *window, box_t *out); void wuss__toggle_box(const wuss_window_t *window, box_t *out); diff --git a/libraries/wuss/furniture/back-box.c b/libraries/wuss/furniture/back-box.c index deab1238..c2d92f25 100644 --- a/libraries/wuss/furniture/back-box.c +++ b/libraries/wuss/furniture/back-box.c @@ -1,4 +1,4 @@ -/* back-box.c -- wuss - minimal window manager */ +/* wuss/furniture/back-box.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/furniture/close-box.c b/libraries/wuss/furniture/close-box.c index 424af7c4..3972fd02 100644 --- a/libraries/wuss/furniture/close-box.c +++ b/libraries/wuss/furniture/close-box.c @@ -1,4 +1,4 @@ -/* close-box.c -- wuss - minimal window manager */ +/* wuss/furniture/close-box.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/furniture/content-box.c b/libraries/wuss/furniture/content-box.c index 72f379eb..14249a4f 100644 --- a/libraries/wuss/furniture/content-box.c +++ b/libraries/wuss/furniture/content-box.c @@ -1,4 +1,4 @@ -/* content-box.c -- wuss - minimal window manager */ +/* wuss/furniture/content-box.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/furniture/drag-resize.c b/libraries/wuss/furniture/drag-resize.c index 9a92a3a3..3e0d02c6 100644 --- a/libraries/wuss/furniture/drag-resize.c +++ b/libraries/wuss/furniture/drag-resize.c @@ -1,4 +1,4 @@ -/* drag-resize.c -- wuss - minimal window manager */ +/* wuss/furniture/drag-resize.c -- wuss - minimal window manager */ #include "base/utils.h" diff --git a/libraries/wuss/furniture/draw.c b/libraries/wuss/furniture/draw.c index a30cb44d..d7433bf6 100644 --- a/libraries/wuss/furniture/draw.c +++ b/libraries/wuss/furniture/draw.c @@ -1,4 +1,4 @@ -/* draw.c -- wuss - minimal window manager */ +/* wuss/furniture/draw.c -- wuss - minimal window manager */ #include @@ -22,7 +22,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&titlebar, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, + screen_fill_rect(wuss->scr, titlebar.x0, titlebar.y0, box_size(&titlebar), wuss->palette[wuss->furniture_colours.title.bg]); @@ -86,7 +86,7 @@ void wuss__furniture_draw(wuss_t *wuss, box_t close; wuss__close_box(window, &close); - screen_draw_rect(wuss->scr, + screen_fill_rect(wuss->scr, close.x0, close.y0, box_size(&close), wuss->palette[wuss->furniture_colours.close]); } @@ -96,7 +96,7 @@ void wuss__furniture_draw(wuss_t *wuss, box_t back; wuss__back_box(window, &back); - screen_draw_rect(wuss->scr, + screen_fill_rect(wuss->scr, back.x0, back.y0, box_size(&back), wuss->palette[wuss->furniture_colours.back]); } @@ -106,7 +106,7 @@ void wuss__furniture_draw(wuss_t *wuss, box_t toggle; wuss__toggle_box(window, &toggle); - screen_draw_rect(wuss->scr, + screen_fill_rect(wuss->scr, toggle.x0, toggle.y0, box_size(&toggle), wuss->palette[wuss->furniture_colours.toggle]); } @@ -120,7 +120,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&resize, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, + screen_fill_rect(wuss->scr, resize.x0, resize.y0, box_size(&resize), wuss->palette[wuss->furniture_colours.resize]); } @@ -134,7 +134,7 @@ 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, box_size(&up), + screen_fill_rect(wuss->scr, up.x0, up.y0, box_size(&up), wuss->palette[wuss->furniture_colours.scroll.arrows]); } @@ -142,7 +142,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&down, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, down.x0, down.y0, box_size(&down), + screen_fill_rect(wuss->scr, down.x0, down.y0, box_size(&down), wuss->palette[wuss->furniture_colours.scroll.arrows]); } @@ -150,7 +150,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&well, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, well.x0, well.y0, box_size(&well), + screen_fill_rect(wuss->scr, well.x0, well.y0, box_size(&well), wuss->palette[wuss->furniture_colours.scroll.wells]); } @@ -158,7 +158,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&sausage, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, sausage.x0, sausage.y0, box_size(&sausage), + screen_fill_rect(wuss->scr, sausage.x0, sausage.y0, box_size(&sausage), wuss->palette[wuss->furniture_colours.scroll.sausages]); } } @@ -171,7 +171,7 @@ 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, box_size(&left), + screen_fill_rect(wuss->scr, left.x0, left.y0, box_size(&left), wuss->palette[wuss->furniture_colours.scroll.arrows]); } @@ -179,7 +179,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&right, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, right.x0, right.y0, box_size(&right), + screen_fill_rect(wuss->scr, right.x0, right.y0, box_size(&right), wuss->palette[wuss->furniture_colours.scroll.arrows]); } @@ -187,7 +187,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&well, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, well.x0, well.y0, box_size(&well), + screen_fill_rect(wuss->scr, well.x0, well.y0, box_size(&well), wuss->palette[wuss->furniture_colours.scroll.wells]); } @@ -195,7 +195,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&sausage, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, sausage.x0, sausage.y0, box_size(&sausage), + screen_fill_rect(wuss->scr, sausage.x0, sausage.y0, box_size(&sausage), wuss->palette[wuss->furniture_colours.scroll.sausages]); } } @@ -218,7 +218,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&rule, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, rule.x0, rule.y0, box_size(&rule), + screen_fill_rect(wuss->scr, rule.x0, rule.y0, box_size(&rule), wuss->palette[wuss->furniture_colours.title.bg]); } } @@ -232,7 +232,7 @@ void wuss__furniture_draw(wuss_t *wuss, if (!box_intersection(&rule, full, &clipped)) { wuss->scr->clip = clipped; - screen_draw_rect(wuss->scr, rule.x0, rule.y0, box_size(&rule), + screen_fill_rect(wuss->scr, rule.x0, rule.y0, box_size(&rule), wuss->palette[wuss->furniture_colours.title.bg]); } } @@ -248,9 +248,9 @@ void wuss__furniture_draw(wuss_t *wuss, border = wuss->palette[wuss->furniture_colours.title.bg]; /* no dedicated outline class; matches titlebar chrome */ wuss->scr->clip = visible_clipped; - screen_draw_rect(wuss->scr, window->visible.x0, window->visible.y0, SIZE2D(width, 1), border); - screen_draw_rect(wuss->scr, window->visible.x0, window->visible.y1 - 1, SIZE2D(width, 1), border); - screen_draw_rect(wuss->scr, window->visible.x0, window->visible.y0, SIZE2D(1, height), border); - screen_draw_rect(wuss->scr, window->visible.x1 - 1, window->visible.y0, SIZE2D(1, height), border); + screen_fill_rect(wuss->scr, window->visible.x0, window->visible.y0, SIZE2D(width, 1), border); + screen_fill_rect(wuss->scr, window->visible.x0, window->visible.y1 - 1, SIZE2D(width, 1), border); + screen_fill_rect(wuss->scr, window->visible.x0, window->visible.y0, SIZE2D(1, height), border); + screen_fill_rect(wuss->scr, window->visible.x1 - 1, window->visible.y0, SIZE2D(1, height), border); } } diff --git a/libraries/wuss/furniture/hit-test.c b/libraries/wuss/furniture/hit-test.c index d6e0705b..15eb1972 100644 --- a/libraries/wuss/furniture/hit-test.c +++ b/libraries/wuss/furniture/hit-test.c @@ -1,4 +1,4 @@ -/* hit-test.c -- wuss - minimal window manager */ +/* wuss/furniture/hit-test.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/furniture/hscroll-box.c b/libraries/wuss/furniture/hscroll-box.c index 31bbdc9b..b3b2cf81 100644 --- a/libraries/wuss/furniture/hscroll-box.c +++ b/libraries/wuss/furniture/hscroll-box.c @@ -1,4 +1,4 @@ -/* hscroll-box.c -- wuss - minimal window manager */ +/* wuss/furniture/hscroll-box.c -- wuss - minimal window manager */ #include "../impl.h" @@ -70,27 +70,33 @@ 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, inset; + int well_px, track_px, end_gap, 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; + well_px = well.x1 - well.x0; + + /* Leave a cosmetic gap at each end of the well; drop it if the well is + * too short to spare two gaps plus a minimum sausage. */ + end_gap = (well_px > 2 * WUSS_SCROLL_END_GAP + WUSS_MIN_SAUSAGE) ? WUSS_SCROLL_END_GAP : 0; + track_px = well_px - 2 * end_gap; + content_size = content.x1 - content.x0; doc_size = window->doc.w; if (doc_size < content_size) doc_size = content_size; - sausage_px = (doc_size > 0) ? well_px * content_size / doc_size : well_px; + sausage_px = (doc_size > 0) ? track_px * content_size / doc_size : track_px; if (sausage_px < WUSS_MIN_SAUSAGE) sausage_px = WUSS_MIN_SAUSAGE; - if (sausage_px > well_px) - sausage_px = well_px; + if (sausage_px > track_px) + sausage_px = track_px; - if (doc_size > content_size && well_px > sausage_px) - sausage_x0 = well.x0 + (well_px - sausage_px) * window->scroll.x / (doc_size - content_size); + if (doc_size > content_size && track_px > sausage_px) + sausage_x0 = well.x0 + end_gap + (track_px - sausage_px) * window->scroll.x / (doc_size - content_size); else - sausage_x0 = well.x0; + sausage_x0 = well.x0 + end_gap; /* 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 diff --git a/libraries/wuss/furniture/invalidate.c b/libraries/wuss/furniture/invalidate.c index dc2e3ba7..ad058751 100644 --- a/libraries/wuss/furniture/invalidate.c +++ b/libraries/wuss/furniture/invalidate.c @@ -1,4 +1,4 @@ -/* invalidate.c -- wuss - minimal window manager */ +/* wuss/furniture/invalidate.c -- wuss - minimal window manager */ #include "../impl.h" @@ -7,7 +7,8 @@ * (toggle-size) also mark the *old* furniture strips dirty, since a blit * that reused the old pixels leaves stale titlebar/scrollbar/outline * pixels sitting wherever those strips used to be. */ -void wuss__furniture_invalidate_for(wuss_window_t *window, const box_t *visible) +void wuss__furniture_invalidate_for(wuss_window_t *window, + const box_t *visible) { int outline_px; point_t carve; diff --git a/libraries/wuss/furniture/ops.c b/libraries/wuss/furniture/ops.c new file mode 100644 index 00000000..15789161 --- /dev/null +++ b/libraries/wuss/furniture/ops.c @@ -0,0 +1,16 @@ +/* wuss/furniture/ops.c -- wuss - core-to-furniture dispatch table */ + +#include "../impl.h" + +/* The built-in furniture: wuss_create points every window's furniture_ops + * here unless a caller overrides it. */ +const wuss__furniture_ops_t wuss__furniture_default_ops = +{ + wuss__furniture_hit_test, + wuss__furniture_draw, + wuss__furniture_invalidate, + wuss__furniture_invalidate_for, + wuss__furniture_toggle_size, + wuss__furniture_drag_resize, + wuss__furniture_drag_sausage +}; diff --git a/libraries/wuss/furniture/resize-box.c b/libraries/wuss/furniture/resize-box.c index 143ab98c..789a2e67 100644 --- a/libraries/wuss/furniture/resize-box.c +++ b/libraries/wuss/furniture/resize-box.c @@ -1,4 +1,4 @@ -/* resize-box.c -- wuss - minimal window manager */ +/* wuss/furniture/resize-box.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/furniture/scroll-action.c b/libraries/wuss/furniture/scroll-action.c index 6ac7a498..8b84d759 100644 --- a/libraries/wuss/furniture/scroll-action.c +++ b/libraries/wuss/furniture/scroll-action.c @@ -1,4 +1,4 @@ -/* scroll-action.c -- wuss - minimal window manager */ +/* wuss/furniture/scroll-action.c -- wuss - minimal window manager */ #include "../impl.h" @@ -8,7 +8,7 @@ void wuss__furniture_drag_sausage(wuss_window_t *window, int horizontal) { box_t content; - int well_px, content_size, doc_size, max_scroll, new_scroll; + int well_px, track_px, end_gap, content_size, doc_size, max_scroll, new_scroll; wuss__content_box(window, &content); @@ -25,14 +25,19 @@ void wuss__furniture_drag_sausage(wuss_window_t *window, doc_size = window->doc.h; } + /* Sausage travels over the well minus the cosmetic end gaps; must match + * wuss__*scroll_sausage_box so drag tracks the pointer 1:1. */ + end_gap = (well_px > 2 * WUSS_SCROLL_END_GAP + WUSS_MIN_SAUSAGE) ? WUSS_SCROLL_END_GAP : 0; + track_px = well_px - 2 * end_gap; + max_scroll = doc_size - content_size; if (max_scroll < 0) max_scroll = 0; - if (well_px <= 0 || doc_size <= content_size) + if (track_px <= 0 || doc_size <= content_size) new_scroll = 0; else - new_scroll = scroll_start + delta_px * doc_size / well_px; + new_scroll = scroll_start + delta_px * doc_size / track_px; if (new_scroll < 0) new_scroll = 0; diff --git a/libraries/wuss/furniture/titlebar-box.c b/libraries/wuss/furniture/titlebar-box.c index 52a9dade..0d1faa49 100644 --- a/libraries/wuss/furniture/titlebar-box.c +++ b/libraries/wuss/furniture/titlebar-box.c @@ -1,4 +1,4 @@ -/* titlebar-box.c -- wuss - minimal window manager */ +/* wuss/furniture/titlebar-box.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/furniture/toggle-action.c b/libraries/wuss/furniture/toggle-action.c index 6a330d6f..7e2724b6 100644 --- a/libraries/wuss/furniture/toggle-action.c +++ b/libraries/wuss/furniture/toggle-action.c @@ -1,4 +1,4 @@ -/* toggle-action.c -- wuss - minimal window manager */ +/* wuss/furniture/toggle-action.c -- wuss - minimal window manager */ #include "base/utils.h" diff --git a/libraries/wuss/furniture/toggle-box.c b/libraries/wuss/furniture/toggle-box.c index 4d9cf3f4..2a77a588 100644 --- a/libraries/wuss/furniture/toggle-box.c +++ b/libraries/wuss/furniture/toggle-box.c @@ -1,4 +1,4 @@ -/* toggle-box.c -- wuss - minimal window manager */ +/* wuss/furniture/toggle-box.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/furniture/vscroll-box.c b/libraries/wuss/furniture/vscroll-box.c index ba873db6..1d936b71 100644 --- a/libraries/wuss/furniture/vscroll-box.c +++ b/libraries/wuss/furniture/vscroll-box.c @@ -1,4 +1,4 @@ -/* vscroll-box.c -- wuss - minimal window manager */ +/* wuss/furniture/vscroll-box.c -- wuss - minimal window manager */ #include "../impl.h" @@ -72,27 +72,33 @@ 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, inset; + int well_px, track_px, end_gap, 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; + well_px = well.y1 - well.y0; + + /* Leave a cosmetic gap at each end of the well; drop it if the well is + * too short to spare two gaps plus a minimum sausage. */ + end_gap = (well_px > 2 * WUSS_SCROLL_END_GAP + WUSS_MIN_SAUSAGE) ? WUSS_SCROLL_END_GAP : 0; + track_px = well_px - 2 * end_gap; + content_size = content.y1 - content.y0; doc_size = window->doc.h; if (doc_size < content_size) doc_size = content_size; - sausage_px = (doc_size > 0) ? well_px * content_size / doc_size : well_px; + sausage_px = (doc_size > 0) ? track_px * content_size / doc_size : track_px; if (sausage_px < WUSS_MIN_SAUSAGE) sausage_px = WUSS_MIN_SAUSAGE; - if (sausage_px > well_px) - sausage_px = well_px; + if (sausage_px > track_px) + sausage_px = track_px; - if (doc_size > content_size && well_px > sausage_px) - sausage_y0 = well.y0 + (well_px - sausage_px) * window->scroll.y / (doc_size - content_size); + if (doc_size > content_size && track_px > sausage_px) + sausage_y0 = well.y0 + end_gap + (track_px - sausage_px) * window->scroll.y / (doc_size - content_size); else - sausage_y0 = well.y0; + sausage_y0 = well.y0 + end_gap; /* 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 diff --git a/libraries/wuss/get-font.c b/libraries/wuss/get-font.c index 206cd958..f54fabd4 100644 --- a/libraries/wuss/get-font.c +++ b/libraries/wuss/get-font.c @@ -1,4 +1,4 @@ -/* get-font.c -- wuss - minimal window manager */ +/* wuss/get-font.c -- wuss - minimal window manager */ #include diff --git a/libraries/wuss/get-pointer.c b/libraries/wuss/get-pointer.c new file mode 100644 index 00000000..db0b6d45 --- /dev/null +++ b/libraries/wuss/get-pointer.c @@ -0,0 +1,12 @@ +/* wuss/get-pointer.c -- wuss - minimal window manager */ + +#include + +#include "impl.h" + +point_t wuss_get_pointer(const wuss_t *wuss) +{ + assert(wuss != NULL); + + return wuss->pointer; +} diff --git a/libraries/wuss/icon.h b/libraries/wuss/icon.h index 13c2c67f..366aa84f 100644 --- a/libraries/wuss/icon.h +++ b/libraries/wuss/icon.h @@ -1,4 +1,4 @@ -/* icon.h -- wuss - work-area icons, internal */ +/* wuss/icon.h -- work-area icons, internal */ #ifndef WUSS_ICON_IMPL_H #define WUSS_ICON_IMPL_H @@ -11,19 +11,68 @@ #include "wuss/wuss.h" #include "wuss/icon.h" +/* Per-instance transient state, kept as bitflags with wuss__icon_* accessors + * (mirrors wuss_window_state_t) so more can be added without growing the + * struct. Not part of the public appearance API. */ +typedef enum wuss_icon_state +{ + wuss_ICON_STATE_NONE = 0, + wuss_ICON_STATE_PRESSED = 1 << 0, /* button: held with the pointer inside */ + wuss_ICON_STATE_SELECTED = 1 << 1, /* radio/option: latched on; menu entry: + * draw a tick */ + wuss_ICON_STATE_HOVERED = 1 << 2 /* menu entry: pointer is over it */ +} +wuss_icon_state_t; + struct wuss_icon { wuss_window_t *window; /* owner; back-pointer for invalidate/get_window */ box_t bbox; /* virtual document space */ wuss_icon_type_t type; char *text; /* owned; never NULL ("" instead) */ - wuss_colour_t fg; - wuss_colour_t bg; + wuss_colour_t fg, bg; screen_pattern_t pattern; /* wuss_ICON_TYPE_PATTERN tile; 0 otherwise */ + const bitmap_t *bitmap; /* wuss_ICON_TYPE_BITMAP image; borrowed, or NULL */ + int group; /* radio: exclusive-selection group; 0 = none */ wuss_icon_flags_t flags; - int pressed; /* button: 1 while held with the pointer inside */ + wuss_icon_state_t state; }; +static inline int wuss__icon_pressed(const wuss_icon_t *icon) +{ + return (icon->state & wuss_ICON_STATE_PRESSED) != 0; +} + +static inline int wuss__icon_selected(const wuss_icon_t *icon) +{ + return (icon->state & wuss_ICON_STATE_SELECTED) != 0; +} + +static inline int wuss__icon_hovered(const wuss_icon_t *icon) +{ + return (icon->state & wuss_ICON_STATE_HOVERED) != 0; +} + +static inline void wuss__icon_set_state(wuss_icon_t *icon, + wuss_icon_state_t bit, + int on) +{ + if (on) + icon->state |= bit; + else + icon->state &= (wuss_icon_state_t) ~bit; +} + +/* Set icon->selected, invalidating it. For a radio with a non-zero group, + * selecting it also clears every other selected radio on the same window with + * that group. Ignored for types with no latched state. */ +void wuss__icon_select(wuss_icon_t *icon, int selected); + +/* Make "icon" (may be NULL) the hovered icon: clears the hovered flag on the + * previous wuss->hover_icon and sets it on the new one, invalidating whichever + * of the two changed. A no-op if nothing changed. */ +void wuss__icon_set_hover(wuss_t *wuss, wuss_icon_t *icon); + /* Map an icon bbox (virtual document space) into screen space: * screen = content.x0 - scroll.x + bbox. wuss__icon_draw paints through this * so hit-testing and invalidation cannot drift from what is drawn. */ diff --git a/libraries/wuss/icon/create-array.c b/libraries/wuss/icon/create-array.c index 5da88935..07bdef4d 100644 --- a/libraries/wuss/icon/create-array.c +++ b/libraries/wuss/icon/create-array.c @@ -1,4 +1,4 @@ -/* create-array.c -- wuss - create several work-area icons at once */ +/* wuss/icon/create-array.c -- create several work-area icons at once */ #include #include diff --git a/libraries/wuss/icon/create.c b/libraries/wuss/icon/create.c index 86686b56..9393061e 100644 --- a/libraries/wuss/icon/create.c +++ b/libraries/wuss/icon/create.c @@ -1,4 +1,4 @@ -/* create.c -- wuss - create a work-area icon */ +/* wuss/icon/create.c -- create a work-area icon */ #include #include @@ -26,16 +26,31 @@ result_t wuss_icon_create(wuss_window_t *window, w = window->wuss; - if (spec->type != wuss_ICON_TYPE_LABEL && - spec->type != wuss_ICON_TYPE_BUTTON && - spec->type != wuss_ICON_TYPE_PATTERN) + switch (spec->type) + { + case wuss_ICON_TYPE_LABEL: + case wuss_ICON_TYPE_BUTTON: + case wuss_ICON_TYPE_PATTERN: + case wuss_ICON_TYPE_FRAME: + case wuss_ICON_TYPE_RADIO: + case wuss_ICON_TYPE_OPTION: + case wuss_ICON_TYPE_BITMAP: + case wuss_ICON_TYPE_MENU_ENTRY: + case wuss_ICON_TYPE_RULE: + break; + + default: return result_WUSS_BAD_ICON; + } if ((spec->type == wuss_ICON_TYPE_BUTTON || spec->type == wuss_ICON_TYPE_PATTERN) && spec->bg == wuss_NO_BACKGROUND) return result_WUSS_BAD_ICON; + if (spec->type == wuss_ICON_TYPE_BITMAP && spec->bitmap == NULL) + return result_WUSS_BAD_ICON; + if (spec->type == wuss_ICON_TYPE_PATTERN && (spec->pattern < 0 || spec->pattern >= screen_PATTERN__LIMIT)) return result_WUSS_BAD_ICON; @@ -50,36 +65,38 @@ result_t wuss_icon_create(wuss_window_t *window, if (window->nicons == window->cap_icons) { newcap = (window->cap_icons == 0) ? 4 : window->cap_icons * 2; - grown = realloc(window->icons, newcap * sizeof(*window->icons)); + grown = wuss__realloc(w, window->icons, newcap * sizeof(*window->icons)); if (grown == NULL) return result_OOM; window->icons = grown; window->cap_icons = newcap; } - it = malloc(sizeof(*it)); + it = wuss__malloc(w, sizeof(*it)); if (it == NULL) return result_OOM; src = (spec->text != NULL) ? spec->text : ""; len = strlen(src); - it->text = malloc(len + 1); + it->text = wuss__malloc(w, len + 1); if (it->text == NULL) { - free(it); + wuss__free(w, it); return result_OOM; } memcpy(it->text, src, len + 1); - it->window = window; - it->bbox = spec->bbox; - it->type = spec->type; - it->fg = spec->fg; - it->bg = spec->bg; - it->pattern = (spec->type == wuss_ICON_TYPE_PATTERN) ? spec->pattern - : screen_PATTERN_SOLID; - it->flags = spec->flags; - it->pressed = 0; + it->window = window; + it->bbox = spec->bbox; + it->type = spec->type; + it->fg = spec->fg; + it->bg = spec->bg; + it->pattern = (spec->type == wuss_ICON_TYPE_PATTERN) ? spec->pattern + : screen_PATTERN_SOLID; + it->bitmap = (spec->type == wuss_ICON_TYPE_BITMAP) ? spec->bitmap : NULL; + it->group = (spec->type == wuss_ICON_TYPE_RADIO) ? spec->group : 0; + it->flags = spec->flags; + it->state = wuss_ICON_STATE_NONE; window->icons[window->nicons++] = it; diff --git a/libraries/wuss/icon/delete.c b/libraries/wuss/icon/delete.c index 5f5932b7..097690cd 100644 --- a/libraries/wuss/icon/delete.c +++ b/libraries/wuss/icon/delete.c @@ -1,4 +1,4 @@ -/* delete.c -- wuss - destroy a work-area icon */ +/* wuss/icon/delete.c -- destroy a work-area icon */ #include @@ -20,6 +20,8 @@ void wuss_icon_delete(wuss_icon_t *icon) if (window->wuss->pressed_icon == icon) window->wuss->pressed_icon = NULL; + if (window->wuss->hover_icon == icon) + window->wuss->hover_icon = NULL; wuss__icon_invalidate(icon); @@ -32,6 +34,6 @@ void wuss_icon_delete(wuss_icon_t *icon) } } - free(icon->text); - free(icon); + wuss__free(window->wuss, icon->text); + wuss__free(window->wuss, icon); } diff --git a/libraries/wuss/icon/draw.c b/libraries/wuss/icon/draw.c index 2a06be2b..cc48f56f 100644 --- a/libraries/wuss/icon/draw.c +++ b/libraries/wuss/icon/draw.c @@ -1,4 +1,4 @@ -/* draw.c -- wuss - draw a work-area icon */ +/* wuss/icon/draw.c -- draw a work-area icon */ #include @@ -13,13 +13,31 @@ /* ----------------------------------------------------------------------- */ +/* Shared per-draw state, resolved once by wuss__icon_draw and handed to each + * type's draw helper. "b" is the icon's screen box; "fg" is its resolved + * foreground; "font_height" is 0 and "have_font" is 0 when there is no font or + * no text to draw. */ +typedef struct icon_draw_ctx +{ + wuss_t *wuss; + screen_t *scr; + const wuss_icon_t *icon; + box_t b; + colour_t fg; + int font_height; + int have_font; +} +icon_draw_ctx_t; + +/* ----------------------------------------------------------------------- */ + static void icon_bevel(screen_t *scr, const box_t *b, colour_t fill, colour_t light, colour_t dark) { - screen_draw_rect(scr, b->x0, b->y0, + screen_fill_rect(scr, b->x0, b->y0, SIZE2D(b->x1 - b->x0, b->y1 - b->y0), fill); screen_draw_line(scr, b->x0, b->y0, b->x1 - 1, b->y0, light); @@ -28,6 +46,385 @@ static void icon_bevel(screen_t *scr, screen_draw_line(scr, b->x1 - 1, b->y0, b->x1 - 1, b->y1 - 1, dark); } +/* Resolve the ground an icon's text/glyph blends against: an explicit icon bg, + * else the window's dominant backdrop colour (a pattern fill blends against its + * clear-bit colour, not its foreground), else "fallback" so bmfont still has a + * blend colour. */ +static colour_t icon_blend_ground(const icon_draw_ctx_t *c, + colour_t fallback) +{ + const wuss_icon_t *icon = c->icon; + + if (icon->bg != wuss_NO_BACKGROUND) + return c->wuss->palette[icon->bg]; + + if (icon->window->bg.colour != wuss_NO_BACKGROUND) + return (icon->window->bg.pattern != screen_PATTERN_SOLID) + ? c->wuss->palette[icon->window->bg.pattern_bg] + : c->wuss->palette[icon->window->bg.colour]; + + return fallback; +} + +/* ----------------------------------------------------------------------- */ + +static void wuss__icon_draw_pattern(const icon_draw_ctx_t *c, + const box_t *content, + point_t scroll) +{ + const wuss_icon_t *icon = c->icon; + colour_t pat_fg; + + /* disabled: fold the pattern into its own ground so it reads as greyed, + * mirroring the button's fg-swap */ + pat_fg = (icon->flags & wuss_ICON_FLAGS_DISABLED) + ? c->wuss->palette[icon->bg] + : c->fg; + + screen_fill_pattern(c->scr, &c->b, icon->pattern, + content->x0 - scroll.x, content->y0 - scroll.y, + pat_fg, c->wuss->palette[icon->bg]); +} + +/* ----------------------------------------------------------------------- */ + +static void wuss__icon_draw_label(const icon_draw_ctx_t *c) +{ + const wuss_icon_t *icon = c->icon; + const box_t *b = &c->b; + colour_t bg; + point_t pos; + int interior_w, split_point; + bmfont_width_t width; + + if (icon->bg != wuss_NO_BACKGROUND) + { + bg = c->wuss->palette[icon->bg]; + screen_fill_rect(c->scr, b->x0, b->y0, + SIZE2D(b->x1 - b->x0, b->y1 - b->y0), bg); + } + else + { + bg = icon_blend_ground(c, c->fg); + } + + if (!c->have_font) + return; + + interior_w = (b->x1 - b->x0) - 2; + if (interior_w < 1) + interior_w = 1; + + bmfont_measure(c->wuss->font, icon->text, (int) strlen(icon->text), + interior_w, &split_point, &width); + + if (icon->flags & wuss_ICON_FLAGS_JUSTIFY_CENTRE) + pos.x = b->x0 + ((b->x1 - b->x0) - width) / 2; + else if (icon->flags & wuss_ICON_FLAGS_JUSTIFY_RIGHT) + pos.x = b->x1 - 1 - width; + else + pos.x = b->x0 + 1; + pos.y = b->y0 + (b->y1 - b->y0 - c->font_height) / 2; + + bmfont_draw(c->wuss->font, c->scr, icon->text, (int) strlen(icon->text), + c->fg, bg, &pos, NULL); +} + +/* ----------------------------------------------------------------------- */ + +static void wuss__icon_draw_frame(const icon_draw_ctx_t *c) +{ + const wuss_icon_t *icon = c->icon; + const box_t *b = &c->b; + colour_t bg; + int cap_w, cap_x, gap_x0, gap_x1, mid_y; + + bg = icon_blend_ground(c, c->fg); + + cap_w = 0; + if (c->have_font) + { + int split_point; + bmfont_width_t width; + + bmfont_measure(c->wuss->font, icon->text, (int) strlen(icon->text), + (b->x1 - b->x0) - WUSS_FRAME_CAPTION_INSET * 2, + &split_point, &width); + cap_w = width; + } + + mid_y = b->y0 + c->font_height / 2; + + /* left, right and bottom edges are unbroken */ + screen_draw_line(c->scr, b->x0, mid_y, b->x0, b->y1 - 1, c->fg); + screen_draw_line(c->scr, b->x1 - 1, mid_y, b->x1 - 1, b->y1 - 1, c->fg); + screen_draw_line(c->scr, b->x0, b->y1 - 1, b->x1 - 1, b->y1 - 1, c->fg); + + /* the top edge is broken around the caption */ + cap_x = b->x0 + WUSS_FRAME_CAPTION_INSET; + gap_x0 = cap_x - WUSS_FRAME_CAPTION_PAD; + gap_x1 = cap_x + cap_w + WUSS_FRAME_CAPTION_PAD; + if (gap_x0 > b->x0) + screen_draw_line(c->scr, b->x0, mid_y, gap_x0, mid_y, c->fg); + if (gap_x1 < b->x1 - 1) + screen_draw_line(c->scr, gap_x1, mid_y, b->x1 - 1, mid_y, c->fg); + + if (c->have_font && cap_w > 0) + { + point_t pos; + + pos.x = cap_x; + pos.y = b->y0; + bmfont_draw(c->wuss->font, c->scr, icon->text, (int) strlen(icon->text), + c->fg, bg, &pos, NULL); + } +} + +/* ----------------------------------------------------------------------- */ + +static void wuss__icon_draw_button(const icon_draw_ctx_t *c) +{ + const wuss_icon_t *icon = c->icon; + const box_t *b = &c->b; + colour_t light, dark, base, label; + int pressed, is_default; + + pressed = wuss__icon_pressed(icon); + is_default = (icon->flags & wuss_ICON_FLAGS_DEFAULT) != 0; + + if (is_default) + { + /* default action button: a flat accent-filled rectangle inside a + * one-pixel accent-text border, distinct from the bevelled ordinary + * buttons around it */ + base = c->wuss->palette[c->wuss->accent_bg]; + light = c->wuss->palette[c->wuss->accent_fg]; + dark = light; + label = c->wuss->palette[c->wuss->accent_fg]; + } + else + { + base = c->wuss->palette[icon->bg]; + light = c->wuss->palette[c->wuss->bevel_light]; + dark = c->wuss->palette[c->wuss->bevel_dark]; + label = c->fg; + } + + if (icon->flags & wuss_ICON_FLAGS_DISABLED) + label = c->wuss->palette[c->wuss->bevel_dark]; /* greyed: sink toward dark */ + + if (pressed) + icon_bevel(c->scr, b, base, dark, light); + else + icon_bevel(c->scr, b, base, light, dark); + + if (c->have_font) + { + point_t pos; + int interior_w, split_point; + bmfont_width_t width; + + interior_w = (b->x1 - b->x0) - 2; + if (interior_w < 1) + interior_w = 1; + + bmfont_measure(c->wuss->font, icon->text, (int) strlen(icon->text), + interior_w, &split_point, &width); + + pos.x = b->x0 + ((b->x1 - b->x0) - width) / 2; + pos.y = b->y0 + (b->y1 - b->y0 - c->font_height) / 2; + if (pressed) + { + pos.x += 1; + pos.y += 1; + } + + bmfont_draw(c->wuss->font, c->scr, icon->text, (int) strlen(icon->text), + label, base, &pos, NULL); + } +} + +/* ----------------------------------------------------------------------- */ + +/* wuss_ICON_TYPE_RADIO and wuss_ICON_TYPE_OPTION: a font-height square glyph at + * the left, vertically centred, with the label to its right. RADIO draws a + * square ring with a solid centre when selected; OPTION draws a box with a tick + * when selected. */ +static void wuss__icon_draw_radio_option(const icon_draw_ctx_t *c) +{ + const wuss_icon_t *icon = c->icon; + const box_t *b = &c->b; + colour_t glyph, bg; + box_t g; + int gsz, gy, tx; + + gsz = (c->font_height >= 8) ? c->font_height : 8; + if (gsz > b->y1 - b->y0) + gsz = b->y1 - b->y0; + gy = b->y0 + (b->y1 - b->y0 - gsz) / 2; + + g.x0 = b->x0; + g.y0 = gy; + g.x1 = b->x0 + gsz; + g.y1 = gy + gsz; + + glyph = (icon->flags & wuss_ICON_FLAGS_DISABLED) + ? c->wuss->palette[c->wuss->bevel_dark] + : c->fg; + + bg = icon_blend_ground(c, glyph); + + if (icon->bg != wuss_NO_BACKGROUND) + screen_fill_rect(c->scr, b->x0, b->y0, + SIZE2D(b->x1 - b->x0, b->y1 - b->y0), bg); + + if (icon->type == wuss_ICON_TYPE_RADIO) + { + /* a square ring (no circle primitive); a solid centre when selected */ + screen_draw_line(c->scr, g.x0 + 2, g.y0, g.x1 - 3, g.y0, glyph); + screen_draw_line(c->scr, g.x0 + 2, g.y1 - 1, g.x1 - 3, g.y1 - 1, glyph); + screen_draw_line(c->scr, g.x0, g.y0 + 2, g.x0, g.y1 - 3, glyph); + screen_draw_line(c->scr, g.x1 - 1, g.y0 + 2, g.x1 - 1, g.y1 - 3, glyph); + if (wuss__icon_selected(icon)) + screen_fill_rect(c->scr, g.x0 + 3, g.y0 + 3, + SIZE2D(gsz - 6, gsz - 6), glyph); + } + else + { + /* a box; a tick (two strokes) when selected */ + screen_draw_rect(c->scr, g.x0, g.y0, + SIZE2D(g.x1 - g.x0, g.y1 - g.y0), glyph); + if (wuss__icon_selected(icon)) + { + screen_draw_line(c->scr, g.x0 + 2, g.y0 + gsz / 2, + g.x0 + gsz / 2 - 1, g.y1 - 3, glyph); + screen_draw_line(c->scr, g.x0 + gsz / 2 - 1, g.y1 - 3, + g.x1 - 3, g.y0 + 2, glyph); + } + } + + if (c->have_font) + { + point_t pos; + int interior_w, split_point; + bmfont_width_t width; + + tx = g.x1 + 4; + interior_w = (b->x1 - tx) - 1; + if (interior_w < 1) + interior_w = 1; + + bmfont_measure(c->wuss->font, icon->text, (int) strlen(icon->text), + interior_w, &split_point, &width); + NOT_USED(width); + + pos.x = tx; + pos.y = b->y0 + (b->y1 - b->y0 - c->font_height) / 2; + bmfont_draw(c->wuss->font, c->scr, icon->text, (int) strlen(icon->text), + glyph, bg, &pos, NULL); + } +} + +/* ----------------------------------------------------------------------- */ + +static void wuss__icon_draw_bitmap(const icon_draw_ctx_t *c) +{ + const box_t *b = &c->b; + screen_t clipped; + + if (c->icon->bitmap == NULL) + return; + + /* screen_draw_bitmap clips to scr->clip and does not scale, so narrow the + * clip to the icon box (intersected with whatever redraw already set) and + * blit at the box's top-left */ + clipped = *c->scr; + if (box_intersection(&c->scr->clip, b, &clipped.clip)) + return; + + screen_draw_bitmap(&clipped, b->x0, b->y0, c->icon->bitmap); +} + +/* ----------------------------------------------------------------------- */ + +static void wuss__icon_draw_menu_entry(const icon_draw_ctx_t *c) +{ + const wuss_icon_t *icon = c->icon; + const box_t *b = &c->b; + colour_t ink, ground, tmp; + int disabled, highlit, pad; + + disabled = (icon->flags & wuss_ICON_FLAGS_DISABLED) != 0; + highlit = wuss__icon_hovered(icon) && !disabled; + pad = 4; + + /* resolve the row's own ink over its own/inherited ground */ + ground = icon_blend_ground(c, c->fg); + ink = disabled ? c->wuss->palette[c->wuss->bevel_dark] : c->fg; + + /* highlight simply swaps the row's fg/bg */ + if (highlit) + { + tmp = ink; + ink = ground; + ground = tmp; + } + + if (highlit || icon->bg != wuss_NO_BACKGROUND) + screen_fill_rect(c->scr, b->x0, b->y0, + SIZE2D(b->x1 - b->x0, b->y1 - b->y0), ground); + + /* left-edge tick when selected */ + if (wuss__icon_selected(icon)) + { + int cx, cy, h; + + h = (c->font_height >= 8) ? c->font_height : 8; + cx = b->x0 + pad; + cy = b->y0 + (b->y1 - b->y0 - h) / 2; + screen_draw_line(c->scr, cx, cy + h / 2, cx + h / 2 - 1, cy + h - 2, ink); + screen_draw_line(c->scr, cx + h / 2 - 1, cy + h - 2, cx + h - 2, cy, ink); + } + + /* right-edge arrow for a submenu entry */ + if (icon->flags & wuss_ICON_FLAGS_SUBMENU) + { + int ax, ay, r, dy; + + r = (c->font_height >= 8) ? c->font_height / 3 : 3; + ax = b->x1 - 1 - pad - r; + ay = b->y0 + (b->y1 - b->y0) / 2; + for (dy = -r; dy <= r; dy++) + screen_draw_line(c->scr, ax, ay + dy, + ax + (r - (dy < 0 ? -dy : dy)), ay + dy, ink); + } + + if (c->have_font && icon->text != NULL && icon->text[0] != '\0') + { + point_t pos; + + pos.x = b->x0 + pad + c->font_height; /* leave room for a tick */ + pos.y = b->y0 + (b->y1 - b->y0 - c->font_height) / 2; + bmfont_draw(c->wuss->font, c->scr, icon->text, (int) strlen(icon->text), + ink, ground, &pos, NULL); + } +} + +/* ----------------------------------------------------------------------- */ + +/* wuss_ICON_TYPE_RULE: a dashed rule centred in the box, drawn in the icon's + * fg over whatever ground it inherits. Inert -- see wuss__icon_hit_test. */ +static void wuss__icon_draw_rule(const icon_draw_ctx_t *c) +{ + const box_t *b = &c->b; + int mid_y; + + mid_y = b->y0 + (b->y1 - b->y0) / 2; + screen_draw_dashed_line(c->scr, + b->x0, mid_y, b->x1 - 1, mid_y, + 2, 2, c->fg); +} + /* ----------------------------------------------------------------------- */ void wuss__icon_draw(wuss_t *wuss, @@ -35,123 +432,62 @@ void wuss__icon_draw(wuss_t *wuss, const box_t *content, point_t scroll) { - screen_t *scr; - box_t b; - colour_t fg; - int font_width, font_height; - int have_font; + icon_draw_ctx_t c; + int font_width; if (icon->flags & wuss_ICON_FLAGS_HIDDEN) return; - scr = wuss->scr; + wuss__icon_box_to_screen(content, scroll, &icon->bbox, &c.b); - wuss__icon_box_to_screen(content, scroll, &icon->bbox, &b); - - if (b.x1 <= b.x0 || b.y1 <= b.y0) + if (box_is_empty(&c.b)) return; - fg = wuss->palette[icon->fg]; + c.wuss = wuss; + c.scr = wuss->scr; + c.icon = icon; + c.fg = wuss->palette[icon->fg]; - have_font = (wuss->font != NULL && icon->text[0] != '\0'); - if (have_font) - bmfont_get_info(wuss->font, &font_width, &font_height); + c.have_font = (wuss->font != NULL && icon->text[0] != '\0'); + if (c.have_font) + bmfont_get_info(wuss->font, &font_width, &c.font_height); else - font_width = font_height = 0; + font_width = c.font_height = 0; NOT_USED(font_width); switch (icon->type) { case wuss_ICON_TYPE_PATTERN: - { - colour_t pat_fg; - - /* disabled: fold the pattern into its own ground so it reads as greyed, - * mirroring the button's fg-swap */ - pat_fg = (icon->flags & wuss_ICON_FLAGS_DISABLED) - ? wuss->palette[icon->bg] - : fg; - - screen_fill_pattern(scr, &b, icon->pattern, - content->x0 - scroll.x, content->y0 - scroll.y, - pat_fg, wuss->palette[icon->bg]); - } + wuss__icon_draw_pattern(&c, content, scroll); break; case wuss_ICON_TYPE_LABEL: - { - colour_t bg; - - if (icon->bg != wuss_NO_BACKGROUND) - { - bg = wuss->palette[icon->bg]; - screen_draw_rect(scr, b.x0, b.y0, - SIZE2D(b.x1 - b.x0, b.y1 - b.y0), bg); - } - else if (icon->window->bg != wuss_NO_BACKGROUND) - { - bg = wuss->palette[icon->window->bg]; - } - else - { - bg = fg; /* bmfont needs a blend colour; nothing better to offer */ - } - - if (have_font) - { - point_t pos; - - pos.x = b.x0 + 1; - pos.y = b.y0 + (b.y1 - b.y0 - font_height) / 2; - bmfont_draw(wuss->font, scr, icon->text, (int) strlen(icon->text), - fg, bg, &pos, NULL); - } - } + wuss__icon_draw_label(&c); + break; + + case wuss_ICON_TYPE_FRAME: + wuss__icon_draw_frame(&c); break; case wuss_ICON_TYPE_BUTTON: - { - colour_t light, dark, base; - int pressed; - - base = wuss->palette[icon->bg]; - light = wuss->palette[wuss->bevel_light]; - dark = wuss->palette[wuss->bevel_dark]; - pressed = icon->pressed; - - if (icon->flags & wuss_ICON_FLAGS_DISABLED) - fg = dark; /* greyed: label sinks toward the dark bevel shade */ - - if (pressed) - icon_bevel(scr, &b, base, dark, light); - else - icon_bevel(scr, &b, base, light, dark); - - if (have_font) - { - point_t pos; - int interior_w, split_point; - bmfont_width_t width; - - interior_w = (b.x1 - b.x0) - 2; - if (interior_w < 1) - interior_w = 1; - - bmfont_measure(wuss->font, icon->text, (int) strlen(icon->text), - interior_w, &split_point, &width); - - pos.x = b.x0 + ((b.x1 - b.x0) - width) / 2; - pos.y = b.y0 + (b.y1 - b.y0 - font_height) / 2; - if (pressed) - { - pos.x += 1; - pos.y += 1; - } - - bmfont_draw(wuss->font, scr, icon->text, (int) strlen(icon->text), - fg, base, &pos, NULL); - } - } + wuss__icon_draw_button(&c); + break; + + case wuss_ICON_TYPE_RADIO: + case wuss_ICON_TYPE_OPTION: + wuss__icon_draw_radio_option(&c); + break; + + case wuss_ICON_TYPE_BITMAP: + wuss__icon_draw_bitmap(&c); + break; + + case wuss_ICON_TYPE_MENU_ENTRY: + wuss__icon_draw_menu_entry(&c); + break; + + case wuss_ICON_TYPE_RULE: + wuss__icon_draw_rule(&c); break; } } diff --git a/libraries/wuss/icon/free.c b/libraries/wuss/icon/free.c index d7cbac7e..d8a08432 100644 --- a/libraries/wuss/icon/free.c +++ b/libraries/wuss/icon/free.c @@ -1,4 +1,4 @@ -/* free.c -- wuss - free a window's whole icon store */ +/* wuss/icon/free.c -- free a window's whole icon store */ #include @@ -10,15 +10,18 @@ void wuss__icons_free(wuss_window_t *window) { - int i; + wuss_t *w; + int i; + + w = window->wuss; for (i = 0; i < window->nicons; i++) { - free(window->icons[i]->text); - free(window->icons[i]); + wuss__free(w, window->icons[i]->text); + wuss__free(w, window->icons[i]); } - free(window->icons); + wuss__free(w, window->icons); window->icons = NULL; window->nicons = 0; window->cap_icons = 0; diff --git a/libraries/wuss/icon/get-bbox.c b/libraries/wuss/icon/get-bbox.c index 55bdbd8d..700f96d9 100644 --- a/libraries/wuss/icon/get-bbox.c +++ b/libraries/wuss/icon/get-bbox.c @@ -1,4 +1,4 @@ -/* get-bbox.c -- wuss - read a work-area icon's bounding box */ +/* wuss/icon/get-bbox.c -- read a work-area icon's bounding box */ #include "../impl.h" diff --git a/libraries/wuss/icon/get-selected.c b/libraries/wuss/icon/get-selected.c new file mode 100644 index 00000000..3029e5d9 --- /dev/null +++ b/libraries/wuss/icon/get-selected.c @@ -0,0 +1,12 @@ +/* wuss/icon/get-selected.c -- query a radio/option icon's latched state */ + +#include + +#include "../impl.h" + +int wuss_icon_get_selected(const wuss_icon_t *icon) +{ + assert(icon != NULL); + + return wuss__icon_selected(icon); +} diff --git a/libraries/wuss/icon/get-text.c b/libraries/wuss/icon/get-text.c index 2978cdb7..783f685f 100644 --- a/libraries/wuss/icon/get-text.c +++ b/libraries/wuss/icon/get-text.c @@ -1,4 +1,4 @@ -/* get-text.c -- wuss - read a work-area icon's label */ +/* wuss/icon/get-text.c -- read a work-area icon's label */ #include "../impl.h" diff --git a/libraries/wuss/icon/get-type.c b/libraries/wuss/icon/get-type.c index 84b34091..2335286b 100644 --- a/libraries/wuss/icon/get-type.c +++ b/libraries/wuss/icon/get-type.c @@ -1,4 +1,4 @@ -/* get-type.c -- wuss - read a work-area icon's type */ +/* wuss/icon/get-type.c -- read a work-area icon's type */ #include "../impl.h" diff --git a/libraries/wuss/icon/get-window.c b/libraries/wuss/icon/get-window.c index 9921af6d..43fa0127 100644 --- a/libraries/wuss/icon/get-window.c +++ b/libraries/wuss/icon/get-window.c @@ -1,4 +1,4 @@ -/* get-window.c -- wuss - read a work-area icon's owning window */ +/* wuss/icon/get-window.c -- read a work-area icon's owning window */ #include "../impl.h" diff --git a/libraries/wuss/icon/hit-test.c b/libraries/wuss/icon/hit-test.c index d7579e21..2e9ab7c8 100644 --- a/libraries/wuss/icon/hit-test.c +++ b/libraries/wuss/icon/hit-test.c @@ -1,4 +1,4 @@ -/* hit-test.c -- wuss - work-area icon hit testing */ +/* wuss/icon/hit-test.c -- work-area icon hit testing */ #include "geom/box.h" @@ -14,8 +14,20 @@ wuss_icon_t *wuss__icon_hit_test(wuss_window_t *window, point_t doc_point) { it = window->icons[i]; - if (it->type != wuss_ICON_TYPE_BUTTON) + /* a bitmap icon is interactive only when it asks to be; RULE and the + * static types are always inert; the other types always click */ + if (it->type == wuss_ICON_TYPE_BITMAP) + { + if (!(it->flags & wuss_ICON_FLAGS_INTERACTIVE)) + continue; + } + else if (it->type != wuss_ICON_TYPE_BUTTON && + it->type != wuss_ICON_TYPE_RADIO && + it->type != wuss_ICON_TYPE_OPTION && + it->type != wuss_ICON_TYPE_MENU_ENTRY) + { continue; + } if (it->flags & (wuss_ICON_FLAGS_HIDDEN | wuss_ICON_FLAGS_DISABLED)) continue; diff --git a/libraries/wuss/icon/invalidate.c b/libraries/wuss/icon/invalidate.c index 91c9e47f..d702fb36 100644 --- a/libraries/wuss/icon/invalidate.c +++ b/libraries/wuss/icon/invalidate.c @@ -1,4 +1,4 @@ -/* invalidate.c -- wuss - mark a work-area icon's bbox dirty */ +/* wuss/icon/invalidate.c -- mark a work-area icon's bbox dirty */ #include "../impl.h" diff --git a/libraries/wuss/icon/screen-box.c b/libraries/wuss/icon/screen-box.c index 59e4e4e0..3f630519 100644 --- a/libraries/wuss/icon/screen-box.c +++ b/libraries/wuss/icon/screen-box.c @@ -1,4 +1,4 @@ -/* screen-box.c -- wuss - work-area icon bbox to screen space */ +/* wuss/icon/screen-box.c -- work-area icon bbox to screen space */ #include "geom/box.h" #include "geom/point.h" @@ -14,8 +14,5 @@ void wuss__icon_box_to_screen(const box_t *content, const box_t *bbox, box_t *out) { - out->x0 = content->x0 - scroll.x + bbox->x0; - out->y0 = content->y0 - scroll.y + bbox->y0; - out->x1 = content->x0 - scroll.x + bbox->x1; - out->y1 = content->y0 - scroll.y + bbox->y1; + box_translated(bbox, content->x0 - scroll.x, content->y0 - scroll.y, out); } diff --git a/libraries/wuss/icon/set-hidden.c b/libraries/wuss/icon/set-hidden.c index c76e35a8..7adfee0d 100644 --- a/libraries/wuss/icon/set-hidden.c +++ b/libraries/wuss/icon/set-hidden.c @@ -1,4 +1,4 @@ -/* set-hidden.c -- wuss - show or hide a work-area icon */ +/* wuss/icon/set-hidden.c -- show or hide a work-area icon */ #include "../impl.h" diff --git a/libraries/wuss/icon/set-hover.c b/libraries/wuss/icon/set-hover.c new file mode 100644 index 00000000..08ef518a --- /dev/null +++ b/libraries/wuss/icon/set-hover.c @@ -0,0 +1,38 @@ +/* wuss/icon/set-hover.c -- track which work-area icon the pointer is over */ + +#include + +#include "../impl.h" + +/* Only wuss_ICON_TYPE_MENU_ENTRY changes appearance on hover (see + * wuss__icon_draw_menu_entry). Buttons and every other type ignore the hover state, + * so redrawing them on pointer enter/leave is wasted work. */ +static int wuss__icon_hover_visible(const wuss_icon_t *icon) +{ + return icon->type == wuss_ICON_TYPE_MENU_ENTRY; +} + +void wuss__icon_set_hover(wuss_t *wuss, wuss_icon_t *icon) +{ + wuss_icon_t *prev; + + prev = wuss->hover_icon; + if (prev == icon) + return; + + if (prev != NULL) + { + wuss__icon_set_state(prev, wuss_ICON_STATE_HOVERED, 0); + if (wuss__icon_hover_visible(prev)) + wuss__icon_invalidate(prev); + } + + wuss->hover_icon = icon; + + if (icon != NULL) + { + wuss__icon_set_state(icon, wuss_ICON_STATE_HOVERED, 1); + if (wuss__icon_hover_visible(icon)) + wuss__icon_invalidate(icon); + } +} diff --git a/libraries/wuss/icon/set-selected.c b/libraries/wuss/icon/set-selected.c new file mode 100644 index 00000000..6344ac65 --- /dev/null +++ b/libraries/wuss/icon/set-selected.c @@ -0,0 +1,51 @@ +/* wuss/icon/set-selected.c -- latch a radio/option icon's selected state */ + +#include + +#include "../impl.h" + +void wuss__icon_select(wuss_icon_t *icon, int selected) +{ + wuss_window_t *window; + wuss_icon_t *other; + int i; + + assert(icon != NULL); + + if (icon->type != wuss_ICON_TYPE_RADIO && + icon->type != wuss_ICON_TYPE_OPTION) + return; + + selected = selected ? 1 : 0; + + /* selecting a grouped radio clears its siblings first */ + if (selected && + icon->type == wuss_ICON_TYPE_RADIO && + icon->group != 0) + { + window = icon->window; + for (i = 0; i < window->nicons; i++) + { + other = window->icons[i]; + if (other == icon) + continue; + if (other->type != wuss_ICON_TYPE_RADIO || other->group != icon->group) + continue; + if (!wuss__icon_selected(other)) + continue; + wuss__icon_set_state(other, wuss_ICON_STATE_SELECTED, 0); + wuss__icon_invalidate(other); + } + } + + if (wuss__icon_selected(icon) == selected) + return; + + wuss__icon_set_state(icon, wuss_ICON_STATE_SELECTED, selected); + wuss__icon_invalidate(icon); +} + +void wuss_icon_set_selected(wuss_icon_t *icon, int selected) +{ + wuss__icon_select(icon, selected); +} diff --git a/libraries/wuss/icon/set-text.c b/libraries/wuss/icon/set-text.c index 3a5eca25..2c5d517a 100644 --- a/libraries/wuss/icon/set-text.c +++ b/libraries/wuss/icon/set-text.c @@ -1,4 +1,4 @@ -/* set-text.c -- wuss - replace a work-area icon's label */ +/* wuss/icon/set-text.c -- replace a work-area icon's label */ #include #include @@ -11,19 +11,21 @@ result_t wuss_icon_set_text(wuss_icon_t *icon, const char *text) { + wuss_t *w; const char *src; char *dup; size_t len; + w = icon->window->wuss; src = (text != NULL) ? text : ""; len = strlen(src); - dup = malloc(len + 1); + dup = wuss__malloc(w, len + 1); if (dup == NULL) return result_OOM; memcpy(dup, src, len + 1); - free(icon->text); + wuss__free(w, icon->text); icon->text = dup; wuss__icon_invalidate(icon); diff --git a/libraries/wuss/idle.c b/libraries/wuss/idle.c index c8de3625..e0be5fb6 100644 --- a/libraries/wuss/idle.c +++ b/libraries/wuss/idle.c @@ -1,4 +1,4 @@ -/* idle.c -- wuss - minimal window manager */ +/* wuss/idle.c -- wuss - minimal window manager */ #include "impl.h" diff --git a/libraries/wuss/impl.h b/libraries/wuss/impl.h index fa8f52da..8c857328 100644 --- a/libraries/wuss/impl.h +++ b/libraries/wuss/impl.h @@ -1,4 +1,4 @@ -/* impl.h -- wuss - minimal window manager */ +/* wuss/impl.h -- wuss - minimal window manager */ #ifndef IMPL_H #define IMPL_H @@ -21,6 +21,9 @@ #ifdef WUSS_ICONS #include "icon.h" #endif +#ifdef WUSS_MENUS +#include "menu.h" +#endif #define WUSS_TITLE_MAX 63 #define WUSS_DEFAULT_TITLEBAR_HEIGHT 20 @@ -35,6 +38,11 @@ #define WUSS_PLACE_GUTTER 6 /* px left between windows auto-placed by wuss_window_create_placed */ #define WUSS_BUTTON_INSET 3 /* shared by close/back/toggle/resize furniture buttons and scrollbar breadth */ + +#ifdef WUSS_ICONS +#define WUSS_FRAME_CAPTION_INSET 8 /* x offset of a wuss_ICON_TYPE_FRAME caption from the frame's left edge */ +#define WUSS_FRAME_CAPTION_PAD 2 /* gap left in the frame's top edge either side of the caption */ +#endif #define WUSS_MIN_CONTENT 20 /* resize-drag floor: content can never be squeezed smaller than this */ #define WUSS_SCROLL_INSET 2 /* sausage cross-axis margin from its well's edges, purely cosmetic */ #define WUSS_DIVIDER_PX 1 /* interior rule between the content area and the furniture on its right/bottom */ @@ -53,22 +61,36 @@ struct wuss { screen_t *scr; bmfont_t *font; /* nullable, not owned */ + wuss_alloc_t alloc; /* malloc/realloc/free hooks, copied in + * by wuss_create; used for every heap + * block this wuss_t owns */ colour_t *palette; /* owned */ int npalette; + struct + { + wuss_colour_t white; /* palette index nearest to white */ + wuss_colour_t black; /* palette index nearest to black */ + } + palettecache; #ifdef WUSS_FURNITURE - wuss_palette_t furniture_colours; + wuss_furniture_palette_t furniture_colours; #endif #if defined(WUSS_FURNITURE) || defined(WUSS_ICONS) wuss_colour_t bevel_light; /* work-area button top/left edge */ wuss_colour_t bevel_dark; /* work-area button bottom/right edge */ + wuss_colour_t accent_bg; /* default action button fill */ + wuss_colour_t accent_fg; /* default action button text */ #endif - wuss_colour_t backdrop; /* wuss_NO_BACKGROUND for none */ + wuss_backdrop_t backdrop; /* colour==wuss_NO_BACKGROUND: none */ #ifdef WUSS_FURNITURE int titlebar_height; #endif list_t z_order; /* anchor; head = topmost window */ #ifdef WUSS_FURNITURE - struct wuss__furniture furniture; + struct wuss__furniture furniture; /* drag state */ + const wuss__furniture_ops_t *furniture_ops; /* core->furniture dispatch; + * wuss_create defaults it to + * wuss__furniture_default_ops */ #endif box_t dirty[WUSS_MAX_DIRTY]; /* accumulated by wuss_invalidate; reset by a redraw */ int ndirty; @@ -77,11 +99,27 @@ struct wuss * created on first auto-placement */ point_t cascade; /* next cascade offset, used once the * layout packer has no room left */ + point_t pointer; /* last pointer position, screen + * space, from any mouse click/move */ #ifdef WUSS_ICONS wuss_icon_t *pressed_icon; /* button icon held down, NULL when * idle; released on any MOUSE_UP * even if a new window now covers * its owner */ + wuss_icon_t *hover_icon; /* icon the pointer is currently + * over, NULL when none; drives + * hover-highlight repaint of + * menu-entry icons */ +#endif +#ifdef WUSS_MENUS + struct wuss__menu *menu_chain; /* head (root) of the open pop-up + * menu chain, NULL when none open; + * consulted by mouse-click.c to + * dismiss on a click-away */ + int menu_eat_up; /* a menu opened on the last + * MOUSE_DOWN; swallow its matching + * MOUSE_UP so the release does not + * immediately pick row 0 */ #endif }; @@ -92,7 +130,7 @@ 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_backdrop_t bg; /* content background; colour==wuss_NO_BACKGROUND: none */ wuss_window_flags_t flags; point_t scroll; /* offset into virtual content space of the * content box's top-left; see wuss_window_set_scroll */ @@ -119,6 +157,41 @@ struct wuss_window wuss_window_t *wuss__window_at(wuss_t *wuss, point_t p); +/* Allocation through a wuss_t's configured hooks (see struct wuss::alloc). + * Every heap block a wuss_t owns -- windows, icons, icon-pointer arrays, menu + * nodes -- goes through these so a caller can supply its own allocator. */ +static inline void *wuss__malloc(const wuss_t *wuss, size_t size) +{ + return wuss->alloc.malloc(size); +} + +static inline void *wuss__realloc(const wuss_t *wuss, void *ptr, size_t size) +{ + return wuss->alloc.realloc(ptr, size); +} + +static inline void wuss__free(const wuss_t *wuss, void *ptr) +{ + wuss->alloc.free(ptr); +} + +/* Range-check a backdrop spec against the palette: its colour, and -- when a + * non-solid pattern is set -- its pattern index and background colour. + * Returns result_OK or result_WUSS_BAD_COLOUR. */ +result_t wuss__validate_backdrop(const wuss_t *wuss, + const wuss_backdrop_t *backdrop); + +/* Paint "backdrop" into "area" on "scr": a flat fill in the SOLID case, + * otherwise the 8x8 pattern tiled in colour over pattern_bg, phased against + * (origin_x, origin_y). No-op when colour is wuss_NO_BACKGROUND. Caller sets + * scr->clip. */ +void wuss__fill_backdrop(screen_t *scr, + const colour_t *palette, + const wuss_backdrop_t *backdrop, + const box_t *area, + int origin_x, + int origin_y); + /* clamp "desired" to the window's scrollable range; step the current offset * by "delta" and apply it. Core (furniture-independent) -- used by the wheel * and, when built, the scrollbar furniture. */ @@ -158,6 +231,16 @@ int wuss__subtract_boxes(const box_t *whole, int ncuts, box_t *out); +/* Given "n" single-rect blits, each moving "clean[i]" to "dest[i]", find an + * order in which no blit's destination overwrites a still-unread source of a + * later blit. Writes the piece indices to "order" (capacity + * WUSS_MAX_INVALIDATE_PIECES) and returns non-zero on success; returns zero + * when the overlap graph has a cycle and no safe order exists. */ +int wuss__order_pieces(const box_t *clean, + const box_t *dest, + int n, + int *order); + /* 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. */ @@ -193,7 +276,8 @@ static inline void wuss__release_packed(wuss_window_t *window) * the client's min_doc where it set one, but never below WUSS_MIN_CONTENT (a * window must stay big enough to grab) nor above the window's own doc extent * (a window can't be forced larger than the document it shows). */ -static inline void wuss__min_content(const wuss_window_t *window, size2d_t *min) +static inline void wuss__min_content(const wuss_window_t *window, + size2d_t *min) { min->w = CLAMP(window->min_doc.w, WUSS_MIN_CONTENT, MAX(window->doc.w, WUSS_MIN_CONTENT)); @@ -218,7 +302,8 @@ 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) +static inline void wuss__window_set_toggled(wuss_window_t *window, + int toggled) { if (toggled) window->state |= wuss_WINDOW_STATE_TOGGLED; @@ -337,4 +422,31 @@ static inline void wuss__furniture_carve_for(wuss_window_flags_t flags, } #endif /* WUSS_FURNITURE */ +/* Largest content width/height whose visible box (content + outline + + * titlebar + scrollbar/resize carve) still fits the screen from the + * window's current top-left. Never returns below WUSS_MIN_CONTENT: a + * window jammed hard against the far edge stays grabbable even though it + * then overhangs. Shared by wuss_window_create and wuss_window_resize so + * no path can produce a window larger than the screen. */ +static inline void wuss__max_content_on_screen(const wuss_window_t *window, + size2d_t *max) +{ + int outline_px, titlebar_height; + point_t carve; + + outline_px = wuss__outline_px(window); + titlebar_height = wuss__titlebar_height(window); + wuss__furniture_carve_for(window->flags, wuss__button_size(window), &carve); + + max->w = window->wuss->scr->size.w - window->visible.x0 + - 2 * outline_px - carve.x; + max->h = window->wuss->scr->size.h - window->visible.y0 + - 2 * outline_px - titlebar_height - carve.y; + + if (max->w < WUSS_MIN_CONTENT) + max->w = WUSS_MIN_CONTENT; + if (max->h < WUSS_MIN_CONTENT) + max->h = WUSS_MIN_CONTENT; +} + #endif /* IMPL_H */ diff --git a/libraries/wuss/invalidate.c b/libraries/wuss/invalidate.c index 1bca4190..1b027431 100644 --- a/libraries/wuss/invalidate.c +++ b/libraries/wuss/invalidate.c @@ -1,4 +1,4 @@ -/* invalidate.c -- wuss - minimal window manager */ +/* wuss/invalidate.c -- wuss - minimal window manager */ #include diff --git a/libraries/wuss/menu.h b/libraries/wuss/menu.h new file mode 100644 index 00000000..4d5a5b1e --- /dev/null +++ b/libraries/wuss/menu.h @@ -0,0 +1,41 @@ +/* wuss/menu.h -- wuss pop-up menu helper, internal */ + +#ifndef WUSS_MENU_IMPL_H +#define WUSS_MENU_IMPL_H + +#include "geom/point.h" + +#include "wuss/wuss.h" +#include "wuss/menu.h" + +/* One open level of a menu chain: its window and per-item icon handles, plus a + * link to the level it opened from. The head (root) is wuss->menu_chain; each + * child points at its parent so wuss_menu_close can walk leaf-to-root. */ +struct wuss__menu +{ + wuss_t *wuss; + wuss_window_t *window; /* the borderless menu window; for a + * borrowed-window level (see borrowed) the + * caller's own window instead */ + const wuss_menu_t *menu; /* borrowed source description; NULL for a + * borrowed-window level */ + wuss_icon_t **icons; /* owned array, menu->nitems entries, in + * item order; NULL for a borrowed-window + * level */ + struct wuss__menu *parent; /* level this one opened from, NULL at root */ + struct wuss__menu *child; /* open submenu level, NULL when none */ + wuss_menu_select_fn_t *on_select; + void *ctx; + int open_index; /* item index whose submenu `child` is, + * -1 when no child open */ + int borrowed; /* 1: `window` is a caller-owned window + * shown in place of a submenu -- hide it + * rather than close it on teardown */ +}; + +/* Called from wuss_mouse_click on a MOUSE_DOWN before the window hit-test: if a + * menu chain is open and `hit` is not one of its windows, close the whole chain + * and return 1 (the click is spent on dismissal). Returns 0 otherwise. */ +int wuss__menu_click_outside(wuss_t *wuss, const wuss_window_t *hit); + +#endif /* WUSS_MENU_IMPL_H */ diff --git a/libraries/wuss/menu/create-from-desc.c b/libraries/wuss/menu/create-from-desc.c new file mode 100644 index 00000000..74dc441c --- /dev/null +++ b/libraries/wuss/menu/create-from-desc.c @@ -0,0 +1,514 @@ +/* wuss/menu/create-from-desc.c -- build a menu tree from a descriptor string */ + +/* The token machine (Token / Parser / getopt / getname / getnext / isdelim) + * and the explicit menu stack are ported from PrivateEye's + * libs/appengine/wimp/menu/create-from-desc.c. What changed for wuss: the + * output is a heap wuss_menu_t tree rather than a wimp_menu, there is no title + * row (the first token of a { } block is parsed then discarded, so descriptor + * strings stay source-compatible), and a '>' submenu is deep-copied from the + * const wuss_menu_t * vararg so the whole result tree is owned uniformly and + * freed by one wuss_menu_destroy. */ + +#include +#include +#include +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/result.h" +#include "utils/array.h" + +#include "wuss/menu.h" + +/* ----------------------------------------------------------------------- */ + +enum { WUSS_MENU_DESC_DEPTH = 8 }; /* nested { } limit, as the Wimp original */ +enum { WUSS_MENU_DESC_TEXT = 64 }; /* per-token expansion buffer */ + +/* ----------------------------------------------------------------------- */ + +typedef enum { Name, Sep, DashSep, Push, Pop, End, Error } Token; + +enum { Tick = 1 << 0, Shade = 1 << 1, SubMenu = 1 << 2 }; + +typedef struct +{ + const char *p; + const char *start, *end; /* end exclusive */ + unsigned int flags; +} +Parser; + +static void getopt_(Parser *parser) +{ + const char *p; + int c; + + parser->flags = 0; + p = parser->p; + + for (;;) + { + c = *p; + if (c == '!') parser->flags |= Tick; + else if (c == '>') parser->flags |= SubMenu; + else if (c == '~') parser->flags |= Shade; + else { parser->p = p; return; } + p++; + } +} + +static int isdelim(int c) +{ + return c == ',' || c == '|' || c == '{' || c == '}' || c == '\0'; +} + +static void getname(Parser *parser) +{ + const char *p; + + getopt_(parser); + p = parser->p; + parser->start = p; + + while (!isdelim(*p)) + p++; + + parser->p = p; + + /* trim trailing whitespace so "Foo { " yields the name "Foo", not "Foo " */ + while (p > parser->start && isspace((unsigned char) p[-1])) + p--; + + parser->end = p; +} + +static Token getnext(Parser *parser) +{ + while (isspace((unsigned char) *parser->p)) + parser->p++; + + if (!isdelim(*parser->p)) + { + getname(parser); + if (parser->start == parser->end) + return Error; + return Name; + } + + switch (*parser->p++) + { + case ',': return Sep; + case '|': return DashSep; + case '{': return Push; + case '}': return Pop; + } + + assert(parser->p[-1] == '\0'); + return End; +} + +/* ----------------------------------------------------------------------- */ + +void wuss_menu_destroy(wuss_menu_t *menu) +{ + int i; + + if (menu == NULL) + return; + + for (i = 0; i < menu->nitems; i++) + { + free((void *) menu->items[i].text); + wuss_menu_destroy((wuss_menu_t *) menu->items[i].submenu); + } + free((void *) menu->title); + free((void *) menu->items); + free(menu); +} + +/* Deep-copy a caller-supplied menu tree so a '>' submenu can be owned by, and + * freed with, the result of wuss_menu_create_from_desc. */ +static result_t menu_deep_copy(const wuss_menu_t *src, wuss_menu_t **out) +{ + wuss_menu_t *m; + wuss_menu_item_t *items; + int i; + result_t rc; + + m = malloc(sizeof(*m)); + if (m == NULL) + return result_OOM; + + items = (src->nitems > 0) + ? calloc((size_t) src->nitems, sizeof(*items)) + : NULL; + if (src->nitems > 0 && items == NULL) + { + free(m); + return result_OOM; + } + + m->title = src->title ? strdup(src->title) : NULL; + if (src->title != NULL && m->title == NULL) + { + free(items); + free(m); + return result_OOM; + } + + m->items = items; + m->nitems = src->nitems; + + for (i = 0; i < src->nitems; i++) + { + items[i].text = strdup(src->items[i].text ? src->items[i].text : ""); + items[i].flags = src->items[i].flags; + items[i].submenu = NULL; + items[i].window = src->items[i].window; /* borrowed, as in the source */ + + if (items[i].text == NULL) + { + m->nitems = i; /* only [0, i) are populated -- keeps destroy correct */ + wuss_menu_destroy(m); + return result_OOM; + } + + if (src->items[i].submenu != NULL) + { + wuss_menu_t *child; + + rc = menu_deep_copy(src->items[i].submenu, &child); + if (rc != result_OK) + { + m->nitems = i + 1; + wuss_menu_destroy(m); + return rc; + } + items[i].submenu = child; + } + } + + *out = m; + return result_OK; +} + +/* ----------------------------------------------------------------------- */ + +/* A menu under construction: a growable wuss_menu_item_t array plus the + * pending '|' dash flag owed to the next-added item. */ +typedef struct +{ + wuss_menu_item_t *items; + int n; + int cap; + int pending_dash; +} +Building; + +static result_t build_emit(Building *b, + const char *text, + unsigned int flags, + wuss_menu_t *submenu) +{ + wuss_menu_item_t *it; + char *copy; + + if (array_grow((void **) &b->items, sizeof(*b->items), + b->n, &b->cap, 1, 4)) + return result_OOM; + + copy = strdup(text ? text : ""); + if (copy == NULL) + return result_OOM; + + it = &b->items[b->n++]; + it->text = copy; + it->flags = flags; + it->submenu = submenu; + it->window = NULL; /* array_grow leaves this uninitialised otherwise */ + return result_OK; +} + +static result_t build_add(Building *b, + const char *text, + unsigned int opt, + wuss_menu_t *submenu) +{ + unsigned int flags; + + flags = wuss_MENU_ITEM_NONE; + + /* '|' before this item draws a dashed rule above it: set DASHED on the item + * itself, which stays an ordinary interactive row. */ + if (b->pending_dash) + { + b->pending_dash = 0; + flags |= wuss_MENU_ITEM_DASHED; + } + + if (opt & Tick) + flags |= wuss_MENU_ITEM_TICKED; + if (opt & Shade) + flags |= wuss_MENU_ITEM_DISABLED; + + return build_emit(b, text, flags, submenu); +} + +/* Seal a Building into a heap wuss_menu_t, transferring its item array. */ +static wuss_menu_t *build_seal(Building *b) +{ + wuss_menu_t *m; + + m = malloc(sizeof(*m)); + if (m == NULL) + return NULL; + + /* ponytail: the descriptor's { } title token is still discarded, so a + * parser-built menu has no caption. Wire it through Building if that + * changes. */ + m->title = NULL; + m->items = b->items; + m->nitems = b->n; + + b->items = NULL; + b->n = b->cap = 0; + + return m; +} + +/* Free an unsealed Building (error path): everything in it is ours. */ +static void build_discard(Building *b) +{ + int i; + + for (i = 0; i < b->n; i++) + { + free((void *) b->items[i].text); + wuss_menu_destroy((wuss_menu_t *) b->items[i].submenu); + } + free(b->items); + b->items = NULL; + b->n = b->cap = 0; +} + +/* ----------------------------------------------------------------------- */ + +result_t wuss_menu_create_from_desc(wuss_menu_t **out, const char *desc, ...) +{ + Parser parser; + Building stack[WUSS_MENU_DESC_DEPTH]; + int need_title[WUSS_MENU_DESC_DEPTH]; + char *titles[WUSS_MENU_DESC_DEPTH]; + int sp; + va_list ap; + result_t rc; + char text[WUSS_MENU_DESC_TEXT]; + int i; + + assert(out != NULL); + assert(desc != NULL); + + parser.p = desc; + + for (i = 0; i < WUSS_MENU_DESC_DEPTH; i++) + { + stack[i].items = NULL; + stack[i].n = stack[i].cap = stack[i].pending_dash = 0; + need_title[i] = 0; + titles[i] = NULL; + } + + /* the descriptor's first token is the root menu's title, exactly as the + * first token inside a { } is that submenu's -- so the same strings port + * from PrivateEye unchanged */ + need_title[0] = 1; + + sp = 0; + rc = result_OK; + + va_start(ap, desc); + + for (;;) + { + Token tok; + + tok = getnext(&parser); + + if (tok == End) + break; + + if (tok == Error) + { + rc = result_BAD_ARG; + break; + } + + if (tok == Sep) + continue; + + if (tok == DashSep) + { + stack[sp].pending_dash = 1; + continue; + } + + if (tok == Push) + { + if (sp + 1 >= WUSS_MENU_DESC_DEPTH) + { + rc = result_BAD_ARG; /* too deep */ + break; + } + sp++; + need_title[sp] = 1; /* first token inside { } is a title, discard it */ + continue; + } + + if (tok == Pop) + { + wuss_menu_t *sub; + + if (sp == 0) + { + rc = result_BAD_ARG; /* } without { */ + break; + } + + sub = build_seal(&stack[sp]); + if (sub == NULL) + { + rc = result_OOM; + break; + } + sp--; + + if (stack[sp].n == 0) + { + wuss_menu_destroy(sub); + rc = result_BAD_ARG; /* { } with no preceding item */ + break; + } + stack[sp].items[stack[sp].n - 1].submenu = sub; + continue; + } + + /* tok == Name */ + { + char *q; + const char *s; + unsigned int opt; + int overflow; + + opt = parser.flags; + q = text; + overflow = 0; + + for (s = parser.start; s != parser.end; s++) + { + if (*s == '%' && (s + 1) != parser.end && s[1] == 's') + { + const char *arg; + size_t len; + + arg = va_arg(ap, const char *); + len = strlen(arg); + if ((size_t) (q - text) + len >= sizeof(text)) + { + overflow = 1; + break; + } + memcpy(q, arg, len); + q += len; + s++; + } + else + { + if ((size_t) (q - text) + 1 >= sizeof(text)) + { + overflow = 1; + break; + } + *q++ = *s; + } + } + + if (overflow) + { + rc = result_BAD_ARG; + break; + } + *q = '\0'; + + if (need_title[sp]) + { + /* the menu's title token -- captured, not emitted as an item; kept + * only for the root (sp 0), submenus get no caption */ + need_title[sp] = 0; + if (sp == 0) + { + titles[0] = strdup(text); + if (titles[0] == NULL) + { + rc = result_OOM; + break; + } + } + continue; + } + + { + wuss_menu_t *submenu; + + submenu = NULL; + if (opt & SubMenu) + { + const wuss_menu_t *borrowed; + + borrowed = va_arg(ap, const wuss_menu_t *); + rc = menu_deep_copy(borrowed, &submenu); + if (rc != result_OK) + break; + } + + rc = build_add(&stack[sp], text, opt, submenu); + if (rc != result_OK) + { + wuss_menu_destroy(submenu); + break; + } + } + } + } + + va_end(ap); + + if (rc == result_OK && sp != 0) + rc = result_BAD_ARG; /* unclosed { */ + + if (rc != result_OK) + { + for (i = 0; i < WUSS_MENU_DESC_DEPTH; i++) + { + build_discard(&stack[i]); + free(titles[i]); + } + return rc; + } + + *out = build_seal(&stack[0]); + if (*out == NULL) + { + build_discard(&stack[0]); + free(titles[0]); + return result_OOM; + } + + (*out)->title = titles[0]; /* ownership transferred; NULL if none given */ + + return result_OK; +} diff --git a/libraries/wuss/menu/menu.c b/libraries/wuss/menu/menu.c new file mode 100644 index 00000000..9db1d39e --- /dev/null +++ b/libraries/wuss/menu/menu.c @@ -0,0 +1,581 @@ +/* wuss/menu/menu.c -- wuss pop-up menu helper */ + +#include +#include +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/result.h" +#include "base/utils.h" +#include "framebuf/bmfont.h" +#include "geom/box.h" +#include "geom/point.h" +#include "geom/size.h" + +#include "wuss/icon.h" +#include "wuss/task.h" +#include "wuss/window.h" +#include "wuss/wuss.h" + +#include "../impl.h" + +/* Row padding above/below the glyph, and the gutters left for the tick (left) + * and the submenu arrow (right). All in pixels. */ +#define WUSS_MENU_ROW_PAD 4 +#define WUSS_MENU_TICK_W 14 +#define WUSS_MENU_ARROW_W 14 +#define WUSS_MENU_TEXT_PAD 6 +#define WUSS_MENU_SUBMENU_OVERLAP 2 /* px a submenu overlaps its parent's right edge */ + +/* ----------------------------------------------------------------------- */ + +static result_t wuss__menu_spawn(wuss_t *wuss, + const wuss_menu_t *menu, + point_t at, + struct wuss__menu *parent, + wuss_menu_select_fn_t *on_select, + void *ctx, + struct wuss__menu **out); + +/* Close `node` and everything it opened, leaf-first. The caller clears any + * parent's child pointer. */ +static void wuss__menu_close_from(struct wuss__menu *node) +{ + if (node == NULL) + return; + + wuss__menu_close_from(node->child); + node->child = NULL; + + { + wuss_t *w; + + w = node->wuss; + if (node->borrowed) + wuss_window_set_hidden(node->window, 1); /* caller's window: hide, keep */ + else + wuss_window_close(node->window); + wuss__free(w, node->icons); /* NULL for a borrowed level */ + wuss__free(w, node); + } +} + +/* Compute where a submenu (or a borrowed window standing in for one) opens + * off the row `icon` in parent level `self`: content top-left in screen + * space, the child's own titlebar then sitting above it so the two rows line + * up. Mirrors the maths in wuss__menu_handle's MOUSE_MOVE branch. */ +static point_t wuss__submenu_anchor(struct wuss__menu *self, + const wuss_icon_t *icon) +{ + box_t wb; + box_t ib; + point_t scroll; + point_t at; + + wuss_window_get_content_bounds(self->window, &wb); + wuss_icon_get_bbox(icon, &ib); + wuss_window_get_scroll(self->window, &scroll); + + at.x = wb.x1 - WUSS_MENU_SUBMENU_OVERLAP; + at.y = wb.y0 + ib.y0 - scroll.y; + return at; +} + +/* Open item `index`'s borrowed window as level `self->child`: position it + * where a submenu would appear, un-hide it, bring it to the front. */ +static result_t wuss__menu_open_window(struct wuss__menu *self, int index) +{ + struct wuss__menu *node; + wuss_window_t *win; + point_t at; + + win = self->menu->items[index].window; + + node = wuss__malloc(self->wuss, sizeof(*node)); + if (node == NULL) + return result_OOM; + + node->wuss = self->wuss; + node->window = win; + node->menu = NULL; + node->icons = NULL; + node->parent = self; + node->child = NULL; + node->on_select = self->on_select; + node->ctx = self->ctx; + node->open_index = -1; + node->borrowed = 1; + + at = wuss__submenu_anchor(self, self->icons[index]); + wuss_window_move(win, at); + wuss_window_set_hidden(win, 0); + wuss_window_restack(win, wuss_ZORDER_FRONT); + + self->child = node; + return result_OK; +} + +/* ----------------------------------------------------------------------- */ + +/* The menu window's task delegate. task_data is the struct wuss__menu. */ +static result_t wuss__menu_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) +{ + struct wuss__menu *self; + const wuss_icon_t *icon; + const wuss_menu_item_t *item; + int index; + int i; + + NOT_USED(window); + + self = task_data; + + if (event->kind != wuss_EVENT_ICON) + return result_OK; + + icon = event->data.icon.icon; + index = -1; + for (i = 0; i < self->menu->nitems; i++) + { + if (self->icons[i] == icon) + { + index = i; + break; + } + } + if (index < 0) + return result_OK; + + item = &self->menu->items[index]; + + if (item->flags & wuss_MENU_ITEM_DISABLED) + return result_OK; + + if (event->data.icon.action == wuss_MOUSE_MOVE) + { + point_t at; + + /* Hovering a different row closes any submenu the previous row opened. */ + if (self->child != NULL && self->open_index != index) + { + wuss__menu_close_from(self->child); + self->child = NULL; + self->open_index = -1; + } + + if (self->child != NULL) + return result_OK; + + /* A borrowed window opens where a submenu would; a submenu spawns as a + * fresh menu level. Either lines its row 0 up with this row -- the + * child's own titlebar sits above `at`. */ + if (item->window != NULL) + { + if (wuss__menu_open_window(self, index) == result_OK) + self->open_index = index; + return result_OK; + } + + if (item->submenu == NULL) + return result_OK; + + at = wuss__submenu_anchor(self, icon); + if (wuss__menu_spawn(self->wuss, item->submenu, at, self, + self->on_select, self->ctx, &self->child) == result_OK) + self->open_index = index; + + return result_OK; + } + + if (event->data.icon.action == wuss_MOUSE_UP) + { + wuss_button_t button; + struct wuss__menu *root; + wuss_menu_select_fn_t *on_select; + const wuss_menu_t *menu; + void *ctx; + + button = event->data.icon.button; + + if (item->submenu != NULL || item->window != NULL) + return result_OK; /* a submenu/window row opens on hover, not a pick */ + + /* Capture what the callback needs, then tear the chain down *before* + * invoking it: on_select may itself open a new menu (freeing this one), + * so `self` must not be touched afterwards. */ + on_select = self->on_select; + menu = self->menu; + ctx = self->ctx; + + if (!(button & wuss_BUTTON_ADJUST)) + { + root = self; + while (root->parent != NULL) + root = root->parent; + + root->wuss->menu_chain = NULL; + wuss__menu_close_from(root); + } + + if (on_select != NULL) + on_select(menu, index, button, ctx); + } + + return result_OK; +} + +/* ----------------------------------------------------------------------- */ + +/* Measure the menu, create its borderless window and one MENU_ENTRY icon per + * item, and hang the node off *out. */ +static result_t wuss__menu_spawn(wuss_t *wuss, + const wuss_menu_t *menu, + point_t at, + struct wuss__menu *parent, + wuss_menu_select_fn_t *on_select, + void *ctx, + struct wuss__menu **out) +{ + struct wuss__menu *node; + wuss_icon_spec_t *specs; + wuss_icon_t **made; + wuss_task_t task; + result_t rc; + int fh; + int pitch; + int sep_h; + int y; + int widest; + int width, height; + int doc_h; + int max_h; + int i; + int ndashed; + int nspecs; + int s; + int outline_px; + int titlebar_height; + point_t carve; + wuss_window_flags_t menu_flags; + size2d_t doc; + size2d_t min_doc; + box_t content; + + assert(wuss != NULL); + assert(menu != NULL); + assert(wuss->font != NULL); + + if (menu->nitems <= 0) + return result_WUSS_BAD_ICON; + + menu_flags = wuss_WINDOW_NO_CLOSE | wuss_WINDOW_NO_BACK + | wuss_WINDOW_NO_TOGGLE_SIZE + | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL + | wuss_WINDOW_NO_RESIZE; + + outline_px = wuss__outline_px_for(menu_flags); + titlebar_height = wuss__titlebar_height_for(wuss, menu_flags); + + bmfont_get_info(wuss->font, NULL, &fh); + pitch = fh + 2 * WUSS_MENU_ROW_PAD; + sep_h = 2 * WUSS_MENU_ROW_PAD; + + ndashed = 0; + for (i = 0; i < menu->nitems; i++) + if (menu->items[i].flags & wuss_MENU_ITEM_DASHED) + ndashed++; + nspecs = menu->nitems + ndashed; + + widest = 0; + for (i = 0; i < menu->nitems; i++) + { + const char *text; + int len; + int split; + bmfont_width_t w; + + text = menu->items[i].text ? menu->items[i].text : ""; + len = (int) strlen(text); + if (len == 0) + continue; /* empty label (bare rule row); nothing to measure */ + if (bmfont_measure(wuss->font, text, len, + INT_MAX, &split, &w) == result_OK && (int) w > widest) + widest = (int) w; + } + + width = WUSS_MENU_TICK_W + widest + WUSS_MENU_TEXT_PAD + WUSS_MENU_ARROW_W; + + /* every item is a full row now; a dashed item also gets a sep_h rule above. + * doc_h is the whole menu; `height` is what the window actually shows. When + * the menu is taller than the space the screen leaves for it, cap `height` + * and give the window a real vertical scrollbar -- the icon draw and hit + * test already honour window->scroll, so the rows just move under it. */ + doc_h = menu->nitems * pitch + ndashed * sep_h; + + max_h = wuss->scr->size.h - 2 * outline_px - titlebar_height; + + if (doc_h > max_h) + { + height = max_h; + menu_flags &= (wuss_window_flags_t) ~wuss_WINDOW_NO_VSCROLL; + } + else + { + height = doc_h; + } + + wuss__furniture_carve_for(menu_flags, wuss__button_size_for(wuss, menu_flags), + &carve); + + /* Nudge onto the screen where it can be, but keep the top-left on screen. + * `at` is the content top-left; the window's visible box also spans the + * outline on all four sides, the titlebar above and (for a scrolling menu) + * the scrollbar carve on the right, so clamp against that whole extent -- + * otherwise wuss_window_create's own on-screen size clamp trims the content + * and the last row (e.g. a trailing "Quit") is cropped. */ + if (at.x + width + outline_px + carve.x > wuss->scr->size.w) + at.x = wuss->scr->size.w - width - outline_px - carve.x; + if (at.y + height + outline_px > wuss->scr->size.h) + at.y = wuss->scr->size.h - height - outline_px; + if (at.x - outline_px < 0) + at.x = outline_px; + if (at.y - outline_px - titlebar_height < 0) + at.y = outline_px + titlebar_height; + + node = wuss__malloc(wuss, sizeof(*node)); + if (node == NULL) + return result_OOM; + + node->icons = wuss__malloc(wuss, (size_t) menu->nitems * sizeof(*node->icons)); + specs = wuss__malloc(wuss, (size_t) nspecs * sizeof(*specs)); + made = wuss__malloc(wuss, (size_t) nspecs * sizeof(*made)); + if (node->icons == NULL || specs == NULL || made == NULL) + { + wuss__free(wuss, made); + wuss__free(wuss, specs); + wuss__free(wuss, node->icons); + wuss__free(wuss, node); + return result_OOM; + } + memset(node->icons, 0, (size_t) menu->nitems * sizeof(*node->icons)); + + node->wuss = wuss; + node->window = NULL; + node->menu = menu; + node->parent = parent; + node->child = NULL; + node->on_select = on_select; + node->ctx = ctx; + node->open_index = -1; + node->borrowed = 0; + + /* One MENU_ENTRY icon per item, in item order, preceded by an inert + * wuss_ICON_TYPE_RULE icon for each dashed item. specs[] therefore holds + * nspecs entries; s walks it while i walks the items. */ + y = 0; + s = 0; + for (i = 0; i < menu->nitems; i++) + { + const wuss_menu_item_t *item; + wuss_icon_flags_t flags; + + item = &menu->items[i]; + flags = wuss_ICON_FLAGS_NONE; + + if (item->flags & wuss_MENU_ITEM_DASHED) + { + flags |= wuss_ICON_FLAGS_SEPARATOR; + + specs[s].type = wuss_ICON_TYPE_RULE; + specs[s].bbox.x0 = 0; + specs[s].bbox.y0 = y; + specs[s].bbox.x1 = width; + specs[s].bbox.y1 = y + sep_h; + specs[s].text = ""; + specs[s].fg = wuss->palettecache.black; + specs[s].bg = wuss_NO_BACKGROUND; + specs[s].flags = wuss_ICON_FLAGS_NONE; + s++; + y += sep_h; + } + + if (item->flags & wuss_MENU_ITEM_DISABLED) + flags |= wuss_ICON_FLAGS_DISABLED; + if (item->submenu != NULL || item->window != NULL) + flags |= wuss_ICON_FLAGS_SUBMENU; + + specs[s].type = wuss_ICON_TYPE_MENU_ENTRY; + specs[s].bbox.x0 = 0; + specs[s].bbox.y0 = y; + specs[s].bbox.x1 = width; + specs[s].bbox.y1 = y + pitch; + specs[s].text = item->text ? item->text : ""; + specs[s].fg = wuss->palettecache.black; + specs[s].bg = wuss_NO_BACKGROUND; + specs[s].flags = flags; + s++; + + y += pitch; + } + + /* doc spans the whole menu so the window can scroll it; min_doc is what the + * window shows, so its own resize floor never exceeds what fits. */ + doc = SIZE2D(width, doc_h); + min_doc = SIZE2D(width, height); + task = wuss_task_start(wuss__menu_handle, node); + + /* `at` is the content top-left, already clamped on screen above. Create the + * window there directly -- creating it elsewhere and wuss_window_move-ing it + * afterwards would blit its not-yet-rendered pixels and leave the titlebar + * unpainted. */ + content.x0 = at.x; + content.y0 = at.y; + content.x1 = at.x + width; + content.y1 = at.y + height; + + rc = wuss_window_create(wuss, &content, + menu->title ? menu->title : "", + menu_flags, + wuss_BACKDROP_COLOUR(wuss->palettecache.white), + &task, doc, min_doc, &node->window); + if (rc != result_OK) + { + wuss__free(wuss, made); + wuss__free(wuss, specs); + wuss__free(wuss, node->icons); + wuss__free(wuss, node); + return rc; + } + + rc = wuss_icon_create_array(node->window, specs, nspecs, made); + wuss__free(wuss, specs); + if (rc != result_OK) + { + wuss__free(wuss, made); + wuss_window_close(node->window); + wuss__free(wuss, node->icons); + wuss__free(wuss, node); + return rc; + } + + /* keep only the entry handles, item-indexed; the rule icons stay owned by + * the window and need no further handling. Walk made[] with the same + * rule-then-entry interleave as the specs loop. */ + s = 0; + for (i = 0; i < menu->nitems; i++) + { + if (menu->items[i].flags & wuss_MENU_ITEM_DASHED) + s++; + node->icons[i] = made[s]; + s++; + + if (menu->items[i].flags & wuss_MENU_ITEM_TICKED) + wuss_icon_set_selected(node->icons[i], 1); + } + wuss__free(wuss, made); + + *out = node; + return result_OK; +} + +/* ----------------------------------------------------------------------- */ + +result_t wuss_menu_open(wuss_t *wuss, + const wuss_menu_t *menu, + point_t at, + wuss_menu_select_fn_t *on_select, + void *ctx, + wuss_menu_handle_t *out) +{ + struct wuss__menu *root; + result_t rc; + + assert(wuss != NULL); + assert(menu != NULL); + + if (wuss->menu_chain != NULL) + { + wuss__menu_close_from(wuss->menu_chain); + wuss->menu_chain = NULL; + } + + /* RISC OS convention: the pointer opens the menu sitting a little inside its + * first item, not on the top-left corner. Shift the content top-left up and + * left so `at` (the pointer) lands over row 0. */ + at.x -= WUSS_MENU_TICK_W; + at.y -= WUSS_MENU_ROW_PAD; + + rc = wuss__menu_spawn(wuss, menu, at, NULL, on_select, ctx, &root); + if (rc != result_OK) + return rc; + + wuss->menu_chain = root; + + /* A menu is opened from a task's MOUSE_DOWN handler; the matching MOUSE_UP is + * still to come and would land on the fresh menu's row 0. Mark it to be + * eaten. wuss_mouse_click clears this on the next MOUSE_UP whether or not it + * hit the menu. */ + wuss->menu_eat_up = 1; + + if (out != NULL) + *out = root; + return result_OK; +} + +void wuss_menu_close(wuss_menu_handle_t handle) +{ + struct wuss__menu *root; + + if (handle == NULL) + return; + + root = handle; + while (root->parent != NULL) + root = root->parent; + + if (root->wuss->menu_chain != root) + return; /* stale handle */ + + root->wuss->menu_chain = NULL; + wuss__menu_close_from(root); +} + +int wuss_menu_is_open(wuss_menu_handle_t handle) +{ + struct wuss__menu *root; + + if (handle == NULL) + return 0; + + root = handle; + while (root->parent != NULL) + root = root->parent; + + return root->wuss->menu_chain == root; +} + +/* ----------------------------------------------------------------------- */ + +int wuss__menu_click_outside(wuss_t *wuss, const wuss_window_t *hit) +{ + struct wuss__menu *node; + + if (wuss->menu_chain == NULL) + return 0; + + for (node = wuss->menu_chain; node != NULL; node = node->child) + { + if (node->window == hit) + return 0; + } + + wuss__menu_close_from(wuss->menu_chain); + wuss->menu_chain = NULL; + return 1; +} diff --git a/libraries/wuss/mouse-click.c b/libraries/wuss/mouse-click.c index 8028deb7..68037d55 100644 --- a/libraries/wuss/mouse-click.c +++ b/libraries/wuss/mouse-click.c @@ -1,4 +1,4 @@ -/* mouse-click.c -- wuss - minimal window manager */ +/* wuss/mouse-click.c -- wuss - minimal window manager */ #include "impl.h" @@ -15,6 +15,8 @@ result_t wuss_mouse_click(wuss_t *wuss, x = p.x; y = p.y; + wuss->pointer = p; + #ifdef WUSS_ICONS /* Release a held button icon on any MOUSE_UP, before the hit-test picks a * window: the up may land on a window that opened over the icon's owner on @@ -24,9 +26,9 @@ result_t wuss_mouse_click(wuss_t *wuss, wuss_icon_t *pressed = wuss->pressed_icon; wuss->pressed_icon = NULL; - if (pressed->pressed) + if (wuss__icon_pressed(pressed)) { - pressed->pressed = 0; + wuss__icon_set_state(pressed, wuss_ICON_STATE_PRESSED, 0); wuss__icon_invalidate(pressed); } } @@ -49,6 +51,27 @@ result_t wuss_mouse_click(wuss_t *wuss, if (hit != NULL) *hit = win; +#ifdef WUSS_MENUS + /* The MOUSE_UP that follows the MENU press which opened the chain is spent: + * without this it would land on the new menu's row 0 and pick it. */ + if (action == wuss_MOUSE_UP && wuss->menu_eat_up) + { + wuss->menu_eat_up = 0; + return result_OK; + } + + /* Any fresh press ends the eat-up window: the release it pairs with is a + * real click, not the tail of the menu-opening press. */ + if (action == wuss_MOUSE_DOWN) + wuss->menu_eat_up = 0; + + /* A press outside every window in the open menu chain dismisses the chain + * and is spent doing so. Presses inside the chain fall through to the menu + * window's own delegate. */ + if (action == wuss_MOUSE_DOWN && wuss__menu_click_outside(wuss, win)) + return result_OK; +#endif + if (win == NULL) return result_OK; @@ -56,7 +79,7 @@ result_t wuss_mouse_click(wuss_t *wuss, { wuss_furniture_region_t region; - region = wuss__furniture_hit_test(win, POINT(x, y)); + region = wuss->furniture_ops->hit_test(win, POINT(x, y)); if (region == wuss_FURNITURE_CLOSE && action == wuss_MOUSE_DOWN && @@ -101,7 +124,7 @@ result_t wuss_mouse_click(wuss_t *wuss, { case wuss_FURNITURE_TOGGLE_SIZE: if (button & wuss_BUTTON_SELECT) - wuss__furniture_toggle_size(win); + wuss->furniture_ops->toggle_size(win); break; case wuss_FURNITURE_VSCROLL_UP: wuss__scroll_step(win, POINT(0, -step)); @@ -124,7 +147,8 @@ result_t wuss_mouse_click(wuss_t *wuss, if (region == wuss_FURNITURE_CLOSE || region == wuss_FURNITURE_TITLE) { - if (action == wuss_MOUSE_DOWN) + if (action == wuss_MOUSE_DOWN && + (button & (wuss_BUTTON_SELECT | wuss_BUTTON_ADJUST))) { box_t content; @@ -144,7 +168,8 @@ result_t wuss_mouse_click(wuss_t *wuss, region == wuss_FURNITURE_VSCROLL_WELL || region == wuss_FURNITURE_HSCROLL_WELL) { - if (action == wuss_MOUSE_DOWN) + if (action == wuss_MOUSE_DOWN && + (button & (wuss_BUTTON_SELECT | wuss_BUTTON_ADJUST))) { point_t scroll; @@ -197,15 +222,24 @@ result_t wuss_mouse_click(wuss_t *wuss, if (action == wuss_MOUSE_DOWN && (button & (wuss_BUTTON_SELECT | wuss_BUTTON_ADJUST))) { - icon->pressed = 1; + wuss__icon_set_state(icon, wuss_ICON_STATE_PRESSED, 1); wuss->pressed_icon = icon; wuss__icon_invalidate(icon); } - else if (action == wuss_MOUSE_UP && icon->pressed) + else if (action == wuss_MOUSE_UP && wuss__icon_pressed(icon)) { - icon->pressed = 0; + wuss__icon_set_state(icon, wuss_ICON_STATE_PRESSED, 0); wuss->pressed_icon = NULL; wuss__icon_invalidate(icon); + + /* a completed click latches radio/option state before the task is + * told, so the wuss_EVENT_ICON handler sees the new value */ + if (icon->type == wuss_ICON_TYPE_OPTION) + wuss__icon_select(icon, !wuss__icon_selected(icon)); + else if (icon->type == wuss_ICON_TYPE_RADIO) + wuss__icon_select(icon, + (button & wuss_BUTTON_ADJUST) ? !wuss__icon_selected(icon) + : 1); } event.kind = wuss_EVENT_ICON; diff --git a/libraries/wuss/mouse-move.c b/libraries/wuss/mouse-move.c index e07144ee..32453285 100644 --- a/libraries/wuss/mouse-move.c +++ b/libraries/wuss/mouse-move.c @@ -1,4 +1,4 @@ -/* mouse-move.c -- wuss - minimal window manager */ +/* wuss/mouse-move.c -- wuss - minimal window manager */ #include "impl.h" @@ -10,6 +10,8 @@ result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit) x = p.x; y = p.y; + wuss->pointer = p; + #ifdef WUSS_FURNITURE if (wuss->furniture.dragging != NULL) { @@ -20,15 +22,15 @@ result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit) switch (wuss->furniture.drag_kind) { case wuss_FURNITURE_DRAG_RESIZE: - wuss__furniture_drag_resize(win, POINT(x, y)); + wuss->furniture_ops->drag_resize(win, POINT(x, y)); break; case wuss_FURNITURE_DRAG_VSCROLL_SAUSAGE: - wuss__furniture_drag_sausage(win, y - wuss->furniture.drag.y, wuss->furniture.drag_scroll_start, 0); + wuss->furniture_ops->drag_sausage(win, y - wuss->furniture.drag.y, wuss->furniture.drag_scroll_start, 0); break; case wuss_FURNITURE_DRAG_HSCROLL_SAUSAGE: - wuss__furniture_drag_sausage(win, x - wuss->furniture.drag.x, wuss->furniture.drag_scroll_start, 1); + wuss->furniture_ops->drag_sausage(win, x - wuss->furniture.drag.x, wuss->furniture.drag_scroll_start, 1); break; case wuss_FURNITURE_DRAG_MOVE: @@ -46,14 +48,31 @@ result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit) *hit = win; if (win == NULL) + { +#ifdef WUSS_ICONS + wuss__icon_set_hover(wuss, NULL); +#endif return result_OK; + } #ifdef WUSS_FURNITURE - if (wuss__furniture_hit_test(win, POINT(x, y)) != wuss_FURNITURE_CONTENT) + if (wuss->furniture_ops->hit_test(win, POINT(x, y)) != wuss_FURNITURE_CONTENT) + { +#ifdef WUSS_ICONS + wuss__icon_set_hover(wuss, NULL); +#endif return result_OK; + } +#endif + + if (win->task.handle == NULL) + { +#ifdef WUSS_ICONS + wuss__icon_set_hover(wuss, NULL); #endif + return result_OK; + } - if (win->task.handle != NULL) { box_t content; point_t doc_point; @@ -70,6 +89,8 @@ result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit) icon = wuss__icon_hit_test(win, doc_point); + wuss__icon_set_hover(wuss, icon); + /* Clear the pressed state of any button the pointer has left. This does * not re-press a button on drag-back-in, and does not track which mouse * button is held -- wuss keeps no persistent "button down over content" @@ -78,9 +99,9 @@ result_t wuss_mouse_move(wuss_t *wuss, point_t p, wuss_window_t **hit) { wuss_icon_t *it = win->icons[k]; - if (it->pressed && it != icon) + if (wuss__icon_pressed(it) && it != icon) { - it->pressed = 0; + wuss__icon_set_state(it, wuss_ICON_STATE_PRESSED, 0); if (wuss->pressed_icon == it) wuss->pressed_icon = NULL; wuss__icon_invalidate(it); diff --git a/libraries/wuss/nearest-colour.c b/libraries/wuss/nearest-colour.c new file mode 100644 index 00000000..39fe3800 --- /dev/null +++ b/libraries/wuss/nearest-colour.c @@ -0,0 +1,40 @@ +/* wuss/nearest-colour.c -- wuss - minimal window manager */ + +#include + +#include "framebuf/pixelfmt.h" + +#include "impl.h" + +wuss_colour_t wuss_nearest_colour(const wuss_t *wuss, int r, int g, int b) +{ + unsigned long best_d; + wuss_colour_t best_i; + int i; + + assert(wuss != NULL); + assert(wuss->npalette > 0); + + best_d = ~0UL; + best_i = 0; + for (i = 0; i < wuss->npalette; i++) + { + unsigned int px; + long dr, dg, db; + unsigned long d; + + /* rgba8888 is 0xAABBGGRR (see pixelfmt.h). */ + px = wuss->palette[i].primary; + dr = r - (long) ( px & 0xFF); + dg = g - (long) ((px >> 8) & 0xFF); + db = b - (long) ((px >> 16) & 0xFF); + d = (unsigned long) (dr * dr + dg * dg + db * db); + if (d < best_d) + { + best_d = d; + best_i = i; + } + } + + return best_i; +} diff --git a/libraries/wuss/redraw.c b/libraries/wuss/redraw.c index d86e3567..38a0cafc 100644 --- a/libraries/wuss/redraw.c +++ b/libraries/wuss/redraw.c @@ -1,4 +1,4 @@ -/* redraw.c -- wuss - minimal window manager */ +/* wuss/redraw.c -- wuss - minimal window manager */ #include "impl.h" @@ -13,11 +13,14 @@ static void redraw_window(wuss_t *wuss, box_t pieces[WUSS_MAX_INVALIDATE_PIECES]; int npieces, i; + if (win->flags & wuss_WINDOW_HIDDEN) + return; /* not drawn while hidden */ + if (box_intersection(&win->visible, full, &visible_clipped)) return; /* offscreen */ #ifdef WUSS_FURNITURE - wuss__furniture_draw(wuss, win, full); + wuss->furniture_ops->draw(wuss, win, full); #endif wuss__content_box(win, &content); @@ -34,10 +37,11 @@ static void redraw_window(wuss_t *wuss, { wuss->scr->clip = pieces[i]; - if (win->bg != wuss_NO_BACKGROUND) - screen_draw_rect(wuss->scr, - content.x0, content.y0, box_size(&content), - wuss->palette[win->bg]); + /* phase any pattern against the scroll origin so it locks to the + * content rather than crawling as the window scrolls */ + wuss__fill_backdrop(wuss->scr, wuss->palette, &win->bg, &content, + content.x0 - win->scroll.x, + content.y0 - win->scroll.y); if (win->task.handle != NULL) { @@ -105,12 +109,8 @@ result_t wuss_redraw(wuss_t *wuss) 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]); - } + wuss->scr->clip = full; + wuss__fill_backdrop(wuss->scr, wuss->palette, &wuss->backdrop, &full, 0, 0); rc = result_OK; redraw_from(wuss, wuss->z_order.next, &full, &rc); @@ -137,13 +137,9 @@ 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(wuss->dirty[i].x1 - wuss->dirty[i].x0, wuss->dirty[i].y1 - wuss->dirty[i].y0), - wuss->palette[wuss->backdrop]); - } + wuss->scr->clip = wuss->dirty[i]; + wuss__fill_backdrop(wuss->scr, wuss->palette, &wuss->backdrop, + &wuss->dirty[i], 0, 0); redraw_from(wuss, wuss->z_order.next, &wuss->dirty[i], &rc); } diff --git a/libraries/wuss/scroll-step.c b/libraries/wuss/scroll-step.c index c69766e8..5267b006 100644 --- a/libraries/wuss/scroll-step.c +++ b/libraries/wuss/scroll-step.c @@ -1,4 +1,4 @@ -/* scroll-step.c -- wuss - clamp and apply a scroll offset */ +/* wuss/scroll-step.c -- clamp and apply a scroll offset */ #include "impl.h" @@ -32,6 +32,16 @@ void wuss__scroll_step(wuss_window_t *window, point_t delta) { point_t scroll; + /* A window that declared an axis non-scrollable (e.g. a pop-up menu) has no + * scrollbar to clamp against and its geometry assumes scroll == 0, so a wheel + * turn over it must not move the content -- doing so corrupts the display. */ + if (window->flags & wuss_WINDOW_NO_HSCROLL) + delta.x = 0; + if (window->flags & wuss_WINDOW_NO_VSCROLL) + delta.y = 0; + if (delta.x == 0 && delta.y == 0) + return; + wuss_window_get_scroll(window, &scroll); scroll.x += delta.x; scroll.y += delta.y; diff --git a/libraries/wuss/scroll.c b/libraries/wuss/scroll.c index bcabbb43..d956954c 100644 --- a/libraries/wuss/scroll.c +++ b/libraries/wuss/scroll.c @@ -1,7 +1,31 @@ -/* scroll.c -- wuss - minimal window manager */ +/* wuss/scroll.c -- wuss - minimal window manager */ #include "impl.h" +/* Scrolling moves content under a stationary pointer, so the icon the pointer + * now sits over may differ from before. Re-resolve the hovered icon so a + * highlighted wuss_ICON_TYPE_MENU_ENTRY does not keep its highlight after + * scrolling out from under the pointer. */ +static void wuss__scroll_rehover(wuss_t *wuss, + wuss_window_t *win, + point_t screen_point) +{ +#ifdef WUSS_ICONS + box_t content; + point_t doc_point; + + wuss__content_box(win, &content); + doc_point.x = screen_point.x - content.x0 + win->scroll.x; + doc_point.y = screen_point.y - content.y0 + win->scroll.y; + + wuss__icon_set_hover(wuss, wuss__icon_hit_test(win, doc_point)); +#else + (void) wuss; + (void) win; + (void) screen_point; +#endif +} + result_t wuss_scroll(wuss_t *wuss, point_t p, int delta, wuss_window_t **hit) { wuss_window_t *win; @@ -42,11 +66,13 @@ result_t wuss_scroll(wuss_t *wuss, point_t p, int delta, wuss_window_t **hit) event.data.scroll.delta = delta; wuss__scroll_step(win, POINT(0, delta)); + wuss__scroll_rehover(wuss, win, POINT(x, y)); return win->task.handle(win, &event, win->task.task_data); } wuss__scroll_step(win, POINT(0, delta)); + wuss__scroll_rehover(wuss, win, POINT(x, y)); return result_OK; } diff --git a/libraries/wuss/set-palette.c b/libraries/wuss/set-palette.c new file mode 100644 index 00000000..6a550a7a --- /dev/null +++ b/libraries/wuss/set-palette.c @@ -0,0 +1,56 @@ +/* wuss/set-palette.c -- swap the system palette mid-session */ + +#include +#include + +#include "geom/box.h" + +#include "impl.h" + +result_t wuss_set_palette(wuss_t *wuss, + const colour_t *palette, + int npalette) +{ + wuss_event_t event; + box_t screen; + result_t rc; + list_t *e; + + assert(wuss != NULL); + assert(palette != NULL); + + /* Same length only: keeps every stored furniture/bevel/backdrop colour + * index in range, and matches the fixed-size framebuffer palette on a + * paletted screen. */ + if (npalette != wuss->npalette) + return result_BAD_ARG; + + memcpy(wuss->palette, palette, npalette * sizeof(*wuss->palette)); + + wuss->palettecache.white = wuss_nearest_colour(wuss, 255, 255, 255); + wuss->palettecache.black = wuss_nearest_colour(wuss, 0, 0, 0); + + rc = result_OK; + event.kind = wuss_EVENT_PALETTE; + for (e = wuss->z_order.next; e != NULL; e = e->next) + { + wuss_window_t *win; + result_t crc; + + win = (wuss_window_t *) e; + if (win->task.handle == NULL) + continue; + + crc = win->task.handle(win, &event, win->task.task_data); + if (crc != result_OK && rc == result_OK) + rc = crc; + } + + screen.x0 = 0; + screen.y0 = 0; + screen.x1 = wuss->scr->size.w; + screen.y1 = wuss->scr->size.h; + wuss_invalidate(wuss, &screen); + + return rc; +} diff --git a/libraries/wuss/task/start.c b/libraries/wuss/task/start.c index 65eb812b..f9736b4a 100644 --- a/libraries/wuss/task/start.c +++ b/libraries/wuss/task/start.c @@ -1,4 +1,4 @@ -/* start.c -- wuss - minimal window manager */ +/* wuss/task/start.c -- wuss - minimal window manager */ #include diff --git a/libraries/wuss/task/stop.c b/libraries/wuss/task/stop.c index b8cbd80b..deffd847 100644 --- a/libraries/wuss/task/stop.c +++ b/libraries/wuss/task/stop.c @@ -1,4 +1,4 @@ -/* stop.c -- wuss - minimal window manager */ +/* wuss/task/stop.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/test/tasks/ball.c b/libraries/wuss/test/tasks/ball.c index 2ee7a424..29352abe 100644 --- a/libraries/wuss/test/tasks/ball.c +++ b/libraries/wuss/test/tasks/ball.c @@ -1,4 +1,4 @@ -/* ball.c -- wuss test - bouncing ball task */ +/* wuss/test/tasks/ball.c -- bouncing ball task */ #ifdef USE_SDL @@ -14,12 +14,31 @@ #include "ball.h" -result_t ball_create(wuss_t *wuss, const colour_t *palette, ball_task_t *task) +/* Window-local invalidation box covering a ball (or its swept range) whose + * centre spans [vx0,vx1] x [vy0,vy1] in virtual content space. */ +static box_t ball_local_box(int vx0, + int vy0, + int vx1, + int vy1, + int radius, + point_t scroll) +{ + box_t local; + + local.x0 = vx0 - radius - scroll.x; + local.y0 = vy0 - radius - scroll.y; + local.x1 = vx1 + radius - scroll.x; + local.y1 = vy1 + radius - scroll.y; + + return local; +} + +result_t ball_create(wuss_t *wuss, ball_task_t *task) { wuss_task_t delegate; - task->bg = palette[palette_PICO8_RED]; - task->ball = palette[palette_PICO8_WHITE]; + task->bg = colour_rgb(0xFF, 0x00, 0x00); + task->ball = colour_rgb(0xFF, 0xFF, 0xFF); task->nballs = 1; task->balls[0].x = 50; @@ -34,20 +53,20 @@ result_t ball_create(wuss_t *wuss, const colour_t *palette, ball_task_t *task) SIZE2D(200, 160), "Bouncing Ball", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate, SIZE2D(200, 160), SIZE2D(0, 0), &task->window); } - static result_t ball_redraw(const wuss_event_t *event, void *task_data) { ball_task_t *bc; screen_t *scr; const box_t *content, *bounds; - int i, sx, sy; + int sx, sy; + int i; bc = task_data; @@ -57,7 +76,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, box_size(content), + screen_fill_rect(scr, content->x0, content->y0, box_size(content), bc->bg); for (i = 0; i < bc->nballs; i++) @@ -66,7 +85,10 @@ static result_t ball_redraw(const wuss_event_t *event, void *task_data) b = &bc->balls[i]; - screen_draw_rect(scr, bounds->x0 - sx + b->x - b->radius, bounds->y0 - sy + b->y - b->radius, SIZE2D(b->radius * 2, b->radius * 2), bc->ball); + screen_fill_rect(scr, bounds->x0 - sx + b->x - b->radius, + bounds->y0 - sy + b->y - b->radius, + SIZE2D(b->radius * 2, b->radius * 2), + bc->ball); } return result_OK; @@ -107,10 +129,7 @@ static result_t ball_mouse(wuss_window_t *window, b->dy = (bc->nballs & 2) ? 2 : -2; b->radius = 8; - local.x0 = b->x - b->radius - scroll.x; - local.y0 = b->y - b->radius - scroll.y; - local.x1 = b->x + b->radius - scroll.x; - local.y1 = b->y + b->radius - scroll.y; + local = ball_local_box(b->x, b->y, b->x, b->y, b->radius, scroll); wuss_window_invalidate(bc->window, &local); } else if (button & wuss_BUTTON_ADJUST) @@ -122,10 +141,7 @@ static result_t ball_mouse(wuss_window_t *window, b = &bc->balls[--bc->nballs]; - local.x0 = b->x - b->radius - scroll.x; - local.y0 = b->y - b->radius - scroll.y; - local.x1 = b->x + b->radius - scroll.x; - local.y1 = b->y + b->radius - scroll.y; + local = ball_local_box(b->x, b->y, b->x, b->y, b->radius, scroll); wuss_window_invalidate(bc->window, &local); } @@ -166,10 +182,9 @@ static result_t ball_idle(void *task_data) if (b->y - b->radius < scroll.y) { b->y = scroll.y + b->radius; b->dy = -b->dy; } else if (b->y + b->radius > scroll.y + height){ b->y = scroll.y + height - b->radius; b->dy = -b->dy; } - local.x0 = MIN(old_x, b->x) - b->radius - scroll.x; - local.y0 = MIN(old_y, b->y) - b->radius - scroll.y; - local.x1 = MAX(old_x, b->x) + b->radius - scroll.x; - local.y1 = MAX(old_y, b->y) + b->radius - scroll.y; + local = ball_local_box(MIN(old_x, b->x), MIN(old_y, b->y), + MAX(old_x, b->x), MAX(old_y, b->y), + b->radius, scroll); wuss_window_invalidate(bc->window, &local); } diff --git a/libraries/wuss/test/tasks/ball.h b/libraries/wuss/test/tasks/ball.h index f42fb579..ae373f25 100644 --- a/libraries/wuss/test/tasks/ball.h +++ b/libraries/wuss/test/tasks/ball.h @@ -1,4 +1,4 @@ -/* ball.h -- wuss test - bouncing ball task */ +/* wuss/test/tasks/ball.h -- bouncing ball task */ #ifndef TASKS_BALL_H #define TASKS_BALL_H @@ -36,7 +36,7 @@ wuss_event_fn_t ball_handle; /* create the bouncing-ball window against the given wuss instance; "task" is a * per-instance block owned by the window and freed when it closes */ -result_t ball_create(wuss_t *wuss, const colour_t *palette, ball_task_t *task); +result_t ball_create(wuss_t *wuss, ball_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/blank.c b/libraries/wuss/test/tasks/blank.c index 603bec2d..55d651e5 100644 --- a/libraries/wuss/test/tasks/blank.c +++ b/libraries/wuss/test/tasks/blank.c @@ -1,4 +1,4 @@ -/* blank.c -- wuss test - colour-cycling task */ +/* wuss/test/tasks/blank.c -- colour-cycling task */ #ifdef USE_SDL @@ -17,12 +17,12 @@ #define BLANK_CYCLE_FRAMES 30 /* colour advances every half-second at 60fps */ -result_t blank_create(wuss_t *wuss, int npalette, blank_task_t *task) +result_t blank_create(wuss_t *wuss, blank_task_t *task) { wuss_task_t delegate; - task->npalette = npalette; - task->index = palette_PICO8_GREEN; + task->npalette = 16; // TODO: Read max palette index from wuss + task->index = 0; task->frame_count = 0; delegate = wuss_task_start(blank_handle, task); /* wuss fills the content area itself */ @@ -31,7 +31,7 @@ result_t blank_create(wuss_t *wuss, int npalette, blank_task_t *task) SIZE2D(200, 160), NULL, wuss_WINDOW_NO_TITLEBAR | wuss_WINDOW_NO_OUTLINE, - palette_PICO8_GREEN, + wuss_BACKDROP_COLOUR(task->index), &delegate, SIZE2D(200, 160), SIZE2D(0, 0), @@ -51,7 +51,8 @@ static result_t blank_idle(void *task_data) bc->frame_count = 0; bc->index = (bc->index + 1) % bc->npalette; - rc = wuss_window_set_background(bc->window, bc->index); + rc = wuss_window_set_background(bc->window, + wuss_BACKDROP_COLOUR(bc->index)); if (rc != result_OK) logf_warning("blank_idle: wuss_window_set_background(%d) failed", bc->index); diff --git a/libraries/wuss/test/tasks/blank.h b/libraries/wuss/test/tasks/blank.h index b1ffd74f..3a42f270 100644 --- a/libraries/wuss/test/tasks/blank.h +++ b/libraries/wuss/test/tasks/blank.h @@ -1,4 +1,4 @@ -/* blank.h -- wuss test - colour-cycling task */ +/* wuss/test/tasks/blank.h -- colour-cycling task */ #ifndef TASKS_BLANK_H #define TASKS_BLANK_H @@ -23,7 +23,7 @@ blank_task_t; wuss_event_fn_t blank_handle; /* create the colour-cycling blank window against the given wuss instance */ -result_t blank_create(wuss_t *wuss, int npalette, blank_task_t *task); +result_t blank_create(wuss_t *wuss, blank_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/chars.c b/libraries/wuss/test/tasks/chars.c index 1680d005..cf98bece 100644 --- a/libraries/wuss/test/tasks/chars.c +++ b/libraries/wuss/test/tasks/chars.c @@ -1,4 +1,4 @@ -/* chars.c -- wuss test - system font glyph grid task */ +/* wuss/test/tasks/chars.c -- system font glyph grid task */ #ifdef USE_SDL @@ -21,9 +21,7 @@ #define CHARS_ROWS 8 #define CHARS_PAD 2 -result_t chars_create(wuss_t *wuss, - const colour_t *palette, - chars_task_t *task) +result_t chars_create(wuss_t *wuss, chars_task_t *task) { wuss_task_t delegate; size2d_t sz; @@ -42,8 +40,8 @@ result_t chars_create(wuss_t *wuss, cell_h = font_height + CHARS_PAD * 2; task->font = font; - task->fg = palette[palette_PICO8_BLACK]; - task->bg = palette[palette_PICO8_WHITE]; + task->fg = colour_rgb(0x00, 0x00, 0x00); + task->bg = colour_rgb(0xFF, 0xFF, 0xFF); delegate = wuss_task_start(chars_handle, task); /* chars_redraw paints every cell itself */ sz = SIZE2D(cell_w * CHARS_COLS, cell_h * CHARS_ROWS); @@ -55,7 +53,7 @@ result_t chars_create(wuss_t *wuss, wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate, sz, SIZE2D(0, 0), @@ -101,7 +99,7 @@ static result_t chars_redraw(const wuss_event_t *event, void *task_data) x = bounds->x0 - sx + col * cell_w; y = bounds->y0 - sy + row * cell_h; - screen_draw_rect(scr, x, y, SIZE2D(cell_w, cell_h), cc->bg); + screen_fill_rect(scr, x, y, SIZE2D(cell_w, cell_h), cc->bg); if (i < first || i >= first + count) continue; /* no glyph for this byte value: leave the cell blank */ diff --git a/libraries/wuss/test/tasks/chars.h b/libraries/wuss/test/tasks/chars.h index 5df4199c..d43fa125 100644 --- a/libraries/wuss/test/tasks/chars.h +++ b/libraries/wuss/test/tasks/chars.h @@ -1,4 +1,4 @@ -/* chars.h -- wuss test - system font glyph grid task */ +/* wuss/test/tasks/chars.h -- system font glyph grid task */ #ifndef TASKS_CHARS_H #define TASKS_CHARS_H @@ -13,9 +13,9 @@ * 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; + wuss_window_t *window; + bmfont_t *font; + colour_t fg, bg; } chars_task_t; @@ -23,10 +23,7 @@ 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); - +result_t chars_create(wuss_t *wuss, chars_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/checker.c b/libraries/wuss/test/tasks/checker.c index cecbbc48..7fc5c957 100644 --- a/libraries/wuss/test/tasks/checker.c +++ b/libraries/wuss/test/tasks/checker.c @@ -1,4 +1,4 @@ -/* checker.c -- wuss test - checkerboard task */ +/* wuss/test/tasks/checker.c -- checkerboard task */ #ifdef USE_SDL @@ -18,15 +18,13 @@ #define CHECKER_BAND_MIN 1 #define CHECKER_BAND_MAX 32 -result_t checker_create(wuss_t *wuss, - const colour_t *palette, - checker_task_t *task) +result_t checker_create(wuss_t*wuss, checker_task_t *task) { wuss_task_t delegate; result_t rc; - task->black = palette[palette_PICO8_BLACK]; - task->white = palette[palette_PICO8_WHITE]; + task->black = colour_rgb(0x00, 0x00, 0x00); + task->white = colour_rgb(0xFF, 0xFF, 0xFF); task->pattern = checker_PATTERN_CHECKERBOARD; task->pattern2 = checker_PATTERN_VERTICAL; task->band = CHECKER_BAND_DEFAULT; @@ -38,7 +36,7 @@ result_t checker_create(wuss_t *wuss, SIZE2D(160, 160), "Checker 1", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate, SIZE2D(160, 160), SIZE2D(0, 0), @@ -50,7 +48,7 @@ result_t checker_create(wuss_t *wuss, SIZE2D(160, 160), "Checker 2", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate, SIZE2D(160, 160), SIZE2D(0, 0), @@ -100,7 +98,7 @@ static result_t checker_redraw(wuss_window_t *window, default: band = lx / band_px + ly / band_px; break; /* CHECKERBOARD */ } - screen_draw_pixel(scr, x, y, (band & 1) ? cc->black : cc->white); + screen_set_pixel(scr, x, y, (band & 1) ? cc->black : cc->white); } } diff --git a/libraries/wuss/test/tasks/checker.h b/libraries/wuss/test/tasks/checker.h index ca3983ce..9983ecc3 100644 --- a/libraries/wuss/test/tasks/checker.h +++ b/libraries/wuss/test/tasks/checker.h @@ -1,4 +1,4 @@ -/* checker.h -- wuss test - checkerboard task */ +/* wuss/test/tasks/checker.h -- checkerboard task */ #ifndef TASKS_CHECKER_H #define TASKS_CHECKER_H @@ -33,10 +33,7 @@ checker_task_t; wuss_event_fn_t checker_handle; /* create the two checkerboard windows against the given wuss instance */ -result_t checker_create(wuss_t *wuss, - const colour_t *palette, - checker_task_t *task); - +result_t checker_create(wuss_t *wuss, checker_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/curve.c b/libraries/wuss/test/tasks/curve.c index 09456f0c..0f91fb05 100644 --- a/libraries/wuss/test/tasks/curve.c +++ b/libraries/wuss/test/tasks/curve.c @@ -1,4 +1,4 @@ -/* curve.c -- wuss test - draggable Bezier curve task */ +/* wuss/test/tasks/curve.c -- draggable Bezier curve task */ #ifdef USE_SDL @@ -23,15 +23,13 @@ #define CURVE_SEGMENTS_MIN 4 #define CURVE_SEGMENTS_MAX 128 -result_t curve_create(wuss_t *wuss, - const colour_t *palette, - curve_task_t *task) +result_t curve_create(wuss_t *wuss, curve_task_t *task) { wuss_task_t delegate; - task->bg = palette[palette_PICO8_WHITE]; - task->line = palette[palette_PICO8_BLACK]; - task->blob = palette[palette_PICO8_RED]; + task->bg = colour_rgb(0xFF, 0xFF, 0xFF); + task->line = colour_rgb(0x00, 0x00, 0x00); + task->blob = colour_rgb(0xFF, 0x00, 0x00); task->nsegments = CURVE_SEGMENTS_DEFAULT; task->dragging = -1; @@ -46,7 +44,7 @@ result_t curve_create(wuss_t *wuss, SIZE2D(220, 160), "Curve", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate, SIZE2D(220, 160), SIZE2D(0, 0), @@ -75,7 +73,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, box_size(content), + screen_fill_rect(scr, content->x0, content->y0, box_size(content), task->bg); prev = task->points[0]; @@ -98,7 +96,7 @@ static result_t curve_redraw(const wuss_event_t *event, curve_task_t *task) { cur = task->points[i]; cur.x += bounds->x0 - sx; cur.y += bounds->y0 - sy; - screen_draw_square(scr, cur.x - half, cur.y - half, CURVE_BLOBSZ, task->blob); + screen_fill_square(scr, cur.x - half, cur.y - half, CURVE_BLOBSZ, task->blob); } return result_OK; diff --git a/libraries/wuss/test/tasks/curve.h b/libraries/wuss/test/tasks/curve.h index de7a2e80..59ce2516 100644 --- a/libraries/wuss/test/tasks/curve.h +++ b/libraries/wuss/test/tasks/curve.h @@ -1,4 +1,4 @@ -/* curve.h -- wuss test - draggable Bezier curve task */ +/* wuss/test/tasks/curve.h -- draggable Bezier curve task */ #ifndef TASKS_CURVE_H #define TASKS_CURVE_H @@ -26,10 +26,7 @@ curve_task_t; wuss_event_fn_t curve_handle; /* create the curve window against the given wuss instance */ -result_t curve_create(wuss_t *wuss, - const colour_t *palette, - curve_task_t *task); - +result_t curve_create(wuss_t *wuss, curve_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/gradient.c b/libraries/wuss/test/tasks/gradient.c index 0602325c..b8b746ee 100644 --- a/libraries/wuss/test/tasks/gradient.c +++ b/libraries/wuss/test/tasks/gradient.c @@ -1,4 +1,4 @@ -/* gradient.c -- wuss test - gradient fill task */ +/* wuss/test/tasks/gradient.c -- gradient fill task */ #ifdef USE_SDL @@ -43,7 +43,7 @@ result_t gradient_create(wuss_t *wuss, gradient_task_t *task) SIZE2D(GRADIENT_OPEN_WIDTH, GRADIENT_OPEN_HEIGHT), "Gradient", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate, SIZE2D(GRADIENT_DOC_WIDTH, GRADIENT_DOC_HEIGHT), SIZE2D(0, 0), @@ -71,7 +71,7 @@ static result_t gradient_redraw(const wuss_event_t *event, void *task_data) lx = x - bounds->x0 + sx; ly = y - bounds->y0 + sy; - screen_draw_pixel(scr, x, y, + screen_set_pixel(scr, x, y, colour_rgb(dither(lx * 255 / GRADIENT_DOC_WIDTH, lx, ly), dither(ly * 255 / GRADIENT_DOC_HEIGHT, lx, ly), dither(255 - (lx + ly) * 255 / (GRADIENT_DOC_WIDTH + GRADIENT_DOC_HEIGHT), lx, ly))); diff --git a/libraries/wuss/test/tasks/gradient.h b/libraries/wuss/test/tasks/gradient.h index 8f720073..9f475596 100644 --- a/libraries/wuss/test/tasks/gradient.h +++ b/libraries/wuss/test/tasks/gradient.h @@ -1,4 +1,4 @@ -/* gradient.h -- wuss test - gradient fill task */ +/* wuss/test/tasks/gradient.h -- gradient fill task */ #ifndef TASKS_GRADIENT_H #define TASKS_GRADIENT_H diff --git a/libraries/wuss/test/tasks/icons.c b/libraries/wuss/test/tasks/icons.c index a9953354..97bf3bce 100644 --- a/libraries/wuss/test/tasks/icons.c +++ b/libraries/wuss/test/tasks/icons.c @@ -1,4 +1,4 @@ -/* icons.c -- wuss test - work-area icons task */ +/* wuss/test/tasks/icons.c -- work-area icons task */ #ifdef USE_SDL #include "framebuf/palettes.h" @@ -11,37 +11,62 @@ #include "fortify/fortify.h" #endif +#include "framebuf/bitmap.h" #include "framebuf/palettes.h" #include "framebuf/screen.h" #include "geom/box.h" #include "geom/point.h" #include "geom/size.h" +#include "io/path.h" #include "icons.h" #define ICONS_DOC_W 220 -#define ICONS_DOC_H 520 /* taller than the window, so scrolling is exercised */ +#define ICONS_DOC_H 640 /* taller than the window, so scrolling is exercised */ -result_t icons_create(wuss_t *wuss, - const colour_t *palette, - bmfont_t *font, - icons_task_t *task) +result_t icons_create(wuss_t *wuss, + bmfont_t *font, + const char *resources, + icons_task_t *task) { - /* [0..3] plus one swatch per built-in pattern */ + /* [0..3] heading, counter button, counter label and a scrolled-away button, + * then [.. +3] a grouping frame with two differently-justified labels inside + * it, then [.. +5] three grouped radios, a standalone option and a label + * echoing the selection, then [.. +2] a decorative bitmap icon and an + * interactive one that bumps the counter, then [.. +4] a strip of menu-entry + * icons that hover-highlight */ + enum { ICONS_NSPECS = 4 + 3 + 5 + 2 + 5 }; wuss_task_t delegate; - wuss_icon_spec_t specs[4 + screen_PATTERN__LIMIT]; - wuss_icon_t *made[4 + screen_PATTERN__LIMIT]; - int p; + wuss_colour_t black, grey5, grey6; + wuss_icon_spec_t specs[ICONS_NSPECS]; + wuss_icon_t *made[ICONS_NSPECS]; + const char *sprite_path; + int g; /* index of the first frame spec */ + int r; /* radio index */ + int m; /* index of the first menu-entry spec */ + int nspecs; /* live spec count (sprite icons are optional) */ result_t rc; - task->font = font; - task->ink = palette[palette_PICO8_LAVENDER]; - task->label = palette[palette_PICO8_DARK_BLUE]; - task->paper = palette[palette_PICO8_LIGHT_GREY]; /* the window bg, below */ - task->window = NULL; - task->button = NULL; - task->counter = NULL; - task->count = 0; + black = wuss_nearest_colour(wuss, 0x00, 0x00, 0x00); + grey5 = wuss_nearest_colour(wuss, 0xBB, 0xBB, 0xBB); + grey6 = wuss_nearest_colour(wuss, 0xDD, 0xDD, 0xDD); + + task->font = font; + task->label = colour_rgb(0x00, 0x00, 0x00); + task->paper = colour_rgb(0xDD, 0xDD, 0xDD); /* the window bg, below */ + task->window = NULL; + task->button = NULL; + task->counter = NULL; + task->count = 0; + task->opt = NULL; + task->state = NULL; + task->has_sprite = 0; + task->hotspot = NULL; + + sprite_path = path_join_filename(resources, 3, "resources", "wuss", + path_join_leafname("9tile", "png")); + if (bitmap_load_png(&task->sprite, sprite_path) == result_OK) + task->has_sprite = 1; delegate = wuss_task_start(icons_handle, task); @@ -49,13 +74,19 @@ result_t icons_create(wuss_t *wuss, SIZE2D(ICONS_DOC_W, 160), "Icons", wuss_WINDOW_NONE, - palette_PICO8_LIGHT_GREY, + wuss_BACKDROP_PATTERN(grey5, + screen_PATTERN_CROSSHATCH, + grey6), &delegate, SIZE2D(ICONS_DOC_W, ICONS_DOC_H), SIZE2D(0, 0), &task->window); if (rc != result_OK) + { + if (task->has_sprite) + free(task->sprite.base); return rc; + } memset(specs, 0, sizeof(specs)); @@ -66,54 +97,157 @@ result_t icons_create(wuss_t *wuss, specs[0].bbox = (box_t) BOX_POS_SIZE(28, 28, 180, 14); specs[0].type = wuss_ICON_TYPE_LABEL; specs[0].text = "Work-area icons:"; - specs[0].fg = palette_PICO8_DARK_BLUE; + specs[0].fg = black; specs[0].bg = wuss_NO_BACKGROUND; - /* [1] the button that bumps the counter */ - specs[1].bbox = (box_t) BOX_POS_SIZE(28, 50, 80, 22); - specs[1].type = wuss_ICON_TYPE_BUTTON; - specs[1].text = "Press me"; - specs[1].fg = palette_PICO8_BLACK; - specs[1].bg = palette_PICO8_LIGHT_GREY; + /* [1] the button that bumps the counter -- a default action button, so it + * shows the accent styling */ + specs[1].bbox = (box_t) BOX_POS_SIZE(28, 50, 80, 22); + specs[1].type = wuss_ICON_TYPE_BUTTON; + specs[1].text = "Press me"; + specs[1].fg = black; + specs[1].bg = grey6; + specs[1].flags = wuss_ICON_FLAGS_DEFAULT; /* [2] the counter label beside it */ specs[2].bbox = (box_t) BOX_POS_SIZE(120, 50, 120, 22); specs[2].type = wuss_ICON_TYPE_LABEL; specs[2].text = "0"; - specs[2].fg = palette_PICO8_DARK_BLUE; + specs[2].fg = black; specs[2].bg = wuss_NO_BACKGROUND; /* [3] a button far down the document, to prove icons scroll and stay clickable */ specs[3].bbox = (box_t) BOX_POS_SIZE(28, 460, 90, 52); specs[3].type = wuss_ICON_TYPE_BUTTON; specs[3].text = "Scrolled"; - specs[3].fg = palette_PICO8_BLACK; - specs[3].bg = palette_PICO8_LIGHT_GREY; + specs[3].fg = black; + specs[3].bg = grey6; + + /* [g] a grouping frame down the document, with [g+1] a right-justified and + * [g+2] a centred label sat inside it */ + g = 4; + + specs[g].bbox = (box_t) BOX_POS_SIZE(28, 300, 170, 70); + specs[g].type = wuss_ICON_TYPE_FRAME; + specs[g].text = "Grouping frame"; + specs[g].fg = black; + specs[g].bg = wuss_NO_BACKGROUND; + + specs[g + 1].bbox = (box_t) BOX_POS_SIZE(38, 320, 150, 14); + specs[g + 1].type = wuss_ICON_TYPE_LABEL; + specs[g + 1].text = "right"; + specs[g + 1].fg = black; + specs[g + 1].bg = wuss_NO_BACKGROUND; + specs[g + 1].flags = wuss_ICON_FLAGS_JUSTIFY_RIGHT; + + specs[g + 2].bbox = (box_t) BOX_POS_SIZE(38, 342, 150, 14); + specs[g + 2].type = wuss_ICON_TYPE_LABEL; + specs[g + 2].text = "centre"; + specs[g + 2].fg = black; + specs[g + 2].bg = wuss_NO_BACKGROUND; + specs[g + 2].flags = wuss_ICON_FLAGS_JUSTIFY_CENTRE; + + /* [g+3..g+5] three radios sharing group 1, [g+6] a standalone option, + * [g+7] a label echoing whichever control last changed */ + for (r = 0; r < 3; r++) + { + specs[g + 3 + r].bbox = (box_t) BOX_POS_SIZE(28, 380 + r * 20, 150, 16); + specs[g + 3 + r].type = wuss_ICON_TYPE_RADIO; + specs[g + 3 + r].text = (r == 0) ? "Red" : (r == 1) ? "Green" : "Blue"; + specs[g + 3 + r].fg = black; + specs[g + 3 + r].bg = wuss_NO_BACKGROUND; + specs[g + 3 + r].group = 1; + } + + specs[g + 6].bbox = (box_t) BOX_POS_SIZE(28, 444, 150, 16); + specs[g + 6].type = wuss_ICON_TYPE_OPTION; + specs[g + 6].text = "Wireframe"; + specs[g + 6].fg = black; + specs[g + 6].bg = wuss_NO_BACKGROUND; - /* [4..] one swatch per built-in pattern, in a column down the document so - * they scroll through the window and stay phase-locked while doing so */ - for (p = 0; p < screen_PATTERN__LIMIT; p++) + specs[g + 7].bbox = (box_t) BOX_POS_SIZE(28, 464, 180, 14); + specs[g + 7].type = wuss_ICON_TYPE_LABEL; + specs[g + 7].text = "(no selection)"; + specs[g + 7].fg = black; + specs[g + 7].bg = wuss_NO_BACKGROUND; + + nspecs = g + 8; + + /* [g+8] a decorative bitmap, [g+9] an interactive one that bumps the + * counter -- only if the sprite loaded */ + if (task->has_sprite) { - specs[4 + p].bbox = (box_t) BOX_POS_SIZE(28, 90 + p * 44, 90, 36); - specs[4 + p].type = wuss_ICON_TYPE_PATTERN; - specs[4 + p].fg = palette_PICO8_DARK_BLUE; - specs[4 + p].bg = palette_PICO8_LIGHT_GREY; - specs[4 + p].pattern = (screen_pattern_t) p; + specs[g + 8].bbox = (box_t) BOX_POS_SIZE(28, 484, task->sprite.size.w, + task->sprite.size.h); + specs[g + 8].type = wuss_ICON_TYPE_BITMAP; + specs[g + 8].bitmap = &task->sprite; + + specs[g + 9].bbox = (box_t) BOX_POS_SIZE(120, 484, task->sprite.size.w, + task->sprite.size.h); + specs[g + 9].type = wuss_ICON_TYPE_BITMAP; + specs[g + 9].bitmap = &task->sprite; + specs[g + 9].flags = wuss_ICON_FLAGS_INTERACTIVE; + + nspecs = g + 10; } - rc = wuss_icon_create_array(task->window, specs, - 4 + screen_PATTERN__LIMIT, made); + /* [nspecs..nspecs+4] a menu-entry strip: plain, ticked, submenu, then a + * standalone dashed rule and a SEPARATOR-flagged entry below it. Hover the + * pointer over them to see the highlight track; the rule stays inert. */ + m = nspecs; + + specs[m].bbox = (box_t) BOX_POS_SIZE(28, 524, 160, 16); + specs[m].type = wuss_ICON_TYPE_MENU_ENTRY; + specs[m].text = "Open"; + specs[m].fg = black; + specs[m].bg = wuss_NO_BACKGROUND; + + specs[m + 1].bbox = (box_t) BOX_POS_SIZE(28, 542, 160, 16); + specs[m + 1].type = wuss_ICON_TYPE_MENU_ENTRY; + specs[m + 1].text = "Show grid"; + specs[m + 1].fg = black; + specs[m + 1].bg = wuss_NO_BACKGROUND; + + specs[m + 2].bbox = (box_t) BOX_POS_SIZE(28, 560, 160, 16); + specs[m + 2].type = wuss_ICON_TYPE_MENU_ENTRY; + specs[m + 2].text = "Export"; + specs[m + 2].fg = black; + specs[m + 2].bg = wuss_NO_BACKGROUND; + specs[m + 2].flags = wuss_ICON_FLAGS_SUBMENU; + + specs[m + 3].bbox = (box_t) BOX_POS_SIZE(28, 578, 160, 10); + specs[m + 3].type = wuss_ICON_TYPE_RULE; + specs[m + 3].fg = black; + specs[m + 3].bg = wuss_NO_BACKGROUND; + + specs[m + 4].bbox = (box_t) BOX_POS_SIZE(28, 588, 160, 16); + specs[m + 4].type = wuss_ICON_TYPE_MENU_ENTRY; + specs[m + 4].text = "Quit"; + specs[m + 4].fg = black; + specs[m + 4].bg = wuss_NO_BACKGROUND; + specs[m + 4].flags = wuss_ICON_FLAGS_SEPARATOR; + + nspecs = m + 5; + + rc = wuss_icon_create_array(task->window, specs, nspecs, made); if (rc != result_OK) goto failure; task->button = made[1]; task->counter = made[2]; + task->opt = made[g + 6]; + task->state = made[g + 7]; + if (task->has_sprite) + task->hotspot = made[g + 9]; + wuss_icon_set_selected(made[m + 1], 1); /* "Show grid" starts ticked */ return result_OK; failure: wuss_window_close(task->window); task->window = NULL; + if (task->has_sprite) + free(task->sprite.base); return rc; } @@ -165,16 +299,9 @@ static result_t icons_redraw(const wuss_event_t *event, void *task_data) * blit assumes. Nothing here is pinned to a window edge. Every draw is * clipped to the dirty rectangle (content) so partial redraws stay cheap. */ - /* a faint grid across the whole work area */ - for (x = icons_grid_first(bounds->x0, scroll.x, content->x0); - x < content->x1; - x += ICONS_GRID) - screen_draw_line(scr, x, content->y0, x, content->y1 - 1, tcx->ink); - - for (y = icons_grid_first(bounds->y0, scroll.y, content->y0); - y < content->y1; - y += ICONS_GRID) - screen_draw_line(scr, content->x0, y, content->x1 - 1, y, tcx->ink); + /* the faint crosshatch backdrop is now the window's own bg (a + * wuss_backdrop_t pattern fill), painted by Wuss before this event and + * phase-locked to the scroll origin -- nothing to draw here */ /* x-axis ruler: document x printed just below the y=0 line, at each * labelled grid column. Scrolls with the document like the grid. */ @@ -214,13 +341,27 @@ static result_t icons_redraw(const wuss_event_t *event, void *task_data) static result_t icons_icon(const wuss_event_t *event, void *task_data) { icons_task_t *tcx; - char buf[16]; + wuss_icon_t *icon; + char buf[48]; - tcx = task_data; + tcx = task_data; + icon = event->data.icon.icon; + + /* a radio/option latches on MOUSE_UP -- report the state then */ + if (event->data.icon.action == wuss_MOUSE_UP && + (wuss_icon_get_type(icon) == wuss_ICON_TYPE_RADIO || + wuss_icon_get_type(icon) == wuss_ICON_TYPE_OPTION)) + { + snprintf(buf, sizeof(buf), "%s: %s%s", + wuss_icon_get_text(icon), + wuss_icon_get_selected(icon) ? "on" : "off", + (icon == tcx->opt) ? "" : " (radio)"); + return wuss_icon_set_text(tcx->state, buf); + } if (event->data.icon.action != wuss_MOUSE_DOWN) return result_OK; - if (event->data.icon.icon != tcx->button) + if (icon != tcx->button && icon != tcx->hotspot) return result_OK; tcx->count++; @@ -246,7 +387,9 @@ result_t icons_handle(wuss_window_t *window, return icons_icon(event, task_data); case wuss_EVENT_CLOSE: - wuss_window_close(window); + wuss_window_close(window); /* frees the icons, which only borrowed sprite */ + if (tcx->has_sprite) + free(tcx->sprite.base); free(tcx); /* calloc'd per instance by the spawner */ return result_OK; diff --git a/libraries/wuss/test/tasks/icons.h b/libraries/wuss/test/tasks/icons.h index aebf0ea2..9707ad61 100644 --- a/libraries/wuss/test/tasks/icons.h +++ b/libraries/wuss/test/tasks/icons.h @@ -1,10 +1,11 @@ -/* icons.h -- wuss test - work-area icons task */ +/* wuss/test/tasks/icons.h -- work-area icons task */ #ifndef TASKS_ICONS_H #define TASKS_ICONS_H #ifdef USE_SDL +#include "framebuf/bitmap.h" #include "framebuf/bmfont.h" #include "framebuf/colour.h" #include "wuss/icon.h" @@ -17,23 +18,26 @@ typedef struct icons_task { wuss_window_t *window; bmfont_t *font; - colour_t ink; /* grid lines */ colour_t label; /* axis coordinate text */ colour_t paper; /* window bg, for bmfont_draw glyph blending */ wuss_icon_t *button; /* "Press me" */ wuss_icon_t *counter; /* label showing hit count */ int count; + wuss_icon_t *opt; /* standalone option button */ + wuss_icon_t *state; /* label echoing radio/option selection */ + bitmap_t sprite; /* borrowed by the two BITMAP icons; freed on close */ + int has_sprite; + wuss_icon_t *hotspot; /* the interactive BITMAP icon */ } icons_task_t; wuss_event_fn_t icons_handle; /* create the icons window against the given wuss instance */ -result_t icons_create(wuss_t *wuss, - const colour_t *palette, - bmfont_t *font, - icons_task_t *task); - +result_t icons_create(wuss_t *wuss, + bmfont_t *font, + const char *resources, + icons_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/image.c b/libraries/wuss/test/tasks/image.c index 943836e1..381590db 100644 --- a/libraries/wuss/test/tasks/image.c +++ b/libraries/wuss/test/tasks/image.c @@ -1,4 +1,4 @@ -/* image.c -- wuss test - static bitmap image task */ +/* wuss/test/tasks/image.c -- static bitmap image task */ #ifdef USE_SDL @@ -16,11 +16,10 @@ #define BORDER 16 -result_t image_create(wuss_t *wuss, - const colour_t *palette, - const char *path, - const char *background_path, - image_task_t *task) +result_t image_create(wuss_t *wuss, + const char *path, + const char *background_path, + image_task_t *task) { wuss_task_t delegate; result_t rc; @@ -38,7 +37,7 @@ result_t image_create(wuss_t *wuss, } delegate = wuss_task_start(image_handle, task); /* shows through the image's transparent pixels */ - + sz.w = task->bitmap.size.w + BORDER * 2; sz.h = task->bitmap.size.h + BORDER * 2; @@ -47,7 +46,7 @@ result_t image_create(wuss_t *wuss, SIZE2D(sz.w, sz.h * 2 / 3), "Image", wuss_WINDOW_NONE, - palette_PICO8_PINK, + wuss_BACKDROP_COLOUR(palette_PICO8_PINK), &delegate, sz, SIZE2D(32, 32), @@ -73,7 +72,7 @@ static result_t image_redraw(const wuss_event_t *event, void *task_data) by = bounds->y0 - sy + BORDER; #define NINEPATCHSZ 9 - + behind.x0 = bx - NINEPATCHSZ; behind.y0 = by - NINEPATCHSZ; behind.x1 = behind.x0 + ic->bitmap.size.w + NINEPATCHSZ * 2; diff --git a/libraries/wuss/test/tasks/image.h b/libraries/wuss/test/tasks/image.h index f62eb928..8943564c 100644 --- a/libraries/wuss/test/tasks/image.h +++ b/libraries/wuss/test/tasks/image.h @@ -1,4 +1,4 @@ -/* image.h -- wuss test - static bitmap image task */ +/* wuss/test/tasks/image.h -- static bitmap image task */ #ifndef TASKS_IMAGE_H #define TASKS_IMAGE_H @@ -23,12 +23,10 @@ wuss_event_fn_t image_handle; /* load the PNG at path (and the 9-patch PNG at background_path, drawn tiled * behind it) and create its window against the given wuss instance */ -result_t image_create(wuss_t *wuss, - const colour_t *palette, - const char *path, - const char *background_path, - image_task_t *task); - +result_t image_create(wuss_t *wuss, + const char *path, + const char *background_path, + image_task_t *task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/launcher.c b/libraries/wuss/test/tasks/launcher.c deleted file mode 100644 index d01b565e..00000000 --- a/libraries/wuss/test/tasks/launcher.c +++ /dev/null @@ -1,117 +0,0 @@ -/* launcher.c -- wuss test - clickable list of names that spawn other tasks */ - -#ifdef USE_SDL - -#include -#include - -#ifdef FORTIFY -#include "fortify/fortify.h" -#endif - -#include "base/utils.h" -#include "framebuf/palettes.h" -#include "geom/box.h" - -#include "wuss/icon.h" - -#include "launcher.h" - -#define LAUNCHER_ROW_HEIGHT 22 -#define LAUNCHER_PAD 4 -#define LAUNCHER_WIDTH 160 - -result_t launcher_create(wuss_t *wuss, - const launcher_entry_t *entries, - int nentries, - launcher_task_t *task) -{ - wuss_task_t delegate; - wuss_icon_spec_t specs[LAUNCHER_MAX_ENTRIES]; - size2d_t sz; - int i; - result_t rc; - - assert(nentries <= LAUNCHER_MAX_ENTRIES); - - task->entries = entries; - task->nentries = nentries; - task->window = NULL; - memset(task->icons, 0, sizeof(task->icons)); - - delegate = wuss_task_start(launcher_handle, task); - sz = SIZE2D(LAUNCHER_WIDTH, - LAUNCHER_PAD * 2 + nentries * LAUNCHER_ROW_HEIGHT); - - rc = wuss_window_create_placed(wuss, - sz, - "Launcher", - wuss_WINDOW_NO_CLOSE, - palette_PICO8_LIGHT_GREY, - &delegate, - sz, - SIZE2D(0, 0), - &task->window); - if (rc != result_OK) - return rc; - - memset(specs, 0, sizeof(specs)); - - for (i = 0; i < nentries; i++) - { - specs[i].bbox = (box_t) BOX_POS_SIZE(LAUNCHER_PAD, - LAUNCHER_PAD + i * LAUNCHER_ROW_HEIGHT, - LAUNCHER_WIDTH - LAUNCHER_PAD * 2, - LAUNCHER_ROW_HEIGHT - 2); - specs[i].type = wuss_ICON_TYPE_BUTTON; - specs[i].text = entries[i].name; - specs[i].fg = palette_PICO8_BLACK; - specs[i].bg = palette_PICO8_LIGHT_GREY; - } - - rc = wuss_icon_create_array(task->window, specs, nentries, task->icons); - if (rc != result_OK) - { - wuss_window_close(task->window); - task->window = NULL; - return rc; - } - - return result_OK; -} - -void launcher_destroy(launcher_task_t *task) -{ - wuss_window_close(task->window); -} - -static result_t launcher_icon(launcher_task_t *lc, const wuss_icon_t *icon) -{ - int i; - - for (i = 0; i < lc->nentries; i++) - if (lc->icons[i] == icon) - return lc->entries[i].spawn(); - - return result_OK; -} - -result_t launcher_handle(wuss_window_t *window, - const wuss_event_t *event, - void *task_data) -{ - NOT_USED(window); - - switch (event->kind) - { - case wuss_EVENT_ICON: - if (event->data.icon.action != wuss_MOUSE_DOWN) - return result_OK; - return launcher_icon(task_data, event->data.icon.icon); - - default: - return result_OK; - } -} - -#endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/launcher.h b/libraries/wuss/test/tasks/launcher.h deleted file mode 100644 index 573f3e07..00000000 --- a/libraries/wuss/test/tasks/launcher.h +++ /dev/null @@ -1,49 +0,0 @@ -/* launcher.h -- wuss test - clickable list of names that spawn other tasks */ - -#ifndef TASKS_LAUNCHER_H -#define TASKS_LAUNCHER_H - -#ifdef USE_SDL - -#include - -#include "wuss/icon.h" -#include "wuss/window.h" - -typedef result_t (*launcher_spawn_fn_t)(void); - -/* one clickable row */ -typedef struct launcher_entry -{ - const char *name; - launcher_spawn_fn_t spawn; -} -launcher_entry_t; - -/* each row is a work-area button icon; clicking one spawns a fresh instance - * of its task, so a row may be clicked any number of times */ -#define LAUNCHER_MAX_ENTRIES 32 - -typedef struct launcher_task -{ - const launcher_entry_t *entries; /* owned by the caller, must outlive the window */ - int nentries; - wuss_icon_t *icons[LAUNCHER_MAX_ENTRIES]; - wuss_window_t *window; -} -launcher_task_t; - -wuss_event_fn_t launcher_handle; - -/* create a window of button icons, one per entry; each click on a button - * calls its spawn function */ -result_t launcher_create(wuss_t *wuss, - const launcher_entry_t *entries, - int nentries, - launcher_task_t *task); - -void launcher_destroy(launcher_task_t *task); - -#endif /* USE_SDL */ - -#endif /* TASKS_LAUNCHER_H */ diff --git a/libraries/wuss/test/tasks/lissajous.c b/libraries/wuss/test/tasks/lissajous.c new file mode 100644 index 00000000..8c26a674 --- /dev/null +++ b/libraries/wuss/test/tasks/lissajous.c @@ -0,0 +1,166 @@ +/* wuss/test/tasks/lissajous.c -- Lissajous figure task */ + +#ifdef USE_SDL + +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "base/utils.h" +#include "framebuf/palettes.h" +#include "geom/box.h" + +#include "lissajous.h" + +/* frequency pairs cycled by a Select click */ +static const int lissajous_freqs[][2] = +{ + { 3, 2 }, { 5, 4 }, { 3, 4 }, { 5, 6 }, { 1, 2 }, { 7, 4 } +}; + +result_t lissajous_create(wuss_t *wuss, lissajous_task_t *task) +{ + wuss_task_t delegate; + + task->bg = colour_rgb(0x00, 0x00, 0x00); + task->fg = colour_rgb(0x00, 0xFF, 0x00); + task->freq_index = 0; + task->a = lissajous_freqs[0][0]; + task->b = lissajous_freqs[0][1]; + task->phase = 0.0; + task->drift = 0.01; + + delegate = wuss_task_start(lissajous_handle, task); /* redraw paints its own background every frame */ + + return wuss_window_create_placed(wuss, + SIZE2D(220, 220), + "Lissajous", + wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate, + SIZE2D(220, 220), + SIZE2D(0, 0), + &task->window); +} + +static result_t lissajous_redraw(const wuss_event_t *event, void *task_data) +{ + lissajous_task_t *lc; + screen_t *scr; + const box_t *content, *bounds; + int sx, sy; + int width, height, cx, cy, rx, ry; + int i; + + lc = task_data; + + scr = event->data.redraw.scr; + content = event->data.redraw.content; + bounds = event->data.redraw.bounds; + sx = event->data.redraw.scroll.x; + sy = event->data.redraw.scroll.y; + + screen_fill_rect(scr, content->x0, content->y0, box_size(content), lc->bg); + + width = bounds->x1 - bounds->x0; + height = bounds->y1 - bounds->y0; + cx = bounds->x0 - sx + width / 2; + cy = bounds->y0 - sy + height / 2; + rx = width / 2 - 10; + ry = height / 2 - 10; + + for (i = 0; i < LISSAJOUS_POINTS; i++) + { + double t; + int px, py; + + t = (double) i / LISSAJOUS_POINTS * 2.0 * M_PI; + px = cx + (int) (rx * sin(lc->a * t + lc->phase)); + py = cy + (int) (ry * sin(lc->b * t)); + + screen_set_pixel(scr, px, py, lc->fg); + } + + return result_OK; +} + +static result_t lissajous_mouse(wuss_window_t *window, + wuss_mouse_action_t action, + wuss_button_t button, + void *task_data) +{ + lissajous_task_t *lc; + + NOT_USED(window); + + lc = task_data; + + if (action != wuss_MOUSE_DOWN) + return result_OK; + + if (button & wuss_BUTTON_SELECT) + { + lc->freq_index = (lc->freq_index + 1) % (int) NELEMS(lissajous_freqs); + lc->a = lissajous_freqs[lc->freq_index][0]; + lc->b = lissajous_freqs[lc->freq_index][1]; + wuss_window_invalidate_all(lc->window); /* whole figure changes */ + } + else if (button & wuss_BUTTON_ADJUST) + { + lc->drift = -lc->drift; + } + + return result_OK; +} + +static result_t lissajous_idle(void *task_data) +{ + lissajous_task_t *lc; + + lc = task_data; + + lc->phase += lc->drift; + if (lc->phase > 2.0 * M_PI) + lc->phase -= 2.0 * M_PI; + else if (lc->phase < 0.0) + lc->phase += 2.0 * M_PI; + + wuss_window_invalidate_all(lc->window); /* ponytail: repaint whole content; figure fills it and is cheap */ + + return result_OK; +} + +result_t lissajous_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) +{ + lissajous_task_t *lc; + + lc = task_data; + + switch (event->kind) + { + case wuss_EVENT_REDRAW: + return lissajous_redraw(event, task_data); + + case wuss_EVENT_MOUSE: + return lissajous_mouse(window, event->data.mouse.action, + event->data.mouse.button, task_data); + + case wuss_EVENT_IDLE: + return lissajous_idle(task_data); + + case wuss_EVENT_CLOSE: + wuss_window_close(window); + free(lc); /* task_data was calloc'd per instance by the spawner */ + return result_OK; + + default: + return result_OK; + } +} + +#endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/lissajous.h b/libraries/wuss/test/tasks/lissajous.h new file mode 100644 index 00000000..36e359a4 --- /dev/null +++ b/libraries/wuss/test/tasks/lissajous.h @@ -0,0 +1,35 @@ +/* wuss/test/tasks/lissajous.h -- Lissajous figure task */ + +#ifndef TASKS_LISSAJOUS_H +#define TASKS_LISSAJOUS_H + +#ifdef USE_SDL + +#include "framebuf/colour.h" +#include "wuss/window.h" + +#define LISSAJOUS_POINTS 512 /* samples plotted along the curve */ + +/* window task: a Lissajous figure x=sin(a*t+phase), y=sin(b*t) plotted as a + * ring of dots. The phase drifts each idle tick so the figure slowly morphs. + * Select cycles the frequency pair (a,b); Adjust reverses the drift. */ +typedef struct lissajous_task +{ + wuss_window_t *window; + colour_t bg, fg; + int a, b; /* frequency ratio */ + double phase; + double drift; + int freq_index; +} +lissajous_task_t; + +wuss_event_fn_t lissajous_handle; + +/* create the Lissajous window against the given wuss instance; "task" is a + * per-instance block owned by the window and freed when it closes */ +result_t lissajous_create(wuss_t *wuss, lissajous_task_t *task); + +#endif /* USE_SDL */ + +#endif /* TASKS_LISSAJOUS_H */ diff --git a/libraries/wuss/test/tasks/palette.c b/libraries/wuss/test/tasks/palette.c index dfcc2eee..0bf9cde2 100644 --- a/libraries/wuss/test/tasks/palette.c +++ b/libraries/wuss/test/tasks/palette.c @@ -1,4 +1,4 @@ -/* palette.c -- wuss test - desktop palette swatch grid task */ +/* wuss/test/tasks/palette.c -- desktop palette swatch grid task */ #ifdef USE_SDL @@ -30,7 +30,7 @@ result_t palette_create(wuss_t *wuss, SIZE2D(100, 100), "Palette", wuss_WINDOW_NO_RESIZE_BLIT, /* swatch grid is laid out across the whole window, so a resize must redraw all of it, not just the newly (un)covered edge */ - palette_PICO8_BLACK, + wuss_BACKDROP_COLOUR(palette_PICO8_BLACK), &delegate, SIZE2D(100, 100), SIZE2D(0, 0), @@ -73,7 +73,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, SIZE2D(cell_w, cell_h), pc->palette[i]); + screen_fill_rect(scr, x, y, SIZE2D(cell_w, cell_h), pc->palette[i]); } return result_OK; diff --git a/libraries/wuss/test/tasks/palette.h b/libraries/wuss/test/tasks/palette.h index 8463df9a..ea8b3cc6 100644 --- a/libraries/wuss/test/tasks/palette.h +++ b/libraries/wuss/test/tasks/palette.h @@ -1,4 +1,4 @@ -/* palette.h -- wuss test - desktop palette swatch grid task */ +/* wuss/test/tasks/palette.h -- desktop palette swatch grid task */ #ifndef TASKS_PALETTE_H #define TASKS_PALETTE_H diff --git a/libraries/wuss/test/tasks/porter-duff.c b/libraries/wuss/test/tasks/porter-duff.c index 14fc1dc4..15db71fb 100644 --- a/libraries/wuss/test/tasks/porter-duff.c +++ b/libraries/wuss/test/tasks/porter-duff.c @@ -1,4 +1,4 @@ -/* porter-duff.c -- wuss test - animated Porter-Duff compositing task */ +/* wuss/test/tasks/porter-duff.c -- animated Porter-Duff compositing task */ #ifdef USE_SDL @@ -197,7 +197,7 @@ result_t porter_duff_create(wuss_t *wuss, SIZE2D(PD_SIZE, PD_SIZE + PD_LABEL_HEIGHT), "Porter-Duff", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate, SIZE2D(PD_SIZE, PD_SIZE + PD_LABEL_HEIGHT), SIZE2D(0, 0), @@ -275,11 +275,12 @@ static void porter_duff_draw_checkerboard(const porter_duff_task_t *pd, ly = y - bounds->y0; band = lx / PD_CHECKER_BAND + ly / PD_CHECKER_BAND; - screen_draw_pixel(scr, x, y, (band & 1) ? pd->dark : pd->light); + screen_set_pixel(scr, x, y, (band & 1) ? pd->dark : pd->light); } } -static result_t porter_duff_redraw(const wuss_event_t *event, void *task_data) +static result_t porter_duff_redraw(const wuss_event_t *event, + void *task_data) { porter_duff_task_t *pd; screen_t *scr; diff --git a/libraries/wuss/test/tasks/porter-duff.h b/libraries/wuss/test/tasks/porter-duff.h index 3a7f210e..361722f7 100644 --- a/libraries/wuss/test/tasks/porter-duff.h +++ b/libraries/wuss/test/tasks/porter-duff.h @@ -1,4 +1,4 @@ -/* porter-duff.h -- wuss test - animated Porter-Duff compositing task */ +/* wuss/test/tasks/porter-duff.h -- animated Porter-Duff compositing task */ #ifndef TASKS_PORTER_DUFF_H #define TASKS_PORTER_DUFF_H diff --git a/libraries/wuss/test/tasks/sofa.c b/libraries/wuss/test/tasks/sofa.c index 28cef250..70870bd9 100644 --- a/libraries/wuss/test/tasks/sofa.c +++ b/libraries/wuss/test/tasks/sofa.c @@ -1,4 +1,4 @@ -/* sofa.c -- wuss test - rotating wireframe sofa and spaceship task */ +/* wuss/test/tasks/sofa.c -- rotating wireframe sofa and spaceship task */ #ifdef USE_SDL @@ -347,20 +347,20 @@ static void draw_vertex_dots(screen_t *scr, half = SOFA_VERTEX_DOT / 2; for (i = 0; i < nvertices; i++) - screen_draw_square(scr, + screen_fill_square(scr, FIX8_ROUND_TO_INT(screen[i].x) - half, FIX8_ROUND_TO_INT(screen[i].y) - half, SOFA_VERTEX_DOT, colour); } -result_t sofa_create(wuss_t *wuss, const colour_t *palette, sofa_task_t *task) +result_t sofa_create(wuss_t*wuss, sofa_task_t*task) { wuss_task_t delegate; - task->bg = palette[palette_PICO8_DARK_PURPLE]; - task->line = palette[palette_PICO8_ORANGE]; - task->dot = palette[palette_PICO8_WHITE]; + task->bg = colour_rgb(0x7E, 0x25, 0x53); + task->line = colour_rgb(0xFF, 0xA3, 0x00); + task->dot = colour_rgb(0xFF, 0xFF, 0xFF); task->angle = 0.0; task->zoom = 1.0; task->spinning = true; @@ -373,7 +373,7 @@ result_t sofa_create(wuss_t *wuss, const colour_t *palette, sofa_task_t *task) SIZE2D(180, 160), "Sofa", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate, SIZE2D(180, 160), SIZE2D(0, 0), @@ -397,7 +397,7 @@ static result_t sofa_redraw(const wuss_event_t *event, void *task_data) sx = event->data.redraw.scroll.x; sy = event->data.redraw.scroll.y; - screen_draw_rect(scr, + screen_fill_rect(scr, content->x0, content->y0, box_size(content), sc->bg); @@ -516,7 +516,9 @@ static result_t sofa_mouse(wuss_window_t *window, return result_OK; } -static result_t sofa_scroll(wuss_window_t *window, int delta, void *task_data) +static result_t sofa_scroll(wuss_window_t *window, + int delta, + void *task_data) { sofa_task_t *sc; diff --git a/libraries/wuss/test/tasks/sofa.h b/libraries/wuss/test/tasks/sofa.h index 1a8c9ffd..280f2793 100644 --- a/libraries/wuss/test/tasks/sofa.h +++ b/libraries/wuss/test/tasks/sofa.h @@ -1,4 +1,4 @@ -/* sofa.h -- wuss test - rotating wireframe sofa task */ +/* wuss/test/tasks/sofa.h -- rotating wireframe sofa task */ #ifndef TASKS_SOFA_H #define TASKS_SOFA_H @@ -43,8 +43,7 @@ sofa_task_t; wuss_event_fn_t sofa_handle; /* create the sofa window against the given wuss instance */ -result_t sofa_create(wuss_t *wuss, const colour_t *palette, sofa_task_t *task); - +result_t sofa_create(wuss_t*wuss, sofa_task_t*task); #endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/swatches.c b/libraries/wuss/test/tasks/swatches.c new file mode 100644 index 00000000..22cdd12c --- /dev/null +++ b/libraries/wuss/test/tasks/swatches.c @@ -0,0 +1,131 @@ +/* wuss/test/tasks/swatches.c -- fill-pattern swatch grid task */ + +#ifdef USE_SDL + +#include +#include + +#ifdef FORTIFY +#include "fortify/fortify.h" +#endif + +#include "framebuf/palettes.h" +#include "framebuf/screen.h" +#include "geom/box.h" +#include "geom/size.h" +#include "wuss/icon.h" + +#include "swatches.h" + +#define SWATCHES_DOC_W 160 +#define SWATCHES_DOC_H 320 /* taller than the window, so scrolling is exercised */ + +/* Delete the current swatch icons and lay a fresh set out, resolving the + * heading and swatch colours through the palette as it stands now. Called at + * create time and again on wuss_EVENT_PALETTE. */ +static result_t swatches_build(swatches_task_t *task) +{ + wuss_icon_spec_t specs[SWATCHES_NSPECS]; + wuss_colour_t fg, bg; + int p; + + for (p = 0; p < SWATCHES_NSPECS; p++) + { + wuss_icon_delete(task->icons[p]); + task->icons[p] = NULL; + } + + fg = wuss_nearest_colour(task->wuss, 0x00, 0x00, 0x00); + bg = wuss_nearest_colour(task->wuss, 0xFF, 0xFF, 0xFF); + + memset(specs, 0, sizeof(specs)); + + /* [0] a heading label */ + specs[0].bbox = (box_t) BOX_POS_SIZE(12, 12, 140, 14); + specs[0].type = wuss_ICON_TYPE_LABEL; + specs[0].text = "Fill patterns:"; + specs[0].fg = fg; + specs[0].bg = wuss_NO_BACKGROUND; + + /* [1..] one 16x16 swatch per built-in fill pattern, laid out as a grid down + * the document so it scrolls through the window and stays phase-locked while + * doing so */ + for (p = 0; p < screen_PATTERN__LIMIT; p++) + { + enum { SWATCHES_COLS = 6, SWATCHES_PITCH = 20 }; + int col, row; + + col = p % SWATCHES_COLS; + row = p / SWATCHES_COLS; + + specs[1 + p].bbox = (box_t) BOX_POS_SIZE(12 + col * SWATCHES_PITCH, + 34 + row * SWATCHES_PITCH, + 16, 16); + specs[1 + p].type = wuss_ICON_TYPE_PATTERN; + specs[1 + p].fg = fg; + specs[1 + p].bg = bg; + specs[1 + p].pattern = (screen_pattern_t) p; + } + + return wuss_icon_create_array(task->window, specs, SWATCHES_NSPECS, + task->icons); +} + +result_t swatches_create(wuss_t *wuss, swatches_task_t *task) +{ + wuss_task_t delegate; + result_t rc; + + task->wuss = wuss; + task->window = NULL; + memset(task->icons, 0, sizeof(task->icons)); + + delegate = wuss_task_start(swatches_handle, task); + + rc = wuss_window_create_placed(wuss, + SIZE2D(SWATCHES_DOC_W, 140), + "Swatches", + wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_nearest_colour(wuss, 0xDD, 0xDD, 0xDD)), + &delegate, + SIZE2D(SWATCHES_DOC_W, SWATCHES_DOC_H), + SIZE2D(0, 0), + &task->window); + if (rc != result_OK) + return rc; + + rc = swatches_build(task); + if (rc != result_OK) + { + wuss_window_close(task->window); + task->window = NULL; + return rc; + } + + return result_OK; +} + +result_t swatches_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) +{ + swatches_task_t *task; + + task = task_data; + + switch (event->kind) + { + case wuss_EVENT_PALETTE: + return swatches_build(task); + + case wuss_EVENT_CLOSE: + wuss_window_close(window); + free(task_data); /* calloc'd per instance by the spawner */ + return result_OK; + + default: + return result_OK; + } +} + +#endif /* USE_SDL */ diff --git a/libraries/wuss/test/tasks/swatches.h b/libraries/wuss/test/tasks/swatches.h new file mode 100644 index 00000000..96253d6e --- /dev/null +++ b/libraries/wuss/test/tasks/swatches.h @@ -0,0 +1,33 @@ +/* wuss/test/tasks/swatches.h -- fill-pattern swatch grid task */ + +#ifndef TASKS_SWATCHES_H +#define TASKS_SWATCHES_H + +#ifdef USE_SDL + +#include "framebuf/screen.h" +#include "wuss/icon.h" +#include "wuss/window.h" + +/* one 16x16 PATTERN icon per built-in screen fill pattern, laid out as a + * grid down a document taller than the window so the swatches scroll + * through it and stay phase-locked while doing so */ +enum { SWATCHES_NSPECS = 1 + screen_PATTERN__LIMIT }; + +typedef struct swatches_task +{ + wuss_t *wuss; /* for re-resolving swatch colours on a palette swap */ + wuss_window_t *window; + wuss_icon_t *icons[SWATCHES_NSPECS]; /* current icons, deleted on rebuild */ +} +swatches_task_t; + +wuss_event_fn_t swatches_handle; + +/* create the swatches window against the given wuss instance */ +result_t swatches_create(wuss_t *wuss, swatches_task_t *task); + + +#endif /* USE_SDL */ + +#endif /* TASKS_SWATCHES_H */ diff --git a/libraries/wuss/test/tasks/text.c b/libraries/wuss/test/tasks/text.c index aa094435..65a2b233 100644 --- a/libraries/wuss/test/tasks/text.c +++ b/libraries/wuss/test/tasks/text.c @@ -1,10 +1,9 @@ -/* text.c -- wuss test - static paragraph task */ +/* wuss/test/tasks/text.c -- static paragraph task */ #ifdef USE_SDL #include -#include #include #include @@ -17,6 +16,7 @@ #include "framebuf/palettes.h" #include "geom/box.h" #include "geom/point.h" +#include "text/bmtext.h" #include "text.h" @@ -28,18 +28,17 @@ static const char paragraph[] = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, " "sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."; -result_t text_create(wuss_t *wuss, - const colour_t *palette, - bmfont_t *font, - text_task_t *task) +result_t text_create(wuss_t *wuss, + bmfont_t *font, + text_task_t *task) { wuss_task_t delegate; size2d_t sz; result_t rc; task->font = font; - task->bg = palette[palette_PICO8_BLUE]; /* matches the "Lorem Ipsum" window's bg, for bmfont_draw's glyph blending */ - task->fg = palette[palette_PICO8_WHITE]; + task->bg = colour_rgb(0xFF, 0xFF, 0xFF); + task->fg = colour_rgb(0x00, 0x00, 0x00); task->frame_count = 0; task->resizing = true; @@ -53,7 +52,7 @@ result_t text_create(wuss_t *wuss, sz, "Lorem Ipsum", wuss_WINDOW_NO_RESIZE_BLIT, /* paragraph reflows across the whole window, so a resize must redraw all of it, not just the newly (un)covered edge */ - palette_PICO8_BLUE, + wuss_BACKDROP_COLOUR(wuss_nearest_colour(wuss, 0xFF, 0xFF, 0xFF)), &delegate, sz, SIZE2D(0, 0), @@ -62,61 +61,38 @@ result_t text_create(wuss_t *wuss, return rc; } +#define INSET 4 +#define LEADING 2 +#define MAX_LINES 64 /* paragraph is short and fixed; overflow is dropped */ + static result_t text_redraw(const wuss_event_t *event, void *task_data) { - text_task_t *tcx; - screen_t *scr; - const box_t *bounds; - int font_width, font_height, sy; - const char *string; - int stringlen; - point_t pos; + text_task_t *tcx; + screen_t *scr; + const box_t *bounds; + int sx, sy; + bmtext_line_t lines[MAX_LINES]; + int nlines; + point_t origin; tcx = 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(tcx->font, &font_width, &font_height); - - string = paragraph; - stringlen = (int) strlen(paragraph); + nlines = bmtext_layout(tcx->font, + paragraph, + (int) strlen(paragraph), + (bounds->x1 - INSET) - (bounds->x0 + INSET), + lines, + MAX_LINES); - pos.x = bounds->x0 + 4; - pos.y = bounds->y0 - sy + 4; + origin.x = bounds->x0 - sx + INSET; + origin.y = bounds->y0 - sy + INSET; - while (stringlen > 0 && pos.y + font_height <= bounds->y1) - { - int absolute_break, friendly_break; - bmfont_width_t width; - - bmfont_measure(tcx->font, string, stringlen, bounds->x1 - 4 - pos.x, &absolute_break, &width); - - friendly_break = absolute_break; - if (absolute_break < stringlen) - { - /* line didn't fit whole: try to break at the last space within it */ - for (friendly_break = absolute_break - 1; friendly_break > 0; friendly_break--) - if (isspace((unsigned char) string[friendly_break])) - break; - if (friendly_break <= 0) - friendly_break = absolute_break; /* no space to break at: hard break */ - } - - bmfont_draw(tcx->font, scr, string, friendly_break, tcx->fg, tcx->bg, &pos, NULL); - - string += friendly_break; - stringlen -= friendly_break; - while (stringlen > 0 && isspace((unsigned char) *string)) - { - string++; - stringlen--; - } - - pos.x = bounds->x0 + 4; - pos.y += font_height + 2; - } + bmtext_draw(tcx->font, scr, lines, nlines, tcx->fg, tcx->bg, LEADING, origin); return result_OK; } diff --git a/libraries/wuss/test/tasks/text.h b/libraries/wuss/test/tasks/text.h index d7a985af..f729b786 100644 --- a/libraries/wuss/test/tasks/text.h +++ b/libraries/wuss/test/tasks/text.h @@ -1,4 +1,4 @@ -/* text.h -- wuss test - static paragraph task */ +/* wuss/test/tasks/text.h -- static paragraph task */ #ifndef TASKS_TEXT_H #define TASKS_TEXT_H @@ -30,11 +30,9 @@ wuss_event_fn_t text_handle; /* create the paragraph-of-text window against the given wuss instance */ result_t text_create(wuss_t *wuss, - const colour_t *palette, bmfont_t *font, text_task_t *task); - #endif /* USE_SDL */ #endif /* TASKS_TEXT_H */ diff --git a/libraries/wuss/test/wuss-test.c b/libraries/wuss/test/wuss-test.c index 6017f6f4..3de12433 100644 --- a/libraries/wuss/test/wuss-test.c +++ b/libraries/wuss/test/wuss-test.c @@ -1,5 +1,6 @@ -/* wuss-test.c -- wuss - minimal window manager */ +/* wuss/test/wuss-test.c -- wuss - minimal window manager */ +#include #include #include #include @@ -20,534 +21,17 @@ #include "io/path.h" #include "wuss/wuss.h" #include "wuss/window.h" +#ifdef WUSS_MENUS +#include "wuss/menu.h" +#endif #include "test/all-tests.h" /* ----------------------------------------------------------------------- */ -#ifdef USE_SDL - -#include - -#include "tasks/ball.h" -#include "tasks/blank.h" -#include "tasks/chars.h" -#include "tasks/checker.h" -#include "tasks/curve.h" -#include "tasks/gradient.h" -#include "tasks/icons.h" -#include "tasks/image.h" -#include "tasks/launcher.h" -#include "tasks/palette.h" -#include "tasks/porter-duff.h" -#include "tasks/sofa.h" -#include "tasks/text.h" - -/* Screen pixel format for the interactive test: 1 = 32bpp pixelfmt_bgrx8888 - * (feeds SDL directly, no per-frame conversion); 0 = pixelfmt_p4 paletted - * (exercises screen_copy_rect's nibble-packed blit path instead). */ -#define WUSS_TEST_32BPP 0 - -/* the launcher's spawn callbacks take no arguments, so the pieces they need - * are stashed here instead; wuss_interactive_test runs at most once per - * process, so file-scope statics are as good as a context struct */ -static wuss_t *g_wuss; -static const colour_t *g_palette; -static int g_npalette; -static const char *g_resources; -static bmfont_t *g_daydream_font; - -/* Each spawn allocates a fresh per-instance task block so a task may run in - * several windows at once; the block is owned by its window and freed by the - * task's wuss_EVENT_CLOSE handler. If create fails, or (for the font-less - * chars task) returns OK without opening a window, the block is freed here -- - * otherwise it would leak. */ - -static result_t spawn_ball(void) -{ - ball_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = ball_create(g_wuss, g_palette, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_text(void) -{ - text_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = text_create(g_wuss, g_palette, g_daydream_font, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_blank(void) -{ - blank_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = blank_create(g_wuss, g_npalette, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_chars(void) -{ - chars_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = chars_create(g_wuss, g_palette, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_palette(void) -{ - palette_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = palette_create(g_wuss, g_palette, g_npalette, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_image(void) -{ - image_task_t *t; - const char *leafname; - const char *filename; - char buf[DPTLIB_MAXPATH]; - const char *ninepatch; - result_t rc; - - t = calloc(1, sizeof(*t)); - if (t == NULL) return result_OOM; - - leafname = path_join_leafname("jessica", "png"); - filename = path_join_filename(g_resources, 3, "resources", "images", leafname); - strcpy(buf, filename); - ninepatch = path_join_filename(g_resources, 3, "resources", "wuss", - path_join_leafname("9tile", "png")); - - rc = image_create(g_wuss, g_palette, buf, ninepatch, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_checker(void) -{ - checker_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = checker_create(g_wuss, g_palette, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_curve(void) -{ - curve_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = curve_create(g_wuss, g_palette, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_sofa(void) -{ - sofa_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = sofa_create(g_wuss, g_palette, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_gradient(void) -{ - gradient_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = gradient_create(g_wuss, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_icons(void) -{ - icons_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = icons_create(g_wuss, g_palette, g_daydream_font, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static result_t spawn_porter_duff(void) -{ - porter_duff_task_t *t = calloc(1, sizeof(*t)); - result_t rc; - if (t == NULL) return result_OOM; - rc = porter_duff_create(g_wuss, g_palette, g_daydream_font, g_resources, t); - if (rc != result_OK || t->window == NULL) { free(t); return rc; } - return result_OK; -} - -static const launcher_entry_t g_launcher_entries[] = -{ - { "Ball", spawn_ball }, - { "Text", spawn_text }, - { "Blank", spawn_blank }, - { "Chars", spawn_chars }, - { "Palette", spawn_palette }, - { "Image", spawn_image }, - { "Checker", spawn_checker }, - { "Curve", spawn_curve }, - { "Sofa", spawn_sofa }, - { "Gradient", spawn_gradient }, - { "Icons", spawn_icons }, - { "Porter-Duff", spawn_porter_duff } -}; - -static wuss_button_t sdl_button_to_wuss(Uint8 button) -{ - switch (button) - { - case SDL_BUTTON_MIDDLE: return wuss_BUTTON_MENU; - case SDL_BUTTON_RIGHT: return wuss_BUTTON_ADJUST; - default: return wuss_BUTTON_SELECT; - } -} - -/* SDL delivers mouse coordinates in window space, which F2 can scale away - * from the fixed-size Wuss screen; map back down to screen space */ -static void sdl_pos_to_scr(SDL_Window *window, - int scr_width, - int scr_height, - float in_x, - float in_y, - int *out_x, - int *out_y) -{ - int win_w, win_h; - - SDL_GetWindowSize(window, &win_w, &win_h); - - *out_x = (int) (in_x * scr_width / win_w); - *out_y = (int) (in_y * scr_height / win_h); -} - -/* click windows to bring to front, drag titlebars to move, resize the - * SDL window to see the Wuss screen scale; F2 doubles the SDL window size, - * Shift-F2 halves it; F3 redraws the whole screen one pixel at a time, to - * catch tasks whose drawing routines misbehave under a 1x1 clip; Q or - * close to quit */ -static result_t wuss_interactive_test(const char *resources) -{ - const int scr_width = 640; - const int scr_height = 480; -#if WUSS_TEST_32BPP - const int rowbytes = scr_width * 4; /* pixelfmt_bgrx8888: 4 bytes/pixel */ -#else - const int rowbytes = scr_width / 2; /* pixelfmt_p4: 2 pixels/byte */ -#endif - - result_t rc; - const char *leafname; - const char *filename; - bmfont_t *font; - bmfont_t *daydream_font; - void *pixels; - bitmap_t bm; -#if !WUSS_TEST_32BPP - bitmap_t *disp; -#endif - screen_t scr; - colour_t palette[16]; - wuss_t *wuss; - launcher_task_t launcher_task; - SDL_Window *window; - SDL_Renderer *renderer; - SDL_Texture *texture; - bool quit; - bool garbage_pending; - bool pixel_stress_pending; - int i; - - define_pico8_palette(palette); - - leafname = path_join_leafname("digits", "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", "png"); - filename = path_join_filename(resources, 3, "resources", "bmfonts", leafname); - rc = bmfont_create(filename, &daydream_font); - if (rc != result_OK) - goto Failure; - - pixels = malloc(rowbytes * scr_height); - if (pixels == NULL) - goto Failure; - -#if WUSS_TEST_32BPP - rc = bitmap_init(&bm, SIZE2D(scr_width, scr_height), pixelfmt_bgrx8888, rowbytes, palette, pixels); -#else - rc = bitmap_init(&bm, SIZE2D(scr_width, scr_height), pixelfmt_p4, rowbytes, palette, pixels); -#endif - if (rc != result_OK) - goto Failure; - - bitmap_clear(&bm, palette[palette_PICO8_WHITE]); - - screen_for_bitmap(&scr, &bm); - - if (!SDL_Init(SDL_INIT_VIDEO)) - { - fprintf(stderr, "Error: SDL_Init: %s\n", SDL_GetError()); - goto Failure; - } - - window = SDL_CreateWindow("DPTLib Wuss Test", scr_width, scr_height, 0); - if (window == NULL) - { - fprintf(stderr, "Error: SDL_CreateWindow: %s\n", SDL_GetError()); - goto Failure; - } - - renderer = SDL_CreateRenderer(window, NULL); - if (renderer == NULL) - { - fprintf(stderr, "Error: SDL_CreateRenderer: %s\n", SDL_GetError()); - goto Failure; - } - - texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_ARGB8888, - SDL_TEXTUREACCESS_STREAMING, - scr_width, scr_height); - if (texture == NULL) - { - fprintf(stderr, "Error: SDL_CreateTexture: %s\n", SDL_GetError()); - goto Failure; - } - - SDL_SetTextureBlendMode(texture, SDL_BLENDMODE_NONE); - SDL_SetTextureScaleMode(texture, SDL_SCALEMODE_NEAREST); /* keep pixels crisp when F2 scales the window up */ - - { - 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.scroll.arrows = palette_PICO8_BLUE; - config.palette.scroll.wells = palette_PICO8_DARK_BLUE; - config.palette.scroll.sausages = palette_PICO8_LIGHT_GREY; - config.bevel.light = palette_PICO8_WHITE; - config.bevel.dark = palette_PICO8_DARK_GREY; - config.backdrop = palette_PICO8_LIGHT_GREY; - - rc = wuss_create(&scr, font, palette, NELEMS(palette), &config, &wuss); - if (rc != result_OK) - goto Failure; - } - - g_wuss = wuss; - g_palette = palette; - g_npalette = NELEMS(palette); - g_resources = resources; - g_daydream_font = daydream_font; - - rc = launcher_create(wuss, g_launcher_entries, NELEMS(g_launcher_entries), &launcher_task); - if (rc != result_OK) - goto Failure; - - quit = false; - garbage_pending = false; - pixel_stress_pending = false; - - wuss_redraw(wuss); - - while (!quit) - { - SDL_Event event; - - while (SDL_PollEvent(&event)) - { - switch (event.type) - { - case SDL_EVENT_QUIT: - quit = true; - break; - - case SDL_EVENT_KEY_UP: - if (event.key.key == SDLK_Q) - quit = true; - else if (event.key.key == SDLK_F1 && (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) - pixel_stress_pending = true; - else if (event.key.key == SDLK_F2) - { - int w, h; - - SDL_GetWindowSize(window, &w, &h); - if (event.key.mod & SDL_KMOD_SHIFT) - SDL_SetWindowSize(window, w / 2, h / 2); - else - SDL_SetWindowSize(window, w * 2, h * 2); - } - break; - - case SDL_EVENT_MOUSE_BUTTON_DOWN: - { - int x, y; - - sdl_pos_to_scr(window, scr_width, scr_height, event.button.x, event.button.y, &x, &y); - wuss_mouse_click(wuss, POINT(x, y), sdl_button_to_wuss(event.button.button), wuss_MOUSE_DOWN, NULL); - } - break; - - case SDL_EVENT_MOUSE_BUTTON_UP: - { - int x, y; - - sdl_pos_to_scr(window, scr_width, scr_height, event.button.x, event.button.y, &x, &y); - wuss_mouse_click(wuss, POINT(x, y), sdl_button_to_wuss(event.button.button), wuss_MOUSE_UP, NULL); - } - break; - - case SDL_EVENT_MOUSE_MOTION: - { - int x, y; - - sdl_pos_to_scr(window, scr_width, scr_height, event.motion.x, event.motion.y, &x, &y); - wuss_mouse_move(wuss, POINT(x, y), NULL); - } - break; - - case SDL_EVENT_MOUSE_WHEEL: - { - int x, y; - - sdl_pos_to_scr(window, scr_width, scr_height, event.wheel.mouse_x, event.wheel.mouse_y, &x, &y); - wuss_scroll(wuss, POINT(x, y), (int) event.wheel.y, NULL); - } - break; - - default: - break; - } - } - - wuss_idle(wuss); - - if (garbage_pending) - { - /* full-screen corruption, drawn over whatever's already on screen - * (windows included) and left untouched: nothing here is invalidated, - * so it stays put until something actually redraws over it, e.g. the - * ball's own small per-frame dirty rect eating a trail through it, or - * a window being dragged across it */ - unsigned char *p; - size_t n; - size_t i; - - p = pixels; - n = (size_t) rowbytes * scr_height; - for (i = 0; i < n; i++) - p[i] = (unsigned char) rand(); - - garbage_pending = false; - } - else if (pixel_stress_pending) - { - /* redraw the whole screen one pixel at a time: each wuss_redraw_dirty - * call is flushed before the next pixel is invalidated, so no two - * pixels are ever coalesced into one redraw. A task whose drawing - * routine assumes it always gets handed a multi-pixel/aligned clip - * (rather than trusting scr->clip) will visibly misdraw here even - * though it looks fine under normal, larger dirty regions. */ - int x, y; - - for (y = 0; y < scr_height; y++) - { - for (x = 0; x < scr_width; x++) - { - box_t px; - - px.x0 = x; px.y0 = y; - px.x1 = x + 1; px.y1 = y + 1; - - wuss_invalidate(wuss, &px); - wuss_redraw_dirty(wuss); - } - } - - pixel_stress_pending = false; - } - else - { - wuss_redraw_dirty(wuss); - } - -#if WUSS_TEST_32BPP - SDL_UpdateTexture(texture, NULL, bm.base, bm.rowbytes); /* bm is already bgrx8888: no conversion needed */ -#else - rc = bitmap_convert(&bm, pixelfmt_bgrx8888, &disp); - if (rc == result_OK) - { - SDL_UpdateTexture(texture, NULL, disp->base, disp->rowbytes); - free(disp->base); - free(disp); - } -#endif - - SDL_RenderTexture(renderer, texture, NULL, NULL); - SDL_RenderPresent(renderer); - - SDL_Delay(1000 / 60); - } - - /* ponytail: wuss_destroy() below frees every still-open window but not the - * per-instance task block hung off it, so any task window left open at quit - * leaks its block. Harmless at process exit; add a wuss close callback if a - * task ever needs deterministic teardown. */ - launcher_destroy(&launcher_task); - - wuss_destroy(wuss); - bmfont_destroy(font); - bmfont_destroy(daydream_font); - - SDL_DestroyTexture(texture); - SDL_DestroyRenderer(renderer); - SDL_DestroyWindow(window); - SDL_Quit(); - - free(pixels); - - return result_TEST_PASSED; - - -Failure: - - printf("wuss_interactive_test: failed (rc=0x%X)\n", rc); - - return result_TEST_FAILED; -} - -#endif /* USE_SDL */ +/* The SDL interactive driver that used to live here (spawn_* callbacks, the + * launcher table and the run_wuss loop) is now the standalone `wuss` app + * in apps/wuss/main.c. This file is the Wuss unit test only. */ /* ----------------------------------------------------------------------- */ @@ -562,6 +46,7 @@ typedef struct test_task int close_count; int stop_count; int open_count; + int palette_count; } test_task_t; @@ -606,6 +91,10 @@ static result_t test_handle(wuss_window_t *window, tc->open_count++; break; + case wuss_EVENT_PALETTE: + tc->palette_count++; + break; + default: break; } @@ -613,6 +102,49 @@ static result_t test_handle(wuss_window_t *window, return result_OK; } +/* A redraw handler that paints its content as one-pixel horizontal lines + * whose colour encodes the document-space Y of each row, so a test can read + * the framebuffer back and tell not just whose pixels are where but whether + * a blit slid them by the right amount. task_data points to a colour_t used + * only as a base hue (its blue channel is replaced per row). NULL task_data + * means flood with a single fixed colour instead. */ +static result_t paint_handle(wuss_window_t *window, + const wuss_event_t *event, + void *task_data) +{ + const box_t *clip; + const box_t *bounds; + point_t scroll; + int y, doc_y; + + NOT_USED(window); + + if (event->kind != wuss_EVENT_REDRAW) + return result_OK; + + clip = event->data.redraw.content; + bounds = event->data.redraw.bounds; + scroll = event->data.redraw.scroll; + + if (task_data == NULL) + { + screen_fill_rect(event->data.redraw.scr, clip->x0, clip->y0, + SIZE2D(clip->x1 - clip->x0, clip->y1 - clip->y0), + colour_rgb(0xcc, 0xdd, 0xee)); + return result_OK; + } + + for (y = clip->y0; y < clip->y1; y++) + { + doc_y = y - bounds->y0 + scroll.y; + screen_fill_rect(event->data.redraw.scr, clip->x0, y, + SIZE2D(clip->x1 - clip->x0, 1), + colour_rgb(0x20, 0x40, doc_y & 0xff)); + } + + return result_OK; +} + /* ----------------------------------------------------------------------- */ /* Total area covered by the dirty list, counting overlapped pixels once. @@ -685,16 +217,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.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); + bad_config.furniture.title.bg = 999; + bad_config.furniture.title.fg = 0; + bad_config.furniture.back = 0; + bad_config.furniture.close = 0; + bad_config.furniture.toggle = 0; + bad_config.furniture.resize = 0; + bad_config.furniture.scroll.arrows = 0; + bad_config.furniture.scroll.wells = 0; + bad_config.furniture.scroll.sausages = 0; + rc = wuss_create(&scr, NULL, NULL, 0, &bad_config, NULL, &bad_wuss); if (rc != result_WUSS_BAD_COLOUR) goto Failure; @@ -703,7 +235,7 @@ result_t wuss_test(const char *resources) { wuss_t *custom_wuss; - rc = wuss_create(&scr, NULL, custom_palette, 2, NULL, &custom_wuss); + rc = wuss_create(&scr, NULL, custom_palette, 2, NULL, NULL, &custom_wuss); if (rc != result_OK) goto Failure; wuss_destroy(custom_wuss); @@ -711,7 +243,7 @@ result_t wuss_test(const char *resources) printf("test: wuss_create with default palette\n"); - rc = wuss_create(&scr, NULL, NULL, 0, NULL, &wuss); + rc = wuss_create(&scr, NULL, NULL, 0, NULL, NULL, &wuss); if (rc != result_OK) goto Failure; @@ -725,7 +257,7 @@ result_t wuss_test(const char *resources) &box_a, "toosmall", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), NULL, box_size(&box_a), SIZE2D(0, 0), @@ -751,7 +283,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_a, box_size(&box_a), SIZE2D(0, 0), @@ -774,7 +306,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_b, box_size(&box_b), SIZE2D(0, 0), @@ -1045,6 +577,59 @@ result_t wuss_test(const char *resources) if (width != 52 || height != 72) goto Failure; + printf("test: window_resize can never grow a window past the screen\n"); + + /* screen is 200x200; win_a's visible box sits at (0,25) with a 1px + * outline all round and a 20px titlebar. asking for a 500x500 content + * area must clamp so the visible box still fits: width 200-0-2, + * height 200-25-2-20. */ + rc = wuss_window_resize(win_a, SIZE2D(500, 500)); + if (rc != result_OK) + goto Failure; + wuss_window_get_content_bounds(win_a, &content); + if (content.x1 - content.x0 != 198 || content.y1 - content.y0 != 153) + goto Failure; + wuss_window_get_visible_bounds(win_a, &visible); + if (visible.x1 != 200 || visible.y1 != 200) + goto Failure; + + /* a request that already fits is honoured verbatim */ + rc = wuss_window_resize(win_a, SIZE2D(60, 40)); + if (rc != result_OK) + goto Failure; + wuss_window_get_content_bounds(win_a, &content); + if (content.x1 - content.x0 != 60 || content.y1 - content.y0 != 40) + goto Failure; + + /* restore for the tests that follow */ + rc = wuss_window_resize(win_a, SIZE2D(50, 50)); + if (rc != result_OK) + goto Failure; + + printf("test: window_create can never make a window bigger than the " + "screen\n"); + + { + box_t box_big; + wuss_window_t *win_big; + + /* content box asks for 10,10..400,400; the on-screen nudge pulls the + * visible top-left (10-1 outline) back to (0,0), then the clamp caps + * the content at 200 - 2*1 - 20(titlebar) tall, 200 - 2*1 wide. */ + box_big.x0 = 10; box_big.y0 = 10; box_big.x1 = 400; box_big.y1 = 400; + rc = wuss_window_create(wuss, &box_big, "BIG", wuss_WINDOW_NO_VSCROLL | + wuss_WINDOW_NO_HSCROLL | wuss_WINDOW_NO_RESIZE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), NULL, + SIZE2D(400, 400), SIZE2D(0, 0), &win_big); + if (rc != result_OK) + goto Failure; + wuss_window_get_visible_bounds(win_big, &visible); + if (visible.x0 != 0 || visible.y0 != 0 || + visible.x1 != 200 || visible.y1 != 200) + goto Failure; + wuss_window_close(win_big); + } + printf("test: title-less, no-outline window has no furniture, so visible == content\n"); tc_d.redraw_count = 0; @@ -1060,7 +645,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_d, box_size(&box_d), SIZE2D(0, 0), @@ -1108,7 +693,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_e, box_size(&box_e), SIZE2D(0, 0), @@ -1129,7 +714,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_f, box_size(&box_f), SIZE2D(0, 0), @@ -1165,11 +750,11 @@ result_t wuss_test(const char *resources) printf("test: wuss_window_set_background\n"); - rc = wuss_window_set_background(win_d, 999); + rc = wuss_window_set_background(win_d, wuss_BACKDROP_COLOUR(999)); if (rc != result_WUSS_BAD_COLOUR) goto Failure; - rc = wuss_window_set_background(win_d, palette_PICO8_ORANGE); + rc = wuss_window_set_background(win_d, wuss_BACKDROP_COLOUR(1)); if (rc != result_OK) goto Failure; @@ -1197,7 +782,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_h, box_size(&box_h), SIZE2D(0, 0), @@ -1218,7 +803,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_g, box_size(&box_g), SIZE2D(0, 0), @@ -1280,7 +865,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_i, box_size(&box_i), SIZE2D(0, 0), @@ -1301,7 +886,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_j, box_size(&box_j), SIZE2D(0, 0), @@ -1352,7 +937,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_m, box_size(&box_m), SIZE2D(0, 0), @@ -1405,7 +990,7 @@ result_t wuss_test(const char *resources) &box_h, "H", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_h, box_size(&box_h), SIZE2D(0, 0), @@ -1424,7 +1009,7 @@ result_t wuss_test(const char *resources) &box_g, "G", wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_g, box_size(&box_g), SIZE2D(0, 0), @@ -1544,7 +1129,7 @@ result_t wuss_test(const char *resources) box_m.x0 = 10; box_m.y0 = 10; box_m.x1 = 210; box_m.y1 = 210; /* 200x200 content, floored at 80x60 */ rc = wuss_window_create(wuss, &box_m, "M", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_m, SIZE2D(200, 200), SIZE2D(80, 60), &win_m); @@ -1595,7 +1180,7 @@ result_t wuss_test(const char *resources) 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, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_t, SIZE2D(200, 200), SIZE2D(0, 0), &win_t); if (rc != result_OK) @@ -1761,7 +1346,7 @@ result_t wuss_test(const char *resources) 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, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_r, SIZE2D(70, 70), SIZE2D(0, 0), &win_r); if (rc != result_OK) @@ -1841,6 +1426,488 @@ result_t wuss_test(const char *resources) wuss_window_close(win_r); } + printf("test: wuss_window_resize on a topmost window at max scroll blits the still-valid content rather than redrawing it all\n"); + + { + /* Unlike the toggle-size path above, a direct wuss_window_resize (the + * interactive drag-resize path) runs with the screen still showing this + * window's content at the old geometry and old scroll offset. When the + * grow forces a scroll re-clamp, the overlap of the old and new content + * boxes is genuine on-screen content that just needs sliding by the + * scroll delta -- only the newly-exposed strip needs a real repaint. */ + test_task_t tc_d; + wuss_task_t delegate_d; + box_t box_d, content_before; + wuss_window_t *win_d; + point_t scroll_d; + int i; + int top_x, top_y, mid_x, mid_y, bottom_x, bottom_y; + int top_dirty, mid_dirty, bottom_dirty; + + tc_d.redraw_count = 0; + tc_d.mouse_count = 0; + delegate_d.handle = test_handle; + delegate_d.task_data = &tc_d; + + box_d.x0 = 10; box_d.y0 = 10; + box_d.x1 = 90; box_d.y1 = 90; /* 80x80 content; doc taller, so it starts scrollable */ + rc = wuss_window_create(wuss, &box_d, "D", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate_d, + SIZE2D(80, 140), SIZE2D(0, 0), &win_d); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush the create's own invalidate */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_d, &content_before); + + /* scroll to the bottom: max_y = doc.h (140) - content height (80) = 60 */ + wuss_window_set_scroll(win_d, POINT(0, 60)); + rc = wuss_redraw_dirty(wuss); /* flush the scroll's own invalidate */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_scroll(win_d, &scroll_d); + if (scroll_d.y != 60) + goto Failure; + + /* Three probe points at the same x, well clear of the scrollbar column. + * The re-clamp drops scroll.y from 60 to 0, so on-screen content slides + * DOWN by 60px: the top ~60px of the content box is newly revealed and + * must be repainted, everything below it was already on screen and must + * be reused by the blit. */ + top_x = content_before.x0 + 8; + top_y = content_before.y0 + 8; /* < 60px down: in the revealed strip */ + mid_x = content_before.x0 + 8; + mid_y = content_before.y0 + 70; /* > 60px down: reused */ + bottom_x = content_before.x0 + 8; + bottom_y = content_before.y1 - 8; /* near old viewport bottom: reused */ + + /* grow the content taller than the doc: content height goes to >= 140, + * so max_y drops to 0 and scroll.y is clamped back from 60 to 0 */ + rc = wuss_window_resize(win_d, SIZE2D(80, 160)); + if (rc != result_OK) + goto Failure; + + wuss_window_get_scroll(win_d, &scroll_d); + if (scroll_d.y != 0) + goto Failure; /* the grow must have re-clamped the offset */ + + if (wuss_get_dirty_count(wuss) == 0) + goto Failure; + + top_dirty = 0; + mid_dirty = 0; + bottom_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + box_t region; + + wuss_get_dirty(wuss, i, ®ion); + if (box_contains_point(®ion, top_x, top_y)) + top_dirty = 1; + if (box_contains_point(®ion, mid_x, mid_y)) + mid_dirty = 1; + if (box_contains_point(®ion, bottom_x, bottom_y)) + bottom_dirty = 1; + } + + if (!top_dirty) + goto Failure; /* the newly-revealed strip must be repainted */ + + if (mid_dirty || bottom_dirty) + goto Failure; /* content that stayed on screen must be reused by the + * blit-and-shift, not redrawn -- a full content-box + * invalidate has regressed the optimisation */ + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_d); + } + + printf("test: wuss_window_resize on a non-topmost window blits the un-occluded content and never dirties the occluder\n"); + + { + /* Another window covers the right half of D's content. The blit source + * is the old content box MINUS what the occluder covered, so the + * un-occluded left half is slid into place; the right half still shows + * the occluder's own correct pixels, so it must stay clean -- dirtying + * it would redraw the occluding window for nothing. Only the un-occluded + * newly-revealed top strip needs a repaint. */ + test_task_t tc_d, tc_o; + wuss_task_t delegate_d, delegate_o; + box_t box_d, box_o, content_before; + wuss_window_t *win_d, *win_o; + point_t scroll_d; + int i, split_x; + int occluded_x, occluded_y, kept_x, kept_y, strip_x, strip_y; + int occluded_dirty, kept_dirty, strip_dirty; + + tc_d.redraw_count = 0; tc_d.mouse_count = 0; + tc_o.redraw_count = 0; tc_o.mouse_count = 0; + delegate_d.handle = test_handle; delegate_d.task_data = &tc_d; + delegate_o.handle = test_handle; delegate_o.task_data = &tc_o; + + box_d.x0 = 10; box_d.y0 = 10; + box_d.x1 = 110; box_d.y1 = 90; /* 100x80 content; doc taller, so scrollable */ + rc = wuss_window_create(wuss, &box_d, "D", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate_d, + SIZE2D(100, 140), SIZE2D(0, 0), &win_d); + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_d, &content_before); + split_x = (content_before.x0 + content_before.x1) / 2; + + /* occluder covering the right half of D's content box and beyond */ + box_o.x0 = split_x; box_o.y0 = content_before.y0 - 5; + box_o.x1 = content_before.x1 + 40; box_o.y1 = content_before.y1 + 40; + rc = wuss_window_create(wuss, &box_o, "O", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate_o, + SIZE2D(box_o.x1 - box_o.x0, box_o.y1 - box_o.y0), + SIZE2D(0, 0), &win_o); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush both creates */ + if (rc != result_OK) + goto Failure; + + wuss_window_set_scroll(win_d, POINT(0, 60)); /* max_y = 140 - 80 = 60 */ + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_get_scroll(win_d, &scroll_d); + if (scroll_d.y != 60) + goto Failure; + + /* occluded: under O -- must stay clean. kept: un-occluded, below the + * ~60px revealed strip -- reused by the blit, must stay clean. strip: + * un-occluded, in the newly-revealed top band -- must be repainted. */ + occluded_x = split_x + 8; + occluded_y = content_before.y1 - 8; + kept_x = content_before.x0 + 8; + kept_y = content_before.y1 - 8; + strip_x = content_before.x0 + 8; + strip_y = content_before.y0 + 8; + + rc = wuss_window_resize(win_d, SIZE2D(100, 160)); /* grow past doc height */ + if (rc != result_OK) + goto Failure; + + wuss_window_get_scroll(win_d, &scroll_d); + if (scroll_d.y != 0) + goto Failure; + + if (wuss_get_dirty_count(wuss) == 0) + goto Failure; + + occluded_dirty = 0; + kept_dirty = 0; + strip_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + box_t region; + + wuss_get_dirty(wuss, i, ®ion); + if (box_contains_point(®ion, occluded_x, occluded_y)) + occluded_dirty = 1; + if (box_contains_point(®ion, kept_x, kept_y)) + kept_dirty = 1; + if (box_contains_point(®ion, strip_x, strip_y)) + strip_dirty = 1; + } + + if (occluded_dirty) + goto Failure; /* under the occluder: its pixels are already correct, + * dirtying this would redraw the occluding window */ + + if (kept_dirty) + goto Failure; /* un-occluded content must be reused by the blit */ + + if (!strip_dirty) + goto Failure; /* the un-occluded newly-revealed strip must be repainted */ + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_o); + wuss_window_close(win_d); + } + + printf("test: wuss_window_set_scroll on a non-topmost window blits the un-occluded content and never dirties the occluder\n"); + + { + /* A window covered on its right half by another still blits its + * un-occluded left half on a programmatic scroll. The right half shows + * the occluder's own correct pixels, so it must stay clean -- dirtying + * it would redraw the occluding window. Only the un-occluded + * newly-exposed edge strip needs a repaint. */ + test_task_t tc_s, tc_o; + wuss_task_t delegate_s, delegate_o; + box_t box_s, box_o, content; + wuss_window_t *win_s, *win_o; + int i, split_x; + int occluded_x, occluded_y, kept_x, kept_y, strip_x, strip_y; + int occluded_dirty, kept_dirty, strip_dirty; + + tc_s.redraw_count = 0; tc_s.mouse_count = 0; + tc_o.redraw_count = 0; tc_o.mouse_count = 0; + delegate_s.handle = test_handle; delegate_s.task_data = &tc_s; + delegate_o.handle = test_handle; delegate_o.task_data = &tc_o; + + box_s.x0 = 10; box_s.y0 = 10; + box_s.x1 = 110; box_s.y1 = 90; /* 100x80 content; doc taller, so scrollable */ + rc = wuss_window_create(wuss, &box_s, "S", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate_s, + SIZE2D(100, 200), SIZE2D(0, 0), &win_s); + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_s, &content); + split_x = (content.x0 + content.x1) / 2; + + box_o.x0 = split_x; box_o.y0 = content.y0 - 5; + box_o.x1 = content.x1 + 40; box_o.y1 = content.y1 + 40; + rc = wuss_window_create(wuss, &box_o, "O", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate_o, + SIZE2D(box_o.x1 - box_o.x0, box_o.y1 - box_o.y0), + SIZE2D(0, 0), &win_o); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush both creates */ + if (rc != result_OK) + goto Failure; + + /* kept: un-occluded, near the bottom -- content there just slides up by + * the scroll delta, so the blit must reuse it. occluded: under O -- must + * stay clean. strip: un-occluded, in the newly-exposed bottom band -- + * must be repainted. */ + kept_x = content.x0 + 8; + kept_y = content.y0 + 8; + occluded_x = split_x + 8; + occluded_y = content.y1 - 20; + strip_x = content.x0 + 8; + strip_y = content.y1 - 6; + + wuss_window_set_scroll(win_s, POINT(0, 12)); + + if (wuss_get_dirty_count(wuss) == 0) + goto Failure; + + kept_dirty = 0; + occluded_dirty = 0; + strip_dirty = 0; + for (i = 0; i < wuss_get_dirty_count(wuss); i++) + { + box_t region; + + wuss_get_dirty(wuss, i, ®ion); + if (box_contains_point(®ion, kept_x, kept_y)) + kept_dirty = 1; + if (box_contains_point(®ion, occluded_x, occluded_y)) + occluded_dirty = 1; + if (box_contains_point(®ion, strip_x, strip_y)) + strip_dirty = 1; + } + + if (occluded_dirty) + goto Failure; /* under the occluder: its pixels are already correct, + * dirtying this would redraw the occluding window */ + + if (kept_dirty) + goto Failure; /* un-occluded content must be reused by the blit */ + + if (!strip_dirty) + goto Failure; /* the un-occluded newly-exposed strip must be repainted */ + + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + wuss_window_close(win_o); + wuss_window_close(win_s); + } + + printf("test: wuss_window_set_scroll never blits a scrolled window's content over a mid-content occluder\n"); + + { + /* Regression: a small window O sits in the middle of a larger scrollable + * window M's content, covering neither M's top nor bottom edge. A + * programmatic vertical scroll of M takes a blit source slice from below + * O and shifts it up by the scroll delta, so its destination overlaps O. + * screen_copy_rect only clips to the content box, so without the fix it + * paints M's content over O and marks that area "copied" -- excluding it + * from the repaint set, so O's pixels stay overpainted with M's colour. + * With the fix each destination is clipped against the occluders and only + * the un-occluded sub-pieces are blitted, leaving O's own pixels intact. + * Read the framebuffer behind O to check. */ + colour_t cm; + uint32_t fb_m, fb_o; + test_task_t tc_m, tc_o; + wuss_task_t delegate_m, delegate_o; + box_t box_m, box_o, content; + wuss_window_t *win_m, *win_o; + int occ_x, occ_y, mid_x, mid_y, delta; + + cm = colour_rgb(0x11, 0x22, 0x33); + tc_m.redraw_count = 0; tc_m.mouse_count = 0; + tc_o.redraw_count = 0; tc_o.mouse_count = 0; + delegate_m.handle = paint_handle; delegate_m.task_data = &cm; + delegate_o.handle = paint_handle; delegate_o.task_data = NULL; + + box_m.x0 = 10; box_m.y0 = 10; + box_m.x1 = 110; box_m.y1 = 110; /* 100x100 content; doc taller, so scrollable */ + rc = wuss_window_create(wuss, &box_m, "M", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate_m, + SIZE2D(100, 300), SIZE2D(0, 0), &win_m); + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_m, &content); + + /* occluder spanning M's full content width, a band in the vertical + * middle -- so wuss__clip_to_visible splits M's blit source into a top + * and a bottom band with nothing behind O. Scrolling down slides the + * bottom band up; part of its destination lands behind O, yet no dirty + * repaint region touches O, so without the fix O stays overpainted. */ + box_o.x0 = content.x0 - 5; box_o.y0 = content.y0 + 40; + box_o.x1 = content.x1 + 5; box_o.y1 = content.y0 + 70; + rc = wuss_window_create(wuss, &box_o, "O", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate_o, + SIZE2D(box_o.x1 - box_o.x0, box_o.y1 - box_o.y0), + SIZE2D(0, 0), &win_o); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush both creates: M then O paint */ + if (rc != result_OK) + goto Failure; + + /* sample the framebuffer: occ_* sits behind O, mid_* sits in M's content + * clear of O -- record what each colour actually renders as */ + occ_x = (box_o.x0 + box_o.x1) / 2; + occ_y = box_o.y1 - 3; /* near O's bottom: the bottom band's blit + * destination reaches up to here */ + mid_x = content.x0 + 5; + mid_y = content.y0 + 5; + fb_o = ((const uint32_t *) pixels)[occ_y * 200 + occ_x]; + fb_m = ((const uint32_t *) pixels)[mid_y * 200 + mid_x]; + if (fb_o == fb_m) + goto Failure; /* the two windows must render distinguishable pixels */ + + delta = 20; + wuss_window_set_scroll(win_m, POINT(0, delta)); + rc = wuss_redraw_dirty(wuss); /* apply the scroll's blit + any repaint */ + if (rc != result_OK) + goto Failure; + + /* the pixel behind O must still be O's, not M's content blitted over it */ + if (((const uint32_t *) pixels)[occ_y * 200 + occ_x] != fb_o) + goto Failure; + + wuss_window_close(win_o); + wuss_window_close(win_m); + } + + printf("test: wuss_window_set_scroll orders its blit sub-pieces so one never clobbers another's source\n"); + + { + /* Regression: a small window O floats in the middle of a larger + * scrollable window M, with a gap all round it. wuss__clip_to_visible + * carves M's blittable content into bands around O; a vertical scroll + * shifts each band by the same delta, and one band's shifted + * destination lands on another band's still-unread source. Blitting the + * bands in clip-emit order corrupts M's own content -- pixels near O's + * bottom edge end up double-shifted. The blit must be ordered (or fall + * back). paint_handle paints M as one-pixel rows whose blue channel + * encodes document Y, so a mis-shifted row is detectable exactly. */ + colour_t cm; + test_task_t tc_m, tc_o; + wuss_task_t delegate_m, delegate_o; + box_t box_m, box_o, content, ovis; + wuss_window_t *win_m, *win_o; + int gx, gy, delta, bad; + + cm = colour_rgb(0x11, 0x22, 0x33); + tc_m.redraw_count = 0; tc_m.mouse_count = 0; + tc_o.redraw_count = 0; tc_o.mouse_count = 0; + delegate_m.handle = paint_handle; delegate_m.task_data = &cm; + delegate_o.handle = paint_handle; delegate_o.task_data = NULL; + + box_m.x0 = 10; box_m.y0 = 10; + box_m.x1 = 110; box_m.y1 = 110; /* 100x100 content; doc taller, scrollable */ + rc = wuss_window_create(wuss, &box_m, "M", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate_m, + SIZE2D(100, 300), SIZE2D(0, 0), &win_m); + if (rc != result_OK) + goto Failure; + + wuss_window_get_content_bounds(win_m, &content); + + /* O strictly inside M's content, gap on every side */ + box_o.x0 = content.x0 + 25; box_o.y0 = content.y0 + 25; + box_o.x1 = content.x0 + 75; box_o.y1 = content.y0 + 65; + rc = wuss_window_create(wuss, &box_o, "O", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), + &delegate_o, + SIZE2D(box_o.x1 - box_o.x0, box_o.y1 - box_o.y0), + SIZE2D(0, 0), &win_o); + if (rc != result_OK) + goto Failure; + + rc = wuss_redraw_dirty(wuss); /* flush both creates */ + if (rc != result_OK) + goto Failure; + + /* probe the gap column just right of O: pure M content, must slide up by + * exactly the scroll delta with no discontinuity */ + wuss_window_get_visible_bounds(win_o, &ovis); + gx = (ovis.x1 + content.x1) / 2; + delta = 20; + + wuss_window_set_scroll(win_m, POINT(0, delta)); + rc = wuss_redraw_dirty(wuss); + if (rc != result_OK) + goto Failure; + + /* every row in the gap column, across O's vertical span, must show the + * document Y that scrolling put there: doc_y = (screen_y - content.y0) + + * delta, low byte carried in blue by paint_handle */ + bad = 0; + for (gy = ovis.y0 + 2; gy < ovis.y1 - 2; gy++) + { + uint32_t px, blue; + + if (gy < content.y0 || gy >= content.y1) + continue; + px = ((const uint32_t *) pixels)[gy * 200 + gx]; + blue = px & 0xff; + if (blue != (uint32_t) ((gy - content.y0 + delta) & 0xff)) + bad = 1; + } + if (bad) + goto Failure; /* a band blit clobbered another band's source */ + + wuss_window_close(win_o); + wuss_window_close(win_m); + } + printf("test: wuss_WINDOW_NO_RESIZE_BLIT redraws the whole window instead of blitting\n"); { @@ -1859,7 +1926,7 @@ result_t wuss_test(const char *resources) 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_RESIZE_BLIT, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_nb, SIZE2D(200, 200), SIZE2D(0, 0), &win_nb); if (rc != result_OK) @@ -1938,7 +2005,8 @@ result_t wuss_test(const char *resources) 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, wuss_NO_BACKGROUND, /* scrollbars on: carve.x/y = icon size */ + rc = wuss_window_create(wuss, &box_u, "U", wuss_WINDOW_NONE, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), /* scrollbars on: carve.x/y = icon size */ &delegate_u, SIZE2D(70, 70), SIZE2D(0, 0), &win_u); /* doc size well within the 200x200 screen: growth is doc-limited, not screen-limited */ if (rc != result_OK) @@ -2046,7 +2114,7 @@ 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, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_v, SIZE2D(200, 200), SIZE2D(0, 0), &win_v); if (rc != result_OK) @@ -2159,7 +2227,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_k, box_size(&box_k), SIZE2D(0, 0), @@ -2180,7 +2248,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_l, box_size(&box_l), SIZE2D(0, 0), @@ -2247,7 +2315,7 @@ result_t wuss_test(const char *resources) "M2", wuss_WINDOW_NO_BACK | wuss_WINDOW_NO_TOGGLE_SIZE | wuss_WINDOW_NO_VSCROLL | wuss_WINDOW_NO_HSCROLL, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_m2, box_size(&box_m2), SIZE2D(0, 0), @@ -2338,7 +2406,7 @@ result_t wuss_test(const char *resources) 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_nb2, box_size(&box_nb2), SIZE2D(0, 0), @@ -2390,7 +2458,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_n, box_size(&box_n), SIZE2D(0, 0), @@ -2409,7 +2477,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_o, box_size(&box_o), SIZE2D(0, 0), @@ -2494,7 +2562,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_b, box_size(&box_b), SIZE2D(0, 0), @@ -2513,7 +2581,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_a, box_size(&box_a), SIZE2D(0, 0), @@ -2597,7 +2665,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_b, box_size(&box_b), SIZE2D(0, 0), @@ -2616,7 +2684,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_a, box_size(&box_a), SIZE2D(0, 0), @@ -2677,7 +2745,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_b, box_size(&box_b), SIZE2D(0, 0), @@ -2696,7 +2764,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_a, box_size(&box_a), SIZE2D(0, 0), @@ -2790,7 +2858,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_b, box_size(&box_b), SIZE2D(0, 0), @@ -2809,7 +2877,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_a, box_size(&box_a), SIZE2D(0, 0), @@ -2886,7 +2954,7 @@ 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_c, box_size(&box_c), SIZE2D(0, 0), @@ -2921,7 +2989,7 @@ result_t wuss_test(const char *resources) 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, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_s, SIZE2D(200, 200), SIZE2D(0, 0), &win_s); if (rc != result_OK) @@ -2997,7 +3065,7 @@ result_t wuss_test(const char *resources) &box_r, "rules", wuss_WINDOW_NONE, /* all furniture present */ - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_r, SIZE2D(400, 400), SIZE2D(0, 0), @@ -3042,7 +3110,7 @@ result_t wuss_test(const char *resources) SIZE2D(40, 30), "P", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_p, SIZE2D(40, 30), SIZE2D(0, 0), @@ -3066,7 +3134,7 @@ result_t wuss_test(const char *resources) SIZE2D(40, 30), "P", wuss_WINDOW_NONE, - wuss_NO_BACKGROUND, + wuss_BACKDROP_COLOUR(wuss_NO_BACKGROUND), &delegate_p, SIZE2D(40, 30), SIZE2D(0, 0), @@ -3086,6 +3154,33 @@ result_t wuss_test(const char *resources) wuss_window_close(win_p[k]); } + printf("test: wuss_set_palette broadcasts wuss_EVENT_PALETTE and rejects a length mismatch\n"); + { + /* the wuss under test was made with a 2-entry palette (see top of this + * function); a fresh 2-entry palette must broadcast to every task, a + * wrong-length one must be refused without broadcasting */ + static const colour_t swapped[2] = { { 0xFF202020 }, { 0xFFE0E0E0 } }; + static const colour_t too_long[3] = + { { 0xFF000000 }, { 0xFF808080 }, { 0xFFFFFFFF } }; + + tc_a.palette_count = 0; + tc_b.palette_count = 0; + + rc = wuss_set_palette(wuss, swapped, NELEMS(swapped)); + if (rc != result_OK) + goto Failure; + if (tc_a.palette_count != 1 || tc_b.palette_count != 1) + goto Failure; + + rc = wuss_set_palette(wuss, too_long, NELEMS(too_long)); + if (rc != result_BAD_ARG) + goto Failure; + if (tc_a.palette_count != 1 || tc_b.palette_count != 1) + goto Failure; /* rejected call must not have broadcast */ + + rc = result_OK; + } + printf("test: wuss_task_stop sends wuss_EVENT_QUIT to each window's task\n"); tc_a.stop_count = 0; @@ -3097,16 +3192,76 @@ result_t wuss_test(const char *resources) if (tc_a.stop_count != 1 || tc_b.stop_count != 1) goto Failure; +#ifdef WUSS_MENUS + printf("test: wuss_menu_create_from_desc parses a descriptor tree\n"); + { + static const wuss_menu_item_t borrowed_items[] = + { + { "Info", wuss_MENU_ITEM_NONE, NULL }, + { "About", wuss_MENU_ITEM_NONE, NULL } + }; + static const wuss_menu_t borrowed = + { + "Help", borrowed_items, NELEMS(borrowed_items) + }; + wuss_menu_t *m; + const wuss_menu_t *sub; + + /* "Root" is the root caption (first token), then items App, + * File{ %s title discarded, Grid(!), Export(~), |Quit }, Help(> borrowed), + * with "File" substituted for both %s. '|Quit' sets DASHED on Quit itself, + * so the submenu holds 3 rows. */ + rc = wuss_menu_create_from_desc(&m, + "Root, App, %s { %s, !Grid, ~Export, |Quit }, >Help", + "File", "File", &borrowed); + if (rc != result_OK) + goto Failure; + + if (m->nitems != 3) goto MenuFail; + if (m->title == NULL || strcmp(m->title, "Root") != 0) goto MenuFail; + if (strcmp(m->items[0].text, "App") != 0) goto MenuFail; + if (m->items[0].submenu != NULL) goto MenuFail; + + /* items[1] "File" carries the { } submenu; its first token was the title + * and is not emitted; '|Quit' sets DASHED on the Quit row itself, which + * keeps its label */ + if (strcmp(m->items[1].text, "File") != 0) goto MenuFail; + sub = m->items[1].submenu; + if (sub == NULL || sub->nitems != 3) goto MenuFail; + if (strcmp(sub->items[0].text, "Grid") != 0) goto MenuFail; + if (!(sub->items[0].flags & wuss_MENU_ITEM_TICKED)) goto MenuFail; + if (!(sub->items[1].flags & wuss_MENU_ITEM_DISABLED)) goto MenuFail; + if (strcmp(sub->items[2].text, "Quit") != 0) goto MenuFail; + if (!(sub->items[2].flags & wuss_MENU_ITEM_DASHED)) goto MenuFail; + + /* items[2] "Help" got a deep copy of the borrowed menu */ + if (strcmp(m->items[2].text, "Help") != 0) goto MenuFail; + sub = m->items[2].submenu; + if (sub == NULL || sub == &borrowed) goto MenuFail; + if (sub->nitems != 2) goto MenuFail; + if (strcmp(sub->items[1].text, "About") != 0) goto MenuFail; + + wuss_menu_destroy(m); + + /* malformed: unbalanced brace */ + rc = wuss_menu_create_from_desc(&m, "T, A { B, C"); + if (rc != result_BAD_ARG) + goto MenuFail; + + rc = result_OK; + goto MenuOK; + +MenuFail: + printf("wuss_test: menu-desc check failed\n"); + return result_TEST_FAILED; +MenuOK: ; + } +#endif + wuss_destroy(wuss); free(pixels); -#ifdef USE_SDL - rc = wuss_interactive_test(resources); - if (rc != result_TEST_PASSED) - goto Failure; -#endif - return result_TEST_PASSED; @@ -3159,7 +3314,7 @@ result_t wuss_test(const char *resources) printf("test: wuss_create (core)\n"); - rc = wuss_create(&scr, NULL, NULL, 0, NULL, &wuss); + rc = wuss_create(&scr, NULL, NULL, 0, NULL, NULL, &wuss); if (rc != result_OK) goto Failure; @@ -3199,6 +3354,59 @@ result_t wuss_test(const char *resources) content.x1 != 100 || content.y1 != 100) goto Failure; + printf("test: window_resize can never grow a window past the screen\n"); + + /* screen is 200x200; win_a sits at (0,0), chromeless. asking for a + * 500x500 content area must clamp to the 200x200 screen. */ + rc = wuss_window_resize(win_a, SIZE2D(500, 500)); + if (rc != result_OK) + goto Failure; + wuss_window_get_content_bounds(win_a, &content); + if (content.x1 - content.x0 != 200 || content.y1 - content.y0 != 200) + goto Failure; + + /* a request that already fits is left exactly as asked */ + rc = wuss_window_resize(win_a, SIZE2D(120, 90)); + if (rc != result_OK) + goto Failure; + wuss_window_get_content_bounds(win_a, &content); + if (content.x1 - content.x0 != 120 || content.y1 - content.y0 != 90) + goto Failure; + + /* a window whose top-left is offset only gets the space that is left */ + wuss_window_move(win_a, POINT(60, 40)); + rc = wuss_window_resize(win_a, SIZE2D(500, 500)); + if (rc != result_OK) + goto Failure; + wuss_window_get_content_bounds(win_a, &content); + if (content.x1 - content.x0 != 140 || content.y1 - content.y0 != 160) + goto Failure; + wuss_window_move(win_a, POINT(0, 0)); + rc = wuss_window_resize(win_a, SIZE2D(100, 100)); + if (rc != result_OK) + goto Failure; + + printf("test: window_create can never make a window bigger than the " + "screen\n"); + + { + box_t box_big; + wuss_window_t *win_big; + + /* chromeless and the on-screen nudge drags the top-left back to (0,0), + * so the clamp caps content at the full 200x200 screen */ + box_big.x0 = 10; box_big.y0 = 10; box_big.x1 = 400; box_big.y1 = 400; + rc = wuss_window_create(wuss, &box_big, "BIG", chromeless, + wuss_NO_BACKGROUND, NULL, SIZE2D(400, 400), + SIZE2D(0, 0), &win_big); + if (rc != result_OK) + goto Failure; + wuss_window_get_content_bounds(win_big, &content); + if (content.x1 - content.x0 != 200 || content.y1 - content.y0 != 200) + goto Failure; + wuss_window_close(win_big); + } + printf("test: redraw delivers REDRAW events\n"); wuss_redraw(wuss); diff --git a/libraries/wuss/window/at.c b/libraries/wuss/window/at.c index 963f428c..c450a8fb 100644 --- a/libraries/wuss/window/at.c +++ b/libraries/wuss/window/at.c @@ -1,4 +1,4 @@ -/* at.c -- wuss - minimal window manager */ +/* wuss/window/at.c -- wuss - minimal window manager */ #include "../impl.h" @@ -11,6 +11,8 @@ wuss_window_t *wuss__window_at(wuss_t *wuss, point_t p) wuss_window_t *win; win = (wuss_window_t *) e; + if (win->flags & wuss_WINDOW_HIDDEN) + continue; if (box_contains_point(&win->visible, p.x, p.y)) return win; } diff --git a/libraries/wuss/window/close.c b/libraries/wuss/window/close.c index 570425d0..91c32ede 100644 --- a/libraries/wuss/window/close.c +++ b/libraries/wuss/window/close.c @@ -1,4 +1,4 @@ -/* close.c -- wuss - minimal window manager */ +/* wuss/window/close.c -- wuss - minimal window manager */ #include @@ -23,6 +23,8 @@ void wuss_window_close(wuss_window_t *doomed) #ifdef WUSS_ICONS if (wuss->pressed_icon != NULL && wuss->pressed_icon->window == doomed) wuss->pressed_icon = NULL; + if (wuss->hover_icon != NULL && wuss->hover_icon->window == doomed) + wuss->hover_icon = NULL; #endif wuss__release_packed(doomed); @@ -35,5 +37,5 @@ void wuss_window_close(wuss_window_t *doomed) wuss__icons_free(doomed); #endif - free(doomed); + wuss__free(wuss, doomed); } diff --git a/libraries/wuss/window/create-placed.c b/libraries/wuss/window/create-placed.c index 6088a289..2ff3e00c 100644 --- a/libraries/wuss/window/create-placed.c +++ b/libraries/wuss/window/create-placed.c @@ -1,4 +1,4 @@ -/* create-placed.c -- wuss - window creation with wuss-chosen position */ +/* wuss/window/create-placed.c -- window creation with wuss-chosen position */ #include @@ -78,7 +78,7 @@ result_t wuss_window_create_placed(wuss_t *wuss, size2d_t size, const char *title, wuss_window_flags_t flags, - wuss_colour_t bg, + wuss_backdrop_t bg, const wuss_task_t *task, size2d_t doc, size2d_t min_doc, diff --git a/libraries/wuss/window/create.c b/libraries/wuss/window/create.c index 11bb445b..29936b2a 100644 --- a/libraries/wuss/window/create.c +++ b/libraries/wuss/window/create.c @@ -1,4 +1,4 @@ -/* create.c -- wuss - minimal window manager */ +/* wuss/window/create.c -- wuss - minimal window manager */ #include #include @@ -14,7 +14,7 @@ result_t wuss_window_create(wuss_t *wuss, const box_t *content, const char *title, wuss_window_flags_t flags, - wuss_colour_t bg, + wuss_backdrop_t bg, const wuss_task_t *task, size2d_t doc, size2d_t min_doc, @@ -34,7 +34,7 @@ result_t wuss_window_create(wuss_t *wuss, if (!wuss__size_ok(width, height)) return result_WUSS_TOO_SMALL; - win = malloc(sizeof(*win)); + win = wuss__malloc(wuss, sizeof(*win)); if (win == NULL) return result_OOM; @@ -74,6 +74,22 @@ result_t wuss_window_create(wuss_t *wuss, win->visible.y0 += dy; win->visible.y1 += dy; win->flags = flags; + + /* clamp so the window can never be born larger than the screen; done + * after the on-screen nudge above so it measures from the final + * top-left. only the bottom-right corner moves. */ + { + size2d_t max; + + wuss__max_content_on_screen(win, &max); + if (win->visible.x1 - win->visible.x0 - 2 * outline_px - carve.x > max.w) + win->visible.x1 = win->visible.x0 + 2 * outline_px + carve.x + max.w; + if (win->visible.y1 - win->visible.y0 - 2 * outline_px + - titlebar_height - carve.y > max.h) + win->visible.y1 = win->visible.y0 + 2 * outline_px + titlebar_height + + carve.y + max.h; + } + win->scroll.x = 0; win->scroll.y = 0; win->doc = doc; @@ -94,9 +110,9 @@ result_t wuss_window_create(wuss_t *wuss, else memset(&win->task, 0, sizeof(win->task)); - if (bg != wuss_NO_BACKGROUND && (bg < 0 || bg >= wuss->npalette)) + if (wuss__validate_backdrop(wuss, &bg) != result_OK) { - free(win); + wuss__free(wuss, win); return result_WUSS_BAD_COLOUR; } win->bg = bg; @@ -117,7 +133,8 @@ result_t wuss_window_create(wuss_t *wuss, list_add_to_head(&wuss->z_order, &win->link); - wuss_invalidate(wuss, &win->visible); + if (!(flags & wuss_WINDOW_HIDDEN)) + wuss_invalidate(wuss, &win->visible); *window = win; diff --git a/libraries/wuss/window/get-content-bounds.c b/libraries/wuss/window/get-content-bounds.c index be2187d7..4a8cbab5 100644 --- a/libraries/wuss/window/get-content-bounds.c +++ b/libraries/wuss/window/get-content-bounds.c @@ -1,8 +1,9 @@ -/* get-content-bounds.c -- wuss - minimal window manager */ +/* wuss/window/get-content-bounds.c -- wuss - minimal window manager */ #include "../impl.h" -void wuss_window_get_content_bounds(const wuss_window_t *window, box_t *content) +void wuss_window_get_content_bounds(const wuss_window_t *window, + box_t *content) { wuss__content_box(window, content); } diff --git a/libraries/wuss/window/get-scroll.c b/libraries/wuss/window/get-scroll.c index 18d09d8d..b2345194 100644 --- a/libraries/wuss/window/get-scroll.c +++ b/libraries/wuss/window/get-scroll.c @@ -1,4 +1,4 @@ -/* get-scroll.c -- wuss - minimal window manager */ +/* wuss/window/get-scroll.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/window/get-visible-bounds.c b/libraries/wuss/window/get-visible-bounds.c index b33f3bed..d305ad4e 100644 --- a/libraries/wuss/window/get-visible-bounds.c +++ b/libraries/wuss/window/get-visible-bounds.c @@ -1,8 +1,9 @@ -/* get-visible-bounds.c -- wuss - minimal window manager */ +/* wuss/window/get-visible-bounds.c -- wuss - minimal window manager */ #include "../impl.h" -void wuss_window_get_visible_bounds(const wuss_window_t *window, box_t *visible) +void wuss_window_get_visible_bounds(const wuss_window_t *window, + box_t *visible) { *visible = window->visible; } diff --git a/libraries/wuss/window/invalidate.c b/libraries/wuss/window/invalidate.c index accaa784..ac2c4c53 100644 --- a/libraries/wuss/window/invalidate.c +++ b/libraries/wuss/window/invalidate.c @@ -1,4 +1,4 @@ -/* invalidate.c -- wuss - minimal window manager */ +/* wuss/window/invalidate.c -- wuss - minimal window manager */ #include @@ -45,7 +45,9 @@ static void box_subtract_into(const box_t *piece, /* Clip "box" (screen space) down to the parts not already covered by * windows above "window" in the z-order, writing the surviving pieces to * "out" (capacity WUSS_MAX_INVALIDATE_PIECES) and returning their count. */ -int wuss__clip_to_visible(wuss_window_t *window, const box_t *box, box_t *out) +int wuss__clip_to_visible(wuss_window_t *window, + const box_t *box, + box_t *out) { box_t scratch[WUSS_MAX_INVALIDATE_PIECES]; box_t *cur, *nxt, *tmp; @@ -63,6 +65,8 @@ int wuss__clip_to_visible(wuss_window_t *window, const box_t *box, box_t *out) int nnext, p; occluder = (wuss_window_t *) e; + if (occluder->flags & wuss_WINDOW_HIDDEN) + continue; /* a hidden window occludes nothing */ nnext = 0; for (p = 0; p < ncur; p++) @@ -148,6 +152,60 @@ int wuss__subtract_boxes(const box_t *whole, return ncur; } +/* Sequential single-rect blits (each a self-consistent memmove) can still + * corrupt each other when one piece's destination lands on another piece's + * still-unread source -- but that only actually matters if no blit order + * avoids it. Build the "must happen before" graph (piece j before piece i + * whenever dest[i] would overwrite clean[j]'s still-unread source) and + * topologically sort it: any window with more than one occluder-carved + * piece near a shared edge -- e.g. two bands split by a corner occluder -- + * routinely has one such pairwise overlap without there being a genuine + * cycle, and rejecting those outright regressed plain corner-occlusion + * drags into full fallback redraws. Only an actual cycle (i must precede j + * and j must precede i) has no safe order and needs the fallback. */ +int wuss__order_pieces(const box_t *clean, + const box_t *dest, + int n, + int *order) +{ + int adj[WUSS_MAX_INVALIDATE_PIECES][WUSS_MAX_INVALIDATE_PIECES]; + int indeg[WUSS_MAX_INVALIDATE_PIECES]; + int queue[WUSS_MAX_INVALIDATE_PIECES]; + int i, j, head, tail, nout, u; + + for (i = 0; i < n; i++) + indeg[i] = 0; + for (j = 0; j < n; j++) + for (i = 0; i < n; i++) + adj[j][i] = 0; + + for (i = 0; i < n; i++) + for (j = 0; j < n; j++) + if (i != j && !adj[j][i] && box_intersects(&dest[i], &clean[j])) + { + adj[j][i] = 1; /* j must be blitted before i */ + indeg[i]++; + } + + tail = 0; + for (i = 0; i < n; i++) + if (indeg[i] == 0) + queue[tail++] = i; + + head = nout = 0; + while (head < tail) + { + u = queue[head++]; + order[nout++] = u; + + for (i = 0; i < n; i++) + if (adj[u][i] && --indeg[i] == 0) + queue[tail++] = i; + } + + return nout == n; +} + /* Invalidate the parts of "window"'s footprint that are hidden behind other * windows at the current z-order -- the rest of its footprint is already * showing its own correct pixels, so redrawing that too would just be @@ -186,7 +244,9 @@ void wuss__invalidate_clipped(wuss_window_t *window, const box_t *box) /* Invalidate the part of "whole" not already covered by "keep" -- used * after a blit has slid a window's pixels from "whole" to "keep", so only * the vacated sliver still needs an actual repaint. */ -void wuss__invalidate_minus(wuss_t *wuss, const box_t *whole, const box_t *keep) +void wuss__invalidate_minus(wuss_t *wuss, + const box_t *whole, + const box_t *keep) { box_t pieces[WUSS_MAX_INVALIDATE_PIECES]; box_t cut; @@ -220,10 +280,8 @@ void wuss_window_invalidate(wuss_window_t *window, const box_t *local_box) local_box = &whole; } - screen_box.x0 = content.x0 - window->scroll.x + local_box->x0; - screen_box.y0 = content.y0 - window->scroll.y + local_box->y0; - screen_box.x1 = content.x0 - window->scroll.x + local_box->x1; - screen_box.y1 = content.y0 - window->scroll.y + local_box->y1; + box_translated(local_box, content.x0 - window->scroll.x, + content.y0 - window->scroll.y, &screen_box); wuss__invalidate_clipped(window, &screen_box); } diff --git a/libraries/wuss/window/move.c b/libraries/wuss/window/move.c index a23eaa17..036dd655 100644 --- a/libraries/wuss/window/move.c +++ b/libraries/wuss/window/move.c @@ -1,4 +1,4 @@ -/* move.c -- wuss - minimal window manager */ +/* wuss/window/move.c -- wuss - minimal window manager */ #include "../impl.h" @@ -14,60 +14,6 @@ static void translate_box(const box_t *box, int dx, int dy, box_t *out) /* 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) { box_t clean[WUSS_MAX_INVALIDATE_PIECES]; @@ -90,6 +36,18 @@ void wuss_window_move(wuss_window_t *window, point_t p) titlebar_height = wuss__titlebar_height(window); before = window->visible; + /* a hidden window has nothing on screen to slide and must paint nothing; + * just translate its footprint so it is in place when shown again */ + if (window->flags & wuss_WINDOW_HIDDEN) + { + window->visible.x0 = p.x - outline_px; + window->visible.y0 = p.y - outline_px - titlebar_height; + window->visible.x1 = window->visible.x0 + width; + window->visible.y1 = window->visible.y0 + height; + wuss__notify_open(window); + return; + } + /* The clean (non-occluded) pieces of "before" are genuinely this * window's own rendering; whatever isn't clean is hidden behind some * other window and has no valid pixels of this window's content to diff --git a/libraries/wuss/window/resize.c b/libraries/wuss/window/resize.c index bcaca693..5c4a46af 100644 --- a/libraries/wuss/window/resize.c +++ b/libraries/wuss/window/resize.c @@ -1,6 +1,7 @@ -/* resize.c -- wuss - minimal window manager */ +/* wuss/window/resize.c -- wuss - minimal window manager */ #include "base/utils.h" +#include "geom/box.h" #include "../impl.h" @@ -33,8 +34,10 @@ static void invalidate_grown_or_shrunk(wuss_window_t *window, result_t wuss_window_resize(wuss_window_t *window, size2d_t size) { int outline_px, titlebar_height; - box_t before; + box_t before, before_content; point_t carve; + point_t old_scroll; + point_t clamped; if (!wuss__size_ok(size.w, size.h)) return result_WUSS_TOO_SMALL; @@ -42,9 +45,22 @@ result_t wuss_window_resize(wuss_window_t *window, size2d_t size) /* a manual resize desyncs the window from its layout-packer slot */ wuss__release_packed(window); + /* a window can never grow larger than the screen */ + { + size2d_t max; + + wuss__max_content_on_screen(window, &max); + if (size.w > max.w) + size.w = max.w; + if (size.h > max.h) + size.h = max.h; + } + outline_px = wuss__outline_px(window); titlebar_height = wuss__titlebar_height(window); before = window->visible; + old_scroll = window->scroll; + wuss__content_box(window, &before_content); wuss__furniture_carve_for(window->flags, wuss__button_size(window), &carve); window->visible.x1 = window->visible.x0 + size.w + 2 * outline_px + carve.x; @@ -52,6 +68,66 @@ result_t wuss_window_resize(wuss_window_t *window, size2d_t size) wuss__notify_open(window); + /* Enlarging the viewport (or growing it past the doc extent) can scroll + * content that no longer exists into view; pull the offset back so only + * the document extent is ever shown. The content pixels already on screen + * are still this window's own rendering, just at the old scroll offset -- + * so slide them by the scroll delta and only the newly-exposed strip(s) + * need a real repaint. The valid source is the old content box minus + * whatever windows above it were covering (those areas hold occluder + * pixels, not this window's), so blit it piece by piece; the clip bounds + * every destination to the new content box, discarding anything sliding + * off the old box's far edge rather than dragging stale pixels in. If the + * screen format can't blit, fall back to repainting the whole content + * box. Either way the grown/shrunk-sliver logic below still assumes an + * unchanged interior, which now holds. */ + clamped = wuss__scroll_clamp(window, window->scroll); + if (clamped.x != window->scroll.x || clamped.y != window->scroll.y) + { + box_t content; + box_t src[WUSS_MAX_INVALIDATE_PIECES]; + box_t copied[WUSS_MAX_INVALIDATE_PIECES]; + box_t dirty[WUSS_MAX_INVALIDATE_PIECES]; + int dx, dy, nsrc, ncopied, ndirty, i; + + dx = clamped.x - old_scroll.x; + dy = clamped.y - old_scroll.y; + window->scroll = clamped; + wuss__content_box(window, &content); + + nsrc = wuss__clip_to_visible(window, &before_content, src); + ncopied = 0; + window->wuss->scr->clip = content; + for (i = 0; i < nsrc; i++) + { + box_t got; + + if (screen_copy_rect(window->wuss->scr, &src[i], + POINT(src[i].x0 - dx, src[i].y0 - dy), + &got) != 0 && + ncopied < WUSS_MAX_INVALIDATE_PIECES) + copied[ncopied++] = got; + } + + if (ncopied > 0) + { + /* Repaint the content box minus what the blit reused, each survivor + * clipped to this window's visible area: parts that were behind an + * occluder still show the occluder's own correct pixels, so leaving + * them out keeps the resize from redrawing the occluding window. */ + ndirty = wuss__subtract_boxes(&content, copied, ncopied, dirty); + for (i = 0; i < ndirty; i++) + wuss__invalidate_clipped(window, &dirty[i]); + } + else + { + wuss__invalidate_clipped(window, &content); + } +#ifdef WUSS_FURNITURE + window->wuss->furniture_ops->invalidate(window); +#endif + } + if (window->flags & wuss_WINDOW_NO_RESIZE_BLIT) { /* This task's content isn't just anchored positions plus furniture -- @@ -81,8 +157,8 @@ result_t wuss_window_resize(wuss_window_t *window, size2d_t size) #ifdef WUSS_FURNITURE if (window->visible.x1 - window->visible.x0 > before.x1 - before.x0 || window->visible.y1 - window->visible.y0 > before.y1 - before.y0) - wuss__furniture_invalidate_for(window, &before); - wuss__furniture_invalidate(window); + window->wuss->furniture_ops->invalidate_for(window, &before); + window->wuss->furniture_ops->invalidate(window); #endif } diff --git a/libraries/wuss/window/restack.c b/libraries/wuss/window/restack.c index 5192bdfb..d4d7ee9e 100644 --- a/libraries/wuss/window/restack.c +++ b/libraries/wuss/window/restack.c @@ -1,4 +1,4 @@ -/* restack.c -- wuss - minimal window manager */ +/* wuss/window/restack.c -- wuss - minimal window manager */ #include "../impl.h" diff --git a/libraries/wuss/window/set-background.c b/libraries/wuss/window/set-background.c index 8cfe7cb0..5e846a33 100644 --- a/libraries/wuss/window/set-background.c +++ b/libraries/wuss/window/set-background.c @@ -1,12 +1,13 @@ -/* set-background.c -- wuss - minimal window manager */ +/* wuss/window/set-background.c -- wuss - minimal window manager */ #include "../impl.h" -result_t wuss_window_set_background(wuss_window_t *window, wuss_colour_t bg) +result_t wuss_window_set_background(wuss_window_t *window, + wuss_backdrop_t bg) { box_t content; - if (bg != wuss_NO_BACKGROUND && (bg < 0 || bg >= window->wuss->npalette)) + if (wuss__validate_backdrop(window->wuss, &bg) != result_OK) return result_WUSS_BAD_COLOUR; window->bg = bg; diff --git a/libraries/wuss/window/set-hidden.c b/libraries/wuss/window/set-hidden.c new file mode 100644 index 00000000..76de1528 --- /dev/null +++ b/libraries/wuss/window/set-hidden.c @@ -0,0 +1,25 @@ +/* wuss/window/set-hidden.c -- wuss - minimal window manager */ + +#include "../impl.h" + +void wuss_window_set_hidden(wuss_window_t *window, int hidden) +{ + int was_hidden; + + was_hidden = (window->flags & wuss_WINDOW_HIDDEN) != 0; + if (!was_hidden == !hidden) + return; /* no change */ + + if (hidden) + { + /* still on screen: repaint its footprint now, then mark it gone */ + wuss_invalidate(window->wuss, &window->visible); + window->flags |= wuss_WINDOW_HIDDEN; + } + else + { + /* mark it back, then repaint its footprint so it appears */ + window->flags &= (wuss_window_flags_t) ~wuss_WINDOW_HIDDEN; + wuss_invalidate(window->wuss, &window->visible); + } +} diff --git a/libraries/wuss/window/set-scroll.c b/libraries/wuss/window/set-scroll.c index e1371013..721d23f5 100644 --- a/libraries/wuss/window/set-scroll.c +++ b/libraries/wuss/window/set-scroll.c @@ -1,11 +1,21 @@ -/* set-scroll.c -- wuss - minimal window manager */ +/* wuss/window/set-scroll.c -- wuss - minimal window manager */ #include "../impl.h" void wuss_window_set_scroll(wuss_window_t *window, point_t p) { - box_t content, copied; - int dx, dy; + box_t content; + box_t stale[WUSS_MAX_DIRTY]; + box_t clean[WUSS_MAX_INVALIDATE_PIECES]; + box_t src[WUSS_MAX_INVALIDATE_PIECES]; + box_t blit_src[WUSS_MAX_INVALIDATE_PIECES]; + box_t blit_dest[WUSS_MAX_INVALIDATE_PIECES]; + box_t copied[WUSS_MAX_INVALIDATE_PIECES]; + box_t dirty[WUSS_MAX_INVALIDATE_PIECES]; + box_t vis[WUSS_MAX_INVALIDATE_PIECES]; + int order[WUSS_MAX_INVALIDATE_PIECES]; + int dx, dy, nstale, nclean, nsrc, nblit, ncopied, ndirty, nvis, i, j, idx; + int overflow, blit_failed; dx = p.x - window->scroll.x; dy = p.y - window->scroll.y; @@ -16,25 +26,114 @@ void wuss_window_set_scroll(wuss_window_t *window, point_t p) wuss__content_box(window, &content); + /* Any content-box area already sitting in the dirty list holds stale pixels + * (e.g. the strip exposed by an earlier scroll this frame, not yet + * redrawn): a wheel spin can deliver several scrolls before wuss_redraw_dirty + * runs, and the blit below is a framebuffer memmove -- feeding it a stale + * source would smear that stale content into the interior. Collect those + * regions now, before the furniture invalidate adds its own unrelated + * rects, and exclude them from the blit source further down. */ + nstale = 0; + for (i = 0; i < window->wuss->ndirty; i++) + if (box_intersects(&window->wuss->dirty[i], &content)) + stale[nstale++] = window->wuss->dirty[i]; + #ifdef WUSS_FURNITURE /* the scrollbar sausage position depends on scroll, so its well needs * redrawing too -- content invalidation alone never touches it */ - wuss__furniture_invalidate(window); + window->wuss->furniture_ops->invalidate(window); #endif - if (window->wuss->z_order.next == &window->link) + /* The valid blit source is the content box minus whatever windows above it + * were covering (those areas hold occluder pixels, not this window's). + * Sliding a source piece by the scroll delta can still land its + * destination on screen a higher window owns -- screen_copy_rect only + * clips to the content box, so it would paint over the occluder there. + * Clip each destination against the occluders too and keep only the + * surviving sub-pieces, each with its own matching source offset. */ + nclean = wuss__clip_to_visible(window, &content, clean); + nblit = 0; + overflow = 0; + + /* Carve the stale regions out of each clean source piece. Each subtract + * can split a piece into up to four bands, so this can overflow the piece + * budget on a badly fragmented window -- treat that as "no safe fast + * path" and let the blit_failed branch below invalidate what's left. */ + nsrc = 0; + for (i = 0; i < nclean; i++) + { + box_t kept[WUSS_MAX_INVALIDATE_PIECES]; + int nkept, k; + + nkept = wuss__subtract_boxes(&clean[i], stale, nstale, kept); + for (k = 0; k < nkept; k++) + { + if (nsrc == WUSS_MAX_INVALIDATE_PIECES) + { + overflow = 1; + break; + } + src[nsrc++] = kept[k]; + } + if (overflow) + break; + } + + for (i = 0; i < nsrc && !overflow; i++) + { + box_t want; + + box_translated(&src[i], -dx, -dy, &want); + nvis = wuss__clip_to_visible(window, &want, vis); + for (j = 0; j < nvis; j++) + { + if (nblit == WUSS_MAX_INVALIDATE_PIECES) + { + overflow = 1; + break; + } + blit_dest[nblit] = vis[j]; + box_translated(&vis[j], dx, dy, &blit_src[nblit]); + nblit++; + } + } + + /* Each sub-piece blit is a self-consistent memmove, but one piece's + * destination can land on another piece's still-unread source (e.g. the + * bands carved around a floating occluder overlap once shifted). Order + * them so that never happens; fall back to a full content invalidate if + * no safe order exists or the piece budget overflowed. */ + window->wuss->scr->clip = content; + ncopied = 0; + blit_failed = nsrc == 0 || overflow || + !wuss__order_pieces(blit_src, blit_dest, nblit, order); + + for (i = 0; i < nblit && !blit_failed; i++) { - /* Topmost, so every pixel in "content" is genuinely this window's own - * rendering: slide it by the scroll delta (clipped to the viewport, so - * source and destination both stay inside "content") and only the - * newly-exposed edge strip(s) need an actual repaint. */ - window->wuss->scr->clip = content; - if (screen_copy_rect(window->wuss->scr, &content, - POINT(content.x0 - dx, content.y0 - dy), &copied)) + box_t got; + + idx = order[i]; + if (screen_copy_rect(window->wuss->scr, &blit_src[idx], + POINT(blit_dest[idx].x0, blit_dest[idx].y0), + &got) == 0) { - wuss__invalidate_minus(window->wuss, &content, &copied); - return; + blit_failed = 1; + break; } + if (ncopied < WUSS_MAX_INVALIDATE_PIECES) + copied[ncopied++] = got; + } + + if (!blit_failed && ncopied > 0) + { + /* Repaint the content box minus what the blit reused, then clip each + * survivor to this window's visible area: the parts that were behind an + * occluder still show the occluder's own correct pixels, so leaving + * them out keeps the scroll from redrawing the occluding window. */ + ndirty = wuss__subtract_boxes(&content, copied, ncopied, dirty); + for (i = 0; i < ndirty; i++) + wuss__invalidate_clipped(window, &dirty[i]); + return; } wuss__invalidate_clipped(window, &content); diff --git a/tools/test_wrap_doxygen.py b/tools/test_wrap_doxygen.py index b0fe1a5e..f252a2e7 100644 --- a/tools/test_wrap_doxygen.py +++ b/tools/test_wrap_doxygen.py @@ -16,10 +16,10 @@ int g(void); '''.splitlines() -out, overflow = process(src, width=80) +out, overflow = process(src, width=77) assert overflow == [], overflow -assert all(len(l) <= 80 for l in out), [l for l in out if len(l) > 80] +assert all(len(l) <= 77 for l in out), [l for l in out if len(l) > 77] assert out[0] == '/**' assert out[1] == ' * A short line.' # continuation lines must keep the "*" one column right of "/**", with a @@ -31,7 +31,7 @@ assert out.count('/**') == 2 # original block + the promoted single-liner # idempotent: reformatting already-reformatted text changes nothing -out2, overflow2 = process(out, width=80) +out2, overflow2 = process(out, width=77) assert out2 == out assert overflow2 == [] diff --git a/tools/test_wrap_protos.py b/tools/test_wrap_protos.py index abef8405..c09d3eaf 100644 --- a/tools/test_wrap_protos.py +++ b/tools/test_wrap_protos.py @@ -2,7 +2,7 @@ """assert-based self-check for wrap_protos.py""" from wrap_protos import process, split_top_level -W = 80 +W = 77 def one(src): @@ -41,7 +41,7 @@ def test_idempotent(): def test_rewrap_bad_alignment(): - # joined form is >80, and it arrives already (badly) wrapped + # joined form is >77, and it arrives already (badly) wrapped src = ( "static int screen_copy_rectangle_p4(screen_t *scr,\n" " const box_t *s, const box_t *d,\n" @@ -59,7 +59,7 @@ def test_rewrap_bad_alignment(): def test_short_multiline_untouched(): - # already multi-line but joins to <80 -- leave the hand grouping alone + # already multi-line but joins to <77 -- leave the hand grouping alone src = ( "void draw_rect(screen_t *scr,\n" " int x, int y,\n" @@ -117,7 +117,7 @@ def test_variadic_bails(): def test_call_statement_not_touched(): src = (" array_squeeze(v->base, v->used, v->width, width_argument_here); " - "// a call, way over 80 columns of course yes indeed\n") + "// a call, way over 77 columns of course yes indeed\n") assert one(src) is None diff --git a/tools/wrap_doxygen.py b/tools/wrap_doxygen.py index f64906ba..4c6b2e8a 100644 --- a/tools/wrap_doxygen.py +++ b/tools/wrap_doxygen.py @@ -131,7 +131,7 @@ def flush(): 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('--width', type=int, default=77) ap.add_argument('--check', action='store_true', help="don't write; exit 1 if any file would change") args = ap.parse_args() diff --git a/tools/wrap_protos.py b/tools/wrap_protos.py index 03191596..2787af3a 100644 --- a/tools/wrap_protos.py +++ b/tools/wrap_protos.py @@ -302,7 +302,7 @@ def main(): description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) ap.add_argument('files', nargs='+') - ap.add_argument('-w', '--width', type=int, default=80) + ap.add_argument('-w', '--width', type=int, default=77) ap.add_argument('-n', '--dry-run', action='store_true', help='report files that would change, write nothing') args = ap.parse_args()