Instantly share code, notes, and snippets.
Last active
August 19, 2025 04:22
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
-
Save Omustardo/3d22d2de4edbfd01a90a2faf76a8446c to your computer and use it in GitHub Desktop.
progress button demo
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 eframe::egui; | |
| use std::f32::consts::PI; | |
| #[derive(Debug, Clone, PartialEq)] | |
| pub enum ButtonState { | |
| Order, | |
| Processing, | |
| Complete, | |
| } | |
| pub struct ProgressButtonApp { | |
| linear_state: ButtonState, | |
| circular_state: ButtonState, | |
| linear_progress: f32, | |
| circular_progress: f32, | |
| } | |
| impl Default for ProgressButtonApp { | |
| fn default() -> Self { | |
| Self { | |
| linear_state: ButtonState::Order, | |
| circular_state: ButtonState::Order, | |
| linear_progress: 0.0, | |
| circular_progress: 0.0, | |
| } | |
| } | |
| } | |
| impl eframe::App for ProgressButtonApp { | |
| fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { | |
| egui::CentralPanel::default().show(ctx, |ui| { | |
| ui.heading("Progress Buttons Demo"); | |
| ui.add_space(20.0); | |
| ui.label("Linear progress button"); | |
| ui.add_space(10.0); | |
| if linear_progress_button(ui, &self.linear_state, &mut self.linear_progress).clicked() { | |
| match self.linear_state { | |
| ButtonState::Order => { | |
| self.linear_state = ButtonState::Processing; | |
| self.linear_progress = 0.0; | |
| } | |
| ButtonState::Processing => { | |
| // Can't click during processing | |
| } | |
| ButtonState::Complete => { | |
| self.linear_state = ButtonState::Order; | |
| self.linear_progress = 0.0; | |
| } | |
| } | |
| } | |
| ui.add_space(30.0); | |
| ui.label("Circular progress button"); | |
| ui.add_space(10.0); | |
| if circular_progress_button(ui, &self.circular_state, &mut self.circular_progress) | |
| .clicked() | |
| { | |
| match self.circular_state { | |
| ButtonState::Order => { | |
| self.circular_state = ButtonState::Processing; | |
| self.circular_progress = 0.0; | |
| } | |
| ButtonState::Processing => { | |
| // Can't click during processing | |
| } | |
| ButtonState::Complete => { | |
| self.circular_state = ButtonState::Order; | |
| self.circular_progress = 0.0; | |
| } | |
| } | |
| } | |
| }); | |
| // Update progress for processing states | |
| if matches!(self.linear_state, ButtonState::Processing) { | |
| self.linear_progress += ctx.input(|i| i.stable_dt * 0.5); // Progress speed | |
| if self.linear_progress >= 1.0 { | |
| self.linear_progress = 1.0; | |
| self.linear_state = ButtonState::Complete; | |
| } | |
| ctx.request_repaint_after(std::time::Duration::from_millis(16)); | |
| } | |
| if matches!(self.circular_state, ButtonState::Processing) { | |
| self.circular_progress += ctx.input(|i| i.stable_dt * 0.5); // Progress speed | |
| if self.circular_progress >= 1.0 { | |
| self.circular_progress = 1.0; | |
| self.circular_state = ButtonState::Complete; | |
| } | |
| ctx.request_repaint_after(std::time::Duration::from_millis(16)); | |
| } | |
| } | |
| } | |
| fn linear_progress_button( | |
| ui: &mut egui::Ui, | |
| state: &ButtonState, | |
| progress: &mut f32, | |
| ) -> egui::Response { | |
| let button_size = egui::vec2(120.0, 40.0); | |
| let (rect, response) = ui.allocate_exact_size(button_size, egui::Sense::click()); | |
| if ui.is_rect_visible(rect) { | |
| let rounding = egui::CornerRadius::same(20); | |
| // Base button color | |
| let base_color = match state { | |
| ButtonState::Order => egui::Color32::from_rgb(70, 100, 150), | |
| ButtonState::Processing => egui::Color32::from_rgb(80, 140, 200), | |
| ButtonState::Complete => egui::Color32::from_rgb(60, 120, 180), | |
| }; | |
| // Draw button background | |
| ui.painter().rect_filled(rect, rounding, base_color); | |
| match state { | |
| ButtonState::Order => { | |
| // Simple button | |
| let text_color = egui::Color32::WHITE; | |
| ui.painter().text( | |
| rect.center(), | |
| egui::Align2::CENTER_CENTER, | |
| "Order", | |
| egui::FontId::default(), | |
| text_color, | |
| ); | |
| } | |
| ButtonState::Processing => { | |
| // Linear progress animation with proper clipping | |
| // Create a clipping region for the progress bar | |
| let clip_rect = egui::Rect::from_min_size( | |
| rect.min, | |
| egui::vec2(rect.width() * *progress, rect.height()), | |
| ); | |
| ui.painter().with_clip_rect(clip_rect).rect_filled( | |
| rect, | |
| rounding, | |
| egui::Color32::from_rgb(100, 160, 220), | |
| ); | |
| let text_color = egui::Color32::WHITE; | |
| ui.painter().text( | |
| rect.center(), | |
| egui::Align2::CENTER_CENTER, | |
| "Processing...", | |
| egui::FontId::default(), | |
| text_color, | |
| ); | |
| } | |
| ButtonState::Complete => { | |
| // Checkmark | |
| let text_color = egui::Color32::WHITE; | |
| ui.painter().text( | |
| rect.center(), | |
| egui::Align2::CENTER_CENTER, | |
| "✓ Complete", | |
| egui::FontId::proportional(14.0), | |
| text_color, | |
| ); | |
| } | |
| } | |
| } | |
| response | |
| } | |
| fn circular_progress_button( | |
| ui: &mut egui::Ui, | |
| state: &ButtonState, | |
| progress: &mut f32, | |
| ) -> egui::Response { | |
| let button_size = egui::vec2(120.0, 40.0); | |
| let (rect, response) = ui.allocate_exact_size(button_size, egui::Sense::click()); | |
| if ui.is_rect_visible(rect) { | |
| let rounding = egui::CornerRadius::same(20); | |
| // Base button color | |
| let base_color = match state { | |
| ButtonState::Order => egui::Color32::from_rgb(70, 100, 150), | |
| ButtonState::Processing => egui::Color32::from_rgb(80, 140, 200), | |
| ButtonState::Complete => egui::Color32::from_rgb(60, 120, 180), | |
| }; | |
| // Draw button background | |
| ui.painter().rect_filled(rect, rounding, base_color); | |
| match state { | |
| ButtonState::Order => { | |
| // Simple button | |
| let text_color = egui::Color32::WHITE; | |
| ui.painter().text( | |
| rect.center(), | |
| egui::Align2::CENTER_CENTER, | |
| "Order", | |
| egui::FontId::default(), | |
| text_color, | |
| ); | |
| } | |
| ButtonState::Processing => { | |
| // Circular progress arc | |
| let spinner_radius = 8.0; | |
| let spinner_center = egui::pos2(rect.min.x + 20.0, rect.center().y); | |
| // Draw background circle | |
| ui.painter().circle_stroke( | |
| spinner_center, | |
| spinner_radius, | |
| egui::Stroke::new(2.0, egui::Color32::from_gray(100)), | |
| ); | |
| // Draw progress arc | |
| if *progress > 0.0 { | |
| let end_angle = -PI / 2.0 + (2.0 * PI * *progress); | |
| draw_arc( | |
| ui.painter(), | |
| spinner_center, | |
| spinner_radius, | |
| -PI / 2.0, // Start from top | |
| end_angle, | |
| 2.0, | |
| egui::Color32::WHITE, | |
| ); | |
| } | |
| let text_color = egui::Color32::WHITE; | |
| ui.painter().text( | |
| egui::pos2(rect.center().x + 10.0, rect.center().y), | |
| egui::Align2::CENTER_CENTER, | |
| "Processing...", | |
| egui::FontId::default(), | |
| text_color, | |
| ); | |
| } | |
| ButtonState::Complete => { | |
| // Checkmark | |
| let text_color = egui::Color32::WHITE; | |
| ui.painter().text( | |
| rect.center(), | |
| egui::Align2::CENTER_CENTER, | |
| "✓ Complete", | |
| egui::FontId::proportional(14.0), | |
| text_color, | |
| ); | |
| } | |
| } | |
| } | |
| response | |
| } | |
| fn draw_arc( | |
| painter: &egui::Painter, | |
| center: egui::Pos2, | |
| radius: f32, | |
| start_angle: f32, | |
| end_angle: f32, | |
| stroke_width: f32, | |
| color: egui::Color32, | |
| ) { | |
| let num_segments = 20; | |
| let angle_step = (end_angle - start_angle) / num_segments as f32; | |
| for i in 0..num_segments { | |
| let angle1 = start_angle + i as f32 * angle_step; | |
| let angle2 = start_angle + (i + 1) as f32 * angle_step; | |
| let p1 = center + radius * egui::vec2(angle1.cos(), angle1.sin()); | |
| let p2 = center + radius * egui::vec2(angle2.cos(), angle2.sin()); | |
| painter.line_segment([p1, p2], egui::Stroke::new(stroke_width, color)); | |
| } | |
| } | |
| fn main() -> Result<(), eframe::Error> { | |
| let options = eframe::NativeOptions { | |
| viewport: egui::ViewportBuilder::default().with_inner_size([400.0, 300.0]), | |
| ..Default::default() | |
| }; | |
| eframe::run_native( | |
| "Progress Buttons", | |
| options, | |
| Box::new(|_cc| Ok(Box::new(ProgressButtonApp::default()))), | |
| ) | |
| } |
Author
Author
Generated with Claude and a bit of manual review / cleanup. https://claude.ai/public/artifacts/e26e5b6c-ff05-49e3-bb84-4ba8e3e442af
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
egui.progress_button.demo.webm