Skip to main content

winio_ui_app_kit/widgets/
list_box.rs

1use std::cell::RefCell;
2
3use inherit_methods_macro::inherit_methods;
4use objc2::{
5    DefinedClass, MainThreadMarker, MainThreadOnly, define_class, msg_send,
6    rc::{Allocated, Retained},
7    runtime::{AnyObject, ProtocolObject},
8};
9use objc2_app_kit::{
10    NSControl, NSControlTextEditingDelegate, NSFont, NSFontAttributeName, NSScrollView,
11    NSStringDrawing, NSTableColumn, NSTableView, NSTableViewColumnAutoresizingStyle,
12    NSTableViewDataSource, NSTableViewDelegate,
13};
14use objc2_foundation::{
15    NSDictionary, NSIndexSet, NSInteger, NSNotification, NSObject, NSObjectProtocol, NSSize,
16    NSString,
17};
18use winio_callback::Callback;
19use winio_handle::AsContainer;
20use winio_primitive::{Point, Size};
21
22use crate::{GlobalRuntime, Result, Widget, catch, from_cgsize};
23
24#[derive(Debug)]
25pub struct ListBox {
26    handle: Widget,
27    #[allow(unused)]
28    view: Retained<NSScrollView>,
29    table: Retained<NSTableView>,
30    #[allow(unused)]
31    column: Retained<NSTableColumn>,
32    delegate: Retained<ListBoxDelegate>,
33}
34
35#[inherit_methods(from = "self.handle")]
36impl ListBox {
37    pub fn new(parent: impl AsContainer) -> Result<Self> {
38        let parent = parent.as_container();
39        let mtm = parent.as_app_kit().mtm();
40
41        catch(|| unsafe {
42            let table = NSTableView::new(mtm);
43            let column = NSTableColumn::new(mtm);
44            column.setEditable(false);
45            table.addTableColumn(&column);
46            table.setHeaderView(None);
47            table.setColumnAutoresizingStyle(
48                NSTableViewColumnAutoresizingStyle::UniformColumnAutoresizingStyle,
49            );
50
51            let view = NSScrollView::new(mtm);
52            view.setHasVerticalScroller(true);
53            view.setDocumentView(Some(&table));
54            let handle = Widget::from_nsview(parent, Retained::cast_unchecked(view.clone()))?;
55
56            let delegate = ListBoxDelegate::new(mtm);
57            let del_obj = ProtocolObject::from_ref(&*delegate);
58            table.setDelegate(Some(del_obj));
59            let del_obj = ProtocolObject::from_ref(&*delegate);
60            table.setDataSource(Some(del_obj));
61
62            Ok(Self {
63                handle,
64                view,
65                table,
66                column,
67                delegate,
68            })
69        })
70        .flatten()
71    }
72
73    pub fn is_visible(&self) -> Result<bool>;
74
75    pub fn set_visible(&mut self, v: bool) -> Result<()>;
76
77    pub fn is_enabled(&self) -> Result<bool>;
78
79    pub fn set_enabled(&mut self, v: bool) -> Result<()>;
80
81    pub fn min_size(&self) -> Result<Size> {
82        catch(|| unsafe {
83            let font = NSFont::systemFontOfSize(NSFont::systemFontSize());
84            let attrs = NSDictionary::from_slices(&[NSFontAttributeName], &[font.as_ref()]);
85            let mut width = 0.0f64;
86            let mut height = 0.0f64;
87            for s in self.delegate.ivars().data.borrow().iter() {
88                let s = NSString::from_str(s);
89                let size = s.sizeWithAttributes(Some(&attrs));
90                width = width.max(size.width);
91                height = height.max(size.height);
92            }
93            Size::new(width + 40.0, height)
94        })
95    }
96
97    pub fn preferred_size(&self) -> Result<Size> {
98        let mut size = catch(|| unsafe {
99            from_cgsize(
100                Retained::cast_unchecked::<NSControl>(self.table.clone())
101                    .sizeThatFits(NSSize::ZERO),
102            )
103        })?;
104        size.width = self.min_size()?.width;
105        Ok(size)
106    }
107
108    pub fn loc(&self) -> Result<Point>;
109
110    pub fn set_loc(&mut self, p: Point) -> Result<()>;
111
112    pub fn size(&self) -> Result<Size>;
113
114    pub fn set_size(&mut self, v: Size) -> Result<()>;
115
116    pub fn tooltip(&self) -> Result<String>;
117
118    pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()>;
119
120    pub async fn wait_select(&self) {
121        self.delegate.ivars().select.wait().await
122    }
123
124    pub fn is_multiple(&self) -> Result<bool> {
125        catch(|| self.table.allowsMultipleSelection())
126    }
127
128    pub fn set_multiple(&mut self, v: bool) -> Result<()> {
129        catch(|| self.table.setAllowsMultipleSelection(v))
130    }
131
132    pub fn is_selected(&self, i: usize) -> Result<bool> {
133        catch(|| self.table.isRowSelected(i as _))
134    }
135
136    pub fn set_selected(&mut self, i: usize, v: bool) -> Result<()> {
137        catch(|| {
138            if v {
139                self.table
140                    .selectRowIndexes_byExtendingSelection(&NSIndexSet::indexSetWithIndex(i), true);
141            } else {
142                self.table.deselectRow(i as _);
143            }
144        })
145    }
146
147    pub fn len(&self) -> Result<usize> {
148        Ok(self.delegate.ivars().data.borrow().len())
149    }
150
151    pub fn is_empty(&self) -> Result<bool> {
152        Ok(self.len()? == 0)
153    }
154
155    pub fn clear(&mut self) -> Result<()> {
156        self.delegate.ivars().data.borrow_mut().clear();
157        catch(|| self.table.reloadData())
158    }
159
160    pub fn get(&self, i: usize) -> Result<String> {
161        Ok(self.delegate.ivars().data.borrow()[i].clone())
162    }
163
164    pub fn set(&mut self, i: usize, s: impl AsRef<str>) -> Result<()> {
165        self.delegate.ivars().data.borrow_mut()[i] = s.as_ref().to_string();
166        catch(|| self.table.reloadData())
167    }
168
169    pub fn insert(&mut self, i: usize, s: impl AsRef<str>) -> Result<()> {
170        self.delegate
171            .ivars()
172            .data
173            .borrow_mut()
174            .insert(i, s.as_ref().to_string());
175        catch(|| self.table.reloadData())
176    }
177
178    pub fn remove(&mut self, i: usize) -> Result<()> {
179        self.delegate.ivars().data.borrow_mut().remove(i);
180        catch(|| self.table.reloadData())
181    }
182}
183
184winio_handle::impl_as_widget!(ListBox, handle);
185
186#[derive(Debug, Default)]
187struct ListBoxDelegateIvars {
188    select: Callback,
189    data: RefCell<Vec<String>>,
190}
191
192define_class! {
193    #[unsafe(super(NSObject))]
194    #[name = "WinioListBoxDelegate"]
195    #[ivars = ListBoxDelegateIvars]
196    #[thread_kind = MainThreadOnly]
197    #[derive(Debug)]
198    struct ListBoxDelegate;
199
200    #[allow(non_snake_case)]
201    impl ListBoxDelegate {
202        #[unsafe(method_id(init))]
203        fn init(this: Allocated<Self>) -> Option<Retained<Self>> {
204            let this = this.set_ivars(ListBoxDelegateIvars::default());
205            unsafe { msg_send![super(this), init] }
206        }
207    }
208
209    unsafe impl NSObjectProtocol for ListBoxDelegate {}
210
211    unsafe impl NSControlTextEditingDelegate for ListBoxDelegate {}
212
213    #[allow(non_snake_case)]
214    unsafe impl NSTableViewDelegate for ListBoxDelegate {
215        #[unsafe(method(tableViewSelectionDidChange:))]
216        unsafe fn tableViewSelectionDidChange(&self, _notification: &NSNotification) {
217            self.ivars().select.signal::<GlobalRuntime>(());
218        }
219    }
220
221    #[allow(non_snake_case)]
222    unsafe impl NSTableViewDataSource for ListBoxDelegate {
223        #[unsafe(method(numberOfRowsInTableView:))]
224        unsafe fn numberOfRowsInTableView(&self, _table_view: &NSTableView) -> NSInteger {
225            self.ivars().data.borrow().len() as _
226        }
227
228        #[unsafe(method_id(tableView:objectValueForTableColumn:row:))]
229        unsafe fn tableView_objectValueForTableColumn_row(
230            &self,
231            _table_view: &NSTableView,
232            _table_column: Option<&NSTableColumn>,
233            row: NSInteger,
234        ) -> Option<Retained<AnyObject>> {
235            self.ivars().data.borrow().get(row as usize).map(|s| unsafe { Retained::cast_unchecked(NSString::from_str(s)) })
236        }
237    }
238}
239
240impl ListBoxDelegate {
241    pub fn new(mtm: MainThreadMarker) -> Retained<Self> {
242        unsafe { msg_send![mtm.alloc::<Self>(), init] }
243    }
244}