native_windows_gui/controls/
check_box.rs

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