native_windows_gui2/controls/
text_input.rs

1use super::{ControlBase, ControlHandle};
2use crate::win32::base_helper::{check_hwnd, to_utf16};
3use crate::win32::window_helper as wh;
4use crate::{Font, HTextAlign, NwgError, RawEventHandler};
5use std::cell::RefCell;
6use std::char;
7use std::ops::Range;
8use winapi::shared::{
9    minwindef::{LPARAM, UINT, WPARAM},
10    windef::HBRUSH,
11};
12use winapi::um::{
13    wingdi::DeleteObject,
14    winuser::{
15        ES_AUTOHSCROLL, ES_CENTER, ES_LEFT, ES_NUMBER, ES_RIGHT, WS_DISABLED, WS_TABSTOP,
16        WS_VISIBLE,
17    },
18};
19
20const NOT_BOUND: &'static str = "TextInput is not yet bound to a winapi object";
21const BAD_HANDLE: &'static str = "INTERNAL ERROR: TextInput handle is not HWND!";
22
23bitflags! {
24    /**
25        The text input flags
26
27        * VISIBLE:     The text input is immediatly visible after creation
28        * DISABLED:    The text input cannot be interacted with by the user. It also has a grayed out look.
29        * NUMBER:      The text input only accepts number
30        * AUTO_SCROLL: The text input automatically scrolls text to the right by 10 characters when the user types a character
31                       at the end of the line. When the user presses the ENTER key, the control scrolls all text back to position zero.
32        * TAB_STOP:    The text input can be selected using tab navigation
33    */
34    pub struct TextInputFlags: u32 {
35        const VISIBLE = WS_VISIBLE;
36        const DISABLED = WS_DISABLED;
37        const NUMBER = ES_NUMBER;
38        const AUTO_SCROLL = ES_AUTOHSCROLL;
39        const TAB_STOP = WS_TABSTOP;
40    }
41}
42
43/**
44An edit control is a rectangular control window to permit the user to enter and edit text by typing on the keyboard
45This control only allow a single line input. For block of text, use `TextBox`.
46Winapi documentation: https://docs.microsoft.com/en-us/windows/win32/controls/about-edit-controls#text-and-input-styles
47
48TextInput is not behind any features.
49
50**Builder parameters:**
51  * `parent`:           **Required.** The text input parent container.
52  * `text`:             The text input text.
53  * `size`:             The text input size.
54  * `position`:         The text input position.
55  * `flags`:            A combination of the TextInputFlags values.
56  * `ex_flags`:         A combination of win32 window extended flags. Unlike `flags`, ex_flags must be used straight from winapi
57  * `font`:             The font used for the text input text
58  * `limit`:            The maximum number of character that can be inserted in the control
59  * `readonly`:         If the text input should allow user input or not
60  * `password`:         The password character. If set to None, the textinput is a regular control.
61  * `align`:            The alignment of the text in the text input
62  * `background_color`: The color of the textinput top and bottom padding. This is not the white background under the text.
63  * `focus`:            The control receive focus after being created
64
65**Control events:**
66  * `OnTextInput`: When a TextInput value is changed
67  * `MousePress(_)`: Generic mouse press events on the button
68  * `OnMouseMove`: Generic mouse mouse event
69  * `OnMouseWheel`: Generic mouse wheel event
70
71```rust
72use native_windows_gui2 as nwg;
73fn build_box(tbox: &mut nwg::TextInput, window: &nwg::Window, font: &nwg::Font) {
74    nwg::TextInput::builder()
75        .text("Hello")
76        .font(Some(font))
77        .parent(window)
78        .build(tbox);
79}
80```
81*/
82#[derive(Default)]
83pub struct TextInput {
84    pub handle: ControlHandle,
85    background_brush: Option<HBRUSH>,
86    handler0: RefCell<Option<RawEventHandler>>,
87}
88
89impl TextInput {
90    pub fn builder<'a>() -> TextInputBuilder<'a> {
91        TextInputBuilder {
92            text: "",
93            placeholder_text: None,
94            size: (100, 25),
95            position: (0, 0),
96            flags: None,
97            ex_flags: 0,
98            limit: 0,
99            password: None,
100            align: HTextAlign::Left,
101            readonly: false,
102            focus: false,
103            font: None,
104            parent: None,
105            background_color: None,
106        }
107    }
108
109    /// Return the font of the control
110    pub fn font(&self) -> Option<Font> {
111        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
112        let font_handle = wh::get_window_font(handle);
113        if font_handle.is_null() {
114            None
115        } else {
116            Some(Font {
117                handle: font_handle,
118            })
119        }
120    }
121
122    /// Set the font of the control
123    pub fn set_font(&self, font: Option<&Font>) {
124        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
125
126        wh::set_window_font(handle, font.map(|f| f.handle), true);
127    }
128
129    /// Return the password character displayed by the text input. If the input is not a password, return None.
130    pub fn password_char(&self) -> Option<char> {
131        use winapi::um::winuser::EM_GETPASSWORDCHAR;
132
133        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
134
135        let raw_char = wh::send_message(handle, EM_GETPASSWORDCHAR as u32, 0, 0) as u32;
136        match raw_char {
137            0 => None,
138            v => char::from_u32(v),
139        }
140    }
141
142    /// Set or Remove the password character displayed by the text input.
143    /// If the input is not a password all character are re-rendered with the new character
144    pub fn set_password_char(&self, c: Option<char>) {
145        use winapi::um::winuser::{EM_SETPASSWORDCHAR, InvalidateRect};
146
147        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
148        wh::send_message(
149            handle,
150            EM_SETPASSWORDCHAR as u32,
151            c.map(|c| c as usize).unwrap_or(0),
152            0,
153        );
154
155        // The control needs to be manually refreshed
156        unsafe {
157            InvalidateRect(handle, ::std::ptr::null(), 1);
158        }
159    }
160
161    /// Return the number of maximum character allowed in this text input
162    pub fn limit(&self) -> u32 {
163        use winapi::um::winuser::EM_GETLIMITTEXT;
164
165        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
166        wh::send_message(handle, EM_GETLIMITTEXT as u32, 0, 0) as u32
167    }
168
169    /// Set the number of maximum character allowed in this text input
170    /// If `limit` is 0, the text length is set to 0x7FFFFFFE characters
171    pub fn set_limit(&self, limit: usize) {
172        use winapi::um::winuser::EM_SETLIMITTEXT;
173
174        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
175        wh::send_message(handle, EM_SETLIMITTEXT as u32, limit, 0);
176    }
177
178    /// Check if the content of the text input was modified after it's creation
179    pub fn modified(&self) -> bool {
180        use winapi::um::winuser::EM_GETMODIFY;
181
182        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
183        wh::send_message(handle, EM_GETMODIFY as u32, 0, 0) != 0
184    }
185
186    /// Manually set modified flag of the text input
187    pub fn set_modified(&self, e: bool) {
188        use winapi::um::winuser::EM_SETMODIFY;
189        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
190        wh::send_message(handle, EM_SETMODIFY as u32, e as usize, 0);
191    }
192
193    /// Undo the last action by the user in the control
194    pub fn undo(&self) {
195        use winapi::um::winuser::EM_UNDO;
196
197        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
198        wh::send_message(handle, EM_UNDO as u32, 0, 0);
199    }
200
201    /// Return the selected range of characters by the user in the text input
202    pub fn selection(&self) -> Range<u32> {
203        use winapi::um::winuser::EM_GETSEL;
204
205        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
206
207        let mut start = 0u32;
208        let mut end = 0u32;
209        let ptr1 = &mut start as *mut u32;
210        let ptr2 = &mut end as *mut u32;
211        wh::send_message(handle, EM_GETSEL as UINT, ptr1 as WPARAM, ptr2 as LPARAM);
212
213        start..end
214    }
215
216    /// Return the selected range of characters by the user in the text input
217    pub fn set_selection(&self, r: Range<u32>) {
218        use winapi::um::winuser::EM_SETSEL;
219
220        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
221        wh::send_message(handle, EM_SETSEL as u32, r.start as usize, r.end as isize);
222    }
223
224    /// Return the length of the user input in the control. This is better than `input.text().len()` as it
225    /// does not allocate a string in memory
226    pub fn len(&self) -> u32 {
227        use winapi::um::winuser::EM_LINELENGTH;
228        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
229        wh::send_message(handle, EM_LINELENGTH as u32, 0, 0) as u32
230    }
231
232    /// Return true if the TextInput value cannot be edited. Retrurn false otherwise.
233    /// A user can still copy text from a readonly TextEdit (unlike disabled)
234    pub fn readonly(&self) -> bool {
235        use winapi::um::winuser::ES_READONLY;
236        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
237        wh::get_style(handle) & ES_READONLY == ES_READONLY
238    }
239
240    /// Set the readonly flag of the text input
241    /// A user can still copy text from a readonly TextEdit (unlike disabled)
242    pub fn set_readonly(&self, r: bool) {
243        use winapi::um::winuser::EM_SETREADONLY;
244        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
245        wh::send_message(handle, EM_SETREADONLY as u32, r as WPARAM, 0);
246    }
247
248    /// Return true if the control currently has the keyboard focus
249    pub fn focus(&self) -> bool {
250        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
251        wh::get_focus(handle)
252    }
253
254    /// Set the keyboard focus on the button
255    pub fn set_focus(&self) {
256        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
257
258        wh::set_focus(handle);
259    }
260
261    /// Return true if the control user can interact with the control, return false otherwise
262    pub fn enabled(&self) -> bool {
263        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
264        wh::get_window_enabled(handle)
265    }
266
267    /// Enable or disable the control
268    pub fn set_enabled(&self, v: bool) {
269        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
270        wh::set_window_enabled(handle, v)
271    }
272
273    /// Return true if the control is visible to the user. Will return true even if the
274    /// control is outside of the parent client view (ex: at the position (10000, 10000))
275    pub fn visible(&self) -> bool {
276        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
277        wh::get_window_visibility(handle)
278    }
279
280    /// Show or hide the control to the user
281    pub fn set_visible(&self, v: bool) {
282        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
283        wh::set_window_visibility(handle, v)
284    }
285
286    /// Return the size of the button in the parent window
287    pub fn size(&self) -> (u32, u32) {
288        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
289        wh::get_window_size(handle)
290    }
291
292    /// Set the size of the button in the parent window
293    pub fn set_size(&self, x: u32, y: u32) {
294        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
295        wh::set_window_size(handle, x, y, false)
296    }
297
298    /// Return the position of the button in the parent window
299    pub fn position(&self) -> (i32, i32) {
300        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
301        wh::get_window_position(handle)
302    }
303
304    /// Set the position of the button in the parent window
305    pub fn set_position(&self, x: i32, y: i32) {
306        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
307        wh::set_window_position(handle, x, y)
308    }
309
310    /// Return the text displayed in the TextInput
311    pub fn text(&self) -> String {
312        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
313        wh::get_window_text(handle)
314    }
315
316    /// Set the text displayed in the TextInput
317    pub fn set_text<'a>(&self, v: &'a str) {
318        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
319        wh::set_window_text(handle, v)
320    }
321
322    /// Return the placeholder text displayed in the TextInput
323    /// when it is empty and does not have focus. The string returned will be
324    /// as long as the user specified, however it might be longer or shorter than
325    /// the actual placeholder text.
326    pub fn placeholder_text<'a>(&self, text_length: usize) -> String {
327        use std::ffi::OsString;
328        use std::os::windows::ffi::OsStringExt;
329        use winapi::shared::ntdef::WCHAR;
330        use winapi::um::commctrl::EM_GETCUEBANNER;
331
332        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
333        let mut placeholder_text: Vec<WCHAR> = Vec::with_capacity(text_length);
334        unsafe {
335            placeholder_text.set_len(text_length);
336            wh::send_message(
337                handle,
338                EM_GETCUEBANNER,
339                placeholder_text.as_mut_ptr() as WPARAM,
340                placeholder_text.len() as LPARAM,
341            );
342            OsString::from_wide(&placeholder_text)
343                .into_string()
344                .unwrap_or("".to_string())
345        }
346    }
347
348    /// Set the placeholder text displayed in the TextInput
349    /// when it is empty and does not have focus
350    pub fn set_placeholder_text<'a>(&self, v: Option<&'a str>) {
351        use winapi::um::commctrl::EM_SETCUEBANNER;
352
353        let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
354        let placeholder_text = v.unwrap_or("");
355        let text = to_utf16(placeholder_text);
356        wh::send_message(handle, EM_SETCUEBANNER, 0, text.as_ptr() as LPARAM);
357    }
358
359    /// Winapi class name used during control creation
360    pub fn class_name(&self) -> &'static str {
361        "EDIT"
362    }
363
364    /// Winapi base flags used during window creation
365    pub fn flags(&self) -> u32 {
366        ::winapi::um::winuser::WS_VISIBLE
367    }
368
369    /// Winapi flags required by the control
370    pub fn forced_flags(&self) -> u32 {
371        use winapi::um::winuser::{WS_BORDER, WS_CHILD};
372
373        WS_BORDER | WS_TABSTOP | ES_AUTOHSCROLL | WS_CHILD
374    }
375
376    /// Center the text vertically. Can't believe that must be manually hacked in.
377    fn hook_non_client_size(&mut self, bg: Option<[u8; 3]>) {
378        use crate::bind_raw_event_handler_inner;
379        use std::{mem, ptr};
380        use winapi::shared::windef::{HGDIOBJ, POINT, RECT};
381        use winapi::um::wingdi::{CreateSolidBrush, RGB, SelectObject};
382        use winapi::um::winuser::{
383            COLOR_WINDOW, DT_CALCRECT, DT_LEFT, NCCALCSIZE_PARAMS, WM_NCCALCSIZE, WM_NCPAINT,
384            WM_SIZE,
385        };
386        use winapi::um::winuser::{
387            DrawTextW, FillRect, GetClientRect, GetDC, GetWindowRect, ReleaseDC, ScreenToClient,
388            SetWindowPos,
389        };
390        use winapi::um::winuser::{SWP_FRAMECHANGED, SWP_NOMOVE, SWP_NOOWNERZORDER, SWP_NOSIZE};
391
392        if self.handle.blank() {
393            panic!("{}", NOT_BOUND);
394        }
395        self.handle.hwnd().expect(BAD_HANDLE);
396
397        let brush = match bg {
398            Some(c) => {
399                let b = unsafe { CreateSolidBrush(RGB(c[0], c[1], c[2])) };
400                self.background_brush = Some(b);
401                b
402            }
403            None => COLOR_WINDOW as HBRUSH,
404        };
405
406        unsafe {
407            let handler = bind_raw_event_handler_inner(&self.handle, 0, move |hwnd, msg, w, l| {
408                match msg {
409                    WM_NCCALCSIZE => {
410                        if w == 0 {
411                            return None;
412                        }
413
414                        // Calculate client area height needed for a font
415                        let font_handle = wh::get_window_font(hwnd);
416                        let mut r: RECT = mem::zeroed();
417                        let dc = GetDC(hwnd);
418
419                        let old = SelectObject(dc, font_handle as HGDIOBJ);
420                        let calc: [u16; 2] = [75, 121];
421                        DrawTextW(dc, calc.as_ptr(), 2, &mut r, DT_CALCRECT | DT_LEFT);
422
423                        let client_height = r.bottom;
424
425                        SelectObject(dc, old);
426                        ReleaseDC(hwnd, dc);
427
428                        // Calculate NC area to center text.
429                        let mut client: RECT = mem::zeroed();
430                        let mut window: RECT = mem::zeroed();
431                        GetClientRect(hwnd, &mut client);
432                        GetWindowRect(hwnd, &mut window);
433
434                        let window_height = window.bottom - window.top;
435                        let center = ((window_height - client_height) / 2) - 4;
436
437                        // Save the info
438                        let info_ptr: *mut NCCALCSIZE_PARAMS = l as *mut NCCALCSIZE_PARAMS;
439                        let info = &mut *info_ptr;
440
441                        info.rgrc[0].top += center;
442                        info.rgrc[0].bottom -= center;
443                    }
444                    WM_NCPAINT => {
445                        let mut window: RECT = mem::zeroed();
446                        let mut client: RECT = mem::zeroed();
447                        GetWindowRect(hwnd, &mut window);
448                        GetClientRect(hwnd, &mut client);
449
450                        let mut pt1 = POINT {
451                            x: window.left,
452                            y: window.top,
453                        };
454                        ScreenToClient(hwnd, &mut pt1);
455
456                        let mut pt2 = POINT {
457                            x: window.right,
458                            y: window.bottom,
459                        };
460                        ScreenToClient(hwnd, &mut pt2);
461
462                        let top = RECT {
463                            left: 0,
464                            top: pt1.y,
465                            right: client.right,
466                            bottom: client.top,
467                        };
468
469                        let bottom = RECT {
470                            left: 0,
471                            top: client.bottom,
472                            right: client.right,
473                            bottom: pt2.y,
474                        };
475
476                        let dc = GetDC(hwnd);
477                        FillRect(dc, &top, brush);
478                        FillRect(dc, &bottom, brush);
479                        ReleaseDC(hwnd, dc);
480                    }
481                    WM_SIZE => {
482                        SetWindowPos(
483                            hwnd,
484                            ptr::null_mut(),
485                            0,
486                            0,
487                            0,
488                            0,
489                            SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOMOVE | SWP_FRAMECHANGED,
490                        );
491                    }
492                    _ => {}
493                }
494
495                None
496            });
497
498            *self.handler0.borrow_mut() = Some(handler.unwrap());
499        }
500    }
501}
502
503impl Drop for TextInput {
504    fn drop(&mut self) {
505        use crate::unbind_raw_event_handler;
506
507        let handler = self.handler0.borrow();
508        if let Some(h) = handler.as_ref() {
509            drop(unbind_raw_event_handler(h));
510        }
511
512        if let Some(bg) = self.background_brush {
513            unsafe {
514                DeleteObject(bg as _);
515            }
516        }
517
518        self.handle.destroy();
519    }
520}
521
522pub struct TextInputBuilder<'a> {
523    text: &'a str,
524    placeholder_text: Option<&'a str>,
525    size: (i32, i32),
526    position: (i32, i32),
527    flags: Option<TextInputFlags>,
528    ex_flags: u32,
529    limit: usize,
530    password: Option<char>,
531    align: HTextAlign,
532    readonly: bool,
533    font: Option<&'a Font>,
534    parent: Option<ControlHandle>,
535    background_color: Option<[u8; 3]>,
536    focus: bool,
537}
538
539impl<'a> TextInputBuilder<'a> {
540    pub fn flags(mut self, flags: TextInputFlags) -> TextInputBuilder<'a> {
541        self.flags = Some(flags);
542        self
543    }
544
545    pub fn ex_flags(mut self, flags: u32) -> TextInputBuilder<'a> {
546        self.ex_flags = flags;
547        self
548    }
549
550    pub fn text(mut self, text: &'a str) -> TextInputBuilder<'a> {
551        self.text = text;
552        self
553    }
554
555    pub fn placeholder_text(mut self, placeholder_text: Option<&'a str>) -> TextInputBuilder<'a> {
556        self.placeholder_text = placeholder_text;
557        self
558    }
559
560    pub fn size(mut self, size: (i32, i32)) -> TextInputBuilder<'a> {
561        self.size = size;
562        self
563    }
564
565    pub fn position(mut self, pos: (i32, i32)) -> TextInputBuilder<'a> {
566        self.position = pos;
567        self
568    }
569
570    pub fn limit(mut self, limit: usize) -> TextInputBuilder<'a> {
571        self.limit = limit;
572        self
573    }
574
575    pub fn password(mut self, psw: Option<char>) -> TextInputBuilder<'a> {
576        self.password = psw;
577        self
578    }
579
580    pub fn align(mut self, align: HTextAlign) -> TextInputBuilder<'a> {
581        self.align = align;
582        self
583    }
584
585    pub fn readonly(mut self, read: bool) -> TextInputBuilder<'a> {
586        self.readonly = read;
587        self
588    }
589
590    pub fn font(mut self, font: Option<&'a Font>) -> TextInputBuilder<'a> {
591        self.font = font;
592        self
593    }
594
595    pub fn background_color(mut self, color: Option<[u8; 3]>) -> TextInputBuilder<'a> {
596        self.background_color = color;
597        self
598    }
599
600    pub fn focus(mut self, focus: bool) -> TextInputBuilder<'a> {
601        self.focus = focus;
602        self
603    }
604
605    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> TextInputBuilder<'a> {
606        self.parent = Some(p.into());
607        self
608    }
609
610    pub fn build(self, out: &mut TextInput) -> Result<(), NwgError> {
611        let mut flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
612
613        match self.align {
614            HTextAlign::Left => flags |= ES_LEFT,
615            HTextAlign::Center => flags |= ES_CENTER,
616            HTextAlign::Right => {
617                flags |= ES_RIGHT;
618                flags &= !ES_AUTOHSCROLL;
619            }
620        }
621
622        let parent = match self.parent {
623            Some(p) => Ok(p),
624            None => Err(NwgError::no_parent("TextInput")),
625        }?;
626
627        *out = Default::default();
628
629        out.handle = ControlBase::build_hwnd()
630            .class_name(out.class_name())
631            .forced_flags(out.forced_flags())
632            .flags(flags)
633            .ex_flags(self.ex_flags)
634            .size(self.size)
635            .position(self.position)
636            .text(self.text)
637            .parent(Some(parent))
638            .build()?;
639
640        out.hook_non_client_size(self.background_color);
641
642        if self.limit > 0 {
643            out.set_limit(self.limit);
644        }
645
646        if self.password.is_some() {
647            out.set_password_char(self.password)
648        }
649
650        if self.readonly {
651            out.set_readonly(self.readonly);
652        }
653
654        if self.focus {
655            out.set_focus();
656        }
657
658        if self.font.is_some() {
659            out.set_font(self.font);
660        } else {
661            out.set_font(Font::global_default().as_ref());
662        }
663
664        if self.placeholder_text.is_some() {
665            out.set_placeholder_text(self.placeholder_text);
666        }
667
668        Ok(())
669    }
670}
671
672impl PartialEq for TextInput {
673    fn eq(&self, other: &Self) -> bool {
674        self.handle == other.handle
675    }
676}