virtual_desktop_manager\nwg_ext/
number_select.rs

1//! Fork of [`nwg::NumberSelect`] that fixes some issues. See [`NumberSelect2`]
2//! for more info.
3
4mod high_dpi {
5    //! Fork of [`nwg::win32::high_dpi`].
6
7    #[allow(deprecated, unused_imports)]
8    pub use nwg::{dpi, scale_factor, set_dpi_awareness};
9
10    #[cfg(not(feature = "nwg_high_dpi"))]
11    pub unsafe fn logical_to_physical(x: i32, y: i32) -> (i32, i32) {
12        (x, y)
13    }
14
15    #[cfg(feature = "nwg_high_dpi")]
16    pub unsafe fn logical_to_physical(x: i32, y: i32) -> (i32, i32) {
17        use muldiv::MulDiv;
18        use windows::Win32::UI::WindowsAndMessaging::USER_DEFAULT_SCREEN_DPI;
19
20        let dpi = dpi();
21        let x = x
22            .mul_div_round(dpi, USER_DEFAULT_SCREEN_DPI as i32)
23            .unwrap_or(x);
24        let y = y
25            .mul_div_round(dpi, USER_DEFAULT_SCREEN_DPI as i32)
26            .unwrap_or(y);
27        (x, y)
28    }
29
30    #[cfg(not(feature = "nwg_high_dpi"))]
31    pub unsafe fn physical_to_logical(x: i32, y: i32) -> (i32, i32) {
32        (x, y)
33    }
34
35    #[cfg(feature = "nwg_high_dpi")]
36    pub unsafe fn physical_to_logical(x: i32, y: i32) -> (i32, i32) {
37        use muldiv::MulDiv;
38        use windows::Win32::UI::WindowsAndMessaging::USER_DEFAULT_SCREEN_DPI;
39
40        let dpi = dpi();
41        let x = x
42            .mul_div_round(USER_DEFAULT_SCREEN_DPI as i32, dpi)
43            .unwrap_or(x);
44        let y = y
45            .mul_div_round(USER_DEFAULT_SCREEN_DPI as i32, dpi)
46            .unwrap_or(y);
47        (x, y)
48    }
49}
50
51mod wh {
52    //! Adapted from [`nwg::win32::window_helper`].
53
54    use windows::{
55        core::PCWSTR,
56        Win32::{
57            Foundation::{BOOL, HWND, LPARAM, POINT, RECT, WPARAM},
58            Graphics::Gdi::{InvalidateRect, ScreenToClient, UpdateWindow, HFONT},
59            UI::{
60                Input::KeyboardAndMouse::{GetFocus, SetFocus},
61                WindowsAndMessaging::{
62                    AdjustWindowRectEx, GetClientRect, GetParent, GetWindowLongW, GetWindowRect,
63                    GetWindowTextLengthW, GetWindowTextW, SendMessageW, SetWindowPos,
64                    SetWindowTextW, ShowWindow, GWL_EXSTYLE, GWL_STYLE, SWP_NOACTIVATE,
65                    SWP_NOCOPYBITS, SWP_NOMOVE, SWP_NOOWNERZORDER, SWP_NOSIZE, SWP_NOZORDER,
66                    SW_HIDE, SW_SHOW, WINDOW_EX_STYLE, WINDOW_STYLE, WM_GETFONT, WM_SETFONT,
67                    WS_DISABLED,
68                },
69            },
70        },
71    };
72
73    use crate::nwg_ext::{from_utf16, to_utf16};
74
75    use super::high_dpi;
76
77    #[inline(always)]
78    #[cfg(target_pointer_width = "64")]
79    pub fn get_window_long(handle: HWND, index: i32) -> isize {
80        use windows::Win32::UI::WindowsAndMessaging::{GetWindowLongPtrW, WINDOW_LONG_PTR_INDEX};
81
82        unsafe { GetWindowLongPtrW(handle, WINDOW_LONG_PTR_INDEX(index)) }
83    }
84
85    #[inline(always)]
86    #[cfg(target_pointer_width = "32")]
87    pub fn get_window_long2(handle: HWND, index: i32) -> i32 {
88        use windows::Win32::UI::WindowsAndMessaging::{GetWindowLongW, WINDOW_LONG_PTR_INDEX};
89        unsafe { GetWindowLongW(handle, WINDOW_LONG_PTR_INDEX(index)) }
90    }
91
92    #[inline(always)]
93    #[cfg(target_pointer_width = "64")]
94    pub fn set_window_long(handle: HWND, index: i32, v: usize) {
95        use windows::Win32::UI::WindowsAndMessaging::{SetWindowLongPtrW, WINDOW_LONG_PTR_INDEX};
96
97        unsafe {
98            SetWindowLongPtrW(handle, WINDOW_LONG_PTR_INDEX(index), v as isize);
99        }
100    }
101
102    #[inline(always)]
103    #[cfg(target_pointer_width = "32")]
104    pub fn set_window_long(handle: HWND, index: i32, v: usize) {
105        use windows::Win32::UI::WindowsAndMessaging::{SetWindowLongW, WINDOW_LONG_PTR_INDEX};
106        unsafe {
107            SetWindowLongW(handle, WINDOW_LONG_PTR_INDEX(index), v as i32);
108        }
109    }
110
111    /// Set the font of a window
112    pub unsafe fn set_window_font(handle: HWND, font_handle: Option<HFONT>, redraw: bool) {
113        let font_handle = font_handle.unwrap_or_default();
114
115        SendMessageW(
116            handle,
117            WM_SETFONT,
118            WPARAM(font_handle.0 as usize),
119            LPARAM(redraw as isize),
120        );
121    }
122
123    pub fn get_window_font(handle: HWND) -> HFONT {
124        unsafe {
125            let h = SendMessageW(handle, WM_GETFONT, WPARAM(0), LPARAM(0));
126            HFONT(h.0 as *mut _)
127        }
128    }
129    pub unsafe fn set_focus(handle: HWND) {
130        _ = SetFocus(handle);
131    }
132
133    pub unsafe fn get_focus(handle: HWND) -> bool {
134        GetFocus() == handle
135    }
136    pub unsafe fn get_window_enabled(handle: HWND) -> bool {
137        let style = get_window_long(handle, GWL_STYLE.0) as u32;
138        (style & WS_DISABLED.0) != WS_DISABLED.0
139    }
140
141    pub unsafe fn set_window_enabled(handle: HWND, enabled: bool) {
142        let old_style = get_window_long(handle, GWL_STYLE.0) as usize;
143        if enabled {
144            set_window_long(handle, GWL_STYLE.0, old_style & (!WS_DISABLED.0 as usize));
145        } else {
146            set_window_long(handle, GWL_STYLE.0, old_style | (WS_DISABLED.0 as usize));
147        }
148
149        // Tell the control to redraw itself to show the new style.
150        let _ = InvalidateRect(handle, None, BOOL::from(true));
151        let _ = UpdateWindow(handle);
152    }
153    pub unsafe fn get_window_text(handle: HWND) -> String {
154        let buffer_size = GetWindowTextLengthW(handle) as usize + 1;
155        if buffer_size == 0 {
156            return String::new();
157        }
158
159        let mut buffer: Vec<u16> = vec![0; buffer_size];
160
161        if GetWindowTextW(handle, &mut buffer) == 0 {
162            String::new()
163        } else {
164            from_utf16(&buffer[..])
165        }
166    }
167    pub unsafe fn set_window_text(handle: HWND, text: &str) {
168        let text = to_utf16(text);
169        let _ = SetWindowTextW(handle, PCWSTR::from_raw(text.as_ptr()));
170    }
171
172    pub unsafe fn set_window_position(handle: HWND, x: i32, y: i32) {
173        nwg::dpi();
174        let (x, y) = high_dpi::logical_to_physical(x, y);
175        let _ = SetWindowPos(
176            handle,
177            HWND::default(),
178            x,
179            y,
180            0,
181            0,
182            SWP_NOZORDER | SWP_NOSIZE | SWP_NOACTIVATE | SWP_NOOWNERZORDER,
183        );
184    }
185    pub unsafe fn get_window_position(handle: HWND) -> (i32, i32) {
186        let mut r = RECT::default();
187        let _ = GetWindowRect(handle, &mut r);
188
189        let parent = GetParent(handle);
190        let (x, y) = if let Ok(parent) = parent {
191            let mut pt = POINT {
192                x: r.left,
193                y: r.top,
194            };
195            let _ = ScreenToClient(parent, &mut pt);
196            (pt.x, pt.y)
197        } else {
198            (r.left, r.top)
199        };
200
201        high_dpi::physical_to_logical(x, y)
202    }
203
204    pub unsafe fn set_window_size(handle: HWND, w: u32, h: u32, fix: bool) {
205        let (mut w, mut h) = high_dpi::logical_to_physical(w as i32, h as i32);
206
207        if fix {
208            let flags = GetWindowLongW(handle, GWL_STYLE) as u32;
209            let ex_flags = GetWindowLongW(handle, GWL_EXSTYLE) as u32;
210            let mut rect = RECT {
211                left: 0,
212                top: 0,
213                right: w,
214                bottom: h,
215            };
216            let _ = AdjustWindowRectEx(
217                &mut rect,
218                WINDOW_STYLE(flags),
219                BOOL::from(false),
220                WINDOW_EX_STYLE(ex_flags),
221            );
222
223            w = rect.right - rect.left;
224            h = rect.bottom - rect.top;
225        }
226
227        let _ = SetWindowPos(
228            handle,
229            HWND::default(),
230            0,
231            0,
232            w,
233            h,
234            SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOCOPYBITS | SWP_NOOWNERZORDER,
235        );
236    }
237
238    pub unsafe fn get_window_size(handle: HWND) -> (u32, u32) {
239        get_window_size_impl(handle, false)
240    }
241
242    #[allow(unused)]
243    pub unsafe fn get_window_physical_size(handle: HWND) -> (u32, u32) {
244        get_window_size_impl(handle, true)
245    }
246
247    unsafe fn get_window_size_impl(handle: HWND, return_physical: bool) -> (u32, u32) {
248        let mut r = RECT::default();
249        let _ = GetClientRect(handle, &mut r);
250
251        let (w, h) = if return_physical {
252            (r.right, r.bottom)
253        } else {
254            high_dpi::physical_to_logical(r.right, r.bottom)
255        };
256
257        (w as u32, h as u32)
258    }
259    pub unsafe fn set_window_visibility(handle: HWND, visible: bool) {
260        let visible = if visible { SW_SHOW } else { SW_HIDE };
261        let _ = ShowWindow(handle, visible);
262    }
263
264    pub unsafe fn get_window_visibility(handle: HWND) -> bool {
265        windows::Win32::UI::WindowsAndMessaging::IsWindowVisible(handle).as_bool()
266    }
267}
268
269use std::{cell::RefCell, cmp::Ordering, rc::Rc};
270
271use nwg::{
272    bind_raw_event_handler, unbind_raw_event_handler, Button, ButtonFlags, ControlBase,
273    ControlHandle, Font, Notice, NumberSelectData, NumberSelectFlags, NwgError, RawEventHandler,
274    TextInput, TextInputFlags,
275};
276use windows::Win32::{
277    Foundation::HWND,
278    Graphics::Gdi::HFONT,
279    UI::WindowsAndMessaging::{
280        BN_CLICKED, WM_COMMAND, WS_BORDER, WS_CHILD, WS_CLIPCHILDREN, WS_EX_CONTROLPARENT,
281        WS_TABSTOP, WS_VISIBLE,
282    },
283};
284
285const NOT_BOUND: &str = "UpDown is not yet bound to a winapi object";
286const BAD_HANDLE: &str = "INTERNAL ERROR: UpDown handle is not HWND!";
287
288/// Adapted from [`native_windows_gui::win32::base_helper::check_hwnd`].
289fn check_hwnd(handle: &ControlHandle, not_bound: &str, bad_handle: &str) -> HWND {
290    if handle.blank() {
291        panic!("{}", not_bound);
292    }
293    match handle.hwnd() {
294        Some(hwnd) => {
295            if unsafe { windows::Win32::UI::WindowsAndMessaging::IsWindow(HWND(hwnd.cast())) }
296                .as_bool()
297            {
298                HWND(hwnd.cast())
299            } else {
300                panic!("The window handle is no longer valid. This usually means the control was freed by the OS");
301            }
302        }
303        None => {
304            panic!("{}", bad_handle);
305        }
306    }
307}
308
309/**
310Fork of [`nwg::NumberSelect`] that has some improvements.
311
312# Differences
313
314- Up and Down arrow keys will increment and decrement the number.
315- Scroll events on the select control will increment or decrement the number.
316- Manual text edits in the field will be validated and used to update the number data.
317- Event that will be used whenever the number data is changed by the UI.
318   - Listen to `OnNotice` event to see changes.
319
320# Original docs
321
322A NumberSelect control is a pair of arrow buttons that the user can click to increment or decrement a value.
323NumberSelect is implemented as a custom control because the one provided by winapi really sucks.
324
325Requires the `number-select` feature.
326
327**Builder parameters:**
328  * `parent`:   **Required.** The number select parent container.
329  * `value`:    The default value of the number select
330  * `size`:     The number select size.
331  * `position`: The number select position.
332  * `enabled`:  If the number select can be used by the user. It also has a grayed out look if disabled.
333  * `flags`:    A combination of the NumberSelectFlags values.
334  * `font`:     The font used for the number select text
335
336**Control events:**
337  * `MousePress(_)`: Generic mouse press events on the button
338  * `OnMouseMove`: Generic mouse mouse event
339
340```rust
341use virtual_desktop_manager::nwg_ext;
342
343fn build_number_select(num_select: &mut nwg_ext::NumberSelect2, window: &nwg::Window, font: &nwg::Font) {
344    nwg_ext::NumberSelect2::builder()
345        .font(Some(font))
346        .parent(window)
347        .build(num_select);
348}
349```
350
351*/
352#[derive(Default)]
353pub struct NumberSelect2 {
354    pub handle: ControlHandle,
355    data: Rc<RefCell<NumberSelectData>>,
356    edit: TextInput,
357    btn_up: Button,
358    btn_down: Button,
359    notice: Notice,
360    handler: Option<RawEventHandler>,
361    edit_handler: Option<RawEventHandler>,
362}
363
364impl NumberSelect2 {
365    pub fn builder<'a>() -> NumberSelectBuilder<'a> {
366        NumberSelectBuilder {
367            size: (100, 25),
368            position: (0, 0),
369            data: NumberSelectData::default(),
370            enabled: true,
371            flags: None,
372            font: None,
373            parent: None,
374        }
375    }
376
377    /// Returns inner data specifying the possible input of a number select
378    /// See [NumberSelectData](enum.NumberSelectData.html) for the possible values
379    pub fn data(&self) -> NumberSelectData {
380        *self.data.borrow()
381    }
382
383    /// Sets the inner data specifying the possible input of a number select. Also update the value display.
384    /// See [NumberSelectData](enum.NumberSelectData.html) for the possible values
385    pub fn set_data(&self, v: NumberSelectData) {
386        *self.data.borrow_mut() = v;
387        self.edit.set_text(&v.formatted_value());
388    }
389
390    /// Returns the font of the control
391    pub fn font(&self) -> Option<Font> {
392        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
393        let font_handle = wh::get_window_font(handle);
394        if font_handle.0.is_null() {
395            None
396        } else {
397            Some(Font {
398                handle: font_handle.0 as *mut _,
399            })
400        }
401    }
402
403    /// Sets the font of the control
404    pub fn set_font(&self, font: Option<&Font>) {
405        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
406        unsafe {
407            wh::set_window_font(handle, font.map(|f| HFONT(f.handle.cast())), true);
408        }
409    }
410
411    /// Returns true if the control currently has the keyboard focus
412    pub fn focus(&self) -> bool {
413        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
414        unsafe { wh::get_focus(handle) }
415    }
416
417    /// Sets the keyboard focus on the button.
418    pub fn set_focus(&self) {
419        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
420        unsafe {
421            wh::set_focus(handle);
422        }
423    }
424
425    /// Returns true if the control user can interact with the control, return false otherwise
426    pub fn enabled(&self) -> bool {
427        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
428        unsafe { wh::get_window_enabled(handle) }
429    }
430
431    /// Enable or disable the control
432    pub fn set_enabled(&self, v: bool) {
433        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
434        unsafe { wh::set_window_enabled(handle, v) }
435    }
436
437    /// Returns true if the control is visible to the user. Will return true even if the
438    /// control is outside of the parent client view (ex: at the position (10000, 10000))
439    pub fn visible(&self) -> bool {
440        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
441        unsafe { wh::get_window_visibility(handle) }
442    }
443
444    /// Show or hide the control to the user
445    pub fn set_visible(&self, v: bool) {
446        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
447        unsafe { wh::set_window_visibility(handle, v) }
448    }
449
450    /// Returns the size of the control in the parent window
451    pub fn size(&self) -> (u32, u32) {
452        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
453        unsafe { wh::get_window_size(handle) }
454    }
455
456    /// Sets the size of the control in the parent window
457    pub fn set_size(&self, x: u32, y: u32) {
458        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
459        unsafe { wh::set_window_size(handle, x, y, false) }
460    }
461
462    /// Returns the position of the control in the parent window
463    pub fn position(&self) -> (i32, i32) {
464        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
465        unsafe { wh::get_window_position(handle) }
466    }
467
468    /// Sets the position of the control in the parent window
469    pub fn set_position(&self, x: i32, y: i32) {
470        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
471        unsafe { wh::set_window_position(handle, x, y) }
472    }
473
474    /// Winapi class name used during control creation
475    pub fn class_name(&self) -> &'static str {
476        "NativeWindowsGuiWindow"
477    }
478
479    /// Winapi base flags used during window creation
480    pub fn flags(&self) -> u32 {
481        WS_VISIBLE.0
482    }
483
484    /// Winapi flags required by the control
485    pub fn forced_flags(&self) -> u32 {
486        (WS_CHILD | WS_BORDER | WS_CLIPCHILDREN).0
487    }
488}
489
490impl Drop for NumberSelect2 {
491    fn drop(&mut self) {
492        if let Some(h) = self.handler.as_ref() {
493            drop(unbind_raw_event_handler(h));
494        }
495
496        self.handle.destroy();
497    }
498}
499
500pub struct NumberSelectBuilder<'a> {
501    size: (i32, i32),
502    position: (i32, i32),
503    data: NumberSelectData,
504    enabled: bool,
505    flags: Option<NumberSelectFlags>,
506    font: Option<&'a Font>,
507    parent: Option<ControlHandle>,
508}
509
510impl<'a> NumberSelectBuilder<'a> {
511    pub fn flags(mut self, flags: NumberSelectFlags) -> NumberSelectBuilder<'a> {
512        self.flags = Some(flags);
513        self
514    }
515
516    pub fn size(mut self, size: (i32, i32)) -> NumberSelectBuilder<'a> {
517        self.size = size;
518        self
519    }
520
521    pub fn position(mut self, pos: (i32, i32)) -> NumberSelectBuilder<'a> {
522        self.position = pos;
523        self
524    }
525
526    pub fn enabled(mut self, e: bool) -> NumberSelectBuilder<'a> {
527        self.enabled = e;
528        self
529    }
530
531    pub fn font(mut self, font: Option<&'a Font>) -> NumberSelectBuilder<'a> {
532        self.font = font;
533        self
534    }
535
536    // Int values
537    pub fn value_int(mut self, v: i64) -> NumberSelectBuilder<'a> {
538        match &mut self.data {
539            NumberSelectData::Int { value, .. } => {
540                *value = v;
541            }
542            data => {
543                *data = NumberSelectData::Int {
544                    value: v,
545                    step: 1,
546                    max: i64::MAX,
547                    min: i64::MIN,
548                }
549            }
550        }
551        self
552    }
553
554    pub fn step_int(mut self, v: i64) -> NumberSelectBuilder<'a> {
555        match &mut self.data {
556            NumberSelectData::Int { step, .. } => {
557                *step = v;
558            }
559            data => {
560                *data = NumberSelectData::Int {
561                    value: 0,
562                    step: v,
563                    max: i64::MAX,
564                    min: i64::MIN,
565                }
566            }
567        }
568        self
569    }
570
571    pub fn max_int(mut self, v: i64) -> NumberSelectBuilder<'a> {
572        match &mut self.data {
573            NumberSelectData::Int { max, .. } => {
574                *max = v;
575            }
576            data => {
577                *data = NumberSelectData::Int {
578                    value: 0,
579                    step: 1,
580                    max: v,
581                    min: i64::MIN,
582                }
583            }
584        }
585        self
586    }
587
588    pub fn min_int(mut self, v: i64) -> NumberSelectBuilder<'a> {
589        match &mut self.data {
590            NumberSelectData::Int { min, .. } => {
591                *min = v;
592            }
593            data => {
594                *data = NumberSelectData::Int {
595                    value: 0,
596                    step: 1,
597                    max: i64::MAX,
598                    min: v,
599                }
600            }
601        }
602        self
603    }
604
605    // Float values
606    pub fn value_float(mut self, v: f64) -> NumberSelectBuilder<'a> {
607        match &mut self.data {
608            NumberSelectData::Float { value, .. } => {
609                *value = v;
610            }
611            data => {
612                *data = NumberSelectData::Float {
613                    value: v,
614                    step: 1.0,
615                    max: 1000000.0,
616                    min: -1000000.0,
617                    decimals: 2,
618                }
619            }
620        }
621        self
622    }
623
624    pub fn step_float(mut self, v: f64) -> NumberSelectBuilder<'a> {
625        match &mut self.data {
626            NumberSelectData::Float { step, .. } => {
627                *step = v;
628            }
629            data => {
630                *data = NumberSelectData::Float {
631                    value: 0.0,
632                    step: v,
633                    max: 1000000.0,
634                    min: -1000000.0,
635                    decimals: 2,
636                }
637            }
638        }
639        self
640    }
641
642    pub fn max_float(mut self, v: f64) -> NumberSelectBuilder<'a> {
643        match &mut self.data {
644            NumberSelectData::Float { max, .. } => {
645                *max = v;
646            }
647            data => {
648                *data = NumberSelectData::Float {
649                    value: 0.0,
650                    step: 1.0,
651                    max: v,
652                    min: -1000000.0,
653                    decimals: 2,
654                }
655            }
656        }
657        self
658    }
659
660    pub fn min_float(mut self, v: f64) -> NumberSelectBuilder<'a> {
661        match &mut self.data {
662            NumberSelectData::Float { min, .. } => {
663                *min = v;
664            }
665            data => {
666                *data = NumberSelectData::Float {
667                    value: 0.0,
668                    step: 1.0,
669                    max: 1000000.0,
670                    min: v,
671                    decimals: 2,
672                }
673            }
674        }
675        self
676    }
677
678    pub fn decimals(mut self, v: u8) -> NumberSelectBuilder<'a> {
679        match &mut self.data {
680            NumberSelectData::Float { decimals, .. } => {
681                *decimals = v;
682            }
683            data => {
684                *data = NumberSelectData::Float {
685                    value: 0.0,
686                    step: 1.0,
687                    max: 1000000.0,
688                    min: -1000000.0,
689                    decimals: v,
690                }
691            }
692        }
693        self
694    }
695
696    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> NumberSelectBuilder<'a> {
697        self.parent = Some(p.into());
698        self
699    }
700
701    pub fn build(self, out: &mut NumberSelect2) -> Result<(), NwgError> {
702        let flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
703        let (btn_flags, text_flags) = if flags & WS_TABSTOP.0 == WS_TABSTOP.0 {
704            (
705                ButtonFlags::VISIBLE | ButtonFlags::TAB_STOP,
706                TextInputFlags::VISIBLE | TextInputFlags::TAB_STOP,
707            )
708        } else {
709            (ButtonFlags::VISIBLE, TextInputFlags::VISIBLE)
710        };
711
712        let parent = match self.parent {
713            Some(p) => Ok(p),
714            None => Err(NwgError::no_parent("NumberSelect")),
715        }?;
716
717        *out = Default::default();
718
719        let (w, h) = self.size;
720
721        if out.handler.is_some() {
722            unbind_raw_event_handler(out.handler.as_ref().unwrap())?;
723        }
724
725        *out = NumberSelect2::default();
726        *out.data.borrow_mut() = self.data;
727
728        out.handle = ControlBase::build_hwnd()
729            .class_name(out.class_name())
730            .forced_flags(out.forced_flags())
731            .ex_flags(WS_EX_CONTROLPARENT.0)
732            .flags(flags)
733            .size(self.size)
734            .position(self.position)
735            .parent(Some(parent))
736            .build()?;
737
738        TextInput::builder()
739            .text(&self.data.formatted_value())
740            .size((w - 19, h))
741            .parent(out.handle)
742            .flags(text_flags)
743            .build(&mut out.edit)?;
744
745        Button::builder()
746            .text("▴") // Alt: ▲ +
747            .size((20, h / 2 + 1))
748            .position((w - 20, -1))
749            .parent(out.handle)
750            .flags(btn_flags)
751            .build(&mut out.btn_up)?;
752
753        Button::builder()
754            .text("▾") // Alt: ▼ -
755            .size((20, h / 2 + 1))
756            .position((w - 20, (h / 2) - 1))
757            .parent(out.handle)
758            .flags(btn_flags)
759            .build(&mut out.btn_down)?;
760
761        Notice::builder()
762            .parent(out.handle)
763            .build(&mut out.notice)?;
764
765        if self.font.is_some() {
766            out.btn_up.set_font(self.font);
767            out.btn_down.set_font(self.font);
768            out.edit.set_font(self.font);
769        } else {
770            let font = Font::global_default();
771            let font_ref = font.as_ref();
772            out.btn_up.set_font(font_ref);
773            out.btn_down.set_font(font_ref);
774            out.edit.set_font(font_ref);
775        }
776
777        let plus_button = out.btn_up.handle;
778        let minus_button = out.btn_down.handle;
779        let text_handle = out.edit.handle;
780
781        let set_text = move |text: &str| {
782            let handle = text_handle.hwnd().unwrap();
783            unsafe {
784                wh::set_window_text(HWND(handle.cast()), text);
785            }
786        };
787
788        let handler = bind_raw_event_handler(&out.handle, 0xA4545, {
789            let notifier = out.notice.sender();
790            let handler_data = out.data.clone();
791            move |_hwnd, msg, w, l| {
792                if WM_COMMAND == msg {
793                    let handle = ControlHandle::Hwnd(l as _);
794                    let message = w as u32 >> 16;
795                    if message == windows::Win32::UI::WindowsAndMessaging::EN_CHANGE {
796                        // Corresponds to `nwg::Event::OnTextInput`
797                        let handle = text_handle.hwnd().unwrap();
798                        let text = unsafe { wh::get_window_text(HWND(handle.cast())) };
799                        let mut data = handler_data.borrow_mut();
800                        let mut valid = false;
801                        match &mut *data {
802                            NumberSelectData::Int {
803                                value, max, min, ..
804                            } => {
805                                if let Ok(new) = text.parse::<i64>() {
806                                    if *min <= new && new <= *max {
807                                        *value = new;
808                                        valid = true;
809                                    }
810                                }
811                            }
812                            NumberSelectData::Float {
813                                value, max, min, ..
814                            } => {
815                                if let Ok(new) = text.parse::<f64>() {
816                                    if *min <= new && new <= *max {
817                                        *value = new;
818                                        valid = true;
819                                    }
820                                }
821                            }
822                        }
823                        if valid {
824                            drop(data);
825                            notifier.notice();
826                        } else {
827                            let text = data.formatted_value();
828                            drop(data);
829                            set_text(&text);
830                        }
831                        return None;
832                    }
833                    let text = if message == BN_CLICKED && handle == plus_button {
834                        let mut data = handler_data.borrow_mut();
835                        data.increase();
836                        data.formatted_value()
837                    } else if message == BN_CLICKED && handle == minus_button {
838                        let mut data = handler_data.borrow_mut();
839                        data.decrease();
840                        data.formatted_value()
841                    } else {
842                        return None;
843                    };
844                    set_text(&text);
845                    notifier.notice();
846                } else if msg == windows::Win32::UI::WindowsAndMessaging::WM_MOUSEWHEEL {
847                    let scroll = (w as u32 >> 16) as i16;
848                    let mut data = handler_data.borrow_mut();
849                    match scroll.cmp(&0) {
850                        Ordering::Equal => return None,
851                        Ordering::Less => data.decrease(),
852                        Ordering::Greater => data.increase(),
853                    }
854                    let text = data.formatted_value();
855                    drop(data);
856                    set_text(&text);
857                    notifier.notice();
858                }
859                None
860            }
861        });
862        let edit_handler = bind_raw_event_handler(&out.edit.handle, 0xA4545, {
863            let notifier = out.notice.sender();
864            let handler_data = out.data.clone();
865            move |_hwnd, msg, w, _l| {
866                if msg == windows::Win32::UI::WindowsAndMessaging::WM_KEYDOWN {
867                    // https://learn.microsoft.com/en-us/windows/win32/inputdev/wm-keydown
868                    let keycode = w as u32;
869                    let text = if keycode == 38 {
870                        let mut data = handler_data.borrow_mut();
871                        data.increase();
872                        data.formatted_value()
873                    } else if keycode == 40 {
874                        let mut data = handler_data.borrow_mut();
875                        data.decrease();
876                        data.formatted_value()
877                    } else {
878                        return None;
879                    };
880                    set_text(&text);
881                    notifier.notice();
882                    // Suppress default action:
883                    return Some(0);
884                }
885                None
886            }
887        });
888
889        out.handler = Some(handler.unwrap());
890        out.edit_handler =
891            Some(edit_handler.expect("should create event handler for number select's text box"));
892
893        if !self.enabled {
894            out.set_enabled(self.enabled);
895        }
896
897        Ok(())
898    }
899}
900
901/// Adapted from the [`native_windows_gui::controls::handle_from_control`] module.
902macro_rules! handles {
903    ($control:ty) => {
904        #[allow(deprecated)]
905        impl From<&$control> for ControlHandle {
906            fn from(control: &$control) -> Self {
907                control.handle
908            }
909        }
910
911        #[allow(deprecated)]
912        impl From<&mut $control> for ControlHandle {
913            fn from(control: &mut $control) -> Self {
914                control.handle
915            }
916        }
917
918        #[allow(deprecated)]
919        impl PartialEq<ControlHandle> for $control {
920            fn eq(&self, other: &ControlHandle) -> bool {
921                self.handle == *other || self.notice.handle == *other
922            }
923        }
924
925        #[allow(deprecated)]
926        impl PartialEq<$control> for ControlHandle {
927            fn eq(&self, other: &$control) -> bool {
928                *self == other.handle || *self == other.notice.handle
929            }
930        }
931    };
932}
933handles!(NumberSelect2);