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