Skip to main content

native_windows_gui/controls/
radio_button.rs

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