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 #[allow(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 #[allow(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
544impl std::fmt::Debug for WidgetInfo {
545 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
546 let Self {
547 typ,
548 enabled,
549 label,
550 current_text_value: text_value,
551 prev_text_value,
552 selected,
553 value,
554 text_selection,
555 } = self;
556
557 let mut s = f.debug_struct("WidgetInfo");
558
559 s.field("typ", typ);
560
561 if !enabled {
562 s.field("enabled", enabled);
563 }
564
565 if let Some(label) = label {
566 s.field("label", label);
567 }
568 if let Some(text_value) = text_value {
569 s.field("text_value", text_value);
570 }
571 if let Some(prev_text_value) = prev_text_value {
572 s.field("prev_text_value", prev_text_value);
573 }
574 if let Some(selected) = selected {
575 s.field("selected", selected);
576 }
577 if let Some(value) = value {
578 s.field("value", value);
579 }
580 if let Some(text_selection) = text_selection {
581 s.field("text_selection", text_selection);
582 }
583
584 s.finish()
585 }
586}
587
588impl WidgetInfo {
589 pub fn new(typ: WidgetType) -> Self {
590 Self {
591 typ,
592 enabled: true,
593 label: None,
594 current_text_value: None,
595 prev_text_value: None,
596 selected: None,
597 value: None,
598 text_selection: None,
599 }
600 }
601
602 #[allow(clippy::needless_pass_by_value)]
603 pub fn labeled(typ: WidgetType, enabled: bool, label: impl ToString) -> Self {
604 Self {
605 enabled,
606 label: Some(label.to_string()),
607 ..Self::new(typ)
608 }
609 }
610
611 #[allow(clippy::needless_pass_by_value)]
613 pub fn selected(typ: WidgetType, enabled: bool, selected: bool, label: impl ToString) -> Self {
614 Self {
615 enabled,
616 label: Some(label.to_string()),
617 selected: Some(selected),
618 ..Self::new(typ)
619 }
620 }
621
622 pub fn drag_value(enabled: bool, value: f64) -> Self {
623 Self {
624 enabled,
625 value: Some(value),
626 ..Self::new(WidgetType::DragValue)
627 }
628 }
629
630 #[allow(clippy::needless_pass_by_value)]
631 pub fn slider(enabled: bool, value: f64, label: impl ToString) -> Self {
632 let label = label.to_string();
633 Self {
634 enabled,
635 label: if label.is_empty() { None } else { Some(label) },
636 value: Some(value),
637 ..Self::new(WidgetType::Slider)
638 }
639 }
640
641 #[allow(clippy::needless_pass_by_value)]
642 pub fn text_edit(
643 enabled: bool,
644 prev_text_value: impl ToString,
645 text_value: impl ToString,
646 ) -> Self {
647 let text_value = text_value.to_string();
648 let prev_text_value = prev_text_value.to_string();
649 let prev_text_value = if text_value == prev_text_value {
650 None
651 } else {
652 Some(prev_text_value)
653 };
654 Self {
655 enabled,
656 current_text_value: Some(text_value),
657 prev_text_value,
658 ..Self::new(WidgetType::TextEdit)
659 }
660 }
661
662 #[allow(clippy::needless_pass_by_value)]
663 pub fn text_selection_changed(
664 enabled: bool,
665 text_selection: std::ops::RangeInclusive<usize>,
666 current_text_value: impl ToString,
667 ) -> Self {
668 Self {
669 enabled,
670 text_selection: Some(text_selection),
671 current_text_value: Some(current_text_value.to_string()),
672 ..Self::new(WidgetType::TextEdit)
673 }
674 }
675
676 pub fn description(&self) -> String {
678 let Self {
679 typ,
680 enabled,
681 label,
682 current_text_value: text_value,
683 prev_text_value: _,
684 selected,
685 value,
686 text_selection: _,
687 } = self;
688
689 let widget_type = match typ {
691 WidgetType::Link => "link",
692 WidgetType::TextEdit => "text edit",
693 WidgetType::Button => "button",
694 WidgetType::Checkbox => "checkbox",
695 WidgetType::RadioButton => "radio",
696 WidgetType::RadioGroup => "radio group",
697 WidgetType::SelectableLabel => "selectable",
698 WidgetType::ComboBox => "combo",
699 WidgetType::Slider => "slider",
700 WidgetType::DragValue => "drag value",
701 WidgetType::ColorButton => "color button",
702 WidgetType::ImageButton => "image button",
703 WidgetType::Image => "image",
704 WidgetType::CollapsingHeader => "collapsing header",
705 WidgetType::ProgressIndicator => "progress indicator",
706 WidgetType::Window => "window",
707 WidgetType::Label | WidgetType::Other => "",
708 };
709
710 let mut description = widget_type.to_owned();
711
712 if let Some(selected) = selected {
713 if *typ == WidgetType::Checkbox {
714 let state = if *selected { "checked" } else { "unchecked" };
715 description = format!("{state} {description}");
716 } else {
717 description += if *selected { "selected" } else { "" };
718 };
719 }
720
721 if let Some(label) = label {
722 description = format!("{label}: {description}");
723 }
724
725 if typ == &WidgetType::TextEdit {
726 let text = if let Some(text_value) = text_value {
727 if text_value.is_empty() {
728 "blank".into()
729 } else {
730 text_value.to_string()
731 }
732 } else {
733 "blank".into()
734 };
735 description = format!("{text}: {description}");
736 }
737
738 if let Some(value) = value {
739 description += " ";
740 description += &value.to_string();
741 }
742
743 if !enabled {
744 description += ": disabled";
745 }
746 description.trim().to_owned()
747 }
748}