use crate::core::Rect;
use crate::render::RenderContext;
use crate::signal::Signal1;
use crate::widget::{BaseWidget, Draw, Widget, WidgetKind};
pub struct RichEdit {
base: BaseWidget,
text: String,
selection: Option<(usize, usize)>,
read_only: bool,
pub text_changed: Signal1<String>,
pub selection_changed: Signal1<Option<(usize, usize)>>,
pub read_only_changed: Signal1<bool>,
pub cursor_position_changed: Signal1<usize>,
}
impl RichEdit {
pub fn new(geometry: Rect) -> Self {
Self {
base: BaseWidget::new(WidgetKind::RichEdit, geometry, "RichEdit"),
text: String::new(),
selection: None,
read_only: false,
text_changed: Signal1::new(),
selection_changed: Signal1::new(),
read_only_changed: Signal1::new(),
cursor_position_changed: Signal1::new(),
}
}
pub fn text(&self) -> &str {
&self.text
}
pub fn set_text(&mut self, text: String) {
if self.read_only || self.text == text {
return;
}
self.text = text;
self.selection = None;
self.text_changed.emit(self.text.clone());
self.cursor_position_changed.emit(self.text.len());
}
pub fn selection(&self) -> Option<(usize, usize)> {
self.selection
}
pub fn set_selection(&mut self, start: usize, end: usize) {
if self.read_only {
return;
}
let start = start.min(self.text.len());
let end = end.min(self.text.len());
if self.selection == Some((start, end)) {
return;
}
self.selection = Some((start, end));
self.selection_changed.emit(self.selection);
}
pub fn clear_selection(&mut self) {
if self.selection.is_none() {
return;
}
self.selection = None;
self.selection_changed.emit(None);
}
pub fn is_read_only(&self) -> bool {
self.read_only
}
pub fn set_read_only(&mut self, read_only: bool) {
if self.read_only == read_only {
return;
}
self.read_only = read_only;
self.read_only_changed.emit(read_only);
}
pub fn cursor_position(&self) -> usize {
self.selection.map_or(0, |(start, _)| start)
}
pub fn set_cursor_position(&mut self, position: usize) {
if self.read_only {
return;
}
let position = position.min(self.text.len());
self.selection = Some((position, position));
self.cursor_position_changed.emit(position);
}
}
impl Widget for RichEdit {
fn base(&self) -> &BaseWidget {
&self.base
}
fn base_mut(&mut self) -> &mut BaseWidget {
&mut self.base
}
}
impl Draw for RichEdit {
fn draw(&mut self, context: &mut RenderContext) {
let rect = self.base.geometry();
use crate::core::Color;
context.fill_rect(rect, Color::from_rgb(255, 255, 255));
context.draw_rect(
rect,
if self.read_only {
Color::from_rgb(220, 220, 220)
} else {
Color::from_rgb(180, 180, 180)
},
);
if !self.text.is_empty() {
let line = self.text.lines().next().unwrap_or("");
context.draw_text(
crate::core::Point::new(rect.x + 2, rect.y + rect.height as i32 / 2),
line,
&crate::core::Font::default(),
Color::from_rgb(0, 0, 0),
);
}
}
}
impl crate::event::EventHandler for RichEdit {
fn handle_event(&mut self, event: &crate::event::Event) {
if !self.base.is_enabled() || self.read_only {
return;
}
match event {
crate::event::Event::MousePress { pos: _, button } if *button == 1 => {
self.base.set_mouse_pressed(true);
}
crate::event::Event::MouseRelease { pos: _, button } if *button == 1 => {
self.base.set_mouse_pressed(false);
}
_ => { }
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::Rect;
#[test]
fn richedit_creation_defaults() {
let re = RichEdit::new(Rect::new(0, 0, 400, 300));
assert!(re.text().is_empty());
assert!(re.selection().is_none());
assert!(!re.is_read_only());
assert_eq!(re.cursor_position(), 0);
}
#[test]
fn richedit_set_text() {
let mut re = RichEdit::new(Rect::new(0, 0, 400, 300));
re.set_text("Hello RichEdit".to_string());
assert_eq!(re.text(), "Hello RichEdit");
}
#[test]
fn richedit_set_text_read_only() {
let mut re = RichEdit::new(Rect::new(0, 0, 400, 300));
re.set_read_only(true);
re.set_text("Should not change".to_string());
assert!(re.text().is_empty());
}
#[test]
fn richedit_read_only() {
let mut re = RichEdit::new(Rect::new(0, 0, 400, 300));
assert!(!re.is_read_only());
re.set_read_only(true);
assert!(re.is_read_only());
re.set_read_only(false);
assert!(!re.is_read_only());
}
#[test]
fn richedit_set_selection() {
let mut re = RichEdit::new(Rect::new(0, 0, 400, 300));
re.set_text("Hello World".to_string());
re.set_selection(0, 5);
assert_eq!(re.selection(), Some((0, 5)));
}
#[test]
fn richedit_clear_selection() {
let mut re = RichEdit::new(Rect::new(0, 0, 400, 300));
re.set_text("Hello World".to_string());
re.set_selection(0, 5);
re.clear_selection();
assert!(re.selection().is_none());
}
#[test]
fn richedit_set_cursor_position() {
let mut re = RichEdit::new(Rect::new(0, 0, 400, 300));
re.set_text("Hello".to_string());
re.set_cursor_position(3);
assert_eq!(re.cursor_position(), 3);
re.set_cursor_position(100); assert_eq!(re.cursor_position(), 5);
}
#[test]
fn richedit_geometry_delegation() {
let mut re = RichEdit::new(Rect::new(0, 0, 400, 300));
re.set_geometry(Rect::new(10, 10, 500, 400));
assert_eq!(re.geometry(), Rect::new(10, 10, 500, 400));
}
#[test]
fn richedit_visibility() {
let mut re = RichEdit::new(Rect::new(0, 0, 400, 300));
assert!(re.is_visible());
re.hide();
assert!(!re.is_visible());
re.show();
assert!(re.is_visible());
}
#[test]
fn richedit_enabled() {
let mut re = RichEdit::new(Rect::new(0, 0, 400, 300));
assert!(re.is_enabled());
re.set_enabled(false);
assert!(!re.is_enabled());
re.set_enabled(true);
assert!(re.is_enabled());
}
#[test]
fn richedit_id_kind() {
let re_a = RichEdit::new(Rect::new(0, 0, 100, 100));
let re_b = RichEdit::new(Rect::new(0, 0, 100, 100));
assert_ne!(re_a.id(), re_b.id());
assert_eq!(re_a.kind(), WidgetKind::RichEdit);
assert_eq!(re_b.kind(), WidgetKind::RichEdit);
}
#[test]
fn richedit_signal_accessors() {
let re = RichEdit::new(Rect::new(0, 0, 100, 100));
let _ = &re.text_changed;
let _ = &re.selection_changed;
let _ = &re.read_only_changed;
let _ = &re.cursor_position_changed;
}
}