stack: Animate tab scrolling

This commit is contained in:
Victoria Brekenfeld 2023-06-23 16:09:01 +02:00
parent 208c0a1078
commit 9af2f93b27

View file

@ -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());