egui-uix 0.1.0

A collection of custom UI components for egui.
Documentation
use egui::{Response, Sense, StrokeKind, Ui, Widget, WidgetInfo, WidgetText, WidgetType};

/// A toggle switch widget.
///
/// Example:
/// ```
/// # let mut on = false;
/// # // Mocking a UI context is complex in doc tests without extra setup, so we just show usage syntax
/// # let _ = egui_uix::components::toggle::Toggle::new(&mut on, "Switch me");
/// ```
pub struct Toggle<'a> {
    on: &'a mut bool,
    text: WidgetText,
}

impl<'a> Toggle<'a> {
    /// Create a new toggle switch.
    ///
    /// * `on`: The state of the switch.
    /// * `text`: The label to display next to the switch.
    pub fn new(on: &'a mut bool, text: impl Into<WidgetText>) -> Self {
        Self {
            on,
            text: text.into(),
        }
    }
}

impl<'a> Widget for Toggle<'a> {
    fn ui(self, ui: &mut Ui) -> Response {
        let Toggle { on, text } = self;

        ui.horizontal(|ui| {
            let height = ui.spacing().interact_size.y;
            let desired_size = egui::vec2(2.0 * height, height);

            let (rect, mut response) = ui.allocate_exact_size(desired_size, Sense::click());
            if response.clicked() {
                *on = !*on;
                response.mark_changed();
            }
            response
                .widget_info(|| WidgetInfo::selected(WidgetType::Checkbox, true, *on, text.text()));

            if ui.is_rect_visible(rect) {
                let how_on = ui.ctx().animate_bool(response.id, *on);
                let visuals = ui.style().interact_selectable(&response, *on);
                let rect = rect.expand(visuals.expansion);
                let radius = 0.5 * rect.height();
                ui.painter().rect(
                    rect,
                    radius,
                    visuals.bg_fill,
                    visuals.bg_stroke,
                    StrokeKind::Outside,
                );
                let circle_x = egui::lerp((rect.left() + radius)..=(rect.right() - radius), how_on);
                let center = egui::pos2(circle_x, rect.center().y);
                ui.painter()
                    .circle(center, 0.75 * radius, visuals.bg_fill, visuals.fg_stroke);
            }

            let label_response = ui.add(egui::Label::new(text).sense(Sense::click()));
            if label_response.clicked() {
                *on = !*on;
                response.mark_changed();
            }

            response | label_response
        })
        .inner
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_toggle_creation() {
        let mut on = false;
        // Test that we can create the widget
        let _toggle = Toggle::new(&mut on, "Test");
    }
}