1use crate::{OrderedViewportIdMap, RepaintCause, 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: OrderedViewportIdMap<ViewportOutput>,
36}
37
38impl FullOutput {
39 pub fn append(&mut self, newer: Self) {
41 use std::collections::btree_map::Entry;
42
43 let Self {
44 platform_output,
45 textures_delta,
46 shapes,
47 pixels_per_point,
48 viewport_output,
49 } = newer;
50
51 self.platform_output.append(platform_output);
52 self.textures_delta.append(textures_delta);
53 self.shapes = shapes; self.pixels_per_point = pixels_per_point; for (id, new_viewport) in viewport_output {
57 match self.viewport_output.entry(id) {
58 Entry::Vacant(entry) => {
59 entry.insert(new_viewport);
60 }
61 Entry::Occupied(mut entry) => {
62 entry.get_mut().append(new_viewport);
63 }
64 }
65 }
66 }
67}
68
69#[derive(Copy, Clone, Debug, PartialEq, Eq)]
73#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
74pub struct IMEOutput {
75 pub rect: crate::Rect,
77
78 pub cursor_rect: crate::Rect,
82}
83
84#[derive(Clone, Debug, PartialEq, Eq)]
88#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
89pub enum OutputCommand {
90 CopyText(String),
94
95 CopyImage(crate::ColorImage),
97
98 OpenUrl(OpenUrl),
100}
101
102#[derive(Default, Clone, PartialEq)]
108#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
109pub struct PlatformOutput {
110 pub commands: Vec<OutputCommand>,
112
113 pub cursor_icon: CursorIcon,
115
116 pub events: Vec<OutputEvent>,
118
119 pub mutable_text_under_cursor: bool,
122
123 pub ime: Option<IMEOutput>,
127
128 pub accesskit_update: Option<accesskit::TreeUpdate>,
132
133 pub num_completed_passes: usize,
140
141 #[cfg_attr(feature = "serde", serde(skip))]
147 pub request_discard_reasons: Vec<RepaintCause>,
148}
149
150impl PlatformOutput {
151 pub fn events_description(&self) -> String {
153 if let Some(event) = self.events.iter().next_back() {
155 match event {
156 OutputEvent::Clicked(widget_info)
157 | OutputEvent::DoubleClicked(widget_info)
158 | OutputEvent::TripleClicked(widget_info)
159 | OutputEvent::FocusGained(widget_info)
160 | OutputEvent::TextSelectionChanged(widget_info)
161 | OutputEvent::ValueChanged(widget_info) => {
162 return widget_info.description();
163 }
164 }
165 }
166 Default::default()
167 }
168
169 pub fn append(&mut self, newer: Self) {
171 let Self {
172 mut commands,
173 cursor_icon,
174 mut events,
175 mutable_text_under_cursor,
176 ime,
177 accesskit_update,
178 num_completed_passes,
179 mut request_discard_reasons,
180 } = newer;
181
182 self.commands.append(&mut commands);
183 self.cursor_icon = cursor_icon;
184 self.events.append(&mut events);
185 self.mutable_text_under_cursor = mutable_text_under_cursor;
186 self.ime = ime.or(self.ime);
187 self.num_completed_passes += num_completed_passes;
188 self.request_discard_reasons
189 .append(&mut request_discard_reasons);
190
191 self.accesskit_update = accesskit_update;
193 }
194
195 pub fn take(&mut self) -> Self {
197 let taken = std::mem::take(self);
198 self.cursor_icon = taken.cursor_icon; taken
200 }
201
202 pub fn requested_discard(&self) -> bool {
204 !self.request_discard_reasons.is_empty()
205 }
206}
207
208#[derive(Clone, Debug, PartialEq, Eq)]
212#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
213pub struct OpenUrl {
214 pub url: String,
215
216 pub new_tab: bool,
220}
221
222impl OpenUrl {
223 #[expect(clippy::needless_pass_by_value)]
224 pub fn same_tab(url: impl ToString) -> Self {
225 Self {
226 url: url.to_string(),
227 new_tab: false,
228 }
229 }
230
231 #[expect(clippy::needless_pass_by_value)]
232 pub fn new_tab(url: impl ToString) -> Self {
233 Self {
234 url: url.to_string(),
235 new_tab: true,
236 }
237 }
238}
239
240#[derive(Clone, Copy, Debug, PartialEq, Eq)]
246#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
247pub enum UserAttentionType {
248 Critical,
250
251 Informational,
253
254 Reset,
256}
257
258#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
264#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
265pub enum CursorIcon {
266 #[default]
268 Default,
269
270 None,
272
273 ContextMenu,
277
278 Help,
280
281 PointingHand,
283
284 Progress,
286
287 Wait,
289
290 Cell,
294
295 Crosshair,
297
298 Text,
300
301 VerticalText,
303
304 Alias,
308
309 Copy,
311
312 Move,
314
315 NoDrop,
317
318 NotAllowed,
320
321 Grab,
323
324 Grabbing,
326
327 AllScroll,
330
331 ResizeHorizontal,
335
336 ResizeNeSw,
338
339 ResizeNwSe,
341
342 ResizeVertical,
344
345 ResizeEast,
349
350 ResizeSouthEast,
352
353 ResizeSouth,
355
356 ResizeSouthWest,
358
359 ResizeWest,
361
362 ResizeNorthWest,
364
365 ResizeNorth,
367
368 ResizeNorthEast,
370
371 ResizeColumn,
374
375 ResizeRow,
377
378 ZoomIn,
382
383 ZoomOut,
385}
386
387impl CursorIcon {
388 pub const ALL: [Self; 35] = [
389 Self::Default,
390 Self::None,
391 Self::ContextMenu,
392 Self::Help,
393 Self::PointingHand,
394 Self::Progress,
395 Self::Wait,
396 Self::Cell,
397 Self::Crosshair,
398 Self::Text,
399 Self::VerticalText,
400 Self::Alias,
401 Self::Copy,
402 Self::Move,
403 Self::NoDrop,
404 Self::NotAllowed,
405 Self::Grab,
406 Self::Grabbing,
407 Self::AllScroll,
408 Self::ResizeHorizontal,
409 Self::ResizeNeSw,
410 Self::ResizeNwSe,
411 Self::ResizeVertical,
412 Self::ResizeEast,
413 Self::ResizeSouthEast,
414 Self::ResizeSouth,
415 Self::ResizeSouthWest,
416 Self::ResizeWest,
417 Self::ResizeNorthWest,
418 Self::ResizeNorth,
419 Self::ResizeNorthEast,
420 Self::ResizeColumn,
421 Self::ResizeRow,
422 Self::ZoomIn,
423 Self::ZoomOut,
424 ];
425}
426
427#[derive(Clone, PartialEq)]
431#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
432pub enum OutputEvent {
433 Clicked(WidgetInfo),
435
436 DoubleClicked(WidgetInfo),
438
439 TripleClicked(WidgetInfo),
441
442 FocusGained(WidgetInfo),
444
445 TextSelectionChanged(WidgetInfo),
447
448 ValueChanged(WidgetInfo),
450}
451
452impl OutputEvent {
453 pub fn widget_info(&self) -> &WidgetInfo {
454 match self {
455 Self::Clicked(info)
456 | Self::DoubleClicked(info)
457 | Self::TripleClicked(info)
458 | Self::FocusGained(info)
459 | Self::TextSelectionChanged(info)
460 | Self::ValueChanged(info) => info,
461 }
462 }
463}
464
465impl std::fmt::Debug for OutputEvent {
466 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
467 match self {
468 Self::Clicked(wi) => write!(f, "Clicked({wi:?})"),
469 Self::DoubleClicked(wi) => write!(f, "DoubleClicked({wi:?})"),
470 Self::TripleClicked(wi) => write!(f, "TripleClicked({wi:?})"),
471 Self::FocusGained(wi) => write!(f, "FocusGained({wi:?})"),
472 Self::TextSelectionChanged(wi) => write!(f, "TextSelectionChanged({wi:?})"),
473 Self::ValueChanged(wi) => write!(f, "ValueChanged({wi:?})"),
474 }
475 }
476}
477
478#[derive(Clone, PartialEq)]
480#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
481pub struct WidgetInfo {
482 pub typ: WidgetType,
484
485 pub enabled: bool,
487
488 pub label: Option<String>,
490
491 pub current_text_value: Option<String>,
493
494 pub prev_text_value: Option<String>,
496
497 pub selected: Option<bool>,
499
500 pub value: Option<f64>,
502
503 pub text_selection: Option<std::ops::RangeInclusive<usize>>,
505
506 pub hint_text: Option<String>,
508}
509
510impl std::fmt::Debug for WidgetInfo {
511 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
512 let Self {
513 typ,
514 enabled,
515 label,
516 current_text_value: text_value,
517 prev_text_value,
518 selected,
519 value,
520 text_selection,
521 hint_text,
522 } = self;
523
524 let mut s = f.debug_struct("WidgetInfo");
525
526 s.field("typ", typ);
527
528 if !enabled {
529 s.field("enabled", enabled);
530 }
531
532 if let Some(label) = label {
533 s.field("label", label);
534 }
535 if let Some(text_value) = text_value {
536 s.field("text_value", text_value);
537 }
538 if let Some(prev_text_value) = prev_text_value {
539 s.field("prev_text_value", prev_text_value);
540 }
541 if let Some(selected) = selected {
542 s.field("selected", selected);
543 }
544 if let Some(value) = value {
545 s.field("value", value);
546 }
547 if let Some(text_selection) = text_selection {
548 s.field("text_selection", text_selection);
549 }
550 if let Some(hint_text) = hint_text {
551 s.field("hint_text", hint_text);
552 }
553
554 s.finish()
555 }
556}
557
558impl WidgetInfo {
559 pub fn new(typ: WidgetType) -> Self {
560 Self {
561 typ,
562 enabled: true,
563 label: None,
564 current_text_value: None,
565 prev_text_value: None,
566 selected: None,
567 value: None,
568 text_selection: None,
569 hint_text: None,
570 }
571 }
572
573 #[expect(clippy::needless_pass_by_value)]
574 pub fn labeled(typ: WidgetType, enabled: bool, label: impl ToString) -> Self {
575 Self {
576 enabled,
577 label: Some(label.to_string()),
578 ..Self::new(typ)
579 }
580 }
581
582 #[expect(clippy::needless_pass_by_value)]
584 pub fn selected(typ: WidgetType, enabled: bool, selected: bool, label: impl ToString) -> Self {
585 Self {
586 enabled,
587 label: Some(label.to_string()),
588 selected: Some(selected),
589 ..Self::new(typ)
590 }
591 }
592
593 pub fn drag_value(enabled: bool, value: f64) -> Self {
594 Self {
595 enabled,
596 value: Some(value),
597 ..Self::new(WidgetType::DragValue)
598 }
599 }
600
601 #[expect(clippy::needless_pass_by_value)]
602 pub fn slider(enabled: bool, value: f64, label: impl ToString) -> Self {
603 let label = label.to_string();
604 Self {
605 enabled,
606 label: if label.is_empty() { None } else { Some(label) },
607 value: Some(value),
608 ..Self::new(WidgetType::Slider)
609 }
610 }
611
612 #[expect(clippy::needless_pass_by_value)]
613 pub fn text_edit(
614 enabled: bool,
615 prev_text_value: impl ToString,
616 text_value: impl ToString,
617 hint_text: impl ToString,
618 ) -> Self {
619 let text_value = text_value.to_string();
620 let prev_text_value = prev_text_value.to_string();
621 let hint_text = hint_text.to_string();
622 let prev_text_value = if text_value == prev_text_value {
623 None
624 } else {
625 Some(prev_text_value)
626 };
627 Self {
628 enabled,
629 current_text_value: Some(text_value),
630 prev_text_value,
631 hint_text: Some(hint_text),
632 ..Self::new(WidgetType::TextEdit)
633 }
634 }
635
636 #[expect(clippy::needless_pass_by_value)]
637 pub fn text_selection_changed(
638 enabled: bool,
639 text_selection: std::ops::RangeInclusive<usize>,
640 current_text_value: impl ToString,
641 ) -> Self {
642 Self {
643 enabled,
644 text_selection: Some(text_selection),
645 current_text_value: Some(current_text_value.to_string()),
646 ..Self::new(WidgetType::TextEdit)
647 }
648 }
649
650 pub fn description(&self) -> String {
652 let Self {
653 typ,
654 enabled,
655 label,
656 current_text_value: text_value,
657 prev_text_value: _,
658 selected,
659 value,
660 text_selection: _,
661 hint_text: _,
662 } = self;
663
664 let widget_type = match typ {
666 WidgetType::Link => "link",
667 WidgetType::TextEdit => "text edit",
668 WidgetType::Button => "button",
669 WidgetType::Checkbox => "checkbox",
670 WidgetType::RadioButton => "radio",
671 WidgetType::RadioGroup => "radio group",
672 WidgetType::SelectableLabel => "selectable",
673 WidgetType::ComboBox => "combo",
674 WidgetType::Slider => "slider",
675 WidgetType::DragValue => "drag value",
676 WidgetType::ColorButton => "color button",
677 WidgetType::Image => "image",
678 WidgetType::CollapsingHeader => "collapsing header",
679 WidgetType::Panel => "panel",
680 WidgetType::ProgressIndicator => "progress indicator",
681 WidgetType::Window => "window",
682 WidgetType::ScrollBar => "scroll bar",
683 WidgetType::ResizeHandle => "resize handle",
684 WidgetType::Label | WidgetType::Other => "",
685 };
686
687 let mut description = widget_type.to_owned();
688
689 if let Some(selected) = selected {
690 if *typ == WidgetType::Checkbox {
691 let state = if *selected { "checked" } else { "unchecked" };
692 description = format!("{state} {description}");
693 } else {
694 description += if *selected { "selected" } else { "" };
695 }
696 }
697
698 if let Some(label) = label {
699 description = format!("{label}: {description}");
700 }
701
702 if typ == &WidgetType::TextEdit {
703 let text = if let Some(text_value) = text_value {
704 if text_value.is_empty() {
705 "blank".into()
706 } else {
707 text_value.clone()
708 }
709 } else {
710 "blank".into()
711 };
712 description = format!("{text}: {description}");
713 }
714
715 if let Some(value) = value {
716 description += " ";
717 description += &value.to_string();
718 }
719
720 if !enabled {
721 description += ": disabled";
722 }
723 description.trim().to_owned()
724 }
725}