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#[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}