diff options
Diffstat (limited to 'examples/notepad.c')
| -rw-r--r-- | examples/notepad.c | 552 |
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; } |
