ferroui_ios/
native_control_host_impl.rs1use ferroui_base::{Rect, Size};
6
7fn math_max(val1: f64, val2: f64) -> f64 {
8 if val1.is_nan() || val2.is_nan() {
9 f64::NAN
10 } else {
11 val1.max(val2)
12 }
13}
14
15pub fn hidden_frame(size: Size) -> Rect {
18 Rect::new(0.0, 0.0, math_max(1.0, size.width), math_max(1.0, size.height))
19}
20
21pub fn shown_frame(bounds: Rect) -> Rect {
24 Rect::new(bounds.x, bounds.y, math_max(1.0, bounds.width), math_max(1.0, bounds.height))
25}
26
27#[cfg(target_os = "ios")]
28pub use uikit::UIViewControlHandle;
29#[cfg(target_os = "ios")]
30pub(crate) use uikit::NativeControlHostImpl;
31
32#[cfg(target_os = "ios")]
33mod uikit {
34 use super::{hidden_frame, shown_frame};
35 use crate::extensions::to_cg_rect;
36 use crate::ferro_view::FerroView;
37 use ferroui_base::reactive::IDisposable;
38 use ferroui_base::{Rect, Size};
39 use ferroui_controls::platform::{
40 INativeControlHostControlTopLevelAttachment, INativeControlHostDestroyableControlHandle,
41 INativeControlHostImpl, IPlatformHandle,
42 };
43 use objc2::rc::{Retained, Weak};
44 use objc2::{MainThreadMarker, MainThreadOnly};
45 use objc2_ui_kit::UIView;
46 use std::any::Any;
47 use std::cell::RefCell;
48 use std::rc::{Rc, Weak as RcWeak};
49
50 pub(crate) struct NativeControlHostImpl {
52 this: RcWeak<NativeControlHostImpl>,
53 ferro_view: Weak<FerroView>,
54 }
55
56 impl NativeControlHostImpl {
57 pub(crate) fn new(ferro_view: Weak<FerroView>) -> Rc<Self> {
58 Rc::new_cyclic(|this| Self { this: this.clone(), ferro_view })
59 }
60
61 fn rc(&self) -> Rc<NativeControlHostImpl> {
62 match self.this.upgrade() {
63 Some(this) => this,
64 None => panic!("The native control host is used while it is released."),
65 }
66 }
67
68 fn view(&self) -> Retained<FerroView> {
69 match self.ferro_view.load() {
70 Some(view) => view,
71 None => panic!("The view of the native control host was released."),
72 }
73 }
74 }
75
76 impl INativeControlHostImpl for NativeControlHostImpl {
77 fn as_any(&self) -> &dyn Any {
78 self
79 }
80
81 fn create_default_child(
82 &self,
83 _parent: Rc<dyn IPlatformHandle>,
84 ) -> Rc<dyn INativeControlHostDestroyableControlHandle> {
85 let mtm = self.view().mtm();
86 Rc::new(UIViewControlHandle::new(UIView::new(mtm)))
87 }
88
89 fn create_new_attachment_with(
90 &self,
91 create: Rc<dyn Fn(Rc<dyn IPlatformHandle>) -> Rc<dyn IPlatformHandle>>,
92 ) -> Rc<dyn INativeControlHostControlTopLevelAttachment> {
93 let parent = Rc::new(UIViewControlHandle::new(Retained::into_super(self.view())));
94 let child = create(parent);
98 let attachment = Rc::new(NativeControlAttachment::new(child));
99 attachment.set_attached_to(Some(self.rc()));
100 attachment
101 }
102
103 fn create_new_attachment(
104 &self,
105 handle: Rc<dyn IPlatformHandle>,
106 ) -> Rc<dyn INativeControlHostControlTopLevelAttachment> {
107 let attachment = Rc::new(NativeControlAttachment::new(handle));
108 attachment.set_attached_to(Some(self.rc()));
109 attachment
110 }
111
112 fn is_compatible_with(&self, handle: &dyn IPlatformHandle) -> bool {
113 handle.handle_descriptor() == Some(UIViewControlHandle::UI_VIEW_DESCRIPTOR)
114 }
115 }
116
117 fn view_holder(handle: &dyn IPlatformHandle) -> Option<Retained<UIView>> {
120 MainThreadMarker::new()?;
121 if handle.handle_descriptor() != Some(UIViewControlHandle::UI_VIEW_DESCRIPTOR) {
122 return None;
123 }
124 unsafe { Retained::retain(handle.handle() as *mut UIView) }
129 }
130
131 struct NativeControlAttachment {
132 child: RefCell<Option<Rc<dyn IPlatformHandle>>>,
134 view: RefCell<Option<Retained<UIView>>>,
135 attached_to: RefCell<Option<Rc<NativeControlHostImpl>>>,
136 }
137
138 impl NativeControlAttachment {
139 fn new(child: Rc<dyn IPlatformHandle>) -> Self {
140 let view = match child.as_any().downcast_ref::<UIViewControlHandle>() {
141 Some(handle) => Some(handle.view().clone()),
142 None => view_holder(&*child),
143 };
144 let Some(view) = view else {
145 panic!("The handle of a native control is not the handle of a UIView.");
146 };
147
148 Self { child: RefCell::new(Some(child)), view: RefCell::new(Some(view)), attached_to: RefCell::new(None) }
149 }
150
151 fn check_disposed(&self) -> Retained<UIView> {
152 match self.view.borrow().clone() {
153 Some(view) => view,
154 None => panic!("Cannot access a disposed object. Object name: 'NativeControlAttachment'."),
155 }
156 }
157 }
158
159 impl IDisposable for NativeControlAttachment {
160 fn dispose(&self) {
161 let view = self.view.borrow_mut().take();
162 if let Some(view) = &view {
163 view.removeFromSuperview();
164 }
165 *self.child.borrow_mut() = None;
166 *self.attached_to.borrow_mut() = None;
167 }
168 }
169
170 impl INativeControlHostControlTopLevelAttachment for NativeControlAttachment {
171 fn attached_to(&self) -> Option<Rc<dyn INativeControlHostImpl>> {
172 self.attached_to.borrow().clone().map(|host| host as Rc<dyn INativeControlHostImpl>)
173 }
174
175 fn set_attached_to(&self, value: Option<Rc<dyn INativeControlHostImpl>>) {
176 let view = self.check_disposed();
177
178 let host = value.map(|value| match value.as_any().downcast_ref::<NativeControlHostImpl>() {
179 Some(host) => host.rc(),
180 None => panic!("Unable to cast the native control host to NativeControlHostImpl."),
181 });
182 match &host {
183 None => view.removeFromSuperview(),
184 Some(host) => host.view().addSubview(&view),
185 }
186 *self.attached_to.borrow_mut() = host;
187 }
188
189 fn is_compatible_with(&self, host: &dyn INativeControlHostImpl) -> bool {
190 host.as_any().is::<NativeControlHostImpl>()
191 }
192
193 fn hide_with_size(&self, size: Size) {
194 let view = self.check_disposed();
195 if self.attached_to.borrow().is_none() {
196 return;
197 }
198
199 view.setHidden(true);
200 view.setFrame(to_cg_rect(hidden_frame(size)));
201 }
202
203 fn show_in_bounds(&self, bounds: Rect) {
204 let view = self.check_disposed();
205 if self.attached_to.borrow().is_none() {
206 panic!("The control isn't currently attached to a toplevel");
207 }
208
209 view.setFrame(to_cg_rect(shown_frame(bounds)));
210 view.setHidden(false);
211 }
212 }
213
214 pub struct UIViewControlHandle {
216 view: Retained<UIView>,
217 }
218
219 impl UIViewControlHandle {
220 pub const UI_VIEW_DESCRIPTOR: &'static str = "UIView";
222
223 pub fn new(view: Retained<UIView>) -> Self {
225 Self { view }
226 }
227
228 pub fn view(&self) -> &Retained<UIView> {
230 &self.view
231 }
232 }
233
234 impl IPlatformHandle for UIViewControlHandle {
235 fn handle(&self) -> isize {
236 Retained::as_ptr(&self.view) as isize
237 }
238
239 fn handle_descriptor(&self) -> Option<&str> {
240 Some(Self::UI_VIEW_DESCRIPTOR)
241 }
242
243 fn as_any(&self) -> &dyn Any {
244 self
245 }
246 }
247
248 impl INativeControlHostDestroyableControlHandle for UIViewControlHandle {
249 fn destroy(&self) {
250 self.view.removeFromSuperview();
255 }
256 }
257}
258
259#[cfg(test)]
260mod tests {
261 use super::*;
263
264 #[test]
265 fn a_hidden_control_keeps_a_size_at_the_origin() {
266 assert_eq!(Rect::new(0.0, 0.0, 120.0, 40.0), hidden_frame(Size::new(120.0, 40.0)));
267 assert_eq!(Rect::new(0.0, 0.0, 1.0, 1.0), hidden_frame(Size::new(0.0, 0.5)));
268 }
269
270 #[test]
271 fn a_shown_control_is_at_least_a_point_wide_and_high() {
272 assert_eq!(Rect::new(10.0, 20.0, 120.0, 40.0), shown_frame(Rect::new(10.0, 20.0, 120.0, 40.0)));
273 assert_eq!(Rect::new(10.0, 20.0, 1.0, 1.0), shown_frame(Rect::new(10.0, 20.0, 0.0, -3.0)));
274 }
275}