stack: Animate tab scrolling
This commit is contained in:
parent
208c0a1078
commit
9af2f93b27
1 changed files with 61 additions and 7 deletions
|
|
@ -26,6 +26,8 @@ use cosmic::{
|
||||||
theme,
|
theme,
|
||||||
widget::{icon, Icon},
|
widget::{icon, Icon},
|
||||||
};
|
};
|
||||||
|
use cosmic_time::{Cubic, Ease, Tween};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
pub struct Tabs<'a, Message, Renderer>
|
pub struct Tabs<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
|
|
@ -40,10 +42,18 @@ where
|
||||||
scroll_to: Option<usize>,
|
scroll_to: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
struct ScrollAnimationState {
|
||||||
|
start_time: Instant,
|
||||||
|
start: Offset,
|
||||||
|
end: Offset,
|
||||||
|
}
|
||||||
|
|
||||||
/// The local state of [`Tabs`].
|
/// The local state of [`Tabs`].
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct State {
|
pub struct State {
|
||||||
offset_x: Offset,
|
offset_x: Offset,
|
||||||
|
scroll_animation: Option<ScrollAnimationState>,
|
||||||
scroll_to: Option<usize>,
|
scroll_to: Option<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,7 +63,13 @@ impl Scrollable for State {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn scroll_to(&mut self, offset: AbsoluteOffset) {
|
fn scroll_to(&mut self, offset: AbsoluteOffset) {
|
||||||
self.offset_x = Offset::Absolute(offset.x.max(0.0));
|
let new_offset = Offset::Absolute(offset.x.max(0.0));
|
||||||
|
self.scroll_animation = Some(ScrollAnimationState {
|
||||||
|
start_time: Instant::now(),
|
||||||
|
start: self.offset_x,
|
||||||
|
end: new_offset,
|
||||||
|
});
|
||||||
|
self.offset_x = new_offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,6 +77,7 @@ impl Default for State {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
State {
|
State {
|
||||||
offset_x: Offset::Absolute(0.),
|
offset_x: Offset::Absolute(0.),
|
||||||
|
scroll_animation: None,
|
||||||
scroll_to: None,
|
scroll_to: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,6 +98,8 @@ impl Offset {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ANIMATION_DURATION: Duration = Duration::from_millis(200);
|
||||||
|
|
||||||
impl<'a, Message, Renderer> Tabs<'a, Message, Renderer>
|
impl<'a, Message, Renderer> Tabs<'a, Message, Renderer>
|
||||||
where
|
where
|
||||||
Renderer: cosmic::iced_core::Renderer + 'a,
|
Renderer: cosmic::iced_core::Renderer + 'a,
|
||||||
|
|
@ -210,10 +229,38 @@ where
|
||||||
|
|
||||||
impl State {
|
impl State {
|
||||||
pub fn offset(&self, bounds: Rectangle, content_bounds: Size) -> Vector {
|
pub fn offset(&self, bounds: Rectangle, content_bounds: Size) -> Vector {
|
||||||
Vector::new(
|
if let Some(animation) = self.scroll_animation {
|
||||||
self.offset_x.absolute(bounds.width, content_bounds.width),
|
let percentage = {
|
||||||
0.,
|
let percentage = (Instant::now()
|
||||||
)
|
.duration_since(animation.start_time)
|
||||||
|
.as_millis() as f32
|
||||||
|
/ ANIMATION_DURATION.as_millis() as f32)
|
||||||
|
.min(1.0);
|
||||||
|
|
||||||
|
Ease::Cubic(Cubic::InOut).tween(percentage)
|
||||||
|
};
|
||||||
|
|
||||||
|
Vector::new(
|
||||||
|
animation.start.absolute(bounds.width, content_bounds.width)
|
||||||
|
+ (animation.end.absolute(bounds.width, content_bounds.width)
|
||||||
|
- animation.start.absolute(bounds.width, content_bounds.width))
|
||||||
|
* percentage,
|
||||||
|
0.,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Vector::new(
|
||||||
|
self.offset_x.absolute(bounds.width, content_bounds.width),
|
||||||
|
0.,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn cleanup_old_animations(&mut self) {
|
||||||
|
if let Some(animation) = self.scroll_animation.as_ref() {
|
||||||
|
if Instant::now().duration_since(animation.start_time) > ANIMATION_DURATION {
|
||||||
|
self.scroll_animation.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -533,6 +580,7 @@ where
|
||||||
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
|
operation: &mut dyn Operation<OperationOutputWrapper<Message>>,
|
||||||
) {
|
) {
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
state.cleanup_old_animations();
|
||||||
|
|
||||||
operation.scrollable(state, self.id.as_ref());
|
operation.scrollable(state, self.id.as_ref());
|
||||||
|
|
||||||
|
|
@ -560,6 +608,7 @@ where
|
||||||
shell: &mut Shell<'_, Message>,
|
shell: &mut Shell<'_, Message>,
|
||||||
) -> event::Status {
|
) -> event::Status {
|
||||||
let state = tree.state.downcast_mut::<State>();
|
let state = tree.state.downcast_mut::<State>();
|
||||||
|
state.cleanup_old_animations();
|
||||||
|
|
||||||
let mut bounds = layout.bounds();
|
let mut bounds = layout.bounds();
|
||||||
let content_bounds = layout.children().fold(Size::new(0., 0.), |a, b| Size {
|
let content_bounds = layout.children().fold(Size::new(0., 0.), |a, b| Size {
|
||||||
|
|
@ -590,7 +639,7 @@ where
|
||||||
if (left_offset - current_start).is_sign_negative()
|
if (left_offset - current_start).is_sign_negative()
|
||||||
|| (current_end - right_offset).is_sign_negative()
|
|| (current_end - right_offset).is_sign_negative()
|
||||||
{
|
{
|
||||||
let offset = if (left_offset - current_start).abs()
|
let new_offset = if (left_offset - current_start).abs()
|
||||||
< (right_offset - current_end).abs()
|
< (right_offset - current_end).abs()
|
||||||
{
|
{
|
||||||
AbsoluteOffset {
|
AbsoluteOffset {
|
||||||
|
|
@ -604,7 +653,12 @@ where
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
state.offset_x = Offset::Absolute(offset.x);
|
state.scroll_animation = Some(ScrollAnimationState {
|
||||||
|
start_time: Instant::now(),
|
||||||
|
start: Offset::Absolute(offset.x),
|
||||||
|
end: Offset::Absolute(new_offset.x),
|
||||||
|
});
|
||||||
|
state.offset_x = Offset::Absolute(new_offset.x);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
shell.publish(Message::scrolled());
|
shell.publish(Message::scrolled());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue