Skip to main content

winio_ui_app_kit/widgets/
combo_box.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    NSComboBox, NSComboBoxDelegate, NSControlTextEditingDelegate, NSTextFieldDelegate,
9};
10use objc2_foundation::{
11    MainThreadMarker, NSNotification, NSObject, NSObjectProtocol, NSString, ns_string,
12};
13use winio_callback::Callback;
14use winio_handle::AsContainer;
15use winio_primitive::{Point, Size};
16
17use crate::{GlobalRuntime, Result, Widget, catch, from_nsstring};
18
19#[derive(Debug)]
20pub struct ComboBox {
21    handle: Widget,
22    view: Retained<NSComboBox>,
23    delegate: Retained<ComboBoxDelegate>,
24}
25
26#[inherit_methods(from = "self.handle")]
27impl ComboBox {
28    pub fn new(parent: impl AsContainer) -> Result<Self> {
29        let parent = parent.as_container();
30        let mtm = parent.as_app_kit().mtm();
31
32        catch(|| unsafe {
33            let view = NSComboBox::new(mtm);
34            view.setBezeled(true);
35            view.setDrawsBackground(false);
36            view.setEditable(false);
37            view.setSelectable(false);
38            let handle = Widget::from_nsview(parent, Retained::cast_unchecked(view.clone()))?;
39
40            let delegate = ComboBoxDelegate::new(mtm);
41            let del_obj = ProtocolObject::from_ref(&*delegate);
42            view.setDelegate(Some(del_obj));
43
44            Ok(Self {
45                handle,
46                view,
47                delegate,
48            })
49        })
50        .flatten()
51    }
52
53    pub fn is_visible(&self) -> Result<bool>;
54
55    pub fn set_visible(&mut self, v: bool) -> Result<()>;
56
57    pub fn is_enabled(&self) -> Result<bool>;
58
59    pub fn set_enabled(&mut self, v: bool) -> Result<()>;
60
61    pub fn preferred_size(&self) -> Result<Size>;
62
63    pub fn loc(&self) -> Result<Point>;
64
65    pub fn set_loc(&mut self, p: Point) -> Result<()>;
66
67    pub fn size(&self) -> Result<Size>;
68
69    pub fn set_size(&mut self, v: Size) -> Result<()>;
70
71    pub fn tooltip(&self) -> Result<String>;
72
73    pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()>;
74
75    pub fn text(&self) -> Result<String>;
76
77    pub fn set_text(&mut self, s: impl AsRef<str>) -> Result<()>;
78
79    pub fn selection(&self) -> Result<Option<usize>> {
80        catch(|| {
81            let index = self.view.indexOfSelectedItem();
82            if index < 0 { None } else { Some(index as _) }
83        })
84    }
85
86    pub fn set_selection(&mut self, i: usize) -> Result<()> {
87        let old_sel = self.selection()?;
88        if Some(i) != old_sel {
89            if let Some(i) = self.selection()? {
90                catch(|| self.view.deselectItemAtIndex(i as _))?;
91            }
92            catch(|| self.view.selectItemAtIndex(i as _))?;
93        }
94        Ok(())
95    }
96
97    pub fn is_editable(&self) -> Result<bool> {
98        catch(|| self.view.isEditable())
99    }
100
101    pub fn set_editable(&mut self, v: bool) -> Result<()> {
102        catch(|| {
103            self.view.setDrawsBackground(v);
104            self.view.setEditable(v);
105            self.view.setSelectable(v);
106        })
107    }
108
109    pub async fn wait_change(&self) {
110        self.delegate.ivars().changed.wait().await
111    }
112
113    pub async fn wait_select(&self) {
114        self.delegate.ivars().select.wait().await
115    }
116
117    pub fn len(&self) -> Result<usize> {
118        catch(|| self.view.numberOfItems() as _)
119    }
120
121    pub fn is_empty(&self) -> Result<bool> {
122        Ok(self.len()? == 0)
123    }
124
125    pub fn clear(&mut self) -> Result<()> {
126        catch(|| self.view.removeAllItems())
127    }
128
129    pub fn get(&self, i: usize) -> Result<String> {
130        catch(|| unsafe {
131            let s = Retained::cast_unchecked(self.view.itemObjectValueAtIndex(i as _));
132            from_nsstring(&s)
133        })
134    }
135
136    pub fn set(&mut self, i: usize, s: impl AsRef<str>) -> Result<()> {
137        self.remove(i)?;
138        self.insert(i, s)
139    }
140
141    pub fn insert(&mut self, i: usize, s: impl AsRef<str>) -> Result<()> {
142        catch(|| unsafe {
143            let s = NSString::from_str(s.as_ref());
144            self.view.insertItemWithObjectValue_atIndex(&s, i as _);
145        })?;
146        if (!self.is_editable()?) && self.len()? == 1 {
147            self.set_selection(0)?;
148        }
149        Ok(())
150    }
151
152    pub fn remove(&mut self, i: usize) -> Result<()> {
153        let i = i as isize;
154        catch(|| {
155            let remove_current = self.view.indexOfSelectedItem() == i;
156            self.view.removeItemAtIndex(i);
157            let len = self.view.numberOfItems();
158            if remove_current && (!self.is_editable()?) {
159                if len > 0 {
160                    self.view.selectItemAtIndex(i.min(len - 1));
161                } else {
162                    self.view.setStringValue(ns_string!(""));
163                }
164            }
165            Ok(())
166        })
167        .flatten()
168    }
169}
170
171winio_handle::impl_as_widget!(ComboBox, handle);
172
173#[derive(Debug, Default)]
174struct ComboBoxDelegateIvars {
175    changed: Callback,
176    select: Callback,
177}
178
179define_class! {
180    #[unsafe(super(NSObject))]
181    #[name = "WinioComboBoxDelegate"]
182    #[ivars = ComboBoxDelegateIvars]
183    #[thread_kind = MainThreadOnly]
184    #[derive(Debug)]
185    struct ComboBoxDelegate;
186
187    #[allow(non_snake_case)]
188    impl ComboBoxDelegate {
189        #[unsafe(method_id(init))]
190        fn init(this: Allocated<Self>) -> Option<Retained<Self>> {
191            let this = this.set_ivars(ComboBoxDelegateIvars::default());
192            unsafe { msg_send![super(this), init] }
193        }
194    }
195
196    unsafe impl NSObjectProtocol for ComboBoxDelegate {}
197
198    #[allow(non_snake_case)]
199    unsafe impl NSControlTextEditingDelegate for ComboBoxDelegate {
200        #[unsafe(method(controlTextDidChange:))]
201        fn controlTextDidChange(&self, _notification: &NSNotification) {
202            self.ivars().changed.signal::<GlobalRuntime>(());
203        }
204    }
205
206    unsafe impl NSTextFieldDelegate for ComboBoxDelegate {}
207
208    #[allow(non_snake_case)]
209    unsafe impl NSComboBoxDelegate for ComboBoxDelegate {
210        #[unsafe(method(comboBoxSelectionDidChange:))]
211        unsafe fn comboBoxSelectionDidChange(&self, _notification: &NSNotification) {
212            self.ivars().select.signal::<GlobalRuntime>(());
213        }
214    }
215}
216
217impl ComboBoxDelegate {
218    pub fn new(mtm: MainThreadMarker) -> Retained<Self> {
219        unsafe { msg_send![mtm.alloc::<Self>(), init] }
220    }
221}