we (web engine): Experimental web browser project to understand the limits of Claude
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}