Created
July 2, 2025 11:15
-
-
Save bq-wrongway/5d98c493ca9787083b648fd939593bb3 to your computer and use it in GitHub Desktop.
loading_wave_widget working
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 rand::Rng; | |
| // use iced::time::{Duration, Instant}; | |
| // use iced::widget::canvas::{Frame, Geometry, Path, Program}; | |
| // use iced::widget::{button, canvas, center, column, text}; | |
| // use iced::{Element, Point, Rectangle, Renderer, Size, Theme}; | |
| // use iced::{Fill, Subscription, Task}; | |
| // const NUM_BARS: usize = 10; | |
| // const MAX_BAR_HEIGHT: f32 = 0.7; | |
| // const BAR_SPACING: f32 = 2.0; | |
| // fn main() -> iced::Result { | |
| // iced::application( | |
| // NowPlayingApp::new, | |
| // NowPlayingApp::update, | |
| // NowPlayingApp::view, | |
| // ) | |
| // .subscription(NowPlayingApp::subscription) | |
| // .title("iced • Now Playing") | |
| // .window_size([400.0, 200.0]) | |
| // .theme(|_| iced::Theme::GruvboxDark) | |
| // .centered() | |
| // .run() | |
| // } | |
| // #[derive(Debug, Clone)] | |
| // pub enum Message { | |
| // TogglePlay, | |
| // Tick, | |
| // Pulse, | |
| // } | |
| // #[derive(Debug)] | |
| // struct NowPlaying { | |
| // is_playing: bool, | |
| // bars: [f32; NUM_BARS], | |
| // target_bars: [f32; NUM_BARS], | |
| // peaks: [f32; NUM_BARS], | |
| // peak_hold_timers: [u8; NUM_BARS], | |
| // } | |
| // impl Default for NowPlaying { | |
| // fn default() -> Self { | |
| // // Start with all bars and targets at zero (empty) | |
| // Self { | |
| // is_playing: false, | |
| // bars: [0.0; NUM_BARS], | |
| // target_bars: [0.0; NUM_BARS], | |
| // peaks: [0.0; NUM_BARS], | |
| // peak_hold_timers: [0; NUM_BARS], | |
| // } | |
| // } | |
| // } | |
| // impl NowPlaying { | |
| // fn create_wave(&mut self, center_index: usize, intensity: f32) { | |
| // for i in 0..NUM_BARS { | |
| // let distance = (i as isize - center_index as isize).abs() as f32; | |
| // let wave_intensity = intensity * (-distance * 0.25).exp(); | |
| // if wave_intensity > 0.1 { | |
| // let random_factor = 0.85 + rand::rng().random_range(0.0..0.35); | |
| // self.target_bars[i] = self.target_bars[i].max(wave_intensity * random_factor); | |
| // } | |
| // } | |
| // } | |
| // } | |
| // struct NowPlayingApp { | |
| // state: NowPlaying, | |
| // last_pulse_time: Instant, | |
| // next_pulse_interval: Duration, | |
| // } | |
| // impl NowPlayingApp { | |
| // fn new() -> (Self, Task<Message>) { | |
| // // Create an initial wave using create_wave | |
| // let mut state = NowPlaying::default(); | |
| // state.create_wave(NUM_BARS / 2, 0.8); | |
| // ( | |
| // Self { | |
| // state, | |
| // last_pulse_time: Instant::now(), | |
| // next_pulse_interval: Duration::from_millis(800), | |
| // }, | |
| // Task::none(), | |
| // ) | |
| // } | |
| // fn subscription(&self) -> Subscription<Message> { | |
| // iced::time::every(Duration::from_millis(16)).map(|_| Message::Tick) | |
| // } | |
| // fn update(&mut self, message: Message) -> Task<Message> { | |
| // match message { | |
| // Message::TogglePlay => { | |
| // self.state.is_playing = !self.state.is_playing; | |
| // } | |
| // Message::Tick => { | |
| // // --- Pulse scheduling logic (matches JS) --- | |
| // if self.state.is_playing { | |
| // let now = Instant::now(); | |
| // if now.duration_since(self.last_pulse_time) > self.next_pulse_interval { | |
| // self.last_pulse_time = now; | |
| // // Randomize next interval between 150-350ms | |
| // let next_ms = 150 + (rand::rng().random_range(0.0..200.0)) as u64; | |
| // self.next_pulse_interval = Duration::from_millis(next_ms); | |
| // // Create 1-3 wave centers | |
| // let num_waves = 1 + (rand::rng().random_range(0.0..3.0)) as usize; | |
| // for _ in 0..num_waves { | |
| // let center = rand::rng().random_range(0..NUM_BARS); | |
| // let intensity = 0.6 + rand::rng().random_range(0.0..0.4); | |
| // self.state.create_wave(center, intensity); | |
| // } | |
| // } | |
| // } | |
| // // Always update bars and peaks (decay/animation) | |
| // for i in 0..NUM_BARS { | |
| // // Smoothly move bars toward target | |
| // let diff = self.state.target_bars[i] - self.state.bars[i]; | |
| // self.state.bars[i] += diff * 0.25; | |
| // // Peak logic (ghost bar) | |
| // if self.state.bars[i] > self.state.peaks[i] { | |
| // self.state.peaks[i] = self.state.bars[i]; | |
| // self.state.peak_hold_timers[i] = 30; // Hold for ~0.5s at 60fps | |
| // } else if self.state.peak_hold_timers[i] > 0 { | |
| // self.state.peak_hold_timers[i] -= 1; | |
| // } else { | |
| // self.state.peaks[i] *= 0.95; | |
| // } | |
| // // Decay the target | |
| // self.state.target_bars[i] *= 0.92; | |
| // // Additional decay to bars | |
| // self.state.bars[i] *= 0.96; | |
| // // Clamp values | |
| // self.state.bars[i] = self.state.bars[i].max(0.0).min(1.0); | |
| // self.state.target_bars[i] = self.state.target_bars[i].max(0.0).min(1.0); | |
| // self.state.peaks[i] = self.state.peaks[i].max(0.0).min(1.0); | |
| // } | |
| // } | |
| // _ => {} | |
| // } | |
| // Task::none() | |
| // } | |
| // fn view(&self) -> Element<Message> { | |
| // let content = column![ | |
| // button( | |
| // text( | |
| // if self.state.is_playing { | |
| // "Pause" | |
| // } else { | |
| // "Play" | |
| // } | |
| // .to_uppercase() | |
| // ) | |
| // .size(12) | |
| // ) | |
| // .on_press(Message::TogglePlay), | |
| // canvas(NowPlayingViewer { | |
| // bars: self.state.bars, | |
| // }) | |
| // .width(Fill) | |
| // .height(Fill) | |
| // ] | |
| // .spacing(10) | |
| // .padding(10); | |
| // center(content).into() | |
| // } | |
| // } | |
| // struct NowPlayingViewer { | |
| // bars: [f32; NUM_BARS], | |
| // } | |
| // impl<Message> Program<Message> for NowPlayingViewer { | |
| // type State = (); | |
| // fn draw( | |
| // &self, | |
| // _state: &Self::State, | |
| // renderer: &Renderer, | |
| // theme: &Theme, | |
| // bounds: Rectangle, | |
| // _cursor: iced::mouse::Cursor, | |
| // ) -> Vec<Geometry> { | |
| // let mut frame = Frame::new(renderer, bounds.size()); | |
| // let bar_width = bounds.width / NUM_BARS as f32; | |
| // let actual_bar_width = (bar_width - BAR_SPACING) / 2.; | |
| // let palette = theme.extended_palette(); | |
| // let bar_color = palette.primary.base.color; | |
| // for (i, &height) in self.bars.iter().enumerate() { | |
| // let scaled_height = height * MAX_BAR_HEIGHT * bounds.height; | |
| // let x = i as f32 * bar_width + BAR_SPACING ; | |
| // let y = bounds.height - scaled_height; | |
| // // Draw main bar | |
| // // Create the Path for the rounded rectangle | |
| // let rounded_rect_path = Path::new(|p| { | |
| // p.rounded_rectangle( | |
| // Point::new(x, y / 2.0), | |
| // Size::new(actual_bar_width, scaled_height), | |
| // 15.into(), | |
| // ); | |
| // }); | |
| // // Draw (fill) the rounded rectangle | |
| // frame.fill(&rounded_rect_path, bar_color); | |
| // // let bar = Path::rectangle( | |
| // // Point::new(x, y / 2.0), | |
| // // Size::new(actual_bar_width, scaled_height), | |
| // // ); | |
| // // Simple solid color for bars | |
| // // frame.fill(&bar, bar_color); | |
| // // Draw peak indicator | |
| // // if self.peaks[i] > 0.01 { | |
| // // let peak_y = bounds.height - (self.peaks[i] * MAX_BAR_HEIGHT * bounds.height); | |
| // // let peak = Path::rectangle( | |
| // // Point::new(x, peak_y - 2.0), | |
| // // Size::new(actual_bar_width, 2.0), | |
| // // ); | |
| // // frame.fill(&peak, peak_color); | |
| // // } | |
| // } | |
| // vec![frame.into_geometry()] | |
| // } | |
| // } | |
| // mod wave_loader; | |
| use iced::{ | |
| widget::{button, canvas::{self, Canvas, Frame, Path, Program }, column, row}, Element, Length, Point, Renderer, Settings, Size, Subscription, Task, Theme | |
| }; | |
| use std::time::{Duration, Instant}; | |
| use std::f32::consts::PI; | |
| fn main() -> iced::Result { | |
| iced::application( | |
| SineWaveApp::new, | |
| SineWaveApp::update, | |
| SineWaveApp::view, | |
| ) | |
| .subscription(SineWaveApp::subscription) | |
| .title("iced • Now Playing") | |
| .window_size([400.0, 200.0]) | |
| .theme(|_| iced::Theme::GruvboxDark) | |
| .centered().run() | |
| } | |
| #[derive(Debug, Clone, Copy)] | |
| enum Message { | |
| Toggle, | |
| Tick(Instant), | |
| } | |
| struct SineWaveApp { | |
| playing: bool, | |
| phase: f32, | |
| last_tick: Instant, | |
| } | |
| impl SineWaveApp { | |
| fn new() -> (Self, Task<Message>) { | |
| ( | |
| SineWaveApp { | |
| playing: false, | |
| phase: 0.0, | |
| last_tick: Instant::now(), | |
| }, | |
| Task::none(), | |
| ) | |
| } | |
| fn title(&self) -> String { | |
| String::from("Iced Sinewave Widget [master]") | |
| } | |
| fn update(&mut self, message: Message) -> Task<Message> { | |
| match message { | |
| Message::Toggle => { | |
| self.playing = !self.playing; | |
| } | |
| Message::Tick(now) => { | |
| if self.playing { | |
| let delta = now.duration_since(self.last_tick).as_secs_f32(); | |
| self.phase = (self.phase + delta * 2.0 * PI * 0.6) % (2.0 * PI); // 0.5 Hz | |
| } | |
| self.last_tick = now; | |
| } | |
| } | |
| Task::none() | |
| } | |
| fn view(&self) -> Element<Message> { | |
| let sine_widget = Canvas::new(SineWaveWidget { phase: self.phase }) | |
| .width(Length::Fill) | |
| .height(Length::FillPortion(3)); | |
| let btn = button(if self.playing { "Pause" } else { "Play" }) | |
| .on_press(Message::Toggle); | |
| column![ | |
| sine_widget, | |
| row![btn].spacing(20), | |
| ] | |
| .padding(20) | |
| .spacing(20) | |
| .into() | |
| } | |
| fn subscription(&self) -> Subscription<Message> { | |
| iced::time::every(Duration::from_millis(16)) | |
| .map(Message::Tick) | |
| } | |
| } | |
| struct SineWaveWidget { | |
| phase: f32, | |
| } | |
| impl<Message> Program<Message> for SineWaveWidget { | |
| type State = (); | |
| fn draw( | |
| &self, | |
| _state: &Self::State, | |
| renderer: &Renderer, | |
| theme: &Theme, | |
| bounds: iced::Rectangle, | |
| _cursor: iced::mouse::Cursor, | |
| ) -> Vec<canvas::Geometry> { | |
| let mut frame = Frame::new(renderer,bounds.size()); | |
| let width = bounds.width; | |
| let height = bounds.height; | |
| let loader_width = width * 0.8; | |
| let loader_height = height * 0.6; | |
| let x_start = (width - loader_width) / 2.0; | |
| let y_base = height / 2.0; | |
| let bar_count = 7; | |
| let bar_width = loader_width / (bar_count as f32 * 2.0); | |
| let bar_spacing = loader_width / (bar_count as f32); | |
| for i in 0..bar_count { | |
| let x = x_start + i as f32 * bar_spacing; | |
| // Animate each bar with a phase offset | |
| let offset = i as f32 * 0.4; | |
| let wave = ((self.phase + offset).sin() + 1.0) / 2.0; | |
| let bar_h = loader_height * (0.3 + 0.7 * wave); | |
| let y = y_base - bar_h / 2.0; | |
| let rect = iced::Rectangle { | |
| x, | |
| y, | |
| width: bar_width, | |
| height: bar_h, | |
| }; | |
| let rounded_rect_path = Path::new(|p| { | |
| p.rounded_rectangle( | |
| Point::new(x, y / 2.0), | |
| rect.size(), | |
| 15.into(), | |
| ); | |
| }); | |
| let palette = theme.extended_palette(); | |
| let custom_color = palette.success.base.color; | |
| frame.fill( | |
| &rounded_rect_path,custom_color | |
| ); | |
| // // Optional: round the ends | |
| // frame.stroke_rectangle( | |
| // rect.position(), | |
| // rect.size(), | |
| // canvas::Stroke { | |
| // width: 1.5, | |
| // // color: Color::from_rgb(0.1, 0.4, 0.7), | |
| // ..Default::default() | |
| // }, | |
| // ); | |
| } | |
| vec![frame.into_geometry()] | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment