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 &menu_bar_state,
301 &pending_action,
302 renderer,
303 viewport,
304 cursor,
305 self.window_id,
306 );
307
308 self.inner
309 .set_context_menu_position(&mut tree.children[0], None);
310 }
311
312 {
314 let wrapper_state = tree.state.downcast_ref::<EditorWrapperState>();
315 let pending_action = wrapper_state.pending_action.clone();
316 if let Some(action) =
317 crate::widget::text_context_menu::take_pending_action(&pending_action)
318 {
319 match action {
320 crate::widget::text_context_menu::TextCtxAction::Copy => {}
321 crate::widget::text_context_menu::TextCtxAction::Cut => {
322 self.inner.delete_selection(&mut tree.children[0]);
323 }
324 crate::widget::text_context_menu::TextCtxAction::Paste => {
325 let content: String = clipboard
326 .read(iced_core::clipboard::Kind::Standard)
327 .unwrap_or_default();
328 self.inner.paste_text(&mut tree.children[0], &content);
329 }
330 crate::widget::text_context_menu::TextCtxAction::SelectAll => {
331 self.inner.select_all(&mut tree.children[0]);
332 }
333 }
334 }
335 }
336
337 let wrapper_state = tree.state.downcast_ref::<EditorWrapperState>();
339 let menu_bar_state = wrapper_state.menu_bar_state.clone();
340 crate::widget::text_context_menu::dismiss_popup_on_event(
341 &menu_bar_state,
342 event,
343 self.window_id,
344 );
345 }
346 } else {
347 ew_mut::<Message>(&mut self.inner).update(
348 tree, event, layout, cursor, renderer, clipboard, shell, viewport,
349 );
350 }
351 }
352
353 fn mouse_interaction(
354 &self,
355 tree: &Tree,
356 layout: Layout<'_>,
357 cursor: mouse::Cursor,
358 viewport: &Rectangle,
359 renderer: &crate::Renderer,
360 ) -> mouse::Interaction {
361 let inner_tree = if self.has_context_menu {
362 &tree.children[0]
363 } else {
364 tree
365 };
366 ew::<Message>(&self.inner).mouse_interaction(inner_tree, layout, cursor, viewport, renderer)
367 }
368
369 fn overlay<'b>(
370 &'b mut self,
371 tree: &'b mut Tree,
372 layout: Layout<'b>,
373 renderer: &crate::Renderer,
374 viewport: &Rectangle,
375 translation: Vector,
376 ) -> Option<overlay::Element<'b, Message, crate::Theme, crate::Renderer>> {
377 if self.has_context_menu {
378 if !self.uses_popup_context_menu() {
379 use iced_core::widget::text::HasSelectableText;
380 let inner_tree = &mut tree.children[0];
381 if self.inner.context_menu_position(inner_tree).is_some() {
382 let menu_bar_state = tree
383 .state
384 .downcast_ref::<EditorWrapperState>()
385 .menu_bar_state
386 .clone();
387 let inner_tree = &mut tree.children[0];
388 return crate::widget::text_context_menu::context_menu_overlay(
389 &self.inner,
390 inner_tree,
391 None,
392 translation,
393 menu_bar_state,
394 );
395 }
396 }
397
398 ew_mut::<Message>(&mut self.inner).overlay(
399 &mut tree.children[0],
400 layout,
401 renderer,
402 viewport,
403 translation,
404 )
405 } else {
406 ew_mut::<Message>(&mut self.inner).overlay(
407 tree,
408 layout,
409 renderer,
410 viewport,
411 translation,
412 )
413 }
414 }
415
416 fn operate(
417 &mut self,
418 tree: &mut Tree,
419 layout: Layout<'_>,
420 renderer: &crate::Renderer,
421 operation: &mut dyn iced_core::widget::Operation,
422 ) {
423 let inner_tree = if self.has_context_menu {
424 &mut tree.children[0]
425 } else {
426 tree
427 };
428 ew_mut::<Message>(&mut self.inner).operate(inner_tree, layout, renderer, operation);
429 }
430
431 #[cfg(feature = "a11y")]
432 fn a11y_nodes(
433 &self,
434 layout: Layout<'_>,
435 state: &Tree,
436 cursor: mouse::Cursor,
437 ) -> iced_accessibility::A11yTree {
438 let inner_state = if self.has_context_menu {
439 &state.children[0]
440 } else {
441 state
442 };
443 ew::<Message>(&self.inner).a11y_nodes(layout, inner_state, cursor)
444 }
445
446 fn id(&self) -> Option<iced_core::widget::Id> {
447 ew::<Message>(&self.inner).id()
448 }
449
450 fn set_id(&mut self, id: iced_core::widget::Id) {
451 ew_mut::<Message>(&mut self.inner).set_id(id);
452 }
453}
454
455impl<'a, Message: Clone + 'static> From<TextEditor<'a, Message>> for crate::Element<'a, Message> {
456 fn from(editor: TextEditor<'a, Message>) -> Self {
457 Self::new(editor)
458 }
459}