From 6aeaa171dc1ca43392f53cbd02097f76e1b1c5a0 Mon Sep 17 00:00:00 2001 From: David Moc Date: Sun, 31 May 2026 03:47:04 +0200 Subject: Hardened API, tetris, MD-View --- src/render.c | 813 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 795 insertions(+), 18 deletions(-) (limited to 'src/render.c') diff --git a/src/render.c b/src/render.c index 64a85f1..86bc535 100644 --- a/src/render.c +++ b/src/render.c @@ -1,6 +1,7 @@ #include "render.h" #include "common.h" +#include "media.h" #include #include @@ -474,6 +475,68 @@ static void draw_buffer_line(app_t *app, free(line); } + +static void draw_media_buffer(app_t *app, buffer_t *buf, view_rect_t rect) { + if (!app || !buf || !ecex_media_buffer_has_pixels(buf)) return; + + if (buf->media_texture == 0) { + glGenTextures(1, &buf->media_texture); + buf->media_texture_width = 0; + buf->media_texture_height = 0; + } + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, buf->media_texture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + + if (buf->media_dirty || + buf->media_texture_width != buf->media_width || + buf->media_texture_height != buf->media_height) { + glTexImage2D(GL_TEXTURE_2D, + 0, + GL_RGBA, + buf->media_width, + buf->media_height, + 0, + GL_RGBA, + GL_UNSIGNED_BYTE, + buf->media_pixels); + buf->media_texture_width = buf->media_width; + buf->media_texture_height = buf->media_height; + buf->media_dirty = 0; + } + + ui_metrics_t m = ui_metrics(app); + float avail_x = rect.x + m.content_x; + float avail_y = rect.y + m.content_top + app->font.line_height * 4.0f; + float avail_w = rect.w - m.content_x * 2.0f; + float avail_h = rect.h - (avail_y - rect.y) - m.content_bottom_pad; + if (avail_w < 8.0f || avail_h < 8.0f) return; + + float iw = (float)buf->media_width; + float ih = (float)buf->media_height; + float scale = avail_w / iw; + if (ih * scale > avail_h) scale = avail_h / ih; + if (scale <= 0.0f) return; + + float draw_w = iw * scale; + float draw_h = ih * scale; + float x = avail_x + (avail_w - draw_w) * 0.5f; + float y = avail_y + (avail_h - draw_h) * 0.5f; + + glColor3f(1.0f, 1.0f, 1.0f); + glBegin(GL_QUADS); + glTexCoord2f(0.0f, 0.0f); glVertex2f(x, y); + glTexCoord2f(1.0f, 0.0f); glVertex2f(x + draw_w, y); + glTexCoord2f(1.0f, 1.0f); glVertex2f(x + draw_w, y + draw_h); + glTexCoord2f(0.0f, 1.0f); glVertex2f(x, y + draw_h); + glEnd(); + glDisable(GL_TEXTURE_2D); +} + static void render_cursor(app_t *app, buffer_t *buf, view_rect_t rect) { ui_metrics_t m = ui_metrics(app); @@ -522,6 +585,708 @@ static void draw_window_border(app_t *app, view_rect_t rect, int active) { draw_rect(rect.x + rect.w - 1.0f, rect.y, 1.0f, rect.h); } + +static app_t *draw_context_app(ecex_draw_context_t *ctx) { + return ctx ? (app_t *)ctx->internal : NULL; +} + +static void draw_context_vertex(ecex_draw_context_t *ctx, float x, float y) { + glVertex2f(ctx->x + x, ctx->y + y); +} + +void ecex_draw_set_color(ecex_draw_context_t *ctx, float r, float g, float b, float a) { + (void)ctx; + glColor4f(r, g, b, a); +} + +void ecex_draw_rect(ecex_draw_context_t *ctx, float x, float y, float w, float h) { + if (!ctx || w <= 0.0f || h <= 0.0f) return; + glDisable(GL_TEXTURE_2D); + glBegin(GL_QUADS); + draw_context_vertex(ctx, x, y); + draw_context_vertex(ctx, x + w, y); + draw_context_vertex(ctx, x + w, y + h); + draw_context_vertex(ctx, x, y + h); + glEnd(); +} + +void ecex_draw_rect_outline(ecex_draw_context_t *ctx, float x, float y, float w, float h, float thickness) { + if (!ctx || w <= 0.0f || h <= 0.0f) return; + if (thickness <= 0.0f) thickness = 1.0f; + ecex_draw_rect(ctx, x, y, w, thickness); + ecex_draw_rect(ctx, x, y + h - thickness, w, thickness); + ecex_draw_rect(ctx, x, y, thickness, h); + ecex_draw_rect(ctx, x + w - thickness, y, thickness, h); +} + +void ecex_draw_line(ecex_draw_context_t *ctx, float x1, float y1, float x2, float y2, float thickness) { + if (!ctx) return; + if (thickness <= 0.0f) thickness = 1.0f; + glDisable(GL_TEXTURE_2D); + glLineWidth(thickness); + glBegin(GL_LINES); + draw_context_vertex(ctx, x1, y1); + draw_context_vertex(ctx, x2, y2); + glEnd(); + glLineWidth(1.0f); +} + +void ecex_draw_text(ecex_draw_context_t *ctx, float x, float y, const char *text) { + app_t *app = draw_context_app(ctx); + if (!ctx || !app || !text) return; + float baseline = y + (app->font.ascent_px > 1.0f ? app->font.ascent_px : app->font.size_px * 0.80f); + draw_text(&app->font, ctx->x + x, ctx->y + baseline, text); +} + +float ecex_draw_text_width(ecex_draw_context_t *ctx, const char *text) { + app_t *app = draw_context_app(ctx); + if (!ctx || !app || !text) return 0.0f; + return text_width(&app->font, text); +} + +void ecex_draw_text_aligned(ecex_draw_context_t *ctx, float x, float y, float w, const char *text, int align) { + if (!ctx || !text) return; + float tw = ecex_draw_text_width(ctx, text); + float tx = x; + if (align == ECEX_TEXT_ALIGN_CENTER) tx = x + (w - tw) * 0.5f; + else if (align == ECEX_TEXT_ALIGN_RIGHT) tx = x + w - tw; + ecex_draw_text(ctx, tx, y, text); +} + +void ecex_draw_color_rgba8(ecex_draw_context_t *ctx, int r, int g, int b, int a) { + if (!ctx) return; + if (r < 0) r = 0; if (r > 255) r = 255; + if (g < 0) g = 0; if (g > 255) g = 255; + if (b < 0) b = 0; if (b > 255) b = 255; + if (a < 0) a = 0; if (a > 255) a = 255; + ecex_draw_set_color(ctx, (float)r / 255.0f, (float)g / 255.0f, (float)b / 255.0f, (float)a / 255.0f); +} + +void ecex_draw_rect_i(ecex_draw_context_t *ctx, int x, int y, int w, int h) { + ecex_draw_rect(ctx, (float)x, (float)y, (float)w, (float)h); +} + +void ecex_draw_rect_outline_i(ecex_draw_context_t *ctx, int x, int y, int w, int h, int thickness) { + ecex_draw_rect_outline(ctx, (float)x, (float)y, (float)w, (float)h, (float)thickness); +} + +void ecex_draw_line_i(ecex_draw_context_t *ctx, int x1, int y1, int x2, int y2, int thickness) { + ecex_draw_line(ctx, (float)x1, (float)y1, (float)x2, (float)y2, (float)thickness); +} + +void ecex_draw_text_i(ecex_draw_context_t *ctx, int x, int y, const char *text) { + ecex_draw_text(ctx, (float)x, (float)y, text); +} + + +static const char *ecex_draw_label_text(int label_id) { + switch (label_id) { + case 1: return "Tetris"; + case 2: return "score: "; + case 3: return "lines: "; + case 4: return "level: "; + case 5: return "next:"; + case 6: return "keys:"; + case 7: return "h/l or left/right move"; + case 8: return "j or down soft drop"; + case 9: return "k/up rotate"; + case 10: return "space hard drop"; + case 11: return "p pause, n new, q quit"; + case 12: return "GAME OVER - press n"; + case 13: return "PAUSED"; + case 20: return "Render demo"; + case 21: return "host vars and mouse input"; + case 22: return "integer safe drawing"; + case 23: return "smooth click animation"; + case 24: return "position: "; + case 25: return "click box to move cube"; + default: return ""; + } +} + +static unsigned char ecex_ascii5x7_bits(char ch, int row) { + /* Tiny host-side pixel font used by CCDJIT-safe label helpers. This avoids + * routing plugin labels through the normal stb/font text renderer, which is + * useful while plugin callback ABI issues are being isolated. Bits are + * returned left-to-right in the low 5 bits. */ + if (row < 0 || row >= 7) return 0; + switch (ch) { + case '0': { static const unsigned char b[7] = {14,17,19,21,25,17,14}; return b[row]; } + case '1': { static const unsigned char b[7] = {4,12,4,4,4,4,14}; return b[row]; } + case '2': { static const unsigned char b[7] = {14,17,1,2,4,8,31}; return b[row]; } + case '3': { static const unsigned char b[7] = {30,1,1,14,1,1,30}; return b[row]; } + case '4': { static const unsigned char b[7] = {2,6,10,18,31,2,2}; return b[row]; } + case '5': { static const unsigned char b[7] = {31,16,16,30,1,1,30}; return b[row]; } + case '6': { static const unsigned char b[7] = {14,16,16,30,17,17,14}; return b[row]; } + case '7': { static const unsigned char b[7] = {31,1,2,4,8,8,8}; return b[row]; } + case '8': { static const unsigned char b[7] = {14,17,17,14,17,17,14}; return b[row]; } + case '9': { static const unsigned char b[7] = {14,17,17,15,1,1,14}; return b[row]; } + case 'A': case 'a': { static const unsigned char b[7] = {14,17,17,31,17,17,17}; return b[row]; } + case 'B': case 'b': { static const unsigned char b[7] = {30,17,17,30,17,17,30}; return b[row]; } + case 'C': case 'c': { static const unsigned char b[7] = {14,17,16,16,16,17,14}; return b[row]; } + case 'D': case 'd': { static const unsigned char b[7] = {30,17,17,17,17,17,30}; return b[row]; } + case 'E': case 'e': { static const unsigned char b[7] = {31,16,16,30,16,16,31}; return b[row]; } + case 'F': case 'f': { static const unsigned char b[7] = {31,16,16,30,16,16,16}; return b[row]; } + case 'G': case 'g': { static const unsigned char b[7] = {14,17,16,23,17,17,15}; return b[row]; } + case 'H': case 'h': { static const unsigned char b[7] = {17,17,17,31,17,17,17}; return b[row]; } + case 'I': case 'i': { static const unsigned char b[7] = {14,4,4,4,4,4,14}; return b[row]; } + case 'J': case 'j': { static const unsigned char b[7] = {7,2,2,2,2,18,12}; return b[row]; } + case 'K': case 'k': { static const unsigned char b[7] = {17,18,20,24,20,18,17}; return b[row]; } + case 'L': case 'l': { static const unsigned char b[7] = {16,16,16,16,16,16,31}; return b[row]; } + case 'M': case 'm': { static const unsigned char b[7] = {17,27,21,21,17,17,17}; return b[row]; } + case 'N': case 'n': { static const unsigned char b[7] = {17,25,21,19,17,17,17}; return b[row]; } + case 'O': case 'o': { static const unsigned char b[7] = {14,17,17,17,17,17,14}; return b[row]; } + case 'P': case 'p': { static const unsigned char b[7] = {30,17,17,30,16,16,16}; return b[row]; } + case 'Q': case 'q': { static const unsigned char b[7] = {14,17,17,17,21,18,13}; return b[row]; } + case 'R': case 'r': { static const unsigned char b[7] = {30,17,17,30,20,18,17}; return b[row]; } + case 'S': case 's': { static const unsigned char b[7] = {15,16,16,14,1,1,30}; return b[row]; } + case 'T': case 't': { static const unsigned char b[7] = {31,4,4,4,4,4,4}; return b[row]; } + case 'U': case 'u': { static const unsigned char b[7] = {17,17,17,17,17,17,14}; return b[row]; } + case 'V': case 'v': { static const unsigned char b[7] = {17,17,17,17,17,10,4}; return b[row]; } + case 'W': case 'w': { static const unsigned char b[7] = {17,17,17,21,21,21,10}; return b[row]; } + case 'X': case 'x': { static const unsigned char b[7] = {17,17,10,4,10,17,17}; return b[row]; } + case 'Y': case 'y': { static const unsigned char b[7] = {17,17,10,4,4,4,4}; return b[row]; } + case 'Z': case 'z': { static const unsigned char b[7] = {31,1,2,4,8,16,31}; return b[row]; } + case ':': { static const unsigned char b[7] = {0,4,4,0,4,4,0}; return b[row]; } + case '-': { static const unsigned char b[7] = {0,0,0,14,0,0,0}; return b[row]; } + case '/': { static const unsigned char b[7] = {1,1,2,4,8,16,16}; return b[row]; } + case ',': { static const unsigned char b[7] = {0,0,0,0,4,4,8}; return b[row]; } + case '.': { static const unsigned char b[7] = {0,0,0,0,0,12,12}; return b[row]; } + case ' ': default: return 0; + } +} + +static void ecex_draw_mini_text_i(ecex_draw_context_t *ctx, int x, int y, const char *text) { + int scale = 2; + int advance = 12; + int cx = x; + const char *p = text; + if (!ctx || !text) return; + while (*p) { + int row; + for (row = 0; row < 7; ++row) { + unsigned char bits = ecex_ascii5x7_bits(*p, row); + int col; + for (col = 0; col < 5; ++col) { + if (bits & (1u << (4 - col))) { + ecex_draw_rect_i(ctx, cx + col * scale, y + row * scale, scale, scale); + } + } + } + cx += advance; + ++p; + } +} + + +extern const char *ecex_text_get_for_draw(ecex_t *ed, void *owner, int id); + +void ecex_draw_text_id_i(ecex_draw_context_t *ctx, void *owner, int id, int x, int y) { + app_t *app; + const char *text; + if (!ctx || !ctx->internal) return; + app = (app_t *)ctx->internal; + if (!app || !app->ed) return; + text = ecex_text_get_for_draw(app->ed, owner, id); + if (!text) text = ""; + + /* + * Plugin-safe real-font path: plugin code passes only owner/id and integer + * coordinates. The string itself lives in the host text registry, and the + * actual font renderer is called here on the host side, not directly from + * JIT-owned stack/string memory. Fixed labels may still use the mini-font + * helpers, but arbitrary plugin text such as Markdown should render with + * the loaded editor font. + */ + ecex_draw_text(ctx, (float)x, (float)y, text); +} + + + +int ecex_draw_context_height_i(ecex_draw_context_t *ctx) { + if (!ctx) return 0; + return (int)ctx->h; +} + +int ecex_draw_context_line_height_i(ecex_draw_context_t *ctx) { + int line_h; + if (!ctx) return 18; + line_h = (int)ctx->line_height; + return line_h < 18 ? 18 : line_h; +} + +int ecex_markdown_body_y_i(ecex_draw_context_t *ctx) { + int line_h; + if (!ctx) return 36; + line_h = ecex_draw_context_line_height_i(ctx); + return (int)ctx->content_y + line_h * 2; +} + +static int md_host_strlen(const char *s) { + int n = 0; + if (!s) return 0; + while (s[n]) ++n; + return n; +} + +static int md_host_is_digit(char c) { + return c >= '0' && c <= '9'; +} + +static const char *md_host_skip_indent(const char *line) { + if (!line) return ""; + while (*line == ' ' || *line == '\t') ++line; + return line; +} + +static int md_host_line_is_fence(const char *line) { + const char *p = md_host_skip_indent(line); + return (p[0] == '`' && p[1] == '`' && p[2] == '`') || + (p[0] == '~' && p[1] == '~' && p[2] == '~'); +} + +static int md_host_line_is_hr(const char *line) { + const char *p = md_host_skip_indent(line); + char mark = 0; + int count = 0; + while (*p) { + if (*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') { ++p; continue; } + if (*p != '-' && *p != '*' && *p != '_') return 0; + if (!mark) mark = *p; + if (*p != mark) return 0; + ++count; + ++p; + } + return count >= 3; +} + +static int md_host_heading_level(const char *line, const char **out_text) { + const char *p = md_host_skip_indent(line); + int level = 0; + while (p[level] == '#' && level < 6) ++level; + if (level > 0 && (p[level] == ' ' || p[level] == '\t' || p[level] == '\0')) { + p += level; + while (*p == ' ' || *p == '\t') ++p; + if (out_text) *out_text = p; + return level; + } + return 0; +} + +static const char *md_host_list_text(const char *line) { + const char *p = md_host_skip_indent(line); + if ((p[0] == '-' || p[0] == '*' || p[0] == '+') && (p[1] == ' ' || p[1] == '\t')) { + p += 2; + while (*p == ' ' || *p == '\t') ++p; + return p; + } + if (md_host_is_digit(p[0])) { + const char *q = p; + while (md_host_is_digit(*q)) ++q; + if (*q == '.' && (q[1] == ' ' || q[1] == '\t')) { + q += 2; + while (*q == ' ' || *q == '\t') ++q; + return q; + } + } + return NULL; +} + +static const char *md_host_quote_text(const char *line) { + const char *p = md_host_skip_indent(line); + if (*p != '>') return NULL; + ++p; + if (*p == ' ') ++p; + return p; +} + +static const char *md_host_trim_start(const char *text) { + if (!text) return ""; + while (*text == ' ' || *text == '\t') ++text; + return text; +} + +static int md_host_trim_len(const char *text) { + int n; + if (!text) return 0; + while (*text == ' ' || *text == '\t') ++text; + n = md_host_strlen(text); + while (n > 0 && (text[n - 1] == ' ' || text[n - 1] == '\t')) --n; + if (n > 224) n = 224; + return n; +} + +static void md_host_set_and_draw(ecex_draw_context_t *ctx, void *owner, int y, int style, const char *text) { + app_t *app; + const char *start; + int len; + if (!ctx || !ctx->internal) return; + app = (app_t *)ctx->internal; + if (!app || !app->ed) return; + start = md_host_trim_start(text); + len = md_host_trim_len(start); + if (ecex_text_set(app->ed, owner, 2, start, len) == 0) { + ecex_draw_markdown_line_auto_i(ctx, owner, 2, y, style); + } +} + +int ecex_markdown_draw_line_from_buffer_i(ecex_draw_context_t *ctx, void *owner, buffer_t *buffer, int line_index, int y, int in_code) { + char line[512]; + const char *text = NULL; + int copied; + int line_h; + int heading; + int next_in_code = in_code ? 1 : 0; + int advance; + app_t *app; + + if (!ctx || !ctx->internal || !buffer) return 18; + app = (app_t *)ctx->internal; + line_h = ecex_draw_context_line_height_i(ctx); + copied = ecex_buffer_line_copy(buffer, line_index, line, (int)sizeof(line)); + if (line_index < 4) { + ecex_log_int("markdown_host_line: index=", line_index); + ecex_log_int("markdown_host_line: copied=", copied); + } + if (copied < 0) return line_h; + line[sizeof(line) - 1] = '\0'; + + if (md_host_line_is_fence(line)) { + next_in_code = !next_in_code; + if (app && app->ed) ecex_text_set(app->ed, owner, 2, next_in_code ? "code" : "end code", -1); + ecex_draw_markdown_line_auto_i(ctx, owner, 2, y, 5); + advance = line_h + 8; + return advance | (next_in_code ? 0x10000 : 0); + } + + if (next_in_code) { + if (app && app->ed) ecex_text_set(app->ed, owner, 2, line, copied > 224 ? 224 : copied); + ecex_draw_markdown_line_auto_i(ctx, owner, 2, y, 3); + return line_h | 0x10000; + } + + heading = md_host_heading_level(line, &text); + if (heading) { + md_host_set_and_draw(ctx, owner, y, 1, text); + advance = line_h + (7 - heading) * 3 + 8; + return advance; + } + + if (md_host_line_is_hr(line)) { + if (app && app->ed) ecex_text_set(app->ed, owner, 2, "", 0); + ecex_draw_markdown_line_auto_i(ctx, owner, 2, y, 6); + return line_h; + } + + text = md_host_quote_text(line); + if (text) { + md_host_set_and_draw(ctx, owner, y, 2, text); + return line_h; + } + + text = md_host_list_text(line); + if (text) { + md_host_set_and_draw(ctx, owner, y, 4, text); + return line_h; + } + + if (line[0] == '\0') return line_h / 2; + + md_host_set_and_draw(ctx, owner, y, 0, line); + return line_h; +} + +void ecex_draw_markdown_canvas_i(ecex_draw_context_t *ctx, void *owner, int title_id, int x, int y, int w, int line_h) { + app_t *app; + const char *title; + int full_w; + int full_h; + if (!ctx || !ctx->internal) return; + app = (app_t *)ctx->internal; + full_w = (int)ctx->w; + full_h = (int)ctx->h; + if (w < 1) w = full_w - x * 2; + if (w < 1) w = 1; + if (line_h < 18) line_h = 18; + ecex_draw_color_rgba8(ctx, 29, 32, 33, 255); + ecex_draw_rect_i(ctx, 0, 0, full_w, full_h); + ecex_draw_color_rgba8(ctx, 250, 241, 199, 255); + title = (app && app->ed) ? ecex_text_get_for_draw(app->ed, owner, title_id) : ""; + if (!title) title = ""; + ecex_draw_text(ctx, (float)x, (float)y, title); + ecex_draw_color_rgba8(ctx, 80, 73, 69, 255); + ecex_draw_line_i(ctx, x, y + line_h + 8, x + w, y + line_h + 8, 1); +} + +void ecex_draw_markdown_text_i(ecex_draw_context_t *ctx, void *owner, int text_id, int x, int y, int w, int line_h, int style) { + app_t *app; + const char *text; + int h; + if (!ctx || !ctx->internal) return; + app = (app_t *)ctx->internal; + text = (app && app->ed) ? ecex_text_get_for_draw(app->ed, owner, text_id) : ""; + if (!text) text = ""; + if (w < 1) w = 1; + if (line_h < 18) line_h = 18; + h = line_h + 4; + switch (style) { + case 1: /* heading */ + ecex_draw_color_rgba8(ctx, 69, 84, 96, 255); + ecex_draw_rect_i(ctx, x, y - 5, w, h + 6); + ecex_draw_color_rgba8(ctx, 250, 189, 47, 255); + ecex_draw_text(ctx, (float)(x + 10), (float)y, text); + break; + case 2: /* quote */ + ecex_draw_color_rgba8(ctx, 131, 165, 152, 255); + ecex_draw_rect_i(ctx, x, y - 2, 4, h); + ecex_draw_color_rgba8(ctx, 213, 196, 161, 255); + ecex_draw_text(ctx, (float)(x + 14), (float)y, text); + break; + case 3: /* code */ + ecex_draw_color_rgba8(ctx, 40, 40, 40, 255); + ecex_draw_rect_i(ctx, x, y - 2, w, h); + ecex_draw_color_rgba8(ctx, 213, 196, 161, 255); + ecex_draw_text(ctx, (float)(x + 10), (float)y, text); + break; + case 4: /* list */ + ecex_draw_color_rgba8(ctx, 184, 187, 38, 255); + ecex_draw_rect_i(ctx, x + 6, y + line_h / 2 - 3, 6, 6); + ecex_draw_color_rgba8(ctx, 235, 219, 178, 255); + ecex_draw_text(ctx, (float)(x + 24), (float)y, text); + break; + case 5: /* fence */ + ecex_draw_color_rgba8(ctx, 80, 73, 69, 255); + ecex_draw_rect_i(ctx, x, y - 3, w, line_h + 6); + ecex_draw_color_rgba8(ctx, 142, 192, 124, 255); + ecex_draw_text(ctx, (float)(x + 10), (float)y, text); + break; + case 6: /* hr */ + ecex_draw_color_rgba8(ctx, 80, 73, 69, 255); + ecex_draw_line_i(ctx, x, y + line_h / 2, x + w, y + line_h / 2, 2); + break; + case 0: + default: + ecex_draw_color_rgba8(ctx, 235, 219, 178, 255); + ecex_draw_text(ctx, (float)x, (float)y, text); + break; + } +} + +void ecex_draw_markdown_canvas_auto_i(ecex_draw_context_t *ctx, void *owner, int title_id) { + int x; + int y; + int w; + int line_h; + if (!ctx) return; + ecex_log("draw_markdown_canvas_auto: enter"); + x = (int)ctx->content_x; + y = (int)ctx->content_y; + w = (int)ctx->content_w; + line_h = (int)ctx->line_height; + if (line_h < 18) line_h = 18; + if (w < 1) w = (int)ctx->w - x * 2; + if (w < 1) w = 1; + ecex_log("draw_markdown_canvas_auto: dispatch"); + ecex_draw_markdown_canvas_i(ctx, owner, title_id, x, y, w, line_h); + ecex_log("draw_markdown_canvas_auto: leave"); +} + +void ecex_draw_markdown_line_auto_i(ecex_draw_context_t *ctx, void *owner, int text_id, int y, int style) { + int x; + int w; + int line_h; + if (!ctx) return; + x = (int)ctx->content_x; + w = (int)ctx->content_w; + line_h = (int)ctx->line_height; + if (line_h < 18) line_h = 18; + if (w < 1) w = (int)ctx->w - x * 2; + if (w < 1) w = 1; + ecex_draw_markdown_text_i(ctx, owner, text_id, x, y, w, line_h, style); +} + +void ecex_draw_label_i(ecex_draw_context_t *ctx, int x, int y, int label_id) { + ecex_draw_mini_text_i(ctx, x, y, ecex_draw_label_text(label_id)); +} + +static void ecex_i32_to_ascii(int value, char *buf, size_t cap) { + char tmp[16]; + size_t n = 0; + size_t out = 0; + unsigned int v; + if (!buf || cap == 0) return; + if (value < 0) { + if (out + 1 < cap) buf[out++] = '-'; + v = (unsigned int)(-value); + } else { + v = (unsigned int)value; + } + do { + tmp[n++] = (char)('0' + (v % 10u)); + v /= 10u; + } while (v && n < sizeof(tmp)); + while (n && out + 1 < cap) buf[out++] = tmp[--n]; + buf[out] = '\0'; +} + +void ecex_draw_stat_i(ecex_draw_context_t *ctx, int x, int y, int label_id, int value) { + char num[24]; + const char *prefix = ecex_draw_label_text(label_id); + if (!ctx || !prefix) return; + ecex_draw_mini_text_i(ctx, x, y, prefix); + ecex_i32_to_ascii(value, num, sizeof(num)); + ecex_draw_mini_text_i(ctx, x + (int)strlen(prefix) * 12, y, num); +} + + +static int ecex_tetris_preview_shape_cell(int piece, int rot, int col, int row) { + int p = piece % 7; + int r = rot & 3; + if (p < 0) p += 7; + if (col < 0 || col >= 4 || row < 0 || row >= 4) return 0; + + if (p == 0) { + if ((r & 1) == 0) return row == 1; + return col == 1; + } + if (p == 1) return (row == 1 || row == 2) && (col == 1 || col == 2); + if (p == 2) { + if (r == 0) return (row == 1 && col >= 0 && col <= 2) || (row == 2 && col == 1); + if (r == 1) return (col == 1 && row >= 0 && row <= 2) || (row == 1 && col == 2); + if (r == 2) return (row == 1 && col >= 0 && col <= 2) || (row == 0 && col == 1); + return (col == 1 && row >= 0 && row <= 2) || (row == 1 && col == 0); + } + if (p == 3) { + if ((r & 1) == 0) return (row == 1 && (col == 1 || col == 2)) || (row == 2 && (col == 0 || col == 1)); + return (col == 1 && (row == 0 || row == 1)) || (col == 2 && (row == 1 || row == 2)); + } + if (p == 4) { + if ((r & 1) == 0) return (row == 1 && (col == 0 || col == 1)) || (row == 2 && (col == 1 || col == 2)); + return (col == 2 && (row == 0 || row == 1)) || (col == 1 && (row == 1 || row == 2)); + } + if (p == 5) { + if (r == 0) return (row == 1 && col >= 0 && col <= 2) || (row == 0 && col == 0); + if (r == 1) return (col == 1 && row >= 0 && row <= 2) || (row == 0 && col == 2); + if (r == 2) return (row == 1 && col >= 0 && col <= 2) || (row == 2 && col == 2); + return (col == 1 && row >= 0 && row <= 2) || (row == 2 && col == 0); + } + if (r == 0) return (row == 1 && col >= 0 && col <= 2) || (row == 0 && col == 2); + if (r == 1) return (col == 1 && row >= 0 && row <= 2) || (row == 2 && col == 2); + if (r == 2) return (row == 1 && col >= 0 && col <= 2) || (row == 2 && col == 0); + return (col == 1 && row >= 0 && row <= 2) || (row == 0 && col == 0); +} + +static void ecex_draw_tetris_preview_color(ecex_draw_context_t *ctx, int piece, int alpha) { + if (piece == 0) ecex_draw_color_rgba8(ctx, 51, 191, 242, alpha); + else if (piece == 1) ecex_draw_color_rgba8(ctx, 242, 217, 51, alpha); + else if (piece == 2) ecex_draw_color_rgba8(ctx, 179, 89, 242, alpha); + else if (piece == 3) ecex_draw_color_rgba8(ctx, 77, 217, 89, alpha); + else if (piece == 4) ecex_draw_color_rgba8(ctx, 242, 64, 64, alpha); + else if (piece == 5) ecex_draw_color_rgba8(ctx, 64, 102, 242, alpha); + else ecex_draw_color_rgba8(ctx, 242, 140, 51, alpha); +} + +void ecex_draw_tetris_preview_i(ecex_draw_context_t *ctx, int piece, int x, int y, int cell, int alpha) { + int r; + int c; + int p = piece % 7; + if (!ctx) return; + if (p < 0) p += 7; + if (cell < 3) cell = 3; + if (alpha < 0) alpha = 0; + if (alpha > 255) alpha = 255; + + /* Clear a small preview box first so the old preview shape cannot linger + * when the new piece occupies fewer cells than the previous I piece. */ + ecex_draw_color_rgba8(ctx, 20, 23, 28, 255); + ecex_draw_rect_i(ctx, x - 2, y - 2, cell * 4 + 4, cell * 4 + 4); + + for (r = 0; r < 4; ++r) { + for (c = 0; c < 4; ++c) { + if (!ecex_tetris_preview_shape_cell(p, 0, c, r)) continue; + ecex_draw_tetris_preview_color(ctx, p, alpha); + ecex_draw_rect_i(ctx, x + c * cell + 1, y + r * cell + 1, cell - 2, cell - 2); + } + } +} + +void ecex_draw_rgba(ecex_draw_context_t *ctx, + float x, + float y, + float w, + float h, + const unsigned char *rgba, + int image_w, + int image_h) { + if (!ctx || !rgba || image_w <= 0 || image_h <= 0 || w <= 0.0f || h <= 0.0f) return; + + GLuint tex = 0; + glGenTextures(1, &tex); + if (tex == 0) return; + + glEnable(GL_TEXTURE_2D); + glBindTexture(GL_TEXTURE_2D, tex); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image_w, image_h, 0, GL_RGBA, GL_UNSIGNED_BYTE, rgba); + + glColor4f(1.0f, 1.0f, 1.0f, 1.0f); + glBegin(GL_QUADS); + glTexCoord2f(0.0f, 0.0f); draw_context_vertex(ctx, x, y); + glTexCoord2f(1.0f, 0.0f); draw_context_vertex(ctx, x + w, y); + glTexCoord2f(1.0f, 1.0f); draw_context_vertex(ctx, x + w, y + h); + glTexCoord2f(0.0f, 1.0f); draw_context_vertex(ctx, x, y + h); + glEnd(); + + glBindTexture(GL_TEXTURE_2D, 0); + glDeleteTextures(1, &tex); + glDisable(GL_TEXTURE_2D); +} + +static ecex_draw_context_t make_draw_context(app_t *app, view_rect_t rect, size_t index) { + ui_metrics_t m = ui_metrics(app); + ecex_draw_context_t ctx; + memset(&ctx, 0, sizeof(ctx)); + ctx.x = rect.x; + ctx.y = rect.y; + ctx.w = rect.w; + ctx.h = rect.h; + ctx.content_x = m.content_x; + ctx.content_y = m.content_top; + ctx.content_w = rect.w - m.content_x * 2.0f; + ctx.content_h = rect.h - m.content_top - m.content_bottom_pad; + if (ctx.content_w < 0.0f) ctx.content_w = 0.0f; + if (ctx.content_h < 0.0f) ctx.content_h = 0.0f; + ctx.font_size = app->font.size_px; + ctx.line_height = app->font.line_height; + ctx.char_width = mono_cell_width(app); + ctx.window_index = index; + ctx.active = index == app->ed->current_window_index; + ctx.internal = app; + return ctx; +} + +static void render_custom_buffer(app_t *app, buffer_t *buf, view_rect_t rect, size_t index) { + if (!app || !buf || !buf->render_fn) return; + ecex_draw_context_t ctx = make_draw_context(app, rect, index); + int trace_callbacks = 0; + const char *trace_env = getenv("ECEX_TRACE_CALLBACKS"); + trace_callbacks = trace_env && trace_env[0] && trace_env[0] != '0'; + if (trace_callbacks) { + fprintf(stderr, "ecex-log: render_callback_enter buffer=%p fn=%p userdata=%p window=%zu %.1fx%.1f\n", + (void *)buf, (void *)buf->render_fn, buf->render_userdata, index, rect.w, rect.h); + fflush(stderr); + } + int result = buf->render_fn(app->ed, buf, &ctx, buf->render_userdata); + if (trace_callbacks) { + fprintf(stderr, "ecex-log: render_callback_leave buffer=%p result=%d\n", + (void *)buf, result); + fflush(stderr); + } +} + static void render_buffer_window(app_t *app, ecex_window_t *win, size_t index, float editor_h) { if (!app || !win || !win->buffer) return; @@ -549,29 +1314,41 @@ static void render_buffer_window(app_t *app, ecex_window_t *win, size_t index, f ensure_cursor_visible(app, buf, rect); } - size_t rows = visible_row_count_for_rect(app, rect); - size_t pos = offset_for_line(buf, buf->scroll_line); - float line_top = rect.y + m.content_top; + int replace_content = buf->render_fn && (buf->render_flags & ECEX_RENDER_REPLACE_CONTENT); + + if (!replace_content) { + size_t rows = visible_row_count_for_rect(app, rect); + size_t pos = offset_for_line(buf, buf->scroll_line); + float line_top = rect.y + m.content_top; + + for (size_t row = 0; row < rows; row++) { + size_t line_start = pos; + size_t line_end = buffer_line_end_at(buf, line_start); - for (size_t row = 0; row < rows; row++) { - size_t line_start = pos; - size_t line_end = buffer_line_end_at(buf, line_start); + draw_buffer_line(app, + buf, + rect, + buf->scroll_line + row, + line_start, + line_end, + line_top); - draw_buffer_line(app, - buf, - rect, - buf->scroll_line + row, - line_start, - line_end, - line_top); + if (line_end >= buf->len) break; + pos = line_end + 1; + line_top += app->font.line_height; + } + + if (buf->media_kind != ECEX_MEDIA_NONE) { + draw_media_buffer(app, buf, rect); + } - if (line_end >= buf->len) break; - pos = line_end + 1; - line_top += app->font.line_height; + if (index == app->ed->current_window_index && buf->media_kind == ECEX_MEDIA_NONE) { + render_cursor(app, buf, rect); + } } - if (index == app->ed->current_window_index) { - render_cursor(app, buf, rect); + if (buf->render_fn) { + render_custom_buffer(app, buf, rect, index); } glDisable(GL_SCISSOR_TEST); -- cgit v1.2.3