1use iced_widget::checkbox as iced_checkbox;
8use iced_widget::container as iced_container;
9use iced_widget::core::svg as core_svg;
10use iced_widget::core::text as core_text;
11use iced_widget::core::time::Instant;
12use iced_widget::core::widget as core_widget;
13use iced_widget::core::widget::tree::{self, Tree};
14use iced_widget::core::{
15 Background, Border, Clipboard, Color, Element, Event, Layout, Length, Padding, Pixels, Point,
16 Rectangle, Shell, Size, Vector, Widget, alignment, border, input_method, layout, mouse,
17 overlay, renderer, touch, window,
18};
19use iced_widget::radio as iced_radio;
20use iced_widget::rule as iced_rule;
21use iced_widget::text::{self, LineHeight};
22use iced_widget::text_editor as iced_text_editor;
23use iced_widget::text_input as iced_text_input;
24use iced_widget::toggler as iced_toggler;
25use iced_widget::tooltip as iced_tooltip;
26use iced_widget::{
27 Container, Row, Rule, Text, TextEditor as IcedTextEditor, TextInput as IcedTextInput,
28};
29
30use crate::style::{
31 button as button_style, checkbox as checkbox_style, container as container_style,
32 rule as rule_style, slider as slider_style, text_editor as text_editor_style,
33 text_input as text_input_style, toggler as toggler_style, tooltip as tooltip_style,
34};
35use crate::utils::mix;
36use crate::{Theme, fonts, tokens, web_input};
37
38#[path = "widget/component/app_bar.rs"]
39pub mod app_bar;
40#[path = "widget/component/badge.rs"]
41pub mod badge;
42#[path = "widget/component/card.rs"]
43pub mod card;
44#[path = "widget/component/combobox.rs"]
45pub mod combobox;
46#[path = "widget/component/data_table.rs"]
47pub mod data_table;
48#[path = "widget/component/dialog.rs"]
49pub mod dialog;
50#[path = "widget/component/list.rs"]
51pub mod list;
52#[path = "widget/component/log_viewer.rs"]
53pub mod log_viewer;
54#[path = "widget/internal/menu_overlay.rs"]
55mod menu_overlay;
56#[path = "widget/component/navigation.rs"]
57pub mod navigation;
58#[path = "widget/component/page.rs"]
59pub mod page;
60#[path = "widget/component/picker.rs"]
61pub mod picker;
62#[path = "widget/component/progress_bar.rs"]
63pub mod progress_bar;
64#[path = "widget/internal/reveal.rs"]
65mod reveal;
66#[path = "widget/internal/ripple.rs"]
67mod ripple;
68#[path = "widget/component/search.rs"]
69pub mod search;
70#[path = "widget/component/segmented_button.rs"]
71pub mod segmented_button;
72#[path = "widget/component/select.rs"]
73pub mod select;
74#[path = "widget/component/sheet.rs"]
75pub mod sheet;
76#[path = "widget/component/snackbar.rs"]
77pub mod snackbar;
78#[path = "widget/internal/support.rs"]
79mod support;
80#[path = "widget/component/tabs.rs"]
81pub mod tabs;
82#[path = "widget/component/theme_picker.rs"]
83pub mod theme_picker;
84#[path = "widget/component/toolbar.rs"]
85pub mod toolbar;
86#[path = "widget/component/viewport.rs"]
87pub mod viewport;
88
89use support::{
90 AnimatedScalar, SelectionState, TextFieldState, TextFieldTouchActivation, alpha_border,
91 alpha_color, bool_value, draw_text_field_notched, draw_text_field_outline, duration_ms, lerp,
92 scaled_rect, solid_color, text_field_floating_label_notch,
93};
94
95const TEXT_FIELD_TOUCH_SLOP: f32 = 8.0;
96
97fn absolute_line_height(value: f32) -> LineHeight {
98 LineHeight::Absolute(value.into())
99}
100
101#[cfg(target_os = "windows")]
102fn normalize_windows_ime_request(
103 input_method: &mut input_method::InputMethod,
104 avoid_bounds: Rectangle,
105) {
106 let input_method::InputMethod::Enabled {
107 cursor, preedit, ..
108 } = input_method
109 else {
110 return;
111 };
112
113 if !preedit
114 .as_ref()
115 .is_some_and(|preedit| !preedit.content.is_empty())
116 {
117 return;
118 }
119
120 *preedit = None;
121
122 let bounds_right = avoid_bounds.x + avoid_bounds.width;
123 let bounds_bottom = avoid_bounds.y + avoid_bounds.height;
124 let cursor_right = cursor.x + cursor.width;
125 let cursor_bottom = cursor.y + cursor.height;
126
127 if cursor.x < bounds_right
128 && cursor_right > avoid_bounds.x
129 && cursor.y < bounds_bottom
130 && cursor_bottom > avoid_bounds.y
131 {
132 cursor.x = avoid_bounds.x;
133 cursor.width = avoid_bounds.width;
134 cursor.height = (bounds_bottom - cursor.y).max(cursor.height);
135 }
136}
137
138#[cfg(not(target_os = "windows"))]
139fn normalize_windows_ime_request(
140 _input_method: &mut input_method::InputMethod,
141 _avoid_bounds: Rectangle,
142) {
143}
144
145fn text_with_metrics<'a, Renderer>(
146 content: impl text::IntoFragment<'a>,
147 size: f32,
148 line_height: f32,
149) -> Text<'a, Theme, Renderer>
150where
151 Renderer: core_text::Renderer,
152{
153 Text::new(content)
154 .size(size)
155 .line_height(absolute_line_height(line_height))
156}
157
158fn centered_icon_text<'a, Renderer>(
159 icon: impl text::IntoFragment<'a>,
160 size: f32,
161) -> Text<'a, Theme, Renderer>
162where
163 Renderer: core_text::Renderer,
164 iced_widget::core::Font: Into<Renderer::Font>,
165{
166 fonts::icon(icon, size)
167 .width(Length::Fixed(size))
168 .height(Length::Fixed(size))
169 .center()
170}
171
172fn text_field_touch_cursor(event: &Event, cursor: mouse::Cursor) -> mouse::Cursor {
173 match event {
174 Event::Touch(
175 touch::Event::FingerPressed { position, .. }
176 | touch::Event::FingerMoved { position, .. }
177 | touch::Event::FingerLifted { position, .. }
178 | touch::Event::FingerLost { position, .. },
179 ) if cursor.position().is_none() && !cursor.is_levitating() => {
180 mouse::Cursor::Available(*position)
181 }
182 _ => cursor,
183 }
184}
185
186fn touch_as_mouse_event(event: &Event) -> Option<Event> {
187 match event {
188 Event::Touch(touch::Event::FingerPressed { .. }) => Some(Event::Mouse(
189 mouse::Event::ButtonPressed(mouse::Button::Left),
190 )),
191 Event::Touch(touch::Event::FingerMoved { position, .. }) => {
192 Some(Event::Mouse(mouse::Event::CursorMoved {
193 position: *position,
194 }))
195 }
196 Event::Touch(touch::Event::FingerLifted { .. } | touch::Event::FingerLost { .. }) => Some(
197 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)),
198 ),
199 _ => None,
200 }
201}
202
203fn text_field_touch_position(position: Point, cursor: mouse::Cursor) -> Option<Point> {
204 if let Some(cursor_position) = cursor.position() {
205 return Some(cursor_position);
206 }
207
208 if cursor.is_levitating() {
209 return None;
210 }
211
212 Some(position)
213}
214
215fn text_field_keyboard_activation(
216 touch_activation: &mut Option<TextFieldTouchActivation>,
217 event: &Event,
218 bounds: Rectangle,
219 cursor: mouse::Cursor,
220) -> bool {
221 match event {
222 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => cursor.is_over(bounds),
223 Event::Touch(touch::Event::FingerPressed { id, position }) => {
224 if let Some(position) = text_field_touch_position(*position, cursor)
225 && bounds.contains(position)
226 {
227 *touch_activation = Some(TextFieldTouchActivation::new(*id, position));
228 } else {
229 *touch_activation = None;
230 }
231
232 false
233 }
234 Event::Touch(touch::Event::FingerMoved { id, position }) => {
235 if let Some(position) = text_field_touch_position(*position, cursor)
236 && touch_activation.is_some_and(|activation| {
237 activation.matches(*id)
238 && activation.moved_beyond_slop(position, TEXT_FIELD_TOUCH_SLOP)
239 })
240 {
241 *touch_activation = None;
242 }
243
244 false
245 }
246 Event::Touch(touch::Event::FingerLifted { id, position }) => {
247 let position = text_field_touch_position(*position, cursor);
248
249 touch_activation.take().is_some_and(|activation| {
250 position
251 .is_some_and(|position| activation.matches(*id) && bounds.contains(position))
252 })
253 }
254 Event::Touch(touch::Event::FingerLost { id, .. }) => {
255 if touch_activation.is_some_and(|activation| activation.matches(*id)) {
256 *touch_activation = None;
257 }
258
259 false
260 }
261 _ => false,
262 }
263}
264
265#[derive(Debug, Clone, Copy, PartialEq, Eq)]
266enum TextFieldInnerTouchHandling {
267 Forward,
268 Suppress,
269 ConfirmedTap,
270}
271
272#[derive(Debug, Clone, Copy)]
273enum TextFieldTouchBounds {
274 Visible(Rectangle),
275 Hidden,
276}
277
278impl TextFieldTouchBounds {
279 fn visible(bounds: Option<Rectangle>) -> Self {
280 bounds.map(Self::Visible).unwrap_or(Self::Hidden)
281 }
282}
283
284#[derive(Debug, Clone, Copy)]
285struct TextFieldTouchContext<'a> {
286 is_enabled: bool,
287 event: &'a Event,
288 bounds: TextFieldTouchBounds,
289 cursor: mouse::Cursor,
290 activation_before: Option<TextFieldTouchActivation>,
291 confirmed_tap: bool,
292}
293
294impl TextFieldTouchContext<'_> {
295 fn keyboard_activation(&self, touch_activation: &mut Option<TextFieldTouchActivation>) -> bool {
296 let TextFieldTouchBounds::Visible(bounds) = self.bounds else {
297 if matches!(self.event, Event::Touch(_)) {
298 *touch_activation = None;
299 }
300
301 return false;
302 };
303
304 text_field_keyboard_activation(touch_activation, self.event, bounds, self.cursor)
305 }
306
307 fn inner_handling(self) -> TextFieldInnerTouchHandling {
308 if !self.is_enabled {
309 return TextFieldInnerTouchHandling::Forward;
310 }
311
312 if self.confirmed_tap {
313 return TextFieldInnerTouchHandling::ConfirmedTap;
314 }
315
316 let TextFieldTouchBounds::Visible(bounds) = self.bounds else {
317 return if matches!(self.event, Event::Touch(_)) {
318 TextFieldInnerTouchHandling::Suppress
319 } else {
320 TextFieldInnerTouchHandling::Forward
321 };
322 };
323
324 if self.press_is_over(bounds) || self.matches_activation() {
325 TextFieldInnerTouchHandling::Suppress
326 } else {
327 TextFieldInnerTouchHandling::Forward
328 }
329 }
330
331 fn press_is_over(self, bounds: Rectangle) -> bool {
332 matches!(
333 self.event,
334 Event::Touch(touch::Event::FingerPressed { position, .. })
335 if text_field_touch_position(*position, self.cursor)
336 .is_some_and(|position| bounds.contains(position))
337 )
338 }
339
340 fn matches_activation(self) -> bool {
341 let Some(activation) = self.activation_before else {
342 return false;
343 };
344
345 match self.event {
346 Event::Touch(
347 touch::Event::FingerMoved { id, .. }
348 | touch::Event::FingerLifted { id, .. }
349 | touch::Event::FingerLost { id, .. },
350 ) => activation.matches(*id),
351 _ => false,
352 }
353 }
354}
355
356#[derive(Debug, Clone, Copy)]
357struct TextInputActivation {
358 cursor: mouse::Cursor,
359 request_mobile_keyboard: bool,
360 web_input_anchor: Option<Rectangle>,
361 web_input_translation: Vector,
362 inner_touch_handling: TextFieldInnerTouchHandling,
363}
364
365#[derive(Debug, Clone, Copy, Default)]
366struct WebInputPositionState {
367 raw_pointer_position: Option<Point>,
371 translation: Vector,
372}
373
374impl WebInputPositionState {
375 fn update(&mut self, event: &Event, cursor: mouse::Cursor) {
376 let raw_pointer_position = match event {
377 Event::Mouse(mouse::Event::CursorMoved { position })
378 | Event::Touch(
379 touch::Event::FingerPressed { position, .. }
380 | touch::Event::FingerMoved { position, .. }
381 | touch::Event::FingerLifted { position, .. }
382 | touch::Event::FingerLost { position, .. },
383 ) => Some(*position),
384 _ => None,
385 };
386
387 if let Some(position) = raw_pointer_position {
388 self.raw_pointer_position = Some(position);
389 }
390
391 if let (Some(raw), Some(translated)) = (self.raw_pointer_position, cursor.land().position())
392 {
393 self.translation = translated - raw;
394 }
395 }
396}
397
398#[derive(Debug, Clone, Copy, Default)]
399struct MobileTextInputState {
400 touch_activation: Option<TextFieldTouchActivation>,
401 web_input_position: WebInputPositionState,
402}
403
404fn mobile_text_input_activation(
405 is_enabled: bool,
406 state: &mut MobileTextInputState,
407 event: &Event,
408 visible_bounds: Option<Rectangle>,
409 cursor: mouse::Cursor,
410) -> TextInputActivation {
411 text_input_activation(
412 is_enabled,
413 &mut state.touch_activation,
414 &mut state.web_input_position,
415 event,
416 visible_bounds,
417 cursor,
418 )
419}
420
421fn text_input_activation(
422 is_enabled: bool,
423 touch_activation: &mut Option<TextFieldTouchActivation>,
424 web_input_position: &mut WebInputPositionState,
425 event: &Event,
426 visible_bounds: Option<Rectangle>,
427 cursor: mouse::Cursor,
428) -> TextInputActivation {
429 web_input_position.update(event, cursor);
430 let inner_cursor = text_field_touch_cursor(event, cursor);
431 let touch = TextFieldTouchContext {
432 is_enabled,
433 event,
434 bounds: TextFieldTouchBounds::visible(visible_bounds),
435 cursor: inner_cursor,
436 activation_before: *touch_activation,
437 confirmed_tap: false,
438 };
439 let request_mobile_keyboard = is_enabled && touch.keyboard_activation(touch_activation);
440 let web_input_anchor = if request_mobile_keyboard {
441 visible_bounds.map(|bounds| {
442 let position = inner_cursor.position().unwrap_or(bounds.position());
443
444 Rectangle::new(position, Size::UNIT)
445 })
446 } else {
447 None
448 };
449 let inner_touch_handling = TextFieldTouchContext {
450 confirmed_tap: request_mobile_keyboard,
451 ..touch
452 }
453 .inner_handling();
454
455 TextInputActivation {
456 cursor: inner_cursor,
457 request_mobile_keyboard,
458 web_input_anchor,
459 web_input_translation: web_input_position.translation,
460 inner_touch_handling,
461 }
462}
463
464fn web_input_anchor(
465 input_method: &input_method::InputMethod,
466 visible_bounds: Option<Rectangle>,
467 activation: TextInputActivation,
468 started_focused: bool,
469 is_focused: bool,
470) -> Option<Rectangle> {
471 if !is_focused {
472 return None;
473 }
474
475 let anchor = match input_method {
476 input_method::InputMethod::Enabled { cursor, .. } => Some(*cursor),
477 input_method::InputMethod::Disabled if activation.web_input_anchor.is_some() => {
478 activation.web_input_anchor
479 }
480 input_method::InputMethod::Disabled if started_focused != is_focused => {
481 visible_bounds.map(|bounds| {
482 Rectangle::new(bounds.position(), Size::new(1.0, bounds.height.max(1.0)))
483 })
484 }
485 input_method::InputMethod::Disabled => None,
486 }?;
487
488 let anchor = visible_bounds.map_or(anchor, |bounds| {
489 let right = bounds.x + bounds.width;
490 let bottom = bounds.y + bounds.height;
491
492 Rectangle::new(
493 Point::new(
494 anchor.x.clamp(bounds.x, right),
495 anchor.y.clamp(bounds.y, bottom),
496 ),
497 Size::new(anchor.width.max(1.0), anchor.height.max(1.0)),
498 )
499 });
500
501 Some(anchor - activation.web_input_translation)
502}
503
504fn sync_mobile_keyboard(
505 started_focused: bool,
506 is_focused: bool,
507 request_mobile_keyboard: bool,
508 input_anchor: Option<Rectangle>,
509) {
510 if let Some(anchor) = input_anchor {
511 web_input::position_mobile_keyboard(anchor);
512 }
513
514 if started_focused != is_focused {
515 if is_focused {
516 if !request_mobile_keyboard {
517 web_input::show_mobile_keyboard();
518 }
519 } else {
520 web_input::hide_mobile_keyboard();
521 }
522 }
523
524 if request_mobile_keyboard {
525 web_input::show_mobile_keyboard();
526 }
527}
528
529fn register_mobile_text_region(is_enabled: bool, bounds: Rectangle, viewport: &Rectangle) {
530 if is_enabled && let Some(visible_bounds) = bounds.intersection(viewport) {
531 web_input::register_text_region(visible_bounds);
532 }
533}
534
535struct TextInputUpdateContext<'a, 'b, Message, Renderer> {
536 renderer: &'a Renderer,
537 clipboard: &'a mut dyn Clipboard,
538 shell: &'a mut Shell<'b, Message>,
539 viewport: &'a Rectangle,
540}
541
542fn update_mobile_text_input<'a, Message, Renderer>(
543 input: &mut IcedTextInput<'a, Message, Theme, Renderer>,
544 tree: &mut Tree,
545 event: &Event,
546 layout: Layout<'_>,
547 activation: TextInputActivation,
548 context: TextInputUpdateContext<'_, '_, Message, Renderer>,
549) where
550 Message: Clone,
551 Renderer: iced_widget::core::Renderer + core_text::Renderer,
552{
553 match activation.inner_touch_handling {
554 TextFieldInnerTouchHandling::Forward => {
555 input.update(
556 tree,
557 event,
558 layout,
559 activation.cursor,
560 context.renderer,
561 &mut *context.clipboard,
562 &mut *context.shell,
563 context.viewport,
564 );
565 }
566 TextFieldInnerTouchHandling::Suppress => {}
567 TextFieldInnerTouchHandling::ConfirmedTap => {
568 let press = Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left));
569 input.update(
570 tree,
571 &press,
572 layout,
573 activation.cursor,
574 context.renderer,
575 &mut *context.clipboard,
576 &mut *context.shell,
577 context.viewport,
578 );
579
580 let release = Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left));
581 input.update(
582 tree,
583 &release,
584 layout,
585 activation.cursor,
586 context.renderer,
587 &mut *context.clipboard,
588 &mut *context.shell,
589 context.viewport,
590 );
591 }
592 }
593
594 refresh_text_input_caret(
595 tree.state
596 .downcast_mut::<iced_text_input::State<Renderer::Paragraph>>(),
597 event,
598 &mut *context.shell,
599 );
600}
601
602fn press_is_over(event: &Event, bounds: Rectangle, cursor: mouse::Cursor) -> bool {
603 match event {
604 Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Left)) => cursor.is_over(bounds),
605 Event::Touch(touch::Event::FingerPressed { position, .. }) => {
606 touch_event_is_over(*position, bounds, cursor)
607 }
608 _ => false,
609 }
610}
611
612fn release_is_over(event: &Event, bounds: Rectangle, cursor: mouse::Cursor) -> bool {
613 match event {
614 Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left)) => cursor.is_over(bounds),
615 Event::Touch(touch::Event::FingerLifted { position, .. }) => {
616 touch_event_is_over(*position, bounds, cursor)
617 }
618 _ => false,
619 }
620}
621
622fn touch_event_is_over(position: Point, bounds: Rectangle, cursor: mouse::Cursor) -> bool {
623 if cursor.position().is_some() {
624 return cursor.is_over(bounds);
625 }
626
627 if cursor.is_levitating() {
628 return false;
629 }
630
631 bounds.contains(position)
632}
633
634fn selection_control_hit_bounds(layout: Layout<'_>, target_size: f32) -> Rectangle {
635 let content_bounds = layout.bounds();
636 let control_bounds = layout
637 .children()
638 .next()
639 .map_or(content_bounds, |control| control.bounds());
640
641 SelectionControlHitTarget {
642 content: content_bounds,
643 control: control_bounds,
644 target_size,
645 }
646 .bounds()
647}
648
649#[derive(Debug, Clone, Copy)]
650struct SelectionControlHitTarget {
651 content: Rectangle,
652 control: Rectangle,
653 target_size: f32,
654}
655
656impl SelectionControlHitTarget {
657 fn bounds(self) -> Rectangle {
658 let target_height = self.content.height.max(self.target_size);
659 let content_target = Rectangle {
660 y: self.content.center_y() - target_height / 2.0,
661 height: target_height,
662 ..self.content
663 };
664 let control_padding =
665 ((self.target_size - self.control.width.min(self.control.height)) / 2.0).max(0.0);
666 let control_target = Rectangle {
667 x: self.control.x - control_padding,
668 y: self.control.y - control_padding,
669 width: self.control.width + control_padding * 2.0,
670 height: self.control.height + control_padding * 2.0,
671 };
672
673 union_bounds(content_target, control_target)
674 }
675}
676
677fn union_bounds(a: Rectangle, b: Rectangle) -> Rectangle {
678 let x = a.x.min(b.x);
679 let y = a.y.min(b.y);
680 let right = (a.x + a.width).max(b.x + b.width);
681 let bottom = (a.y + a.height).max(b.y + b.height);
682
683 Rectangle {
684 x,
685 y,
686 width: right - x,
687 height: bottom - y,
688 }
689}
690
691fn should_suppress_ime_caret() -> bool {
692 !cfg!(any(
693 target_arch = "wasm32",
694 target_os = "android",
695 target_os = "windows"
696 ))
697}
698
699fn text_caret_refresh_event(event: &Event) -> bool {
700 match event {
701 Event::Keyboard(iced_widget::core::keyboard::Event::KeyPressed { key, text, .. }) => {
702 text.as_ref()
703 .is_some_and(|text| text.chars().any(|c| !c.is_control()))
704 || matches!(
705 key.as_ref(),
706 iced_widget::core::keyboard::Key::Named(
707 iced_widget::core::keyboard::key::Named::Enter
708 | iced_widget::core::keyboard::key::Named::Backspace
709 | iced_widget::core::keyboard::key::Named::Delete
710 )
711 )
712 }
713 Event::InputMethod(input_method::Event::Preedit(content, _)) => !content.is_empty(),
714 Event::InputMethod(input_method::Event::Commit(content)) => !content.is_empty(),
715 _ => false,
716 }
717}
718
719fn refresh_text_input_caret<Message, P>(
720 state: &mut iced_text_input::State<P>,
721 event: &Event,
722 shell: &mut Shell<'_, Message>,
723) where
724 P: core_text::Paragraph,
725{
726 if !state.is_focused() || !text_caret_refresh_event(event) {
727 return;
728 }
729
730 let value = {
731 let text = <iced_text_input::State<P> as core_widget::operation::TextInput>::text(state);
732 iced_text_input::Value::new(text)
733 };
734 let cursor = state.cursor().state(&value);
735
736 core_widget::operation::Focusable::focus(state);
737
738 match cursor {
739 iced_text_input::cursor::State::Index(index) => {
740 state.move_cursor_to(index);
741 }
742 iced_text_input::cursor::State::Selection { start, end } => {
743 state.select_range(start, end);
744 }
745 }
746
747 shell.request_redraw();
748}
749
750fn mobile_text_input<'a, Message, Renderer>(
751 input: IcedTextInput<'a, Message, Theme, Renderer>,
752 is_enabled: bool,
753) -> Element<'a, Message, Theme, Renderer>
754where
755 Message: Clone + 'a,
756 Renderer: iced_widget::core::Renderer + core_text::Renderer + 'a,
757{
758 Element::new(MobileTextInput { input, is_enabled })
759}
760
761struct MobileTextInput<'a, Message, Renderer>
762where
763 Renderer: iced_widget::core::Renderer + core_text::Renderer,
764{
765 input: IcedTextInput<'a, Message, Theme, Renderer>,
766 is_enabled: bool,
767}
768
769impl<Message, Renderer> Widget<Message, Theme, Renderer> for MobileTextInput<'_, Message, Renderer>
770where
771 Message: Clone,
772 Renderer: iced_widget::core::Renderer + core_text::Renderer,
773{
774 fn tag(&self) -> tree::Tag {
775 tree::Tag::of::<MobileTextInputState>()
776 }
777
778 fn state(&self) -> tree::State {
779 tree::State::new(MobileTextInputState::default())
780 }
781
782 fn children(&self) -> Vec<Tree> {
783 let input: &dyn Widget<Message, Theme, Renderer> = &self.input;
784
785 vec![Tree::new(input)]
786 }
787
788 fn diff(&self, tree: &mut Tree) {
789 if tree.children.is_empty() {
790 tree.children = self.children();
791 } else {
792 self.input.diff(&mut tree.children[0]);
793 tree.children.truncate(1);
794 }
795 }
796
797 fn size(&self) -> Size<Length> {
798 Widget::<Message, Theme, Renderer>::size(&self.input)
799 }
800
801 fn size_hint(&self) -> Size<Length> {
802 Widget::<Message, Theme, Renderer>::size_hint(&self.input)
803 }
804
805 fn layout(
806 &mut self,
807 tree: &mut Tree,
808 renderer: &Renderer,
809 limits: &layout::Limits,
810 ) -> layout::Node {
811 let input = <IcedTextInput<'_, Message, Theme, Renderer> as Widget<
812 Message,
813 Theme,
814 Renderer,
815 >>::layout(&mut self.input, &mut tree.children[0], renderer, limits);
816
817 layout::Node::with_children(input.size(), vec![input])
818 }
819
820 fn operate(
821 &mut self,
822 tree: &mut Tree,
823 layout: Layout<'_>,
824 renderer: &Renderer,
825 operation: &mut dyn core_widget::Operation,
826 ) {
827 self.input.operate(
828 &mut tree.children[0],
829 layout.children().next().unwrap(),
830 renderer,
831 operation,
832 );
833 }
834
835 fn update(
836 &mut self,
837 tree: &mut Tree,
838 event: &Event,
839 layout: Layout<'_>,
840 cursor: mouse::Cursor,
841 renderer: &Renderer,
842 clipboard: &mut dyn Clipboard,
843 shell: &mut Shell<'_, Message>,
844 viewport: &Rectangle,
845 ) {
846 let bounds = layout.bounds();
847 let visible_bounds = bounds.intersection(viewport);
848 let input_layout = layout.children().next().unwrap();
849
850 let started_focused = {
851 let state = tree.children[0]
852 .state
853 .downcast_ref::<iced_text_input::State<Renderer::Paragraph>>();
854
855 state.is_focused()
856 };
857
858 let activation = mobile_text_input_activation(
859 self.is_enabled,
860 tree.state.downcast_mut::<MobileTextInputState>(),
861 event,
862 visible_bounds,
863 cursor,
864 );
865
866 update_mobile_text_input(
867 &mut self.input,
868 &mut tree.children[0],
869 event,
870 input_layout,
871 activation,
872 TextInputUpdateContext {
873 renderer,
874 clipboard,
875 shell,
876 viewport,
877 },
878 );
879
880 normalize_windows_ime_request(shell.input_method_mut(), bounds);
881
882 let is_focused = {
883 let state = tree.children[0]
884 .state
885 .downcast_ref::<iced_text_input::State<Renderer::Paragraph>>();
886
887 state.is_focused()
888 };
889
890 let input_anchor = web_input_anchor(
891 shell.input_method(),
892 visible_bounds,
893 activation,
894 started_focused,
895 is_focused,
896 );
897
898 sync_mobile_keyboard(
899 started_focused,
900 is_focused,
901 activation.request_mobile_keyboard,
902 input_anchor,
903 );
904 }
905
906 fn mouse_interaction(
907 &self,
908 tree: &Tree,
909 layout: Layout<'_>,
910 cursor: mouse::Cursor,
911 viewport: &Rectangle,
912 renderer: &Renderer,
913 ) -> mouse::Interaction {
914 self.input.mouse_interaction(
915 &tree.children[0],
916 layout.children().next().unwrap(),
917 cursor,
918 viewport,
919 renderer,
920 )
921 }
922
923 fn draw(
924 &self,
925 tree: &Tree,
926 renderer: &mut Renderer,
927 theme: &Theme,
928 defaults: &renderer::Style,
929 layout: Layout<'_>,
930 cursor: mouse::Cursor,
931 viewport: &Rectangle,
932 ) {
933 register_mobile_text_region(self.is_enabled, layout.bounds(), viewport);
934
935 <IcedTextInput<'_, Message, Theme, Renderer> as Widget<Message, Theme, Renderer>>::draw(
936 &self.input,
937 &tree.children[0],
938 renderer,
939 theme,
940 defaults,
941 layout.children().next().unwrap(),
942 cursor,
943 viewport,
944 );
945 }
946}
947
948fn checkbox_checkmark_svg(mark_progress: f32) -> Vec<u8> {
949 let progress = mark_progress.clamp(0.0, 1.0);
950 let short_height = lerp(
951 tokens::component::checkbox::CHECKMARK_STROKE_WIDTH,
952 tokens::component::checkbox::CHECKMARK_SHORT_MARK_SIZE,
953 progress,
954 );
955 let long_width = tokens::component::checkbox::CHECKMARK_LONG_MARK_SIZE * progress;
956
957 format!(
958 r#"<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 18 18"><g transform="scale(1 -1) translate({} {}) rotate(45)"><rect width="{}" height="{short_height}"/><rect width="{long_width}" height="{}"/></g></svg>"#,
959 tokens::component::checkbox::CHECKMARK_BOTTOM_LEFT_X,
960 tokens::component::checkbox::CHECKMARK_BOTTOM_LEFT_Y,
961 tokens::component::checkbox::CHECKMARK_STROKE_WIDTH,
962 tokens::component::checkbox::CHECKMARK_STROKE_WIDTH,
963 )
964 .into_bytes()
965}
966
967#[path = "widget/component/button.rs"]
968pub mod button;
969#[path = "widget/component/slider.rs"]
970pub mod slider;
971
972#[path = "widget/component/rule.rs"]
973pub mod rule;
974
975#[path = "widget/component/container.rs"]
976pub mod container;
977
978#[path = "widget/component/text_input.rs"]
979pub mod text_input;
980#[path = "widget/component/tooltip.rs"]
981pub mod tooltip;
982
983#[path = "widget/component/text_editor.rs"]
984pub mod text_editor;
985
986#[path = "widget/component/radio.rs"]
987pub mod radio;
988
989#[path = "widget/component/checkbox.rs"]
990pub mod checkbox;
991
992#[path = "widget/component/toggler.rs"]
993pub mod toggler;
994
995#[cfg(test)]
996#[path = "../tests/widget.rs"]
997mod tests;