egui/data/
output.rs

1//! All the data egui returns to the backend at the end of each frame.
2
3use crate::{OrderedViewportIdMap, RepaintCause, 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: OrderedViewportIdMap<ViewportOutput>,
36}
37
38impl FullOutput {
39    /// Add on new output.
40    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; // Only paint the latest
54        self.pixels_per_point = pixels_per_point; // Use latest
55
56        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/// Information about text being edited.
70///
71/// Useful for IME.
72#[derive(Copy, Clone, Debug, PartialEq, Eq)]
73#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
74pub struct IMEOutput {
75    /// Where the [`crate::TextEdit`] is located on screen.
76    pub rect: crate::Rect,
77
78    /// Where the primary cursor is.
79    ///
80    /// This is a very thin rectangle.
81    pub cursor_rect: crate::Rect,
82}
83
84/// Commands that the egui integration should execute at the end of a frame.
85///
86/// Commands that are specific to a viewport should be put in [`crate::ViewportCommand`] instead.
87#[derive(Clone, Debug, PartialEq, Eq)]
88#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
89pub enum OutputCommand {
90    /// Put this text to the system clipboard.
91    ///
92    /// This is often a response to [`crate::Event::Copy`] or [`crate::Event::Cut`].
93    CopyText(String),
94
95    /// Put this image to the system clipboard.
96    CopyImage(crate::ColorImage),
97
98    /// Open this url in a browser.
99    OpenUrl(OpenUrl),
100}
101
102/// The non-rendering part of what egui emits each frame.
103///
104/// You can access (and modify) this with [`crate::Context::output`].
105///
106/// The backend should use this.
107#[derive(Default, Clone, PartialEq)]
108#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
109pub struct PlatformOutput {
110    /// Commands that the egui integration should execute at the end of a frame.
111    pub commands: Vec<OutputCommand>,
112
113    /// Set the cursor to this icon.
114    pub cursor_icon: CursorIcon,
115
116    /// Events that may be useful to e.g. a screen reader.
117    pub events: Vec<OutputEvent>,
118
119    /// Is there a mutable [`TextEdit`](crate::TextEdit) under the cursor?
120    /// Use by `eframe` web to show/hide mobile keyboard and IME agent.
121    pub mutable_text_under_cursor: bool,
122
123    /// This is set if, and only if, the user is currently editing text.
124    ///
125    /// Useful for IME.
126    pub ime: Option<IMEOutput>,
127
128    /// The difference in the widget tree since last frame.
129    ///
130    /// NOTE: this needs to be per-viewport.
131    #[cfg(feature = "accesskit")]
132    pub accesskit_update: Option<accesskit::TreeUpdate>,
133
134    /// How many ui passes is this the sum of?
135    ///
136    /// See [`crate::Context::request_discard`] for details.
137    ///
138    /// This is incremented at the END of each frame,
139    /// so this will be `0` for the first pass.
140    pub num_completed_passes: usize,
141
142    /// Was [`crate::Context::request_discard`] called during the latest pass?
143    ///
144    /// If so, what was the reason(s) for it?
145    ///
146    /// If empty, there was never any calls.
147    #[cfg_attr(feature = "serde", serde(skip))]
148    pub request_discard_reasons: Vec<RepaintCause>,
149}
150
151impl PlatformOutput {
152    /// This can be used by a text-to-speech system to describe the events (if any).
153    pub fn events_description(&self) -> String {
154        // only describe last event:
155        if let Some(event) = self.events.iter().next_back() {
156            match event {
157                OutputEvent::Clicked(widget_info)
158                | OutputEvent::DoubleClicked(widget_info)
159                | OutputEvent::TripleClicked(widget_info)
160                | OutputEvent::FocusGained(widget_info)
161                | OutputEvent::TextSelectionChanged(widget_info)
162                | OutputEvent::ValueChanged(widget_info) => {
163                    return widget_info.description();
164                }
165            }
166        }
167        Default::default()
168    }
169
170    /// Add on new output.
171    pub fn append(&mut self, newer: Self) {
172        let Self {
173            mut commands,
174            cursor_icon,
175            mut events,
176            mutable_text_under_cursor,
177            ime,
178            #[cfg(feature = "accesskit")]
179            accesskit_update,
180            num_completed_passes,
181            mut request_discard_reasons,
182        } = newer;
183
184        self.commands.append(&mut commands);
185        self.cursor_icon = cursor_icon;
186        self.events.append(&mut events);
187        self.mutable_text_under_cursor = mutable_text_under_cursor;
188        self.ime = ime.or(self.ime);
189        self.num_completed_passes += num_completed_passes;
190        self.request_discard_reasons
191            .append(&mut request_discard_reasons);
192
193        #[cfg(feature = "accesskit")]
194        {
195            // egui produces a complete AccessKit tree for each frame,
196            // so overwrite rather than appending.
197            self.accesskit_update = accesskit_update;
198        }
199    }
200
201    /// Take everything ephemeral (everything except `cursor_icon` currently)
202    pub fn take(&mut self) -> Self {
203        let taken = std::mem::take(self);
204        self.cursor_icon = taken.cursor_icon; // everything else is ephemeral
205        taken
206    }
207
208    /// Was [`crate::Context::request_discard`] called?
209    pub fn requested_discard(&self) -> bool {
210        !self.request_discard_reasons.is_empty()
211    }
212}
213
214/// What URL to open, and how.
215///
216/// Use with [`crate::Context::open_url`].
217#[derive(Clone, Debug, PartialEq, Eq)]
218#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
219pub struct OpenUrl {
220    pub url: String,
221
222    /// If `true`, open the url in a new tab.
223    /// If `false` open it in the same tab.
224    /// Only matters when in a web browser.
225    pub new_tab: bool,
226}
227
228impl OpenUrl {
229    #[expect(clippy::needless_pass_by_value)]
230    pub fn same_tab(url: impl ToString) -> Self {
231        Self {
232            url: url.to_string(),
233            new_tab: false,
234        }
235    }
236
237    #[expect(clippy::needless_pass_by_value)]
238    pub fn new_tab(url: impl ToString) -> Self {
239        Self {
240            url: url.to_string(),
241            new_tab: true,
242        }
243    }
244}
245
246/// Types of attention to request from a user when a native window is not in focus.
247///
248/// See [winit's documentation][user_attention_type] for platform-specific meaning of the attention types.
249///
250/// [user_attention_type]: https://docs.rs/winit/latest/winit/window/enum.UserAttentionType.html
251#[derive(Clone, Copy, Debug, PartialEq, Eq)]
252#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
253pub enum UserAttentionType {
254    /// Request an elevated amount of animations and flair for the window and the task bar or dock icon.
255    Critical,
256
257    /// Request a standard amount of attention-grabbing actions.
258    Informational,
259
260    /// Reset the attention request and interrupt related animations and flashes.
261    Reset,
262}
263
264/// A mouse cursor icon.
265///
266/// egui emits a [`CursorIcon`] in [`PlatformOutput`] each frame as a request to the integration.
267///
268/// Loosely based on <https://developer.mozilla.org/en-US/docs/Web/CSS/cursor>.
269#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
270#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
271pub enum CursorIcon {
272    /// Normal cursor icon, whatever that is.
273    #[default]
274    Default,
275
276    /// Show no cursor
277    None,
278
279    // ------------------------------------
280    // Links and status:
281    /// A context menu is available
282    ContextMenu,
283
284    /// Question mark
285    Help,
286
287    /// Pointing hand, used for e.g. web links
288    PointingHand,
289
290    /// Shows that processing is being done, but that the program is still interactive.
291    Progress,
292
293    /// Not yet ready, try later.
294    Wait,
295
296    // ------------------------------------
297    // Selection:
298    /// Hover a cell in a table
299    Cell,
300
301    /// For precision work
302    Crosshair,
303
304    /// Text caret, e.g. "Click here to edit text"
305    Text,
306
307    /// Vertical text caret, e.g. "Click here to edit vertical text"
308    VerticalText,
309
310    // ------------------------------------
311    // Drag-and-drop:
312    /// Indicated an alias, e.g. a shortcut
313    Alias,
314
315    /// Indicate that a copy will be made
316    Copy,
317
318    /// Omnidirectional move icon (e.g. arrows in all cardinal directions)
319    Move,
320
321    /// Can't drop here
322    NoDrop,
323
324    /// Forbidden
325    NotAllowed,
326
327    /// The thing you are hovering can be grabbed
328    Grab,
329
330    /// You are grabbing the thing you are hovering
331    Grabbing,
332
333    // ------------------------------------
334    /// Something can be scrolled in any direction (panned).
335    AllScroll,
336
337    // ------------------------------------
338    // Resizing in two directions:
339    /// Horizontal resize `-` to make something wider or more narrow (left to/from right)
340    ResizeHorizontal,
341
342    /// Diagonal resize `/` (right-up to/from left-down)
343    ResizeNeSw,
344
345    /// Diagonal resize `\` (left-up to/from right-down)
346    ResizeNwSe,
347
348    /// Vertical resize `|` (up-down or down-up)
349    ResizeVertical,
350
351    // ------------------------------------
352    // Resizing in one direction:
353    /// Resize something rightwards (e.g. when dragging the right-most edge of something)
354    ResizeEast,
355
356    /// Resize something down and right (e.g. when dragging the bottom-right corner of something)
357    ResizeSouthEast,
358
359    /// Resize something downwards (e.g. when dragging the bottom edge of something)
360    ResizeSouth,
361
362    /// Resize something down and left (e.g. when dragging the bottom-left corner of something)
363    ResizeSouthWest,
364
365    /// Resize something leftwards (e.g. when dragging the left edge of something)
366    ResizeWest,
367
368    /// Resize something up and left (e.g. when dragging the top-left corner of something)
369    ResizeNorthWest,
370
371    /// Resize something up (e.g. when dragging the top edge of something)
372    ResizeNorth,
373
374    /// Resize something up and right (e.g. when dragging the top-right corner of something)
375    ResizeNorthEast,
376
377    // ------------------------------------
378    /// Resize a column
379    ResizeColumn,
380
381    /// Resize a row
382    ResizeRow,
383
384    // ------------------------------------
385    // Zooming:
386    /// Enhance!
387    ZoomIn,
388
389    /// Let's get a better overview
390    ZoomOut,
391}
392
393impl CursorIcon {
394    pub const ALL: [Self; 35] = [
395        Self::Default,
396        Self::None,
397        Self::ContextMenu,
398        Self::Help,
399        Self::PointingHand,
400        Self::Progress,
401        Self::Wait,
402        Self::Cell,
403        Self::Crosshair,
404        Self::Text,
405        Self::VerticalText,
406        Self::Alias,
407        Self::Copy,
408        Self::Move,
409        Self::NoDrop,
410        Self::NotAllowed,
411        Self::Grab,
412        Self::Grabbing,
413        Self::AllScroll,
414        Self::ResizeHorizontal,
415        Self::ResizeNeSw,
416        Self::ResizeNwSe,
417        Self::ResizeVertical,
418        Self::ResizeEast,
419        Self::ResizeSouthEast,
420        Self::ResizeSouth,
421        Self::ResizeSouthWest,
422        Self::ResizeWest,
423        Self::ResizeNorthWest,
424        Self::ResizeNorth,
425        Self::ResizeNorthEast,
426        Self::ResizeColumn,
427        Self::ResizeRow,
428        Self::ZoomIn,
429        Self::ZoomOut,
430    ];
431}
432
433/// Things that happened during this frame that the integration may be interested in.
434///
435/// In particular, these events may be useful for accessibility, i.e. for screen readers.
436#[derive(Clone, PartialEq)]
437#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
438pub enum OutputEvent {
439    /// A widget was clicked.
440    Clicked(WidgetInfo),
441
442    /// A widget was double-clicked.
443    DoubleClicked(WidgetInfo),
444
445    /// A widget was triple-clicked.
446    TripleClicked(WidgetInfo),
447
448    /// A widget gained keyboard focus (by tab key).
449    FocusGained(WidgetInfo),
450
451    /// Text selection was updated.
452    TextSelectionChanged(WidgetInfo),
453
454    /// A widget's value changed.
455    ValueChanged(WidgetInfo),
456}
457
458impl OutputEvent {
459    pub fn widget_info(&self) -> &WidgetInfo {
460        match self {
461            Self::Clicked(info)
462            | Self::DoubleClicked(info)
463            | Self::TripleClicked(info)
464            | Self::FocusGained(info)
465            | Self::TextSelectionChanged(info)
466            | Self::ValueChanged(info) => info,
467        }
468    }
469}
470
471impl std::fmt::Debug for OutputEvent {
472    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
473        match self {
474            Self::Clicked(wi) => write!(f, "Clicked({wi:?})"),
475            Self::DoubleClicked(wi) => write!(f, "DoubleClicked({wi:?})"),
476            Self::TripleClicked(wi) => write!(f, "TripleClicked({wi:?})"),
477            Self::FocusGained(wi) => write!(f, "FocusGained({wi:?})"),
478            Self::TextSelectionChanged(wi) => write!(f, "TextSelectionChanged({wi:?})"),
479            Self::ValueChanged(wi) => write!(f, "ValueChanged({wi:?})"),
480        }
481    }
482}
483
484/// Describes a widget such as a [`crate::Button`] or a [`crate::TextEdit`].
485#[derive(Clone, PartialEq)]
486#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
487pub struct WidgetInfo {
488    /// The type of widget this is.
489    pub typ: WidgetType,
490
491    /// Whether the widget is enabled.
492    pub enabled: bool,
493
494    /// The text on labels, buttons, checkboxes etc.
495    pub label: Option<String>,
496
497    /// The contents of some editable text (for [`TextEdit`](crate::TextEdit) fields).
498    pub current_text_value: Option<String>,
499
500    /// The previous text value.
501    pub prev_text_value: Option<String>,
502
503    /// The current value of checkboxes and radio buttons.
504    pub selected: Option<bool>,
505
506    /// The current value of sliders etc.
507    pub value: Option<f64>,
508
509    /// Selected range of characters in [`Self::current_text_value`].
510    pub text_selection: Option<std::ops::RangeInclusive<usize>>,
511
512    /// The hint text for text edit fields.
513    pub hint_text: Option<String>,
514}
515
516impl std::fmt::Debug for WidgetInfo {
517    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
518        let Self {
519            typ,
520            enabled,
521            label,
522            current_text_value: text_value,
523            prev_text_value,
524            selected,
525            value,
526            text_selection,
527            hint_text,
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        if let Some(hint_text) = hint_text {
557            s.field("hint_text", hint_text);
558        }
559
560        s.finish()
561    }
562}
563
564impl WidgetInfo {
565    pub fn new(typ: WidgetType) -> Self {
566        Self {
567            typ,
568            enabled: true,
569            label: None,
570            current_text_value: None,
571            prev_text_value: None,
572            selected: None,
573            value: None,
574            text_selection: None,
575            hint_text: None,
576        }
577    }
578
579    #[expect(clippy::needless_pass_by_value)]
580    pub fn labeled(typ: WidgetType, enabled: bool, label: impl ToString) -> Self {
581        Self {
582            enabled,
583            label: Some(label.to_string()),
584            ..Self::new(typ)
585        }
586    }
587
588    /// checkboxes, radio-buttons etc
589    #[expect(clippy::needless_pass_by_value)]
590    pub fn selected(typ: WidgetType, enabled: bool, selected: bool, label: impl ToString) -> Self {
591        Self {
592            enabled,
593            label: Some(label.to_string()),
594            selected: Some(selected),
595            ..Self::new(typ)
596        }
597    }
598
599    pub fn drag_value(enabled: bool, value: f64) -> Self {
600        Self {
601            enabled,
602            value: Some(value),
603            ..Self::new(WidgetType::DragValue)
604        }
605    }
606
607    #[expect(clippy::needless_pass_by_value)]
608    pub fn slider(enabled: bool, value: f64, label: impl ToString) -> Self {
609        let label = label.to_string();
610        Self {
611            enabled,
612            label: if label.is_empty() { None } else { Some(label) },
613            value: Some(value),
614            ..Self::new(WidgetType::Slider)
615        }
616    }
617
618    #[expect(clippy::needless_pass_by_value)]
619    pub fn text_edit(
620        enabled: bool,
621        prev_text_value: impl ToString,
622        text_value: impl ToString,
623        hint_text: impl ToString,
624    ) -> Self {
625        let text_value = text_value.to_string();
626        let prev_text_value = prev_text_value.to_string();
627        let hint_text = hint_text.to_string();
628        let prev_text_value = if text_value == prev_text_value {
629            None
630        } else {
631            Some(prev_text_value)
632        };
633        Self {
634            enabled,
635            current_text_value: Some(text_value),
636            prev_text_value,
637            hint_text: Some(hint_text),
638            ..Self::new(WidgetType::TextEdit)
639        }
640    }
641
642    #[expect(clippy::needless_pass_by_value)]
643    pub fn text_selection_changed(
644        enabled: bool,
645        text_selection: std::ops::RangeInclusive<usize>,
646        current_text_value: impl ToString,
647    ) -> Self {
648        Self {
649            enabled,
650            text_selection: Some(text_selection),
651            current_text_value: Some(current_text_value.to_string()),
652            ..Self::new(WidgetType::TextEdit)
653        }
654    }
655
656    /// This can be used by a text-to-speech system to describe the widget.
657    pub fn description(&self) -> String {
658        let Self {
659            typ,
660            enabled,
661            label,
662            current_text_value: text_value,
663            prev_text_value: _,
664            selected,
665            value,
666            text_selection: _,
667            hint_text: _,
668        } = self;
669
670        // TODO(emilk): localization
671        let widget_type = match typ {
672            WidgetType::Link => "link",
673            WidgetType::TextEdit => "text edit",
674            WidgetType::Button => "button",
675            WidgetType::Checkbox => "checkbox",
676            WidgetType::RadioButton => "radio",
677            WidgetType::RadioGroup => "radio group",
678            WidgetType::SelectableLabel => "selectable",
679            WidgetType::ComboBox => "combo",
680            WidgetType::Slider => "slider",
681            WidgetType::DragValue => "drag value",
682            WidgetType::ColorButton => "color button",
683            WidgetType::Image => "image",
684            WidgetType::CollapsingHeader => "collapsing header",
685            WidgetType::Panel => "panel",
686            WidgetType::ProgressIndicator => "progress indicator",
687            WidgetType::Window => "window",
688            WidgetType::Label | WidgetType::Other => "",
689        };
690
691        let mut description = widget_type.to_owned();
692
693        if let Some(selected) = selected {
694            if *typ == WidgetType::Checkbox {
695                let state = if *selected { "checked" } else { "unchecked" };
696                description = format!("{state} {description}");
697            } else {
698                description += if *selected { "selected" } else { "" };
699            }
700        }
701
702        if let Some(label) = label {
703            description = format!("{label}: {description}");
704        }
705
706        if typ == &WidgetType::TextEdit {
707            let text = if let Some(text_value) = text_value {
708                if text_value.is_empty() {
709                    "blank".into()
710                } else {
711                    text_value.clone()
712                }
713            } else {
714                "blank".into()
715            };
716            description = format!("{text}: {description}");
717        }
718
719        if let Some(value) = value {
720            description += " ";
721            description += &value.to_string();
722        }
723
724        if !enabled {
725            description += ": disabled";
726        }
727        description.trim().to_owned()
728    }
729}