Skip to main content

minifb_ui/ui/
checkbox.rs

1pub struct Checkbox {
2    pub pos_x: usize,
3    pub pos_y: usize,
4    pub size: usize,
5
6    pub checked: bool,
7    anim_t: f32,
8    pub anim_speed: f32,
9
10    pub box_color: crate::color::Color,
11    pub box_color_checked: crate::color::Color,
12    pub check_color: crate::color::Color,
13    pub border_color: crate::color::Color,
14    pub border_size: usize,
15    pub radius: usize,
16
17    pub label: Option<crate::ui::text::Text>,
18    pub label_size: f32,
19    pub label_color: crate::color::Color,
20    pub label_gap: usize,
21
22    lmb_was_down: bool,
23    toggled_this_frame: bool,
24}
25
26impl Default for Checkbox {
27    fn default() -> Self {
28        Self {
29            pos_x: 0,
30            pos_y: 0,
31            size: 20,
32            checked: false,
33            anim_t: 0.0,
34            anim_speed: 0.15,
35            box_color: crate::color::Color::new(40, 40, 50),
36            box_color_checked: crate::color::Color::new(108, 92, 231),
37            check_color: crate::color::Color::new(255, 255, 255),
38            border_color: crate::color::Color::new(100, 100, 120),
39            border_size: 1,
40            radius: 4,
41            label: None,
42            label_size: 14.0,
43            label_color: crate::color::Color::new(200, 200, 210),
44            label_gap: 8,
45            lmb_was_down: false,
46            toggled_this_frame: false,
47        }
48    }
49}
50
51impl Checkbox {
52    pub fn position(mut self, x: usize, y: usize) -> Self {
53        self.pos_x = x;
54        self.pos_y = y;
55        self
56    }
57
58    pub fn size(mut self, size: usize) -> Self {
59        self.size = size;
60        self
61    }
62
63    pub fn default_checked(mut self, checked: bool) -> Self {
64        self.checked = checked;
65        self.anim_t = if checked { 1.0 } else { 0.0 };
66        self
67    }
68
69    pub fn box_color(mut self, color: crate::color::Color) -> Self {
70        self.box_color = color;
71        self
72    }
73
74    pub fn box_color_checked(mut self, color: crate::color::Color) -> Self {
75        self.box_color_checked = color;
76        self
77    }
78
79    pub fn check_color(mut self, color: crate::color::Color) -> Self {
80        self.check_color = color;
81        self
82    }
83
84    pub fn border_color(mut self, color: crate::color::Color) -> Self {
85        self.border_color = color;
86        self
87    }
88
89    pub fn border(mut self, size: usize) -> Self {
90        self.border_size = size;
91        self
92    }
93
94    pub fn radius(mut self, radius: usize) -> Self {
95        self.radius = radius;
96        self
97    }
98
99    pub fn label(mut self, text: &str, font: crate::ttf::Font, size: f32) -> Self {
100        self.label = Some(crate::ui::text::Text::new(text, font));
101        self.label_size = size;
102        self
103    }
104
105    pub fn label_color(mut self, color: crate::color::Color) -> Self {
106        self.label_color = color;
107        self
108    }
109
110    pub fn label_gap(mut self, gap: usize) -> Self {
111        self.label_gap = gap;
112        self
113    }
114
115    pub fn is_checked(&self) -> bool {
116        self.checked
117    }
118
119    pub fn set_checked(&mut self, checked: bool) {
120        self.checked = checked;
121    }
122
123    pub fn just_toggled(&self) -> bool {
124        self.toggled_this_frame
125    }
126
127    fn update(&mut self, window: &mut crate::window::Window) {
128        self.toggled_this_frame = false;
129
130        let mouse = window.get_mouse_state();
131        let lmb = mouse.lmb_clicked;
132        let lmb_just = lmb && !self.lmb_was_down;
133
134        if lmb_just {
135            let mx = mouse.pos_x;
136            let my = mouse.pos_y;
137            let hit_w = self.size + self.label.as_ref().map_or(0, |l| {
138                self.label_gap + l.get_width(self.label_size)
139            });
140            let in_bounds = mx >= self.pos_x as f32
141                && mx < (self.pos_x + hit_w) as f32
142                && my >= self.pos_y as f32
143                && my < (self.pos_y + self.size) as f32;
144            if in_bounds {
145                self.checked = !self.checked;
146                self.toggled_this_frame = true;
147            }
148        }
149
150        self.lmb_was_down = lmb;
151
152        let target = if self.checked { 1.0 } else { 0.0 };
153        let diff = target - self.anim_t;
154        if diff.abs() > 0.001 {
155            self.anim_t += diff.signum() * self.anim_speed;
156            self.anim_t = self.anim_t.clamp(0.0, 1.0);
157        } else {
158            self.anim_t = target;
159        }
160    }
161
162    pub fn draw(&mut self, window: &mut crate::window::Window) {
163        self.update(window);
164
165        let bg = self.box_color.lerp(&self.box_color_checked, self.anim_t);
166
167        // Border
168        window.draw_rect_f(
169            self.pos_x, self.pos_y, self.size, self.size,
170            self.radius, &self.border_color, 0,
171        );
172
173        // Inner box
174        let inner_x = self.pos_x + self.border_size;
175        let inner_y = self.pos_y + self.border_size;
176        let inner_s = self.size.saturating_sub(self.border_size * 2);
177        let inner_r = self.radius.saturating_sub(self.border_size);
178        window.draw_rect_f(inner_x, inner_y, inner_s, inner_s, inner_r, &bg, 0);
179
180        // Checkmark (two lines forming a check)
181        if self.anim_t > 0.1 {
182            let s = self.size as f32;
183            let ox = self.pos_x as f32;
184            let oy = self.pos_y as f32;
185
186            // Check mark points (relative to box, scaled)
187            let p1 = (ox + s * 0.22, oy + s * 0.50);
188            let p2 = (ox + s * 0.42, oy + s * 0.72);
189            let p3 = (ox + s * 0.78, oy + s * 0.30);
190
191            let th = (s * 0.12).max(1.5) as usize;
192            let alpha_color = crate::color::Color::rgba(
193                self.check_color.r, self.check_color.g, self.check_color.b,
194                (self.anim_t * 255.0) as u8,
195            );
196
197            window.draw_line(p1.0 as isize, p1.1 as isize, p2.0 as isize, p2.1 as isize, th, alpha_color);
198            window.draw_line(p2.0 as isize, p2.1 as isize, p3.0 as isize, p3.1 as isize, th, alpha_color);
199        }
200
201        // Label
202        if let Some(ref label) = self.label {
203            let label_x = self.pos_x + self.size + self.label_gap;
204            let label_y = self.pos_y as f32 + (self.size as f32 - self.label_size) / 2.0;
205            window.draw_text(label_x, label_y as usize, label, self.label_size, &self.label_color);
206        }
207    }
208}