bevy_egui/
output.rs

1use crate::{
2    helpers, input::WindowToEguiContextMap, EguiContext, EguiContextSettings, EguiFullOutput,
3    EguiGlobalSettings, EguiOutput, EguiRenderOutput,
4};
5use bevy_ecs::{
6    entity::Entity,
7    event::EventWriter,
8    system::{Commands, Local, Query, Res},
9};
10use bevy_platform::collections::HashMap;
11use bevy_window::RequestRedraw;
12use bevy_winit::cursor::CursorIcon;
13
14/// Reads Egui output.
15#[allow(clippy::too_many_arguments)]
16pub fn process_output_system(
17    mut commands: Commands,
18    mut context_query: Query<(
19        Entity,
20        &mut EguiContext,
21        &mut EguiFullOutput,
22        &mut EguiRenderOutput,
23        &mut EguiOutput,
24        &EguiContextSettings,
25    )>,
26    #[cfg(all(feature = "manage_clipboard", not(target_os = "android")))]
27    mut egui_clipboard: bevy_ecs::system::ResMut<crate::EguiClipboard>,
28    mut event: EventWriter<RequestRedraw>,
29    mut last_cursor_icon: Local<HashMap<Entity, egui::CursorIcon>>,
30    egui_global_settings: Res<EguiGlobalSettings>,
31    window_to_egui_context_map: Res<WindowToEguiContextMap>,
32) {
33    let mut should_request_redraw = false;
34
35    for (entity, mut context, mut full_output, mut render_output, mut egui_output, settings) in
36        context_query.iter_mut()
37    {
38        let ctx = context.get_mut();
39        let Some(full_output) = full_output.0.take() else {
40            bevy_log::error!("bevy_egui pass output has not been prepared (if EguiSettings::run_manually is set to true, make sure to call egui::Context::run or egui::Context::begin_pass and egui::Context::end_pass)");
41            continue;
42        };
43        let egui::FullOutput {
44            platform_output,
45            shapes,
46            textures_delta,
47            pixels_per_point,
48            viewport_output: _,
49        } = full_output;
50        let paint_jobs = ctx.tessellate(shapes, pixels_per_point);
51
52        render_output.paint_jobs = paint_jobs;
53        render_output.textures_delta = textures_delta;
54        egui_output.platform_output = platform_output;
55
56        for command in &egui_output.platform_output.commands {
57            match command {
58                egui::OutputCommand::CopyText(_text) =>
59                {
60                    #[cfg(all(feature = "manage_clipboard", not(target_os = "android")))]
61                    if !_text.is_empty() {
62                        egui_clipboard.set_text(_text);
63                    }
64                }
65                egui::OutputCommand::CopyImage(_image) => {
66                    #[cfg(all(feature = "manage_clipboard", not(target_os = "android")))]
67                    egui_clipboard.set_image(_image);
68                }
69                egui::OutputCommand::OpenUrl(_url) => {
70                    #[cfg(feature = "open_url")]
71                    {
72                        let egui::output::OpenUrl { url, new_tab } = _url;
73                        let target = if *new_tab {
74                            "_blank"
75                        } else {
76                            settings
77                                .default_open_url_target
78                                .as_deref()
79                                .unwrap_or("_self")
80                        };
81                        if let Err(err) = webbrowser::open_browser_with_options(
82                            webbrowser::Browser::Default,
83                            url,
84                            webbrowser::BrowserOptions::new().with_target_hint(target),
85                        ) {
86                            bevy_log::error!("Failed to open '{}': {:?}", url, err);
87                        }
88                    }
89                }
90            }
91        }
92
93        if egui_global_settings.enable_cursor_icon_updates && settings.enable_cursor_icon_updates {
94            if let Some(window_entity) = window_to_egui_context_map.context_to_window.get(&entity) {
95                let last_cursor_icon = last_cursor_icon.entry(entity).or_default();
96                if *last_cursor_icon != egui_output.platform_output.cursor_icon {
97                    commands.entity(*window_entity).insert(CursorIcon::System(
98                        helpers::egui_to_winit_cursor_icon(egui_output.platform_output.cursor_icon)
99                            .unwrap_or(bevy_window::SystemCursorIcon::Default),
100                    ));
101                    *last_cursor_icon = egui_output.platform_output.cursor_icon;
102                }
103            }
104        }
105
106        let needs_repaint = !render_output.is_empty();
107        should_request_redraw |= ctx.has_requested_repaint() && needs_repaint;
108    }
109
110    if should_request_redraw {
111        event.write(RequestRedraw);
112    }
113}