pixels_graphics_lib/ui/
checkbox.rs

1use crate::prelude::*;
2use crate::ui::layout::LayoutView;
3use crate::ui::prelude::CheckboxStyle;
4use crate::ui::{PixelView, ViewState};
5
6#[derive(Debug)]
7pub struct Checkbox {
8    bounds: Rect,
9    text: Text,
10    checked: bool,
11    style: CheckboxStyle,
12    state: ViewState,
13}
14
15impl Checkbox {
16    pub fn new<P: Into<Coord>>(pos: P, text: &str, checked: bool, style: &CheckboxStyle) -> Self {
17        let pos = pos.into();
18        let (w, h) = style.font.measure(text);
19        let w = w + style.spacing + style.check_box.width() as usize;
20        let bounds = Rect::new_with_size(pos, w, h);
21        let text = Checkbox::layout(style, bounds.clone(), text);
22        Self {
23            bounds,
24            text,
25            checked,
26            style: style.clone(),
27            state: ViewState::Normal,
28        }
29    }
30
31    fn text(&self) -> String {
32        self.text
33            .contents()
34            .iter()
35            .map(|bytes| String::from_utf8_lossy(bytes).to_string())
36            .collect::<Vec<String>>()
37            .join("\n")
38    }
39
40    fn layout(style: &CheckboxStyle, bounds: Rect, text: &str) -> Text {
41        let text_width = bounds
42            .width()
43            .saturating_sub(style.check_box.width() as usize);
44        let pos = bounds.top_left()
45            + (
46                style.check_box.width() as usize + style.spacing,
47                bounds.height() / 2,
48            );
49        Text::new(
50            text,
51            TextPos::px(pos),
52            (
53                TRANSPARENT,
54                style.font,
55                WrappingStrategy::SpaceBeforeCol(style.font.px_to_cols(text_width)),
56                Positioning::LeftCenter,
57            ),
58        )
59    }
60}
61
62impl Checkbox {
63    /// # Returns
64    /// * `None` - Click was outside view or view is disabled
65    /// * `Some(bool)` - new checked state
66    #[must_use]
67    pub fn on_mouse_click(&mut self, down: Coord, up: Coord) -> Option<bool> {
68        if self.state != ViewState::Disabled {
69            if self.bounds.contains(down) && self.bounds.contains(up) {
70                self.checked = !self.checked;
71                Some(self.checked)
72            } else {
73                None
74            }
75        } else {
76            None
77        }
78    }
79
80    pub fn is_checked(&self) -> bool {
81        self.checked
82    }
83
84    pub fn set_checked(&mut self, checked: bool) {
85        self.checked = checked;
86    }
87}
88
89impl PixelView for Checkbox {
90    fn set_position(&mut self, top_left: Coord) {
91        self.set_bounds(self.bounds.move_to(top_left));
92    }
93
94    fn bounds(&self) -> &Rect {
95        &self.bounds
96    }
97
98    fn render(&self, graphics: &mut Graphics, mouse: &MouseData) {
99        let hover = self.bounds.contains(mouse.xy);
100        let (err, dir) = self.state.get_err_dis();
101        graphics.draw_indexed_image(self.bounds.top_left(), &self.style.check_box);
102        if self.checked {
103            graphics.draw_indexed_image(self.bounds.top_left(), &self.style.checked_icon);
104        }
105        if let Some(color) = self.style.text.get(hover, err, dir) {
106            self.text.with_color(color).render(graphics);
107        }
108    }
109
110    fn update(&mut self, _: &Timing) {}
111
112    fn set_state(&mut self, new_state: ViewState) {
113        self.state = new_state;
114    }
115
116    fn get_state(&self) -> ViewState {
117        self.state
118    }
119}
120
121impl LayoutView for Checkbox {
122    fn set_bounds(&mut self, bounds: Rect) {
123        let text = Checkbox::layout(
124            &self.style,
125            bounds
126                .clone()
127                .translate_by(coord!(0, self.text.formatting().font().spacing())),
128            &self.text(),
129        );
130        self.bounds = bounds;
131        self.text = text;
132    }
133}