native_windows_gui2/controls/
rich_label.rs

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