1use anyhow::Error;
6use carnelian::color::Color;
7use carnelian::drawing::{FontFace, load_font, measure_text_width};
8use carnelian::scene::facets::{
9 FacetId, SetColorMessage, SetSizeMessage, SetTextMessage, TextFacetOptions,
10 TextHorizontalAlignment, TextVerticalAlignment,
11};
12use carnelian::scene::layout::{Alignment, Stack, StackOptions};
13use carnelian::scene::scene::{Scene, SceneBuilder};
14use carnelian::{AppSender, MessageTarget, Point, Size, ViewKey, make_message};
15use euclid::size2;
16use fuchsia_async as fasync;
17use fuchsia_sync::Mutex;
18use futures::StreamExt;
19use futures::channel::mpsc::{Sender, channel as pipe};
20use std::path::PathBuf;
21use std::sync::Arc;
22use std::sync::atomic::{AtomicU32, Ordering};
23use zx::MonotonicDuration;
24
25const PROGRESS_GRANULARITY: f32 = 1000.0;
26
27pub enum ProgressBarConfig {
29 Text(ProgressBarText),
30 Size(Size),
31 TextWithSize(ProgressBarText, Size),
32}
33
34pub enum ProgressBarMessages {
36 SetProgress(f32),
37 SetProgressSmooth(
38 f32,
39 MonotonicDuration,
40 ),
41 SetInternalProgress(f32),
42 SetProgressBarText(String),
43}
44
45pub struct ProgressBar {
46 label: Option<FacetId>,
47 progress_bar_size: Size,
48 progress_rectangle: FacetId,
49 background_rectangle: FacetId,
50 app_sender: AppSender,
51 current_progress: Arc<AtomicU32>,
53 final_progress: Arc<AtomicU32>,
55 task_running_mutex: Arc<Mutex<bool>>,
57 task_sender: Option<Sender<(u32, i64)>>,
59}
60
61#[derive(Clone)]
62pub struct ProgressBarText {
63 pub text: String,
64 pub font_size: f32,
65 pub padding: f32,
66 pub alignment: TextHorizontalAlignment,
67}
68
69impl ProgressBar {
70 pub fn new(
71 config: ProgressBarConfig,
72 builder: &mut SceneBuilder,
73 app_sender: AppSender,
74 ) -> Result<ProgressBar, Error> {
75 let options =
76 StackOptions { alignment: Alignment::center_left(), ..StackOptions::default() };
77
78 builder.start_group("progress_bar", Stack::with_options_ptr(options));
80
81 let (label, progress_bar_size) = build_progress_text(config, builder)?;
82 let progress_rectangle = builder.rectangle(
83 size2(0.0, progress_bar_size.height),
84 Color { r: 0x0b, g: 0x57, b: 0xd0, a: 0xff },
85 );
86 let background_rectangle =
87 builder.rectangle(progress_bar_size, Color { r: 0xbe, g: 0xd5, b: 0xfc, a: 0xff });
88 builder.end_group();
89
90 Ok(ProgressBar {
91 label,
92 progress_bar_size,
93 progress_rectangle,
94 background_rectangle,
95 app_sender,
96 current_progress: Arc::new(AtomicU32::new(0)),
97 final_progress: Arc::new(AtomicU32::new(0)),
98 task_running_mutex: Arc::new(Mutex::new(false)),
99 task_sender: None,
100 })
101 }
102
103 pub fn set_percent(&mut self, scene: &mut Scene, percent_complete: f32) {
105 self.set_progress(scene, percent_complete / 100.0);
106 }
107
108 pub fn set_progress(&mut self, scene: &mut Scene, progress: f32) {
110 self.final_progress.store((progress * PROGRESS_GRANULARITY) as u32, Ordering::Release);
111 self.current_progress.store((progress * PROGRESS_GRANULARITY) as u32, Ordering::Release);
112 self.set_internal_progress(scene, progress);
113 }
114
115 pub fn set_internal_progress(&mut self, scene: &mut Scene, progress: f32) {
117 let progress = num_traits::clamp(progress, 0.0, 1.0);
118 let width = self.progress_bar_size.width;
119 let height = self.progress_bar_size.height;
120 let filled = width * progress;
121 scene.send_message(
122 &self.progress_rectangle,
123 Box::new(SetSizeMessage { size: size2(filled, height) }),
124 );
125 }
126
127 pub fn set_percent_smooth(&mut self, view_key: ViewKey, percent_complete: f32) {
128 self.set_progress_smooth(
129 view_key,
130 percent_complete / 100.0,
131 MonotonicDuration::from_seconds(1),
132 );
133 }
134
135 pub fn set_progress_smooth(
137 &mut self,
138 view_key: ViewKey,
139 progress: f32,
140 elapsed_time: MonotonicDuration,
141 ) {
142 let progress = (progress * PROGRESS_GRANULARITY) as u32;
143 let current_progress = self.current_progress.load(Ordering::Acquire);
144 let step_time_ms = (elapsed_time.into_millis()
145 / (current_progress as i32 - progress as i32).abs() as i64)
146 as i64;
147 let mut running = self.task_running_mutex.lock();
148 if !*running {
149 *running = true;
150 let app_sender = self.app_sender.clone();
151 let current_progress = self.current_progress.clone();
152 let final_progress = self.final_progress.clone();
153 let (tx, mut rx) = pipe::<(u32, i64)>(1);
154 self.task_sender = Some(tx);
155 let f = async move {
156 let mut sleep_time = MonotonicDuration::from_millis(step_time_ms);
157 loop {
158 let current = current_progress.load(Ordering::Acquire);
159
160 let end = final_progress.load(Ordering::Acquire);
161 let difference = end as i64 - current as i64;
162 if difference == 0 {
163 let (progress, step_time_ms) = rx.next().await.unwrap();
164 sleep_time = MonotonicDuration::from_millis(step_time_ms);
165 final_progress.store(progress as u32, Ordering::Release);
166 } else {
167 match rx.try_recv() {
168 Ok((progress, step_time_ms)) => {
169 sleep_time = MonotonicDuration::from_millis(step_time_ms);
170 final_progress.store(progress as u32, Ordering::Release);
171 }
172 Err(_) => { }
174 }
175 if difference > 0 {
176 current_progress.fetch_add(1, Ordering::AcqRel);
177 } else {
178 current_progress.fetch_sub(1, Ordering::AcqRel);
179 }
180 app_sender.queue_message(
181 MessageTarget::View(view_key),
182 make_message(ProgressBarMessages::SetInternalProgress(
183 current_progress.load(Ordering::Acquire) as f32
184 / PROGRESS_GRANULARITY,
185 )),
186 );
187 fuchsia_async::Timer::new(sleep_time).await;
188 }
189 }
190 };
191 fasync::Task::local(f).detach();
192 }
193 let _ = self.task_sender.as_mut().unwrap().start_send((progress, step_time_ms));
194 }
195
196 pub fn set_color(&mut self, scene: &mut Scene, fg_color: Color, bg_color: Color) {
197 scene.send_message(&self.progress_rectangle, Box::new(SetColorMessage { color: fg_color }));
198 scene.send_message(
199 &self.background_rectangle,
200 Box::new(SetColorMessage { color: bg_color }),
201 );
202 }
203
204 pub fn set_text(&mut self, scene: &mut Scene, text: String) {
205 if let Some(label) = self.label.as_ref() {
206 scene.send_message(label, Box::new(SetTextMessage { text }));
207 }
208 }
209
210 pub fn set_text_color(&mut self, scene: &mut Scene, color: Color) {
211 if let Some(label) = self.label.as_ref() {
212 scene.send_message(label, Box::new(SetColorMessage { color }));
213 }
214 }
215
216 pub fn handle_message(
217 &mut self,
218 scene: &mut Scene,
219 view_key: ViewKey,
220 message: ProgressBarMessages,
221 ) {
222 match message {
223 ProgressBarMessages::SetProgress(progress) => {
224 self.set_progress(scene, progress);
225 }
226 ProgressBarMessages::SetProgressSmooth(progress, step_time_ms) => {
227 self.set_progress_smooth(view_key, progress, step_time_ms);
228 }
229 ProgressBarMessages::SetInternalProgress(progress) => {
230 self.set_internal_progress(scene, progress);
231 }
232 ProgressBarMessages::SetProgressBarText(text) => {
233 self.set_text(scene, text);
234 }
235 }
236 }
237}
238
239fn build_text_facet(
240 builder: &mut SceneBuilder,
241 progress_bar_text: &ProgressBarText,
242 face: FontFace,
243) -> Result<FacetId, Error> {
244 let label = builder.text(
245 face,
246 &progress_bar_text.text,
247 progress_bar_text.font_size,
248 Point::zero(),
249 TextFacetOptions {
250 color: Color::blue(),
251 horizontal_alignment: progress_bar_text.alignment,
252 vertical_alignment: TextVerticalAlignment::Top,
253 ..TextFacetOptions::default()
254 },
255 );
256 Ok(label)
257}
258
259fn build_progress_text(
260 config: ProgressBarConfig,
261 builder: &mut SceneBuilder,
262) -> Result<(Option<FacetId>, Size), Error> {
263 Ok(match config {
264 ProgressBarConfig::Text(progress_bar_text) => {
265 let face = load_font(PathBuf::from("/pkg/data/fonts/Roboto-Regular.ttf"))?;
266 let label_width =
267 measure_text_width(&face, progress_bar_text.font_size, &progress_bar_text.text);
268 let width = label_width + progress_bar_text.padding * 2.0;
269 let height = progress_bar_text.font_size + progress_bar_text.padding * 2.0;
270 let size = size2(width, height);
271 let label = build_text_facet(builder, &progress_bar_text, face)?;
272 (Some(label), size)
273 }
274 ProgressBarConfig::TextWithSize(progress_bar_text, size) => {
275 let face = load_font(PathBuf::from("/pkg/data/fonts/Roboto-Regular.ttf"))?;
276 let label = build_text_facet(builder, &progress_bar_text, face)?;
277 (Some(label), size)
278 }
279 ProgressBarConfig::Size(size) => (None, size),
280 })
281}