egui/data/
output.rs

1//! All the data egui returns to the backend at the end of each frame.
2
3use crate::{RepaintCause, ViewportIdMap, ViewportOutput, WidgetType};
4
5/// What egui emits each frame from [`crate::Context::run`].
6///
7/// The backend should use this.
8#[derive(Clone, Default)]
9pub struct FullOutput {
10    /// Non-rendering related output.
11    pub platform_output: PlatformOutput,
12
13    /// Texture changes since last frame (including the font texture).
14    ///
15    /// The backend needs to apply [`crate::TexturesDelta::set`] _before_ painting,
16    /// and free any texture in [`crate::TexturesDelta::free`] _after_ painting.
17    ///
18    /// It is assumed that all egui viewports share the same painter and texture namespace.
19    pub textures_delta: epaint::textures::TexturesDelta,
20
21    /// What to paint.
22    ///
23    /// You can use [`crate::Context::tessellate`] to turn this into triangles.
24    pub shapes: Vec<epaint::ClippedShape>,
25
26    /// The number of physical pixels per logical ui point, for the viewport that was updated.
27    ///
28    /// You can pass this to [`crate::Context::tessellate`] together with [`Self::shapes`].
29    pub pixels_per_point: f32,
30
31    /// All the active viewports, including the root.
32    ///
33    /// It is up to the integration to spawn a native window for each viewport,
34    /// and to close any window that no longer has a viewport in this map.
35    pub viewport_output: ViewportIdMap<ViewportOutput>,
36}
37
38impl FullOutput {
39    /// Add on new output.
40    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; // Only paint the latest
52        self.pixels_per_point = pixels_per_point; // Use latest
53
54        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/// Information about text being edited.
68///
69/// Useful for IME.
70#[derive(Copy, Clone, Debug, PartialEq, Eq)]
71#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
72pub struct IMEOutput {
73    /// Where the [`crate::TextEdit`] is located on screen.
74    pub rect: crate::Rect,
75
76    /// Where the primary cursor is.
77    ///
78    /// This is a very thin rectangle.
79    pub cursor_rect: crate::Rect,
80}
81
82/// The non-rendering part of what egui emits each frame.
83///
84/// You can access (and modify) this with [`crate::Context::output`].
85///
86/// The backend should use this.
87#[derive(Default, Clone, PartialEq)]
88#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
89pub struct PlatformOutput {
90    /// Set the cursor to this icon.
91    pub cursor_icon: CursorIcon,
92
93    /// If set, open this url.
94    pub open_url: Option<OpenUrl>,
95
96    /// If set, put this text in the system clipboard. Ignore if empty.
97    ///
98    /// This is often a response to [`crate::Event::Copy`] or [`crate::Event::Cut`].
99    ///
100    /// ```
101    /// # egui::__run_test_ui(|ui| {
102    /// if ui.button("📋").clicked() {
103    ///     ui.output_mut(|o| o.copied_text = "some_text".to_string());
104    /// }
105    /// # });
106    /// ```
107    pub copied_text: String,
108
109    /// Events that may be useful to e.g. a screen reader.
110    pub events: Vec<OutputEvent>,
111
112    /// Is there a mutable [`TextEdit`](crate::TextEdit) under the cursor?
113    /// Use by `eframe` web to show/hide mobile keyboard and IME agent.
114    pub mutable_text_under_cursor: bool,
115
116    /// This is set if, and only if, the user is currently editing text.
117    ///
118    /// Useful for IME.
119    pub ime: Option<IMEOutput>,
120
121    /// The difference in the widget tree since last frame.
122    ///
123    /// NOTE: this needs to be per-viewport.
124    #[cfg(feature = "accesskit")]
125    pub accesskit_update: Option<accesskit::TreeUpdate>,
126
127    /// How many ui passes is this the sum of?
128    ///
129    /// See [`crate::Context::request_discard`] for details.
130    ///
131    /// This is incremented at the END of each frame,
132    /// so this will be `0` for the first pass.
133    pub num_completed_passes: usize,
134
135    /// Was [`crate::Context::request_discard`] called during the latest pass?
136    ///
137    /// If so, what was the reason(s) for it?
138    ///
139    /// If empty, there was never any calls.
140    #[cfg_attr(feature = "serde", serde(skip))]
141    pub request_discard_reasons: Vec<RepaintCause>,
142}
143
144impl PlatformOutput {
145    /// This can be used by a text-to-speech system to describe the events (if any).
146    pub fn events_description(&self) -> String {
147        // only describe last event:
148        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    /// Add on new output.
164    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            // egui produces a complete AccessKit tree for each frame,
195            // so overwrite rather than appending.
196            self.accesskit_update = accesskit_update;
197        }
198    }
199
200    /// Take everything ephemeral (everything except `cursor_icon` currently)
201    pub fn take(&mut self) -> Self {
202        let taken = std::mem::take(self);
203        self.cursor_icon = taken.cursor_icon; // everything else is ephemeral
204        taken
205    }
206
207    /// Was [`crate::Context::request_discard`] called?
208    pub fn requested_discard(&self) -> bool {
209        !self.request_discard_reasons.is_empty()
210    }
211}
212
213/// What URL to open, and how.
214///
215/// Use with [`crate::Context::open_url`].
216#[derive(Clone, PartialEq, Eq)]
217#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
218pub struct OpenUrl {
219    pub url: String,
220
221    /// If `true`, open the url in a new tab.
222    /// If `false` open it in the same tab.
223    /// Only matters when in a web browser.
224    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/// Types of attention to request from a user when a native window is not in focus.
246///
247/// See [winit's documentation][user_attention_type] for platform-specific meaning of the attention types.
248///
249/// [user_attention_type]: https://docs.rs/winit/latest/winit/window/enum.UserAttentionType.html
250#[derive(Clone, Copy, Debug, PartialEq, Eq)]
251#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
252pub enum UserAttentionType {
253    /// Request an elevated amount of animations and flair for the window and the task bar or dock icon.
254    Critical,
255
256    /// Request a standard amount of attention-grabbing actions.
257    Informational,
258
259    /// Reset the attention request and interrupt related animations and flashes.
260    Reset,
261}
262
263/// A mouse cursor icon.
264///
265/// egui emits a [`CursorIcon`] in [`PlatformOutput`] each frame as a request to the integration.
266///
267/// Loosely based on <https://developer.mozilla.org/en-US/docs/Web/CSS/cursor>.
268#[derive(Clone, Copy, Debug, PartialEq, Eq)]
269#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
270pub enum CursorIcon {
271    /// Normal cursor icon, whatever that is.
272    Default,
273
274    /// Show no cursor
275    None,
276
277    // ------------------------------------
278    // Links and status:
279    /// A context menu is available
280    ContextMenu,
281
282    /// Question mark
283    Help,
284
285    /// Pointing hand, used for e.g. web links
286    PointingHand,
287
288    /// Shows that processing is being done, but that the program is still interactive.
289    Progress,
290
291    /// Not yet ready, try later.
292    Wait,
293
294    // ------------------------------------
295    // Selection:
296    /// Hover a cell in a table
297    Cell,
298
299    /// For precision work
300    Crosshair,
301
302    /// Text caret, e.g. "Click here to edit text"
303    Text,
304
305    /// Vertical text caret, e.g. "Click here to edit vertical text"
306    VerticalText,
307
308    // ------------------------------------
309    // Drag-and-drop:
310    /// Indicated an alias, e.g. a shortcut
311    Alias,
312
313    /// Indicate that a copy will be made
314    Copy,
315
316    /// Omnidirectional move icon (e.g. arrows in all cardinal directions)
317    Move,
318
319    /// Can't drop here
320    NoDrop,
321
322    /// Forbidden
323    NotAllowed,
324
325    /// The thing you are hovering can be grabbed
326    Grab,
327
328    /// You are grabbing the thing you are hovering
329    Grabbing,
330
331    // ------------------------------------
332    /// Something can be scrolled in any direction (panned).
333    AllScroll,
334
335    // ------------------------------------
336    // Resizing in two directions:
337    /// Horizontal resize `-` to make something wider or more narrow (left to/from right)
338    ResizeHorizontal,
339
340    /// Diagonal resize `/` (right-up to/from left-down)
341    ResizeNeSw,
342
343    /// Diagonal resize `\` (left-up to/from right-down)
344    ResizeNwSe,
345
346    /// Vertical resize `|` (up-down or down-up)
347    ResizeVertical,
348
349    // ------------------------------------
350    // Resizing in one direction:
351    /// Resize something rightwards (e.g. when dragging the right-most edge of something)
352    ResizeEast,
353
354    /// Resize something down and right (e.g. when dragging the bottom-right corner of something)
355    ResizeSouthEast,
356
357    /// Resize something downwards (e.g. when dragging the bottom edge of something)
358    ResizeSouth,
359
360    /// Resize something down and left (e.g. when dragging the bottom-left corner of something)
361    ResizeSouthWest,
362
363    /// Resize something leftwards (e.g. when dragging the left edge of something)
364    ResizeWest,
365
366    /// Resize something up and left (e.g. when dragging the top-left corner of something)
367    ResizeNorthWest,
368
369    /// Resize something up (e.g. when dragging the top edge of something)
370    ResizeNorth,
371
372    /// Resize something up and right (e.g. when dragging the top-right corner of something)
373    ResizeNorthEast,
374
375    // ------------------------------------
376    /// Resize a column
377    ResizeColumn,
378
379    /// Resize a row
380    ResizeRow,
381
382    // ------------------------------------
383    // Zooming:
384    /// Enhance!
385    ZoomIn,
386
387    /// Let's get a better overview
388    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/// Things that happened during this frame that the integration may be interested in.
438///
439/// In particular, these events may be useful for accessibility, i.e. for screen readers.
440#[derive(Clone, PartialEq)]
441#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
442pub enum OutputEvent {
443    /// A widget was clicked.
444    Clicked(WidgetInfo),
445
446    /// A widget was double-clicked.
447    DoubleClicked(WidgetInfo),
448
449    /// A widget was triple-clicked.
450    TripleClicked(WidgetInfo),
451
452    /// A widget gained keyboard focus (by tab key).
453    FocusGained(WidgetInfo),
454
455    /// Text selection was updated.
456    TextSelectionChanged(WidgetInfo),
457
458    /// A widget's value changed.
459    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/// Describes a widget such as a [`crate::Button`] or a [`crate::TextEdit`].
489#[derive(Clone, PartialEq)]
490#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
491pub struct WidgetInfo {
492    /// The type of widget this is.
493    pub typ: WidgetType,
494
495    /// Whether the widget is enabled.
496    pub enabled: bool,
497
498    /// The text on labels, buttons, checkboxes etc.
499    pub label: Option<String>,
500
501    /// The contents of some editable text (for [`TextEdit`](crate::TextEdit) fields).
502    pub current_text_value: Option<String>,
503
504    /// The previous text value.
505    pub prev_text_value: Option<String>,
506
507    /// The current value of checkboxes and radio buttons.
508    pub selected: Option<bool>,
509
510    /// The current value of sliders etc.
511    pub value: Option<f64>,
512
513    /// Selected range of characters in [`Self::current_text_value`].
514    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    /// checkboxes, radio-buttons etc
585    #[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    /// This can be used by a text-to-speech system to describe the widget.
650    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        // TODO(emilk): localization
663        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}