1use crate::platform::{IInputPane, InputPaneStateEventArgs};
2use crate::{ControlImpl, Decorator, InputPaneAwareBehavior, TopLevel};
3use ferroui_base::animation::easings::{Easing, LinearEasing};
4use ferroui_base::animation::{DoubleTransition, ITransition, TimeSpan, Transitions};
5use ferroui_base::input::InputElementImpl;
6use ferroui_base::interactivity::InteractiveImpl;
7use ferroui_base::layout::{Layoutable, LayoutableImpl, LayoutableImplExt};
8use ferroui_base::reactive::IDisposable;
9use ferroui_base::{
10 ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate, DirectProperty, FerroObjectImpl,
11 FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Matrix, Point, Rect, Ref, Size,
12 StyledElementImpl, StyledProperty, Thickness, VisualImpl, VisualImplExt, VisualTreeAttachmentEventArgs,
13};
14use std::cell::{Cell, RefCell};
15use std::rc::Rc;
16
17#[repr(C)]
21pub struct InputPaneAwareDecorator {
22 base: Decorator,
23 behavior: Cell<InputPaneAwareBehavior>,
24 input_pane: RefCell<Option<Rc<dyn IInputPane>>>,
25 input_pane_state_changed: RefCell<Option<Rc<dyn IDisposable>>>,
27 first_layout_done: Cell<bool>,
28}
29
30ferro_class!(InputPaneAwareDecorator: Decorator);
31ferro_class_info!(InputPaneAwareDecorator { new: InputPaneAwareDecorator::new });
32ferro_impl_classes!(InputPaneAwareDecorator: StyledElementImpl, InteractiveImpl, InputElementImpl, ControlImpl);
33
34impl FerroObjectImpl for InputPaneAwareDecorator {
35 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
36 Self::parent_on_property_changed(this, change);
37
38 if change.property() == Self::behavior_property().as_property() {
39 this.ensure_input_pane_padding_applied();
40 }
41 }
42}
43
44impl LayoutableImpl for InputPaneAwareDecorator {
45 fn measure_override(this: &Self, available_size: Size) -> Size {
46 if this.input_pane.borrow().is_some()
47 && this.first_layout_done.get()
48 && this.visual_root().is_some()
49 && this.behavior() == InputPaneAwareBehavior::Resize
50 {
51 this.set_current_value(
52 Decorator::padding_property(),
53 Thickness::new(0.0, 0.0, 0.0, this.current_input_pane_padding()),
54 );
55 }
56 Self::parent_measure_override(this, available_size)
57 }
58
59 fn arrange_override(this: &Self, final_size: Size) -> Size {
60 this.first_layout_done.set(true);
61
62 if this.behavior() == InputPaneAwareBehavior::Pan {
63 if let Some(child) = this.child() {
64 child.arrange(Rect::from_position_size(
65 Point::new(0.0, -this.current_input_pane_padding()),
66 final_size,
67 ));
68 }
69
70 final_size
71 } else {
72 Self::parent_arrange_override(this, final_size)
73 }
74 }
75}
76
77impl VisualImpl for InputPaneAwareDecorator {
78 fn on_attached_to_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
79 Self::parent_on_attached_to_visual_tree(this, e);
80
81 let input_pane = TopLevel::get_top_level(Some(this)).and_then(|top_level| top_level.input_pane());
82 if let Some(input_pane) = &input_pane {
83 let weak = this.to_ref().downgrade();
86 let subscription = input_pane.state_changed(Rc::new(move |e| {
87 if let Some(this) = weak.upgrade() {
88 this.input_pane_aware_view_state_changed(e);
89 }
90 }));
91 *this.input_pane_state_changed.borrow_mut() = Some(subscription);
92 }
93 *this.input_pane.borrow_mut() = input_pane;
94 this.ensure_input_pane_padding_applied();
95 }
96
97 fn on_detached_from_visual_tree(this: &Self, e: &VisualTreeAttachmentEventArgs) {
98 Self::parent_on_detached_from_visual_tree(this, e);
99 let subscription = this.input_pane_state_changed.borrow_mut().take();
100 if let Some(subscription) = subscription {
101 subscription.dispose();
102 }
103 *this.input_pane.borrow_mut() = None;
104 this.set_current_value(Decorator::padding_property(), Thickness::default());
105 this.set_current_input_pane_padding(0.0);
106 }
107}
108
109ferro_properties! {
110 impl InputPaneAwareDecorator {
111 pub fn behavior_property() -> DirectProperty<InputPaneAwareDecorator, InputPaneAwareBehavior> {
113 FerroProperty::register_direct::<InputPaneAwareDecorator, _>(
114 "Behavior",
115 |o| o.behavior(),
116 Some(|o, x| o.set_behavior(x)),
117 InputPaneAwareBehavior::None,
118 )
119 }
120
121 fn current_input_pane_padding_property() -> StyledProperty<f64> {
122 FerroProperty::register::<InputPaneAwareDecorator, _>("CurrentInputPanePadding", 0.0)
123 }
124 }
125}
126
127impl InputPaneAwareDecorator {
128 fn static_constructor() {
129 Layoutable::affects_measure::<InputPaneAwareDecorator>(&[
130 Self::behavior_property().as_property(),
131 Self::current_input_pane_padding_property().as_property(),
132 ]);
133 Layoutable::affects_arrange::<InputPaneAwareDecorator>(&[
134 Self::current_input_pane_padding_property().as_property()
135 ]);
136 }
137
138 pub fn construct() -> Self {
140 Self {
141 base: Decorator::construct(),
142 behavior: Cell::new(InputPaneAwareBehavior::None),
143 input_pane: RefCell::new(None),
144 input_pane_state_changed: RefCell::new(None),
145 first_layout_done: Cell::new(false),
146 }
147 }
148
149 pub fn new() -> Ref<Self> {
150 instantiate(Self::construct())
151 }
152
153 pub fn behavior(&self) -> InputPaneAwareBehavior {
155 self.behavior.get()
156 }
157
158 pub fn set_behavior(&self, value: InputPaneAwareBehavior) {
159 self.set_and_raise_cell(Self::behavior_property(), &self.behavior, value);
160 }
161
162 fn current_input_pane_padding(&self) -> f64 {
163 self.get_value(Self::current_input_pane_padding_property())
164 }
165
166 fn set_current_input_pane_padding(&self, value: f64) {
167 self.set_value(Self::current_input_pane_padding_property(), value)
168 }
169
170 fn ensure_input_pane_padding_applied(&self) {
171 self.set_current_value(Decorator::padding_property(), Thickness::default());
172
173 let input_pane = self.input_pane.borrow().clone();
174 match (input_pane, self.visual_root()) {
175 (Some(input_pane), Some(root))
176 if self.first_layout_done.get() && self.behavior() != InputPaneAwareBehavior::None =>
177 {
178 let occluded_rect = input_pane.occluded_rect();
179
180 let transform_matrix = self.transform_to_visual(&root).unwrap_or(Matrix::IDENTITY);
181
182 let translated_rect =
183 Rect::from_size(self.bounds().size()).transform_to_aabb(transform_matrix);
184
185 let intersect = occluded_rect.intersect(translated_rect);
186 self.set_current_input_pane_padding(intersect.height);
187 }
188 _ => self.set_current_input_pane_padding(0.0),
189 }
190 }
191
192 fn input_pane_aware_view_state_changed(&self, e: &InputPaneStateEventArgs) {
193 let transition = DoubleTransition::new();
194 transition.set_property(Some(Self::current_input_pane_padding_property().as_property()));
195 transition.set_duration(TimeSpan::from(e.animation_duration()));
196 transition.set_easing(match e.easing() {
199 Some(easing) if easing.derives_from_easing() => Easing::from_rc(easing.clone()),
200 _ => Easing::new(LinearEasing::new()),
201 });
202
203 let transition: Rc<dyn ITransition> = transition.into();
204 self.set_transitions(Some(Transitions::from_items([transition])));
205
206 self.ensure_input_pane_padding_applied();
207
208 self.invalidate_measure();
209 }
210}