1119 lines
41 KiB
Objective-C
1119 lines
41 KiB
Objective-C
/*
|
|
* el_uikit.m — UIKit backend for the el native widget system.
|
|
*
|
|
* This file implements the iOS UIKit widget layer that el_seed.c calls
|
|
* through to when EL_TARGET_IOS is defined.
|
|
*
|
|
* Architecture:
|
|
* el program (el code)
|
|
* → __widget_* C builtins in el_seed.c
|
|
* → el_uikit_* C-callable functions declared here
|
|
* → UIView/UIWindow/UIStackView/UIButton/etc. via ObjC
|
|
*
|
|
* Widget handles: every widget (window, view, control) is assigned an int64_t
|
|
* slot index into _el_widgets[]. The el program holds these as opaque Int values.
|
|
* Slot 0 is never valid (reserved as null handle = -1 convention).
|
|
*
|
|
* Threading: All UIKit calls MUST run on the main thread. el_uikit_sync_main
|
|
* bounces work to the main thread synchronously if called from a background thread.
|
|
*
|
|
* Callback mechanism: when a widget fires an event (button tap, text change),
|
|
* the bridge looks up the registered callback function name, then calls
|
|
* dlsym(RTLD_DEFAULT, fn_name)(widget_handle, event_data_string)
|
|
* This resolves the El function symbol at runtime, matching the __thread_create
|
|
* pattern already established in el_seed.c.
|
|
*
|
|
* iOS lifecycle: UIApplicationMain never returns. The el program's "main" body
|
|
* (window creation, layout, show) must execute inside applicationDidFinishLaunchingWithOptions.
|
|
* The caller sets el_main_entry_fn before calling __native_run_loop so the
|
|
* delegate can invoke it at the right time.
|
|
*
|
|
* Compile (MRC — no ARC; manual retain/release needed for struct-embedded id):
|
|
* clang -std=gnu11 -ObjC -fno-objc-arc -DEL_TARGET_IOS -framework UIKit \
|
|
* -c el_uikit.m -o el_uikit.o
|
|
* Then link el_uikit.o alongside el_seed.c.
|
|
*
|
|
* Do NOT use -fobjc-arc: the widget table stores id in a plain C struct. ARC
|
|
* forbids explicit retain/release on struct-embedded object pointers and cannot
|
|
* insert automatic retain/release through C struct boundaries. MRC is the
|
|
* correct choice for this kind of C/ObjC bridge.
|
|
*
|
|
* Link flags required:
|
|
* -framework UIKit
|
|
*/
|
|
|
|
#ifdef EL_TARGET_IOS
|
|
|
|
#import <UIKit/UIKit.h>
|
|
#import <objc/runtime.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <dlfcn.h>
|
|
#include "el_runtime.h"
|
|
|
|
/* ── Widget table ─────────────────────────────────────────────────────────── */
|
|
|
|
#define EL_UIKIT_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,
|
|
EL_WIDGET_DIVIDER = 11,
|
|
EL_WIDGET_SPACER = 12,
|
|
} ElWidgetKind;
|
|
|
|
typedef struct {
|
|
ElWidgetKind kind;
|
|
id obj; /* UIWindow* or UIView* subclass, retained */
|
|
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_UIKIT_MAX_WIDGETS];
|
|
static int _el_widget_count = 0;
|
|
|
|
/* Side table for ObjC delegate objects keyed by widget slot (MRC retained). */
|
|
static id _el_delegates[EL_UIKIT_MAX_WIDGETS];
|
|
|
|
/* Allocate a slot and return its index. Returns -1 if full. */
|
|
static int64_t el_widget_alloc(ElWidgetKind kind, id obj) {
|
|
for (int i = 1; i < EL_UIKIT_MAX_WIDGETS; i++) {
|
|
if (_el_widgets[i].kind == EL_WIDGET_FREE) {
|
|
_el_widgets[i].kind = kind;
|
|
_el_widgets[i].obj = [obj retain];
|
|
_el_widgets[i].cb_click = NULL;
|
|
_el_widgets[i].cb_change = NULL;
|
|
if (i > _el_widget_count) _el_widget_count = i;
|
|
return (int64_t)i;
|
|
}
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
static ElWidget* el_widget_get(int64_t handle) {
|
|
if (handle <= 0 || handle >= EL_UIKIT_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->obj release];
|
|
w->obj = nil;
|
|
w->kind = EL_WIDGET_FREE;
|
|
free(w->cb_click); w->cb_click = NULL;
|
|
free(w->cb_change); w->cb_change = NULL;
|
|
}
|
|
|
|
/* ── Dispatch helpers ─────────────────────────────────────────────────────── */
|
|
|
|
/* Run a block on the main thread, waiting for completion. Safe to call from
|
|
* any thread including the main thread. Uses dispatch_sync so callers can
|
|
* rely on the block having run before the function returns. */
|
|
static void el_uikit_sync_main(void (^block)(void)) {
|
|
if ([NSThread isMainThread]) {
|
|
block();
|
|
} else {
|
|
dispatch_sync(dispatch_get_main_queue(), block);
|
|
}
|
|
}
|
|
|
|
/* ── El callback invocation ──────────────────────────────────────────────── */
|
|
/*
|
|
* Invoke an El callback by symbol name. The El function must have the
|
|
* signature: fn handler(handle: Int, data: String) -> Void
|
|
* which compiles to: void fn_name(el_val_t handle, el_val_t data)
|
|
*/
|
|
typedef void (*ElCb2)(int64_t handle, int64_t data);
|
|
|
|
static void el_uikit_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) return;
|
|
ElCb2 fn = (ElCb2)sym;
|
|
fn(handle, (int64_t)(uintptr_t)(data ? data : ""));
|
|
}
|
|
|
|
/* ── ObjC helper: ElEventProxy — button tap and text event target ─────────── */
|
|
|
|
/*
|
|
* ElEventProxy is a lightweight ObjC action target / delegate aggregator.
|
|
* One instance is allocated per widget that has events. Stored in _el_delegates[].
|
|
*/
|
|
@interface ElEventProxy : NSObject <UITextFieldDelegate>
|
|
@property (nonatomic, assign) int64_t widgetHandle;
|
|
/* UIButton tap */
|
|
- (void)elButtonTapped:(id)sender;
|
|
/* UITextField UIControlEventEditingChanged */
|
|
- (void)elTextChanged:(id)sender;
|
|
@end
|
|
|
|
@implementation ElEventProxy
|
|
|
|
- (void)elButtonTapped:(id)sender {
|
|
(void)sender;
|
|
ElWidget* w = el_widget_get(self.widgetHandle);
|
|
if (w && w->cb_click) {
|
|
el_uikit_invoke_cb(w->cb_click, self.widgetHandle, "");
|
|
}
|
|
}
|
|
|
|
- (void)elTextChanged:(id)sender {
|
|
UITextField* tf = (UITextField*)sender;
|
|
ElWidget* w = el_widget_get(self.widgetHandle);
|
|
if (!w) return;
|
|
const char* val = [tf.text UTF8String];
|
|
if (w->cb_change) {
|
|
el_uikit_invoke_cb(w->cb_change, self.widgetHandle, val ? val : "");
|
|
}
|
|
}
|
|
|
|
/* UITextFieldDelegate — return key fires on_submit (stored in cb_click). */
|
|
- (BOOL)textFieldShouldReturn:(UITextField*)textField {
|
|
ElWidget* w = el_widget_get(self.widgetHandle);
|
|
if (w && w->cb_click) {
|
|
const char* val = [textField.text UTF8String];
|
|
el_uikit_invoke_cb(w->cb_click, self.widgetHandle, val ? val : "");
|
|
}
|
|
[textField resignFirstResponder];
|
|
return YES;
|
|
}
|
|
|
|
@end
|
|
|
|
/* ── UITextView change delegate ──────────────────────────────────────────── */
|
|
|
|
@interface ElTextViewDelegate : NSObject <UITextViewDelegate>
|
|
@property (nonatomic, assign) int64_t widgetHandle;
|
|
@end
|
|
|
|
@implementation ElTextViewDelegate
|
|
|
|
- (void)textViewDidChange:(UITextView*)textView {
|
|
ElWidget* w = el_widget_get(self.widgetHandle);
|
|
if (!w || !w->cb_change) return;
|
|
const char* val = [textView.text UTF8String];
|
|
el_uikit_invoke_cb(w->cb_change, self.widgetHandle, val ? val : "");
|
|
}
|
|
|
|
@end
|
|
|
|
/* ── iOS application lifecycle ───────────────────────────────────────────── */
|
|
|
|
/*
|
|
* el_main_entry_fn — set by el's main() (via __native_run_loop) before calling
|
|
* UIApplicationMain. The app delegate invokes it in didFinishLaunching so that
|
|
* the el program's window/widget setup runs at the correct lifecycle point.
|
|
*/
|
|
void (*el_main_entry_fn)(void) = NULL;
|
|
|
|
/* argc/argv forwarded from el_runtime_init_args (or main). */
|
|
static int _el_argc = 0;
|
|
static char** _el_argv = NULL;
|
|
|
|
/*
|
|
* el_uikit_set_args — call from el_runtime_init_args / main() before
|
|
* __native_run_loop to forward argc/argv to UIApplicationMain.
|
|
*/
|
|
void el_uikit_set_args(int argc, char** argv) {
|
|
_el_argc = argc;
|
|
_el_argv = argv;
|
|
}
|
|
|
|
@interface ElAppDelegate : UIResponder <UIApplicationDelegate>
|
|
@property (nonatomic, strong) UIWindow *keyWindow;
|
|
@end
|
|
|
|
@implementation ElAppDelegate
|
|
|
|
- (BOOL)application:(UIApplication*)app
|
|
didFinishLaunchingWithOptions:(NSDictionary*)launchOptions {
|
|
(void)app; (void)launchOptions;
|
|
/*
|
|
* The el program called __native_init and set up el_main_entry_fn before
|
|
* UIApplicationMain was entered. We call it here so window creation and
|
|
* widget layout happen on the main thread at the right lifecycle moment.
|
|
*/
|
|
if (el_main_entry_fn) {
|
|
el_main_entry_fn();
|
|
}
|
|
return YES;
|
|
}
|
|
|
|
- (void)applicationWillResignActive:(UIApplication*)app { (void)app; }
|
|
- (void)applicationDidEnterBackground:(UIApplication*)app { (void)app; }
|
|
- (void)applicationWillEnterForeground:(UIApplication*)app { (void)app; }
|
|
- (void)applicationDidBecomeActive:(UIApplication*)app { (void)app; }
|
|
|
|
@end
|
|
|
|
/* ── Public C API ─────────────────────────────────────────────────────────── */
|
|
|
|
/*
|
|
* el_uikit_init — one-time initialisation. Idempotent. Call before any other
|
|
* el_uikit_* function. On iOS there is no UIApplication equivalent of
|
|
* [NSApplication sharedApplication] to call here — the real initialisation
|
|
* happens inside UIApplicationMain which is entered from el_uikit_run_loop.
|
|
* This function records that init was requested.
|
|
*/
|
|
static int _el_uikit_init_done = 0;
|
|
|
|
void el_uikit_init(void) {
|
|
_el_uikit_init_done = 1;
|
|
}
|
|
|
|
/*
|
|
* el_uikit_run_loop — enters UIApplicationMain. Never returns.
|
|
* Must be called from the process main thread as the final step of main().
|
|
* Sets el_main_entry_fn to the provided function pointer before entering
|
|
* UIApplicationMain so the app delegate can call back into el code.
|
|
*/
|
|
void el_uikit_run_loop(void (*entry_fn)(void)) {
|
|
el_main_entry_fn = entry_fn;
|
|
UIApplicationMain(_el_argc, _el_argv, nil, @"ElAppDelegate");
|
|
}
|
|
|
|
/*
|
|
* el_uikit_window_create — create a UIWindow and configure a root
|
|
* UINavigationController + UIViewController. Returns window handle.
|
|
*
|
|
* On iOS there is exactly one full-screen window. width/height from the
|
|
* manifest are stored but the window always fills the screen. The title
|
|
* is applied to the root view controller's navigationItem.
|
|
*/
|
|
int64_t el_uikit_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;
|
|
__block int64_t handle = -1;
|
|
el_uikit_sync_main(^{
|
|
UIScreen* screen = [UIScreen mainScreen];
|
|
UIWindow* win = [[UIWindow alloc] initWithFrame:[screen bounds]];
|
|
win.backgroundColor = [UIColor systemBackgroundColor];
|
|
|
|
/* Root view controller with a vertical UIStackView as content. */
|
|
UIViewController* rootVC = [[UIViewController alloc] init];
|
|
NSString* titleStr = [NSString stringWithUTF8String:title ? title : ""];
|
|
rootVC.title = titleStr;
|
|
|
|
UIStackView* rootStack = [[UIStackView alloc] initWithFrame:CGRectZero];
|
|
rootStack.axis = UILayoutConstraintAxisVertical;
|
|
rootStack.alignment = UIStackViewAlignmentFill;
|
|
rootStack.distribution = UIStackViewDistributionFill;
|
|
rootStack.spacing = 0.0;
|
|
rootStack.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[rootVC.view addSubview:rootStack];
|
|
|
|
/* Pin root stack to safe area. */
|
|
UILayoutGuide* safe = rootVC.view.safeAreaLayoutGuide;
|
|
[NSLayoutConstraint activateConstraints:@[
|
|
[rootStack.topAnchor constraintEqualToAnchor:safe.topAnchor],
|
|
[rootStack.leadingAnchor constraintEqualToAnchor:safe.leadingAnchor],
|
|
[rootStack.trailingAnchor constraintEqualToAnchor:safe.trailingAnchor],
|
|
[rootStack.bottomAnchor constraintEqualToAnchor:safe.bottomAnchor],
|
|
]];
|
|
|
|
UINavigationController* navCtrl =
|
|
[[UINavigationController alloc] initWithRootViewController:rootVC];
|
|
win.rootViewController = navCtrl;
|
|
|
|
/* Store the window. The root stack is accessible via
|
|
* ((UINavigationController*)win.rootViewController)
|
|
* .topViewController.view.subviews[0]
|
|
* but we reach it by casting obj to UIWindow* and digging at add_child time. */
|
|
handle = el_widget_alloc(EL_WIDGET_WINDOW, win);
|
|
[win release];
|
|
[rootVC release];
|
|
[rootStack release];
|
|
[navCtrl release];
|
|
});
|
|
return handle;
|
|
}
|
|
|
|
/*
|
|
* el_uikit_window_show — make the window key and visible.
|
|
*/
|
|
void el_uikit_window_show(int64_t handle) {
|
|
el_uikit_sync_main(^{
|
|
ElWidget* w = el_widget_get(handle);
|
|
if (!w || w->kind != EL_WIDGET_WINDOW) return;
|
|
UIWindow* win = (UIWindow*)w->obj;
|
|
[win makeKeyAndVisible];
|
|
});
|
|
}
|
|
|
|
/*
|
|
* el_uikit_window_set_title — update nav bar title at runtime.
|
|
*/
|
|
void el_uikit_window_set_title(int64_t handle, const char* title) {
|
|
el_uikit_sync_main(^{
|
|
ElWidget* w = el_widget_get(handle);
|
|
if (!w || w->kind != EL_WIDGET_WINDOW) return;
|
|
UIWindow* win = (UIWindow*)w->obj;
|
|
UINavigationController* nav = (UINavigationController*)win.rootViewController;
|
|
if ([nav isKindOfClass:[UINavigationController class]]) {
|
|
nav.topViewController.title =
|
|
[NSString stringWithUTF8String:title ? title : ""];
|
|
}
|
|
});
|
|
}
|
|
|
|
/* ── Layout container factories ───────────────────────────────────────────── */
|
|
|
|
static int64_t make_uistack(UILayoutConstraintAxis axis, CGFloat spacing) {
|
|
__block int64_t handle = -1;
|
|
el_uikit_sync_main(^{
|
|
UIStackView* sv = [[UIStackView alloc] initWithFrame:CGRectZero];
|
|
sv.axis = axis;
|
|
sv.alignment = UIStackViewAlignmentFill;
|
|
sv.distribution = UIStackViewDistributionFill;
|
|
sv.spacing = spacing;
|
|
sv.translatesAutoresizingMaskIntoConstraints = NO;
|
|
ElWidgetKind kind = (axis == UILayoutConstraintAxisVertical)
|
|
? EL_WIDGET_VSTACK : EL_WIDGET_HSTACK;
|
|
handle = el_widget_alloc(kind, sv);
|
|
[sv release];
|
|
});
|
|
return handle;
|
|
}
|
|
|
|
int64_t el_uikit_vstack_create(int spacing) {
|
|
return make_uistack(UILayoutConstraintAxisVertical, (CGFloat)spacing);
|
|
}
|
|
|
|
int64_t el_uikit_hstack_create(int spacing) {
|
|
return make_uistack(UILayoutConstraintAxisHorizontal, (CGFloat)spacing);
|
|
}
|
|
|
|
int64_t el_uikit_zstack_create(void) {
|
|
/* ZStack = plain UIView; children are added with addSubview and use
|
|
* Auto Layout or absolute frames set by the caller. */
|
|
__block int64_t handle = -1;
|
|
el_uikit_sync_main(^{
|
|
UIView* v = [[UIView alloc] initWithFrame:CGRectZero];
|
|
v.translatesAutoresizingMaskIntoConstraints = NO;
|
|
handle = el_widget_alloc(EL_WIDGET_ZSTACK, v);
|
|
[v release];
|
|
});
|
|
return handle;
|
|
}
|
|
|
|
int64_t el_uikit_scroll_create(void) {
|
|
__block int64_t handle = -1;
|
|
el_uikit_sync_main(^{
|
|
UIScrollView* sv = [[UIScrollView alloc] initWithFrame:CGRectZero];
|
|
sv.translatesAutoresizingMaskIntoConstraints = NO;
|
|
sv.alwaysBounceVertical = YES;
|
|
sv.showsHorizontalScrollIndicator = NO;
|
|
handle = el_widget_alloc(EL_WIDGET_SCROLL, sv);
|
|
[sv release];
|
|
});
|
|
return handle;
|
|
}
|
|
|
|
/* ── Widget factories ─────────────────────────────────────────────────────── */
|
|
|
|
int64_t el_uikit_label_create(const char* text) {
|
|
__block int64_t handle = -1;
|
|
el_uikit_sync_main(^{
|
|
UILabel* lbl = [[UILabel alloc] initWithFrame:CGRectZero];
|
|
lbl.text = [NSString stringWithUTF8String:text ? text : ""];
|
|
lbl.numberOfLines = 0;
|
|
lbl.translatesAutoresizingMaskIntoConstraints = NO;
|
|
handle = el_widget_alloc(EL_WIDGET_LABEL, lbl);
|
|
[lbl release];
|
|
});
|
|
return handle;
|
|
}
|
|
|
|
int64_t el_uikit_button_create(const char* label) {
|
|
__block int64_t handle = -1;
|
|
el_uikit_sync_main(^{
|
|
UIButton* btn = [UIButton buttonWithType:UIButtonTypeSystem];
|
|
[btn setTitle:[NSString stringWithUTF8String:label ? label : ""]
|
|
forState:UIControlStateNormal];
|
|
btn.translatesAutoresizingMaskIntoConstraints = NO;
|
|
|
|
ElEventProxy* proxy = [[ElEventProxy alloc] init];
|
|
[btn addTarget:proxy
|
|
action:@selector(elButtonTapped:)
|
|
forControlEvents:UIControlEventTouchUpInside];
|
|
|
|
int64_t h = el_widget_alloc(EL_WIDGET_BUTTON, btn);
|
|
proxy.widgetHandle = h;
|
|
_el_delegates[h] = proxy; /* retain in side table */
|
|
handle = h;
|
|
/* btn is autoreleased from buttonWithType: — no release needed */
|
|
});
|
|
return handle;
|
|
}
|
|
|
|
int64_t el_uikit_text_field_create(const char* placeholder) {
|
|
__block int64_t handle = -1;
|
|
el_uikit_sync_main(^{
|
|
UITextField* tf = [[UITextField alloc] initWithFrame:CGRectZero];
|
|
tf.translatesAutoresizingMaskIntoConstraints = NO;
|
|
if (placeholder && *placeholder) {
|
|
tf.placeholder = [NSString stringWithUTF8String:placeholder];
|
|
}
|
|
tf.borderStyle = UITextBorderStyleRoundedRect;
|
|
tf.autocorrectionType = UITextAutocorrectionTypeNo;
|
|
|
|
ElEventProxy* proxy = [[ElEventProxy alloc] init];
|
|
tf.delegate = proxy;
|
|
[tf addTarget:proxy
|
|
action:@selector(elTextChanged:)
|
|
forControlEvents:UIControlEventEditingChanged];
|
|
|
|
int64_t h = el_widget_alloc(EL_WIDGET_TEXTFIELD, tf);
|
|
proxy.widgetHandle = h;
|
|
_el_delegates[h] = proxy;
|
|
handle = h;
|
|
[tf release];
|
|
});
|
|
return handle;
|
|
}
|
|
|
|
int64_t el_uikit_text_area_create(const char* placeholder) {
|
|
__block int64_t handle = -1;
|
|
el_uikit_sync_main(^{
|
|
UITextView* tv = [[UITextView alloc] initWithFrame:CGRectZero];
|
|
tv.translatesAutoresizingMaskIntoConstraints = NO;
|
|
tv.font = [UIFont systemFontOfSize:14.0];
|
|
tv.layer.borderColor = [[UIColor systemGray4Color] CGColor];
|
|
tv.layer.borderWidth = 1.0;
|
|
tv.layer.cornerRadius = 6.0;
|
|
tv.clipsToBounds = YES;
|
|
/* UITextView has no native placeholder; set accessibility hint. */
|
|
if (placeholder && *placeholder) {
|
|
tv.accessibilityHint = [NSString stringWithUTF8String:placeholder];
|
|
}
|
|
|
|
ElTextViewDelegate* del = [[ElTextViewDelegate alloc] init];
|
|
tv.delegate = del;
|
|
|
|
int64_t h = el_widget_alloc(EL_WIDGET_TEXTAREA, tv);
|
|
del.widgetHandle = h;
|
|
_el_delegates[h] = del;
|
|
handle = h;
|
|
[tv release];
|
|
});
|
|
return handle;
|
|
}
|
|
|
|
int64_t el_uikit_image_create(const char* path_or_name) {
|
|
__block int64_t handle = -1;
|
|
el_uikit_sync_main(^{
|
|
UIImage* img = nil;
|
|
if (path_or_name && *path_or_name) {
|
|
/* Try filesystem path first, then bundle asset name. */
|
|
img = [UIImage imageWithContentsOfFile:
|
|
[NSString stringWithUTF8String:path_or_name]];
|
|
if (!img) {
|
|
img = [UIImage imageNamed:
|
|
[NSString stringWithUTF8String:path_or_name]];
|
|
}
|
|
}
|
|
UIImageView* iv = [[UIImageView alloc] initWithFrame:CGRectZero];
|
|
iv.translatesAutoresizingMaskIntoConstraints = NO;
|
|
iv.contentMode = UIViewContentModeScaleAspectFit;
|
|
if (img) {
|
|
iv.image = img;
|
|
}
|
|
handle = el_widget_alloc(EL_WIDGET_IMAGE, iv);
|
|
[iv release];
|
|
});
|
|
return handle;
|
|
}
|
|
|
|
/* ── Widget property setters ─────────────────────────────────────────────── */
|
|
|
|
void el_uikit_widget_set_text(int64_t handle, const char* text) {
|
|
el_uikit_sync_main(^{
|
|
ElWidget* w = el_widget_get(handle);
|
|
if (!w) return;
|
|
NSString* s = [NSString stringWithUTF8String:text ? text : ""];
|
|
switch (w->kind) {
|
|
case EL_WIDGET_LABEL:
|
|
((UILabel*)w->obj).text = s;
|
|
break;
|
|
case EL_WIDGET_TEXTFIELD:
|
|
((UITextField*)w->obj).text = s;
|
|
break;
|
|
case EL_WIDGET_BUTTON:
|
|
[(UIButton*)w->obj setTitle:s forState:UIControlStateNormal];
|
|
break;
|
|
case EL_WIDGET_TEXTAREA:
|
|
((UITextView*)w->obj).text = s;
|
|
break;
|
|
case EL_WIDGET_WINDOW:
|
|
el_uikit_window_set_title(handle, text);
|
|
break;
|
|
default: break;
|
|
}
|
|
});
|
|
}
|
|
|
|
const char* el_uikit_widget_get_text(int64_t handle) {
|
|
__block const char* result = "";
|
|
el_uikit_sync_main(^{
|
|
ElWidget* w = el_widget_get(handle);
|
|
if (!w) return;
|
|
NSString* s = nil;
|
|
switch (w->kind) {
|
|
case EL_WIDGET_LABEL:
|
|
s = ((UILabel*)w->obj).text;
|
|
break;
|
|
case EL_WIDGET_TEXTFIELD:
|
|
s = ((UITextField*)w->obj).text;
|
|
break;
|
|
case EL_WIDGET_BUTTON:
|
|
s = [(UIButton*)w->obj titleForState:UIControlStateNormal];
|
|
break;
|
|
case EL_WIDGET_TEXTAREA:
|
|
s = ((UITextView*)w->obj).text;
|
|
break;
|
|
default: break;
|
|
}
|
|
if (s) result = strdup([s UTF8String]);
|
|
});
|
|
return result;
|
|
}
|
|
|
|
void el_uikit_widget_set_color(int64_t handle, float r, float g, float b, float a) {
|
|
el_uikit_sync_main(^{
|
|
ElWidget* w = el_widget_get(handle);
|
|
if (!w) return;
|
|
UIColor* c = [UIColor colorWithRed:(CGFloat)(r / 255.0)
|
|
green:(CGFloat)(g / 255.0)
|
|
blue:(CGFloat)(b / 255.0)
|
|
alpha:(CGFloat)(a / 255.0)];
|
|
switch (w->kind) {
|
|
case EL_WIDGET_LABEL:
|
|
((UILabel*)w->obj).textColor = c;
|
|
break;
|
|
case EL_WIDGET_TEXTFIELD:
|
|
((UITextField*)w->obj).textColor = c;
|
|
break;
|
|
case EL_WIDGET_TEXTAREA:
|
|
((UITextView*)w->obj).textColor = c;
|
|
break;
|
|
case EL_WIDGET_BUTTON:
|
|
[(UIButton*)w->obj setTitleColor:c forState:UIControlStateNormal];
|
|
((UIButton*)w->obj).tintColor = c;
|
|
break;
|
|
case EL_WIDGET_VSTACK:
|
|
case EL_WIDGET_HSTACK:
|
|
case EL_WIDGET_ZSTACK:
|
|
((UIView*)w->obj).backgroundColor = c;
|
|
break;
|
|
default: break;
|
|
}
|
|
});
|
|
}
|
|
|
|
void el_uikit_widget_set_bg_color(int64_t handle, float r, float g, float b, float a) {
|
|
el_uikit_sync_main(^{
|
|
ElWidget* w = el_widget_get(handle);
|
|
if (!w) return;
|
|
if (w->kind == EL_WIDGET_WINDOW) {
|
|
UIWindow* win = (UIWindow*)w->obj;
|
|
win.backgroundColor = [UIColor colorWithRed:(CGFloat)(r / 255.0)
|
|
green:(CGFloat)(g / 255.0)
|
|
blue:(CGFloat)(b / 255.0)
|
|
alpha:(CGFloat)(a / 255.0)];
|
|
return;
|
|
}
|
|
UIView* v = (UIView*)w->obj;
|
|
v.backgroundColor = [UIColor colorWithRed:(CGFloat)(r / 255.0)
|
|
green:(CGFloat)(g / 255.0)
|
|
blue:(CGFloat)(b / 255.0)
|
|
alpha:(CGFloat)(a / 255.0)];
|
|
});
|
|
}
|
|
|
|
void el_uikit_widget_set_font(int64_t handle, const char* family, int size, int bold) {
|
|
el_uikit_sync_main(^{
|
|
ElWidget* w = el_widget_get(handle);
|
|
if (!w) return;
|
|
UIFont* font;
|
|
if (bold) {
|
|
font = [UIFont boldSystemFontOfSize:(CGFloat)size];
|
|
} else if (family && *family && strcmp(family, "system") != 0) {
|
|
font = [UIFont fontWithName:[NSString stringWithUTF8String:family]
|
|
size:(CGFloat)size];
|
|
if (!font) font = [UIFont systemFontOfSize:(CGFloat)size];
|
|
} else {
|
|
font = [UIFont systemFontOfSize:(CGFloat)size];
|
|
}
|
|
switch (w->kind) {
|
|
case EL_WIDGET_LABEL:
|
|
((UILabel*)w->obj).font = font;
|
|
break;
|
|
case EL_WIDGET_TEXTFIELD:
|
|
((UITextField*)w->obj).font = font;
|
|
break;
|
|
case EL_WIDGET_TEXTAREA:
|
|
((UITextView*)w->obj).font = font;
|
|
break;
|
|
case EL_WIDGET_BUTTON:
|
|
((UIButton*)w->obj).titleLabel.font = font;
|
|
break;
|
|
default: break;
|
|
}
|
|
});
|
|
}
|
|
|
|
void el_uikit_widget_set_padding(int64_t handle, int top, int right, int bottom, int left) {
|
|
el_uikit_sync_main(^{
|
|
ElWidget* w = el_widget_get(handle);
|
|
if (!w) return;
|
|
UIEdgeInsets insets = UIEdgeInsetsMake(
|
|
(CGFloat)top, (CGFloat)left, (CGFloat)bottom, (CGFloat)right);
|
|
if (w->kind == EL_WIDGET_VSTACK || w->kind == EL_WIDGET_HSTACK) {
|
|
UIStackView* sv = (UIStackView*)w->obj;
|
|
sv.layoutMargins = insets;
|
|
sv.layoutMarginsRelativeArrangement = YES;
|
|
} else if (w->kind == EL_WIDGET_TEXTAREA) {
|
|
UITextView* tv = (UITextView*)w->obj;
|
|
tv.textContainerInset = insets;
|
|
} else {
|
|
UIView* v = (UIView*)w->obj;
|
|
v.layoutMargins = insets;
|
|
}
|
|
});
|
|
}
|
|
|
|
void el_uikit_widget_set_width(int64_t handle, int width) {
|
|
el_uikit_sync_main(^{
|
|
ElWidget* w = el_widget_get(handle);
|
|
if (!w || w->kind == EL_WIDGET_WINDOW) return;
|
|
UIView* v = (UIView*)w->obj;
|
|
NSLayoutConstraint* c = [v.widthAnchor
|
|
constraintEqualToConstant:(CGFloat)width];
|
|
c.priority = UILayoutPriorityDefaultHigh;
|
|
c.active = YES;
|
|
});
|
|
}
|
|
|
|
void el_uikit_widget_set_height(int64_t handle, int height) {
|
|
el_uikit_sync_main(^{
|
|
ElWidget* w = el_widget_get(handle);
|
|
if (!w || w->kind == EL_WIDGET_WINDOW) return;
|
|
UIView* v = (UIView*)w->obj;
|
|
NSLayoutConstraint* c = [v.heightAnchor
|
|
constraintEqualToConstant:(CGFloat)height];
|
|
c.priority = UILayoutPriorityDefaultHigh;
|
|
c.active = YES;
|
|
});
|
|
}
|
|
|
|
void el_uikit_widget_set_flex(int64_t handle, int flex) {
|
|
el_uikit_sync_main(^{
|
|
ElWidget* w = el_widget_get(handle);
|
|
if (!w || w->kind == EL_WIDGET_WINDOW) return;
|
|
UIView* v = (UIView*)w->obj;
|
|
/* flex > 0 → low hugging priority so the view expands to fill space.
|
|
* flex == 0 → high hugging priority so the view stays at intrinsic size. */
|
|
UILayoutPriority p = (flex > 0)
|
|
? UILayoutPriorityDefaultLow
|
|
: UILayoutPriorityDefaultHigh;
|
|
[v setContentHuggingPriority:p
|
|
forAxis:UILayoutConstraintAxisHorizontal];
|
|
[v setContentHuggingPriority:p
|
|
forAxis:UILayoutConstraintAxisVertical];
|
|
});
|
|
}
|
|
|
|
void el_uikit_widget_set_corner_radius(int64_t handle, int radius) {
|
|
el_uikit_sync_main(^{
|
|
ElWidget* w = el_widget_get(handle);
|
|
if (!w || w->kind == EL_WIDGET_WINDOW) return;
|
|
UIView* v = (UIView*)w->obj;
|
|
v.layer.cornerRadius = (CGFloat)radius;
|
|
v.clipsToBounds = YES;
|
|
});
|
|
}
|
|
|
|
void el_uikit_widget_set_disabled(int64_t handle, int disabled) {
|
|
el_uikit_sync_main(^{
|
|
ElWidget* w = el_widget_get(handle);
|
|
if (!w) return;
|
|
switch (w->kind) {
|
|
case EL_WIDGET_BUTTON:
|
|
((UIButton*)w->obj).enabled = !disabled;
|
|
break;
|
|
case EL_WIDGET_TEXTFIELD:
|
|
((UITextField*)w->obj).enabled = !disabled;
|
|
break;
|
|
case EL_WIDGET_TEXTAREA:
|
|
((UITextView*)w->obj).editable = !disabled;
|
|
break;
|
|
default:
|
|
((UIView*)w->obj).userInteractionEnabled = !disabled;
|
|
break;
|
|
}
|
|
});
|
|
}
|
|
|
|
void el_uikit_widget_set_hidden(int64_t handle, int hidden) {
|
|
el_uikit_sync_main(^{
|
|
ElWidget* w = el_widget_get(handle);
|
|
if (!w || w->kind == EL_WIDGET_WINDOW) return;
|
|
((UIView*)w->obj).hidden = (BOOL)hidden;
|
|
});
|
|
}
|
|
|
|
/* ── Child management ─────────────────────────────────────────────────────── */
|
|
|
|
/*
|
|
* el_uikit_widget_add_child — attach child widget to parent.
|
|
*
|
|
* - Window: child is added to the root UIStackView inside the root VC.
|
|
* - VStack/HStack: child is added as an arranged subview.
|
|
* - ZStack: child is added as a plain subview (caller manages layout).
|
|
* - ScrollView: child is added as a subview and pinned to content layout guide.
|
|
* - Generic view: child is added as a plain subview.
|
|
*/
|
|
void el_uikit_widget_add_child(int64_t parent, int64_t child) {
|
|
el_uikit_sync_main(^{
|
|
ElWidget* pw = el_widget_get(parent);
|
|
ElWidget* cw = el_widget_get(child);
|
|
if (!pw || !cw) return;
|
|
if (cw->kind == EL_WIDGET_WINDOW) return; /* can't add window as subview */
|
|
|
|
UIView* childView = (UIView*)cw->obj;
|
|
|
|
switch (pw->kind) {
|
|
case EL_WIDGET_WINDOW: {
|
|
/* Dig to the root stack view: window → navCtrl → topVC → view → subviews[0] */
|
|
UIWindow* win = (UIWindow*)pw->obj;
|
|
UINavigationController* nav =
|
|
(UINavigationController*)win.rootViewController;
|
|
UIView* rootVCView = nav.topViewController.view;
|
|
UIStackView* stack = nil;
|
|
for (UIView* sub in rootVCView.subviews) {
|
|
if ([sub isKindOfClass:[UIStackView class]]) {
|
|
stack = (UIStackView*)sub;
|
|
break;
|
|
}
|
|
}
|
|
if (stack) {
|
|
[stack addArrangedSubview:childView];
|
|
} else {
|
|
[rootVCView addSubview:childView];
|
|
}
|
|
break;
|
|
}
|
|
case EL_WIDGET_VSTACK:
|
|
case EL_WIDGET_HSTACK: {
|
|
UIStackView* sv = (UIStackView*)pw->obj;
|
|
[sv addArrangedSubview:childView];
|
|
break;
|
|
}
|
|
case EL_WIDGET_SCROLL: {
|
|
UIScrollView* sv = (UIScrollView*)pw->obj;
|
|
[sv addSubview:childView];
|
|
/* Pin child to content layout guide so scroll view knows content size. */
|
|
UILayoutGuide* cg = sv.contentLayoutGuide;
|
|
childView.translatesAutoresizingMaskIntoConstraints = NO;
|
|
[NSLayoutConstraint activateConstraints:@[
|
|
[childView.topAnchor constraintEqualToAnchor:cg.topAnchor],
|
|
[childView.leadingAnchor constraintEqualToAnchor:cg.leadingAnchor],
|
|
[childView.trailingAnchor constraintEqualToAnchor:cg.trailingAnchor],
|
|
[childView.bottomAnchor constraintEqualToAnchor:cg.bottomAnchor],
|
|
/* Width matches scroll view's frame (vertical scroll only). */
|
|
[childView.widthAnchor constraintEqualToAnchor:sv.frameLayoutGuide.widthAnchor],
|
|
]];
|
|
break;
|
|
}
|
|
case EL_WIDGET_ZSTACK:
|
|
default: {
|
|
UIView* pv = (UIView*)pw->obj;
|
|
[pv addSubview:childView];
|
|
break;
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/*
|
|
* el_uikit_widget_remove_child — detach a child widget from its parent.
|
|
*/
|
|
void el_uikit_widget_remove_child(int64_t parent, int64_t child) {
|
|
el_uikit_sync_main(^{
|
|
ElWidget* pw = el_widget_get(parent);
|
|
ElWidget* cw = el_widget_get(child);
|
|
if (!cw || cw->kind == EL_WIDGET_WINDOW) return;
|
|
UIView* childView = (UIView*)cw->obj;
|
|
/* If parent is a stack view, remove from arranged subviews too. */
|
|
if (pw && (pw->kind == EL_WIDGET_VSTACK || pw->kind == EL_WIDGET_HSTACK)) {
|
|
UIStackView* sv = (UIStackView*)pw->obj;
|
|
[sv removeArrangedSubview:childView];
|
|
} else if (pw && pw->kind == EL_WIDGET_WINDOW) {
|
|
/* Find and remove from root stack. */
|
|
UIWindow* win = (UIWindow*)pw->obj;
|
|
UINavigationController* nav =
|
|
(UINavigationController*)win.rootViewController;
|
|
for (UIView* sub in nav.topViewController.view.subviews) {
|
|
if ([sub isKindOfClass:[UIStackView class]]) {
|
|
[(UIStackView*)sub removeArrangedSubview:childView];
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
[childView removeFromSuperview];
|
|
});
|
|
}
|
|
|
|
/* ── Event registration ───────────────────────────────────────────────────── */
|
|
|
|
void el_uikit_widget_on_click(int64_t handle, const char* fn_name) {
|
|
ElWidget* w = el_widget_get(handle);
|
|
if (!w) return;
|
|
free(w->cb_click);
|
|
w->cb_click = (fn_name && *fn_name) ? strdup(fn_name) : NULL;
|
|
}
|
|
|
|
void el_uikit_widget_on_change(int64_t handle, const char* fn_name) {
|
|
ElWidget* w = el_widget_get(handle);
|
|
if (!w) return;
|
|
free(w->cb_change);
|
|
w->cb_change = (fn_name && *fn_name) ? strdup(fn_name) : NULL;
|
|
}
|
|
|
|
void el_uikit_widget_on_submit(int64_t handle, const char* fn_name) {
|
|
/* For text fields, submit = return key → stored in cb_click slot. */
|
|
el_uikit_widget_on_click(handle, fn_name);
|
|
}
|
|
|
|
/* ── Widget destroy ───────────────────────────────────────────────────────── */
|
|
|
|
void el_uikit_widget_destroy(int64_t handle) {
|
|
el_uikit_sync_main(^{
|
|
ElWidget* w = el_widget_get(handle);
|
|
if (!w) return;
|
|
if (w->kind != EL_WIDGET_WINDOW) {
|
|
UIView* v = (UIView*)w->obj;
|
|
/* If it's in a stack view, also remove from arranged subviews. */
|
|
if ([v.superview isKindOfClass:[UIStackView class]]) {
|
|
[(UIStackView*)v.superview removeArrangedSubview:v];
|
|
}
|
|
[v removeFromSuperview];
|
|
}
|
|
[_el_delegates[handle] release];
|
|
_el_delegates[handle] = nil;
|
|
el_widget_free(handle);
|
|
});
|
|
}
|
|
|
|
/* ── Manifest reader ──────────────────────────────────────────────────────── */
|
|
|
|
/*
|
|
* el_uikit_manifest_read — read an app manifest file. Tries the given path
|
|
* first; if not found, looks in the main bundle. Returns a C string with the
|
|
* file contents (heap-allocated; caller must not free — matches AppKit bridge
|
|
* convention of returning static/permanent lifetime strings from manifest).
|
|
* Returns NULL on failure.
|
|
*/
|
|
const char* el_uikit_manifest_read(const char* path) {
|
|
if (!path || !*path) return NULL;
|
|
|
|
NSString* pathStr = [NSString stringWithUTF8String:path];
|
|
NSString* content = [NSString stringWithContentsOfFile:pathStr
|
|
encoding:NSUTF8StringEncoding
|
|
error:nil];
|
|
if (!content) {
|
|
/* Try bundle resource lookup — strip to last component without extension. */
|
|
NSString* baseName = [[pathStr lastPathComponent]
|
|
stringByDeletingPathExtension];
|
|
NSString* ext = [pathStr pathExtension];
|
|
NSString* bundlePath = [[NSBundle mainBundle]
|
|
pathForResource:baseName ofType:(ext.length ? ext : nil)];
|
|
if (bundlePath) {
|
|
content = [NSString stringWithContentsOfFile:bundlePath
|
|
encoding:NSUTF8StringEncoding
|
|
error:nil];
|
|
}
|
|
}
|
|
if (!content) return NULL;
|
|
return strdup([content UTF8String]);
|
|
}
|
|
|
|
/* ── __widget_* C API (called from el_seed.c) ─────────────────────────────── */
|
|
/*
|
|
* These are the symbols that el compiled programs actually call. They forward
|
|
* directly to the el_uikit_* implementations above. el_val_t is int64_t (the
|
|
* same type used by the AppKit bridge).
|
|
*/
|
|
|
|
void __native_init(void) {
|
|
el_uikit_init();
|
|
}
|
|
|
|
void __native_run_loop(void) {
|
|
/* el_main_entry_fn must be set by the caller before this. On iOS the
|
|
* entry function pointer pattern is inverted vs macOS: the el program
|
|
* packages up its "build UI" work into a lambda / function and registers
|
|
* it via el_main_entry_fn, then calls __native_run_loop.
|
|
* el_uikit_run_loop takes that fn pointer and passes it to UIApplicationMain. */
|
|
el_uikit_run_loop(el_main_entry_fn);
|
|
}
|
|
|
|
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_uikit_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_uikit_window_show((int64_t)handle);
|
|
}
|
|
|
|
void __window_set_title(el_val_t handle, el_val_t title) {
|
|
el_uikit_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_uikit_vstack_create((int)spacing);
|
|
}
|
|
|
|
el_val_t __hstack_create(el_val_t spacing) {
|
|
return (el_val_t)el_uikit_hstack_create((int)spacing);
|
|
}
|
|
|
|
el_val_t __zstack_create(void) {
|
|
return (el_val_t)el_uikit_zstack_create();
|
|
}
|
|
|
|
el_val_t __scroll_create(void) {
|
|
return (el_val_t)el_uikit_scroll_create();
|
|
}
|
|
|
|
el_val_t __label_create(el_val_t text) {
|
|
return (el_val_t)el_uikit_label_create((const char*)(uintptr_t)text);
|
|
}
|
|
|
|
el_val_t __button_create(el_val_t label) {
|
|
return (el_val_t)el_uikit_button_create((const char*)(uintptr_t)label);
|
|
}
|
|
|
|
el_val_t __text_field_create(el_val_t placeholder) {
|
|
return (el_val_t)el_uikit_text_field_create(
|
|
(const char*)(uintptr_t)placeholder);
|
|
}
|
|
|
|
el_val_t __text_area_create(el_val_t placeholder) {
|
|
return (el_val_t)el_uikit_text_area_create(
|
|
(const char*)(uintptr_t)placeholder);
|
|
}
|
|
|
|
el_val_t __image_create(el_val_t path_or_name) {
|
|
return (el_val_t)el_uikit_image_create(
|
|
(const char*)(uintptr_t)path_or_name);
|
|
}
|
|
|
|
void __widget_set_text(el_val_t handle, el_val_t text) {
|
|
el_uikit_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_uikit_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_uikit_widget_set_color((int64_t)handle,
|
|
(float)r, (float)g, (float)b, (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_uikit_widget_set_bg_color((int64_t)handle,
|
|
(float)r, (float)g, (float)b, (float)a);
|
|
}
|
|
|
|
void __widget_set_font(el_val_t handle, el_val_t family,
|
|
el_val_t size, el_val_t bold) {
|
|
el_uikit_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_uikit_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_uikit_widget_set_width((int64_t)handle, (int)width);
|
|
}
|
|
|
|
void __widget_set_height(el_val_t handle, el_val_t height) {
|
|
el_uikit_widget_set_height((int64_t)handle, (int)height);
|
|
}
|
|
|
|
void __widget_set_flex(el_val_t handle, el_val_t flex) {
|
|
el_uikit_widget_set_flex((int64_t)handle, (int)flex);
|
|
}
|
|
|
|
void __widget_set_corner_radius(el_val_t handle, el_val_t radius) {
|
|
el_uikit_widget_set_corner_radius((int64_t)handle, (int)radius);
|
|
}
|
|
|
|
void __widget_set_disabled(el_val_t handle, el_val_t disabled) {
|
|
el_uikit_widget_set_disabled((int64_t)handle, (int)disabled);
|
|
}
|
|
|
|
void __widget_set_hidden(el_val_t handle, el_val_t hidden) {
|
|
el_uikit_widget_set_hidden((int64_t)handle, (int)hidden);
|
|
}
|
|
|
|
void __widget_add_child(el_val_t parent, el_val_t child) {
|
|
el_uikit_widget_add_child((int64_t)parent, (int64_t)child);
|
|
}
|
|
|
|
void __widget_remove_child(el_val_t parent, el_val_t child) {
|
|
el_uikit_widget_remove_child((int64_t)parent, (int64_t)child);
|
|
}
|
|
|
|
void __widget_destroy(el_val_t handle) {
|
|
el_uikit_widget_destroy((int64_t)handle);
|
|
}
|
|
|
|
void __widget_on_click(el_val_t handle, el_val_t fn_name) {
|
|
el_uikit_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_uikit_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_uikit_widget_on_submit((int64_t)handle,
|
|
(const char*)(uintptr_t)fn_name);
|
|
}
|
|
|
|
el_val_t __manifest_read(el_val_t path) {
|
|
return (el_val_t)(uintptr_t)el_uikit_manifest_read(
|
|
(const char*)(uintptr_t)path);
|
|
}
|
|
|
|
#endif /* EL_TARGET_IOS */
|