···11use we_platform::appkit;
22+use we_platform::cg::{BitmapContext, CGRect};
2334fn main() {
45 let _pool = appkit::AutoreleasePool::new();
···910 appkit::install_app_delegate(&app);
10111112 let window = appkit::create_standard_window("we");
1212- window.make_key_and_order_front();
1313+1414+ // Create a bitmap context for software rendering.
1515+ let bitmap = BitmapContext::new(800, 600).expect("failed to create bitmap context");
13161717+ // Draw a colored rectangle as proof of life.
1818+ // Clear to dark gray background.
1919+ bitmap.clear(0.15, 0.15, 0.15, 1.0);
2020+ // Draw a blue rectangle in the center.
2121+ bitmap.fill_rect(CGRect::new(200.0, 150.0, 400.0, 300.0), 0.2, 0.4, 0.8, 1.0);
2222+2323+ // Create a custom view backed by the bitmap context and set it as
2424+ // the window's content view.
2525+ let frame = appkit::NSRect::new(0.0, 0.0, 800.0, 600.0);
2626+ let view = appkit::BitmapView::new(frame, &bitmap);
2727+ window.set_content_view(&view.id());
2828+2929+ window.make_key_and_order_front();
1430 app.activate();
1531 app.run();
1632}
+170
crates/platform/src/appkit.rs
···99//! The `platform` crate is one of the few crates where `unsafe` is permitted.
10101111use crate::cf::CfString;
1212+use crate::cg::{self, BitmapContext, CGRect};
1213use crate::objc::{Class, Id, Imp, Sel};
1314use crate::{class, msg_send};
1515+use std::ffi::CStr;
1416use std::os::raw::c_void;
15171618// ---------------------------------------------------------------------------
···219221 unsafe { Id::from_raw(view as *mut _) }.expect("contentView returned nil")
220222 }
221223224224+ /// Set the window's content view.
225225+ pub fn set_content_view(&self, view: &Id) {
226226+ let _: *mut c_void = msg_send![self.window.as_ptr(), setContentView: view.as_ptr()];
227227+ }
228228+222229 /// Return the underlying Objective-C object.
223230 pub fn id(&self) -> Id {
224231 self.window
···226233}
227234228235// ---------------------------------------------------------------------------
236236+// BitmapView — custom NSView subclass for rendering a CG bitmap
237237+// ---------------------------------------------------------------------------
238238+239239+/// The name of the ivar storing the BitmapContext pointer in WeView.
240240+const BITMAP_CTX_IVAR: &CStr = c"_bitmapCtx";
241241+242242+/// Register the `WeView` custom NSView subclass.
243243+///
244244+/// The class overrides `drawRect:` to blit a CGImage from the associated
245245+/// BitmapContext into the view's graphics context, and `isFlipped` to use a
246246+/// top-left coordinate origin.
247247+fn register_we_view_class() {
248248+ if class!("WeView").is_some() {
249249+ return;
250250+ }
251251+252252+ let superclass = class!("NSView").expect("NSView not found");
253253+ let view_class =
254254+ Class::allocate(superclass, c"WeView", 0).expect("failed to allocate WeView class");
255255+256256+ // Add an ivar to hold a raw pointer to a BitmapContext.
257257+ // Size = 8 (pointer), alignment = 3 (log2(8)), type = "^v" (pointer to void).
258258+ view_class.add_ivar(BITMAP_CTX_IVAR, 8, 3, c"^v");
259259+260260+ // drawRect:
261261+ extern "C" fn draw_rect(this: *mut c_void, _sel: *mut c_void, _dirty_rect: NSRect) {
262262+ let this_id = unsafe { Id::from_raw(this as *mut _) };
263263+ let this_id = match this_id {
264264+ Some(id) => id,
265265+ None => return,
266266+ };
267267+268268+ // Get the BitmapContext pointer from our ivar.
269269+ let ctx_ptr = unsafe { this_id.get_ivar(BITMAP_CTX_IVAR) };
270270+ if ctx_ptr.is_null() {
271271+ return;
272272+ }
273273+ let bitmap_ctx = unsafe { &*(ctx_ptr as *const BitmapContext) };
274274+275275+ // Create a CGImage from the bitmap context.
276276+ let image = match bitmap_ctx.create_image() {
277277+ Some(img) => img,
278278+ None => return,
279279+ };
280280+281281+ // Get the view's bounds: [self bounds]
282282+ let bounds: NSRect = msg_send![this, bounds];
283283+284284+ // Get the current NSGraphicsContext:
285285+ // [NSGraphicsContext currentContext]
286286+ let gfx_ctx_cls = class!("NSGraphicsContext").expect("NSGraphicsContext not found");
287287+ let gfx_ctx: *mut c_void = msg_send![gfx_ctx_cls.as_ptr(), currentContext];
288288+ if gfx_ctx.is_null() {
289289+ return;
290290+ }
291291+292292+ // Get the CGContextRef: [gfxCtx CGContext]
293293+ let cg_context: *mut c_void = msg_send![gfx_ctx, CGContext];
294294+ if cg_context.is_null() {
295295+ return;
296296+ }
297297+298298+ // Draw the image into the CG context.
299299+ let rect = CGRect::new(
300300+ bounds.origin.x,
301301+ bounds.origin.y,
302302+ bounds.size.width,
303303+ bounds.size.height,
304304+ );
305305+ unsafe {
306306+ cg::draw_image_in_context(cg_context, rect, &image);
307307+ }
308308+ }
309309+310310+ let sel = Sel::register(c"drawRect:");
311311+ view_class.add_method(
312312+ sel,
313313+ unsafe { std::mem::transmute::<*const (), Imp>(draw_rect as *const ()) },
314314+ c"v@:{CGRect={CGPoint=dd}{CGSize=dd}}",
315315+ );
316316+317317+ // isFlipped -> YES (top-left origin)
318318+ extern "C" fn is_flipped(_this: *mut c_void, _sel: *mut c_void) -> bool {
319319+ true
320320+ }
321321+322322+ let sel = Sel::register(c"isFlipped");
323323+ view_class.add_method(
324324+ sel,
325325+ unsafe { std::mem::transmute::<*const (), Imp>(is_flipped as *const ()) },
326326+ c"B@:",
327327+ );
328328+329329+ view_class.register();
330330+}
331331+332332+/// A custom NSView backed by a [`BitmapContext`].
333333+///
334334+/// When the view needs to draw, it creates a `CGImage` from the bitmap
335335+/// context and blits it into the view's graphics context.
336336+///
337337+/// The `BitmapContext` must outlive this view. The caller is responsible
338338+/// for ensuring this (typically by keeping the context in a `Box` alongside
339339+/// the view).
340340+pub struct BitmapView {
341341+ view: Id,
342342+}
343343+344344+impl BitmapView {
345345+ /// Create a new `BitmapView` with the given frame and bitmap context.
346346+ ///
347347+ /// The `bitmap_ctx` pointer is stored in the view's instance variable.
348348+ /// The caller must ensure the `BitmapContext` outlives this view.
349349+ pub fn new(frame: NSRect, bitmap_ctx: &BitmapContext) -> BitmapView {
350350+ register_we_view_class();
351351+352352+ let cls = class!("WeView").expect("WeView class not found");
353353+ let view: *mut c_void = msg_send![cls.as_ptr(), alloc];
354354+ let view: *mut c_void = msg_send![view, initWithFrame: frame];
355355+ let view = unsafe { Id::from_raw(view as *mut _) }.expect("WeView initWithFrame failed");
356356+357357+ // Store the BitmapContext pointer in the ivar.
358358+ unsafe {
359359+ view.set_ivar(
360360+ BITMAP_CTX_IVAR,
361361+ bitmap_ctx as *const BitmapContext as *mut c_void,
362362+ );
363363+ }
364364+365365+ BitmapView { view }
366366+ }
367367+368368+ /// Request the view to redraw.
369369+ ///
370370+ /// Call this after modifying the bitmap context's pixels to
371371+ /// schedule a redraw.
372372+ pub fn set_needs_display(&self) {
373373+ let _: *mut c_void = msg_send![self.view.as_ptr(), setNeedsDisplay: true];
374374+ }
375375+376376+ /// Return the underlying Objective-C view object.
377377+ pub fn id(&self) -> Id {
378378+ self.view
379379+ }
380380+}
381381+382382+// ---------------------------------------------------------------------------
229383// App delegate for handling window close -> app termination
230384// ---------------------------------------------------------------------------
231385···346500 | NS_WINDOW_STYLE_MASK_MINIATURIZABLE
347501 | NS_WINDOW_STYLE_MASK_RESIZABLE;
348502 assert_eq!(style, 0b1111);
503503+ }
504504+505505+ #[test]
506506+ fn we_view_class_registration() {
507507+ register_we_view_class();
508508+ let cls = class!("WeView");
509509+ assert!(cls.is_some(), "WeView class should be registered");
510510+ }
511511+512512+ #[test]
513513+ fn bitmap_view_create() {
514514+ let _pool = AutoreleasePool::new();
515515+ let bitmap = BitmapContext::new(100, 100).expect("should create bitmap context");
516516+ let frame = NSRect::new(0.0, 0.0, 100.0, 100.0);
517517+ let view = BitmapView::new(frame, &bitmap);
518518+ assert!(!view.id().as_ptr().is_null());
349519 }
350520}
+369
crates/platform/src/cg.rs
···11+//! CoreGraphics FFI bindings for bitmap rendering.
22+//!
33+//! Provides wrappers around `CGColorSpace`, `CGContext` (bitmap contexts),
44+//! and `CGImage` for software rendering into a Rust-owned pixel buffer.
55+//!
66+//! # Safety
77+//!
88+//! This module contains `unsafe` code for FFI with CoreGraphics.
99+//! The `platform` crate is one of the few crates where `unsafe` is permitted.
1010+1111+use std::os::raw::c_void;
1212+use std::ptr::NonNull;
1313+1414+// ---------------------------------------------------------------------------
1515+// CoreGraphics framework link
1616+// ---------------------------------------------------------------------------
1717+1818+#[link(name = "CoreGraphics", kind = "framework")]
1919+extern "C" {
2020+ fn CGColorSpaceCreateDeviceRGB() -> *mut c_void;
2121+ fn CGColorSpaceRelease(space: *mut c_void);
2222+2323+ fn CGBitmapContextCreate(
2424+ data: *mut c_void,
2525+ width: usize,
2626+ height: usize,
2727+ bits_per_component: usize,
2828+ bytes_per_row: usize,
2929+ colorspace: *mut c_void,
3030+ bitmap_info: u32,
3131+ ) -> *mut c_void;
3232+3333+ fn CGBitmapContextCreateImage(context: *mut c_void) -> *mut c_void;
3434+3535+ fn CGContextRelease(context: *mut c_void);
3636+ fn CGImageRelease(image: *mut c_void);
3737+3838+ fn CGContextDrawImage(context: *mut c_void, rect: CGRect, image: *mut c_void);
3939+ fn CGContextSetRGBFillColor(context: *mut c_void, red: f64, green: f64, blue: f64, alpha: f64);
4040+ fn CGContextFillRect(context: *mut c_void, rect: CGRect);
4141+}
4242+4343+// ---------------------------------------------------------------------------
4444+// Geometry (re-export compatible with appkit types)
4545+// ---------------------------------------------------------------------------
4646+4747+/// `CGRect` — a rectangle defined by origin and size.
4848+///
4949+/// This is layout-compatible with `NSRect` / the appkit `NSRect` type.
5050+#[repr(C)]
5151+#[derive(Debug, Clone, Copy)]
5252+pub struct CGRect {
5353+ pub origin: CGPoint,
5454+ pub size: CGSize,
5555+}
5656+5757+/// `CGPoint` — a point in 2D space.
5858+#[repr(C)]
5959+#[derive(Debug, Clone, Copy)]
6060+pub struct CGPoint {
6161+ pub x: f64,
6262+ pub y: f64,
6363+}
6464+6565+/// `CGSize` — a 2D size.
6666+#[repr(C)]
6767+#[derive(Debug, Clone, Copy)]
6868+pub struct CGSize {
6969+ pub width: f64,
7070+ pub height: f64,
7171+}
7272+7373+impl CGRect {
7474+ /// Create a new rectangle.
7575+ pub fn new(x: f64, y: f64, width: f64, height: f64) -> CGRect {
7676+ CGRect {
7777+ origin: CGPoint { x, y },
7878+ size: CGSize { width, height },
7979+ }
8080+ }
8181+}
8282+8383+// ---------------------------------------------------------------------------
8484+// CGBitmapInfo constants
8585+// ---------------------------------------------------------------------------
8686+8787+/// Alpha info mask (lower 5 bits of CGBitmapInfo).
8888+pub const K_CG_IMAGE_ALPHA_PREMULTIPLIED_FIRST: u32 = 2;
8989+9090+/// Byte order mask: 32-bit host byte order.
9191+pub const K_CG_BITMAP_BYTE_ORDER_32_HOST: u32 = 0;
9292+9393+/// Standard BGRA premultiplied alpha format used by macOS windowing.
9494+///
9595+/// Pixel layout in memory on little-endian (ARM64): B, G, R, A
9696+/// This matches what CoreGraphics and AppKit expect for display.
9797+pub const BITMAP_INFO_PREMULTIPLIED_FIRST: u32 =
9898+ K_CG_IMAGE_ALPHA_PREMULTIPLIED_FIRST | K_CG_BITMAP_BYTE_ORDER_32_HOST;
9999+100100+// ---------------------------------------------------------------------------
101101+// ColorSpace
102102+// ---------------------------------------------------------------------------
103103+104104+/// An owned CoreGraphics device RGB color space.
105105+pub struct ColorSpace(NonNull<c_void>);
106106+107107+impl ColorSpace {
108108+ /// Create a device RGB color space.
109109+ pub fn device_rgb() -> Option<ColorSpace> {
110110+ let ptr = unsafe { CGColorSpaceCreateDeviceRGB() };
111111+ NonNull::new(ptr).map(ColorSpace)
112112+ }
113113+114114+ /// Return the raw pointer.
115115+ #[inline]
116116+ pub fn as_ptr(&self) -> *mut c_void {
117117+ self.0.as_ptr()
118118+ }
119119+}
120120+121121+impl Drop for ColorSpace {
122122+ fn drop(&mut self) {
123123+ unsafe { CGColorSpaceRelease(self.as_ptr()) }
124124+ }
125125+}
126126+127127+// ---------------------------------------------------------------------------
128128+// BitmapContext — a CGBitmapContext backed by a Rust-owned pixel buffer
129129+// ---------------------------------------------------------------------------
130130+131131+/// A CoreGraphics bitmap context backed by a Rust-owned pixel buffer.
132132+///
133133+/// The pixel buffer is BGRA, 8 bits per component, premultiplied alpha.
134134+/// The buffer is owned by this struct and the `CGBitmapContext` renders into it.
135135+pub struct BitmapContext {
136136+ context: NonNull<c_void>,
137137+ buffer: Vec<u8>,
138138+ width: usize,
139139+ height: usize,
140140+}
141141+142142+impl BitmapContext {
143143+ /// Create a new bitmap context with the given dimensions.
144144+ ///
145145+ /// Allocates a Rust-owned pixel buffer and wraps it in a CGBitmapContext.
146146+ /// Pixel format: 4 bytes per pixel (BGRA, premultiplied alpha).
147147+ pub fn new(width: usize, height: usize) -> Option<BitmapContext> {
148148+ if width == 0 || height == 0 {
149149+ return None;
150150+ }
151151+152152+ let bytes_per_row = width * 4;
153153+ let mut buffer = vec![0u8; bytes_per_row * height];
154154+ let color_space = ColorSpace::device_rgb()?;
155155+156156+ let ctx = unsafe {
157157+ CGBitmapContextCreate(
158158+ buffer.as_mut_ptr() as *mut c_void,
159159+ width,
160160+ height,
161161+ 8, // bits per component
162162+ bytes_per_row,
163163+ color_space.as_ptr(),
164164+ BITMAP_INFO_PREMULTIPLIED_FIRST,
165165+ )
166166+ };
167167+168168+ let context = NonNull::new(ctx)?;
169169+ Some(BitmapContext {
170170+ context,
171171+ buffer,
172172+ width,
173173+ height,
174174+ })
175175+ }
176176+177177+ /// Get the width in pixels.
178178+ #[inline]
179179+ pub fn width(&self) -> usize {
180180+ self.width
181181+ }
182182+183183+ /// Get the height in pixels.
184184+ #[inline]
185185+ pub fn height(&self) -> usize {
186186+ self.height
187187+ }
188188+189189+ /// Get the bytes per row (stride).
190190+ #[inline]
191191+ pub fn bytes_per_row(&self) -> usize {
192192+ self.width * 4
193193+ }
194194+195195+ /// Get a shared reference to the raw pixel buffer.
196196+ ///
197197+ /// Pixel format: BGRA, 8 bits per component, premultiplied alpha.
198198+ /// Layout: row-major, bottom-to-top (CG default coordinate system).
199199+ #[inline]
200200+ pub fn pixels(&self) -> &[u8] {
201201+ &self.buffer
202202+ }
203203+204204+ /// Get a mutable reference to the raw pixel buffer.
205205+ ///
206206+ /// After modifying pixels directly, changes are immediately visible to
207207+ /// CoreGraphics since the bitmap context wraps this buffer.
208208+ #[inline]
209209+ pub fn pixels_mut(&mut self) -> &mut [u8] {
210210+ &mut self.buffer
211211+ }
212212+213213+ /// Create a `CGImage` from the current bitmap context contents.
214214+ ///
215215+ /// Returns `None` if the image cannot be created.
216216+ pub fn create_image(&self) -> Option<Image> {
217217+ let img = unsafe { CGBitmapContextCreateImage(self.context.as_ptr()) };
218218+ NonNull::new(img).map(Image)
219219+ }
220220+221221+ /// Fill a rectangle with an RGBA color using CoreGraphics.
222222+ ///
223223+ /// Color components are in 0.0..=1.0 range.
224224+ pub fn fill_rect(&self, rect: CGRect, r: f64, g: f64, b: f64, a: f64) {
225225+ unsafe {
226226+ CGContextSetRGBFillColor(self.context.as_ptr(), r, g, b, a);
227227+ CGContextFillRect(self.context.as_ptr(), rect);
228228+ }
229229+ }
230230+231231+ /// Clear the entire buffer to a given RGBA color.
232232+ ///
233233+ /// Color components are in 0.0..=1.0 range. This uses CoreGraphics
234234+ /// `CGContextFillRect` to fill the entire bitmap.
235235+ pub fn clear(&self, r: f64, g: f64, b: f64, a: f64) {
236236+ let rect = CGRect::new(0.0, 0.0, self.width as f64, self.height as f64);
237237+ self.fill_rect(rect, r, g, b, a);
238238+ }
239239+240240+ /// Return the raw CGContextRef pointer.
241241+ ///
242242+ /// Useful for passing to other CG drawing functions.
243243+ #[inline]
244244+ pub fn as_ptr(&self) -> *mut c_void {
245245+ self.context.as_ptr()
246246+ }
247247+}
248248+249249+impl Drop for BitmapContext {
250250+ fn drop(&mut self) {
251251+ unsafe { CGContextRelease(self.context.as_ptr()) }
252252+ }
253253+}
254254+255255+// ---------------------------------------------------------------------------
256256+// Image — an owned CGImage
257257+// ---------------------------------------------------------------------------
258258+259259+/// An owned CoreGraphics image (CGImageRef).
260260+///
261261+/// Created from a [`BitmapContext`] via [`BitmapContext::create_image`].
262262+pub struct Image(NonNull<c_void>);
263263+264264+impl Image {
265265+ /// Return the raw CGImageRef pointer.
266266+ #[inline]
267267+ pub fn as_ptr(&self) -> *mut c_void {
268268+ self.0.as_ptr()
269269+ }
270270+}
271271+272272+impl Drop for Image {
273273+ fn drop(&mut self) {
274274+ unsafe { CGImageRelease(self.as_ptr()) }
275275+ }
276276+}
277277+278278+// ---------------------------------------------------------------------------
279279+// Drawing a CGImage into a CGContext
280280+// ---------------------------------------------------------------------------
281281+282282+/// Draw a `CGImage` into a CoreGraphics context at the given rectangle.
283283+///
284284+/// This is used in `drawRect:` to blit the bitmap into the view's context.
285285+///
286286+/// # Safety
287287+///
288288+/// `target_context` must be a valid CGContextRef (e.g., from
289289+/// `NSGraphicsContext.currentContext.CGContext`).
290290+pub unsafe fn draw_image_in_context(target_context: *mut c_void, rect: CGRect, image: &Image) {
291291+ CGContextDrawImage(target_context, rect, image.as_ptr());
292292+}
293293+294294+// ---------------------------------------------------------------------------
295295+// Tests
296296+// ---------------------------------------------------------------------------
297297+298298+#[cfg(test)]
299299+mod tests {
300300+ use super::*;
301301+302302+ #[test]
303303+ fn cgrect_new() {
304304+ let rect = CGRect::new(10.0, 20.0, 300.0, 400.0);
305305+ assert_eq!(rect.origin.x, 10.0);
306306+ assert_eq!(rect.origin.y, 20.0);
307307+ assert_eq!(rect.size.width, 300.0);
308308+ assert_eq!(rect.size.height, 400.0);
309309+ }
310310+311311+ #[test]
312312+ fn bitmap_info_constant() {
313313+ assert_eq!(BITMAP_INFO_PREMULTIPLIED_FIRST, 2);
314314+ }
315315+316316+ #[test]
317317+ fn create_color_space() {
318318+ let cs = ColorSpace::device_rgb().expect("should create device RGB color space");
319319+ assert!(!cs.as_ptr().is_null());
320320+ }
321321+322322+ #[test]
323323+ fn create_bitmap_context() {
324324+ let ctx = BitmapContext::new(100, 100).expect("should create bitmap context");
325325+ assert_eq!(ctx.width(), 100);
326326+ assert_eq!(ctx.height(), 100);
327327+ assert_eq!(ctx.bytes_per_row(), 400);
328328+ assert_eq!(ctx.pixels().len(), 100 * 400);
329329+ }
330330+331331+ #[test]
332332+ fn bitmap_context_zero_size_returns_none() {
333333+ assert!(BitmapContext::new(0, 100).is_none());
334334+ assert!(BitmapContext::new(100, 0).is_none());
335335+ assert!(BitmapContext::new(0, 0).is_none());
336336+ }
337337+338338+ #[test]
339339+ fn bitmap_context_pixels_mut() {
340340+ let mut ctx = BitmapContext::new(10, 10).expect("should create");
341341+ let pixels = ctx.pixels_mut();
342342+ // Write a red pixel at (0, 0): BGRA format
343343+ pixels[0] = 0; // B
344344+ pixels[1] = 0; // G
345345+ pixels[2] = 255; // R
346346+ pixels[3] = 255; // A
347347+ assert_eq!(ctx.pixels()[0], 0);
348348+ assert_eq!(ctx.pixels()[2], 255);
349349+ }
350350+351351+ #[test]
352352+ fn bitmap_context_clear_and_create_image() {
353353+ let ctx = BitmapContext::new(50, 50).expect("should create");
354354+ ctx.clear(0.0, 0.0, 1.0, 1.0); // blue
355355+ let img = ctx.create_image().expect("should create image");
356356+ assert!(!img.as_ptr().is_null());
357357+ }
358358+359359+ #[test]
360360+ fn bitmap_context_fill_rect() {
361361+ let ctx = BitmapContext::new(100, 100).expect("should create");
362362+ let rect = CGRect::new(10.0, 10.0, 50.0, 50.0);
363363+ ctx.fill_rect(rect, 1.0, 0.0, 0.0, 1.0); // red rectangle
364364+ let img = ctx
365365+ .create_image()
366366+ .expect("should create image from filled context");
367367+ assert!(!img.as_ptr().is_null());
368368+ }
369369+}
+1
crates/platform/src/lib.rs
···2233pub mod appkit;
44pub mod cf;
55+pub mod cg;
56pub mod objc;
+60-1
crates/platform/src/objc.rs
···10101111use std::ffi::CStr;
1212use std::fmt;
1313-use std::os::raw::c_char;
1313+use std::os::raw::{c_char, c_void};
1414use std::ptr::NonNull;
15151616// ---------------------------------------------------------------------------
···5858 types: *const c_char,
5959 ) -> bool;
6060 fn class_getName(cls: *mut ObjcClass) -> *const c_char;
6161+ fn class_addIvar(
6262+ cls: *mut ObjcClass,
6363+ name: *const c_char,
6464+ size: usize,
6565+ alignment: u8,
6666+ types: *const c_char,
6767+ ) -> bool;
6868+6969+ fn object_setInstanceVariable(
7070+ obj: *mut ObjcObject,
7171+ name: *const c_char,
7272+ value: *mut c_void,
7373+ ) -> *mut c_void; // returns Ivar (opaque, we don't use it)
7474+ fn object_getInstanceVariable(
7575+ obj: *mut ObjcObject,
7676+ name: *const c_char,
7777+ out_value: *mut *mut c_void,
7878+ ) -> *mut c_void; // returns Ivar
61796280 // objc_msgSend has a variadic ABI. On AArch64 the calling convention is
6381 // the standard C calling convention (arguments in x0..x7, return in x0).
···174192 unsafe { class_addMethod(self.as_ptr(), sel.as_ptr(), imp, types.as_ptr()) }
175193 }
176194195195+ /// Add an instance variable to a class that has not yet been registered.
196196+ ///
197197+ /// `name` is the ivar name, `size` is the size in bytes, `alignment` is
198198+ /// the log2 alignment (e.g., 3 for 8-byte alignment), and `types` is the
199199+ /// ObjC type encoding string.
200200+ ///
201201+ /// Returns `true` if the ivar was added.
202202+ pub fn add_ivar(self, name: &CStr, size: usize, alignment: u8, types: &CStr) -> bool {
203203+ // SAFETY: the class pointer is valid and not yet registered.
204204+ unsafe {
205205+ class_addIvar(
206206+ self.as_ptr(),
207207+ name.as_ptr(),
208208+ size,
209209+ alignment,
210210+ types.as_ptr(),
211211+ )
212212+ }
213213+ }
214214+177215 /// Get the name of this class.
178216 pub fn name(self) -> &'static str {
179217 // SAFETY: class_getName always returns a valid C string for a valid
···247285 #[inline]
248286 pub unsafe fn cast<T>(self) -> *mut T {
249287 self.0.as_ptr().cast()
288288+ }
289289+290290+ /// Set an instance variable by name.
291291+ ///
292292+ /// # Safety
293293+ ///
294294+ /// The object must have an ivar with the given `name` of pointer type.
295295+ /// The caller is responsible for the validity and lifetime of `value`.
296296+ pub unsafe fn set_ivar(self, name: &CStr, value: *mut c_void) {
297297+ object_setInstanceVariable(self.as_ptr(), name.as_ptr(), value);
298298+ }
299299+300300+ /// Get an instance variable by name.
301301+ ///
302302+ /// # Safety
303303+ ///
304304+ /// The object must have an ivar with the given `name` of pointer type.
305305+ pub unsafe fn get_ivar(self, name: &CStr) -> *mut c_void {
306306+ let mut out: *mut c_void = std::ptr::null_mut();
307307+ object_getInstanceVariable(self.as_ptr(), name.as_ptr(), &mut out);
308308+ out
250309 }
251310}
252311