Guess I should put this under version control LOL
This commit is contained in:
commit
2035318460
73 changed files with 8836 additions and 0 deletions
23
cocoa/Cargo.toml
Normal file
23
cocoa/Cargo.toml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
[package]
|
||||
name = "alchemy-cocoa"
|
||||
description = "Cocoa bindings for Alchemy, a cross-platform GUI framework written in Rust."
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
authors = ["Ryan McGrath <ryan@rymc.io>"]
|
||||
license = "MPL-2.0+"
|
||||
repository = "https://github.com/ryanmcgrath/alchemy"
|
||||
categories = ["gui", "rendering::engine", "multimedia"]
|
||||
keywords = ["gui", "cocoa", "macos", "appkit", "react"]
|
||||
|
||||
[badges]
|
||||
maintenance = { status = "actively-developed" }
|
||||
|
||||
[dependencies]
|
||||
alchemy-lifecycle = { version = "0.1", path = "../lifecycle", features = ["cocoa"] }
|
||||
alchemy-styles = { version = "0.1", path = "../styles" }
|
||||
objc = "0.2.6"
|
||||
objc_id = "0.1.1"
|
||||
dispatch = "0.1.4"
|
||||
cocoa = "0.18.4"
|
||||
core-foundation = "0.6"
|
||||
core-graphics = "0.17.1"
|
||||
5
cocoa/README.md
Normal file
5
cocoa/README.md
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
# Alchemy-Cocoa
|
||||
This crate implements a backend for Cocoa-based widgets, such as `NSView`, `NSTextField`, and so on. Note that while it's under development currently, the fate of AppKit is still kind of a gray area. If Apple ends up pushing Marzipan as "the" solution, it's possible this might become obsolete, or would run in tandem with the iOS crate for iOS/Marzipan.
|
||||
|
||||
## Questions, Comments?
|
||||
Open an issue, or hit me up on [Twitter](https://twitter.com/ryanmcgrath/).
|
||||
153
cocoa/src/app.rs
Normal file
153
cocoa/src/app.rs
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
//! A wrapper for `NSApplication` on macOS. If you opt in to the `cocoa` feature on
|
||||
//! Alchemy, this will loop system-level application events back to your `AppDelegate`.
|
||||
|
||||
use std::sync::{Once, ONCE_INIT};
|
||||
|
||||
use cocoa::base::{id, nil};
|
||||
use cocoa::appkit::{NSApplication, NSRunningApplication};
|
||||
|
||||
use objc_id::Id;
|
||||
use objc::declare::ClassDecl;
|
||||
use objc::runtime::{Class, Object, Sel};
|
||||
use objc::{msg_send, class, sel, sel_impl};
|
||||
|
||||
use alchemy_lifecycle::traits::AppDelegate;
|
||||
|
||||
static ALCHEMY_APP_PTR: &str = "alchemyParentAppPtr";
|
||||
|
||||
/// A wrapper for `NSApplication`. It holds (retains) pointers for the Objective-C runtime,
|
||||
/// which is where our application instance lives. It also injects an `NSObject` subclass,
|
||||
/// which acts as the Delegate, looping back into our Alchemy shared application.
|
||||
pub struct App {
|
||||
pub inner: Id<Object>,
|
||||
pub delegate: Id<Object>
|
||||
}
|
||||
|
||||
impl App {
|
||||
/// Creates an NSAutoReleasePool, configures various NSApplication properties (e.g, activation
|
||||
/// policies), injects an `NSObject` delegate wrapper, and retains everything on the
|
||||
/// Objective-C side of things.
|
||||
pub fn new<T: AppDelegate>(parent_app_ptr: *const T) -> Self {
|
||||
let inner = unsafe {
|
||||
let _pool = cocoa::foundation::NSAutoreleasePool::new(nil);
|
||||
let app = cocoa::appkit::NSApp();
|
||||
app.setActivationPolicy_(cocoa::appkit::NSApplicationActivationPolicyRegular);
|
||||
Id::from_ptr(app)
|
||||
};
|
||||
|
||||
let delegate = unsafe {
|
||||
let delegate_class = register_app_delegate_class::<T>();
|
||||
let delegate: id = msg_send![delegate_class, new];
|
||||
(&mut *delegate).set_ivar(ALCHEMY_APP_PTR, parent_app_ptr as usize);
|
||||
msg_send![&*inner, setDelegate:delegate];
|
||||
Id::from_ptr(delegate)
|
||||
};
|
||||
|
||||
App {
|
||||
delegate: delegate,
|
||||
inner: inner
|
||||
}
|
||||
}
|
||||
|
||||
/// Kicks off the NSRunLoop for the NSApplication instance. This blocks when called.
|
||||
pub fn run(&self) {
|
||||
unsafe {
|
||||
let current_app = cocoa::appkit::NSRunningApplication::currentApplication(nil);
|
||||
current_app.activateWithOptions_(cocoa::appkit::NSApplicationActivateIgnoringOtherApps);
|
||||
let shared_app: id = msg_send![class!(NSApplication), sharedApplication];
|
||||
msg_send![shared_app, run];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Fires when the Application Delegate receives a `applicationWillFinishLaunching` notification.
|
||||
extern fn will_finish_launching<T: AppDelegate>(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR);
|
||||
let app = app_ptr as *mut T;
|
||||
(*app).will_finish_launching();
|
||||
};
|
||||
}
|
||||
|
||||
/// Fires when the Application Delegate receives a `applicationDidFinishLaunching` notification.
|
||||
extern fn did_finish_launching<T: AppDelegate>(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR);
|
||||
let app = app_ptr as *mut T;
|
||||
(*app).did_finish_launching();
|
||||
};
|
||||
}
|
||||
|
||||
/// Fires when the Application Delegate receives a `applicationWillBecomeActive` notification.
|
||||
extern fn will_become_active<T: AppDelegate>(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR);
|
||||
let app = app_ptr as *mut T;
|
||||
(*app).will_become_active();
|
||||
};
|
||||
}
|
||||
|
||||
/// Fires when the Application Delegate receives a `applicationDidBecomeActive` notification.
|
||||
extern fn did_become_active<T: AppDelegate>(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR);
|
||||
let app = app_ptr as *mut T;
|
||||
(*app).did_become_active();
|
||||
};
|
||||
}
|
||||
|
||||
/// Fires when the Application Delegate receives a `applicationWillResignActive` notification.
|
||||
extern fn will_resign_active<T: AppDelegate>(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR);
|
||||
let app = app_ptr as *mut T;
|
||||
(*app).will_resign_active();
|
||||
};
|
||||
}
|
||||
|
||||
/// Fires when the Application Delegate receives a `applicationDidResignActive` notification.
|
||||
extern fn did_resign_active<T: AppDelegate>(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR);
|
||||
let app = app_ptr as *mut T;
|
||||
(*app).did_resign_active();
|
||||
};
|
||||
}
|
||||
|
||||
/// Fires when the Application Delegate receives a `applicationWillTerminate` notification.
|
||||
extern fn will_terminate<T: AppDelegate>(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let app_ptr: usize = *this.get_ivar(ALCHEMY_APP_PTR);
|
||||
let app = app_ptr as *mut T;
|
||||
(*app).will_terminate();
|
||||
};
|
||||
}
|
||||
|
||||
/// Registers an `NSObject` application delegate, and configures it for the various callbacks and
|
||||
/// pointers we need to have.
|
||||
fn register_app_delegate_class<T: AppDelegate>() -> *const Class {
|
||||
static mut DELEGATE_CLASS: *const Class = 0 as *const Class;
|
||||
static INIT: Once = ONCE_INIT;
|
||||
|
||||
INIT.call_once(|| unsafe {
|
||||
let superclass = Class::get("NSObject").unwrap();
|
||||
let mut decl = ClassDecl::new("AlchemyAppDelegate", superclass).unwrap();
|
||||
|
||||
decl.add_ivar::<usize>(ALCHEMY_APP_PTR);
|
||||
|
||||
// Add callback methods
|
||||
decl.add_method(sel!(applicationWillFinishLaunching:), will_finish_launching::<T> as extern fn(&Object, _, _));
|
||||
decl.add_method(sel!(applicationDidFinishLaunching:), did_finish_launching::<T> as extern fn(&Object, _, _));
|
||||
decl.add_method(sel!(applicationWillBecomeActive:), will_become_active::<T> as extern fn(&Object, _, _));
|
||||
decl.add_method(sel!(applicationDidBecomeActive:), did_become_active::<T> as extern fn(&Object, _, _));
|
||||
decl.add_method(sel!(applicationWillResignActive:), will_resign_active::<T> as extern fn(&Object, _, _));
|
||||
decl.add_method(sel!(applicationDidResignActive:), did_resign_active::<T> as extern fn(&Object, _, _));
|
||||
decl.add_method(sel!(applicationWillTerminate:), will_terminate::<T> as extern fn(&Object, _, _));
|
||||
|
||||
DELEGATE_CLASS = decl.register();
|
||||
});
|
||||
|
||||
unsafe {
|
||||
DELEGATE_CLASS
|
||||
}
|
||||
}
|
||||
29
cocoa/src/color.rs
Normal file
29
cocoa/src/color.rs
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
//! Implements a conversion method for taking an `alchemy::Color` and turning it into
|
||||
//! an `NSColor`.
|
||||
|
||||
use objc_id::Id;
|
||||
use objc::runtime::Object;
|
||||
use objc::{class, msg_send, sel, sel_impl};
|
||||
use core_graphics::base::CGFloat;
|
||||
|
||||
use alchemy_styles::color::Color;
|
||||
|
||||
pub trait IntoNSColor {
|
||||
fn into_nscolor(&self) -> Id<Object>;
|
||||
}
|
||||
|
||||
impl IntoNSColor for Color {
|
||||
/// This creates an NSColor, retains it, and returns it. Dropping this value will
|
||||
/// call `release` on the Objective-C side.
|
||||
fn into_nscolor(&self) -> Id<Object> {
|
||||
let red = self.red as CGFloat / 255.0;
|
||||
let green = self.green as CGFloat / 255.0;
|
||||
let blue = self.blue as CGFloat / 255.0;
|
||||
let alpha = self.alpha as CGFloat / 255.0;
|
||||
|
||||
unsafe {
|
||||
Id::from_ptr(msg_send![class!(NSColor), colorWithRed:red green:green blue:blue alpha:alpha])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
23
cocoa/src/lib.rs
Normal file
23
cocoa/src/lib.rs
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
//! This crate provides a Cocoa backend for Alchemy, the Rust GUI framework.
|
||||
//! This means that, on macOS, you'll be using native `NSView`, `NSTextField`,
|
||||
//! and other assorted controls. Where possible, it attempts to opt into
|
||||
//! smoother rendering paths (e.g, layer-backed views, drawing subview layers
|
||||
//! together where appropriate).
|
||||
//!
|
||||
//! # License
|
||||
//!
|
||||
//! Copyright 2018 Ryan McGrath. See the license files included in the root repository
|
||||
//! for more information, along with credit to applicable parties for who this project
|
||||
//! would not have happened.
|
||||
//!
|
||||
//! # Code of Conduct
|
||||
//!
|
||||
//! Please note that this project is released with a [Contributor Code of
|
||||
//! Conduct][coc]. By participating in this project you agree to abide by its terms.
|
||||
//!
|
||||
//! [coc]: https://www.contributor-covenant.org/version/1/4/code-of-conduct
|
||||
|
||||
pub mod color;
|
||||
pub mod app;
|
||||
pub mod view;
|
||||
pub mod window;
|
||||
147
cocoa/src/view.rs
Normal file
147
cocoa/src/view.rs
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
//! components/view/mod.rs
|
||||
//!
|
||||
//! Implements a View Component struct. The most common
|
||||
//! basic building block of any app. Maps back to native
|
||||
//! layer per-platform.
|
||||
//!
|
||||
//! @author Ryan McGrath <ryan@rymc.io>
|
||||
//! @created 03/29/2019
|
||||
|
||||
use std::sync::{Once, ONCE_INIT};
|
||||
|
||||
use objc_id::{Id, ShareId};
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
use objc::declare::ClassDecl;
|
||||
use objc::runtime::{Class, Object, Sel, BOOL};
|
||||
|
||||
use cocoa::base::{id, nil, YES};
|
||||
use cocoa::foundation::{NSRect, NSPoint, NSSize};
|
||||
|
||||
use crate::color::IntoNSColor;
|
||||
|
||||
use alchemy_styles::color::Color;
|
||||
use alchemy_styles::styles::Style;
|
||||
use alchemy_styles::result::Layout;
|
||||
|
||||
use alchemy_lifecycle::traits::PlatformSpecificNodeType;
|
||||
|
||||
static ALCHEMY_DELEGATE: &str = "alchemyDelegate";
|
||||
static BACKGROUND_COLOR: &str = "alchemyBackgroundColor";
|
||||
|
||||
/// A wrapper for `NSView`. This holds retained pointers for the Objective-C
|
||||
/// runtime - namely, the view itself, and associated things such as background
|
||||
/// colors and so forth.
|
||||
#[derive(Debug)]
|
||||
pub struct View {
|
||||
inner_mut: Id<Object>,
|
||||
inner_share: ShareId<Object>,
|
||||
background_color: Id<Object>
|
||||
}
|
||||
|
||||
impl View {
|
||||
/// Allocates a new `NSView` on the Objective-C side, ensuring that things like coordinate
|
||||
/// flipping occur (macOS still uses (0,0) as lower-left by default), and opting in to layer
|
||||
/// backed views for smoother scrolling.
|
||||
pub fn new() -> View {
|
||||
let (inner_mut, inner_share) = unsafe {
|
||||
let rect_zero = NSRect::new(NSPoint::new(0., 0.), NSSize::new(0., 0.));
|
||||
let alloc: id = msg_send![register_class(), alloc];
|
||||
let view: id = msg_send![alloc, initWithFrame:rect_zero];
|
||||
msg_send![view, setWantsLayer:YES];
|
||||
msg_send![view, setLayerContentsRedrawPolicy:1];
|
||||
let x = view.clone();
|
||||
(Id::from_ptr(view), ShareId::from_ptr(x))
|
||||
};
|
||||
|
||||
View {
|
||||
inner_mut: inner_mut,
|
||||
inner_share: inner_share,
|
||||
background_color: Color::transparent().into_nscolor()
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a pointer to the underlying Objective-C view. The pointer is not mutable; however,
|
||||
/// you can send messages to it (unsafely).
|
||||
pub fn borrow_native_backing_node(&self) -> PlatformSpecificNodeType {
|
||||
self.inner_share.clone()
|
||||
}
|
||||
|
||||
/// Appends a child NSView (or subclassed type) to this view.
|
||||
pub fn append_child(&mut self, child: PlatformSpecificNodeType) {
|
||||
unsafe {
|
||||
msg_send![&*self.inner_mut, addSubview:child];
|
||||
}
|
||||
}
|
||||
|
||||
/// Given a `&Style`, will set the frame, background color, borders and so forth. It then
|
||||
/// calls `setNeedsDisplay:YES` on the Objective-C side, so that Cocoa will re-render this
|
||||
/// view.
|
||||
pub fn apply_styles(&mut self, layout: &Layout, style: &Style) {
|
||||
unsafe {
|
||||
let rect = NSRect::new(
|
||||
NSPoint::new(layout.location.x.into(), layout.location.y.into()),
|
||||
NSSize::new(layout.size.width.into(), layout.size.height.into())
|
||||
);
|
||||
|
||||
self.background_color = style.background_color.into_nscolor();
|
||||
self.inner_mut.set_ivar(BACKGROUND_COLOR, &*self.background_color);
|
||||
|
||||
msg_send![&*self.inner_mut, setFrame:rect];
|
||||
msg_send![&*self.inner_mut, setNeedsDisplay:YES];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// This is used for some specific calls, where macOS NSView needs to be
|
||||
/// forcefully dragged into the modern age (e.g, position coordinates from top left...).
|
||||
extern fn enforce_normalcy(_: &Object, _: Sel) -> BOOL {
|
||||
return YES;
|
||||
}
|
||||
|
||||
/// When an `NSView` has `updateLayer` called, it will get passed through here, at which point we
|
||||
/// instruct the layer how it should render (e.g, background color).
|
||||
extern fn update_layer(this: &Object, _: Sel) {
|
||||
unsafe {
|
||||
let background_color: id = *this.get_ivar(BACKGROUND_COLOR);
|
||||
if background_color != nil {
|
||||
let layer: id = msg_send![this, layer];
|
||||
let cg: id = msg_send![background_color, CGColor];
|
||||
msg_send![layer, setBackgroundColor:cg];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers an `NSView` subclass, and configures it to hold some ivars for various things we need
|
||||
/// to store.
|
||||
fn register_class() -> *const Class {
|
||||
static mut VIEW_CLASS: *const Class = 0 as *const Class;
|
||||
static INIT: Once = ONCE_INIT;
|
||||
|
||||
INIT.call_once(|| unsafe {
|
||||
let superclass = Class::get("NSView").unwrap();
|
||||
let mut decl = ClassDecl::new("AlchemyView", superclass).unwrap();
|
||||
|
||||
// Force NSView to render from the top-left, not bottom-left
|
||||
decl.add_method(sel!(isFlipped), enforce_normalcy as extern fn(&Object, _) -> BOOL);
|
||||
|
||||
// Opt-in to AutoLayout
|
||||
//decl.add_method(sel!(requiresConstraintBasedLayout), enforce_normalcy as extern fn(&Object, _) -> BOOL);
|
||||
|
||||
// Request optimized backing layers
|
||||
decl.add_method(sel!(updateLayer), update_layer as extern fn(&Object, _));
|
||||
decl.add_method(sel!(wantsUpdateLayer), enforce_normalcy as extern fn(&Object, _) -> BOOL);
|
||||
|
||||
// Ensure mouse events and so on work
|
||||
//decl.add_method(sel!(acceptsFirstResponder), update_layer as extern fn(&Object, _));
|
||||
|
||||
// A pointer back to our View, for forwarding mouse + etc events.
|
||||
// Note that NSView's don't really have a "delegate", I'm just using it here
|
||||
// for common terminology sake.
|
||||
decl.add_ivar::<usize>(ALCHEMY_DELEGATE);
|
||||
decl.add_ivar::<id>(BACKGROUND_COLOR);
|
||||
|
||||
VIEW_CLASS = decl.register();
|
||||
});
|
||||
|
||||
unsafe { VIEW_CLASS }
|
||||
}
|
||||
148
cocoa/src/window.rs
Normal file
148
cocoa/src/window.rs
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
//! Implements an `NSWindow` wrapper for MacOS, backed by
|
||||
//! Cocoa and associated widgets. This also handles looping back
|
||||
//! lifecycle events, such as window resizing or close events.
|
||||
|
||||
use std::sync::{Once, ONCE_INIT};
|
||||
|
||||
use cocoa::base::{id, nil, YES, NO};
|
||||
use cocoa::appkit::{NSWindow, NSWindowStyleMask, NSBackingStoreType};
|
||||
use cocoa::foundation::{NSRect, NSPoint, NSSize, NSString, NSAutoreleasePool};
|
||||
|
||||
use objc_id::ShareId;
|
||||
use objc::declare::ClassDecl;
|
||||
use objc::runtime::{Class, Object, Sel};
|
||||
use objc::{msg_send, sel, sel_impl};
|
||||
|
||||
use alchemy_lifecycle::traits::{AppDelegate, Component};
|
||||
|
||||
static APP_PTR: &str = "alchemyAppPtr";
|
||||
static WINDOW_MANAGER_ID: &str = "alchemyWindowManagerID";
|
||||
|
||||
/// A wrapper for `NSWindow`. Holds (retains) pointers for the Objective-C runtime
|
||||
/// where our `NSWindow` and associated delegate live.
|
||||
pub struct Window {
|
||||
pub inner: ShareId<Object>,
|
||||
pub delegate: ShareId<Object>
|
||||
}
|
||||
|
||||
impl Window {
|
||||
/// Creates a new `NSWindow` instance, configures it appropriately (e.g, titlebar appearance),
|
||||
/// injects an `NSObject` delegate wrapper, and retains the necessary Objective-C runtime
|
||||
/// pointers.
|
||||
pub fn new<T: AppDelegate>(window_id: usize, title: &str, dimensions: (f64, f64, f64, f64), content_view: &Component, app_ptr: *const T) -> Window {
|
||||
let (top, left, width, height) = dimensions;
|
||||
let dimensions = NSRect::new(NSPoint::new(top, left), NSSize::new(width, height));
|
||||
|
||||
let style = NSWindowStyleMask::NSResizableWindowMask |
|
||||
NSWindowStyleMask::NSUnifiedTitleAndToolbarWindowMask | NSWindowStyleMask::NSMiniaturizableWindowMask |
|
||||
NSWindowStyleMask::NSClosableWindowMask | NSWindowStyleMask::NSTitledWindowMask;
|
||||
|
||||
let inner = unsafe {
|
||||
let window = NSWindow::alloc(nil).initWithContentRect_styleMask_backing_defer_(
|
||||
dimensions,
|
||||
style,
|
||||
NSBackingStoreType::NSBackingStoreBuffered,
|
||||
NO
|
||||
).autorelease();
|
||||
|
||||
let title = NSString::alloc(nil).init_str(title);
|
||||
window.setTitle_(title);
|
||||
msg_send![window, setTitlebarAppearsTransparent:YES];
|
||||
msg_send![window, setTitleVisibility:1];
|
||||
|
||||
// This is very important! NSWindow is an old class and has some behavior that we need
|
||||
// to disable, like... this. If we don't set this, we'll segfault entirely because the
|
||||
// Objective-C runtime gets out of sync.
|
||||
msg_send![window, setReleasedWhenClosed:NO];
|
||||
|
||||
if let Some(view_ptr) = content_view.borrow_native_backing_node() {
|
||||
msg_send![window, setContentView:view_ptr];
|
||||
}
|
||||
|
||||
ShareId::from_ptr(window)
|
||||
};
|
||||
|
||||
let delegate = unsafe {
|
||||
let delegate_class = register_window_class::<T>();
|
||||
let delegate: id = msg_send![delegate_class, new];
|
||||
(&mut *delegate).set_ivar(APP_PTR, app_ptr as usize);
|
||||
(&mut *delegate).set_ivar(WINDOW_MANAGER_ID, window_id);
|
||||
msg_send![inner, setDelegate:delegate];
|
||||
ShareId::from_ptr(delegate)
|
||||
};
|
||||
|
||||
Window {
|
||||
inner: inner,
|
||||
delegate: delegate
|
||||
}
|
||||
}
|
||||
|
||||
/// On macOS, calling `show()` is equivalent to calling `makeKeyAndOrderFront`. This is the
|
||||
/// most common use case, hence why this method was chosen - if you want or need something
|
||||
/// else, feel free to open an issue to discuss.
|
||||
///
|
||||
/// You should never be calling this yourself, mind you - Alchemy core handles this for you.
|
||||
pub fn show(&self) {
|
||||
unsafe {
|
||||
msg_send![&*self.inner, makeKeyAndOrderFront:nil];
|
||||
}
|
||||
}
|
||||
|
||||
/// On macOS, calling `close()` is equivalent to calling... well, `close`. It closes the
|
||||
/// window.
|
||||
///
|
||||
/// I dunno what else to say here, lol.
|
||||
///
|
||||
/// You should never be calling this yourself, mind you - Alchemy core handles this for you.
|
||||
pub fn close(&self) {
|
||||
unsafe {
|
||||
msg_send![&*self.inner, close];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Window {
|
||||
/// When a Window is dropped on the Rust side, we want to ensure that we break the delegate
|
||||
/// link on the Objective-C side. While this shouldn't actually be an issue, I'd rather be
|
||||
/// safer than sorry.
|
||||
fn drop(&mut self) {
|
||||
// This bridging link needs to be broken on Drop.
|
||||
unsafe {
|
||||
msg_send![&*self.inner, setDelegate:nil];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Called when a Window receives a `windowWillClose:` event. Loops back to the shared
|
||||
/// Alchemy app instance, so that our window manager can act appropriately.
|
||||
extern fn will_close<T: AppDelegate>(this: &Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let app_ptr: usize = *this.get_ivar(APP_PTR);
|
||||
let window_id: usize = *this.get_ivar(WINDOW_MANAGER_ID);
|
||||
let app = app_ptr as *mut T;
|
||||
(*app)._window_will_close(window_id);
|
||||
};
|
||||
}
|
||||
|
||||
/// Injects an `NSObject` delegate subclass, with some callback and pointer ivars for what we
|
||||
/// need to do.
|
||||
fn register_window_class<T: AppDelegate>() -> *const Class {
|
||||
static mut DELEGATE_CLASS: *const Class = 0 as *const Class;
|
||||
static INIT: Once = ONCE_INIT;
|
||||
|
||||
INIT.call_once(|| unsafe {
|
||||
let superclass = Class::get("NSObject").unwrap();
|
||||
let mut decl = ClassDecl::new("alchemyWindowDelegateShim", superclass).unwrap();
|
||||
|
||||
decl.add_ivar::<usize>(APP_PTR);
|
||||
decl.add_ivar::<usize>(WINDOW_MANAGER_ID);
|
||||
|
||||
decl.add_method(sel!(windowWillClose:), will_close::<T> as extern fn(&Object, _, _));
|
||||
|
||||
DELEGATE_CLASS = decl.register();
|
||||
});
|
||||
|
||||
unsafe {
|
||||
DELEGATE_CLASS
|
||||
}
|
||||
}
|
||||
Reference in a new issue