//! Waveform rendering widget: custom egui Painter-based display with playback position and click-to-seek. use egui; use audiofiles_core::analysis::waveform::WaveformData; use crate::ui::theme; /// Draw a waveform display. Returns the response for click-to-seek handling. pub fn draw_waveform( ui: &mut egui::Ui, waveform: &WaveformData, playback_pos: Option, height: f32, ) -> egui::Response { let available_width = ui.available_width(); let size = egui::vec2(available_width, height); let (rect, response) = ui.allocate_exact_size(size, egui::Sense::click()); if !ui.is_rect_visible(rect) { return response; } let painter = ui.painter_at(rect); // Dark background painter.rect_filled(rect, 4.0, theme::bg_primary()); // Center line (zero crossing) let center_y = rect.center().y; painter.line_segment( [ egui::pos2(rect.left(), center_y), egui::pos2(rect.right(), center_y), ], egui::Stroke::new(0.5, theme::text_muted()), ); // Draw waveform peaks let num_buckets = waveform.num_buckets; if num_buckets > 0 && !waveform.peaks.is_empty() { let bucket_width = rect.width() / num_buckets as f32; let half_height = rect.height() / 2.0; for i in 0..num_buckets { let pair_idx = i * 2; if pair_idx + 1 >= waveform.peaks.len() { break; } let min_val = waveform.peaks[pair_idx]; let max_val = waveform.peaks[pair_idx + 1]; let x = rect.left() + (i as f32 + 0.5) * bucket_width; let y_min = center_y - max_val * half_height; let y_max = center_y - min_val * half_height; painter.line_segment( [egui::pos2(x, y_min), egui::pos2(x, y_max)], egui::Stroke::new(1.0, theme::accent_green()), ); } } // Playback position line if let Some(pos) = playback_pos { let x = rect.left() + pos.clamp(0.0, 1.0) * rect.width(); painter.line_segment( [egui::pos2(x, rect.top()), egui::pos2(x, rect.bottom())], egui::Stroke::new(1.5, theme::text_primary()), ); } response }