feat(progress_bar): improve determinate animations
Switches to using a critically damped spring animation, which prevents issues with rapid progress updates, and is generally smoother.
This commit is contained in:
parent
6e116097c0
commit
76bc13ff40
3 changed files with 57 additions and 62 deletions
|
|
@ -84,6 +84,7 @@ pub enum Message {
|
||||||
Hi2,
|
Hi2,
|
||||||
Hi3,
|
Hi3,
|
||||||
Tick,
|
Tick,
|
||||||
|
ValueChanged(f32),
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The [`App`] stores application-specific state.
|
/// The [`App`] stores application-specific state.
|
||||||
|
|
@ -95,6 +96,7 @@ pub struct App {
|
||||||
hidden: bool,
|
hidden: bool,
|
||||||
keybinds: HashMap<KeyBind, Action>,
|
keybinds: HashMap<KeyBind, Action>,
|
||||||
progress: f32,
|
progress: f32,
|
||||||
|
progress_slider: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Implement [`cosmic::Application`] to integrate with COSMIC.
|
/// Implement [`cosmic::Application`] to integrate with COSMIC.
|
||||||
|
|
@ -137,6 +139,7 @@ impl cosmic::Application for App {
|
||||||
hidden: true,
|
hidden: true,
|
||||||
keybinds: HashMap::new(),
|
keybinds: HashMap::new(),
|
||||||
progress: 0.0,
|
progress: 0.0,
|
||||||
|
progress_slider: 0.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let command = app.update_title();
|
let command = app.update_title();
|
||||||
|
|
@ -185,6 +188,9 @@ impl cosmic::Application for App {
|
||||||
Message::Tick => {
|
Message::Tick => {
|
||||||
self.progress = (self.progress + 0.01) % 1.0;
|
self.progress = (self.progress + 0.01) % 1.0;
|
||||||
}
|
}
|
||||||
|
Message::ValueChanged(value) => {
|
||||||
|
self.progress_slider = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Task::none()
|
Task::none()
|
||||||
}
|
}
|
||||||
|
|
@ -201,7 +207,7 @@ impl cosmic::Application for App {
|
||||||
.map_or("No page selected", String::as_str);
|
.map_or("No page selected", String::as_str);
|
||||||
|
|
||||||
let centered = widget::container(
|
let centered = widget::container(
|
||||||
widget::column::with_capacity(14)
|
widget::column::with_capacity(16)
|
||||||
.push(widget::text::body(page_content))
|
.push(widget::text::body(page_content))
|
||||||
.push(
|
.push(
|
||||||
widget::text_input::text_input("", &self.input_1)
|
widget::text_input::text_input("", &self.input_1)
|
||||||
|
|
@ -240,6 +246,16 @@ impl cosmic::Application for App {
|
||||||
widget::progress_bar::linear::Linear::new()
|
widget::progress_bar::linear::Linear::new()
|
||||||
.girth(10.0)
|
.girth(10.0)
|
||||||
.progress(self.progress)
|
.progress(self.progress)
|
||||||
|
.width(Length::Fill),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
widget::slider(0.0..=1.0, self.progress_slider, Message::ValueChanged)
|
||||||
|
.step(0.001),
|
||||||
|
)
|
||||||
|
.push(
|
||||||
|
widget::progress_bar::linear::Linear::new()
|
||||||
|
.girth(10.0)
|
||||||
|
.progress(self.progress_slider)
|
||||||
.width(Length::Fill)
|
.width(Length::Fill)
|
||||||
.markers([0.25, 0.5, 0.75])
|
.markers([0.25, 0.5, 0.75])
|
||||||
.segment_spacing(2),
|
.segment_spacing(2),
|
||||||
|
|
|
||||||
|
|
@ -2,45 +2,44 @@ 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;
|
/// Angular frequency of the critically damped spring (snappiness)
|
||||||
|
const OMEGA: f32 = 15.0;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Progress {
|
pub struct Progress {
|
||||||
pub current: f32,
|
pub current: f32,
|
||||||
target: Option<f32>,
|
velocity: f32,
|
||||||
last: Option<Instant>,
|
last: Option<Instant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Progress {
|
impl Progress {
|
||||||
/// Smoothly chases `target` using exponential decay.
|
/// Chases `target` using a critically damped spring.
|
||||||
/// Returns `true` if a redraw should be requested.
|
/// Returns `true` if a redraw should be requested.
|
||||||
pub fn update(&mut self, target: f32, now: Instant) -> bool {
|
pub fn update(&mut self, target: f32, now: Instant) -> bool {
|
||||||
// Don't animate on start
|
// Don't animate on start
|
||||||
let Some(last) = self.last else {
|
let Some(last) = self.last else {
|
||||||
self.current = target;
|
self.current = target;
|
||||||
self.target = Some(target);
|
self.velocity = 0.0;
|
||||||
self.last = Some(now);
|
self.last = Some(now);
|
||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Sync animation clock when target changes
|
|
||||||
if self.target != Some(target) {
|
|
||||||
self.target = Some(target);
|
|
||||||
self.last = Some(now);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
let dt = (now - last).as_secs_f32();
|
let dt = (now - last).as_secs_f32();
|
||||||
self.last = Some(now);
|
self.last = Some(now);
|
||||||
let diff = target - self.current;
|
let displacement = self.current - target;
|
||||||
|
|
||||||
if diff.abs() > 0.001 {
|
if displacement.abs() < 0.001 && self.velocity.abs() < 0.001 {
|
||||||
self.current += diff * (1.0 - (-dt / LAG).exp());
|
|
||||||
true
|
|
||||||
} else {
|
|
||||||
self.current = target;
|
self.current = target;
|
||||||
false
|
self.velocity = 0.0;
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let exp = (-OMEGA * dt).exp();
|
||||||
|
let coeff = self.velocity + OMEGA * displacement;
|
||||||
|
self.current = target + (displacement + coeff * dt) * exp;
|
||||||
|
self.velocity = (self.velocity - OMEGA * coeff * dt) * exp;
|
||||||
|
|
||||||
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -206,9 +206,9 @@ where
|
||||||
renderer.fill_quad(
|
renderer.fill_quad(
|
||||||
renderer::Quad {
|
renderer::Quad {
|
||||||
bounds: Rectangle {
|
bounds: Rectangle {
|
||||||
x: bounds.x + x,
|
x: bounds.x + x * bounds.width,
|
||||||
y: bounds.y,
|
y: bounds.y,
|
||||||
width,
|
width: width * bounds.width,
|
||||||
height: bounds.height,
|
height: bounds.height,
|
||||||
},
|
},
|
||||||
border,
|
border,
|
||||||
|
|
@ -248,7 +248,6 @@ where
|
||||||
let x_width = (seg_hi - seg_lo) * drawable;
|
let x_width = (seg_hi - seg_lo) * drawable;
|
||||||
let segment_radius = [r_left, r_right, r_right, r_left].into();
|
let segment_radius = [r_left, r_right, r_right, r_left].into();
|
||||||
|
|
||||||
let fill = ((current_p - seg_lo) / (seg_hi - seg_lo)).min(1.0);
|
|
||||||
let border = iced::Border {
|
let border = iced::Border {
|
||||||
width: border_width,
|
width: border_width,
|
||||||
color: border_color,
|
color: border_color,
|
||||||
|
|
@ -257,34 +256,16 @@ where
|
||||||
|
|
||||||
// empty segment
|
// empty segment
|
||||||
if current_p < seg_lo {
|
if current_p < seg_lo {
|
||||||
draw_quad(
|
draw_quad(renderer, x_start, x_width, custom_style.track_color, border);
|
||||||
renderer,
|
|
||||||
x_start * bounds.width,
|
|
||||||
x_width * bounds.width,
|
|
||||||
custom_style.track_color,
|
|
||||||
border,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// filled segment
|
// filled segment
|
||||||
else if current_p > seg_hi {
|
else if current_p > seg_hi {
|
||||||
draw_quad(
|
draw_quad(renderer, x_start, x_width, custom_style.bar_color, border);
|
||||||
renderer,
|
|
||||||
x_start * bounds.width,
|
|
||||||
x_width * bounds.width,
|
|
||||||
custom_style.bar_color,
|
|
||||||
border,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// partially filled segment
|
// partially filled segment
|
||||||
else {
|
else {
|
||||||
draw_quad(
|
let fill = ((current_p - seg_lo) / (seg_hi - seg_lo)).min(1.0);
|
||||||
renderer,
|
draw_quad(renderer, x_start, x_width, custom_style.track_color, border);
|
||||||
x_start * bounds.width,
|
|
||||||
x_width * bounds.width,
|
|
||||||
custom_style.track_color,
|
|
||||||
border,
|
|
||||||
);
|
|
||||||
|
|
||||||
renderer.with_layer(
|
renderer.with_layer(
|
||||||
Rectangle {
|
Rectangle {
|
||||||
x: bounds.x + x_start * bounds.width,
|
x: bounds.x + x_start * bounds.width,
|
||||||
|
|
@ -293,13 +274,7 @@ where
|
||||||
height: bounds.height,
|
height: bounds.height,
|
||||||
},
|
},
|
||||||
|renderer| {
|
|renderer| {
|
||||||
draw_quad(
|
draw_quad(renderer, x_start, x_width, custom_style.bar_color, border);
|
||||||
renderer,
|
|
||||||
x_start * bounds.width,
|
|
||||||
x_width * bounds.width,
|
|
||||||
custom_style.bar_color,
|
|
||||||
border,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -307,6 +282,20 @@ where
|
||||||
}
|
}
|
||||||
// indeterminate progress bar
|
// indeterminate progress bar
|
||||||
else {
|
else {
|
||||||
|
// draw track
|
||||||
|
draw_quad(
|
||||||
|
renderer,
|
||||||
|
0.0,
|
||||||
|
1.0,
|
||||||
|
custom_style.track_color,
|
||||||
|
iced::Border {
|
||||||
|
width: border_width,
|
||||||
|
color: border_color,
|
||||||
|
radius: radius.into(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// draw bar
|
||||||
let (bar_start, bar_end) =
|
let (bar_start, bar_end) =
|
||||||
state
|
state
|
||||||
.animation
|
.animation
|
||||||
|
|
@ -316,19 +305,10 @@ where
|
||||||
let right_width = (1.0 - start).min(length);
|
let right_width = (1.0 - start).min(length);
|
||||||
let left_width = length - right_width;
|
let left_width = length - right_width;
|
||||||
let border = iced::Border {
|
let border = iced::Border {
|
||||||
width: border_width,
|
|
||||||
color: border_color,
|
|
||||||
radius: radius.into(),
|
radius: radius.into(),
|
||||||
|
..iced::Border::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
draw_quad(
|
|
||||||
renderer,
|
|
||||||
0.0,
|
|
||||||
bounds.width,
|
|
||||||
custom_style.track_color,
|
|
||||||
border,
|
|
||||||
);
|
|
||||||
|
|
||||||
renderer.with_layer(
|
renderer.with_layer(
|
||||||
Rectangle {
|
Rectangle {
|
||||||
x: bounds.x,
|
x: bounds.x,
|
||||||
|
|
@ -337,7 +317,7 @@ where
|
||||||
height: bounds.height,
|
height: bounds.height,
|
||||||
},
|
},
|
||||||
|renderer| {
|
|renderer| {
|
||||||
draw_quad(renderer, 0.0, bounds.width, custom_style.bar_color, border);
|
draw_quad(renderer, 0.0, 1.0, custom_style.bar_color, border);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -349,7 +329,7 @@ where
|
||||||
height: bounds.height,
|
height: bounds.height,
|
||||||
},
|
},
|
||||||
|renderer| {
|
|renderer| {
|
||||||
draw_quad(renderer, 0.0, bounds.width, custom_style.bar_color, border);
|
draw_quad(renderer, 0.0, 1.0, custom_style.bar_color, border);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue