native_windows_gui2/controls/
text_box.rs

1use super::{ControlBase, ControlHandle};
2use crate::win32::window_helper as wh;
3use crate::{Font, NwgError};
4use newline_converter::{dos2unix, unix2dos};
5use std::ops::Range;
6use winapi::shared::minwindef::{LPARAM, WPARAM};
7use winapi::um::winuser::{
8    ES_AUTOHSCROLL, ES_AUTOVSCROLL, WS_DISABLED, WS_HSCROLL, WS_TABSTOP, WS_VISIBLE, WS_VSCROLL,
9};
10
11const NOT_BOUND: &'static str = "TextBox is not yet bound to a winapi object";
12const BAD_HANDLE: &'static str = "INTERNAL ERROR: TextBox handle is not HWND!";
13
14bitflags! {
15
16    /**
17        The text box flags
18
19        * VSCROLL:      The text box has a vertical scrollbar
20        * HSCROLL:      The text box has a horizontal scrollbar
21        * VISIBLE:      The text box is immediatly visible after creation
22        * DISABLED:     The text box cannot be interacted with by the user. It also has a grayed out look.
23        * TAB_STOP:     The text box can be selected using tab navigation
24        * AUTOVSCROLL:  The text box automatically scrolls vertically when the caret is near the borders
25        * AUTOHSCROLL:  The text box automatically scrolls horizontally when the caret is near the borders
26    */
27    pub struct TextBoxFlags: u32 {
28        const VSCROLL = WS_VSCROLL;
29        const HSCROLL = WS_HSCROLL;
30        const AUTOVSCROLL = ES_AUTOVSCROLL;
31        const AUTOHSCROLL = ES_AUTOHSCROLL;
32        const VISIBLE = WS_VISIBLE;
33        const DISABLED = WS_DISABLED;
34        const TAB_STOP = WS_TABSTOP;
35    }
36}
37
38/**
39An edit control is a rectangular control window to permit the user to enter and edit text by typing on the keyboard
40This control allow multi line input. For a single line of text, use `TextInput`.
41
42Requires the `textbox` feature.
43
44Note: Use `\r\n` to input a new line not just `\n`.
45
46**Builder parameters:**
47  * `parent`:   **Required.** The text box parent container.
48  * `text`:     The text box text.
49  * `size`:     The text box size.
50  * `position`: The text box position.
51  * `flags`:    A combination of the TextBoxFlags 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 text box text
54  * `limit`:    The maximum number of character that can be inserted in the control
55  * `readonly`: If the textbox should allow user input or not
56  * `focus`:    The control receive focus after being created
57
58**Control events:**
59  * `OnTextInput`: When a TextBox value is changed
60  * `MousePress(_)`: Generic mouse press events on the button
61  * `OnMouseMove`: Generic mouse mouse event
62  * `OnMouseWheel`: Generic mouse wheel event
63  * `OnKeyPress`:    Generic key press event
64  * `OnKeyRelease`:  Generic key release event
65
66```rust
67use native_windows_gui2 as nwg;
68fn build_box(tbox: &mut nwg::TextBox, window: &nwg::Window, font: &nwg::Font) {
69    nwg::TextBox::builder()
70        .text("Hello")
71        .font(Some(font))
72        .parent(window)
73        .build(tbox);
74}
75```
76*/
77#[derive(Default, PartialEq, Eq)]
78pub struct TextBox {
79    pub handle: ControlHandle,
80}
81
82impl TextBox {
83    pub fn builder<'a>() -> TextBoxBuilder<'a> {
84        TextBoxBuilder {
85            text: "",
86            size: (100, 25),
87            position: (0, 0),
88            flags: None,
89            ex_flags: 0,
90            limit: 0,
91            readonly: false,
92            focus: false,
93            font: None,
94            parent: None,
95        }
96    }
97
98    /// Return the font of the control
99    pub fn font(&self) -> Option<Font> {
100        if self.handle.blank() {
101            panic!("{}", NOT_BOUND);
102        }
103        let handle = self.handle.hwnd().expect(BAD_HANDLE);
104
105        let font_handle = wh::get_window_font(handle);
106        if font_handle.is_null() {
107            None
108        } else {
109            Some(Font {
110                handle: font_handle,
111            })
112        }
113    }
114
115    /// Set the font of the control
116    pub fn set_font(&self, font: Option<&Font>) {
117        if self.handle.blank() {
118            panic!("{}", NOT_BOUND);
119        }
120        let handle = self.handle.hwnd().expect(BAD_HANDLE);
121
122        wh::set_window_font(handle, font.map(|f| f.handle), true);
123    }
124
125    /// Return the number of maximum character allowed in this text input
126    pub fn limit(&self) -> u32 {
127        use winapi::um::winuser::EM_GETLIMITTEXT;
128
129        if self.handle.blank() {
130            panic!("{}", NOT_BOUND);
131        }
132        let handle = self.handle.hwnd().expect(BAD_HANDLE);
133
134        wh::send_message(handle, EM_GETLIMITTEXT as u32, 0, 0) as u32
135    }
136
137    /// Set the number of maximum character allowed in this text input
138    pub fn set_limit(&self, limit: usize) {
139        use winapi::um::winuser::EM_SETLIMITTEXT;
140
141        if self.handle.blank() {
142            panic!("{}", NOT_BOUND);
143        }
144        let handle = self.handle.hwnd().expect(BAD_HANDLE);
145
146        wh::send_message(handle, EM_SETLIMITTEXT as u32, limit, 0);
147    }
148
149    /// Check if the content of the text input was modified after it's creation
150    pub fn modified(&self) -> bool {
151        use winapi::um::winuser::EM_GETMODIFY;
152
153        if self.handle.blank() {
154            panic!("{}", NOT_BOUND);
155        }
156        let handle = self.handle.hwnd().expect(BAD_HANDLE);
157
158        wh::send_message(handle, EM_GETMODIFY as u32, 0, 0) != 0
159    }
160
161    /// Manually set modified flag of the text input
162    pub fn set_modified(&self, e: bool) {
163        use winapi::um::winuser::EM_SETMODIFY;
164        if self.handle.blank() {
165            panic!("{}", NOT_BOUND);
166        }
167        let handle = self.handle.hwnd().expect(BAD_HANDLE);
168
169        wh::send_message(handle, EM_SETMODIFY as u32, e as usize, 0);
170    }
171
172    /// Undo the last action by the user in the control
173    pub fn undo(&self) {
174        use winapi::um::winuser::EM_UNDO;
175
176        if self.handle.blank() {
177            panic!("{}", NOT_BOUND);
178        }
179        let handle = self.handle.hwnd().expect(BAD_HANDLE);
180
181        wh::send_message(handle, EM_UNDO as u32, 0, 0);
182    }
183
184    /// Return the selected range of characters by the user in the text input
185    pub fn selection(&self) -> Range<u32> {
186        use winapi::um::winuser::EM_GETSEL;
187
188        if self.handle.blank() {
189            panic!("{}", NOT_BOUND);
190        }
191        let handle = self.handle.hwnd().expect(BAD_HANDLE);
192
193        let (mut out1, mut out2) = (0u32, 0u32);
194        let (ptr1, ptr2) = (&mut out1 as *mut u32, &mut out2 as *mut u32);
195        wh::send_message(handle, EM_GETSEL as u32, ptr1 as WPARAM, ptr2 as LPARAM);
196
197        Range {
198            start: out1 as u32,
199            end: out2 as u32,
200        }
201    }
202
203    /// Return the selected range of characters by the user in the text input
204    pub fn set_selection(&self, r: Range<u32>) {
205        use winapi::um::winuser::EM_SETSEL;
206
207        if self.handle.blank() {
208            panic!("{}", NOT_BOUND);
209        }
210        let handle = self.handle.hwnd().expect(BAD_HANDLE);
211        wh::send_message(handle, EM_SETSEL as u32, r.start as usize, r.end as isize);
212    }
213
214    /// Return the length of the user input in the control. Performs a newline conversion first since
215    /// Windows treats "\r\n" as a single character
216    pub fn len(&self) -> u32 {
217        use std::convert::TryInto;
218
219        dos2unix(&self.text())
220            .chars()
221            .count()
222            .try_into()
223            .unwrap_or_default()
224    }
225
226    /// Return the number of lines in the multiline edit control.
227    /// If the control has no text, the return value is 1.
228    pub fn linecount(&self) -> i32 {
229        use winapi::um::winuser::EM_GETLINECOUNT;
230
231        if self.handle.blank() {
232            panic!("{}", NOT_BOUND);
233        }
234        let handle = self.handle.hwnd().expect(BAD_HANDLE);
235        wh::send_message(handle, EM_GETLINECOUNT as u32, 0, 0) as i32
236    }
237
238    /// Scroll `v` lines in the multiline edit control.
239    pub fn scroll(&self, v: i32) {
240        use winapi::um::winuser::EM_LINESCROLL;
241
242        if self.handle.blank() {
243            panic!("{}", NOT_BOUND);
244        }
245        let handle = self.handle.hwnd().expect(BAD_HANDLE);
246        wh::send_message(handle, EM_LINESCROLL as u32, 0, v as LPARAM);
247    }
248
249    /// Get the linecount and then scroll the text to the last line
250    pub fn scroll_lastline(&self) {
251        let lines = self.linecount();
252        self.scroll(lines * -1);
253        self.scroll(lines - 2);
254    }
255
256    /// Return true if the TextInput value cannot be edited. Retrurn false otherwise.
257    /// A user can still copy text from a readonly TextEdit (unlike disabled)
258    pub fn readonly(&self) -> bool {
259        use winapi::um::winuser::ES_READONLY;
260
261        if self.handle.blank() {
262            panic!("{}", NOT_BOUND);
263        }
264        let handle = self.handle.hwnd().expect(BAD_HANDLE);
265        wh::get_style(handle) & ES_READONLY == ES_READONLY
266    }
267
268    /// Set the readonly flag of the text input
269    /// A user can still copy text from a readonly TextEdit (unlike disabled)
270    pub fn set_readonly(&self, r: bool) {
271        use winapi::um::winuser::EM_SETREADONLY;
272
273        if self.handle.blank() {
274            panic!("{}", NOT_BOUND);
275        }
276        let handle = self.handle.hwnd().expect(BAD_HANDLE);
277        wh::send_message(handle, EM_SETREADONLY as u32, r as WPARAM, 0);
278    }
279
280    /// Remove all text from the textbox
281    pub fn clear(&self) {
282        self.set_text("");
283    }
284
285    /// Return true if the control currently has the keyboard focus
286    pub fn focus(&self) -> bool {
287        if self.handle.blank() {
288            panic!("{}", NOT_BOUND);
289        }
290        let handle = self.handle.hwnd().expect(BAD_HANDLE);
291        wh::get_focus(handle)
292    }
293
294    /// Set the keyboard focus on the button
295    pub fn set_focus(&self) {
296        if self.handle.blank() {
297            panic!("{}", NOT_BOUND);
298        }
299        let handle = self.handle.hwnd().expect(BAD_HANDLE);
300
301        wh::set_focus(handle);
302    }
303
304    /// Return true if the control user can interact with the control, return false otherwise
305    pub fn enabled(&self) -> bool {
306        if self.handle.blank() {
307            panic!("{}", NOT_BOUND);
308        }
309        let handle = self.handle.hwnd().expect(BAD_HANDLE);
310        wh::get_window_enabled(handle)
311    }
312
313    /// Enable or disable the control
314    pub fn set_enabled(&self, v: bool) {
315        if self.handle.blank() {
316            panic!("{}", NOT_BOUND);
317        }
318        let handle = self.handle.hwnd().expect(BAD_HANDLE);
319        wh::set_window_enabled(handle, v)
320    }
321
322    /// Return true if the control is visible to the user. Will return true even if the
323    /// control is outside of the parent client view (ex: at the position (10000, 10000))
324    pub fn visible(&self) -> bool {
325        if self.handle.blank() {
326            panic!("{}", NOT_BOUND);
327        }
328        let handle = self.handle.hwnd().expect(BAD_HANDLE);
329        wh::get_window_visibility(handle)
330    }
331
332    /// Show or hide the control to the user
333    pub fn set_visible(&self, v: bool) {
334        if self.handle.blank() {
335            panic!("{}", NOT_BOUND);
336        }
337        let handle = self.handle.hwnd().expect(BAD_HANDLE);
338        wh::set_window_visibility(handle, v)
339    }
340
341    /// Return the size of the button in the parent window
342    pub fn size(&self) -> (u32, u32) {
343        if self.handle.blank() {
344            panic!("{}", NOT_BOUND);
345        }
346        let handle = self.handle.hwnd().expect(BAD_HANDLE);
347        wh::get_window_size(handle)
348    }
349
350    /// Set the size of the button in the parent window
351    pub fn set_size(&self, x: u32, y: u32) {
352        if self.handle.blank() {
353            panic!("{}", NOT_BOUND);
354        }
355        let handle = self.handle.hwnd().expect(BAD_HANDLE);
356        wh::set_window_size(handle, x, y, false)
357    }
358
359    /// Return the position of the button in the parent window
360    pub fn position(&self) -> (i32, i32) {
361        if self.handle.blank() {
362            panic!("{}", NOT_BOUND);
363        }
364        let handle = self.handle.hwnd().expect(BAD_HANDLE);
365        wh::get_window_position(handle)
366    }
367
368    /// Set the position of the button in the parent window
369    pub fn set_position(&self, x: i32, y: i32) {
370        if self.handle.blank() {
371            panic!("{}", NOT_BOUND);
372        }
373        let handle = self.handle.hwnd().expect(BAD_HANDLE);
374        wh::set_window_position(handle, x, y)
375    }
376
377    /// Return the text displayed in the TextInput
378    pub fn text(&self) -> String {
379        if self.handle.blank() {
380            panic!("{}", NOT_BOUND);
381        }
382        let handle = self.handle.hwnd().expect(BAD_HANDLE);
383        wh::get_window_text(handle)
384    }
385
386    /// Set the text displayed in the TextInput
387    pub fn set_text<'a>(&self, v: &'a str) {
388        if self.handle.blank() {
389            panic!("{}", NOT_BOUND);
390        }
391        let handle = self.handle.hwnd().expect(BAD_HANDLE);
392        wh::set_window_text(handle, v)
393    }
394
395    /// Set the text in the current control, converting unix-style newlines in the input to "\r\n"
396    pub fn set_text_unix2dos<'a>(&self, v: &'a str) {
397        if self.handle.blank() {
398            panic!("{}", NOT_BOUND);
399        }
400        let handle = self.handle.hwnd().expect(BAD_HANDLE);
401        wh::set_window_text(handle, &unix2dos(&v).to_string())
402    }
403
404    /// Append text to the current control
405    pub fn append<'a>(&self, v: &'a str) {
406        let text = self.text() + &unix2dos(&v).to_string();
407        self.set_text(&text);
408        self.scroll_lastline();
409    }
410
411    /// Append text to the current control followed by "\r\n"
412    pub fn appendln<'a>(&self, v: &'a str) {
413        let text = self.text() + &unix2dos(&v).to_string() + "\r\n";
414        self.set_text(&text);
415        self.scroll_lastline();
416    }
417
418    /// Winapi class name used during control creation
419    pub fn class_name(&self) -> &'static str {
420        "EDIT"
421    }
422
423    /// Winapi base flags used during window creation
424    pub fn flags(&self) -> u32 {
425        WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | ES_AUTOVSCROLL | ES_AUTOHSCROLL | WS_TABSTOP
426    }
427
428    /// Winapi flags required by the control
429    pub fn forced_flags(&self) -> u32 {
430        use winapi::um::winuser::{ES_MULTILINE, ES_WANTRETURN, WS_BORDER, WS_CHILD};
431
432        WS_BORDER | WS_CHILD | ES_MULTILINE | ES_WANTRETURN
433    }
434}
435
436impl Drop for TextBox {
437    fn drop(&mut self) {
438        self.handle.destroy();
439    }
440}
441pub struct TextBoxBuilder<'a> {
442    text: &'a str,
443    size: (i32, i32),
444    position: (i32, i32),
445    flags: Option<TextBoxFlags>,
446    ex_flags: u32,
447    limit: usize,
448    readonly: bool,
449    focus: bool,
450    font: Option<&'a Font>,
451    parent: Option<ControlHandle>,
452}
453
454impl<'a> TextBoxBuilder<'a> {
455    pub fn flags(mut self, flags: TextBoxFlags) -> TextBoxBuilder<'a> {
456        self.flags = Some(flags);
457        self
458    }
459
460    pub fn ex_flags(mut self, flags: u32) -> TextBoxBuilder<'a> {
461        self.ex_flags = flags;
462        self
463    }
464
465    pub fn text(mut self, text: &'a str) -> TextBoxBuilder<'a> {
466        self.text = text;
467        self
468    }
469
470    pub fn size(mut self, size: (i32, i32)) -> TextBoxBuilder<'a> {
471        self.size = size;
472        self
473    }
474
475    pub fn position(mut self, pos: (i32, i32)) -> TextBoxBuilder<'a> {
476        self.position = pos;
477        self
478    }
479
480    pub fn limit(mut self, limit: usize) -> TextBoxBuilder<'a> {
481        self.limit = limit;
482        self
483    }
484
485    pub fn readonly(mut self, read: bool) -> TextBoxBuilder<'a> {
486        self.readonly = read;
487        self
488    }
489
490    pub fn focus(mut self, focus: bool) -> TextBoxBuilder<'a> {
491        self.focus = focus;
492        self
493    }
494
495    pub fn font(mut self, font: Option<&'a Font>) -> TextBoxBuilder<'a> {
496        self.font = font;
497        self
498    }
499
500    pub fn parent<C: Into<ControlHandle>>(mut self, p: C) -> TextBoxBuilder<'a> {
501        self.parent = Some(p.into());
502        self
503    }
504
505    pub fn build(self, out: &mut TextBox) -> Result<(), NwgError> {
506        let flags = self.flags.map(|f| f.bits()).unwrap_or(out.flags());
507
508        let parent = match self.parent {
509            Some(p) => Ok(p),
510            None => Err(NwgError::no_parent("TextBox")),
511        }?;
512
513        *out = Default::default();
514
515        out.handle = ControlBase::build_hwnd()
516            .class_name(out.class_name())
517            .forced_flags(out.forced_flags())
518            .flags(flags)
519            .ex_flags(self.ex_flags)
520            .size(self.size)
521            .position(self.position)
522            .text(self.text)
523            .parent(Some(parent))
524            .build()?;
525
526        if self.limit > 0 {
527            out.set_limit(self.limit);
528        }
529
530        if self.readonly {
531            out.set_readonly(self.readonly);
532        }
533
534        if self.focus {
535            out.set_focus();
536        }
537
538        if self.font.is_some() {
539            out.set_font(self.font);
540        } else {
541            out.set_font(Font::global_default().as_ref());
542        }
543
544        Ok(())
545    }
546}