diff --git a/src/input/macos.rs b/src/input/macos.rs index 08e3fcf..2f2ee7f 100644 --- a/src/input/macos.rs +++ b/src/input/macos.rs @@ -11,14 +11,63 @@ use std::sync::Once; use objc::declare::ClassDecl; 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 crate::foundation::{id, YES, NO, NSUInteger}; 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; +/// 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) { + 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() + }); +} + +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() + }); +} + +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() + }); +} + +extern "C" fn text_should_begin_editing( + this: &mut Object, + _: Sel, + 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() + }) +} + +extern "C" fn text_should_end_editing( + this: &mut Object, + _: Sel, + 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() + }) +} + /// 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 /// used if there's no delegates. @@ -42,17 +91,37 @@ pub(crate) fn register_view_class_with_delegate() -> *cons static INIT: Once = Once::new(); INIT.call_once(|| unsafe { - let superclass = class!(NSView); + 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 // move. - decl.add_ivar::(TEXTFIELD_DELEGATE_PTR); - + decl.add_ivar::(TEXTFIELD_DELEGATE_PTR); + + decl.add_method( + sel!(textDidEndEditing:), + text_did_end_editing:: as extern "C" fn(&mut Object, _, _), + ); + decl.add_method( + sel!(textDidBeginEditing:), + text_did_begin_editing:: as extern "C" fn(&mut Object, _, _), + ); + decl.add_method( + sel!(textDidChange:), + text_did_change:: as extern "C" fn(&mut Object, _, _), + ); + decl.add_method( + sel!(textShouldBeginEditing:), + text_should_begin_editing:: as extern "C" fn(&mut Object, Sel, *mut Object) -> bool, + ); + decl.add_method( + sel!(textShouldEndEditing:), + text_should_end_editing:: as extern "C" fn(&mut Object, Sel, *mut Object) -> bool, + ); + VIEW_CLASS = decl.register(); }); - unsafe { - VIEW_CLASS - } + unsafe { VIEW_CLASS } } diff --git a/src/input/mod.rs b/src/input/mod.rs index 21a58fd..5d3bfb0 100644 --- a/src/input/mod.rs +++ b/src/input/mod.rs @@ -3,7 +3,7 @@ //! the implementation. //! //! TextFields implement Autolayout, which enable you to specify how things should appear on the screen. -//! +//! //! ```rust,no_run //! use cacao::color::rgb; //! use cacao::layout::{Layout, LayoutConstraint}; @@ -16,7 +16,7 @@ //! label: TextField, //! window: Window //! } -//! +//! //! impl WindowDelegate for AppWindow { //! fn did_load(&mut self, window: Window) { //! window.set_minimum_content_size(300., 300.); @@ -40,13 +40,13 @@ //! //! 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::{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::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}; #[cfg(target_os = "macos")] @@ -70,20 +70,20 @@ 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 allocate_view(registration_fn: fn() -> *const Class) -> id { unsafe { let view: id = msg_send![registration_fn(), new]; - let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints:NO]; + let _: () = msg_send![view, setTranslatesAutoresizingMaskIntoConstraints: NO]; #[cfg(target_os = "macos")] - let _: () = msg_send![view, setWantsLayer:YES]; + let _: () = msg_send![view, setWantsLayer: YES]; - view + view } } -/// A clone-able handler to an `NSTextField/UITextField` reference in the +/// A clone-able handler to an `NSTextField/UITextField` reference in the /// Objective-C runtime. #[derive(Debug)] pub struct TextField { @@ -115,7 +115,7 @@ pub struct TextField { pub center_x: LayoutAnchorX, /// A pointer to the Objective-C runtime center Y layout constraint. - pub center_y: LayoutAnchorY + pub center_y: LayoutAnchorY, } impl Default for TextField { @@ -125,7 +125,7 @@ impl Default for TextField { } impl TextField { - /// Returns a default `TextField`, suitable for + /// Returns a default `TextField`, suitable for pub fn new() -> Self { let view = allocate_view(register_view_class); @@ -144,18 +144,22 @@ impl TextField { } } -impl TextField where T: TextFieldDelegate + 'static { +impl TextField +where + T: TextFieldDelegate + 'static, +{ /// 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 delegate = Box::new(delegate); - + let mut delegate = Box::new(delegate); + let label = allocate_view(register_view_class_with_delegate::); 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 { @@ -171,7 +175,7 @@ impl TextField where T: TextFieldDelegate + 'static { 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 } @@ -193,15 +197,13 @@ impl TextField { height: self.height.clone(), center_x: self.center_x.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. pub fn get_value(&self) -> String { - let value = NSString::wrap(unsafe { - msg_send![&*self.objc, stringValue] - }); + let value = NSString::wrap(unsafe { msg_send![&*self.objc, stringValue] }); value.to_str().to_string() } @@ -209,11 +211,11 @@ impl TextField { /// Call this to set the background color for the backing layer. pub fn set_background_color(&self, color: Color) { let bg = color.into_platform_specific_color(); - + unsafe { let cg: id = msg_send![bg, CGColor]; 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 TextField { pub fn set_text_alignment(&self, alignment: TextAlign) { unsafe { let alignment: NSInteger = alignment.into(); - let _: () = msg_send![&*self.objc, setAlignment:alignment]; + let _: () = msg_send![&*self.objc, setAlignment: alignment]; } } @@ -249,7 +251,7 @@ impl Layout for TextField { let backing_node = view.get_backing_node(); unsafe { - let _: () = msg_send![&*self.objc, addSubview:backing_node]; + let _: () = msg_send![&*self.objc, addSubview: backing_node]; } } } diff --git a/src/input/traits.rs b/src/input/traits.rs index 8890454..bff4c9d 100644 --- a/src/input/traits.rs +++ b/src/input/traits.rs @@ -1,4 +1,37 @@ //! Various traits used for Labels. +use crate::input::TextField; + 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 field’s cell if it responds. + fn text_did_change(&self, _value: String) {} + + /// Performs validation on the text field’s new value. + fn text_should_end_editing(&self, _value: String) -> bool { + true + } + + fn did_load(&mut self, view: TextField) {} }