feat(runtime): native platform backends and UI vessels onto main
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
plugins {
|
||||
id 'com.android.application'
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.neuron.el'
|
||||
compileSdk 34
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.neuron.el"
|
||||
minSdk 21
|
||||
targetSdk 34
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
cppFlags ""
|
||||
arguments "-DANDROID_STL=c++_shared"
|
||||
}
|
||||
}
|
||||
|
||||
ndk {
|
||||
// Build for the two most relevant ABIs. Add x86/x86_64 for emulator.
|
||||
abiFilters "arm64-v8a", "armeabi-v7a", "x86_64"
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "src/main/jni/CMakeLists.txt"
|
||||
version "3.22.1"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
debug {
|
||||
jniDebuggable true
|
||||
}
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// No third-party dependencies — el-native uses only android.* framework classes.
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# Keep ElBridge and MainActivity so JNI symbol names stay intact.
|
||||
-keep class com.neuron.el.ElBridge { *; }
|
||||
-keep class com.neuron.el.MainActivity { *; }
|
||||
@@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:name=".ElApp"
|
||||
android:label="el-native"
|
||||
android:theme="@style/Theme.AppCompat.Light.NoActionBar"
|
||||
android:allowBackup="false"
|
||||
android:supportsRtl="true">
|
||||
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:configChanges="orientation|screenSize|keyboardHidden">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.neuron.el;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
/**
|
||||
* ElApp — Application subclass for native-hello-android.
|
||||
*
|
||||
* Currently minimal: exists as an anchor for future app-level initialisation
|
||||
* (crash reporting, global state, etc.). Listed in AndroidManifest.xml as
|
||||
* android:name=".ElApp".
|
||||
*/
|
||||
public class ElApp extends Application {
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,711 @@
|
||||
/*
|
||||
* ElBridge.java — Android Java companion to el_android.c.
|
||||
*
|
||||
* All public methods are static. The C JNI layer calls these to create views,
|
||||
* set properties, and manage the widget tree. Views are identified by integer
|
||||
* slot indices matching the C-side handle values.
|
||||
*
|
||||
* Threading: every method that touches a View dispatches to the UI thread
|
||||
* using Activity.runOnUiThread(Runnable) and blocks with a CountDownLatch
|
||||
* until the UI thread completes the operation. This mirrors the AppKit
|
||||
* dispatch_sync(main_queue, ^{}) pattern in el_appkit.m.
|
||||
*
|
||||
* Callbacks: Java sets listeners on views that call back into C via:
|
||||
* nativeOnClick(int slot)
|
||||
* nativeOnChange(int slot, String text)
|
||||
* nativeOnSubmit(int slot, String text)
|
||||
* These are declared native and implemented in el_android.c.
|
||||
*
|
||||
* Usage (in your Activity.onCreate):
|
||||
* System.loadLibrary("elruntime");
|
||||
* ElBridge.init(this);
|
||||
*
|
||||
* The native library calls __native_init() which calls nativeRegisterActivity
|
||||
* via the C side; alternatively call ElBridge.init(this) directly from Java.
|
||||
*
|
||||
* Compile requirements:
|
||||
* Android minSdkVersion 21 (Lollipop) or higher.
|
||||
* No third-party dependencies — uses only android.* framework classes.
|
||||
* For image loading from arbitrary file paths, BitmapFactory is used.
|
||||
* To replace with Glide/Picasso, edit createImageView only.
|
||||
*/
|
||||
|
||||
package com.neuron.el;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Typeface;
|
||||
import android.graphics.drawable.GradientDrawable;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
public class ElBridge {
|
||||
|
||||
/* ── Native callbacks (implemented in el_android.c) ─────────────────── */
|
||||
|
||||
public static native void nativeOnClick(int slot);
|
||||
public static native void nativeOnChange(int slot, String text);
|
||||
public static native void nativeOnSubmit(int slot, String text);
|
||||
public static native void nativeRegisterActivity(Activity activity);
|
||||
|
||||
/* ── State ───────────────────────────────────────────────────────────── */
|
||||
|
||||
private static final int MAX_SLOTS = 4096;
|
||||
|
||||
private static Activity sActivity;
|
||||
private static Handler sUiHandler;
|
||||
private static View[] sViews = new View[MAX_SLOTS];
|
||||
private static int sNextSlot = 1; /* slot 0 reserved / null */
|
||||
|
||||
/* ── Init ────────────────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Must be called from the Activity before any widget operations.
|
||||
* Typically called from Activity.onCreate after System.loadLibrary.
|
||||
*/
|
||||
public static void init(Activity activity) {
|
||||
sActivity = activity;
|
||||
sUiHandler = new Handler(Looper.getMainLooper());
|
||||
nativeRegisterActivity(activity);
|
||||
}
|
||||
|
||||
/* ── Slot management ─────────────────────────────────────────────────── */
|
||||
|
||||
private static int allocSlot(View v) {
|
||||
/* Find a free slot starting from sNextSlot, wrap around. */
|
||||
for (int i = 0; i < MAX_SLOTS - 1; i++) {
|
||||
int idx = ((sNextSlot - 1 + i) % (MAX_SLOTS - 1)) + 1;
|
||||
if (sViews[idx] == null) {
|
||||
sViews[idx] = v;
|
||||
sNextSlot = (idx % (MAX_SLOTS - 1)) + 1;
|
||||
return idx;
|
||||
}
|
||||
}
|
||||
android.util.Log.e("ElBridge", "allocSlot: slot table full");
|
||||
return -1;
|
||||
}
|
||||
|
||||
private static View getView(int slot) {
|
||||
if (slot <= 0 || slot >= MAX_SLOTS) return null;
|
||||
return sViews[slot];
|
||||
}
|
||||
|
||||
/* ── UI-thread dispatch helper ───────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* Dispatch r on the UI thread and block until it completes.
|
||||
* Safe to call from the UI thread itself (runs inline without posting).
|
||||
*/
|
||||
private static void runSync(final Runnable r) {
|
||||
if (Looper.myLooper() == Looper.getMainLooper()) {
|
||||
r.run();
|
||||
} else {
|
||||
final CountDownLatch latch = new CountDownLatch(1);
|
||||
sUiHandler.post(new Runnable() {
|
||||
@Override public void run() {
|
||||
try { r.run(); } finally { latch.countDown(); }
|
||||
}
|
||||
});
|
||||
try { latch.await(); } catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Integer slot returning runSync helper ───────────────────────────── */
|
||||
|
||||
private interface IntSupplier { int get(); }
|
||||
|
||||
private static int runSyncInt(final IntSupplier s) {
|
||||
final int[] result = { -1 };
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() { result[0] = s.get(); }
|
||||
});
|
||||
return result[0];
|
||||
}
|
||||
|
||||
/* ── Context accessor ────────────────────────────────────────────────── */
|
||||
|
||||
private static Context ctx() { return sActivity; }
|
||||
|
||||
/* ── View creation ───────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Create a LinearLayout.
|
||||
* @param orientation 1=VERTICAL, 0=HORIZONTAL
|
||||
* @param spacing gap between children in dp; applied as bottom/right margin
|
||||
*/
|
||||
public static int createLinearLayout(final int orientation, final int spacing) {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
LinearLayout ll = new LinearLayout(ctx());
|
||||
ll.setOrientation(orientation == 1
|
||||
? LinearLayout.VERTICAL
|
||||
: LinearLayout.HORIZONTAL);
|
||||
ll.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
/* Spacing is stored so addChild can apply margins. */
|
||||
ll.setTag(R_TAG_SPACING, spacing);
|
||||
return allocSlot(ll);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Create a FrameLayout (ZStack equivalent). */
|
||||
public static int createFrameLayout() {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
FrameLayout fl = new FrameLayout(ctx());
|
||||
fl.setLayoutParams(new FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return allocSlot(fl);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Create a ScrollView. */
|
||||
public static int createScrollView() {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
ScrollView sv = new ScrollView(ctx());
|
||||
sv.setLayoutParams(new ScrollView.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
sv.setFillViewport(true);
|
||||
return allocSlot(sv);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Create a TextView with initial text. */
|
||||
public static int createTextView(final String text) {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
TextView tv = new TextView(ctx());
|
||||
tv.setText(text != null ? text : "");
|
||||
tv.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return allocSlot(tv);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Create a Button with a label. */
|
||||
public static int createButton(final String label) {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
Button btn = new Button(ctx());
|
||||
btn.setText(label != null ? label : "");
|
||||
btn.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return allocSlot(btn);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an EditText.
|
||||
* @param placeholder hint text
|
||||
* @param singleLine true = single-line text field; false = multi-line text area
|
||||
*/
|
||||
public static int createEditText(final String placeholder, final boolean singleLine) {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
EditText et = new EditText(ctx());
|
||||
et.setHint(placeholder != null ? placeholder : "");
|
||||
if (singleLine) {
|
||||
et.setInputType(InputType.TYPE_CLASS_TEXT
|
||||
| InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
|
||||
et.setMaxLines(1);
|
||||
et.setSingleLine(true);
|
||||
} else {
|
||||
et.setInputType(InputType.TYPE_CLASS_TEXT
|
||||
| InputType.TYPE_TEXT_FLAG_MULTI_LINE);
|
||||
et.setMinLines(3);
|
||||
et.setSingleLine(false);
|
||||
et.setGravity(Gravity.TOP | Gravity.START);
|
||||
}
|
||||
et.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return allocSlot(et);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ImageView, loading from a file path via BitmapFactory.
|
||||
* If path is null/empty the ImageView is created with no image.
|
||||
*/
|
||||
public static int createImageView(final String path) {
|
||||
return runSyncInt(new IntSupplier() {
|
||||
@Override public int get() {
|
||||
ImageView iv = new ImageView(ctx());
|
||||
iv.setScaleType(ImageView.ScaleType.FIT_CENTER);
|
||||
iv.setAdjustViewBounds(true);
|
||||
if (path != null && !path.isEmpty()) {
|
||||
Bitmap bmp = BitmapFactory.decodeFile(path);
|
||||
if (bmp != null) {
|
||||
iv.setImageBitmap(bmp);
|
||||
} else {
|
||||
android.util.Log.w("ElBridge",
|
||||
"createImageView: failed to decode " + path);
|
||||
}
|
||||
}
|
||||
iv.setLayoutParams(new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT));
|
||||
return allocSlot(iv);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Window operations ───────────────────────────────────────────────── */
|
||||
|
||||
/** Set the Activity's content view to the view at slot. */
|
||||
public static void setContentView(final int slot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v != null && sActivity != null) {
|
||||
sActivity.setContentView(v);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set the Activity title. */
|
||||
public static void setTitle(final String title) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
if (sActivity != null) {
|
||||
sActivity.setTitle(title != null ? title : "");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Tree operations ─────────────────────────────────────────────────── */
|
||||
|
||||
/**
|
||||
* Add child view to parent view.
|
||||
* LinearLayout: child added as arranged child with spacing margin.
|
||||
* ScrollView: child replaces current document view.
|
||||
* FrameLayout / other ViewGroup: plain addView.
|
||||
*/
|
||||
public static void addChild(final int parentSlot, final int childSlot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View parent = getView(parentSlot);
|
||||
View child = getView(childSlot);
|
||||
if (parent == null || child == null) return;
|
||||
|
||||
/* Remove child from existing parent first. */
|
||||
if (child.getParent() instanceof ViewGroup) {
|
||||
((ViewGroup) child.getParent()).removeView(child);
|
||||
}
|
||||
|
||||
if (parent instanceof LinearLayout) {
|
||||
LinearLayout ll = (LinearLayout) parent;
|
||||
Object tag = ll.getTag(R_TAG_SPACING);
|
||||
int spacing = (tag instanceof Integer) ? (Integer) tag : 0;
|
||||
LinearLayout.LayoutParams lp;
|
||||
Object existingLp = child.getLayoutParams();
|
||||
if (existingLp instanceof LinearLayout.LayoutParams) {
|
||||
lp = (LinearLayout.LayoutParams) existingLp;
|
||||
} else {
|
||||
lp = new LinearLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
}
|
||||
/* Apply spacing as margin on the leading/top edge (after first child). */
|
||||
if (ll.getChildCount() > 0 && spacing > 0) {
|
||||
int px = dpToPx(spacing);
|
||||
if (ll.getOrientation() == LinearLayout.VERTICAL) {
|
||||
lp.topMargin = px;
|
||||
} else {
|
||||
lp.leftMargin = px;
|
||||
}
|
||||
}
|
||||
child.setLayoutParams(lp);
|
||||
ll.addView(child);
|
||||
} else if (parent instanceof ScrollView) {
|
||||
ScrollView sv = (ScrollView) parent;
|
||||
sv.removeAllViews();
|
||||
sv.addView(child);
|
||||
} else if (parent instanceof ViewGroup) {
|
||||
((ViewGroup) parent).addView(child);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Remove child from its parent. */
|
||||
public static void removeChild(final int parentSlot, final int childSlot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View parent = getView(parentSlot);
|
||||
View child = getView(childSlot);
|
||||
if (parent instanceof ViewGroup && child != null) {
|
||||
((ViewGroup) parent).removeView(child);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Remove the view from its parent and release the slot. */
|
||||
public static void destroyView(final int slot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
if (v.getParent() instanceof ViewGroup) {
|
||||
((ViewGroup) v.getParent()).removeView(v);
|
||||
}
|
||||
sViews[slot] = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Property setters ────────────────────────────────────────────────── */
|
||||
|
||||
public static void setText(final int slot, final String text) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
String s = text != null ? text : "";
|
||||
if (v instanceof EditText) {
|
||||
((EditText) v).setText(s);
|
||||
} else if (v instanceof Button) {
|
||||
((Button) v).setText(s);
|
||||
} else if (v instanceof TextView) {
|
||||
((TextView) v).setText(s);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static String getText(final int slot) {
|
||||
final String[] result = { "" };
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v instanceof TextView) {
|
||||
CharSequence cs = ((TextView) v).getText();
|
||||
result[0] = cs != null ? cs.toString() : "";
|
||||
}
|
||||
}
|
||||
});
|
||||
return result[0];
|
||||
}
|
||||
|
||||
/** Set foreground text color. Components in [0,1]. */
|
||||
public static void setTextColor(final int slot, final float r, final float g,
|
||||
final float b, final float a) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v instanceof TextView) {
|
||||
((TextView) v).setTextColor(floatToArgb(r, g, b, a));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set background color using a GradientDrawable so corner radius is preserved. */
|
||||
public static void setBackgroundColor(final int slot, final float r, final float g,
|
||||
final float b, final float a) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
ensureGradientBackground(v);
|
||||
GradientDrawable gd = (GradientDrawable) v.getBackground();
|
||||
gd.setColor(floatToArgb(r, g, b, a));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set font family and size.
|
||||
* family: "system" or null → system default; otherwise tries to load by name.
|
||||
* bold: if true uses Typeface.BOLD.
|
||||
*/
|
||||
public static void setFont(final int slot, final String family,
|
||||
final int sizeSp, final boolean bold) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (!(v instanceof TextView)) return;
|
||||
TextView tv = (TextView) v;
|
||||
Typeface tf;
|
||||
if (family != null && !family.isEmpty()
|
||||
&& !family.equals("system")) {
|
||||
Typeface base = Typeface.create(family,
|
||||
bold ? Typeface.BOLD : Typeface.NORMAL);
|
||||
tf = (base != null) ? base
|
||||
: Typeface.defaultFromStyle(bold ? Typeface.BOLD : Typeface.NORMAL);
|
||||
} else {
|
||||
tf = Typeface.defaultFromStyle(bold ? Typeface.BOLD : Typeface.NORMAL);
|
||||
}
|
||||
tv.setTypeface(tf);
|
||||
tv.setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, sizeSp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set padding in dp. */
|
||||
public static void setPadding(final int slot, final int top, final int right,
|
||||
final int bottom, final int left) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v != null) {
|
||||
v.setPadding(dpToPx(left), dpToPx(top), dpToPx(right), dpToPx(bottom));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set explicit width in dp. Passes MATCH_PARENT for negative values. */
|
||||
public static void setWidth(final int slot, final int widthDp) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
ViewGroup.LayoutParams lp = v.getLayoutParams();
|
||||
if (lp == null) lp = new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
lp.width = widthDp < 0
|
||||
? ViewGroup.LayoutParams.MATCH_PARENT
|
||||
: dpToPx(widthDp);
|
||||
v.setLayoutParams(lp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set explicit height in dp. */
|
||||
public static void setHeight(final int slot, final int heightDp) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
ViewGroup.LayoutParams lp = v.getLayoutParams();
|
||||
if (lp == null) lp = new ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT);
|
||||
lp.height = heightDp < 0
|
||||
? ViewGroup.LayoutParams.MATCH_PARENT
|
||||
: dpToPx(heightDp);
|
||||
v.setLayoutParams(lp);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set flex weight on a child of a LinearLayout.
|
||||
* flex > 0 → weight = flex, width/height = 0dp (expand).
|
||||
* flex == 0 → weight = 0, wrap_content (shrink to content).
|
||||
*/
|
||||
public static void setFlex(final int slot, final int flex) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
ViewGroup.LayoutParams lp = v.getLayoutParams();
|
||||
if (lp instanceof LinearLayout.LayoutParams) {
|
||||
LinearLayout.LayoutParams llp = (LinearLayout.LayoutParams) lp;
|
||||
if (flex > 0) {
|
||||
llp.weight = (float) flex;
|
||||
/* Determine orientation from parent to set 0dp on the right axis. */
|
||||
if (v.getParent() instanceof LinearLayout) {
|
||||
LinearLayout parent = (LinearLayout) v.getParent();
|
||||
if (parent.getOrientation() == LinearLayout.VERTICAL) {
|
||||
llp.height = 0;
|
||||
} else {
|
||||
llp.width = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
llp.weight = 0f;
|
||||
}
|
||||
v.setLayoutParams(llp);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/** Set corner radius in dp using a GradientDrawable background. */
|
||||
public static void setCornerRadius(final int slot, final float radiusDp) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
ensureGradientBackground(v);
|
||||
GradientDrawable gd = (GradientDrawable) v.getBackground();
|
||||
gd.setCornerRadius(dpToPxF(radiusDp));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static void setEnabled(final int slot, final boolean enabled) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v != null) v.setEnabled(enabled);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show or hide a view.
|
||||
* @param visible true = VISIBLE, false = GONE (matches AppKit setHidden semantics)
|
||||
*/
|
||||
public static void setVisibility(final int slot, final boolean visible) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v != null) v.setVisibility(visible ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Event listener registration ─────────────────────────────────────── */
|
||||
|
||||
/** Register an OnClickListener that calls back into C nativeOnClick. */
|
||||
public static void setOnClickListener(final int slot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (v == null) return;
|
||||
final int capturedSlot = slot;
|
||||
v.setOnClickListener(new View.OnClickListener() {
|
||||
@Override public void onClick(View view) {
|
||||
nativeOnClick(capturedSlot);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a TextWatcher on an EditText that calls back nativeOnChange
|
||||
* for every text change.
|
||||
*/
|
||||
public static void setOnChangeListener(final int slot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (!(v instanceof EditText)) return;
|
||||
final int capturedSlot = slot;
|
||||
((EditText) v).addTextChangedListener(new TextWatcher() {
|
||||
@Override public void beforeTextChanged(CharSequence s, int start,
|
||||
int count, int after) {}
|
||||
@Override public void onTextChanged(CharSequence s, int start,
|
||||
int before, int count) {}
|
||||
@Override public void afterTextChanged(Editable s) {
|
||||
nativeOnChange(capturedSlot, s != null ? s.toString() : "");
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Register an OnEditorActionListener on a single-line EditText that calls
|
||||
* nativeOnSubmit when the user presses the action/enter key.
|
||||
*/
|
||||
public static void setOnSubmitListener(final int slot) {
|
||||
runSync(new Runnable() {
|
||||
@Override public void run() {
|
||||
View v = getView(slot);
|
||||
if (!(v instanceof EditText)) return;
|
||||
final int capturedSlot = slot;
|
||||
((EditText) v).setOnEditorActionListener(
|
||||
new TextView.OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView tv, int actionId,
|
||||
android.view.KeyEvent event) {
|
||||
nativeOnSubmit(capturedSlot,
|
||||
tv.getText() != null ? tv.getText().toString() : "");
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* ── Internal helpers ─────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* Tag key used to stash the spacing value on LinearLayouts so addChild
|
||||
* can apply the correct margin between children.
|
||||
* We use a stable integer resource-id-like value; because we do not have
|
||||
* a resources file here we use View.generateViewId() lazily.
|
||||
*/
|
||||
private static int sSpacingTagKey = 0;
|
||||
|
||||
private static int R_TAG_SPACING;
|
||||
static {
|
||||
R_TAG_SPACING = View.generateViewId();
|
||||
}
|
||||
|
||||
/** Convert dp to pixels using the Activity's display metrics. */
|
||||
private static int dpToPx(float dp) {
|
||||
if (sActivity == null) return (int) dp;
|
||||
float density = sActivity.getResources().getDisplayMetrics().density;
|
||||
return Math.round(dp * density);
|
||||
}
|
||||
|
||||
private static float dpToPxF(float dp) {
|
||||
if (sActivity == null) return dp;
|
||||
float density = sActivity.getResources().getDisplayMetrics().density;
|
||||
return dp * density;
|
||||
}
|
||||
|
||||
/** Convert RGBA float components (0–1) to an Android ARGB int. */
|
||||
private static int floatToArgb(float r, float g, float b, float a) {
|
||||
int ai = Math.round(a * 255f);
|
||||
int ri = Math.round(r * 255f);
|
||||
int gi = Math.round(g * 255f);
|
||||
int bi = Math.round(b * 255f);
|
||||
return Color.argb(ai, ri, gi, bi);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the view has a GradientDrawable background so that both color
|
||||
* and corner radius can be set independently. If the current background
|
||||
* is already a GradientDrawable it is reused; otherwise a new transparent
|
||||
* one is installed.
|
||||
*/
|
||||
private static void ensureGradientBackground(View v) {
|
||||
if (!(v.getBackground() instanceof GradientDrawable)) {
|
||||
GradientDrawable gd = new GradientDrawable();
|
||||
gd.setColor(Color.TRANSPARENT);
|
||||
v.setBackground(gd);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.neuron.el;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.os.Bundle;
|
||||
|
||||
/**
|
||||
* MainActivity — entry point for native-hello-android.
|
||||
*
|
||||
* Loads the el native shared library, initialises ElBridge with the Activity
|
||||
* reference (required before any widget operations), then hands control to the
|
||||
* compiled el program via nativeMain().
|
||||
*
|
||||
* The el boot sequence (native_init → window_from_manifest → app_build →
|
||||
* window_show) runs inside nativeMain. __native_run_loop is a no-op on Android;
|
||||
* the Activity lifecycle owns the UI thread after onCreate returns.
|
||||
*/
|
||||
public class MainActivity extends Activity {
|
||||
|
||||
static {
|
||||
System.loadLibrary("elnative");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
// Register this Activity with the bridge BEFORE nativeMain so that
|
||||
// el_android_init can look up ElBridge methods and __native_init works.
|
||||
ElBridge.init(this);
|
||||
// Run the compiled el program.
|
||||
nativeMain();
|
||||
}
|
||||
|
||||
/**
|
||||
* Implemented in el_android.c as Java_com_neuron_el_MainActivity_nativeMain.
|
||||
* Calls the el program's main(), which runs the full boot sequence.
|
||||
*/
|
||||
private native void nativeMain();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
cmake_minimum_required(VERSION 3.22.1)
|
||||
project("elnative")
|
||||
|
||||
#
|
||||
# CMakeLists.txt for native-hello-android
|
||||
#
|
||||
# Sources:
|
||||
# el_android.c — Android JNI widget bridge
|
||||
# el_seed.c — OS-boundary __-prefixed primitives
|
||||
# el_runtime.c — High-level el builtins (str_len, json_*, etc.)
|
||||
# el_native_vessel.c — el-native vessel (compiled from vessels/el-native/src/main.el)
|
||||
# native_hello.c — App entry point (compiled from examples/native-hello/src/main.el)
|
||||
#
|
||||
# el_runtime.c and el_seed.c both define __-prefixed symbols. On Android the
|
||||
# linker accepts duplicate weak symbols; the shared library loads one copy.
|
||||
# The EL_TARGET_ANDROID guard in el_android.c ensures only the Android widget
|
||||
# backend is compiled in.
|
||||
#
|
||||
|
||||
add_library(
|
||||
elnative
|
||||
SHARED
|
||||
|
||||
# native_hello.c listed first so its main() takes precedence over the
|
||||
# vessel's main() when --allow-multiple-definition is in effect.
|
||||
native_hello.c
|
||||
el_native_vessel.c
|
||||
el_android.c
|
||||
el_seed.c
|
||||
el_runtime.c
|
||||
)
|
||||
|
||||
# EL_TARGET_ANDROID activates the Android JNI widget backend in el_android.c
|
||||
# and the corresponding guards in el_seed.c / el_native_target.h.
|
||||
target_compile_definitions(elnative PRIVATE
|
||||
EL_TARGET_ANDROID
|
||||
)
|
||||
|
||||
target_include_directories(elnative PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
)
|
||||
|
||||
# el_runtime.c and el_seed.c both define __-prefixed OS-boundary symbols.
|
||||
# el_runtime.c's copies are weaker definitions; allow-multiple-definition lets
|
||||
# the linker pick one copy silently (el_seed.c listed later = its copy wins
|
||||
# in the Android linker's right-to-left resolution order).
|
||||
target_link_options(elnative PRIVATE
|
||||
"-Wl,--allow-multiple-definition"
|
||||
)
|
||||
|
||||
find_library(log-lib log)
|
||||
find_library(android-lib android)
|
||||
|
||||
target_link_libraries(elnative
|
||||
${log-lib}
|
||||
${android-lib}
|
||||
)
|
||||
@@ -0,0 +1,964 @@
|
||||
/*
|
||||
* el_android.c — Android JNI backend for the el native widget system.
|
||||
*
|
||||
* This file implements the Android widget layer that el_seed.c calls through
|
||||
* to when EL_TARGET_ANDROID is defined. It is the exact Android counterpart
|
||||
* to el_appkit.m and presents the same C API surface.
|
||||
*
|
||||
* Architecture:
|
||||
* el program (el code)
|
||||
* → __widget_* C builtins in el_seed.c
|
||||
* → el_android_* C-callable functions declared here
|
||||
* → ElBridge static methods in Java via JNI
|
||||
* → android.view.View subclasses on the UI thread
|
||||
*
|
||||
* Widget handles: every widget (window root, view, control) is assigned an
|
||||
* int64_t slot index into view_slots[]. The el program holds these as opaque
|
||||
* Int values. Slot 0 is never valid (null handle = -1 convention).
|
||||
*
|
||||
* Threading: Android requires all UI operations to run on the main (UI) thread.
|
||||
* Every JNI call that mutates a View is dispatched through
|
||||
* Activity.runOnUiThread(Runnable) if the current thread is not the UI thread.
|
||||
* el_android_run_loop is a no-op — Android lifecycle is driven by the Activity.
|
||||
*
|
||||
* Callback mechanism: when a widget fires an event Java calls
|
||||
* nativeOnClick / nativeOnChange / nativeOnSubmit
|
||||
* The C side looks up the registered El function name for that slot, then:
|
||||
* dlsym(RTLD_DEFAULT, fn_name)(widget_handle, event_data_string)
|
||||
* This matches the __thread_create pattern in el_seed.c exactly.
|
||||
*
|
||||
* Compile / link (as part of libelruntime.so):
|
||||
* Compiled by the Android Gradle NDK build system with -DEL_TARGET_ANDROID.
|
||||
* Link flags: -landroid -llog -ldl
|
||||
*
|
||||
* Java companion: ElBridge.java in the same directory must be compiled into
|
||||
* the Android application's APK (package com.neuron.el).
|
||||
*/
|
||||
|
||||
#ifdef EL_TARGET_ANDROID
|
||||
|
||||
#include <jni.h>
|
||||
#include <android/log.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <dlfcn.h>
|
||||
#include "el_runtime.h"
|
||||
|
||||
/* ── Logging ─────────────────────────────────────────────────────────────── */
|
||||
|
||||
#define EL_TAG "ElAndroid"
|
||||
#define EL_LOGI(...) __android_log_print(ANDROID_LOG_INFO, EL_TAG, __VA_ARGS__)
|
||||
#define EL_LOGW(...) __android_log_print(ANDROID_LOG_WARN, EL_TAG, __VA_ARGS__)
|
||||
#define EL_LOGE(...) __android_log_print(ANDROID_LOG_ERROR, EL_TAG, __VA_ARGS__)
|
||||
|
||||
/* ── JNI global state ────────────────────────────────────────────────────── */
|
||||
|
||||
static JavaVM *g_jvm = NULL;
|
||||
static jobject g_activity = NULL; /* global ref to Activity */
|
||||
static jclass g_bridge_class = NULL; /* global ref to ElBridge class */
|
||||
|
||||
/* Cached method IDs on ElBridge — filled in el_android_init(). */
|
||||
static jmethodID g_mid_createLinearLayout = NULL;
|
||||
static jmethodID g_mid_createFrameLayout = NULL;
|
||||
static jmethodID g_mid_createScrollView = NULL;
|
||||
static jmethodID g_mid_createTextView = NULL;
|
||||
static jmethodID g_mid_createButton = NULL;
|
||||
static jmethodID g_mid_createEditText = NULL;
|
||||
static jmethodID g_mid_createImageView = NULL;
|
||||
static jmethodID g_mid_setContentView = NULL;
|
||||
static jmethodID g_mid_setTitle = NULL;
|
||||
static jmethodID g_mid_addChild = NULL;
|
||||
static jmethodID g_mid_removeChild = NULL;
|
||||
static jmethodID g_mid_destroyView = NULL;
|
||||
static jmethodID g_mid_setText = NULL;
|
||||
static jmethodID g_mid_getText = NULL;
|
||||
static jmethodID g_mid_setTextColor = NULL;
|
||||
static jmethodID g_mid_setBackgroundColor = NULL;
|
||||
static jmethodID g_mid_setFont = NULL;
|
||||
static jmethodID g_mid_setPadding = NULL;
|
||||
static jmethodID g_mid_setWidth = NULL;
|
||||
static jmethodID g_mid_setHeight = NULL;
|
||||
static jmethodID g_mid_setFlex = NULL;
|
||||
static jmethodID g_mid_setCornerRadius = NULL;
|
||||
static jmethodID g_mid_setEnabled = NULL;
|
||||
static jmethodID g_mid_setVisibility = NULL;
|
||||
static jmethodID g_mid_setOnClickListener = NULL;
|
||||
static jmethodID g_mid_setOnChangeListener = NULL;
|
||||
static jmethodID g_mid_setOnSubmitListener = NULL;
|
||||
static jmethodID g_mid_runOnUiThread = NULL; /* Activity.runOnUiThread */
|
||||
|
||||
/* ── Widget table ─────────────────────────────────────────────────────────── */
|
||||
|
||||
#define EL_ANDROID_MAX_WIDGETS 4096
|
||||
|
||||
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;
|
||||
|
||||
typedef struct {
|
||||
ElWidgetKind kind;
|
||||
jint slot; /* Java-side slot index (matches C index) */
|
||||
char *cb_click; /* El function name for click / submit events */
|
||||
char *cb_change; /* El function name for value-change events */
|
||||
} ElWidget;
|
||||
|
||||
static ElWidget _el_widgets[EL_ANDROID_MAX_WIDGETS];
|
||||
|
||||
static int64_t el_widget_alloc(ElWidgetKind kind, jint slot) {
|
||||
for (int i = 1; i < EL_ANDROID_MAX_WIDGETS; i++) {
|
||||
if (_el_widgets[i].kind == EL_WIDGET_FREE) {
|
||||
_el_widgets[i].kind = kind;
|
||||
_el_widgets[i].slot = slot;
|
||||
_el_widgets[i].cb_click = NULL;
|
||||
_el_widgets[i].cb_change = NULL;
|
||||
return (int64_t)i;
|
||||
}
|
||||
}
|
||||
EL_LOGE("el_widget_alloc: slot table full");
|
||||
return -1;
|
||||
}
|
||||
|
||||
static ElWidget *el_widget_get(int64_t handle) {
|
||||
if (handle <= 0 || handle >= EL_ANDROID_MAX_WIDGETS) return NULL;
|
||||
if (_el_widgets[handle].kind == EL_WIDGET_FREE) return NULL;
|
||||
return &_el_widgets[handle];
|
||||
}
|
||||
|
||||
static void el_widget_free(int64_t handle) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
w->kind = EL_WIDGET_FREE;
|
||||
w->slot = -1;
|
||||
free(w->cb_click); w->cb_click = NULL;
|
||||
free(w->cb_change); w->cb_change = NULL;
|
||||
}
|
||||
|
||||
/* ── JNI environment helpers ─────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* Obtain a JNIEnv for the calling thread. Attaches the thread to the JVM if
|
||||
* needed (detaches in el_jni_detach_if_attached — call in pairs).
|
||||
*/
|
||||
static int g_was_attached = 0; /* thread-local would be cleaner but this is
|
||||
safe for single-threaded el programs */
|
||||
|
||||
static JNIEnv *el_jni_env(void) {
|
||||
if (!g_jvm) return NULL;
|
||||
JNIEnv *env = NULL;
|
||||
jint rc = (*g_jvm)->GetEnv(g_jvm, (void **)&env, JNI_VERSION_1_6);
|
||||
if (rc == JNI_OK) { g_was_attached = 0; return env; }
|
||||
if (rc == JNI_EDETACHED) {
|
||||
if ((*g_jvm)->AttachCurrentThread(g_jvm, &env, NULL) == JNI_OK) {
|
||||
g_was_attached = 1;
|
||||
return env;
|
||||
}
|
||||
}
|
||||
EL_LOGE("el_jni_env: failed to obtain JNIEnv");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void el_jni_detach_if_attached(void) {
|
||||
if (g_was_attached && g_jvm) {
|
||||
(*g_jvm)->DetachCurrentThread(g_jvm);
|
||||
g_was_attached = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── UI-thread dispatch ──────────────────────────────────────────────────── */
|
||||
/*
|
||||
* Most ElBridge static methods already dispatch to the UI thread internally
|
||||
* (they call Activity.runOnUiThread). The helper below is available for
|
||||
* cases where the caller needs to be sure the call has completed before
|
||||
* returning (ElBridge methods marked "sync" use a CountDownLatch internally).
|
||||
*
|
||||
* For the current implementation we call ElBridge methods directly; ElBridge
|
||||
* itself marshals to the UI thread via Activity.runOnUiThread + latch.
|
||||
* This keeps the C side simple and mirrors the AppKit dispatch_sync pattern.
|
||||
*/
|
||||
|
||||
/* ── JNI_OnLoad ──────────────────────────────────────────────────────────── */
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
(void)reserved;
|
||||
g_jvm = vm;
|
||||
EL_LOGI("JNI_OnLoad: el Android bridge loaded");
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
/* ── el_android_init ─────────────────────────────────────────────────────── */
|
||||
/*
|
||||
* Called from __native_init(). The Activity must have already called
|
||||
* ElBridge.registerActivity(activity) from Java before this runs, which sets
|
||||
* g_activity via the nativeRegisterActivity JNI method below.
|
||||
*
|
||||
* Caches all method IDs used later so individual widget calls avoid repeated
|
||||
* FindClass / GetStaticMethodID lookups.
|
||||
*/
|
||||
void el_android_init(void) {
|
||||
static int done = 0;
|
||||
if (done) return;
|
||||
done = 1;
|
||||
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env) { EL_LOGE("el_android_init: no JNIEnv"); return; }
|
||||
|
||||
jclass cls = (*env)->FindClass(env, "com/neuron/el/ElBridge");
|
||||
if (!cls) { EL_LOGE("el_android_init: ElBridge class not found"); return; }
|
||||
g_bridge_class = (*env)->NewGlobalRef(env, cls);
|
||||
(*env)->DeleteLocalRef(env, cls);
|
||||
|
||||
#define CACHE_STATIC(var, name, sig) \
|
||||
var = (*env)->GetStaticMethodID(env, g_bridge_class, name, sig); \
|
||||
if (!var) EL_LOGW("el_android_init: method not found: %s %s", name, sig)
|
||||
|
||||
CACHE_STATIC(g_mid_createLinearLayout, "createLinearLayout", "(II)I");
|
||||
CACHE_STATIC(g_mid_createFrameLayout, "createFrameLayout", "()I");
|
||||
CACHE_STATIC(g_mid_createScrollView, "createScrollView", "()I");
|
||||
CACHE_STATIC(g_mid_createTextView, "createTextView", "(Ljava/lang/String;)I");
|
||||
CACHE_STATIC(g_mid_createButton, "createButton", "(Ljava/lang/String;)I");
|
||||
CACHE_STATIC(g_mid_createEditText, "createEditText", "(Ljava/lang/String;Z)I");
|
||||
CACHE_STATIC(g_mid_createImageView, "createImageView", "(Ljava/lang/String;)I");
|
||||
CACHE_STATIC(g_mid_setContentView, "setContentView", "(I)V");
|
||||
CACHE_STATIC(g_mid_setTitle, "setTitle", "(Ljava/lang/String;)V");
|
||||
CACHE_STATIC(g_mid_addChild, "addChild", "(II)V");
|
||||
CACHE_STATIC(g_mid_removeChild, "removeChild", "(II)V");
|
||||
CACHE_STATIC(g_mid_destroyView, "destroyView", "(I)V");
|
||||
CACHE_STATIC(g_mid_setText, "setText", "(ILjava/lang/String;)V");
|
||||
CACHE_STATIC(g_mid_getText, "getText", "(I)Ljava/lang/String;");
|
||||
CACHE_STATIC(g_mid_setTextColor, "setTextColor", "(IFFFF)V");
|
||||
CACHE_STATIC(g_mid_setBackgroundColor, "setBackgroundColor", "(IFFFF)V");
|
||||
CACHE_STATIC(g_mid_setFont, "setFont", "(ILjava/lang/String;IZ)V");
|
||||
CACHE_STATIC(g_mid_setPadding, "setPadding", "(IIIII)V");
|
||||
CACHE_STATIC(g_mid_setWidth, "setWidth", "(II)V");
|
||||
CACHE_STATIC(g_mid_setHeight, "setHeight", "(II)V");
|
||||
CACHE_STATIC(g_mid_setFlex, "setFlex", "(II)V");
|
||||
CACHE_STATIC(g_mid_setCornerRadius, "setCornerRadius", "(IF)V");
|
||||
CACHE_STATIC(g_mid_setEnabled, "setEnabled", "(IZ)V");
|
||||
CACHE_STATIC(g_mid_setVisibility, "setVisibility", "(IZ)V");
|
||||
CACHE_STATIC(g_mid_setOnClickListener, "setOnClickListener", "(I)V");
|
||||
CACHE_STATIC(g_mid_setOnChangeListener, "setOnChangeListener", "(I)V");
|
||||
CACHE_STATIC(g_mid_setOnSubmitListener, "setOnSubmitListener", "(I)V");
|
||||
#undef CACHE_STATIC
|
||||
|
||||
el_jni_detach_if_attached();
|
||||
EL_LOGI("el_android_init: complete");
|
||||
}
|
||||
|
||||
/* ── JNI: Activity registration ─────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* Called from Java: ElBridge.registerActivity(activity) calls back here.
|
||||
* Stores a global reference to the Activity so C code can dispatch to it.
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_neuron_el_ElBridge_nativeRegisterActivity(JNIEnv *env, jclass cls,
|
||||
jobject activity) {
|
||||
(void)cls;
|
||||
if (g_activity) {
|
||||
(*env)->DeleteGlobalRef(env, g_activity);
|
||||
g_activity = NULL;
|
||||
}
|
||||
if (activity) {
|
||||
g_activity = (*env)->NewGlobalRef(env, activity);
|
||||
EL_LOGI("nativeRegisterActivity: activity registered");
|
||||
}
|
||||
}
|
||||
|
||||
/* ── El callback invocation ──────────────────────────────────────────────── */
|
||||
/*
|
||||
* Invoke an El callback by symbol name.
|
||||
* Signature matches AppKit: fn(handle: Int, data: String) -> Void
|
||||
* compiled to: void fn(el_val_t handle, el_val_t data)
|
||||
*/
|
||||
typedef void (*ElCb2)(int64_t handle, int64_t data);
|
||||
|
||||
static void el_android_invoke_cb(const char *fn_name, int64_t handle,
|
||||
const char *data) {
|
||||
if (!fn_name || !*fn_name) return;
|
||||
void *sym = dlsym(RTLD_DEFAULT, fn_name);
|
||||
if (!sym) { EL_LOGW("invoke_cb: symbol not found: %s", fn_name); return; }
|
||||
ElCb2 fn = (ElCb2)sym;
|
||||
fn(handle, (int64_t)(uintptr_t)(data ? data : ""));
|
||||
}
|
||||
|
||||
/* ── JNI: callbacks from Java → C ───────────────────────────────────────── */
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_neuron_el_ElBridge_nativeOnClick(JNIEnv *env, jclass cls, jint slot) {
|
||||
(void)env; (void)cls;
|
||||
int64_t handle = (int64_t)slot;
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (w && w->cb_click) {
|
||||
el_android_invoke_cb(w->cb_click, handle, "");
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_neuron_el_ElBridge_nativeOnChange(JNIEnv *env, jclass cls,
|
||||
jint slot, jstring text) {
|
||||
(void)cls;
|
||||
int64_t handle = (int64_t)slot;
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (w && w->cb_change) {
|
||||
const char *ctext = text ? (*env)->GetStringUTFChars(env, text, NULL) : "";
|
||||
el_android_invoke_cb(w->cb_change, handle, ctext);
|
||||
if (text) (*env)->ReleaseStringUTFChars(env, text, ctext);
|
||||
}
|
||||
}
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_neuron_el_ElBridge_nativeOnSubmit(JNIEnv *env, jclass cls,
|
||||
jint slot, jstring text) {
|
||||
(void)cls;
|
||||
int64_t handle = (int64_t)slot;
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (w && w->cb_click) { /* submit stored in cb_click, same as AppKit */
|
||||
const char *ctext = text ? (*env)->GetStringUTFChars(env, text, NULL) : "";
|
||||
el_android_invoke_cb(w->cb_click, handle, ctext);
|
||||
if (text) (*env)->ReleaseStringUTFChars(env, text, ctext);
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Helper: jstring from C string ──────────────────────────────────────── */
|
||||
|
||||
static jstring el_jstr(JNIEnv *env, const char *s) {
|
||||
return (*env)->NewStringUTF(env, s ? s : "");
|
||||
}
|
||||
|
||||
/* ── Window ──────────────────────────────────────────────────────────────── */
|
||||
|
||||
/*
|
||||
* el_android_window_create — on Android a "window" is the root LinearLayout
|
||||
* set as the Activity's content view. We create a vertical LinearLayout and
|
||||
* store it. el_android_window_show calls setContentView on the Activity.
|
||||
*/
|
||||
int64_t el_android_window_create(const char *title, int width, int height,
|
||||
int min_width, int min_height) {
|
||||
(void)width; (void)height; (void)min_width; (void)min_height;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
|
||||
/* VERTICAL LinearLayout with no spacing (spacing added via margins in Java) */
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createLinearLayout,
|
||||
(jint)1 /* VERTICAL */, (jint)0);
|
||||
if ((*env)->ExceptionCheck(env)) {
|
||||
(*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1;
|
||||
}
|
||||
|
||||
/* Set activity title */
|
||||
if (g_mid_setTitle && title) {
|
||||
jstring jtitle = el_jstr(env, title);
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setTitle, jtitle);
|
||||
(*env)->DeleteLocalRef(env, jtitle);
|
||||
}
|
||||
|
||||
int64_t handle = el_widget_alloc(EL_WIDGET_WINDOW, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return handle;
|
||||
}
|
||||
|
||||
void el_android_window_show(int64_t handle) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w || w->kind != EL_WIDGET_WINDOW) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setContentView,
|
||||
(jint)w->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_window_set_title(int64_t handle, const char *title) {
|
||||
(void)handle;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
jstring jtitle = el_jstr(env, title);
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setTitle, jtitle);
|
||||
(*env)->DeleteLocalRef(env, jtitle);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
/* ── Layout containers ───────────────────────────────────────────────────── */
|
||||
|
||||
int64_t el_android_vstack_create(int spacing) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createLinearLayout,
|
||||
(jint)1 /* VERTICAL */, (jint)spacing);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_VSTACK, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_hstack_create(int spacing) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createLinearLayout,
|
||||
(jint)0 /* HORIZONTAL */, (jint)spacing);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_HSTACK, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_zstack_create(void) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createFrameLayout);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_ZSTACK, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_scroll_create(void) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createScrollView);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_SCROLL, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
/* ── Widget factories ─────────────────────────────────────────────────────── */
|
||||
|
||||
int64_t el_android_label_create(const char *text) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jstring jt = el_jstr(env, text);
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createTextView, jt);
|
||||
(*env)->DeleteLocalRef(env, jt);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_LABEL, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_button_create(const char *label) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jstring jl = el_jstr(env, label);
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createButton, jl);
|
||||
(*env)->DeleteLocalRef(env, jl);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_BUTTON, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_text_field_create(const char *placeholder) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jstring jp = el_jstr(env, placeholder);
|
||||
/* singleLine = true */
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createEditText, jp, (jboolean)JNI_TRUE);
|
||||
(*env)->DeleteLocalRef(env, jp);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_TEXTFIELD, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_text_area_create(const char *placeholder) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jstring jp = el_jstr(env, placeholder);
|
||||
/* singleLine = false → multiline EditText */
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createEditText, jp, (jboolean)JNI_FALSE);
|
||||
(*env)->DeleteLocalRef(env, jp);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_TEXTAREA, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
int64_t el_android_image_create(const char *path) {
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return -1;
|
||||
jstring jp = el_jstr(env, path);
|
||||
jint slot = (*env)->CallStaticIntMethod(env, g_bridge_class,
|
||||
g_mid_createImageView, jp);
|
||||
(*env)->DeleteLocalRef(env, jp);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return -1; }
|
||||
int64_t h = el_widget_alloc(EL_WIDGET_IMAGE, (int)slot);
|
||||
el_jni_detach_if_attached();
|
||||
return h;
|
||||
}
|
||||
|
||||
/* ── Widget property setters ─────────────────────────────────────────────── */
|
||||
|
||||
void el_android_widget_set_text(int64_t handle, const char *text) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
jstring jt = el_jstr(env, text);
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setText,
|
||||
(jint)w->slot, jt);
|
||||
(*env)->DeleteLocalRef(env, jt);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
const char *el_android_widget_get_text(int64_t handle) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return "";
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return "";
|
||||
jstring js = (jstring)(*env)->CallStaticObjectMethod(env, g_bridge_class,
|
||||
g_mid_getText,
|
||||
(jint)w->slot);
|
||||
if ((*env)->ExceptionCheck(env)) { (*env)->ExceptionClear(env); el_jni_detach_if_attached(); return ""; }
|
||||
const char *result = "";
|
||||
if (js) {
|
||||
const char *cstr = (*env)->GetStringUTFChars(env, js, NULL);
|
||||
result = cstr ? strdup(cstr) : "";
|
||||
if (cstr) (*env)->ReleaseStringUTFChars(env, js, cstr);
|
||||
(*env)->DeleteLocalRef(env, js);
|
||||
}
|
||||
el_jni_detach_if_attached();
|
||||
return result;
|
||||
}
|
||||
|
||||
void el_android_widget_set_color(int64_t handle, float r, float g, float b, float a) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setTextColor,
|
||||
(jint)w->slot, (jfloat)r, (jfloat)g,
|
||||
(jfloat)b, (jfloat)a);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_bg_color(int64_t handle, float r, float g, float b, float a) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setBackgroundColor,
|
||||
(jint)w->slot, (jfloat)r, (jfloat)g,
|
||||
(jfloat)b, (jfloat)a);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_font(int64_t handle, const char *family, int size, int bold) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
jstring jfam = el_jstr(env, family);
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setFont,
|
||||
(jint)w->slot, jfam, (jint)size,
|
||||
(jboolean)(bold ? JNI_TRUE : JNI_FALSE));
|
||||
(*env)->DeleteLocalRef(env, jfam);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_padding(int64_t handle, int top, int right, int bottom, int left) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setPadding,
|
||||
(jint)w->slot, (jint)top, (jint)right,
|
||||
(jint)bottom, (jint)left);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_width(int64_t handle, int width) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setWidth,
|
||||
(jint)w->slot, (jint)width);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_height(int64_t handle, int height) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setHeight,
|
||||
(jint)w->slot, (jint)height);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_flex(int64_t handle, int flex) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setFlex,
|
||||
(jint)w->slot, (jint)flex);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_corner_radius(int64_t handle, int radius) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setCornerRadius,
|
||||
(jint)w->slot, (jfloat)radius);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_disabled(int64_t handle, int disabled) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setEnabled,
|
||||
(jint)w->slot,
|
||||
(jboolean)(disabled ? JNI_FALSE : JNI_TRUE));
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_set_hidden(int64_t handle, int hidden) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
/* visible=true means NOT hidden */
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setVisibility,
|
||||
(jint)w->slot,
|
||||
(jboolean)(hidden ? JNI_FALSE : JNI_TRUE));
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
/* ── Child management ─────────────────────────────────────────────────────── */
|
||||
|
||||
void el_android_widget_add_child(int64_t parent, int64_t child) {
|
||||
ElWidget *pw = el_widget_get(parent);
|
||||
ElWidget *cw = el_widget_get(child);
|
||||
if (!pw || !cw) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_addChild,
|
||||
(jint)pw->slot, (jint)cw->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_remove_child(int64_t parent, int64_t child) {
|
||||
ElWidget *pw = el_widget_get(parent);
|
||||
ElWidget *cw = el_widget_get(child);
|
||||
if (!pw || !cw) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_removeChild,
|
||||
(jint)pw->slot, (jint)cw->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
/* ── Event registration ───────────────────────────────────────────────────── */
|
||||
|
||||
void el_android_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;
|
||||
if (!w->cb_click) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setOnClickListener,
|
||||
(jint)w->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_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;
|
||||
if (!w->cb_change) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setOnChangeListener,
|
||||
(jint)w->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
void el_android_widget_on_submit(int64_t handle, const char *fn_name) {
|
||||
/* Submit stored in cb_click, same as AppKit. */
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
free(w->cb_click);
|
||||
w->cb_click = (fn_name && *fn_name) ? strdup(fn_name) : NULL;
|
||||
if (!w->cb_click) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (!env || !g_bridge_class) return;
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_setOnSubmitListener,
|
||||
(jint)w->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
/* ── Widget destroy ───────────────────────────────────────────────────────── */
|
||||
|
||||
void el_android_widget_destroy(int64_t handle) {
|
||||
ElWidget *w = el_widget_get(handle);
|
||||
if (!w) return;
|
||||
JNIEnv *env = el_jni_env();
|
||||
if (env && g_bridge_class) {
|
||||
(*env)->CallStaticVoidMethod(env, g_bridge_class, g_mid_destroyView,
|
||||
(jint)w->slot);
|
||||
if ((*env)->ExceptionCheck(env)) (*env)->ExceptionClear(env);
|
||||
}
|
||||
el_widget_free(handle);
|
||||
el_jni_detach_if_attached();
|
||||
}
|
||||
|
||||
/* ── Manifest reader ─────────────────────────────────────────────────────── */
|
||||
/*
|
||||
* __manifest_read: parse the app{} block from a manifest file.
|
||||
* Returns the raw file contents as an el_val_t (const char* cast).
|
||||
* The caller (el program) parses the returned string.
|
||||
* Reads from the filesystem; for APK assets use the AssetManager path instead.
|
||||
*/
|
||||
static char *el_read_file(const char *path) {
|
||||
if (!path || !*path) return NULL;
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return NULL;
|
||||
fseek(f, 0, SEEK_END);
|
||||
long len = ftell(f);
|
||||
fseek(f, 0, SEEK_SET);
|
||||
if (len <= 0) { fclose(f); return NULL; }
|
||||
char *buf = (char *)malloc((size_t)len + 1);
|
||||
if (!buf) { fclose(f); return NULL; }
|
||||
fread(buf, 1, (size_t)len, f);
|
||||
buf[len] = '\0';
|
||||
fclose(f);
|
||||
return buf;
|
||||
}
|
||||
|
||||
el_val_t el_android_manifest_read(const char *path) {
|
||||
char *contents = el_read_file(path);
|
||||
if (!contents) return (el_val_t)(uintptr_t)"";
|
||||
return (el_val_t)(uintptr_t)contents; /* caller owns allocation */
|
||||
}
|
||||
|
||||
/* ── __widget_* C API (called from el_seed.c) ────────────────────────────── */
|
||||
/*
|
||||
* These are the functions declared in el_native_target.h under EL_TARGET_ANDROID.
|
||||
* They forward to the el_android_* internal functions above.
|
||||
*
|
||||
* The el_val_t / int64_t ABI matches the AppKit functions exactly:
|
||||
* - Integer params passed as int64_t, extracted with (int)
|
||||
* - String params passed as int64_t, extracted with (const char*)(uintptr_t)
|
||||
* - Float params (r,g,b,a) passed as int64_t bit-cast from double; extracted
|
||||
* with el_to_float / bit-cast union
|
||||
*/
|
||||
|
||||
static inline float el_val_to_float(el_val_t v) {
|
||||
union { double d; int64_t i; } u;
|
||||
u.i = v;
|
||||
return (float)u.d;
|
||||
}
|
||||
|
||||
void __native_init(void) {
|
||||
el_android_init();
|
||||
}
|
||||
|
||||
void __native_run_loop(void) {
|
||||
/* No-op on Android — lifecycle is driven by the Activity. */
|
||||
}
|
||||
|
||||
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_android_window_create(
|
||||
(const char *)(uintptr_t)title,
|
||||
(int)width, (int)height, (int)min_width, (int)min_height);
|
||||
}
|
||||
|
||||
void __window_show(el_val_t handle) {
|
||||
el_android_window_show((int64_t)handle);
|
||||
}
|
||||
|
||||
void __window_set_title(el_val_t handle, el_val_t title) {
|
||||
el_android_window_set_title((int64_t)handle,
|
||||
(const char *)(uintptr_t)title);
|
||||
}
|
||||
|
||||
el_val_t __vstack_create(el_val_t spacing) {
|
||||
return (el_val_t)el_android_vstack_create((int)spacing);
|
||||
}
|
||||
|
||||
el_val_t __hstack_create(el_val_t spacing) {
|
||||
return (el_val_t)el_android_hstack_create((int)spacing);
|
||||
}
|
||||
|
||||
el_val_t __zstack_create(void) {
|
||||
return (el_val_t)el_android_zstack_create();
|
||||
}
|
||||
|
||||
el_val_t __scroll_create(void) {
|
||||
return (el_val_t)el_android_scroll_create();
|
||||
}
|
||||
|
||||
el_val_t __label_create(el_val_t text) {
|
||||
return (el_val_t)el_android_label_create((const char *)(uintptr_t)text);
|
||||
}
|
||||
|
||||
el_val_t __button_create(el_val_t label) {
|
||||
return (el_val_t)el_android_button_create((const char *)(uintptr_t)label);
|
||||
}
|
||||
|
||||
el_val_t __text_field_create(el_val_t placeholder) {
|
||||
return (el_val_t)el_android_text_field_create((const char *)(uintptr_t)placeholder);
|
||||
}
|
||||
|
||||
el_val_t __text_area_create(el_val_t placeholder) {
|
||||
return (el_val_t)el_android_text_area_create((const char *)(uintptr_t)placeholder);
|
||||
}
|
||||
|
||||
el_val_t __image_create(el_val_t path_or_name) {
|
||||
return (el_val_t)el_android_image_create((const char *)(uintptr_t)path_or_name);
|
||||
}
|
||||
|
||||
void __widget_set_text(el_val_t handle, el_val_t text) {
|
||||
el_android_widget_set_text((int64_t)handle,
|
||||
(const char *)(uintptr_t)text);
|
||||
}
|
||||
|
||||
el_val_t __widget_get_text(el_val_t handle) {
|
||||
return (el_val_t)(uintptr_t)el_android_widget_get_text((int64_t)handle);
|
||||
}
|
||||
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a) {
|
||||
el_android_widget_set_color((int64_t)handle,
|
||||
el_val_to_float(r), el_val_to_float(g),
|
||||
el_val_to_float(b), el_val_to_float(a));
|
||||
}
|
||||
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a) {
|
||||
el_android_widget_set_bg_color((int64_t)handle,
|
||||
el_val_to_float(r), el_val_to_float(g),
|
||||
el_val_to_float(b), el_val_to_float(a));
|
||||
}
|
||||
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold) {
|
||||
el_android_widget_set_font((int64_t)handle,
|
||||
(const char *)(uintptr_t)family,
|
||||
(int)size, (int)bold);
|
||||
}
|
||||
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left) {
|
||||
el_android_widget_set_padding((int64_t)handle,
|
||||
(int)top, (int)right, (int)bottom, (int)left);
|
||||
}
|
||||
|
||||
void __widget_set_width(el_val_t handle, el_val_t width) {
|
||||
el_android_widget_set_width((int64_t)handle, (int)width);
|
||||
}
|
||||
|
||||
void __widget_set_height(el_val_t handle, el_val_t height) {
|
||||
el_android_widget_set_height((int64_t)handle, (int)height);
|
||||
}
|
||||
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex) {
|
||||
el_android_widget_set_flex((int64_t)handle, (int)flex);
|
||||
}
|
||||
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius) {
|
||||
el_android_widget_set_corner_radius((int64_t)handle, (int)radius);
|
||||
}
|
||||
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled) {
|
||||
el_android_widget_set_disabled((int64_t)handle, (int)disabled);
|
||||
}
|
||||
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden) {
|
||||
el_android_widget_set_hidden((int64_t)handle, (int)hidden);
|
||||
}
|
||||
|
||||
void __widget_add_child(el_val_t parent, el_val_t child) {
|
||||
el_android_widget_add_child((int64_t)parent, (int64_t)child);
|
||||
}
|
||||
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child) {
|
||||
el_android_widget_remove_child((int64_t)parent, (int64_t)child);
|
||||
}
|
||||
|
||||
void __widget_destroy(el_val_t handle) {
|
||||
el_android_widget_destroy((int64_t)handle);
|
||||
}
|
||||
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name) {
|
||||
el_android_widget_on_click((int64_t)handle,
|
||||
(const char *)(uintptr_t)fn_name);
|
||||
}
|
||||
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name) {
|
||||
el_android_widget_on_change((int64_t)handle,
|
||||
(const char *)(uintptr_t)fn_name);
|
||||
}
|
||||
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name) {
|
||||
el_android_widget_on_submit((int64_t)handle,
|
||||
(const char *)(uintptr_t)fn_name);
|
||||
}
|
||||
|
||||
el_val_t __manifest_read(el_val_t path) {
|
||||
return el_android_manifest_read((const char *)(uintptr_t)path);
|
||||
}
|
||||
|
||||
/* ── MainActivity JNI entry point ─────────────────────────────────────────── */
|
||||
/*
|
||||
* Java_com_neuron_el_MainActivity_nativeMain — invoked from MainActivity.onCreate
|
||||
* after ElBridge.init(this). Calls the el program's compiled main() which runs
|
||||
* the boot sequence: native_init → window_from_manifest → app_build → window_show.
|
||||
* __native_run_loop is a no-op on Android; the Activity lifecycle drives the UI.
|
||||
*/
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_neuron_el_MainActivity_nativeMain(JNIEnv *env, jobject obj) {
|
||||
(void)env; (void)obj;
|
||||
extern int main(int argc, char **argv);
|
||||
char *argv[] = {"el-app", NULL};
|
||||
main(1, argv);
|
||||
}
|
||||
|
||||
#endif /* EL_TARGET_ANDROID */
|
||||
@@ -0,0 +1,573 @@
|
||||
/*
|
||||
* el_native_target.h — Native widget declarations for el programs targeting
|
||||
* native desktop UI (AppKit / GTK4 / Win32).
|
||||
*
|
||||
* This header is designed to be included AFTER el_runtime.h without conflict:
|
||||
* - It does NOT redefine el_to_float, el_from_float, or any el_runtime.h
|
||||
* static inlines.
|
||||
* - It does NOT redeclare __println, __print, or other functions whose
|
||||
* return types differ between el_seed.h and el_runtime.h.
|
||||
* - It adds: native widget builtins + float arithmetic helpers that the
|
||||
* current el_runtime.h omits but elc still emits calls to.
|
||||
*
|
||||
* Usage:
|
||||
* Inject via -include at compile time, OR #include it after el_runtime.h.
|
||||
*
|
||||
* clang -DEL_TARGET_MACOS -include el_native_target.h -c my_app.c ...
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
/* el_val_t must already be defined by el_runtime.h or el_seed.h. */
|
||||
#ifndef EL_VAL_T_DEFINED
|
||||
typedef int64_t el_val_t;
|
||||
#endif
|
||||
|
||||
/* ── Float arithmetic helpers ───────────────────────────────────────────────
|
||||
* elc emits calls to float_div / float_mul etc. for Float-typed expressions.
|
||||
* These were in el_runtime.c through v1.0.0-20260501 but are missing from the
|
||||
* current el_runtime.h. Redeclared here as static inline to avoid link deps.
|
||||
* Only defined if not already declared (old runtimes that still have them). */
|
||||
|
||||
#ifndef EL_FLOAT_OPS_DEFINED
|
||||
#define EL_FLOAT_OPS_DEFINED
|
||||
|
||||
/* el_to_float / el_from_float — bit-cast between el_val_t and double.
|
||||
* Defined as static inline in both el_runtime.h and el_seed.h; we do NOT
|
||||
* redefine them here. We rely on one of those headers being included first. */
|
||||
|
||||
static inline el_val_t float_div(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b;
|
||||
ur.d = (ub.d != 0.0) ? (ua.d / ub.d) : 0.0;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_mul(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b; ur.d = ua.d * ub.d;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_add(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b; ur.d = ua.d + ub.d;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_sub(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub, ur;
|
||||
ua.i = a; ub.i = b; ur.d = ua.d - ub.d;
|
||||
return ur.i;
|
||||
}
|
||||
|
||||
static inline el_val_t float_lt(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d < ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_gt(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d > ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_lte(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d <= ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_gte(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d >= ub.d);
|
||||
}
|
||||
|
||||
static inline el_val_t float_eq(el_val_t a, el_val_t b) {
|
||||
union { double d; int64_t i; } ua, ub;
|
||||
ua.i = a; ub.i = b;
|
||||
return (el_val_t)(ua.d == ub.d);
|
||||
}
|
||||
|
||||
#endif /* EL_FLOAT_OPS_DEFINED */
|
||||
|
||||
/* ── Native widget system (macOS AppKit) ────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_MACOS and linked with el_appkit.m.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid. */
|
||||
|
||||
#ifdef EL_TARGET_MACOS
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
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);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_MACOS */
|
||||
|
||||
/* ── Native widget system (Linux GTK4) ──────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_LINUX and linked with el_gtk4.c.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
* All functions have the same signatures as EL_TARGET_MACOS above. */
|
||||
|
||||
#ifdef EL_TARGET_LINUX
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
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);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader — same JSON output as EL_TARGET_MACOS */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_LINUX */
|
||||
|
||||
/* ── Native widget system (Windows Win32) ───────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_WIN32 and linked with el_win32.c.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
* Link: el_win32.obj comctl32.lib user32.lib gdi32.lib */
|
||||
|
||||
#ifdef EL_TARGET_WIN32
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
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);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_WIN32 */
|
||||
|
||||
/* ── Native widget system (iOS UIKit) ───────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_IOS and linked with el_uikit.m.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* iOS lifecycle note: UIApplicationMain never returns. The el program must
|
||||
* store its UI-build logic in a void(*)(void) function pointer, assign it to
|
||||
* el_main_entry_fn, then call __native_run_loop. ElAppDelegate invokes
|
||||
* el_main_entry_fn inside didFinishLaunchingWithOptions.
|
||||
* Call el_uikit_set_args(argc, argv) from main() before __native_run_loop. */
|
||||
|
||||
#ifdef EL_TARGET_IOS
|
||||
|
||||
/* Lifecycle entry-function hook — set before calling __native_run_loop. */
|
||||
extern void (*el_main_entry_fn)(void);
|
||||
|
||||
/* Forward argc/argv from main() to UIApplicationMain. */
|
||||
void el_uikit_set_args(int argc, char** argv);
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
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);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_IOS */
|
||||
|
||||
/* ── Native widget system (Android JNI) ─────────────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_ANDROID and linked with
|
||||
* libelruntime.so (which includes el_android.c compiled by the NDK build).
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* Java companion: ElBridge.java (package com.neuron.el) must be compiled into
|
||||
* the APK. The Activity must call ElBridge.init(this) before any widget ops.
|
||||
*
|
||||
* Link flags (in Android.mk or CMakeLists.txt):
|
||||
* -landroid -llog -ldl */
|
||||
|
||||
#ifdef EL_TARGET_ANDROID
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void); /* no-op on Android */
|
||||
|
||||
/* Window */
|
||||
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);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_ANDROID */
|
||||
|
||||
/* ── Native widget system (LVGL v9 — embedded / microcontroller) ─────────────
|
||||
* Available when compiled with -DEL_TARGET_LVGL and linked with el_lvgl.c
|
||||
* and the LVGL library (lvgl.a or lvgl source tree).
|
||||
*
|
||||
* Target platforms: ESP32, STM32, industrial panels. Any system with 256KB+
|
||||
* RAM and an LVGL-compatible display driver. No OS required.
|
||||
*
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* Bare-metal / no dynamic linker:
|
||||
* Compile with -DEL_LVGL_NO_DLSYM and provide:
|
||||
* el_val_t el_lvgl_dispatch(const char *fn, el_val_t a, el_val_t b);
|
||||
*
|
||||
* Compile:
|
||||
* gcc -DEL_TARGET_LVGL -I./lvgl el_lvgl.c -c -o el_lvgl.o
|
||||
* # Then link with lvgl.a. */
|
||||
|
||||
#ifdef EL_TARGET_LVGL
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
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);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader — same JSON output as all other native targets */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_LVGL */
|
||||
|
||||
/* ── Native widget system (SDL2 — embedded / Pi) ────────────────────────────
|
||||
* Available when compiled with -DEL_TARGET_SDL2 and linked with el_sdl2.c.
|
||||
* Widget handles are opaque int64_t slot indices; -1 = invalid.
|
||||
*
|
||||
* Target: Raspberry Pi Zero, embedded Linux, any system with a framebuffer
|
||||
* and SDL2 available. No GTK, no desktop environment required.
|
||||
*
|
||||
* Compile:
|
||||
* gcc -DEL_TARGET_SDL2 $(sdl2-config --cflags) -c el_sdl2.c -o el_sdl2.o
|
||||
* Link:
|
||||
* $(sdl2-config --libs) -lSDL2_ttf -lSDL2_image -ldl */
|
||||
|
||||
#ifdef EL_TARGET_SDL2
|
||||
|
||||
/* Initialisation */
|
||||
void __native_init(void);
|
||||
void __native_run_loop(void);
|
||||
|
||||
/* Window */
|
||||
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);
|
||||
void __window_show(el_val_t handle);
|
||||
void __window_set_title(el_val_t handle, el_val_t title);
|
||||
|
||||
/* Layout containers */
|
||||
el_val_t __vstack_create(el_val_t spacing);
|
||||
el_val_t __hstack_create(el_val_t spacing);
|
||||
el_val_t __zstack_create(void);
|
||||
el_val_t __scroll_create(void);
|
||||
|
||||
/* Widgets */
|
||||
el_val_t __label_create(el_val_t text);
|
||||
el_val_t __button_create(el_val_t label);
|
||||
el_val_t __text_field_create(el_val_t placeholder);
|
||||
el_val_t __text_area_create(el_val_t placeholder);
|
||||
el_val_t __image_create(el_val_t path_or_name);
|
||||
|
||||
/* Widget properties */
|
||||
void __widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t __widget_get_text(el_val_t handle);
|
||||
void __widget_set_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g,
|
||||
el_val_t b, el_val_t a);
|
||||
void __widget_set_font(el_val_t handle, el_val_t family,
|
||||
el_val_t size, el_val_t bold);
|
||||
void __widget_set_padding(el_val_t handle, el_val_t top, el_val_t right,
|
||||
el_val_t bottom, el_val_t left);
|
||||
void __widget_set_width(el_val_t handle, el_val_t width);
|
||||
void __widget_set_height(el_val_t handle, el_val_t height);
|
||||
void __widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
void __widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
void __widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
void __widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
|
||||
/* Layout / tree */
|
||||
void __widget_add_child(el_val_t parent, el_val_t child);
|
||||
void __widget_remove_child(el_val_t parent, el_val_t child);
|
||||
void __widget_destroy(el_val_t handle);
|
||||
|
||||
/* Events */
|
||||
void __widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
void __widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
|
||||
/* Manifest reader */
|
||||
el_val_t __manifest_read(el_val_t path);
|
||||
|
||||
#endif /* EL_TARGET_SDL2 */
|
||||
@@ -0,0 +1,459 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "el_runtime.h"
|
||||
|
||||
el_val_t native_init(void);
|
||||
el_val_t native_run_loop(void);
|
||||
el_val_t manifest_read(el_val_t path);
|
||||
el_val_t manifest_title(el_val_t m);
|
||||
el_val_t manifest_width(el_val_t m);
|
||||
el_val_t manifest_height(el_val_t m);
|
||||
el_val_t manifest_min_width(el_val_t m);
|
||||
el_val_t manifest_min_height(el_val_t m);
|
||||
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);
|
||||
el_val_t window_from_manifest(el_val_t manifest_path);
|
||||
el_val_t window_show(el_val_t handle);
|
||||
el_val_t window_set_title(el_val_t handle, el_val_t title);
|
||||
el_val_t vstack(el_val_t spacing);
|
||||
el_val_t vstack_tight(void);
|
||||
el_val_t hstack(el_val_t spacing);
|
||||
el_val_t zstack(void);
|
||||
el_val_t scroll(void);
|
||||
el_val_t label(el_val_t text);
|
||||
el_val_t button(el_val_t label);
|
||||
el_val_t text_field(el_val_t placeholder);
|
||||
el_val_t text_area(el_val_t placeholder);
|
||||
el_val_t image(el_val_t path_or_name);
|
||||
el_val_t widget_set_text(el_val_t handle, el_val_t text);
|
||||
el_val_t widget_get_text(el_val_t handle);
|
||||
el_val_t widget_set_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
el_val_t widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a);
|
||||
el_val_t widget_set_font(el_val_t handle, el_val_t family, el_val_t size, el_val_t bold);
|
||||
el_val_t widget_set_padding(el_val_t handle, el_val_t top, el_val_t right, el_val_t bottom, el_val_t left);
|
||||
el_val_t widget_set_padding_all(el_val_t handle, el_val_t p);
|
||||
el_val_t widget_set_padding_xy(el_val_t handle, el_val_t px, el_val_t py);
|
||||
el_val_t widget_set_width(el_val_t handle, el_val_t width);
|
||||
el_val_t widget_set_height(el_val_t handle, el_val_t height);
|
||||
el_val_t widget_set_flex(el_val_t handle, el_val_t flex);
|
||||
el_val_t widget_set_corner_radius(el_val_t handle, el_val_t radius);
|
||||
el_val_t widget_set_disabled(el_val_t handle, el_val_t disabled);
|
||||
el_val_t widget_set_hidden(el_val_t handle, el_val_t hidden);
|
||||
el_val_t widget_add_child(el_val_t parent, el_val_t child);
|
||||
el_val_t widget_remove_child(el_val_t parent, el_val_t child);
|
||||
el_val_t widget_destroy(el_val_t handle);
|
||||
el_val_t widget_on_click(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t widget_on_change(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t widget_on_submit(el_val_t handle, el_val_t fn_name);
|
||||
el_val_t hex_channel(el_val_t s, el_val_t offset);
|
||||
el_val_t hex_nibble(el_val_t c);
|
||||
el_val_t color_hex_r(el_val_t hex);
|
||||
el_val_t color_hex_g(el_val_t hex);
|
||||
el_val_t color_hex_b(el_val_t hex);
|
||||
el_val_t color_hex_a(el_val_t hex);
|
||||
el_val_t widget_set_color_hex(el_val_t handle, el_val_t hex);
|
||||
el_val_t widget_set_bg_color_hex(el_val_t handle, el_val_t hex);
|
||||
el_val_t style_surface(el_val_t handle);
|
||||
el_val_t style_button_primary(el_val_t handle);
|
||||
el_val_t style_label_body(el_val_t handle);
|
||||
el_val_t style_label_heading(el_val_t handle);
|
||||
el_val_t style_label_muted(el_val_t handle);
|
||||
|
||||
el_val_t TOKEN_PRIMARY;
|
||||
el_val_t TOKEN_ON_PRIMARY;
|
||||
el_val_t TOKEN_BACKGROUND;
|
||||
el_val_t TOKEN_ON_BG;
|
||||
el_val_t TOKEN_SURFACE;
|
||||
el_val_t TOKEN_ON_SURFACE;
|
||||
el_val_t TOKEN_OUTLINE;
|
||||
el_val_t TOKEN_ERROR;
|
||||
|
||||
el_val_t native_init(void) {
|
||||
__native_init();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t native_run_loop(void) {
|
||||
__native_run_loop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_read(el_val_t path) {
|
||||
return __manifest_read(path);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_title(el_val_t m) {
|
||||
return json_get_string(m, EL_STR("title"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_width(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("width"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_height(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("height"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_min_width(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("min_width"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t manifest_min_height(el_val_t m) {
|
||||
return json_get_int(m, EL_STR("min_height"));
|
||||
return 0;
|
||||
}
|
||||
|
||||
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 __window_create(title, width, height, min_width, min_height);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_from_manifest(el_val_t manifest_path) {
|
||||
el_val_t m = manifest_read(manifest_path);
|
||||
el_val_t title = manifest_title(m);
|
||||
el_val_t w = manifest_width(m);
|
||||
el_val_t h = manifest_height(m);
|
||||
el_val_t mw = manifest_min_width(m);
|
||||
el_val_t mh = manifest_min_height(m);
|
||||
el_val_t safe_w = ({ el_val_t _if_result_1 = 0; if ((w > 0)) { _if_result_1 = (w); } else { _if_result_1 = (1200); } _if_result_1; });
|
||||
el_val_t safe_h = ({ el_val_t _if_result_2 = 0; if ((h > 0)) { _if_result_2 = (h); } else { _if_result_2 = (800); } _if_result_2; });
|
||||
el_val_t safe_mw = ({ el_val_t _if_result_3 = 0; if ((mw > 0)) { _if_result_3 = (mw); } else { _if_result_3 = (600); } _if_result_3; });
|
||||
el_val_t safe_mh = ({ el_val_t _if_result_4 = 0; if ((mh > 0)) { _if_result_4 = (mh); } else { _if_result_4 = (400); } _if_result_4; });
|
||||
el_val_t safe_t = ({ el_val_t _if_result_5 = 0; if ((str_len(title) > 0)) { _if_result_5 = (title); } else { _if_result_5 = (EL_STR("App")); } _if_result_5; });
|
||||
return window_create(safe_t, safe_w, safe_h, safe_mw, safe_mh);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_show(el_val_t handle) {
|
||||
__window_show(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t window_set_title(el_val_t handle, el_val_t title) {
|
||||
__window_set_title(handle, title);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t vstack(el_val_t spacing) {
|
||||
return __vstack_create(spacing);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t vstack_tight(void) {
|
||||
return __vstack_create(0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t hstack(el_val_t spacing) {
|
||||
return __hstack_create(spacing);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t zstack(void) {
|
||||
return __zstack_create();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t scroll(void) {
|
||||
return __scroll_create();
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t label(el_val_t text) {
|
||||
return __label_create(text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t button(el_val_t label) {
|
||||
return __button_create(label);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t text_field(el_val_t placeholder) {
|
||||
return __text_field_create(placeholder);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t text_area(el_val_t placeholder) {
|
||||
return __text_area_create(placeholder);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t image(el_val_t path_or_name) {
|
||||
return __image_create(path_or_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_text(el_val_t handle, el_val_t text) {
|
||||
__widget_set_text(handle, text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_get_text(el_val_t handle) {
|
||||
return __widget_get_text(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a) {
|
||||
__widget_set_color(handle, r, g, b, a);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_bg_color(el_val_t handle, el_val_t r, el_val_t g, el_val_t b, el_val_t a) {
|
||||
__widget_set_bg_color(handle, r, g, b, a);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_font(el_val_t handle, el_val_t family, el_val_t size, el_val_t bold) {
|
||||
el_val_t bold_int = ({ el_val_t _if_result_6 = 0; if (bold) { _if_result_6 = (1); } else { _if_result_6 = (0); } _if_result_6; });
|
||||
__widget_set_font(handle, family, size, bold_int);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_padding(el_val_t handle, el_val_t top, el_val_t right, el_val_t bottom, el_val_t left) {
|
||||
__widget_set_padding(handle, top, right, bottom, left);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_padding_all(el_val_t handle, el_val_t p) {
|
||||
__widget_set_padding(handle, p, p, p, p);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_padding_xy(el_val_t handle, el_val_t px, el_val_t py) {
|
||||
__widget_set_padding(handle, py, px, py, px);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_width(el_val_t handle, el_val_t width) {
|
||||
__widget_set_width(handle, width);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_height(el_val_t handle, el_val_t height) {
|
||||
__widget_set_height(handle, height);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_flex(el_val_t handle, el_val_t flex) {
|
||||
__widget_set_flex(handle, flex);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_corner_radius(el_val_t handle, el_val_t radius) {
|
||||
__widget_set_corner_radius(handle, radius);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_disabled(el_val_t handle, el_val_t disabled) {
|
||||
el_val_t d = ({ el_val_t _if_result_7 = 0; if (disabled) { _if_result_7 = (1); } else { _if_result_7 = (0); } _if_result_7; });
|
||||
__widget_set_disabled(handle, d);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_hidden(el_val_t handle, el_val_t hidden) {
|
||||
el_val_t h = ({ el_val_t _if_result_8 = 0; if (hidden) { _if_result_8 = (1); } else { _if_result_8 = (0); } _if_result_8; });
|
||||
__widget_set_hidden(handle, h);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_add_child(el_val_t parent, el_val_t child) {
|
||||
__widget_add_child(parent, child);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_remove_child(el_val_t parent, el_val_t child) {
|
||||
__widget_remove_child(parent, child);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_destroy(el_val_t handle) {
|
||||
__widget_destroy(handle);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_on_click(el_val_t handle, el_val_t fn_name) {
|
||||
__widget_on_click(handle, fn_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_on_change(el_val_t handle, el_val_t fn_name) {
|
||||
__widget_on_change(handle, fn_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_on_submit(el_val_t handle, el_val_t fn_name) {
|
||||
__widget_on_submit(handle, fn_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t hex_channel(el_val_t s, el_val_t offset) {
|
||||
el_val_t hi = str_slice(s, offset, (offset + 1));
|
||||
el_val_t lo = str_slice(s, (offset + 1), (offset + 2));
|
||||
el_val_t h = hex_nibble(hi);
|
||||
el_val_t l = hex_nibble(lo);
|
||||
return ((h * 16) + l);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t hex_nibble(el_val_t c) {
|
||||
if (str_eq(c, EL_STR("0"))) {
|
||||
return 0;
|
||||
}
|
||||
if (str_eq(c, EL_STR("1"))) {
|
||||
return 1;
|
||||
}
|
||||
if (str_eq(c, EL_STR("2"))) {
|
||||
return 2;
|
||||
}
|
||||
if (str_eq(c, EL_STR("3"))) {
|
||||
return 3;
|
||||
}
|
||||
if (str_eq(c, EL_STR("4"))) {
|
||||
return 4;
|
||||
}
|
||||
if (str_eq(c, EL_STR("5"))) {
|
||||
return 5;
|
||||
}
|
||||
if (str_eq(c, EL_STR("6"))) {
|
||||
return 6;
|
||||
}
|
||||
if (str_eq(c, EL_STR("7"))) {
|
||||
return 7;
|
||||
}
|
||||
if (str_eq(c, EL_STR("8"))) {
|
||||
return 8;
|
||||
}
|
||||
if (str_eq(c, EL_STR("9"))) {
|
||||
return 9;
|
||||
}
|
||||
if (str_eq(c, EL_STR("a"))) {
|
||||
return 10;
|
||||
}
|
||||
if (str_eq(c, EL_STR("b"))) {
|
||||
return 11;
|
||||
}
|
||||
if (str_eq(c, EL_STR("c"))) {
|
||||
return 12;
|
||||
}
|
||||
if (str_eq(c, EL_STR("d"))) {
|
||||
return 13;
|
||||
}
|
||||
if (str_eq(c, EL_STR("e"))) {
|
||||
return 14;
|
||||
}
|
||||
if (str_eq(c, EL_STR("f"))) {
|
||||
return 15;
|
||||
}
|
||||
if (str_eq(c, EL_STR("A"))) {
|
||||
return 10;
|
||||
}
|
||||
if (str_eq(c, EL_STR("B"))) {
|
||||
return 11;
|
||||
}
|
||||
if (str_eq(c, EL_STR("C"))) {
|
||||
return 12;
|
||||
}
|
||||
if (str_eq(c, EL_STR("D"))) {
|
||||
return 13;
|
||||
}
|
||||
if (str_eq(c, EL_STR("E"))) {
|
||||
return 14;
|
||||
}
|
||||
if (str_eq(c, EL_STR("F"))) {
|
||||
return 15;
|
||||
}
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_r(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_9 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_9 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_9 = (hex); } _if_result_9; });
|
||||
el_val_t v = hex_channel(s, 0);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_g(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_10 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_10 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_10 = (hex); } _if_result_10; });
|
||||
el_val_t v = hex_channel(s, 2);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_b(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_11 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_11 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_11 = (hex); } _if_result_11; });
|
||||
el_val_t v = hex_channel(s, 4);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t color_hex_a(el_val_t hex) {
|
||||
el_val_t s = ({ el_val_t _if_result_12 = 0; if (str_starts_with(hex, EL_STR("#"))) { _if_result_12 = (str_slice(hex, 1, str_len(hex))); } else { _if_result_12 = (hex); } _if_result_12; });
|
||||
if (str_len(s) < 8) {
|
||||
return el_from_float(1.0);
|
||||
}
|
||||
el_val_t v = hex_channel(s, 6);
|
||||
return float_div(int_to_float(v), el_from_float(255.0));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_color_hex(el_val_t handle, el_val_t hex) {
|
||||
widget_set_color(handle, color_hex_r(hex), color_hex_g(hex), color_hex_b(hex), color_hex_a(hex));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t widget_set_bg_color_hex(el_val_t handle, el_val_t hex) {
|
||||
widget_set_bg_color(handle, color_hex_r(hex), color_hex_g(hex), color_hex_b(hex), color_hex_a(hex));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_surface(el_val_t handle) {
|
||||
widget_set_bg_color_hex(handle, TOKEN_SURFACE);
|
||||
widget_set_corner_radius(handle, 8);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_button_primary(el_val_t handle) {
|
||||
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);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_label_body(el_val_t handle) {
|
||||
widget_set_color_hex(handle, TOKEN_ON_BG);
|
||||
widget_set_font(handle, EL_STR("system"), 14, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_label_heading(el_val_t handle) {
|
||||
widget_set_color_hex(handle, TOKEN_ON_BG);
|
||||
widget_set_font(handle, EL_STR("system"), 20, 1);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t style_label_muted(el_val_t handle) {
|
||||
widget_set_color_hex(handle, TOKEN_OUTLINE);
|
||||
widget_set_font(handle, EL_STR("system"), 12, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int el_vessel_main(int _argc, char** _argv) {
|
||||
el_runtime_init_args(_argc, _argv);
|
||||
TOKEN_PRIMARY = EL_STR("#60a5fa");
|
||||
TOKEN_ON_PRIMARY = EL_STR("#0f172a");
|
||||
TOKEN_BACKGROUND = EL_STR("#0f172a");
|
||||
TOKEN_ON_BG = EL_STR("#f8fafc");
|
||||
TOKEN_SURFACE = EL_STR("#1e293b");
|
||||
TOKEN_ON_SURFACE = EL_STR("#f8fafc");
|
||||
TOKEN_OUTLINE = EL_STR("#475569");
|
||||
TOKEN_ERROR = EL_STR("#f87171");
|
||||
return 0;
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,883 @@
|
||||
/*
|
||||
* el_runtime.h — El language C runtime header
|
||||
*
|
||||
* Declares all built-in functions available to compiled El programs.
|
||||
* Include this in every generated .c file.
|
||||
*
|
||||
* Value model:
|
||||
* All El values are represented as el_val_t (= int64_t).
|
||||
* On 64-bit systems a pointer fits in int64_t.
|
||||
* String values are cast: (el_val_t)(uintptr_t)"hello"
|
||||
* Integer values are stored directly.
|
||||
* This lets arithmetic work naturally while still passing strings around.
|
||||
*
|
||||
* Type conventions (El -> C):
|
||||
* String -> el_val_t (holds const char* via uintptr_t cast)
|
||||
* Int -> el_val_t
|
||||
* Bool -> el_val_t (0 = false, nonzero = true)
|
||||
* Any -> el_val_t
|
||||
* Void -> void
|
||||
*
|
||||
* Macros for convenience:
|
||||
* EL_STR(s) cast string literal to el_val_t
|
||||
* EL_CSTR(v) cast el_val_t back to const char*
|
||||
* EL_INT(v) identity — el_val_t is already int64_t
|
||||
* EL_NULL null / zero value
|
||||
* EL_FALSE boolean false (0)
|
||||
* EL_TRUE boolean true (1)
|
||||
*
|
||||
* Link requirements:
|
||||
* -lcurl — required for the HTTP client (http_get, http_post, llm_*).
|
||||
* -lpthread — required for the HTTP server (one detached thread per
|
||||
* connection, capped at 64 concurrent).
|
||||
* -loqs — optional; required only when liboqs is installed and the
|
||||
* pq_* / sha3_256_hex entry points are needed. Detected at
|
||||
* compile time via __has_include(<oqs/oqs.h>).
|
||||
* -lcrypto — optional; pulled in alongside -loqs. Used for X25519 in
|
||||
* pq_hybrid_* and HKDF-SHA256 derivation.
|
||||
*
|
||||
* Canonical compile command:
|
||||
* cc -std=c11 -I el-compiler/runtime -lcurl -lpthread \
|
||||
* -o <out> <prog>.c el-compiler/runtime/el_runtime.c
|
||||
*
|
||||
* With liboqs (post-quantum stack):
|
||||
* cc -std=c11 -I el-compiler/runtime -lcurl -lpthread -loqs -lcrypto \
|
||||
* -o <out> <prog>.c el-compiler/runtime/el_runtime.c
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
typedef int64_t el_val_t;
|
||||
|
||||
#define EL_STR(s) ((el_val_t)(uintptr_t)(s))
|
||||
#define EL_CSTR(v) ((const char*)(uintptr_t)(v))
|
||||
#define EL_INT(v) (v)
|
||||
#define EL_NULL ((el_val_t)0)
|
||||
#define EL_FALSE ((el_val_t)0)
|
||||
#define EL_TRUE ((el_val_t)1)
|
||||
|
||||
/* Float values share the el_val_t (int64) slot via a bit-cast.
|
||||
* The codegen emits Float literals as `el_from_float(<dbl>)` so the
|
||||
* underlying bits represent the IEEE 754 double. Float-aware builtins
|
||||
* (math, format, json) round-trip via these helpers. */
|
||||
static inline double el_to_float(el_val_t v) {
|
||||
union { int64_t i; double f; } u;
|
||||
u.i = (int64_t)v;
|
||||
return u.f;
|
||||
}
|
||||
|
||||
static inline el_val_t el_from_float(double f) {
|
||||
union { double f; int64_t i; } u;
|
||||
u.f = f;
|
||||
return (el_val_t)u.i;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* ── I/O ──────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t println(el_val_t s);
|
||||
el_val_t print(el_val_t s);
|
||||
el_val_t readline(void);
|
||||
|
||||
/* ── String builtins ─────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_str_concat(el_val_t a, el_val_t b);
|
||||
el_val_t str_eq(el_val_t a, el_val_t b);
|
||||
el_val_t str_starts_with(el_val_t s, el_val_t prefix);
|
||||
el_val_t str_ends_with(el_val_t s, el_val_t suffix);
|
||||
el_val_t str_len(el_val_t s);
|
||||
el_val_t str_concat(el_val_t a, el_val_t b);
|
||||
el_val_t int_to_str(el_val_t n);
|
||||
el_val_t str_to_int(el_val_t s);
|
||||
el_val_t native_str_to_int(el_val_t s);
|
||||
el_val_t str_slice(el_val_t s, el_val_t start, el_val_t end);
|
||||
el_val_t str_contains(el_val_t s, el_val_t sub);
|
||||
el_val_t str_replace(el_val_t s, el_val_t from, el_val_t to);
|
||||
el_val_t str_to_upper(el_val_t s);
|
||||
el_val_t str_to_lower(el_val_t s);
|
||||
el_val_t str_trim(el_val_t s);
|
||||
|
||||
/* ── Math ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_abs(el_val_t n);
|
||||
el_val_t el_max(el_val_t a, el_val_t b);
|
||||
el_val_t el_min(el_val_t a, el_val_t b);
|
||||
|
||||
/* ── Refcount (ARC) ──────────────────────────────────────────────────────────
|
||||
* Lists and Maps carry a refcount. Strings and ints do not — el_retain and
|
||||
* el_release are safe no-ops on non-refcounted values (they sniff a magic
|
||||
* header at offset 0 and only act if the magic matches).
|
||||
*
|
||||
* Codegen emits these at let-binding shadowing, function entry (params), and
|
||||
* function exit (locals other than the returned value). The refcount lets
|
||||
* el_list_append and el_map_set mutate in place when uniquely owned (cheap)
|
||||
* and copy-on-write when shared (preserves persistent semantics across
|
||||
* accumulator patterns in the compiler itself). */
|
||||
|
||||
void el_retain(el_val_t v);
|
||||
void el_release(el_val_t v);
|
||||
|
||||
/* ── Scoped arena (CLI use) ───────────────────────────────────────────────── */
|
||||
el_val_t el_arena_push(void);
|
||||
el_val_t el_arena_pop(el_val_t mark);
|
||||
|
||||
/* ── List ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_list_new(el_val_t count, ...);
|
||||
el_val_t el_list_len(el_val_t list);
|
||||
el_val_t el_list_get(el_val_t list, el_val_t index);
|
||||
el_val_t el_list_append(el_val_t list, el_val_t elem);
|
||||
el_val_t el_list_empty(void);
|
||||
el_val_t el_list_clone(el_val_t list);
|
||||
|
||||
/* ── Map ─────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t el_map_new(el_val_t pair_count, ...);
|
||||
el_val_t el_get_field(el_val_t map, el_val_t key);
|
||||
el_val_t el_map_get(el_val_t map, el_val_t key);
|
||||
el_val_t el_map_set(el_val_t map, el_val_t key, el_val_t value);
|
||||
|
||||
/* ── HTTP ─────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t http_get(el_val_t url);
|
||||
el_val_t http_post(el_val_t url, el_val_t body);
|
||||
el_val_t http_post_json(el_val_t url, el_val_t json_body);
|
||||
el_val_t http_get_with_headers(el_val_t url, el_val_t headers_map);
|
||||
el_val_t http_post_with_headers(el_val_t url, el_val_t body, el_val_t headers_map);
|
||||
el_val_t http_post_json_with_headers(el_val_t url, el_val_t headers_map, el_val_t json_body);
|
||||
el_val_t http_post_form_auth(el_val_t url, el_val_t form_body, el_val_t auth_header);
|
||||
el_val_t http_delete(el_val_t url);
|
||||
el_val_t http_serve(el_val_t port, el_val_t handler);
|
||||
el_val_t http_set_handler(el_val_t name);
|
||||
|
||||
/* HTTP server v2 ─────────────────────────────────────────────────────────────
|
||||
* Same dispatch model as http_serve, but the handler signature is widened:
|
||||
*
|
||||
* el_val_t handler(method, path, headers_map, body)
|
||||
*
|
||||
* `headers_map` is an ElMap from lowercased header name → header value (both
|
||||
* Strings). Repeated headers are joined with ", " per RFC 7230.
|
||||
*
|
||||
* Response value: the handler may return either
|
||||
* (a) a plain body string — same auto-content-type / 200-OK behaviour as
|
||||
* http_serve (3-arg) — or
|
||||
* (b) a response envelope built with `http_response(status, headers_json,
|
||||
* body)`. The runtime detects the envelope discriminator
|
||||
* `"el_http_response":1` at the start of the returned string and
|
||||
* unpacks status / headers / body before sending.
|
||||
*
|
||||
* The 3-arg http_serve(port, handler) remains supported unchanged for
|
||||
* existing handlers (e.g. products/web/server.el): it dispatches with
|
||||
* (method, path, body), hardcodes 200 OK, and auto-detects content type. */
|
||||
el_val_t http_serve_v2(el_val_t port, el_val_t handler);
|
||||
el_val_t http_set_handler_v2(el_val_t name);
|
||||
|
||||
/* Build an HTTP response envelope. `headers_json` should be a JSON object
|
||||
* literal like `{"WWW-Authenticate":"Basic"}` (or "" / "{}" for none). The
|
||||
* returned string carries the discriminator `{"el_http_response":1,...}`
|
||||
* which the runtime's send-path detects and unpacks. Detection happens
|
||||
* uniformly inside http_send_response, so a 3-arg handler may also return
|
||||
* an envelope. The 3-arg variant remains documented as a fixed 200-OK
|
||||
* auto-content-type contract for legacy handlers that return plain bodies. */
|
||||
el_val_t http_response(el_val_t status, el_val_t headers_json, el_val_t body);
|
||||
|
||||
/* SSE connection fd — set by http_worker_v2 before calling the El handler,
|
||||
* cleared afterwards. Defined in el_seed.c; called from el_runtime.c.
|
||||
* The getter is exposed as __http_conn_fd() to El programs. */
|
||||
void el_seed_set_http_conn_fd(int fd);
|
||||
|
||||
/* HTTP timeout — every libcurl request honors EL_HTTP_TIMEOUT_MS (default
|
||||
* 60000ms). Read lazily on first use, so setting the env var any time before
|
||||
* the first http_* call is sufficient. */
|
||||
|
||||
/* Streaming variants — write the response body straight to a file via
|
||||
* libcurl's CURLOPT_WRITEFUNCTION = fwrite. These bypass the el_val_t string
|
||||
* wrapper entirely, so binary payloads (audio/mpeg, image/png, etc.) survive
|
||||
* embedded NUL bytes that would truncate a strlen()-based code path.
|
||||
*
|
||||
* Both honor EL_HTTP_TIMEOUT_MS, follow redirects, and accept the same
|
||||
* `headers_map` shape as http_post_with_headers (ElMap of String→String).
|
||||
*
|
||||
* Return value: 1 on success (file fully written), 0 on any failure
|
||||
* (network, file open, partial write). On failure the output file is removed
|
||||
* so callers cannot mistake a partially-written file for a valid one. */
|
||||
el_val_t http_post_to_file(el_val_t url, el_val_t body, el_val_t headers_map, el_val_t output_path);
|
||||
el_val_t http_get_to_file(el_val_t url, el_val_t headers_map, el_val_t output_path);
|
||||
|
||||
/* ── URL encoding ────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t url_encode(el_val_t s); /* RFC 3986 unreserved set */
|
||||
el_val_t url_decode(el_val_t s); /* '+' → space, %XX → byte */
|
||||
|
||||
/* ── HTML allowlist sanitizer ────────────────────────────────────────────────
|
||||
* el_html_sanitize(input_html, allowlist_json) — strict allowlist HTML
|
||||
* cleaner. State-machine parser; tag/attribute names compared case-
|
||||
* insensitively against the allowlist; `<a href>` / `<… src>` URL schemes
|
||||
* validated (http, https, mailto, fragment-only, or relative); whole-
|
||||
* subtree drop for script / style / iframe / object / embed / form; HTML-
|
||||
* escapes free text outside dropped subtrees.
|
||||
*
|
||||
* The allowlist is JSON of the form
|
||||
* {"p":[],"a":["href","title"],"strong":[],...}
|
||||
* where each value is the array of attribute names allowed for that tag. */
|
||||
el_val_t el_html_sanitize(el_val_t input_html, el_val_t allowlist_json);
|
||||
el_val_t html_raw(el_val_t s);
|
||||
el_val_t html_escape(el_val_t s);
|
||||
|
||||
/* ── Filesystem ──────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t fs_read(el_val_t path);
|
||||
el_val_t fs_write(el_val_t path, el_val_t content);
|
||||
el_val_t fs_list(el_val_t path);
|
||||
el_val_t fs_list_json(el_val_t path);
|
||||
el_val_t fs_exists(el_val_t path);
|
||||
el_val_t fs_mkdir(el_val_t path); /* mkdir -p, mode 0755 */
|
||||
|
||||
/* Length-explicit binary write. `length` is an Int (el_val_t holding the
|
||||
* byte count). The caller knows the length from context — typically because
|
||||
* `bytes` came from base64_decode (which produces a magic-tagged binary
|
||||
* buffer with embedded NULs possible) and the caller already tracks the
|
||||
* decoded length, OR because the bytes came from a fixed-size source
|
||||
* (sha256_bytes = 32, hmac_sha256_bytes = 32). Bypasses strlen entirely.
|
||||
*
|
||||
* Returns 1 on success, 0 on failure (invalid path, can't open, partial
|
||||
* write, negative length). On partial-write failure, the file is removed
|
||||
* so callers cannot read back a truncated artefact. */
|
||||
el_val_t fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t length);
|
||||
|
||||
/* ── JSON ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t json_get(el_val_t json, el_val_t key);
|
||||
el_val_t json_parse(el_val_t s);
|
||||
el_val_t json_stringify(el_val_t v);
|
||||
el_val_t json_get_string(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_int(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_float(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_bool(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_get_raw(el_val_t json_str, el_val_t key);
|
||||
el_val_t json_set(el_val_t json_str, el_val_t key, el_val_t value);
|
||||
el_val_t json_array_len(el_val_t json_str);
|
||||
el_val_t json_array_get(el_val_t json_str, el_val_t index);
|
||||
el_val_t json_array_get_string(el_val_t json_str, el_val_t index);
|
||||
el_val_t json_escape_string(el_val_t sv);
|
||||
el_val_t json_build_object(el_val_t kvs);
|
||||
el_val_t json_build_array(el_val_t items);
|
||||
|
||||
/* ── Time ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t time_now(void);
|
||||
el_val_t time_now_utc(void);
|
||||
el_val_t sleep_secs(el_val_t secs);
|
||||
el_val_t sleep_ms(el_val_t ms);
|
||||
el_val_t time_format(el_val_t ts, el_val_t fmt);
|
||||
el_val_t time_to_parts(el_val_t ts);
|
||||
el_val_t time_from_parts(el_val_t secs, el_val_t ns, el_val_t tz);
|
||||
el_val_t time_add(el_val_t ts, el_val_t n, el_val_t unit);
|
||||
el_val_t time_diff(el_val_t ts1, el_val_t ts2, el_val_t unit);
|
||||
el_val_t now_ns(void);
|
||||
|
||||
/* ── Instant + Duration: first-class temporal types ──────────────────────────
|
||||
* Both types share the el_val_t (int64) slot. Instants are nanoseconds
|
||||
* since the Unix epoch; Durations are signed nanoseconds. Type discipline
|
||||
* is enforced at codegen-time: BinOps on names registered as Instant or
|
||||
* Duration route through the typed wrappers below; mismatches like
|
||||
* Instant+Instant become #error at the C compiler.
|
||||
*
|
||||
* Postfix literals — `30.seconds`, `1.hour`, `500.millis`, `30.nanos` — are
|
||||
* recognised by the parser as DurationLit AST nodes and lowered to literal
|
||||
* int64 nanoseconds at codegen time. The runtime never sees the units. */
|
||||
|
||||
el_val_t el_now_instant(void);
|
||||
el_val_t now(void);
|
||||
el_val_t unix_seconds(el_val_t n);
|
||||
el_val_t unix_millis(el_val_t n);
|
||||
el_val_t instant_from_iso8601(el_val_t s);
|
||||
|
||||
el_val_t el_duration_from_nanos(el_val_t ns);
|
||||
el_val_t duration_seconds(el_val_t n);
|
||||
el_val_t duration_millis(el_val_t n);
|
||||
el_val_t duration_nanos(el_val_t n);
|
||||
|
||||
el_val_t el_instant_add_dur(el_val_t inst, el_val_t dur);
|
||||
el_val_t el_instant_sub_dur(el_val_t inst, el_val_t dur);
|
||||
el_val_t el_instant_diff(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_add(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_sub(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_scale(el_val_t dur, el_val_t scalar);
|
||||
el_val_t el_duration_div(el_val_t dur, el_val_t scalar);
|
||||
|
||||
el_val_t el_instant_lt(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_le(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_gt(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_ge(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_eq(el_val_t a, el_val_t b);
|
||||
el_val_t el_instant_ne(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_lt(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_le(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_gt(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_ge(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_eq(el_val_t a, el_val_t b);
|
||||
el_val_t el_duration_ne(el_val_t a, el_val_t b);
|
||||
|
||||
el_val_t instant_to_unix_seconds(el_val_t i);
|
||||
el_val_t instant_to_unix_millis(el_val_t i);
|
||||
el_val_t instant_to_iso8601(el_val_t i);
|
||||
el_val_t duration_to_seconds(el_val_t d);
|
||||
el_val_t duration_to_millis(el_val_t d);
|
||||
el_val_t duration_to_nanos(el_val_t d);
|
||||
|
||||
el_val_t el_sleep_duration(el_val_t dur);
|
||||
el_val_t unix_timestamp(void);
|
||||
|
||||
el_val_t ttl_cache_set(el_val_t key, el_val_t value);
|
||||
el_val_t ttl_cache_get(el_val_t key, el_val_t max_age);
|
||||
el_val_t ttl_cache_age(el_val_t key);
|
||||
|
||||
/* ── Calendar + CalendarTime + Rhythm + LocalDate/Time/DateTime ─────────────
|
||||
* Phase 1.5 of the time system. Calendar is pluggable: EarthCalendar (IANA
|
||||
* zones, Gregorian, DST) is the user-facing default; MarsCalendar,
|
||||
* CycleCalendar(period), NoCycleCalendar, RelativeCalendar handle non-Earth
|
||||
* domains.
|
||||
*
|
||||
* A Calendar interprets an Instant under a particular cycle convention and
|
||||
* produces a CalendarTime. CalendarTime carries the underlying Instant and
|
||||
* a back-pointer to its Calendar; arithmetic and formatting consult the
|
||||
* Calendar to convert ns since epoch into year/month/day/hour/minute/second
|
||||
* (or sol/phase, or cycle/phase, depending on kind).
|
||||
*
|
||||
* Storage convention: Calendar / CalendarTime / Rhythm / LocalDate /
|
||||
* LocalDateTime are heap-allocated structs whose pointers are cast into
|
||||
* el_val_t. A 24-bit magic header at offset 0 lets the runtime identify
|
||||
* the kind safely. LocalTime is small enough to live in the int64 slot
|
||||
* directly (nanos since midnight, signed). */
|
||||
|
||||
/* Zone — opaque IANA zone or fixed offset, used by EarthCalendar.
|
||||
* `zone_id` is either an IANA name ("America/New_York", "UTC") or a fixed
|
||||
* offset string ("+05:30", "-08:00"). The runtime resolves it via tzset()
|
||||
* on first use of the owning EarthCalendar. */
|
||||
el_val_t zone(el_val_t id);
|
||||
el_val_t zone_utc(void);
|
||||
el_val_t zone_local(void);
|
||||
el_val_t zone_offset(el_val_t hours, el_val_t minutes);
|
||||
|
||||
/* Calendar constructors. Each returns an el_val_t pointer to a heap-
|
||||
* allocated, magic-tagged Calendar struct. Calendars are interned by
|
||||
* (kind, zone_id, period_ns, epoch_ns) so identical constructors return
|
||||
* the same pointer — equality is reference equality. */
|
||||
el_val_t earth_calendar(el_val_t z);
|
||||
el_val_t earth_calendar_default(void);
|
||||
el_val_t mars_calendar(void);
|
||||
el_val_t cycle_calendar(el_val_t period_dur);
|
||||
el_val_t no_cycle_calendar(void);
|
||||
el_val_t relative_calendar(el_val_t epoch_inst);
|
||||
|
||||
/* CalendarTime constructors and methods. Returns a heap-allocated struct
|
||||
* whose pointer fits in el_val_t. */
|
||||
el_val_t now_in(el_val_t cal);
|
||||
el_val_t in_calendar(el_val_t inst, el_val_t cal);
|
||||
el_val_t cal_format(el_val_t ct, el_val_t pattern);
|
||||
el_val_t cal_to_instant(el_val_t ct);
|
||||
el_val_t cal_cycle_phase(el_val_t ct);
|
||||
el_val_t cal_in(el_val_t ct, el_val_t cal);
|
||||
|
||||
/* LocalDate / LocalTime / LocalDateTime — calendar-agnostic value types.
|
||||
* LocalTime carries nanoseconds since midnight as a signed int64 directly
|
||||
* in the el_val_t slot (no allocation). LocalDate / LocalDateTime are
|
||||
* heap-allocated structs with magic headers. */
|
||||
el_val_t local_date(el_val_t y, el_val_t m, el_val_t d);
|
||||
el_val_t local_time(el_val_t h, el_val_t m, el_val_t s, el_val_t ns);
|
||||
el_val_t local_datetime(el_val_t date, el_val_t time);
|
||||
el_val_t zoned(el_val_t date, el_val_t time, el_val_t cal);
|
||||
|
||||
el_val_t local_date_year(el_val_t ld);
|
||||
el_val_t local_date_month(el_val_t ld);
|
||||
el_val_t local_date_day(el_val_t ld);
|
||||
el_val_t local_time_hour(el_val_t lt);
|
||||
el_val_t local_time_minute(el_val_t lt);
|
||||
el_val_t local_time_second(el_val_t lt);
|
||||
el_val_t local_time_nanos(el_val_t lt);
|
||||
|
||||
el_val_t el_local_date_add_dur(el_val_t ld, el_val_t dur);
|
||||
el_val_t el_local_time_add_dur(el_val_t lt, el_val_t dur);
|
||||
el_val_t el_local_date_lt(el_val_t a, el_val_t b);
|
||||
el_val_t el_local_date_eq(el_val_t a, el_val_t b);
|
||||
|
||||
/* Rhythm — pluggable recurrence AST. Returns a heap-allocated struct
|
||||
* pointer in el_val_t; rhythms are immutable so callers may share them. */
|
||||
el_val_t rhythm_cycle_start(void);
|
||||
el_val_t rhythm_cycle_phase(el_val_t phase);
|
||||
el_val_t rhythm_duration(el_val_t d);
|
||||
el_val_t rhythm_session_start(void);
|
||||
el_val_t rhythm_event(el_val_t name);
|
||||
el_val_t rhythm_and(el_val_t a, el_val_t b);
|
||||
el_val_t rhythm_or(el_val_t a, el_val_t b);
|
||||
el_val_t rhythm_weekday(el_val_t day);
|
||||
el_val_t rhythm_weekly_at(el_val_t day, el_val_t hour, el_val_t minute);
|
||||
el_val_t rhythm_next_after(el_val_t r, el_val_t after, el_val_t cal);
|
||||
el_val_t rhythm_matches(el_val_t r, el_val_t ct);
|
||||
|
||||
/* ── UUID ────────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t uuid_new(void);
|
||||
el_val_t uuid_v4(void);
|
||||
|
||||
/* ── Environment ─────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t env(el_val_t key);
|
||||
|
||||
/* ── In-process state K/V ────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t state_set(el_val_t key, el_val_t value);
|
||||
el_val_t state_get(el_val_t key);
|
||||
el_val_t state_del(el_val_t key);
|
||||
el_val_t state_keys(void);
|
||||
el_val_t state_has(el_val_t key);
|
||||
el_val_t state_get_or(el_val_t key, el_val_t default_val);
|
||||
|
||||
/* ── Float formatting ────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t float_to_str(el_val_t f);
|
||||
el_val_t int_to_float(el_val_t n);
|
||||
el_val_t float_to_int(el_val_t f);
|
||||
el_val_t format_float(el_val_t f, el_val_t decimals);
|
||||
el_val_t decimal_round(el_val_t f, el_val_t decimals);
|
||||
el_val_t str_to_float(el_val_t s);
|
||||
|
||||
/* ── Math (Float-aware) ──────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t math_sqrt(el_val_t f);
|
||||
el_val_t math_log(el_val_t f);
|
||||
el_val_t math_ln(el_val_t f);
|
||||
el_val_t math_sin(el_val_t f);
|
||||
el_val_t math_cos(el_val_t f);
|
||||
el_val_t math_pi(void);
|
||||
|
||||
/* ── String additions ────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t str_index_of(el_val_t s, el_val_t sub);
|
||||
el_val_t str_split(el_val_t s, el_val_t sep);
|
||||
el_val_t str_char_at(el_val_t s, el_val_t i);
|
||||
el_val_t str_char_code(el_val_t s, el_val_t i);
|
||||
el_val_t str_pad_left(el_val_t s, el_val_t width, el_val_t pad);
|
||||
el_val_t str_pad_right(el_val_t s, el_val_t width, el_val_t pad);
|
||||
el_val_t str_format(el_val_t fmt, el_val_t data);
|
||||
el_val_t str_lower(el_val_t s);
|
||||
el_val_t str_upper(el_val_t s);
|
||||
|
||||
/* ── Text-processing primitives (Phase 1: byte/codepoint, ASCII char classes)
|
||||
* Phase 2 (filed): Unicode-grapheme awareness, NFC/NFD normalization, regex.
|
||||
* is_* predicates: empty input returns false; multi-char requires ALL bytes
|
||||
* to match. ASCII ranges only in Phase 1. */
|
||||
|
||||
/* Counting */
|
||||
el_val_t str_count(el_val_t s, el_val_t sub); /* non-overlapping */
|
||||
el_val_t str_count_chars(el_val_t s); /* codepoint count */
|
||||
el_val_t str_count_bytes(el_val_t s); /* alias of str_len */
|
||||
el_val_t str_count_lines(el_val_t s);
|
||||
el_val_t str_count_words(el_val_t s);
|
||||
el_val_t str_count_letters(el_val_t s); /* ASCII [A-Za-z] */
|
||||
el_val_t str_count_digits(el_val_t s); /* ASCII [0-9] */
|
||||
|
||||
/* Find / position */
|
||||
el_val_t str_index_of_all(el_val_t s, el_val_t sub); /* [Int] of byte offsets */
|
||||
el_val_t str_last_index_of(el_val_t s, el_val_t sub);
|
||||
el_val_t str_find_chars(el_val_t s, el_val_t any_of); /* first idx of any ch */
|
||||
|
||||
/* Transform */
|
||||
el_val_t str_repeat(el_val_t s, el_val_t n);
|
||||
el_val_t str_reverse(el_val_t s); /* by codepoint */
|
||||
el_val_t str_strip_prefix(el_val_t s, el_val_t prefix);
|
||||
el_val_t str_strip_suffix(el_val_t s, el_val_t suffix);
|
||||
el_val_t str_strip_chars(el_val_t s, el_val_t chars);
|
||||
el_val_t str_lstrip(el_val_t s);
|
||||
el_val_t str_rstrip(el_val_t s);
|
||||
|
||||
/* Char classification (Bool) */
|
||||
el_val_t is_letter(el_val_t s);
|
||||
el_val_t is_digit(el_val_t s);
|
||||
el_val_t is_alphanumeric(el_val_t s);
|
||||
el_val_t is_whitespace(el_val_t s);
|
||||
el_val_t is_punctuation(el_val_t s);
|
||||
el_val_t is_uppercase(el_val_t s);
|
||||
el_val_t is_lowercase(el_val_t s);
|
||||
|
||||
/* Split / join */
|
||||
el_val_t str_split_lines(el_val_t s);
|
||||
el_val_t str_split_chars(el_val_t s); /* alias of native_string_chars */
|
||||
el_val_t str_split_n(el_val_t s, el_val_t sep, el_val_t n);
|
||||
el_val_t str_join(el_val_t list, el_val_t sep); /* alias of list_join */
|
||||
|
||||
/* ── List additions ──────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t list_push(el_val_t list, el_val_t elem);
|
||||
el_val_t list_push_front(el_val_t list, el_val_t elem);
|
||||
el_val_t list_join(el_val_t list, el_val_t sep);
|
||||
el_val_t list_range(el_val_t start, el_val_t end);
|
||||
|
||||
/* ── Bool helpers ────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t bool_to_str(el_val_t b);
|
||||
|
||||
/* ── Numeric parsing ─────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t parse_int(el_val_t s, el_val_t default_val);
|
||||
|
||||
/* ── Process ─────────────────────────────────────────────────────────────── */
|
||||
|
||||
el_val_t exit_program(el_val_t code);
|
||||
el_val_t getpid_now(void);
|
||||
|
||||
/* Self-terminating memory guard. Reads ELC_MAX_MEM_MB (default 512) and
|
||||
* exits with code 1 if resident memory exceeds the limit. Call periodically
|
||||
* during long compilation loops (e.g. after each function is compiled).
|
||||
* Returns 0 when memory is within bounds. */
|
||||
el_val_t el_mem_check(void);
|
||||
|
||||
/* ── CGI identity ─────────────────────────────────────────────────────────────
|
||||
* Called at the start of main() in CGI programs (those with a `cgi {}` block).
|
||||
* Records the program's DHARMA identity before any other code executes. */
|
||||
|
||||
void el_cgi_init(el_val_t name, el_val_t dharma_id, el_val_t principal,
|
||||
el_val_t network, el_val_t engram);
|
||||
|
||||
/* ── DHARMA network builtins ─────────────────────────────────────────────────
|
||||
* Available to CGI programs (declared with a `cgi {}` block).
|
||||
*
|
||||
* Peers are addressed by `dharma_id` of the form
|
||||
* "<registry-id>@<transport-url>" e.g. "ntn-genesis@http://localhost:7770"
|
||||
* If the @<url> portion is omitted, transport defaults to
|
||||
* "http://localhost:7770" (the local CGI daemon assumption).
|
||||
*
|
||||
* Wire protocol (all peers expose):
|
||||
* POST <url>/dharma/recv { channel, from, content } → response body
|
||||
* POST <url>/dharma/event { type, payload, source, timestamp }
|
||||
* POST <url>/api/activate { query } → list of nodes
|
||||
*
|
||||
* Hosting application's responsibility: an El program with a `cgi {}` block
|
||||
* runs http_serve() with its own request handler; that handler should route
|
||||
* "/dharma/event" requests by calling el_runtime_dharma_event_arrive() so
|
||||
* incoming events feed dharma_field() queues. The runtime itself does not
|
||||
* intercept any /dharma path. */
|
||||
|
||||
el_val_t dharma_connect(el_val_t cgi_id);
|
||||
el_val_t dharma_send(el_val_t channel, el_val_t content);
|
||||
el_val_t dharma_activate(el_val_t query);
|
||||
void dharma_emit(el_val_t event_type, el_val_t payload);
|
||||
el_val_t dharma_field(el_val_t event_type);
|
||||
void dharma_strengthen(el_val_t cgi_id, el_val_t weight);
|
||||
el_val_t dharma_relationship(el_val_t cgi_id);
|
||||
el_val_t dharma_peers(void);
|
||||
|
||||
/* Public C API: called by an El program's HTTP handler when a /dharma/event
|
||||
* request arrives. Pushes onto the per-event-type queue and signals any
|
||||
* pending dharma_field() blockers. All three arguments must be NUL-terminated
|
||||
* C strings (or NULL — then treated as empty). */
|
||||
void el_runtime_dharma_event_arrive(const char* event_type,
|
||||
const char* payload,
|
||||
const char* source);
|
||||
|
||||
/* ── Engram local graph primitives ───────────────────────────────────────────
|
||||
* Operate on the CGI's local Engram knowledge graph.
|
||||
* `engram_activate` queries the local graph only; `dharma_activate` is
|
||||
* network-wide across all connected CGI graphs. */
|
||||
|
||||
el_val_t engram_node(el_val_t content, el_val_t node_type, el_val_t salience);
|
||||
el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
|
||||
el_val_t salience, el_val_t importance, el_val_t confidence,
|
||||
el_val_t tier, el_val_t tags);
|
||||
/* Layered consciousness — see el_runtime.c for the layered architecture
|
||||
* design notes (search "Layered consciousness architecture"). The five
|
||||
* canonical layers (safety / core-identity / domain-knowledge / imprint /
|
||||
* suit) are seeded automatically; engram_add_layer extends the registry
|
||||
* with imprint or suit overlays at runtime. Nodes default to layer 1
|
||||
* (core-identity) when created via engram_node / engram_node_full. */
|
||||
el_val_t engram_node_layered(el_val_t content, el_val_t node_type, el_val_t label,
|
||||
el_val_t salience, el_val_t certainty, el_val_t confidence,
|
||||
el_val_t status, el_val_t tags, el_val_t layer_id);
|
||||
el_val_t engram_add_layer(el_val_t name, el_val_t priority, el_val_t suppressible,
|
||||
el_val_t transparent, el_val_t injectable);
|
||||
el_val_t engram_remove_layer(el_val_t layer_id);
|
||||
el_val_t engram_list_layers(void);
|
||||
el_val_t engram_get_node(el_val_t id);
|
||||
void engram_strengthen(el_val_t node_id);
|
||||
void engram_forget(el_val_t node_id);
|
||||
el_val_t engram_node_count(void);
|
||||
el_val_t engram_search(el_val_t query, el_val_t limit);
|
||||
el_val_t engram_scan_nodes(el_val_t limit, el_val_t offset);
|
||||
void engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t relation);
|
||||
el_val_t engram_edge_between(el_val_t from_id, el_val_t to_id);
|
||||
el_val_t engram_neighbors(el_val_t node_id);
|
||||
el_val_t engram_neighbors_filtered(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
||||
el_val_t engram_edge_count(void);
|
||||
/* Three-pass activation: background fan-out → working-memory promotion →
|
||||
* Layer 0 override. See "Three-pass activation" in el_runtime.c. */
|
||||
el_val_t engram_activate(el_val_t query, el_val_t depth);
|
||||
el_val_t engram_save(el_val_t path);
|
||||
el_val_t engram_load(el_val_t path);
|
||||
|
||||
/* JSON-string accessors — return pre-serialized JSON so HTTP handlers
|
||||
* can pass results straight through without round-tripping ElList/ElMap
|
||||
* through json_stringify. */
|
||||
el_val_t engram_get_node_json(el_val_t id);
|
||||
el_val_t engram_search_json(el_val_t query, el_val_t limit);
|
||||
el_val_t engram_scan_nodes_json(el_val_t limit, el_val_t offset);
|
||||
el_val_t engram_scan_nodes_by_type_json(el_val_t node_type, el_val_t limit, el_val_t offset);
|
||||
el_val_t engram_neighbors_json(el_val_t node_id, el_val_t max_depth, el_val_t direction);
|
||||
el_val_t engram_activate_json(el_val_t query, el_val_t depth);
|
||||
el_val_t engram_stats_json(void);
|
||||
el_val_t engram_list_layers_json(void);
|
||||
/* engram_compile_layered_json — produce a prompt-ready text block split
|
||||
* into "[LAYER 0 — STRUCTURAL]" (non-suppressible layers, sacred fire)
|
||||
* and "[ENGRAM CONTEXT]" (standard suppressible layers). Returns "" if
|
||||
* no nodes promoted to working memory. */
|
||||
el_val_t engram_compile_layered_json(el_val_t intent, el_val_t depth);
|
||||
|
||||
/* ── LLM (Anthropic API client) ─────────────────────────────────────────────
|
||||
* All functions call https://api.anthropic.com/v1/messages with the API key
|
||||
* from env ANTHROPIC_API_KEY. Default model when empty: claude-sonnet-4-5. */
|
||||
|
||||
el_val_t llm_call(el_val_t model, el_val_t prompt);
|
||||
el_val_t llm_call_system(el_val_t model, el_val_t system_prompt, el_val_t user_prompt);
|
||||
el_val_t llm_call_agentic(el_val_t model, el_val_t system, el_val_t user, el_val_t tools);
|
||||
el_val_t llm_vision(el_val_t model, el_val_t system, el_val_t prompt, el_val_t image_url_or_b64);
|
||||
el_val_t llm_models(void);
|
||||
|
||||
/* Register a tool handler by name. The handler is looked up via dlsym
|
||||
* (mirroring http_set_handler), so any El `fn <name>(input)` compiles to
|
||||
* a global C symbol that this function can locate at runtime.
|
||||
* Handler signature: `el_val_t handler(el_val_t input_json)` — receives
|
||||
* the tool input as a JSON-string el_val_t and returns a JSON-string
|
||||
* el_val_t result. Used by llm_call_agentic. */
|
||||
void llm_register_tool(el_val_t name, el_val_t handler_fn_name);
|
||||
|
||||
/* ── args() ─────────────────────────────────────────────────────────────────
|
||||
* Provides access to command-line arguments passed to the program.
|
||||
* Populated by el_runtime_init_args() before main() runs. */
|
||||
|
||||
el_val_t args(void);
|
||||
void el_runtime_init_args(int argc, char** argv);
|
||||
|
||||
/* ── Crypto primitives ─────────────────────────────────────────────────────
|
||||
* SHA-256, HMAC-SHA-256, and base64 (standard + URL-safe).
|
||||
* Self-contained — no OpenSSL/libcrypto dependency. The implementations are
|
||||
* adapted from public-domain reference code (Brad Conte / RFC 4648).
|
||||
*
|
||||
* Bytes-returning variants (sha256_bytes, hmac_sha256_bytes) return a string
|
||||
* value whose contents are raw binary; callers usually feed these into
|
||||
* base64_encode. Note that el_val_t strings are NUL-terminated by convention,
|
||||
* so the binary payload may contain embedded NULs — pass it directly into
|
||||
* base64_encode (which uses an explicit length) rather than treating it as
|
||||
* a printable C string.
|
||||
*
|
||||
* The "base64" variants emit/accept RFC 4648 standard alphabet with padding.
|
||||
* The "base64url" variants use URL-safe alphabet (`-`/`_`) with no padding,
|
||||
* as used in JWTs. */
|
||||
|
||||
el_val_t sha256_hex(el_val_t input);
|
||||
el_val_t sha256_bytes(el_val_t input);
|
||||
el_val_t hmac_sha256_hex(el_val_t key, el_val_t message);
|
||||
el_val_t hmac_sha256_bytes(el_val_t key, el_val_t message);
|
||||
el_val_t base64_encode(el_val_t input);
|
||||
el_val_t base64_decode(el_val_t input);
|
||||
el_val_t base64url_encode(el_val_t input);
|
||||
el_val_t base64url_decode(el_val_t input);
|
||||
|
||||
/* Length-aware variants (internal — exposed for the rare caller that already
|
||||
* has a known-length binary buffer and doesn't want to round-trip through
|
||||
* a NUL-terminated el_val_t string). Sha256_bytes and hmac_sha256_bytes feed
|
||||
* these implicitly. */
|
||||
el_val_t el_sha256_bytes_n(const unsigned char* data, size_t len);
|
||||
el_val_t el_base64_encode_n(const unsigned char* data, size_t len, int url_safe);
|
||||
|
||||
/* ── Post-quantum primitives (liboqs-backed) ────────────────────────────────
|
||||
* All inputs/outputs hex-encoded. Algorithm choices:
|
||||
* Signature: CRYSTALS-Dilithium-3 (NIST level 3, balanced)
|
||||
* KEM: CRYSTALS-Kyber-768 (NIST level 3)
|
||||
* Hash: SHA3-256 (Keccak) (PQ-aware protocols favour SHA3 over SHA2)
|
||||
*
|
||||
* If liboqs is not linked (detected via __has_include(<oqs/oqs.h>) at compile
|
||||
* time), the pq_* entry points return a JSON-shaped error string so callers
|
||||
* fail loudly rather than silently fall back to classical schemes:
|
||||
* {"error":"liboqs not linked, post-quantum primitives unavailable"}
|
||||
*
|
||||
* The hybrid handshake pairs X25519 with Kyber-768 per NIST PQ guidance and
|
||||
* CNSA 2.0. Combined shared secret is HKDF-SHA256(x25519_ss || kyber_ss).
|
||||
* Even if Kyber falls, X25519 holds; if X25519 falls under quantum attack,
|
||||
* Kyber holds. SHA3-256 also remains usable independent of liboqs (the
|
||||
* Keccak permutation is PQ-OK as a primitive). */
|
||||
|
||||
el_val_t pq_keygen_signature(void);
|
||||
el_val_t pq_sign(el_val_t secret_key_hex, el_val_t message);
|
||||
el_val_t pq_verify(el_val_t public_key_hex, el_val_t message, el_val_t signature_hex);
|
||||
|
||||
el_val_t pq_kem_keygen(void);
|
||||
el_val_t pq_kem_encaps(el_val_t public_key_hex);
|
||||
el_val_t pq_kem_decaps(el_val_t secret_key_hex, el_val_t ciphertext_hex);
|
||||
|
||||
el_val_t pq_hybrid_keygen(void);
|
||||
el_val_t pq_hybrid_handshake(el_val_t remote_pub_combined);
|
||||
|
||||
el_val_t sha3_256_hex(el_val_t input);
|
||||
|
||||
/* ── AEAD: AES-256-GCM (libcrypto-backed) ───────────────────────────────────
|
||||
* Symmetric authenticated encryption used to wrap envelopes after a KEM
|
||||
* handshake. Caller MUST supply a 32-byte key (64 hex chars) — typically the
|
||||
* Kyber-768 / hybrid shared_secret, optionally normalized via SHA3-256.
|
||||
*
|
||||
* aead_encrypt returns a JSON map {"nonce":"...","ciphertext":"..."} where
|
||||
* ciphertext is the AES-256-GCM output with the 16-byte auth tag appended.
|
||||
* Nonce is a fresh 12-byte CSPRNG draw — callers never pick the nonce, which
|
||||
* structurally rules out the GCM nonce-reuse footgun.
|
||||
*
|
||||
* aead_decrypt returns the plaintext String, or "" on any failure (including
|
||||
* auth-tag mismatch). Callers MUST check for "" before trusting the result. */
|
||||
el_val_t aead_encrypt(el_val_t key_hex, el_val_t plaintext);
|
||||
el_val_t aead_decrypt(el_val_t key_hex, el_val_t nonce_hex, el_val_t ciphertext_hex);
|
||||
|
||||
/* ── Native VM builtin aliases (for compiled El source) ─────────────────────
|
||||
* These match the El VM's native_* builtins so that El source compiled
|
||||
* to C can call the same names without modification. */
|
||||
|
||||
el_val_t native_list_get(el_val_t list, el_val_t index);
|
||||
el_val_t native_list_len(el_val_t list);
|
||||
el_val_t native_list_append(el_val_t list, el_val_t elem);
|
||||
el_val_t native_list_empty(void);
|
||||
el_val_t native_list_clone(el_val_t list);
|
||||
el_val_t native_string_chars(el_val_t s);
|
||||
el_val_t native_int_to_str(el_val_t n);
|
||||
|
||||
/* ── Method-call shorthand aliases ──────────────────────────────────────────
|
||||
* The El method-call convention `obj.method(args)` compiles to
|
||||
* `method(obj, args)`. These aliases expose the runtime functions under
|
||||
* the short names that result from method calls in El source.
|
||||
*
|
||||
* Example: `myList.append(x)` → `append(myList, x)` (calls this alias)
|
||||
* `myList.len()` → `len(myList)` (calls this alias) */
|
||||
|
||||
el_val_t append(el_val_t list, el_val_t elem); /* el_list_append */
|
||||
el_val_t len(el_val_t list); /* el_list_len */
|
||||
el_val_t get(el_val_t list, el_val_t index); /* el_list_get */
|
||||
el_val_t map_get(el_val_t map, el_val_t key); /* el_map_get */
|
||||
el_val_t map_set(el_val_t map, el_val_t key, el_val_t value); /* el_map_set */
|
||||
|
||||
/* ── OTLP/HTTP Observability ─────────────────────────────────────────────── */
|
||||
/* See bottom of el_runtime.c for the implementation.
|
||||
* Configured by env vars OTLP_ENDPOINT, OTEL_SERVICE_NAME, OTEL_SERVICE_VERSION.
|
||||
* No-op when OTLP_ENDPOINT is unset. Drop-on-failure semantics. */
|
||||
/* ── Subprocess execution ────────────────────────────────────────────────── */
|
||||
el_val_t exec_command(el_val_t cmd); /* run shell command, return exit code */
|
||||
el_val_t exec_capture(el_val_t cmd); /* run shell command, capture stdout */
|
||||
el_val_t exec(el_val_t cmd); /* exec(cmd) → stdout String (30s timeout) */
|
||||
el_val_t exec_bg(el_val_t cmd); /* exec_bg(cmd) → PID String (non-blocking) */
|
||||
|
||||
/* ── Stdout redirection (used by compiler JS pipeline) ───────────────────── */
|
||||
el_val_t stdout_to_file(el_val_t path); /* redirect process stdout to a file */
|
||||
el_val_t stdout_restore(void); /* restore process stdout to terminal */
|
||||
|
||||
el_val_t emit_log(el_val_t level, el_val_t msg, el_val_t fields_json);
|
||||
el_val_t emit_metric(el_val_t name, el_val_t value, el_val_t tags_json);
|
||||
el_val_t trace_span_start(el_val_t name);
|
||||
el_val_t trace_span_end(el_val_t span_handle);
|
||||
el_val_t emit_event(el_val_t name, el_val_t duration_ms);
|
||||
|
||||
el_val_t __thread_create(el_val_t fn_name_v, el_val_t arg_v);
|
||||
el_val_t __thread_join(el_val_t tid_v);
|
||||
|
||||
/* ── __ prefixed aliases (self-hosting compiler ABI) ─────────────────────────
|
||||
* The El self-hosting compiler emits calls to __-prefixed names. These are
|
||||
* forwarding wrappers around the existing el_runtime functions above. */
|
||||
|
||||
/* I/O */
|
||||
el_val_t __println(el_val_t s);
|
||||
el_val_t __print(el_val_t s);
|
||||
el_val_t __readline(void);
|
||||
|
||||
/* String */
|
||||
el_val_t __int_to_str(el_val_t n);
|
||||
el_val_t __str_to_int(el_val_t s);
|
||||
el_val_t __float_to_str(el_val_t f);
|
||||
el_val_t __str_to_float(el_val_t s);
|
||||
el_val_t __str_len(el_val_t s);
|
||||
el_val_t __str_char_at(el_val_t s, el_val_t i);
|
||||
el_val_t __str_cmp(el_val_t a, el_val_t b);
|
||||
el_val_t __str_ncmp(el_val_t a, el_val_t b, el_val_t n);
|
||||
el_val_t __str_concat_raw(el_val_t a, el_val_t b);
|
||||
el_val_t __str_slice_raw(el_val_t s, el_val_t start, el_val_t end);
|
||||
el_val_t __str_alloc(el_val_t n);
|
||||
el_val_t __str_set_char(el_val_t s, el_val_t i, el_val_t c);
|
||||
|
||||
/* URL encoding */
|
||||
el_val_t __url_encode(el_val_t s);
|
||||
el_val_t __url_decode(el_val_t s);
|
||||
|
||||
/* Environment */
|
||||
el_val_t __env_get(el_val_t key);
|
||||
|
||||
/* Subprocess */
|
||||
el_val_t __exec(el_val_t cmd);
|
||||
el_val_t __exec_bg(el_val_t cmd);
|
||||
|
||||
/* Process */
|
||||
el_val_t __exit_program(el_val_t code);
|
||||
|
||||
/* Filesystem */
|
||||
el_val_t __fs_exists(el_val_t path);
|
||||
el_val_t __fs_mkdir(el_val_t path);
|
||||
el_val_t __fs_read(el_val_t path);
|
||||
el_val_t __fs_write(el_val_t path, el_val_t content);
|
||||
el_val_t __fs_write_bytes(el_val_t path, el_val_t bytes, el_val_t n);
|
||||
el_val_t __fs_list_raw(el_val_t path);
|
||||
|
||||
/* HTTP server */
|
||||
el_val_t __http_response(el_val_t status, el_val_t headers_json, el_val_t body);
|
||||
el_val_t __http_serve(el_val_t port, el_val_t handler);
|
||||
el_val_t __http_serve_v2(el_val_t port, el_val_t handler);
|
||||
|
||||
/* HTTP conn fd / SSE (weak; overridden by el_seed.c when linked together) */
|
||||
el_val_t __http_conn_fd(void);
|
||||
el_val_t __http_sse_open(el_val_t conn_id);
|
||||
el_val_t __http_sse_send(el_val_t conn_id, el_val_t data);
|
||||
el_val_t __http_sse_close(el_val_t conn_id);
|
||||
|
||||
/* HTTP client (requires HAVE_CURL; stubs provided for no-curl builds) */
|
||||
el_val_t __http_do(el_val_t method, el_val_t url, el_val_t body,
|
||||
el_val_t headers_map, el_val_t timeout_ms);
|
||||
el_val_t __http_do_map(el_val_t method, el_val_t url, el_val_t body,
|
||||
el_val_t headers_json, el_val_t timeout_ms);
|
||||
el_val_t __http_do_map_to_file(el_val_t method, el_val_t url, el_val_t body,
|
||||
el_val_t headers_json, el_val_t output_path);
|
||||
|
||||
/* JSON */
|
||||
el_val_t __json_array_get(el_val_t json, el_val_t index);
|
||||
el_val_t __json_array_get_string(el_val_t json, el_val_t index);
|
||||
el_val_t __json_array_len(el_val_t json);
|
||||
el_val_t __json_get(el_val_t json, el_val_t key);
|
||||
el_val_t __json_get_raw(el_val_t json, el_val_t key);
|
||||
el_val_t __json_set(el_val_t json, el_val_t key, el_val_t value);
|
||||
el_val_t __json_parse_map(el_val_t json_str);
|
||||
el_val_t __json_stringify_val(el_val_t val);
|
||||
|
||||
/* Hashing */
|
||||
el_val_t __sha256_hex(el_val_t s);
|
||||
|
||||
/* State K/V */
|
||||
el_val_t __state_del(el_val_t key);
|
||||
el_val_t __state_get(el_val_t key);
|
||||
el_val_t __state_keys(void);
|
||||
el_val_t __state_set(el_val_t key, el_val_t val);
|
||||
|
||||
/* UUID */
|
||||
el_val_t __uuid_v4(void);
|
||||
|
||||
/* Args */
|
||||
el_val_t __args_json(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,123 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include "el_runtime.h"
|
||||
|
||||
el_val_t on_greet_click(el_val_t widget, el_val_t data);
|
||||
el_val_t on_counter_click(el_val_t widget, el_val_t data);
|
||||
el_val_t on_name_change(el_val_t widget, el_val_t data);
|
||||
el_val_t app_build(el_val_t window);
|
||||
|
||||
el_val_t g_window;
|
||||
el_val_t g_label;
|
||||
el_val_t g_input;
|
||||
el_val_t g_button;
|
||||
el_val_t g_counter;
|
||||
el_val_t g_counter_lbl;
|
||||
el_val_t manifest_env;
|
||||
el_val_t manifest_path;
|
||||
el_val_t win;
|
||||
el_val_t g_window;
|
||||
|
||||
el_val_t on_greet_click(el_val_t widget, el_val_t data) {
|
||||
el_val_t name = widget_get_text(g_input);
|
||||
el_val_t greeting = ({ el_val_t _if_result_1 = 0; if ((str_len(name) > 0)) { _if_result_1 = (el_str_concat(el_str_concat(EL_STR("Hello, "), name), EL_STR("!"))); } else { _if_result_1 = (EL_STR("Hello, World!")); } _if_result_1; });
|
||||
widget_set_text(g_label, greeting);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t on_counter_click(el_val_t widget, el_val_t data) {
|
||||
el_val_t g_counter = (g_counter + 1);
|
||||
widget_set_text(g_counter_lbl, el_str_concat(EL_STR("Clicks: "), int_to_str(g_counter)));
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t on_name_change(el_val_t widget, el_val_t data) {
|
||||
el_val_t has_text = (str_len(data) > 0);
|
||||
widget_set_disabled(g_button, !has_text);
|
||||
return 0;
|
||||
}
|
||||
|
||||
el_val_t app_build(el_val_t window) {
|
||||
el_val_t root = vstack(0);
|
||||
widget_set_padding_all(root, 24);
|
||||
widget_set_bg_color_hex(root, EL_STR("#0f172a"));
|
||||
widget_set_flex(root, 1);
|
||||
el_val_t title = label(EL_STR("el-native \xe2\x80\x94 native widget demo"));
|
||||
style_label_heading(title);
|
||||
widget_add_child(root, title);
|
||||
el_val_t gap1 = label(EL_STR(""));
|
||||
widget_set_height(gap1, 16);
|
||||
widget_add_child(root, gap1);
|
||||
el_val_t subtitle = label(EL_STR("AppKit controls from el code, no ObjC in the app layer."));
|
||||
style_label_muted(subtitle);
|
||||
widget_add_child(root, subtitle);
|
||||
el_val_t gap2 = label(EL_STR(""));
|
||||
widget_set_height(gap2, 24);
|
||||
widget_add_child(root, gap2);
|
||||
el_val_t input_row = hstack(8);
|
||||
el_val_t name_label = label(EL_STR("Name:"));
|
||||
style_label_body(name_label);
|
||||
widget_set_width(name_label, 60);
|
||||
el_val_t input = text_field(EL_STR("Enter your name\xe2\x80\xa6"));
|
||||
style_label_body(input);
|
||||
widget_set_flex(input, 1);
|
||||
widget_on_change(input, EL_STR("on_name_change"));
|
||||
el_val_t g_input = input;
|
||||
el_val_t greet_btn = button(EL_STR("Greet"));
|
||||
style_button_primary(greet_btn);
|
||||
widget_set_disabled(greet_btn, 1);
|
||||
widget_on_click(greet_btn, EL_STR("on_greet_click"));
|
||||
el_val_t g_button = greet_btn;
|
||||
widget_add_child(input_row, name_label);
|
||||
widget_add_child(input_row, input);
|
||||
widget_add_child(input_row, greet_btn);
|
||||
widget_add_child(root, input_row);
|
||||
el_val_t gap3 = label(EL_STR(""));
|
||||
widget_set_height(gap3, 12);
|
||||
widget_add_child(root, gap3);
|
||||
el_val_t greeting = label(EL_STR("Waiting for name\xe2\x80\xa6"));
|
||||
style_label_body(greeting);
|
||||
widget_set_color_hex(greeting, EL_STR("#60a5fa"));
|
||||
widget_set_font(greeting, EL_STR("system"), 16, 1);
|
||||
el_val_t g_label = greeting;
|
||||
widget_add_child(root, greeting);
|
||||
el_val_t gap4 = label(EL_STR(""));
|
||||
widget_set_height(gap4, 24);
|
||||
widget_add_child(root, gap4);
|
||||
el_val_t counter_row = hstack(12);
|
||||
el_val_t counter_lbl = label(EL_STR("Clicks: 0"));
|
||||
style_label_body(counter_lbl);
|
||||
el_val_t g_counter_lbl = counter_lbl;
|
||||
el_val_t counter_btn = button(EL_STR("Click me"));
|
||||
style_button_primary(counter_btn);
|
||||
widget_on_click(counter_btn, EL_STR("on_counter_click"));
|
||||
widget_add_child(counter_row, counter_lbl);
|
||||
widget_add_child(counter_row, counter_btn);
|
||||
widget_add_child(root, counter_row);
|
||||
widget_add_child(window, root);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int _argc, char** _argv) {
|
||||
el_runtime_init_args(_argc, _argv);
|
||||
g_window = (-1);
|
||||
g_label = (-1);
|
||||
g_input = (-1);
|
||||
g_button = (-1);
|
||||
g_counter = 0;
|
||||
g_counter_lbl = (-1);
|
||||
native_init();
|
||||
manifest_env = env(EL_STR("EL_MANIFEST"));
|
||||
manifest_path = ({ el_val_t _if_result_2 = 0; if ((str_len(manifest_env) > 0)) { _if_result_2 = (manifest_env); } else { _if_result_2 = (EL_STR("manifest.el")); } _if_result_2; });
|
||||
win = window_from_manifest(manifest_path);
|
||||
g_window = win;
|
||||
if (win < 0) {
|
||||
println(EL_STR("Error: failed to create window. Is EL_TARGET_MACOS defined?"));
|
||||
exit_program(1);
|
||||
}
|
||||
app_build(win);
|
||||
window_show(win);
|
||||
native_run_loop();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
activity_main.xml — placeholder layout.
|
||||
|
||||
The el program sets the content view programmatically via ElBridge.setContentView
|
||||
(called from __window_show → el_android_window_show). This layout is not used
|
||||
at runtime but satisfies Gradle's res/ validation checks.
|
||||
-->
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical" />
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">el-native</string>
|
||||
</resources>
|
||||
Reference in New Issue
Block a user