From 1e72c3e6cc2a221346488ebe9c4d5ba4f35da428 Mon Sep 17 00:00:00 2001 From: yvt Date: Sat, 23 May 2020 23:52:53 +0900 Subject: [PATCH] feat(ui): add `Entry[Core]::{text, set_text}` --- tcw3/meta/views/entry.tcwdl | 14 ++++++ tcw3/src/ui/views/entry.rs | 43 ++++++++++++++++++ tcw3/src/ui/views/entry/tests.rs | 76 ++++++++++++++++++++++++++++++++ 3 files changed, 133 insertions(+) diff --git a/tcw3/meta/views/entry.tcwdl b/tcw3/meta/views/entry.tcwdl index bf19b554..817a1dc3 100644 --- a/tcw3/meta/views/entry.tcwdl +++ b/tcw3/meta/views/entry.tcwdl @@ -13,6 +13,13 @@ pub comp crate::ui::views::Entry { const view: HView { pub get clone; } = ?; const style_elem: HElem { pub get clone; } = ?; + + /// Set or retrieve the text content. + /// + /// When you assign to this property, if the new value is different from the + /// current one, it resets various internal states such as an undo history. + /// Otherwise, it does nothing. + prop text: String { pub set; pub get clone; } = ?; } #[prototype_only] @@ -24,4 +31,11 @@ pub comp crate::ui::views::EntryCore { const view: HView { pub get clone; } = ?; const style_elem: HElem { pub get clone; } = ?; + + /// Set or retrieve the text content. + /// + /// When you assign to this property, if the new value is different from the + /// current one, it resets various internal states such as an undo history. + /// Otherwise, it does nothing. + prop text: String { pub set; pub get clone; } = ?; } diff --git a/tcw3/src/ui/views/entry.rs b/tcw3/src/ui/views/entry.rs index 8a5a39f7..253de38f 100644 --- a/tcw3/src/ui/views/entry.rs +++ b/tcw3/src/ui/views/entry.rs @@ -4,6 +4,7 @@ use arrayvec::ArrayVec; use cggeom::{box2, prelude::*, Box2}; use cgmath::{Matrix3, Point2, Vector2}; use flags_macro::flags; +use momo::momo; use rc_borrow::RcBorrow; use std::{ cell::{Cell, RefCell, RefMut}, @@ -84,6 +85,19 @@ impl Entry { pub fn class_set(&self) -> ClassSet { self.styled_box.class_set() } + + /// Get the text content. + pub fn text(&self) -> String { + self.core.text() + } + + /// Set the text content. + /// + /// If the new value is different from the current one, it resets various + /// internal states such as an undo history. Otherwise, it does nothing. + pub fn set_text(&self, value: impl Into) { + self.core.set_text(value) + } } impl Widget for Entry { @@ -238,6 +252,35 @@ impl EntryCore { pub fn style_elem(&self) -> theming::HElem { self.inner.style_elem.helem() } + + /// Get the text content. + pub fn text(&self) -> String { + self.inner.state.borrow().text.clone() + } + + /// Set the text content. + /// + /// If the new value is different from the current one, it resets various + /// internal states such as an undo history. Otherwise, it does nothing. + #[momo] + pub fn set_text(&self, value: impl Into) { + if self.inner.state.borrow().text == value { + return; + } + + let mut value = Some(value); + update_state( + self.view.as_ref(), + RcBorrow::from(&self.inner), + &mut |state| { + state.text = value.take().unwrap(); + state.sel_range = [0, 0]; + state.history = history::History::new(); + + UpdateStateFlags::ANY + }, + ); + } } impl State { diff --git a/tcw3/src/ui/views/entry/tests.rs b/tcw3/src/ui/views/entry/tests.rs index 13c5948d..8e0f6d62 100644 --- a/tcw3/src/ui/views/entry/tests.rs +++ b/tcw3/src/ui/views/entry/tests.rs @@ -72,3 +72,79 @@ fn text_input_ctx_activation(twm: &dyn TestingWm) { assert!(twm.expect_unique_active_text_input_ctx().is_none()); } + +#[allow(dead_code)] +struct TestWithOneEntry { + wm: pal::Wm, + hwnd: HWnd, + pal_hwnd: pal::HWnd, + entry: Entry, +} +fn init_test_with_one_entry(twm: &dyn TestingWm) -> TestWithOneEntry { + let wm = twm.wm(); + + let style_manager = Manager::global(wm); + + let entry = Entry::new(wm, style_manager); + + let wnd = HWnd::new(wm); + wnd.content_view().set_layout(TableLayout::stack_vert(vec![ + (entry.view(), AlignFlags::JUSTIFY), + ( + Spacer::new().with_min([100.0, 0.0]).into_view(), + AlignFlags::JUSTIFY, + ), + ])); + wnd.set_visibility(true); + + twm.step_unsend(); + + // Focus the window + let pal_hwnd = try_match!([x] = twm.hwnds().as_slice() => x.clone()) + .expect("could not get a single window"); + + twm.set_wnd_focused(&pal_hwnd, true); + twm.step_unsend(); + + TestWithOneEntry { + wm, + hwnd: wnd, + pal_hwnd, + entry, + } +} + +#[use_testing_wm(testing = "crate::testing")] +#[test] +fn set_text(twm: &dyn TestingWm) { + let TestWithOneEntry { + entry, + hwnd: _hwnd, + pal_hwnd, + .. + } = init_test_with_one_entry(twm); + + // Focus the text field by clicking it + let bounds = entry.view_ref().global_frame(); + simulate_click(twm, &pal_hwnd, bounds.min.average2(&bounds.min)); + + // Type something + { + let mut edit = twm.raise_edit(&twm.expect_unique_active_text_input_ctx().unwrap(), true); + edit.replace(0..0, "hello"); + } + twm.step_unsend(); + + assert!(entry.core().inner.state.borrow().history.can_undo()); + assert_eq!(entry.text(), "hello"); + + // Assign a new text + entry.set_text("world"); + twm.step_unsend(); + + // The new text should be there + assert_eq!(entry.text(), "world"); + + // .. and that history should be forgotten + assert!(!entry.core().inner.state.borrow().history.can_undo()); +}