winio_ui_app_kit/widgets/
scroll_bar.rs1use inherit_methods_macro::inherit_methods;
2use objc2::{
3 DefinedClass, MainThreadMarker, MainThreadOnly, define_class, msg_send,
4 rc::{Allocated, Retained},
5};
6use objc2_app_kit::{NSControlSize, NSEvent, NSScroller, NSScrollerStyle};
7use objc2_foundation::{NSPoint, NSRect, NSSize};
8use winio_callback::Callback;
9use winio_handle::{AsContainer, BorrowedContainer};
10use winio_primitive::{Orient, Point, Size};
11
12use crate::{GlobalRuntime, Result, catch, widgets::Widget};
13
14#[derive(Debug)]
15struct ScrollBarImpl {
16 handle: Widget,
17 view: Retained<CustomScroller>,
18 min: usize,
19 max: usize,
20}
21
22#[inherit_methods(from = "self.handle")]
23impl ScrollBarImpl {
24 pub fn new(parent: impl AsContainer, vertical: bool) -> Result<Self> {
25 let parent = parent.as_container();
26 let mtm = parent.as_app_kit().mtm();
27
28 catch(|| unsafe {
29 let view = CustomScroller::new(
30 mtm,
31 if vertical {
32 NSRect::new(NSPoint::ZERO, NSSize::new(10.0, 20.0))
33 } else {
34 NSRect::new(NSPoint::ZERO, NSSize::new(20.0, 10.0))
35 },
36 );
37 let handle = Widget::from_nsview(parent, Retained::cast_unchecked(view.clone()))?;
38
39 view.setEnabled(true);
40
41 Ok(Self {
42 handle,
43 view,
44 min: 0,
45 max: 0,
46 })
47 })
48 .flatten()
49 }
50
51 pub fn is_visible(&self) -> Result<bool>;
52
53 pub fn set_visible(&mut self, v: bool) -> Result<()>;
54
55 pub fn is_enabled(&self) -> Result<bool>;
56
57 pub fn set_enabled(&mut self, v: bool) -> Result<()>;
58
59 pub fn loc(&self) -> Result<Point>;
60
61 pub fn set_loc(&mut self, p: Point) -> Result<()>;
62
63 pub fn size(&self) -> Result<Size>;
64
65 pub fn set_size(&mut self, v: Size) -> Result<()>;
66
67 pub fn tooltip(&self) -> Result<String>;
68
69 pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()>;
70
71 pub fn minimum(&self) -> Result<usize> {
72 Ok(self.min)
73 }
74
75 pub fn set_minimum(&mut self, v: usize) -> Result<()> {
76 let pos = self.pos()?;
77 self.min = v;
78 self.set_pos(pos)
79 }
80
81 pub fn maximum(&self) -> Result<usize> {
82 Ok(self.max)
83 }
84
85 pub fn set_maximum(&mut self, v: usize) -> Result<()> {
86 let pos = self.pos()?;
87 self.max = v;
88 self.set_pos(pos)
89 }
90
91 pub fn page(&self) -> Result<usize> {
92 catch(|| (self.view.knobProportion() * (self.max - self.min) as f64) as usize)
93 }
94
95 pub fn set_page(&mut self, v: usize) -> Result<()> {
96 catch(|| {
97 self.view
98 .setKnobProportion(v as f64 / ((self.max - self.min) as f64))
99 })
100 }
101
102 pub fn pos(&self) -> Result<usize> {
103 let page = self.page()?;
104 catch(|| (self.view.doubleValue() * (self.max - page - self.min) as f64) as usize)
105 }
106
107 pub fn set_pos(&mut self, v: usize) -> Result<()> {
108 let page = self.page()?;
109 catch(|| {
110 self.view
111 .setDoubleValue(v as f64 / ((self.max - page - self.min) as f64))
112 })
113 }
114
115 pub async fn wait_change(&self) {
116 self.view.ivars().on_move.wait().await
117 }
118}
119
120winio_handle::impl_as_widget!(ScrollBarImpl, handle);
121
122#[derive(Debug, Default)]
123struct CustomScrollerIvars {
124 on_move: Callback,
125}
126
127define_class! {
128 #[unsafe(super(NSScroller))]
129 #[name = "WinioCustomScroller"]
130 #[ivars = CustomScrollerIvars]
131 #[thread_kind = MainThreadOnly]
132 #[derive(Debug)]
133 struct CustomScroller;
134
135 #[allow(non_snake_case)]
136 impl CustomScroller {
137 #[unsafe(method_id(initWithFrame:))]
138 fn initWithFrame(this: Allocated<Self>, frame: NSRect) -> Option<Retained<Self>> {
139 let this = this.set_ivars(CustomScrollerIvars::default());
140 unsafe { msg_send![super(this), initWithFrame: frame] }
141 }
142
143 #[unsafe(method(trackKnob:))]
144 unsafe fn trackKnob(&self, event: &NSEvent) {
145 let () = unsafe { msg_send![super(self), trackKnob:event] };
146 self.ivars().on_move.signal::<GlobalRuntime>(());
147 }
148 }
149}
150
151impl CustomScroller {
152 pub fn new(mtm: MainThreadMarker, frame: NSRect) -> Retained<Self> {
153 unsafe { msg_send![mtm.alloc::<Self>(), initWithFrame: frame] }
154 }
155}
156
157#[derive(Debug)]
158pub struct ScrollBar {
159 handle: ScrollBarImpl,
160 vertical: bool,
161}
162
163#[inherit_methods(from = "self.handle")]
164impl ScrollBar {
165 pub fn new(parent: impl AsContainer) -> Result<Self> {
166 let handle = ScrollBarImpl::new(&parent, false)?;
167 Ok(Self {
168 handle,
169 vertical: false,
170 })
171 }
172
173 fn recreate(&mut self, vertical: bool) -> Result<()> {
174 let parent = self.handle.handle.parent()?;
175 let mut new_handle = ScrollBarImpl::new(BorrowedContainer::app_kit(&parent), vertical)?;
176 new_handle.set_visible(self.handle.is_visible()?)?;
177 new_handle.set_enabled(self.handle.is_enabled()?)?;
178 new_handle.set_loc(self.handle.loc()?)?;
179 new_handle.set_size(self.handle.size()?)?;
180 new_handle.set_tooltip(self.handle.tooltip()?)?;
181 new_handle.set_minimum(self.handle.minimum()?)?;
182 new_handle.set_maximum(self.handle.maximum()?)?;
183 new_handle.set_page(self.handle.page()?)?;
184 new_handle.set_pos(self.handle.pos()?)?;
185 self.handle = new_handle;
186 Ok(())
187 }
188
189 pub fn is_visible(&self) -> Result<bool>;
190
191 pub fn set_visible(&mut self, v: bool) -> Result<()>;
192
193 pub fn is_enabled(&self) -> Result<bool>;
194
195 pub fn set_enabled(&mut self, v: bool) -> Result<()>;
196
197 pub fn preferred_size(&self) -> Result<Size> {
198 catch(|| {
199 let width = NSScroller::scrollerWidthForControlSize_scrollerStyle(
200 NSControlSize::Regular,
201 NSScrollerStyle::Overlay,
202 self.handle.view.mtm(),
203 );
204 if self.vertical {
205 Size::new(width, 0.0)
206 } else {
207 Size::new(0.0, width)
208 }
209 })
210 }
211
212 pub fn loc(&self) -> Result<Point>;
213
214 pub fn set_loc(&mut self, p: Point) -> Result<()>;
215
216 pub fn size(&self) -> Result<Size>;
217
218 pub fn set_size(&mut self, v: Size) -> Result<()>;
219
220 pub fn tooltip(&self) -> Result<String>;
221
222 pub fn set_tooltip(&mut self, s: impl AsRef<str>) -> Result<()>;
223
224 pub fn orient(&self) -> Result<Orient> {
225 if self.vertical {
226 Ok(Orient::Vertical)
227 } else {
228 Ok(Orient::Horizontal)
229 }
230 }
231
232 pub fn set_orient(&mut self, v: Orient) -> Result<()> {
233 let v = matches!(v, Orient::Vertical);
234 if self.vertical != v {
235 self.recreate(v)?;
236 self.vertical = v;
237 }
238 Ok(())
239 }
240
241 pub fn minimum(&self) -> Result<usize>;
242
243 pub fn set_minimum(&mut self, v: usize) -> Result<()>;
244
245 pub fn maximum(&self) -> Result<usize>;
246
247 pub fn set_maximum(&mut self, v: usize) -> Result<()>;
248
249 pub fn page(&self) -> Result<usize>;
250
251 pub fn set_page(&mut self, v: usize) -> Result<()>;
252
253 pub fn pos(&self) -> Result<usize>;
254
255 pub fn set_pos(&mut self, v: usize) -> Result<()>;
256
257 pub async fn wait_change(&self) {
258 self.handle.wait_change().await
259 }
260}
261
262winio_handle::impl_as_widget!(ScrollBar, handle);