Skip to main content

winio_ui_app_kit/widgets/
text_box.rs

1use inherit_methods_macro::inherit_methods;
2use objc2::{
3    AnyThread, DeclaredClass, MainThreadOnly, define_class, msg_send,
4    rc::{Allocated, Retained},
5    runtime::ProtocolObject,
6};
7use objc2_app_kit::{
8    NSAttributedStringNSStringDrawing, NSFontAttributeName, NSTextAlignment, NSTextDelegate,
9    NSTextView, NSTextViewDelegate,
10};
11use objc2_foundation::{
12    MainThreadMarker, NSAttributedString, NSDictionary, NSNotification, NSObject, NSObjectProtocol,
13    NSString,
14};
15use winio_callback::Callback;
16use winio_handle::AsContainer;
17use winio_primitive::{HAlign, Point, Size};
18
19use crate::{Error, GlobalRuntime, Result, Widget, catch, from_cgsize, from_nsstring};
20
21#[derive(Debug)]
22pub struct TextBox {
23    handle: Widget,
24    text_view: Retained<NSTextView>,
25    delegate: Retained<TextBoxDelegate>,
26}
27
28#[inherit_methods(from = "self.handle")]
29impl TextBox {
30    pub fn new(parent: impl AsContainer) -> Result<Self> {
31        let parent = parent.as_container();
32        let mtm = parent.as_app_kit().mtm();
33
34        catch(|| unsafe {
35            let view = NSTextView::scrollableTextView(mtm);
36            let text_view = Retained::cast_unchecked::<NSTextView>(
37                view.documentView().ok_or(Error::NullPointer)?,
38            );
39            text_view.setRichText(false);
40            text_view.setEditable(true);
41            text_view.setSelectable(true);
42
43            let handle = Widget::from_nsview(parent, Retained::cast_unchecked(view.clone()))?;
44
45            let delegate = TextBoxDelegate::new(mtm);
46            let del_obj = ProtocolObject::from_ref(&*delegate);
47            text_view.setDelegate(Some(del_obj));
48            Ok(Self {
49                handle,
50                text_view,
51                delegate,
52            })
53        })
54        .flatten()
55    }
56
57    pub fn is_visible(&self) -> Result<bool>;
58
59    pub fn set_visible(&mut self, v: bool) -> Result<()>;
60
61    pub fn is_enabled(&self) -> Result<bool>;
62
63    pub fn set_enabled(&mut self, v: bool) -> Result<()>;
64
65    pub fn min_size(&self) -> Result<Size> {
66        let text = self.text()?;
67        catch(|| unsafe {
68            let font = self.text_view.font();
69            let text = NSAttributedString::initWithString_attributes(
70                NSAttributedString::alloc(),
71                &NSString::from_str(text.split('\n').next().unwrap_or(&text)),
72                if let Some(font) = font {
73                    Some(NSDictionary::from_slices(
74                        &[NSFontAttributeName],
75                        &[font.as_ref()],
76                    ))
77                } else {
78                    None
79                }
80                .as_deref(),
81            );
82            from_cgsize(text.size())
83        })
84    }
85
86    pub fn preferred_size(&self) -> Result<Size> {
87        catch(|| unsafe {
88            let font = self.text_view.font();
89            let text = NSAttributedString::initWithString_attributes(
90                NSAttributedString::alloc(),
91                &self.text_view.string(),
92                if let Some(font) = font {
93                    Some(NSDictionary::from_slices(
94                        &[NSFontAttributeName],
95                        &[font.as_ref()],
96                    ))
97                } else {
98                    None
99                }
100                .as_deref(),
101            );
102            from_cgsize(text.size())
103        })
104    }
105
106    pub fn loc(&self) -> Result<Point>;
107
108    pub fn set_loc(&mut self, p: Point) -> Result<()>;
109
110    pub fn size(&self) -> Result<Size>;
111
112    pub fn set_size(&mut self, v: Size) -> Result<()>;
113
114    pub fn tooltip(&self) -> Result<String>;
115
116    pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()>;
117
118    pub fn text(&self) -> Result<String> {
119        catch(|| from_nsstring(&self.text_view.string()))
120    }
121
122    pub fn set_text(&mut self, s: impl AsRef<str>) -> Result<()> {
123        catch(|| self.text_view.setString(&NSString::from_str(s.as_ref())))
124    }
125
126    pub fn halign(&self) -> Result<HAlign> {
127        let align = catch(|| self.text_view.alignment())?;
128        let align = match align {
129            NSTextAlignment::Right => HAlign::Right,
130            NSTextAlignment::Center => HAlign::Center,
131            NSTextAlignment::Justified => HAlign::Stretch,
132            _ => HAlign::Left,
133        };
134        Ok(align)
135    }
136
137    pub fn set_halign(&mut self, align: HAlign) -> Result<()> {
138        let align = match align {
139            HAlign::Left => NSTextAlignment::Left,
140            HAlign::Center => NSTextAlignment::Center,
141            HAlign::Right => NSTextAlignment::Right,
142            HAlign::Stretch => NSTextAlignment::Justified,
143        };
144        catch(|| self.text_view.setAlignment(align))
145    }
146
147    pub fn is_readonly(&self) -> Result<bool> {
148        catch(|| !self.text_view.isEditable())
149    }
150
151    pub fn set_readonly(&mut self, v: bool) -> Result<()> {
152        catch(|| self.text_view.setEditable(!v))
153    }
154
155    pub async fn wait_change(&self) {
156        self.delegate.ivars().changed.wait().await
157    }
158}
159
160winio_handle::impl_as_widget!(TextBox, handle);
161
162#[derive(Debug, Default)]
163struct TextBoxDelegateIvars {
164    changed: Callback,
165}
166
167define_class! {
168    #[unsafe(super(NSObject))]
169    #[name = "WinioTextBoxDelegate"]
170    #[ivars = TextBoxDelegateIvars]
171    #[thread_kind = MainThreadOnly]
172    #[derive(Debug)]
173    struct TextBoxDelegate;
174
175    #[allow(non_snake_case)]
176    impl TextBoxDelegate {
177        #[unsafe(method_id(init))]
178        fn init(this: Allocated<Self>) -> Option<Retained<Self>> {
179            let this = this.set_ivars(TextBoxDelegateIvars::default());
180            unsafe { msg_send![super(this), init] }
181        }
182    }
183
184    unsafe impl NSObjectProtocol for TextBoxDelegate {}
185
186    #[allow(non_snake_case)]
187    unsafe impl NSTextDelegate for TextBoxDelegate {
188        #[unsafe(method(textDidChange:))]
189        fn textDidChange(&self, _notification: &NSNotification) {
190            self.ivars().changed.signal::<GlobalRuntime>(());
191        }
192    }
193
194    unsafe impl NSTextViewDelegate for TextBoxDelegate {}
195}
196
197impl TextBoxDelegate {
198    pub fn new(mtm: MainThreadMarker) -> Retained<Self> {
199        unsafe { msg_send![mtm.alloc::<Self>(), init] }
200    }
201}