Skip to main content

native_windows_gui2/controls/
radio_button.rs

1use super::{ControlBase, ControlHandle};
2use crate::win32::base_helper::check_hwnd;
3use crate::win32::window_helper as wh;
4use crate::{Font, NwgError, RawEventHandler, unbind_raw_event_handler};
5use std::cell::RefCell;
6use winapi::shared::windef::HBRUSH;
7use winapi::um::{
8    wingdi::DeleteObject,
9    winuser::{WS_DISABLED, WS_GROUP, WS_TABSTOP, WS_VISIBLE},
10};
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_gui2 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_gui2 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    pub fn builder<'a>() -> RadioButtonBuilder<'a> {
120        RadioButtonBuilder {
121            text: "A radio button",
122            size: (100, 25),
123            position: (0, 0),
124            focus: false,
125            background_color: None,
126            check_state: RadioButtonState::Unchecked,
127            flags: None,
128            ex_flags: 0,
129            font: None,
130            parent: None,
131        }
132    }
133
134    /// Return the check state of the check box
135    pub fn check_state(&self) -> RadioButtonState {
136        use winapi::um::winuser::{BM_GETCHECK, BST_CHECKED, BST_UNCHECKED};
137
138        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
139
140        match wh::send_message(handle, BM_GETCHECK, 0, 0) as usize {
141            BST_UNCHECKED => RadioButtonState::Unchecked,
142            BST_CHECKED => RadioButtonState::Checked,
143            _ => unreachable!(),
144        }
145    }
146
147    /// Sets the check state of the check box
148    pub fn set_check_state(&self, state: RadioButtonState) {
149        use winapi::shared::minwindef::WPARAM;
150        use winapi::um::winuser::{BM_SETCHECK, BST_CHECKED, BST_UNCHECKED};
151
152        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
153
154        let x = match state {
155            RadioButtonState::Unchecked => BST_UNCHECKED,
156            RadioButtonState::Checked => BST_CHECKED,
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 {
171                handle: font_handle,
172            })
173        }
174    }
175
176    /// Set the font of the control
177    pub fn set_font(&self, font: Option<&Font>) {
178        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
179
180        wh::set_window_font(handle, font.map(|f| f.handle), true);
181    }
182
183    /// Return true if the control currently has the keyboard focus
184    pub fn focus(&self) -> bool {
185        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
186        wh::get_focus(handle)
187    }
188
189    /// Set the keyboard focus on the button.
190    pub fn set_focus(&self) {
191        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
192
193        wh::set_focus(handle);
194    }
195
196    /// Return true if the control user can interact with the control, return false otherwise
197    pub fn enabled(&self) -> bool {
198        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
199        wh::get_window_enabled(handle)
200    }
201
202    /// Enable or disable the control
203    pub fn set_enabled(&self, v: bool) {
204        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
205        wh::set_window_enabled(handle, v)
206    }
207
208    /// Return true if the control is visible to the user. Will return true even if the
209    /// control is outside of the parent client view (ex: at the position (10000, 10000))
210    pub fn visible(&self) -> bool {
211        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
212        wh::get_window_visibility(handle)
213    }
214
215    /// Show or hide the control to the user
216    pub fn set_visible(&self, v: bool) {
217        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
218        wh::set_window_visibility(handle, v)
219    }
220
221    /// Return the size of the radio button in the parent window
222    pub fn size(&self) -> (u32, u32) {
223        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
224        wh::get_window_size(handle)
225    }
226
227    /// Set the size of the radio button in the parent window
228    pub fn set_size(&self, x: u32, y: u32) {
229        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
230        wh::set_window_size(handle, x, y, false)
231    }
232
233    /// Return the position of the radio button in the parent window
234    pub fn position(&self) -> (i32, i32) {
235        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
236        wh::get_window_position(handle)
237    }
238
239    /// Set the position of the radio button in the parent window
240    pub fn set_position(&self, x: i32, y: i32) {
241        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
242        wh::set_window_position(handle, x, y)
243    }
244
245    /// Return the radio button label
246    pub fn text(&self) -> String {
247        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
248        wh::get_window_text(handle)
249    }
250
251    /// Set the radio button label
252    pub fn set_text<'a>(&self, v: &'a str) {
253        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
254        wh::set_window_text(handle, v)
255    }
256
257    /// Winapi class name used during control creation
258    pub fn class_name(&self) -> &'static str {
259        "BUTTON"
260    }
261
262    /// Winapi base flags used during window creation
263    pub fn flags(&self) -> u32 {
264        WS_VISIBLE
265    }
266
267    /// Winapi flags required by the control
268    pub fn forced_flags(&self) -> u32 {
269        use winapi::um::winuser::{BS_AUTORADIOBUTTON, BS_NOTIFY, WS_CHILD};
270
271        BS_NOTIFY | WS_CHILD | BS_AUTORADIOBUTTON
272    }
273
274    /// Change the radio button background color.
275    fn hook_background_color(&mut self, c: [u8; 3]) {
276        use crate::bind_raw_event_handler_inner;
277        use winapi::shared::{basetsd::UINT_PTR, minwindef::LRESULT, windef::HWND};
278        use winapi::um::wingdi::{CreateSolidBrush, RGB};
279        use winapi::um::winuser::WM_CTLCOLORSTATIC;
280
281        if self.handle.blank() {
282            panic!("{}", NOT_BOUND);
283        }
284        let handle = self.handle.hwnd().expect(BAD_HANDLE);
285
286        let parent_handle = ControlHandle::Hwnd(wh::get_window_parent(handle));
287        let brush = unsafe { CreateSolidBrush(RGB(c[0], c[1], c[2])) };
288        self.background_brush = Some(brush);
289
290        let handler = bind_raw_event_handler_inner(
291            &parent_handle,
292            handle as UINT_PTR,
293            move |_hwnd, msg, _w, l| {
294                match msg {
295                    WM_CTLCOLORSTATIC => {
296                        let child = l as HWND;
297                        if child == handle {
298                            return Some(brush as LRESULT);
299                        }
300                    }
301                    _ => {}
302                }
303
304                None
305            },
306        );
307
308        *self.handler0.borrow_mut() = Some(handler.unwrap());
309    }
310}
311
312impl Drop for RadioButton {
313    fn drop(&mut self) {
314        let handler = self.handler0.borrow();
315        if let Some(h) = handler.as_ref() {
316            drop(unbind_raw_event_handler(h));
317        }
318
319        if let Some(bg) = self.background_brush {
320            unsafe {
321                DeleteObject(bg as _);
322            }
323        }
324
325        self.handle.destroy();
326    }
327}
328
329pub struct RadioButtonBuilder<'a> {
330    text: &'a str,
331    size: (i32, i32),
332    position: (i32, i32),
333    focus: bool,
334    background_color: Option<[u8; 3]>,
335    check_state: RadioButtonState,
336    flags: Option<RadioButtonFlags>,
337    ex_flags: u32,
338    font: Option<&'a Font>,
339    parent: Option<ControlHandle>,
340}
341
342impl<'a> RadioButtonBuilder<'a> {
343    pub fn flags(mut self, flags: RadioButtonFlags) -> RadioButtonBuilder<'a> {
344        self.flags = Some(flags);
345        self
346    }
347
348    pub fn ex_flags(mut self, flags: u32) -> RadioButtonBuilder<'a> {
349        self.ex_flags = flags;
350        self
351    }
352
353    pub fn text(mut self, text: &'a str) -> RadioButtonBuilder<'a> {
354        self.text = text;
355        self
356    }
357
358    pub fn size(mut self, size: (i32, i32)) -> RadioButtonBuilder<'a> {
359        self.size = size;
360        self
361    }
362
363    pub fn position(mut self, pos: (i32, i32)) -> RadioButtonBuilder<'a> {
364        self.position = pos;
365        self
366    }
367
368    pub fn focus(mut self, focus: bool) -> RadioButtonBuilder<'a> {
369        self.focus = focus;
370        self
371    }
372
373    pub fn check_state(mut self, check: RadioButtonState) -> RadioButtonBuilder<'a> {
374        self.check_state = check;
375        self
376    }
377
378    pub fn background_color(mut self, color: Option<[u8; 3]>) -> RadioButtonBuilder<'a> {
379        self.background_color = color;
380        self
381    }
382
383    pub fn font(mut self, font: Option<&'a Font>) -> RadioButtonBuilder<'a> {
384        self.font = font;
385        self
386    }
387
388    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> RadioButtonBuilder<'a> {
389        self.parent = Some(p.into());
390        self
391    }
392
393    pub fn build(self, out: &mut RadioButton) -> Result<(), NwgError> {
394        let flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
395
396        let parent = match self.parent {
397            Some(p) => Ok(p),
398            None => Err(NwgError::no_parent("RadioButton")),
399        }?;
400
401        *out = Default::default();
402
403        out.handle = ControlBase::build_hwnd()
404            .class_name(out.class_name())
405            .forced_flags(out.forced_flags())
406            .flags(flags)
407            .ex_flags(self.ex_flags)
408            .size(self.size)
409            .position(self.position)
410            .text(self.text)
411            .parent(Some(parent))
412            .build()?;
413
414        if self.font.is_some() {
415            out.set_font(self.font);
416        } else {
417            out.set_font(Font::global_default().as_ref());
418        }
419
420        if self.background_color.is_some() {
421            out.hook_background_color(self.background_color.unwrap());
422        }
423
424        if self.focus {
425            out.set_focus();
426        }
427
428        out.set_check_state(self.check_state);
429
430        Ok(())
431    }
432}
433
434impl PartialEq for RadioButton {
435    fn eq(&self, other: &Self) -> bool {
436        self.handle == other.handle
437    }
438}