blob: 5185bed756746fa6c18605282e6800a8e77ad002 [file] [edit]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use anyhow::Error;
use carnelian::color::Color;
use carnelian::drawing::{FontFace, load_font, measure_text_width};
use carnelian::scene::facets::{
FacetId, SetColorMessage, SetSizeMessage, SetTextMessage, TextFacetOptions,
TextHorizontalAlignment, TextVerticalAlignment,
};
use carnelian::scene::layout::{Alignment, Stack, StackOptions};
use carnelian::scene::scene::{Scene, SceneBuilder};
use carnelian::{AppSender, MessageTarget, Point, Size, ViewKey, make_message};
use euclid::size2;
use fuchsia_async as fasync;
use fuchsia_sync::Mutex;
use futures::StreamExt;
use futures::channel::mpsc::{Sender, channel as pipe};
use std::path::PathBuf;
use std::sync::Arc;
use std::sync::atomic::{AtomicU32, Ordering};
use zx::MonotonicDuration;
const PROGRESS_GRANULARITY: f32 = 1000.0;
// We calculate the size from a provided size or displayed text, we must have at least one
pub enum ProgressBarConfig {
Text(ProgressBarText),
Size(Size),
TextWithSize(ProgressBarText, Size),
}
// Progress is [0.0..1.0[
pub enum ProgressBarMessages {
SetProgress(/* progress*/ f32),
SetProgressSmooth(
/* progress */ f32,
/* time to get to progress */ MonotonicDuration,
),
SetInternalProgress(f32),
SetProgressBarText(String),
}
pub struct ProgressBar {
label: Option<FacetId>,
progress_bar_size: Size,
progress_rectangle: FacetId,
background_rectangle: FacetId,
app_sender: AppSender,
// Progress stored as 0 to PROGRESS_GRANULARITY
current_progress: Arc<AtomicU32>,
// Progress stored as 0 to PROGRESS_GRANULARITY
final_progress: Arc<AtomicU32>,
// To make sure that we are only starting one copy of the background task
task_running_mutex: Arc<Mutex<bool>>,
// Channel to send desired progress to the background task
task_sender: Option<Sender<(/* progress */ u32, /* step_time_ms */ i64)>>,
}
#[derive(Clone)]
pub struct ProgressBarText {
pub text: String,
pub font_size: f32,
pub padding: f32,
pub alignment: TextHorizontalAlignment,
}
impl ProgressBar {
pub fn new(
config: ProgressBarConfig,
builder: &mut SceneBuilder,
app_sender: AppSender,
) -> Result<ProgressBar, Error> {
let options =
StackOptions { alignment: Alignment::center_left(), ..StackOptions::default() };
// Construct the progress bar itself
builder.start_group("progress_bar", Stack::with_options_ptr(options));
let (label, progress_bar_size) = build_progress_text(config, builder)?;
let progress_rectangle = builder.rectangle(
size2(0.0, progress_bar_size.height),
Color { r: 0x0b, g: 0x57, b: 0xd0, a: 0xff },
);
let background_rectangle =
builder.rectangle(progress_bar_size, Color { r: 0xbe, g: 0xd5, b: 0xfc, a: 0xff });
builder.end_group();
Ok(ProgressBar {
label,
progress_bar_size,
progress_rectangle,
background_rectangle,
app_sender,
current_progress: Arc::new(AtomicU32::new(0)),
final_progress: Arc::new(AtomicU32::new(0)),
task_running_mutex: Arc::new(Mutex::new(false)),
task_sender: None,
})
}
/// Set progress between 0 to 100 percent
pub fn set_percent(&mut self, scene: &mut Scene, percent_complete: f32) {
self.set_progress(scene, percent_complete / 100.0);
}
/// Set progress from 0.0 to 1.0
pub fn set_progress(&mut self, scene: &mut Scene, progress: f32) {
self.final_progress.store((progress * PROGRESS_GRANULARITY) as u32, Ordering::Release);
self.current_progress.store((progress * PROGRESS_GRANULARITY) as u32, Ordering::Release);
self.set_internal_progress(scene, progress);
}
/// Set progress from 0 to 1.0
pub fn set_internal_progress(&mut self, scene: &mut Scene, progress: f32) {
let progress = num_traits::clamp(progress, 0.0, 1.0);
let width = self.progress_bar_size.width;
let height = self.progress_bar_size.height;
let filled = width * progress;
scene.send_message(
&self.progress_rectangle,
Box::new(SetSizeMessage { size: size2(filled, height) }),
);
}
pub fn set_percent_smooth(&mut self, view_key: ViewKey, percent_complete: f32) {
self.set_progress_smooth(
view_key,
percent_complete / 100.0,
MonotonicDuration::from_seconds(1),
);
}
/// Set progress from 0.0 to 1.0
pub fn set_progress_smooth(
&mut self,
view_key: ViewKey,
progress: f32,
elapsed_time: MonotonicDuration,
) {
let progress = (progress * PROGRESS_GRANULARITY) as u32;
let current_progress = self.current_progress.load(Ordering::Acquire);
let step_time_ms = (elapsed_time.into_millis()
/ (current_progress as i32 - progress as i32).abs() as i64)
as i64;
let mut running = self.task_running_mutex.lock();
if !*running {
*running = true;
let app_sender = self.app_sender.clone();
let current_progress = self.current_progress.clone();
let final_progress = self.final_progress.clone();
let (tx, mut rx) = pipe::<(u32, i64)>(1);
self.task_sender = Some(tx);
let f = async move {
let mut sleep_time = MonotonicDuration::from_millis(step_time_ms);
loop {
let current = current_progress.load(Ordering::Acquire);
let end = final_progress.load(Ordering::Acquire);
let difference = end as i64 - current as i64;
if difference == 0 {
let (progress, step_time_ms) = rx.next().await.unwrap();
sleep_time = MonotonicDuration::from_millis(step_time_ms);
final_progress.store(progress as u32, Ordering::Release);
} else {
match rx.try_recv() {
Ok((progress, step_time_ms)) => {
sleep_time = MonotonicDuration::from_millis(step_time_ms);
final_progress.store(progress as u32, Ordering::Release);
}
Err(_) => { // Ignore
}
}
if difference > 0 {
current_progress.fetch_add(1, Ordering::AcqRel);
} else {
current_progress.fetch_sub(1, Ordering::AcqRel);
}
app_sender.queue_message(
MessageTarget::View(view_key),
make_message(ProgressBarMessages::SetInternalProgress(
current_progress.load(Ordering::Acquire) as f32
/ PROGRESS_GRANULARITY,
)),
);
fuchsia_async::Timer::new(sleep_time).await;
}
}
};
fasync::Task::local(f).detach();
}
let _ = self.task_sender.as_mut().unwrap().start_send((progress, step_time_ms));
}
pub fn set_color(&mut self, scene: &mut Scene, fg_color: Color, bg_color: Color) {
scene.send_message(&self.progress_rectangle, Box::new(SetColorMessage { color: fg_color }));
scene.send_message(
&self.background_rectangle,
Box::new(SetColorMessage { color: bg_color }),
);
}
pub fn set_text(&mut self, scene: &mut Scene, text: String) {
if let Some(label) = self.label.as_ref() {
scene.send_message(label, Box::new(SetTextMessage { text }));
}
}
pub fn set_text_color(&mut self, scene: &mut Scene, color: Color) {
if let Some(label) = self.label.as_ref() {
scene.send_message(label, Box::new(SetColorMessage { color }));
}
}
pub fn handle_message(
&mut self,
scene: &mut Scene,
view_key: ViewKey,
message: ProgressBarMessages,
) {
match message {
ProgressBarMessages::SetProgress(progress) => {
self.set_progress(scene, progress);
}
ProgressBarMessages::SetProgressSmooth(progress, step_time_ms) => {
self.set_progress_smooth(view_key, progress, step_time_ms);
}
ProgressBarMessages::SetInternalProgress(progress) => {
self.set_internal_progress(scene, progress);
}
ProgressBarMessages::SetProgressBarText(text) => {
self.set_text(scene, text);
}
}
}
}
fn build_text_facet(
builder: &mut SceneBuilder,
progress_bar_text: &ProgressBarText,
face: FontFace,
) -> Result<FacetId, Error> {
let label = builder.text(
face,
&progress_bar_text.text,
progress_bar_text.font_size,
Point::zero(),
TextFacetOptions {
color: Color::blue(),
horizontal_alignment: progress_bar_text.alignment,
vertical_alignment: TextVerticalAlignment::Top,
..TextFacetOptions::default()
},
);
Ok(label)
}
fn build_progress_text(
config: ProgressBarConfig,
builder: &mut SceneBuilder,
) -> Result<(Option<FacetId>, Size), Error> {
Ok(match config {
ProgressBarConfig::Text(progress_bar_text) => {
let face = load_font(PathBuf::from("/pkg/data/fonts/Roboto-Regular.ttf"))?;
let label_width =
measure_text_width(&face, progress_bar_text.font_size, &progress_bar_text.text);
let width = label_width + progress_bar_text.padding * 2.0;
let height = progress_bar_text.font_size + progress_bar_text.padding * 2.0;
let size = size2(width, height);
let label = build_text_facet(builder, &progress_bar_text, face)?;
(Some(label), size)
}
ProgressBarConfig::TextWithSize(progress_bar_text, size) => {
let face = load_font(PathBuf::from("/pkg/data/fonts/Roboto-Regular.ttf"))?;
let label = build_text_facet(builder, &progress_bar_text, face)?;
(Some(label), size)
}
ProgressBarConfig::Size(size) => (None, size),
})
}