Skip to main content

native_windows_gui/controls/
label.rs

1use winapi::um::{
2    winuser::{WS_VISIBLE, WS_DISABLED, SS_WORDELLIPSIS},
3    wingdi::DeleteObject
4};
5
6use winapi::shared::windef::HBRUSH;
7use crate::win32::window_helper as wh;
8use crate::win32::base_helper::check_hwnd;
9use crate::{Font, NwgError, HTextAlign, VTextAlign, RawEventHandler, unbind_raw_event_handler};
10use super::{ControlBase, ControlHandle};
11use std::cell::RefCell;
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_gui 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
83    pub fn builder<'a>() -> LabelBuilder<'a> {
84        LabelBuilder {
85            text: "A label",
86            size: (130, 25),
87            position: (0, 0),
88            flags: None,
89            ex_flags: 0,
90            font: None,
91            parent: None,
92            h_align: HTextAlign::Left,
93            v_align: VTextAlign::Center,
94            background_color: None
95        }
96    }
97
98    /// Return the font of the control
99    pub fn font(&self) -> Option<Font> {
100        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
101
102        let font_handle = wh::get_window_font(handle);
103        if font_handle.is_null() {
104            None
105        } else {
106            Some(Font { handle: font_handle })
107        }
108    }
109
110    /// Set the font of the control
111    pub fn set_font(&self, font: Option<&Font>) {
112        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
113        unsafe { wh::set_window_font(handle, font.map(|f| f.handle), true); }
114    }
115
116    /// Return true if the control currently has the keyboard focus
117    pub fn focus(&self) -> bool {
118        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
119        unsafe { wh::get_focus(handle) }
120    }
121
122    /// Set the keyboard focus on the button.
123    pub fn set_focus(&self) {
124        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
125        unsafe { wh::set_focus(handle); }
126    }
127
128    /// Return true if the control user can interact with the control, return false otherwise
129    pub fn enabled(&self) -> bool {
130        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
131        unsafe { wh::get_window_enabled(handle) }
132    }
133
134    /// Enable or disable the control
135    pub fn set_enabled(&self, v: bool) {
136        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
137        unsafe { wh::set_window_enabled(handle, v) }
138    }
139
140    /// Return true if the control is visible to the user. Will return true even if the 
141    /// control is outside of the parent client view (ex: at the position (10000, 10000))
142    pub fn visible(&self) -> bool {
143        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
144        unsafe { wh::get_window_visibility(handle) }
145    }
146
147    /// Show or hide the control to the user
148    pub fn set_visible(&self, v: bool) {
149        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
150        unsafe { wh::set_window_visibility(handle, v) }
151    }
152
153    /// Return the size of the label in the parent window
154    pub fn size(&self) -> (u32, u32) {
155        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
156        unsafe { wh::get_window_size(handle) }
157    }
158
159    /// Set the size of the label in the parent window
160    pub fn set_size(&self, x: u32, y: u32) {
161        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
162        unsafe { wh::set_window_size(handle, x, y, false) }
163    }
164
165    /// Return the position of the label in the parent window
166    pub fn position(&self) -> (i32, i32) {
167        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
168        unsafe { wh::get_window_position(handle) }
169    }
170
171    /// Set the position of the label in the parent window
172    pub fn set_position(&self, x: i32, y: i32) {
173        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
174        unsafe { wh::set_window_position(handle, x, y) }
175    }
176
177    /// Return the label text
178    pub fn text(&self) -> String { 
179        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
180        unsafe { wh::get_window_text(handle) }
181    }
182
183    /// Set the label text
184    pub fn set_text<'a>(&self, v: &'a str) {
185        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
186        unsafe { wh::set_window_text(handle, v) }
187    }
188
189    /// Winapi class name used during control creation
190    pub fn class_name(&self) -> &'static str {
191        "STATIC"
192    }
193
194    /// Winapi base flags used during window creation
195    pub fn flags(&self) -> u32 {
196        use winapi::um::winuser::{SS_NOPREFIX, SS_LEFT};
197
198        WS_VISIBLE | SS_NOPREFIX | SS_LEFT
199    }
200
201    /// Winapi flags required by the control
202    pub fn forced_flags(&self) -> u32 {
203        use winapi::um::winuser::{SS_NOTIFY, WS_CHILD};
204
205        WS_CHILD | SS_NOTIFY
206    }
207
208    /// Center the text vertically.
209    fn hook_non_client_size(&mut self, bg: Option<[u8; 3]>, v_align: VTextAlign) {
210        use crate::bind_raw_event_handler_inner;
211        use winapi::shared::windef::{HWND, HGDIOBJ, RECT, POINT};
212        use winapi::shared::{basetsd::UINT_PTR, minwindef::LRESULT};
213        use winapi::um::winuser::{WM_CTLCOLORSTATIC, WM_NCCALCSIZE, WM_NCPAINT, WM_SIZE, DT_CALCRECT, DT_LEFT, NCCALCSIZE_PARAMS, COLOR_WINDOW};
214        use winapi::um::winuser::{SWP_NOOWNERZORDER, SWP_NOSIZE, SWP_NOMOVE, SWP_FRAMECHANGED};
215        use winapi::um::winuser::{GetDC, DrawTextW, ReleaseDC, GetClientRect, GetWindowRect, FillRect, ScreenToClient, SetWindowPos, GetWindowTextW, GetWindowTextLengthW};
216        use winapi::um::wingdi::{SelectObject, CreateSolidBrush, RGB};
217        use std::{mem, ptr};
218
219        if self.handle.blank() { panic!("{}", NOT_BOUND); }
220        let handle = self.handle.hwnd().expect(BAD_HANDLE);
221
222        let parent_handle = ControlHandle::Hwnd(wh::get_window_parent(handle));
223
224        let brush = match bg {
225            Some(c) => {
226                let b = unsafe { CreateSolidBrush(RGB(c[0], c[1], c[2])) };
227                self.background_brush = Some(b);
228                b
229            },
230            None => COLOR_WINDOW as HBRUSH
231        };
232
233        unsafe {
234
235        if bg.is_some() {
236            let handler0 = bind_raw_event_handler_inner(&parent_handle, handle as UINT_PTR, move |_hwnd, msg, _w, l| {
237                match msg {
238                    WM_CTLCOLORSTATIC => {
239                        let child = l as HWND;
240                        if child == handle {
241                            return Some(brush as LRESULT);
242                        }
243                    },
244                    _ => {}
245                }
246    
247                None
248            });
249
250            *self.handler0.borrow_mut() = Some(handler0.unwrap());
251        }
252
253        let handler1 = bind_raw_event_handler_inner(&self.handle, 0, move |hwnd, msg, w, l| {
254            match msg {
255                WM_NCCALCSIZE  => {
256                    if w == 0 { return None }
257
258                    // Calculate client area height needed for a font
259                    let font_handle = wh::get_window_font(hwnd);
260                    let mut r: RECT = mem::zeroed();
261                    let dc = GetDC(hwnd);
262                    
263                    let old = SelectObject(dc, font_handle as HGDIOBJ);
264
265                    let mut newline_count = 1;
266                    let buffer_size = GetWindowTextLengthW(handle) as usize;
267                    match buffer_size == 0 { 
268                        true => {
269                            let calc: [u16;2] = [75, 121];
270                            DrawTextW(dc, calc.as_ptr(), 2, &mut r, DT_CALCRECT | DT_LEFT);
271                        },
272                        false => {
273                            let mut buffer: Vec<u16> = vec![0; buffer_size + 1];
274                            if GetWindowTextW(handle, buffer.as_mut_ptr(), buffer_size as _) == 0 {
275                                let calc: [u16;2] = [75, 121];
276                                DrawTextW(dc, calc.as_ptr(), 2, &mut r, DT_CALCRECT | DT_LEFT);
277                            } else {
278                                for &c in buffer.iter() {
279                                    if c == b'\n' as u16 {
280                                        newline_count += 1;
281                                    }
282                                }
283                                DrawTextW(dc, buffer.as_ptr(), 2, &mut r, DT_CALCRECT | DT_LEFT);
284                            }
285                        }
286                    }
287
288                    let client_height = r.bottom * newline_count;
289
290                    SelectObject(dc, old);
291                    ReleaseDC(hwnd, dc);
292
293                    // Calculate NC area to center text.
294                    let mut client: RECT = mem::zeroed();
295                    let mut window: RECT = mem::zeroed();
296                    GetClientRect(hwnd, &mut client);
297                    GetWindowRect(hwnd, &mut window);
298
299                    let window_height = window.bottom - window.top;
300                    let info_ptr: *mut NCCALCSIZE_PARAMS = l as *mut NCCALCSIZE_PARAMS;
301                    let info = &mut *info_ptr;
302                    match v_align {
303                        VTextAlign::Top => {
304                            info.rgrc[0].bottom -= window_height - client_height;
305                        },
306                        VTextAlign::Center => {
307                            let center = ((window_height - client_height) / 2) - 1;
308                            info.rgrc[0].top += center;
309                            info.rgrc[0].bottom -= center;
310                        },
311                        VTextAlign::Bottom => {
312                            info.rgrc[0].top += window_height - client_height;
313                        },
314                    }
315                },
316                WM_NCPAINT  => {
317                    let mut window: RECT = mem::zeroed();
318                    let mut client: RECT = mem::zeroed();
319                    GetWindowRect(hwnd, &mut window);
320                    GetClientRect(hwnd, &mut client);
321
322                    let mut pt1 = POINT {x: window.left, y: window.top};
323                    ScreenToClient(hwnd, &mut pt1);
324
325                    let mut pt2 = POINT {x: window.right, y: window.bottom};
326                    ScreenToClient(hwnd, &mut pt2);
327
328                    let top = RECT {
329                        left: 0,
330                        top: pt1.y,
331                        right: client.right,
332                        bottom: client.top
333                    };
334
335                    let bottom = RECT {
336                        left: 0,
337                        top: client.bottom,
338                        right: client.right,
339                        bottom: pt2.y
340                    };
341
342                    let dc = GetDC(hwnd);
343                    FillRect(dc, &top, brush);
344                    FillRect(dc, &bottom, brush);
345                    ReleaseDC(hwnd, dc);
346                },
347                WM_SIZE => {
348                    SetWindowPos(hwnd, ptr::null_mut(), 0, 0, 0, 0, SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED);
349                },
350                _ => {}
351            }
352
353            None
354        });
355
356        *self.handler1.borrow_mut() = Some(handler1.unwrap());
357
358        }
359    }
360
361}
362
363impl PartialEq for Label {
364    fn eq(&self, other: &Self) -> bool {
365        self.handle == other.handle
366    }
367}
368
369
370impl Drop for Label {
371    fn drop(&mut self) {
372        let handler = self.handler0.borrow();
373        if let Some(h) = handler.as_ref() {
374            drop(unbind_raw_event_handler(h));
375        }
376
377        let handler = self.handler1.borrow();
378        if let Some(h) = handler.as_ref() {
379            drop(unbind_raw_event_handler(h));
380        }
381
382        if let Some(bg) = self.background_brush {
383            unsafe { DeleteObject(bg as _); }
384        }
385
386        self.handle.destroy();
387    }
388}
389
390pub struct LabelBuilder<'a> {
391    text: &'a str,
392    size: (i32, i32),
393    position: (i32, i32),
394    background_color: Option<[u8; 3]>,
395    flags: Option<LabelFlags>,
396    ex_flags: u32,
397    font: Option<&'a Font>,
398    h_align: HTextAlign,
399    v_align: VTextAlign,
400    parent: Option<ControlHandle>
401}
402
403impl<'a> LabelBuilder<'a> {
404
405    pub fn flags(mut self, flags: LabelFlags) -> LabelBuilder<'a> {
406        self.flags = Some(flags);
407        self
408    }
409
410    pub fn ex_flags(mut self, flags: u32) -> LabelBuilder<'a> {
411        self.ex_flags = flags;
412        self
413    }
414
415    pub fn text(mut self, text: &'a str) -> LabelBuilder<'a> {
416        self.text = text;
417        self
418    }
419
420    pub fn size(mut self, size: (i32, i32)) -> LabelBuilder<'a> {
421        self.size = size;
422        self
423    }
424
425    pub fn position(mut self, pos: (i32, i32)) -> LabelBuilder<'a> {
426        self.position = pos;
427        self
428    }
429
430    pub fn font(mut self, font: Option<&'a Font>) -> LabelBuilder<'a> {
431        self.font = font;
432        self
433    }
434
435    pub fn background_color(mut self, color: Option<[u8;3]>) -> LabelBuilder<'a> {
436        self.background_color = color;
437        self
438    }
439
440    pub fn h_align(mut self, align: HTextAlign) -> LabelBuilder<'a> {
441        self.h_align = align;
442        self
443    }
444
445    pub fn v_align(mut self, align: VTextAlign) -> LabelBuilder<'a> {
446        self.v_align = align;
447        self
448    }
449
450    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> LabelBuilder<'a> {
451        self.parent = Some(p.into());
452        self
453    }
454
455    pub fn build(self, out: &mut Label) -> Result<(), NwgError> {
456        use winapi::um::winuser::{SS_LEFT, SS_RIGHT, SS_CENTER};
457
458        let mut flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
459
460        match self.h_align {
461            HTextAlign::Left => { flags |= SS_LEFT; },
462            HTextAlign::Right => { flags |= SS_RIGHT; },
463            HTextAlign::Center => { flags |= SS_CENTER; },
464        }
465
466        let parent = match self.parent {
467            Some(p) => Ok(p),
468            None => Err(NwgError::no_parent("Label"))
469        }?;
470
471        // Drop the old object
472        *out = Label::default();
473
474        out.handle = ControlBase::build_hwnd()
475            .class_name(out.class_name())
476            .forced_flags(out.forced_flags())
477            .flags(flags)
478            .ex_flags(self.ex_flags)
479            .size(self.size)
480            .position(self.position)
481            .text(self.text)
482            .parent(Some(parent))
483            .build()?;
484
485        if self.font.is_some() {
486            out.set_font(self.font);
487        } else {
488            out.set_font(Font::global_default().as_ref());
489        }
490
491        out.hook_non_client_size(self.background_color, self.v_align);
492
493        Ok(())
494    }
495
496}
497