Cleans up a few pieces of #5.

- Changes trait callbacks to receive a `&str` rather than `String`.

- Fixes a bug where multiple text field types with different delegates
  would not receive the right delegate type when coming from the
  Objective-C side.

- Minor cleanup.
This commit is contained in:
Ryan McGrath 2021-03-17 21:57:52 -07:00
parent c3922633b9
commit 42e4a0d798
No known key found for this signature in database
GPG key ID: DA6CBD9233593DEA
3 changed files with 47 additions and 67 deletions

View file

@ -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 std::sync::Once;
use objc::declare::ClassDecl; use objc::declare::ClassDecl;
@ -15,57 +6,54 @@ use objc::{class, msg_send, sel, sel_impl};
use objc_id::Id; use objc_id::Id;
use crate::dragdrop::DragInfo; 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::input::{TextFieldDelegate, TEXTFIELD_DELEGATE_PTR};
use crate::utils::load; use crate::utils::load;
/// Called when editing this text field has ended (e.g. user pressed enter). /// Called when editing this text field has ended (e.g. user pressed enter).
extern "C" fn text_did_end_editing<T: TextFieldDelegate>(this: &mut Object, _: Sel, info: id) { extern "C" fn text_did_end_editing<T: TextFieldDelegate>(this: &mut Object, _: Sel, _info: id) {
let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR); let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR);
view.text_did_end_editing({ let s = NSString::from_retained(unsafe { msg_send![this, stringValue] });
let s = NSString::wrap(unsafe { msg_send![this, stringValue] }); view.text_did_end_editing(s.to_str());
s.to_str().to_string()
});
} }
extern "C" fn text_did_begin_editing<T: TextFieldDelegate>(this: &mut Object, _: Sel, info: id) { extern "C" fn text_did_begin_editing<T: TextFieldDelegate>(this: &mut Object, _: Sel, _info: id) {
let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR); let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR);
view.text_did_begin_editing({ let s = NSString::from_retained(unsafe { msg_send![this, stringValue] });
let s = NSString::wrap(unsafe { msg_send![this, stringValue] }); view.text_did_begin_editing(s.to_str());
s.to_str().to_string()
});
} }
extern "C" fn text_did_change<T: TextFieldDelegate>(this: &mut Object, _: Sel, info: id) { extern "C" fn text_did_change<T: TextFieldDelegate>(this: &mut Object, _: Sel, _info: id) {
let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR); let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR);
view.text_did_change({ let s = NSString::from_retained(unsafe { msg_send![this, stringValue] });
let s = NSString::wrap(unsafe { msg_send![this, stringValue] }); view.text_did_change(s.to_str());
s.to_str().to_string()
});
} }
extern "C" fn text_should_begin_editing<T: TextFieldDelegate>( extern "C" fn text_should_begin_editing<T: TextFieldDelegate>(
this: &mut Object, this: &mut Object,
_: Sel, _: Sel,
info: id, _info: id,
) -> bool { ) -> BOOL {
let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR); let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR);
view.text_should_begin_editing({ let s = NSString::from_retained(unsafe { msg_send![this, stringValue] });
let s = NSString::wrap(unsafe { msg_send![this, stringValue] });
s.to_str().to_string() match view.text_should_begin_editing(s.to_str()) {
}) true => YES,
false => NO
}
} }
extern "C" fn text_should_end_editing<T: TextFieldDelegate>( extern "C" fn text_should_end_editing<T: TextFieldDelegate>(
this: &mut Object, this: &mut Object,
_: Sel, _: Sel,
info: id, _info: id,
) -> bool { ) -> BOOL {
let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR); let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR);
view.text_should_end_editing({ let s = NSString::from_retained(unsafe { msg_send![this, stringValue] });
let s = NSString::wrap(unsafe { msg_send![this, stringValue] }); match view.text_should_end_editing(s.to_str()) {
s.to_str().to_string() true => YES,
}) false => NO
}
} }
/// Injects an `NSTextField` subclass. This is used for the default views that don't use delegates - we /// 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 /// Injects an `NSTextField` subclass, with some callback and pointer ivars for what we
/// need to do. /// need to do.
pub(crate) fn register_view_class_with_delegate<T: TextFieldDelegate>() -> *const Class { pub(crate) fn register_view_class_with_delegate<T: TextFieldDelegate>(instance: &T) -> *const Class {
static mut VIEW_CLASS: *const Class = 0 as *const Class; load_or_register_class("NSTextField", instance.subclass_name(), |decl| unsafe {
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();
// A pointer to the "view controller" on the Rust side. It's expected that this doesn't // A pointer to the "view controller" on the Rust side. It's expected that this doesn't
// move. // move.
decl.add_ivar::<usize>(TEXTFIELD_DELEGATE_PTR); decl.add_ivar::<usize>(TEXTFIELD_DELEGATE_PTR);
@ -113,15 +94,11 @@ pub(crate) fn register_view_class_with_delegate<T: TextFieldDelegate>() -> *cons
); );
decl.add_method( decl.add_method(
sel!(textShouldBeginEditing:), sel!(textShouldBeginEditing:),
text_should_begin_editing::<T> as extern "C" fn(&mut Object, Sel, *mut Object) -> bool, text_should_begin_editing::<T> as extern "C" fn(&mut Object, Sel, id) -> bool,
); );
decl.add_method( decl.add_method(
sel!(textShouldEndEditing:), sel!(textShouldEndEditing:),
text_should_end_editing::<T> as extern "C" fn(&mut Object, Sel, *mut Object) -> bool, text_should_end_editing::<T> as extern "C" fn(&mut Object, Sel, id) -> bool,
); );
})
VIEW_CLASS = decl.register();
});
unsafe { VIEW_CLASS }
} }

View file

@ -67,9 +67,9 @@ pub use traits::TextFieldDelegate;
pub(crate) static TEXTFIELD_DELEGATE_PTR: &str = "rstTextFieldDelegatePtr"; pub(crate) static TEXTFIELD_DELEGATE_PTR: &str = "rstTextFieldDelegatePtr";
/// A helper method for instantiating view classes and applying default settings to them. /// 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 { unsafe {
let view: id = msg_send![registration_fn(), new]; let view: id = msg_send![class, new];
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints: NO]; let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints: NO];
@ -130,7 +130,8 @@ impl Default for TextField {
impl TextField { impl TextField {
/// Returns a default `TextField`, suitable for /// Returns a default `TextField`, suitable for
pub fn new() -> Self { pub fn new() -> Self {
let view = allocate_view(register_view_class); let class = register_view_class();
let view = common_init(class);
TextField { TextField {
delegate: None, delegate: None,
@ -156,15 +157,13 @@ where
/// Initializes a new TextField with a given `TextFieldDelegate`. This enables you to respond to events /// 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. /// and customize the view as a module, similar to class-based systems.
pub fn with(delegate: T) -> TextField<T> { pub fn with(delegate: T) -> TextField<T> {
let class = register_view_class_with_delegate(&delegate);
let mut delegate = Box::new(delegate); let mut delegate = Box::new(delegate);
let label = allocate_view(register_view_class_with_delegate::<T>); let label = common_init(class);
unsafe { unsafe {
//let view: id = msg_send![register_view_class_with_delegate::<T>(), new];
//let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO];
let ptr: *const T = &*delegate; let ptr: *const T = &*delegate;
(&mut *label).set_ivar(TEXTFIELD_DELEGATE_PTR, ptr as usize); (&mut *label).set_ivar(TEXTFIELD_DELEGATE_PTR, ptr as usize);
// let _: () = msg_send![self., setDelegate: label];
}; };
let mut label = TextField { let mut label = TextField {

View file

@ -2,6 +2,8 @@
use crate::input::TextField; use crate::input::TextField;
/// This trait can be used for implementing custom text field behavior.
#[allow(unused_variables)]
pub trait TextFieldDelegate { pub trait TextFieldDelegate {
/// Used to cache subclass creations on the Objective-C side. /// Used to cache subclass creations on the Objective-C side.
/// You can just set this to be the name of your view type. This /// You can just set this to be the name of your view type. This
@ -14,24 +16,26 @@ pub trait TextFieldDelegate {
Self::NAME 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. /// 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. /// 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 true
} }
/// Posts a notification to the default notification center that the text is about to go into edit mode. /// 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 fields cell if it responds. /// Posts a notification when the text changes, and forwards the message to the text fields cell if it responds.
fn text_did_change(&self, _value: String) {} fn text_did_change(&self, value: &str) {}
/// Performs validation on the text fields new value. /// Performs validation on the text fields new value.
fn text_should_end_editing(&self, _value: String) -> bool { fn text_should_end_editing(&self, value: &str) -> bool {
true true
} }
fn did_load(&mut self, view: TextField) {}
} }