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}