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_next() {
168 Ok(value) => {
169 if let Some((progress, step_time_ms)) = value {
170 sleep_time = MonotonicDuration::from_millis(step_time_ms);
171 final_progress.store(progress as u32, Ordering::Release);
172 }
173 }
174 Err(_) => { }
176 }
177 if difference > 0 {
178 current_progress.fetch_add(1, Ordering::AcqRel);
179 } else {
180 current_progress.fetch_sub(1, Ordering::AcqRel);
181 }
182 app_sender.queue_message(
183 MessageTarget::View(view_key),
184 make_message(ProgressBarMessages::SetInternalProgress(
185 current_progress.load(Ordering::Acquire) as f32
186 / PROGRESS_GRANULARITY,
187 )),
188 );
189 fuchsia_async::Timer::new(sleep_time).await;
190 }
191 }
192 };
193 fasync::Task::local(f).detach();
194 }
195 let _ = self.task_sender.as_mut().unwrap().start_send((progress, step_time_ms));
196 }
197
198 pub fn set_color(&mut self, scene: &mut Scene, fg_color: Color, bg_color: Color) {
199 scene.send_message(&self.progress_rectangle, Box::new(SetColorMessage { color: fg_color }));
200 scene.send_message(
201 &self.background_rectangle,
202 Box::new(SetColorMessage { color: bg_color }),
203 );
204 }
205
206 pub fn set_text(&mut self, scene: &mut Scene, text: String) {
207 if let Some(label) = self.label.as_ref() {
208 scene.send_message(label, Box::new(SetTextMessage { text }));
209 }
210 }
211
212 pub fn set_text_color(&mut self, scene: &mut Scene, color: Color) {
213 if let Some(label) = self.label.as_ref() {
214 scene.send_message(label, Box::new(SetColorMessage { color }));
215 }
216 }
217
218 pub fn handle_message(
219 &mut self,
220 scene: &mut Scene,
221 view_key: ViewKey,
222 message: ProgressBarMessages,
223 ) {
224 match message {
225 ProgressBarMessages::SetProgress(progress) => {
226 self.set_progress(scene, progress);
227 }
228 ProgressBarMessages::SetProgressSmooth(progress, step_time_ms) => {
229 self.set_progress_smooth(view_key, progress, step_time_ms);
230 }
231 ProgressBarMessages::SetInternalProgress(progress) => {
232 self.set_internal_progress(scene, progress);
233 }
234 ProgressBarMessages::SetProgressBarText(text) => {
235 self.set_text(scene, text);
236 }
237 }
238 }
239}
240
241fn build_text_facet(
242 builder: &mut SceneBuilder,
243 progress_bar_text: &ProgressBarText,
244 face: FontFace,
245) -> Result<FacetId, Error> {
246 let label = builder.text(
247 face,
248 &progress_bar_text.text,
249 progress_bar_text.font_size,
250 Point::zero(),
251 TextFacetOptions {
252 color: Color::blue(),
253 horizontal_alignment: progress_bar_text.alignment,
254 vertical_alignment: TextVerticalAlignment::Top,
255 ..TextFacetOptions::default()
256 },
257 );
258 Ok(label)
259}
260
261fn build_progress_text(
262 config: ProgressBarConfig,
263 builder: &mut SceneBuilder,
264) -> Result<(Option<FacetId>, Size), Error> {
265 Ok(match config {
266 ProgressBarConfig::Text(progress_bar_text) => {
267 let face = load_font(PathBuf::from("/pkg/data/fonts/Roboto-Regular.ttf"))?;
268 let label_width =
269 measure_text_width(&face, progress_bar_text.font_size, &progress_bar_text.text);
270 let width = label_width + progress_bar_text.padding * 2.0;
271 let height = progress_bar_text.font_size + progress_bar_text.padding * 2.0;
272 let size = size2(width, height);
273 let label = build_text_facet(builder, &progress_bar_text, face)?;
274 (Some(label), size)
275 }
276 ProgressBarConfig::TextWithSize(progress_bar_text, size) => {
277 let face = load_font(PathBuf::from("/pkg/data/fonts/Roboto-Regular.ttf"))?;
278 let label = build_text_facet(builder, &progress_bar_text, face)?;
279 (Some(label), size)
280 }
281 ProgressBarConfig::Size(size) => (None, size),
282 })
283}