native_windows_gui/controls/
text_input.rs

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