diff --git a/examples/66-uikit-platform.sx b/examples/66-uikit-platform.sx index 17137a0..5c3dd3c 100644 --- a/examples/66-uikit-platform.sx +++ b/examples/66-uikit-platform.sx @@ -5,13 +5,18 @@ // on-screen keyboard so safe_insets.bottom can be observed growing / // shrinking under it. // +// To visualize the safe-area / keyboard inset, the frame draws a red +// bar at the bottom whose height equals `safe_insets.bottom`. The +// platform interpolates `keyboard_height` over the keyboard's own +// animation duration, so the bar slides in lockstep with iOS's +// keyboard. +// // Build + run: // sx build --target ios-sim examples/66-uikit-platform.sx \ // -o /tmp/SxUIKitBoot --bundle /tmp/SxUIKitBoot.app \ // --bundle-id co.swipelab.sxuikit -F ~/Library/Frameworks // xcrun simctl install booted /tmp/SxUIKitBoot.app -// xcrun simctl launch --console booted co.swipelab.sxuikit -// xcrun simctl io booted screenshot /tmp/screen.png +// xcrun simctl launch booted co.swipelab.sxuikit #import "modules/std.sx"; #import "modules/std/uikit.sx"; @@ -22,12 +27,26 @@ #import "modules/ui/events.sx"; #import "modules/platform/uikit.sx"; +GL_SCISSOR_TEST :u32: 0x0C11; +glEnable_ : (u32) -> void = ---; +glDisable_ : (u32) -> void = ---; +glScissor_ : (s32, s32, s32, s32) -> void = ---; + g_color_index : s64 = 0; g_keyboard_up : bool = false; +g_loaded : bool = false; tap_frame :: () { fc := g_uikit_plat.begin_frame(); + if !g_loaded { + // Cache the GL fn-ptrs we use beyond what modules/opengl.sx loads. + glEnable_ = xx ios_gl_proc("glEnable".ptr); + glDisable_ = xx ios_gl_proc("glDisable".ptr); + glScissor_ = xx ios_gl_proc("glScissor".ptr); + g_loaded = true; + } + events := g_uikit_plat.poll_events(); i : s64 = 0; while i < events.len { @@ -51,9 +70,22 @@ tap_frame :: () { r : f32 = if phase == 0 then 0.8 else 0.1; g : f32 = if phase == 1 then 0.8 else 0.1; b : f32 = if phase == 2 then 0.8 else 0.1; + glViewport(0, 0, fc.pixel_w, fc.pixel_h); glClearColor(r, g, b, 1.0); glClear(GL_COLOR_BUFFER_BIT); + + // Bottom bar = the interpolated safe-area bottom inset. + insets := g_uikit_plat.safe_insets(); + bar_h_px : s32 = xx (insets.bottom * fc.dpi_scale); + if bar_h_px > 0 { + glEnable_(GL_SCISSOR_TEST); + glScissor_(0, 0, fc.pixel_w, bar_h_px); + glClearColor(0.95, 0.25, 0.25, 1.0); + glClear(GL_COLOR_BUFFER_BIT); + glDisable_(GL_SCISSOR_TEST); + } + g_uikit_plat.end_frame(); } diff --git a/library/modules/platform/uikit.sx b/library/modules/platform/uikit.sx index d89a366..fff5958 100644 --- a/library/modules/platform/uikit.sx +++ b/library/modules/platform/uikit.sx @@ -90,6 +90,17 @@ UIKitPlatform :: struct { keyboard_visible: bool = false; keyboard_height: f32 = 0.0; + // Keyboard height SNAPS to its target value when the observer fires. + // It does NOT interpolate in lockstep with iOS's keyboard animation. + // Reason: with OpenGL ES + CAEAGLLayer, our renderbuffer is baked at + // `presentRenderbuffer` time, while UIKit's keyboard view is composited + // by CoreAnimation at vsync. We can't make the compositor interpolate + // the renderbuffer's contents in lockstep with the keyboard's frame. + // True lockstep requires a Metal renderer (CAMetalLayer + + // `present(at: targetTimestamp)` keeps the pipeline at 1 frame) plus + // curve-accurate prediction. Tracked as the Metal port in + // current/CHECKPOINT.md. + saved_title: [*]u8 = null; } @@ -316,6 +327,8 @@ uikit_keyboard_will_change_frame :: (self: *void, _cmd: *void, notification: *vo if h < 0.0 { h = 0.0; } if h > sh { h = sh; } + // SNAP to target. See comment on UIKitPlatform.keyboard_height for why + // lockstep interpolation is deferred until the Metal renderer. plat.keyboard_height = xx h; plat.keyboard_visible = h > 0.5; } @@ -587,11 +600,10 @@ uikit_gl_view_tick :: (self: *void, _cmd: *void, link: *void) callconv(.c) { 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; + dur_d : f64 = msg_d(link, sel_dur); + plat.delta_time = xx dur_d; fn := plat.frame_closure; fn();