Skip to main content

native_windows_gui/controls/
number_select.rs

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