ferroui_controls/page/
content_page.rs1use super::safe_area_padding_extensions::SafeAreaPaddingExtensions;
2use super::{Page, PageImpl};
3use crate::presenters::ContentPresenter;
4use crate::primitives::{
5 TemplateAppliedEventArgs, TemplatedControlImpl, TemplatedControlImplExt,
6};
7use crate::templates::IDataTemplate;
8use crate::{ContentControl, Control, ControlImpl};
9use ferroui_base::input::InputElementImpl;
10use ferroui_base::interactivity::InteractiveImpl;
11use ferroui_base::layout::{HorizontalAlignment, LayoutableImpl, VerticalAlignment};
12use ferroui_base::{
13 ferro_class, ferro_class_info, ferro_impl_classes, ferro_properties, instantiate, BoxedValue, FerroObject,
14 FerroObjectImpl, FerroObjectImplExt, FerroProperty, FerroPropertyChangedEventArgs, Ref, StaticType, StyledElement,
15 StyledElementImpl, StyledProperty, StyledPropertyMetadata, TypeInfo, VisualImpl,
16};
17use std::cell::RefCell;
18use std::rc::Rc;
19
20#[repr(C)]
23pub struct ContentPage {
24 base: Page,
25 content_presenter: RefCell<Option<Ref<ContentPresenter>>>,
26 top_command_bar_presenter: RefCell<Option<Ref<ContentPresenter>>>,
27 bottom_command_bar_presenter: RefCell<Option<Ref<ContentPresenter>>>,
28}
29
30ferro_class!(ContentPage: Page);
31
32ferro_class_info!(ContentPage {
33 new: ContentPage::new,
34 markup: {
35 content: Content,
37 property_attributes: [Content: [DependsOn("ContentTemplate")]],
38 attributes: [
39 TemplatePart("PART_ContentPresenter", type(Ref<ContentPresenter>)),
40 TemplatePart("PART_TopCommandBar", type(Ref<ContentPresenter>)),
41 TemplatePart("PART_BottomCommandBar", type(Ref<ContentPresenter>)),
42 ],
43 },
44});
45
46ferro_impl_classes!(ContentPage: VisualImpl, LayoutableImpl, InteractiveImpl, InputElementImpl);
47
48impl FerroObjectImpl for ContentPage {
49 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
50 Self::parent_on_property_changed(this, change);
51
52 if change.property() == Self::automatically_apply_safe_area_padding_property().as_property()
53 || change.property() == Self::content_property().as_property()
54 {
55 this.update_content_safe_area_padding();
56 } else if change.property() == Self::top_command_bar_property().as_property()
57 || change.property() == Self::bottom_command_bar_property().as_property()
58 {
59 this.update_command_bars();
60 }
61 }
62}
63
64impl StyledElementImpl for ContentPage {
65 fn style_key_override(_this: &Self) -> &'static TypeInfo {
66 <ContentPage as StaticType>::TYPE
67 }
68}
69
70impl TemplatedControlImpl for ContentPage {
71 fn on_apply_template(this: &Self, e: &TemplateAppliedEventArgs) {
72 Self::parent_on_apply_template(this, e);
73
74 *this.content_presenter.borrow_mut() = Some(e.name_scope().get_as::<ContentPresenter>("PART_ContentPresenter"));
75 *this.top_command_bar_presenter.borrow_mut() = e.name_scope().find_as::<ContentPresenter>("PART_TopCommandBar");
76 *this.bottom_command_bar_presenter.borrow_mut() =
77 e.name_scope().find_as::<ContentPresenter>("PART_BottomCommandBar");
78
79 this.update_command_bars();
80 this.update_content_safe_area_padding();
81 }
82}
83
84impl PageImpl for ContentPage {
85 fn update_content_safe_area_padding(this: &Self) {
86 let content_presenter = this.content_presenter.borrow().clone();
87 if let Some(content_presenter) = content_presenter {
88 content_presenter.set_padding(if this.automatically_apply_safe_area_padding() {
89 this.padding().apply_safe_area_padding(this.safe_area_padding())
90 } else {
91 this.padding()
92 });
93 content_presenter.invalidate_measure();
94 }
95 }
96}
97
98impl ControlImpl for ContentPage {
99 fn on_create_automation_peer(this: &Self) -> Ref<crate::automation::peers::AutomationPeer> {
100 crate::automation::peers::ContentPageAutomationPeer::new(this).upcast()
101 }
102}
103
104ferro_properties! {
105 impl ContentPage {
106 pub fn content_property() -> StyledProperty<Option<BoxedValue>> {
108 ContentControl::content_property()
109 .add_owner_with::<ContentPage>(StyledPropertyMetadata::new(None).with_coerce(ContentPage::coerce_content))
110 }
111
112 pub fn content_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
114 ContentControl::content_template_property().add_owner::<ContentPage>()
115 }
116
117 pub fn automatically_apply_safe_area_padding_property() -> StyledProperty<bool> {
119 FerroProperty::register::<ContentPage, _>("AutomaticallyApplySafeAreaPadding", true)
120 }
121
122 pub fn top_command_bar_property() -> StyledProperty<Option<BoxedValue>> {
124 FerroProperty::register::<ContentPage, _>("TopCommandBar", None)
125 }
126
127 pub fn bottom_command_bar_property() -> StyledProperty<Option<BoxedValue>> {
129 FerroProperty::register::<ContentPage, _>("BottomCommandBar", None)
130 }
131
132 pub fn horizontal_content_alignment_property() -> StyledProperty<HorizontalAlignment> {
134 ContentControl::horizontal_content_alignment_property().add_owner::<ContentPage>()
135 }
136
137 pub fn vertical_content_alignment_property() -> StyledProperty<VerticalAlignment> {
139 ContentControl::vertical_content_alignment_property().add_owner::<ContentPage>()
140 }
141 }
142}
143
144impl ContentPage {
145 fn static_constructor() {
146 Self::content_property().changed().add_class_handler::<ContentPage>(|x, e| x.content_changed(e));
147 }
148
149 pub fn construct() -> Self {
151 Self {
152 base: Page::construct(),
153 content_presenter: RefCell::new(None),
154 top_command_bar_presenter: RefCell::new(None),
155 bottom_command_bar_presenter: RefCell::new(None),
156 }
157 }
158
159 pub fn new() -> Ref<Self> {
160 instantiate(Self::construct())
161 }
162
163 fn coerce_content(_instance: &FerroObject, value: Option<BoxedValue>) -> Option<BoxedValue> {
165 let is_page =
166 value.as_ref().and_then(Control::from_boxed).is_some_and(|control| control.is::<Page>());
167 if is_page {
168 panic!(
169 "A Page cannot be used as the content of a ContentPage. Use a MultiPage subclass such as NavigationPage, TabbedPage, DrawerPage, or CarouselPage to host child pages."
170 );
171 }
172 value
173 }
174
175 pub fn horizontal_content_alignment(&self) -> HorizontalAlignment {
177 self.get_value(Self::horizontal_content_alignment_property())
178 }
179
180 pub fn set_horizontal_content_alignment(&self, value: HorizontalAlignment) {
181 self.set_value(Self::horizontal_content_alignment_property(), value)
182 }
183
184 pub fn vertical_content_alignment(&self) -> VerticalAlignment {
186 self.get_value(Self::vertical_content_alignment_property())
187 }
188
189 pub fn set_vertical_content_alignment(&self, value: VerticalAlignment) {
190 self.set_value(Self::vertical_content_alignment_property(), value)
191 }
192
193 pub fn content(&self) -> Option<BoxedValue> {
199 self.get_value(Self::content_property())
200 }
201
202 pub fn set_content(&self, value: Option<BoxedValue>) {
207 self.set_value(Self::content_property(), value)
208 }
209
210 pub fn content_template(&self) -> Option<Rc<dyn IDataTemplate>> {
212 self.get_value(Self::content_template_property())
213 }
214
215 pub fn set_content_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
216 self.set_value(Self::content_template_property(), value)
217 }
218
219 pub fn automatically_apply_safe_area_padding(&self) -> bool {
222 self.get_value(Self::automatically_apply_safe_area_padding_property())
223 }
224
225 pub fn set_automatically_apply_safe_area_padding(&self, value: bool) {
226 self.set_value(Self::automatically_apply_safe_area_padding_property(), value)
227 }
228
229 pub fn top_command_bar(&self) -> Option<BoxedValue> {
231 self.get_value(Self::top_command_bar_property())
232 }
233
234 pub fn set_top_command_bar(&self, value: Option<BoxedValue>) {
235 self.set_value(Self::top_command_bar_property(), value)
236 }
237
238 pub fn bottom_command_bar(&self) -> Option<BoxedValue> {
240 self.get_value(Self::bottom_command_bar_property())
241 }
242
243 pub fn set_bottom_command_bar(&self, value: Option<BoxedValue>) {
244 self.set_value(Self::bottom_command_bar_property(), value)
245 }
246
247 fn content_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
248 let (old_value, new_value) = e.get_old_and_new_value::<Option<BoxedValue>>();
249
250 if let Some(old_child) = old_value.as_ref().and_then(Control::logical_from_boxed) {
251 StyledElement::logical_children(self).remove(&old_child);
252 }
253
254 if let Some(new_child) = new_value.as_ref().and_then(Control::logical_from_boxed) {
255 StyledElement::logical_children(self).add(new_child);
256 }
257 }
258
259 fn update_command_bars(&self) {
260 let top_command_bar_presenter = self.top_command_bar_presenter.borrow().clone();
261 if let Some(presenter) = top_command_bar_presenter {
262 let top_command_bar = self.top_command_bar();
263 presenter.set_content(top_command_bar.clone());
264 presenter.set_is_visible(top_command_bar.is_some());
265 }
266
267 let bottom_command_bar_presenter = self.bottom_command_bar_presenter.borrow().clone();
268 if let Some(presenter) = bottom_command_bar_presenter {
269 let bottom_command_bar = self.bottom_command_bar();
270 presenter.set_content(bottom_command_bar.clone());
271 presenter.set_is_visible(bottom_command_bar.is_some());
272 }
273 }
274}