/** * @file notepad.c * A complete, reactive, software-rendered text editor built on YASL Approach A. * * Demonstrates: * - Reactive presentation (0% CPU when idle via YASL_PACING_ON_DEMAND) * - Clean event callbacks (unified yasl_event) * - Monospace text rendering, line numbering gutter, and status bar * - Full text navigation, insertion, deletion, and line splitting * - Blinking text caret driven by yasl_app_add_timer() * - Mouse click cursor placement & smooth scroll wheel handling * - Background async worker thread reporting stats via yasl_post_event() * - HiDPI dynamic scale awareness */ #define _DEFAULT_SOURCE #include #include #include #include #include #include #include #include #include #define MAX_LINES 2048 #define MAX_LINE_LEN 512 /* Classic 8x8 font bitmap covering ASCII 32 (' ') to 126 ('~') */ static const uint8_t font8x8[95][8] = { {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00}, /* */ {0x18,0x3C,0x3C,0x18,0x18,0x00,0x18,0x00}, /* ! */ {0x66,0x66,0x24,0x00,0x00,0x00,0x00,0x00}, /* " */ {0x6C,0x6C,0xFE,0x6C,0xFE,0x6C,0x6C,0x00}, /* # */ {0x18,0x3E,0x60,0x3C,0x06,0x7C,0x18,0x00}, /* $ */ {0x00,0x63,0x66,0x0C,0x18,0x33,0x63,0x00}, /* % */ {0x38,0x6C,0x38,0x76,0xDC,0xCC,0x76,0x00}, /* & */ {0x18,0x18,0x30,0x00,0x00,0x00,0x00,0x00}, /* ' */ {0x0C,0x18,0x30,0x30,0x30,0x18,0x0C,0x00}, /* ( */ {0x30,0x18,0x0C,0x0C,0x0C,0x18,0x30,0x00}, /* ) */ {0x00,0x66,0x3C,0xFF,0x3C,0x66,0x00,0x00}, /* * */ {0x00,0x18,0x18,0x7E,0x18,0x18,0x00,0x00}, /* + */ {0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x30}, /* , */ {0x00,0x00,0x00,0x7E,0x00,0x00,0x00,0x00}, /* - */ {0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00}, /* . */ {0x06,0x0C,0x18,0x30,0x60,0xC0,0x80,0x00}, /* / */ {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 */ {0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x00}, /* : */ {0x00,0x18,0x18,0x00,0x00,0x18,0x18,0x30}, /* ; */ {0x0C,0x18,0x30,0x60,0x30,0x18,0x0C,0x00}, /* < */ {0x00,0x00,0x7E,0x00,0x7E,0x00,0x00,0x00}, /* = */ {0x30,0x18,0x0C,0x06,0x0C,0x18,0x30,0x00}, /* > */ {0x3C,0x66,0x06,0x0C,0x18,0x00,0x18,0x00}, /* ? */ {0x3C,0x66,0x6E,0x6A,0x6E,0x60,0x3C,0x00}, /* @ */ {0x18,0x3C,0x66,0x7E,0x66,0x66,0x66,0x00}, /* A */ {0x7C,0x66,0x66,0x7C,0x66,0x66,0x7C,0x00}, /* B */ {0x3C,0x66,0x60,0x60,0x60,0x66,0x3C,0x00}, /* C */ {0x78,0x6C,0x66,0x66,0x66,0x6C,0x78,0x00}, /* D */ {0x7E,0x60,0x60,0x7C,0x60,0x60,0x7E,0x00}, /* E */ {0x7E,0x60,0x60,0x7C,0x60,0x60,0x60,0x00}, /* F */ {0x3C,0x66,0x60,0x6E,0x66,0x66,0x3C,0x00}, /* G */ {0x66,0x66,0x66,0x7E,0x66,0x66,0x66,0x00}, /* H */ {0x3C,0x18,0x18,0x18,0x18,0x18,0x3C,0x00}, /* I */ {0x0E,0x06,0x06,0x06,0x66,0x66,0x3C,0x00}, /* J */ {0x66,0x6C,0x78,0x70,0x78,0x6C,0x66,0x00}, /* K */ {0x60,0x60,0x60,0x60,0x60,0x60,0x7E,0x00}, /* L */ {0x63,0x77,0x7F,0x6B,0x63,0x63,0x63,0x00}, /* M */ {0x66,0x76,0x7E,0x7E,0x6E,0x66,0x66,0x00}, /* N */ {0x3C,0x66,0x66,0x66,0x66,0x66,0x3C,0x00}, /* O */ {0x7C,0x66,0x66,0x7C,0x60,0x60,0x60,0x00}, /* P */ {0x3C,0x66,0x66,0x66,0x6E,0x3C,0x0E,0x00}, /* Q */ {0x7C,0x66,0x66,0x7C,0x6C,0x66,0x66,0x00}, /* R */ {0x3C,0x66,0x30,0x18,0x0C,0x66,0x3C,0x00}, /* S */ {0x7E,0x18,0x18,0x18,0x18,0x18,0x18,0x00}, /* T */ {0x66,0x66,0x66,0x66,0x66,0x66,0x3C,0x00}, /* U */ {0x66,0x66,0x66,0x66,0x66,0x3C,0x18,0x00}, /* V */ {0x63,0x63,0x63,0x6B,0x7F,0x77,0x63,0x00}, /* W */ {0x66,0x66,0x3C,0x18,0x3C,0x66,0x66,0x00}, /* X */ {0x66,0x66,0x66,0x3C,0x18,0x18,0x18,0x00}, /* Y */ {0x7E,0x06,0x0C,0x18,0x30,0x60,0x7E,0x00}, /* Z */ {0x3C,0x30,0x30,0x30,0x30,0x30,0x3C,0x00}, /* [ */ {0x80,0xC0,0x60,0x30,0x18,0x0C,0x06,0x00}, /* \ */ {0x3C,0x0C,0x0C,0x0C,0x0C,0x0C,0x3C,0x00}, /* ] */ {0x18,0x3C,0x66,0x00,0x00,0x00,0x00,0x00}, /* ^ */ {0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00}, /* _ */ {0x30,0x18,0x0C,0x00,0x00,0x00,0x00,0x00}, /* ` */ {0x00,0x00,0x3C,0x06,0x3E,0x66,0x3B,0x00}, /* a */ {0x60,0x60,0x7C,0x66,0x66,0x66,0x7C,0x00}, /* b */ {0x00,0x00,0x3C,0x66,0x60,0x66,0x3C,0x00}, /* c */ {0x06,0x06,0x3E,0x66,0x66,0x66,0x3E,0x00}, /* d */ {0x00,0x00,0x3C,0x66,0x7E,0x60,0x3C,0x00}, /* e */ {0x1C,0x30,0x30,0x78,0x30,0x30,0x30,0x00}, /* f */ {0x00,0x00,0x3B,0x66,0x66,0x3E,0x06,0x3C}, /* g */ {0x60,0x60,0x7C,0x66,0x66,0x66,0x66,0x00}, /* h */ {0x18,0x00,0x38,0x18,0x18,0x18,0x3C,0x00}, /* i */ {0x06,0x00,0x0E,0x06,0x06,0x66,0x66,0x3C}, /* j */ {0x60,0x60,0x66,0x6C,0x78,0x6C,0x66,0x00}, /* k */ {0x38,0x18,0x18,0x18,0x18,0x18,0x3C,0x00}, /* l */ {0x00,0x00,0x66,0x7F,0x7F,0x6B,0x63,0x00}, /* m */ {0x00,0x00,0x7C,0x66,0x66,0x66,0x66,0x00}, /* n */ {0x00,0x00,0x3C,0x66,0x66,0x66,0x3C,0x00}, /* o */ {0x00,0x00,0x7C,0x66,0x66,0x7C,0x60,0x60}, /* p */ {0x00,0x00,0x3E,0x66,0x66,0x3E,0x06,0x06}, /* q */ {0x00,0x00,0x7C,0x66,0x60,0x60,0x60,0x00}, /* r */ {0x00,0x00,0x3E,0x60,0x3C,0x06,0x7C,0x00}, /* s */ {0x18,0x18,0x7E,0x18,0x18,0x18,0x0E,0x00}, /* t */ {0x00,0x00,0x66,0x66,0x66,0x66,0x3B,0x00}, /* u */ {0x00,0x00,0x66,0x66,0x66,0x3C,0x18,0x00}, /* v */ {0x00,0x00,0x63,0x6B,0x7F,0x3E,0x36,0x00}, /* w */ {0x00,0x00,0x66,0x3C,0x18,0x3C,0x66,0x00}, /* x */ {0x00,0x00,0x66,0x66,0x66,0x3E,0x06,0x3C}, /* y */ {0x00,0x00,0x7E,0x0C,0x18,0x30,0x7E,0x00}, /* z */ {0x0E,0x18,0x18,0x70,0x18,0x18,0x0E,0x00}, /* { */ {0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x00}, /* | */ {0x70,0x18,0x18,0x0E,0x18,0x18,0x70,0x00}, /* } */ {0x76,0xDC,0x00,0x00,0x00,0x00,0x00,0x00} /* ~ */ }; #define MSG_STATS_READY 100 struct doc_stats { int words; int chars; }; struct editor { struct yasl_display *display; struct yasl_surface *surface; char lines[MAX_LINES][MAX_LINE_LEN]; int line_count; int cursor_row; int cursor_col; int scroll_y; bool caret_visible; /* Background stats from async worker */ struct doc_stats stats; bool worker_running; bool worker_dirty; pthread_t worker_tid; pthread_mutex_t worker_mutex; pthread_cond_t worker_cond; }; static void editor_notify_worker(struct editor *ed) { pthread_mutex_lock(&ed->worker_mutex); ed->worker_dirty = true; pthread_cond_signal(&ed->worker_cond); pthread_mutex_unlock(&ed->worker_mutex); } static void *worker_thread(void *arg) { struct editor *ed = arg; while (ed->worker_running) { pthread_mutex_lock(&ed->worker_mutex); while (!ed->worker_dirty && ed->worker_running) { pthread_cond_wait(&ed->worker_cond, &ed->worker_mutex); } if (!ed->worker_running) { pthread_mutex_unlock(&ed->worker_mutex); break; } ed->worker_dirty = false; pthread_mutex_unlock(&ed->worker_mutex); /* Debounce / simulate asynchronous LSP analysis */ usleep(30000); int word_count = 0; int char_count = 0; for (int i = 0; i < ed->line_count; i++) { const char *p = ed->lines[i]; bool in_word = false; while (*p) { char_count++; if (isspace((unsigned char)*p)) { in_word = false; } else if (!in_word) { in_word = true; word_count++; } p++; } } struct doc_stats *s = malloc(sizeof(*s)); if (s) { s->words = word_count; s->chars = char_count; yasl_post_event(ed->display, ed->surface, MSG_STATS_READY, s, sizeof(*s)); } } 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; /* Repeat every 500ms */ } /* Drawing primitives */ static void fill_rect(uint32_t *pixels, int w, int h, int rx, int ry, int rw, int rh, uint32_t col) { int x0 = rx < 0 ? 0 : rx; int y0 = ry < 0 ? 0 : ry; int x1 = rx + rw > w ? w : rx + rw; int y1 = ry + rh > h ? h : ry + rh; for (int y = y0; y < y1; y++) { uint32_t *row = pixels + (size_t)y * w; for (int x = x0; x < x1; x++) { row[x] = col; } } } static void draw_char(uint32_t *pixels, int w, int h, int px, int py, char c, uint32_t col, int scale) { if (c < 32 || c > 126) c = ' '; const uint8_t *glyph = font8x8[c - 32]; for (int y = 0; y < 8; y++) { uint8_t row = glyph[y]; for (int x = 0; x < 8; x++) { if (row & (1 << x)) { fill_rect(pixels, w, h, px + x * scale, py + y * scale, scale, scale, col); } } } } static void draw_string(uint32_t *pixels, int w, int h, int px, int py, const char *str, uint32_t col, int scale) { while (*str) { draw_char(pixels, w, h, px, py, *str, col, scale); px += 8 * scale; str++; } } /* Text editor actions */ static void insert_char_at_cursor(struct editor *ed, char c) { char *line = ed->lines[ed->cursor_row]; int len = (int)strlen(line); if (len >= MAX_LINE_LEN - 2) return; memmove(line + ed->cursor_col + 1, line + ed->cursor_col, (size_t)(len - ed->cursor_col + 1)); line[ed->cursor_col] = c; ed->cursor_col++; ed->caret_visible = true; editor_notify_worker(ed); } static void insert_text(struct editor *ed, const char *text, size_t len) { for (size_t i = 0; i < len; i++) { if (text[i] == '\n') { /* Split line */ if (ed->line_count >= MAX_LINES - 1) break; char *curr = ed->lines[ed->cursor_row]; char remainder[MAX_LINE_LEN]; snprintf(remainder, sizeof(remainder), "%s", curr + ed->cursor_col); curr[ed->cursor_col] = '\0'; memmove(ed->lines[ed->cursor_row + 2], ed->lines[ed->cursor_row + 1], (size_t)(ed->line_count - ed->cursor_row - 1) * MAX_LINE_LEN); ed->line_count++; snprintf(ed->lines[ed->cursor_row + 1], MAX_LINE_LEN, "%s", remainder); ed->cursor_row++; ed->cursor_col = 0; } else if (text[i] >= 32 && text[i] <= 126) { insert_char_at_cursor(ed, text[i]); } } ed->caret_visible = true; editor_notify_worker(ed); } static void handle_backspace(struct editor *ed) { char *line = ed->lines[ed->cursor_row]; if (ed->cursor_col > 0) { int len = (int)strlen(line); memmove(line + ed->cursor_col - 1, line + ed->cursor_col, (size_t)(len - ed->cursor_col + 1)); ed->cursor_col--; } else if (ed->cursor_row > 0) { /* Merge with previous line */ char *prev = ed->lines[ed->cursor_row - 1]; int prev_len = (int)strlen(prev); int curr_len = (int)strlen(line); if (prev_len + curr_len < MAX_LINE_LEN - 1) { strncat(prev, line, (size_t)(MAX_LINE_LEN - prev_len - 1)); memmove(ed->lines[ed->cursor_row], ed->lines[ed->cursor_row + 1], (size_t)(ed->line_count - ed->cursor_row - 1) * MAX_LINE_LEN); ed->line_count--; ed->cursor_row--; ed->cursor_col = prev_len; } } ed->caret_visible = true; editor_notify_worker(ed); } static void handle_delete(struct editor *ed) { char *line = ed->lines[ed->cursor_row]; int len = (int)strlen(line); if (ed->cursor_col < len) { memmove(line + ed->cursor_col, line + ed->cursor_col + 1, (size_t)(len - ed->cursor_col)); } else if (ed->cursor_row < ed->line_count - 1) { /* Merge with next line */ char *next = ed->lines[ed->cursor_row + 1]; int next_len = (int)strlen(next); if (len + next_len < MAX_LINE_LEN - 1) { strncat(line, next, (size_t)(MAX_LINE_LEN - len - 1)); memmove(ed->lines[ed->cursor_row + 1], ed->lines[ed->cursor_row + 2], (size_t)(ed->line_count - ed->cursor_row - 2) * MAX_LINE_LEN); ed->line_count--; } } ed->caret_visible = true; editor_notify_worker(ed); } static void handle_enter(struct editor *ed) { if (ed->line_count >= MAX_LINES - 1) return; char *curr = ed->lines[ed->cursor_row]; char remainder[MAX_LINE_LEN]; snprintf(remainder, sizeof(remainder), "%s", curr + ed->cursor_col); curr[ed->cursor_col] = '\0'; memmove(ed->lines[ed->cursor_row + 2], ed->lines[ed->cursor_row + 1], (size_t)(ed->line_count - ed->cursor_row - 1) * MAX_LINE_LEN); ed->line_count++; snprintf(ed->lines[ed->cursor_row + 1], MAX_LINE_LEN, "%s", remainder); ed->cursor_row++; ed->cursor_col = 0; ed->caret_visible = true; editor_notify_worker(ed); } /* YASL App callbacks */ static int on_init(void *ud, struct yasl_display *d) { struct editor *ed = ud; ed->display = d; struct yasl_surface_spec spec = { .title = "YASL Notepad (Approach A Example)", .size = {860, 560}, }; ed->surface = yasl_surface_create_toplevel(d, &spec); if (!ed->surface) { perror("yasl_surface_create_toplevel"); return YASL_APP_QUIT; } yasl_surface_set_pacing(ed->surface, YASL_PACING_ON_DEMAND); /* Start cursor blink timer (500ms) */ yasl_app_add_timer(d, YASL_MS_TO_NS(500), on_blink_timer, ed); /* Start background analysis thread */ ed->worker_running = true; pthread_mutex_init(&ed->worker_mutex, NULL); pthread_cond_init(&ed->worker_cond, NULL); pthread_create(&ed->worker_tid, NULL, worker_thread, ed); /* Trigger initial analysis and frame */ editor_notify_worker(ed); yasl_surface_request_frame(ed->surface); return YASL_APP_CONTINUE; } static int on_event(void *ud, const struct yasl_event *ev) { struct editor *ed = ud; switch (ev->type) { case YASL_EVENT_TEXT: insert_text(ed, ev->text.text, ev->text.len); yasl_surface_request_frame(ed->surface); break; case YASL_EVENT_KEY: if (ev->key.action == YASL_KEY_PRESS || ev->key.action == YASL_KEY_REPEAT) { switch (ev->key.key) { case YASL_KEY_BACKSPACE: handle_backspace(ed); break; case YASL_KEY_DELETE: handle_delete(ed); break; case YASL_KEY_ENTER: handle_enter(ed); break; case YASL_KEY_LEFT: if (ed->cursor_col > 0) { ed->cursor_col--; } else if (ed->cursor_row > 0) { ed->cursor_row--; ed->cursor_col = (int)strlen(ed->lines[ed->cursor_row]); } ed->caret_visible = true; break; case YASL_KEY_RIGHT: { int len = (int)strlen(ed->lines[ed->cursor_row]); if (ed->cursor_col < len) { ed->cursor_col++; } else if (ed->cursor_row < ed->line_count - 1) { ed->cursor_row++; ed->cursor_col = 0; } ed->caret_visible = true; break; } case YASL_KEY_UP: if (ed->cursor_row > 0) { ed->cursor_row--; int len = (int)strlen(ed->lines[ed->cursor_row]); if (ed->cursor_col > len) ed->cursor_col = len; } ed->caret_visible = true; break; case YASL_KEY_DOWN: if (ed->cursor_row < ed->line_count - 1) { ed->cursor_row++; int len = (int)strlen(ed->lines[ed->cursor_row]); if (ed->cursor_col > len) ed->cursor_col = len; } ed->caret_visible = true; break; case YASL_KEY_HOME: ed->cursor_col = 0; ed->caret_visible = true; break; case YASL_KEY_END: ed->cursor_col = (int)strlen(ed->lines[ed->cursor_row]); ed->caret_visible = true; break; default: break; } yasl_surface_request_frame(ed->surface); } break; case YASL_EVENT_MOUSE_BUTTON: if (ev->mouse_button.pressed && ev->mouse_button.button == 0) { /* Compute scale-dependent dimensions */ struct yasl_geometry geom = yasl_surface_geometry(ed->surface); int scale = geom.scale_120 >= 240 ? 2 : 1; int char_w = 8 * scale; int line_h = 16 * scale; int gutter_w = 48 * scale; int header_h = 8 * scale; int row = (ev->mouse_button.pos.y - header_h) / line_h + ed->scroll_y; int col = (ev->mouse_button.pos.x - gutter_w) / char_w; 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; ed->cursor_row = row; ed->cursor_col = col; ed->caret_visible = true; yasl_surface_request_frame(ed->surface); } break; case YASL_EVENT_MOUSE_SCROLL: { ed->scroll_y -= (int)(ev->mouse_scroll.scroll.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); break; } case YASL_EVENT_USER: if (ev->user.type == MSG_STATS_READY && ev->user.payload) { struct doc_stats *s = (struct doc_stats *)ev->user.payload; ed->stats = *s; free(s); yasl_surface_request_frame(ed->surface); } break; case YASL_EVENT_WINDOW_CLOSE_REQUEST: return YASL_APP_QUIT; default: break; } return YASL_APP_CONTINUE; } static void on_frame(void *ud, struct yasl_surface *s, struct yasl_frame_context *ctx) { (void)s; struct editor *ed = ud; int w = ctx->size.x; int h = ctx->size.y; uint32_t *pix = ctx->cpu.pixels; if (!pix || w <= 0 || h <= 0) return; int scale = ctx->scale_120 >= 240 ? 2 : 1; int char_w = 8 * scale; int line_h = 16 * scale; int gutter_w = 48 * scale; int header_h = 8 * scale; int status_h = 24 * scale; /* Catppuccin-inspired modern dark theme */ uint32_t bg_main = 0xff1e1e2e; uint32_t bg_gutter = 0xff181825; uint32_t bg_active_line = 0xff252538; uint32_t bg_status = 0xff11111b; uint32_t text_fg = 0xffcdd6f4; uint32_t text_gutter = 0xff6c7086; uint32_t text_status = 0xffa6adc8; uint32_t caret_col = 0xfff38ba8; uint32_t status_accent = 0xffa6e3a1; /* 1. Clear background */ fill_rect(pix, w, h, 0, 0, w, h, bg_main); /* 2. Gutter background */ fill_rect(pix, w, h, 0, 0, gutter_w, h - status_h, bg_gutter); /* 3. Ensure cursor is within visible bounds */ int visible_lines = (h - header_h - status_h) / line_h; if (ed->cursor_row < ed->scroll_y) { ed->scroll_y = ed->cursor_row; } else if (ed->cursor_row >= ed->scroll_y + visible_lines) { ed->scroll_y = ed->cursor_row - visible_lines + 1; } /* 4. Active line highlight */ int active_line_y = header_h + (ed->cursor_row - ed->scroll_y) * line_h; if (active_line_y >= header_h && active_line_y < h - status_h) { fill_rect(pix, w, h, gutter_w, active_line_y, w - gutter_w, line_h, bg_active_line); } /* 5. Render visible lines and gutter numbers */ for (int i = 0; i < visible_lines; i++) { int line_idx = ed->scroll_y + i; if (line_idx >= ed->line_count) break; int y = header_h + i * line_h + 4 * scale; /* Line number */ char num_buf[16]; snprintf(num_buf, sizeof(num_buf), "%3d", line_idx + 1); draw_string(pix, w, h, 8 * scale, y, num_buf, text_gutter, scale); /* Text content */ const char *line_text = ed->lines[line_idx]; draw_string(pix, w, h, gutter_w + 8 * scale, y, line_text, text_fg, scale); } /* 6. Render caret */ if (ed->caret_visible && active_line_y >= header_h && active_line_y < h - status_h) { int caret_x = gutter_w + 8 * scale + ed->cursor_col * char_w; int caret_y = active_line_y + 3 * scale; fill_rect(pix, w, h, caret_x, caret_y, 2 * scale, 10 * scale, caret_col); } /* 7. Status bar */ int status_y = h - status_h; fill_rect(pix, w, h, 0, status_y, w, status_h, bg_status); char status_left[128]; 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); draw_string(pix, w, h, 12 * scale, status_y + 6 * scale, status_left, text_status, scale); char status_right[64]; snprintf(status_right, sizeof(status_right), "[0%% CPU Idle] Scale: %.1fx", (double)ctx->scale_120 / 120.0); int right_w = (int)strlen(status_right) * char_w; draw_string(pix, w, h, w - right_w - 12 * scale, status_y + 6 * scale, status_right, status_accent, scale); /* Present full surface */ yasl_frame_present_cpu(ctx, NULL, 0); } static void on_quit(void *ud, struct yasl_display *d) { (void)d; struct editor *ed = ud; ed->worker_running = false; pthread_mutex_lock(&ed->worker_mutex); pthread_cond_signal(&ed->worker_cond); pthread_mutex_unlock(&ed->worker_mutex); pthread_join(ed->worker_tid, NULL); pthread_mutex_destroy(&ed->worker_mutex); pthread_cond_destroy(&ed->worker_cond); } int main(void) { static struct editor ed; /* Initialize starter document */ const char *initial_content[] = { "// Welcome to YASL Notepad!", "// A reactive, modern desktop text editor built on YASL Approach A.", "", "#include ", "", "int main(int argc, char **argv) {", " printf(\"Hello, modern YASL!\\n\");", " return 0;", "}", "", "Features demonstrated in this example:", " [x] 0% CPU at idle (YASL_PACING_ON_DEMAND)", " [x] Unified event callback (yasl_event)", " [x] Smooth caret blink timer via yasl_app_add_timer()", " [x] Asynchronous background linter/stats via yasl_post_event()", " [x] Full keyboard autorepeat & text navigation", " [x] Mouse click cursor positioning & scroll wheel support", " [x] Dynamic HiDPI scale awareness", "", "Try typing here or pressing Arrow keys, Enter, Backspace, Home, End..." }; ed.line_count = sizeof(initial_content) / sizeof(initial_content[0]); for (int i = 0; i < ed.line_count; i++) { snprintf(ed.lines[i], MAX_LINE_LEN, "%s", initial_content[i]); } ed.cursor_row = 6; ed.cursor_col = 35; ed.caret_visible = true; 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); }