native_windows_gui2/controls/
label.rs

1use winapi::um::{
2    wingdi::DeleteObject,
3    winuser::{SS_WORDELLIPSIS, WS_DISABLED, WS_VISIBLE},
4};
5
6use super::{ControlBase, ControlHandle};
7use crate::win32::base_helper::check_hwnd;
8use crate::win32::window_helper as wh;
9use crate::{Font, HTextAlign, NwgError, RawEventHandler, VTextAlign, unbind_raw_event_handler};
10use std::cell::RefCell;
11use winapi::shared::windef::HBRUSH;
12
13const NOT_BOUND: &'static str = "Label is not yet bound to a winapi object";
14const BAD_HANDLE: &'static str = "INTERNAL ERROR: Label handle is not HWND!";
15
16bitflags! {
17    /**
18        The label flags
19
20        * NONE:     No flags. Equivalent to a invisible blank label.
21        * VISIBLE:  The label is immediatly visible after creation
22        * DISABLED: The label cannot be interacted with by the user. It also has a grayed out look.
23    */
24    pub struct LabelFlags: u32 {
25        const NONE = 0;
26        const VISIBLE = WS_VISIBLE;
27        const DISABLED = WS_DISABLED;
28
29        /// Truncate the label if the text is too long. A label with this style CANNOT have multiple lines.
30        const ELIPSIS = SS_WORDELLIPSIS;
31    }
32}
33
34/**
35A label is a single line of static text. Use `\r\n` to split the text on multiple lines.
36
37Label is not behind any features.
38
39**Builder parameters:**
40  * `parent`:           **Required.** The label parent container.
41  * `text`:             The label text.
42  * `size`:             The label size.
43  * `position`:         The label position.
44  * `enabled`:          If the label is enabled. A disabled label won't trigger events
45  * `flags`:            A combination of the LabelFlags values.
46  * `ex_flags`:         A combination of win32 window extended flags. Unlike `flags`, ex_flags must be used straight from winapi
47  * `font`:             The font used for the label text
48  * `background_color`: The background color of the label
49  * `h_align`:          The horizontal aligment of the label
50
51**Control events:**
52  * `OnLabelClick`: When the user click the label
53  * `OnLabelDoubleClick`: When the user double click a label
54  * `MousePress(_)`: Generic mouse press events on the label
55  * `OnMouseMove`: Generic mouse mouse event
56  * `OnMouseWheel`: Generic mouse wheel event
57
58
59** Example **
60
61```rust
62use native_windows_gui2 as nwg;
63fn build_label(label: &mut nwg::Label, window: &nwg::Window, font: &nwg::Font) {
64    nwg::Label::builder()
65        .text("Hello")
66        .font(Some(font))
67        .parent(window)
68        .build(label);
69}
70```
71
72*/
73#[derive(Default)]
74pub struct Label {
75    pub handle: ControlHandle,
76    background_brush: Option<HBRUSH>,
77    handler0: RefCell<Option<RawEventHandler>>,
78    handler1: RefCell<Option<RawEventHandler>>,
79}
80
81impl Label {
82    pub fn builder<'a>() -> LabelBuilder<'a> {
83        LabelBuilder {
84            text: "A label",
85            size: (130, 25),
86            position: (0, 0),
87            flags: None,
88            ex_flags: 0,
89            font: None,
90            parent: None,
91            h_align: HTextAlign::Left,
92            v_align: VTextAlign::Center,
93            background_color: None,
94        }
95    }
96
97    /// Return the font of the control
98    pub fn font(&self) -> Option<Font> {
99        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
100
101        let font_handle = wh::get_window_font(handle);
102        if font_handle.is_null() {
103            None
104        } else {
105            Some(Font {
106                handle: font_handle,
107            })
108        }
109    }
110
111    /// Set the font of the control
112    pub fn set_font(&self, font: Option<&Font>) {
113        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
114
115        wh::set_window_font(handle, font.map(|f| f.handle), true);
116    }
117
118    /// Return true if the control currently has the keyboard focus
119    pub fn focus(&self) -> bool {
120        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
121        wh::get_focus(handle)
122    }
123
124    /// Set the keyboard focus on the button.
125    pub fn set_focus(&self) {
126        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
127
128        wh::set_focus(handle);
129    }
130
131    /// Return true if the control user can interact with the control, return false otherwise
132    pub fn enabled(&self) -> bool {
133        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
134        wh::get_window_enabled(handle)
135    }
136
137    /// Enable or disable the control
138    pub fn set_enabled(&self, v: bool) {
139        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
140        wh::set_window_enabled(handle, v)
141    }
142
143    /// Return true if the control is visible to the user. Will return true even if the
144    /// control is outside of the parent client view (ex: at the position (10000, 10000))
145    pub fn visible(&self) -> bool {
146        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
147        wh::get_window_visibility(handle)
148    }
149
150    /// Show or hide the control to the user
151    pub fn set_visible(&self, v: bool) {
152        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
153        wh::set_window_visibility(handle, v)
154    }
155
156    /// Return the size of the label in the parent window
157    pub fn size(&self) -> (u32, u32) {
158        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
159        wh::get_window_size(handle)
160    }
161
162    /// Set the size of the label in the parent window
163    pub fn set_size(&self, x: u32, y: u32) {
164        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
165        wh::set_window_size(handle, x, y, false)
166    }
167
168    /// Return the position of the label in the parent window
169    pub fn position(&self) -> (i32, i32) {
170        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
171        wh::get_window_position(handle)
172    }
173
174    /// Set the position of the label in the parent window
175    pub fn set_position(&self, x: i32, y: i32) {
176        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
177        wh::set_window_position(handle, x, y)
178    }
179
180    /// Return the label text
181    pub fn text(&self) -> String {
182        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
183        wh::get_window_text(handle)
184    }
185
186    /// Set the label text
187    pub fn set_text<'a>(&self, v: &'a str) {
188        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
189        wh::set_window_text(handle, v)
190    }
191
192    /// Winapi class name used during control creation
193    pub fn class_name(&self) -> &'static str {
194        "STATIC"
195    }
196
197    /// Winapi base flags used during window creation
198    pub fn flags(&self) -> u32 {
199        use winapi::um::winuser::{SS_LEFT, SS_NOPREFIX};
200
201        WS_VISIBLE | SS_NOPREFIX | SS_LEFT
202    }
203
204    /// Winapi flags required by the control
205    pub fn forced_flags(&self) -> u32 {
206        use winapi::um::winuser::{SS_NOTIFY, WS_CHILD};
207
208        WS_CHILD | SS_NOTIFY
209    }
210
211    /// Center the text vertically.
212    fn hook_non_client_size(&mut self, bg: Option<[u8; 3]>, v_align: VTextAlign) {
213        use crate::bind_raw_event_handler_inner;
214        use std::{mem, ptr};
215        use winapi::shared::windef::{HGDIOBJ, HWND, POINT, RECT};
216        use winapi::shared::{basetsd::UINT_PTR, minwindef::LRESULT};
217        use winapi::um::wingdi::{CreateSolidBrush, RGB, SelectObject};
218        use winapi::um::winuser::{
219            COLOR_WINDOW, DT_CALCRECT, DT_LEFT, NCCALCSIZE_PARAMS, WM_CTLCOLORSTATIC,
220            WM_NCCALCSIZE, WM_NCPAINT, WM_SIZE,
221        };
222        use winapi::um::winuser::{
223            DrawTextW, FillRect, GetClientRect, GetDC, GetWindowRect, GetWindowTextLengthW,
224            GetWindowTextW, ReleaseDC, ScreenToClient, SetWindowPos,
225        };
226        use winapi::um::winuser::{SWP_FRAMECHANGED, SWP_NOMOVE, SWP_NOOWNERZORDER, SWP_NOSIZE};
227
228        if self.handle.blank() {
229            panic!("{}", NOT_BOUND);
230        }
231        let handle = self.handle.hwnd().expect(BAD_HANDLE);
232
233        let parent_handle = ControlHandle::Hwnd(wh::get_window_parent(handle));
234
235        let brush = match bg {
236            Some(c) => {
237                let b = unsafe { CreateSolidBrush(RGB(c[0], c[1], c[2])) };
238                self.background_brush = Some(b);
239                b
240            }
241            None => COLOR_WINDOW as HBRUSH,
242        };
243
244        unsafe {
245            if bg.is_some() {
246                let handler0 = bind_raw_event_handler_inner(
247                    &parent_handle,
248                    handle as UINT_PTR,
249                    move |_hwnd, msg, _w, l| {
250                        match msg {
251                            WM_CTLCOLORSTATIC => {
252                                let child = l as HWND;
253                                if child == handle {
254                                    return Some(brush as LRESULT);
255                                }
256                            }
257                            _ => {}
258                        }
259
260                        None
261                    },
262                );
263
264                *self.handler0.borrow_mut() = Some(handler0.unwrap());
265            }
266
267            let handler1 = bind_raw_event_handler_inner(&self.handle, 0, move |hwnd, msg, w, l| {
268                match msg {
269                    WM_NCCALCSIZE => {
270                        if w == 0 {
271                            return None;
272                        }
273
274                        // Calculate client area height needed for a font
275                        let font_handle = wh::get_window_font(hwnd);
276                        let mut r: RECT = mem::zeroed();
277                        let dc = GetDC(hwnd);
278
279                        let old = SelectObject(dc, font_handle as HGDIOBJ);
280
281                        let mut newline_count = 1;
282                        let buffer_size = GetWindowTextLengthW(handle) as usize;
283                        match buffer_size == 0 {
284                            true => {
285                                let calc: [u16; 2] = [75, 121];
286                                DrawTextW(dc, calc.as_ptr(), 2, &mut r, DT_CALCRECT | DT_LEFT);
287                            }
288                            false => {
289                                let mut buffer: Vec<u16> = vec![0; buffer_size + 1];
290                                if GetWindowTextW(handle, buffer.as_mut_ptr(), buffer_size as _)
291                                    == 0
292                                {
293                                    let calc: [u16; 2] = [75, 121];
294                                    DrawTextW(dc, calc.as_ptr(), 2, &mut r, DT_CALCRECT | DT_LEFT);
295                                } else {
296                                    for &c in buffer.iter() {
297                                        if c == b'\n' as u16 {
298                                            newline_count += 1;
299                                        }
300                                    }
301                                    DrawTextW(
302                                        dc,
303                                        buffer.as_ptr(),
304                                        2,
305                                        &mut r,
306                                        DT_CALCRECT | DT_LEFT,
307                                    );
308                                }
309                            }
310                        }
311
312                        let client_height = r.bottom * newline_count;
313
314                        SelectObject(dc, old);
315                        ReleaseDC(hwnd, dc);
316
317                        // Calculate NC area to center text.
318                        let mut client: RECT = mem::zeroed();
319                        let mut window: RECT = mem::zeroed();
320                        GetClientRect(hwnd, &mut client);
321                        GetWindowRect(hwnd, &mut window);
322
323                        let window_height = window.bottom - window.top;
324                        let info_ptr: *mut NCCALCSIZE_PARAMS = l as *mut NCCALCSIZE_PARAMS;
325                        let info = &mut *info_ptr;
326                        match v_align {
327                            VTextAlign::Top => {
328                                info.rgrc[0].bottom -= window_height - client_height;
329                            }
330                            VTextAlign::Center => {
331                                let center = ((window_height - client_height) / 2) - 1;
332                                info.rgrc[0].top += center;
333                                info.rgrc[0].bottom -= center;
334                            }
335                            VTextAlign::Bottom => {
336                                info.rgrc[0].top += window_height - client_height;
337                            }
338                        }
339                    }
340                    WM_NCPAINT => {
341                        let mut window: RECT = mem::zeroed();
342                        let mut client: RECT = mem::zeroed();
343                        GetWindowRect(hwnd, &mut window);
344                        GetClientRect(hwnd, &mut client);
345
346                        let mut pt1 = POINT {
347                            x: window.left,
348                            y: window.top,
349                        };
350                        ScreenToClient(hwnd, &mut pt1);
351
352                        let mut pt2 = POINT {
353                            x: window.right,
354                            y: window.bottom,
355                        };
356                        ScreenToClient(hwnd, &mut pt2);
357
358                        let top = RECT {
359                            left: 0,
360                            top: pt1.y,
361                            right: client.right,
362                            bottom: client.top,
363                        };
364
365                        let bottom = RECT {
366                            left: 0,
367                            top: client.bottom,
368                            right: client.right,
369                            bottom: pt2.y,
370                        };
371
372                        let dc = GetDC(hwnd);
373                        FillRect(dc, &top, brush);
374                        FillRect(dc, &bottom, brush);
375                        ReleaseDC(hwnd, dc);
376                    }
377                    WM_SIZE => {
378                        SetWindowPos(
379                            hwnd,
380                            ptr::null_mut(),
381                            0,
382                            0,
383                            0,
384                            0,
385                            SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED,
386                        );
387                    }
388                    _ => {}
389                }
390
391                None
392            });
393
394            *self.handler1.borrow_mut() = Some(handler1.unwrap());
395        }
396    }
397}
398
399impl PartialEq for Label {
400    fn eq(&self, other: &Self) -> bool {
401        self.handle == other.handle
402    }
403}
404
405impl Drop for Label {
406    fn drop(&mut self) {
407        let handler = self.handler0.borrow();
408        if let Some(h) = handler.as_ref() {
409            drop(unbind_raw_event_handler(h));
410        }
411
412        let handler = self.handler1.borrow();
413        if let Some(h) = handler.as_ref() {
414            drop(unbind_raw_event_handler(h));
415        }
416
417        if let Some(bg) = self.background_brush {
418            unsafe {
419                DeleteObject(bg as _);
420            }
421        }
422
423        self.handle.destroy();
424    }
425}
426
427pub struct LabelBuilder<'a> {
428    text: &'a str,
429    size: (i32, i32),
430    position: (i32, i32),
431    background_color: Option<[u8; 3]>,
432    flags: Option<LabelFlags>,
433    ex_flags: u32,
434    font: Option<&'a Font>,
435    h_align: HTextAlign,
436    v_align: VTextAlign,
437    parent: Option<ControlHandle>,
438}
439
440impl<'a> LabelBuilder<'a> {
441    pub fn flags(mut self, flags: LabelFlags) -> LabelBuilder<'a> {
442        self.flags = Some(flags);
443        self
444    }
445
446    pub fn ex_flags(mut self, flags: u32) -> LabelBuilder<'a> {
447        self.ex_flags = flags;
448        self
449    }
450
451    pub fn text(mut self, text: &'a str) -> LabelBuilder<'a> {
452        self.text = text;
453        self
454    }
455
456    pub fn size(mut self, size: (i32, i32)) -> LabelBuilder<'a> {
457        self.size = size;
458        self
459    }
460
461    pub fn position(mut self, pos: (i32, i32)) -> LabelBuilder<'a> {
462        self.position = pos;
463        self
464    }
465
466    pub fn font(mut self, font: Option<&'a Font>) -> LabelBuilder<'a> {
467        self.font = font;
468        self
469    }
470
471    pub fn background_color(mut self, color: Option<[u8; 3]>) -> LabelBuilder<'a> {
472        self.background_color = color;
473        self
474    }
475
476    pub fn h_align(mut self, align: HTextAlign) -> LabelBuilder<'a> {
477        self.h_align = align;
478        self
479    }
480
481    pub fn v_align(mut self, align: VTextAlign) -> LabelBuilder<'a> {
482        self.v_align = align;
483        self
484    }
485
486    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> LabelBuilder<'a> {
487        self.parent = Some(p.into());
488        self
489    }
490
491    pub fn build(self, out: &mut Label) -> Result<(), NwgError> {
492        use winapi::um::winuser::{SS_CENTER, SS_LEFT, SS_RIGHT};
493
494        let mut flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
495
496        match self.h_align {
497            HTextAlign::Left => {
498                flags |= SS_LEFT;
499            }
500            HTextAlign::Right => {
501                flags |= SS_RIGHT;
502            }
503            HTextAlign::Center => {
504                flags |= SS_CENTER;
505            }
506        }
507
508        let parent = match self.parent {
509            Some(p) => Ok(p),
510            None => Err(NwgError::no_parent("Label")),
511        }?;
512
513        // Drop the old object
514        *out = Label::default();
515
516        out.handle = ControlBase::build_hwnd()
517            .class_name(out.class_name())
518            .forced_flags(out.forced_flags())
519            .flags(flags)
520            .ex_flags(self.ex_flags)
521            .size(self.size)
522            .position(self.position)
523            .text(self.text)
524            .parent(Some(parent))
525            .build()?;
526
527        if self.font.is_some() {
528            out.set_font(self.font);
529        } else {
530            out.set_font(Font::global_default().as_ref());
531        }
532
533        out.hook_non_client_size(self.background_color, self.v_align);
534
535        Ok(())
536    }
537}