diff --git a/src/input/macos.rs b/src/input/macos.rs index 2f2ee7f..c905ce6 100644 --- a/src/input/macos.rs +++ b/src/input/macos.rs @@ -1,12 +1,3 @@ -//! This module does one specific thing: register a custom `NSView` class that's... brought to the -//! modern era. -//! -//! I kid, I kid. -//! -//! It just enforces that coordinates are judged from the top-left, which is what most people look -//! for in the modern era. It also implements a few helpers for things like setting a background -//! color, and enforcing layer backing by default. - use std::sync::Once; use objc::declare::ClassDecl; @@ -15,57 +6,54 @@ use objc::{class, msg_send, sel, sel_impl}; use objc_id::Id; use crate::dragdrop::DragInfo; -use crate::foundation::{id, NSString, NSUInteger, NO, YES}; +use crate::foundation::{load_or_register_class, id, NSString, NSUInteger, NO, YES}; use crate::input::{TextFieldDelegate, TEXTFIELD_DELEGATE_PTR}; use crate::utils::load; /// Called when editing this text field has ended (e.g. user pressed enter). -extern "C" fn text_did_end_editing(this: &mut Object, _: Sel, info: id) { +extern "C" fn text_did_end_editing(this: &mut Object, _: Sel, _info: id) { let view = load::(this, TEXTFIELD_DELEGATE_PTR); - view.text_did_end_editing({ - let s = NSString::wrap(unsafe { msg_send![this, stringValue] }); - s.to_str().to_string() - }); + let s = NSString::from_retained(unsafe { msg_send![this, stringValue] }); + view.text_did_end_editing(s.to_str()); } -extern "C" fn text_did_begin_editing(this: &mut Object, _: Sel, info: id) { +extern "C" fn text_did_begin_editing(this: &mut Object, _: Sel, _info: id) { let view = load::(this, TEXTFIELD_DELEGATE_PTR); - view.text_did_begin_editing({ - let s = NSString::wrap(unsafe { msg_send![this, stringValue] }); - s.to_str().to_string() - }); + let s = NSString::from_retained(unsafe { msg_send![this, stringValue] }); + view.text_did_begin_editing(s.to_str()); } -extern "C" fn text_did_change(this: &mut Object, _: Sel, info: id) { +extern "C" fn text_did_change(this: &mut Object, _: Sel, _info: id) { let view = load::(this, TEXTFIELD_DELEGATE_PTR); - view.text_did_change({ - let s = NSString::wrap(unsafe { msg_send![this, stringValue] }); - s.to_str().to_string() - }); + let s = NSString::from_retained(unsafe { msg_send![this, stringValue] }); + view.text_did_change(s.to_str()); } extern "C" fn text_should_begin_editing( this: &mut Object, _: Sel, - info: id, -) -> bool { + _info: id, +) -> BOOL { let view = load::(this, TEXTFIELD_DELEGATE_PTR); - view.text_should_begin_editing({ - let s = NSString::wrap(unsafe { msg_send![this, stringValue] }); - s.to_str().to_string() - }) + let s = NSString::from_retained(unsafe { msg_send![this, stringValue] }); + + match view.text_should_begin_editing(s.to_str()) { + true => YES, + false => NO + } } extern "C" fn text_should_end_editing( this: &mut Object, _: Sel, - info: id, -) -> bool { + _info: id, +) -> BOOL { let view = load::(this, TEXTFIELD_DELEGATE_PTR); - view.text_should_end_editing({ - let s = NSString::wrap(unsafe { msg_send![this, stringValue] }); - s.to_str().to_string() - }) + let s = NSString::from_retained(unsafe { msg_send![this, stringValue] }); + match view.text_should_end_editing(s.to_str()) { + true => YES, + false => NO + } } /// Injects an `NSTextField` subclass. This is used for the default views that don't use delegates - we @@ -86,15 +74,8 @@ pub(crate) fn register_view_class() -> *const Class { /// Injects an `NSTextField` subclass, with some callback and pointer ivars for what we /// need to do. -pub(crate) fn register_view_class_with_delegate() -> *const Class { - static mut VIEW_CLASS: *const Class = 0 as *const Class; - static INIT: Once = Once::new(); - - INIT.call_once(|| unsafe { - let superclass = class!(NSTextField); - // let superclass = class!(NSView); - let mut decl = ClassDecl::new("RSTTextInputFieldWithDelegate", superclass).unwrap(); - +pub(crate) fn register_view_class_with_delegate(instance: &T) -> *const Class { + load_or_register_class("NSTextField", instance.subclass_name(), |decl| unsafe { // A pointer to the "view controller" on the Rust side. It's expected that this doesn't // move. decl.add_ivar::(TEXTFIELD_DELEGATE_PTR); @@ -113,15 +94,11 @@ pub(crate) fn register_view_class_with_delegate() -> *cons ); decl.add_method( sel!(textShouldBeginEditing:), - text_should_begin_editing:: as extern "C" fn(&mut Object, Sel, *mut Object) -> bool, + text_should_begin_editing:: as extern "C" fn(&mut Object, Sel, id) -> bool, ); decl.add_method( sel!(textShouldEndEditing:), - text_should_end_editing:: as extern "C" fn(&mut Object, Sel, *mut Object) -> bool, + text_should_end_editing:: as extern "C" fn(&mut Object, Sel, id) -> bool, ); - - VIEW_CLASS = decl.register(); - }); - - unsafe { VIEW_CLASS } + }) } diff --git a/src/input/mod.rs b/src/input/mod.rs index 61c650c..dcfbd72 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -67,9 +67,9 @@ pub use traits::TextFieldDelegate; pub(crate) static TEXTFIELD_DELEGATE_PTR: &str = "rstTextFieldDelegatePtr"; /// A helper method for instantiating view classes and applying default settings to them. -fn allocate_view(registration_fn: fn() -> *const Class) -> id { +fn common_init(class: *const Class) -> id { unsafe { - let view: id = msg_send![registration_fn(), new]; + let view: id = msg_send![class, new]; let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints: NO]; @@ -130,7 +130,8 @@ impl Default for TextField { impl TextField { /// Returns a default `TextField`, suitable for pub fn new() -> Self { - let view = allocate_view(register_view_class); + let class = register_view_class(); + let view = common_init(class); TextField { delegate: None, @@ -156,15 +157,13 @@ where /// Initializes a new TextField with a given `TextFieldDelegate`. This enables you to respond to events /// and customize the view as a module, similar to class-based systems. pub fn with(delegate: T) -> TextField { + let class = register_view_class_with_delegate(&delegate); let mut delegate = Box::new(delegate); - let label = allocate_view(register_view_class_with_delegate::); + let label = common_init(class); unsafe { - //let view: id = msg_send![register_view_class_with_delegate::(), new]; - //let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO]; let ptr: *const T = &*delegate; (&mut *label).set_ivar(TEXTFIELD_DELEGATE_PTR, ptr as usize); - // let _: () = msg_send![self., setDelegate: label]; }; let mut label = TextField { diff --git a/src/input/traits.rs b/src/input/traits.rs index bff4c9d..8b43386 100644 --- a/src/input/traits.rs +++ b/src/input/traits.rs @@ -2,6 +2,8 @@ use crate::input::TextField; +/// This trait can be used for implementing custom text field behavior. +#[allow(unused_variables)] pub trait TextFieldDelegate { /// Used to cache subclass creations on the Objective-C side. /// You can just set this to be the name of your view type. This @@ -14,24 +16,26 @@ pub trait TextFieldDelegate { Self::NAME } + /// Called when the text field is loaded. You're passed a reference to the underlying text + /// field for future local use. + fn did_load(&mut self, view: TextField) {} + /// Posts a notification when the text is no longer in edit mode. - fn text_did_end_editing(&self, _value: String) {} + fn text_did_end_editing(&self, value: &str) {} /// Requests permission to begin editing a text object. - fn text_should_begin_editing(&self, _value: String) -> bool { + fn text_should_begin_editing(&self, value: &str) -> bool { true } /// Posts a notification to the default notification center that the text is about to go into edit mode. - fn text_did_begin_editing(&self, _value: String) {} + fn text_did_begin_editing(&self, value: &str) {} /// Posts a notification when the text changes, and forwards the message to the text field’s cell if it responds. - fn text_did_change(&self, _value: String) {} + fn text_did_change(&self, value: &str) {} /// Performs validation on the text field’s new value. - fn text_should_end_editing(&self, _value: String) -> bool { + fn text_should_end_editing(&self, value: &str) -> bool { true } - - fn did_load(&mut self, view: TextField) {} }