Skip to content

Instantly share code, notes, and snippets.

@Omustardo
Last active August 19, 2025 04:22
Show Gist options
  • Select an option

  • Save Omustardo/3d22d2de4edbfd01a90a2faf76a8446c to your computer and use it in GitHub Desktop.

Select an option

Save Omustardo/3d22d2de4edbfd01a90a2faf76a8446c to your computer and use it in GitHub Desktop.
progress button demo
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()))),
)
}
@Omustardo
Copy link
Author

egui.progress_button.demo.webm

@Omustardo
Copy link
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