Skip to main content

egui_cha_ds/atoms/core/
button.rs

1//! Button atom
2
3use egui::{Color32, Response, RichText, Stroke, Ui, Widget};
4use egui_cha::ViewCtx;
5
6use crate::Theme;
7
8/// Button variant for different styles
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
10pub enum ButtonVariant {
11    #[default]
12    Primary,
13    Secondary,
14    Outline,
15    Ghost,
16    Danger,
17    Warning,
18    Success,
19    Info,
20}
21
22/// A styled button component
23#[derive(Clone)]
24pub struct Button<'a> {
25    label: &'a str,
26    variant: ButtonVariant,
27    disabled: bool,
28    icon: Option<&'a str>,
29}
30
31impl<'a> Button<'a> {
32    /// Create a new primary button
33    pub fn new(label: &'a str) -> Self {
34        Self {
35            label,
36            variant: ButtonVariant::Primary,
37            disabled: false,
38            icon: None,
39        }
40    }
41
42    /// Create a primary variant button
43    pub fn primary(label: &'a str) -> Self {
44        Self::new(label).variant(ButtonVariant::Primary)
45    }
46
47    /// Create a secondary variant button
48    pub fn secondary(label: &'a str) -> Self {
49        Self::new(label).variant(ButtonVariant::Secondary)
50    }
51
52    /// Create an outline variant button
53    pub fn outline(label: &'a str) -> Self {
54        Self::new(label).variant(ButtonVariant::Outline)
55    }
56
57    /// Create a ghost variant button
58    pub fn ghost(label: &'a str) -> Self {
59        Self::new(label).variant(ButtonVariant::Ghost)
60    }
61
62    /// Create a danger variant button
63    pub fn danger(label: &'a str) -> Self {
64        Self::new(label).variant(ButtonVariant::Danger)
65    }
66
67    /// Create a warning variant button
68    pub fn warning(label: &'a str) -> Self {
69        Self::new(label).variant(ButtonVariant::Warning)
70    }
71
72    /// Create a success variant button
73    pub fn success(label: &'a str) -> Self {
74        Self::new(label).variant(ButtonVariant::Success)
75    }
76
77    /// Create an info variant button
78    pub fn info(label: &'a str) -> Self {
79        Self::new(label).variant(ButtonVariant::Info)
80    }
81
82    /// Set the variant
83    pub fn variant(mut self, variant: ButtonVariant) -> Self {
84        self.variant = variant;
85        self
86    }
87
88    /// Set disabled state
89    pub fn disabled(mut self, disabled: bool) -> Self {
90        self.disabled = disabled;
91        self
92    }
93
94    /// Add an icon prefix
95    pub fn icon(mut self, icon: &'a str) -> Self {
96        self.icon = Some(icon);
97        self
98    }
99
100    /// Show the button and emit msg on click
101    pub fn on_click<Msg>(self, ctx: &mut ViewCtx<'_, Msg>, msg: Msg) -> bool {
102        let clicked = self.show(ctx.ui);
103        if clicked {
104            ctx.emit(msg);
105        }
106        clicked
107    }
108
109    /// Show the button (returns true if clicked)
110    pub fn show(self, ui: &mut Ui) -> bool {
111        let text = match self.icon {
112            Some(icon) => format!("{} {}", icon, self.label),
113            None => self.label.to_string(),
114        };
115
116        let theme = Theme::current(ui.ctx());
117        let (fill, text_color, stroke) = self.variant_style(&theme);
118
119        let rich_text = RichText::new(text).color(text_color);
120        let mut button = egui::Button::new(rich_text).fill(fill);
121
122        if let Some(s) = stroke {
123            button = button.stroke(s);
124        }
125
126        let response = ui.add_enabled(!self.disabled, button);
127        response.clicked()
128    }
129
130    /// Get style colors for variant from theme
131    fn variant_style(&self, theme: &Theme) -> (Color32, Color32, Option<Stroke>) {
132        match self.variant {
133            ButtonVariant::Primary => (theme.primary, theme.primary_text, None),
134            ButtonVariant::Secondary => (theme.secondary, theme.secondary_text, None),
135            ButtonVariant::Outline => (
136                Color32::TRANSPARENT,
137                theme.text_primary,
138                Some(Stroke::new(1.0, theme.border)),
139            ),
140            ButtonVariant::Ghost => (Color32::TRANSPARENT, theme.text_secondary, None),
141            ButtonVariant::Danger => (theme.state_danger, theme.state_danger_text, None),
142            ButtonVariant::Warning => (theme.state_warning, theme.state_warning_text, None),
143            ButtonVariant::Success => (theme.state_success, theme.state_success_text, None),
144            ButtonVariant::Info => (theme.state_info, theme.state_info_text, None),
145        }
146    }
147}
148
149impl<'a> Widget for Button<'a> {
150    fn ui(self, ui: &mut Ui) -> Response {
151        let text = match self.icon {
152            Some(icon) => format!("{} {}", icon, self.label),
153            None => self.label.to_string(),
154        };
155
156        let theme = Theme::current(ui.ctx());
157        let (fill, text_color, stroke) = self.variant_style(&theme);
158
159        let rich_text = RichText::new(text).color(text_color);
160        let mut button = egui::Button::new(rich_text).fill(fill);
161
162        if let Some(s) = stroke {
163            button = button.stroke(s);
164        }
165
166        ui.add_enabled(!self.disabled, button)
167    }
168}