-
Notifications
You must be signed in to change notification settings - Fork 44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Better class declaration #30
Comments
Work on syntax extension that would help with declaring new classes (ideas in part from unsafe pub struct MyTitleView: NSView {
pub close_button: Option<Id<NSButton, Owned>>,
pub miniaturize_button: Option<Id<NSButton, Owned>>,
pub text_attributes: Id<NSMutableDictionary, Owned>,
pub title_color: Id<NSColor, Owned>,
owner: *mut Object,
owned_by_menu: Bool,
is_key_window: Bool,
is_main_window: Bool,
is_active_application: Bool,
} Approximate Objective-C equivalent. /// Initialization & deallocation
unsafe impl MyTitleView {
fn height() -> CGFloat {
let h: CGFloat = unsafe { msg_send![class!(NSMenuView) menuBarHeight] };
h + 1.0
}
// TODO: How do we handle `self: Option<...>`?
fn init(self: Id<MaybeUninit<Self>, Owned>) -> Id<Self, Owned> {
let self = unsafe { msg_send_id![super(self, class!(NSView)), init] };
self.owner = ptr::null_mut();
self.owned_by_menu = Bool::NO;
self.is_key_window = Bool::NO;
self.is_main_window = Bool::NO;
self.is_active_application = Bool::NO;
unsafe { msg_send![self, setAutoresizingMask: NSViewWidthSizable | NSViewMinYMargin] };
self.text_attributes = unsafe { NSMutableDictionary::from(&[
(msg_send_id![class!(NSFont), boldSystemFontOfSize: 0], NSFontAttributeName),
(msg_send_id![class!(NSFont), boldSystemFontOfSize: 0], NSForegroundColorAttributeName),
])};
self.title_color = msg_send_id![NSColor, lightGrayColor];
unsafe { self.assume_init() }
}
#[selector(initWithOwner:)]
fn init_with_owner(self: Id<MaybeUninit<Self>, Owned>, owner: *mut Object) -> Id<Self, Owned> {
let self = unsafe { msg_send_id![self, init] };
let _: () = unsafe { msg_send![self, setOwner: owner] };
self
}
#[selector(setOwner:)]
fn set_owner(&mut self, owner: Id<NSObject, Shared>) {
let center = NSNotificationCenter::default_center();
if (owner.is_kind_of(class!(NSWindow)) {
log::debug("GSTitleView: owner is NSWindow or NSPanel");
self.owner = owner.as_ptr();
self.owned_by_menu = Bool::NO;
msg_send![
self,
setFrame: NSRect::new(
-1,
self.owner.frame().size.height - MyTitleView::height() - 40,
self.owner.frame().size.width + 2,
MyTitleView::height()
),
];
if (msg_send![self.owner, styleMask] & NSClosableWindowMask) {
msg_send![self, addCloseButtonWithAction: sel!(performClose:)];
}
if (msg_send![self.owner, styleMask] & NSMiniaturizableWindowMask) {
msg_send![self, addMiniaturizeButtonWithAction: sel!(performMiniaturize:)];
}
center.add_observer(self, sel!(windowBecomeKey:), NSWindowDidBecomeKeyNotification, self.owner);
center.add_observer(self, sel!(windowResignKey:), NSWindowDidResignKeyNotification, self.owner);
center.add_observer(self, sel!(windowBecomeMain:), NSWindowDidBecomeMainNotification, self.owner);
center.add_observer(self, sel!(windowResignMain:), NSWindowDidResignMainNotification, self.owner);
center.add_observer(self, sel!(applicationBecomeActive:), NSApplicationWillBecomeActiveNotification, self.owner);
center.add_observer(self, sel!(applicationResignActive:), NSApplicationWillResignActiveNotification, self.owner);
} else if (owner.is_kind_of(class!(NSMenu)) {
log::debug("GSTitleView: owner is NSMenu");
self.owner = owner.as_ptr();
self.owned_by_menu = Bool::YES;
let theme: Id<GSTheme, Unknown> = unsafe { msg_send_id![class!(GSTheme), theme] };
if let Some(color) = msg_send_id![theme, colorNamed: @"GSMenuBar", state: GSThemeNormalState] {
self.title_color = color;
} else {
self.title_color = msg_send_id![NSColor, blackColor];
}
let text_color = unsafe {
msg_send_id![theme, colorNamed: @"GSMenuBarTitle", state: GSThemeNormalState]
}.unwrap_or_else(|| {
msg_send_id![NSColor, whiteColor]
});
[self.text_attributes, setObject: text_color, forKey: NSForegroundColorAttributeName];
} else {
log::debug!("GSTitleView: {} owner is not NSMenu or NSWindow or NSPanel", owner.class().name());
}
}
fn owner(&self) -> *mut Object {
self.owner
}
fn dealloc(&mut self) {
if (self.owned_by_menu.is_false()) {
unsafe {
let center = msg_send_id![class!(NSNotificationCenter), defaultCenter];
msg_send![center, removeObserver: self];
};
}
unsafe { msg_send![msg_send_id![class!(GSTheme), theme], setName: ptr::null(), forElement: msg_send![self.close_button, cell], temporary: Bool::NO] };
unsafe { msg_send![super(self, class!(NSView)), dealloc] };
// Drop impl called after this
}
}
/// Drawing
unsafe impl MyTitleView {
fn title_size(&self) -> NSSize {
let s: Id<NSString, Shared> = unsafe { msg_send_id![self.owner, title] };
s.size_with_attributes(self.text_attributes)
}
fn title_size(&self) -> NSSize {
let s: Id<NSString, Shared> = unsafe { msg_send_id![self.owner, title] };
s.size_with_attributes(self.text_attributes)
}
}
/// Mouse actions
unsafe impl MyTitleView {
#[selector(acceptsFirstMouse:)]
fn accepts_first_mouse(&self, _event: &NSEvent) -> Bool {
Bool::YES
}
#[selector(mouseDown:)]
fn mouse_down(&self, _event: &NSEvent) {
todo!()
}
#[selector(rightMouseDown:)]
fn right_mouse_down(&self, _event: &NSEvent) {
// Explicitly does not call super
}
#[selector(menuForEvent:)]
fn menu_for_event(&self, _event: &NSEvent) -> Option<Id<NSMenu, Unknown>> {
None
}
}
/// NSWindow & NSApplication notifications
unsafe impl MyTitleView {
#[selector(applicationBecomeActive:)]
fn application_becomes_active(&self, _notification: &NSNotification) {
self.is_active_application = Bool::YES;
}
// ...
}
// ... Approximate Objective-C equivalent. |
Regarding instance variables, an idea would be to have a macro that expands to roughly: struct MyObjectIvars {
ivar1: i32,
ivar2: Box<u8>,
}
impl MyObject {
fn ivars(&self) -> &MyObjectIvars { ... }
fn ivars_mut(&mut self) -> &mut MyObjectIvars { ... }
} (Assuming that the order that ivars are laid out in are guaranteed, haven't researched this yet). That would then make it trivial for users to access these as EDIT: Found a better solution to this, see #190 |
Idea for fn init(this: Allocated<Self>) -> Id<Self, Owned> {
let this: Option<PartialInit<Self>> = unsafe { msg_send_id![super(self, class!(NSView)), init] };
this.map(|mut this| {
// this.owner is MaybeUninit<*mut Object>
this.owner.write(ptr::null_mut());
// this.owned_by_menu is MaybeUninit<Bool>
this.owned_by_menu.write(Bool::NO);
this.is_key_window.write(Bool::NO);
this.is_main_window.write(Bool::NO);
this.is_active_application.write(Bool::NO);
// Discouraged; no way to verify that `this` is initialized!
unsafe { msg_send![this, setAutoresizingMask: NSViewWidthSizable | NSViewMinYMargin] };
this.text_attributes.write(unsafe { NSMutableDictionary::from(&[
(msg_send_id![class!(NSFont), boldSystemFontOfSize: 0], NSFontAttributeName),
(msg_send_id![class!(NSFont), boldSystemFontOfSize: 0], NSForegroundColorAttributeName),
])});
this.title_color.write(unsafe { msg_send_id![NSColor, lightGrayColor] });
unsafe { this.assume_init() }
})
} EDIT: Postponed, see #252 for the interim solution |
We want to keep the "declare this class" and "use this class" use-cases separate (may at some point merge these together again, but at least for now). The "use this class" pattern will probably be implemented as part of #161. Reasoning: Declared classes are often used for delegate classes, who don't need to be called from Rust, and it would be a waste trying to create methods for doing so. More importantly, if you do |
The situation has gone a long way since I opened this issue! A few remaining things are #282 and #283, but I went and opened separate issues for that to make it easier to track progress. See also #250 (comment) about improving safety when implementing protocols. |
Blocked on at least #21.Fundamentally cannot be made safe, since you're calling into unknown Objective-C classes. Not even sure
add_ivar
is sound (since Objective-C could be using the same ivar in a superclass)?Fix Adding a method isn't ergonomic SSheldon/rust-objc#12
Fixed by Consistently implement
MethodImplementation
wrt. lifetimes #193.Rename
ClassDecl
->ClassBuilder
Can it be made more ergonomic with macros? See More macros SSheldon/rust-objc#74.
Done in various PRs (primarily
declare_class!
macro)Returning an autoreleased object in a custom / overridden method is very unergonomic:
Will also need something like an
autorelease_return
method onId
which callsobjc_autoreleaseReturnValue
.Fixed by Add
Id::autorelease_return
#191. See Addautoreleasepool_leaking
#112 as well for an alternate solution.Differentiate between static class references and "registered" classes, see Sending messages is inefficient because of selector lookup SSheldon/rust-objc#49 (comment)Moved to Add static classes #185.Memory management rules are not automatically followed. Fixed by Allow returning
Id
fromextern_methods!
#244Uninitialized data in
init
methods. Fixed by Better ivar initialization #252Allow
Drop
types in ivars. Fixed by AllowDrop
types in ivars #254The text was updated successfully, but these errors were encountered: