This repository has been archived on 2026-08-20. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
el-retired/ui/vessels/el-native/src/main.el
T

387 lines
14 KiB
EmacsLisp
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// el-native El native widget system, macOS AppKit target.
//
// This vessel wraps the __widget_* C builtins (added to el_seed.c under
// EL_TARGET_MACOS) in clean El-level functions. Applications import this
// vessel and use these functions directly.
//
// The el-ui-compiler's native codegen target emits calls to these functions
// when it processes component templates.
//
// Layer hierarchy:
// Application .el
// el-native functions (this file)
// __widget_* / __window_* C builtins
// el_appkit.m ObjC bridge
// AppKit NSWindow / NSView / NSButton / etc.
//
// Widget handles are opaque Int values. -1 means invalid/null.
// Pass handles between functions freely they are small integers.
//
// Color convention: r/g/b/a are Float values 0.01.0.
// Use color_from_hex() or color_from_rgba() to build color arguments.
// Bootstrap
// init initialize the native widget system. Call once before creating widgets.
fn native_init() -> Void {
__native_init()
}
// run_loop start the platform run loop. Never returns.
// Call this as the final step of your application entry point.
fn native_run_loop() -> Void {
__native_run_loop()
}
// Manifest
// manifest_read parse the app{} block from a manifest.el file.
// Returns a JSON string: {"title":"...","width":N,"height":N,"min_width":N,"min_height":N}
fn manifest_read(path: String) -> String {
__manifest_read(path)
}
fn manifest_title(m: String) -> String {
json_get_string(m, "title")
}
fn manifest_width(m: String) -> Int {
json_get_int(m, "width")
}
fn manifest_height(m: String) -> Int {
json_get_int(m, "height")
}
fn manifest_min_width(m: String) -> Int {
json_get_int(m, "min_width")
}
fn manifest_min_height(m: String) -> Int {
json_get_int(m, "min_height")
}
// Window
// window_create create a native window. Returns handle (Int).
// Reads sizing from manifest. Most apps should call window_from_manifest().
fn window_create(title: String, width: Int, height: Int,
min_width: Int, min_height: Int) -> Int {
__window_create(title, width, height, min_width, min_height)
}
// window_from_manifest create a window using settings from manifest.el.
fn window_from_manifest(manifest_path: String) -> Int {
let m: String = manifest_read(manifest_path)
let title: String = manifest_title(m)
let w: Int = manifest_width(m)
let h: Int = manifest_height(m)
let mw: Int = manifest_min_width(m)
let mh: Int = manifest_min_height(m)
let safe_w: Int = if w > 0 { w } else { 1200 }
let safe_h: Int = if h > 0 { h } else { 800 }
let safe_mw: Int = if mw > 0 { mw } else { 600 }
let safe_mh: Int = if mh > 0 { mh } else { 400 }
let safe_t: String = if str_len(title) > 0 { title } else { "App" }
window_create(safe_t, safe_w, safe_h, safe_mw, safe_mh)
}
fn window_show(handle: Int) -> Void {
__window_show(handle)
}
fn window_set_title(handle: Int, title: String) -> Void {
__window_set_title(handle, title)
}
// Layout containers
// vstack vertical stack. Children are laid out top-to-bottom.
fn vstack(spacing: Int) -> Int {
__vstack_create(spacing)
}
// vstack_tight 0 spacing vertical stack.
fn vstack_tight() -> Int {
__vstack_create(0)
}
// hstack horizontal stack. Children are laid out left-to-right.
fn hstack(spacing: Int) -> Int {
__hstack_create(spacing)
}
// zstack children are layered on top of each other (Z axis).
fn zstack() -> Int {
__zstack_create()
}
// scroll scrollable container. Add a single child (usually a vstack).
fn scroll() -> Int {
__scroll_create()
}
// Widgets
// label static text display.
fn label(text: String) -> Int {
__label_create(text)
}
// button tappable button. Register handler with widget_on_click().
fn button(label: String) -> Int {
__button_create(label)
}
// text_field single-line text input.
fn text_field(placeholder: String) -> Int {
__text_field_create(placeholder)
}
// text_area multi-line text input.
fn text_area(placeholder: String) -> Int {
__text_area_create(placeholder)
}
// image display an image by filesystem path or system image name.
fn image(path_or_name: String) -> Int {
__image_create(path_or_name)
}
// Widget properties
fn widget_set_text(handle: Int, text: String) -> Void {
__widget_set_text(handle, text)
}
fn widget_get_text(handle: Int) -> String {
__widget_get_text(handle)
}
// Color setters. r/g/b/a are Float 0.01.0.
fn widget_set_color(handle: Int, r: Float, g: Float, b: Float, a: Float) -> Void {
__widget_set_color(handle, r, g, b, a)
}
fn widget_set_bg_color(handle: Int, r: Float, g: Float, b: Float, a: Float) -> Void {
__widget_set_bg_color(handle, r, g, b, a)
}
// widget_set_font set font family, size (pt), and bold flag.
// Use "system" or "" for the platform default font.
fn widget_set_font(handle: Int, family: String, size: Int, bold: Bool) -> Void {
let bold_int: Int = if bold { 1 } else { 0 }
__widget_set_font(handle, family, size, bold_int)
}
// widget_set_padding set padding on all four sides (pixels).
fn widget_set_padding(handle: Int, top: Int, right: Int, bottom: Int, left: Int) -> Void {
__widget_set_padding(handle, top, right, bottom, left)
}
// widget_set_padding_all uniform padding on all sides.
fn widget_set_padding_all(handle: Int, p: Int) -> Void {
__widget_set_padding(handle, p, p, p, p)
}
// widget_set_padding_xy horizontal and vertical padding.
fn widget_set_padding_xy(handle: Int, px: Int, py: Int) -> Void {
__widget_set_padding(handle, py, px, py, px)
}
fn widget_set_width(handle: Int, width: Int) -> Void {
__widget_set_width(handle, width)
}
fn widget_set_height(handle: Int, height: Int) -> Void {
__widget_set_height(handle, height)
}
// widget_set_flex flex > 0 means expand to fill available space.
fn widget_set_flex(handle: Int, flex: Int) -> Void {
__widget_set_flex(handle, flex)
}
fn widget_set_corner_radius(handle: Int, radius: Int) -> Void {
__widget_set_corner_radius(handle, radius)
}
fn widget_set_disabled(handle: Int, disabled: Bool) -> Void {
let d: Int = if disabled { 1 } else { 0 }
__widget_set_disabled(handle, d)
}
fn widget_set_hidden(handle: Int, hidden: Bool) -> Void {
let h: Int = if hidden { 1 } else { 0 }
__widget_set_hidden(handle, h)
}
// Layout
fn widget_add_child(parent: Int, child: Int) -> Void {
__widget_add_child(parent, child)
}
fn widget_remove_child(parent: Int, child: Int) -> Void {
__widget_remove_child(parent, child)
}
fn widget_destroy(handle: Int) -> Void {
__widget_destroy(handle)
}
// Events
// widget_on_click register an El function to call when widget is clicked.
// fn_name: the compiled El symbol name of the handler function.
// Handler signature: fn my_handler(widget: Int, data: String) -> Void
fn widget_on_click(handle: Int, fn_name: String) -> Void {
__widget_on_click(handle, fn_name)
}
// widget_on_change called when text field value changes.
fn widget_on_change(handle: Int, fn_name: String) -> Void {
__widget_on_change(handle, fn_name)
}
// widget_on_submit called when text field receives Enter (submit).
fn widget_on_submit(handle: Int, fn_name: String) -> Void {
__widget_on_submit(handle, fn_name)
}
// widget_set_data attach an arbitrary data string to a widget.
// The data string is passed as the `data` argument when the widget's click
// callback fires. Use this to associate a session ID (or any identifier)
// with a clickable row so the callback knows which item was selected.
fn widget_set_data(handle: Int, data: String) -> Void {
__widget_set_data(handle, data)
}
// Color helpers
//
// Colors are passed as Float components (0.01.0). These helpers convert from
// common formats.
// hex_channel parse two hex digits as a byte 0..255.
fn hex_channel(s: String, offset: Int) -> Int {
let hi: String = str_slice(s, offset, offset + 1)
let lo: String = str_slice(s, offset + 1, offset + 2)
let h: Int = hex_nibble(hi)
let l: Int = hex_nibble(lo)
h * 16 + l
}
fn hex_nibble(c: String) -> Int {
if str_eq(c, "0") { return 0 }
if str_eq(c, "1") { return 1 }
if str_eq(c, "2") { return 2 }
if str_eq(c, "3") { return 3 }
if str_eq(c, "4") { return 4 }
if str_eq(c, "5") { return 5 }
if str_eq(c, "6") { return 6 }
if str_eq(c, "7") { return 7 }
if str_eq(c, "8") { return 8 }
if str_eq(c, "9") { return 9 }
if str_eq(c, "a") { return 10 }
if str_eq(c, "b") { return 11 }
if str_eq(c, "c") { return 12 }
if str_eq(c, "d") { return 13 }
if str_eq(c, "e") { return 14 }
if str_eq(c, "f") { return 15 }
if str_eq(c, "A") { return 10 }
if str_eq(c, "B") { return 11 }
if str_eq(c, "C") { return 12 }
if str_eq(c, "D") { return 13 }
if str_eq(c, "E") { return 14 }
if str_eq(c, "F") { return 15 }
0
}
// color_hex_r/g/b/a extract RGBA Float components from a hex color string.
// Accepts "#RRGGBB" or "#RRGGBBAA".
fn color_hex_r(hex: String) -> Float {
let s: String = if str_starts_with(hex, "#") { str_slice(hex, 1, str_len(hex)) } else { hex }
let v: Int = hex_channel(s, 0)
float_div(int_to_float(v), 255.0)
}
fn color_hex_g(hex: String) -> Float {
let s: String = if str_starts_with(hex, "#") { str_slice(hex, 1, str_len(hex)) } else { hex }
let v: Int = hex_channel(s, 2)
float_div(int_to_float(v), 255.0)
}
fn color_hex_b(hex: String) -> Float {
let s: String = if str_starts_with(hex, "#") { str_slice(hex, 1, str_len(hex)) } else { hex }
let v: Int = hex_channel(s, 4)
float_div(int_to_float(v), 255.0)
}
fn color_hex_a(hex: String) -> Float {
let s: String = if str_starts_with(hex, "#") { str_slice(hex, 1, str_len(hex)) } else { hex }
if str_len(s) < 8 { return 1.0 }
let v: Int = hex_channel(s, 6)
float_div(int_to_float(v), 255.0)
}
// widget_set_color_hex convenience: set foreground color from a hex string.
fn widget_set_color_hex(handle: Int, hex: String) -> Void {
widget_set_color(handle,
color_hex_r(hex), color_hex_g(hex), color_hex_b(hex), color_hex_a(hex))
}
// widget_set_bg_color_hex convenience: set background color from a hex string.
fn widget_set_bg_color_hex(handle: Int, hex: String) -> Void {
widget_set_bg_color(handle,
color_hex_r(hex), color_hex_g(hex), color_hex_b(hex), color_hex_a(hex))
}
// Style tokens native
//
// Map el-style semantic tokens (from el-style vessel) to native widget properties.
// This is the bridge between the design system and platform rendering.
//
// Token colors match el-style's color_scheme_dark() by default.
// Applications can override by calling the individual setters directly.
let TOKEN_PRIMARY: String = "#60a5fa"
let TOKEN_ON_PRIMARY: String = "#0f172a"
let TOKEN_BACKGROUND: String = "#0f172a"
let TOKEN_ON_BG: String = "#f8fafc"
let TOKEN_SURFACE: String = "#1e293b"
let TOKEN_ON_SURFACE: String = "#f8fafc"
let TOKEN_OUTLINE: String = "#475569"
let TOKEN_ERROR: String = "#f87171"
// Apply standard surface styling to a container.
fn style_surface(handle: Int) -> Void {
widget_set_bg_color_hex(handle, TOKEN_SURFACE)
widget_set_corner_radius(handle, 8)
}
// Apply primary button style.
fn style_button_primary(handle: Int) -> Void {
widget_set_bg_color_hex(handle, TOKEN_PRIMARY)
widget_set_color_hex(handle, TOKEN_ON_PRIMARY)
widget_set_corner_radius(handle, 8)
widget_set_padding_xy(handle, 16, 8)
}
// Apply body text style to a label.
fn style_label_body(handle: Int) -> Void {
widget_set_color_hex(handle, TOKEN_ON_BG)
widget_set_font(handle, "system", 14, false)
}
// Apply heading style to a label.
fn style_label_heading(handle: Int) -> Void {
widget_set_color_hex(handle, TOKEN_ON_BG)
widget_set_font(handle, "system", 20, true)
}
// Apply muted/secondary text style.
fn style_label_muted(handle: Int) -> Void {
widget_set_color_hex(handle, TOKEN_OUTLINE)
widget_set_font(handle, "system", 12, false)
}