native_windows_gui/controls/
tooltip.rs

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