summaryrefslogtreecommitdiff
path: root/examples/tablet.c
diff options
context:
space:
mode:
Diffstat (limited to 'examples/tablet.c')
-rw-r--r--examples/tablet.c419
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 = &target;
- 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;
}