summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
Diffstat (limited to 'examples')
-rw-r--r--examples/game.c403
-rw-r--r--examples/hello.c54
-rw-r--r--examples/notepad.c552
-rw-r--r--examples/simple_window.c232
-rw-r--r--examples/tablet.c419
5 files changed, 537 insertions, 1123 deletions
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 <yasl/app.h>
-
-#include <math.h>
-#include <stdbool.h>
-#include <stdint.h>
+/* GPU example: the application owns context binding, drawing and swap pacing. */
+#include <yasl/yasl.h>
+#include <yasl/gl.h>
#include <stdio.h>
-#include <stdlib.h>
+#include <math.h>
#include <string.h>
-#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 <yasl/app.h>
-#include <stdio.h>
+/* A CPU-rendered window. Build instructions: docs/getting-started.md. */
+#include <yasl/yasl.h>
+#include <yasl/cpu.h>
-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 <yasl/app.h>
+#include <yasl/yasl.h>
+#include <yasl/cpu.h>
#include <ctype.h>
+#include <math.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
@@ -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 <stdio.h>",
"",
@@ -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 <yasl/app.h>
-#include <stdio.h>
+/* Mouse and pen input on a CPU surface. */
+#include <yasl/yasl.h>
+#include <yasl/cpu.h>
#include <string.h>
-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 <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;
}