-
Notifications
You must be signed in to change notification settings - Fork 882
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Soft keyboard input for iOS #3571
Changes from 8 commits
f9b058e
78bac6b
77d049c
9ce9105
66c9658
d3a54a2
e276c06
ab4a9e9
7200d84
11728ba
7bd5742
d8094f1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
#![allow(clippy::unnecessary_cast)] | ||
|
||
use std::cell::RefCell; | ||
|
||
use icrate::Foundation::{CGPoint, CGRect, CGSize, MainThreadMarker, NSObject, NSObjectProtocol}; | ||
use objc2::rc::Id; | ||
use objc2::runtime::ProtocolObject; | ||
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass, extern_methods}; | ||
use super::app_state::{self, EventWrapper}; | ||
|
||
use super::uikit::{UIResponder, UITextView, UITextViewDelegate}; | ||
use super::window::WinitUIWindow; | ||
use crate::{ | ||
keyboard::{ | ||
KeyCode, | ||
PhysicalKey, | ||
Key, | ||
KeyLocation, | ||
}, | ||
dpi::PhysicalPosition, | ||
event::{Event, KeyEvent, Force, Touch, TouchPhase, WindowEvent}, | ||
platform_impl::platform::DEVICE_ID, | ||
window::{WindowAttributes, WindowId as RootWindowId}, | ||
}; | ||
|
||
pub struct WinitTextFieldState { | ||
delegate: RefCell<Id<WinitTextFieldDelegate>>, | ||
} | ||
|
||
declare_class!( | ||
pub(crate) struct WinitTextField; | ||
|
||
unsafe impl ClassType for WinitTextField { | ||
#[inherits(UIResponder, NSObject)] | ||
type Super = UITextView; | ||
type Mutability = mutability::InteriorMutable; | ||
const NAME: &'static str = "WinitUITextView"; | ||
} | ||
|
||
impl DeclaredClass for WinitTextField { | ||
type Ivars = WinitTextFieldState; | ||
} | ||
|
||
unsafe impl WinitTextField { } | ||
); | ||
extern_methods!( | ||
unsafe impl WinitTextField { | ||
// These are methods from UIResponder | ||
#[method(becomeFirstResponder)] | ||
pub fn focus(&self) -> bool; | ||
|
||
#[method(resignFirstResponder)] | ||
pub fn unfocus(&self) -> bool; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should be moved to an |
||
|
||
fn window(&self) -> Option<Id<WinitUIWindow>> { | ||
unsafe { msg_send_id![self, window] } | ||
} | ||
Comment on lines
+48
to
+50
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd rather have this on |
||
} | ||
); | ||
|
||
declare_class!( | ||
pub(crate) struct WinitTextFieldDelegate; | ||
|
||
unsafe impl ClassType for WinitTextFieldDelegate { | ||
type Super = NSObject; | ||
type Mutability = mutability::MainThreadOnly; | ||
const NAME: &'static str = "WinitTextViewDelegate"; | ||
} | ||
|
||
impl DeclaredClass for WinitTextFieldDelegate { | ||
type Ivars = (); | ||
} | ||
|
||
unsafe impl NSObjectProtocol for WinitTextFieldDelegate {} | ||
unsafe impl UITextViewDelegate for WinitTextFieldDelegate { | ||
#[method(textViewDidBeginEditing:)] | ||
unsafe fn text_field_did_begin_editing(&self, sender: &WinitTextField) { | ||
let text = sender.text(); | ||
//println!("DidBeginEditing: {text}"); | ||
} | ||
|
||
#[method(textViewDidEndEditing:)] | ||
unsafe fn text_field_did_end_editing(&self, sender: &WinitTextField) { | ||
let text = sender.text(); | ||
//println!("DidEndEditing: {text}"); | ||
} | ||
|
||
#[method(textViewDidChange:)] | ||
unsafe fn text_field_did_change(&self, sender: &WinitTextField) { | ||
let text = sender.text(); | ||
//println!("textViewDidChange: {text}"); | ||
sender.text_changed(); | ||
} | ||
} | ||
); | ||
|
||
|
||
impl WinitTextField { | ||
|
||
pub(crate) fn new(mtm: MainThreadMarker) -> Id<Self> { | ||
// TODO: This should be hidden someplace. | ||
let frame = CGRect { | ||
origin: CGPoint { x: -20.0, y: -50.0 }, | ||
size: CGSize { | ||
width: 200.0, | ||
height: 40.0, | ||
}, | ||
}; | ||
let delegate: Id<WinitTextFieldDelegate> = unsafe { objc2::msg_send_id![mtm.alloc(), init]}; | ||
let this = Self::alloc().set_ivars( WinitTextFieldState{ | ||
delegate: RefCell::new(delegate), | ||
}); | ||
let this: Id<WinitTextField> = unsafe { msg_send_id![super(this), init] }; | ||
|
||
{ | ||
let delegate = this.ivars().delegate.borrow(); | ||
this.setDelegate(Some(ProtocolObject::from_ref(&*delegate.clone()))); | ||
} | ||
|
||
this.setFrame(frame); | ||
|
||
this | ||
} | ||
fn text_changed(&self) { | ||
let window = self.window().unwrap(); | ||
let mtm = MainThreadMarker::new().unwrap(); | ||
let text = self.text(); | ||
let text = text.to_string(); | ||
app_state::handle_nonuser_event( | ||
mtm, | ||
EventWrapper::StaticEvent(Event::WindowEvent { | ||
window_id: RootWindowId(window.id()), | ||
event: WindowEvent::KeyboardInput { | ||
device_id: DEVICE_ID, | ||
event: KeyEvent { | ||
physical_key: PhysicalKey::Code(KeyCode::F35), | ||
logical_key: Key::Character(text.clone().into()), | ||
text: Some(text.into()), | ||
location: KeyLocation::Standard, | ||
state: crate::event::ElementState::Pressed, | ||
repeat: false, | ||
platform_specific: super::KeyEventExtra{}, | ||
}, | ||
is_synthetic: false, | ||
}, | ||
}), | ||
); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
use super::UIView; | ||
use icrate::Foundation::{NSObject, NSString}; | ||
use objc2::mutability::IsMainThreadOnly; | ||
use objc2::rc::Id; | ||
use objc2::runtime::{NSObjectProtocol, ProtocolObject}; | ||
use objc2::{extern_class, extern_methods, extern_protocol, mutability, ClassType, ProtocolType}; | ||
|
||
extern_class!( | ||
#[derive(Debug, PartialEq, Eq, Hash)] | ||
pub(crate) struct UITextView; | ||
|
||
unsafe impl ClassType for UITextView { | ||
#[inherits(NSObject)] | ||
simlay marked this conversation as resolved.
Show resolved
Hide resolved
|
||
type Super = UIView; | ||
type Mutability = mutability::InteriorMutable; | ||
} | ||
); | ||
extern_methods!( | ||
unsafe impl UITextView { | ||
#[method(text)] | ||
pub fn text(&self) -> &NSString; | ||
simlay marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
#[method(setText:)] | ||
pub fn setText(&self, text: &NSString); | ||
|
||
#[method_id(@__retain_semantics Other delegate)] | ||
simlay marked this conversation as resolved.
Show resolved
Hide resolved
|
||
pub unsafe fn delegate(&self) -> Option<Id<ProtocolObject<dyn UITextViewDelegate>>>; | ||
|
||
#[method(setDelegate:)] | ||
pub fn setDelegate(&self, delegate: Option<&ProtocolObject<dyn UITextViewDelegate>>); | ||
} | ||
); | ||
extern_protocol!( | ||
pub unsafe trait UITextViewDelegate: NSObjectProtocol + IsMainThreadOnly { | ||
#[optional] | ||
#[method(textViewShouldBeginEditing:)] | ||
unsafe fn textViewShouldBeginEditing(&self, sender: &UITextView) -> bool; | ||
|
||
#[optional] | ||
#[method(textViewDidBeginEditing:)] | ||
unsafe fn textViewDidBeginEditing(&self, sender: &UITextView); | ||
|
||
#[optional] | ||
#[method(textViewShouldEndEditing:)] | ||
unsafe fn textViewShouldEndEditing(&self, sender: &UITextView) -> bool; | ||
|
||
#[optional] | ||
#[method(textViewDidEndEditing:)] | ||
unsafe fn textViewDidEndEditing(&self, sender: &UITextView); | ||
|
||
#[optional] | ||
#[method(textViewDidChange:)] | ||
unsafe fn textViewDidChange(&self, sender: &UITextView); | ||
} | ||
unsafe impl ProtocolType for dyn UITextViewDelegate {} | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suspect you don't need to subclass
UITextField
, i.e. this class is unnecessary as long as you make sure to store theWinitTextFieldDelegate
somewhere else (e.g. onWinitView
, as done currently).