feat(progress_bar): animate determinate progress
This commit is contained in:
parent
4d39cf3d7b
commit
8b4c8adec8
3 changed files with 95 additions and 55 deletions
|
|
@ -2,6 +2,39 @@ use crate::anim::smootherstep;
|
||||||
use iced::time::Instant;
|
use iced::time::Instant;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
const LAG: f32 = 0.1;
|
||||||
|
|
||||||
|
pub struct Progress {
|
||||||
|
pub current: f32,
|
||||||
|
last: Instant,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Progress {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
current: 0.0,
|
||||||
|
last: Instant::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Progress {
|
||||||
|
/// Smoothly chases `target` using exponential decay.
|
||||||
|
/// Returns `true` if still animating and a redraw should be requested.
|
||||||
|
pub fn update(&mut self, target: f32, now: Instant) -> bool {
|
||||||
|
let dt = (now - self.last).as_secs_f32();
|
||||||
|
self.last = now;
|
||||||
|
let next = self.current + (target - self.current) * (1.0 - (-dt / LAG).exp());
|
||||||
|
if (next - target).abs() > 0.001 {
|
||||||
|
self.current = next;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
self.current = target;
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct Animation {
|
pub struct Animation {
|
||||||
expanding: bool,
|
expanding: bool,
|
||||||
|
|
@ -26,12 +59,12 @@ impl Animation {
|
||||||
pub fn timed_transition(
|
pub fn timed_transition(
|
||||||
&self,
|
&self,
|
||||||
cycle_duration: Duration,
|
cycle_duration: Duration,
|
||||||
rotation_duration: Duration,
|
period: Duration,
|
||||||
wrap: f32,
|
wrap: f32,
|
||||||
now: Instant,
|
now: Instant,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let additional = ((now - self.last).as_secs_f32() / rotation_duration.as_secs_f32()
|
let additional =
|
||||||
* u32::MAX as f32) as u32;
|
((now - self.last).as_secs_f32() / period.as_secs_f32() * u32::MAX as f32) as u32;
|
||||||
let new_offset = self.offset.wrapping_add(additional);
|
let new_offset = self.offset.wrapping_add(additional);
|
||||||
|
|
||||||
if !cycle_duration.is_zero() && now.duration_since(self.start) > cycle_duration {
|
if !cycle_duration.is_zero() && now.duration_since(self.start) > cycle_duration {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
//! Show a circular progress indicator.
|
//! Show a circular progress indicator.
|
||||||
use super::animation::Animation;
|
use super::animation::{Animation, Progress};
|
||||||
use super::style::StyleSheet;
|
use super::style::StyleSheet;
|
||||||
use iced::advanced::layout;
|
use iced::advanced::layout;
|
||||||
use iced::advanced::renderer;
|
use iced::advanced::renderer;
|
||||||
|
|
@ -24,7 +24,7 @@ where
|
||||||
bar_height: f32,
|
bar_height: f32,
|
||||||
style: Theme::Style,
|
style: Theme::Style,
|
||||||
cycle_duration: Duration,
|
cycle_duration: Duration,
|
||||||
rotation_duration: Duration,
|
period: Duration,
|
||||||
progress: Option<f32>,
|
progress: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -39,7 +39,7 @@ where
|
||||||
bar_height: 4.0,
|
bar_height: 4.0,
|
||||||
style: Theme::Style::default(),
|
style: Theme::Style::default(),
|
||||||
cycle_duration: Duration::from_millis(1500),
|
cycle_duration: Duration::from_millis(1500),
|
||||||
rotation_duration: Duration::from_secs(2),
|
period: Duration::from_secs(2),
|
||||||
progress: None,
|
progress: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -68,10 +68,10 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the base rotation duration of this [`Circular`]. This is the duration that a full
|
/// Sets the base period of this [`Circular`]. This is the duration that a full rotation
|
||||||
/// rotation would take if the cycle duration were set to 0.0 (no expanding or contracting)
|
/// would take if the cycle duration were set to 0.0 (no expanding or contracting)
|
||||||
pub fn rotation_duration(mut self, duration: Duration) -> Self {
|
pub fn period(mut self, duration: Duration) -> Self {
|
||||||
self.rotation_duration = duration;
|
self.period = duration;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -101,7 +101,7 @@ where
|
||||||
struct State {
|
struct State {
|
||||||
animation: Animation,
|
animation: Animation,
|
||||||
cache: canvas::Cache,
|
cache: canvas::Cache,
|
||||||
progress: Option<f32>,
|
progress: Progress,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Message, Theme> Widget<Message, Theme, Renderer> for Circular<Theme>
|
impl<Message, Theme> Widget<Message, Theme, Renderer> for Circular<Theme>
|
||||||
|
|
@ -145,23 +145,21 @@ where
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
if self.progress.is_some() {
|
|
||||||
if state.progress != self.progress {
|
|
||||||
state.progress = self.progress;
|
|
||||||
state.cache.clear();
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if let Event::Window(window::Event::RedrawRequested(now)) = event {
|
if let Event::Window(window::Event::RedrawRequested(now)) = event {
|
||||||
let (_, wrap) = self.min_wrap(self.size / 2.0 - self.bar_height);
|
if let Some(target) = self.progress {
|
||||||
state.animation = state.animation.timed_transition(
|
if state.progress.update(target, *now) {
|
||||||
self.cycle_duration,
|
state.cache.clear();
|
||||||
self.rotation_duration,
|
shell.request_redraw();
|
||||||
wrap,
|
}
|
||||||
*now,
|
} else {
|
||||||
);
|
let (_, wrap) = self.min_wrap(self.size / 2.0 - self.bar_height);
|
||||||
state.cache.clear();
|
state.animation =
|
||||||
shell.request_redraw();
|
state
|
||||||
|
.animation
|
||||||
|
.timed_transition(self.cycle_duration, self.period, wrap, *now);
|
||||||
|
state.cache.clear();
|
||||||
|
shell.request_redraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -231,7 +229,7 @@ where
|
||||||
draw_cap(frame, start, true);
|
draw_cap(frame, start, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(progress) = self.progress {
|
if self.progress.is_some() {
|
||||||
if let Some(border_color) = custom_style.border_color {
|
if let Some(border_color) = custom_style.border_color {
|
||||||
for radius_offset in [self.bar_height / 2.0, -(self.bar_height / 2.0)] {
|
for radius_offset in [self.bar_height / 2.0, -(self.bar_height / 2.0)] {
|
||||||
let border_path =
|
let border_path =
|
||||||
|
|
@ -244,7 +242,7 @@ where
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
draw_bar(frame, 0.0, progress);
|
draw_bar(frame, 0.0, state.progress.current);
|
||||||
} else {
|
} else {
|
||||||
let (min, wrap) = self.min_wrap(track_radius);
|
let (min, wrap) = self.min_wrap(track_radius);
|
||||||
let (start, end) = state
|
let (start, end) = state
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
//! Show a linear progress indicator.
|
//! Show a linear progress indicator.
|
||||||
use super::animation::Animation;
|
use super::animation::{Animation, Progress};
|
||||||
use super::style::StyleSheet;
|
use super::style::StyleSheet;
|
||||||
use iced::advanced::layout;
|
use iced::advanced::layout;
|
||||||
use iced::advanced::renderer;
|
use iced::advanced::renderer;
|
||||||
|
|
@ -23,7 +23,7 @@ where
|
||||||
girth: Length,
|
girth: Length,
|
||||||
style: Theme::Style,
|
style: Theme::Style,
|
||||||
cycle_duration: Duration,
|
cycle_duration: Duration,
|
||||||
traversal_duration: Duration,
|
period: Duration,
|
||||||
progress: Option<f32>,
|
progress: Option<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -38,7 +38,7 @@ where
|
||||||
girth: Length::Fixed(4.0),
|
girth: Length::Fixed(4.0),
|
||||||
style: Theme::Style::default(),
|
style: Theme::Style::default(),
|
||||||
cycle_duration: Duration::from_millis(1500),
|
cycle_duration: Duration::from_millis(1500),
|
||||||
traversal_duration: Duration::from_secs(2),
|
period: Duration::from_secs(2),
|
||||||
progress: None,
|
progress: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -67,10 +67,10 @@ where
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the base traversal duration of this [`Linear`]. This is the duration that a full
|
/// Sets the base period of this [`Linear`]. This is the duration that a full traversal
|
||||||
/// traversal would take if the cycle duration were set to 0.0 (no expanding or contracting)
|
/// would take if the cycle duration were set to 0.0 (no expanding or contracting)
|
||||||
pub fn traversal_duration(mut self, duration: Duration) -> Self {
|
pub fn period(mut self, duration: Duration) -> Self {
|
||||||
self.traversal_duration = duration;
|
self.period = duration;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,6 +90,12 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct State {
|
||||||
|
animation: Animation,
|
||||||
|
progress: Progress,
|
||||||
|
}
|
||||||
|
|
||||||
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Linear<Theme>
|
impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> for Linear<Theme>
|
||||||
where
|
where
|
||||||
Message: Clone,
|
Message: Clone,
|
||||||
|
|
@ -97,11 +103,11 @@ where
|
||||||
Renderer: advanced::Renderer,
|
Renderer: advanced::Renderer,
|
||||||
{
|
{
|
||||||
fn tag(&self) -> tree::Tag {
|
fn tag(&self) -> tree::Tag {
|
||||||
tree::Tag::of::<Animation>()
|
tree::Tag::of::<State>()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn state(&self) -> tree::State {
|
fn state(&self) -> tree::State {
|
||||||
tree::State::new(Animation::default())
|
tree::State::new(State::default())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn size(&self) -> Size<Length> {
|
fn size(&self) -> Size<Length> {
|
||||||
|
|
@ -131,20 +137,21 @@ where
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
_viewport: &Rectangle,
|
_viewport: &Rectangle,
|
||||||
) {
|
) {
|
||||||
if self.progress.is_some() {
|
let state = tree.state.downcast_mut::<State>();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let animation = tree.state.downcast_mut::<Animation>();
|
|
||||||
|
|
||||||
if let Event::Window(window::Event::RedrawRequested(now)) = event {
|
if let Event::Window(window::Event::RedrawRequested(now)) = event {
|
||||||
*animation = animation.timed_transition(
|
if let Some(target) = self.progress {
|
||||||
self.cycle_duration,
|
if state.progress.update(target, *now) {
|
||||||
self.traversal_duration,
|
shell.request_redraw();
|
||||||
WRAP_LENGTH,
|
}
|
||||||
*now,
|
} else {
|
||||||
);
|
state.animation = state.animation.timed_transition(
|
||||||
shell.request_redraw();
|
self.cycle_duration,
|
||||||
|
self.period,
|
||||||
|
WRAP_LENGTH,
|
||||||
|
*now,
|
||||||
|
);
|
||||||
|
shell.request_redraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -160,7 +167,7 @@ where
|
||||||
) {
|
) {
|
||||||
let bounds = layout.bounds();
|
let bounds = layout.bounds();
|
||||||
let custom_style = theme.appearance(&self.style, self.progress.is_some(), false);
|
let custom_style = theme.appearance(&self.style, self.progress.is_some(), false);
|
||||||
let animation = tree.state.downcast_ref::<Animation>();
|
let state = tree.state.downcast_ref::<State>();
|
||||||
|
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
|
|
@ -203,11 +210,13 @@ where
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(progress) = self.progress {
|
if self.progress.is_some() {
|
||||||
draw_segment(0.0, progress);
|
draw_segment(0.0, state.progress.current);
|
||||||
} else {
|
} else {
|
||||||
let (bar_start, bar_end) =
|
let (bar_start, bar_end) =
|
||||||
animation.bar_positions(self.cycle_duration, MIN_LENGTH, WRAP_LENGTH);
|
state
|
||||||
|
.animation
|
||||||
|
.bar_positions(self.cycle_duration, MIN_LENGTH, WRAP_LENGTH);
|
||||||
let length = bar_end - bar_start;
|
let length = bar_end - bar_start;
|
||||||
let start = bar_start % 1.0;
|
let start = bar_start % 1.0;
|
||||||
let right_width = (1.0 - start).min(length);
|
let right_width = (1.0 - start).min(length);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue