polyhorn_ios/components/
keyboard_avoiding_view.rs1use polyhorn_core::CommandBuffer;
2use polyhorn_ios_sys::coregraphics::CGRect;
3use polyhorn_ios_sys::foundation::NSNumber;
4use polyhorn_ios_sys::polykit::{PLYCallback, PLYKeyboardAvoidingView, PLYView};
5use polyhorn_ui::geometry::{ByEdge, Dimension};
6use polyhorn_ui::layout::LayoutAxisY;
7use polyhorn_ui::styles::{Position, Relative, ViewStyle};
8use std::sync::Arc;
9
10use crate::prelude::*;
11use crate::raw::{Builtin, Container, ContainerID, OpaqueContainer};
12use crate::{Key, Reference};
13
14#[derive(Default)]
18pub struct LayoutAdjustment {
19 margin: ByEdge<Dimension<f32>>,
20}
21
22impl LayoutAdjustment {
23 pub fn new() -> LayoutAdjustment {
25 Default::default()
26 }
27
28 pub fn margin_bottom(self, bottom: Dimension<f32>) -> LayoutAdjustment {
31 LayoutAdjustment {
32 margin: ByEdge {
33 vertical: LayoutAxisY {
34 bottom,
35 ..self.margin.vertical
36 },
37 ..self.margin
38 },
39 ..self
40 }
41 }
42}
43
44#[derive(Default)]
47pub struct KeyboardAvoidingView {
48 pub transform: Option<Arc<dyn Fn(f32) -> LayoutAdjustment + Send + Sync>>,
51}
52
53impl Container for PLYKeyboardAvoidingView {
54 fn mount(&mut self, child: &mut OpaqueContainer) {
55 if let Some(view) = child.container().to_view() {
56 PLYKeyboardAvoidingView::to_view(self).add_subview(&view)
57 }
58 }
59
60 fn unmount(&mut self) {
61 PLYKeyboardAvoidingView::to_view(self).remove_from_superview();
62 }
63
64 fn to_view(&self) -> Option<PLYView> {
65 Some(PLYKeyboardAvoidingView::to_view(self))
66 }
67}
68
69impl Component for KeyboardAvoidingView {
70 fn render(&self, manager: &mut Manager) -> Element {
71 let view_ref: Reference<Option<ContainerID>> = use_reference!(manager, None);
72
73 let transform = self.transform.clone();
74
75 use_layout_effect!(manager, move |link, buffer| {
76 let id = match view_ref.apply(link, |id| id.to_owned()) {
77 Some(id) => id,
78 None => return,
79 };
80
81 buffer.mutate(&[id], move |containers, _| {
82 let container = &mut containers[0];
83
84 let layout = match container.layout() {
85 Some(layout) => layout.clone(),
86 None => return,
87 };
88
89 if let Some(view) = container.downcast_mut::<PLYKeyboardAvoidingView>() {
90 {
91 let layout = layout.clone();
92
93 view.set_on_keyboard(PLYCallback::new(move |height: NSNumber| {
94 if let Some(transform) = transform.as_ref() {
95 let adjustment = transform(height.float_value());
96
97 layout.set_style(ViewStyle {
98 position: Position::Relative(Relative {
99 flex_grow: 1.0,
100 ..Default::default()
101 }),
102 margin: ByEdge {
103 vertical: LayoutAxisY {
104 bottom: adjustment.margin.vertical.bottom,
105 ..Default::default()
106 },
107 ..Default::default()
108 },
109 ..Default::default()
110 });
111
112 layout.layouter().write().unwrap().recompute_roots();
113 }
114 }));
115 }
116
117 view.to_view().set_layout(move || {
118 let current = layout.current();
119
120 CGRect::new(
121 current.origin.x as _,
122 current.origin.y as _,
123 current.size.width as _,
124 current.size.height as _,
125 )
126 });
127 }
128 });
129 });
130
131 Element::builtin(
132 Key::new(()),
133 Builtin::KeyboardAvoidingView,
134 manager.children(),
135 Some(view_ref.weak(manager)),
136 )
137 }
138}