Skip to main content

native_windows_gui/controls/
scroll_bar.rs

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