1use super::*;
4
5pub use iced_text_editor::{Action, Binding, Content, KeyPress};
6
7pub const OUTLINED_AREA_HEIGHT: f32 = tokens::component::text_field::CONTAINER_HEIGHT * 2.0;
9
10pub struct TextEditor<'a, Message, Renderer = iced_widget::Renderer>
12where
13 Renderer: core_text::Renderer,
14{
15 is_enabled: bool,
16 inner: IcedTextEditor<'a, core_text::highlighter::PlainText, Message, Theme, Renderer>,
17}
18
19impl<Message, Renderer> std::fmt::Debug for TextEditor<'_, Message, Renderer>
20where
21 Renderer: core_text::Renderer,
22{
23 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
24 f.debug_struct("TextEditor")
25 .field("is_enabled", &self.is_enabled)
26 .finish_non_exhaustive()
27 }
28}
29
30impl<'a, Message, Renderer> TextEditor<'a, Message, Renderer>
31where
32 Message: 'a,
33 Renderer: core_text::Renderer + 'a,
34{
35 pub fn new(content: &'a Content<Renderer>) -> Self {
36 let inner = IcedTextEditor::new(content)
37 .padding(Padding {
38 top: tokens::component::text_field::TOP_SPACE,
39 right: tokens::component::text_field::TRAILING_SPACE,
40 bottom: tokens::component::text_field::BOTTOM_SPACE,
41 left: tokens::component::text_field::LEADING_SPACE,
42 })
43 .size(tokens::component::text_field::INPUT_TEXT_SIZE)
44 .line_height(absolute_line_height(
45 tokens::component::text_field::INPUT_TEXT_LINE_HEIGHT,
46 ))
47 .min_height(tokens::component::text_field::CONTAINER_HEIGHT)
48 .style(text_editor_style::default);
49
50 Self {
51 is_enabled: false,
52 inner,
53 }
54 }
55
56 pub fn id(mut self, id: impl Into<core_widget::Id>) -> Self {
57 self.inner = self.inner.id(id);
58 self
59 }
60
61 pub fn placeholder(mut self, placeholder: impl text::IntoFragment<'a>) -> Self {
62 self.inner = self.inner.placeholder(placeholder);
63 self
64 }
65
66 pub fn width(mut self, width: impl Into<Pixels>) -> Self {
67 self.inner = self.inner.width(width);
68 self
69 }
70
71 pub fn height(mut self, height: impl Into<Length>) -> Self {
72 self.inner = self.inner.height(height);
73 self
74 }
75
76 pub fn min_height(mut self, min_height: impl Into<Pixels>) -> Self {
77 self.inner = self.inner.min_height(min_height);
78 self
79 }
80
81 pub fn max_height(mut self, max_height: impl Into<Pixels>) -> Self {
82 self.inner = self.inner.max_height(max_height);
83 self
84 }
85
86 pub fn on_action(mut self, on_edit: impl Fn(Action) -> Message + 'a) -> Self {
87 self.is_enabled = true;
88 self.inner = self.inner.on_action(on_edit);
89 self
90 }
91
92 pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
93 self.inner = self.inner.font(font);
94 self
95 }
96
97 pub fn size(mut self, size: impl Into<Pixels>) -> Self {
98 self.inner = self.inner.size(size);
99 self
100 }
101
102 pub fn line_height(mut self, line_height: impl Into<core_text::LineHeight>) -> Self {
103 self.inner = self.inner.line_height(line_height);
104 self
105 }
106
107 pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
108 self.inner = self.inner.padding(padding);
109 self
110 }
111
112 pub fn wrapping(mut self, wrapping: core_text::Wrapping) -> Self {
113 self.inner = self.inner.wrapping(wrapping);
114 self
115 }
116
117 pub fn key_binding(
118 mut self,
119 key_binding: impl Fn(KeyPress) -> Option<Binding<Message>> + 'a,
120 ) -> Self {
121 self.inner = self.inner.key_binding(key_binding);
122 self
123 }
124
125 pub fn style(
126 mut self,
127 style: impl Fn(&Theme, iced_text_editor::Status) -> iced_text_editor::Style + 'a,
128 ) -> Self
129 where
130 <Theme as iced_text_editor::Catalog>::Class<'a>: From<iced_text_editor::StyleFn<'a, Theme>>,
131 {
132 self.inner = self.inner.style(style);
133 self
134 }
135}
136
137#[derive(Debug, Default)]
138struct TextEditorState {
139 touch_activation: Option<TextFieldTouchActivation>,
140}
141
142impl<Message, Renderer> Widget<Message, Theme, Renderer> for TextEditor<'_, Message, Renderer>
143where
144 Renderer: core_text::Renderer,
145{
146 fn tag(&self) -> tree::Tag {
147 tree::Tag::of::<TextEditorState>()
148 }
149
150 fn state(&self) -> tree::State {
151 tree::State::new(TextEditorState::default())
152 }
153
154 fn children(&self) -> Vec<Tree> {
155 let inner: &dyn Widget<Message, Theme, Renderer> = &self.inner;
156
157 vec![Tree::new(inner)]
158 }
159
160 fn diff(&self, tree: &mut Tree) {
161 if tree.children.is_empty() {
162 tree.children = self.children();
163 } else {
164 self.inner.diff(&mut tree.children[0]);
165 tree.children.truncate(1);
166 }
167 }
168
169 fn size(&self) -> Size<Length> {
170 Widget::<Message, Theme, Renderer>::size(&self.inner)
171 }
172
173 fn size_hint(&self) -> Size<Length> {
174 Widget::<Message, Theme, Renderer>::size_hint(&self.inner)
175 }
176
177 fn layout(
178 &mut self,
179 tree: &mut Tree,
180 renderer: &Renderer,
181 limits: &layout::Limits,
182 ) -> layout::Node {
183 let inner = self.inner.layout(&mut tree.children[0], renderer, limits);
184
185 layout::Node::with_children(inner.size(), vec![inner])
186 }
187
188 fn operate(
189 &mut self,
190 tree: &mut Tree,
191 layout: Layout<'_>,
192 renderer: &Renderer,
193 operation: &mut dyn core_widget::Operation,
194 ) {
195 self.inner.operate(
196 &mut tree.children[0],
197 layout.children().next().unwrap(),
198 renderer,
199 operation,
200 );
201 }
202
203 fn update(
204 &mut self,
205 tree: &mut Tree,
206 event: &Event,
207 layout: Layout<'_>,
208 cursor: mouse::Cursor,
209 renderer: &Renderer,
210 clipboard: &mut dyn Clipboard,
211 shell: &mut Shell<'_, Message>,
212 viewport: &Rectangle,
213 ) {
214 let bounds = layout.bounds();
215 let visible_bounds = bounds.intersection(viewport);
216 let translated_event = touch_as_mouse_event(event);
217 let inner_event = translated_event.as_ref().unwrap_or(event);
218 let activation = {
219 let state = tree.state.downcast_mut::<TextEditorState>();
220 text_input_activation(
221 self.is_enabled,
222 &mut state.touch_activation,
223 event,
224 visible_bounds,
225 cursor,
226 )
227 };
228
229 let started_focused = {
230 let state = tree.children[0]
231 .state
232 .downcast_ref::<iced_text_editor::State<core_text::highlighter::PlainText>>();
233
234 state.is_focused()
235 };
236
237 match activation.inner_touch_handling {
238 TextFieldInnerTouchHandling::Forward => {
239 self.inner.update(
240 &mut tree.children[0],
241 inner_event,
242 layout.children().next().unwrap(),
243 activation.cursor,
244 renderer,
245 clipboard,
246 shell,
247 viewport,
248 );
249 }
250 TextFieldInnerTouchHandling::Suppress => {}
251 TextFieldInnerTouchHandling::ConfirmedTap => {
252 let press = Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left));
253 self.inner.update(
254 &mut tree.children[0],
255 &press,
256 layout.children().next().unwrap(),
257 activation.cursor,
258 renderer,
259 clipboard,
260 shell,
261 viewport,
262 );
263
264 let release = Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left));
265 self.inner.update(
266 &mut tree.children[0],
267 &release,
268 layout.children().next().unwrap(),
269 activation.cursor,
270 renderer,
271 clipboard,
272 shell,
273 viewport,
274 );
275 }
276 }
277
278 if activation.request_mobile_keyboard {
279 shell.request_redraw();
280 }
281
282 normalize_windows_ime_request(shell.input_method_mut(), bounds);
283
284 let is_focused = {
285 let state = tree.children[0]
286 .state
287 .downcast_ref::<iced_text_editor::State<core_text::highlighter::PlainText>>();
288
289 state.is_focused()
290 };
291
292 if is_focused && text_caret_refresh_event(event) {
293 let state = tree.children[0]
294 .state
295 .downcast_mut::<iced_text_editor::State<core_text::highlighter::PlainText>>();
296
297 core_widget::operation::Focusable::focus(state);
298 shell.request_redraw();
299 }
300
301 if started_focused != is_focused {
302 sync_mobile_keyboard(
303 started_focused,
304 is_focused,
305 activation.request_mobile_keyboard,
306 );
307 }
308
309 if started_focused == is_focused {
310 sync_mobile_keyboard(
311 started_focused,
312 is_focused,
313 activation.request_mobile_keyboard,
314 );
315 }
316 }
317
318 fn mouse_interaction(
319 &self,
320 tree: &Tree,
321 layout: Layout<'_>,
322 cursor: mouse::Cursor,
323 viewport: &Rectangle,
324 renderer: &Renderer,
325 ) -> mouse::Interaction {
326 self.inner.mouse_interaction(
327 &tree.children[0],
328 layout.children().next().unwrap(),
329 cursor,
330 viewport,
331 renderer,
332 )
333 }
334
335 fn draw(
336 &self,
337 tree: &Tree,
338 renderer: &mut Renderer,
339 theme: &Theme,
340 defaults: &renderer::Style,
341 layout: Layout<'_>,
342 cursor: mouse::Cursor,
343 viewport: &Rectangle,
344 ) {
345 register_mobile_text_region(self.is_enabled, layout.bounds(), viewport);
346
347 self.inner.draw(
348 &tree.children[0],
349 renderer,
350 theme,
351 defaults,
352 layout.children().next().unwrap(),
353 cursor,
354 viewport,
355 );
356 }
357
358 fn overlay<'b>(
359 &'b mut self,
360 tree: &'b mut Tree,
361 layout: Layout<'b>,
362 renderer: &Renderer,
363 viewport: &Rectangle,
364 translation: Vector,
365 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
366 self.inner.overlay(
367 &mut tree.children[0],
368 layout.children().next().unwrap(),
369 renderer,
370 viewport,
371 translation,
372 )
373 }
374}
375
376impl<'a, Message, Renderer> From<TextEditor<'a, Message, Renderer>>
377 for Element<'a, Message, Theme, Renderer>
378where
379 Message: 'a,
380 Renderer: core_text::Renderer + 'a,
381{
382 fn from(text_editor: TextEditor<'a, Message, Renderer>) -> Self {
383 Element::new(text_editor)
384 }
385}
386
387pub fn outlined<'a, Message, Renderer>(
388 content: &'a Content<Renderer>,
389) -> TextEditor<'a, Message, Renderer>
390where
391 Renderer: core_text::Renderer + 'a,
392 Message: 'a,
393{
394 TextEditor::new(content)
395}
396
397pub fn outlined_area<'a, Message, Renderer>(
398 content: &'a Content<Renderer>,
399) -> TextEditor<'a, Message, Renderer>
400where
401 Renderer: core_text::Renderer + 'a,
402 Message: 'a,
403{
404 outlined(content).height(Length::Fixed(OUTLINED_AREA_HEIGHT))
405}