#include "ecex.h" #define RENDER_DEMO_BUF "*render-demo*" #define RENDER_DEMO_VAR_X "x_milli" #define RENDER_DEMO_VAR_Y "y_milli" #define RENDER_DEMO_VAR_TARGET_X "target_x_milli" #define RENDER_DEMO_VAR_TARGET_Y "target_y_milli" #define RENDER_DEMO_VAR_MOVING "moving" #define RENDER_DEMO_VAR_BOX_X "box_x" #define RENDER_DEMO_VAR_BOX_Y "box_y" #define RENDER_DEMO_VAR_BOX_SIZE "box_size" #define RENDER_DEMO_VAR_TRAVEL_X "travel_x" #define RENDER_DEMO_VAR_TRAVEL_Y "travel_y" #define RENDER_DEMO_VAR_AREA_X "area_x" #define RENDER_DEMO_VAR_AREA_Y "area_y" #define RENDER_DEMO_LABEL_TITLE 20 #define RENDER_DEMO_LABEL_SUBTITLE 21 #define RENDER_DEMO_LABEL_SAFE_DRAW 22 #define RENDER_DEMO_LABEL_ANIM 23 #define RENDER_DEMO_LABEL_POSITION 24 #define RENDER_DEMO_LABEL_CLICK 25 typedef struct render_demo_state { ecex_t *ed; int log_draw_count; } render_demo_state_t; static int render_demo_get(ecex_t *ed, render_demo_state_t *s, const char *name, int fallback) { if (!ed || !s || !name) return fallback; return ecex_var_i32(ed, s, name, fallback); } static void render_demo_set(ecex_t *ed, render_demo_state_t *s, const char *name, int value) { if (!ed || !s || !name) return; ecex_var_i32_set_scalar(ed, s, name, value); } static void render_demo_reset(ecex_t *ed, render_demo_state_t *s) { ecex_log_ptr("render_demo_reset: state=", s); if (!ed || !s) return; /* Store mutable demo state in the host variable registry. This mirrors the * Tetris plugin flow and avoids relying on CCDJIT struct-field writes for * values that must survive renderer, animation, and mouse callbacks. * Values are fixed-point integers: 0..1000 represents the available travel * range inside the demo frame. */ render_demo_set(ed, s, RENDER_DEMO_VAR_X, 0); render_demo_set(ed, s, RENDER_DEMO_VAR_Y, 500); render_demo_set(ed, s, RENDER_DEMO_VAR_TARGET_X, 0); render_demo_set(ed, s, RENDER_DEMO_VAR_TARGET_Y, 500); render_demo_set(ed, s, RENDER_DEMO_VAR_MOVING, 0); s->log_draw_count = 0; } static int render_demo_draw(ecex_t *ed, buffer_t *buffer, ecex_draw_context_t *ctx, void *userdata) { render_demo_state_t *s = (render_demo_state_t *)userdata; int w; int h; int cx; int cy; int cw; int ch; int line_h; int box; int travel; int x_milli; int y_milli; int box_x; int box_y; int travel_y; int area_x; int area_y; (void)buffer; if (!ed || !ctx || !s) return 0; s->log_draw_count++; if (s->log_draw_count <= 4 || (s->log_draw_count % 60) == 0) { ecex_log_int("render_demo_draw: count=", s->log_draw_count); } w = (int)ctx->w; h = (int)ctx->h; cx = (int)ctx->content_x; cy = (int)ctx->content_y; cw = (int)ctx->content_w; ch = (int)ctx->content_h; line_h = (int)ctx->line_height; if (line_h < 18) line_h = 18; if (cw < 1) cw = w - cx * 2; if (ch < 1) ch = h - cy * 2; if (cw < 1) cw = 1; if (ch < 1) ch = 1; ecex_draw_color_rgba8(ctx, 26, 28, 36, 255); ecex_draw_rect_i(ctx, 0, 0, w, h); ecex_draw_color_rgba8(ctx, 48, 96, 180, 255); ecex_draw_rect_i(ctx, cx, cy, (cw * 55) / 100, line_h * 2); ecex_draw_color_rgba8(ctx, 242, 230, 191, 255); ecex_draw_rect_outline_i(ctx, cx, cy, cw, ch, 2); ecex_draw_label_i(ctx, cx + 12, cy + 10, RENDER_DEMO_LABEL_TITLE); ecex_draw_color_rgba8(ctx, 229, 89, 64, 255); ecex_draw_line_i(ctx, cx, cy + ch, cx + cw, cy, 3); ecex_draw_color_rgba8(ctx, 160, 230, 180, 255); ecex_draw_label_i(ctx, cx + 12, cy + line_h * 3, RENDER_DEMO_LABEL_SUBTITLE); ecex_draw_label_i(ctx, cx + 12, cy + line_h * 4, RENDER_DEMO_LABEL_SAFE_DRAW); ecex_draw_label_i(ctx, cx + 12, cy + line_h * 5, RENDER_DEMO_LABEL_ANIM); ecex_draw_label_i(ctx, cx + 12, cy + line_h * 6, RENDER_DEMO_LABEL_CLICK); x_milli = render_demo_get(ed, s, RENDER_DEMO_VAR_X, 0); y_milli = render_demo_get(ed, s, RENDER_DEMO_VAR_Y, 500); ecex_draw_stat_i(ctx, cx + 12, cy + line_h * 8, RENDER_DEMO_LABEL_POSITION, x_milli); box = line_h * 2; if (box < 36) box = 36; if (box > 72) box = 72; travel = cw - box - 24; if (travel < 0) travel = 0; area_x = cx + 12; area_y = cy + line_h * 10; travel_y = ch - (area_y - cy) - box - 12; if (travel_y < 0) travel_y = 0; if (x_milli < 0) x_milli = 0; if (x_milli > 1000) x_milli = 1000; if (y_milli < 0) y_milli = 0; if (y_milli > 1000) y_milli = 1000; box_x = area_x + (travel * x_milli) / 1000; box_y = area_y + (travel_y * y_milli) / 1000; render_demo_set(ed, s, RENDER_DEMO_VAR_BOX_X, box_x); render_demo_set(ed, s, RENDER_DEMO_VAR_BOX_Y, box_y); render_demo_set(ed, s, RENDER_DEMO_VAR_BOX_SIZE, box); render_demo_set(ed, s, RENDER_DEMO_VAR_TRAVEL_X, travel); render_demo_set(ed, s, RENDER_DEMO_VAR_TRAVEL_Y, travel_y); render_demo_set(ed, s, RENDER_DEMO_VAR_AREA_X, area_x); render_demo_set(ed, s, RENDER_DEMO_VAR_AREA_Y, area_y); ecex_draw_color_rgba8(ctx, 70, 76, 90, 255); ecex_draw_rect_outline_i(ctx, area_x, area_y, travel + box, travel_y + box, 1); ecex_draw_color_rgba8(ctx, 89, 242, 140, 255); ecex_draw_rect_i(ctx, box_x, box_y, box, box); ecex_draw_color_rgba8(ctx, 20, 24, 30, 255); ecex_draw_rect_outline_i(ctx, box_x, box_y, box, box, 2); return 0; } static int render_demo_clamp_milli(int value) { if (value < 0) return 0; if (value > 1000) return 1000; return value; } static int render_demo_delta_step(int current, int target) { int delta = target - current; int step; if (delta < 0) { if (delta >= -4) return target; step = delta / 5; if (step == 0) step = -1; return current + step; } if (delta <= 4) return target; step = delta / 5; if (step == 0) step = 1; return current + step; } static int render_demo_tick(ecex_t *ed, buffer_t *buffer, int now_ms, void *userdata) { render_demo_state_t *s = (render_demo_state_t *)userdata; int x; int y; int target_x; int target_y; int next_x; int next_y; (void)buffer; (void)now_ms; if (!ed || !s) return 0; if (!render_demo_get(ed, s, RENDER_DEMO_VAR_MOVING, 0)) return 0; x = render_demo_get(ed, s, RENDER_DEMO_VAR_X, 0); y = render_demo_get(ed, s, RENDER_DEMO_VAR_Y, 500); target_x = render_demo_get(ed, s, RENDER_DEMO_VAR_TARGET_X, x); target_y = render_demo_get(ed, s, RENDER_DEMO_VAR_TARGET_Y, y); next_x = render_demo_delta_step(x, target_x); next_y = render_demo_delta_step(y, target_y); render_demo_set(ed, s, RENDER_DEMO_VAR_X, render_demo_clamp_milli(next_x)); render_demo_set(ed, s, RENDER_DEMO_VAR_Y, render_demo_clamp_milli(next_y)); if (next_x == target_x && next_y == target_y) { render_demo_set(ed, s, RENDER_DEMO_VAR_MOVING, 0); ecex_log("render_demo_tick: target reached"); } return 1; } static int render_demo_mouse(ecex_t *ed, buffer_t *buffer, int event, int x, int y, int button, void *userdata) { render_demo_state_t *s = (render_demo_state_t *)userdata; int box; int area_x; int area_y; int travel_x; int travel_y; int nx; int ny; (void)buffer; if (!ed || !s || button != ECEX_MOUSE_BUTTON_LEFT) return 0; if (event != ECEX_MOUSE_PRESS) return 0; box = render_demo_get(ed, s, RENDER_DEMO_VAR_BOX_SIZE, 0); area_x = render_demo_get(ed, s, RENDER_DEMO_VAR_AREA_X, 0); area_y = render_demo_get(ed, s, RENDER_DEMO_VAR_AREA_Y, 0); travel_x = render_demo_get(ed, s, RENDER_DEMO_VAR_TRAVEL_X, 0); travel_y = render_demo_get(ed, s, RENDER_DEMO_VAR_TRAVEL_Y, 0); if (box <= 0) return 0; if (x < area_x || x >= area_x + travel_x + box || y < area_y || y >= area_y + travel_y + box) { return 0; } nx = x - area_x - box / 2; ny = y - area_y - box / 2; if (travel_x <= 0) render_demo_set(ed, s, RENDER_DEMO_VAR_TARGET_X, 0); else render_demo_set(ed, s, RENDER_DEMO_VAR_TARGET_X, render_demo_clamp_milli((nx * 1000) / travel_x)); if (travel_y <= 0) render_demo_set(ed, s, RENDER_DEMO_VAR_TARGET_Y, 0); else render_demo_set(ed, s, RENDER_DEMO_VAR_TARGET_Y, render_demo_clamp_milli((ny * 1000) / travel_y)); render_demo_set(ed, s, RENDER_DEMO_VAR_MOVING, 1); ecex_log("render_demo_mouse: target set"); return 1; } static void render_demo_free_state(void *userdata) { render_demo_state_t *s = (render_demo_state_t *)userdata; if (!s) return; ecex_var_free_owner(s->ed, s); ecex_config_free(s); } static int cmd_render_demo(ecex_t *ed) { buffer_t *buffer; render_demo_state_t *s; ecex_log("cmd_render_demo: enter"); if (!ed) return -1; buffer = ecex_find_buffer(ed, RENDER_DEMO_BUF); if (!buffer) buffer = ecex_create_interactive_buffer(ed, RENDER_DEMO_BUF); if (!buffer) return -1; s = (render_demo_state_t *)ecex_buffer_renderer_userdata(buffer); if (!s) { s = (render_demo_state_t *)ecex_config_calloc(1, sizeof(*s)); if (!s) return -1; s->ed = ed; render_demo_reset(ed, s); if (ecex_buffer_set_renderer(buffer, render_demo_draw, s, render_demo_free_state, ECEX_RENDER_REPLACE_CONTENT) != 0) { render_demo_free_state(s); return -1; } if (ecex_buffer_set_mouse_handler(buffer, render_demo_mouse, s, 0) != 0) { ecex_buffer_clear_renderer(buffer); return -1; } if (ecex_buffer_set_animation_ms(buffer, render_demo_tick, s, 0, 60) != 0) { ecex_buffer_clear_mouse_handler(buffer); ecex_buffer_clear_renderer(buffer); return -1; } } if (ecex_buffer_replace_text(buffer, "Render demo. The renderer replaces normal buffer content.\n") != 0) { return -1; } ecex_buffer_set_modified(buffer, 0); return ecex_switch_buffer(ed, RENDER_DEMO_BUF); } int ecex_render_demo_plugin(ecex_t *ed) { ECEX_CONFIG_COMMAND("render-demo", cmd_render_demo); return 0; } #ifndef ECEX_NO_STANDALONE_CONFIG ECEX_CONFIG_BEGIN ECEX_CONFIG_INCLUDE(ecex_render_demo_plugin); ECEX_CONFIG_END #endif