Skip to main content

native_windows_gui2/controls/
tooltip.rs

1use super::{ControlBase, ControlHandle};
2use crate::win32::base_helper::{check_hwnd, from_utf16, to_utf16};
3use crate::win32::window_helper as wh;
4use crate::{Icon, NwgError};
5use std::{mem, ptr};
6use winapi::shared::minwindef::{LPARAM, UINT, WPARAM};
7use winapi::um::winnt::WCHAR;
8
9const NOT_BOUND: &'static str = "Tooltip is not yet bound to a winapi object";
10const BAD_HANDLE: &'static str = "INTERNAL ERROR: Tooltip handle is not HWND!";
11
12/// A select of default icon to show in a tooltip
13#[derive(Copy, Clone, Debug)]
14pub enum TooltipIcon {
15    None,
16    Info,
17    Warning,
18    Error,
19    InfoLarge,
20    WarningLarge,
21    ErrorLarge,
22}
23
24/**
25Tooltips appear automatically, or pop up, when the user pauses the mouse pointer over a tool or
26some other UI element. The tooltip appears near the pointer and disappears when the user
27clicks a mouse button, moves the pointer away from the tool, or simply waits for a few seconds.
28
29A tooltip can be applied to multiple controls, each with their own custom text.
30This is done/undone using the `register`/`unregister` functions. So do not think
31as Tooltip as a standalone toolip, but more like a manager.
32
33A tooltip can support static text using `register` and dynamic text using `register_callback`.
34
35Tooltip requires the `tooltip` features
36
37Example:
38
39```rust
40use native_windows_gui2 as nwg;
41
42/// Building a tooltip and add tooltips at the same time
43fn build_tooltip(tt: &mut nwg::Tooltip, btn1: &nwg::Button, btn2: &nwg::Button) {
44    nwg::Tooltip::builder()
45        .register(btn1, "A test button")
46        .register_callback(btn2)
47        .build(tt);
48}
49
50/// Adding/Updating a tooltip after the initial tooltip creation
51fn add_tooltip(btn: &nwg::Button, tt: &nwg::Tooltip) {
52    tt.register(btn, "This is a button!");
53}
54
55/// Dynamic tooltip callback setup
56fn add_dynamic_tooltip(tt: &nwg::Tooltip, btn: &nwg::Button) {
57    tt.register_callback(btn);
58}
59
60
61struct GuiStruct {
62    // Skipping other members
63    tt: nwg::Tooltip,
64    button: nwg::Button
65}
66
67impl GuiStruct {
68    /// The dynamic tooltip callback, triggered by the event loop
69    fn events_callback(&self, evt: nwg::Event, evt_data: &nwg::EventData, handle: nwg::ControlHandle) {
70        match evt {
71            nwg::Event::OnTooltipText => {
72                // Compare the handle to check which control will display the tooltip
73                if &handle == &self.button {
74                    let tooltip_data = evt_data.on_tooltip_text();
75                    tooltip_data.set_text(&format!("Button text: \"{}\"", self.button.text()));
76                }
77            },
78            _ => {}
79        }
80    }
81}
82
83
84
85```
86
87*/
88#[derive(Default, PartialEq, Eq)]
89pub struct Tooltip {
90    pub handle: ControlHandle,
91}
92
93impl Tooltip {
94    pub fn builder<'a>() -> TooltipBuilder<'a> {
95        TooltipBuilder {
96            title: None,
97            ico: None,
98            default_ico: None,
99            register: Vec::new(),
100            register_cb: Vec::new(),
101        }
102    }
103
104    /*
105    Work with Comclt32.dll version 6.0. Should be implemented eventually
106    Return the icon if it is a icon defined in TooltipIcon. If not, returns `None`.
107    pub fn default_icon(&self) -> Option<TooltipIcon> {
108        use winapi::um::commctrl::{TTGETTITLE, TTM_GETTITLE};
109        use winapi::um::commctrl::{TTI_NONE, TTI_INFO, TTI_WARNING, TTI_ERROR, TTI_INFO_LARGE, TTI_WARNING_LARGE, TTI_ERROR_LARGE};
110
111        if self.handle.blank() { panic!("{}", NOT_BOUND); }
112        let handle = self.handle.hwnd().expect(BAD_HANDLE);
113
114        let mut tt = TTGETTITLE {
115            dwSize: mem::size_of::<TTGETTITLE>() as DWORD,
116            uTitleBitmap: 0,
117            cch: 0,
118            pszTitle: ptr::null_mut()
119        };
120
121        let tt_ptr = &mut tt as *mut TTGETTITLE;
122        wh::send_message(handle, TTM_GETTITLE, 0, tt_ptr as LPARAM);
123
124        println!("{:?}", tt.uTitleBitmap);
125
126        match tt.uTitleBitmap as usize {
127            TTI_NONE => Some(TooltipIcon::None),
128            TTI_INFO => Some(TooltipIcon::Info),
129            TTI_WARNING => Some(TooltipIcon::Warning),
130            TTI_ERROR => Some(TooltipIcon::Error),
131            TTI_INFO_LARGE => Some(TooltipIcon::InfoLarge),
132            TTI_WARNING_LARGE => Some(TooltipIcon::WarningLarge),
133            TTI_ERROR_LARGE => Some(TooltipIcon::ErrorLarge),
134            _ => None
135        }
136    }
137    */
138
139    /// Return the current text of the tooltip. There is no way to know the size of the text so you have
140    /// to pass the buffer size. The default buffer size is 200 characters.
141    pub fn text(&self, owner: &ControlHandle, buffer_size: Option<usize>) -> String {
142        use winapi::shared::{basetsd::UINT_PTR, windef::RECT};
143        use winapi::um::commctrl::{TTF_IDISHWND, TTF_SUBCLASS, TTM_GETTEXTW, TTTOOLINFOW};
144
145        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
146
147        let owner_handle = {
148            if owner.blank() {
149                panic!("{}", NOT_BOUND);
150            }
151            owner.hwnd().expect(BAD_HANDLE)
152        };
153
154        let buffer_size = buffer_size.unwrap_or(200);
155        let mut text: Vec<WCHAR> = Vec::with_capacity(buffer_size);
156        unsafe {
157            text.set_len(buffer_size);
158        }
159
160        let mut tool = TTTOOLINFOW {
161            cbSize: mem::size_of::<TTTOOLINFOW>() as UINT,
162            uFlags: TTF_IDISHWND | TTF_SUBCLASS,
163            hwnd: owner_handle,
164            uId: owner_handle as UINT_PTR,
165            rect: RECT {
166                left: 0,
167                top: 0,
168                right: 0,
169                bottom: 0,
170            },
171            hinst: ptr::null_mut(),
172            lpszText: text.as_mut_ptr(),
173            lParam: 0,
174            lpReserved: ptr::null_mut(),
175        };
176
177        let tool_ptr = &mut tool as *mut TTTOOLINFOW;
178        wh::send_message(handle, TTM_GETTEXTW, 0, tool_ptr as LPARAM);
179
180        from_utf16(&text)
181    }
182
183    /// Change the text of a previously registered control
184    /// Use the `register` function is associate a control with this tooltip
185    pub fn set_text<'a>(&self, owner: &ControlHandle, text: &'a str) {
186        use winapi::shared::{basetsd::UINT_PTR, windef::RECT};
187        use winapi::um::commctrl::{TTF_IDISHWND, TTF_SUBCLASS, TTM_UPDATETIPTEXTW, TTTOOLINFOW};
188
189        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
190
191        let mut text = to_utf16(text);
192        let owner_handle = {
193            if owner.blank() {
194                panic!("{}", NOT_BOUND);
195            }
196            owner.hwnd().expect(BAD_HANDLE)
197        };
198
199        let tool = TTTOOLINFOW {
200            cbSize: mem::size_of::<TTTOOLINFOW>() as UINT,
201            uFlags: TTF_IDISHWND | TTF_SUBCLASS,
202            hwnd: owner_handle,
203            uId: owner_handle as UINT_PTR,
204            rect: RECT {
205                left: 0,
206                top: 0,
207                right: 0,
208                bottom: 0,
209            },
210            hinst: ptr::null_mut(),
211            lpszText: text.as_mut_ptr(),
212            lParam: 0,
213            lpReserved: ptr::null_mut(),
214        };
215
216        let tool_ptr = &tool as *const TTTOOLINFOW;
217        wh::send_message(handle, TTM_UPDATETIPTEXTW, 0, tool_ptr as LPARAM);
218    }
219
220    /// Set the icon and the title of a tooltip. This method use custom icon defined by user resources
221    pub fn set_decoration<'a>(&self, title: &'a str, ico: &Icon) {
222        use winapi::um::commctrl::TTM_SETTITLEW;
223
224        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
225        let title = to_utf16(title);
226        wh::send_message(
227            handle,
228            TTM_SETTITLEW,
229            ico.handle as WPARAM,
230            title.as_ptr() as LPARAM,
231        );
232    }
233
234    /// Set the icon and the title of a tooltip. This method use built-in icon defined by TooltipIcon
235    pub fn set_default_decoration<'a>(&self, title: &'a str, icon: TooltipIcon) {
236        use winapi::um::commctrl::TTM_SETTITLEW;
237        use winapi::um::commctrl::{
238            TTI_ERROR, TTI_ERROR_LARGE, TTI_INFO, TTI_INFO_LARGE, TTI_NONE, TTI_WARNING,
239            TTI_WARNING_LARGE,
240        };
241
242        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
243
244        let bitmap_handle = match icon {
245            TooltipIcon::None => TTI_NONE,
246            TooltipIcon::Info => TTI_INFO,
247            TooltipIcon::Warning => TTI_WARNING,
248            TooltipIcon::Error => TTI_ERROR,
249            TooltipIcon::InfoLarge => TTI_INFO_LARGE,
250            TooltipIcon::WarningLarge => TTI_WARNING_LARGE,
251            TooltipIcon::ErrorLarge => TTI_ERROR_LARGE,
252        };
253
254        let title = to_utf16(title);
255
256        wh::send_message(
257            handle,
258            TTM_SETTITLEW,
259            bitmap_handle as WPARAM,
260            title.as_ptr() as LPARAM,
261        );
262    }
263
264    /// Hide the tooltip popup
265    pub fn hide(&self) {
266        use winapi::um::commctrl::TTM_POP;
267
268        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
269        wh::send_message(handle, TTM_POP, 0, 0);
270    }
271
272    /// Return the number of controls registered by the tooltip
273    pub fn count(&self) -> usize {
274        use winapi::um::commctrl::TTM_GETTOOLCOUNT;
275
276        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
277        wh::send_message(handle, TTM_GETTOOLCOUNT, 0, 0) as usize
278    }
279
280    /// Set the delay time for the tooltip to spawn in milliseconds
281    /// Set the value to `None` to reset the value to default
282    pub fn set_delay_time(&self, delay: Option<u16>) {
283        use winapi::um::commctrl::{TTDT_INITIAL, TTM_SETDELAYTIME};
284
285        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
286        let value = match delay {
287            Some(d) => d & 0xFFFF,
288            None => u16::max_value() & 0xFFFF,
289        };
290
291        wh::send_message(
292            handle,
293            TTM_SETDELAYTIME,
294            TTDT_INITIAL as WPARAM,
295            value as LPARAM,
296        );
297    }
298
299    /// Return the delay time of the tooltip in milliseconds
300    pub fn delay_time(&self) -> u16 {
301        use winapi::um::commctrl::{TTDT_INITIAL, TTM_GETDELAYTIME};
302
303        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
304        wh::send_message(handle, TTM_GETDELAYTIME, TTDT_INITIAL as WPARAM, 0) as u16
305    }
306
307    /// Enable or disable the control
308    /// Windows does not support reading the enabled state of a tooltip btw.
309    pub fn set_enabled(&self, v: bool) {
310        use winapi::um::commctrl::TTM_ACTIVATE;
311
312        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
313        wh::send_message(handle, TTM_ACTIVATE, v as WPARAM, 0);
314    }
315
316    /// Register the tooltip under a control.
317    /// `owner` must be a window control.
318    pub fn register<'a, W: Into<ControlHandle>>(&self, owner: W, text: &'a str) {
319        use winapi::shared::{basetsd::UINT_PTR, windef::RECT};
320        use winapi::um::commctrl::{TTF_IDISHWND, TTF_SUBCLASS, TTM_ADDTOOLW, TTTOOLINFOW};
321
322        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
323
324        let owner = owner.into();
325
326        let mut text = to_utf16(text);
327        let owner_handle = {
328            if owner.blank() {
329                panic!("{}", NOT_BOUND);
330            }
331            owner.hwnd().expect(BAD_HANDLE)
332        };
333
334        let tool = TTTOOLINFOW {
335            cbSize: mem::size_of::<TTTOOLINFOW>() as UINT,
336            uFlags: TTF_IDISHWND | TTF_SUBCLASS,
337            hwnd: owner_handle,
338            uId: owner_handle as UINT_PTR,
339            rect: RECT {
340                left: 0,
341                top: 0,
342                right: 0,
343                bottom: 0,
344            },
345            hinst: ptr::null_mut(),
346            lpszText: text.as_mut_ptr(),
347            lParam: 0,
348            lpReserved: ptr::null_mut(),
349        };
350
351        let tool_ptr = &tool as *const TTTOOLINFOW;
352        wh::send_message(handle, TTM_ADDTOOLW, 0, tool_ptr as LPARAM);
353    }
354
355    /// Register the tooltip under a control.
356    /// `owner` must be a window control.
357    /// When the user trigger the tooltip, the application receives a `OnTooltipText` event
358    pub fn register_callback<W: Into<ControlHandle>>(&self, owner: W) {
359        use winapi::shared::{basetsd::UINT_PTR, windef::RECT};
360        use winapi::um::commctrl::{
361            LPSTR_TEXTCALLBACKW, TTF_IDISHWND, TTF_SUBCLASS, TTM_ADDTOOLW, TTTOOLINFOW,
362        };
363
364        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
365
366        let owner = owner.into();
367        let owner_handle = {
368            if owner.blank() {
369                panic!("{}", NOT_BOUND);
370            }
371            owner.hwnd().expect(BAD_HANDLE)
372        };
373
374        let tool = TTTOOLINFOW {
375            cbSize: mem::size_of::<TTTOOLINFOW>() as UINT,
376            uFlags: TTF_IDISHWND | TTF_SUBCLASS,
377            hwnd: owner_handle,
378            uId: owner_handle as UINT_PTR,
379            rect: RECT {
380                left: 0,
381                top: 0,
382                right: 0,
383                bottom: 0,
384            },
385            hinst: ptr::null_mut(),
386            lpszText: LPSTR_TEXTCALLBACKW,
387            lParam: 0,
388            lpReserved: ptr::null_mut(),
389        };
390
391        let tool_ptr = &tool as *const TTTOOLINFOW;
392        wh::send_message(handle, TTM_ADDTOOLW, 0, tool_ptr as LPARAM);
393    }
394
395    /// Remove the tooltip from a control
396    pub fn unregister<W: Into<ControlHandle>>(&self, owner: W) {
397        use winapi::shared::{basetsd::UINT_PTR, windef::RECT};
398        use winapi::um::commctrl::{TTF_IDISHWND, TTF_SUBCLASS, TTM_DELTOOLW, TTTOOLINFOW};
399
400        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
401        let owner = owner.into();
402
403        let owner_handle = {
404            if owner.blank() {
405                panic!("{}", NOT_BOUND);
406            }
407            owner.hwnd().expect(BAD_HANDLE)
408        };
409
410        let tool = TTTOOLINFOW {
411            cbSize: mem::size_of::<TTTOOLINFOW>() as UINT,
412            uFlags: TTF_IDISHWND | TTF_SUBCLASS,
413            hwnd: owner_handle,
414            uId: owner_handle as UINT_PTR,
415            rect: RECT {
416                left: 0,
417                top: 0,
418                right: 0,
419                bottom: 0,
420            },
421            hinst: ptr::null_mut(),
422            lpszText: ptr::null_mut(),
423            lParam: 0,
424            lpReserved: ptr::null_mut(),
425        };
426
427        let tool_ptr = &tool as *const TTTOOLINFOW;
428        wh::send_message(handle, TTM_DELTOOLW, 0, tool_ptr as LPARAM);
429    }
430
431    /// Winapi class name used during control creation
432    pub fn class_name(&self) -> &'static str {
433        winapi::um::commctrl::TOOLTIPS_CLASS
434    }
435
436    /// Winapi base flags used during window creation
437    pub fn flags(&self) -> u32 {
438        0
439    }
440
441    /// Winapi flags required by the control
442    pub fn forced_flags(&self) -> u32 {
443        use winapi::um::commctrl::{TTS_ALWAYSTIP, TTS_NOPREFIX};
444        use winapi::um::winuser::WS_POPUP;
445
446        WS_POPUP | TTS_ALWAYSTIP | TTS_NOPREFIX
447    }
448}
449
450impl Drop for Tooltip {
451    fn drop(&mut self) {
452        self.handle.destroy();
453    }
454}
455pub struct TooltipBuilder<'a> {
456    title: Option<&'a str>,
457    ico: Option<&'a Icon>,
458    default_ico: Option<TooltipIcon>,
459    register: Vec<(ControlHandle, &'a str)>,
460    register_cb: Vec<ControlHandle>,
461}
462
463impl<'a> TooltipBuilder<'a> {
464    pub fn register<W: Into<ControlHandle>>(
465        mut self,
466        widget: W,
467        text: &'a str,
468    ) -> TooltipBuilder<'a> {
469        self.register.push((widget.into(), text));
470        self
471    }
472
473    pub fn register_callback<W: Into<ControlHandle>>(mut self, widget: W) -> TooltipBuilder<'a> {
474        self.register_cb.push(widget.into());
475        self
476    }
477
478    pub fn decoration(
479        mut self,
480        title: Option<&'a str>,
481        ico: Option<&'a Icon>,
482    ) -> TooltipBuilder<'a> {
483        self.title = title;
484        self.ico = ico;
485        self
486    }
487
488    pub fn default_decoration(
489        mut self,
490        title: Option<&'a str>,
491        ico: Option<TooltipIcon>,
492    ) -> TooltipBuilder<'a> {
493        self.title = title;
494        self.default_ico = ico;
495        self
496    }
497
498    pub fn build(self, tooltip: &mut Tooltip) -> Result<(), NwgError> {
499        *tooltip = Default::default();
500
501        tooltip.handle = ControlBase::build_hwnd()
502            .class_name(tooltip.class_name())
503            .forced_flags(tooltip.forced_flags())
504            .flags(tooltip.flags())
505            .build()?;
506
507        if self.title.is_some() || self.ico.is_some() || self.default_ico.is_some() {
508            let title = self.title.unwrap_or("");
509            match (self.ico, self.default_ico) {
510                (Some(ico), None) | (Some(ico), _) => tooltip.set_decoration(title, ico),
511                (None, Some(ico)) => tooltip.set_default_decoration(title, ico),
512                (None, None) => tooltip.set_default_decoration(title, TooltipIcon::None),
513            }
514        }
515
516        for (handle, text) in self.register {
517            tooltip.register(&handle, text);
518        }
519
520        for handle in self.register_cb {
521            tooltip.register_callback(&handle);
522        }
523
524        Ok(())
525    }
526}