From 76073fe5fad8799e5850805f2f090af2cb62c741 Mon Sep 17 00:00:00 2001 From: Berke Güzel Date: Tue, 13 Oct 2026 23:41:31 +0300 Subject: 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 --- examples/game.c | 403 +++++----------------------------- examples/hello.c | 54 ++--- examples/notepad.c | 552 ++++++++++++++++++++++++++++------------------- examples/simple_window.c | 232 ++++---------------- examples/tablet.c | 419 ++++++++--------------------------- 5 files changed, 537 insertions(+), 1123 deletions(-) (limited to 'examples') diff --git a/examples/game.c b/examples/game.c index 33c2f6e..79beb7c 100644 --- a/examples/game.c +++ b/examples/game.c @@ -1,360 +1,53 @@ -/** - * @file game.c - * A continuous 60/144+ FPS game loop demonstration built on YASL Approach A. - * - * Demonstrates: - * - Continuous display-synchronized ticks (YASL_PACING_CONTINUOUS) - * - 3D software rendering (rotating shaded cube + starfield) - * - WASD continuous player movement & key state tracking - * - Relative pointer mode (YASL_POINTER_RELATIVE) for smooth first-person camera orbit - * - Spacebar toggles pointer lock (Relative vs Normal cursor) - * - Real-time FPS counter and frame latency display - * - Dynamic window resizing with automatic aspect ratio adjustment - */ - -#define _DEFAULT_SOURCE -#include - -#include -#include -#include +/* GPU example: the application owns context binding, drawing and swap pacing. */ +#include +#include #include -#include +#include #include -#define NUM_STARS 256 -#define FOV_SCALE 320.0f - -struct star { - float x, y, z; -}; - -struct vec3 { - float x, y, z; -}; - -struct game_state { - struct yasl_display *display; - struct yasl_surface *surface; - - /* Camera & controls */ - float cam_x, cam_y, cam_z; - float cam_yaw, cam_pitch; - bool key_w, key_a, key_s, key_d; - bool key_q, key_e; - bool relative_mouse; - - /* Simulation objects */ - float cube_rot_x, cube_rot_y; - struct star stars[NUM_STARS]; - - /* FPS & timing stats */ - uint64_t last_stat_time_ns; - int frame_count; - float current_fps; - float avg_frame_time_ms; -}; - -/* 8x8 bitmap font for overlay HUD */ -static const uint8_t font8x8_digits[10][8] = { - {0x3C,0x66,0x6E,0x76,0x66,0x66,0x3C,0x00}, /* 0 */ - {0x18,0x38,0x18,0x18,0x18,0x18,0x7E,0x00}, /* 1 */ - {0x3C,0x66,0x06,0x0C,0x18,0x30,0x7E,0x00}, /* 2 */ - {0x3C,0x66,0x06,0x1C,0x06,0x66,0x3C,0x00}, /* 3 */ - {0x0C,0x1C,0x34,0x64,0x7E,0x04,0x04,0x00}, /* 4 */ - {0x7E,0x60,0x7C,0x06,0x06,0x66,0x3C,0x00}, /* 5 */ - {0x1C,0x30,0x60,0x7C,0x66,0x66,0x3C,0x00}, /* 6 */ - {0x7E,0x06,0x0C,0x18,0x30,0x30,0x30,0x00}, /* 7 */ - {0x3C,0x66,0x66,0x3C,0x66,0x66,0x3C,0x00}, /* 8 */ - {0x3C,0x66,0x66,0x3E,0x06,0x0C,0x38,0x00}, /* 9 */ -}; - -static void draw_digit(uint32_t *pix, int w, int h, int px, int py, int digit, uint32_t col, int s) -{ - if (digit < 0 || digit > 9) return; - const uint8_t *glyph = font8x8_digits[digit]; - for (int y = 0; y < 8; y++) { - uint8_t row = glyph[y]; - for (int x = 0; x < 8; x++) { - if (row & (0x80 >> x)) { - for (int dy = 0; dy < s; dy++) { - int yy = py + y * s + dy; - if (yy < 0 || yy >= h) continue; - for (int dx = 0; dx < s; dx++) { - int xx = px + x * s + dx; - if (xx >= 0 && xx < w) pix[(size_t)yy * w + xx] = col; - } - } - } +typedef void (*clear_color_fn)(float, float, float, float); +typedef void (*clear_fn)(unsigned); +typedef void (*viewport_fn)(int, int, int, int); + +int main(int argc, char **argv) +{ + bool smoke = argc == 2 && !strcmp(argv[1], "--smoke-test"); + yasl_surface *s = yasl_gl_surface_create("YASL OpenGL", 800, 600, 0, NULL, NULL); + if (!s || !yasl_gl_make_current(s)) { + const char *error; + yasl_get_error(&error); + fprintf(stderr, "%s\n", error); + yasl_shutdown(); + return 1; + } + clear_color_fn clear_color = (clear_color_fn)yasl_gl_get_proc_address("glClearColor"); + clear_fn clear = (clear_fn)yasl_gl_get_proc_address("glClear"); + viewport_fn viewport = (viewport_fn)yasl_gl_get_proc_address("glViewport"); + if (!clear_color || !clear || !viewport) { + yasl_shutdown(); + return 1; + } + yasl_gl_swap_interval(s, 1); + int result = 0, frames = 0; + for (bool running = true; running;) { + yasl_event e; + int n; + while ((n = yasl_wait_event(&e, 0)) > 0) { + if (e.type == YASL_EVENT_CLOSE || e.type == YASL_EVENT_QUIT || + (e.type == YASL_EVENT_KEY && e.key.key == YASL_KEY_ESCAPE)) running = false; + if (e.type == YASL_EVENT_DISPLAY_LOST) { result = 1; running = false; } } - } -} - -static void draw_number(uint32_t *pix, int w, int h, int px, int py, int num, uint32_t col, int scale) -{ - char buf[16]; - snprintf(buf, sizeof(buf), "%d", num); - for (int i = 0; buf[i]; i++) { - draw_digit(pix, w, h, px + i * 8 * scale, py, buf[i] - '0', col, scale); - } -} - -static void draw_line(uint32_t *pix, int w, int h, int x0, int y0, int x1, int y1, uint32_t col) -{ - int dx = abs(x1 - x0), sx = x0 < x1 ? 1 : -1; - int dy = -abs(y1 - y0), sy = y0 < y1 ? 1 : -1; - int err = dx + dy, e2; - - while (1) { - if (x0 >= 0 && x0 < w && y0 >= 0 && y0 < h) { - pix[(size_t)y0 * w + x0] = col; - } - if (x0 == x1 && y0 == y1) break; - e2 = 2 * err; - if (e2 >= dy) { err += dy; x0 += sx; } - if (e2 <= dx) { err += dx; y0 += sy; } - } -} - -static struct vec3 rotate_vertex(struct vec3 v, float rx, float ry) -{ - /* Rotate around Y */ - float cy = cosf(ry), sy = sinf(ry); - float x1 = v.x * cy + v.z * sy; - float z1 = -v.x * sy + v.z * cy; - - /* Rotate around X */ - float cx = cosf(rx), sx = sinf(rx); - float y2 = v.y * cx - z1 * sx; - float z2 = v.y * sx + z1 * cx; - - return (struct vec3){x1, y2, z2}; -} - -static bool project_point(struct vec3 p, int w, int h, int *out_x, int *out_y) -{ - if (p.z <= 0.2f) return false; - float inv_z = FOV_SCALE / p.z; - *out_x = (int)(w * 0.5f + p.x * inv_z); - *out_y = (int)(h * 0.5f - p.y * inv_z); - return true; -} - -static enum yasl_app_result on_init(void *ud, struct yasl_display *d) -{ - struct game_state *game = ud; - game->display = d; - - struct yasl_window_spec spec = { - .title = "YASL 3D Game Loop (Approach A)", - .size_logical = {800, 600}, - }; - game->surface = yasl_surface_create_toplevel(d, &spec); - if (!game->surface) return YASL_APP_ERROR; - - /* Drive continuous 60/144Hz ticks */ - if (!yasl_surface_set_pacing(game->surface, YASL_PACING_CONTINUOUS)) - return YASL_APP_ERROR; - - game->cam_z = 3.5f; - game->relative_mouse = false; - - /* Initialize stars */ - for (int i = 0; i < NUM_STARS; i++) { - game->stars[i].x = ((float)(rand() % 2000) - 1000.0f) / 100.0f; - game->stars[i].y = ((float)(rand() % 2000) - 1000.0f) / 100.0f; - game->stars[i].z = ((float)(rand() % 1000) + 10.0f) / 50.0f; - } - - game->last_stat_time_ns = yasl_loop_now_ns(); - return YASL_APP_CONTINUE; -} - -static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) -{ - struct game_state *game = ud; - - switch (ev->type) { - case YASL_EVENT_KEY: { - bool down = ev->key.action != YASL_KEY_RELEASE; - - switch (ev->key.key) { - case YASL_KEY_W: game->key_w = down; break; - case YASL_KEY_S: game->key_s = down; break; - case YASL_KEY_A: game->key_a = down; break; - case YASL_KEY_D: game->key_d = down; break; - case YASL_KEY_Q: game->key_q = down; break; - case YASL_KEY_E: game->key_e = down; break; - case YASL_KEY_SPACE: - if (ev->key.action == YASL_KEY_PRESS) { - /* Toggle relative pointer mode; unsupported backends stay normal. */ - game->relative_mouse = !game->relative_mouse && - yasl_surface_set_pointer_mode(game->surface, YASL_POINTER_RELATIVE); - if (!game->relative_mouse) - yasl_surface_set_pointer_mode(game->surface, YASL_POINTER_NORMAL); - } - break; - case YASL_KEY_ESCAPE: - if (ev->key.action == YASL_KEY_PRESS) { - if (game->relative_mouse) { - game->relative_mouse = false; - yasl_surface_set_pointer_mode(game->surface, YASL_POINTER_NORMAL); - } else { - return YASL_APP_QUIT; - } - } - break; - default: - break; - } - break; - } - - case YASL_EVENT_POINTER_MOTION: - if (game->relative_mouse) { - /* Smooth first-person camera orbit */ - game->cam_yaw += (float)ev->pointer.delta.x * 0.005f; - game->cam_pitch += (float)ev->pointer.delta.y * 0.005f; - if (game->cam_pitch > 1.4f) game->cam_pitch = 1.4f; - if (game->cam_pitch < -1.4f) game->cam_pitch = -1.4f; - } - break; - - case YASL_EVENT_POINTER_DOWN: - if (!game->relative_mouse) { - /* Click window to capture cursor */ - game->relative_mouse = - yasl_surface_set_pointer_mode(game->surface, YASL_POINTER_RELATIVE); - } - break; - - case YASL_EVENT_WINDOW_CLOSE: - return YASL_APP_QUIT; - - default: - break; - } - - return YASL_APP_CONTINUE; -} - -static void on_frame(void *ud, struct yasl_surface *s, struct yasl_frame *ctx) -{ - (void)s; - struct game_state *game = ud; - int w = ctx->target.size.x; - int h = ctx->target.size.y; - uint32_t *pix = ctx->target.pixels; - if (!pix || w <= 0 || h <= 0) return; - - /* Update game simulation */ - float speed = 0.04f; - float forward_x = sinf(game->cam_yaw); - float forward_z = cosf(game->cam_yaw); - float right_x = cosf(game->cam_yaw); - float right_z = -sinf(game->cam_yaw); - - if (game->key_w) { game->cam_x -= forward_x * speed; game->cam_z -= forward_z * speed; } - if (game->key_s) { game->cam_x += forward_x * speed; game->cam_z += forward_z * speed; } - if (game->key_a) { game->cam_x -= right_x * speed; game->cam_z -= right_z * speed; } - if (game->key_d) { game->cam_x += right_x * speed; game->cam_z += right_z * speed; } - if (game->key_q) { game->cam_y -= speed; } - if (game->key_e) { game->cam_y += speed; } - - game->cube_rot_x += 0.015f; - game->cube_rot_y += 0.025f; - - /* Clear screen to deep space dark blue */ - memset(pix, 0x08, (size_t)w * h * sizeof(uint32_t)); - - /* 1. Render 3D Starfield */ - for (int i = 0; i < NUM_STARS; i++) { - game->stars[i].z -= 0.05f; - if (game->stars[i].z <= 0.2f) game->stars[i].z += 20.0f; - - struct vec3 sp = {game->stars[i].x - game->cam_x, - game->stars[i].y - game->cam_y, - game->stars[i].z}; - int sx, sy; - if (project_point(sp, w, h, &sx, &sy) && sx >= 0 && sx < w && sy >= 0 && sy < h) { - uint8_t brightness = (uint8_t)(255.0f * (1.0f - sp.z / 20.0f)); - uint32_t star_col = 0xff000000 | (brightness << 16) | (brightness << 8) | brightness; - pix[(size_t)sy * w + sx] = star_col; - } - } - - /* 2. Render 3D Cube */ - static const struct vec3 cube_vertices[8] = { - {-1.0f, -1.0f, -1.0f}, { 1.0f, -1.0f, -1.0f}, - { 1.0f, 1.0f, -1.0f}, {-1.0f, 1.0f, -1.0f}, - {-1.0f, -1.0f, 1.0f}, { 1.0f, -1.0f, 1.0f}, - { 1.0f, 1.0f, 1.0f}, {-1.0f, 1.0f, 1.0f}, - }; - - static const int cube_edges[12][2] = { - {0, 1}, {1, 2}, {2, 3}, {3, 0}, - {4, 5}, {5, 6}, {6, 7}, {7, 4}, - {0, 4}, {1, 5}, {2, 6}, {3, 7}, - }; - - int proj_x[8], proj_y[8]; - bool valid[8]; - - for (int i = 0; i < 8; i++) { - struct vec3 rot = rotate_vertex(cube_vertices[i], game->cube_rot_x, game->cube_rot_y); - rot.x -= game->cam_x; - rot.y -= game->cam_y; - rot.z += game->cam_z; - valid[i] = project_point(rot, w, h, &proj_x[i], &proj_y[i]); - } - - uint32_t cyan = 0xff00ffff; - for (int i = 0; i < 12; i++) { - int u = cube_edges[i][0]; - int v = cube_edges[i][1]; - if (valid[u] && valid[v]) { - draw_line(pix, w, h, proj_x[u], proj_y[u], proj_x[v], proj_y[v], cyan); - } - } - - /* 3. Center crosshair in relative mouse mode */ - if (game->relative_mouse) { - int cx = w / 2, cy = h / 2; - draw_line(pix, w, h, cx - 6, cy, cx + 6, cy, 0xffffffff); - draw_line(pix, w, h, cx, cy - 6, cx, cy + 6, 0xffffffff); - } - - /* 4. FPS counter calculation */ - game->frame_count++; - uint64_t now = yasl_loop_now_ns(); - if (now - game->last_stat_time_ns >= 1000000000ull) { - uint64_t elapsed = now - game->last_stat_time_ns; - game->current_fps = (float)game->frame_count * 1000000000.0f / (float)elapsed; - game->avg_frame_time_ms = 1000.0f / (game->current_fps > 0 ? game->current_fps : 1.0f); - game->frame_count = 0; - game->last_stat_time_ns = now; - } - - /* Draw FPS overlay digits in top-left */ - int scale = ctx->scale_120 >= 240 ? 2 : 1; - draw_number(pix, w, h, 16 * scale, 16 * scale, (int)game->current_fps, 0xff00ff00, scale); - - /* Submit frame */ - if (!yasl_frame_present(ctx, NULL, 0)) - yasl_app_exit(yasl_surface_display(s), YASL_APP_ERROR); -} - -int main(void) -{ - static struct game_state game; - struct yasl_app_desc desc = { - .app_id = "org.yasl.game", - .backend = YASL_BACKEND_AUTO, - .userdata = &game, - .init = on_init, - .event = on_event, - .frame = on_frame, - }; - - return yasl_run(&desc); + if (n < 0) { result = 1; break; } + int w, h; + yasl_surface_get_framebuffer_size(s, &w, &h); + viewport(0, 0, w, h); + double t = yasl_time_ns() / 1e9; + clear_color((float)(0.3 + 0.2 * sin(t)), 0.2f, 0.4f, 1); + clear(0x00004000u); + if (!yasl_gl_swap_buffers(s)) { result = 1; break; } + if (smoke && ++frames == 3) break; + } + yasl_gl_make_current(NULL); + yasl_shutdown(); + return result; } diff --git a/examples/hello.c b/examples/hello.c index 31ab18b..9e324a6 100644 --- a/examples/hello.c +++ b/examples/hello.c @@ -1,38 +1,26 @@ -/* A complete CPU-rendered window. Build instructions: docs/getting-started.md. */ -#include -#include +/* A CPU-rendered window. Build instructions: docs/getting-started.md. */ +#include +#include -static bool failed; -static void draw(void *ud, struct yasl_surface *surface, struct yasl_frame *frame) +int main(void) { - (void)ud; - int w = frame->target.size.x, h = frame->target.size.y; - for (int y = 0; y < h; y++) { - uint32_t *row = frame->target.pixels + (size_t)y * (frame->target.stride_bytes / 4); - for (int x = 0; x < w; x++) { - /* Red grows rightward, green downward, blue fades; opaque premultiplied ARGB. */ - uint32_t r = (uint32_t)(x * 255 / (w > 1 ? w - 1 : 1)); - uint32_t g = (uint32_t)(y * 255 / (h > 1 ? h - 1 : 1)); - row[x] = 0xff000000 | r << 16 | g << 8 | (255 - r); + yasl_surface *surface = yasl_surface_create("Hello YASL", 640, 480, YASL_SURFACE_CPU); + yasl_event event; + while (surface && yasl_wait_event(&event, -1) >= 0 && + event.type != YASL_EVENT_CLOSE && event.type != YASL_EVENT_QUIT) { + if (event.type != YASL_EVENT_REFRESH) continue; + int w, h; + uint32_t *pixels = yasl_cpu_pixels(surface, &w, &h); + for (int y = 0; pixels && y < h; y++) { + for (int x = 0; x < w; x++) { + /* Red grows rightward, green downward, blue fades; opaque premultiplied ARGB. */ + uint32_t r = (uint32_t)(x * 255 / (w > 1 ? w - 1 : 1)); + uint32_t g = (uint32_t)(y * 255 / (h > 1 ? h - 1 : 1)); + pixels[(size_t)y * w + x] = 0xff000000 | r << 16 | g << 8 | (255 - r); + } } + yasl_cpu_present(surface); } - - if (!yasl_frame_present(frame, NULL, 0)) { - fprintf(stderr, "Could not present the draw target\n"); - failed = true; - } - if (failed) yasl_app_exit(yasl_surface_display(surface), YASL_APP_ERROR); -} - -int main(void) -{ - struct yasl_window_spec window = {.title = "Hello YASL", .size_logical = {640, 480}}; - struct yasl_app_desc desc = { - .app_id = "org.example.hello", - .window = &window, - .frame = draw, - }; - int result = yasl_run(&desc); - if (result) fprintf(stderr, "Could not run the application\n"); - return result || failed ? 1 : 0; + yasl_shutdown(); + return surface ? 0 : 1; } diff --git a/examples/notepad.c b/examples/notepad.c index 0b331e8..92b9d44 100644 --- a/examples/notepad.c +++ b/examples/notepad.c @@ -1,6 +1,6 @@ /** * @file notepad.c - * A full-featured desktop text editor built on YASL Approach A. + * A desktop text editor built on YASL's public CPU surface API. * * Features: * - New (Ctrl+N), Open (Ctrl+O, CLI arg, drag-and-drop), Save (Ctrl+S), Save As (Ctrl+Shift+S) @@ -14,14 +14,15 @@ * - Zoom In / Out / Reset (Ctrl+=, Ctrl+-, Ctrl+0) * - Mouse selection with click, drag, double-click (word), triple-click (line) * - Unsaved changes confirmation on exit - * - Real-time asynchronous background stats via yasl_display_post() - * - 0% CPU consumption when idle (YASL_PACING_ON_DEMAND) + * - Real-time asynchronous background stats via a worker and yasl_wake() */ #define _DEFAULT_SOURCE -#include +#include +#include #include +#include #include #include #include @@ -134,8 +135,6 @@ static const uint8_t font8x8[95][8] = { {0x76,0xDC,0x00,0x00,0x00,0x00,0x00,0x00} /* ~ */ }; -#define MSG_STATS_READY 100 - typedef enum prompt_mode { PROMPT_NONE = 0, PROMPT_OPEN, @@ -160,8 +159,15 @@ struct undo_snapshot { }; struct editor { - struct yasl_display *display; - struct yasl_surface *surface; + yasl_surface *surface; + double surface_scale; + bool dirty, running, failed; + uint64_t text_revision; + char ime_snapshot[MAX_LINE_LEN]; + size_t ime_begin, ime_end; + bool ime_snapshot_ready; + double ime_caret_x, ime_caret_y; + bool ime_caret_ready; /* File tracking */ char file_path[512]; @@ -182,7 +188,7 @@ struct editor { /* Double/Triple click tracking */ uint64_t last_click_time; - struct yasl_vector2i last_click_pos; + double last_click_x, last_click_y; int click_count; /* Viewport & Layout */ @@ -213,8 +219,10 @@ struct editor { struct doc_stats stats; _Atomic bool worker_running; bool worker_started, worker_mutex_ready, worker_cond_ready; - uint64_t worker_surface_id; bool worker_dirty; + char (*worker_lines)[MAX_LINE_LEN]; + int worker_line_count; + uint64_t stats_generation; pthread_t worker_tid; pthread_mutex_t worker_mutex; pthread_cond_t worker_cond; @@ -223,7 +231,13 @@ struct editor { static void editor_set_status(struct editor *ed, const char *msg) { snprintf(ed->status_msg, sizeof(ed->status_msg), "%s", msg); - ed->status_msg_time = yasl_loop_now_ns(); + ed->status_msg_time = yasl_time_ns(); + ed->dirty = true; +} + +static void editor_request_render(struct editor *ed) +{ + if (ed) ed->dirty = true; } static void editor_update_title(struct editor *ed) @@ -316,7 +330,13 @@ static void editor_redo(struct editor *ed) static void editor_notify_worker(struct editor *ed) { + char (*snapshot)[MAX_LINE_LEN] = malloc(sizeof(ed->lines)); + if (!snapshot) return; + memcpy(snapshot, ed->lines, sizeof(ed->lines)); pthread_mutex_lock(&ed->worker_mutex); + free(ed->worker_lines); + ed->worker_lines = snapshot; + ed->worker_line_count = ed->line_count; ed->worker_dirty = true; pthread_cond_signal(&ed->worker_cond); pthread_mutex_unlock(&ed->worker_mutex); @@ -334,15 +354,17 @@ static void *worker_thread(void *arg) pthread_mutex_unlock(&ed->worker_mutex); break; } + char (*lines)[MAX_LINE_LEN] = ed->worker_lines; + int line_count = ed->worker_line_count; + ed->worker_lines = NULL; ed->worker_dirty = false; pthread_mutex_unlock(&ed->worker_mutex); - - usleep(30000); + if (!lines) continue; int word_count = 0; int char_count = 0; - for (int i = 0; i < ed->line_count; i++) { - const char *p = ed->lines[i]; + for (int i = 0; i < line_count; i++) { + const char *p = lines[i]; bool in_word = false; while (*p) { char_count++; @@ -356,31 +378,19 @@ static void *worker_thread(void *arg) } } + free(lines); if (!ed->worker_running) break; - - struct doc_stats *s = malloc(sizeof(*s)); - if (s) { - s->words = word_count; - s->chars = char_count; - if (!yasl_display_post(ed->display, &(struct yasl_message){ - .target_id = ed->worker_surface_id, .code = MSG_STATS_READY, - .payload = s, .size = sizeof(*s), .release = free})) - free(s); - } + pthread_mutex_lock(&ed->worker_mutex); + ed->stats.words = word_count; + ed->stats.chars = char_count; + ed->stats_generation++; + pthread_mutex_unlock(&ed->worker_mutex); + yasl_wake(); } return NULL; } -static uint64_t on_blink_timer(void *ud, yasl_timer_id id, uint64_t interval_ns) -{ - (void)id; - struct editor *ed = ud; - ed->caret_visible = !ed->caret_visible; - yasl_surface_request_frame(ed->surface); - return interval_ns; -} - /* File I/O */ static bool editor_open_file(struct editor *ed, const char *path) { @@ -472,27 +482,6 @@ static void editor_new_file(struct editor *ed) editor_notify_worker(ed); } -static void decode_uri_path(const char *uri, char *out, size_t max_out) -{ - if (strncmp(uri, "file://", 7) == 0) { - uri += 7; - if (strncmp(uri, "localhost/", 10) == 0) { - uri += 9; - } - } - size_t out_len = 0; - while (*uri && *uri != '\r' && *uri != '\n' && out_len < max_out - 1) { - if (*uri == '%' && isxdigit((unsigned char)uri[1]) && isxdigit((unsigned char)uri[2])) { - char hex[3] = {uri[1], uri[2], '\0'}; - out[out_len++] = (char)strtol(hex, NULL, 16); - uri += 3; - } else { - out[out_len++] = *uri++; - } - } - out[out_len] = '\0'; -} - /* Selection helper */ static void get_selection_bounds(const struct editor *ed, int *r0, int *c0, int *r1, int *c1) { @@ -505,7 +494,8 @@ static void get_selection_bounds(const struct editor *ed, int *r0, int *c0, int (ed->sel_anchor_row == ed->cursor_row && ed->sel_anchor_col <= ed->cursor_col)) { *r0 = ed->sel_anchor_row; *c0 = ed->sel_anchor_col; *r1 = ed->cursor_row; *c1 = ed->cursor_col; - } else { + } + else { *r0 = ed->cursor_row; *c0 = ed->cursor_col; *r1 = ed->sel_anchor_row; *c1 = ed->sel_anchor_col; } @@ -535,7 +525,8 @@ static void editor_delete_selection(struct editor *ed) char *line = ed->lines[r0]; int len = (int)strlen(line); memmove(line + c0, line + c1, (size_t)(len - c1 + 1)); - } else { + } + else { char *first = ed->lines[r0]; char *last = ed->lines[r1]; char temp[MAX_LINE_LEN]; @@ -585,7 +576,7 @@ static void editor_copy(struct editor *ed) snprintf(buf, (size_t)MAX_LINES * MAX_LINE_LEN, "%s\n", ed->lines[ed->cursor_row]); } - if (!yasl_clipboard_set(ed->display, YASL_CLIPBOARD, buf)) + if (!yasl_clipboard_set(YASL_CLIPBOARD, buf)) editor_set_status(ed, "Clipboard copy failed"); free(buf); editor_set_status(ed, "Copied to clipboard"); @@ -995,30 +986,19 @@ static void prompt_execute(struct editor *ed) } } -/* YASL App callbacks */ -static enum yasl_app_result on_init(void *ud, struct yasl_display *d) +static bool editor_init(struct editor *ed) { - struct editor *ed = ud; - ed->display = d; - - struct yasl_window_spec spec = { - .title = "Untitled - YASL Notepad", - .size_logical = {960, 640}, - }; - ed->surface = yasl_surface_create_toplevel(d, &spec); - if (!ed->surface) return YASL_APP_ERROR; - ed->worker_surface_id = yasl_surface_id(ed->surface); - - yasl_surface_set_pacing(ed->surface, YASL_PACING_ON_DEMAND); - if (!yasl_surface_add_timer(ed->surface, YASL_MS_TO_NS(500), on_blink_timer, ed)) - return YASL_APP_ERROR; + ed->surface = yasl_surface_create("Untitled - YASL Notepad", 960, 640, YASL_SURFACE_CPU); + if (!ed->surface) return false; + ed->surface_scale = yasl_surface_get_scale(ed->surface); + yasl_surface_set_text_input(ed->surface, true, YASL_TEXT_NORMAL); ed->worker_running = true; - if (pthread_mutex_init(&ed->worker_mutex, NULL)) return YASL_APP_ERROR; + if (pthread_mutex_init(&ed->worker_mutex, NULL)) return false; ed->worker_mutex_ready = true; - if (pthread_cond_init(&ed->worker_cond, NULL)) return YASL_APP_ERROR; + if (pthread_cond_init(&ed->worker_cond, NULL)) return false; ed->worker_cond_ready = true; - if (pthread_create(&ed->worker_tid, NULL, worker_thread, ed)) return YASL_APP_ERROR; + if (pthread_create(&ed->worker_tid, NULL, worker_thread, ed)) return false; ed->worker_started = true; if (ed->has_file) { @@ -1028,49 +1008,104 @@ static enum yasl_app_result on_init(void *ud, struct yasl_display *d) } editor_notify_worker(ed); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + ed->running = true; + return true; } -static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) +static void editor_update_ime(struct editor *ed) { - struct editor *ed = ud; + if (!ed->surface || ed->line_count <= 0) return; + const char *line = ed->lines[ed->cursor_row]; + size_t length = strlen(line); + size_t begin = (size_t)ed->cursor_col, end = begin; + if (ed->has_selection && ed->sel_anchor_row == ed->cursor_row) { + begin = (size_t)(ed->sel_anchor_col < ed->cursor_col ? ed->sel_anchor_col : ed->cursor_col); + end = (size_t)(ed->sel_anchor_col > ed->cursor_col ? ed->sel_anchor_col : ed->cursor_col); + } + if (begin > length) begin = length; + if (end > length) end = length; + bool snapshot_changed = !ed->ime_snapshot_ready || strcmp(ed->ime_snapshot, line) || + begin != ed->ime_begin || end != ed->ime_end; + if (snapshot_changed) { + snprintf(ed->ime_snapshot, sizeof(ed->ime_snapshot), "%s", line); + ed->ime_begin = begin; + ed->ime_end = end; + ed->ime_snapshot_ready = true; + ed->text_revision++; + } + if (snapshot_changed) + yasl_surface_set_surrounding_text(ed->surface, ed->ime_snapshot, strlen(ed->ime_snapshot), + ed->ime_begin, ed->ime_end, ed->text_revision); + double scale = yasl_surface_get_scale(ed->surface); + int zoom = (scale >= 2.0 ? 2 : 1) + ed->zoom_offset; + if (zoom < 1) zoom = 1; + double caret_x = (48.0 * zoom + 8.0 * zoom + ed->cursor_col * 8.0 * zoom) / scale; + double caret_y = (24.0 * zoom + (ed->cursor_row - ed->scroll_y) * 16.0 * zoom) / scale; + if (!ed->ime_caret_ready || caret_x != ed->ime_caret_x || caret_y != ed->ime_caret_y) { + ed->ime_caret_x = caret_x; + ed->ime_caret_y = caret_y; + ed->ime_caret_ready = true; + yasl_surface_set_ime_caret(ed->surface, caret_x, caret_y, 2.0, 16.0); + } +} +static void on_event(struct editor *ed, const yasl_event *ev) +{ switch (ev->type) { case YASL_EVENT_TEXT: if (ed->prompt != PROMPT_NONE) { - for (size_t i = 0; i < ev->text.len && ed->prompt_len < (int)sizeof(ed->prompt_buf) - 1; i++) { + for (size_t i = 0; i < ev->text.length && ed->prompt_len < (int)sizeof(ed->prompt_buf) - 1; i++) { if (ev->text.data[i] >= 32 && ev->text.data[i] <= 126) { ed->prompt_buf[ed->prompt_len++] = ev->text.data[i]; ed->prompt_buf[ed->prompt_len] = '\0'; } } } else { - insert_text(ed, ev->text.data, ev->text.len); + insert_text(ed, ev->text.data, ev->text.length); } - yasl_surface_request_frame(ed->surface); + editor_request_render(ed); break; - case YASL_EVENT_PASTE: - if (ev->paste.status != YASL_TRANSFER_OK) { - editor_set_status(ed, ev->paste.status == YASL_TRANSFER_UNAVAILABLE + case YASL_EVENT_CLIPBOARD: + if (ev->clipboard.status != YASL_TRANSFER_OK) { + editor_set_status(ed, ev->clipboard.status == YASL_TRANSFER_UNAVAILABLE ? "Clipboard has no text" : "Clipboard transfer failed"); - yasl_surface_request_frame(ed->surface); + editor_request_render(ed); break; } - if (ev->text.data && ev->text.len > 0) { - insert_text(ed, ev->text.data, ev->text.len); - yasl_surface_request_frame(ed->surface); + if (ev->clipboard.data && ev->clipboard.length > 0) { + insert_text(ed, ev->clipboard.data, ev->clipboard.length); + editor_request_render(ed); + } + break; + + case YASL_EVENT_TEXT_EDIT: + if (ed->prompt == PROMPT_NONE && ev->text_edit.data && + ev->text_edit.revision == ed->text_revision && + ev->text_edit.begin <= ev->text_edit.end && + ev->text_edit.end <= strlen(ed->ime_snapshot)) { + ed->cursor_col = (int)ev->text_edit.begin; + ed->sel_anchor_row = ed->cursor_row; + ed->sel_anchor_col = (int)ev->text_edit.begin; + ed->cursor_col = (int)ev->text_edit.end; + ed->has_selection = ev->text_edit.begin != ev->text_edit.end; + insert_text(ed, ev->text_edit.data, ev->text_edit.length); + editor_request_render(ed); } break; case YASL_EVENT_DROP: - if (ev->text.data && ev->text.len > 0) { + if (ev->drop.action == YASL_DROP_DATA) { + const char *cursor = ev->drop.data; char path[512]; - decode_uri_path(ev->text.data, path, sizeof(path)); - if (path[0]) { - editor_open_file(ed, path); - yasl_surface_request_frame(ed->surface); + size_t length; + while ((length = yasl_drop_next_path(&cursor, path, sizeof(path))) > 0) { + if (length < sizeof(path)) { + editor_open_file(ed, path); + editor_request_render(ed); + break; + } } } break; @@ -1089,30 +1124,30 @@ static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) } else if (ev->key.key == YASL_KEY_BACKSPACE && ed->prompt_len > 0) { ed->prompt_buf[--ed->prompt_len] = '\0'; } - yasl_surface_request_frame(ed->surface); + editor_request_render(ed); break; } /* Function Keys */ if (ev->key.key == YASL_KEY_F3) { editor_find_next(ed, ed->search_query, !shift); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; } else if (ev->key.key == YASL_KEY_F5) { insert_date_time(ed); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; } /* Shortcuts */ if (ctrl) { switch (ev->key.key) { case YASL_KEY_Q: - return YASL_APP_QUIT; + ed->running = false; return; case YASL_KEY_N: editor_new_file(ed); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_S: if (shift || !ed->has_file) { ed->prompt = PROMPT_SAVE; @@ -1121,89 +1156,89 @@ static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) } else { editor_save_file(ed, ed->file_path); } - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_O: ed->prompt = PROMPT_OPEN; ed->prompt_len = 0; ed->prompt_buf[0] = '\0'; - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_F: ed->prompt = PROMPT_FIND; ed->prompt_len = 0; ed->prompt_buf[0] = '\0'; - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_H: ed->prompt = PROMPT_REPLACE_FIND; ed->prompt_len = 0; ed->prompt_buf[0] = '\0'; - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_G: ed->prompt = PROMPT_GOTO; ed->prompt_len = 0; ed->prompt_buf[0] = '\0'; - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_W: ed->word_wrap = !ed->word_wrap; editor_set_status(ed, ed->word_wrap ? "Word Wrap: ON" : "Word Wrap: OFF"); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_EQUAL: /* Zoom In */ if (ed->zoom_offset < 2) ed->zoom_offset++; editor_set_status(ed, "Zoom In"); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_MINUS: /* Zoom Out */ if (ed->zoom_offset > -1) ed->zoom_offset--; editor_set_status(ed, "Zoom Out"); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_0: /* Zoom Reset */ ed->zoom_offset = 0; editor_set_status(ed, "Zoom Reset"); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_A: ed->sel_anchor_row = 0; ed->sel_anchor_col = 0; ed->cursor_row = ed->line_count - 1; ed->cursor_col = (int)strlen(ed->lines[ed->line_count - 1]); ed->has_selection = true; - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_C: editor_copy(ed); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_X: editor_cut(ed); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_V: if (!yasl_clipboard_request(ed->surface, YASL_CLIPBOARD)) editor_set_status(ed, "Clipboard paste failed"); - return YASL_APP_CONTINUE; + return; case YASL_KEY_Z: if (shift) editor_redo(ed); else editor_undo(ed); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_Y: editor_redo(ed); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_BACKSPACE: handle_backspace(ed, true); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; case YASL_KEY_DELETE: handle_delete(ed, true); - yasl_surface_request_frame(ed->surface); - return YASL_APP_CONTINUE; + editor_request_render(ed); + return; default: break; } @@ -1212,7 +1247,7 @@ static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) /* Tab key */ if (ev->key.key == YASL_KEY_TAB) { handle_tab(ed, shift); - yasl_surface_request_frame(ed->surface); + editor_request_render(ed); break; } @@ -1301,38 +1336,40 @@ static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) break; } ed->caret_visible = true; - yasl_surface_request_frame(ed->surface); + editor_request_render(ed); } break; - case YASL_EVENT_POINTER_DOWN: { - if (ev->pointer.button == YASL_BUTTON_PRIMARY) { - struct yasl_geometry geom = yasl_surface_geometry(ed->surface); - int scale = (geom.scale_120 >= 240 ? 2 : 1) + ed->zoom_offset; + case YASL_EVENT_POINTER: { + double device_scale = yasl_surface_get_scale(ed->surface); + int px = (int)lround(ev->pointer.x * device_scale); + int py = (int)lround(ev->pointer.y * device_scale); + if (ev->pointer.action == YASL_POINTER_DOWN && ev->pointer.button == YASL_BUTTON_PRIMARY) { + int scale = (device_scale >= 2.0 ? 2 : 1) + ed->zoom_offset; if (scale < 1) scale = 1; int char_w = 8 * scale; int line_h = 16 * scale; int gutter_w = 48 * scale; int header_h = 24 * scale; - int row = (ev->pointer.pos.y - header_h) / line_h + ed->scroll_y; - int col = (ev->pointer.pos.x - gutter_w) / char_w + (ed->word_wrap ? 0 : ed->scroll_x); + int row = (py - header_h) / line_h + ed->scroll_y; + int col = (px - gutter_w) / char_w + (ed->word_wrap ? 0 : ed->scroll_x); if (row < 0) row = 0; if (row >= ed->line_count) row = ed->line_count - 1; int len = (int)strlen(ed->lines[row]); if (col < 0) col = 0; if (col > len) col = len; - uint64_t now = yasl_loop_now_ns(); + uint64_t now = yasl_time_ns(); if (now - ed->last_click_time < 350000000ull && - abs(ev->pointer.pos.x - ed->last_click_pos.x) < 8 && - abs(ev->pointer.pos.y - ed->last_click_pos.y) < 8) { + abs(px - (int)ed->last_click_x) < 8 && abs(py - (int)ed->last_click_y) < 8) { ed->click_count++; } else { ed->click_count = 1; } ed->last_click_time = now; - ed->last_click_pos = ev->pointer.pos; + ed->last_click_x = px; + ed->last_click_y = py; if (ed->click_count == 2) { /* Double click: select word */ @@ -1357,29 +1394,21 @@ static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) ed->mouse_dragging = true; } ed->caret_visible = true; - yasl_surface_request_frame(ed->surface); - } - break; - } - - case YASL_EVENT_POINTER_UP: - if (ev->pointer.button == YASL_BUTTON_PRIMARY) { + editor_request_render(ed); + } else if (ev->pointer.action == YASL_POINTER_UP && ev->pointer.button == YASL_BUTTON_PRIMARY) { ed->mouse_dragging = false; - } - break; - - case YASL_EVENT_POINTER_MOTION: - if (ed->mouse_dragging) { - struct yasl_geometry geom = yasl_surface_geometry(ed->surface); - int scale = (geom.scale_120 >= 240 ? 2 : 1) + ed->zoom_offset; + } else if (ev->pointer.action == YASL_POINTER_LEAVE) { + ed->mouse_dragging = false; + } else if (ev->pointer.action == YASL_POINTER_MOVE && ed->mouse_dragging) { + int scale = (device_scale >= 2.0 ? 2 : 1) + ed->zoom_offset; if (scale < 1) scale = 1; int char_w = 8 * scale; int line_h = 16 * scale; int gutter_w = 48 * scale; int header_h = 24 * scale; - int row = (ev->pointer.pos.y - header_h) / line_h + ed->scroll_y; - int col = (ev->pointer.pos.x - gutter_w) / char_w + (ed->word_wrap ? 0 : ed->scroll_x); + int row = (py - header_h) / line_h + ed->scroll_y; + int col = (px - gutter_w) / char_w + (ed->word_wrap ? 0 : ed->scroll_x); if (row < 0) row = 0; if (row >= ed->line_count) row = ed->line_count - 1; int len = (int)strlen(ed->lines[row]); @@ -1389,56 +1418,81 @@ static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) ed->cursor_row = row; ed->cursor_col = col; ed->has_selection = (ed->cursor_row != ed->sel_anchor_row || ed->cursor_col != ed->sel_anchor_col); - yasl_surface_request_frame(ed->surface); + editor_request_render(ed); + } + break; + } + + case YASL_EVENT_PEN: { + yasl_event pointer = {0}; + pointer.type = YASL_EVENT_POINTER; + pointer.surface = ev->surface; + pointer.pointer.x = ev->pen.x; + pointer.pointer.y = ev->pen.y; + pointer.pointer.button = YASL_BUTTON_PRIMARY; + pointer.pointer.mods = 0; + switch (ev->pen.action) { + case YASL_PEN_DOWN: pointer.pointer.action = YASL_POINTER_DOWN; break; + case YASL_PEN_UP: pointer.pointer.action = YASL_POINTER_UP; break; + case YASL_PEN_LEAVE: + case YASL_PEN_CANCEL: pointer.pointer.action = YASL_POINTER_LEAVE; break; + default: pointer.pointer.action = YASL_POINTER_MOVE; break; } + on_event(ed, &pointer); break; + } - case YASL_EVENT_POINTER_SCROLL: - if (ev->pointer.mods & (YASL_MOD_CTRL | YASL_MOD_SUPER)) { + case YASL_EVENT_SCROLL: + if (ev->scroll.mods & (YASL_MOD_CTRL | YASL_MOD_SUPER)) { /* Ctrl + Wheel = Zoom */ - if (ev->pointer.scroll.y > 0 && ed->zoom_offset < 2) ed->zoom_offset++; - else if (ev->pointer.scroll.y < 0 && ed->zoom_offset > -1) ed->zoom_offset--; - } else if (ev->pointer.mods & YASL_MOD_SHIFT) { + if (ev->scroll.lines_y > 0 && ed->zoom_offset < 2) ed->zoom_offset++; + else if (ev->scroll.lines_y < 0 && ed->zoom_offset > -1) ed->zoom_offset--; + } else if (ev->scroll.mods & YASL_MOD_SHIFT) { /* Shift + Wheel = Horizontal scroll */ - ed->scroll_x -= (int)(ev->pointer.scroll.y * 3.0); + ed->scroll_x -= (int)(ev->scroll.lines_y * 3.0); if (ed->scroll_x < 0) ed->scroll_x = 0; } else { /* Normal vertical scroll */ - ed->scroll_y -= (int)(ev->pointer.scroll.y * 3.0); + ed->scroll_y -= (int)(ev->scroll.lines_y * 3.0); if (ed->scroll_y < 0) ed->scroll_y = 0; if (ed->scroll_y >= ed->line_count) ed->scroll_y = ed->line_count - 1; } - yasl_surface_request_frame(ed->surface); + editor_request_render(ed); break; - case YASL_EVENT_USER: - if (ev->user.code == MSG_STATS_READY && ev->user.payload) { - struct doc_stats *s = (struct doc_stats *)ev->user.payload; - ed->stats = *s; - yasl_surface_request_frame(ed->surface); - } - break; + case YASL_EVENT_CLOSE: + ed->running = false; return; + case YASL_EVENT_QUIT: + ed->running = false; return; + case YASL_EVENT_DISPLAY_LOST: + ed->failed = true; + ed->running = false; + return; - case YASL_EVENT_WINDOW_CLOSE: - return YASL_APP_QUIT; + case YASL_EVENT_WINDOW: + ed->surface_scale = ev->window.scale; + if (ev->window.state & YASL_STATE_ACTIVATED) ed->caret_visible = true; + editor_request_render(ed); + break; + case YASL_EVENT_REFRESH: + editor_request_render(ed); + break; default: break; } - return YASL_APP_CONTINUE; + return; } -static void on_frame(void *ud, struct yasl_surface *s, struct yasl_frame *ctx) +static void editor_render(struct editor *ed) { - (void)s; - struct editor *ed = ud; - int w = ctx->target.size.x; - int h = ctx->target.size.y; - uint32_t *pix = ctx->target.pixels; - if (!pix || w <= 0 || h <= 0) return; - - int scale = (ctx->scale_120 >= 240 ? 2 : 1) + ed->zoom_offset; + int w, h; + uint32_t *pix = yasl_cpu_pixels(ed->surface, &w, &h); + if (!pix) return; + + double display_scale = yasl_surface_get_scale(ed->surface); + int scale = (display_scale >= 2.0 ? 2 : 1) + ed->zoom_offset; if (scale < 1) scale = 1; int char_w = 8 * scale; int line_h = 16 * scale; @@ -1554,11 +1608,14 @@ static void on_frame(void *ud, struct yasl_surface *s, struct yasl_frame *ctx) fill_rect(pix, w, h, 0, status_y, w, status_h, bg_status); char status_left[160]; - if (ed->status_msg[0] && (yasl_loop_now_ns() - ed->status_msg_time < 3000000000ull)) { + if (ed->status_msg[0] && (yasl_time_ns() - ed->status_msg_time < 3000000000ull)) { snprintf(status_left, sizeof(status_left), "%s", ed->status_msg); } else { + pthread_mutex_lock(&ed->worker_mutex); + struct doc_stats stats = ed->stats; + pthread_mutex_unlock(&ed->worker_mutex); snprintf(status_left, sizeof(status_left), "Ln %d, Col %d Lines: %d Words: %d Chars: %d", - ed->cursor_row + 1, ed->cursor_col + 1, ed->line_count, ed->stats.words, ed->stats.chars); + ed->cursor_row + 1, ed->cursor_col + 1, ed->line_count, stats.words, stats.chars); } draw_string(pix, w, h, 12 * scale, status_y + 6 * scale, status_left, text_status, scale); @@ -1571,14 +1628,17 @@ static void on_frame(void *ud, struct yasl_surface *s, struct yasl_frame *ctx) ed->is_modified ? 0xfffab387 : status_accent, scale); } - if (!yasl_frame_present(ctx, NULL, 0)) - yasl_app_exit(yasl_surface_display(s), YASL_APP_ERROR); + if (!yasl_cpu_present(ed->surface)) { + fprintf(stderr, "Could not present CPU image\n"); + ed->failed = true; + ed->running = false; + return; + } + ed->dirty = false; } -static void on_quit(void *ud, struct yasl_display *d) +static void editor_shutdown(struct editor *ed) { - (void)d; - struct editor *ed = ud; ed->worker_running = false; if (ed->worker_started) { pthread_mutex_lock(&ed->worker_mutex); @@ -1586,6 +1646,7 @@ static void on_quit(void *ud, struct yasl_display *d) pthread_mutex_unlock(&ed->worker_mutex); pthread_join(ed->worker_tid, NULL); } + free(ed->worker_lines); if (ed->worker_cond_ready) pthread_cond_destroy(&ed->worker_cond); if (ed->worker_mutex_ready) pthread_mutex_destroy(&ed->worker_mutex); } @@ -1593,15 +1654,19 @@ static void on_quit(void *ud, struct yasl_display *d) int main(int argc, char **argv) { static struct editor ed; + bool smoke_test = false; - /* Check command-line file argument */ - if (argc > 1) { - snprintf(ed.file_path, sizeof(ed.file_path), "%s", argv[1]); - ed.has_file = true; - } else { + for (int i = 1; i < argc; i++) { + if (!strcmp(argv[i], "--smoke-test")) smoke_test = true; + else if (!ed.has_file) { + snprintf(ed.file_path, sizeof(ed.file_path), "%s", argv[i]); + ed.has_file = true; + } + } + if (!ed.has_file) { const char *initial_content[] = { "// Welcome to YASL Notepad!", - "// A full-featured desktop text editor built on YASL Approach A.", + "// A full-featured desktop text editor built with YASL.", "", "#include ", "", @@ -1636,15 +1701,68 @@ int main(int argc, char **argv) } } - struct yasl_app_desc desc = { - .app_id = "org.yasl.notepad", - .backend = YASL_BACKEND_AUTO, - .userdata = &ed, - .init = on_init, - .event = on_event, - .frame = on_frame, - .quit = on_quit, - }; - - return yasl_run(&desc); + if (!yasl_set_app_id("org.yasl.notepad") || !editor_init(&ed)) { + fprintf(stderr, "Could not initialize YASL Notepad\n"); + editor_shutdown(&ed); + yasl_shutdown(); + return 1; + } + + editor_update_ime(&ed); + editor_render(&ed); + if (smoke_test) { + editor_shutdown(&ed); + yasl_surface_destroy(ed.surface); + yasl_shutdown(); + return ed.failed ? 1 : 0; + } + + uint64_t next_blink = yasl_time_ns() + 500000000ull; + uint64_t seen_stats = 0; + int exit_status = 0; + while (ed.running) { + uint64_t now = yasl_time_ns(); + int timeout_ms = next_blink > now ? (int)((next_blink - now + 999999ull) / 1000000ull) : 0; + yasl_event ev; + int result = yasl_wait_event(&ev, timeout_ms); + if (result > 0) { + on_event(&ed, &ev); + } else if (result < 0) { + fprintf(stderr, "YASL event wait failed\n"); + exit_status = 1; + break; + } + int queued = 0; + while (ed.running && (queued = yasl_wait_event(&ev, 0)) > 0) + on_event(&ed, &ev); + if (queued < 0) { + fprintf(stderr, "YASL event poll failed\n"); + exit_status = 1; + break; + } + + now = yasl_time_ns(); + if (now >= next_blink) { + ed.caret_visible = !ed.caret_visible; + ed.dirty = true; + next_blink = now + 500000000ull; + } + pthread_mutex_lock(&ed.worker_mutex); + uint64_t stats_generation = ed.stats_generation; + pthread_mutex_unlock(&ed.worker_mutex); + if (stats_generation != seen_stats) { + seen_stats = stats_generation; + ed.dirty = true; + } + editor_update_ime(&ed); + if (ed.dirty) { + editor_render(&ed); + editor_update_ime(&ed); + } + } + + editor_shutdown(&ed); + yasl_surface_destroy(ed.surface); + yasl_shutdown(); + return exit_status || ed.failed ? 1 : 0; } diff --git a/examples/simple_window.c b/examples/simple_window.c index 5a8b314..114bfbf 100644 --- a/examples/simple_window.c +++ b/examples/simple_window.c @@ -1,198 +1,50 @@ -#include -#include +/* Mouse and pen input on a CPU surface. */ +#include +#include #include -static bool failed, smoke_test; -static int mouse_x = -1; -static int mouse_y = -1; -static bool mouse_down = false; -static int color_theme = 0; -static uint16_t pen_pressure = 0; -static int8_t pen_tilt_x = 0; -static int8_t pen_tilt_y = 0; -static enum yasl_pointer_device pointer_device = YASL_PTR_DEV_MOUSE; - -static const char *device_name(enum yasl_pointer_device dev) -{ - switch (dev) { - case YASL_PTR_DEV_PEN: return "Pen/Stylus"; - case YASL_PTR_DEV_ERASER: return "Eraser"; - case YASL_PTR_DEV_TOUCH: return "Touch"; - default: return "Mouse"; - } -} - -static void on_draw(void *ud, struct yasl_surface *s, struct yasl_frame *frame) +int main(int argc, char **argv) { - (void)ud; - int w = frame->target.size.x; - int h = frame->target.size.y; - uint32_t *pixels = frame->target.pixels; - if (!pixels || w <= 0 || h <= 0) - return; - - for (int y = 0; y < h; y++) { - for (int x = 0; x < w; x++) { - uint8_t r, g, b; - uint8_t u = (uint8_t)((x * 255) / (w > 0 ? w : 1)); - uint8_t v = (uint8_t)((y * 255) / (h > 0 ? h : 1)); - - switch (color_theme % 3) { - case 0: - r = u; - g = v; - b = 160; - break; - case 1: - r = 40; - g = u; - b = v; - break; - default: - r = v; - g = 60; - b = u; - break; - } - - /* Draw interactive pointer / stylus indicator */ - if (mouse_x >= 0 && mouse_y >= 0) { - int dx = x - mouse_x; - int dy = y - mouse_y; - int dist_sq = dx * dx + dy * dy; - - /* Pressure-sensitive radius: scales dynamically from 8px to 64px */ - int radius; - if (pen_pressure > 0) { - radius = 8 + (int)(((uint32_t)pen_pressure * 56) / 65535); - } else { - radius = mouse_down ? 36 : 18; - } - - if (dist_sq < radius * radius) { - r = (uint8_t)(255 - r / 2); - g = (uint8_t)(255 - g / 2); - b = (uint8_t)(255 - b / 2); + bool smoke = argc == 2 && !strcmp(argv[1], "--smoke-test"); + yasl_surface *s = yasl_surface_create("YASL input", 640, 480, YASL_SURFACE_CPU); + if (!s) return 1; + double px = -100, py = -100, pressure = 0; + bool dirty = false; + int frames = 0, result = 1; + yasl_event e; + for (;;) { + int n = yasl_wait_event(&e, smoke ? 20 : -1); + if (n < 0 || e.type == YASL_EVENT_DISPLAY_LOST) break; + if (e.type == YASL_EVENT_CLOSE || e.type == YASL_EVENT_QUIT || + (e.type == YASL_EVENT_KEY && e.key.key == YASL_KEY_ESCAPE)) { result = 0; break; } + if (e.type == YASL_EVENT_POINTER) { + px = e.pointer.x; py = e.pointer.y; + pressure = (e.pointer.buttons & YASL_BUTTON_BIT(YASL_BUTTON_PRIMARY)) ? 0.5 : 0; + dirty = true; + } + if (e.type == YASL_EVENT_PEN) { + px = e.pen.x; py = e.pen.y; + pressure = (e.pen.axes_valid & YASL_PEN_PRESSURE) ? e.pen.pressure : 0; + dirty = true; + } + if (e.type == YASL_EVENT_REFRESH) dirty = true; + if (dirty) { + int w, h; + uint32_t *pixels = yasl_cpu_pixels(s, &w, &h); + if (!pixels) break; + double scale = yasl_surface_get_scale(s), radius = (8 + pressure * 48) * scale; + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + double dx = x - px * scale, dy = y - py * scale; + pixels[(size_t)y * w + x] = dx * dx + dy * dy < radius * radius ? 0xffffc060u : 0xff203040u; } } - - pixels[y * w + x] = (255u << 24) | ((uint32_t)r << 16) | ((uint32_t)g << 8) | b; - } - } - - if (!yasl_frame_present(frame, NULL, 0)) { - fprintf(stderr, "Failed to present frame\n"); - failed = true; - } - if (failed) yasl_app_exit(yasl_surface_display(s), YASL_APP_ERROR); - else if (smoke_test) yasl_surface_destroy(s); -} - -static enum yasl_app_result on_init(void *ud, struct yasl_display *display) -{ - (void)ud; - struct yasl_window_spec window_spec = { - .title = "YASL Interactive Window (Pen & Touch Enabled)", - .size_logical = { .x = 640, .y = 480 }, - }; - struct yasl_surface *s = yasl_surface_create_toplevel(display, &window_spec); - if (!s) { - fprintf(stderr, "Failed to create surface\n"); - failed = true; - return YASL_APP_ERROR; - } - - yasl_surface_request_frame(s); - - printf("=== YASL Demo Running ===\n"); - printf(" - Stylus/Pen: full pressure (0..65535) and tilt detection\n"); - printf(" - Mouse/Touch: interactive click/motion highlights\n"); - printf(" - Space: cycle background themes\n"); - printf(" - Escape: exit\n\n"); - - return YASL_APP_CONTINUE; -} - -static void pointer_sample(struct yasl_vector2i pos, enum yasl_pointer_device device, - struct yasl_pointer_pen pen) -{ - mouse_x = pos.x; - mouse_y = pos.y; - pointer_device = device; - pen_pressure = pen.pressure; - pen_tilt_x = pen.tilt_x; - pen_tilt_y = pen.tilt_y; -} - -static enum yasl_app_result on_event(void *ud, const struct yasl_event *ev) -{ - (void)ud; - switch (ev->type) { - case YASL_EVENT_WINDOW_CLOSE: - return YASL_APP_QUIT; - case YASL_EVENT_WINDOW_RESIZE: { - uint32_t flags = yasl_surface_state(ev->surface); - const char *mode = flags & YASL_STATE_FULLSCREEN ? "fullscreen" : - flags & YASL_STATE_MAXIMIZED ? "maximized" : - flags & YASL_STATE_TILED ? "tiled" : "windowed"; - printf("[YASL] Resized to %dx%d (scale: %.2fx, %s)\n", ev->resize.geometry.size.x, - ev->resize.geometry.size.y, (double)ev->resize.geometry.scale_120 / YASL_SCALE_UNIT, mode); - break; - } - case YASL_EVENT_KEY: - if (ev->key.action != YASL_KEY_PRESS) return YASL_APP_CONTINUE; - if (ev->key.key == YASL_KEY_ESCAPE) return YASL_APP_QUIT; - if (ev->key.key == YASL_KEY_SPACE || ev->key.key == YASL_KEY_ENTER) - color_theme++; - break; - case YASL_EVENT_POINTER_DOWN: - case YASL_EVENT_POINTER_UP: - case YASL_EVENT_POINTER_MOTION: - pointer_sample(ev->pointer.pos, ev->pointer.device, ev->pointer.pen); - if (ev->type == YASL_EVENT_POINTER_DOWN && ev->pointer.button == YASL_BUTTON_PRIMARY) { - mouse_down = true; - } else if (ev->type == YASL_EVENT_POINTER_UP && ev->pointer.button == YASL_BUTTON_PRIMARY) { - mouse_down = false; - pen_pressure = 0; + if (!yasl_cpu_present(s)) break; + dirty = false; + frames++; } - break; - case YASL_EVENT_FOCUS: - if (ev->focus.focused) return YASL_APP_CONTINUE; - /* Fall through to cancel held input on focus loss. */ - [[fallthrough]]; - case YASL_EVENT_INPUT_RESET: - case YASL_EVENT_POINTER_LEAVE: - mouse_x = mouse_y = -1; - mouse_down = false; - pen_pressure = 0; - break; - default: - return YASL_APP_CONTINUE; - } - if (pen_pressure > 0) { - printf("[Pen] Pressure: %5u/65535 (%.1f%%) | Tilt: (%+3d°, %+3d°) | Device: %s\n", - pen_pressure, pen_pressure * 100.0 / 65535.0, - pen_tilt_x, pen_tilt_y, device_name(pointer_device)); - } - yasl_surface_request_frame(ev->surface); - return YASL_APP_CONTINUE; -} - -int main(int argc, char **argv) -{ - if (argc == 2 && strcmp(argv[1], "--smoke-test") == 0) - smoke_test = true; - else if (argc != 1) { - fprintf(stderr, "Usage: %s [--smoke-test]\n", argv[0]); - return 1; + if (smoke && frames && n == 0) { result = 0; break; } } - struct yasl_app_desc desc = { - .app_id = "org.yasl.example", - .init = on_init, - .event = on_event, - .frame = on_draw, - }; - int result = yasl_run(&desc); - if (result) perror("yasl_run"); - return result || failed ? 1 : 0; + yasl_shutdown(); + return result; } 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 - -#include -#include -#include +/* Pen canvas: pressure, eraser and coherent strokes drawn straight into the CPU surface. */ +#include +#include #include +#include -#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; } -- cgit v1.2.3