1use crate::{RepaintCause, ViewportIdMap, ViewportOutput, WidgetType};
4
5#[derive(Clone, Default)]
9pub struct FullOutput {
10 pub platform_output: PlatformOutput,
12
13 pub textures_delta: epaint::textures::TexturesDelta,
20
21 pub shapes: Vec<epaint::ClippedShape>,
25
26 pub pixels_per_point: f32,
30
31 pub viewport_output: ViewportIdMap<ViewportOutput>,
36}
37
38impl FullOutput {
39 pub fn append(&mut self, newer: Self) {
41 let Self {
42 platform_output,
43 textures_delta,
44 shapes,
45 pixels_per_point,
46 viewport_output,
47 } = newer;
48
49 self.platform_output.append(platform_output);
50 self.textures_delta.append(textures_delta);
51 self.shapes = shapes; self.pixels_per_point = pixels_per_point; for (id, new_viewport) in viewport_output {
55 match self.viewport_output.entry(id) {
56 std::collections::hash_map::Entry::Vacant(entry) => {
57 entry.insert(new_viewport);
58 }
59 std::collections::hash_map::Entry::Occupied(mut entry) => {
60 entry.get_mut().append(new_viewport);
61 }
62 }
63 }
64 }
65}
66
67#[derive(Copy, Clone, Debug, PartialEq, Eq)]
71#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
72pub struct IMEOutput {
73 pub rect: crate::Rect,
75
76 pub cursor_rect: crate::Rect,
80}
81
82#[derive(Clone, Debug, PartialEq, Eq)]
86#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
87pub enum OutputCommand {
88 CopyText(String),
92
93 CopyImage(crate::ColorImage),
95
96 OpenUrl(OpenUrl),
98}
99
100#[derive(Default, Clone, PartialEq)]
106#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
107pub struct PlatformOutput {
108 pub commands: Vec<OutputCommand>,
110
111 pub cursor_icon: CursorIcon,
113
114 #[deprecated = "Use `Context::open_url` or `PlatformOutput::commands` instead"]
116 pub open_url: Option<OpenUrl>,
117
118 #[deprecated = "Use `Context::copy_text` or `PlatformOutput::commands` instead"]
130 pub copied_text: String,
131
132 pub events: Vec<OutputEvent>,
134
135 pub mutable_text_under_cursor: bool,
138
139 pub ime: Option<IMEOutput>,
143
144 #[cfg(feature = "accesskit")]
148 pub accesskit_update: Option<accesskit::TreeUpdate>,
149
150 pub num_completed_passes: usize,
157
158 #[cfg_attr(feature = "serde", serde(skip))]
164 pub request_discard_reasons: Vec<RepaintCause>,
165}
166
167impl PlatformOutput {
168 pub fn events_description(&self) -> String {
170 if let Some(event) = self.events.iter().next_back() {
172 match event {
173 OutputEvent::Clicked(widget_info)
174 | OutputEvent::DoubleClicked(widget_info)
175 | OutputEvent::TripleClicked(widget_info)
176 | OutputEvent::FocusGained(widget_info)
177 | OutputEvent::TextSelectionChanged(widget_info)
178 | OutputEvent::ValueChanged(widget_info) => {
179 return widget_info.description();
180 }
181 }
182 }
183 Default::default()
184 }
185
186 pub fn append(&mut self, newer: Self) {
188 #![allow(deprecated)]
189
190 let Self {
191 mut commands,
192 cursor_icon,
193 open_url,
194 copied_text,
195 mut events,
196 mutable_text_under_cursor,
197 ime,
198 #[cfg(feature = "accesskit")]
199 accesskit_update,
200 num_completed_passes,
201 mut request_discard_reasons,
202 } = newer;
203
204 self.commands.append(&mut commands);
205 self.cursor_icon = cursor_icon;
206 if open_url.is_some() {
207 self.open_url = open_url;
208 }
209 if !copied_text.is_empty() {
210 self.copied_text = copied_text;
211 }
212 self.events.append(&mut events);
213 self.mutable_text_under_cursor = mutable_text_under_cursor;
214 self.ime = ime.or(self.ime);
215 self.num_completed_passes += num_completed_passes;
216 self.request_discard_reasons
217 .append(&mut request_discard_reasons);
218
219 #[cfg(feature = "accesskit")]
220 {
221 self.accesskit_update = accesskit_update;
224 }
225 }
226
227 pub fn take(&mut self) -> Self {
229 let taken = std::mem::take(self);
230 self.cursor_icon = taken.cursor_icon; taken
232 }
233
234 pub fn requested_discard(&self) -> bool {
236 !self.request_discard_reasons.is_empty()
237 }
238}
239
240#[derive(Clone, Debug, PartialEq, Eq)]
244#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
245pub struct OpenUrl {
246 pub url: String,
247
248 pub new_tab: bool,
252}
253
254impl OpenUrl {
255 #[expect(clippy::needless_pass_by_value)]
256 pub fn same_tab(url: impl ToString) -> Self {
257 Self {
258 url: url.to_string(),
259 new_tab: false,
260 }
261 }
262
263 #[expect(clippy::needless_pass_by_value)]
264 pub fn new_tab(url: impl ToString) -> Self {
265 Self {
266 url: url.to_string(),
267 new_tab: true,
268 }
269 }
270}
271
272#[derive(Clone, Copy, Debug, PartialEq, Eq)]
278#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
279pub enum UserAttentionType {
280 Critical,
282
283 Informational,
285
286 Reset,
288}
289
290#[derive(Clone, Copy, Debug, PartialEq, Eq)]
296#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
297pub enum CursorIcon {
298 Default,
300
301 None,
303
304 ContextMenu,
308
309 Help,
311
312 PointingHand,
314
315 Progress,
317
318 Wait,
320
321 Cell,
325
326 Crosshair,
328
329 Text,
331
332 VerticalText,
334
335 Alias,
339
340 Copy,
342
343 Move,
345
346 NoDrop,
348
349 NotAllowed,
351
352 Grab,
354
355 Grabbing,
357
358 AllScroll,
361
362 ResizeHorizontal,
366
367 ResizeNeSw,
369
370 ResizeNwSe,
372
373 ResizeVertical,
375
376 ResizeEast,
380
381 ResizeSouthEast,
383
384 ResizeSouth,
386
387 ResizeSouthWest,
389
390 ResizeWest,
392
393 ResizeNorthWest,
395
396 ResizeNorth,
398
399 ResizeNorthEast,
401
402 ResizeColumn,
405
406 ResizeRow,
408
409 ZoomIn,
413
414 ZoomOut,
416}
417
418impl CursorIcon {
419 pub const ALL: [Self; 35] = [
420 Self::Default,
421 Self::None,
422 Self::ContextMenu,
423 Self::Help,
424 Self::PointingHand,
425 Self::Progress,
426 Self::Wait,
427 Self::Cell,
428 Self::Crosshair,
429 Self::Text,
430 Self::VerticalText,
431 Self::Alias,
432 Self::Copy,
433 Self::Move,
434 Self::NoDrop,
435 Self::NotAllowed,
436 Self::Grab,
437 Self::Grabbing,
438 Self::AllScroll,
439 Self::ResizeHorizontal,
440 Self::ResizeNeSw,
441 Self::ResizeNwSe,
442 Self::ResizeVertical,
443 Self::ResizeEast,
444 Self::ResizeSouthEast,
445 Self::ResizeSouth,
446 Self::ResizeSouthWest,
447 Self::ResizeWest,
448 Self::ResizeNorthWest,
449 Self::ResizeNorth,
450 Self::ResizeNorthEast,
451 Self::ResizeColumn,
452 Self::ResizeRow,
453 Self::ZoomIn,
454 Self::ZoomOut,
455 ];
456}
457
458impl Default for CursorIcon {
459 fn default() -> Self {
460 Self::Default
461 }
462}
463
464#[derive(Clone, PartialEq)]
468#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
469pub enum OutputEvent {
470 Clicked(WidgetInfo),
472
473 DoubleClicked(WidgetInfo),
475
476 TripleClicked(WidgetInfo),
478
479 FocusGained(WidgetInfo),
481
482 TextSelectionChanged(WidgetInfo),
484
485 ValueChanged(WidgetInfo),
487}
488
489impl OutputEvent {
490 pub fn widget_info(&self) -> &WidgetInfo {
491 match self {
492 Self::Clicked(info)
493 | Self::DoubleClicked(info)
494 | Self::TripleClicked(info)
495 | Self::FocusGained(info)
496 | Self::TextSelectionChanged(info)
497 | Self::ValueChanged(info) => info,
498 }
499 }
500}
501
502impl std::fmt::Debug for OutputEvent {
503 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
504 match self {
505 Self::Clicked(wi) => write!(f, "Clicked({wi:?})"),
506 Self::DoubleClicked(wi) => write!(f, "DoubleClicked({wi:?})"),
507 Self::TripleClicked(wi) => write!(f, "TripleClicked({wi:?})"),
508 Self::FocusGained(wi) => write!(f, "FocusGained({wi:?})"),
509 Self::TextSelectionChanged(wi) => write!(f, "TextSelectionChanged({wi:?})"),
510 Self::ValueChanged(wi) => write!(f, "ValueChanged({wi:?})"),
511 }
512 }
513}
514
515#[derive(Clone, PartialEq)]
517#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
518pub struct WidgetInfo {
519 pub typ: WidgetType,
521
522 pub enabled: bool,
524
525 pub label: Option<String>,
527
528 pub current_text_value: Option<String>,
530
531 pub prev_text_value: Option<String>,
533
534 pub selected: Option<bool>,
536
537 pub value: Option<f64>,
539
540 pub text_selection: Option<std::ops::RangeInclusive<usize>>,
542
543 pub hint_text: Option<String>,
545}
546
547impl std::fmt::Debug for WidgetInfo {
548 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
549 let Self {
550 typ,
551 enabled,
552 label,
553 current_text_value: text_value,
554 prev_text_value,
555 selected,
556 value,
557 text_selection,
558 hint_text,
559 } = self;
560
561 let mut s = f.debug_struct("WidgetInfo");
562
563 s.field("typ", typ);
564
565 if !enabled {
566 s.field("enabled", enabled);
567 }
568
569 if let Some(label) = label {
570 s.field("label", label);
571 }
572 if let Some(text_value) = text_value {
573 s.field("text_value", text_value);
574 }
575 if let Some(prev_text_value) = prev_text_value {
576 s.field("prev_text_value", prev_text_value);
577 }
578 if let Some(selected) = selected {
579 s.field("selected", selected);
580 }
581 if let Some(value) = value {
582 s.field("value", value);
583 }
584 if let Some(text_selection) = text_selection {
585 s.field("text_selection", text_selection);
586 }
587 if let Some(hint_text) = hint_text {
588 s.field("hint_text", hint_text);
589 }
590
591 s.finish()
592 }
593}
594
595impl WidgetInfo {
596 pub fn new(typ: WidgetType) -> Self {
597 Self {
598 typ,
599 enabled: true,
600 label: None,
601 current_text_value: None,
602 prev_text_value: None,
603 selected: None,
604 value: None,
605 text_selection: None,
606 hint_text: None,
607 }
608 }
609
610 #[expect(clippy::needless_pass_by_value)]
611 pub fn labeled(typ: WidgetType, enabled: bool, label: impl ToString) -> Self {
612 Self {
613 enabled,
614 label: Some(label.to_string()),
615 ..Self::new(typ)
616 }
617 }
618
619 #[expect(clippy::needless_pass_by_value)]
621 pub fn selected(typ: WidgetType, enabled: bool, selected: bool, label: impl ToString) -> Self {
622 Self {
623 enabled,
624 label: Some(label.to_string()),
625 selected: Some(selected),
626 ..Self::new(typ)
627 }
628 }
629
630 pub fn drag_value(enabled: bool, value: f64) -> Self {
631 Self {
632 enabled,
633 value: Some(value),
634 ..Self::new(WidgetType::DragValue)
635 }
636 }
637
638 #[expect(clippy::needless_pass_by_value)]
639 pub fn slider(enabled: bool, value: f64, label: impl ToString) -> Self {
640 let label = label.to_string();
641 Self {
642 enabled,
643 label: if label.is_empty() { None } else { Some(label) },
644 value: Some(value),
645 ..Self::new(WidgetType::Slider)
646 }
647 }
648
649 #[expect(clippy::needless_pass_by_value)]
650 pub fn text_edit(
651 enabled: bool,
652 prev_text_value: impl ToString,
653 text_value: impl ToString,
654 hint_text: impl ToString,
655 ) -> Self {
656 let text_value = text_value.to_string();
657 let prev_text_value = prev_text_value.to_string();
658 let hint_text = hint_text.to_string();
659 let prev_text_value = if text_value == prev_text_value {
660 None
661 } else {
662 Some(prev_text_value)
663 };
664 Self {
665 enabled,
666 current_text_value: Some(text_value),
667 prev_text_value,
668 hint_text: Some(hint_text),
669 ..Self::new(WidgetType::TextEdit)
670 }
671 }
672
673 #[expect(clippy::needless_pass_by_value)]
674 pub fn text_selection_changed(
675 enabled: bool,
676 text_selection: std::ops::RangeInclusive<usize>,
677 current_text_value: impl ToString,
678 ) -> Self {
679 Self {
680 enabled,
681 text_selection: Some(text_selection),
682 current_text_value: Some(current_text_value.to_string()),
683 ..Self::new(WidgetType::TextEdit)
684 }
685 }
686
687 pub fn description(&self) -> String {
689 let Self {
690 typ,
691 enabled,
692 label,
693 current_text_value: text_value,
694 prev_text_value: _,
695 selected,
696 value,
697 text_selection: _,
698 hint_text: _,
699 } = self;
700
701 let widget_type = match typ {
703 WidgetType::Link => "link",
704 WidgetType::TextEdit => "text edit",
705 WidgetType::Button => "button",
706 WidgetType::Checkbox => "checkbox",
707 WidgetType::RadioButton => "radio",
708 WidgetType::RadioGroup => "radio group",
709 WidgetType::SelectableLabel => "selectable",
710 WidgetType::ComboBox => "combo",
711 WidgetType::Slider => "slider",
712 WidgetType::DragValue => "drag value",
713 WidgetType::ColorButton => "color button",
714 WidgetType::ImageButton => "image button",
715 WidgetType::Image => "image",
716 WidgetType::CollapsingHeader => "collapsing header",
717 WidgetType::ProgressIndicator => "progress indicator",
718 WidgetType::Window => "window",
719 WidgetType::Label | WidgetType::Other => "",
720 };
721
722 let mut description = widget_type.to_owned();
723
724 if let Some(selected) = selected {
725 if *typ == WidgetType::Checkbox {
726 let state = if *selected { "checked" } else { "unchecked" };
727 description = format!("{state} {description}");
728 } else {
729 description += if *selected { "selected" } else { "" };
730 };
731 }
732
733 if let Some(label) = label {
734 description = format!("{label}: {description}");
735 }
736
737 if typ == &WidgetType::TextEdit {
738 let text = if let Some(text_value) = text_value {
739 if text_value.is_empty() {
740 "blank".into()
741 } else {
742 text_value.clone()
743 }
744 } else {
745 "blank".into()
746 };
747 description = format!("{text}: {description}");
748 }
749
750 if let Some(value) = value {
751 description += " ";
752 description += &value.to_string();
753 }
754
755 if !enabled {
756 description += ": disabled";
757 }
758 description.trim().to_owned()
759 }
760}