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 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#[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 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 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 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 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 unsafe {
157 InvalidateRect(handle, ::std::ptr::null(), 1);
158 }
159 }
160
161 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 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 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 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 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 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 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 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 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 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 pub fn focus(&self) -> bool {
250 let handle = check_hwnd(&self.handle, NOT_BOUND, BAD_HANDLE);
251 wh::get_focus(handle)
252 }
253
254 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn class_name(&self) -> &'static str {
361 "EDIT"
362 }
363
364 pub fn flags(&self) -> u32 {
366 ::winapi::um::winuser::WS_VISIBLE
367 }
368
369 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 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 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 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 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}