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