Merge pull request #5 from MerlinDE/TextField_event_handler
Standard macOS event handlers for TextField
This commit is contained in:
commit
ce1bca84ce
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
//! the implementation.
|
//! the implementation.
|
||||||
//!
|
//!
|
||||||
//! TextFields implement Autolayout, which enable you to specify how things should appear on the screen.
|
//! TextFields implement Autolayout, which enable you to specify how things should appear on the screen.
|
||||||
//!
|
//!
|
||||||
//! ```rust,no_run
|
//! ```rust,no_run
|
||||||
//! use cacao::color::rgb;
|
//! use cacao::color::rgb;
|
||||||
//! use cacao::layout::{Layout, LayoutConstraint};
|
//! use cacao::layout::{Layout, LayoutConstraint};
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
//! label: TextField,
|
//! label: TextField,
|
||||||
//! window: Window
|
//! window: Window
|
||||||
//! }
|
//! }
|
||||||
//!
|
//!
|
||||||
//! impl WindowDelegate for AppWindow {
|
//! impl WindowDelegate for AppWindow {
|
||||||
//! fn did_load(&mut self, window: Window) {
|
//! fn did_load(&mut self, window: Window) {
|
||||||
//! window.set_minimum_content_size(300., 300.);
|
//! 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.
|
//! 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")]
|
||||||
|
@ -70,20 +70,20 @@ 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 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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.
|
/// Objective-C runtime.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TextField<T = ()> {
|
pub struct TextField<T = ()> {
|
||||||
|
@ -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 {
|
||||||
|
@ -125,7 +125,7 @@ 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 view = allocate_view(register_view_class);
|
||||||
|
|
||||||
|
@ -144,18 +144,22 @@ 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 {
|
||||||
//let view: id = msg_send![register_view_class_with_delegate::<T>(), new];
|
//let view: id = msg_send![register_view_class_with_delegate::<T>(), new];
|
||||||
//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()
|
||||||
}
|
}
|
||||||
|
@ -209,11 +211,11 @@ impl<T> TextField<T> {
|
||||||
/// Call this to set the background color for the backing layer.
|
/// Call this to set the background color for the backing layer.
|
||||||
pub fn set_background_color(&self, color: Color) {
|
pub fn set_background_color(&self, color: Color) {
|
||||||
let bg = color.into_platform_specific_color();
|
let bg = color.into_platform_specific_color();
|
||||||
|
|
||||||
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];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 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) {}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue