pub struct Metronome { position: f32, // 0.0 - 1.0 } impl Metronome { pub fn new(position: f32) -> Self { Self { position: position.clamp(0.0, 1.0), } } } impl egui::Widget for Metronome { fn ui(self, ui: &mut egui::Ui) -> egui::Response { let sf = ui.available_width() / 100.0; // Simple scaling values let rail_height = sf * 1.0; let circle_radius = sf * 2.0; let h_padding = sf * 5.0; let v_padding = sf * 3.0; // Colors let rail_color = egui::Color32::from_rgb(51, 51, 51); // #333333 let circle_color = egui::Color32::from_rgb(0, 255, 136); // #00ff88 // Calculate widget size let width = ui.available_width(); let height = rail_height + 2.0 * v_padding; let size = egui::vec2(width, height); // Allocate the space we need let (rect, response) = ui.allocate_exact_size(size, egui::Sense::hover()); // Calculate rail position (centered) let rail_left = rect.min.x + h_padding; let rail_right = rect.max.x - h_padding; let rail_width = rail_right - rail_left; let rail_center_y = rect.center().y; let rail_top = rail_center_y - rail_height / 2.0; let rail_bottom = rail_center_y + rail_height / 2.0; let rail_rect = egui::Rect::from_min_max( egui::pos2(rail_left, rail_top), egui::pos2(rail_right, rail_bottom), ); // Calculate circle position let circle_x = rail_left + self.position * rail_width; let circle_center = egui::pos2(circle_x, rail_center_y); // Draw rail ui.painter().rect_filled( rail_rect, egui::Rounding::same(rail_height / 2.0), rail_color, ); // Draw circle ui.painter() .circle_filled(circle_center, circle_radius, circle_color); response } }