summaryrefslogtreecommitdiff
path: root/examples/notepad.c
diff options
context:
space:
mode:
authorBerke Güzel <wenekar1@gmail.com>2026-10-13 23:41:31 +0300
committerBerke Güzel <wenekar1@gmail.com>2026-10-13 23:41:31 +0300
commit76073fe5fad8799e5850805f2f090af2cb62c741 (patch)
tree9658019babe4760a3634bdab4da0731a10e42e68 /examples/notepad.c
parentdd1775a9927922c14f8679ccbc4d3ca8d5917e20 (diff)
Simplify the public event and CPU API
Applications now need far less ceremony: hello.c is a 20-line loop that handles REFRESH, CLOSE and QUIT and draws into the surface's own pixels. - Fold GEOMETRY, STATE and FOCUS into one WINDOW event carrying size, scale and state; unchanged values send nothing. - Guarantee REFRESH once a window has a size and after every resize, never at size zero. - Merge DROP_HOVER into DROP with over/leave/data actions and give drag and drop actions enums. - Keep event strings valid until the next wait and drop yasl_event_release; empty or failed waits report NONE. - Replace copy-in presentation with a lent pixel buffer: yasl_cpu_pixels and yasl_cpu_present. - Remove yasl_poll_event, yasl_cpu_read_pixels, unused event fields and comments that restated names. - Wayland: resize floating windows in request_size instead of reporting it unsupported, and stop marking new GPU surfaces as Vulkan so yasl_vulkan_create_surface no longer fails with NATIVE_WINDOW_IN_USE. Also includes the in-progress public API refactor that was uncommitted in the tree (removal of the app/loop layer, new surface, GL and Vulkan entry points). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'examples/notepad.c')
-rw-r--r--examples/notepad.c552
1 files changed, 335 insertions, 217 deletions
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;
}