1pub use iced_widget::text_editor::{
7 Action, Binding, Catalog, Content, Cursor, Edit, Id, KeyPress, Line, LineEnding, Motion,
8 Position, Selection, State, Status, Style, StyleFn,
9};
10
11use crate::widget::menu::MenuBarState;
12
13use iced_core::event::Event;
14use iced_core::text::highlighter;
15use iced_core::widget::Widget;
16use iced_core::widget::tree::{self, Tree};
17use iced_core::{
18 Clipboard, Layout, Length, Rectangle, Shell, Size, Vector, mouse, overlay, renderer, window,
19};
20
21type InnerEditor<'a, Message> =
22 iced_widget::TextEditor<'a, highlighter::PlainText, Message, crate::Theme, crate::Renderer>;
23
24pub struct TextEditor<'a, Message> {
25 inner: InnerEditor<'a, Message>,
26 has_context_menu: bool,
27 window_id: window::Id,
28}
29
30struct EditorWrapperState {
31 menu_bar_state: MenuBarState,
32 pending_action: crate::widget::text_context_menu::PendingAction,
33}
34
35impl<'a, Message: Clone + 'static> TextEditor<'a, Message> {
36 pub fn new(content: &'a Content<crate::Renderer>) -> Self {
38 Self {
39 inner: iced_widget::text_editor(content),
40 has_context_menu: true,
41 window_id: crate::widget::text_context_menu::current_window_id(),
42 }
43 }
44
45 pub fn context_menu(mut self, enabled: bool) -> Self {
49 self.has_context_menu = enabled;
50 self
51 }
52
53 pub fn id(mut self, id: impl Into<iced_core::widget::Id>) -> Self {
54 self.inner = self.inner.id(id);
55 self
56 }
57
58 pub fn placeholder(mut self, placeholder: impl iced_core::text::IntoFragment<'a>) -> Self {
59 self.inner = self.inner.placeholder(placeholder);
60 self
61 }
62
63 pub fn width(mut self, width: impl Into<iced_core::Pixels>) -> Self {
64 self.inner = self.inner.width(width);
65 self
66 }
67
68 pub fn height(mut self, height: impl Into<Length>) -> Self {
69 self.inner = self.inner.height(height);
70 self
71 }
72
73 pub fn min_height(mut self, min_height: impl Into<iced_core::Pixels>) -> Self {
74 self.inner = self.inner.min_height(min_height);
75 self
76 }
77
78 pub fn max_height(mut self, max_height: impl Into<iced_core::Pixels>) -> Self {
79 self.inner = self.inner.max_height(max_height);
80 self
81 }
82
83 pub fn on_action(mut self, on_edit: impl Fn(Action) -> Message + 'a) -> Self {
84 self.inner = self.inner.on_action(on_edit);
85 self
86 }
87
88 pub fn font(
89 mut self,
90 font: impl Into<<crate::Renderer as iced_core::text::Renderer>::Font>,
91 ) -> Self {
92 self.inner = self.inner.font(font);
93 self
94 }
95
96 pub fn size(mut self, size: impl Into<iced_core::Pixels>) -> Self {
97 self.inner = self.inner.size(size);
98 self
99 }
100
101 pub fn line_height(mut self, line_height: impl Into<iced_core::text::LineHeight>) -> Self {
102 self.inner = self.inner.line_height(line_height);
103 self
104 }
105
106 pub fn padding(mut self, padding: impl Into<iced_core::Padding>) -> Self {
107 self.inner = self.inner.padding(padding);
108 self
109 }
110
111 pub fn wrapping(mut self, wrapping: iced_core::text::Wrapping) -> Self {
112 self.inner = self.inner.wrapping(wrapping);
113 self
114 }
115
116 pub fn key_binding(
117 mut self,
118 key_binding: impl Fn(KeyPress) -> Option<Binding<Message>> + 'a,
119 ) -> Self {
120 self.inner = self.inner.key_binding(key_binding);
121 self
122 }
123
124 #[must_use]
125 pub fn style(mut self, style: impl Fn(&crate::Theme, Status) -> Style + 'a) -> Self
126 where
127 <crate::Theme as Catalog>::Class<'a>:
128 From<iced_widget::text_editor::StyleFn<'a, crate::Theme>>,
129 {
130 self.inner = self.inner.style(style);
131 self
132 }
133
134 fn uses_popup_context_menu(&self) -> bool {
135 #[cfg(all(wayland_platform, feature = "winit"))]
136 if matches!(
137 crate::app::cosmic::WINDOWING_SYSTEM.get(),
138 Some(crate::app::cosmic::WindowingSystem::Wayland)
139 ) {
140 return true;
141 }
142 false
143 }
144}
145
146pub fn text_editor<'a, Message: Clone + 'static>(
148 content: &'a Content<crate::Renderer>,
149) -> TextEditor<'a, Message> {
150 TextEditor::new(content)
151}
152
153fn ew<'x, Message>(
154 inner: &'x InnerEditor<'_, Message>,
155) -> &'x dyn Widget<Message, crate::Theme, crate::Renderer> {
156 inner
157}
158
159fn ew_mut<'x, Message>(
160 inner: &'x mut InnerEditor<'_, Message>,
161) -> &'x mut dyn Widget<Message, crate::Theme, crate::Renderer> {
162 inner
163}
164
165impl<'a, Message: Clone + 'static> Widget<Message, crate::Theme, crate::Renderer>
166 for TextEditor<'a, Message>
167{
168 fn tag(&self) -> tree::Tag {
169 if self.has_context_menu {
170 tree::Tag::of::<EditorWrapperState>()
171 } else {
172 ew::<Message>(&self.inner).tag()
173 }
174 }
175
176 fn state(&self) -> tree::State {
177 if self.has_context_menu {
178 tree::State::new(EditorWrapperState {
179 menu_bar_state: MenuBarState::default(),
180 pending_action: crate::widget::text_context_menu::pending_action(),
181 })
182 } else {
183 ew::<Message>(&self.inner).state()
184 }
185 }
186
187 fn children(&self) -> Vec<Tree> {
188 if self.has_context_menu {
189 vec![Tree::new(ew::<Message>(&self.inner))]
190 } else {
191 ew::<Message>(&self.inner).children()
192 }
193 }
194
195 fn diff(&mut self, tree: &mut Tree) {
196 if self.has_context_menu {
197 if let Some(child) = tree.children.first_mut() {
198 child.diff(
199 &mut self.inner as &mut dyn Widget<Message, crate::Theme, crate::Renderer>,
200 );
201 }
202 } else {
203 ew_mut::<Message>(&mut self.inner).diff(tree);
204 }
205 }
206
207 fn size(&self) -> Size<Length> {
208 ew::<Message>(&self.inner).size()
209 }
210
211 fn layout(
212 &mut self,
213 tree: &mut Tree,
214 renderer: &crate::Renderer,
215 limits: &iced_core::layout::Limits,
216 ) -> iced_core::layout::Node {
217 let inner_tree = if self.has_context_menu {
218 &mut tree.children[0]
219 } else {
220 tree
221 };
222 ew_mut::<Message>(&mut self.inner).layout(inner_tree, renderer, limits)
223 }
224
225 fn draw(
226 &self,
227 tree: &Tree,
228 renderer: &mut crate::Renderer,
229 theme: &crate::Theme,
230 defaults: &renderer::Style,
231 layout: Layout<'_>,
232 cursor: mouse::Cursor,
233 viewport: &Rectangle,
234 ) {
235 let inner_tree = if self.has_context_menu {
236 &tree.children[0]
237 } else {
238 tree
239 };
240 ew::<Message>(&self.inner).draw(
241 inner_tree, renderer, theme, defaults, layout, cursor, viewport,
242 );
243 }
244
245 fn update(
246 &mut self,
247 tree: &mut Tree,
248 event: &Event,
249 layout: Layout<'_>,
250 cursor: mouse::Cursor,
251 renderer: &crate::Renderer,
252 clipboard: &mut dyn Clipboard,
253 shell: &mut Shell<'_, Message>,
254 viewport: &Rectangle,
255 ) {
256 if self.has_context_menu {
257 ew_mut::<Message>(&mut self.inner).update(
258 &mut tree.children[0],
259 event,
260 layout,
261 cursor,
262 renderer,
263 clipboard,
264 shell,
265 viewport,
266 );
267
268 use iced_core::widget::text::HasSelectableText;
269 if self.inner.is_focused(&tree.children[0])
270 && matches!(
271 event,
272 Event::Mouse(mouse::Event::ButtonPressed(_))
273 | Event::Touch(iced_core::touch::Event::FingerPressed { .. })
274 )
275 && cursor.is_over(layout.bounds())
276 {
277 crate::widget::text_input::notify_focus_change();
278 }
279
280 #[cfg(wayland_platform)]
281 if self.uses_popup_context_menu() {
282 if self
283 .inner
284 .context_menu_position(&tree.children[0])
285 .is_some()
286 {
287 let selected_text = self.inner.selected_text(&tree.children[0]);
288 let has_selection = selected_text.is_some();
289 let click_position =
290 self.inner.context_menu_position(&tree.children[0]).unwrap();
291 let wrapper_state = tree.state.downcast_ref::<EditorWrapperState>();
292 let menu_bar_state = wrapper_state.menu_bar_state.clone();
293 let pending_action = wrapper_state.pending_action.clone();
294
295 crate::widget::text_context_menu::create_text_context_popup(
296 click_position,
297 selected_text,
298 self.inner.is_editable(),
299 has_selection,
300 self.inner.has_text(&tree.children[0]),
301 self.inner.clipboard_has_text(&tree.children[0]),
302 &menu_bar_state,
303 &pending_action,
304 renderer,
305 viewport,
306 cursor,
307 self.window_id,
308 );
309
310 self.inner
311 .set_context_menu_position(&mut tree.children[0], None);
312 }
313
314 {
316 let wrapper_state = tree.state.downcast_ref::<EditorWrapperState>();
317 let pending_action = wrapper_state.pending_action.clone();
318 if let Some(action) =
319 crate::widget::text_context_menu::take_pending_action(&pending_action)
320 {
321 match action {
322 crate::widget::text_context_menu::TextCtxAction::Copy => {}
323 crate::widget::text_context_menu::TextCtxAction::Cut => {
324 self.inner.delete_selection(&mut tree.children[0]);
325 }
326 crate::widget::text_context_menu::TextCtxAction::Paste => {
327 let content: String = clipboard
328 .read(iced_core::clipboard::Kind::Standard)
329 .unwrap_or_default();
330 self.inner.paste_text(&mut tree.children[0], &content);
331 }
332 crate::widget::text_context_menu::TextCtxAction::SelectAll => {
333 self.inner.select_all(&mut tree.children[0]);
334 }
335 }
336 }
337 }
338
339 let wrapper_state = tree.state.downcast_ref::<EditorWrapperState>();
341 let menu_bar_state = wrapper_state.menu_bar_state.clone();
342 crate::widget::text_context_menu::dismiss_popup_on_event(
343 &menu_bar_state,
344 event,
345 self.window_id,
346 );
347 }
348 } else {
349 ew_mut::<Message>(&mut self.inner).update(
350 tree, event, layout, cursor, renderer, clipboard, shell, viewport,
351 );
352 }
353 }
354
355 fn mouse_interaction(
356 &self,
357 tree: &Tree,
358 layout: Layout<'_>,
359 cursor: mouse::Cursor,
360 viewport: &Rectangle,
361 renderer: &crate::Renderer,
362 ) -> mouse::Interaction {
363 let inner_tree = if self.has_context_menu {
364 &tree.children[0]
365 } else {
366 tree
367 };
368 ew::<Message>(&self.inner).mouse_interaction(inner_tree, layout, cursor, viewport, renderer)
369 }
370
371 fn overlay<'b>(
372 &'b mut self,
373 tree: &'b mut Tree,
374 layout: Layout<'b>,
375 renderer: &crate::Renderer,
376 viewport: &Rectangle,
377 translation: Vector,
378 ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
379 if self.has_context_menu {
380 if !self.uses_popup_context_menu() {
381 use iced_core::widget::text::HasSelectableText;
382 let inner_tree = &mut tree.children[0];
383 if self.inner.context_menu_position(inner_tree).is_some() {
384 let menu_bar_state = tree
385 .state
386 .downcast_ref::<EditorWrapperState>()
387 .menu_bar_state
388 .clone();
389 let inner_tree = &mut tree.children[0];
390 return crate::widget::text_context_menu::context_menu_overlay(
391 &self.inner,
392 inner_tree,
393 None,
394 translation,
395 menu_bar_state,
396 );
397 }
398 }
399
400 ew_mut::<Message>(&mut self.inner).overlay(
401 &mut tree.children[0],
402 layout,
403 renderer,
404 viewport,
405 translation,
406 )
407 } else {
408 ew_mut::<Message>(&mut self.inner).overlay(
409 tree,
410 layout,
411 renderer,
412 viewport,
413 translation,
414 )
415 }
416 }
417
418 fn operate(
419 &mut self,
420 tree: &mut Tree,
421 layout: Layout<'_>,
422 renderer: &crate::Renderer,
423 operation: &mut dyn iced_core::widget::Operation,
424 ) {
425 let inner_tree = if self.has_context_menu {
426 &mut tree.children[0]
427 } else {
428 tree
429 };
430 ew_mut::<Message>(&mut self.inner).operate(inner_tree, layout, renderer, operation);
431 }
432
433 #[cfg(feature = "a11y")]
434 fn a11y_nodes(
435 &self,
436 layout: Layout<'_>,
437 state: &Tree,
438 cursor: mouse::Cursor,
439 ) -> iced_accessibility::A11yTree {
440 let inner_state = if self.has_context_menu {
441 &state.children[0]
442 } else {
443 state
444 };
445 ew::<Message>(&self.inner).a11y_nodes(layout, inner_state, cursor)
446 }
447
448 fn id(&self) -> Option<iced_core::widget::Id> {
449 ew::<Message>(&self.inner).id()
450 }
451
452 fn set_id(&mut self, id: iced_core::widget::Id) {
453 ew_mut::<Message>(&mut self.inner).set_id(id);
454 }
455}
456
457impl<'a, Message: Clone + 'static> From<TextEditor<'a, Message>> for crate::Element<'a, Message> {
458 fn from(editor: TextEditor<'a, Message>) -> Self {
459 Self::new(editor)
460 }
461}