use crate::core::{Color, Font, Point, Rect};
use crate::event::{Event, EventHandler};
use crate::render::RenderContext;
use crate::signal::Signal1;
use crate::widget::{BaseWidget, Draw, Widget, WidgetKind};
pub struct TextEdit {
base: BaseWidget,
text: String,
placeholder_text: String,
max_length: Option<usize>,
read_only: bool,
line_wrap: bool,
pub text_changed: Signal1<String>,
pub cursor_position_changed: Signal1<usize>,
}
impl TextEdit {
pub fn new(geometry: Rect) -> Self {
Self {
base: BaseWidget::new(WidgetKind::TextEdit, geometry, "TextEdit"),
text: String::new(),
placeholder_text: String::new(),
max_length: None,
read_only: false,
line_wrap: true,
text_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.text == text {
return;
}
self.text = text;
self.text_changed.emit(self.text.clone());
}
pub fn placeholder_text(&self) -> &str {
&self.placeholder_text
}
pub fn set_placeholder_text(&mut self, text: String) {
self.placeholder_text = text;
}
pub fn max_length(&self) -> Option<usize> {
self.max_length
}
pub fn set_max_length(&mut self, max_length: Option<usize>) {
self.max_length = max_length;
if let Some(max) = max_length {
if self.text.len() > max {
self.text.truncate(max);
self.text_changed.emit(self.text.clone());
}
}
}
pub fn is_read_only(&self) -> bool {
self.read_only
}
pub fn set_read_only(&mut self, read_only: bool) {
self.read_only = read_only;
}
pub fn line_wrap(&self) -> bool {
self.line_wrap
}
pub fn set_line_wrap(&mut self, line_wrap: bool) {
self.line_wrap = line_wrap;
}
pub fn line_count(&self) -> usize {
if self.text.is_empty() {
1
} else {
self.text.chars().filter(|&c| c == '\n').count() + 1
}
}
pub fn line_text(&self, line: usize) -> Option<&str> {
let mut start = 0;
let mut current_line = 0;
for (i, ch) in self.text.char_indices() {
if ch == '\n' {
if current_line == line {
return Some(&self.text[start..i]);
}
start = i + 1;
current_line += 1;
}
}
if current_line == line {
Some(&self.text[start..])
} else {
None
}
}
pub fn append(&mut self, text: &str) {
self.text.push_str(text);
self.text_changed.emit(self.text.clone());
}
pub fn clear(&mut self) {
self.set_text(String::new());
}
pub fn is_empty(&self) -> bool {
self.text.is_empty()
}
}
impl Widget for TextEdit {
fn base(&self) -> &BaseWidget {
&self.base
}
fn base_mut(&mut self) -> &mut BaseWidget {
&mut self.base
}
}
impl EventHandler for TextEdit {
fn handle_event(&mut self, event: &Event) {
self.base.handle_event(event);
if !self.base.is_enabled() || self.read_only {
return;
}
if let Event::KeyPress { key, .. } = event {
match *key {
8 => {
if !self.text.is_empty() {
self.text.pop();
self.text_changed.emit(self.text.clone());
}
}
13 => {
self.text.push('\n');
self.text_changed.emit(self.text.clone());
}
_ => {
if let Some(ch) = char::from_u32(*key) {
if ch.is_ascii_graphic() || ch == ' ' || ch == '\t' {
self.text.push(ch);
self.text_changed.emit(self.text.clone());
}
}
}
}
}
}
}
impl Draw for TextEdit {
fn draw(&mut self, context: &mut RenderContext) {
let rect = self.geometry();
let padding = 4;
let text_x = rect.x + padding;
let text_y = rect.y + padding;
context.fill_rect(rect, Color::from_rgb(255, 255, 255));
context.draw_rect(rect, Color::from_rgb(200, 200, 200));
let display_text = if self.text.is_empty() && !self.placeholder_text.is_empty() {
&self.placeholder_text
} else {
&self.text
};
if !display_text.is_empty() {
context.draw_text(
Point::new(text_x, text_y),
display_text,
&Font::default(),
Color::from_rgb(0, 0, 0),
);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::Rect;
#[test]
fn textedit_creation_defaults() {
let te = TextEdit::new(Rect::new(0, 0, 300, 200));
assert!(te.text().is_empty());
assert!(te.placeholder_text().is_empty());
assert_eq!(te.max_length(), None);
assert!(!te.is_read_only());
assert!(te.line_wrap());
assert!(te.is_empty());
assert_eq!(te.line_count(), 1);
}
#[test]
fn textedit_set_text() {
let mut te = TextEdit::new(Rect::new(0, 0, 300, 200));
te.set_text("Hello World".to_string());
assert_eq!(te.text(), "Hello World");
assert!(!te.is_empty());
}
#[test]
fn textedit_set_text_empty() {
let mut te = TextEdit::new(Rect::new(0, 0, 300, 200));
te.set_text("Some text".to_string());
te.set_text(String::new());
assert!(te.text().is_empty());
}
#[test]
fn textedit_placeholder() {
let mut te = TextEdit::new(Rect::new(0, 0, 300, 200));
assert!(te.placeholder_text().is_empty());
te.set_placeholder_text("Enter text here".to_string());
assert_eq!(te.placeholder_text(), "Enter text here");
te.set_placeholder_text(String::new());
assert!(te.placeholder_text().is_empty());
}
#[test]
fn textedit_max_length() {
let mut te = TextEdit::new(Rect::new(0, 0, 300, 200));
assert_eq!(te.max_length(), None);
te.set_max_length(Some(10));
assert_eq!(te.max_length(), Some(10));
te.set_max_length(None);
assert_eq!(te.max_length(), None);
}
#[test]
fn textedit_max_length_truncates() {
let mut te = TextEdit::new(Rect::new(0, 0, 300, 200));
te.set_text("Hello World Too Long".to_string());
te.set_max_length(Some(10));
assert_eq!(te.text().len(), 10);
}
#[test]
fn textedit_read_only() {
let mut te = TextEdit::new(Rect::new(0, 0, 300, 200));
assert!(!te.is_read_only());
te.set_read_only(true);
assert!(te.is_read_only());
te.set_read_only(false);
assert!(!te.is_read_only());
}
#[test]
fn textedit_line_wrap() {
let mut te = TextEdit::new(Rect::new(0, 0, 300, 200));
assert!(te.line_wrap());
te.set_line_wrap(false);
assert!(!te.line_wrap());
te.set_line_wrap(true);
assert!(te.line_wrap());
}
#[test]
fn textedit_line_count() {
let mut te = TextEdit::new(Rect::new(0, 0, 300, 200));
assert_eq!(te.line_count(), 1);
te.set_text("Line 1\nLine 2\nLine 3".to_string());
assert_eq!(te.line_count(), 3);
}
#[test]
fn textedit_line_text() {
let mut te = TextEdit::new(Rect::new(0, 0, 300, 200));
te.set_text("First\nSecond\nThird".to_string());
assert_eq!(te.line_text(0), Some("First"));
assert_eq!(te.line_text(1), Some("Second"));
assert_eq!(te.line_text(2), Some("Third"));
assert_eq!(te.line_text(5), None);
}
#[test]
fn textedit_append() {
let mut te = TextEdit::new(Rect::new(0, 0, 300, 200));
te.append("Hello");
assert_eq!(te.text(), "Hello");
te.append(" World");
assert_eq!(te.text(), "Hello World");
}
#[test]
fn textedit_clear() {
let mut te = TextEdit::new(Rect::new(0, 0, 300, 200));
te.set_text("Some text".to_string());
te.clear();
assert!(te.is_empty());
}
#[test]
fn textedit_geometry_delegation() {
let mut te = TextEdit::new(Rect::new(0, 0, 300, 200));
te.set_geometry(Rect::new(10, 10, 400, 300));
assert_eq!(te.geometry(), Rect::new(10, 10, 400, 300));
}
#[test]
fn textedit_visibility() {
let mut te = TextEdit::new(Rect::new(0, 0, 300, 200));
assert!(te.is_visible());
te.hide();
assert!(!te.is_visible());
te.show();
assert!(te.is_visible());
}
#[test]
fn textedit_enabled() {
let mut te = TextEdit::new(Rect::new(0, 0, 300, 200));
assert!(te.is_enabled());
te.set_enabled(false);
assert!(!te.is_enabled());
te.set_enabled(true);
assert!(te.is_enabled());
}
#[test]
fn textedit_id_kind() {
let te_a = TextEdit::new(Rect::new(0, 0, 100, 100));
let te_b = TextEdit::new(Rect::new(0, 0, 100, 100));
assert_ne!(te_a.id(), te_b.id());
assert_eq!(te_a.kind(), WidgetKind::TextEdit);
assert_eq!(te_b.kind(), WidgetKind::TextEdit);
}
#[test]
fn textedit_signal_accessors() {
let te = TextEdit::new(Rect::new(0, 0, 100, 100));
let _ = &te.text_changed;
let _ = &te.cursor_position_changed;
}
}