use std::{
cell::RefCell,
sync::Arc,
};
use glfw::{Modifiers};
use crate::FlutterEngineInner;
use super::{ Plugin, PlatformMessage, PluginRegistry};
use codec::{ MethodCall };
use serde_json::Value;
use utils::StringUtils;
use channel::{ Channel, JsonMethodChannel };
pub struct TextInputPlugin {
client_id: Option<i64>,
editing_state: RefCell<Option<TextEditingState>>,
channel: JsonMethodChannel,
}
impl TextInputPlugin {
pub fn new() -> TextInputPlugin {
TextInputPlugin {
client_id: None,
editing_state: RefCell::new(None),
channel: JsonMethodChannel::new("flutter/textinput"),
}
}
pub fn with_state(&self, cbk: impl Fn(&mut TextEditingState)) {
if let Ok(mut state) = self.editing_state.try_borrow_mut() {
if let Some(state) = &mut *state {
cbk(state);
}
}
}
fn get_lo_and_hi_idx(&self, s: &TextEditingState) -> (i64, i64) {
let (lo, hi) = if s.selection_base <= s.selection_extent {
(s.selection_base, s.selection_extent)
} else {
(s.selection_extent, s.selection_base)
};
return (lo, hi);
}
fn get_next_word_boundary(&self, s: &TextEditingState, start: i64, forward: bool) -> i64 {
let v: Vec<char> = s.text.chars().collect();
if forward {
let max = s.text.count() as i64;
if start >= max {
return max;
}
let s = &v[(start as usize) + 1..];
s.iter().position(|&c| !c.is_alphanumeric()).map_or(max, |n| {
start + n as i64 + 1
})
} else {
if start <= 0 {
return 0;
}
let s = &v[..(start as usize) - 1];
let len = s.iter().count() as i64;
s.iter().rposition(|c| !c.is_alphanumeric()).map_or(0, |n| {
start - len + n as i64
})
}
}
pub fn add_chars(&self, c: &str) {
self.remove_selected_text();
self.with_state(|s: &mut TextEditingState| {
let mut text = String::from(s.text.substring(0, s.selection_base as usize));
text.push_str(c);
text.push_str(&s.text.substring(s.selection_base as usize, s.text.count()));
s.text = text;
s.selection_base += c.chars().count() as i64;
s.selection_extent = s.selection_base;
});
self.notify_changes();
}
pub fn select_all(&self) {
self.with_state(|s: &mut TextEditingState| {
s.selection_base = 0;
s.selection_extent = s.text.count() as i64;
s.selection_is_directional = true;
});
self.notify_changes();
}
pub fn remove_selected_text(&self) -> bool {
let mut ret = false;
if let Some(s) = &mut *self.editing_state.borrow_mut() {
if s.selection_base != s.selection_extent {
let (lo, hi) = self.get_lo_and_hi_idx(s);
s.text = String::from(s.text.substring(0, lo as usize))
+ &s.text.substring(hi as usize, s.text.count());
s.selection_base = lo;
s.selection_extent = lo;
s.selection_is_directional = false;
ret = true;
}
}
if ret {
self.notify_changes();
}
ret
}
pub fn backspace(&self) {
if !self.remove_selected_text() {
self.with_state(|s: &mut TextEditingState| {
if s.selection_base > 0 {
s.selection_base -= 1;
s.selection_extent = s.selection_base;
s.selection_is_directional = false;
s.text = String::from(s.text.substring(0, s.selection_base as usize))
+ &s.text.substring(s.selection_extent as usize + 1, s.text.count());
}
});
self.notify_changes();
}
}
pub fn delete(&self) {
if !self.remove_selected_text() {
self.with_state(|s: &mut TextEditingState| {
if s.selection_extent < s.text.count() as i64 {
s.selection_extent -= 1;
s.selection_is_directional = false;
s.text = String::from(s.text.substring(0, s.selection_base as usize))
+ &s.text.substring(s.selection_extent as usize + 1, s.text.count());
}
});
self.notify_changes();
}
}
pub fn move_cursor_up(&self, modifiers: Modifiers) {
self.with_state(|s: &mut TextEditingState| {
let (lo, _) = self.get_lo_and_hi_idx(s);
let p = s.get_next_line_pos(lo as usize, false) as i64;
if modifiers.contains(Modifiers::Shift) {
s.select_to(p);
} else {
s.move_to(p);
}
});
self.notify_changes();
}
pub fn move_cursor_down(&self, modifiers: Modifiers) {
self.with_state(|s: &mut TextEditingState| {
let (_, hi) = self.get_lo_and_hi_idx(s);
let p = s.get_next_line_pos(hi as usize, true) as i64;
if modifiers.contains(Modifiers::Shift) {
s.select_to(p);
} else {
s.move_to(p);
}
});
self.notify_changes();
}
pub fn move_cursor_left(&self, modifiers: Modifiers) {
self.with_state(|s: &mut TextEditingState| {
let (lo, _) = self.get_lo_and_hi_idx(s);
let current_pos = if modifiers.contains(Modifiers::Shift) {
s.selection_extent
} else if s.selection_base != s.selection_extent {
lo + 1
} else {
lo
};
let next_pos = if modifiers.contains(Modifiers::Control) {
self.get_next_word_boundary(s, current_pos, false)
} else {
(current_pos - 1).max(0)
};
if modifiers.contains(Modifiers::Shift) {
s.select_to(next_pos);
} else {
s.move_to(next_pos);
}
});
self.notify_changes();
}
pub fn move_cursor_right(&self, modifiers: glfw::Modifiers) {
self.with_state(|s: &mut TextEditingState| {
let (_, hi) = self.get_lo_and_hi_idx(s);
let current_pos = if modifiers.contains(Modifiers::Shift) {
s.selection_extent
} else if s.selection_base != s.selection_extent {
hi - 1
} else {
hi
};
let next_pos = if modifiers.contains(Modifiers::Control) {
self.get_next_word_boundary(s, current_pos, true)
} else {
(current_pos + 1).min(s.text.count() as i64)
};
if modifiers.contains(Modifiers::Shift) {
s.select_to(next_pos);
} else {
s.move_to(next_pos);
}
});
self.notify_changes();
}
pub fn move_cursor_home(&self, modifiers: glfw::Modifiers) {
self.with_state(|s: &mut TextEditingState| {
if modifiers.contains(Modifiers::Shift) {
s.selection_is_directional = true;
} else {
s.selection_base = 0;
s.selection_is_directional = false;
}
s.selection_extent = 0;
});
self.notify_changes();
}
pub fn move_cursor_end(&self, modifiers: glfw::Modifiers) {
self.with_state(|s: &mut TextEditingState| {
if modifiers.contains(Modifiers::Shift) {
s.selection_is_directional = true;
} else {
s.selection_base = s.text.count() as i64;
s.selection_is_directional = false;
}
s.selection_extent = s.text.count() as i64;
});
self.notify_changes();
}
pub fn get_selected_text(&self) -> String {
if let Some(s) = &mut *self.editing_state.borrow_mut() {
if s.selection_base == s.selection_extent {
return "".to_string();
}
let (lo, hi) = self.get_lo_and_hi_idx(s);
s.text.substring(lo as usize, hi as usize).to_owned()
} else {
return "".to_string();
}
}
pub fn perform_action(&self, action: &str) {
self.channel.invoke_method(MethodCall {
method: String::from("TextInputClient.performAction"),
args: json!([self.client_id, "TextInputAction.".to_owned() + action])
});
}
fn notify_changes(&self) {
self.with_state(|s: &mut TextEditingState| {
self.channel.invoke_method(MethodCall {
method: String::from("TextInputClient.updateEditingState"),
args: json!([self.client_id, s]),
});
});
}
}
impl Plugin for TextInputPlugin {
fn init_channel(&self, registry: &PluginRegistry) -> &str {
self.channel.init(registry);
return self.channel.get_name();
}
fn handle(&mut self, msg: &PlatformMessage, _: Arc<FlutterEngineInner>, _: &mut glfw::Window) {
let decoded = self.channel.decode_method_call(msg);
info!("textinput mehod {:?}", decoded.method);
info!("textinput mehod {:?}", decoded.args);
match decoded.method.as_str() {
"TextInput.setClient" => {
if let Value::Array(v) = &decoded.args {
if v.len() > 0 {
if let Some(n) = v[0].as_i64() {
self.client_id = Some(n);
}
}
}
},
"TextInput.clearClient" => {
self.client_id = None;
self.editing_state.replace(None);
},
"TextInput.setEditingState" => {
if self.client_id.is_some() {
self.editing_state.replace(TextEditingState::from(&decoded.args));
}
},
"TextInput.show" => {},
"TextInput.hide" => {},
_ => {}
}
}
}
#[derive(Serialize, Deserialize, Default, Debug)]
#[serde(rename_all = "camelCase")]
pub struct TextEditingState {
composing_base: i64,
composing_extent: i64,
selection_affinity: String,
selection_base: i64,
selection_extent: i64,
selection_is_directional: bool,
text: String,
}
impl TextEditingState {
fn from(v: &Value) -> Option<Self> {
if let Some(m) = v.as_object() {
Some(Self {
composing_base: m.get("composingBase").unwrap_or(&json!(-1)).as_i64().unwrap(),
composing_extent: m.get("composingExtent").unwrap_or(&json!(-1)).as_i64().unwrap(),
selection_affinity: String::from(m.get("selectionAffinity").unwrap_or(&json!("")).as_str().unwrap()),
selection_base: m.get("selectionBase").unwrap_or(&json!(-1)).as_i64().unwrap(),
selection_extent: m.get("selectionExtent").unwrap_or(&json!(-1)).as_i64().unwrap(),
selection_is_directional: m.get("selectionIsDirectional").unwrap_or(&json!(false)).as_bool().unwrap(),
text: String::from(m.get("text").unwrap_or(&json!("")).as_str().unwrap()),
.. Default::default()
})
} else {
None
}
}
fn move_to(&mut self, p: i64) {
self.selection_base = p;
self.selection_extent = p;
self.selection_is_directional = false;
}
fn select_to(&mut self, p: i64) {
self.selection_is_directional = true;
self.selection_extent = p;
}
fn get_next_line_pos(&self, start: usize, forward: bool) -> usize {
let v: Vec<char> = self.text.chars().collect();
if forward {
let max = self.text.count();
if start >= max {
return max;
}
let s = &v[start + 1..];
s.iter().position(|&c| c == '\n').map_or(max, |n| {
start + n + 1
})
} else {
if start < 1 {
return 0;
}
let s = &v[..start - 1];
let len = s.iter().count();
s.iter().rposition(|&c| c == '\n').map_or(0, |n| {
start - len + n
})
}
}
}