bevy_egui/
output.rs

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