Files
sx/library/modules/platform/uikit.sx
agra 4e27a7e6c9 platform: UIKitPlatform end-to-end — chess game runs on iOS sim
What works on iOS sim now:
- pure-UIKit boot via UIApplicationMain (no SDL3 on iOS)
- SxGLView (CAEAGLLayer) + EAGLContext(GLES3) + CADisplayLink
- GLES3 shader path in modules/ui/renderer.sx (was wasm-only; now
  wasm-OR-ios)
- UITouch -> ui.Event translation (mouse_down/moved/up) on touchesBegan/
  Moved/Ended/Cancelled. Verified by tapping the chess board: the
  expected pawn highlights and its legal moves show as green dots.
- chdir to NSBundle.mainBundle.resourcePath inside UIKitPlatform.init so
  the game's relative fopen("assets/...") calls resolve.

Required restructuring to fix four problems discovered along the way:

1. GL context + load_gl must happen BEFORE UIApplicationMain so the
   game's pipeline.init (which compiles shaders) doesn't crash on null
   function pointers. Pulled EAGLContext creation + load_gl out of
   didFinishLaunching: into UIKitPlatform.init via uikit_create_gl_context.

2. UIScreen.nativeScale returns CGFloat (=double on 64-bit Apple).
   Reading it through a `(*void, *void) -> f32` msgSend signature
   clobbers the value to 0 — the upper 32 bits of d0 land where the f32
   reads from. Replaced msg_f with msg_d returning f64 (and added
   msg_odbl for setContentScaleFactor: which takes CGFloat).

3. `xx <f64-call-result>` directly assigned to an f32 field through a
   sema path lowers as `sitofp` (integer→float) on the double — LLVM
   verification rejects it. Workaround: hoist into an `f64` local first.

4. The renderer was selecting the GLSL 330 core shader on every non-wasm
   target, including iOS GLES3 where it silently fails to compile and
   no quads render. Added OS == .ios to the GLES branch.

Game changes:
- main.sx: g_plat is now a boxed `Platform` (not concrete *SdlPlatform).
  Backend chosen per-target via `inline if OS == .ios { ... }`. The
  ESC-to-stop handling is OS-guarded (mobile apps don't quit on key
  press, and SDL_Keycode references would force-link SDL on iOS).
- build.sx: iOS no longer adds SDL3; it adds UIKit + OpenGLES +
  QuartzCore instead.
- delta_time and viewport dims are now mirrored to free globals so the
  dock subsystem (`g_dock_delta_time = @g_delta_time`) and build_ui
  layout decisions don't need a pointer through the boxed protocol.

Other:
- Added `stop()` to the Platform protocol (no-op on UIKitPlatform).
- examples/66-uikit-platform.sx updated: taps advance the clear color
  (red → green → blue) — smoke test for the touch IMP wiring.
- shutdown() on UIKitPlatform is a no-op (mobile apps don't tear down).

Outstanding for next session:
- The Dynamic Island notch overlaps the top of the board because we
  haven't read UIView.safeAreaInsets yet (CGRect/UIEdgeInsets struct
  returns require a different msgSend ABI than we currently express).
- Keyboard observer (UIKeyboardWillChangeFrameNotification + animation
  duration) — the load-bearing iOS feature.
- Real-device codesigning workflow for the new build.

Two more sx compiler bugs to file out of this work:
- xx(f64 call result) → f32 emits sitofp (problem #3 above).
- Inline `#import` inside `inline if` fails to parse (we worked around
  by importing both backends unconditionally; the unused-backend's
  Obj-C calls are gated by `inline if OS == .ios`).
2026-05-17 16:52:03 +03:00

566 lines
23 KiB
Plaintext

// Pure UIKit + CAEAGLLayer + CADisplayLink backend for iOS.
//
// Linking is per-target via the game's build.sx (`opts.add_framework("UIKit")`
// + `opts.add_framework("OpenGLES")` + `opts.add_framework("QuartzCore")` on
// `.ios`). The file compiles cleanly on every target — the UIKit-touching
// bodies live behind `inline if OS == .ios` guards, so non-iOS builds never
// reach the unresolved Obj-C symbols.
#import "modules/std.sx";
#import "modules/std/objc.sx";
#import "modules/compiler.sx";
#import "modules/opengl.sx";
#import "modules/ui/types.sx";
#import "modules/ui/events.sx";
#import "modules/platform/types.sx";
#import "modules/platform/api.sx";
UIApplicationMain :: (argc: s32, argv: *void, principal_class: *void, delegate_class: *void) -> s32 #foreign;
dlsym :: (handle: *void, name: [*]u8) -> *void #foreign;
chdir :: (path: [*]u8) -> s32 #foreign;
// kEAGLRenderingAPIOpenGLES3 = 3
EAGL_API_GLES3 :: 3;
// CGFloat is a `double` on 64-bit Apple platforms; CGPoint = {x, y} fits in
// 16 bytes and returns via the FP-register path on arm64.
CGPoint :: struct { x: f64; y: f64; }
// GLenum constants for renderbuffer/framebuffer setup that aren't in opengl.sx's
// loader path (they live on the framework's symbol table directly).
GL_RENDERBUFFER :u32: 0x8D41;
GL_FRAMEBUFFER :u32: 0x8D40;
GL_COLOR_ATTACHMENT0 :u32: 0x8CE0;
GL_FRAMEBUFFER_COMPLETE :u32: 0x8CD5;
g_uikit_plat : *UIKitPlatform = null;
UIKitPlatform :: struct {
window: *void = null; // UIWindow*
root_vc: *void = null; // UIViewController*
gl_view: *void = null; // SxGLView*
gl_layer: *void = null; // CAEAGLLayer* (= gl_view.layer)
gl_ctx: *void = null; // EAGLContext*
display_link: *void = null;
color_renderbuffer: u32 = 0;
framebuffer: u32 = 0;
gl_initialized: bool = false;
viewport_w: f32 = 0.0;
viewport_h: f32 = 0.0;
pixel_w: s32 = 0;
pixel_h: s32 = 0;
dpi_scale: f32 = 1.0;
delta_time: f32 = 0.016;
frame_closure: Closure() = ---;
has_frame_closure: bool = false;
events: List(Event) = .{};
safe_top: f32 = 0.0;
safe_left: f32 = 0.0;
safe_bottom: f32 = 0.0;
safe_right: f32 = 0.0;
keyboard_visible: bool = false;
keyboard_height: f32 = 0.0;
saved_title: [*]u8 = null;
}
impl Platform for UIKitPlatform {
init :: (self: *UIKitPlatform, title: [:0]u8, w: s32, h: s32) -> bool {
self.dpi_scale = 1.0;
self.delta_time = 0.016;
self.has_frame_closure = false;
self.gl_initialized = false;
self.keyboard_visible = false;
self.keyboard_height = 0.0;
self.saved_title = title.ptr;
g_uikit_plat = self;
// iOS apps start with CWD=/. chdir to the bundle's resourcePath so the
// game's relative `fopen("assets/...")` calls find their data — must
// happen BEFORE any code that loads fonts/textures from disk.
inline if OS == .ios {
uikit_chdir_to_bundle();
uikit_register_classes();
uikit_create_gl_context(self);
}
true;
}
run_frame_loop :: (self: *UIKitPlatform, frame_fn: Closure()) {
self.frame_closure = frame_fn;
self.has_frame_closure = true;
g_uikit_plat = self;
inline if OS == .ios {
UIApplicationMain(0, xx 0, xx 0, ns_string("SxAppDelegate".ptr));
}
}
poll_events :: (self: *UIKitPlatform) -> []Event {
result : []Event = ---;
result.ptr = self.events.items;
result.len = self.events.len;
self.events.len = 0;
result;
}
begin_frame :: (self: *UIKitPlatform) -> FrameContext {
FrameContext.{
viewport_w = self.viewport_w,
viewport_h = self.viewport_h,
pixel_w = self.pixel_w,
pixel_h = self.pixel_h,
dpi_scale = self.dpi_scale,
delta_time = self.delta_time,
};
}
end_frame :: (self: *UIKitPlatform) {
inline if OS == .ios {
uikit_present_renderbuffer(self);
}
}
safe_insets :: (self: *UIKitPlatform) -> EdgeInsets {
bottom := self.safe_bottom;
if self.keyboard_visible {
if self.keyboard_height > bottom { bottom = self.keyboard_height; }
}
EdgeInsets.{
top = self.safe_top,
left = self.safe_left,
bottom = bottom,
right = self.safe_right,
};
}
keyboard :: (self: *UIKitPlatform) -> KeyboardState {
KeyboardState.{
visible = self.keyboard_visible,
height = self.keyboard_height,
};
}
show_keyboard :: (self: *UIKitPlatform) { }
hide_keyboard :: (self: *UIKitPlatform) { }
stop :: (self: *UIKitPlatform) { }
shutdown :: (self: *UIKitPlatform) { }
}
// dlsym(RTLD_DEFAULT, name) — Apple platforms. RTLD_DEFAULT is (void*)-2.
ios_gl_proc :: (name: [*]u8) -> *void {
rtld_default : *void = xx (0 - 2);
dlsym(rtld_default, name);
}
// Read a `extern NSString * const k...` global from the loaded image. The
// extern variable holds a pointer to the NSString instance — dlsym returns
// the address of that variable, which we dereference.
uikit_extern_nsstring :: (name: [*]u8) -> *void {
rtld_default : *void = xx (0 - 2);
p := dlsym(rtld_default, name);
if p == null { return null; }
pp : **void = xx p;
pp.*;
}
// ───────────────────────────────────────────────────────────────────────────
// iOS-only helpers — only reachable from `inline if OS == .ios` call sites,
// so non-iOS builds never reference the unresolved UIKit symbols below.
// ───────────────────────────────────────────────────────────────────────────
uikit_chdir_to_bundle :: () {
inline if OS != .ios { return; }
NSBundle := objc_getClass("NSBundle".ptr);
sel_main_bundle := sel_registerName("mainBundle".ptr);
sel_resource_path := sel_registerName("resourcePath".ptr);
msg_o : (*void, *void) -> *void = xx objc_msgSend;
bundle := msg_o(NSBundle, sel_main_bundle);
rsrc := msg_o(bundle, sel_resource_path);
if rsrc == null { return; }
chdir(c_string(rsrc));
}
uikit_register_classes :: () {
inline if OS == .ios {
UIResponder := objc_getClass("UIResponder".ptr);
SxAppDelegate := objc_allocateClassPair(UIResponder, "SxAppDelegate".ptr, 0);
class_addMethod(SxAppDelegate,
sel_registerName("application:didFinishLaunchingWithOptions:".ptr),
xx uikit_did_finish_launching, "c@:@@".ptr);
class_addMethod(SxAppDelegate,
sel_registerName("window".ptr),
xx uikit_window_getter, "@@:".ptr);
class_addMethod(SxAppDelegate,
sel_registerName("setWindow:".ptr),
xx uikit_window_setter, "v@:@".ptr);
objc_registerClassPair(SxAppDelegate);
uikit_register_gl_view_class();
}
}
uikit_create_gl_context :: (plat: *UIKitPlatform) {
inline if OS != .ios { return; }
EAGLContext := objc_getClass("EAGLContext".ptr);
UIScreen := objc_getClass("UIScreen".ptr);
sel_alloc := sel_registerName("alloc".ptr);
sel_init_with_api := sel_registerName("initWithAPI:".ptr);
sel_set_current_ctx := sel_registerName("setCurrentContext:".ptr);
sel_main_screen := sel_registerName("mainScreen".ptr);
sel_native_scale := sel_registerName("nativeScale".ptr);
msg_o : (*void, *void) -> *void = xx objc_msgSend;
msg_oo : (*void, *void, *void) -> void = xx objc_msgSend;
msg_oi32 : (*void, *void, s32) -> *void = xx objc_msgSend;
msg_d : (*void, *void) -> f64 = xx objc_msgSend;
// Read the screen scale up-front so callers can size font caches and
// textures with the right DPI before the window even exists.
screen := msg_o(UIScreen, sel_main_screen);
scale_d : f64 = msg_d(screen, sel_native_scale);
plat.dpi_scale = xx scale_d;
ctx_raw := msg_o(EAGLContext, sel_alloc);
plat.gl_ctx = msg_oi32(ctx_raw, sel_init_with_api, EAGL_API_GLES3);
msg_oo(EAGLContext, sel_set_current_ctx, plat.gl_ctx);
load_gl(@ios_gl_proc);
}
uikit_window_getter :: (self: *void, _cmd: *void) -> *void callconv(.c) {
if g_uikit_plat == null { return xx 0; }
g_uikit_plat.window;
}
uikit_window_setter :: (self: *void, _cmd: *void, w: *void) callconv(.c) {
if g_uikit_plat == null { return; }
g_uikit_plat.window = w;
}
uikit_did_finish_launching :: (self: *void, _cmd: *void, app: *void, opts: *void) -> u8 callconv(.c) {
result : u8 = 1;
inline if OS == .ios {
result = uikit_did_finish_launching_ios(app);
}
result;
}
uikit_did_finish_launching_ios :: (app: *void) -> u8 {
if g_uikit_plat == null {
NSLog(ns_string("[sx] no platform\n".ptr));
return 0;
}
plat := g_uikit_plat;
UIWindow := objc_getClass("UIWindow".ptr);
UIViewController := objc_getClass("UIViewController".ptr);
SxGLView := objc_getClass("SxGLView".ptr);
EAGLContext := objc_getClass("EAGLContext".ptr);
CADisplayLink := objc_getClass("CADisplayLink".ptr);
NSRunLoop := objc_getClass("NSRunLoop".ptr);
sel_alloc := sel_registerName("alloc".ptr);
sel_init := sel_registerName("init".ptr);
sel_init_with_scene := sel_registerName("initWithWindowScene:".ptr);
sel_init_with_frame := sel_registerName("initWithFrame:".ptr);
sel_view := sel_registerName("view".ptr);
sel_set_root_vc := sel_registerName("setRootViewController:".ptr);
sel_make_key_visible := sel_registerName("makeKeyAndVisible".ptr);
sel_connected_scenes := sel_registerName("connectedScenes".ptr);
sel_any_object := sel_registerName("anyObject".ptr);
sel_add_subview := sel_registerName("addSubview:".ptr);
sel_set_frame := sel_registerName("setFrame:".ptr);
sel_bounds := sel_registerName("bounds".ptr);
sel_set_autoresizing := sel_registerName("setAutoresizingMask:".ptr);
sel_init_with_api := sel_registerName("initWithAPI:".ptr);
sel_set_current_ctx := sel_registerName("setCurrentContext:".ptr);
sel_layer := sel_registerName("layer".ptr);
sel_set_content_scale := sel_registerName("setContentScaleFactor:".ptr);
sel_screen := sel_registerName("screen".ptr);
sel_native_scale := sel_registerName("nativeScale".ptr);
sel_link_with_target := sel_registerName("displayLinkWithTarget:selector:".ptr);
sel_add_to_runloop := sel_registerName("addToRunLoop:forMode:".ptr);
sel_current_runloop := sel_registerName("currentRunLoop".ptr);
sel_tick := sel_registerName("sxTick:".ptr);
sel_safe_insets := sel_registerName("safeAreaInsets".ptr);
msg_o : (*void, *void) -> *void = xx objc_msgSend;
msg_v : (*void, *void) -> void = xx objc_msgSend;
msg_oo : (*void, *void, *void) -> void = xx objc_msgSend;
msg_ooo : (*void, *void, *void) -> *void = xx objc_msgSend;
msg_oso : (*void, *void, *void, *void) -> *void = xx objc_msgSend;
msg_oi32 : (*void, *void, s32) -> *void = xx objc_msgSend;
msg_oou64 : (*void, *void, u64) -> void = xx objc_msgSend;
// CGFloat-returning msgSend. CGFloat is `double` on 64-bit Apple — reading
// it as f32 reads the low 32 bits of `d0` which isn't a valid float
// representation of the underlying double, so the value comes back as 0.
msg_d : (*void, *void) -> f64 = xx objc_msgSend;
msg_odbl : (*void, *void, f64) -> void = xx objc_msgSend;
scenes := msg_o(app, sel_connected_scenes);
scene := msg_o(scenes, sel_any_object);
if scene == xx 0 {
NSLog(ns_string("[sx] no scene\n".ptr));
return 0;
}
win_raw := msg_o(UIWindow, sel_alloc);
plat.window = msg_ooo(win_raw, sel_init_with_scene, scene);
vc_raw := msg_o(UIViewController, sel_alloc);
plat.root_vc = msg_o(vc_raw, sel_init);
// Allocate SxGLView and install it as the VC's view, so the standard
// ViewController layout pipeline sizes the GL view to the window. Setting
// it BEFORE setRootViewController avoids the VC lazy-loading a default
// view first.
glv_raw := msg_o(SxGLView, sel_alloc);
plat.gl_view = msg_o(glv_raw, sel_init);
sel_set_view := sel_registerName("setView:".ptr);
msg_oo(plat.root_vc, sel_set_view, plat.gl_view);
msg_oo(plat.window, sel_set_root_vc, plat.root_vc);
plat.gl_layer = msg_o(plat.gl_view, sel_layer);
// Mark the layer opaque (no compositor blend) + set the drawable properties
// required by EAGLContext.renderbufferStorage:fromDrawable: (color format,
// non-retained backing). Without this dict the renderbuffer allocation
// silently fails and the framebuffer reports INCOMPLETE.
sel_set_opaque := sel_registerName("setOpaque:".ptr);
msg_obool : (*void, *void, u8) -> void = xx objc_msgSend;
msg_obool(plat.gl_layer, sel_set_opaque, 1);
NSMutableDictionary := objc_getClass("NSMutableDictionary".ptr);
NSNumber := objc_getClass("NSNumber".ptr);
sel_dictionary := sel_registerName("dictionary".ptr);
sel_set_obj_for_key := sel_registerName("setObject:forKey:".ptr);
sel_number_bool := sel_registerName("numberWithBool:".ptr);
sel_set_drawable := sel_registerName("setDrawableProperties:".ptr);
msg_oio : (*void, *void, u8) -> *void = xx objc_msgSend;
ns_no := msg_oio(NSNumber, sel_number_bool, 0);
// The EAGL dict keys/values must be the framework-provided NSString
// constants (pointer identity is checked) — dlsym them from OpenGLES.
retained_key := uikit_extern_nsstring("kEAGLDrawablePropertyRetainedBacking".ptr);
colorformat_key := uikit_extern_nsstring("kEAGLDrawablePropertyColorFormat".ptr);
rgba8_value := uikit_extern_nsstring("kEAGLColorFormatRGBA8".ptr);
dict := msg_o(NSMutableDictionary, sel_dictionary);
msg_o3 : (*void, *void, *void, *void) -> void = xx objc_msgSend;
msg_o3(dict, sel_set_obj_for_key, ns_no, retained_key);
msg_o3(dict, sel_set_obj_for_key, rgba8_value, colorformat_key);
msg_oo(plat.gl_layer, sel_set_drawable, dict);
// EAGLContext + load_gl were already done in uikit_create_gl_context()
// back when the game's main called plat.init() — so shaders/textures
// built before the window exists already work.
// Match the layer's drawable scale to the screen's native scale so we get
// pixel-accurate rendering on retina displays. CGFloat is `double` on
// 64-bit Apple platforms; reading as f32 would clobber the value.
screen := msg_o(plat.window, sel_screen);
scale := msg_d(screen, sel_native_scale);
plat.dpi_scale = xx scale;
msg_odbl(plat.gl_view, sel_set_content_scale, scale);
// Renderbuffer is allocated lazily in -[SxGLView layoutSubviews] once
// the layer has its real on-screen bounds. makeKeyAndVisible triggers
// a layout pass; layoutSubviews calls uikit_setup_renderbuffer.
msg_v(plat.window, sel_make_key_visible);
// Safe insets stay zero for now — struct-return ABI for `safeAreaInsets`
// (UIEdgeInsets = 4 CGFloats) isn't expressible without dedicated
// msgSend_stret plumbing. Revisit when keyboard sync needs it.
plat.safe_top = 0.0;
plat.safe_left = 0.0;
plat.safe_bottom = 0.0;
plat.safe_right = 0.0;
// CADisplayLink: vsync-driven tick into our SxGLView.
plat.display_link = msg_oso(CADisplayLink, sel_link_with_target, plat.gl_view, sel_tick);
runloop := msg_o(NSRunLoop, sel_current_runloop);
mode := ns_string("kCFRunLoopDefaultMode".ptr);
msg_oso(plat.display_link, sel_add_to_runloop, runloop, mode);
NSLog(ns_string("[sx] UIKitPlatform booted\n".ptr));
1;
}
// Allocate the color renderbuffer + framebuffer and bind them. The renderbuffer
// gets its pixel storage from the CAEAGLLayer via
// `[ctx renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer]`, so the layer
// must already be in the view hierarchy at the right size when this runs.
uikit_setup_renderbuffer :: (plat: *UIKitPlatform) {
inline if OS != .ios { return; }
glGenFramebuffers(1, @plat.framebuffer);
glGenRenderbuffers(1, @plat.color_renderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, plat.color_renderbuffer);
sel_renderbuffer_storage := sel_registerName("renderbufferStorage:fromDrawable:".ptr);
msg_o_u32_o : (*void, *void, u32, *void) -> u8 = xx objc_msgSend;
msg_o_u32_o(plat.gl_ctx, sel_renderbuffer_storage, GL_RENDERBUFFER, plat.gl_layer);
glBindFramebuffer(GL_FRAMEBUFFER, plat.framebuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, plat.color_renderbuffer);
// Query the actual pixel dimensions from the renderbuffer.
pw : s32 = 0;
ph : s32 = 0;
GL_RENDERBUFFER_WIDTH :u32: 0x8D42;
GL_RENDERBUFFER_HEIGHT :u32: 0x8D43;
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, @pw);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, @ph);
plat.pixel_w = pw;
plat.pixel_h = ph;
plat.viewport_w = xx pw;
plat.viewport_h = xx ph;
if plat.dpi_scale > 0.0 {
plat.viewport_w = plat.viewport_w / plat.dpi_scale;
plat.viewport_h = plat.viewport_h / plat.dpi_scale;
}
glViewport(0, 0, pw, ph);
status := glCheckFramebufferStatus(GL_FRAMEBUFFER);
if status != GL_FRAMEBUFFER_COMPLETE {
NSLog(ns_string("[sx] framebuffer incomplete after renderbuffer setup\n".ptr));
}
}
uikit_present_renderbuffer :: (self: *UIKitPlatform) {
inline if OS != .ios { return; }
glBindRenderbuffer(GL_RENDERBUFFER, self.color_renderbuffer);
sel_present := sel_registerName("presentRenderbuffer:".ptr);
msg_ou : (*void, *void, u32) -> u8 = xx objc_msgSend;
msg_ou(self.gl_ctx, sel_present, GL_RENDERBUFFER);
}
// ── SxGLView class ─────────────────────────────────────────────────────────
// UIView subclass overriding `+layerClass` to return [CAEAGLLayer class].
// Instance method `sxTick:` is what CADisplayLink calls.
uikit_gl_view_layer_class :: (cls: *void, _cmd: *void) -> *void callconv(.c) {
objc_getClass("CAEAGLLayer".ptr);
}
uikit_gl_view_tick :: (self: *void, _cmd: *void, link: *void) callconv(.c) {
if g_uikit_plat == null { return; }
plat := g_uikit_plat;
if !plat.has_frame_closure { return; }
if !plat.gl_initialized { return; }
// Pull this frame's duration from the display link.
sel_dur := sel_registerName("duration".ptr);
msg_d : (*void, *void) -> f64 = xx objc_msgSend;
d := msg_d(link, sel_dur);
plat.delta_time = xx d;
fn := plat.frame_closure;
fn();
}
uikit_gl_view_layout :: (self: *void, _cmd: *void) callconv(.c) {
// Call super first via objc_msgSendSuper would be cleaner, but UIView's
// default layoutSubviews is a no-op anyway.
if g_uikit_plat == null { return; }
plat := g_uikit_plat;
if plat.gl_initialized { return; }
uikit_setup_renderbuffer(plat);
plat.gl_initialized = true;
}
// Touch IMPs — UIKit fires touchesBegan/Moved/Ended/Cancelled with an
// NSSet<UITouch *> + UIEvent. We take the first touch (single-touch model
// matching the chess game's drag-and-tap UX) and push the resulting
// Event into the platform's queue for the next poll_events drain.
uikit_touch_location :: (touch: *void, view: *void) -> Point {
sel_location := sel_registerName("locationInView:".ptr);
msg_pt : (*void, *void, *void) -> CGPoint = xx objc_msgSend;
p := msg_pt(touch, sel_location, view);
Point.{ x = xx p.x, y = xx p.y };
}
uikit_first_touch :: (touches: *void) -> *void {
sel_any := sel_registerName("anyObject".ptr);
msg_o : (*void, *void) -> *void = xx objc_msgSend;
msg_o(touches, sel_any);
}
uikit_gl_view_touches_began :: (self: *void, _cmd: *void, touches: *void, event: *void) callconv(.c) {
if g_uikit_plat == null { return; }
touch := uikit_first_touch(touches);
if touch == null { return; }
pos := uikit_touch_location(touch, self);
g_uikit_plat.events.append(.mouse_down(.{ position = pos, button = .left }));
}
uikit_gl_view_touches_moved :: (self: *void, _cmd: *void, touches: *void, event: *void) callconv(.c) {
if g_uikit_plat == null { return; }
touch := uikit_first_touch(touches);
if touch == null { return; }
pos := uikit_touch_location(touch, self);
g_uikit_plat.events.append(.mouse_moved(.{ position = pos, delta = Point.zero() }));
}
uikit_gl_view_touches_ended :: (self: *void, _cmd: *void, touches: *void, event: *void) callconv(.c) {
if g_uikit_plat == null { return; }
touch := uikit_first_touch(touches);
if touch == null { return; }
pos := uikit_touch_location(touch, self);
g_uikit_plat.events.append(.mouse_up(.{ position = pos, button = .left }));
}
uikit_register_gl_view_class :: () {
inline if OS == .ios {
UIView := objc_getClass("UIView".ptr);
SxGLView := objc_allocateClassPair(UIView, "SxGLView".ptr, 0);
// +layerClass is a CLASS method — registered on the metaclass.
metaclass := object_getClass(SxGLView);
class_addMethod(metaclass,
sel_registerName("layerClass".ptr),
xx uikit_gl_view_layer_class, "#@:".ptr);
// -sxTick: is the CADisplayLink callback. -layoutSubviews allocates
// the renderbuffer when the layer first gets non-zero bounds.
class_addMethod(SxGLView,
sel_registerName("sxTick:".ptr),
xx uikit_gl_view_tick, "v@:@".ptr);
class_addMethod(SxGLView,
sel_registerName("layoutSubviews".ptr),
xx uikit_gl_view_layout, "v@:".ptr);
// Touch dispatch.
class_addMethod(SxGLView,
sel_registerName("touchesBegan:withEvent:".ptr),
xx uikit_gl_view_touches_began, "v@:@@".ptr);
class_addMethod(SxGLView,
sel_registerName("touchesMoved:withEvent:".ptr),
xx uikit_gl_view_touches_moved, "v@:@@".ptr);
class_addMethod(SxGLView,
sel_registerName("touchesEnded:withEvent:".ptr),
xx uikit_gl_view_touches_ended, "v@:@@".ptr);
class_addMethod(SxGLView,
sel_registerName("touchesCancelled:withEvent:".ptr),
xx uikit_gl_view_touches_ended, "v@:@@".ptr);
objc_registerClassPair(SxGLView);
}
}