Skip to main content

native_windows_gui2/controls/
check_box.rs

1use super::{ControlBase, ControlHandle};
2use crate::win32::{base_helper::check_hwnd, window_helper as wh};
3use crate::{Font, NwgError, RawEventHandler};
4use std::cell::RefCell;
5use winapi::shared::windef::HBRUSH;
6use winapi::um::{
7    wingdi::DeleteObject,
8    winuser::{BS_AUTO3STATE, BS_AUTOCHECKBOX, BS_PUSHLIKE, WS_DISABLED, WS_TABSTOP, WS_VISIBLE},
9};
10
11const NOT_BOUND: &'static str = "CheckBox is not yet bound to a winapi object";
12const BAD_HANDLE: &'static str = "INTERNAL ERROR: CheckBox handle is not HWND!";
13
14bitflags! {
15    /**
16        The CheckBox flags
17
18        * NONE:     No flags. Equivalent to a invisible default checkbox.
19        * VISIBLE:  The checkbox is immediatly visible after creation
20        * DISABLED: The checkbox cannot be interacted with by the user. It also has a grayed out look.
21        * TRISTATE: The checkbox will have a 3rd state
22        * PUSHLIKE: The checkbox will look like a regular button
23        * TAB_STOP: The control can be selected using tab navigation
24    */
25    pub struct CheckBoxFlags: u32 {
26        const NONE = 0;
27        const VISIBLE = WS_VISIBLE;
28        const DISABLED = WS_DISABLED;
29        const TRISTATE = BS_AUTO3STATE;
30        const PUSHLIKE = BS_PUSHLIKE;
31        const TAB_STOP = WS_TABSTOP;
32    }
33}
34
35/// Represents the check status of a checkbox
36#[derive(Debug, Copy, Clone, PartialEq, Eq)]
37pub enum CheckBoxState {
38    Checked,
39    Unchecked,
40
41    /// New state for the tristate checkbox
42    Indeterminate,
43}
44
45/**
46A check box consists of a square box and an application-defined labe that indicates a choice the user can make by selecting the button.
47Applications typically display check boxes to enable the user to choose one or more options that are not mutually exclusive.
48
49CheckBox is not behind any features.
50
51**Builder parameters:**
52  * `parent`:           **Required.** The checkbox parent container.
53  * `text`:             The checkbox text.
54  * `size`:             The checkbox size.
55  * `position`:         The checkbox position.
56  * `enabled`:          If the checkbox can be used by the user. It also has a grayed out look if disabled.
57  * `flags`:            A combination of the CheckBoxFlags values.
58  * `ex_flags`:         A combination of win32 window extended flags. Unlike `flags`, ex_flags must be used straight from winapi
59  * `font`:             The font used for the checkbox text
60  * `background_color`: The background color of the checkbox. Defaults to the default window background (light gray)
61  * `check_state`:      The default check state
62  * `focus`:            The control receive focus after being created
63
64**Control events:**
65  * `OnButtonClick`: When the checkbox is clicked once by the user
66  * `OnButtonDoubleClick`: When the checkbox is clicked twice rapidly by the user
67  * `MousePress(_)`: Generic mouse press events on the checkbox
68  * `OnMouseMove`: Generic mouse mouse event
69  * `OnMouseWheel`: Generic mouse wheel event
70
71
72```rust
73use native_windows_gui2 as nwg;
74fn build_checkbox(button: &mut nwg::CheckBox, window: &nwg::Window, font: &nwg::Font) {
75    nwg::CheckBox::builder()
76        .text("Hello")
77        .flags(nwg::CheckBoxFlags::VISIBLE)
78        .font(Some(font))
79        .parent(window)
80        .build(button);
81}
82```
83*/
84#[derive(Default)]
85pub struct CheckBox {
86    pub handle: ControlHandle,
87    background_brush: Option<HBRUSH>,
88    handler0: RefCell<Option<RawEventHandler>>,
89}
90
91impl CheckBox {
92    pub fn builder<'a>() -> CheckBoxBuilder<'a> {
93        CheckBoxBuilder {
94            text: "A checkbox",
95            size: (100, 25),
96            position: (0, 0),
97            enabled: true,
98            focus: false,
99            background_color: None,
100            check_state: CheckBoxState::Unchecked,
101            flags: None,
102            ex_flags: 0,
103            font: None,
104            parent: None,
105        }
106    }
107
108    /// Return `true` if the checkbox can have a third state or `false` otherwise
109    pub fn tristate(&self) -> bool {
110        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
111        let style = wh::get_style(handle);
112        style & BS_AUTO3STATE == BS_AUTO3STATE
113    }
114
115    /// Sets or unsets the checkbox as tristate
116    pub fn set_tristate(&self, tri: bool) {
117        use winapi::shared::minwindef::WPARAM;
118        use winapi::um::winuser::BM_SETSTYLE;
119
120        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
121
122        let style = match tri {
123            true => BS_AUTO3STATE,
124            false => BS_AUTOCHECKBOX,
125        };
126
127        wh::send_message(handle, BM_SETSTYLE, style as WPARAM, 1);
128    }
129
130    /// Return the check state of the check box
131    pub fn check_state(&self) -> CheckBoxState {
132        use winapi::um::winuser::{BM_GETCHECK, BST_CHECKED, BST_INDETERMINATE, BST_UNCHECKED};
133
134        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
135
136        match wh::send_message(handle, BM_GETCHECK, 0, 0) as usize {
137            BST_UNCHECKED => CheckBoxState::Unchecked,
138            BST_CHECKED => CheckBoxState::Checked,
139            BST_INDETERMINATE => CheckBoxState::Indeterminate,
140            _ => unreachable!(),
141        }
142    }
143
144    /// Sets the check state of the check box
145    pub fn set_check_state(&self, state: CheckBoxState) {
146        use winapi::shared::minwindef::WPARAM;
147        use winapi::um::winuser::{BM_SETCHECK, BST_CHECKED, BST_INDETERMINATE, BST_UNCHECKED};
148
149        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
150
151        let x = match state {
152            CheckBoxState::Unchecked => BST_UNCHECKED,
153            CheckBoxState::Checked => BST_CHECKED,
154            CheckBoxState::Indeterminate => BST_INDETERMINATE,
155        };
156
157        wh::send_message(handle, BM_SETCHECK, x as WPARAM, 0);
158    }
159
160    /// Return the font of the control
161    pub fn font(&self) -> Option<Font> {
162        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
163
164        let font_handle = wh::get_window_font(handle);
165        if font_handle.is_null() {
166            None
167        } else {
168            Some(Font {
169                handle: font_handle,
170            })
171        }
172    }
173
174    /// Set the font of the control
175    pub fn set_font(&self, font: Option<&Font>) {
176        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
177
178        wh::set_window_font(handle, font.map(|f| f.handle), true);
179    }
180
181    /// Return true if the control currently has the keyboard focus
182    pub fn focus(&self) -> bool {
183        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
184        wh::get_focus(handle)
185    }
186
187    /// Set the keyboard focus on the button.
188    pub fn set_focus(&self) {
189        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
190
191        wh::set_focus(handle);
192    }
193
194    /// Return true if the control user can interact with the control, return false otherwise
195    pub fn enabled(&self) -> bool {
196        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
197        wh::get_window_enabled(handle)
198    }
199
200    /// Enable or disable the control
201    pub fn set_enabled(&self, v: bool) {
202        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
203        wh::set_window_enabled(handle, v)
204    }
205
206    /// Return true if the control is visible to the user. Will return true even if the
207    /// control is outside of the parent client view (ex: at the position (10000, 10000))
208    pub fn visible(&self) -> bool {
209        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
210        wh::get_window_visibility(handle)
211    }
212
213    /// Show or hide the control to the user
214    pub fn set_visible(&self, v: bool) {
215        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
216        wh::set_window_visibility(handle, v)
217    }
218
219    /// Return the size of the check box in the parent window
220    pub fn size(&self) -> (u32, u32) {
221        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
222        wh::get_window_size(handle)
223    }
224
225    /// Set the size of the check box in the parent window
226    pub fn set_size(&self, x: u32, y: u32) {
227        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
228        wh::set_window_size(handle, x, y, false)
229    }
230
231    /// Return the position of the check box in the parent window
232    pub fn position(&self) -> (i32, i32) {
233        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
234        wh::get_window_position(handle)
235    }
236
237    /// Set the position of the check box in the parent window
238    pub fn set_position(&self, x: i32, y: i32) {
239        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
240        wh::set_window_position(handle, x, y)
241    }
242
243    /// Return the check box label
244    pub fn text(&self) -> String {
245        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
246        wh::get_window_text(handle)
247    }
248
249    /// Set the check box label
250    pub fn set_text<'a>(&self, v: &'a str) {
251        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
252        wh::set_window_text(handle, v)
253    }
254
255    /// Winapi class name used during control creation
256    pub fn class_name(&self) -> &'static str {
257        "BUTTON"
258    }
259
260    /// Winapi base flags used during window creation
261    pub fn flags(&self) -> u32 {
262        WS_VISIBLE | WS_TABSTOP
263    }
264
265    /// Winapi flags required by the control
266    pub fn forced_flags(&self) -> u32 {
267        use winapi::um::winuser::{BS_NOTIFY, WS_CHILD};
268
269        BS_NOTIFY | WS_CHILD
270    }
271
272    /// Change the checkbox background color.
273    fn hook_background_color(&mut self, c: [u8; 3]) {
274        use crate::bind_raw_event_handler_inner;
275        use winapi::shared::{basetsd::UINT_PTR, minwindef::LRESULT, windef::HWND};
276        use winapi::um::wingdi::{CreateSolidBrush, RGB};
277        use winapi::um::winuser::WM_CTLCOLORSTATIC;
278
279        if self.handle.blank() {
280            panic!("{}", NOT_BOUND);
281        }
282        let handle = self.handle.hwnd().expect(BAD_HANDLE);
283
284        let parent_handle = ControlHandle::Hwnd(wh::get_window_parent(handle));
285
286        let brush = unsafe { CreateSolidBrush(RGB(c[0], c[1], c[2])) };
287        self.background_brush = Some(brush);
288
289        let handler = bind_raw_event_handler_inner(
290            &parent_handle,
291            handle as UINT_PTR,
292            move |_hwnd, msg, _w, l| {
293                match msg {
294                    WM_CTLCOLORSTATIC => {
295                        let child = l as HWND;
296                        if child == handle {
297                            return Some(brush as LRESULT);
298                        }
299                    }
300                    _ => {}
301                }
302
303                None
304            },
305        );
306
307        *self.handler0.borrow_mut() = Some(handler.unwrap());
308    }
309}
310
311impl Drop for CheckBox {
312    fn drop(&mut self) {
313        use crate::unbind_raw_event_handler;
314
315        let handler = self.handler0.borrow();
316        if let Some(h) = handler.as_ref() {
317            drop(unbind_raw_event_handler(h));
318        }
319
320        if let Some(bg) = self.background_brush {
321            unsafe {
322                DeleteObject(bg as _);
323            }
324        }
325
326        self.handle.destroy();
327    }
328}
329
330pub struct CheckBoxBuilder<'a> {
331    text: &'a str,
332    size: (i32, i32),
333    position: (i32, i32),
334    enabled: bool,
335    focus: bool,
336    background_color: Option<[u8; 3]>,
337    check_state: CheckBoxState,
338    flags: Option<CheckBoxFlags>,
339    ex_flags: u32,
340    font: Option<&'a Font>,
341    parent: Option<ControlHandle>,
342}
343
344impl<'a> CheckBoxBuilder<'a> {
345    pub fn flags(mut self, flags: CheckBoxFlags) -> CheckBoxBuilder<'a> {
346        self.flags = Some(flags);
347        self
348    }
349
350    pub fn ex_flags(mut self, flags: u32) -> CheckBoxBuilder<'a> {
351        self.ex_flags = flags;
352        self
353    }
354
355    pub fn text(mut self, text: &'a str) -> CheckBoxBuilder<'a> {
356        self.text = text;
357        self
358    }
359
360    pub fn size(mut self, size: (i32, i32)) -> CheckBoxBuilder<'a> {
361        self.size = size;
362        self
363    }
364
365    pub fn position(mut self, pos: (i32, i32)) -> CheckBoxBuilder<'a> {
366        self.position = pos;
367        self
368    }
369
370    pub fn enabled(mut self, e: bool) -> CheckBoxBuilder<'a> {
371        self.enabled = e;
372        self
373    }
374
375    pub fn focus(mut self, focus: bool) -> CheckBoxBuilder<'a> {
376        self.focus = focus;
377        self
378    }
379
380    pub fn check_state(mut self, check: CheckBoxState) -> CheckBoxBuilder<'a> {
381        self.check_state = check;
382        self
383    }
384
385    pub fn background_color(mut self, color: Option<[u8; 3]>) -> CheckBoxBuilder<'a> {
386        self.background_color = color;
387        self
388    }
389
390    pub fn font(mut self, font: Option<&'a Font>) -> CheckBoxBuilder<'a> {
391        self.font = font;
392        self
393    }
394
395    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> CheckBoxBuilder<'a> {
396        self.parent = Some(p.into());
397        self
398    }
399
400    pub fn build(self, out: &mut CheckBox) -> Result<(), NwgError> {
401        let mut flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
402        if flags & BS_AUTO3STATE == 0 {
403            flags |= BS_AUTOCHECKBOX;
404        }
405
406        let parent = match self.parent {
407            Some(p) => Ok(p),
408            None => Err(NwgError::no_parent("CheckBox")),
409        }?;
410
411        // Drop the old object
412        *out = CheckBox::default();
413
414        out.handle = ControlBase::build_hwnd()
415            .class_name(out.class_name())
416            .forced_flags(out.forced_flags())
417            .flags(flags)
418            .ex_flags(self.ex_flags)
419            .size(self.size)
420            .position(self.position)
421            .text(self.text)
422            .parent(Some(parent))
423            .build()?;
424
425        if self.font.is_some() {
426            out.set_font(self.font);
427        } else {
428            out.set_font(Font::global_default().as_ref());
429        }
430
431        out.set_enabled(self.enabled);
432
433        if self.background_color.is_some() {
434            out.hook_background_color(self.background_color.unwrap());
435        }
436
437        if self.focus {
438            out.set_focus();
439        }
440
441        out.set_check_state(self.check_state);
442
443        Ok(())
444    }
445}
446
447impl PartialEq for CheckBox {
448    fn eq(&self, other: &Self) -> bool {
449        self.handle == other.handle
450    }
451}