we (web engine): Experimental web browser project to understand the limits of Claude
at gif-decoder 789 lines 28 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 view_class.register(); 423} 424 425/// A custom NSView backed by a [`BitmapContext`]. 426/// 427/// When the view needs to draw, it creates a `CGImage` from the bitmap 428/// context and blits it into the view's graphics context. 429/// 430/// The `BitmapContext` must outlive this view. The caller is responsible 431/// for ensuring this (typically by keeping the context in a `Box` alongside 432/// the view). 433pub struct BitmapView { 434 view: Id, 435} 436 437impl BitmapView { 438 /// Create a new `BitmapView` with the given frame and bitmap context. 439 /// 440 /// The `bitmap_ctx` pointer is stored in the view's instance variable. 441 /// The caller must ensure the `BitmapContext` outlives this view. 442 pub fn new(frame: NSRect, bitmap_ctx: &BitmapContext) -> BitmapView { 443 register_we_view_class(); 444 445 let cls = class!("WeView").expect("WeView class not found"); 446 let view: *mut c_void = msg_send![cls.as_ptr(), alloc]; 447 let view: *mut c_void = msg_send![view, initWithFrame: frame]; 448 let view = unsafe { Id::from_raw(view as *mut _) }.expect("WeView initWithFrame failed"); 449 450 // Store the BitmapContext pointer in the ivar. 451 unsafe { 452 view.set_ivar( 453 BITMAP_CTX_IVAR, 454 bitmap_ctx as *const BitmapContext as *mut c_void, 455 ); 456 } 457 458 BitmapView { view } 459 } 460 461 /// Update the bitmap context pointer stored in the view. 462 /// 463 /// Call this when the bitmap context has been replaced (e.g., on resize). 464 /// The new `BitmapContext` must outlive this view. 465 pub fn update_bitmap(&self, bitmap_ctx: &BitmapContext) { 466 unsafe { 467 self.view.set_ivar( 468 BITMAP_CTX_IVAR, 469 bitmap_ctx as *const BitmapContext as *mut c_void, 470 ); 471 } 472 } 473 474 /// Request the view to redraw. 475 /// 476 /// Call this after modifying the bitmap context's pixels to 477 /// schedule a redraw. 478 pub fn set_needs_display(&self) { 479 let _: *mut c_void = msg_send![self.view.as_ptr(), setNeedsDisplay: true]; 480 } 481 482 /// Return the underlying Objective-C view object. 483 pub fn id(&self) -> Id { 484 self.view 485 } 486} 487 488// --------------------------------------------------------------------------- 489// Global resize handler 490// --------------------------------------------------------------------------- 491 492/// Global resize callback, called from `windowDidResize:` with the new 493/// content view dimensions (width, height) in points. 494/// 495/// # Safety 496/// 497/// Accessed only from the main thread (the AppKit event loop). 498static mut RESIZE_HANDLER: Option<fn(f64, f64)> = None; 499 500/// Register a function to be called when the window is resized. 501/// 502/// The handler receives the new content view width and height in points. 503/// Only one handler can be active at a time; setting a new one replaces 504/// any previous handler. 505pub fn set_resize_handler(handler: fn(f64, f64)) { 506 // SAFETY: Called from the main thread before `app.run()`. 507 unsafe { 508 RESIZE_HANDLER = Some(handler); 509 } 510} 511 512// --------------------------------------------------------------------------- 513// Window delegate for handling resize and close events 514// --------------------------------------------------------------------------- 515 516/// Register the `WeWindowDelegate` class if not already registered. 517/// 518/// The class implements: 519/// - `windowDidResize:` — marks the content view as needing display 520/// - `windowShouldClose:` — returns YES (allows closing) 521fn register_we_window_delegate_class() { 522 if class!("WeWindowDelegate").is_some() { 523 return; 524 } 525 526 let superclass = class!("NSObject").expect("NSObject not found"); 527 let delegate_class = Class::allocate(superclass, c"WeWindowDelegate", 0) 528 .expect("failed to allocate WeWindowDelegate class"); 529 530 // windowDidResize: — call resize handler and mark view as needing display 531 extern "C" fn window_did_resize( 532 _this: *mut c_void, 533 _sel: *mut c_void, 534 notification: *mut c_void, 535 ) { 536 let win: *mut c_void = msg_send![notification, object]; 537 if win.is_null() { 538 return; 539 } 540 let content_view: *mut c_void = msg_send![win, contentView]; 541 if content_view.is_null() { 542 return; 543 } 544 // Get the content view's bounds to determine new dimensions. 545 let bounds: NSRect = msg_send![content_view, bounds]; 546 // Call the resize handler if one has been registered. 547 // SAFETY: We are on the main thread (AppKit event loop). 548 unsafe { 549 if let Some(handler) = RESIZE_HANDLER { 550 handler(bounds.size.width, bounds.size.height); 551 } 552 } 553 let _: *mut c_void = msg_send![content_view, setNeedsDisplay: true]; 554 } 555 556 let sel = Sel::register(c"windowDidResize:"); 557 delegate_class.add_method( 558 sel, 559 unsafe { std::mem::transmute::<*const (), Imp>(window_did_resize as *const ()) }, 560 c"v@:@", 561 ); 562 563 // windowShouldClose: -> YES 564 extern "C" fn window_should_close( 565 _this: *mut c_void, 566 _sel: *mut c_void, 567 _sender: *mut c_void, 568 ) -> bool { 569 true 570 } 571 572 let sel = Sel::register(c"windowShouldClose:"); 573 delegate_class.add_method( 574 sel, 575 unsafe { std::mem::transmute::<*const (), Imp>(window_should_close as *const ()) }, 576 c"B@:@", 577 ); 578 579 delegate_class.register(); 580} 581 582/// Install a window delegate that handles resize and close events. 583/// 584/// Creates a `WeWindowDelegate` class (if not already registered) and sets 585/// an instance as the window's delegate. The app delegate then terminates 586/// the app when the last window closes. 587pub fn install_window_delegate(window: &Window) { 588 register_we_window_delegate_class(); 589 590 let cls = class!("WeWindowDelegate").expect("WeWindowDelegate not found"); 591 let delegate: *mut c_void = msg_send![cls.as_ptr(), alloc]; 592 let delegate: *mut c_void = msg_send![delegate, init]; 593 let delegate = 594 unsafe { Id::from_raw(delegate as *mut _) }.expect("WeWindowDelegate init failed"); 595 window.set_delegate(&delegate); 596} 597 598// --------------------------------------------------------------------------- 599// App delegate for handling window close -> app termination 600// --------------------------------------------------------------------------- 601 602/// Install an application delegate that terminates the app when the last 603/// window is closed. 604/// 605/// This creates a custom Objective-C class `WeAppDelegate` that implements 606/// `applicationShouldTerminateAfterLastWindowClosed:` returning `YES`. 607pub fn install_app_delegate(app: &App) { 608 // Only register the delegate class once. 609 if class!("WeAppDelegate").is_some() { 610 // Already registered, just create an instance and set it. 611 set_delegate(app); 612 return; 613 } 614 615 let superclass = class!("NSObject").expect("NSObject not found"); 616 let delegate_class = Class::allocate(superclass, c"WeAppDelegate", 0) 617 .expect("failed to allocate WeAppDelegate class"); 618 619 // applicationShouldTerminateAfterLastWindowClosed: 620 extern "C" fn should_terminate_after_last_window_closed( 621 _this: *mut c_void, 622 _sel: *mut c_void, 623 _app: *mut c_void, 624 ) -> bool { 625 true 626 } 627 628 let sel = Sel::register(c"applicationShouldTerminateAfterLastWindowClosed:"); 629 delegate_class.add_method( 630 sel, 631 unsafe { 632 std::mem::transmute::<*const (), Imp>( 633 should_terminate_after_last_window_closed as *const (), 634 ) 635 }, 636 c"B@:@", 637 ); 638 639 delegate_class.register(); 640 set_delegate(app); 641} 642 643fn set_delegate(app: &App) { 644 let cls = class!("WeAppDelegate").expect("WeAppDelegate not found"); 645 let delegate: *mut c_void = msg_send![cls.as_ptr(), alloc]; 646 let delegate: *mut c_void = msg_send![delegate, init]; 647 let _: *mut c_void = msg_send![app.id().as_ptr(), setDelegate: delegate]; 648} 649 650// --------------------------------------------------------------------------- 651// Convenience: create a standard browser window 652// --------------------------------------------------------------------------- 653 654/// Create a standard window suitable for a browser. 655/// 656/// Returns a window with title bar, close, minimize, and resize controls, 657/// centered at (200, 200), sized 800x600. 658pub fn create_standard_window(title: &str) -> Window { 659 let style = NS_WINDOW_STYLE_MASK_TITLED 660 | NS_WINDOW_STYLE_MASK_CLOSABLE 661 | NS_WINDOW_STYLE_MASK_MINIATURIZABLE 662 | NS_WINDOW_STYLE_MASK_RESIZABLE; 663 664 let rect = NSRect::new(200.0, 200.0, 800.0, 600.0); 665 let window = Window::new(rect, style, NS_BACKING_STORE_BUFFERED, false); 666 window.set_title(title); 667 window 668} 669 670// --------------------------------------------------------------------------- 671// Tests 672// --------------------------------------------------------------------------- 673 674#[cfg(test)] 675mod tests { 676 use super::*; 677 678 #[test] 679 fn nsrect_new() { 680 let rect = NSRect::new(10.0, 20.0, 300.0, 400.0); 681 assert_eq!(rect.origin.x, 10.0); 682 assert_eq!(rect.origin.y, 20.0); 683 assert_eq!(rect.size.width, 300.0); 684 assert_eq!(rect.size.height, 400.0); 685 } 686 687 #[test] 688 fn style_mask_constants() { 689 // Verify the constants match AppKit's expected values. 690 assert_eq!(NS_WINDOW_STYLE_MASK_TITLED, 1); 691 assert_eq!(NS_WINDOW_STYLE_MASK_CLOSABLE, 2); 692 assert_eq!(NS_WINDOW_STYLE_MASK_MINIATURIZABLE, 4); 693 assert_eq!(NS_WINDOW_STYLE_MASK_RESIZABLE, 8); 694 } 695 696 #[test] 697 fn backing_store_constant() { 698 assert_eq!(NS_BACKING_STORE_BUFFERED, 2); 699 } 700 701 #[test] 702 fn activation_policy_constant() { 703 assert_eq!(NS_APPLICATION_ACTIVATION_POLICY_REGULAR, 0); 704 } 705 706 #[test] 707 fn autorelease_pool_create_and_drop() { 708 // Creating and dropping an autorelease pool should not crash. 709 let _pool = AutoreleasePool::new(); 710 } 711 712 #[test] 713 fn combined_style_mask() { 714 let style = NS_WINDOW_STYLE_MASK_TITLED 715 | NS_WINDOW_STYLE_MASK_CLOSABLE 716 | NS_WINDOW_STYLE_MASK_MINIATURIZABLE 717 | NS_WINDOW_STYLE_MASK_RESIZABLE; 718 assert_eq!(style, 0b1111); 719 } 720 721 #[test] 722 fn we_view_class_registration() { 723 register_we_view_class(); 724 let cls = class!("WeView"); 725 assert!(cls.is_some(), "WeView class should be registered"); 726 } 727 728 #[test] 729 fn bitmap_view_create() { 730 let _pool = AutoreleasePool::new(); 731 let bitmap = BitmapContext::new(100, 100).expect("should create bitmap context"); 732 let frame = NSRect::new(0.0, 0.0, 100.0, 100.0); 733 let view = BitmapView::new(frame, &bitmap); 734 assert!(!view.id().as_ptr().is_null()); 735 } 736 737 #[test] 738 fn we_view_accepts_first_responder() { 739 let _pool = AutoreleasePool::new(); 740 let bitmap = BitmapContext::new(100, 100).expect("should create bitmap context"); 741 let frame = NSRect::new(0.0, 0.0, 100.0, 100.0); 742 let view = BitmapView::new(frame, &bitmap); 743 let accepts: bool = msg_send![view.id().as_ptr(), acceptsFirstResponder]; 744 assert!(accepts, "WeView should accept first responder"); 745 } 746 747 #[test] 748 fn we_view_responds_to_key_down() { 749 let _pool = AutoreleasePool::new(); 750 register_we_view_class(); 751 let cls = class!("WeView").expect("WeView should be registered"); 752 let sel = Sel::register(c"keyDown:"); 753 let instances_respond: bool = 754 msg_send![cls.as_ptr(), instancesRespondToSelector: sel.as_ptr()]; 755 assert!(instances_respond, "WeView should respond to keyDown:"); 756 } 757 758 #[test] 759 fn we_view_responds_to_mouse_events() { 760 let _pool = AutoreleasePool::new(); 761 register_we_view_class(); 762 let cls = class!("WeView").expect("WeView should be registered"); 763 764 for sel_name in [c"mouseDown:", c"mouseUp:", c"mouseMoved:"] { 765 let sel = Sel::register(sel_name); 766 let responds: bool = msg_send![cls.as_ptr(), instancesRespondToSelector: sel.as_ptr()]; 767 assert!(responds, "WeView should respond to {:?}", sel_name); 768 } 769 } 770 771 #[test] 772 fn we_window_delegate_class_registration() { 773 register_we_window_delegate_class(); 774 let cls = class!("WeWindowDelegate"); 775 assert!(cls.is_some(), "WeWindowDelegate class should be registered"); 776 } 777 778 #[test] 779 fn we_window_delegate_responds_to_resize() { 780 register_we_window_delegate_class(); 781 let cls = class!("WeWindowDelegate").expect("WeWindowDelegate should be registered"); 782 let sel = Sel::register(c"windowDidResize:"); 783 let responds: bool = msg_send![cls.as_ptr(), instancesRespondToSelector: sel.as_ptr()]; 784 assert!( 785 responds, 786 "WeWindowDelegate should respond to windowDidResize:" 787 ); 788 } 789}