Skip to main content

native_windows_gui/controls/
text_box.rs

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