aboutsummaryrefslogtreecommitdiff
path: root/src/render.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/render.c')
-rw-r--r--src/render.c813
1 files changed, 795 insertions, 18 deletions
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 <GLFW/glfw3.h>
#include <stdio.h>
@@ -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);