Created
July 2, 2025 12:56
-
-
Save bq-wrongway/fcc7d750194ba7a72a725e1280d766fe to your computer and use it in GitHub Desktop.
wave_loading
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use iced::advanced::layout; | |
| use iced::advanced::renderer::{self, Quad}; | |
| use iced::advanced::widget::tree::{self, Tree}; | |
| use iced::advanced::{self, Clipboard, Layout, Shell, Widget}; | |
| use iced::mouse; | |
| use iced::time::Instant; | |
| use iced::window; | |
| use iced::{Background, Color, Element, Event, Length, Rectangle, Size}; | |
| use std::time::Duration; | |
| use std::f32::consts::PI; | |
| #[allow(missing_debug_implementations)] | |
| pub struct WaveLoading< Theme> | |
| where | |
| Theme: StyleSheet, | |
| { | |
| width: Length, | |
| height: Length, | |
| style: Theme::Style, | |
| bar_count: usize, | |
| cycle_duration: Duration, | |
| } | |
| impl<'a, Theme> WaveLoading< Theme> | |
| where | |
| Theme: StyleSheet, | |
| { | |
| /// Creates a new [`WaveLoading`]. | |
| pub fn new() -> Self { | |
| Self { | |
| width: Length::Fill, | |
| height: Length::Fill, | |
| style: Theme::Style::default(), | |
| bar_count: 7, | |
| cycle_duration: Duration::from_millis(900), | |
| } | |
| } | |
| /// Sets the width of the [`WaveLoading`]. | |
| pub fn width(mut self, width: impl Into<Length>) -> Self { | |
| self.width = width.into(); | |
| self | |
| } | |
| /// Sets the height of the [`WaveLoading`]. | |
| pub fn height(mut self, height: impl Into<Length>) -> Self { | |
| self.height = height.into(); | |
| self | |
| } | |
| /// Sets the style variant of this [`WaveLoading`]. | |
| pub fn style(mut self, style: impl Into<Theme::Style>) -> Self { | |
| self.style = style.into(); | |
| self | |
| } | |
| /// Sets the bar count of this [`WaveLoading`]. | |
| pub fn bar_count(mut self, count: usize) -> Self { | |
| self.bar_count = count; | |
| self | |
| } | |
| /// Sets the animation cycle duration. | |
| pub fn cycle_duration(mut self, duration: Duration) -> Self { | |
| self.cycle_duration = duration; | |
| self | |
| } | |
| } | |
| impl<Theme> Default for WaveLoading< Theme> | |
| where | |
| Theme: StyleSheet, | |
| { | |
| fn default() -> Self { | |
| Self::new() | |
| } | |
| } | |
| #[derive(Clone, Copy)] | |
| struct State { | |
| last_update: Instant, | |
| phase: f32, | |
| } | |
| impl Default for State { | |
| fn default() -> Self { | |
| Self { | |
| last_update: Instant::now(), | |
| phase: 0.0, | |
| } | |
| } | |
| } | |
| impl State { | |
| fn update(&mut self, now: Instant, cycle_duration: Duration) { | |
| let elapsed = now.duration_since(self.last_update); | |
| self.last_update = now; | |
| let cycle_sec = cycle_duration.as_secs_f32(); | |
| self.phase = (self.phase + 2.0 * PI * (elapsed.as_secs_f32() / cycle_sec)) % (2.0 * PI); | |
| } | |
| } | |
| impl<'a, Message, Theme, Renderer> Widget<Message, Theme, Renderer> | |
| for WaveLoading< Theme> | |
| where | |
| Message: Clone + 'a, | |
| Theme: StyleSheet + 'a, | |
| Renderer: advanced::Renderer + 'a, | |
| { | |
| fn tag(&self) -> tree::Tag { | |
| tree::Tag::of::<State>() | |
| } | |
| fn state(&self) -> tree::State { | |
| tree::State::new(State::default()) | |
| } | |
| fn size(&self) -> Size<Length> { | |
| Size { | |
| width: self.width, | |
| height: self.height, | |
| } | |
| } | |
| fn layout( | |
| &self, | |
| _tree: &mut Tree, | |
| _renderer: &Renderer, | |
| limits: &layout::Limits, | |
| ) -> layout::Node { | |
| layout::atomic(limits, self.width, self.height) | |
| } | |
| fn update( | |
| &mut self, | |
| tree: &mut Tree, | |
| event: &Event, | |
| _layout: Layout<'_>, | |
| _cursor: mouse::Cursor, | |
| _renderer: &Renderer, | |
| _clipboard: &mut dyn Clipboard, | |
| shell: &mut Shell<'_, Message>, | |
| _viewport: &Rectangle, | |
| ) { | |
| let state = tree.state.downcast_mut::<State>(); | |
| if let Event::Window(window::Event::RedrawRequested(now)) = event { | |
| state.update(*now, self.cycle_duration); | |
| shell.request_redraw(); | |
| } | |
| } | |
| fn draw( | |
| &self, | |
| tree: &Tree, | |
| renderer: &mut Renderer, | |
| theme: &Theme, | |
| _style: &renderer::Style, | |
| layout: Layout<'_>, | |
| _cursor: mouse::Cursor, | |
| _viewport: &Rectangle, | |
| ) { | |
| let bounds = layout.bounds(); | |
| let custom_style = theme.appearance(&self.style); | |
| let state = tree.state.downcast_ref::<State>(); | |
| // Background fill | |
| renderer.fill_quad( | |
| renderer::Quad { | |
| bounds, | |
| ..renderer::Quad::default() | |
| }, | |
| Background::Color(custom_style.track_color), | |
| ); | |
| // Wave bars | |
| let bar_count = self.bar_count; | |
| let width = bounds.width; | |
| let height = bounds.height; | |
| let w = width * 0.8; | |
| let h = height * 0.6; | |
| let x_start = bounds.x + (width - w) / 2.0; | |
| let y_base = bounds.y + height / 2.0; | |
| let bar_width = w / (bar_count as f32 * 2.0); | |
| let bar_spacing = w / (bar_count as f32); | |
| for i in 0..bar_count { | |
| let x = x_start + i as f32 * bar_spacing; | |
| let offset = i as f32 * 0.4; | |
| let wave = ((state.phase + offset).sin() + 1.0) / 2.0; | |
| let bar_h = h * (0.3 + 0.7 * wave); | |
| let y = y_base - bar_h / 2.0; | |
| renderer.fill_quad( | |
| Quad { | |
| bounds: Rectangle { | |
| x, | |
| y, | |
| width: bar_width, | |
| height: bar_h, | |
| }, | |
| // border_radius: (bar_width / 2.0).min(bar_h / 2.0).into(), | |
| ..Quad::default() | |
| }, | |
| Background::Color(custom_style.bar_color), | |
| ); | |
| } | |
| } | |
| } | |
| impl<'a, Message, Theme, Renderer> From<WaveLoading< Theme>> | |
| for Element<'a, Message, Theme, Renderer> | |
| where | |
| Message: Clone + 'a, | |
| Theme: StyleSheet + 'a, | |
| Renderer: iced::advanced::Renderer + 'a, | |
| { | |
| fn from(wave: WaveLoading< Theme>) -> Self { | |
| Self::new(wave) | |
| } | |
| } | |
| // --- Style infrastructure is exactly the same as your 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 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 an 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, | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment