#include "render.h" #include "common.h" #include "media.h" #include #include #include #include /* * All UI measurements are derived from the active font. draw_text() uses a * baseline y coordinate, so render.c keeps both line-top and baseline values * explicit. This avoids the old fixed 24/28/34 pixel constants drifting when * font size changes. */ typedef struct view_rect { float x; float y; float w; float h; } view_rect_t; typedef struct ui_metrics { float pad_x; float pad_y; float content_x; float content_top; float content_bottom_pad; float status_h; float minibuffer_h; float cursor_w; float cursor_h; } ui_metrics_t; static float ecex_maxf(float a, float b) { return a > b ? a : b; } static ui_metrics_t ui_metrics(app_t *app) { float size = app && app->font.size_px > 1.0f ? app->font.size_px : 16.0f; float line_h = app && app->font.line_height > 1.0f ? app->font.line_height : size * 1.20f; ui_metrics_t m; m.pad_x = ecex_maxf(8.0f, size * 0.75f); m.pad_y = ecex_maxf(4.0f, size * 0.35f); m.content_x = m.pad_x; m.content_top = m.pad_y; m.content_bottom_pad = m.pad_y; m.status_h = line_h + m.pad_y * 2.0f; m.minibuffer_h = line_h + m.pad_y * 2.0f; m.cursor_w = ecex_maxf(2.0f, size * 0.10f); m.cursor_h = ecex_maxf(1.0f, app ? app->font.ascent_px + app->font.descent_px : size); return m; } static float line_baseline(app_t *app, float line_top) { float ascent = app->font.ascent_px > 1.0f ? app->font.ascent_px : app->font.size_px * 0.80f; return line_top + ascent; } static void setup_2d(int width, int height) { glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho(0, width, height, 0, -1, 1); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } static void draw_rect(float x, float y, float w, float h) { glBegin(GL_QUADS); glVertex2f(x, y); glVertex2f(x + w, y); glVertex2f(x + w, y + h); glVertex2f(x, y + h); glEnd(); } static float mono_cell_width(app_t *app) { float w = text_width(&app->font, "M"); return w > 1.0f ? w : app->font.size_px * 0.6f; } static int minibuffer_visible(app_t *app) { return app->mode == APP_MODE_MX || app->mode == APP_MODE_PREFIX || app->mode == APP_MODE_PROMPT || app->mode == APP_MODE_ISEARCH || app->message[0] != '\0'; } static size_t visible_row_count_for_rect(app_t *app, view_rect_t r) { ui_metrics_t m = ui_metrics(app); float usable = r.h - m.content_top - m.content_bottom_pad; if (usable <= app->font.line_height || app->font.line_height <= 1.0f) { return 1; } size_t rows = (size_t)(usable / app->font.line_height); return rows ? rows : 1; } static float gutter_width(app_t *app, buffer_t *buf) { if (!app || !app->ed || !buf || !app->ed->theme.line_numbers_enabled) return 0.0f; char tmp[32]; snprintf(tmp, sizeof(tmp), "%zu ", buffer_line_count(buf)); return text_width(&app->font, tmp) + ui_metrics(app).pad_x * 0.5f; } static size_t visible_col_count_for_rect(app_t *app, view_rect_t r) { ui_metrics_t m = ui_metrics(app); float cell = mono_cell_width(app); buffer_t *buf = ecex_current_buffer(app->ed); float width = r.w - m.content_x * 2.0f - gutter_width(app, buf); if (width <= cell) return 1; return (size_t)(width / cell); } static size_t offset_for_line(buffer_t *buf, size_t target_line) { if (!buf || target_line == 0) return 0; size_t line = 0; for (size_t i = 0; i < buf->len; i++) { if (buf->data[i] == '\n') { line++; if (line == target_line) { return i + 1; } } } return buf->len; } static void ensure_cursor_visible(app_t *app, buffer_t *buf, view_rect_t rect) { if (!app || !buf) return; size_t rows = visible_row_count_for_rect(app, rect); size_t cols = visible_col_count_for_rect(app, rect); size_t cursor_line = buffer_current_line_number(buf); if (cursor_line > 0) cursor_line--; size_t cursor_col = buffer_current_column(buf); if (cursor_line < buf->scroll_line) { buf->scroll_line = cursor_line; } else if (cursor_line >= buf->scroll_line + rows) { buf->scroll_line = cursor_line - rows + 1; } if (cursor_col < buf->scroll_col) { buf->scroll_col = cursor_col; } else if (cursor_col >= buf->scroll_col + cols) { buf->scroll_col = cursor_col - cols + 1; } } static void render_status_bar(app_t *app) { buffer_t *buf = ecex_current_buffer(app->ed); if (!buf) return; ui_metrics_t m = ui_metrics(app); float y = (float)app->height - m.status_h; float text_x = m.pad_x; float text_y = line_baseline(app, y + m.pad_y); glDisable(GL_TEXTURE_2D); glColor3f(app->ed->theme.status_bg.r, app->ed->theme.status_bg.g, app->ed->theme.status_bg.b); draw_rect(0.0f, y, (float)app->width, m.status_h); glColor3f(app->ed->theme.status_border.r, app->ed->theme.status_border.g, app->ed->theme.status_border.b); draw_rect(0.0f, y, (float)app->width, 1.0f); char status[768]; snprintf(status, sizeof(status), " %s%s %s line:%zu col:%zu top:%zu size:%zu buffers:%zu windows:%zu commands:%zu%s%s", buf->name ? buf->name : "(unnamed)", buf->modified ? " *" : "", ecex_buffer_major_mode_name(app->ed, buf), buffer_current_line_number(buf), buffer_current_column(buf), buf->scroll_line + 1, buf->len, app->ed->buffer_count, ecex_window_count(app->ed), app->ed->command_count, buf->path ? " " : "", buf->path ? buf->path : ""); glColor3f(app->ed->theme.status_fg.r, app->ed->theme.status_fg.g, app->ed->theme.status_fg.b); draw_text(&app->font, text_x, text_y, status); } static void render_command_completion(app_t *app, const char *line, float x, float y) { const char *completion = ecex_complete_command(app->ed, app->minibuffer); if (!completion || !completion[0]) return; size_t input_len = strlen(app->minibuffer); if (input_len == 0) return; float ghost_x = x + text_width(&app->font, line); glColor3f(app->ed->theme.completion_fg.r, app->ed->theme.completion_fg.g, app->ed->theme.completion_fg.b); if (strncmp(completion, app->minibuffer, input_len) == 0 && completion[input_len] != '\0') { draw_text(&app->font, ghost_x, y, completion + input_len); } else if (strcmp(completion, app->minibuffer) != 0) { char hint[ECEX_MINIBUFFER_SIZE + 16]; snprintf(hint, sizeof(hint), " -> %s", completion); draw_text(&app->font, ghost_x, y, hint); } } static void render_minibuffer(app_t *app) { if (!minibuffer_visible(app)) { return; } ui_metrics_t m = ui_metrics(app); float h = m.minibuffer_h; float y = (float)app->height - m.status_h - h; float text_x = m.pad_x; float text_y = line_baseline(app, y + m.pad_y); glDisable(GL_TEXTURE_2D); glColor3f(app->ed->theme.minibuffer_bg.r, app->ed->theme.minibuffer_bg.g, app->ed->theme.minibuffer_bg.b); draw_rect(0.0f, y, (float)app->width, h); glColor3f(app->ed->theme.minibuffer_fg.r, app->ed->theme.minibuffer_fg.g, app->ed->theme.minibuffer_fg.b); if (app->mode == APP_MODE_ISEARCH) { char line[ECEX_MINIBUFFER_SIZE + 64]; snprintf(line, sizeof(line), "%s%s%s", app->isearch_backward ? "I-search backward: " : "I-search: ", app->isearch_query, app->isearch_has_match || app->isearch_len == 0 ? "" : " [failing]"); draw_text(&app->font, text_x, text_y, line); } else if (app->mode == APP_MODE_MX) { char line[ECEX_MINIBUFFER_SIZE + 8]; snprintf(line, sizeof(line), "M-x %s", app->minibuffer); draw_text(&app->font, text_x, text_y, line); render_command_completion(app, line, text_x, text_y); } else if (app->mode == APP_MODE_PROMPT) { char line[1200]; snprintf(line, sizeof(line), "%s%s", app->prompt_label, app->prompt_input); draw_text(&app->font, text_x, text_y, line); if (app->prompt_completion_active && app->prompt_completion_count > 0) { char hint[128]; snprintf(hint, sizeof(hint), " [%zu/%zu]", app->prompt_completion_index + 1, app->prompt_completion_count); glColor3f(app->ed->theme.completion_fg.r, app->ed->theme.completion_fg.g, app->ed->theme.completion_fg.b); draw_text(&app->font, text_x + text_width(&app->font, line), text_y, hint); if (app->prompt_completion_preview_count > 0) { float row_h = app->font.line_height; float popup_pad = m.pad_y; float popup_h = row_h * (float)app->prompt_completion_preview_count + popup_pad * 2.0f; float popup_y = y - popup_h; glDisable(GL_TEXTURE_2D); glColor3f(app->ed->theme.minibuffer_bg.r, app->ed->theme.minibuffer_bg.g, app->ed->theme.minibuffer_bg.b); draw_rect(0.0f, popup_y, (float)app->width, popup_h); for (size_t i = 0; i < app->prompt_completion_preview_count; i++) { size_t absolute = app->prompt_completion_preview_start + i; char row[1100]; snprintf(row, sizeof(row), "%c %s", absolute == app->prompt_completion_index ? '>' : ' ', app->prompt_completion_preview[i]); if (absolute == app->prompt_completion_index) { glColor3f(app->ed->theme.minibuffer_fg.r, app->ed->theme.minibuffer_fg.g, app->ed->theme.minibuffer_fg.b); } else { glColor3f(app->ed->theme.completion_fg.r, app->ed->theme.completion_fg.g, app->ed->theme.completion_fg.b); } draw_text(&app->font, text_x, line_baseline(app, popup_y + popup_pad + row_h * (float)i), row); } } } } else if (app->mode == APP_MODE_PREFIX) { char line[ECEX_PREFIX_SIZE + 8]; snprintf(line, sizeof(line), "%s-", app->prefix); draw_text(&app->font, text_x, text_y, line); } else { draw_text(&app->font, text_x, text_y, app->message); } } static int should_highlight_interactive_line(buffer_t *buf, size_t zero_based_line) { if (!buf || !buffer_is_interactive(buf)) return 0; size_t current_line = buffer_current_line_number(buf); if (current_line > 0) current_line--; return current_line == zero_based_line; } static void set_editor_text_color(app_t *app, int highlighted) { if (highlighted) { glColor3f(app->ed->theme.interactive_highlight_fg.r, app->ed->theme.interactive_highlight_fg.g, app->ed->theme.interactive_highlight_fg.b); } else { glColor3f(app->ed->theme.fg.r, app->ed->theme.fg.g, app->ed->theme.fg.b); } } static float text_width_range(app_t *app, buffer_t *buf, size_t start, size_t end) { char *text = buffer_substring(buf, start, end); if (!text) return 0.0f; float width = text_width(&app->font, text); free(text); return width; } static void draw_selection_for_line(app_t *app, buffer_t *buf, view_rect_t rect, size_t visible_start, size_t line_end, float line_top) { if (!buffer_has_selection(buf)) return; size_t sel_start = 0; size_t sel_end = 0; buffer_selection_range(buf, &sel_start, &sel_end); if (sel_end <= visible_start || sel_start > line_end) return; size_t draw_start = ECEX_MAX(sel_start, visible_start); size_t draw_end = ECEX_MIN(sel_end, line_end); float x = rect.x + ui_metrics(app).content_x + gutter_width(app, buf) + text_width_range(app, buf, visible_start, draw_start); float w = text_width_range(app, buf, draw_start, draw_end); /* If the selection contains only this line's newline, show a visible sliver * at end-of-line instead of making the active region appear to vanish. */ if (w < 1.0f && sel_end > line_end && draw_start == line_end) { w = mono_cell_width(app) * 0.5f; } if (w < 1.0f) return; glDisable(GL_TEXTURE_2D); glColor3f(app->ed->theme.region_bg.r, app->ed->theme.region_bg.g, app->ed->theme.region_bg.b); draw_rect(x, line_top, w, app->font.line_height); } static void draw_buffer_line(app_t *app, buffer_t *buf, view_rect_t rect, size_t zero_based_line, size_t line_start, size_t line_end, float line_top) { if (line_start > line_end) return; ui_metrics_t m = ui_metrics(app); int highlighted = should_highlight_interactive_line(buf, zero_based_line); size_t current_zero = buffer_current_line_number(buf); if (current_zero > 0) current_zero--; if (app->ed->theme.current_line_enabled && current_zero == zero_based_line) { glDisable(GL_TEXTURE_2D); glColor3f(app->ed->theme.current_line_bg.r, app->ed->theme.current_line_bg.g, app->ed->theme.current_line_bg.b); draw_rect(rect.x, line_top, rect.w, app->font.line_height); } if (highlighted) { glDisable(GL_TEXTURE_2D); glColor3f(app->ed->theme.interactive_highlight_bg.r, app->ed->theme.interactive_highlight_bg.g, app->ed->theme.interactive_highlight_bg.b); draw_rect(rect.x, line_top, rect.w, app->font.line_height); } size_t line_len = line_end - line_start; size_t col = ECEX_MIN(buf->scroll_col, line_len); size_t start = line_start + col; draw_selection_for_line(app, buf, rect, start, line_end, line_top); float gutter = gutter_width(app, buf); if (gutter > 0.0f) { char nbuf[32]; snprintf(nbuf, sizeof(nbuf), "%zu", zero_based_line + 1); glColor3f(app->ed->theme.completion_fg.r, app->ed->theme.completion_fg.g, app->ed->theme.completion_fg.b); draw_text(&app->font, rect.x + m.pad_x * 0.5f, line_baseline(app, line_top), nbuf); } char *line = buffer_substring(buf, start, line_end); if (!line) return; set_editor_text_color(app, highlighted); draw_text(&app->font, rect.x + m.content_x + gutter, line_baseline(app, line_top), line); 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); size_t cursor_line = buffer_current_line_number(buf); if (cursor_line > 0) cursor_line--; if (cursor_line < buf->scroll_line) return; size_t visible_row = cursor_line - buf->scroll_line; if (visible_row >= visible_row_count_for_rect(app, rect)) return; size_t line_start = buffer_current_line_start(buf); size_t cursor_col = buffer_current_column(buf); size_t visible_start = line_start + ECEX_MIN(buf->scroll_col, cursor_col); char *prefix = buffer_substring(buf, visible_start, buf->point); float x = rect.x + m.content_x + gutter_width(app, buf) + (prefix ? text_width(&app->font, prefix) : 0.0f); float line_top = rect.y + m.content_top + (float)visible_row * app->font.line_height; float cursor_top = line_top + (app->font.line_height - m.cursor_h) * 0.5f; free(prefix); glDisable(GL_TEXTURE_2D); glColor3f(app->ed->theme.cursor.r, app->ed->theme.cursor.g, app->ed->theme.cursor.b); draw_rect(x, cursor_top, m.cursor_w, m.cursor_h); } static void draw_window_border(app_t *app, view_rect_t rect, int active) { glDisable(GL_TEXTURE_2D); if (active) { glColor3f(app->ed->theme.cursor.r, app->ed->theme.cursor.g, app->ed->theme.cursor.b); } else { glColor3f(app->ed->theme.status_border.r, app->ed->theme.status_border.g, app->ed->theme.status_border.b); } draw_rect(rect.x, rect.y, rect.w, 1.0f); draw_rect(rect.x, rect.y + rect.h - 1.0f, rect.w, 1.0f); draw_rect(rect.x, rect.y, 1.0f, rect.h); 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; view_rect_t rect; rect.x = win->x * (float)app->width; rect.y = win->y * editor_h; rect.w = win->w * (float)app->width; rect.h = win->h * editor_h; if (rect.w < 2.0f || rect.h < 2.0f) return; buffer_t *buf = win->buffer; ui_metrics_t m = ui_metrics(app); int sx = (int)rect.x; int sy = app->height - (int)(rect.y + rect.h); int sw = (int)rect.w; int sh = (int)rect.h; if (sw < 1 || sh < 1) return; glEnable(GL_SCISSOR_TEST); glScissor(sx, sy, sw, sh); if (index == app->ed->current_window_index) { ensure_cursor_visible(app, buf, rect); } 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); 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 (index == app->ed->current_window_index && buf->media_kind == ECEX_MEDIA_NONE) { render_cursor(app, buf, rect); } } if (buf->render_fn) { render_custom_buffer(app, buf, rect, index); } glDisable(GL_SCISSOR_TEST); draw_window_border(app, rect, index == app->ed->current_window_index); } static void render_windows(app_t *app) { ui_metrics_t m = ui_metrics(app); float editor_h = (float)app->height - m.status_h; if (minibuffer_visible(app)) editor_h -= m.minibuffer_h; if (editor_h < 1.0f) editor_h = 1.0f; if (app->ed->window_count == 0) return; for (size_t i = 0; i < app->ed->window_count; i++) { render_buffer_window(app, &app->ed->windows[i], i, editor_h); } } void render(app_t *app) { if (!app || !app->window || !app->ed) return; glfwGetFramebufferSize(app->window, &app->width, &app->height); setup_2d(app->width, app->height); glClearColor(app->ed->theme.bg.r, app->ed->theme.bg.g, app->ed->theme.bg.b, 1.0f); glClear(GL_COLOR_BUFFER_BIT); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); render_windows(app); render_minibuffer(app); render_status_bar(app); }