winio_ui_app_kit/widgets/
tab_view.rs1use inherit_methods_macro::inherit_methods;
2use objc2::{
3 DefinedClass, MainThreadOnly, define_class, msg_send,
4 rc::{Allocated, Retained},
5 runtime::ProtocolObject,
6};
7use objc2_app_kit::{NSTabView, NSTabViewDelegate, NSTabViewItem, NSView};
8use objc2_foundation::{MainThreadMarker, NSObject, NSObjectProtocol, NSString};
9use winio_callback::Callback;
10use winio_handle::{AsContainer, BorrowedContainer};
11use winio_primitive::{Point, Size};
12
13use crate::{Error, GlobalRuntime, Result, catch, from_cgsize, from_nsstring, widgets::Widget};
14
15#[derive(Debug)]
16pub struct TabView {
17 handle: Widget,
18 view: Retained<NSTabView>,
19 delegate: Retained<TabViewDelegate>,
20}
21
22#[inherit_methods(from = "self.handle")]
23impl TabView {
24 pub fn new(parent: impl AsContainer) -> Result<Self> {
25 let parent = parent.as_container();
26 let mtm = parent.as_app_kit().mtm();
27
28 catch(|| unsafe {
29 let view = NSTabView::new(mtm);
30 let handle = Widget::from_nsview(parent, Retained::cast_unchecked(view.clone()))?;
31
32 let delegate = TabViewDelegate::new(mtm);
33 let del_obj = ProtocolObject::from_ref(&*delegate);
34 view.setDelegate(Some(del_obj));
35
36 Ok(Self {
37 handle,
38 view,
39 delegate,
40 })
41 })
42 .flatten()
43 }
44
45 pub fn is_visible(&self) -> Result<bool>;
46
47 pub fn set_visible(&mut self, v: bool) -> Result<()>;
48
49 pub fn is_enabled(&self) -> Result<bool>;
50
51 pub fn set_enabled(&mut self, v: bool) -> Result<()>;
52
53 pub fn loc(&self) -> Result<Point>;
54
55 pub fn set_loc(&mut self, p: Point) -> Result<()>;
56
57 pub fn size(&self) -> Result<Size>;
58
59 pub fn set_size(&mut self, v: Size) -> Result<()>;
60
61 pub fn selection(&self) -> Result<Option<usize>> {
62 catch(|| {
63 self.view
64 .selectedTabViewItem()
65 .map(|item| self.view.indexOfTabViewItem(&item) as _)
66 })
67 }
68
69 pub fn set_selection(&mut self, i: usize) -> Result<()> {
70 catch(|| self.view.selectTabViewItemAtIndex(i as _))
71 }
72
73 pub async fn wait_select(&self) {
74 self.delegate.ivars().did_select.wait().await
75 }
76
77 pub fn insert(&mut self, i: usize, item: &TabViewItem) -> Result<()> {
78 catch(|| self.view.insertTabViewItem_atIndex(&item.item, i as _))
79 }
80
81 pub fn remove(&mut self, i: usize) -> Result<()> {
82 catch(|| {
83 self.view
84 .removeTabViewItem(&self.view.tabViewItemAtIndex(i as _))
85 })
86 }
87
88 pub fn len(&self) -> Result<usize> {
89 catch(|| self.view.numberOfTabViewItems() as _)
90 }
91
92 pub fn is_empty(&self) -> Result<bool> {
93 Ok(self.len()? == 0)
94 }
95
96 pub fn clear(&mut self) -> Result<()> {
97 catch(|| {
98 while self.view.numberOfTabViewItems() > 0 {
99 self.view
100 .removeTabViewItem(&self.view.tabViewItemAtIndex(0));
101 }
102 })
103 }
104}
105
106winio_handle::impl_as_widget!(TabView, handle);
107
108#[derive(Debug, Default)]
109struct TabViewDelegateIvars {
110 did_select: Callback,
111}
112
113define_class! {
114 #[unsafe(super(NSObject))]
115 #[name = "WinioTabViewDelegate"]
116 #[ivars = TabViewDelegateIvars]
117 #[thread_kind = MainThreadOnly]
118 #[derive(Debug)]
119 struct TabViewDelegate;
120
121 impl TabViewDelegate {
122 #[unsafe(method_id(init))]
123 fn init(this: Allocated<Self>) -> Option<Retained<Self>> {
124 let this = this.set_ivars(TabViewDelegateIvars::default());
125 unsafe { msg_send![super(this), init] }
126 }
127 }
128
129 unsafe impl NSObjectProtocol for TabViewDelegate {}
130
131 #[allow(non_snake_case)]
132 unsafe impl NSTabViewDelegate for TabViewDelegate {
133 #[unsafe(method(tabView:didSelectTabViewItem:))]
134 unsafe fn tabView_didSelectTabViewItem(
135 &self,
136 _tab_view: &NSTabView,
137 _tab_view_item: Option<&NSTabViewItem>,
138 ) {
139 self.ivars().did_select.signal::<GlobalRuntime>(());
140 }
141 }
142}
143
144impl TabViewDelegate {
145 pub fn new(mtm: MainThreadMarker) -> Retained<Self> {
146 unsafe { msg_send![mtm.alloc::<Self>(), init] }
147 }
148}
149
150#[derive(Debug)]
151pub struct TabViewItem {
152 item: Retained<NSTabViewItem>,
153 view: Retained<NSView>,
154}
155
156impl TabViewItem {
157 pub fn new() -> Result<Self> {
158 let mtm = MainThreadMarker::new().ok_or(Error::NotMainThread)?;
159 catch(|| {
160 let item = NSTabViewItem::new();
161 let view = NSView::new(mtm);
162 item.setView(Some(&view));
163 Self { item, view }
164 })
165 }
166
167 pub fn text(&self) -> Result<String> {
168 catch(|| from_nsstring(&self.item.label()))
169 }
170
171 pub fn set_text(&mut self, s: impl AsRef<str>) -> Result<()> {
172 catch(|| self.item.setLabel(&NSString::from_str(s.as_ref())))
173 }
174
175 pub fn size(&self) -> Result<Size> {
176 catch(|| {
177 let frame = self.view.frame().size;
178 Ok(from_cgsize(frame))
179 })
180 .flatten()
181 }
182}
183
184impl AsContainer for TabViewItem {
185 fn as_container(&self) -> BorrowedContainer<'_> {
186 BorrowedContainer::app_kit(&self.view)
187 }
188}