#![allow(unsafe_code)]
use std::cell::RefCell;
use std::ffi::{CString, c_void};
use crate::geometry::{PhysicalSize, Point, ScaleFactor};
use crate::platform::ControlFlow;
use crate::platform::error::PlatformError;
use crate::platform::event::{
ButtonState, Event, KeyCode, Modifiers, PointerButton, Touch, TouchPhase, WindowId,
};
use crate::platform::input;
use crate::platform::window::{Window, WindowAttributes};
use crate::render::{Pixmap, Surface};
mod display_link;
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 EdgeInsets {
top: f64,
left: f64,
bottom: f64,
right: 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;
fn class_addProtocol(cls: Class, protocol: *mut c_void) -> bool;
fn objc_getProtocol(name: *const i8) -> *mut c_void;
fn objc_msgSendSuper();
}
#[repr(C)]
struct ObjcSuper {
receiver: Id,
super_class: Class,
}
#[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_bool(obj: Id, s: Sel) -> bool {
let f: unsafe extern "C" fn(Id, Sel) -> i8 =
unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
unsafe { f(obj, s) != 0 }
}
unsafe fn msg_usize(obj: Id, s: Sel) -> usize {
let f: unsafe extern "C" fn(Id, Sel) -> usize =
unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
unsafe { f(obj, s) }
}
unsafe fn msg_id_usize(obj: Id, s: Sel, a: usize) -> Id {
let f: unsafe extern "C" fn(Id, Sel, usize) -> Id =
unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
unsafe { f(obj, s, a) }
}
unsafe fn msg_f64(obj: Id, s: Sel) -> f64 {
let f: unsafe extern "C" fn(Id, Sel) -> f64 =
unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
unsafe { f(obj, s) }
}
unsafe fn msg_insets(obj: Id, s: Sel) -> EdgeInsets {
let f: unsafe extern "C" fn(Id, Sel) -> EdgeInsets =
unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
unsafe { f(obj, s) }
}
unsafe fn msg_void_rect(obj: Id, s: Sel, a: CgRect) {
let f: unsafe extern "C" fn(Id, Sel, CgRect) =
unsafe { std::mem::transmute(objc_msgSend as *const c_void) };
unsafe { f(obj, s, a) }
}
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())
}
}
unsafe fn string_of(s: Id) -> String {
if s.is_null() {
return String::new();
}
unsafe {
let f: unsafe extern "C" fn(Id, Sel) -> *const std::ffi::c_char =
std::mem::transmute(objc_msgSend as *const c_void);
let p = f(s, sel("UTF8String"));
if p.is_null() {
String::new()
} else {
std::ffi::CStr::from_ptr(p).to_string_lossy().into_owned()
}
}
}
unsafe fn set_objects(set: Id) -> Vec<Id> {
if set.is_null() {
return Vec::new();
}
unsafe {
let all = msg_id(set, sel("allObjects"));
(0..msg_usize(all, sel("count")))
.map(|i| msg_id_usize(all, sel("objectAtIndex:"), i))
.collect()
}
}
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,
scale: f64,
background: Option<crate::render::Color>,
handler: *mut DynHandler,
primary: Option<u64>,
text_input: bool,
}
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 (width, height) = (c.w as f64 / c.scale, c.h as f64 / c.scale);
let rect = CgRect {
origin: CgPoint { x: 0.0, y: 0.0 },
size: CgSize { width, height },
};
CGContextSaveGState(cg);
CGContextTranslateCTM(cg, 0.0, height);
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 scale = msg_f64(msg_id(class("UIScreen"), sel("mainScreen")), sel("scale"));
let window = msg_alloc_init_frame(class("UIWindow"), bounds);
let root = msg_alloc_init_frame(class("UIView"), bounds);
let background = match CTX.with(|c| c.borrow().background) {
Some(color) => {
let (r, g, b, a) = (color.r, color.g, color.b, color.a);
let c = |v: u8| v as f64 / 255.0;
let f: unsafe extern "C" fn(Id, Sel, f64, f64, f64, f64) -> Id =
std::mem::transmute(objc_msgSend as *const c_void);
f(
class("UIColor"),
sel("colorWithRed:green:blue:alpha:"),
c(r),
c(g),
c(b),
c(a),
)
}
None => msg_id(class("UIColor"), sel("systemBackgroundColor")),
};
msg_void_id(root, sel("setBackgroundColor:"), background);
let view = msg_alloc_init_frame(register_view_class(), bounds);
msg_void_bool(view, sel("setMultipleTouchEnabled:"), true);
msg_void_id(root, sel("addSubview:"), view);
let vc = msg_id(msg_id(class("UIViewController"), sel("alloc")), sel("init"));
msg_void_id(vc, sel("setView:"), root);
msg_void_id(window, sel("setRootViewController:"), vc);
CTX.with(|c| {
let mut c = c.borrow_mut();
c.view = view;
c.window = window;
c.scale = scale;
});
msg_void(window, sel("makeKeyAndVisible"));
let safe = msg_insets(window, sel("safeAreaInsets"));
let frame = CgRect {
origin: CgPoint {
x: bounds.origin.x + safe.left,
y: bounds.origin.y + safe.top,
},
size: CgSize {
width: bounds.size.width - safe.left - safe.right,
height: bounds.size.height - safe.top - safe.bottom,
},
};
msg_void_rect(view, sel("setFrame:"), frame);
let size = PhysicalSize::new(
(frame.size.width * scale).round() as u32,
(frame.size.height * scale).round() as u32,
);
CTX.with(|c| c.borrow_mut().size = size);
let win = IosWindow { size };
call_handler(Event::Resized(size), &win);
call_handler(Event::RedrawRequested, &win);
crate::platform::wake::install(
std::sync::Arc::new(|| {
dispatch_async_f(main_queue(), std::ptr::null_mut(), on_wake);
}),
Some(std::sync::Arc::new(|at: std::time::Instant| {
let delay = at.saturating_duration_since(std::time::Instant::now());
let ns = delay.as_nanos().min(i64::MAX as u128) as i64;
dispatch_after_f(
dispatch_time(DISPATCH_TIME_NOW, ns),
main_queue(),
std::ptr::null_mut(),
on_wake,
);
})),
);
display_link::install();
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
}
unsafe fn touches_in(view: Id, touches: Id) -> Vec<(u64, Point)> {
unsafe {
let location: unsafe extern "C" fn(Id, Sel, Id) -> CgPoint =
std::mem::transmute(objc_msgSend as *const c_void);
set_objects(touches)
.into_iter()
.map(|touch| {
let p = location(touch, sel("locationInView:"), view);
(touch as u64, Point::new(p.x, p.y))
})
.collect()
}
}
fn current_window() -> IosWindow {
IosWindow {
size: CTX.with(|c| c.borrow().size),
}
}
fn touch_input(phase: TouchPhase, touches: &[(u64, Point)]) {
let win = current_window();
for &(id, position) in touches {
call_handler(
Event::Touch(Touch {
id,
phase,
position,
}),
&win,
);
}
let primary = CTX.with(|c| c.borrow().primary);
let pressed = |position| Event::PointerButton {
button: PointerButton::Left,
state: ButtonState::Pressed,
position,
};
let released = |position| Event::PointerButton {
button: PointerButton::Left,
state: ButtonState::Released,
position,
};
match phase {
TouchPhase::Started => {
if let (None, Some(&(id, position))) = (primary, touches.first()) {
CTX.with(|c| c.borrow_mut().primary = Some(id));
call_handler(Event::PointerMoved { position }, &win);
call_handler(pressed(position), &win);
}
}
TouchPhase::Moved => {
if let Some(&(_, position)) = touches.iter().find(|t| Some(t.0) == primary) {
call_handler(Event::PointerMoved { position }, &win);
}
}
TouchPhase::Ended | TouchPhase::Cancelled => {
if let Some(&(_, position)) = touches.iter().find(|t| Some(t.0) == primary) {
CTX.with(|c| c.borrow_mut().primary = None);
let at = if phase == TouchPhase::Cancelled {
Point::new(-1.0e9, -1.0e9)
} else {
position
};
call_handler(released(at), &win);
call_handler(Event::PointerLeft, &win);
}
}
}
}
extern "C" fn touches_began(this: Id, _cmd: Sel, touches: Id, _event: Id) {
touch_input(TouchPhase::Started, &unsafe { touches_in(this, touches) });
}
extern "C" fn touches_moved(this: Id, _cmd: Sel, touches: Id, _event: Id) {
touch_input(TouchPhase::Moved, &unsafe { touches_in(this, touches) });
}
extern "C" fn touches_ended(this: Id, _cmd: Sel, touches: Id, _event: Id) {
touch_input(TouchPhase::Ended, &unsafe { touches_in(this, touches) });
}
extern "C" fn touches_cancelled(this: Id, _cmd: Sel, touches: Id, _event: Id) {
touch_input(TouchPhase::Cancelled, &unsafe { touches_in(this, touches) });
}
extern "C" fn can_become_first_responder(_this: Id, _cmd: Sel) -> bool {
true
}
extern "C" fn has_text(_this: Id, _cmd: Sel) -> bool {
true
}
extern "C" fn insert_text(_this: Id, _cmd: Sel, text: Id) {
let text = unsafe { string_of(text) };
let win = current_window();
for (i, line) in text.split('\n').enumerate() {
if i > 0 {
key_tap(KeyCode::Enter, Modifiers::default(), &win);
}
if !line.is_empty() {
call_handler(Event::Text(line.to_string()), &win);
}
}
}
extern "C" fn delete_backward(_this: Id, _cmd: Sel) {
key_tap(KeyCode::Backspace, Modifiers::default(), ¤t_window());
}
fn key_tap(code: KeyCode, modifiers: Modifiers, win: &IosWindow) {
for state in [ButtonState::Pressed, ButtonState::Released] {
call_handler(
Event::Key {
code,
state,
modifiers,
},
win,
);
}
}
unsafe fn presses(this: Id, cmd: Sel, presses: Id, event: Id, state: ButtonState) {
let win = current_window();
let mut to_uikit = false;
for press in unsafe { set_objects(presses) } {
let key = unsafe { msg_id(press, sel("key")) };
if key.is_null() {
to_uikit = true;
continue;
}
let usage = unsafe { msg_usize(key, sel("keyCode")) } as isize;
let modifiers = input::ios_modifiers(unsafe { msg_usize(key, sel("modifierFlags")) });
let chars = unsafe { string_of(msg_id(key, sel("charactersIgnoringModifiers"))) };
match input::ios_key(usage, &chars, modifiers) {
Some(code) => call_handler(
Event::Key {
code,
state,
modifiers,
},
&win,
),
None => to_uikit = true,
}
}
if to_uikit {
unsafe {
let sup = ObjcSuper {
receiver: this,
super_class: class("UIView"),
};
let f: unsafe extern "C" fn(*const ObjcSuper, Sel, Id, Id) =
std::mem::transmute(objc_msgSendSuper as *const c_void);
f(&sup, cmd, presses, event);
}
}
}
extern "C" fn presses_began(this: Id, cmd: Sel, set: Id, event: Id) {
unsafe { presses(this, cmd, set, event, ButtonState::Pressed) }
}
extern "C" fn presses_ended(this: Id, cmd: Sel, set: Id, event: Id) {
unsafe { presses(this, cmd, set, event, ButtonState::Released) }
}
fn set_text_input(active: bool) {
let (view, was) = CTX.with(|c| {
let mut c = c.borrow_mut();
let was = c.text_input;
c.text_input = active;
(c.view, was)
});
if view.is_null() || was == active {
return;
}
unsafe {
if active {
msg_bool(view, sel("becomeFirstResponder"));
} else {
msg_bool(view, sel("resignFirstResponder"));
}
}
}
pub mod simulate {
use super::*;
fn later(f: impl FnOnce() + 'static) {
extern "C" fn trampoline(context: *mut c_void) {
let f = unsafe { Box::from_raw(context.cast::<Box<dyn FnOnce()>>()) };
f();
}
let f: Box<Box<dyn FnOnce()>> = Box::new(Box::new(f));
unsafe { dispatch_async_f(main_queue(), Box::into_raw(f).cast(), trampoline) };
}
pub fn touch(id: u64, phase: TouchPhase, position: Point) {
later(move || touch_input(phase, &[(id, position)]));
}
pub fn insert_text(text: &str) {
let text = text.to_string();
later(move || {
let view = CTX.with(|c| c.borrow().view);
if !view.is_null() {
unsafe { msg_void_id(view, sel("insertText:"), nsstring(&text)) };
}
});
}
pub fn delete_backward() {
later(|| {
let view = CTX.with(|c| c.borrow().view);
if !view.is_null() {
unsafe { msg_void(view, sel("deleteBackward")) };
}
});
}
pub fn keyboard_status() -> String {
let view = CTX.with(|c| c.borrow().view);
if view.is_null() {
return "no view".into();
}
unsafe {
let protocol = objc_getProtocol(c"UIKeyInput".as_ptr());
let conforms = {
let f: unsafe extern "C" fn(Id, Sel, *mut c_void) -> i8 =
std::mem::transmute(objc_msgSend as *const c_void);
!protocol.is_null() && f(view, sel("conformsToProtocol:"), protocol) != 0
};
format!(
"key_input={conforms} can_become={} first_responder={} has_text={}",
msg_bool(view, sel("canBecomeFirstResponder")),
msg_bool(view, sel("isFirstResponder")),
msg_bool(view, sel("hasText")),
)
}
}
}
const DISPATCH_TIME_NOW: u64 = 0;
unsafe extern "C" {
static _dispatch_main_q: u8;
fn dispatch_async_f(
queue: *const c_void,
context: *mut c_void,
work: extern "C" fn(*mut c_void),
);
fn dispatch_after_f(
when: u64,
queue: *const c_void,
context: *mut c_void,
work: extern "C" fn(*mut c_void),
);
fn dispatch_time(when: u64, delta_ns: i64) -> u64;
}
fn main_queue() -> *const c_void {
(&raw const _dispatch_main_q).cast()
}
extern "C" fn on_wake(_context: *mut c_void) {
if crate::platform::wake::take(std::time::Instant::now()) {
call_handler(Event::Wake, ¤t_window());
}
}
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(),
);
let touch_types = c"v@:@@";
let touches: [(&str, extern "C" fn(Id, Sel, Id, Id)); 7] = [
("touchesBegan:withEvent:", touches_began),
("touchesMoved:withEvent:", touches_moved),
("touchesEnded:withEvent:", touches_ended),
("touchesCancelled:withEvent:", touches_cancelled),
("pressesBegan:withEvent:", presses_began),
("pressesEnded:withEvent:", presses_ended),
("pressesCancelled:withEvent:", presses_ended),
];
for (name, imp) in touches {
class_addMethod(cls, sel(name), imp as *const c_void, touch_types.as_ptr());
}
let flags: [(&str, extern "C" fn(Id, Sel) -> bool); 2] = [
("canBecomeFirstResponder", can_become_first_responder),
("hasText", has_text),
];
for (name, imp) in flags {
class_addMethod(cls, sel(name), imp as *const c_void, c"B@:".as_ptr());
}
class_addMethod(
cls,
sel("insertText:"),
insert_text as *const c_void,
c"v@:@".as_ptr(),
);
class_addMethod(
cls,
sel("deleteBackward"),
delete_backward as *const c_void,
c"v@:".as_ptr(),
);
let key_input = objc_getProtocol(c"UIKeyInput".as_ptr());
if !key_input.is_null() {
class_addProtocol(cls, key_input);
}
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::new(CTX.with(|c| c.borrow().scale))
}
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 clipboard(&self) -> Option<String> {
crate::platform::clipboard::text()
}
fn set_clipboard(&self, text: &str) {
crate::platform::clipboard::set_text(text);
}
fn set_text_input_active(&self, active: bool) {
set_text_input(active);
}
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.background = attrs.background;
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(())
}