use std::ops::Range;
use ribir_core::prelude::*;
use crate::prelude::*;
mod edit_text;
mod text_glyphs;
mod text_editable;
mod text_selectable;
pub use edit_text::*;
pub use text_editable::*;
pub use text_glyphs::*;
pub use text_selectable::*;
class_names!(
INPUT,
TEXTAREA,
);
#[declare]
pub struct Input {
#[declare(skip)]
basic: BasicEditor<InputText>,
}
impl Input {
pub fn set_text(&mut self, text: &str) {
let v = text
.chars()
.filter(|c| *c != '\n' && *c != '\r')
.collect::<String>();
*self.basic.text_mut() = InputText::new(v);
let selection = &mut self.basic.selection;
selection.from = CaretPosition::default();
selection.to = CaretPosition::default();
}
pub fn text(&self) -> &CowArc<str> { &self.basic.text().0 }
pub fn select(&mut self, from: usize, to: usize) {
let selection = &mut self.basic.selection;
selection.from = CaretPosition::new(from);
selection.to = CaretPosition::new(to);
}
pub fn selection(&self) -> Range<usize> { self.basic.cluster_rg() }
}
#[declare]
pub struct TextArea {
#[declare(default = true)]
auto_wrap: bool,
#[declare(skip)]
basic: BasicEditor<CowArc<str>>,
}
impl TextArea {
pub fn set_text(&mut self, text: &str) {
*self.basic.text_mut() = text.to_string().into();
let selection = &mut self.basic.selection;
selection.from = CaretPosition::default();
selection.to = CaretPosition::default();
}
pub fn text(&self) -> &CowArc<str> { self.basic.text() }
pub fn select(&mut self, from: usize, to: usize) {
let selection = &mut self.basic.selection;
selection.from = CaretPosition::new(from);
selection.to = CaretPosition::new(to);
}
pub fn selection(&self) -> Range<usize> { self.basic.cluster_rg() }
}
#[derive(Clone, Eq, PartialEq, Default)]
pub struct InputText(CowArc<str>);
impl InputText {
pub fn new(v: impl Into<CowArc<str>>) -> Self { InputText(v.into()) }
pub fn text(&self) -> &CowArc<str> { &self.0 }
}
impl BaseText for InputText {
fn len(&self) -> usize { self.0.len() }
fn substr(&self, rg: Range<usize>) -> Substr { self.0.substr(rg) }
fn measure_bytes(&self, byte_from: usize, char_len: isize) -> usize {
self.0.measure_bytes(byte_from, char_len)
}
fn select_token(&self, byte_from: usize) -> Range<usize> {
BaseText::select_token(&self.0, byte_from)
}
}
impl VisualText for InputText {
fn layout_glyphs(&self, clamp: BoxClamp, ctx: &MeasureCtx) -> ParagraphLayoutRef {
self.0.layout_glyphs(clamp, ctx)
}
fn paint(
&self, painter: &mut Painter, style: PaintingStyle, glyphs: &ParagraphLayoutRef, rect: Rect,
) {
self.0.paint(painter, style, glyphs, rect);
}
}
impl EditText for InputText {
fn insert_str(&mut self, at: usize, v: &str) -> usize {
let new_v = v
.chars()
.filter(|c| *c != '\n' && *c != '\r')
.collect::<String>();
self.0.insert_str(at, new_v.as_str())
}
fn del_rg_str(&mut self, rg: Range<usize>) -> Range<usize> { self.0.del_rg_str(rg) }
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct CaretPosition {
pub cluster: usize,
pub affinity: CaretAffinity,
pub position: Option<(usize, usize)>,
}
impl CaretPosition {
pub const fn new(cluster: usize) -> Self {
Self { cluster, affinity: CaretAffinity::Downstream, position: None }
}
}
impl Default for CaretPosition {
fn default() -> Self { Self::new(0) }
}
impl Compose for Input {
fn compose(this: impl StateWriter<Value = Self>) -> Widget<'static> {
focus_scope! {
skip_host: true,
@TextClamp {
rows: Some(1.),
cols: Some(20.),
class: INPUT,
@FatObj {
scrollable: Scrollable::X,
@part_writer!(&mut this.basic)
}
}
}
.into_widget()
}
}
impl Compose for TextArea {
fn compose(this: impl StateWriter<Value = Self>) -> Widget<'static> {
focus_scope! {
@TextClamp {
rows: Some(2.),
cols: Some(20.),
class: TEXTAREA,
@Scrollbar {
text_overflow: TextOverflow::AutoWrap,
@part_writer!(&mut this.basic)
}
}
}
.into_widget()
}
}
#[cfg(test)]
mod tests {
use ribir_core::{prelude::*, reset_test_env, test_helper::*};
use super::*;
#[test]
fn input_edit() {
reset_test_env!();
let (value, w_value) = split_value(String::default());
let w = fn_widget! {
let input = @Input { auto_focus: true };
watch!($read(input).text().clone())
.subscribe(move |text| *$write(w_value) = text.to_string());
input
};
let wnd = TestWindow::new_with_size(w, Size::new(200., 200.));
wnd.draw_frame();
assert_eq!(*value.read(), "");
wnd.process_receive_chars("hello\nworld".into());
wnd.draw_frame();
assert_eq!(*value.read(), "helloworld");
}
#[test]
fn input_tap_focus() {
reset_test_env!();
let (value, w_value) = split_value(String::default());
let w = fn_widget! {
let input = @Input { };
watch!($read(input).text().clone())
.subscribe(move |text| *$write(w_value) = text.to_string());
@Container {
size: Size::new(200., 24.),
@ { input }
}
};
let wnd = TestWindow::new_with_size(w, Size::new(200., 200.));
wnd.draw_frame();
assert_eq!(*value.read(), "");
wnd.process_cursor_move(Point::new(50., 10.));
wnd.process_mouse_press(Box::new(DummyDeviceId), MouseButtons::PRIMARY);
wnd.process_mouse_release(Box::new(DummyDeviceId), MouseButtons::PRIMARY);
wnd.draw_frame();
wnd.process_receive_chars("hello".into());
wnd.draw_frame();
assert_eq!(*value.read(), "hello");
}
}