Skip to main content

native_windows_gui2/controls/
track_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;
6use std::ops::Range;
7use winapi::shared::{
8    minwindef::{LPARAM, WPARAM},
9    windef::HBRUSH,
10};
11use winapi::um::commctrl::{
12    TBS_AUTOTICKS, TBS_BOTTOM, TBS_ENABLESELRANGE, TBS_HORZ, TBS_LEFT, TBS_NOTICKS, TBS_RIGHT,
13    TBS_TOP, TBS_VERT,
14};
15use winapi::um::{
16    wingdi::DeleteObject,
17    winuser::{WS_TABSTOP, WS_VISIBLE},
18};
19
20const NOT_BOUND: &'static str = "TrackBar is not yet bound to a winapi object";
21const BAD_HANDLE: &'static str = "INTERNAL ERROR: TrackBar handle is not HWND!";
22
23bitflags! {
24    /**
25        The track bar  flags
26    */
27    pub struct TrackBarFlags: u32 {
28        const VISIBLE = WS_VISIBLE;
29        const AUTO_TICK = TBS_AUTOTICKS;
30        const VERTICAL = TBS_VERT;
31        const HORIZONTAL = TBS_HORZ;
32        const TICK_TOP = TBS_TOP;
33        const TICK_BOTTOM = TBS_BOTTOM;
34        const TICK_LEFT = TBS_LEFT;
35        const TICK_RIGHT = TBS_RIGHT;
36        const NO_TICK = TBS_NOTICKS;
37        const RANGE = TBS_ENABLESELRANGE;
38        const TAB_STOP = WS_TABSTOP;
39    }
40}
41
42/**
43A trackbar is a window that contains a slider (sometimes called a thumb) in a channel, and optional tick marks.
44When the user moves the slider, using either the mouse or the direction keys, the trackbar sends notification messages to indicate the change.
45
46Requires the `trackbar` feature.
47
48**Builder parameters:**
49  * `parent`:           **Required.** The trackbar parent container.
50  * `size`:             The trackbar size.
51  * `position`:         The trackbar position.
52  * `focus`:            The control receive focus after being created
53  * `flags`:            A combination of the TrackBarFlags values.
54  * `ex_flags`:         A combination of win32 window extended flags. Unlike `flags`, ex_flags must be used straight from winapi
55  * `range`:            The value range of the trackbar
56  * `selected_range`:   The selected value range of the trackbar. Used with `TrackBarFlags::RANGE`
57  * `pos`:              The current value of the trackbar
58  * `background_color`: The background color the of the trackbar
59
60
61**Control events:**
62  * `OnVerticalScroll`: When the value of a trackbar with the VERTICAL flags is changed
63  * `OnHorizontalScroll`: When the value of a trackbar with the HORIZONTAL flags is changed
64  * `MousePress(_)`: Generic mouse press events on the button
65  * `OnMouseMove`: Generic mouse mouse event
66  * `OnMouseWheel`: Generic mouse wheel event
67
68```rust
69use native_windows_gui2 as nwg;
70fn build_trackbar(track: &mut nwg::TrackBar, window: &nwg::Window) {
71    nwg::TrackBar::builder()
72        .range(Some(0..100))
73        .pos(Some(10))
74        .parent(window)
75        .build(track);
76}
77```
78
79*/
80#[derive(Default)]
81pub struct TrackBar {
82    pub handle: ControlHandle,
83    background_brush: Option<HBRUSH>,
84    handler0: RefCell<Option<RawEventHandler>>,
85}
86
87impl TrackBar {
88    pub fn builder() -> TrackBarBuilder {
89        TrackBarBuilder {
90            size: (100, 20),
91            position: (0, 0),
92            focus: false,
93            range: None,
94            selected_range: None,
95            pos: None,
96            flags: None,
97            ex_flags: 0,
98            parent: None,
99            background_color: None,
100        }
101    }
102
103    /// Retrieves the current logical position of the slider in a trackbar.
104    /// The logical positions are the integer values in the trackbar's range of minimum to maximum slider positions.
105    pub fn pos(&self) -> usize {
106        use winapi::um::commctrl::TBM_GETPOS;
107
108        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
109        wh::send_message(handle, TBM_GETPOS, 0, 0) as usize
110    }
111
112    /// Sets the current logical position of the slider in a trackbar.
113    pub fn set_pos(&self, p: usize) {
114        use winapi::um::commctrl::TBM_SETPOSNOTIFY;
115
116        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
117        wh::send_message(handle, TBM_SETPOSNOTIFY, 1, p as LPARAM);
118    }
119
120    /// Retrieves the starting and ending position of the current selection range in a trackbar.
121    /// Only work for trackbar with the `Range` flags
122    pub fn selection_range_pos(&self) -> Range<usize> {
123        use winapi::um::commctrl::{TBM_GETSELEND, TBM_GETSELSTART};
124
125        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
126
127        let end = wh::send_message(handle, TBM_GETSELEND, 0, 0) as usize;
128        let start = wh::send_message(handle, TBM_GETSELSTART, 0, 0) as usize;
129
130        start..end
131    }
132
133    /// Sets the range value of the trackbar
134    /// Only work for trackbar with the `Range` flags
135    pub fn set_selection_range_pos(&self, value: Range<usize>) {
136        use winapi::um::commctrl::{TBM_SETSELEND, TBM_SETSELSTART};
137
138        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
139
140        wh::send_message(handle, TBM_SETSELEND, 0, value.end as LPARAM);
141        wh::send_message(handle, TBM_SETSELSTART, 1, value.start as LPARAM);
142    }
143
144    /// Retrieves the minimum position for the slider in a trackbar.
145    pub fn range_min(&self) -> usize {
146        use winapi::um::commctrl::TBM_GETRANGEMIN;
147
148        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
149        wh::send_message(handle, TBM_GETRANGEMIN, 0, 0) as usize
150    }
151
152    /// Sets the minium logical position for the slider in a trackbar.
153    pub fn set_range_min(&self, min: usize) {
154        use winapi::um::commctrl::TBM_SETRANGEMIN;
155
156        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
157        wh::send_message(handle, TBM_SETRANGEMIN, 1, min as LPARAM);
158    }
159
160    /// Retrieves the maximum position for the slider in a trackbar.
161    pub fn range_max(&self) -> usize {
162        use winapi::um::commctrl::TBM_GETRANGEMAX;
163
164        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
165        wh::send_message(handle, TBM_GETRANGEMAX, 0, 0) as usize
166    }
167
168    /// Sets the maximum logical position for the slider in a trackbar.
169    pub fn set_range_max(&self, max: usize) {
170        use winapi::um::commctrl::TBM_SETRANGEMAX;
171
172        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
173        wh::send_message(handle, TBM_SETRANGEMAX, 1, max as LPARAM);
174    }
175
176    /// Retrieves the number of tick marks in a trackbar
177    pub fn tics_len(&self) -> usize {
178        use winapi::um::commctrl::TBM_GETNUMTICS;
179
180        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
181        wh::send_message(handle, TBM_GETNUMTICS, 0, 0) as usize
182    }
183
184    /// Retrieves the logical position of a tick mark in a trackbar.
185    /// The logical position can be any of the integer values in the trackbar's range of minimum to maximum slider positions.
186    pub fn tic_value(&self, index: usize) -> usize {
187        use winapi::um::commctrl::TBM_GETTIC;
188
189        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
190        wh::send_message(handle, TBM_GETTIC, index as WPARAM, 0) as usize
191    }
192
193    //
194    // Basic methods
195    //
196
197    /// Return true if the control user can interact with the control, return false otherwise
198    pub fn enabled(&self) -> bool {
199        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
200        wh::get_window_enabled(handle)
201    }
202
203    /// Enable or disable the control
204    pub fn set_enabled(&self, v: bool) {
205        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
206        wh::set_window_enabled(handle, v)
207    }
208
209    /// Return true if the control is visible to the user. Will return true even if the
210    /// control is outside of the parent client view (ex: at the position (10000, 10000))
211    pub fn visible(&self) -> bool {
212        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
213        wh::get_window_visibility(handle)
214    }
215
216    /// Show or hide the control to the user
217    pub fn set_visible(&self, v: bool) {
218        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
219        wh::set_window_visibility(handle, v)
220    }
221
222    /// Return the size of the button in the parent window
223    pub fn size(&self) -> (u32, u32) {
224        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
225        wh::get_window_size(handle)
226    }
227
228    /// Set the size of the button in the parent window
229    pub fn set_size(&self, x: u32, y: u32) {
230        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
231        wh::set_window_size(handle, x, y, false)
232    }
233
234    /// Return the position of the button in the parent window
235    pub fn position(&self) -> (i32, i32) {
236        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
237        wh::get_window_position(handle)
238    }
239
240    /// Set the position of the button in the parent window
241    pub fn set_position(&self, x: i32, y: i32) {
242        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
243        wh::set_window_position(handle, x, y)
244    }
245
246    /// Return true if the control currently has the keyboard focus
247    pub fn focus(&self) -> bool {
248        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
249        wh::get_focus(handle)
250    }
251
252    /// Set the keyboard focus on the track bar
253    pub fn set_focus(&self) {
254        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
255
256        wh::set_focus(handle);
257    }
258
259    /// Winapi class name used during control creation
260    pub fn class_name(&self) -> &'static str {
261        winapi::um::commctrl::TRACKBAR_CLASS
262    }
263
264    /// Winapi base flags used during window creation
265    pub fn flags(&self) -> u32 {
266        WS_VISIBLE | TBS_AUTOTICKS | WS_TABSTOP
267    }
268
269    /// Winapi flags required by the control
270    pub fn forced_flags(&self) -> u32 {
271        use winapi::um::winuser::WS_CHILD;
272
273        WS_CHILD
274    }
275
276    /// Change the label background color to transparent.
277    fn hook_background_color(&mut self, c: [u8; 3]) {
278        use crate::bind_raw_event_handler_inner;
279        use winapi::shared::{basetsd::UINT_PTR, minwindef::LRESULT, windef::HWND};
280        use winapi::um::wingdi::{CreateSolidBrush, RGB};
281        use winapi::um::winuser::WM_CTLCOLORSTATIC;
282
283        if self.handle.blank() {
284            panic!("{}", NOT_BOUND);
285        }
286        let handle = self.handle.hwnd().expect(BAD_HANDLE);
287
288        let parent_handle = ControlHandle::Hwnd(wh::get_window_parent(handle));
289        let brush = unsafe { CreateSolidBrush(RGB(c[0], c[1], c[2])) };
290        self.background_brush = Some(brush);
291
292        let handler = bind_raw_event_handler_inner(
293            &parent_handle,
294            handle as UINT_PTR,
295            move |_hwnd, msg, _w, l| {
296                match msg {
297                    WM_CTLCOLORSTATIC => {
298                        let child = l as HWND;
299                        if child == handle {
300                            return Some(brush as LRESULT);
301                        }
302                    }
303                    _ => {}
304                }
305
306                None
307            },
308        );
309
310        *self.handler0.borrow_mut() = Some(handler.unwrap());
311    }
312}
313
314impl Drop for TrackBar {
315    fn drop(&mut self) {
316        use crate::unbind_raw_event_handler;
317
318        let handler = self.handler0.borrow();
319        if let Some(h) = handler.as_ref() {
320            drop(unbind_raw_event_handler(h));
321        }
322
323        if let Some(bg) = self.background_brush {
324            unsafe {
325                DeleteObject(bg as _);
326            }
327        }
328
329        self.handle.destroy();
330    }
331}
332
333pub struct TrackBarBuilder {
334    size: (i32, i32),
335    position: (i32, i32),
336    focus: bool,
337    range: Option<Range<usize>>,
338    selected_range: Option<Range<usize>>,
339    pos: Option<usize>,
340    flags: Option<TrackBarFlags>,
341    ex_flags: u32,
342    parent: Option<ControlHandle>,
343    background_color: Option<[u8; 3]>,
344}
345
346impl TrackBarBuilder {
347    pub fn flags(mut self, flags: TrackBarFlags) -> TrackBarBuilder {
348        self.flags = Some(flags);
349        self
350    }
351
352    pub fn ex_flags(mut self, flags: u32) -> TrackBarBuilder {
353        self.ex_flags = flags;
354        self
355    }
356
357    pub fn size(mut self, size: (i32, i32)) -> TrackBarBuilder {
358        self.size = size;
359        self
360    }
361
362    pub fn position(mut self, pos: (i32, i32)) -> TrackBarBuilder {
363        self.position = pos;
364        self
365    }
366
367    pub fn focus(mut self, focus: bool) -> TrackBarBuilder {
368        self.focus = focus;
369        self
370    }
371
372    pub fn range(mut self, range: Option<Range<usize>>) -> TrackBarBuilder {
373        self.range = range;
374        self
375    }
376
377    pub fn selected_range(mut self, range: Option<Range<usize>>) -> TrackBarBuilder {
378        self.selected_range = range;
379        self
380    }
381
382    pub fn pos(mut self, pos: Option<usize>) -> TrackBarBuilder {
383        self.pos = pos;
384        self
385    }
386
387    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> TrackBarBuilder {
388        self.parent = Some(p.into());
389        self
390    }
391
392    pub fn background_color(mut self, color: Option<[u8; 3]>) -> TrackBarBuilder {
393        self.background_color = color;
394        self
395    }
396
397    pub fn build(self, out: &mut TrackBar) -> Result<(), NwgError> {
398        let flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
399
400        let parent = match self.parent {
401            Some(p) => Ok(p),
402            None => Err(NwgError::no_parent("TrackBar")),
403        }?;
404
405        *out = Default::default();
406
407        out.handle = ControlBase::build_hwnd()
408            .class_name(out.class_name())
409            .forced_flags(out.forced_flags())
410            .flags(flags)
411            .ex_flags(self.ex_flags)
412            .size(self.size)
413            .position(self.position)
414            .parent(Some(parent))
415            .build()?;
416
417        if self.background_color.is_some() {
418            out.hook_background_color(self.background_color.unwrap());
419        }
420
421        if self.focus {
422            out.set_focus();
423        }
424
425        if let Some(range) = self.range {
426            out.set_range_min(range.start);
427            out.set_range_max(range.end);
428        }
429
430        if let Some(range) = self.selected_range {
431            out.set_selection_range_pos(range);
432        }
433
434        if let Some(pos) = self.pos {
435            out.set_pos(pos);
436        }
437
438        Ok(())
439    }
440}
441
442impl PartialEq for TrackBar {
443    fn eq(&self, other: &Self) -> bool {
444        self.handle == other.handle
445    }
446}