diff options
author | 2023-06-07 15:19:11 -0700 | |
---|---|---|
committer | 2023-06-07 15:42:13 -0700 | |
commit | 2ebc92319711e6fa0dda310939257334625b59c9 (patch) | |
tree | 3643af813fbb72e4308893cd33d383c7a0c12986 /examples/loading_spinners/src/linear.rs | |
parent | cdfb8b30680de164280b8b90fbc08a1638e597e2 (diff) | |
download | iced-2ebc92319711e6fa0dda310939257334625b59c9.tar.gz iced-2ebc92319711e6fa0dda310939257334625b59c9.tar.bz2 iced-2ebc92319711e6fa0dda310939257334625b59c9.zip |
feat: use lyon for easing
Diffstat (limited to 'examples/loading_spinners/src/linear.rs')
-rw-r--r-- | examples/loading_spinners/src/linear.rs | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/examples/loading_spinners/src/linear.rs b/examples/loading_spinners/src/linear.rs new file mode 100644 index 00000000..eb8a7408 --- /dev/null +++ b/examples/loading_spinners/src/linear.rs @@ -0,0 +1,324 @@ +//! Show a linear progress indicator. +use iced::window; +use iced_core::event::{self, Event}; +use iced_core::renderer; +use iced_core::time::Instant; +use iced_core::widget::tree::{self, Tree}; +use iced_core::window::RedrawRequest; +use iced_core::{layout, Size}; +use iced_core::{ + Background, Clipboard, Color, Element, Layout, Length, Point, Rectangle, + Shell, Widget, +}; + +use super::easing::{self, Easing}; + +use std::time::Duration; + +#[allow(missing_debug_implementations)] +pub struct Linear<'a, Renderer> +where + Renderer: iced_core::Renderer, + Renderer::Theme: StyleSheet, +{ + width: Length, + height: Length, + style: <Renderer::Theme as StyleSheet>::Style, + easing: &'a Easing, + cycle_duration: Duration, +} + +impl<'a, Renderer> Linear<'a, Renderer> +where + Renderer: iced_widget::core::Renderer, + Renderer::Theme: StyleSheet, +{ + /// Creates a new [`Linear`] with the given content. + pub fn new() -> Self { + Linear { + width: Length::Fixed(100.0), + height: Length::Fixed(4.0), + style: <Renderer::Theme as StyleSheet>::Style::default(), + easing: &easing::STANDARD, + cycle_duration: Duration::from_millis(600), + } + } + + /// Sets the width of the [`Linear`]. + pub fn width(mut self, width: impl Into<Length>) -> Self { + self.width = width.into(); + self + } + + /// Sets the height of the [`Linear`]. + pub fn height(mut self, height: impl Into<Length>) -> Self { + self.height = height.into(); + self + } + + /// Sets the style variant of this [`Linear`]. + pub fn style( + mut self, + style: <Renderer::Theme as StyleSheet>::Style, + ) -> Self { + self.style = style; + self + } + + /// Sets the motion easing of this [`Linear`]. + pub fn easing(mut self, easing: &'a Easing) -> Self { + self.easing = easing; + self + } + + /// Sets the cycle duration of this [`Linear`]. + pub fn cycle_duration(mut self, duration: Duration) -> Self { + self.cycle_duration = duration / 2; + self + } +} + +impl<'a, Renderer> Default for Linear<'a, Renderer> +where + Renderer: iced_core::Renderer, + Renderer::Theme: StyleSheet, +{ + fn default() -> Self { + Self::new() + } +} + +#[derive(Clone, Copy)] +enum State { + Expanding { start: Instant, progress: f32 }, + Contracting { start: Instant, progress: f32 }, +} + +impl Default for State { + fn default() -> Self { + Self::Expanding { + start: Instant::now(), + progress: 0.0, + } + } +} + +impl State { + fn next(&self, now: Instant) -> Self { + match self { + Self::Expanding { .. } => Self::Contracting { + start: now, + progress: 0.0, + }, + Self::Contracting { .. } => Self::Expanding { + start: now, + progress: 0.0, + }, + } + } + + fn start(&self) -> Instant { + match self { + Self::Expanding { start, .. } | Self::Contracting { start, .. } => { + *start + } + } + } + + fn timed_transition(&self, cycle_duration: Duration, now: Instant) -> Self { + let elapsed = now.duration_since(self.start()); + + match elapsed { + elapsed if elapsed > cycle_duration => self.next(now), + _ => self.with_elapsed(cycle_duration, elapsed), + } + } + + fn with_elapsed( + &self, + cycle_duration: Duration, + elapsed: Duration, + ) -> Self { + let progress = elapsed.as_secs_f32() / cycle_duration.as_secs_f32(); + match self { + Self::Expanding { start, .. } => Self::Expanding { + start: *start, + progress, + }, + Self::Contracting { start, .. } => Self::Contracting { + start: *start, + progress, + }, + } + } +} + +impl<'a, Message, Renderer> Widget<Message, Renderer> for Linear<'a, Renderer> +where + Message: 'a + Clone, + Renderer: 'a + iced_core::Renderer, + Renderer::Theme: StyleSheet, +{ + fn tag(&self) -> tree::Tag { + tree::Tag::of::<State>() + } + + fn state(&self) -> tree::State { + tree::State::new(State::default()) + } + + fn width(&self) -> Length { + self.width + } + + fn height(&self) -> Length { + self.height + } + + fn layout( + &self, + _renderer: &Renderer, + limits: &layout::Limits, + ) -> layout::Node { + let limits = limits.width(self.width).height(self.height); + let size = limits.resolve(Size::ZERO); + + layout::Node::new(size) + } + + fn on_event( + &mut self, + tree: &mut Tree, + event: Event, + _layout: Layout<'_>, + _cursor_position: Point, + _renderer: &Renderer, + _clipboard: &mut dyn Clipboard, + shell: &mut Shell<'_, Message>, + ) -> event::Status { + let state = tree.state.downcast_mut::<State>(); + + if let Event::Window(window::Event::RedrawRequested(now)) = event { + *state = state.timed_transition(self.cycle_duration, now); + + shell.request_redraw(RedrawRequest::NextFrame); + } + + event::Status::Ignored + } + + fn draw( + &self, + tree: &Tree, + renderer: &mut Renderer, + theme: &Renderer::Theme, + _style: &renderer::Style, + layout: Layout<'_>, + _cursor_position: Point, + _viewport: &Rectangle, + ) { + let bounds = layout.bounds(); + let custom_style = theme.appearance(&self.style); + let state = tree.state.downcast_ref::<State>(); + + renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x, + y: bounds.y, + width: bounds.width, + height: bounds.height, + }, + border_radius: 0.0.into(), + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + Background::Color(custom_style.track_color), + ); + + match state { + State::Expanding { progress, .. } => renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x, + y: bounds.y, + width: self.easing.y_at_x(*progress) * bounds.width, + height: bounds.height, + }, + border_radius: 0.0.into(), + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + Background::Color(custom_style.bar_color), + ), + + State::Contracting { progress, .. } => renderer.fill_quad( + renderer::Quad { + bounds: Rectangle { + x: bounds.x + + self.easing.y_at_x(*progress) * bounds.width, + y: bounds.y, + width: (1.0 - self.easing.y_at_x(*progress)) + * bounds.width, + height: bounds.height, + }, + border_radius: 0.0.into(), + border_width: 0.0, + border_color: Color::TRANSPARENT, + }, + Background::Color(custom_style.bar_color), + ), + } + } +} + +impl<'a, Message, Renderer> From<Linear<'a, Renderer>> + for Element<'a, Message, Renderer> +where + Message: Clone + 'a, + Renderer: iced_core::Renderer + 'a, + Renderer::Theme: StyleSheet, +{ + fn from(linear: Linear<'a, Renderer>) -> Self { + Self::new(linear) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Appearance { + /// The track [`Color`] of the progress indicator. + pub track_color: Color, + /// The bar [`Color`] of the progress indicator. + pub bar_color: Color, +} + +impl std::default::Default for Appearance { + fn default() -> Self { + Self { + track_color: Color::TRANSPARENT, + bar_color: Color::BLACK, + } + } +} + +/// A set of rules that dictate the style of an indicator. +pub trait StyleSheet { + /// The supported style of the [`StyleSheet`]. + type Style: Default; + + /// Produces the active [`Appearance`] of a indicator. + fn appearance(&self, style: &Self::Style) -> Appearance; +} + +impl StyleSheet for iced::Theme { + type Style = (); + + fn appearance(&self, _style: &Self::Style) -> Appearance { + let palette = self.extended_palette(); + + Appearance { + track_color: palette.background.weak.color, + bar_color: palette.primary.base.color, + } + } +} |