Standard macOS event handlers for TextField

added text_should_begin_editing(), text_did_begin_editing(), text_did_change(), text_should_end_editing() and text_did_end_editing() event handlers

for now these only take the value from the TextField and hand it over as a String
This commit is contained in:
Alexander Czernay 2021-03-14 17:03:20 +01:00
parent 400e763160
commit a85f41be79
3 changed files with 137 additions and 33 deletions

View file

@ -11,14 +11,63 @@ use std::sync::Once;
use objc::declare::ClassDecl; use objc::declare::ClassDecl;
use objc::runtime::{Class, Object, Sel, BOOL}; use objc::runtime::{Class, Object, Sel, BOOL};
use objc::{class, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
use objc_id::Id; use objc_id::Id;
use crate::foundation::{id, YES, NO, NSUInteger};
use crate::dragdrop::DragInfo; use crate::dragdrop::DragInfo;
use crate::input::{TEXTFIELD_DELEGATE_PTR, TextFieldDelegate}; use crate::foundation::{id, NSString, NSUInteger, NO, YES};
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).
extern "C" fn text_did_end_editing<T: TextFieldDelegate>(this: &mut Object, _: Sel, info: id) {
let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR);
view.text_did_end_editing({
let s = NSString::wrap(unsafe { msg_send![this, stringValue] });
s.to_str().to_string()
});
}
extern "C" fn text_did_begin_editing<T: TextFieldDelegate>(this: &mut Object, _: Sel, info: id) {
let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR);
view.text_did_begin_editing({
let s = NSString::wrap(unsafe { msg_send![this, stringValue] });
s.to_str().to_string()
});
}
extern "C" fn text_did_change<T: TextFieldDelegate>(this: &mut Object, _: Sel, info: id) {
let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR);
view.text_did_change({
let s = NSString::wrap(unsafe { msg_send![this, stringValue] });
s.to_str().to_string()
});
}
extern "C" fn text_should_begin_editing<T: TextFieldDelegate>(
this: &mut Object,
_: Sel,
info: id,
) -> bool {
let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR);
view.text_should_begin_editing({
let s = NSString::wrap(unsafe { msg_send![this, stringValue] });
s.to_str().to_string()
})
}
extern "C" fn text_should_end_editing<T: TextFieldDelegate>(
this: &mut Object,
_: Sel,
info: id,
) -> bool {
let view = load::<T>(this, TEXTFIELD_DELEGATE_PTR);
view.text_should_end_editing({
let s = NSString::wrap(unsafe { msg_send![this, stringValue] });
s.to_str().to_string()
})
}
/// 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
/// have separate classes here since we don't want to waste cycles on methods that will never be /// have separate classes here since we don't want to waste cycles on methods that will never be
/// used if there's no delegates. /// used if there's no delegates.
@ -42,17 +91,37 @@ pub(crate) fn register_view_class_with_delegate<T: TextFieldDelegate>() -> *cons
static INIT: Once = Once::new(); static INIT: Once = Once::new();
INIT.call_once(|| unsafe { INIT.call_once(|| unsafe {
let superclass = class!(NSView); let superclass = class!(NSTextField);
// let superclass = class!(NSView);
let mut decl = ClassDecl::new("RSTTextInputFieldWithDelegate", superclass).unwrap(); 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);
decl.add_method(
sel!(textDidEndEditing:),
text_did_end_editing::<T> as extern "C" fn(&mut Object, _, _),
);
decl.add_method(
sel!(textDidBeginEditing:),
text_did_begin_editing::<T> as extern "C" fn(&mut Object, _, _),
);
decl.add_method(
sel!(textDidChange:),
text_did_change::<T> as extern "C" fn(&mut Object, _, _),
);
decl.add_method(
sel!(textShouldBeginEditing:),
text_should_begin_editing::<T> as extern "C" fn(&mut Object, Sel, *mut Object) -> bool,
);
decl.add_method(
sel!(textShouldEndEditing:),
text_should_end_editing::<T> as extern "C" fn(&mut Object, Sel, *mut Object) -> bool,
);
VIEW_CLASS = decl.register(); VIEW_CLASS = decl.register();
}); });
unsafe { unsafe { VIEW_CLASS }
VIEW_CLASS
}
} }

View file

@ -40,13 +40,13 @@
//! //!
//! For more information on Autolayout, view the module or check out the examples folder. //! For more information on Autolayout, view the module or check out the examples folder.
use objc_id::ShareId;
use objc::runtime::{Class, Object}; use objc::runtime::{Class, Object};
use objc::{msg_send, sel, sel_impl}; use objc::{msg_send, sel, sel_impl};
use objc_id::ShareId;
use crate::foundation::{id, nil, YES, NO, NSArray, NSInteger, NSString};
use crate::color::Color; use crate::color::Color;
use crate::layout::{Layout, LayoutAnchorX, LayoutAnchorY, LayoutAnchorDimension}; use crate::foundation::{id, nil, NSArray, NSInteger, NSString, NO, YES};
use crate::layout::{Layout, LayoutAnchorDimension, LayoutAnchorX, LayoutAnchorY};
use crate::text::{Font, TextAlign}; use crate::text::{Font, TextAlign};
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
@ -74,10 +74,10 @@ fn allocate_view(registration_fn: fn() -> *const Class) -> id {
unsafe { unsafe {
let view: id = msg_send![registration_fn(), new]; let view: id = msg_send![registration_fn(), new];
let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO]; let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints: NO];
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
let _: () = msg_send![view, setWantsLayer:YES]; let _: () = msg_send![view, setWantsLayer: YES];
view view
} }
@ -115,7 +115,7 @@ pub struct TextField<T = ()> {
pub center_x: LayoutAnchorX, pub center_x: LayoutAnchorX,
/// A pointer to the Objective-C runtime center Y layout constraint. /// A pointer to the Objective-C runtime center Y layout constraint.
pub center_y: LayoutAnchorY pub center_y: LayoutAnchorY,
} }
impl Default for TextField { impl Default for TextField {
@ -144,11 +144,14 @@ impl TextField {
} }
} }
impl<T> TextField<T> where T: TextFieldDelegate + 'static { impl<T> TextField<T>
where
T: TextFieldDelegate + 'static,
{
/// 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 delegate = Box::new(delegate); let mut delegate = Box::new(delegate);
let label = allocate_view(register_view_class_with_delegate::<T>); let label = allocate_view(register_view_class_with_delegate::<T>);
unsafe { unsafe {
@ -156,6 +159,7 @@ impl<T> TextField<T> where T: TextFieldDelegate + 'static {
//let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO]; //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 {
@ -171,7 +175,7 @@ impl<T> TextField<T> where T: TextFieldDelegate + 'static {
objc: unsafe { ShareId::from_ptr(label) }, objc: unsafe { ShareId::from_ptr(label) },
}; };
//(&mut delegate).did_load(label.clone_as_handle()); (&mut delegate).did_load(label.clone_as_handle());
label.delegate = Some(delegate); label.delegate = Some(delegate);
label label
} }
@ -193,15 +197,13 @@ impl<T> TextField<T> {
height: self.height.clone(), height: self.height.clone(),
center_x: self.center_x.clone(), center_x: self.center_x.clone(),
center_y: self.center_y.clone(), center_y: self.center_y.clone(),
objc: self.objc.clone() objc: self.objc.clone(),
} }
} }
/// Grabs the value from the textfield and returns it as an owned String. /// Grabs the value from the textfield and returns it as an owned String.
pub fn get_value(&self) -> String { pub fn get_value(&self) -> String {
let value = NSString::wrap(unsafe { let value = NSString::wrap(unsafe { msg_send![&*self.objc, stringValue] });
msg_send![&*self.objc, stringValue]
});
value.to_str().to_string() value.to_str().to_string()
} }
@ -213,7 +215,7 @@ impl<T> TextField<T> {
unsafe { unsafe {
let cg: id = msg_send![bg, CGColor]; let cg: id = msg_send![bg, CGColor];
let layer: id = msg_send![&*self.objc, layer]; let layer: id = msg_send![&*self.objc, layer];
let _: () = msg_send![layer, setBackgroundColor:cg]; let _: () = msg_send![layer, setBackgroundColor: cg];
} }
} }
@ -229,7 +231,7 @@ impl<T> TextField<T> {
pub fn set_text_alignment(&self, alignment: TextAlign) { pub fn set_text_alignment(&self, alignment: TextAlign) {
unsafe { unsafe {
let alignment: NSInteger = alignment.into(); let alignment: NSInteger = alignment.into();
let _: () = msg_send![&*self.objc, setAlignment:alignment]; let _: () = msg_send![&*self.objc, setAlignment: alignment];
} }
} }
@ -249,7 +251,7 @@ impl<T> Layout for TextField<T> {
let backing_node = view.get_backing_node(); let backing_node = view.get_backing_node();
unsafe { unsafe {
let _: () = msg_send![&*self.objc, addSubview:backing_node]; let _: () = msg_send![&*self.objc, addSubview: backing_node];
} }
} }
} }

View file

@ -1,4 +1,37 @@
//! Various traits used for Labels. //! Various traits used for Labels.
use crate::input::TextField;
pub trait TextFieldDelegate { 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
/// value *must* be unique per-type.
const NAME: &'static str;
/// You should rarely (read: probably never) need to implement this yourself.
/// It simply acts as a getter for the associated `NAME` const on this trait.
fn subclass_name(&self) -> &'static str {
Self::NAME
}
/// Posts a notification when the text is no longer in edit mode.
fn text_did_end_editing(&self, _value: String) {}
/// Requests permission to begin editing a text object.
fn text_should_begin_editing(&self, _value: String) -> 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) {}
/// 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) {}
/// Performs validation on the text fields new value.
fn text_should_end_editing(&self, _value: String) -> bool {
true
}
fn did_load(&mut self, view: TextField) {}
} }