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:
parent
c3922633b9
commit
42e4a0d798
|
@ -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 }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 field’s cell if it responds.
|
/// 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.
|
/// 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
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
fn did_load(&mut self, view: TextField) {}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue