Skip to main content

appcui/ui/checkbox/
checkbox.rs

1use super::Type;
2use crate::prelude::*;
3use crate::ui::checkbox::events::EventData;
4
5#[CustomControl(overwrite=OnPaint+OnDefaultAction+OnKeyPressed+OnMouseEvent,internal=true)]
6pub struct CheckBox {
7    caption: Caption,
8    checked: bool,
9    check_symbol: Symbol,
10    uncheck_symbol: Symbol,
11    symbol_width: u8,
12}
13
14impl CheckBox {
15    /// Creates a new checkbox with the specified caption, layout and initial checked state.
16    ///
17    /// # Example
18    /// ```rust, no_run
19    /// use appcui::prelude::*;
20    ///
21    /// let mut checkbox = CheckBox::new("Check me", layout!("x:1,y:1,w:20,h:1"), false);
22    /// ```
23    pub fn new(caption: &str, layout: Layout, checked: bool) -> Self {
24        Self::with_type(caption, layout, checked, Type::Standard)
25    }
26
27    pub fn with_type(caption: &str, layout: Layout, checked: bool, checkbox_type: Type) -> Self {
28        let cs = Symbol::new(checkbox_type.check_symbol());
29        let us = Symbol::new(checkbox_type.uncheck_symbol());
30        if cs.width() != us.width() {
31            panic!("CheckBox: check and uncheck symbols must have the same width (1, 2 or 3 characters)");
32        }
33        if cs.width() == 0 {
34            panic!("CheckBox: check and uncheck symbols must have at least one character");
35        }
36        let symbol_width = cs.width() + 1;
37        let mut cb = CheckBox {
38            base: ControlBase::with_status_flags(layout, StatusFlags::Visible | StatusFlags::Enabled | StatusFlags::AcceptInput),
39            caption: Caption::new(caption, ExtractHotKeyMethod::AltPlusKey),
40            checked,
41            check_symbol: cs,
42            uncheck_symbol: us,
43            symbol_width,
44        };
45        cb.set_size_bounds(5, 1, u16::MAX, u16::MAX);
46        let hotkey = cb.caption.hotkey();
47        cb.set_hotkey(hotkey);
48        cb
49    }
50
51    /// Returns **true** if the checkbox is checked, **false** otherwise.
52    #[inline(always)]
53    pub fn is_checked(&self) -> bool {
54        self.checked
55    }
56
57    /// Sets the checkbox state to checked or unchecked.
58    #[inline(always)]
59    pub fn set_checked(&mut self, checked: bool) {
60        self.checked = checked;
61    }
62
63    /// Sets the checkbox caption. The caption can contain a hotkey, which is indicated by an ampersand (&) before the character.
64    pub fn set_caption(&mut self, caption: &str) {
65        self.caption.set_text(caption, ExtractHotKeyMethod::AltPlusKey);
66        let hotkey = self.caption.hotkey();
67        self.set_hotkey(hotkey);
68    }
69    /// Returns the checkbox caption.
70    #[inline(always)]
71    pub fn caption(&self) -> &str {
72        self.caption.text()
73    }
74}
75impl OnPaint for CheckBox {
76    fn on_paint(&self, surface: &mut Surface, theme: &Theme) {
77        let attr_text = match () {
78            _ if !self.is_enabled() => theme.text.inactive,
79            _ if self.has_focus() => theme.text.focused,
80            _ if self.is_mouse_over() => theme.text.hovered,
81            _ => theme.text.normal,
82        };
83
84        let enabled = self.is_enabled();
85        let col_hot_key = if enabled { theme.text.hot_key } else { theme.text.inactive };
86        let sz = self.size();
87
88        if sz.width > self.symbol_width as u32 {
89            let mut format = TextFormatBuilder::new()
90                .position(self.symbol_width as i32, 0)
91                .attribute(attr_text)
92                .align(TextAlignment::Left)
93                .chars_count(self.caption.chars_count() as u16)
94                .build();
95            if sz.height > 1 {
96                format.set_wrap_type(WrapType::WordWrap(sz.width as u16 - self.symbol_width as u16));
97            }
98            if self.caption.has_hotkey() {
99                format.set_hotkey(col_hot_key, self.caption.hotkey_pos().unwrap() as u32);
100            }
101            surface.write_text(self.caption.text(), &format);
102        }
103        if self.checked {
104            let attr_symbol = if enabled { theme.symbol.checked } else { theme.symbol.inactive };
105            let attr_margin = if self.symbol_width == 4 { attr_text } else { attr_symbol };
106            self.check_symbol.paint(surface, 0, 0, attr_margin, attr_symbol, attr_margin);
107        } else {
108            let attr_symbol = if enabled { theme.symbol.unchecked } else { theme.symbol.inactive };
109            let attr_margin = if self.symbol_width == 4 { attr_text } else { attr_symbol };
110            self.uncheck_symbol.paint(surface, 0, 0, attr_margin, attr_symbol, attr_margin);
111        }
112        if self.has_focus() {
113            surface.set_cursor(if self.symbol_width == 4 { 1 } else { 0 }, 0);
114        }
115    }
116}
117impl OnDefaultAction for CheckBox {
118    fn on_default_action(&mut self) {
119        self.checked = !self.checked;
120        self.raise_event(ControlEvent {
121            emitter: self.handle,
122            receiver: self.event_processor,
123            data: ControlEventData::CheckBox(EventData { checked: self.checked }),
124        });
125    }
126}
127impl OnKeyPressed for CheckBox {
128    fn on_key_pressed(&mut self, key: Key, _character: char) -> EventProcessStatus {
129        if (key.modifier == KeyModifier::None) && ((key.code == KeyCode::Space) || (key.code == KeyCode::Enter)) {
130            self.on_default_action();
131            return EventProcessStatus::Processed;
132        }
133        EventProcessStatus::Ignored
134    }
135}
136impl OnMouseEvent for CheckBox {
137    fn on_mouse_event(&mut self, event: &MouseEvent) -> EventProcessStatus {
138        match event {
139            MouseEvent::Enter => {
140                if self.caption.chars_count() > (self.size().width - 4) as usize {
141                    self.show_tooltip(self.caption.text());
142                }
143                EventProcessStatus::Processed
144            }
145            MouseEvent::Leave => EventProcessStatus::Processed,
146            MouseEvent::Released(data) => {
147                if self.is_coord_in_control(data.x, data.y) {
148                    self.on_default_action();
149                }
150                EventProcessStatus::Processed
151            }
152            _ => EventProcessStatus::Ignored,
153        }
154    }
155}