use std::ops::Range;
use ribir_core::prelude::{anchor::Anchor, *};
use super::{
CaretPosition,
edit_text::EditText,
text_selectable::{Selection, TextSelectable},
};
use crate::{input::text_glyphs::ParagraphLayoutExt, prelude::*};
class_names! {
#[doc = "Class name for the text caret"]
TEXT_CARET,
}
#[derive(Declare, Default)]
pub struct BasicEditor<T: 'static> {
host: TextSelectable<T>,
pre_edit: Option<PreEditState>,
}
impl<T: Default + VisualText + EditText + Clone + 'static> Compose for BasicEditor<T> {
fn compose(this: impl StateWriter<Value = Self>) -> Widget<'static> {
fn_widget! {
let mut text = FatObj::new(part_writer!(&mut this.host));
let caret = pipe!(*$read(text.is_focused()))
.transform(|p| p.distinct_until_changed())
.map(move |v| v.then(|| fn_widget!{ Self::caret_widget($writer(this)) }));
let mut caret = FatObj::new(caret);
@Stack {
fit: StackFit::Passthrough,
@(text) {
margin: pipe!(EdgeInsets::only_right(*$read(caret.layout_width()))),
on_focus_in: move |e| { e.window().set_ime_allowed(true); },
on_focus_out: move|e| { e.window().set_ime_allowed(false); },
on_chars: move |e| {
let mut this = $write(this);
if !this.chars_handle(e) {
this.forget_modifies();
}
},
on_key_down: move |k| {
let mut this = $write(this);
if !this.keys_handle(k) {
this.forget_modifies();
}
},
on_ime_pre_edit: move|e| { $write(this).process_pre_edit(e);},
}
@InParentLayout{ @ { caret } }
}
}
.into_widget()
}
}
impl<T: EditText + 'static> BasicEditor<T> {
fn caret_widget(this: impl StateWriter<Value = Self>) -> Widget<'static> {
fn_widget! {
@Providers {
providers: [Provider::writer(this, Some(DirtyPhase::Layout))],
@CustomAnchor {
data: (),
anchor: |_: &(), _child_size: Size, _clamp: BoxClamp, ctx: &mut PlaceCtx| {
let id = ctx.widget_id();
let editor = Provider::reader_of::<BasicEditor<T>>(ctx).unwrap();
let caret_pos = editor.read().caret_pos();
let in_pre_edit = editor.read().is_in_pre_edit();
let wnd = ctx.window();
let scroll = Provider::writer_of::<ScrollableWidget>(ctx);
wnd.clone().once_layout_ready(move || {
let caret_size = wnd.widget_size(id);
if caret_size.is_none() {
return;
}
let caret_size = caret_size.unwrap();
if !in_pre_edit
&& let Some(scrollable) = scroll {
let lt = scrollable
.write()
.map_to_content(Point::zero(), id, &wnd)
.unwrap();
scrollable
.write()
.visible_content_box(Rect::new(lt, caret_size), Anchor::default());
}
let pos = wnd.map_to_global(Point::zero(), id);
wnd.set_ime_cursor_area(&Rect::new(pos, caret_size));
});
Anchor::left_top(caret_pos.x, caret_pos.y)
},
@TextClamp {
rows: Some(1.),
class: TEXT_CARET,
@ { Void::default() }
}
}
}
}
.into_widget()
}
fn caret_pos(&self) -> Point {
self
.glyphs()
.map(|g| g.cursor(self.selection.to))
.unwrap_or_default()
}
}
impl<T: EditText + 'static> BasicEditor<T> {
fn chars_handle(&mut self, event: &CharsEvent) -> bool {
if event.common.with_command_key() {
return false;
}
let chars = event
.chars
.chars()
.filter(|c| !c.is_control() || c.is_ascii_whitespace())
.collect::<String>();
if !chars.is_empty() {
self.insert(&chars);
return true;
}
false
}
fn keys_handle(&mut self, event: &KeyboardEvent) -> bool {
let mut deal = false;
if event.with_command_key() {
deal = self.edit_with_command(event);
}
if !deal {
deal = self.edit_with_key(event);
}
deal
}
fn edit_with_command(&mut self, event: &KeyboardEvent) -> bool {
if !event.with_command_key() {
return false;
}
match event.key_code() {
PhysicalKey::Code(KeyCode::KeyV) => {
let clipboard = AppCtx::clipboard();
let txt = clipboard.borrow_mut().read_text();
if let Ok(txt) = txt {
self.insert(&txt);
return true;
}
}
PhysicalKey::Code(KeyCode::KeyX) => {
let rg = self.cluster_rg();
if !rg.is_empty() {
let txt = self.substr(rg).to_string();
self.del_sel();
let clipboard = AppCtx::clipboard();
let _ = clipboard.borrow_mut().clear();
let _ = clipboard.borrow_mut().write_text(&txt);
return true;
}
}
_ => {}
};
false
}
fn edit_with_key(&mut self, key: &KeyboardEvent) -> bool {
match key.key() {
VirtualKey::Named(NamedKey::Backspace) => {
let mut rg = self.cluster_rg();
if rg.is_empty() {
let len = self.measure_bytes(rg.start, -1);
rg = Range { start: rg.start - len, end: rg.start };
}
!self.delete(rg).is_empty()
}
VirtualKey::Named(NamedKey::Delete) => {
let mut rg = self.cluster_rg();
if rg.is_empty() {
let len = self.measure_bytes(rg.start, 1);
rg = Range { start: rg.start, end: rg.start + len };
}
!self.delete(rg).is_empty()
}
_ => false,
}
}
fn insert(&mut self, chars: &str) -> usize {
let del_rg = self.del_sel();
let len = self.text_mut().insert_str(del_rg.start, chars);
let pos = CaretPosition::new(len + del_rg.start);
self.host.selection = Selection::splat(pos);
len
}
fn del_sel(&mut self) -> Range<usize> { self.delete(self.cluster_rg()) }
fn delete(&mut self, rg: Range<usize>) -> Range<usize> {
let del_rg = self.text_mut().del_rg_str(rg);
self.host.selection = Selection::splat(CaretPosition::new(del_rg.start));
del_rg
}
fn is_in_pre_edit(&self) -> bool { self.pre_edit.is_some() }
fn process_pre_edit(&mut self, e: &ImePreEditEvent) {
match &e.pre_edit {
ImePreEdit::Begin => {
self.del_sel();
self.pre_edit = Some(PreEditState { position: self.cluster_rg().start, value: None });
}
ImePreEdit::PreEdit { value, cursor } => {
let Some(pre_edit) = self.pre_edit.as_mut() else {
return;
};
let PreEditState { position: pos, value: editing } =
unsafe { &mut *(pre_edit as *mut PreEditState) };
if let Some(txt) = editing {
self.delete(Range { start: *pos, end: *pos + txt.len() });
}
let len = self.insert(value);
let pos = if len == value.len() {
*editing = Some(value.clone());
CaretPosition::new(*pos + cursor.map(|(start, _)| start).unwrap_or(0))
} else {
*editing = Some(
self
.substr(Range { start: *pos, end: *pos + len })
.to_string(),
);
CaretPosition::new(*pos + len)
};
self.host.selection = Selection::splat(pos);
}
ImePreEdit::End => {
if let Some(PreEditState { value: Some(txt), position, .. }) = self.pre_edit.take() {
self.delete(Range { start: position, end: position + txt.len() });
}
}
}
}
}
#[derive(Debug)]
struct PreEditState {
position: usize,
value: Option<String>,
}
impl<T> std::ops::Deref for BasicEditor<T> {
type Target = TextSelectable<T>;
fn deref(&self) -> &Self::Target { &self.host }
}
impl<T> std::ops::DerefMut for BasicEditor<T> {
fn deref_mut(&mut self) -> &mut Self::Target { &mut self.host }
}