web engine - experimental web browser

Merge branch 'cg-surface': CoreGraphics rendering surface

+617 -2
+17 -1
crates/browser/src/main.rs
··· 1 1 use we_platform::appkit; 2 + use we_platform::cg::{BitmapContext, CGRect}; 2 3 3 4 fn main() { 4 5 let _pool = appkit::AutoreleasePool::new(); ··· 9 10 appkit::install_app_delegate(&app); 10 11 11 12 let window = appkit::create_standard_window("we"); 12 - window.make_key_and_order_front(); 13 + 14 + // Create a bitmap context for software rendering. 15 + let bitmap = BitmapContext::new(800, 600).expect("failed to create bitmap context"); 13 16 17 + // Draw a colored rectangle as proof of life. 18 + // Clear to dark gray background. 19 + bitmap.clear(0.15, 0.15, 0.15, 1.0); 20 + // Draw a blue rectangle in the center. 21 + bitmap.fill_rect(CGRect::new(200.0, 150.0, 400.0, 300.0), 0.2, 0.4, 0.8, 1.0); 22 + 23 + // Create a custom view backed by the bitmap context and set it as 24 + // the window's content view. 25 + let frame = appkit::NSRect::new(0.0, 0.0, 800.0, 600.0); 26 + let view = appkit::BitmapView::new(frame, &bitmap); 27 + window.set_content_view(&view.id()); 28 + 29 + window.make_key_and_order_front(); 14 30 app.activate(); 15 31 app.run(); 16 32 }
+170
crates/platform/src/appkit.rs
··· 9 9 //! The `platform` crate is one of the few crates where `unsafe` is permitted. 10 10 11 11 use crate::cf::CfString; 12 + use crate::cg::{self, BitmapContext, CGRect}; 12 13 use crate::objc::{Class, Id, Imp, Sel}; 13 14 use crate::{class, msg_send}; 15 + use std::ffi::CStr; 14 16 use std::os::raw::c_void; 15 17 16 18 // --------------------------------------------------------------------------- ··· 219 221 unsafe { Id::from_raw(view as *mut _) }.expect("contentView returned nil") 220 222 } 221 223 224 + /// Set the window's content view. 225 + pub fn set_content_view(&self, view: &Id) { 226 + let _: *mut c_void = msg_send![self.window.as_ptr(), setContentView: view.as_ptr()]; 227 + } 228 + 222 229 /// Return the underlying Objective-C object. 223 230 pub fn id(&self) -> Id { 224 231 self.window ··· 226 233 } 227 234 228 235 // --------------------------------------------------------------------------- 236 + // BitmapView — custom NSView subclass for rendering a CG bitmap 237 + // --------------------------------------------------------------------------- 238 + 239 + /// The name of the ivar storing the BitmapContext pointer in WeView. 240 + const BITMAP_CTX_IVAR: &CStr = c"_bitmapCtx"; 241 + 242 + /// Register the `WeView` custom NSView subclass. 243 + /// 244 + /// The class overrides `drawRect:` to blit a CGImage from the associated 245 + /// BitmapContext into the view's graphics context, and `isFlipped` to use a 246 + /// top-left coordinate origin. 247 + fn register_we_view_class() { 248 + if class!("WeView").is_some() { 249 + return; 250 + } 251 + 252 + let superclass = class!("NSView").expect("NSView not found"); 253 + let view_class = 254 + Class::allocate(superclass, c"WeView", 0).expect("failed to allocate WeView class"); 255 + 256 + // Add an ivar to hold a raw pointer to a BitmapContext. 257 + // Size = 8 (pointer), alignment = 3 (log2(8)), type = "^v" (pointer to void). 258 + view_class.add_ivar(BITMAP_CTX_IVAR, 8, 3, c"^v"); 259 + 260 + // drawRect: 261 + extern "C" fn draw_rect(this: *mut c_void, _sel: *mut c_void, _dirty_rect: NSRect) { 262 + let this_id = unsafe { Id::from_raw(this as *mut _) }; 263 + let this_id = match this_id { 264 + Some(id) => id, 265 + None => return, 266 + }; 267 + 268 + // Get the BitmapContext pointer from our ivar. 269 + let ctx_ptr = unsafe { this_id.get_ivar(BITMAP_CTX_IVAR) }; 270 + if ctx_ptr.is_null() { 271 + return; 272 + } 273 + let bitmap_ctx = unsafe { &*(ctx_ptr as *const BitmapContext) }; 274 + 275 + // Create a CGImage from the bitmap context. 276 + let image = match bitmap_ctx.create_image() { 277 + Some(img) => img, 278 + None => return, 279 + }; 280 + 281 + // Get the view's bounds: [self bounds] 282 + let bounds: NSRect = msg_send![this, bounds]; 283 + 284 + // Get the current NSGraphicsContext: 285 + // [NSGraphicsContext currentContext] 286 + let gfx_ctx_cls = class!("NSGraphicsContext").expect("NSGraphicsContext not found"); 287 + let gfx_ctx: *mut c_void = msg_send![gfx_ctx_cls.as_ptr(), currentContext]; 288 + if gfx_ctx.is_null() { 289 + return; 290 + } 291 + 292 + // Get the CGContextRef: [gfxCtx CGContext] 293 + let cg_context: *mut c_void = msg_send![gfx_ctx, CGContext]; 294 + if cg_context.is_null() { 295 + return; 296 + } 297 + 298 + // Draw the image into the CG context. 299 + let rect = CGRect::new( 300 + bounds.origin.x, 301 + bounds.origin.y, 302 + bounds.size.width, 303 + bounds.size.height, 304 + ); 305 + unsafe { 306 + cg::draw_image_in_context(cg_context, rect, &image); 307 + } 308 + } 309 + 310 + let sel = Sel::register(c"drawRect:"); 311 + view_class.add_method( 312 + sel, 313 + unsafe { std::mem::transmute::<*const (), Imp>(draw_rect as *const ()) }, 314 + c"v@:{CGRect={CGPoint=dd}{CGSize=dd}}", 315 + ); 316 + 317 + // isFlipped -> YES (top-left origin) 318 + extern "C" fn is_flipped(_this: *mut c_void, _sel: *mut c_void) -> bool { 319 + true 320 + } 321 + 322 + let sel = Sel::register(c"isFlipped"); 323 + view_class.add_method( 324 + sel, 325 + unsafe { std::mem::transmute::<*const (), Imp>(is_flipped as *const ()) }, 326 + c"B@:", 327 + ); 328 + 329 + view_class.register(); 330 + } 331 + 332 + /// A custom NSView backed by a [`BitmapContext`]. 333 + /// 334 + /// When the view needs to draw, it creates a `CGImage` from the bitmap 335 + /// context and blits it into the view's graphics context. 336 + /// 337 + /// The `BitmapContext` must outlive this view. The caller is responsible 338 + /// for ensuring this (typically by keeping the context in a `Box` alongside 339 + /// the view). 340 + pub struct BitmapView { 341 + view: Id, 342 + } 343 + 344 + impl BitmapView { 345 + /// Create a new `BitmapView` with the given frame and bitmap context. 346 + /// 347 + /// The `bitmap_ctx` pointer is stored in the view's instance variable. 348 + /// The caller must ensure the `BitmapContext` outlives this view. 349 + pub fn new(frame: NSRect, bitmap_ctx: &BitmapContext) -> BitmapView { 350 + register_we_view_class(); 351 + 352 + let cls = class!("WeView").expect("WeView class not found"); 353 + let view: *mut c_void = msg_send![cls.as_ptr(), alloc]; 354 + let view: *mut c_void = msg_send![view, initWithFrame: frame]; 355 + let view = unsafe { Id::from_raw(view as *mut _) }.expect("WeView initWithFrame failed"); 356 + 357 + // Store the BitmapContext pointer in the ivar. 358 + unsafe { 359 + view.set_ivar( 360 + BITMAP_CTX_IVAR, 361 + bitmap_ctx as *const BitmapContext as *mut c_void, 362 + ); 363 + } 364 + 365 + BitmapView { view } 366 + } 367 + 368 + /// Request the view to redraw. 369 + /// 370 + /// Call this after modifying the bitmap context's pixels to 371 + /// schedule a redraw. 372 + pub fn set_needs_display(&self) { 373 + let _: *mut c_void = msg_send![self.view.as_ptr(), setNeedsDisplay: true]; 374 + } 375 + 376 + /// Return the underlying Objective-C view object. 377 + pub fn id(&self) -> Id { 378 + self.view 379 + } 380 + } 381 + 382 + // --------------------------------------------------------------------------- 229 383 // App delegate for handling window close -> app termination 230 384 // --------------------------------------------------------------------------- 231 385 ··· 346 500 | NS_WINDOW_STYLE_MASK_MINIATURIZABLE 347 501 | NS_WINDOW_STYLE_MASK_RESIZABLE; 348 502 assert_eq!(style, 0b1111); 503 + } 504 + 505 + #[test] 506 + fn we_view_class_registration() { 507 + register_we_view_class(); 508 + let cls = class!("WeView"); 509 + assert!(cls.is_some(), "WeView class should be registered"); 510 + } 511 + 512 + #[test] 513 + fn bitmap_view_create() { 514 + let _pool = AutoreleasePool::new(); 515 + let bitmap = BitmapContext::new(100, 100).expect("should create bitmap context"); 516 + let frame = NSRect::new(0.0, 0.0, 100.0, 100.0); 517 + let view = BitmapView::new(frame, &bitmap); 518 + assert!(!view.id().as_ptr().is_null()); 349 519 } 350 520 }
+369
crates/platform/src/cg.rs
··· 1 + //! CoreGraphics FFI bindings for bitmap rendering. 2 + //! 3 + //! Provides wrappers around `CGColorSpace`, `CGContext` (bitmap contexts), 4 + //! and `CGImage` for software rendering into a Rust-owned pixel buffer. 5 + //! 6 + //! # Safety 7 + //! 8 + //! This module contains `unsafe` code for FFI with CoreGraphics. 9 + //! The `platform` crate is one of the few crates where `unsafe` is permitted. 10 + 11 + use std::os::raw::c_void; 12 + use std::ptr::NonNull; 13 + 14 + // --------------------------------------------------------------------------- 15 + // CoreGraphics framework link 16 + // --------------------------------------------------------------------------- 17 + 18 + #[link(name = "CoreGraphics", kind = "framework")] 19 + extern "C" { 20 + fn CGColorSpaceCreateDeviceRGB() -> *mut c_void; 21 + fn CGColorSpaceRelease(space: *mut c_void); 22 + 23 + fn CGBitmapContextCreate( 24 + data: *mut c_void, 25 + width: usize, 26 + height: usize, 27 + bits_per_component: usize, 28 + bytes_per_row: usize, 29 + colorspace: *mut c_void, 30 + bitmap_info: u32, 31 + ) -> *mut c_void; 32 + 33 + fn CGBitmapContextCreateImage(context: *mut c_void) -> *mut c_void; 34 + 35 + fn CGContextRelease(context: *mut c_void); 36 + fn CGImageRelease(image: *mut c_void); 37 + 38 + fn CGContextDrawImage(context: *mut c_void, rect: CGRect, image: *mut c_void); 39 + fn CGContextSetRGBFillColor(context: *mut c_void, red: f64, green: f64, blue: f64, alpha: f64); 40 + fn CGContextFillRect(context: *mut c_void, rect: CGRect); 41 + } 42 + 43 + // --------------------------------------------------------------------------- 44 + // Geometry (re-export compatible with appkit types) 45 + // --------------------------------------------------------------------------- 46 + 47 + /// `CGRect` — a rectangle defined by origin and size. 48 + /// 49 + /// This is layout-compatible with `NSRect` / the appkit `NSRect` type. 50 + #[repr(C)] 51 + #[derive(Debug, Clone, Copy)] 52 + pub struct CGRect { 53 + pub origin: CGPoint, 54 + pub size: CGSize, 55 + } 56 + 57 + /// `CGPoint` — a point in 2D space. 58 + #[repr(C)] 59 + #[derive(Debug, Clone, Copy)] 60 + pub struct CGPoint { 61 + pub x: f64, 62 + pub y: f64, 63 + } 64 + 65 + /// `CGSize` — a 2D size. 66 + #[repr(C)] 67 + #[derive(Debug, Clone, Copy)] 68 + pub struct CGSize { 69 + pub width: f64, 70 + pub height: f64, 71 + } 72 + 73 + impl CGRect { 74 + /// Create a new rectangle. 75 + pub fn new(x: f64, y: f64, width: f64, height: f64) -> CGRect { 76 + CGRect { 77 + origin: CGPoint { x, y }, 78 + size: CGSize { width, height }, 79 + } 80 + } 81 + } 82 + 83 + // --------------------------------------------------------------------------- 84 + // CGBitmapInfo constants 85 + // --------------------------------------------------------------------------- 86 + 87 + /// Alpha info mask (lower 5 bits of CGBitmapInfo). 88 + pub const K_CG_IMAGE_ALPHA_PREMULTIPLIED_FIRST: u32 = 2; 89 + 90 + /// Byte order mask: 32-bit host byte order. 91 + pub const K_CG_BITMAP_BYTE_ORDER_32_HOST: u32 = 0; 92 + 93 + /// Standard BGRA premultiplied alpha format used by macOS windowing. 94 + /// 95 + /// Pixel layout in memory on little-endian (ARM64): B, G, R, A 96 + /// This matches what CoreGraphics and AppKit expect for display. 97 + pub const BITMAP_INFO_PREMULTIPLIED_FIRST: u32 = 98 + K_CG_IMAGE_ALPHA_PREMULTIPLIED_FIRST | K_CG_BITMAP_BYTE_ORDER_32_HOST; 99 + 100 + // --------------------------------------------------------------------------- 101 + // ColorSpace 102 + // --------------------------------------------------------------------------- 103 + 104 + /// An owned CoreGraphics device RGB color space. 105 + pub struct ColorSpace(NonNull<c_void>); 106 + 107 + impl ColorSpace { 108 + /// Create a device RGB color space. 109 + pub fn device_rgb() -> Option<ColorSpace> { 110 + let ptr = unsafe { CGColorSpaceCreateDeviceRGB() }; 111 + NonNull::new(ptr).map(ColorSpace) 112 + } 113 + 114 + /// Return the raw pointer. 115 + #[inline] 116 + pub fn as_ptr(&self) -> *mut c_void { 117 + self.0.as_ptr() 118 + } 119 + } 120 + 121 + impl Drop for ColorSpace { 122 + fn drop(&mut self) { 123 + unsafe { CGColorSpaceRelease(self.as_ptr()) } 124 + } 125 + } 126 + 127 + // --------------------------------------------------------------------------- 128 + // BitmapContext — a CGBitmapContext backed by a Rust-owned pixel buffer 129 + // --------------------------------------------------------------------------- 130 + 131 + /// A CoreGraphics bitmap context backed by a Rust-owned pixel buffer. 132 + /// 133 + /// The pixel buffer is BGRA, 8 bits per component, premultiplied alpha. 134 + /// The buffer is owned by this struct and the `CGBitmapContext` renders into it. 135 + pub struct BitmapContext { 136 + context: NonNull<c_void>, 137 + buffer: Vec<u8>, 138 + width: usize, 139 + height: usize, 140 + } 141 + 142 + impl BitmapContext { 143 + /// Create a new bitmap context with the given dimensions. 144 + /// 145 + /// Allocates a Rust-owned pixel buffer and wraps it in a CGBitmapContext. 146 + /// Pixel format: 4 bytes per pixel (BGRA, premultiplied alpha). 147 + pub fn new(width: usize, height: usize) -> Option<BitmapContext> { 148 + if width == 0 || height == 0 { 149 + return None; 150 + } 151 + 152 + let bytes_per_row = width * 4; 153 + let mut buffer = vec![0u8; bytes_per_row * height]; 154 + let color_space = ColorSpace::device_rgb()?; 155 + 156 + let ctx = unsafe { 157 + CGBitmapContextCreate( 158 + buffer.as_mut_ptr() as *mut c_void, 159 + width, 160 + height, 161 + 8, // bits per component 162 + bytes_per_row, 163 + color_space.as_ptr(), 164 + BITMAP_INFO_PREMULTIPLIED_FIRST, 165 + ) 166 + }; 167 + 168 + let context = NonNull::new(ctx)?; 169 + Some(BitmapContext { 170 + context, 171 + buffer, 172 + width, 173 + height, 174 + }) 175 + } 176 + 177 + /// Get the width in pixels. 178 + #[inline] 179 + pub fn width(&self) -> usize { 180 + self.width 181 + } 182 + 183 + /// Get the height in pixels. 184 + #[inline] 185 + pub fn height(&self) -> usize { 186 + self.height 187 + } 188 + 189 + /// Get the bytes per row (stride). 190 + #[inline] 191 + pub fn bytes_per_row(&self) -> usize { 192 + self.width * 4 193 + } 194 + 195 + /// Get a shared reference to the raw pixel buffer. 196 + /// 197 + /// Pixel format: BGRA, 8 bits per component, premultiplied alpha. 198 + /// Layout: row-major, bottom-to-top (CG default coordinate system). 199 + #[inline] 200 + pub fn pixels(&self) -> &[u8] { 201 + &self.buffer 202 + } 203 + 204 + /// Get a mutable reference to the raw pixel buffer. 205 + /// 206 + /// After modifying pixels directly, changes are immediately visible to 207 + /// CoreGraphics since the bitmap context wraps this buffer. 208 + #[inline] 209 + pub fn pixels_mut(&mut self) -> &mut [u8] { 210 + &mut self.buffer 211 + } 212 + 213 + /// Create a `CGImage` from the current bitmap context contents. 214 + /// 215 + /// Returns `None` if the image cannot be created. 216 + pub fn create_image(&self) -> Option<Image> { 217 + let img = unsafe { CGBitmapContextCreateImage(self.context.as_ptr()) }; 218 + NonNull::new(img).map(Image) 219 + } 220 + 221 + /// Fill a rectangle with an RGBA color using CoreGraphics. 222 + /// 223 + /// Color components are in 0.0..=1.0 range. 224 + pub fn fill_rect(&self, rect: CGRect, r: f64, g: f64, b: f64, a: f64) { 225 + unsafe { 226 + CGContextSetRGBFillColor(self.context.as_ptr(), r, g, b, a); 227 + CGContextFillRect(self.context.as_ptr(), rect); 228 + } 229 + } 230 + 231 + /// Clear the entire buffer to a given RGBA color. 232 + /// 233 + /// Color components are in 0.0..=1.0 range. This uses CoreGraphics 234 + /// `CGContextFillRect` to fill the entire bitmap. 235 + pub fn clear(&self, r: f64, g: f64, b: f64, a: f64) { 236 + let rect = CGRect::new(0.0, 0.0, self.width as f64, self.height as f64); 237 + self.fill_rect(rect, r, g, b, a); 238 + } 239 + 240 + /// Return the raw CGContextRef pointer. 241 + /// 242 + /// Useful for passing to other CG drawing functions. 243 + #[inline] 244 + pub fn as_ptr(&self) -> *mut c_void { 245 + self.context.as_ptr() 246 + } 247 + } 248 + 249 + impl Drop for BitmapContext { 250 + fn drop(&mut self) { 251 + unsafe { CGContextRelease(self.context.as_ptr()) } 252 + } 253 + } 254 + 255 + // --------------------------------------------------------------------------- 256 + // Image — an owned CGImage 257 + // --------------------------------------------------------------------------- 258 + 259 + /// An owned CoreGraphics image (CGImageRef). 260 + /// 261 + /// Created from a [`BitmapContext`] via [`BitmapContext::create_image`]. 262 + pub struct Image(NonNull<c_void>); 263 + 264 + impl Image { 265 + /// Return the raw CGImageRef pointer. 266 + #[inline] 267 + pub fn as_ptr(&self) -> *mut c_void { 268 + self.0.as_ptr() 269 + } 270 + } 271 + 272 + impl Drop for Image { 273 + fn drop(&mut self) { 274 + unsafe { CGImageRelease(self.as_ptr()) } 275 + } 276 + } 277 + 278 + // --------------------------------------------------------------------------- 279 + // Drawing a CGImage into a CGContext 280 + // --------------------------------------------------------------------------- 281 + 282 + /// Draw a `CGImage` into a CoreGraphics context at the given rectangle. 283 + /// 284 + /// This is used in `drawRect:` to blit the bitmap into the view's context. 285 + /// 286 + /// # Safety 287 + /// 288 + /// `target_context` must be a valid CGContextRef (e.g., from 289 + /// `NSGraphicsContext.currentContext.CGContext`). 290 + pub unsafe fn draw_image_in_context(target_context: *mut c_void, rect: CGRect, image: &Image) { 291 + CGContextDrawImage(target_context, rect, image.as_ptr()); 292 + } 293 + 294 + // --------------------------------------------------------------------------- 295 + // Tests 296 + // --------------------------------------------------------------------------- 297 + 298 + #[cfg(test)] 299 + mod tests { 300 + use super::*; 301 + 302 + #[test] 303 + fn cgrect_new() { 304 + let rect = CGRect::new(10.0, 20.0, 300.0, 400.0); 305 + assert_eq!(rect.origin.x, 10.0); 306 + assert_eq!(rect.origin.y, 20.0); 307 + assert_eq!(rect.size.width, 300.0); 308 + assert_eq!(rect.size.height, 400.0); 309 + } 310 + 311 + #[test] 312 + fn bitmap_info_constant() { 313 + assert_eq!(BITMAP_INFO_PREMULTIPLIED_FIRST, 2); 314 + } 315 + 316 + #[test] 317 + fn create_color_space() { 318 + let cs = ColorSpace::device_rgb().expect("should create device RGB color space"); 319 + assert!(!cs.as_ptr().is_null()); 320 + } 321 + 322 + #[test] 323 + fn create_bitmap_context() { 324 + let ctx = BitmapContext::new(100, 100).expect("should create bitmap context"); 325 + assert_eq!(ctx.width(), 100); 326 + assert_eq!(ctx.height(), 100); 327 + assert_eq!(ctx.bytes_per_row(), 400); 328 + assert_eq!(ctx.pixels().len(), 100 * 400); 329 + } 330 + 331 + #[test] 332 + fn bitmap_context_zero_size_returns_none() { 333 + assert!(BitmapContext::new(0, 100).is_none()); 334 + assert!(BitmapContext::new(100, 0).is_none()); 335 + assert!(BitmapContext::new(0, 0).is_none()); 336 + } 337 + 338 + #[test] 339 + fn bitmap_context_pixels_mut() { 340 + let mut ctx = BitmapContext::new(10, 10).expect("should create"); 341 + let pixels = ctx.pixels_mut(); 342 + // Write a red pixel at (0, 0): BGRA format 343 + pixels[0] = 0; // B 344 + pixels[1] = 0; // G 345 + pixels[2] = 255; // R 346 + pixels[3] = 255; // A 347 + assert_eq!(ctx.pixels()[0], 0); 348 + assert_eq!(ctx.pixels()[2], 255); 349 + } 350 + 351 + #[test] 352 + fn bitmap_context_clear_and_create_image() { 353 + let ctx = BitmapContext::new(50, 50).expect("should create"); 354 + ctx.clear(0.0, 0.0, 1.0, 1.0); // blue 355 + let img = ctx.create_image().expect("should create image"); 356 + assert!(!img.as_ptr().is_null()); 357 + } 358 + 359 + #[test] 360 + fn bitmap_context_fill_rect() { 361 + let ctx = BitmapContext::new(100, 100).expect("should create"); 362 + let rect = CGRect::new(10.0, 10.0, 50.0, 50.0); 363 + ctx.fill_rect(rect, 1.0, 0.0, 0.0, 1.0); // red rectangle 364 + let img = ctx 365 + .create_image() 366 + .expect("should create image from filled context"); 367 + assert!(!img.as_ptr().is_null()); 368 + } 369 + }
+1
crates/platform/src/lib.rs
··· 2 2 3 3 pub mod appkit; 4 4 pub mod cf; 5 + pub mod cg; 5 6 pub mod objc;
+60 -1
crates/platform/src/objc.rs
··· 10 10 11 11 use std::ffi::CStr; 12 12 use std::fmt; 13 - use std::os::raw::c_char; 13 + use std::os::raw::{c_char, c_void}; 14 14 use std::ptr::NonNull; 15 15 16 16 // --------------------------------------------------------------------------- ··· 58 58 types: *const c_char, 59 59 ) -> bool; 60 60 fn class_getName(cls: *mut ObjcClass) -> *const c_char; 61 + fn class_addIvar( 62 + cls: *mut ObjcClass, 63 + name: *const c_char, 64 + size: usize, 65 + alignment: u8, 66 + types: *const c_char, 67 + ) -> bool; 68 + 69 + fn object_setInstanceVariable( 70 + obj: *mut ObjcObject, 71 + name: *const c_char, 72 + value: *mut c_void, 73 + ) -> *mut c_void; // returns Ivar (opaque, we don't use it) 74 + fn object_getInstanceVariable( 75 + obj: *mut ObjcObject, 76 + name: *const c_char, 77 + out_value: *mut *mut c_void, 78 + ) -> *mut c_void; // returns Ivar 61 79 62 80 // objc_msgSend has a variadic ABI. On AArch64 the calling convention is 63 81 // the standard C calling convention (arguments in x0..x7, return in x0). ··· 174 192 unsafe { class_addMethod(self.as_ptr(), sel.as_ptr(), imp, types.as_ptr()) } 175 193 } 176 194 195 + /// Add an instance variable to a class that has not yet been registered. 196 + /// 197 + /// `name` is the ivar name, `size` is the size in bytes, `alignment` is 198 + /// the log2 alignment (e.g., 3 for 8-byte alignment), and `types` is the 199 + /// ObjC type encoding string. 200 + /// 201 + /// Returns `true` if the ivar was added. 202 + pub fn add_ivar(self, name: &CStr, size: usize, alignment: u8, types: &CStr) -> bool { 203 + // SAFETY: the class pointer is valid and not yet registered. 204 + unsafe { 205 + class_addIvar( 206 + self.as_ptr(), 207 + name.as_ptr(), 208 + size, 209 + alignment, 210 + types.as_ptr(), 211 + ) 212 + } 213 + } 214 + 177 215 /// Get the name of this class. 178 216 pub fn name(self) -> &'static str { 179 217 // SAFETY: class_getName always returns a valid C string for a valid ··· 247 285 #[inline] 248 286 pub unsafe fn cast<T>(self) -> *mut T { 249 287 self.0.as_ptr().cast() 288 + } 289 + 290 + /// Set an instance variable by name. 291 + /// 292 + /// # Safety 293 + /// 294 + /// The object must have an ivar with the given `name` of pointer type. 295 + /// The caller is responsible for the validity and lifetime of `value`. 296 + pub unsafe fn set_ivar(self, name: &CStr, value: *mut c_void) { 297 + object_setInstanceVariable(self.as_ptr(), name.as_ptr(), value); 298 + } 299 + 300 + /// Get an instance variable by name. 301 + /// 302 + /// # Safety 303 + /// 304 + /// The object must have an ivar with the given `name` of pointer type. 305 + pub unsafe fn get_ivar(self, name: &CStr) -> *mut c_void { 306 + let mut out: *mut c_void = std::ptr::null_mut(); 307 + object_getInstanceVariable(self.as_ptr(), name.as_ptr(), &mut out); 308 + out 250 309 } 251 310 } 252 311