Skip to main content

winio_ui_app_kit/widgets/
edit.rs

1use inherit_methods_macro::inherit_methods;
2use objc2::{
3    DeclaredClass, MainThreadOnly, define_class, msg_send,
4    rc::{Allocated, Retained},
5    runtime::ProtocolObject,
6};
7use objc2_app_kit::{
8    NSControlTextEditingDelegate, NSSecureTextField, NSTextAlignment, NSTextField,
9    NSTextFieldDelegate,
10};
11use objc2_foundation::{MainThreadMarker, NSNotification, NSObject, NSObjectProtocol};
12use winio_callback::Callback;
13use winio_handle::{AsContainer, BorrowedContainer};
14use winio_primitive::{HAlign, Point, Size};
15
16use crate::{GlobalRuntime, Result, catch, widgets::Widget};
17
18#[derive(Debug)]
19struct EditImpl {
20    handle: Widget,
21    view: Retained<NSTextField>,
22    delegate: Retained<EditDelegate>,
23}
24
25#[inherit_methods(from = "self.handle")]
26impl EditImpl {
27    pub fn new(
28        parent: impl AsContainer,
29        view: Retained<NSTextField>,
30        delegate: Retained<EditDelegate>,
31    ) -> Result<Self> {
32        catch(|| unsafe {
33            view.setBezeled(true);
34            view.setDrawsBackground(true);
35            view.setEditable(true);
36            view.setSelectable(true);
37            let del_obj = ProtocolObject::from_ref(&*delegate);
38            view.setDelegate(Some(del_obj));
39
40            let handle = Widget::from_nsview(parent, Retained::cast_unchecked(view.clone()))?;
41
42            Ok(Self {
43                handle,
44                view,
45                delegate,
46            })
47        })
48        .flatten()
49    }
50
51    pub fn is_visible(&self) -> Result<bool>;
52
53    pub fn set_visible(&mut self, v: bool) -> Result<()>;
54
55    pub fn is_enabled(&self) -> Result<bool>;
56
57    pub fn set_enabled(&mut self, v: bool) -> Result<()>;
58
59    pub fn preferred_size(&self) -> Result<Size>;
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 align = catch(|| self.view.alignment())?;
79        let align = match align {
80            NSTextAlignment::Right => HAlign::Right,
81            NSTextAlignment::Center => HAlign::Center,
82            NSTextAlignment::Justified => HAlign::Stretch,
83            _ => HAlign::Left,
84        };
85        Ok(align)
86    }
87
88    pub fn set_halign(&mut self, align: HAlign) -> Result<()> {
89        let align = match align {
90            HAlign::Left => NSTextAlignment::Left,
91            HAlign::Center => NSTextAlignment::Center,
92            HAlign::Right => NSTextAlignment::Right,
93            HAlign::Stretch => NSTextAlignment::Justified,
94        };
95        catch(|| self.view.setAlignment(align))
96    }
97
98    pub fn is_readonly(&self) -> Result<bool> {
99        catch(|| !self.view.isEditable())
100    }
101
102    pub fn set_readonly(&mut self, v: bool) -> Result<()> {
103        catch(|| self.view.setEditable(!v))
104    }
105
106    pub async fn wait_change(&self) {
107        self.delegate.ivars().changed.wait().await
108    }
109}
110
111winio_handle::impl_as_widget!(EditImpl, handle);
112
113#[derive(Debug)]
114pub struct Edit {
115    handle: EditImpl,
116    password: bool,
117}
118
119#[inherit_methods(from = "self.handle")]
120impl Edit {
121    pub fn new(parent: impl AsContainer) -> Result<Self> {
122        let parent = parent.as_container();
123        let mtm = parent.as_app_kit().mtm();
124
125        catch(|| {
126            let view = NSTextField::new(mtm);
127            let delegate = EditDelegate::new(mtm);
128            let handle = EditImpl::new(parent, view, delegate)?;
129            Ok(Self {
130                handle,
131                password: false,
132            })
133        })
134        .flatten()
135    }
136
137    fn recreate(&mut self, password: bool, mtm: MainThreadMarker) -> Result<()> {
138        let view = catch(|| unsafe {
139            if password {
140                Retained::cast_unchecked(NSSecureTextField::new(mtm))
141            } else {
142                NSTextField::new(mtm)
143            }
144        })?;
145        let parent = self.handle.handle.parent()?;
146        let mut new_handle = EditImpl::new(
147            BorrowedContainer::app_kit(&parent),
148            view,
149            self.handle.delegate.clone(),
150        )?;
151        new_handle.set_visible(self.handle.is_visible()?)?;
152        new_handle.set_enabled(self.handle.is_enabled()?)?;
153        new_handle.set_loc(self.handle.loc()?)?;
154        new_handle.set_size(self.handle.size()?)?;
155        new_handle.set_text(self.handle.text()?)?;
156        new_handle.set_halign(self.handle.halign()?)?;
157        self.handle = new_handle;
158        Ok(())
159    }
160
161    pub fn is_visible(&self) -> Result<bool>;
162
163    pub fn set_visible(&mut self, v: bool) -> Result<()>;
164
165    pub fn is_enabled(&self) -> Result<bool>;
166
167    pub fn set_enabled(&mut self, v: bool) -> Result<()>;
168
169    pub fn preferred_size(&self) -> Result<Size>;
170
171    pub fn loc(&self) -> Result<Point>;
172
173    pub fn set_loc(&mut self, p: Point) -> Result<()>;
174
175    pub fn size(&self) -> Result<Size>;
176
177    pub fn set_size(&mut self, v: Size) -> Result<()>;
178
179    pub fn tooltip(&self) -> Result<String>;
180
181    pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()>;
182
183    pub fn text(&self) -> Result<String>;
184
185    pub fn set_text(&mut self, s: impl AsRef<str>) -> Result<()>;
186
187    pub fn is_password(&self) -> Result<bool> {
188        Ok(self.password)
189    }
190
191    pub fn set_password(&mut self, v: bool) -> Result<()> {
192        if self.password != v {
193            self.recreate(v, self.handle.view.mtm())?;
194            self.password = v;
195        }
196        Ok(())
197    }
198
199    pub fn halign(&self) -> Result<HAlign>;
200
201    pub fn set_halign(&mut self, align: HAlign) -> Result<()>;
202
203    pub fn is_readonly(&self) -> Result<bool> {
204        if self.is_password()? {
205            Ok(false)
206        } else {
207            self.handle.is_readonly()
208        }
209    }
210
211    pub fn set_readonly(&mut self, v: bool) -> Result<()> {
212        if !self.is_password()? {
213            self.handle.set_readonly(v)?;
214        }
215        Ok(())
216    }
217
218    pub async fn wait_change(&self) {
219        self.handle.wait_change().await
220    }
221}
222
223winio_handle::impl_as_widget!(Edit, handle);
224
225#[derive(Debug, Default)]
226struct EditDelegateIvars {
227    changed: Callback,
228}
229
230define_class! {
231    #[unsafe(super(NSObject))]
232    #[name = "WinioEditDelegate"]
233    #[ivars = EditDelegateIvars]
234    #[thread_kind = MainThreadOnly]
235    #[derive(Debug)]
236    struct EditDelegate;
237
238    #[allow(non_snake_case)]
239    impl EditDelegate {
240        #[unsafe(method_id(init))]
241        fn init(this: Allocated<Self>) -> Option<Retained<Self>> {
242            let this = this.set_ivars(EditDelegateIvars::default());
243            unsafe { msg_send![super(this), init] }
244        }
245    }
246
247    unsafe impl NSObjectProtocol for EditDelegate {}
248
249    #[allow(non_snake_case)]
250    unsafe impl NSControlTextEditingDelegate for EditDelegate {
251        #[unsafe(method(controlTextDidChange:))]
252        fn controlTextDidChange(&self, _notification: &NSNotification) {
253            self.ivars().changed.signal::<GlobalRuntime>(());
254        }
255    }
256
257    unsafe impl NSTextFieldDelegate for EditDelegate {}
258}
259
260impl EditDelegate {
261    pub fn new(mtm: MainThreadMarker) -> Retained<Self> {
262        unsafe { msg_send![mtm.alloc::<Self>(), init] }
263    }
264}