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(Default, Clone, PartialEq)]
88#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
89pub struct PlatformOutput {
90 pub cursor_icon: CursorIcon,
92
93 pub open_url: Option<OpenUrl>,
95
96 pub copied_text: String,
108
109 pub events: Vec<OutputEvent>,
111
112 pub mutable_text_under_cursor: bool,
115
116 pub ime: Option<IMEOutput>,
120
121 #[cfg(feature = "accesskit")]
125 pub accesskit_update: Option<accesskit::TreeUpdate>,
126
127 pub num_completed_passes: usize,
134
135 #[cfg_attr(feature = "serde", serde(skip))]
141 pub request_discard_reasons: Vec<RepaintCause>,
142}
143
144impl PlatformOutput {
145 pub fn events_description(&self) -> String {
147 if let Some(event) = self.events.iter().next_back() {
149 match event {
150 OutputEvent::Clicked(widget_info)
151 | OutputEvent::DoubleClicked(widget_info)
152 | OutputEvent::TripleClicked(widget_info)
153 | OutputEvent::FocusGained(widget_info)
154 | OutputEvent::TextSelectionChanged(widget_info)
155 | OutputEvent::ValueChanged(widget_info) => {
156 return widget_info.description();
157 }
158 }
159 }
160 Default::default()
161 }
162
163 pub fn append(&mut self, newer: Self) {
165 let Self {
166 cursor_icon,
167 open_url,
168 copied_text,
169 mut events,
170 mutable_text_under_cursor,
171 ime,
172 #[cfg(feature = "accesskit")]
173 accesskit_update,
174 num_completed_passes,
175 mut request_discard_reasons,
176 } = newer;
177
178 self.cursor_icon = cursor_icon;
179 if open_url.is_some() {
180 self.open_url = open_url;
181 }
182 if !copied_text.is_empty() {
183 self.copied_text = copied_text;
184 }
185 self.events.append(&mut events);
186 self.mutable_text_under_cursor = mutable_text_under_cursor;
187 self.ime = ime.or(self.ime);
188 self.num_completed_passes += num_completed_passes;
189 self.request_discard_reasons
190 .append(&mut request_discard_reasons);
191
192 #[cfg(feature = "accesskit")]
193 {
194 self.accesskit_update = accesskit_update;
197 }
198 }
199
200 pub fn take(&mut self) -> Self {
202 let taken = std::mem::take(self);
203 self.cursor_icon = taken.cursor_icon; taken
205 }
206
207 pub fn requested_discard(&self) -> bool {
209 !self.request_discard_reasons.is_empty()
210 }
211}
212
213#[derive(Clone, PartialEq, Eq)]
217#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
218pub struct OpenUrl {
219 pub url: String,
220
221 pub new_tab: bool,
225}
226
227impl OpenUrl {
228 #[allow(clippy::needless_pass_by_value)]
229 pub fn same_tab(url: impl ToString) -> Self {
230 Self {
231 url: url.to_string(),
232 new_tab: false,
233 }
234 }
235
236 #[allow(clippy::needless_pass_by_value)]
237 pub fn new_tab(url: impl ToString) -> Self {
238 Self {
239 url: url.to_string(),
240 new_tab: true,
241 }
242 }
243}
244
245#[derive(Clone, Copy, Debug, PartialEq, Eq)]
251#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
252pub enum UserAttentionType {
253 Critical,
255
256 Informational,
258
259 Reset,
261}
262
263#[derive(Clone, Copy, Debug, PartialEq, Eq)]
269#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
270pub enum CursorIcon {
271 Default,
273
274 None,
276
277 ContextMenu,
281
282 Help,
284
285 PointingHand,
287
288 Progress,
290
291 Wait,
293
294 Cell,
298
299 Crosshair,
301
302 Text,
304
305 VerticalText,
307
308 Alias,
312
313 Copy,
315
316 Move,
318
319 NoDrop,
321
322 NotAllowed,
324
325 Grab,
327
328 Grabbing,
330
331 AllScroll,
334
335 ResizeHorizontal,
339
340 ResizeNeSw,
342
343 ResizeNwSe,
345
346 ResizeVertical,
348
349 ResizeEast,
353
354 ResizeSouthEast,
356
357 ResizeSouth,
359
360 ResizeSouthWest,
362
363 ResizeWest,
365
366 ResizeNorthWest,
368
369 ResizeNorth,
371
372 ResizeNorthEast,
374
375 ResizeColumn,
378
379 ResizeRow,
381
382 ZoomIn,
386
387 ZoomOut,
389}
390
391impl CursorIcon {
392 pub const ALL: [Self; 35] = [
393 Self::Default,
394 Self::None,
395 Self::ContextMenu,
396 Self::Help,
397 Self::PointingHand,
398 Self::Progress,
399 Self::Wait,
400 Self::Cell,
401 Self::Crosshair,
402 Self::Text,
403 Self::VerticalText,
404 Self::Alias,
405 Self::Copy,
406 Self::Move,
407 Self::NoDrop,
408 Self::NotAllowed,
409 Self::Grab,
410 Self::Grabbing,
411 Self::AllScroll,
412 Self::ResizeHorizontal,
413 Self::ResizeNeSw,
414 Self::ResizeNwSe,
415 Self::ResizeVertical,
416 Self::ResizeEast,
417 Self::ResizeSouthEast,
418 Self::ResizeSouth,
419 Self::ResizeSouthWest,
420 Self::ResizeWest,
421 Self::ResizeNorthWest,
422 Self::ResizeNorth,
423 Self::ResizeNorthEast,
424 Self::ResizeColumn,
425 Self::ResizeRow,
426 Self::ZoomIn,
427 Self::ZoomOut,
428 ];
429}
430
431impl Default for CursorIcon {
432 fn default() -> Self {
433 Self::Default
434 }
435}
436
437#[derive(Clone, PartialEq)]
441#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
442pub enum OutputEvent {
443 Clicked(WidgetInfo),
445
446 DoubleClicked(WidgetInfo),
448
449 TripleClicked(WidgetInfo),
451
452 FocusGained(WidgetInfo),
454
455 TextSelectionChanged(WidgetInfo),
457
458 ValueChanged(WidgetInfo),
460}
461
462impl OutputEvent {
463 pub fn widget_info(&self) -> &WidgetInfo {
464 match self {
465 Self::Clicked(info)
466 | Self::DoubleClicked(info)
467 | Self::TripleClicked(info)
468 | Self::FocusGained(info)
469 | Self::TextSelectionChanged(info)
470 | Self::ValueChanged(info) => info,
471 }
472 }
473}
474
475impl std::fmt::Debug for OutputEvent {
476 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
477 match self {
478 Self::Clicked(wi) => write!(f, "Clicked({wi:?})"),
479 Self::DoubleClicked(wi) => write!(f, "DoubleClicked({wi:?})"),
480 Self::TripleClicked(wi) => write!(f, "TripleClicked({wi:?})"),
481 Self::FocusGained(wi) => write!(f, "FocusGained({wi:?})"),
482 Self::TextSelectionChanged(wi) => write!(f, "TextSelectionChanged({wi:?})"),
483 Self::ValueChanged(wi) => write!(f, "ValueChanged({wi:?})"),
484 }
485 }
486}
487
488#[derive(Clone, PartialEq)]
490#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
491pub struct WidgetInfo {
492 pub typ: WidgetType,
494
495 pub enabled: bool,
497
498 pub label: Option<String>,
500
501 pub current_text_value: Option<String>,
503
504 pub prev_text_value: Option<String>,
506
507 pub selected: Option<bool>,
509
510 pub value: Option<f64>,
512
513 pub text_selection: Option<std::ops::RangeInclusive<usize>>,
515}
516
517impl std::fmt::Debug for WidgetInfo {
518 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
519 let Self {
520 typ,
521 enabled,
522 label,
523 current_text_value: text_value,
524 prev_text_value,
525 selected,
526 value,
527 text_selection,
528 } = self;
529
530 let mut s = f.debug_struct("WidgetInfo");
531
532 s.field("typ", typ);
533
534 if !enabled {
535 s.field("enabled", enabled);
536 }
537
538 if let Some(label) = label {
539 s.field("label", label);
540 }
541 if let Some(text_value) = text_value {
542 s.field("text_value", text_value);
543 }
544 if let Some(prev_text_value) = prev_text_value {
545 s.field("prev_text_value", prev_text_value);
546 }
547 if let Some(selected) = selected {
548 s.field("selected", selected);
549 }
550 if let Some(value) = value {
551 s.field("value", value);
552 }
553 if let Some(text_selection) = text_selection {
554 s.field("text_selection", text_selection);
555 }
556
557 s.finish()
558 }
559}
560
561impl WidgetInfo {
562 pub fn new(typ: WidgetType) -> Self {
563 Self {
564 typ,
565 enabled: true,
566 label: None,
567 current_text_value: None,
568 prev_text_value: None,
569 selected: None,
570 value: None,
571 text_selection: None,
572 }
573 }
574
575 #[allow(clippy::needless_pass_by_value)]
576 pub fn labeled(typ: WidgetType, enabled: bool, label: impl ToString) -> Self {
577 Self {
578 enabled,
579 label: Some(label.to_string()),
580 ..Self::new(typ)
581 }
582 }
583
584 #[allow(clippy::needless_pass_by_value)]
586 pub fn selected(typ: WidgetType, enabled: bool, selected: bool, label: impl ToString) -> Self {
587 Self {
588 enabled,
589 label: Some(label.to_string()),
590 selected: Some(selected),
591 ..Self::new(typ)
592 }
593 }
594
595 pub fn drag_value(enabled: bool, value: f64) -> Self {
596 Self {
597 enabled,
598 value: Some(value),
599 ..Self::new(WidgetType::DragValue)
600 }
601 }
602
603 #[allow(clippy::needless_pass_by_value)]
604 pub fn slider(enabled: bool, value: f64, label: impl ToString) -> Self {
605 let label = label.to_string();
606 Self {
607 enabled,
608 label: if label.is_empty() { None } else { Some(label) },
609 value: Some(value),
610 ..Self::new(WidgetType::Slider)
611 }
612 }
613
614 #[allow(clippy::needless_pass_by_value)]
615 pub fn text_edit(
616 enabled: bool,
617 prev_text_value: impl ToString,
618 text_value: impl ToString,
619 ) -> Self {
620 let text_value = text_value.to_string();
621 let prev_text_value = prev_text_value.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 ..Self::new(WidgetType::TextEdit)
632 }
633 }
634
635 #[allow(clippy::needless_pass_by_value)]
636 pub fn text_selection_changed(
637 enabled: bool,
638 text_selection: std::ops::RangeInclusive<usize>,
639 current_text_value: impl ToString,
640 ) -> Self {
641 Self {
642 enabled,
643 text_selection: Some(text_selection),
644 current_text_value: Some(current_text_value.to_string()),
645 ..Self::new(WidgetType::TextEdit)
646 }
647 }
648
649 pub fn description(&self) -> String {
651 let Self {
652 typ,
653 enabled,
654 label,
655 current_text_value: text_value,
656 prev_text_value: _,
657 selected,
658 value,
659 text_selection: _,
660 } = self;
661
662 let widget_type = match typ {
664 WidgetType::Link => "link",
665 WidgetType::TextEdit => "text edit",
666 WidgetType::Button => "button",
667 WidgetType::Checkbox => "checkbox",
668 WidgetType::RadioButton => "radio",
669 WidgetType::RadioGroup => "radio group",
670 WidgetType::SelectableLabel => "selectable",
671 WidgetType::ComboBox => "combo",
672 WidgetType::Slider => "slider",
673 WidgetType::DragValue => "drag value",
674 WidgetType::ColorButton => "color button",
675 WidgetType::ImageButton => "image button",
676 WidgetType::CollapsingHeader => "collapsing header",
677 WidgetType::ProgressIndicator => "progress indicator",
678 WidgetType::Window => "window",
679 WidgetType::Label | WidgetType::Other => "",
680 };
681
682 let mut description = widget_type.to_owned();
683
684 if let Some(selected) = selected {
685 if *typ == WidgetType::Checkbox {
686 let state = if *selected { "checked" } else { "unchecked" };
687 description = format!("{state} {description}");
688 } else {
689 description += if *selected { "selected" } else { "" };
690 };
691 }
692
693 if let Some(label) = label {
694 description = format!("{label}: {description}");
695 }
696
697 if typ == &WidgetType::TextEdit {
698 let text = if let Some(text_value) = text_value {
699 if text_value.is_empty() {
700 "blank".into()
701 } else {
702 text_value.to_string()
703 }
704 } else {
705 "blank".into()
706 };
707 description = format!("{text}: {description}");
708 }
709
710 if let Some(value) = value {
711 description += " ";
712 description += &value.to_string();
713 }
714
715 if !enabled {
716 description += ": disabled";
717 }
718 description.trim().to_owned()
719 }
720}