Skip to main content

winio_ui_win32/widgets/
edit.rs

1use inherit_methods_macro::inherit_methods;
2use windows_sys::{
3    Win32::{
4        Foundation::{HWND, LPARAM, LRESULT, WPARAM},
5        UI::{
6            Controls::{
7                EM_GETPASSWORDCHAR, EM_REPLACESEL, EM_SETPASSWORDCHAR, EM_SETREADONLY,
8                ShowScrollBar, WC_EDITW,
9            },
10            Input::KeyboardAndMouse::VK_RETURN,
11            Shell::{DefSubclassProc, SetWindowSubclass},
12            WindowsAndMessaging::{
13                DLGC_WANTALLKEYS, EN_UPDATE, ES_AUTOHSCROLL, ES_AUTOVSCROLL, ES_CENTER, ES_LEFT,
14                ES_MULTILINE, ES_PASSWORD, ES_READONLY, ES_RIGHT, SB_VERT, WM_COMMAND,
15                WM_GETDLGCODE, WM_KEYUP, WS_CHILD, WS_EX_CLIENTEDGE, WS_TABSTOP, WS_VISIBLE,
16            },
17        },
18    },
19    w,
20};
21use winio_handle::{AsContainer, AsWidget};
22use winio_primitive::{HAlign, Point, Size};
23use winio_ui_windows_common::syscall;
24
25use crate::{
26    Result,
27    runtime::WindowMessageCommand,
28    widgets::{Widget, fix_crlf},
29};
30
31#[derive(Debug)]
32struct EditImpl {
33    handle: Widget,
34}
35
36#[inherit_methods(from = "self.handle")]
37impl EditImpl {
38    pub fn new(parent: impl AsContainer, style: u32) -> Result<Self> {
39        let handle = Widget::new(
40            WC_EDITW,
41            style,
42            WS_EX_CLIENTEDGE,
43            parent.as_container().as_win32(),
44        )?;
45        Ok(Self { handle })
46    }
47
48    pub fn is_visible(&self) -> Result<bool>;
49
50    pub fn set_visible(&mut self, v: bool) -> Result<()>;
51
52    pub fn is_enabled(&self) -> Result<bool>;
53
54    pub fn set_enabled(&mut self, v: bool) -> Result<()>;
55
56    pub fn preferred_size(&self) -> Result<Size> {
57        let s = self.handle.measure_text()?;
58        Ok(Size::new(s.width + 8.0, s.height + 4.0))
59    }
60
61    pub fn loc(&self) -> Result<Point>;
62
63    pub fn set_loc(&mut self, p: Point) -> Result<()>;
64
65    pub fn size(&self) -> Result<Size>;
66
67    pub fn set_size(&mut self, v: Size) -> Result<()>;
68
69    pub fn tooltip(&self) -> Result<String>;
70
71    pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()>;
72
73    pub fn text(&self) -> Result<String>;
74
75    pub fn set_text(&mut self, s: impl AsRef<str>) -> Result<()>;
76
77    pub fn halign(&self) -> Result<HAlign> {
78        let style = self.handle.style()? as i32;
79        let style = if (style & ES_RIGHT) == ES_RIGHT {
80            HAlign::Right
81        } else if (style & ES_CENTER) == ES_CENTER {
82            HAlign::Center
83        } else {
84            HAlign::Left
85        };
86        Ok(style)
87    }
88
89    pub fn set_halign(&mut self, align: HAlign) -> Result<()> {
90        let mut style = self.handle.style()?;
91        style &= !(ES_RIGHT as u32);
92        match align {
93            HAlign::Center => style |= ES_CENTER as u32,
94            HAlign::Right => style |= ES_RIGHT as u32,
95            _ => style |= ES_LEFT as u32,
96        }
97        self.handle.set_style(style)
98    }
99
100    pub fn is_readonly(&self) -> Result<bool> {
101        let style = self.handle.style()? as i32;
102        Ok((style & ES_READONLY) == ES_READONLY)
103    }
104
105    pub fn set_readonly(&mut self, v: bool) -> Result<()> {
106        self.handle
107            .send_message(EM_SETREADONLY, if v { 1 } else { 0 }, 0);
108        Ok(())
109    }
110
111    pub async fn wait_change(&self) {
112        loop {
113            let WindowMessageCommand {
114                message, handle, ..
115            } = self.handle.wait_parent(WM_COMMAND).await.command();
116            if std::ptr::eq(handle, self.handle.as_widget().as_win32()) && (message == EN_UPDATE) {
117                break;
118            }
119        }
120    }
121}
122
123winio_handle::impl_as_widget!(EditImpl, handle);
124
125#[derive(Debug)]
126pub struct Edit {
127    handle: EditImpl,
128    pchar: u16,
129}
130
131#[inherit_methods(from = "self.handle")]
132impl Edit {
133    pub fn new(parent: impl AsContainer) -> Result<Self> {
134        let handle = EditImpl::new(
135            parent,
136            WS_CHILD
137                | WS_VISIBLE
138                | WS_TABSTOP
139                | ES_LEFT as u32
140                | ES_AUTOHSCROLL as u32
141                | ES_PASSWORD as u32,
142        )?;
143        let mut pchar = handle.handle.send_message(EM_GETPASSWORDCHAR, 0, 0) as u16;
144        if pchar == 0 {
145            pchar = '*' as u16;
146        }
147        handle.handle.send_message(EM_SETPASSWORDCHAR, 0, 0);
148        Ok(Self { handle, pchar })
149    }
150
151    pub fn is_visible(&self) -> Result<bool>;
152
153    pub fn set_visible(&mut self, v: bool) -> Result<()>;
154
155    pub fn is_enabled(&self) -> Result<bool>;
156
157    pub fn set_enabled(&mut self, v: bool) -> Result<()>;
158
159    pub fn preferred_size(&self) -> Result<Size>;
160
161    pub fn loc(&self) -> Result<Point>;
162
163    pub fn set_loc(&mut self, p: Point) -> Result<()>;
164
165    pub fn size(&self) -> Result<Size>;
166
167    pub fn set_size(&mut self, v: Size) -> Result<()>;
168
169    pub fn tooltip(&self) -> Result<String>;
170
171    pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()>;
172
173    pub fn text(&self) -> Result<String>;
174
175    pub fn set_text(&mut self, s: impl AsRef<str>) -> Result<()>;
176
177    pub fn halign(&self) -> Result<HAlign>;
178
179    pub fn set_halign(&mut self, align: HAlign) -> Result<()>;
180
181    pub fn is_readonly(&self) -> Result<bool> {
182        if self.is_password()? {
183            Ok(false)
184        } else {
185            self.handle.is_readonly()
186        }
187    }
188
189    pub fn set_readonly(&mut self, v: bool) -> Result<()> {
190        if !self.is_password()? {
191            self.handle.set_readonly(v)?;
192        }
193        Ok(())
194    }
195
196    pub fn is_password(&self) -> Result<bool> {
197        Ok(self.handle.handle.send_message(EM_GETPASSWORDCHAR, 0, 0) != 0)
198    }
199
200    pub fn set_password(&mut self, v: bool) -> Result<()> {
201        if v {
202            self.handle
203                .handle
204                .send_message(EM_SETPASSWORDCHAR, self.pchar as _, 0);
205            self.handle.set_readonly(false)?;
206        } else {
207            self.handle.handle.send_message(EM_SETPASSWORDCHAR, 0, 0);
208        }
209        self.handle.handle.invalidate(true)
210    }
211
212    pub async fn wait_change(&self) {
213        self.handle.wait_change().await
214    }
215}
216
217winio_handle::impl_as_widget!(Edit, handle);
218
219#[derive(Debug)]
220pub struct TextBox {
221    handle: EditImpl,
222}
223
224#[inherit_methods(from = "self.handle")]
225impl TextBox {
226    pub fn new(parent: impl AsContainer) -> Result<Self> {
227        let this = Self::new_raw(parent)?;
228        syscall!(
229            BOOL,
230            ShowScrollBar(this.handle.as_widget().as_win32(), SB_VERT, 1)
231        )?;
232        syscall!(
233            BOOL,
234            SetWindowSubclass(
235                this.handle.as_widget().as_win32(),
236                Some(multiline_edit_wnd_proc),
237                0,
238                0,
239            )
240        )?;
241        Ok(this)
242    }
243
244    pub(crate) fn new_raw(parent: impl AsContainer) -> Result<Self> {
245        let handle = EditImpl::new(
246            parent,
247            WS_CHILD
248                | WS_VISIBLE
249                | WS_TABSTOP
250                | ES_LEFT as u32
251                | ES_MULTILINE as u32
252                | ES_AUTOVSCROLL as u32,
253        )?;
254        Ok(Self { handle })
255    }
256
257    pub fn is_visible(&self) -> Result<bool>;
258
259    pub fn set_visible(&mut self, v: bool) -> Result<()>;
260
261    pub fn is_enabled(&self) -> Result<bool>;
262
263    pub fn set_enabled(&mut self, v: bool) -> Result<()>;
264
265    pub fn preferred_size(&self) -> Result<Size>;
266
267    pub fn min_size(&self) -> Result<Size> {
268        let text = self.handle.handle.text_u16()?;
269        let index = text.as_slice().iter().position(|c| *c == '\r' as u16);
270        if let Some(index) = index {
271            let s = self.handle.handle.measure(text.split_at(index).0)?;
272            Ok(Size::new(8.0, s.height + 4.0))
273        } else {
274            self.preferred_size()
275        }
276    }
277
278    pub fn loc(&self) -> Result<Point>;
279
280    pub fn set_loc(&mut self, p: Point) -> Result<()>;
281
282    pub fn size(&self) -> Result<Size>;
283
284    pub fn set_size(&mut self, v: Size) -> Result<()>;
285
286    pub fn tooltip(&self) -> Result<String>;
287
288    pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()>;
289
290    pub fn text(&self) -> Result<String> {
291        Ok(self.handle.text()?.replace("\r\n", "\n"))
292    }
293
294    pub fn set_text(&mut self, s: impl AsRef<str>) -> Result<()> {
295        self.handle.set_text(fix_crlf(s.as_ref()))
296    }
297
298    pub fn halign(&self) -> Result<HAlign>;
299
300    pub fn set_halign(&mut self, align: HAlign) -> Result<()>;
301
302    pub fn is_readonly(&self) -> Result<bool>;
303
304    pub fn set_readonly(&mut self, v: bool) -> Result<()>;
305
306    pub async fn wait_change(&self) {
307        self.handle.wait_change().await
308    }
309}
310
311winio_handle::impl_as_widget!(TextBox, handle);
312
313unsafe extern "system" fn multiline_edit_wnd_proc(
314    hwnd: HWND,
315    umsg: u32,
316    wparam: WPARAM,
317    lparam: LPARAM,
318    _id: usize,
319    _data: usize,
320) -> LRESULT {
321    let mut res = unsafe { DefSubclassProc(hwnd, umsg, wparam, lparam) };
322    match umsg {
323        WM_GETDLGCODE => {
324            res &= !(DLGC_WANTALLKEYS as isize);
325        }
326        WM_KEYUP if wparam == VK_RETURN as _ => {
327            const RETURN_TEXT: *const u16 = w!("\r\n");
328            unsafe { DefSubclassProc(hwnd, EM_REPLACESEL, 1, RETURN_TEXT as _) };
329        }
330        _ => {}
331    }
332    res
333}