1use crate::component::BuildContext;
42use crate::notifier::{Listenable, Subscription, ValueNotifier};
43
44#[derive(Clone, Default)]
48pub struct TextEditingController {
49 text: ValueNotifier<String>,
50}
51
52impl TextEditingController {
53 pub fn new() -> Self {
55 Self::default()
56 }
57
58 pub fn with_text(text: impl Into<String>) -> Self {
60 Self {
61 text: ValueNotifier::new(text.into()),
62 }
63 }
64
65 pub fn text(&self) -> String {
67 self.text.get()
68 }
69
70 pub fn with_text_ref<R>(&self, read: impl FnOnce(&str) -> R) -> R {
72 self.text.with(|text| read(text))
73 }
74
75 pub fn set_text(&self, text: impl Into<String>) {
78 self.text.set(text.into());
79 }
80
81 pub fn clear(&self) {
83 self.set_text("");
84 }
85
86 pub fn is_empty(&self) -> bool {
88 self.text.with(|text| text.is_empty())
89 }
90
91 pub fn len(&self) -> usize {
93 self.text.with(|text| text.chars().count())
94 }
95}
96
97impl Listenable for TextEditingController {
98 fn add_listener(&self, listener: impl Fn() + 'static) -> Subscription {
99 self.text.add_listener(listener)
100 }
101}
102
103impl std::fmt::Debug for TextEditingController {
104 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105 f.debug_tuple("TextEditingController")
106 .field(&self.text())
107 .finish()
108 }
109}
110
111impl BuildContext<'_> {
112 pub fn use_text_controller(&self, initial: &str) -> TextEditingController {
115 self.use_ref(|| TextEditingController::with_text(initial))
116 .get()
117 }
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use crate::{build_deferred, Callback, Runtime, TextField, Theme, Widget};
124 use std::cell::RefCell;
125 use std::rc::Rc;
126
127 #[test]
128 fn a_controller_holds_and_changes_its_text() {
129 let c = TextEditingController::with_text("ab");
130 assert_eq!(
131 (c.text(), c.len(), c.is_empty()),
132 ("ab".to_string(), 2, false)
133 );
134 c.set_text("héllo");
135 assert_eq!(c.len(), 5, "characters, not bytes");
136 c.clear();
137 assert!(c.is_empty());
138 assert!(c.with_text_ref(|t| t.is_empty()));
139 }
140
141 #[test]
142 fn a_clone_is_the_same_text() {
143 let a = TextEditingController::new();
144 let b = a.clone();
145 a.set_text("x");
146 assert_eq!(b.text(), "x");
147 }
148
149 #[test]
150 fn a_listener_hears_every_change_and_only_changes() {
151 let c = TextEditingController::new();
152 let heard = Rc::new(RefCell::new(Vec::new()));
153 let log = heard.clone();
154 let probe = c.clone();
155 let _l = c.add_listener(move || log.borrow_mut().push(probe.text()));
156 c.set_text("a");
157 c.set_text("a");
158 c.set_text("ab");
159 assert_eq!(*heard.borrow(), ["a", "ab"]);
160 }
161
162 #[test]
165 fn a_field_shows_its_controller_and_writes_back_to_it() {
166 let c = TextEditingController::with_text("start");
167 let field: TextField = TextField::new("ignored").controller(&c);
168 assert_eq!(Widget::text_value(&field), Some("start"));
169
170 let message: Callback = Widget::replace_value(&field, "typed".into()).expect("editable");
171 assert_eq!(c.text(), "start", "asking for the message changes nothing");
172 message.call();
173 assert_eq!(c.text(), "typed");
174
175 c.set_text("set from code");
176 let field: TextField = TextField::new("").controller(&c);
177 assert_eq!(
178 Widget::text_value(&field),
179 Some("set from code"),
180 "and what the program writes is what the next build shows"
181 );
182 }
183
184 #[test]
185 fn a_hook_gives_the_same_controller_on_every_build() {
186 let runtime = Runtime::default();
187 let seen: Rc<RefCell<Vec<TextEditingController>>> = Rc::default();
188 type Builder = Rc<dyn Fn(&BuildContext) -> Box<dyn Widget>>;
189 let build: Builder = {
190 let seen = seen.clone();
191 Rc::new(move |cx| {
192 let c = cx.use_text_controller("hello");
193 seen.borrow_mut().push(c.clone());
194 Box::new(TextField::new("").controller(&c))
195 })
196 };
197 let make = || {
198 let build = build.clone();
199 crate::Component::stateless(move |cx: &BuildContext| build(cx))
200 };
201 for _ in 0..2 {
202 runtime.states.begin_build();
203 build_deferred(&make(), &Theme::default(), &runtime);
204 runtime.states.end_frame();
205 seen.borrow()[0].set_text("changed");
206 }
207 let seen = seen.borrow();
208 assert_eq!(seen.len(), 2);
209 assert_eq!(
210 seen[1].text(),
211 "changed",
212 "the same text, kept between builds"
213 );
214 }
215}