use gtk::prelude::*; use std::cell::RefCell; #[derive(Clone)] pub struct StepIndicator { container: gtk::Box, dots: RefCell>, } #[derive(Clone)] struct StepDot { button: gtk::Button, icon: gtk::Image, label: gtk::Label, } impl StepIndicator { pub fn new(step_names: &[String]) -> Self { let container = gtk::Box::builder() .orientation(gtk::Orientation::Horizontal) .halign(gtk::Align::Center) .spacing(0) .margin_top(8) .margin_bottom(8) .margin_start(12) .margin_end(12) .build(); let mut dots = Vec::new(); for (i, name) in step_names.iter().enumerate() { if i > 0 { // Connector line between dots let line = gtk::Separator::builder() .orientation(gtk::Orientation::Horizontal) .hexpand(false) .valign(gtk::Align::Center) .build(); line.set_size_request(24, -1); container.append(&line); } let dot_box = gtk::Box::builder() .orientation(gtk::Orientation::Vertical) .spacing(2) .halign(gtk::Align::Center) .build(); let icon = gtk::Image::builder() .icon_name("radio-symbolic") .pixel_size(16) .build(); let button = gtk::Button::builder() .child(&icon) .has_frame(false) .tooltip_text(format!("Step {}: {}", i + 1, name)) .sensitive(false) .build(); button.add_css_class("circular"); let label = gtk::Label::builder() .label(name) .css_classes(["caption"]) .build(); dot_box.append(&button); dot_box.append(&label); container.append(&dot_box); dots.push(StepDot { button, icon, label, }); } // First step starts as current if let Some(first) = dots.first() { first.icon.set_icon_name(Some("radio-checked-symbolic")); first.button.set_sensitive(true); first.label.add_css_class("accent"); } Self { container, dots: RefCell::new(dots), } } pub fn set_current(&self, index: usize) { let dots = self.dots.borrow(); for (i, dot) in dots.iter().enumerate() { if i == index { dot.icon.set_icon_name(Some("radio-checked-symbolic")); dot.button.set_sensitive(true); dot.label.add_css_class("accent"); } else if dot.icon.icon_name().as_deref() != Some("emblem-ok-symbolic") { dot.icon.set_icon_name(Some("radio-symbolic")); dot.label.remove_css_class("accent"); } } } pub fn set_completed(&self, index: usize) { let dots = self.dots.borrow(); if let Some(dot) = dots.get(index) { dot.icon.set_icon_name(Some("emblem-ok-symbolic")); dot.button.set_sensitive(true); dot.label.remove_css_class("accent"); } } pub fn widget(&self) -> >k::Box { &self.container } } // Allow appending the step indicator to containers impl std::ops::Deref for StepIndicator { type Target = gtk::Box; fn deref(&self) -> &Self::Target { &self.container } }