1use fyrox_core::algebra::{Matrix3, Vector2};
22use fyrox_core::Uuid;
23use fyrox_resource::untyped::ResourceKind;
24use fyrox_ui::button::Button;
25use fyrox_ui::image::Image;
26use fyrox_ui::scroll_viewer::ScrollViewer;
27use fyrox_ui::text::Text;
28use fyrox_ui::window::{Window, WindowAlignment};
29use fyrox_ui::{
30 border::BorderBuilder,
31 button::{ButtonBuilder, ButtonMessage},
32 core::{parking_lot::Mutex, pool::Handle, SafeLock},
33 grid::{Column, GridBuilder, Row},
34 image::ImageBuilder,
35 message::UiMessage,
36 scroll_viewer::{ScrollViewerBuilder, ScrollViewerMessage},
37 stack_panel::StackPanelBuilder,
38 style::{resource::StyleResourceExt, Style},
39 text::{TextBuilder, TextMessage},
40 texture::{
41 TextureImportOptions, TextureMagnificationFilter, TextureMinificationFilter,
42 TextureResource, TextureResourceExtension,
43 },
44 widget::{WidgetBuilder, WidgetMessage},
45 window::{WindowBuilder, WindowMessage, WindowTitle},
46 BuildContext, HorizontalAlignment, Orientation, Thickness, UserInterface,
47};
48use std::{
49 io::{BufRead, BufReader, Read},
50 process::{ChildStderr, ChildStdout},
51 sync::{
52 atomic::{AtomicBool, Ordering},
53 Arc,
54 },
55};
56
57pub struct BuildWindow {
58 window: Handle<Window>,
59 active: Arc<AtomicBool>,
60 changed: Arc<AtomicBool>,
61 log: Arc<Mutex<String>>,
62 log_text: Handle<Text>,
63 stop: Handle<Button>,
64 scroll_viewer: Handle<ScrollViewer>,
65 progress_indicator: Handle<Image>,
66 angle: f32,
67}
68
69impl Drop for BuildWindow {
70 fn drop(&mut self) {
71 self.active.store(false, Ordering::SeqCst);
73 }
74}
75
76impl BuildWindow {
77 pub fn new(project_name: &str, ctx: &mut BuildContext) -> Self {
78 let progress_image = TextureResource::load_from_memory(
79 Uuid::new_v4(),
80 ResourceKind::Embedded,
81 include_bytes!("resources/progress.png"),
82 TextureImportOptions::default()
83 .with_minification_filter(TextureMinificationFilter::LinearMipMapLinear)
84 .with_magnification_filter(TextureMagnificationFilter::Linear)
85 .with_lod_bias(-1.0),
86 )
87 .ok();
88
89 let log_text;
90 let stop;
91 let scroll_viewer;
92 let progress_indicator;
93 let window = WindowBuilder::new(WidgetBuilder::new().with_width(420.0).with_height(200.0))
94 .can_minimize(false)
95 .can_close(false)
96 .can_maximize(false)
97 .open(false)
98 .with_content(
99 GridBuilder::new(
100 WidgetBuilder::new()
101 .with_child(
102 GridBuilder::new(
103 WidgetBuilder::new()
104 .with_child(
105 TextBuilder::new(
106 WidgetBuilder::new()
107 .with_margin(Thickness {
108 left: 5.0,
109 top: 1.0,
110 right: 1.0,
111 bottom: 1.0,
112 })
113 .on_column(0),
114 )
115 .with_text(format!(
116 "Please wait while {project_name} is building..."
117 ))
118 .build(ctx),
119 )
120 .with_child({
121 progress_indicator = ImageBuilder::new(
122 WidgetBuilder::new()
123 .with_width(16.0)
124 .with_height(16.0)
125 .on_column(1)
126 .with_margin(Thickness {
127 left: 1.0,
128 top: 1.0,
129 right: 4.0,
130 bottom: 1.0,
131 })
132 .with_clip_to_bounds(false),
133 )
134 .with_opt_texture(progress_image)
135 .build(ctx);
136 progress_indicator
137 }),
138 )
139 .add_row(Row::stretch())
140 .add_column(Column::stretch())
141 .add_column(Column::auto())
142 .build(ctx),
143 )
144 .with_child(
145 BorderBuilder::new(
146 WidgetBuilder::new()
147 .on_row(1)
148 .with_margin(Thickness::uniform(2.0))
149 .with_background(ctx.style.property(Style::BRUSH_DARKEST))
150 .with_child({
151 scroll_viewer =
152 ScrollViewerBuilder::new(WidgetBuilder::new())
153 .with_content({
154 log_text =
155 TextBuilder::new(WidgetBuilder::new())
156 .build(ctx);
157 log_text
158 })
159 .build(ctx);
160 scroll_viewer
161 }),
162 )
163 .build(ctx),
164 )
165 .with_child(
166 StackPanelBuilder::new(
167 WidgetBuilder::new()
168 .with_horizontal_alignment(HorizontalAlignment::Right)
169 .on_row(2)
170 .with_child({
171 stop = ButtonBuilder::new(
172 WidgetBuilder::new()
173 .with_width(100.0)
174 .with_margin(Thickness::uniform(4.0)),
175 )
176 .with_text("Stop")
177 .build(ctx);
178 stop
179 }),
180 )
181 .with_orientation(Orientation::Horizontal)
182 .build(ctx),
183 ),
184 )
185 .add_row(Row::auto())
186 .add_row(Row::stretch())
187 .add_row(Row::strict(28.0))
188 .add_column(Column::stretch())
189 .build(ctx),
190 )
191 .with_title(WindowTitle::text(format!("Building {project_name}...")))
192 .build(ctx);
193
194 Self {
195 window,
196 log_text,
197 log: Arc::new(Default::default()),
198 active: Arc::new(AtomicBool::new(false)),
199 changed: Arc::new(AtomicBool::new(false)),
200 stop,
201 scroll_viewer,
202 progress_indicator,
203 angle: 0.0,
204 }
205 }
206
207 pub fn listen(&mut self, (stderr, stdout): (ChildStderr, ChildStdout), ui: &UserInterface) {
208 let log = self.log.clone();
209 self.active.store(true, Ordering::SeqCst);
210 let reader_active = self.active.clone();
211 let log_changed = self.changed.clone();
212
213 Self::spawn_pipe_pump(stderr, &reader_active, &log_changed, &log);
214 Self::spawn_pipe_pump(stdout, &reader_active, &log_changed, &log);
215
216 ui.send(
217 self.window,
218 WindowMessage::Open {
219 alignment: WindowAlignment::Center,
220 modal: true,
221 focus_content: true,
222 },
223 );
224 }
225
226 fn spawn_pipe_pump(
227 mut pipe: impl Read + Send + 'static,
228 reader_active: &Arc<AtomicBool>,
229 log_changed: &Arc<AtomicBool>,
230 log: &Arc<Mutex<String>>,
231 ) {
232 let reader_active = reader_active.clone();
233 let log_changed = log_changed.clone();
234 let log = log.clone();
235 std::thread::spawn(move || {
236 let pipe: &mut dyn BufRead = &mut BufReader::new(&mut pipe);
237 while reader_active.load(Ordering::SeqCst) {
238 for line in pipe.lines().take(10).flatten() {
239 let mut log_guard = log.safe_lock();
240 log_guard.push_str(&line);
241 log_guard.push('\n');
242 log_changed.store(true, Ordering::SeqCst);
243 }
244 }
245 });
246 }
247
248 pub fn reset(&mut self, ui: &UserInterface) {
249 self.active.store(false, Ordering::SeqCst);
250 self.changed.store(false, Ordering::SeqCst);
251 self.log.safe_lock().clear();
252 ui.send(self.log_text, TextMessage::Text(Default::default()));
253 }
254
255 pub fn destroy(mut self, ui: &UserInterface) {
256 self.reset(ui);
257 ui.send(self.window, WindowMessage::Close);
258 }
259
260 pub fn update(&mut self, ui: &UserInterface, dt: f32) {
261 if self.changed.load(Ordering::SeqCst) {
262 ui.send(
263 self.log_text,
264 TextMessage::Text(self.log.safe_lock().clone()),
265 );
266 ui.send(self.scroll_viewer, ScrollViewerMessage::ScrollToEnd);
267
268 self.changed.store(false, Ordering::SeqCst);
269 }
270
271 self.angle += 10.0 * dt;
272 ui.send(
273 self.progress_indicator,
274 WidgetMessage::RenderTransform(
275 Matrix3::new_translation(&Vector2::new(8.0, 8.0))
276 * Matrix3::new_rotation(self.angle)
277 * Matrix3::new_translation(&Vector2::new(-8.0, -8.0)),
278 ),
279 );
280 }
281
282 pub fn handle_ui_message(
283 self,
284 message: &UiMessage,
285 ui: &UserInterface,
286 on_stop: impl FnOnce(),
287 ) -> Option<Self> {
288 if let Some(ButtonMessage::Click) = message.data() {
289 if message.destination() == self.stop {
290 on_stop();
291 self.destroy(ui);
292 return None;
293 }
294 }
295 Some(self)
296 }
297}