native_windows_gui/controls/
rich_label.rs

1use winapi::um::winuser::{WS_VISIBLE, ES_MULTILINE, WS_DISABLED, EM_SETSEL};
2use crate::win32::window_helper as wh;
3use crate::win32::base_helper::check_hwnd;
4use crate::win32::richedit as rich;
5use crate::{Font, NwgError, RawEventHandler, HTextAlign, unbind_raw_event_handler};
6use super::{ControlBase, ControlHandle, CharFormat, ParaFormat};
7
8use std::{rc::Rc, ops::Range, cell::RefCell};
9
10
11const NOT_BOUND: &'static str = "RichLabel is not yet bound to a winapi object";
12const BAD_HANDLE: &'static str = "INTERNAL ERROR: RichLabel handle is not HWND!";
13
14bitflags! {
15    /**
16        The rich label flags
17
18        * VISIBLE:        The rich text box is immediatly visible after creation
19        * MULTI_LINE:     The label can be on multiple lines
20        * SAVE_SELECTION: Show the text selection even if the control is not active
21        * DISABLED:       Disable all events and prevent text selection
22    */
23    pub struct RichLabelFlags: u32 {
24        const NONE = 0;
25        const VISIBLE = WS_VISIBLE;
26        const DISABLED = WS_DISABLED;
27        const MULTI_LINE = ES_MULTILINE;
28    }
29}
30
31
32/**
33A rich label is a label that supports rich text. This control is built on top of the rich text box control and as such
34require the `rich-textbox` feature. Enable "MULTI_LINE" to support multi line labels.
35
36Unlike the basic `Label`, this version supports:
37
38* Colored text
39* Multiple fonts
40* Styled text such as bold, underscore, strikeout, etc
41* Bullet point list
42* Paragraph with custom indent/offset
43* Custom line spacing
44
45**Builder parameters:**
46  * `parent`:           **Required.** The label parent container.
47  * `text`:             The label text.
48  * `size`:             The label size.
49  * `position`:         The label position.
50  * `enabled`:          If the label is enabled. A disabled label won't trigger events
51  * `flags`:            A combination of the LabelFlags values.
52  * `ex_flags`:         A combination of win32 window extended flags. Unlike `flags`, ex_flags must be used straight from winapi
53  * `font`:             The font used for the label text
54  * `background_color`: The background color of the label
55  * `h_align`:          The horizontal aligment of the label.
56  * `line_height`:      The line height in pixels for the vertical aligment. Can be None to disable vertical aligment.
57                        Real line height cannot be guessed by NWG due to the text formatting
58
59**Control events:**
60  * `MousePress(_)`: Generic mouse press events on the label
61  * `OnMouseMove`: Generic mouse mouse event
62  * `OnMouseWheel`: Generic mouse wheel event
63
64** Example **
65
66```rust
67use native_windows_gui as nwg;
68fn build_label(label: &mut nwg::RichLabel, window: &nwg::Window, font: &nwg::Font) {
69    nwg::RichLabel::builder()
70        .text("Hello")
71        .font(Some(font))
72        .parent(window)
73        .build(label);
74}
75
76*/
77#[derive(Default)]
78pub struct RichLabel {
79    pub handle: ControlHandle,
80    line_height: Rc<RefCell<Option<i32>>>,
81    handler0: RefCell<Option<RawEventHandler>>,
82}
83
84impl RichLabel {
85
86    pub fn builder<'a>() -> RichLabelBuilder<'a> {
87        RichLabelBuilder {
88            text: "A rich label",
89            size: (130, 25),
90            position: (0, 0),
91            flags: None,
92            ex_flags: 0,
93            font: None,
94            h_align: HTextAlign::Left,
95            background_color: None,
96            line_height: None,
97            parent: None,
98        }
99    }
100
101    /// Sets the background color for a rich edit control.
102    /// You cannot get the background color of a rich label
103    pub fn set_background_color(&self, color: [u8; 3]) {
104        use winapi::um::wingdi::RGB;
105
106        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
107        let color = RGB(color[0], color[1], color[2]);
108        wh::send_message(handle, rich::EM_SETBKGNDCOLOR, 0, color as _);
109    }
110
111    /// Sets the character format of the selected range of text
112    pub fn set_char_format(&self, r: Range<u32>, fmt: &CharFormat) {
113        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
114        wh::send_message(handle, EM_SETSEL as u32, r.start as usize, r.end as isize);
115        rich::set_char_format(handle, fmt);
116        wh::send_message(handle, EM_SETSEL as u32, 0, 0);
117    }
118
119    /// Returns the character format of the selected range of text
120    pub fn char_format(&self, r: Range<u32>) -> CharFormat {
121        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
122
123        wh::send_message(handle, EM_SETSEL as u32, r.start as usize, r.end as isize);
124        let out = rich::char_format(handle);
125        wh::send_message(handle, EM_SETSEL as u32, 0, 0);
126
127        out
128    }
129
130    /// Sets the paragraph formatting for the selected range of text in a rich edit control
131    pub fn set_para_format(&self, r: Range<u32>, fmt: &ParaFormat) {
132        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
133
134        wh::send_message(handle, EM_SETSEL as u32, r.start as usize, r.end as isize);
135        rich::set_para_format(handle, fmt);
136        wh::send_message(handle, EM_SETSEL as u32, 0, 0);
137    }
138
139    /// Returns the paragraph formatting for the selected range of text in a rich edit control
140    /// If more than one paragraph is selected, receive the attributes of the first paragraph
141    pub fn para_format(&self, r: Range<u32>) -> ParaFormat {
142        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
143
144        wh::send_message(handle, EM_SETSEL as u32, r.start as usize, r.end as isize);
145        let out = rich::para_format(handle);
146        wh::send_message(handle, EM_SETSEL as u32, 0, 0);
147
148        out
149    }
150
151    /// Return the selected range of characters by the user in the text input
152    pub fn selection(&self) -> Range<usize> {
153        use winapi::um::winuser::EM_GETSEL;
154
155        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
156
157        let (mut out1, mut out2) = (0u32, 0u32);
158        let (ptr1, ptr2) = (&mut out1 as *mut u32, &mut out2 as *mut u32);
159        wh::send_message(handle, EM_GETSEL as u32, ptr1 as _, ptr2 as _);
160
161        Range { start: out1 as usize, end: out2 as usize }
162    }
163
164    /// Return the selected range of characters by the user in the text input
165    pub fn set_selection(&self, r: Range<u32>) {
166        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
167        wh::send_message(handle, EM_SETSEL as u32, r.start as usize, r.end as isize);
168    }
169
170    /// Return the length of the user input in the control. This is better than `control.text().len()` as it
171    /// does not allocate a string in memory
172    pub fn len(&self) -> u32 {
173        use winapi::um::winuser::EM_LINELENGTH;
174
175        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
176        wh::send_message(handle, EM_LINELENGTH as u32, 0, 0) as u32
177    }
178
179    /// Remove all text from the textbox
180    pub fn clear(&self) {
181        self.set_text("");
182    }
183
184    /// Sets the line height for the vertical alignment
185    pub fn set_line_height(&self, height: Option<i32>) {
186        *self.line_height.borrow_mut() = height;
187    }
188
189    /// Returns the line height for the vertical alignment
190    pub fn line_height(&self) -> Option<i32> {
191        *self.line_height.borrow()
192    }
193
194    /// Set base font of the control
195    /// It is not possible to get the base font handle of a rich label. Use `char_format` instead.
196    pub fn set_font(&self, font: Option<&Font>) {
197        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
198        unsafe { wh::set_window_font(handle, font.map(|f| f.handle), true); }
199    }
200
201    /// Return true if the control is visible to the user. Will return true even if the 
202    /// control is outside of the parent client view (ex: at the position (10000, 10000))
203    pub fn visible(&self) -> bool {
204        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
205        unsafe { wh::get_window_visibility(handle) }
206    }
207
208    /// Show or hide the control to the user
209    pub fn set_visible(&self, v: bool) {
210        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
211        unsafe { wh::set_window_visibility(handle, v) }
212    }
213
214    /// Return the size of the button in the parent window
215    pub fn size(&self) -> (u32, u32) {
216        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
217        unsafe { wh::get_window_size(handle) }
218    }
219
220    /// Set the size of the button in the parent window
221    pub fn set_size(&self, x: u32, y: u32) {
222        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
223        unsafe { wh::set_window_size(handle, x, y, false) }
224    }
225
226    /// Return the position of the button in the parent window
227    pub fn position(&self) -> (i32, i32) {
228        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
229        unsafe { wh::get_window_position(handle) }
230    }
231
232    /// Set the position of the button in the parent window
233    pub fn set_position(&self, x: i32, y: i32) {
234        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
235        unsafe { wh::set_window_position(handle, x, y) }
236    }
237
238    /// Return the text displayed in the TextInput
239    pub fn text(&self) -> String { 
240        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
241        unsafe { wh::get_window_text(handle) }
242    }
243
244    /// Set the text displayed in the TextInput
245    pub fn set_text<'a>(&self, v: &'a str) {
246        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
247        unsafe { wh::set_window_text(handle, v) }
248    }
249
250    /// Winapi class name used during control creation
251    pub fn class_name(&self) -> &'static str {
252        "RICHEDIT50W"
253    }
254
255    /// Winapi base flags used during window creation
256    pub fn flags(&self) -> u32 {
257        WS_VISIBLE
258    }
259
260    /// Winapi flags required by the control
261    pub fn forced_flags(&self) -> u32 {
262        use winapi::um::winuser::{ES_READONLY, WS_CHILD};
263        
264        ES_READONLY | WS_CHILD
265    }
266
267    unsafe fn override_events(&self) {
268        use crate::bind_raw_event_handler_inner;
269        use winapi::shared::windef::{RECT, HBRUSH, POINT};
270        use winapi::um::winuser::{WM_NCCALCSIZE, WM_SIZE, WM_NCPAINT};
271        use winapi::um::winuser::{SWP_NOOWNERZORDER, SWP_NOSIZE, SWP_NOMOVE, SWP_FRAMECHANGED, COLOR_WINDOW, NCCALCSIZE_PARAMS};
272        use winapi::um::winuser::{GetDC, ReleaseDC, GetClientRect, GetWindowRect, ScreenToClient, FillRect, SetWindowPos};
273        use std::{mem, ptr};
274
275        let callback_line_height = self.line_height.clone();
276
277        //let cursor = Cursor::from_system(OemCursor::Normal);
278        let handler0 = bind_raw_event_handler_inner(&self.handle, 0, move |hwnd, msg, w, l| {
279            match msg {
280                WM_NCCALCSIZE => {
281                    let client_height = *callback_line_height.borrow();
282                    if w == 0 || client_height.is_none() { return None }
283
284                    let client_height = client_height.unwrap();
285
286                    // Calculate NC area to center text.
287                    let mut client: RECT = mem::zeroed();
288                    let mut window: RECT = mem::zeroed();
289                    GetClientRect(hwnd, &mut client);
290                    GetWindowRect(hwnd, &mut window);
291
292                    let window_height = window.bottom - window.top;
293                    let center = ((window_height - client_height) / 2) - 1;
294
295                    // Save the info
296                    let info_ptr: *mut NCCALCSIZE_PARAMS = l as *mut NCCALCSIZE_PARAMS;
297                    let info = &mut *info_ptr;
298
299                    info.rgrc[0].top += center;
300                    info.rgrc[0].bottom -= center;
301
302                    None
303                },
304                WM_NCPAINT => {
305                    let client_height = *callback_line_height.borrow();
306                    if client_height.is_none() { return None }
307
308                    let mut window: RECT = mem::zeroed();
309                    let mut client: RECT = mem::zeroed();
310                    GetWindowRect(hwnd, &mut window);
311                    GetClientRect(hwnd, &mut client);
312
313                    let mut pt1 = POINT {x: window.left, y: window.top};
314                    ScreenToClient(hwnd, &mut pt1);
315
316                    let mut pt2 = POINT {x: window.right, y: window.bottom};
317                    ScreenToClient(hwnd, &mut pt2);
318
319                    let top = RECT {
320                        left: 0,
321                        top: pt1.y,
322                        right: client.right,
323                        bottom: client.top
324                    };
325
326                    let bottom = RECT {
327                        left: 0,
328                        top: client.bottom,
329                        right: client.right,
330                        bottom: pt2.y
331                    };
332
333                    let dc = GetDC(hwnd);
334                    let brush = COLOR_WINDOW as HBRUSH;
335                    FillRect(dc, &top, brush);
336                    FillRect(dc, &bottom, brush);
337                    ReleaseDC(hwnd, dc);
338                    None
339                },
340                WM_SIZE => {
341                    SetWindowPos(hwnd, ptr::null_mut(), 0, 0, 0, 0, SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED);
342                    None
343                },
344                _ => None
345            }            
346        });
347
348        *self.handler0.borrow_mut() = Some(handler0.unwrap());
349    }
350
351}
352
353impl PartialEq for RichLabel {
354    fn eq(&self, other: &Self) -> bool {
355        self.handle == other.handle
356    }
357}
358
359impl Drop for RichLabel {
360    fn drop(&mut self) {
361        let handler = self.handler0.borrow();
362        if let Some(h) = handler.as_ref() {
363            drop(unbind_raw_event_handler(h));
364        }
365
366        self.handle.destroy();
367    }
368}
369
370pub struct RichLabelBuilder<'a> {
371    text: &'a str,
372    size: (i32, i32),
373    position: (i32, i32),
374    flags: Option<RichLabelFlags>,
375    ex_flags: u32,
376    font: Option<&'a Font>,
377    h_align: HTextAlign,
378    background_color: Option<[u8; 3]>,
379    line_height: Option<i32>,
380    parent: Option<ControlHandle>,
381}
382
383impl<'a> RichLabelBuilder<'a> {
384
385    pub fn text(mut self, text: &'a str) -> RichLabelBuilder<'a> {
386        self.text = text;
387        self
388    }
389
390    pub fn size(mut self, size: (i32, i32)) -> RichLabelBuilder<'a> {
391        self.size = size;
392        self
393    }
394
395    pub fn position(mut self, pos: (i32, i32)) -> RichLabelBuilder<'a> {
396        self.position = pos;
397        self
398    }
399
400    pub fn font(mut self, font: Option<&'a Font>) -> RichLabelBuilder<'a> {
401        self.font = font;
402        self
403    }
404
405    pub fn flags(mut self, flags: RichLabelFlags) -> RichLabelBuilder<'a> {
406        self.flags = Some(flags);
407        self
408    }
409
410    pub fn ex_flags(mut self, flags: u32) -> RichLabelBuilder<'a> {
411        self.ex_flags = flags;
412        self
413    }
414
415    pub fn h_align(mut self, align: HTextAlign) -> RichLabelBuilder<'a> {
416        self.h_align = align;
417        self
418    }
419
420    pub fn background_color(mut self, color: Option<[u8; 3]>) -> RichLabelBuilder<'a> {
421        self.background_color = color;
422        self
423    }
424
425    pub fn line_height(mut self, height: Option<i32>) -> RichLabelBuilder<'a> {
426        self.line_height = height;
427        self
428    }
429
430    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> RichLabelBuilder<'a> {
431        self.parent = Some(p.into());
432        self
433    }
434
435    pub fn build(self, out: &mut RichLabel) -> Result<(), NwgError> {
436        use winapi::um::winuser::{SS_LEFT, SS_RIGHT, SS_CENTER};
437
438        let mut flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
439        match self.h_align {
440            HTextAlign::Left => { flags |= SS_LEFT; },
441            HTextAlign::Right => { flags |= SS_RIGHT; },
442            HTextAlign::Center => { flags |= SS_CENTER; },
443        }
444
445        let parent = match self.parent {
446            Some(p) => Ok(p),
447            None => Err(NwgError::no_parent("RichLabel"))
448        }?;
449
450        // Drop the old object
451        *out = Default::default();
452    
453        *out.line_height.borrow_mut() = self.line_height;
454        out.handle = ControlBase::build_hwnd()
455            .class_name(out.class_name())
456            .forced_flags(out.forced_flags())
457            .flags(flags)
458            .ex_flags(self.ex_flags)
459            .size(self.size)
460            .position(self.position)
461            .text(self.text)
462            .parent(Some(parent))
463            .build()?;
464
465        if self.font.is_some() {
466            out.set_font(self.font);
467        } else {
468            out.set_font(Font::global_default().as_ref());
469        }
470
471        if let Some(color) = self.background_color {
472            out.set_background_color(color);
473        } else {
474            if let Ok(color) = wh::get_background_color(parent.hwnd().unwrap()) {
475                out.set_background_color(color);
476            }
477        }
478
479        unsafe { out.override_events(); }
480
481        Ok(())
482    }
483
484}