Skip to main content

native_windows_gui2/controls/
number_select.rs

1use std::cell::RefCell;
2use std::rc::Rc;
3use winapi::um::winuser::{WS_DISABLED, WS_EX_CONTROLPARENT, WS_TABSTOP, WS_VISIBLE};
4
5use super::{Button, ButtonFlags, ControlBase, ControlHandle, TextInput, TextInputFlags};
6use crate::win32::base_helper::check_hwnd;
7use crate::win32::window_helper as wh;
8use crate::{
9    Font, NwgError, RawEventHandler, bind_raw_event_handler_inner, unbind_raw_event_handler,
10};
11
12const NOT_BOUND: &'static str = "UpDown is not yet bound to a winapi object";
13const BAD_HANDLE: &'static str = "INTERNAL ERROR: UpDown handle is not HWND!";
14
15bitflags! {
16    /**
17        The NumberSelect flags
18
19        * NONE:     No flags. Equivalent to a invisible blank NumberSelect.
20        * VISIBLE:  The NumberSelect is immediatly visible after creation
21        * DISABLED: The NumberSelect cannot be interacted with by the user. It also has a grayed out look.
22        * TAB_STOP: The control can be selected using tab navigation.
23    */
24    pub struct NumberSelectFlags: u32 {
25        const NONE = 0;
26        const VISIBLE = WS_VISIBLE;
27        const DISABLED = WS_DISABLED;
28        const TAB_STOP = WS_TABSTOP;
29    }
30}
31
32/// The value inside a number select and the limits of that value
33#[derive(Copy, Clone, Debug)]
34pub enum NumberSelectData {
35    Int {
36        value: i64,
37        step: i64,
38        max: i64,
39        min: i64,
40    },
41    Float {
42        value: f64,
43        step: f64,
44        max: f64,
45        min: f64,
46        decimals: u8,
47    },
48}
49
50impl NumberSelectData {
51    pub fn formatted_value(&self) -> String {
52        match self {
53            NumberSelectData::Int { value, .. } => format!("{}", value),
54            NumberSelectData::Float {
55                value, decimals, ..
56            } => format!("{:.*}", *decimals as usize, value),
57        }
58    }
59
60    pub fn decrease(&mut self) {
61        match self {
62            NumberSelectData::Int {
63                value, step, min, ..
64            } => {
65                *value -= *step;
66                *value = i64::max(*value, *min);
67            }
68            NumberSelectData::Float {
69                value, step, min, ..
70            } => {
71                *value -= *step;
72                *value = f64::max(*value, *min);
73            }
74        }
75    }
76
77    pub fn increase(&mut self) {
78        match self {
79            NumberSelectData::Int {
80                value, step, max, ..
81            } => {
82                *value += *step;
83                *value = i64::min(*value, *max);
84            }
85            NumberSelectData::Float {
86                value, step, max, ..
87            } => {
88                *value += *step;
89                *value = f64::min(*value, *max);
90            }
91        }
92    }
93}
94
95impl Default for NumberSelectData {
96    fn default() -> NumberSelectData {
97        NumberSelectData::Int {
98            value: 0,
99            step: 1,
100            max: i64::max_value(),
101            min: i64::min_value(),
102        }
103    }
104}
105
106/**
107A NumberSelect control is a pair of arrow buttons that the user can click to increment or decrement a value.
108NumberSelect is implemented as a custom control because the one provided by winapi really sucks.
109
110Requires the `number-select` feature.
111
112**Builder parameters:**
113  * `parent`:   **Required.** The number select parent container.
114  * `value`:    The default value of the number select
115  * `size`:     The number select size.
116  * `position`: The number select position.
117  * `enabled`:  If the number select can be used by the user. It also has a grayed out look if disabled.
118  * `flags`:    A combination of the NumberSelectFlags values.
119  * `font`:     The font used for the number select text
120
121**Control events:**
122  * `MousePress(_)`: Generic mouse press events on the button
123  * `OnMouseMove`: Generic mouse mouse event
124
125```rust
126use native_windows_gui2 as nwg;
127fn build_number_select(num_select: &mut nwg::NumberSelect, window: &nwg::Window, font: &nwg::Font) {
128    nwg::NumberSelect::builder()
129        .font(Some(font))
130        .parent(window)
131        .build(num_select);
132}
133```
134
135*/
136#[derive(Default)]
137pub struct NumberSelect {
138    pub handle: ControlHandle,
139    data: Rc<RefCell<NumberSelectData>>,
140    edit: TextInput,
141    btn_up: Button,
142    btn_down: Button,
143    handler: Option<RawEventHandler>,
144}
145
146impl NumberSelect {
147    pub fn builder<'a>() -> NumberSelectBuilder<'a> {
148        NumberSelectBuilder {
149            size: (100, 25),
150            position: (0, 0),
151            data: NumberSelectData::default(),
152            enabled: true,
153            flags: None,
154            font: None,
155            parent: None,
156        }
157    }
158
159    /// Returns inner data specifying the possible input of a number select
160    /// See [NumberSelectData](enum.NumberSelectData.html) for the possible values
161    pub fn data(&self) -> NumberSelectData {
162        self.data.borrow().clone()
163    }
164
165    /// Sets the inner data specifying the possible input of a number select. Also update the value display.
166    /// See [NumberSelectData](enum.NumberSelectData.html) for the possible values
167    pub fn set_data(&self, v: NumberSelectData) {
168        *self.data.borrow_mut() = v;
169        self.edit.set_text(&v.formatted_value());
170    }
171
172    /// Returns the font of the control
173    pub fn font(&self) -> Option<Font> {
174        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
175        let font_handle = wh::get_window_font(handle);
176        if font_handle.is_null() {
177            None
178        } else {
179            Some(Font {
180                handle: font_handle,
181            })
182        }
183    }
184
185    /// Sets the font of the control
186    pub fn set_font(&self, font: Option<&Font>) {
187        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
188
189        wh::set_window_font(handle, font.map(|f| f.handle), true);
190    }
191
192    /// Returns true if the control currently has the keyboard focus
193    pub fn focus(&self) -> bool {
194        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
195        wh::get_focus(handle)
196    }
197
198    /// Sets the keyboard focus on the button.
199    pub fn set_focus(&self) {
200        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
201
202        wh::set_focus(handle);
203    }
204
205    /// Returns true if the control user can interact with the control, return false otherwise
206    pub fn enabled(&self) -> bool {
207        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
208        wh::get_window_enabled(handle)
209    }
210
211    /// Enable or disable the control
212    pub fn set_enabled(&self, v: bool) {
213        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
214        wh::set_window_enabled(handle, v)
215    }
216
217    /// Returns true if the control is visible to the user. Will return true even if the
218    /// control is outside of the parent client view (ex: at the position (10000, 10000))
219    pub fn visible(&self) -> bool {
220        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
221        wh::get_window_visibility(handle)
222    }
223
224    /// Show or hide the control to the user
225    pub fn set_visible(&self, v: bool) {
226        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
227        wh::set_window_visibility(handle, v)
228    }
229
230    /// Returns the size of the control in the parent window
231    pub fn size(&self) -> (u32, u32) {
232        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
233        wh::get_window_size(handle)
234    }
235
236    /// Sets the size of the control in the parent window
237    pub fn set_size(&self, x: u32, y: u32) {
238        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
239        wh::set_window_size(handle, x, y, false)
240    }
241
242    /// Returns the position of the control in the parent window
243    pub fn position(&self) -> (i32, i32) {
244        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
245        wh::get_window_position(handle)
246    }
247
248    /// Sets the position of the control in the parent window
249    pub fn set_position(&self, x: i32, y: i32) {
250        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
251        wh::set_window_position(handle, x, y)
252    }
253
254    /// Winapi class name used during control creation
255    pub fn class_name(&self) -> &'static str {
256        "NativeWindowsGuiWindow"
257    }
258
259    /// Winapi base flags used during window creation
260    pub fn flags(&self) -> u32 {
261        ::winapi::um::winuser::WS_VISIBLE
262    }
263
264    /// Winapi flags required by the control
265    pub fn forced_flags(&self) -> u32 {
266        use winapi::um::winuser::{WS_BORDER, WS_CHILD, WS_CLIPCHILDREN};
267        WS_CHILD | WS_BORDER | WS_CLIPCHILDREN
268    }
269}
270
271impl Drop for NumberSelect {
272    fn drop(&mut self) {
273        if let Some(h) = self.handler.as_ref() {
274            drop(unbind_raw_event_handler(h));
275        }
276
277        self.handle.destroy();
278    }
279}
280
281pub struct NumberSelectBuilder<'a> {
282    size: (i32, i32),
283    position: (i32, i32),
284    data: NumberSelectData,
285    enabled: bool,
286    flags: Option<NumberSelectFlags>,
287    font: Option<&'a Font>,
288    parent: Option<ControlHandle>,
289}
290
291impl<'a> NumberSelectBuilder<'a> {
292    pub fn flags(mut self, flags: NumberSelectFlags) -> NumberSelectBuilder<'a> {
293        self.flags = Some(flags);
294        self
295    }
296
297    pub fn size(mut self, size: (i32, i32)) -> NumberSelectBuilder<'a> {
298        self.size = size;
299        self
300    }
301
302    pub fn position(mut self, pos: (i32, i32)) -> NumberSelectBuilder<'a> {
303        self.position = pos;
304        self
305    }
306
307    pub fn enabled(mut self, e: bool) -> NumberSelectBuilder<'a> {
308        self.enabled = e;
309        self
310    }
311
312    pub fn font(mut self, font: Option<&'a Font>) -> NumberSelectBuilder<'a> {
313        self.font = font;
314        self
315    }
316
317    // Int values
318    pub fn value_int(mut self, v: i64) -> NumberSelectBuilder<'a> {
319        match &mut self.data {
320            NumberSelectData::Int { value, .. } => {
321                *value = v;
322            }
323            data => {
324                *data = NumberSelectData::Int {
325                    value: v,
326                    step: 1,
327                    max: i64::max_value(),
328                    min: i64::min_value(),
329                }
330            }
331        }
332        self
333    }
334
335    pub fn step_int(mut self, v: i64) -> NumberSelectBuilder<'a> {
336        match &mut self.data {
337            NumberSelectData::Int { step, .. } => {
338                *step = v;
339            }
340            data => {
341                *data = NumberSelectData::Int {
342                    value: 0,
343                    step: v,
344                    max: i64::max_value(),
345                    min: i64::min_value(),
346                }
347            }
348        }
349        self
350    }
351
352    pub fn max_int(mut self, v: i64) -> NumberSelectBuilder<'a> {
353        match &mut self.data {
354            NumberSelectData::Int { max, .. } => {
355                *max = v;
356            }
357            data => {
358                *data = NumberSelectData::Int {
359                    value: 0,
360                    step: 1,
361                    max: v,
362                    min: i64::min_value(),
363                }
364            }
365        }
366        self
367    }
368
369    pub fn min_int(mut self, v: i64) -> NumberSelectBuilder<'a> {
370        match &mut self.data {
371            NumberSelectData::Int { min, .. } => {
372                *min = v;
373            }
374            data => {
375                *data = NumberSelectData::Int {
376                    value: 0,
377                    step: 1,
378                    max: i64::max_value(),
379                    min: v,
380                }
381            }
382        }
383        self
384    }
385
386    // Float values
387    pub fn value_float(mut self, v: f64) -> NumberSelectBuilder<'a> {
388        match &mut self.data {
389            NumberSelectData::Float { value, .. } => {
390                *value = v;
391            }
392            data => {
393                *data = NumberSelectData::Float {
394                    value: v,
395                    step: 1.0,
396                    max: 1000000.0,
397                    min: -1000000.0,
398                    decimals: 2,
399                }
400            }
401        }
402        self
403    }
404
405    pub fn step_float(mut self, v: f64) -> NumberSelectBuilder<'a> {
406        match &mut self.data {
407            NumberSelectData::Float { step, .. } => {
408                *step = v;
409            }
410            data => {
411                *data = NumberSelectData::Float {
412                    value: 0.0,
413                    step: v,
414                    max: 1000000.0,
415                    min: -1000000.0,
416                    decimals: 2,
417                }
418            }
419        }
420        self
421    }
422
423    pub fn max_float(mut self, v: f64) -> NumberSelectBuilder<'a> {
424        match &mut self.data {
425            NumberSelectData::Float { max, .. } => {
426                *max = v;
427            }
428            data => {
429                *data = NumberSelectData::Float {
430                    value: 0.0,
431                    step: 1.0,
432                    max: v,
433                    min: -1000000.0,
434                    decimals: 2,
435                }
436            }
437        }
438        self
439    }
440
441    pub fn min_float(mut self, v: f64) -> NumberSelectBuilder<'a> {
442        match &mut self.data {
443            NumberSelectData::Float { min, .. } => {
444                *min = v;
445            }
446            data => {
447                *data = NumberSelectData::Float {
448                    value: 0.0,
449                    step: 1.0,
450                    max: 1000000.0,
451                    min: v,
452                    decimals: 2,
453                }
454            }
455        }
456        self
457    }
458
459    pub fn decimals(mut self, v: u8) -> NumberSelectBuilder<'a> {
460        match &mut self.data {
461            NumberSelectData::Float { decimals, .. } => {
462                *decimals = v;
463            }
464            data => {
465                *data = NumberSelectData::Float {
466                    value: 0.0,
467                    step: 1.0,
468                    max: 1000000.0,
469                    min: -1000000.0,
470                    decimals: v,
471                }
472            }
473        }
474        self
475    }
476
477    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> NumberSelectBuilder<'a> {
478        self.parent = Some(p.into());
479        self
480    }
481
482    pub fn build(self, out: &mut NumberSelect) -> Result<(), NwgError> {
483        let flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
484        let (btn_flags, text_flags) = if flags & WS_TABSTOP == WS_TABSTOP {
485            (
486                ButtonFlags::VISIBLE | ButtonFlags::TAB_STOP,
487                TextInputFlags::VISIBLE | TextInputFlags::TAB_STOP,
488            )
489        } else {
490            (ButtonFlags::VISIBLE, TextInputFlags::VISIBLE)
491        };
492
493        let parent = match self.parent {
494            Some(p) => Ok(p),
495            None => Err(NwgError::no_parent("NumberSelect")),
496        }?;
497
498        *out = Default::default();
499
500        let (w, h) = self.size;
501
502        if out.handler.is_some() {
503            unbind_raw_event_handler(out.handler.as_ref().unwrap())?;
504        }
505
506        *out = NumberSelect::default();
507        *out.data.borrow_mut() = self.data;
508
509        out.handle = ControlBase::build_hwnd()
510            .class_name(out.class_name())
511            .forced_flags(out.forced_flags())
512            .ex_flags(WS_EX_CONTROLPARENT)
513            .flags(flags)
514            .size(self.size)
515            .position(self.position)
516            .parent(Some(parent))
517            .build()?;
518
519        TextInput::builder()
520            .text(&self.data.formatted_value())
521            .size((w - 19, h))
522            .parent(&out.handle)
523            .flags(text_flags)
524            .build(&mut out.edit)?;
525
526        Button::builder()
527            .text("+")
528            .size((20, h / 2 + 1))
529            .position((w - 20, -1))
530            .parent(&out.handle)
531            .flags(btn_flags)
532            .build(&mut out.btn_up)?;
533
534        Button::builder()
535            .text("-")
536            .size((20, h / 2 + 1))
537            .position((w - 20, (h / 2) - 1))
538            .parent(&out.handle)
539            .flags(btn_flags)
540            .build(&mut out.btn_down)?;
541
542        if self.font.is_some() {
543            out.btn_up.set_font(self.font);
544            out.btn_down.set_font(self.font);
545            out.edit.set_font(self.font);
546        } else {
547            let font = Font::global_default();
548            let font_ref = font.as_ref();
549            out.btn_up.set_font(font_ref);
550            out.btn_down.set_font(font_ref);
551            out.edit.set_font(font_ref);
552        }
553
554        let handler_data = out.data.clone();
555        let plus_button = out.btn_up.handle.clone();
556        let minus_button = out.btn_down.handle.clone();
557        let text_handle = out.edit.handle.clone();
558
559        let handler = bind_raw_event_handler_inner(&out.handle, 0x4545, move |_hwnd, msg, w, l| {
560            use winapi::shared::minwindef::HIWORD;
561            use winapi::shared::windef::HWND;
562            use winapi::um::winuser::{BN_CLICKED, WM_COMMAND};
563
564            match msg {
565                WM_COMMAND => {
566                    let handle = ControlHandle::Hwnd(l as HWND);
567                    let message = HIWORD(w as u32) as u16;
568                    if message == BN_CLICKED && handle == plus_button {
569                        let mut data = handler_data.borrow_mut();
570                        data.increase();
571
572                        let handle = text_handle.hwnd().unwrap();
573                        let text = data.formatted_value();
574
575                        wh::set_window_text(handle, &text);
576                    } else if message == BN_CLICKED && handle == minus_button {
577                        let mut data = handler_data.borrow_mut();
578                        data.decrease();
579
580                        let handle = text_handle.hwnd().unwrap();
581                        let text = data.formatted_value();
582
583                        wh::set_window_text(handle, &text);
584                    }
585                }
586
587                _ => {}
588            }
589            None
590        });
591
592        out.handler = Some(handler.unwrap());
593
594        if !self.enabled {
595            out.set_enabled(self.enabled);
596        }
597
598        Ok(())
599    }
600}