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 web_input_position: WebInputPositionState,
141}
142
143impl<Message, Renderer> Widget<Message, Theme, Renderer> for TextEditor<'_, Message, Renderer>
144where
145 Renderer: core_text::Renderer,
146{
147 fn tag(&self) -> tree::Tag {
148 tree::Tag::of::<TextEditorState>()
149 }
150
151 fn state(&self) -> tree::State {
152 tree::State::new(TextEditorState::default())
153 }
154
155 fn children(&self) -> Vec<Tree> {
156 let inner: &dyn Widget<Message, Theme, Renderer> = &self.inner;
157
158 vec![Tree::new(inner)]
159 }
160
161 fn diff(&self, tree: &mut Tree) {
162 if tree.children.is_empty() {
163 tree.children = self.children();
164 } else {
165 self.inner.diff(&mut tree.children[0]);
166 tree.children.truncate(1);
167 }
168 }
169
170 fn size(&self) -> Size<Length> {
171 Widget::<Message, Theme, Renderer>::size(&self.inner)
172 }
173
174 fn size_hint(&self) -> Size<Length> {
175 Widget::<Message, Theme, Renderer>::size_hint(&self.inner)
176 }
177
178 fn layout(
179 &mut self,
180 tree: &mut Tree,
181 renderer: &Renderer,
182 limits: &layout::Limits,
183 ) -> layout::Node {
184 let inner = self.inner.layout(&mut tree.children[0], renderer, limits);
185
186 layout::Node::with_children(inner.size(), vec![inner])
187 }
188
189 fn operate(
190 &mut self,
191 tree: &mut Tree,
192 layout: Layout<'_>,
193 renderer: &Renderer,
194 operation: &mut dyn core_widget::Operation,
195 ) {
196 self.inner.operate(
197 &mut tree.children[0],
198 layout.children().next().unwrap(),
199 renderer,
200 operation,
201 );
202 }
203
204 fn update(
205 &mut self,
206 tree: &mut Tree,
207 event: &Event,
208 layout: Layout<'_>,
209 cursor: mouse::Cursor,
210 renderer: &Renderer,
211 clipboard: &mut dyn Clipboard,
212 shell: &mut Shell<'_, Message>,
213 viewport: &Rectangle,
214 ) {
215 let bounds = layout.bounds();
216 let visible_bounds = bounds.intersection(viewport);
217 let translated_event = touch_as_mouse_event(event);
218 let inner_event = translated_event.as_ref().unwrap_or(event);
219 let activation = {
220 let state = tree.state.downcast_mut::<TextEditorState>();
221 text_input_activation(
222 self.is_enabled,
223 &mut state.touch_activation,
224 &mut state.web_input_position,
225 event,
226 visible_bounds,
227 cursor,
228 )
229 };
230
231 let started_focused = {
232 let state = tree.children[0]
233 .state
234 .downcast_ref::<iced_text_editor::State<core_text::highlighter::PlainText>>();
235
236 state.is_focused()
237 };
238
239 match activation.inner_touch_handling {
240 TextFieldInnerTouchHandling::Forward => {
241 self.inner.update(
242 &mut tree.children[0],
243 inner_event,
244 layout.children().next().unwrap(),
245 activation.cursor,
246 renderer,
247 clipboard,
248 shell,
249 viewport,
250 );
251 }
252 TextFieldInnerTouchHandling::Suppress => {}
253 TextFieldInnerTouchHandling::ConfirmedTap => {
254 let press = Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left));
255 self.inner.update(
256 &mut tree.children[0],
257 &press,
258 layout.children().next().unwrap(),
259 activation.cursor,
260 renderer,
261 clipboard,
262 shell,
263 viewport,
264 );
265
266 let release = Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left));
267 self.inner.update(
268 &mut tree.children[0],
269 &release,
270 layout.children().next().unwrap(),
271 activation.cursor,
272 renderer,
273 clipboard,
274 shell,
275 viewport,
276 );
277 }
278 }
279
280 if activation.request_mobile_keyboard {
281 shell.request_redraw();
282 }
283
284 normalize_windows_ime_request(shell.input_method_mut(), bounds);
285
286 let is_focused = {
287 let state = tree.children[0]
288 .state
289 .downcast_ref::<iced_text_editor::State<core_text::highlighter::PlainText>>();
290
291 state.is_focused()
292 };
293
294 if is_focused && text_caret_refresh_event(event) {
295 let state = tree.children[0]
296 .state
297 .downcast_mut::<iced_text_editor::State<core_text::highlighter::PlainText>>();
298
299 core_widget::operation::Focusable::focus(state);
300 shell.request_redraw();
301 }
302
303 let input_anchor = web_input_anchor(
304 shell.input_method(),
305 visible_bounds,
306 activation,
307 started_focused,
308 is_focused,
309 );
310
311 sync_mobile_keyboard(
312 started_focused,
313 is_focused,
314 activation.request_mobile_keyboard,
315 input_anchor,
316 );
317 }
318
319 fn mouse_interaction(
320 &self,
321 tree: &Tree,
322 layout: Layout<'_>,
323 cursor: mouse::Cursor,
324 viewport: &Rectangle,
325 renderer: &Renderer,
326 ) -> mouse::Interaction {
327 self.inner.mouse_interaction(
328 &tree.children[0],
329 layout.children().next().unwrap(),
330 cursor,
331 viewport,
332 renderer,
333 )
334 }
335
336 fn draw(
337 &self,
338 tree: &Tree,
339 renderer: &mut Renderer,
340 theme: &Theme,
341 defaults: &renderer::Style,
342 layout: Layout<'_>,
343 cursor: mouse::Cursor,
344 viewport: &Rectangle,
345 ) {
346 register_mobile_text_region(self.is_enabled, layout.bounds(), viewport);
347
348 self.inner.draw(
349 &tree.children[0],
350 renderer,
351 theme,
352 defaults,
353 layout.children().next().unwrap(),
354 cursor,
355 viewport,
356 );
357 }
358
359 fn overlay<'b>(
360 &'b mut self,
361 tree: &'b mut Tree,
362 layout: Layout<'b>,
363 renderer: &Renderer,
364 viewport: &Rectangle,
365 translation: Vector,
366 ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
367 self.inner.overlay(
368 &mut tree.children[0],
369 layout.children().next().unwrap(),
370 renderer,
371 viewport,
372 translation,
373 )
374 }
375}
376
377impl<'a, Message, Renderer> From<TextEditor<'a, Message, Renderer>>
378 for Element<'a, Message, Theme, Renderer>
379where
380 Message: 'a,
381 Renderer: core_text::Renderer + 'a,
382{
383 fn from(text_editor: TextEditor<'a, Message, Renderer>) -> Self {
384 Element::new(text_editor)
385 }
386}
387
388pub fn outlined<'a, Message, Renderer>(
389 content: &'a Content<Renderer>,
390) -> TextEditor<'a, Message, Renderer>
391where
392 Renderer: core_text::Renderer + 'a,
393 Message: 'a,
394{
395 TextEditor::new(content)
396}
397
398pub fn outlined_area<'a, Message, Renderer>(
399 content: &'a Content<Renderer>,
400) -> TextEditor<'a, Message, Renderer>
401where
402 Renderer: core_text::Renderer + 'a,
403 Message: 'a,
404{
405 outlined(content).height(Length::Fixed(OUTLINED_AREA_HEIGHT))
406}