#![allow(unsafe_code)]
use std::cell::RefCell;
use std::ffi::{CString, c_void};
use crate::geometry::{PhysicalSize, ScaleFactor};
use crate::platform::ControlFlow;
use crate::platform::error::PlatformError;
use crate::platform::event::{Event, WindowId};
use crate::platform::window::{Window, WindowAttributes};
use crate::render::{Pixmap, Surface};
type Id = *mut c_void;
type Sel = *mut c_void;
type Class = *mut c_void;
#[repr(C)]
#[derive(Clone, Copy)]
struct CgPoint {
x: f64,
y: f64,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct CgSize {
width: f64,
height: f64,
}
#[repr(C)]
#[derive(Clone, Copy)]
struct CgRect {
origin: CgPoint,
size: CgSize,
}
#[link(name = "objc", kind = "dylib")]
unsafe extern "C" {
fn objc_getClass(name: *const i8) -> Class;
fn sel_registerName(name: *const i8) -> Sel;
fn objc_msgSend();
fn objc_allocateClassPair(superclass: Class, name: *const i8, extra: usize) -> Class;
fn objc_registerClassPair(cls: Class);
fn class_addMethod(cls: Class, name: Sel, imp: *const c_void, types: *const i8) -> bool;
}
#[link(name = "UIKit", kind = "framework")]
unsafe extern "C" {
fn UIApplicationMain(
argc: i32,
argv: *mut *mut i8,
principal_class_name: Id,
delegate_class_name: Id,
) -> i32;
fn UIGraphicsGetCurrentContext() -> *mut c_void;
}
#[link(name = "CoreGraphics", kind = "framework")]
unsafe extern "C" {
fn CGColorSpaceCreateDeviceRGB() -> *mut c_void;
fn CGColorSpaceRelease(space: *mut c_void);
fn CGDataProviderCreateWithData(
info: *mut c_void,
data: *const c_void,
size: usize,
release: *const c_void,
) -> *mut c_void;
fn CGDataProviderRelease(provider: *mut c_void);
#[allow(clippy::too_many_arguments)]
fn CGImageCreate(
width: usize,
height: usize,
bits_per_component: usize,
bits_per_pixel: usize,
bytes_per_row: usize,
space: *mut c_void,
bitmap_info: u32,
provider: *mut c_void,
decode: *const f64,
should_interpolate: bool,
intent: i32,
) -> *mut c_void;
fn CGImageRelease(image: *mut c_void);
fn CGContextDrawImage(ctx: *mut c_void, rect: CgRect, image: *mut c_void);
fn CGContextSaveGState(ctx: *mut c_void);
fn CGContextRestoreGState(ctx: *mut c_void);
fn CGContextTranslateCTM(ctx: *mut c_void, tx: f64, ty: f64);
fn CGContextScaleCTM(ctx: *mut c_void, sx: f64, sy: f64);
}
const CG_BITMAP_RGBA8: u32 = 1 | (4 << 12);
fn class(name: &str) -> Class {
let c = CString::new(name).unwrap();
unsafe { objc_getClass(c.as_ptr()) }
}
fn sel(name: &str) -> Sel {
let c = CString::new(name).unwrap();
unsafe { sel_registerName(c.as_ptr()) }
}
unsafe fn msg_id(obj: Id, s: Sel) -> Id {
let f: unsafe extern "C" fn(Id, Sel) -> Id =
unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
unsafe { f(obj, s) }
}
unsafe fn msg_void(obj: Id, s: Sel) {
let f: unsafe extern "C" fn(Id, Sel) =
unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
unsafe { f(obj, s) }
}
unsafe fn msg_void_id(obj: Id, s: Sel, a: Id) {
let f: unsafe extern "C" fn(Id, Sel, Id) =
unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
unsafe { f(obj, s, a) }
}
unsafe fn msg_void_bool(obj: Id, s: Sel, a: bool) {
let f: unsafe extern "C" fn(Id, Sel, bool) =
unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
unsafe { f(obj, s, a) }
}
unsafe fn msg_rect(obj: Id, s: Sel) -> CgRect {
let f: unsafe extern "C" fn(Id, Sel) -> CgRect =
unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
unsafe { f(obj, s) }
}
unsafe fn msg_alloc_init_frame(cls: Class, frame: CgRect) -> Id {
unsafe {
let alloc = msg_id(cls, sel("alloc"));
let f: unsafe extern "C" fn(Id, Sel, CgRect) -> Id =
std::mem::transmute(objc_msgSend as *const c_void);
f(alloc, sel("initWithFrame:"), frame)
}
}
fn nsstring(s: &str) -> Id {
let c = CString::new(s).unwrap();
unsafe {
let f: unsafe extern "C" fn(Id, Sel, *const i8) -> Id =
std::mem::transmute(objc_msgSend as *const c_void);
f(class("NSString"), sel("stringWithUTF8String:"), c.as_ptr())
}
}
type DynHandler = *mut dyn FnMut(Event, &dyn Window) -> ControlFlow;
#[derive(Default)]
struct IosCtx {
fb: Vec<u8>, w: usize,
h: usize,
view: Id,
window: Id,
size: PhysicalSize,
handler: *mut DynHandler,
}
thread_local! {
static CTX: RefCell<IosCtx> = RefCell::new(IosCtx::default());
}
extern "C" fn draw_rect(_this: Id, _cmd: Sel, _rect: CgRect) {
CTX.with(|c| {
let c = c.borrow();
if c.fb.is_empty() {
return;
}
unsafe {
let cg = UIGraphicsGetCurrentContext();
if cg.is_null() {
return;
}
let space = CGColorSpaceCreateDeviceRGB();
let provider = CGDataProviderCreateWithData(
std::ptr::null_mut(),
c.fb.as_ptr() as *const c_void,
c.fb.len(),
std::ptr::null(),
);
let image = CGImageCreate(
c.w,
c.h,
8,
32,
c.w * 4,
space,
CG_BITMAP_RGBA8,
provider,
std::ptr::null(),
false,
0,
);
let rect = CgRect {
origin: CgPoint { x: 0.0, y: 0.0 },
size: CgSize {
width: c.w as f64,
height: c.h as f64,
},
};
CGContextSaveGState(cg);
CGContextTranslateCTM(cg, 0.0, c.h as f64);
CGContextScaleCTM(cg, 1.0, -1.0);
CGContextDrawImage(cg, rect, image);
CGContextRestoreGState(cg);
CGImageRelease(image);
CGDataProviderRelease(provider);
CGColorSpaceRelease(space);
}
});
}
extern "C" fn did_finish_launching(_this: Id, _cmd: Sel, _app: Id, _opts: Id) -> bool {
unsafe {
let bounds = msg_rect(msg_id(class("UIScreen"), sel("mainScreen")), sel("bounds"));
let window = msg_alloc_init_frame(class("UIWindow"), bounds);
let view = msg_alloc_init_frame(register_view_class(), bounds);
let vc = msg_id(msg_id(class("UIViewController"), sel("alloc")), sel("init"));
msg_void_id(vc, sel("setView:"), view);
msg_void_id(window, sel("setRootViewController:"), vc);
CTX.with(|c| {
let mut c = c.borrow_mut();
c.view = view;
c.window = window;
});
let win = IosWindow {
size: CTX.with(|c| c.borrow().size),
};
call_handler(Event::RedrawRequested, &win);
msg_void(window, sel("makeKeyAndVisible"));
msg_void_bool(view, sel("setNeedsDisplay"), true);
let (w, h) = CTX.with(|c| {
let c = c.borrow();
(c.w, c.h)
});
let marker = format!("Stipple iOS: window shown, framebuffer {w}x{h}");
println!("{marker}");
let _ = std::fs::write(std::env::temp_dir().join("stipple-ios.txt"), &marker);
}
true
}
fn call_handler(event: Event, win: &dyn Window) {
let ptr = CTX.with(|c| c.borrow().handler);
if ptr.is_null() {
return;
}
unsafe {
let fat = *ptr;
let handler: &mut dyn FnMut(Event, &dyn Window) -> ControlFlow = &mut *fat;
let _ = handler(event, win);
}
}
fn register_view_class() -> Class {
let name = c"StippleUIView";
unsafe {
let existing = objc_getClass(name.as_ptr());
if !existing.is_null() {
return existing;
}
let cls = objc_allocateClassPair(class("UIView"), name.as_ptr(), 0);
let draw_types = c"v@:{CGRect={CGPoint=dd}{CGSize=dd}}";
class_addMethod(
cls,
sel("drawRect:"),
draw_rect as *const c_void,
draw_types.as_ptr(),
);
objc_registerClassPair(cls);
cls
}
}
fn register_app_delegate() -> Class {
let name = c"StippleAppDelegate";
unsafe {
let existing = objc_getClass(name.as_ptr());
if !existing.is_null() {
return existing;
}
let cls = objc_allocateClassPair(class("UIResponder"), name.as_ptr(), 0);
class_addMethod(
cls,
sel("application:didFinishLaunchingWithOptions:"),
did_finish_launching as *const c_void,
c"c@:@@".as_ptr(),
);
objc_registerClassPair(cls);
cls
}
}
#[derive(Debug)]
struct IosWindow {
size: PhysicalSize,
}
impl Window for IosWindow {
fn id(&self) -> WindowId {
WindowId(1)
}
fn inner_size(&self) -> PhysicalSize {
self.size
}
fn scale_factor(&self) -> ScaleFactor {
ScaleFactor::IDENTITY
}
fn request_redraw(&self) {
CTX.with(|c| {
let view = c.borrow().view;
if !view.is_null() {
unsafe { msg_void_bool(view, sel("setNeedsDisplay"), true) };
}
});
}
fn set_title(&self, _title: &str) {}
fn create_surface(&self) -> Box<dyn Surface> {
Box::new(IosSurface { size: self.size })
}
}
#[derive(Debug)]
struct IosSurface {
size: PhysicalSize,
}
impl Surface for IosSurface {
fn resize(&mut self, size: PhysicalSize) {
self.size = size;
}
fn size(&self) -> PhysicalSize {
self.size
}
fn present(&mut self, pixmap: &Pixmap, _damage: &[crate::geometry::Rect]) {
let size = pixmap.size();
CTX.with(|c| {
let mut c = c.borrow_mut();
c.fb = pixmap.as_bytes().to_vec();
c.w = size.width as usize;
c.h = size.height as usize;
if !c.view.is_null() {
unsafe { msg_void_bool(c.view, sel("setNeedsDisplay"), true) };
}
});
}
}
pub fn run<H>(attrs: WindowAttributes, mut handler: H) -> Result<(), PlatformError>
where
H: FnMut(Event, &dyn Window) -> ControlFlow,
{
let size = ScaleFactor::IDENTITY.to_physical(attrs.logical_size);
let dyn_ref: &mut (dyn FnMut(Event, &dyn Window) -> ControlFlow + '_) = &mut handler;
let fat: DynHandler = unsafe { std::mem::transmute(dyn_ref) };
let boxed: *mut DynHandler = Box::into_raw(Box::new(fat));
CTX.with(|c| {
let mut c = c.borrow_mut();
c.size = size;
c.handler = boxed;
});
register_view_class();
register_app_delegate();
unsafe {
let delegate_name = nsstring("StippleAppDelegate");
UIApplicationMain(0, std::ptr::null_mut(), std::ptr::null_mut(), delegate_name);
}
Ok(())
}