diff options
| author | Berke Güzel <wenekar1@gmail.com> | 2026-10-13 23:41:31 +0300 |
|---|---|---|
| committer | Berke Güzel <wenekar1@gmail.com> | 2026-10-13 23:41:31 +0300 |
| commit | 76073fe5fad8799e5850805f2f090af2cb62c741 (patch) | |
| tree | 9658019babe4760a3634bdab4da0731a10e42e68 /examples/tablet.c | |
| parent | dd1775a9927922c14f8679ccbc4d3ca8d5917e20 (diff) | |
Simplify the public event and CPU API
Applications now need far less ceremony: hello.c is a 20-line loop that
handles REFRESH, CLOSE and QUIT and draws into the surface's own pixels.
- Fold GEOMETRY, STATE and FOCUS into one WINDOW event carrying size,
scale and state; unchanged values send nothing.
- Guarantee REFRESH once a window has a size and after every resize,
never at size zero.
- Merge DROP_HOVER into DROP with over/leave/data actions and give drag
and drop actions enums.
- Keep event strings valid until the next wait and drop
yasl_event_release; empty or failed waits report NONE.
- Replace copy-in presentation with a lent pixel buffer:
yasl_cpu_pixels and yasl_cpu_present.
- Remove yasl_poll_event, yasl_cpu_read_pixels, unused event fields and
comments that restated names.
- Wayland: resize floating windows in request_size instead of reporting
it unsupported, and stop marking new GPU surfaces as Vulkan so
yasl_vulkan_create_surface no longer fails with NATIVE_WINDOW_IN_USE.
Also includes the in-progress public API refactor that was uncommitted
in the tree (removal of the app/loop layer, new surface, GL and Vulkan
entry points).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'examples/tablet.c')
| -rw-r--r-- | examples/tablet.c | 419 |
1 files changed, 91 insertions, 328 deletions
diff --git a/examples/tablet.c b/examples/tablet.c index ba1a94f..dad4b30 100644 --- a/examples/tablet.c +++ b/examples/tablet.c @@ -1,346 +1,109 @@ -/* Pressure-sensitive drawing using only YASL's portable API. */ -#include <yasl/app.h> - -#include <math.h> -#include <stdio.h> -#include <stdlib.h> +/* Pen canvas: pressure, eraser and coherent strokes drawn straight into the CPU surface. */ +#include <yasl/yasl.h> +#include <yasl/cpu.h> #include <string.h> +#include <math.h> -#define PAPER 0xfff5f2eau -#define INK 0xff315fadu -#define PANEL 0xff172338u -#define TEXT 0xffe5edf8u -#define ACCENT 0xff59d5b6u -#define HEADER 104 - -struct app { - bool smoke_test, failed, tablet_supported; - bool down, visible, previous; - struct yasl_surface *surface; - enum yasl_backend backend; - struct { - struct yasl_vector2i pos; - enum yasl_pointer_device device; - struct yasl_pointer_pen pen; - } pointer; - double last_x, last_y, last_radius; - uint32_t *canvas; - struct yasl_geometry geometry; -}; - -static const char *backend_name(enum yasl_backend backend) -{ - switch (backend) { - case YASL_BACKEND_WAYLAND: return "WAYLAND"; - case YASL_BACKEND_X11: return "X11"; - case YASL_BACKEND_COCOA: return "COCOA"; - default: return "AUTO"; - } -} - -static const char *device_name(enum yasl_pointer_device device) -{ - switch (device) { - case YASL_PTR_DEV_PEN: return "PEN"; - case YASL_PTR_DEV_ERASER: return "ERASER"; - case YASL_PTR_DEV_TOUCH: return "TOUCH"; - default: return "MOUSE"; - } -} - -static void cancel_stroke(struct app *app) -{ - app->down = app->previous = app->visible = false; - app->pointer.pen = (struct yasl_pointer_pen){0}; -} - -static bool resize_canvas(struct app *app, struct yasl_geometry geometry) -{ - int w = geometry.size.x, h = geometry.size.y; - if (w <= 0 || h <= 0 || !geometry.scale_120 || - (size_t)w > SIZE_MAX / sizeof(uint32_t) / (size_t)h) return false; - if (app->canvas && w == app->geometry.size.x && h == app->geometry.size.y && - geometry.scale_120 == app->geometry.scale_120) return true; - uint32_t *pixels = malloc((size_t)w * h * sizeof(*pixels)); - if (!pixels) return false; - double ratio = (double)app->geometry.scale_120 / geometry.scale_120; - for (int y = 0; y < h; y++) { - int old_y = (int)(y * ratio); - for (int x = 0; x < w; x++) { - int old_x = (int)(x * ratio); - pixels[(size_t)y * w + x] = app->canvas && - old_x < app->geometry.size.x && old_y < app->geometry.size.y ? - app->canvas[(size_t)old_y * app->geometry.size.x + old_x] : PAPER; - } - } - free(app->canvas); - app->canvas = pixels; - app->geometry = geometry; - cancel_stroke(app); - return true; -} - -static void clear_canvas(struct app *app) -{ - for (size_t i = 0, n = (size_t)app->geometry.size.x * app->geometry.size.y; i < n; i++) - app->canvas[i] = PAPER; - app->previous = false; -} - -static double brush_radius(const struct app *app) -{ - return ((double)app->geometry.scale_120 / YASL_SCALE_UNIT) * - ((app->pointer.device == YASL_PTR_DEV_PEN || - app->pointer.device == YASL_PTR_DEV_ERASER) ? - 2 + 24 * ((double)app->pointer.pen.pressure / 65535) : 6); -} - -static void stamp(struct app *app, double cx, double cy, double radius) -{ - int w = app->geometry.size.x, h = app->geometry.size.y; - int top = yasl_scale_to_device(HEADER, app->geometry.scale_120); - int x0 = (int)fmax(0, fmin(w, floor(cx - radius))); - int y0 = (int)fmax(top, fmin(h, floor(cy - radius))); - int x1 = (int)fmax(0, fmin(w, ceil(cx + radius))); - int y1 = (int)fmax(0, fmin(h, ceil(cy + radius))); - uint32_t color = app->pointer.device == YASL_PTR_DEV_ERASER ? PAPER : INK; - for (int y = y0; y < y1; y++) - for (int x = x0; x < x1; x++) { - double dx = x + 0.5 - cx, dy = y + 0.5 - cy; - if (dx * dx + dy * dy <= radius * radius) - app->canvas[(size_t)y * w + x] = color; - } -} - -static void stroke(struct app *app) -{ - double x = app->pointer.pos.x, y = app->pointer.pos.y; - double radius = brush_radius(app); - /* Leaving the canvas breaks the line so re-entry cannot bridge the UI. */ - if (x < 0 || x >= app->geometry.size.x || - y < HEADER * ((double)app->geometry.scale_120 / YASL_SCALE_UNIT) || - y >= app->geometry.size.y) { - app->previous = false; - return; - } - if (app->previous) { - double dx = x - app->last_x, dy = y - app->last_y; - int steps = (int)ceil(hypot(dx, dy)); - for (int i = 1; i <= steps; i++) { - double t = (double)i / steps; - stamp(app, app->last_x + dx * t, app->last_y + dy * t, - app->last_radius + (radius - app->last_radius) * t); - } - } - /* Also stamp pressure-only motion, even when position has not changed. */ - stamp(app, x, y, radius); - app->last_x = x; - app->last_y = y; - app->last_radius = radius; - app->previous = true; -} - -static enum yasl_app_result input(void *ud, const struct yasl_event *event) -{ - struct app *app = ud; - if (event->type == YASL_EVENT_WINDOW_CLOSE) - return YASL_APP_QUIT; - if (event->type == YASL_EVENT_WINDOW_RESIZE) { - if (!resize_canvas(app, event->resize.geometry)) { - app->failed = true; - return YASL_APP_ERROR; - } - } else if (event->type == YASL_EVENT_INPUT_RESET || event->type == YASL_EVENT_POINTER_LEAVE || - (event->type == YASL_EVENT_FOCUS && !event->focus.focused)) { - cancel_stroke(app); - } else if (event->type == YASL_EVENT_KEY && event->key.action == YASL_KEY_PRESS) { - if (event->key.key == YASL_KEY_ESCAPE) return YASL_APP_QUIT; - if (event->key.key == YASL_KEY_C || event->key.key == YASL_KEY_SPACE) - clear_canvas(app); - } else if (event->type == YASL_EVENT_POINTER_DOWN || event->type == YASL_EVENT_POINTER_UP || - event->type == YASL_EVENT_POINTER_MOTION) { - if (app->pointer.device != event->pointer.device) cancel_stroke(app); - app->pointer.pos = event->pointer.pos; - app->pointer.device = event->pointer.device; - app->pointer.pen = event->pointer.pen; - app->visible = true; - if (event->type == YASL_EVENT_POINTER_DOWN && event->pointer.button == YASL_BUTTON_PRIMARY) { - app->down = true; - app->previous = false; - stroke(app); - } else if (event->type == YASL_EVENT_POINTER_UP && event->pointer.button == YASL_BUTTON_PRIMARY) { - app->down = app->previous = false; - app->pointer.pen.pressure = 0; - } else if (event->type == YASL_EVENT_POINTER_MOTION) { - if (app->down) stroke(app); - } - } else { - return YASL_APP_CONTINUE; - } - yasl_surface_request_frame(event->surface); - return YASL_APP_CONTINUE; -} - -/* Tiny built-in 5x7 font keeps this example independent of a text library. */ -static const uint8_t glyphs[128][7] = { - ['A']={14,17,17,31,17,17,17}, ['B']={30,17,17,30,17,17,30}, - ['C']={14,17,16,16,16,17,14}, ['D']={30,17,17,17,17,17,30}, - ['E']={31,16,16,30,16,16,31}, ['F']={31,16,16,30,16,16,16}, - ['G']={14,17,16,23,17,17,14}, ['H']={17,17,17,31,17,17,17}, - ['I']={14,4,4,4,4,4,14}, ['J']={7,2,2,2,2,18,12}, - ['K']={17,18,20,24,20,18,17}, ['L']={16,16,16,16,16,16,31}, - ['M']={17,27,21,21,17,17,17}, ['N']={17,25,21,19,17,17,17}, - ['O']={14,17,17,17,17,17,14}, ['P']={30,17,17,30,16,16,16}, - ['Q']={14,17,17,17,21,18,13}, ['R']={30,17,17,30,20,18,17}, - ['S']={15,16,16,14,1,1,30}, ['T']={31,4,4,4,4,4,4}, - ['U']={17,17,17,17,17,17,14}, ['V']={17,17,17,17,17,10,4}, - ['W']={17,17,17,21,21,21,10}, ['X']={17,17,10,4,10,17,17}, - ['Y']={17,17,10,4,4,4,4}, ['Z']={31,1,2,4,8,16,31}, - ['0']={14,17,19,21,25,17,14}, ['1']={4,12,4,4,4,4,14}, - ['2']={14,17,1,2,4,8,31}, ['3']={30,1,1,14,1,1,30}, - ['4']={2,6,10,18,31,2,2}, ['5']={31,16,16,30,1,1,30}, - ['6']={14,16,16,30,17,17,14}, ['7']={31,1,2,4,8,8,8}, - ['8']={14,17,17,14,17,17,14}, ['9']={14,17,17,15,1,1,14}, - ['-']={0,0,0,31,0,0,0}, ['+']={0,4,4,31,4,4,0}, - ['/']={1,1,2,4,8,16,16}, [':']={0,4,4,0,4,4,0}, - ['.']={0,0,0,0,0,4,4}, ['%']={17,2,4,4,8,16,17}, -}; - -static void rect(struct yasl_framebuffer *fb, int x, int y, int w, int h, uint32_t color) -{ - int right = x + w < fb->size.x ? x + w : fb->size.x; - int bottom = y + h < fb->size.y ? y + h : fb->size.y; - for (int j = y > 0 ? y : 0; j < bottom; j++) - for (int i = x > 0 ? x : 0; i < right; i++) - fb->pixels[(size_t)j * fb->size.x + i] = color; -} - -static void text(struct yasl_framebuffer *fb, double dpi, int x, int y, - const char *str, uint32_t color) -{ - for (; *str; str++, x += 12) { - unsigned ch = (unsigned char)*str; - if (ch >= 128) continue; - for (int row = 0; row < 7; row++) - for (int col = 0; col < 5; col++) { - if (!(glyphs[ch][row] & (1 << (4 - col)))) continue; - int left = (int)lround((x + col * 2) * dpi); - int top = (int)lround((y + row * 2) * dpi); - rect(fb, left, top, - (int)lround((x + col * 2 + 2) * dpi) - left, - (int)lround((y + row * 2 + 2) * dpi) - top, color); - } - } -} +#define PAPER 0xfff0eee8u +#define INK 0xff202830u -static void draw(void *ud, struct yasl_surface *surface, struct yasl_frame *frame) +static void stamp(uint32_t *pixels, int w, int h, double x, double y, double radius, bool erase) { - struct app *app = ud; - struct yasl_geometry geometry = {frame->target.size, frame->scale_120}; - if (!resize_canvas(app, geometry)) { - fprintf(stderr, "Cannot allocate drawing canvas\n"); - app->failed = true; - yasl_app_exit(yasl_surface_display(surface), YASL_APP_ERROR); - yasl_surface_destroy(surface); - app->surface = NULL; - return; - } - struct yasl_framebuffer target = {.size = frame->target.size, .pixels = frame->target.pixels}; - struct yasl_framebuffer *fb = ⌖ - memcpy(fb->pixels, app->canvas, (size_t)geometry.size.x * geometry.size.y * sizeof(uint32_t)); - double dpi = (double)app->geometry.scale_120 / YASL_SCALE_UNIT; - rect(fb, 0, 0, fb->size.x, (int)lround(HEADER * dpi), PANEL); - char line[128]; - snprintf(line, sizeof(line), "YASL TABLET / %s / %s / %s", backend_name(app->backend), - device_name(app->pointer.device), app->down ? "CONTACT" : "HOVER"); - text(fb, dpi, 16, 12, line, TEXT); - if (app->tablet_supported) - snprintf(line, sizeof(line), "PRESSURE %5u / 65535 %5.1f%%", app->pointer.pen.pressure, - app->pointer.pen.pressure * 100.0 / 65535); - else snprintf(line, sizeof(line), "TABLET PROTOCOL UNAVAILABLE / MOUSE DRAWING"); - text(fb, dpi, 16, 36, line, ACCENT); - int bar_width = (int)lround(220 * dpi); - rect(fb, (int)(16 * dpi), (int)(58 * dpi), bar_width, (int)(6 * dpi), 0xff34455eu); - rect(fb, (int)(16 * dpi), (int)(58 * dpi), - (int)((uint32_t)app->pointer.pen.pressure * (uint64_t)bar_width / 65535), (int)(6 * dpi), ACCENT); - snprintf(line, sizeof(line), "TILT X %+d / Y %+d", app->pointer.pen.tilt_x, app->pointer.pen.tilt_y); - text(fb, dpi, 260, 54, line, TEXT); - text(fb, dpi, 16, 82, "DRAW WITH TIP / FLIP TO ERASE / C CLEAR / ESC QUIT", TEXT); - if (app->visible && app->pointer.pos.x >= 0 && app->pointer.pos.x < fb->size.x && - app->pointer.pos.y >= HEADER * dpi && app->pointer.pos.y < fb->size.y) { - double radius = brush_radius(app); - double cx = app->pointer.pos.x, cy = app->pointer.pos.y; - /* Brush outline and tilt vector are overlays, never stored in canvas. */ - for (int i = 0; i < 180; i++) { - double angle = i * (6.283185307179586 / 180); - rect(fb, (int)lround(cx + cos(angle) * radius), - (int)lround(cy + sin(angle) * radius), 1, 1, PANEL); + int left = (int)floor(x - radius), top = (int)floor(y - radius); + int right = (int)ceil(x + radius), bottom = (int)ceil(y + radius); + if (left < 0) left = 0; + if (top < 0) top = 0; + if (right > w) right = w; + if (bottom > h) bottom = h; + for (int row = top; row < bottom; row++) { + for (int col = left; col < right; col++) { + double dx = col - x, dy = row - y; + if (dx * dx + dy * dy <= radius * radius) pixels[(size_t)row * w + col] = erase ? PAPER : INK; } - double tx = app->pointer.pen.tilt_x * dpi / 2; - double ty = app->pointer.pen.tilt_y * dpi / 2; - for (int i = 0; i <= 64; i++) - rect(fb, (int)lround(cx + tx * i / 64), (int)lround(cy + ty * i / 64), 2, 2, ACCENT); - } - if (!yasl_frame_present(frame, NULL, 0)) { - app->failed = true; - } - if (app->failed) yasl_app_exit(yasl_surface_display(surface), YASL_APP_ERROR); - if (app->failed || app->smoke_test) { - yasl_surface_destroy(surface); - app->surface = NULL; } } -static enum yasl_app_result init(void *ud, struct yasl_display *display) +/* Paints paper over the area a resize added, or everything when clear is set. */ +static uint32_t *canvas(yasl_surface *s, int *w, int *h, int *painted_w, int *painted_h, bool clear) { - struct app *app = ud; - app->backend = yasl_display_backend(display); - app->tablet_supported = (yasl_display_caps(display) & YASL_CAP_TABLET) != 0; - struct yasl_window_spec spec = { - .title = "YASL Tablet - pressure and tilt", .size_logical = {900, 640}, - }; - app->surface = yasl_surface_create_toplevel(display, &spec); - if (!app->surface || !resize_canvas(app, yasl_surface_geometry(app->surface))) { - fprintf(stderr, "Cannot create drawing surface\n"); - app->failed = true; - return YASL_APP_ERROR; - } - yasl_surface_set_cursor(app->surface, YASL_CURSOR_HIDDEN); - yasl_surface_request_frame(app->surface); - printf("YASL tablet on %s: pressure changes brush width; eraser removes ink.\n" - "Mouse: fixed-width drawing. C/Space: clear. Escape: quit.\n", - backend_name(app->backend)); - if (!app->tablet_supported) - printf("This display server does not expose tablet input.\n"); - return YASL_APP_CONTINUE; + uint32_t *pixels = yasl_cpu_pixels(s, w, h); + if (!pixels) return NULL; + if (clear) *painted_w = *painted_h = 0; + for (int y = 0; y < *h; y++) + for (int x = y < *painted_h ? *painted_w : 0; x < *w; x++) pixels[(size_t)y * *w + x] = PAPER; + *painted_w = *w; + *painted_h = *h; + return pixels; } int main(int argc, char **argv) { - struct app app = {0}; - enum yasl_backend backend = YASL_BACKEND_AUTO; + bool smoke = false; for (int i = 1; i < argc; i++) { - if (!strcmp(argv[i], "--smoke-test")) app.smoke_test = true; + if (!strcmp(argv[i], "--smoke-test")) smoke = true; else if (!strcmp(argv[i], "--backend") && i + 1 < argc) { - const char *name = argv[++i]; - if (!strcmp(name, "auto")) backend = YASL_BACKEND_AUTO; - else if (!strcmp(name, "wayland")) backend = YASL_BACKEND_WAYLAND; - else if (!strcmp(name, "x11")) backend = YASL_BACKEND_X11; - else if (!strcmp(name, "cocoa")) backend = YASL_BACKEND_COCOA; - else { fprintf(stderr, "Unknown backend: %s\n", name); return 1; } - } else { - bool help = !strcmp(argv[i], "--help"); - fprintf(help ? stdout : stderr, - "Usage: %s [--backend auto|wayland|x11|cocoa] [--smoke-test]\n", argv[0]); - return help ? 0 : 1; + const char *b = argv[++i]; + if (!yasl_set_backend(!strcmp(b, "wayland") ? YASL_BACKEND_WAYLAND : + !strcmp(b, "x11") ? YASL_BACKEND_X11 : YASL_BACKEND_COCOA)) return 1; + } else return 1; + } + yasl_surface *s = yasl_surface_create("YASL pen canvas — C clears, Escape exits", 800, 600, YASL_SURFACE_CPU); + if (!s) return 1; + int painted_w = 0, painted_h = 0, frames = 0, result = 1; + bool contact = false; + double last_x = 0, last_y = 0; + yasl_event e; + for (;;) { + int n = yasl_wait_event(&e, smoke ? 20 : -1); + if (n < 0 || e.type == YASL_EVENT_DISPLAY_LOST) break; + if (smoke && frames && n == 0) { result = 0; break; } + bool escape = e.type == YASL_EVENT_KEY && e.key.action == YASL_KEY_PRESS && e.key.key == YASL_KEY_ESCAPE; + if (e.type == YASL_EVENT_CLOSE || e.type == YASL_EVENT_QUIT || escape) { result = 0; break; } + bool clear = false, draw = false, erase = false, down = false, present = e.type == YASL_EVENT_REFRESH; + double x = 0, y = 0, p = 0.5; + switch (e.type) { + case YASL_EVENT_KEY: + if (e.key.action == YASL_KEY_PRESS && e.key.key == YASL_KEY_C) clear = present = true; + break; + case YASL_EVENT_PEN: + x = e.pen.x; y = e.pen.y; + p = (e.pen.axes_valid & YASL_PEN_PRESSURE) ? e.pen.pressure : 0.5; + erase = e.pen.tool == YASL_PEN_ERASER; + down = e.pen.in_contact; + draw = down && (e.pen.action == YASL_PEN_DOWN || e.pen.action == YASL_PEN_MOVE); + if (e.pen.action == YASL_PEN_LEAVE || e.pen.action == YASL_PEN_CANCEL) contact = false; + break; + case YASL_EVENT_POINTER: + x = e.pointer.x; y = e.pointer.y; + down = (e.pointer.buttons & YASL_BUTTON_BIT(YASL_BUTTON_PRIMARY)) != 0; + draw = down && (e.pointer.action == YASL_POINTER_DOWN || e.pointer.action == YASL_POINTER_MOVE); + break; + case YASL_EVENT_INPUT_RESET: + contact = false; + break; + default: + break; + } + if (draw || present) { + int w, h; + int old_w = painted_w, old_h = painted_h; + uint32_t *pixels = canvas(s, &w, &h, &painted_w, &painted_h, clear); + if (!pixels) break; + if (w != old_w || h != old_h) contact = false; + if (draw) { + double scale = yasl_surface_get_scale(s); + x *= scale; y *= scale; + double radius = (1.5 + 12 * p) * scale, dx = contact ? x - last_x : 0, dy = contact ? y - last_y : 0; + int steps = (int)ceil(hypot(dx, dy) / (radius * 0.4)); + if (steps < 1) steps = 1; + for (int i = 1; i <= steps; i++) stamp(pixels, w, h, x - dx + dx * i / steps, y - dy + dy * i / steps, radius, erase); + last_x = x; last_y = y; + } + if (!yasl_cpu_present(s)) break; + frames++; } + if (e.type == YASL_EVENT_PEN || e.type == YASL_EVENT_POINTER) contact = down; } - struct yasl_app_desc desc = { - .app_id = "org.yasl.tablet", .backend = backend, .userdata = &app, - .init = init, .event = input, .frame = draw, - }; - int result = yasl_run(&desc); - if (result) perror("yasl_run"); - free(app.canvas); - return result || app.failed ? 1 : 0; + yasl_shutdown(); + return result; } |
