Skip to main content

winio_ui_app_kit/widgets/
scroll_bar.rs

1use 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);