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