Skip to main content

native_windows_gui2/controls/
scroll_bar.rs

1use super::{ControlBase, ControlHandle};
2use crate::win32::base_helper::check_hwnd;
3use crate::win32::window_helper as wh;
4use crate::{NwgError, RawEventHandler};
5use std::{cell::RefCell, mem, ops::Range};
6use winapi::um::winuser::{SBS_HORZ, SBS_VERT, WS_CHILD, WS_DISABLED, WS_TABSTOP, WS_VISIBLE};
7
8const NOT_BOUND: &'static str = "Scroll bar is not yet bound to a winapi object";
9const BAD_HANDLE: &'static str = "INTERNAL ERROR: Scroll bar handle is not HWND!";
10
11bitflags! {
12    /**
13        The scroll bar flags
14
15        * NONE:       No flags. Equivalent to a invisible scroll bar.
16        * VISIBLE:    The scroll bar is immediatly visible after creation
17        * DISABLED:   The scroll bar cannot be interacted with by the user. It also has a grayed out look.
18        * TAB_STOP:   The control can be selected using tab navigation
19        * HORIZONTAL: The scroll bar scrolls from top to bottom
20        * VERTICAL:   The scroll bar scrolls from left to right
21    */
22    pub struct ScrollBarFlags: u32 {
23        const NONE = 0;
24        const VISIBLE = WS_VISIBLE;
25        const DISABLED = WS_DISABLED;
26        const TAB_STOP = WS_TABSTOP;
27        const HORIZONTAL = SBS_HORZ;
28        const VERTICAL = SBS_VERT;
29    }
30}
31
32/**
33A window can display a data object, such as a document or a bitmap, that is larger than the window's client area. When provided with a scroll bar, the user can scroll a data object in the client area to bring into view the portions of the object that extend beyond the borders of the window.
34
35Requires the `scroll-bar` feature.
36
37**Builder parameters:**
38  * `parent`:    **Required.** The scroll bar parent container.
39  * `size`:      The scroll bar size.
40  * `position`:  The scroll bar position.
41  * `focus`:     The control receive focus after being created
42  * `flags`:     A combination of the ScrollBarFlags values.
43  * `ex_flags`:  A combination of win32 window extended flags. Unlike `flags`, ex_flags must be used straight from winapi
44  * `range`:     The value range of the scroll bar
45  * `pos`:       he current value of the scroll bar
46
47
48**Control events:**
49  * `OnVerticalScroll`: When the value of a scrollbar with the VERTICAL flags is changed
50  * `OnHorizontalScroll`: When the value of a scrollbar with the HORIZONTAL flags is changed
51  * `MousePress(_)`: Generic mouse press events on the button
52  * `OnMouseMove`: Generic mouse event
53  * `OnMouseWheel`: Generic mouse wheel event
54
55```rust
56use native_windows_gui2 as nwg;
57
58fn build_scrollbar(button: &mut nwg::ScrollBar, window: &nwg::Window) {
59    nwg::ScrollBar::builder()
60        .range(Some(0..100))
61        .pos(Some(10))
62        .parent(window)
63        .build(button);
64}
65```
66*/
67#[derive(Default)]
68pub struct ScrollBar {
69    pub handle: ControlHandle,
70    handler0: RefCell<Option<RawEventHandler>>,
71    handler1: RefCell<Option<RawEventHandler>>,
72}
73
74impl ScrollBar {
75    pub fn builder<'a>() -> ScrollBarBuilder {
76        ScrollBarBuilder {
77            size: (25, 100),
78            position: (0, 0),
79            enabled: true,
80            flags: None,
81            ex_flags: 0,
82            parent: None,
83            focus: false,
84            range: None,
85            pos: None,
86        }
87    }
88
89    /// Retrieves the current logical position of the slider in a scrollbar.
90    /// The logical positions are the integer values in the scrollbar's range of minimum to maximum slider positions.
91    pub fn pos(&self) -> usize {
92        use winapi::um::winuser::SBM_GETPOS;
93
94        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
95        wh::send_message(handle, SBM_GETPOS, 0, 0) as usize
96    }
97
98    /// Sets the current logical position of the slider in a scrollbar. If the value is out of range he value is rounded up or down to the nearest valid value..
99    pub fn set_pos(&self, p: usize) {
100        use winapi::um::winuser::SBM_SETPOS;
101
102        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
103        wh::send_message(handle, SBM_SETPOS, p, 1);
104    }
105
106    /// Returns the range of value the scrollbar can have
107    pub fn range(&self) -> Range<usize> {
108        use winapi::um::winuser::SBM_GETRANGE;
109
110        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
111
112        let mut min: u32 = 0;
113        let mut max: u32 = 0;
114
115        wh::send_message(
116            handle,
117            SBM_GETRANGE,
118            &mut min as *mut u32 as _,
119            &mut max as *mut u32 as _,
120        );
121
122        (min as usize)..(max as usize)
123    }
124
125    /// Sets the range of value the scrollbar can have
126    pub fn set_range(&self, range: Range<usize>) {
127        use winapi::um::winuser::SBM_SETRANGE;
128
129        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
130        wh::send_message(handle, SBM_SETRANGE, range.start as _, range.end as _);
131    }
132
133    /// Returns true if the control currently has the keyboard focus
134    pub fn focus(&self) -> bool {
135        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
136        wh::get_focus(handle)
137    }
138
139    /// Sets the keyboard focus on the button.
140    pub fn set_focus(&self) {
141        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
142
143        wh::set_focus(handle);
144    }
145
146    /// Returns true if the control user can interact with the control, return false otherwise
147    pub fn enabled(&self) -> bool {
148        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
149        wh::get_window_enabled(handle)
150    }
151
152    /// Enable or disable the control
153    pub fn set_enabled(&self, v: bool) {
154        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
155        wh::set_window_enabled(handle, v)
156    }
157
158    /// Returns true if the control is visible to the user. Will return true even if the
159    /// control is outside of the parent client view (ex: at the position (10000, 10000))
160    pub fn visible(&self) -> bool {
161        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
162        wh::get_window_visibility(handle)
163    }
164
165    /// Show or hide the control to the user
166    pub fn set_visible(&self, v: bool) {
167        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
168        wh::set_window_visibility(handle, v)
169    }
170
171    /// Returns the size of the button in the parent window
172    pub fn size(&self) -> (u32, u32) {
173        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
174        wh::get_window_size(handle)
175    }
176
177    /// Sets the size of the button in the parent window
178    pub fn set_size(&self, x: u32, y: u32) {
179        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
180        wh::set_window_size(handle, x, y, false)
181    }
182
183    /// Returns the position of the button in the parent window
184    pub fn position(&self) -> (i32, i32) {
185        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
186        wh::get_window_position(handle)
187    }
188
189    /// Sets the position of the button in the parent window
190    pub fn set_position(&self, x: i32, y: i32) {
191        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
192        wh::set_window_position(handle, x, y)
193    }
194
195    /// Winapi class name used during control creation
196    pub fn class_name(&self) -> &'static str {
197        "SCROLLBAR"
198    }
199
200    /// Winapi base flags used during window creation
201    pub fn flags(&self) -> u32 {
202        WS_VISIBLE | WS_TABSTOP | SBS_VERT
203    }
204
205    /// Winapi flags required by the control
206    pub fn forced_flags(&self) -> u32 {
207        WS_CHILD
208    }
209
210    /// Scrollbar are useless on their own. We need to hook them and handle ALL the message ourself. yay windows...
211    fn hook_scrollbar_controls(&self) {
212        use crate::bind_raw_event_handler_inner;
213        use winapi::shared::{
214            minwindef::{LOWORD, TRUE},
215            windef::HWND,
216        };
217        use winapi::um::winuser::{
218            GET_WHEEL_DELTA_WPARAM, GetScrollInfo, SB_BOTTOM, SB_CTL, SB_LINEDOWN, SB_LINELEFT,
219            SB_LINERIGHT, SB_LINEUP, SB_PAGEDOWN, SB_PAGELEFT, SB_PAGERIGHT, SB_PAGEUP,
220            SB_THUMBTRACK, SB_TOP, SCROLLINFO, SIF_ALL, SIF_POS, SetScrollInfo, WM_HSCROLL,
221            WM_MOUSEWHEEL, WM_VSCROLL,
222        };
223
224        if self.handle.blank() {
225            panic!("{}", NOT_BOUND);
226        }
227        let handle = self.handle.hwnd().expect(BAD_HANDLE);
228        let parent_handle = ControlHandle::Hwnd(wh::get_window_parent(handle));
229
230        let handler1 =
231            bind_raw_event_handler_inner(&parent_handle, handle as _, move |_hwnd, msg, w, l| {
232                let mut si: SCROLLINFO = unsafe { mem::zeroed() };
233                match msg {
234                    WM_HSCROLL => {
235                        if (l as HWND) != handle {
236                            return None;
237                        }
238
239                        si.cbSize = mem::size_of::<SCROLLINFO>() as u32;
240                        si.fMask = SIF_ALL;
241                        unsafe {
242                            GetScrollInfo(handle, SB_CTL as i32, &mut si);
243                        }
244
245                        let event = LOWORD(w as u32) as isize;
246                        match event {
247                            SB_LINELEFT => {
248                                si.nPos -= 1;
249                            }
250                            SB_LINERIGHT => {
251                                si.nPos += 1;
252                            }
253                            SB_PAGELEFT => {
254                                si.nPos -= si.nPage as i32;
255                            }
256                            SB_PAGERIGHT => {
257                                si.nPos += si.nPage as i32;
258                            }
259                            SB_THUMBTRACK => {
260                                si.nPos = si.nTrackPos;
261                            }
262                            _ => {}
263                        }
264
265                        si.fMask = SIF_POS;
266                        unsafe {
267                            SetScrollInfo(handle, SB_CTL as _, &si, TRUE);
268                        }
269                        //return Some(0);
270                    }
271                    WM_VSCROLL => {
272                        if (l as HWND) != handle {
273                            return None;
274                        }
275
276                        si.cbSize = mem::size_of::<SCROLLINFO>() as u32;
277                        si.fMask = SIF_ALL;
278                        unsafe {
279                            GetScrollInfo(handle, SB_CTL as i32, &mut si);
280                        }
281
282                        let event = LOWORD(w as u32) as isize;
283                        match event {
284                            SB_TOP => {
285                                si.nPos = si.nMin;
286                            }
287                            SB_BOTTOM => {
288                                si.nPos = si.nMax;
289                            }
290                            SB_LINEUP => {
291                                si.nPos -= 1;
292                            }
293                            SB_LINEDOWN => {
294                                si.nPos += 1;
295                            }
296                            SB_PAGEUP => {
297                                si.nPos -= si.nPage as i32;
298                            }
299                            SB_PAGEDOWN => {
300                                si.nPos += si.nPage as i32;
301                            }
302                            SB_THUMBTRACK => {
303                                si.nPos = si.nTrackPos;
304                            }
305                            _ => {}
306                        }
307
308                        si.fMask = SIF_POS;
309                        unsafe {
310                            SetScrollInfo(handle, SB_CTL as _, &si, TRUE);
311                        }
312                        //return Some(0);
313                    }
314                    _ => {}
315                }
316
317                None
318            });
319
320        let handler2 = bind_raw_event_handler_inner(&self.handle, 0, move |_hwnd, msg, w, _l| {
321            unsafe {
322                match msg {
323                    WM_MOUSEWHEEL => {
324                        let mut si: SCROLLINFO = mem::zeroed();
325
326                        si.cbSize = mem::size_of::<SCROLLINFO>() as u32;
327                        si.fMask = SIF_ALL;
328                        GetScrollInfo(handle, SB_CTL as i32, &mut si);
329
330                        let delta = GET_WHEEL_DELTA_WPARAM(w);
331                        match delta > 0 {
332                            true => {
333                                si.nPos -= 1;
334                            }
335                            false => {
336                                si.nPos += 1;
337                            }
338                        }
339
340                        si.fMask = SIF_POS;
341                        SetScrollInfo(handle, SB_CTL as _, &si, TRUE);
342                        return Some(0);
343                    }
344                    _ => {}
345                }
346            }
347
348            None
349        });
350
351        *self.handler0.borrow_mut() = Some(handler1.unwrap());
352        *self.handler1.borrow_mut() = Some(handler2.unwrap());
353    }
354}
355
356impl Drop for ScrollBar {
357    fn drop(&mut self) {
358        use crate::unbind_raw_event_handler;
359
360        let handler = self.handler0.borrow();
361        if let Some(h) = handler.as_ref() {
362            drop(unbind_raw_event_handler(h));
363        }
364
365        let handler = self.handler1.borrow();
366        if let Some(h) = handler.as_ref() {
367            drop(unbind_raw_event_handler(h));
368        }
369
370        self.handle.destroy();
371    }
372}
373
374pub struct ScrollBarBuilder {
375    size: (i32, i32),
376    position: (i32, i32),
377    enabled: bool,
378    flags: Option<ScrollBarFlags>,
379    ex_flags: u32,
380    parent: Option<ControlHandle>,
381    focus: bool,
382    range: Option<Range<usize>>,
383    pos: Option<usize>,
384}
385
386impl ScrollBarBuilder {
387    pub fn flags(mut self, flags: ScrollBarFlags) -> ScrollBarBuilder {
388        self.flags = Some(flags);
389        self
390    }
391
392    pub fn ex_flags(mut self, flags: u32) -> ScrollBarBuilder {
393        self.ex_flags = flags;
394        self
395    }
396
397    pub fn size(mut self, size: (i32, i32)) -> ScrollBarBuilder {
398        self.size = size;
399        self
400    }
401
402    pub fn position(mut self, pos: (i32, i32)) -> ScrollBarBuilder {
403        self.position = pos;
404        self
405    }
406
407    pub fn enabled(mut self, e: bool) -> ScrollBarBuilder {
408        self.enabled = e;
409        self
410    }
411
412    pub fn focus(mut self, focus: bool) -> ScrollBarBuilder {
413        self.focus = focus;
414        self
415    }
416
417    pub fn range(mut self, range: Option<Range<usize>>) -> ScrollBarBuilder {
418        self.range = range;
419        self
420    }
421
422    pub fn pos(mut self, pos: Option<usize>) -> ScrollBarBuilder {
423        self.pos = pos;
424        self
425    }
426
427    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> ScrollBarBuilder {
428        self.parent = Some(p.into());
429        self
430    }
431
432    pub fn build(self, out: &mut ScrollBar) -> Result<(), NwgError> {
433        let flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
434
435        let parent = match self.parent {
436            Some(p) => Ok(p),
437            None => Err(NwgError::no_parent("ScrollBar")),
438        }?;
439
440        *out = Default::default();
441
442        out.handle = ControlBase::build_hwnd()
443            .class_name(out.class_name())
444            .forced_flags(out.forced_flags())
445            .flags(flags)
446            .ex_flags(self.ex_flags)
447            .size(self.size)
448            .position(self.position)
449            .parent(Some(parent))
450            .build()?;
451
452        out.set_enabled(self.enabled);
453
454        if let Some(range) = self.range {
455            out.set_range(range);
456        }
457
458        if let Some(pos) = self.pos {
459            out.set_pos(pos);
460        }
461
462        if self.focus {
463            out.set_focus();
464        }
465
466        out.hook_scrollbar_controls();
467
468        Ok(())
469    }
470}
471
472impl PartialEq for ScrollBar {
473    fn eq(&self, other: &Self) -> bool {
474        self.handle == other.handle
475    }
476}