1use crate::{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: viewports,
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 viewports {
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
128impl PlatformOutput {
129 pub fn events_description(&self) -> String {
131 if let Some(event) = self.events.iter().next_back() {
133 match event {
134 OutputEvent::Clicked(widget_info)
135 | OutputEvent::DoubleClicked(widget_info)
136 | OutputEvent::TripleClicked(widget_info)
137 | OutputEvent::FocusGained(widget_info)
138 | OutputEvent::TextSelectionChanged(widget_info)
139 | OutputEvent::ValueChanged(widget_info) => {
140 return widget_info.description();
141 }
142 }
143 }
144 Default::default()
145 }
146
147 pub fn append(&mut self, newer: Self) {
149 let Self {
150 cursor_icon,
151 open_url,
152 copied_text,
153 mut events,
154 mutable_text_under_cursor,
155 ime,
156 #[cfg(feature = "accesskit")]
157 accesskit_update,
158 } = newer;
159
160 self.cursor_icon = cursor_icon;
161 if open_url.is_some() {
162 self.open_url = open_url;
163 }
164 if !copied_text.is_empty() {
165 self.copied_text = copied_text;
166 }
167 self.events.append(&mut events);
168 self.mutable_text_under_cursor = mutable_text_under_cursor;
169 self.ime = ime.or(self.ime);
170
171 #[cfg(feature = "accesskit")]
172 {
173 self.accesskit_update = accesskit_update;
176 }
177 }
178
179 pub fn take(&mut self) -> Self {
181 let taken = std::mem::take(self);
182 self.cursor_icon = taken.cursor_icon; taken
184 }
185}
186
187#[derive(Clone, PartialEq, Eq)]
191#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
192pub struct OpenUrl {
193 pub url: String,
194
195 pub new_tab: bool,
199}
200
201impl OpenUrl {
202 #[allow(clippy::needless_pass_by_value)]
203 pub fn same_tab(url: impl ToString) -> Self {
204 Self {
205 url: url.to_string(),
206 new_tab: false,
207 }
208 }
209
210 #[allow(clippy::needless_pass_by_value)]
211 pub fn new_tab(url: impl ToString) -> Self {
212 Self {
213 url: url.to_string(),
214 new_tab: true,
215 }
216 }
217}
218
219#[derive(Clone, Copy, Debug, PartialEq, Eq)]
225#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
226pub enum UserAttentionType {
227 Critical,
229
230 Informational,
232
233 Reset,
235}
236
237#[derive(Clone, Copy, Debug, PartialEq, Eq)]
243#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
244pub enum CursorIcon {
245 Default,
247
248 None,
250
251 ContextMenu,
255
256 Help,
258
259 PointingHand,
261
262 Progress,
264
265 Wait,
267
268 Cell,
272
273 Crosshair,
275
276 Text,
278
279 VerticalText,
281
282 Alias,
286
287 Copy,
289
290 Move,
292
293 NoDrop,
295
296 NotAllowed,
298
299 Grab,
301
302 Grabbing,
304
305 AllScroll,
308
309 ResizeHorizontal,
313
314 ResizeNeSw,
316
317 ResizeNwSe,
319
320 ResizeVertical,
322
323 ResizeEast,
327
328 ResizeSouthEast,
330
331 ResizeSouth,
333
334 ResizeSouthWest,
336
337 ResizeWest,
339
340 ResizeNorthWest,
342
343 ResizeNorth,
345
346 ResizeNorthEast,
348
349 ResizeColumn,
352
353 ResizeRow,
355
356 ZoomIn,
360
361 ZoomOut,
363}
364
365impl CursorIcon {
366 pub const ALL: [Self; 35] = [
367 Self::Default,
368 Self::None,
369 Self::ContextMenu,
370 Self::Help,
371 Self::PointingHand,
372 Self::Progress,
373 Self::Wait,
374 Self::Cell,
375 Self::Crosshair,
376 Self::Text,
377 Self::VerticalText,
378 Self::Alias,
379 Self::Copy,
380 Self::Move,
381 Self::NoDrop,
382 Self::NotAllowed,
383 Self::Grab,
384 Self::Grabbing,
385 Self::AllScroll,
386 Self::ResizeHorizontal,
387 Self::ResizeNeSw,
388 Self::ResizeNwSe,
389 Self::ResizeVertical,
390 Self::ResizeEast,
391 Self::ResizeSouthEast,
392 Self::ResizeSouth,
393 Self::ResizeSouthWest,
394 Self::ResizeWest,
395 Self::ResizeNorthWest,
396 Self::ResizeNorth,
397 Self::ResizeNorthEast,
398 Self::ResizeColumn,
399 Self::ResizeRow,
400 Self::ZoomIn,
401 Self::ZoomOut,
402 ];
403}
404
405impl Default for CursorIcon {
406 fn default() -> Self {
407 Self::Default
408 }
409}
410
411#[derive(Clone, PartialEq)]
415#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
416pub enum OutputEvent {
417 Clicked(WidgetInfo),
419
420 DoubleClicked(WidgetInfo),
422
423 TripleClicked(WidgetInfo),
425
426 FocusGained(WidgetInfo),
428
429 TextSelectionChanged(WidgetInfo),
431
432 ValueChanged(WidgetInfo),
434}
435
436impl OutputEvent {
437 pub fn widget_info(&self) -> &WidgetInfo {
438 match self {
439 Self::Clicked(info)
440 | Self::DoubleClicked(info)
441 | Self::TripleClicked(info)
442 | Self::FocusGained(info)
443 | Self::TextSelectionChanged(info)
444 | Self::ValueChanged(info) => info,
445 }
446 }
447}
448
449impl std::fmt::Debug for OutputEvent {
450 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
451 match self {
452 Self::Clicked(wi) => write!(f, "Clicked({wi:?})"),
453 Self::DoubleClicked(wi) => write!(f, "DoubleClicked({wi:?})"),
454 Self::TripleClicked(wi) => write!(f, "TripleClicked({wi:?})"),
455 Self::FocusGained(wi) => write!(f, "FocusGained({wi:?})"),
456 Self::TextSelectionChanged(wi) => write!(f, "TextSelectionChanged({wi:?})"),
457 Self::ValueChanged(wi) => write!(f, "ValueChanged({wi:?})"),
458 }
459 }
460}
461
462#[derive(Clone, PartialEq)]
464#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
465pub struct WidgetInfo {
466 pub typ: WidgetType,
468
469 pub enabled: bool,
471
472 pub label: Option<String>,
474
475 pub current_text_value: Option<String>,
477
478 pub prev_text_value: Option<String>,
480
481 pub selected: Option<bool>,
483
484 pub value: Option<f64>,
486
487 pub text_selection: Option<std::ops::RangeInclusive<usize>>,
489}
490
491impl std::fmt::Debug for WidgetInfo {
492 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
493 let Self {
494 typ,
495 enabled,
496 label,
497 current_text_value: text_value,
498 prev_text_value,
499 selected,
500 value,
501 text_selection,
502 } = self;
503
504 let mut s = f.debug_struct("WidgetInfo");
505
506 s.field("typ", typ);
507
508 if !enabled {
509 s.field("enabled", enabled);
510 }
511
512 if let Some(label) = label {
513 s.field("label", label);
514 }
515 if let Some(text_value) = text_value {
516 s.field("text_value", text_value);
517 }
518 if let Some(prev_text_value) = prev_text_value {
519 s.field("prev_text_value", prev_text_value);
520 }
521 if let Some(selected) = selected {
522 s.field("selected", selected);
523 }
524 if let Some(value) = value {
525 s.field("value", value);
526 }
527 if let Some(text_selection) = text_selection {
528 s.field("text_selection", text_selection);
529 }
530
531 s.finish()
532 }
533}
534
535impl WidgetInfo {
536 pub fn new(typ: WidgetType) -> Self {
537 Self {
538 typ,
539 enabled: true,
540 label: None,
541 current_text_value: None,
542 prev_text_value: None,
543 selected: None,
544 value: None,
545 text_selection: None,
546 }
547 }
548
549 #[allow(clippy::needless_pass_by_value)]
550 pub fn labeled(typ: WidgetType, enabled: bool, label: impl ToString) -> Self {
551 Self {
552 enabled,
553 label: Some(label.to_string()),
554 ..Self::new(typ)
555 }
556 }
557
558 #[allow(clippy::needless_pass_by_value)]
560 pub fn selected(typ: WidgetType, enabled: bool, selected: bool, label: impl ToString) -> Self {
561 Self {
562 enabled,
563 label: Some(label.to_string()),
564 selected: Some(selected),
565 ..Self::new(typ)
566 }
567 }
568
569 pub fn drag_value(enabled: bool, value: f64) -> Self {
570 Self {
571 enabled,
572 value: Some(value),
573 ..Self::new(WidgetType::DragValue)
574 }
575 }
576
577 #[allow(clippy::needless_pass_by_value)]
578 pub fn slider(enabled: bool, value: f64, label: impl ToString) -> Self {
579 let label = label.to_string();
580 Self {
581 enabled,
582 label: if label.is_empty() { None } else { Some(label) },
583 value: Some(value),
584 ..Self::new(WidgetType::Slider)
585 }
586 }
587
588 #[allow(clippy::needless_pass_by_value)]
589 pub fn text_edit(
590 enabled: bool,
591 prev_text_value: impl ToString,
592 text_value: impl ToString,
593 ) -> Self {
594 let text_value = text_value.to_string();
595 let prev_text_value = prev_text_value.to_string();
596 let prev_text_value = if text_value == prev_text_value {
597 None
598 } else {
599 Some(prev_text_value)
600 };
601 Self {
602 enabled,
603 current_text_value: Some(text_value),
604 prev_text_value,
605 ..Self::new(WidgetType::TextEdit)
606 }
607 }
608
609 #[allow(clippy::needless_pass_by_value)]
610 pub fn text_selection_changed(
611 enabled: bool,
612 text_selection: std::ops::RangeInclusive<usize>,
613 current_text_value: impl ToString,
614 ) -> Self {
615 Self {
616 enabled,
617 text_selection: Some(text_selection),
618 current_text_value: Some(current_text_value.to_string()),
619 ..Self::new(WidgetType::TextEdit)
620 }
621 }
622
623 pub fn description(&self) -> String {
625 let Self {
626 typ,
627 enabled,
628 label,
629 current_text_value: text_value,
630 prev_text_value: _,
631 selected,
632 value,
633 text_selection: _,
634 } = self;
635
636 let widget_type = match typ {
638 WidgetType::Link => "link",
639 WidgetType::TextEdit => "text edit",
640 WidgetType::Button => "button",
641 WidgetType::Checkbox => "checkbox",
642 WidgetType::RadioButton => "radio",
643 WidgetType::SelectableLabel => "selectable",
644 WidgetType::ComboBox => "combo",
645 WidgetType::Slider => "slider",
646 WidgetType::DragValue => "drag value",
647 WidgetType::ColorButton => "color button",
648 WidgetType::ImageButton => "image button",
649 WidgetType::CollapsingHeader => "collapsing header",
650 WidgetType::ProgressIndicator => "progress indicator",
651 WidgetType::Label | WidgetType::Other => "",
652 };
653
654 let mut description = widget_type.to_owned();
655
656 if let Some(selected) = selected {
657 if *typ == WidgetType::Checkbox {
658 let state = if *selected { "checked" } else { "unchecked" };
659 description = format!("{state} {description}");
660 } else {
661 description += if *selected { "selected" } else { "" };
662 };
663 }
664
665 if let Some(label) = label {
666 description = format!("{label}: {description}");
667 }
668
669 if typ == &WidgetType::TextEdit {
670 let text = if let Some(text_value) = text_value {
671 if text_value.is_empty() {
672 "blank".into()
673 } else {
674 text_value.to_string()
675 }
676 } else {
677 "blank".into()
678 };
679 description = format!("{text}: {description}");
680 }
681
682 if let Some(value) = value {
683 description += " ";
684 description += &value.to_string();
685 }
686
687 if !enabled {
688 description += ": disabled";
689 }
690 description.trim().to_owned()
691 }
692}