winio_ui_app_kit/widgets/
list_box.rs1use 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}