we (web engine): Experimental web browser project to understand the limits of Claude
at main 830 lines 29 kB view raw
1//! AppKit FFI bindings for macOS window creation. 2//! 3//! Provides wrappers around NSApplication, NSWindow, NSAutoreleasePool, and 4//! NSView for opening native macOS windows. 5//! 6//! # Safety 7//! 8//! This module contains `unsafe` code for FFI with AppKit. 9//! The `platform` crate is one of the few crates where `unsafe` is permitted. 10 11use crate::cf::CfString; 12use crate::cg::{self, BitmapContext, CGRect}; 13use crate::objc::{Class, Id, Imp, Sel}; 14use crate::{class, msg_send}; 15use std::ffi::CStr; 16use std::os::raw::{c_char, c_void}; 17 18// --------------------------------------------------------------------------- 19// AppKit framework link 20// --------------------------------------------------------------------------- 21 22#[link(name = "AppKit", kind = "framework")] 23extern "C" {} 24 25// --------------------------------------------------------------------------- 26// Geometry types matching AppKit's expectations 27// --------------------------------------------------------------------------- 28 29/// `NSRect` / `CGRect` — a rectangle defined by origin and size. 30#[repr(C)] 31#[derive(Debug, Clone, Copy)] 32pub struct NSRect { 33 pub origin: NSPoint, 34 pub size: NSSize, 35} 36 37/// `NSPoint` / `CGPoint` — a point in 2D space. 38#[repr(C)] 39#[derive(Debug, Clone, Copy)] 40pub struct NSPoint { 41 pub x: f64, 42 pub y: f64, 43} 44 45/// `NSSize` / `CGSize` — a 2D size. 46#[repr(C)] 47#[derive(Debug, Clone, Copy)] 48pub struct NSSize { 49 pub width: f64, 50 pub height: f64, 51} 52 53impl NSRect { 54 /// Create a new rectangle. 55 pub fn new(x: f64, y: f64, width: f64, height: f64) -> NSRect { 56 NSRect { 57 origin: NSPoint { x, y }, 58 size: NSSize { width, height }, 59 } 60 } 61} 62 63// --------------------------------------------------------------------------- 64// NSWindow style mask constants 65// --------------------------------------------------------------------------- 66 67/// Window has a title bar. 68pub const NS_WINDOW_STYLE_MASK_TITLED: u64 = 1 << 0; 69/// Window has a close button. 70pub const NS_WINDOW_STYLE_MASK_CLOSABLE: u64 = 1 << 1; 71/// Window can be minimized. 72pub const NS_WINDOW_STYLE_MASK_MINIATURIZABLE: u64 = 1 << 2; 73/// Window can be resized. 74pub const NS_WINDOW_STYLE_MASK_RESIZABLE: u64 = 1 << 3; 75 76// --------------------------------------------------------------------------- 77// NSBackingStoreType constants 78// --------------------------------------------------------------------------- 79 80/// Buffered backing store (the standard for modern macOS). 81pub const NS_BACKING_STORE_BUFFERED: u64 = 2; 82 83// --------------------------------------------------------------------------- 84// NSApplicationActivationPolicy constants 85// --------------------------------------------------------------------------- 86 87/// Regular application that appears in the Dock and may have a menu bar. 88pub const NS_APPLICATION_ACTIVATION_POLICY_REGULAR: i64 = 0; 89 90// --------------------------------------------------------------------------- 91// NSAutoreleasePool 92// --------------------------------------------------------------------------- 93 94/// RAII wrapper for `NSAutoreleasePool`. 95/// 96/// Creates a pool on construction and drains it on drop. Required for any 97/// Objective-C code that creates autoreleased objects. 98pub struct AutoreleasePool { 99 pool: Id, 100} 101 102impl Default for AutoreleasePool { 103 fn default() -> Self { 104 Self::new() 105 } 106} 107 108impl AutoreleasePool { 109 /// Create a new autorelease pool. 110 pub fn new() -> AutoreleasePool { 111 let cls = class!("NSAutoreleasePool").expect("NSAutoreleasePool class not found"); 112 let pool: *mut c_void = msg_send![cls.as_ptr(), alloc]; 113 let pool: *mut c_void = msg_send![pool, init]; 114 let pool = unsafe { Id::from_raw(pool as *mut _) }.expect("NSAutoreleasePool init failed"); 115 AutoreleasePool { pool } 116 } 117} 118 119impl Drop for AutoreleasePool { 120 fn drop(&mut self) { 121 let _: *mut c_void = msg_send![self.pool.as_ptr(), drain]; 122 } 123} 124 125// --------------------------------------------------------------------------- 126// NSApplication wrapper 127// --------------------------------------------------------------------------- 128 129/// Wrapper around `NSApplication`. 130pub struct App { 131 app: Id, 132} 133 134impl App { 135 /// Get the shared `NSApplication` instance. 136 /// 137 /// Must be called from the main thread. Creates the application object 138 /// if it doesn't already exist. 139 pub fn shared() -> App { 140 let cls = class!("NSApplication").expect("NSApplication class not found"); 141 let app: *mut c_void = msg_send![cls.as_ptr(), sharedApplication]; 142 let app = unsafe { Id::from_raw(app as *mut _) }.expect("sharedApplication returned nil"); 143 App { app } 144 } 145 146 /// Set the application's activation policy. 147 /// 148 /// Use [`NS_APPLICATION_ACTIVATION_POLICY_REGULAR`] for a normal app that 149 /// appears in the Dock. 150 pub fn set_activation_policy(&self, policy: i64) { 151 let _: bool = msg_send![self.app.as_ptr(), setActivationPolicy: policy]; 152 } 153 154 /// Activate the application, bringing it to the foreground. 155 pub fn activate(&self) { 156 let _: *mut c_void = msg_send![self.app.as_ptr(), activateIgnoringOtherApps: true]; 157 } 158 159 /// Start the application's main event loop. 160 /// 161 /// This method does **not** return under normal circumstances. 162 pub fn run(&self) { 163 let _: *mut c_void = msg_send![self.app.as_ptr(), run]; 164 } 165 166 /// Return the underlying Objective-C object. 167 pub fn id(&self) -> Id { 168 self.app 169 } 170} 171 172// --------------------------------------------------------------------------- 173// NSWindow wrapper 174// --------------------------------------------------------------------------- 175 176/// Wrapper around `NSWindow`. 177pub struct Window { 178 window: Id, 179} 180 181impl Window { 182 /// Create a new window with the given content rect, style mask, and backing. 183 /// 184 /// # Arguments 185 /// 186 /// * `rect` — The content rectangle (position and size). 187 /// * `style` — Bitwise OR of `NS_WINDOW_STYLE_MASK_*` constants. 188 /// * `backing` — Backing store type (use [`NS_BACKING_STORE_BUFFERED`]). 189 /// * `defer` — Whether to defer window device creation. 190 pub fn new(rect: NSRect, style: u64, backing: u64, defer: bool) -> Window { 191 let cls = class!("NSWindow").expect("NSWindow class not found"); 192 let window: *mut c_void = msg_send![cls.as_ptr(), alloc]; 193 let window: *mut c_void = msg_send![ 194 window, 195 initWithContentRect: rect, 196 styleMask: style, 197 backing: backing, 198 defer: defer 199 ]; 200 let window = 201 unsafe { Id::from_raw(window as *mut _) }.expect("NSWindow initWithContentRect failed"); 202 Window { window } 203 } 204 205 /// Set the window's title. 206 pub fn set_title(&self, title: &str) { 207 let cf_title = CfString::new(title).expect("failed to create CFString for title"); 208 // CFStringRef is toll-free bridged to NSString*. 209 let _: *mut c_void = msg_send![self.window.as_ptr(), setTitle: cf_title.as_void_ptr()]; 210 } 211 212 /// Make the window the key window and bring it to the front. 213 pub fn make_key_and_order_front(&self) { 214 let _: *mut c_void = 215 msg_send![self.window.as_ptr(), makeKeyAndOrderFront: std::ptr::null::<c_void>()]; 216 } 217 218 /// Get the window's content view. 219 pub fn content_view(&self) -> Id { 220 let view: *mut c_void = msg_send![self.window.as_ptr(), contentView]; 221 unsafe { Id::from_raw(view as *mut _) }.expect("contentView returned nil") 222 } 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 229 /// Set the window's delegate. 230 pub fn set_delegate(&self, delegate: &Id) { 231 let _: *mut c_void = msg_send![self.window.as_ptr(), setDelegate: delegate.as_ptr()]; 232 } 233 234 /// Enable or disable mouse-moved event delivery for this window. 235 /// 236 /// Must be set to `true` for `mouseMoved:` events to reach the view. 237 pub fn set_accepts_mouse_moved_events(&self, accepts: bool) { 238 let _: *mut c_void = msg_send![self.window.as_ptr(), setAcceptsMouseMovedEvents: accepts]; 239 } 240 241 /// Return the underlying Objective-C object. 242 pub fn id(&self) -> Id { 243 self.window 244 } 245} 246 247// --------------------------------------------------------------------------- 248// BitmapView — custom NSView subclass for rendering a CG bitmap 249// --------------------------------------------------------------------------- 250 251/// The name of the ivar storing the BitmapContext pointer in WeView. 252const BITMAP_CTX_IVAR: &CStr = c"_bitmapCtx"; 253 254/// Register the `WeView` custom NSView subclass. 255/// 256/// The class overrides `drawRect:` to blit a CGImage from the associated 257/// BitmapContext into the view's graphics context, and `isFlipped` to use a 258/// top-left coordinate origin. 259fn register_we_view_class() { 260 if class!("WeView").is_some() { 261 return; 262 } 263 264 let superclass = class!("NSView").expect("NSView not found"); 265 let view_class = 266 Class::allocate(superclass, c"WeView", 0).expect("failed to allocate WeView class"); 267 268 // Add an ivar to hold a raw pointer to a BitmapContext. 269 // Size = 8 (pointer), alignment = 3 (log2(8)), type = "^v" (pointer to void). 270 view_class.add_ivar(BITMAP_CTX_IVAR, 8, 3, c"^v"); 271 272 // drawRect: 273 extern "C" fn draw_rect(this: *mut c_void, _sel: *mut c_void, _dirty_rect: NSRect) { 274 let this_id = unsafe { Id::from_raw(this as *mut _) }; 275 let this_id = match this_id { 276 Some(id) => id, 277 None => return, 278 }; 279 280 // Get the BitmapContext pointer from our ivar. 281 let ctx_ptr = unsafe { this_id.get_ivar(BITMAP_CTX_IVAR) }; 282 if ctx_ptr.is_null() { 283 return; 284 } 285 let bitmap_ctx = unsafe { &*(ctx_ptr as *const BitmapContext) }; 286 287 // Create a CGImage from the bitmap context. 288 let image = match bitmap_ctx.create_image() { 289 Some(img) => img, 290 None => return, 291 }; 292 293 // Get the view's bounds: [self bounds] 294 let bounds: NSRect = msg_send![this, bounds]; 295 296 // Get the current NSGraphicsContext: 297 // [NSGraphicsContext currentContext] 298 let gfx_ctx_cls = class!("NSGraphicsContext").expect("NSGraphicsContext not found"); 299 let gfx_ctx: *mut c_void = msg_send![gfx_ctx_cls.as_ptr(), currentContext]; 300 if gfx_ctx.is_null() { 301 return; 302 } 303 304 // Get the CGContextRef: [gfxCtx CGContext] 305 let cg_context: *mut c_void = msg_send![gfx_ctx, CGContext]; 306 if cg_context.is_null() { 307 return; 308 } 309 310 // Draw the image into the CG context. 311 let rect = CGRect::new( 312 bounds.origin.x, 313 bounds.origin.y, 314 bounds.size.width, 315 bounds.size.height, 316 ); 317 unsafe { 318 cg::draw_image_in_context(cg_context, rect, &image); 319 } 320 } 321 322 let sel = Sel::register(c"drawRect:"); 323 view_class.add_method( 324 sel, 325 unsafe { std::mem::transmute::<*const (), Imp>(draw_rect as *const ()) }, 326 c"v@:{CGRect={CGPoint=dd}{CGSize=dd}}", 327 ); 328 329 // isFlipped -> YES (top-left origin) 330 extern "C" fn is_flipped(_this: *mut c_void, _sel: *mut c_void) -> bool { 331 true 332 } 333 334 let sel = Sel::register(c"isFlipped"); 335 view_class.add_method( 336 sel, 337 unsafe { std::mem::transmute::<*const (), Imp>(is_flipped as *const ()) }, 338 c"B@:", 339 ); 340 341 // acceptsFirstResponder -> YES (allows view to receive key events) 342 extern "C" fn accepts_first_responder(_this: *mut c_void, _sel: *mut c_void) -> bool { 343 true 344 } 345 346 let sel = Sel::register(c"acceptsFirstResponder"); 347 view_class.add_method( 348 sel, 349 unsafe { std::mem::transmute::<*const (), Imp>(accepts_first_responder as *const ()) }, 350 c"B@:", 351 ); 352 353 // keyDown: — log key character and keyCode to stdout 354 extern "C" fn key_down(_this: *mut c_void, _sel: *mut c_void, event: *mut c_void) { 355 let chars: *mut c_void = msg_send![event, characters]; 356 if chars.is_null() { 357 return; 358 } 359 let utf8: *const c_char = msg_send![chars, UTF8String]; 360 if utf8.is_null() { 361 return; 362 } 363 let c_str = unsafe { CStr::from_ptr(utf8) }; 364 let key_code: u16 = msg_send![event, keyCode]; 365 if let Ok(s) = c_str.to_str() { 366 println!("keyDown: '{}' (keyCode: {})", s, key_code); 367 } 368 } 369 370 let sel = Sel::register(c"keyDown:"); 371 view_class.add_method( 372 sel, 373 unsafe { std::mem::transmute::<*const (), Imp>(key_down as *const ()) }, 374 c"v@:@", 375 ); 376 377 // mouseDown: — log mouse location to stdout 378 extern "C" fn mouse_down(this: *mut c_void, _sel: *mut c_void, event: *mut c_void) { 379 let raw_loc: NSPoint = msg_send![event, locationInWindow]; 380 let loc: NSPoint = 381 msg_send![this, convertPoint: raw_loc, fromView: std::ptr::null_mut::<c_void>()]; 382 println!("mouseDown: ({:.1}, {:.1})", loc.x, loc.y); 383 } 384 385 let sel = Sel::register(c"mouseDown:"); 386 view_class.add_method( 387 sel, 388 unsafe { std::mem::transmute::<*const (), Imp>(mouse_down as *const ()) }, 389 c"v@:@", 390 ); 391 392 // mouseUp: — log mouse location to stdout 393 extern "C" fn mouse_up(this: *mut c_void, _sel: *mut c_void, event: *mut c_void) { 394 let raw_loc: NSPoint = msg_send![event, locationInWindow]; 395 let loc: NSPoint = 396 msg_send![this, convertPoint: raw_loc, fromView: std::ptr::null_mut::<c_void>()]; 397 println!("mouseUp: ({:.1}, {:.1})", loc.x, loc.y); 398 } 399 400 let sel = Sel::register(c"mouseUp:"); 401 view_class.add_method( 402 sel, 403 unsafe { std::mem::transmute::<*const (), Imp>(mouse_up as *const ()) }, 404 c"v@:@", 405 ); 406 407 // mouseMoved: — log mouse location to stdout 408 extern "C" fn mouse_moved(this: *mut c_void, _sel: *mut c_void, event: *mut c_void) { 409 let raw_loc: NSPoint = msg_send![event, locationInWindow]; 410 let loc: NSPoint = 411 msg_send![this, convertPoint: raw_loc, fromView: std::ptr::null_mut::<c_void>()]; 412 println!("mouseMoved: ({:.1}, {:.1})", loc.x, loc.y); 413 } 414 415 let sel = Sel::register(c"mouseMoved:"); 416 view_class.add_method( 417 sel, 418 unsafe { std::mem::transmute::<*const (), Imp>(mouse_moved as *const ()) }, 419 c"v@:@", 420 ); 421 422 // scrollWheel: — call scroll handler with delta and mouse location 423 extern "C" fn scroll_wheel(this: *mut c_void, _sel: *mut c_void, event: *mut c_void) { 424 let dx: f64 = msg_send![event, scrollingDeltaX]; 425 let dy: f64 = msg_send![event, scrollingDeltaY]; 426 let raw_loc: NSPoint = msg_send![event, locationInWindow]; 427 let loc: NSPoint = 428 msg_send![this, convertPoint: raw_loc, fromView: std::ptr::null_mut::<c_void>()]; 429 // SAFETY: We are on the main thread (AppKit event loop). 430 unsafe { 431 if let Some(handler) = SCROLL_HANDLER { 432 handler(dx, dy, loc.x, loc.y); 433 } 434 } 435 } 436 437 let sel = Sel::register(c"scrollWheel:"); 438 view_class.add_method( 439 sel, 440 unsafe { std::mem::transmute::<*const (), Imp>(scroll_wheel as *const ()) }, 441 c"v@:@", 442 ); 443 444 view_class.register(); 445} 446 447/// A custom NSView backed by a [`BitmapContext`]. 448/// 449/// When the view needs to draw, it creates a `CGImage` from the bitmap 450/// context and blits it into the view's graphics context. 451/// 452/// The `BitmapContext` must outlive this view. The caller is responsible 453/// for ensuring this (typically by keeping the context in a `Box` alongside 454/// the view). 455pub struct BitmapView { 456 view: Id, 457} 458 459impl BitmapView { 460 /// Create a new `BitmapView` with the given frame and bitmap context. 461 /// 462 /// The `bitmap_ctx` pointer is stored in the view's instance variable. 463 /// The caller must ensure the `BitmapContext` outlives this view. 464 pub fn new(frame: NSRect, bitmap_ctx: &BitmapContext) -> BitmapView { 465 register_we_view_class(); 466 467 let cls = class!("WeView").expect("WeView class not found"); 468 let view: *mut c_void = msg_send![cls.as_ptr(), alloc]; 469 let view: *mut c_void = msg_send![view, initWithFrame: frame]; 470 let view = unsafe { Id::from_raw(view as *mut _) }.expect("WeView initWithFrame failed"); 471 472 // Store the BitmapContext pointer in the ivar. 473 unsafe { 474 view.set_ivar( 475 BITMAP_CTX_IVAR, 476 bitmap_ctx as *const BitmapContext as *mut c_void, 477 ); 478 } 479 480 BitmapView { view } 481 } 482 483 /// Update the bitmap context pointer stored in the view. 484 /// 485 /// Call this when the bitmap context has been replaced (e.g., on resize). 486 /// The new `BitmapContext` must outlive this view. 487 pub fn update_bitmap(&self, bitmap_ctx: &BitmapContext) { 488 unsafe { 489 self.view.set_ivar( 490 BITMAP_CTX_IVAR, 491 bitmap_ctx as *const BitmapContext as *mut c_void, 492 ); 493 } 494 } 495 496 /// Request the view to redraw. 497 /// 498 /// Call this after modifying the bitmap context's pixels to 499 /// schedule a redraw. 500 pub fn set_needs_display(&self) { 501 let _: *mut c_void = msg_send![self.view.as_ptr(), setNeedsDisplay: true]; 502 } 503 504 /// Return the underlying Objective-C view object. 505 pub fn id(&self) -> Id { 506 self.view 507 } 508} 509 510// --------------------------------------------------------------------------- 511// Global resize handler 512// --------------------------------------------------------------------------- 513 514/// Global resize callback, called from `windowDidResize:` with the new 515/// content view dimensions (width, height) in points. 516/// 517/// # Safety 518/// 519/// Accessed only from the main thread (the AppKit event loop). 520static mut RESIZE_HANDLER: Option<fn(f64, f64)> = None; 521 522/// Register a function to be called when the window is resized. 523/// 524/// The handler receives the new content view width and height in points. 525/// Only one handler can be active at a time; setting a new one replaces 526/// any previous handler. 527pub fn set_resize_handler(handler: fn(f64, f64)) { 528 // SAFETY: Called from the main thread before `app.run()`. 529 unsafe { 530 RESIZE_HANDLER = Some(handler); 531 } 532} 533 534/// Global scroll callback, called from `scrollWheel:` with the scroll 535/// deltas (dx, dy) and mouse location (x, y) in view coordinates. 536/// 537/// # Safety 538/// 539/// Accessed only from the main thread (the AppKit event loop). 540static mut SCROLL_HANDLER: Option<fn(f64, f64, f64, f64)> = None; 541 542/// Register a function to be called when a scroll wheel event occurs. 543/// 544/// The handler receives `(delta_x, delta_y, mouse_x, mouse_y)`. 545/// Only one handler can be active at a time. 546pub fn set_scroll_handler(handler: fn(f64, f64, f64, f64)) { 547 // SAFETY: Called from the main thread before `app.run()`. 548 unsafe { 549 SCROLL_HANDLER = Some(handler); 550 } 551} 552 553// --------------------------------------------------------------------------- 554// Window delegate for handling resize and close events 555// --------------------------------------------------------------------------- 556 557/// Register the `WeWindowDelegate` class if not already registered. 558/// 559/// The class implements: 560/// - `windowDidResize:` — marks the content view as needing display 561/// - `windowShouldClose:` — returns YES (allows closing) 562fn register_we_window_delegate_class() { 563 if class!("WeWindowDelegate").is_some() { 564 return; 565 } 566 567 let superclass = class!("NSObject").expect("NSObject not found"); 568 let delegate_class = Class::allocate(superclass, c"WeWindowDelegate", 0) 569 .expect("failed to allocate WeWindowDelegate class"); 570 571 // windowDidResize: — call resize handler and mark view as needing display 572 extern "C" fn window_did_resize( 573 _this: *mut c_void, 574 _sel: *mut c_void, 575 notification: *mut c_void, 576 ) { 577 let win: *mut c_void = msg_send![notification, object]; 578 if win.is_null() { 579 return; 580 } 581 let content_view: *mut c_void = msg_send![win, contentView]; 582 if content_view.is_null() { 583 return; 584 } 585 // Get the content view's bounds to determine new dimensions. 586 let bounds: NSRect = msg_send![content_view, bounds]; 587 // Call the resize handler if one has been registered. 588 // SAFETY: We are on the main thread (AppKit event loop). 589 unsafe { 590 if let Some(handler) = RESIZE_HANDLER { 591 handler(bounds.size.width, bounds.size.height); 592 } 593 } 594 let _: *mut c_void = msg_send![content_view, setNeedsDisplay: true]; 595 } 596 597 let sel = Sel::register(c"windowDidResize:"); 598 delegate_class.add_method( 599 sel, 600 unsafe { std::mem::transmute::<*const (), Imp>(window_did_resize as *const ()) }, 601 c"v@:@", 602 ); 603 604 // windowShouldClose: -> YES 605 extern "C" fn window_should_close( 606 _this: *mut c_void, 607 _sel: *mut c_void, 608 _sender: *mut c_void, 609 ) -> bool { 610 true 611 } 612 613 let sel = Sel::register(c"windowShouldClose:"); 614 delegate_class.add_method( 615 sel, 616 unsafe { std::mem::transmute::<*const (), Imp>(window_should_close as *const ()) }, 617 c"B@:@", 618 ); 619 620 delegate_class.register(); 621} 622 623/// Install a window delegate that handles resize and close events. 624/// 625/// Creates a `WeWindowDelegate` class (if not already registered) and sets 626/// an instance as the window's delegate. The app delegate then terminates 627/// the app when the last window closes. 628pub fn install_window_delegate(window: &Window) { 629 register_we_window_delegate_class(); 630 631 let cls = class!("WeWindowDelegate").expect("WeWindowDelegate not found"); 632 let delegate: *mut c_void = msg_send![cls.as_ptr(), alloc]; 633 let delegate: *mut c_void = msg_send![delegate, init]; 634 let delegate = 635 unsafe { Id::from_raw(delegate as *mut _) }.expect("WeWindowDelegate init failed"); 636 window.set_delegate(&delegate); 637} 638 639// --------------------------------------------------------------------------- 640// App delegate for handling window close -> app termination 641// --------------------------------------------------------------------------- 642 643/// Install an application delegate that terminates the app when the last 644/// window is closed. 645/// 646/// This creates a custom Objective-C class `WeAppDelegate` that implements 647/// `applicationShouldTerminateAfterLastWindowClosed:` returning `YES`. 648pub fn install_app_delegate(app: &App) { 649 // Only register the delegate class once. 650 if class!("WeAppDelegate").is_some() { 651 // Already registered, just create an instance and set it. 652 set_delegate(app); 653 return; 654 } 655 656 let superclass = class!("NSObject").expect("NSObject not found"); 657 let delegate_class = Class::allocate(superclass, c"WeAppDelegate", 0) 658 .expect("failed to allocate WeAppDelegate class"); 659 660 // applicationShouldTerminateAfterLastWindowClosed: 661 extern "C" fn should_terminate_after_last_window_closed( 662 _this: *mut c_void, 663 _sel: *mut c_void, 664 _app: *mut c_void, 665 ) -> bool { 666 true 667 } 668 669 let sel = Sel::register(c"applicationShouldTerminateAfterLastWindowClosed:"); 670 delegate_class.add_method( 671 sel, 672 unsafe { 673 std::mem::transmute::<*const (), Imp>( 674 should_terminate_after_last_window_closed as *const (), 675 ) 676 }, 677 c"B@:@", 678 ); 679 680 delegate_class.register(); 681 set_delegate(app); 682} 683 684fn set_delegate(app: &App) { 685 let cls = class!("WeAppDelegate").expect("WeAppDelegate not found"); 686 let delegate: *mut c_void = msg_send![cls.as_ptr(), alloc]; 687 let delegate: *mut c_void = msg_send![delegate, init]; 688 let _: *mut c_void = msg_send![app.id().as_ptr(), setDelegate: delegate]; 689} 690 691// --------------------------------------------------------------------------- 692// Convenience: create a standard browser window 693// --------------------------------------------------------------------------- 694 695/// Create a standard window suitable for a browser. 696/// 697/// Returns a window with title bar, close, minimize, and resize controls, 698/// centered at (200, 200), sized 800x600. 699pub fn create_standard_window(title: &str) -> Window { 700 let style = NS_WINDOW_STYLE_MASK_TITLED 701 | NS_WINDOW_STYLE_MASK_CLOSABLE 702 | NS_WINDOW_STYLE_MASK_MINIATURIZABLE 703 | NS_WINDOW_STYLE_MASK_RESIZABLE; 704 705 let rect = NSRect::new(200.0, 200.0, 800.0, 600.0); 706 let window = Window::new(rect, style, NS_BACKING_STORE_BUFFERED, false); 707 window.set_title(title); 708 window 709} 710 711// --------------------------------------------------------------------------- 712// Tests 713// --------------------------------------------------------------------------- 714 715#[cfg(test)] 716mod tests { 717 use super::*; 718 719 #[test] 720 fn nsrect_new() { 721 let rect = NSRect::new(10.0, 20.0, 300.0, 400.0); 722 assert_eq!(rect.origin.x, 10.0); 723 assert_eq!(rect.origin.y, 20.0); 724 assert_eq!(rect.size.width, 300.0); 725 assert_eq!(rect.size.height, 400.0); 726 } 727 728 #[test] 729 fn style_mask_constants() { 730 // Verify the constants match AppKit's expected values. 731 assert_eq!(NS_WINDOW_STYLE_MASK_TITLED, 1); 732 assert_eq!(NS_WINDOW_STYLE_MASK_CLOSABLE, 2); 733 assert_eq!(NS_WINDOW_STYLE_MASK_MINIATURIZABLE, 4); 734 assert_eq!(NS_WINDOW_STYLE_MASK_RESIZABLE, 8); 735 } 736 737 #[test] 738 fn backing_store_constant() { 739 assert_eq!(NS_BACKING_STORE_BUFFERED, 2); 740 } 741 742 #[test] 743 fn activation_policy_constant() { 744 assert_eq!(NS_APPLICATION_ACTIVATION_POLICY_REGULAR, 0); 745 } 746 747 #[test] 748 fn autorelease_pool_create_and_drop() { 749 // Creating and dropping an autorelease pool should not crash. 750 let _pool = AutoreleasePool::new(); 751 } 752 753 #[test] 754 fn combined_style_mask() { 755 let style = NS_WINDOW_STYLE_MASK_TITLED 756 | NS_WINDOW_STYLE_MASK_CLOSABLE 757 | NS_WINDOW_STYLE_MASK_MINIATURIZABLE 758 | NS_WINDOW_STYLE_MASK_RESIZABLE; 759 assert_eq!(style, 0b1111); 760 } 761 762 #[test] 763 fn we_view_class_registration() { 764 register_we_view_class(); 765 let cls = class!("WeView"); 766 assert!(cls.is_some(), "WeView class should be registered"); 767 } 768 769 #[test] 770 fn bitmap_view_create() { 771 let _pool = AutoreleasePool::new(); 772 let bitmap = BitmapContext::new(100, 100).expect("should create bitmap context"); 773 let frame = NSRect::new(0.0, 0.0, 100.0, 100.0); 774 let view = BitmapView::new(frame, &bitmap); 775 assert!(!view.id().as_ptr().is_null()); 776 } 777 778 #[test] 779 fn we_view_accepts_first_responder() { 780 let _pool = AutoreleasePool::new(); 781 let bitmap = BitmapContext::new(100, 100).expect("should create bitmap context"); 782 let frame = NSRect::new(0.0, 0.0, 100.0, 100.0); 783 let view = BitmapView::new(frame, &bitmap); 784 let accepts: bool = msg_send![view.id().as_ptr(), acceptsFirstResponder]; 785 assert!(accepts, "WeView should accept first responder"); 786 } 787 788 #[test] 789 fn we_view_responds_to_key_down() { 790 let _pool = AutoreleasePool::new(); 791 register_we_view_class(); 792 let cls = class!("WeView").expect("WeView should be registered"); 793 let sel = Sel::register(c"keyDown:"); 794 let instances_respond: bool = 795 msg_send![cls.as_ptr(), instancesRespondToSelector: sel.as_ptr()]; 796 assert!(instances_respond, "WeView should respond to keyDown:"); 797 } 798 799 #[test] 800 fn we_view_responds_to_mouse_events() { 801 let _pool = AutoreleasePool::new(); 802 register_we_view_class(); 803 let cls = class!("WeView").expect("WeView should be registered"); 804 805 for sel_name in [c"mouseDown:", c"mouseUp:", c"mouseMoved:"] { 806 let sel = Sel::register(sel_name); 807 let responds: bool = msg_send![cls.as_ptr(), instancesRespondToSelector: sel.as_ptr()]; 808 assert!(responds, "WeView should respond to {:?}", sel_name); 809 } 810 } 811 812 #[test] 813 fn we_window_delegate_class_registration() { 814 register_we_window_delegate_class(); 815 let cls = class!("WeWindowDelegate"); 816 assert!(cls.is_some(), "WeWindowDelegate class should be registered"); 817 } 818 819 #[test] 820 fn we_window_delegate_responds_to_resize() { 821 register_we_window_delegate_class(); 822 let cls = class!("WeWindowDelegate").expect("WeWindowDelegate should be registered"); 823 let sel = Sel::register(c"windowDidResize:"); 824 let responds: bool = msg_send![cls.as_ptr(), instancesRespondToSelector: sel.as_ptr()]; 825 assert!( 826 responds, 827 "WeWindowDelegate should respond to windowDidResize:" 828 ); 829 } 830}