Files
el/lang/el-compiler/runtime/el_win32.c

1364 lines
49 KiB
C

/*
* el_win32.c — Win32 backend for the el native widget system.
*
* This file implements the Windows Win32 widget layer that el_seed.c calls
* through to when EL_TARGET_WIN32 is defined.
*
* Architecture:
* el program (el code)
* → __widget_* C builtins in el_seed.c
* → el_win32_* C functions declared here
* → HWND/Win32 API / GDI
*
* Widget handles: every widget (window, view, control) is assigned an int64_t
* slot index into _el_widgets[]. The el program holds these as opaque Int values.
* Slot 0 is never valid (reserved; handle -1 = invalid).
*
* Threading: Win32 UI must run on the main thread. el is single-threaded for
* native UI, so all calls are direct. The message loop runs on the main thread
* and never returns.
*
* Callback mechanism: when a widget fires an event (button click, text change),
* the bridge looks up the registered callback function name, then calls
* GetProcAddress(GetModuleHandle(NULL), fn_name)(widget_handle, event_data)
* This resolves the El function symbol at runtime, matching el_seed.c's
* dlsym(RTLD_DEFAULT, fn_name) pattern on POSIX.
*
* Layout: VStack and HStack containers maintain a child HWND list and reflow
* children on WM_SIZE using SetWindowPos. ZStack uses absolute positioning.
*
* Subclassing: SetWindowSubclass (comctl32) is used to intercept WM_COMMAND
* and WM_NOTIFY on container windows so button clicks and edit changes route
* back to el callbacks.
*
* String encoding: el strings are UTF-8 (const char*). All Win32 text APIs
* use wide strings (WCHAR*). Conversion via MultiByteToWideChar /
* WideCharToMultiByte throughout.
*
* Compile:
* cl /DEL_TARGET_WIN32 el_win32.c /c /Fo:el_win32.obj
* (link with: comctl32.lib user32.lib gdi32.lib)
* or with clang-cl:
* clang-cl /DEL_TARGET_WIN32 -c el_win32.c -o el_win32.obj
*/
#ifdef EL_TARGET_WIN32
#define WIN32_LEAN_AND_MEAN
#ifndef UNICODE
#define UNICODE
#endif
#ifndef _UNICODE
#define _UNICODE
#endif
#include <windows.h>
#include <commctrl.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include "el_runtime.h"
/* ── Compile-time linkage ─────────────────────────────────────────────────── */
#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "user32.lib")
#pragma comment(lib, "gdi32.lib")
/* ── Custom window messages ───────────────────────────────────────────────── */
/* WM_USER+1: posted to a container to trigger a layout reflow pass. */
#define WM_EL_REFLOW (WM_USER + 1)
/* ── Widget type enum ─────────────────────────────────────────────────────── */
#define EL_WIN32_MAX_WIDGETS 4096
#define EL_WIN32_MAX_CHILDREN 256
typedef enum {
EL_WIDGET_FREE = 0,
EL_WIDGET_WINDOW = 1,
EL_WIDGET_VSTACK = 2,
EL_WIDGET_HSTACK = 3,
EL_WIDGET_ZSTACK = 4,
EL_WIDGET_SCROLL = 5,
EL_WIDGET_LABEL = 6,
EL_WIDGET_BUTTON = 7,
EL_WIDGET_TEXTFIELD = 8,
EL_WIDGET_TEXTAREA = 9,
EL_WIDGET_IMAGE = 10,
} ElWidgetKind;
/* Per-slot data. */
typedef struct {
ElWidgetKind kind;
HWND hwnd; /* primary window handle; NULL if free */
char* cb_click; /* El function name for click / submit events */
char* cb_change; /* El function name for value-change events */
/* Layout data (used by VSTACK / HSTACK / ZSTACK / WINDOW containers) */
int spacing; /* pixels between children */
int pad_top;
int pad_right;
int pad_bottom;
int pad_left;
int flex; /* flex weight; 0 = fixed size */
/* Fixed size overrides (0 = not set) */
int fixed_w;
int fixed_h;
/* Child list for layout containers */
int64_t children[EL_WIN32_MAX_CHILDREN];
int child_count;
/* Color state (stored for WM_CTLCOLOR* replies) */
COLORREF fg_color;
COLORREF bg_color;
HBRUSH bg_brush;
int has_fg;
int has_bg;
/* Font */
HFONT font; /* NULL = default system font */
/* Image: HBITMAP loaded for SS_BITMAP static */
HBITMAP hbmp;
} ElWidget;
static ElWidget _el_widgets[EL_WIN32_MAX_WIDGETS];
static int _el_widget_count = 0;
/* ── Window class names ───────────────────────────────────────────────────── */
static const WCHAR* EL_WNDCLASS_VSTACK = L"ElVStack";
static const WCHAR* EL_WNDCLASS_HSTACK = L"ElHStack";
static const WCHAR* EL_WNDCLASS_ZSTACK = L"ElZStack";
static const WCHAR* EL_WNDCLASS_SCROLL = L"ElScroll";
static const WCHAR* EL_WNDCLASS_WINDOW = L"ElWindow";
/* ── Forward declarations ─────────────────────────────────────────────────── */
static void el_win32_reflow(int64_t container_handle);
static LRESULT CALLBACK el_container_wndproc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
static LRESULT CALLBACK el_toplevel_wndproc(HWND hwnd, UINT msg, WPARAM wp, LPARAM lp);
/* ── String helpers ───────────────────────────────────────────────────────── */
/* UTF-8 → WCHAR*. Caller must free(). Returns NULL on failure. */
static WCHAR* el_utf8_to_wide(const char* s) {
if (!s) s = "";
int n = MultiByteToWideChar(CP_UTF8, 0, s, -1, NULL, 0);
if (n <= 0) return _wcsdup(L"");
WCHAR* buf = (WCHAR*)malloc((size_t)n * sizeof(WCHAR));
if (!buf) return _wcsdup(L"");
MultiByteToWideChar(CP_UTF8, 0, s, -1, buf, n);
return buf;
}
/* WCHAR* → UTF-8 char*. Caller must free(). Returns NULL on failure. */
static char* el_wide_to_utf8(const WCHAR* ws) {
if (!ws) return strdup("");
int n = WideCharToMultiByte(CP_UTF8, 0, ws, -1, NULL, 0, NULL, NULL);
if (n <= 0) return strdup("");
char* buf = (char*)malloc((size_t)n);
if (!buf) return strdup("");
WideCharToMultiByte(CP_UTF8, 0, ws, -1, buf, n, NULL, NULL);
return buf;
}
/* ── Slot management ──────────────────────────────────────────────────────── */
static int64_t el_widget_alloc(ElWidgetKind kind, HWND hwnd) {
for (int i = 1; i < EL_WIN32_MAX_WIDGETS; i++) {
if (_el_widgets[i].kind == EL_WIDGET_FREE) {
ElWidget* w = &_el_widgets[i];
memset(w, 0, sizeof(ElWidget));
w->kind = kind;
w->hwnd = hwnd;
if (i > _el_widget_count) _el_widget_count = i;
return (int64_t)i;
}
}
return -1;
}
static ElWidget* el_widget_get(int64_t handle) {
if (handle <= 0 || handle >= EL_WIN32_MAX_WIDGETS) return NULL;
if (_el_widgets[handle].kind == EL_WIDGET_FREE) return NULL;
return &_el_widgets[handle];
}
static void el_widget_free_slot(int64_t handle) {
ElWidget* w = el_widget_get(handle);
if (!w) return;
free(w->cb_click); w->cb_click = NULL;
free(w->cb_change); w->cb_change = NULL;
if (w->bg_brush) { DeleteObject(w->bg_brush); w->bg_brush = NULL; }
if (w->font) { DeleteObject(w->font); w->font = NULL; }
if (w->hbmp) { DeleteObject(w->hbmp); w->hbmp = NULL; }
w->hwnd = NULL;
w->kind = EL_WIDGET_FREE;
}
/* Map HWND → widget slot (O(n) linear scan; table is small). */
static int64_t el_hwnd_to_slot(HWND hwnd) {
for (int i = 1; i <= _el_widget_count; i++) {
if (_el_widgets[i].kind != EL_WIDGET_FREE && _el_widgets[i].hwnd == hwnd)
return (int64_t)i;
}
return -1;
}
/* ── El callback invocation ──────────────────────────────────────────────── */
/*
* Invoke an El callback by symbol name. El functions have the signature:
* fn handler(handle: Int, data: String) -> Void
* compiled to: void fn_name(el_val_t handle, el_val_t data)
*/
typedef void (*ElCb2)(int64_t handle, int64_t data);
static void el_win32_invoke_cb(const char* fn_name, int64_t handle, const char* data) {
if (!fn_name || !*fn_name) return;
FARPROC proc = GetProcAddress(GetModuleHandleW(NULL), fn_name);
if (!proc) return;
ElCb2 fn = (ElCb2)(void*)proc;
fn(handle, (int64_t)(uintptr_t)(data ? data : ""));
}
/* ── Layout reflow ────────────────────────────────────────────────────────── */
/*
* Reflow the direct children of a container widget.
*
* For VSTACK: stack children top-to-bottom, respecting padding and spacing.
* Children with flex > 0 divide the leftover height proportionally.
* Children with fixed_h > 0 use that height; otherwise QueryWidget.
*
* For HSTACK: same logic, left-to-right.
*
* For WINDOW: its content area is the client rect; children stacked vertically
* (WINDOW acts like an outer VSTACK for direct child attachment purposes).
*
* For ZSTACK: children fill the entire client rect (absolute; no reflow).
*
* For SCROLL: the single child fills the client rect width; height is its
* natural height (scroll bar handles overflow).
*/
static void el_win32_reflow(int64_t handle) {
ElWidget* pw = el_widget_get(handle);
if (!pw || !pw->hwnd) return;
RECT cr;
GetClientRect(pw->hwnd, &cr);
int total_w = cr.right - cr.left;
int total_h = cr.bottom - cr.top;
int pt = pw->pad_top;
int pr = pw->pad_right;
int pb = pw->pad_bottom;
int pl = pw->pad_left;
int sp = pw->spacing;
int n = pw->child_count;
if (n == 0) return;
if (pw->kind == EL_WIDGET_ZSTACK) {
/* All children fill the client rect. */
int cw = total_w - pl - pr;
int ch = total_h - pt - pb;
if (cw < 0) cw = 0;
if (ch < 0) ch = 0;
for (int i = 0; i < n; i++) {
ElWidget* cw_ = el_widget_get(pw->children[i]);
if (!cw_ || !cw_->hwnd) continue;
SetWindowPos(cw_->hwnd, NULL, pl, pt, cw, ch,
SWP_NOZORDER | SWP_NOACTIVATE);
}
return;
}
if (pw->kind == EL_WIDGET_SCROLL) {
/* Single child fills width; natural height. */
if (n < 1) return;
ElWidget* cw_ = el_widget_get(pw->children[0]);
if (!cw_ || !cw_->hwnd) return;
int cw = total_w - pl - pr;
if (cw < 0) cw = 0;
/* Measure natural height. */
RECT cr2;
GetWindowRect(cw_->hwnd, &cr2);
int ch = cr2.bottom - cr2.top;
if (cw_->fixed_h > 0) ch = cw_->fixed_h;
SetWindowPos(cw_->hwnd, NULL, pl, pt, cw, ch,
SWP_NOZORDER | SWP_NOACTIVATE);
/* Update scroll range. */
SCROLLINFO si;
si.cbSize = sizeof(si);
si.fMask = SIF_RANGE | SIF_PAGE;
si.nMin = 0;
si.nMax = ch + pt + pb;
si.nPage = (UINT)total_h;
SetScrollInfo(pw->hwnd, SB_VERT, &si, TRUE);
return;
}
/* VSTACK, HSTACK, WINDOW */
int is_vert = (pw->kind == EL_WIDGET_VSTACK ||
pw->kind == EL_WIDGET_WINDOW);
/* First pass: determine fixed sizes and total flex weight. */
int total_flex = 0;
int fixed_used = 0; /* pixels consumed by fixed children + spacing */
/* Per-child resolved size in the main axis. */
int* sizes = (int*)malloc((size_t)n * sizeof(int));
if (!sizes) return;
for (int i = 0; i < n; i++) {
ElWidget* cw_ = el_widget_get(pw->children[i]);
sizes[i] = 0;
if (!cw_ || !cw_->hwnd) continue;
int fixed = is_vert ? cw_->fixed_h : cw_->fixed_w;
if (fixed > 0) {
sizes[i] = fixed;
fixed_used += fixed;
} else if (cw_->flex > 0) {
total_flex += cw_->flex;
} else {
/* No flex, no fixed: measure current window size as default. */
RECT wr;
GetWindowRect(cw_->hwnd, &wr);
int nat = is_vert ? (wr.bottom - wr.top) : (wr.right - wr.left);
if (nat < 1) nat = is_vert ? 24 : 80; /* reasonable fallback */
sizes[i] = nat;
fixed_used += nat;
}
if (i < n - 1) fixed_used += sp; /* spacing between children */
}
/* Available space for flex children. */
int avail = (is_vert ? (total_h - pt - pb) : (total_w - pl - pr)) - fixed_used;
if (avail < 0) avail = 0;
/* Second pass: assign flex sizes. */
if (total_flex > 0) {
for (int i = 0; i < n; i++) {
ElWidget* cw_ = el_widget_get(pw->children[i]);
if (!cw_ || !cw_->hwnd) continue;
if (cw_->flex > 0 && sizes[i] == 0) {
sizes[i] = (avail * cw_->flex) / total_flex;
}
}
}
/* Third pass: position children. */
int cursor = is_vert ? pt : pl;
for (int i = 0; i < n; i++) {
ElWidget* cw_ = el_widget_get(pw->children[i]);
if (!cw_ || !cw_->hwnd) continue;
int x, y, cw, ch;
if (is_vert) {
x = pl;
y = cursor;
cw = (cw_->fixed_w > 0) ? cw_->fixed_w : (total_w - pl - pr);
ch = sizes[i];
if (cw < 0) cw = 0;
if (ch < 0) ch = 0;
cursor += ch + sp;
} else {
x = cursor;
y = pt;
cw = sizes[i];
ch = (cw_->fixed_h > 0) ? cw_->fixed_h : (total_h - pt - pb);
if (cw < 0) cw = 0;
if (ch < 0) ch = 0;
cursor += cw + sp;
}
SetWindowPos(cw_->hwnd, NULL, x, y, cw, ch,
SWP_NOZORDER | SWP_NOACTIVATE);
/* Recursively reflow child containers. */
if (cw_->kind == EL_WIDGET_VSTACK ||
cw_->kind == EL_WIDGET_HSTACK ||
cw_->kind == EL_WIDGET_ZSTACK ||
cw_->kind == EL_WIDGET_SCROLL ||
cw_->kind == EL_WIDGET_WINDOW) {
el_win32_reflow(pw->children[i]);
}
}
free(sizes);
}
/* ── Container WndProc ────────────────────────────────────────────────────── */
/*
* Used for VSTACK, HSTACK, ZSTACK, SCROLL containers.
* Handles WM_SIZE (reflow), WM_CTLCOLOR* (color theming), WM_COMMAND (events),
* WM_VSCROLL (scroll container).
*/
static LRESULT CALLBACK el_container_wndproc(HWND hwnd, UINT msg,
WPARAM wp, LPARAM lp) {
int64_t slot = el_hwnd_to_slot(hwnd);
ElWidget* w = (slot >= 0) ? el_widget_get(slot) : NULL;
switch (msg) {
case WM_SIZE:
if (w) el_win32_reflow(slot);
return 0;
case WM_EL_REFLOW:
if (w) el_win32_reflow(slot);
return 0;
case WM_VSCROLL: {
if (!w || w->kind != EL_WIDGET_SCROLL) break;
SCROLLINFO si;
si.cbSize = sizeof(si);
si.fMask = SIF_ALL;
GetScrollInfo(hwnd, SB_VERT, &si);
int pos = si.nPos;
switch (LOWORD(wp)) {
case SB_LINEUP: pos -= 20; break;
case SB_LINEDOWN: pos += 20; break;
case SB_PAGEUP: pos -= si.nPage; break;
case SB_PAGEDOWN: pos += si.nPage; break;
case SB_THUMBTRACK: pos = si.nTrackPos; break;
case SB_TOP: pos = si.nMin; break;
case SB_BOTTOM: pos = si.nMax; break;
}
if (pos < si.nMin) pos = si.nMin;
if (pos > si.nMax - (int)si.nPage) pos = si.nMax - (int)si.nPage;
si.fMask = SIF_POS;
si.nPos = pos;
SetScrollInfo(hwnd, SB_VERT, &si, TRUE);
/* Scroll child. */
if (w->child_count > 0) {
ElWidget* cw_ = el_widget_get(w->children[0]);
if (cw_ && cw_->hwnd) {
RECT cr; GetClientRect(hwnd, &cr);
SetWindowPos(cw_->hwnd, NULL, 0, -pos,
cr.right - cr.left, 0,
SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
}
}
return 0;
}
case WM_COMMAND: {
/* Route click / change notifications to el callbacks. */
HWND ctrl = (HWND)lp;
if (!ctrl) break;
int64_t cs = el_hwnd_to_slot(ctrl);
ElWidget* cw = (cs >= 0) ? el_widget_get(cs) : NULL;
if (!cw) break;
WORD notif = HIWORD(wp);
if (cw->kind == EL_WIDGET_BUTTON && notif == BN_CLICKED) {
el_win32_invoke_cb(cw->cb_click, cs, "");
} else if (cw->kind == EL_WIDGET_TEXTFIELD) {
if (notif == EN_CHANGE) {
el_win32_invoke_cb(cw->cb_change, cs, "");
}
} else if (cw->kind == EL_WIDGET_TEXTAREA) {
if (notif == EN_CHANGE) {
el_win32_invoke_cb(cw->cb_change, cs, "");
}
}
/* Propagate to parent so top-level window also sees it. */
HWND parent = GetParent(hwnd);
if (parent) SendMessageW(parent, msg, wp, lp);
return 0;
}
case WM_CTLCOLORSTATIC:
case WM_CTLCOLOREDIT:
case WM_CTLCOLORBTN: {
HWND ctrl = (HWND)lp;
int64_t cs = el_hwnd_to_slot(ctrl);
ElWidget* cw = (cs >= 0) ? el_widget_get(cs) : NULL;
if (cw) {
HDC hdc = (HDC)wp;
if (cw->has_fg) SetTextColor(hdc, cw->fg_color);
if (cw->has_bg) {
SetBkColor(hdc, cw->bg_color);
if (cw->bg_brush) return (LRESULT)cw->bg_brush;
}
}
break;
}
case WM_ERASEBKGND: {
if (w && w->has_bg && w->bg_brush) {
HDC hdc = (HDC)wp;
RECT rc;
GetClientRect(hwnd, &rc);
FillRect(hdc, &rc, w->bg_brush);
return 1;
}
break;
}
case WM_DESTROY:
return 0;
}
return DefWindowProcW(hwnd, msg, wp, lp);
}
/* ── Top-level window WndProc ────────────────────────────────────────────── */
static LRESULT CALLBACK el_toplevel_wndproc(HWND hwnd, UINT msg,
WPARAM wp, LPARAM lp) {
int64_t slot = el_hwnd_to_slot(hwnd);
ElWidget* w = (slot >= 0) ? el_widget_get(slot) : NULL;
switch (msg) {
case WM_SIZE:
if (w) el_win32_reflow(slot);
return 0;
case WM_EL_REFLOW:
if (w) el_win32_reflow(slot);
return 0;
case WM_COMMAND: {
/* Bubble COMMAND messages from child controls. */
HWND ctrl = (HWND)lp;
if (!ctrl) break;
int64_t cs = el_hwnd_to_slot(ctrl);
ElWidget* cw = (cs >= 0) ? el_widget_get(cs) : NULL;
if (!cw) {
/* Not a direct slot child — pass to DefWindowProc. */
break;
}
WORD notif = HIWORD(wp);
if (cw->kind == EL_WIDGET_BUTTON && notif == BN_CLICKED) {
el_win32_invoke_cb(cw->cb_click, cs, "");
} else if ((cw->kind == EL_WIDGET_TEXTFIELD ||
cw->kind == EL_WIDGET_TEXTAREA) && notif == EN_CHANGE) {
el_win32_invoke_cb(cw->cb_change, cs, "");
}
return 0;
}
case WM_CTLCOLORSTATIC:
case WM_CTLCOLOREDIT:
case WM_CTLCOLORBTN: {
HWND ctrl = (HWND)lp;
int64_t cs = el_hwnd_to_slot(ctrl);
ElWidget* cw = (cs >= 0) ? el_widget_get(cs) : NULL;
if (cw) {
HDC hdc = (HDC)wp;
if (cw->has_fg) SetTextColor(hdc, cw->fg_color);
if (cw->has_bg) {
SetBkColor(hdc, cw->bg_color);
if (cw->bg_brush) return (LRESULT)cw->bg_brush;
}
}
break;
}
case WM_ERASEBKGND: {
if (w && w->has_bg && w->bg_brush) {
HDC hdc = (HDC)wp;
RECT rc;
GetClientRect(hwnd, &rc);
FillRect(hdc, &rc, w->bg_brush);
return 1;
}
break;
}
case WM_GETMINMAXINFO: {
/* Stored in the window's GWLP_USERDATA as packed two ints: min_w | min_h. */
LONG_PTR ud = GetWindowLongPtrW(hwnd, GWLP_USERDATA);
if (ud) {
int min_w = (int)(ud & 0xFFFF);
int min_h = (int)((ud >> 16) & 0xFFFF);
MINMAXINFO* mm = (MINMAXINFO*)lp;
if (min_w > 0) mm->ptMinTrackSize.x = min_w;
if (min_h > 0) mm->ptMinTrackSize.y = min_h;
}
return 0;
}
case WM_CLOSE:
DestroyWindow(hwnd);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProcW(hwnd, msg, wp, lp);
}
/* ── Subclass proc for EDIT controls (Enter key → on_submit) ────────────── */
static LRESULT CALLBACK el_edit_subclass_proc(HWND hwnd, UINT msg, WPARAM wp,
LPARAM lp, UINT_PTR uid,
DWORD_PTR ref_data) {
(void)uid; (void)ref_data;
if (msg == WM_KEYDOWN && wp == VK_RETURN) {
int64_t slot = el_hwnd_to_slot(hwnd);
ElWidget* w = (slot >= 0) ? el_widget_get(slot) : NULL;
if (w && w->cb_click) {
/* Gather current text as data. */
int len = GetWindowTextLengthW(hwnd);
WCHAR* wbuf = (WCHAR*)malloc((size_t)(len + 2) * sizeof(WCHAR));
if (wbuf) {
GetWindowTextW(hwnd, wbuf, len + 1);
char* utf8 = el_wide_to_utf8(wbuf);
free(wbuf);
el_win32_invoke_cb(w->cb_click, slot, utf8);
free(utf8);
}
return 0; /* suppress default beep */
}
}
return DefSubclassProc(hwnd, msg, wp, lp);
}
/* ── Window-class registration ────────────────────────────────────────────── */
static int _el_classes_registered = 0;
static void el_win32_register_classes(void) {
if (_el_classes_registered) return;
_el_classes_registered = 1;
/* Initialize common controls (needed for SetWindowSubclass). */
INITCOMMONCONTROLSEX icc;
icc.dwSize = sizeof(icc);
icc.dwICC = ICC_WIN95_CLASSES | ICC_STANDARD_CLASSES;
InitCommonControlsEx(&icc);
HINSTANCE hinst = GetModuleHandleW(NULL);
/* Container class (VStack, HStack, ZStack, Scroll) */
WNDCLASSW wc;
memset(&wc, 0, sizeof(wc));
wc.lpfnWndProc = el_container_wndproc;
wc.hInstance = hinst;
wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = EL_WNDCLASS_VSTACK;
RegisterClassW(&wc);
wc.lpszClassName = EL_WNDCLASS_HSTACK;
RegisterClassW(&wc);
wc.lpszClassName = EL_WNDCLASS_ZSTACK;
RegisterClassW(&wc);
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpszClassName = EL_WNDCLASS_SCROLL;
RegisterClassW(&wc);
/* Top-level window class */
memset(&wc, 0, sizeof(wc));
wc.style = CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = el_toplevel_wndproc;
wc.hInstance = hinst;
wc.hCursor = LoadCursorW(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = EL_WNDCLASS_WINDOW;
RegisterClassW(&wc);
}
/* ── Public C API ─────────────────────────────────────────────────────────── */
/*
* el_win32_init — one-time initialisation. Must be called before any other
* el_win32_* function.
*/
void el_win32_init(void) {
static int done = 0;
if (done) return;
done = 1;
el_win32_register_classes();
}
/*
* el_win32_run_loop — enter the Win32 message loop. Never returns.
* Must be called from the main thread after all windows are shown.
*/
void el_win32_run_loop(void) {
MSG msg;
while (GetMessageW(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessageW(&msg);
}
}
/* ── Window ───────────────────────────────────────────────────────────────── */
int64_t el_win32_window_create(const char* title, int width, int height,
int min_width, int min_height) {
el_win32_register_classes();
HINSTANCE hinst = GetModuleHandleW(NULL);
WCHAR* wtitle = el_utf8_to_wide(title ? title : "");
DWORD style = WS_OVERLAPPEDWINDOW;
HWND hwnd = CreateWindowW(
EL_WNDCLASS_WINDOW,
wtitle,
style,
CW_USEDEFAULT, CW_USEDEFAULT,
width, height,
NULL, NULL, hinst, NULL);
free(wtitle);
if (!hwnd) return -1;
/* Pack min_width / min_height into GWLP_USERDATA (low 16 = min_w, next 16 = min_h). */
LONG_PTR ud = ((LONG_PTR)(min_height & 0xFFFF) << 16) | (LONG_PTR)(min_width & 0xFFFF);
SetWindowLongPtrW(hwnd, GWLP_USERDATA, ud);
int64_t handle = el_widget_alloc(EL_WIDGET_WINDOW, hwnd);
if (handle < 0) { DestroyWindow(hwnd); return -1; }
ElWidget* w = el_widget_get(handle);
w->spacing = 0;
return handle;
}
void el_win32_window_show(int64_t handle) {
ElWidget* w = el_widget_get(handle);
if (!w || w->kind != EL_WIDGET_WINDOW || !w->hwnd) return;
ShowWindow(w->hwnd, SW_SHOW);
UpdateWindow(w->hwnd);
}
void el_win32_window_set_title(int64_t handle, const char* title) {
ElWidget* w = el_widget_get(handle);
if (!w || w->kind != EL_WIDGET_WINDOW || !w->hwnd) return;
WCHAR* wt = el_utf8_to_wide(title ? title : "");
SetWindowTextW(w->hwnd, wt);
free(wt);
}
/* ── Layout container factories ───────────────────────────────────────────── */
static int64_t el_win32_make_container(const WCHAR* class_name, ElWidgetKind kind,
int spacing) {
el_win32_register_classes();
HWND hwnd = CreateWindowW(
class_name,
L"",
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN,
0, 0, 0, 0,
HWND_MESSAGE, /* temporary parent; reparented on add_child */
NULL,
GetModuleHandleW(NULL),
NULL);
if (!hwnd) return -1;
int64_t handle = el_widget_alloc(kind, hwnd);
if (handle < 0) { DestroyWindow(hwnd); return -1; }
ElWidget* w = el_widget_get(handle);
w->spacing = spacing;
return handle;
}
int64_t el_win32_vstack_create(int spacing) {
return el_win32_make_container(EL_WNDCLASS_VSTACK, EL_WIDGET_VSTACK, spacing);
}
int64_t el_win32_hstack_create(int spacing) {
return el_win32_make_container(EL_WNDCLASS_HSTACK, EL_WIDGET_HSTACK, spacing);
}
int64_t el_win32_zstack_create(void) {
return el_win32_make_container(EL_WNDCLASS_ZSTACK, EL_WIDGET_ZSTACK, 0);
}
int64_t el_win32_scroll_create(void) {
HWND hwnd = CreateWindowW(
EL_WNDCLASS_SCROLL,
L"",
WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN | WS_VSCROLL,
0, 0, 0, 0,
HWND_MESSAGE,
NULL,
GetModuleHandleW(NULL),
NULL);
if (!hwnd) return -1;
int64_t handle = el_widget_alloc(EL_WIDGET_SCROLL, hwnd);
if (handle < 0) { DestroyWindow(hwnd); return -1; }
return handle;
}
/* ── Widget factories ─────────────────────────────────────────────────────── */
int64_t el_win32_label_create(const char* text) {
el_win32_register_classes();
WCHAR* wt = el_utf8_to_wide(text ? text : "");
HWND hwnd = CreateWindowW(
L"STATIC",
wt,
WS_CHILD | WS_VISIBLE | SS_LEFT,
0, 0, 0, 24,
HWND_MESSAGE, NULL, GetModuleHandleW(NULL), NULL);
free(wt);
if (!hwnd) return -1;
return el_widget_alloc(EL_WIDGET_LABEL, hwnd);
}
int64_t el_win32_button_create(const char* label) {
el_win32_register_classes();
WCHAR* wl = el_utf8_to_wide(label ? label : "");
HWND hwnd = CreateWindowW(
L"BUTTON",
wl,
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
0, 0, 0, 32,
HWND_MESSAGE, NULL, GetModuleHandleW(NULL), NULL);
free(wl);
if (!hwnd) return -1;
return el_widget_alloc(EL_WIDGET_BUTTON, hwnd);
}
int64_t el_win32_text_field_create(const char* placeholder) {
el_win32_register_classes();
HWND hwnd = CreateWindowW(
L"EDIT",
L"",
WS_CHILD | WS_VISIBLE | WS_BORDER |
ES_AUTOHSCROLL | ES_LEFT,
0, 0, 0, 26,
HWND_MESSAGE, NULL, GetModuleHandleW(NULL), NULL);
if (!hwnd) return -1;
/* Set placeholder / cue banner (Windows Vista+). */
if (placeholder && *placeholder) {
WCHAR* wp = el_utf8_to_wide(placeholder);
SendMessageW(hwnd, EM_SETCUEBANNER, TRUE, (LPARAM)wp);
free(wp);
}
int64_t handle = el_widget_alloc(EL_WIDGET_TEXTFIELD, hwnd);
if (handle < 0) { DestroyWindow(hwnd); return -1; }
/* Subclass to intercept VK_RETURN for on_submit. */
SetWindowSubclass(hwnd, el_edit_subclass_proc, (UINT_PTR)handle, 0);
return handle;
}
int64_t el_win32_text_area_create(const char* placeholder) {
el_win32_register_classes();
HWND hwnd = CreateWindowW(
L"EDIT",
L"",
WS_CHILD | WS_VISIBLE | WS_BORDER |
ES_MULTILINE | ES_AUTOVSCROLL | ES_WANTRETURN | WS_VSCROLL,
0, 0, 0, 80,
HWND_MESSAGE, NULL, GetModuleHandleW(NULL), NULL);
if (!hwnd) return -1;
/* EDIT multiline does not support EM_SETCUEBANNER, so no placeholder. */
(void)placeholder;
int64_t handle = el_widget_alloc(EL_WIDGET_TEXTAREA, hwnd);
if (handle < 0) { DestroyWindow(hwnd); return -1; }
return handle;
}
int64_t el_win32_image_create(const char* path) {
el_win32_register_classes();
HWND hwnd = CreateWindowW(
L"STATIC",
L"",
WS_CHILD | WS_VISIBLE | SS_BITMAP | SS_REALSIZECONTROL,
0, 0, 0, 0,
HWND_MESSAGE, NULL, GetModuleHandleW(NULL), NULL);
if (!hwnd) return -1;
int64_t handle = el_widget_alloc(EL_WIDGET_IMAGE, hwnd);
if (handle < 0) { DestroyWindow(hwnd); return -1; }
ElWidget* w = el_widget_get(handle);
if (path && *path) {
WCHAR* wpath = el_utf8_to_wide(path);
HBITMAP hbmp = (HBITMAP)LoadImageW(NULL, wpath, IMAGE_BITMAP,
0, 0,
LR_LOADFROMFILE | LR_DEFAULTSIZE);
free(wpath);
if (hbmp) {
w->hbmp = hbmp;
SendMessageW(hwnd, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hbmp);
}
}
return handle;
}
/* ── Widget property setters ─────────────────────────────────────────────── */
void el_win32_widget_set_text(int64_t handle, const char* text) {
ElWidget* w = el_widget_get(handle);
if (!w || !w->hwnd) return;
WCHAR* wt = el_utf8_to_wide(text ? text : "");
SetWindowTextW(w->hwnd, wt);
free(wt);
}
const char* el_win32_widget_get_text(int64_t handle) {
ElWidget* w = el_widget_get(handle);
if (!w || !w->hwnd) return strdup("");
int len = GetWindowTextLengthW(w->hwnd);
if (len <= 0) return strdup("");
WCHAR* wbuf = (WCHAR*)malloc((size_t)(len + 2) * sizeof(WCHAR));
if (!wbuf) return strdup("");
GetWindowTextW(w->hwnd, wbuf, len + 1);
char* result = el_wide_to_utf8(wbuf);
free(wbuf);
return result; /* caller must free() */
}
void el_win32_widget_set_color(int64_t handle, float r, float g, float b, float a) {
(void)a; /* Win32 classic does not support per-control alpha */
ElWidget* w = el_widget_get(handle);
if (!w || !w->hwnd) return;
w->fg_color = RGB((int)(r * 255.0f), (int)(g * 255.0f), (int)(b * 255.0f));
w->has_fg = 1;
InvalidateRect(w->hwnd, NULL, TRUE);
}
void el_win32_widget_set_bg_color(int64_t handle, float r, float g, float b, float a) {
(void)a;
ElWidget* w = el_widget_get(handle);
if (!w || !w->hwnd) return;
w->bg_color = RGB((int)(r * 255.0f), (int)(g * 255.0f), (int)(b * 255.0f));
w->has_bg = 1;
if (w->bg_brush) DeleteObject(w->bg_brush);
w->bg_brush = CreateSolidBrush(w->bg_color);
InvalidateRect(w->hwnd, NULL, TRUE);
}
void el_win32_widget_set_font(int64_t handle, const char* family, int size, int bold) {
ElWidget* w = el_widget_get(handle);
if (!w || !w->hwnd) return;
LOGFONTW lf;
memset(&lf, 0, sizeof(lf));
lf.lfHeight = -MulDiv(size > 0 ? size : 12, GetDeviceCaps(GetDC(NULL), LOGPIXELSY), 72);
lf.lfWeight = bold ? FW_BOLD : FW_NORMAL;
lf.lfCharSet = DEFAULT_CHARSET;
if (family && *family && strcmp(family, "system") != 0) {
WCHAR* wf = el_utf8_to_wide(family);
wcsncpy(lf.lfFaceName, wf, LF_FACESIZE - 1);
free(wf);
}
/* family == NULL or "system" → lfFaceName stays zeroed → system font */
HFONT hfont = CreateFontIndirectW(&lf);
if (!hfont) return;
if (w->font) DeleteObject(w->font);
w->font = hfont;
SendMessageW(w->hwnd, WM_SETFONT, (WPARAM)hfont, MAKELPARAM(TRUE, 0));
}
void el_win32_widget_set_padding(int64_t handle, int top, int right,
int bottom, int left) {
ElWidget* w = el_widget_get(handle);
if (!w) return;
w->pad_top = top;
w->pad_right = right;
w->pad_bottom = bottom;
w->pad_left = left;
/* Trigger reflow if already parented. */
if (w->hwnd) PostMessageW(w->hwnd, WM_EL_REFLOW, 0, 0);
}
void el_win32_widget_set_width(int64_t handle, int width) {
ElWidget* w = el_widget_get(handle);
if (!w) return;
w->fixed_w = width;
if (w->hwnd) {
RECT r; GetWindowRect(w->hwnd, &r);
int h = r.bottom - r.top;
SetWindowPos(w->hwnd, NULL, 0, 0, width, h,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
}
}
void el_win32_widget_set_height(int64_t handle, int height) {
ElWidget* w = el_widget_get(handle);
if (!w) return;
w->fixed_h = height;
if (w->hwnd) {
RECT r; GetWindowRect(w->hwnd, &r);
int cw = r.right - r.left;
SetWindowPos(w->hwnd, NULL, 0, 0, cw, height,
SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE);
}
}
void el_win32_widget_set_flex(int64_t handle, int flex) {
ElWidget* w = el_widget_get(handle);
if (!w) return;
w->flex = flex;
/* Let parent reflow absorb the change. */
}
void el_win32_widget_set_corner_radius(int64_t handle, int radius) {
/*
* Classic Win32 does not have native per-control corner radii.
* If radius > 0 we apply a rounded rectangular window region so the
* control at least clips to a rounded rect. This is approximate — it
* does not anti-alias and may look boxy on high-DPI displays.
*/
ElWidget* w = el_widget_get(handle);
if (!w || !w->hwnd || radius <= 0) return;
RECT r; GetWindowRect(w->hwnd, &r);
int ww = r.right - r.left;
int wh = r.bottom - r.top;
if (ww <= 0 || wh <= 0) return;
HRGN rgn = CreateRoundRectRgn(0, 0, ww, wh, radius * 2, radius * 2);
SetWindowRgn(w->hwnd, rgn, TRUE);
/* Note: SetWindowRgn takes ownership of rgn; do NOT DeleteObject it. */
}
void el_win32_widget_set_disabled(int64_t handle, int disabled) {
ElWidget* w = el_widget_get(handle);
if (!w || !w->hwnd) return;
EnableWindow(w->hwnd, disabled ? FALSE : TRUE);
}
void el_win32_widget_set_hidden(int64_t handle, int hidden) {
ElWidget* w = el_widget_get(handle);
if (!w || !w->hwnd) return;
ShowWindow(w->hwnd, hidden ? SW_HIDE : SW_SHOW);
}
/* ── Child management ─────────────────────────────────────────────────────── */
/*
* el_win32_widget_add_child — attach child widget to parent.
*
* - WINDOW / VSTACK / HSTACK / ZSTACK: SetParent child into container.
* - SCROLL: first child becomes the scrolled content.
*
* After reparenting, triggers a layout reflow of the parent.
*/
void el_win32_widget_add_child(int64_t parent, int64_t child) {
ElWidget* pw = el_widget_get(parent);
ElWidget* cw = el_widget_get(child);
if (!pw || !cw || !pw->hwnd || !cw->hwnd) return;
if (cw->kind == EL_WIDGET_WINDOW) return; /* cannot add window as child */
if (pw->child_count >= EL_WIN32_MAX_CHILDREN) return;
/* Reparent the child HWND into the container. */
SetParent(cw->hwnd, pw->hwnd);
/* Make sure the WS_CHILD style is set and WS_POPUP is cleared. */
LONG_PTR style = GetWindowLongPtrW(cw->hwnd, GWL_STYLE);
style |= WS_CHILD;
style &= ~WS_POPUP;
SetWindowLongPtrW(cw->hwnd, GWL_STYLE, style);
ShowWindow(cw->hwnd, SW_SHOW);
pw->children[pw->child_count++] = child;
/* Reflow parent so the new child gets positioned. */
el_win32_reflow(parent);
}
void el_win32_widget_remove_child(int64_t parent, int64_t child) {
ElWidget* pw = el_widget_get(parent);
ElWidget* cw = el_widget_get(child);
if (!cw || !cw->hwnd) return;
/* Detach HWND from parent. */
SetParent(cw->hwnd, NULL);
/* Remove from parent's child list. */
if (pw) {
for (int i = 0; i < pw->child_count; i++) {
if (pw->children[i] == child) {
memmove(&pw->children[i], &pw->children[i + 1],
(size_t)(pw->child_count - i - 1) * sizeof(int64_t));
pw->child_count--;
break;
}
}
el_win32_reflow(parent);
}
}
/* ── Event registration ───────────────────────────────────────────────────── */
void el_win32_widget_on_click(int64_t handle, const char* fn_name) {
ElWidget* w = el_widget_get(handle);
if (!w) return;
free(w->cb_click);
w->cb_click = (fn_name && *fn_name) ? strdup(fn_name) : NULL;
}
void el_win32_widget_on_change(int64_t handle, const char* fn_name) {
ElWidget* w = el_widget_get(handle);
if (!w) return;
free(w->cb_change);
w->cb_change = (fn_name && *fn_name) ? strdup(fn_name) : NULL;
}
void el_win32_widget_on_submit(int64_t handle, const char* fn_name) {
/* For text fields, submit (Enter key) is stored in cb_click, same as AppKit. */
el_win32_widget_on_click(handle, fn_name);
}
/* ── Widget destroy ───────────────────────────────────────────────────────── */
void el_win32_widget_destroy(int64_t handle) {
ElWidget* w = el_widget_get(handle);
if (!w || !w->hwnd) return;
DestroyWindow(w->hwnd);
el_widget_free_slot(handle);
}
/* ── Manifest reader ──────────────────────────────────────────────────────── */
/*
* Same parser as the macOS implementation in el_seed.c.
* Reads the app{} block from a manifest.el file:
*
* app {
* window_title "My App"
* window_width 1200
* window_height 800
* min_width 600
* min_height 400
* }
*
* Returns JSON: {"title":"...","width":N,"height":N,"min_width":N,"min_height":N}
* Returns "{}" on failure.
*
* NOTE: el_seed.c already defines __manifest_read for both EL_TARGET_MACOS and
* EL_TARGET_WIN32 using the same parser body. We re-implement it here because
* el_seed.c's implementation is inside #ifdef EL_TARGET_MACOS; the Win32
* version lives here, symmetric with the macOS one.
*/
static void w32_manifest_skip_ws(const char** p) {
while (**p && (unsigned char)**p <= ' ') (*p)++;
}
static char* w32_manifest_read_string(const char** p) {
w32_manifest_skip_ws(p);
if (**p != '"') return NULL;
(*p)++;
const char* start = *p;
while (**p && **p != '"') {
if (**p == '\\') (*p)++;
(*p)++;
}
size_t len = (size_t)(*p - start);
if (**p == '"') (*p)++;
char* out = (char*)malloc(len + 1);
if (!out) return NULL;
memcpy(out, start, len);
out[len] = '\0';
return out;
}
static int64_t w32_manifest_read_int(const char** p) {
w32_manifest_skip_ws(p);
int64_t result = 0;
int neg = 0;
if (**p == '-') { neg = 1; (*p)++; }
while (**p >= '0' && **p <= '9') {
result = result * 10 + (**p - '0');
(*p)++;
}
return neg ? -result : result;
}
static const char* w32_manifest_find_app_block(const char* src) {
while (*src) {
if (src[0] == '/' && src[1] == '/') {
while (*src && *src != '\n') src++;
continue;
}
if (strncmp(src, "app", 3) == 0 &&
(src[3] == ' ' || src[3] == '\t' || src[3] == '\n' || src[3] == '{')) {
src += 3;
while (*src && *src != '{') src++;
if (*src == '{') return src + 1;
return NULL;
}
src++;
}
return NULL;
}
/* el_val_t helpers — mirror of what el_seed.c provides. */
#ifndef EL_CSTR
#define EL_CSTR(v) ((const char*)(uintptr_t)(v))
#endif
#ifndef EL_STR
#define EL_STR(p) ((el_val_t)(uintptr_t)(p))
#endif
el_val_t el_win32_manifest_read(const char* path) {
if (!path || !*path) return EL_STR(strdup("{}"));
FILE* f = fopen(path, "r");
if (!f) return EL_STR(strdup("{}"));
fseek(f, 0, SEEK_END);
long sz = ftell(f);
rewind(f);
if (sz <= 0) { fclose(f); return EL_STR(strdup("{}")); }
char* src = (char*)malloc((size_t)sz + 1);
if (!src) { fclose(f); return EL_STR(strdup("{}")); }
size_t got = fread(src, 1, (size_t)sz, f);
src[got] = '\0';
fclose(f);
const char* app_start = w32_manifest_find_app_block(src);
if (!app_start) { free(src); return EL_STR(strdup("{}")); }
char* title = NULL;
int64_t width = 1200, height = 800;
int64_t min_w = 600, min_h = 400;
const char* cur = app_start;
while (*cur && *cur != '}') {
while (*cur && (unsigned char)*cur <= ' ') cur++;
if (*cur == '}') break;
if (cur[0] == '/' && cur[1] == '/') {
while (*cur && *cur != '\n') cur++;
continue;
}
const char* key_start = cur;
while (*cur && (unsigned char)*cur > ' ' && *cur != '{' && *cur != '}') cur++;
size_t key_len = (size_t)(cur - key_start);
if (key_len == 0) { cur++; continue; }
while (*cur == ' ' || *cur == '\t') cur++;
if (key_len == 12 && strncmp(key_start, "window_title", 12) == 0) { free(title); title = w32_manifest_read_string(&cur); }
else if (key_len == 12 && strncmp(key_start, "window_width", 12) == 0) { width = w32_manifest_read_int(&cur); }
else if (key_len == 13 && strncmp(key_start, "window_height", 13) == 0) { height = w32_manifest_read_int(&cur); }
else if (key_len == 9 && strncmp(key_start, "min_width", 9) == 0) { min_w = w32_manifest_read_int(&cur); }
else if (key_len == 10 && strncmp(key_start, "min_height", 10) == 0) { min_h = w32_manifest_read_int(&cur); }
else { while (*cur && *cur != '\n' && *cur != '}') cur++; }
}
free(src);
const char* t = title ? title : "App";
size_t buf_sz = strlen(t) * 6 + 128;
char* buf = (char*)malloc(buf_sz);
if (!buf) { free(title); return EL_STR(strdup("{}")); }
snprintf(buf, buf_sz,
"{\"title\":\"%s\",\"width\":%lld,\"height\":%lld,\"min_width\":%lld,\"min_height\":%lld}",
t, (long long)width, (long long)height, (long long)min_w, (long long)min_h);
free(title);
return EL_STR(buf);
}
/* ── el_seed.c __* builtins for EL_TARGET_WIN32 ──────────────────────────── */
/*
* These are the thin el_val_t wrappers that el_seed.c (with EL_TARGET_WIN32)
* will call. They mirror the EL_TARGET_MACOS section in el_seed.c exactly.
*
* el_seed.c includes this file's declarations via el_native_target.h and
* links el_win32.o. The __* symbol definitions below are compiled into
* el_win32.c itself (not el_seed.c) when EL_TARGET_WIN32 is defined, keeping
* el_seed.c platform-agnostic.
*/
/* Helper: extract el string pointer. */
static inline const char* _w32_cstr(el_val_t v) {
return (const char*)(uintptr_t)v;
}
static inline el_val_t _w32_str(const char* p) {
return (el_val_t)(uintptr_t)p;
}
/* Bit-cast helper for float args (el_val_t carries IEEE 754 doubles). */
static inline double _w32_to_float(el_val_t v) {
union { double d; int64_t i; } u; u.i = (int64_t)v; return u.d;
}
void __native_init(void) { el_win32_init(); }
void __native_run_loop(void) { el_win32_run_loop(); }
el_val_t __window_create(el_val_t title, el_val_t width, el_val_t height,
el_val_t min_width, el_val_t min_height) {
return (el_val_t)el_win32_window_create(
_w32_cstr(title),
(int)(int64_t)width, (int)(int64_t)height,
(int)(int64_t)min_width, (int)(int64_t)min_height);
}
void __window_show(el_val_t h) { el_win32_window_show((int64_t)h); }
void __window_set_title(el_val_t h, el_val_t t) { el_win32_window_set_title((int64_t)h, _w32_cstr(t)); }
el_val_t __vstack_create(el_val_t spacing) { return (el_val_t)el_win32_vstack_create((int)(int64_t)spacing); }
el_val_t __hstack_create(el_val_t spacing) { return (el_val_t)el_win32_hstack_create((int)(int64_t)spacing); }
el_val_t __zstack_create(void) { return (el_val_t)el_win32_zstack_create(); }
el_val_t __scroll_create(void) { return (el_val_t)el_win32_scroll_create(); }
el_val_t __label_create(el_val_t text) { return (el_val_t)el_win32_label_create(_w32_cstr(text)); }
el_val_t __button_create(el_val_t label) { return (el_val_t)el_win32_button_create(_w32_cstr(label)); }
el_val_t __text_field_create(el_val_t placeholder) { return (el_val_t)el_win32_text_field_create(_w32_cstr(placeholder)); }
el_val_t __text_area_create(el_val_t placeholder) { return (el_val_t)el_win32_text_area_create(_w32_cstr(placeholder)); }
el_val_t __image_create(el_val_t path) { return (el_val_t)el_win32_image_create(_w32_cstr(path)); }
void __widget_set_text(el_val_t h, el_val_t t) { el_win32_widget_set_text((int64_t)h, _w32_cstr(t)); }
el_val_t __widget_get_text(el_val_t h) {
const char* s = el_win32_widget_get_text((int64_t)h);
/* el_win32_widget_get_text returns a malloc'd string; caller treats as
* short-lived (same contract as el_appkit version). */
return s ? _w32_str(s) : _w32_str(strdup(""));
}
void __widget_set_color(el_val_t h, el_val_t r, el_val_t g, el_val_t b, el_val_t a) {
el_win32_widget_set_color((int64_t)h,
(float)_w32_to_float(r), (float)_w32_to_float(g),
(float)_w32_to_float(b), (float)_w32_to_float(a));
}
void __widget_set_bg_color(el_val_t h, el_val_t r, el_val_t g, el_val_t b, el_val_t a) {
el_win32_widget_set_bg_color((int64_t)h,
(float)_w32_to_float(r), (float)_w32_to_float(g),
(float)_w32_to_float(b), (float)_w32_to_float(a));
}
void __widget_set_font(el_val_t h, el_val_t family, el_val_t size, el_val_t bold) {
el_win32_widget_set_font((int64_t)h, _w32_cstr(family),
(int)(int64_t)size, (int)(int64_t)bold);
}
void __widget_set_padding(el_val_t h, el_val_t top, el_val_t right,
el_val_t bottom, el_val_t left) {
el_win32_widget_set_padding((int64_t)h,
(int)(int64_t)top, (int)(int64_t)right,
(int)(int64_t)bottom, (int)(int64_t)left);
}
void __widget_set_width(el_val_t h, el_val_t w) { el_win32_widget_set_width((int64_t)h, (int)(int64_t)w); }
void __widget_set_height(el_val_t h, el_val_t ht) { el_win32_widget_set_height((int64_t)h, (int)(int64_t)ht); }
void __widget_set_flex(el_val_t h, el_val_t f) { el_win32_widget_set_flex((int64_t)h, (int)(int64_t)f); }
void __widget_set_corner_radius(el_val_t h, el_val_t r) { el_win32_widget_set_corner_radius((int64_t)h, (int)(int64_t)r); }
void __widget_set_disabled(el_val_t h, el_val_t d) { el_win32_widget_set_disabled((int64_t)h, (int)(int64_t)d); }
void __widget_set_hidden(el_val_t h, el_val_t hid) { el_win32_widget_set_hidden((int64_t)h, (int)(int64_t)hid); }
void __widget_add_child(el_val_t p, el_val_t c) { el_win32_widget_add_child((int64_t)p, (int64_t)c); }
void __widget_remove_child(el_val_t p, el_val_t c) { el_win32_widget_remove_child((int64_t)p, (int64_t)c); }
void __widget_destroy(el_val_t h) { el_win32_widget_destroy((int64_t)h); }
void __widget_on_click(el_val_t h, el_val_t fn_name) { el_win32_widget_on_click((int64_t)h, _w32_cstr(fn_name)); }
void __widget_on_change(el_val_t h, el_val_t fn_name) { el_win32_widget_on_change((int64_t)h, _w32_cstr(fn_name)); }
void __widget_on_submit(el_val_t h, el_val_t fn_name) { el_win32_widget_on_submit((int64_t)h, _w32_cstr(fn_name)); }
el_val_t __manifest_read(el_val_t path) {
return el_win32_manifest_read(_w32_cstr(path));
}
#endif /* EL_TARGET_WIN32 */