ferroui_controls/
content_control.rs1use crate::utils::debug_display::{append_optional_boxed_value, build_base_debug_display};
2use crate::metadata::{PseudoClassesAttribute, TemplatePartAttribute};
3use crate::presenters::{register_content_presenter_host, ContentPresenter, IContentPresenterHost};
4use crate::primitives::{TemplatedControl, TemplatedControlImpl};
5use crate::templates::{FuncControlTemplate, FuncTemplateNameScopeExtensions, IControlTemplate, IDataTemplate};
6use crate::{Control, ControlImpl};
7use ferroui_base::collections::FerroList;
8use ferroui_base::data::TemplateBinding;
9use ferroui_base::input::InputElementImpl;
10use ferroui_base::interactivity::InteractiveImpl;
11use ferroui_base::layout::{HorizontalAlignment, LayoutableImpl, VerticalAlignment};
12use ferroui_base::{
13 ferro_class, ferro_impl_classes, ferro_property, instantiate, BoxedValue, FerroObjectImpl, FerroObjectImplExt,
14 FerroProperty, FerroPropertyChangedEventArgs, Ref, StaticType, StyledElement, StyledElementImpl, StyledProperty,
15 VisualImpl,
16};
17use std::cell::RefCell;
18use std::rc::Rc;
19
20const PC_EMPTY: &str = ":empty";
21
22#[repr(C)]
24pub struct ContentControl {
25 base: TemplatedControl,
26 presenter: RefCell<Option<Ref<ContentPresenter>>>,
27}
28
29ferro_class! {
30 ContentControl: TemplatedControl, virtuals ContentControlImpl: TemplatedControlImpl {
31 fn register_content_presenter(this, presenter: &ContentPresenter) -> bool;
35 }
36}
37ferroui_base::ferro_class_info!(ContentControl { new: ContentControl::new });
38
39ferro_impl_classes!(
40 ContentControl: StyledElementImpl,
41 VisualImpl,
42 LayoutableImpl,
43 InteractiveImpl,
44 InputElementImpl,
45 ControlImpl,
46 TemplatedControlImpl
47);
48
49ferroui_base::ferro_overrides! { impl FerroObjectImpl for ContentControl {
50 fn constructed(this: &Self) {
51 Self::parent_constructed(this);
52 this.update_pseudo_classes();
53 }
54
55 fn on_property_changed(this: &Self, change: &FerroPropertyChangedEventArgs<'_>) {
56 Self::parent_on_property_changed(this, change);
57
58 if change.property() == Self::content_property().as_property() {
59 this.content_changed(change);
60 }
61 }
62} }
63
64impl ContentControlImpl for ContentControl {
65 fn register_content_presenter(this: &Self, presenter: &ContentPresenter) -> bool {
66 if presenter.name().as_deref() == Some("PART_ContentPresenter") {
67 *this.presenter.borrow_mut() = Some(presenter.to_ref());
68 return true;
69 }
70
71 false
72 }
73}
74
75impl IContentPresenterHost for ContentControl {
76 fn logical_children(&self) -> &FerroList<Ref<StyledElement>> {
77 StyledElement::logical_children(self)
78 }
79
80 fn register_content_presenter(&self, presenter: &ContentPresenter) -> bool {
81 ContentControl::register_content_presenter(self, presenter)
82 }
83}
84
85impl ContentControl {
86 pub const TEMPLATE_PARTS: &'static [TemplatePartAttribute] =
88 &[TemplatePartAttribute::new("PART_ContentPresenter", <ContentPresenter as StaticType>::TYPE)];
89
90 pub const PSEUDO_CLASSES: PseudoClassesAttribute = PseudoClassesAttribute::new(&[PC_EMPTY]);
92}
93
94impl crate::i_content_control::IContentControl for ContentControl {
95 fn content(&self) -> Option<BoxedValue> {
96 ContentControl::content(self)
97 }
98
99 fn set_content(&self, value: Option<BoxedValue>) {
100 ContentControl::set_content(self, value)
101 }
102
103 fn content_template(&self) -> Option<Rc<dyn IDataTemplate>> {
104 ContentControl::content_template(self)
105 }
106
107 fn set_content_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
108 ContentControl::set_content_template(self, value)
109 }
110
111 fn horizontal_content_alignment(&self) -> HorizontalAlignment {
112 ContentControl::horizontal_content_alignment(self)
113 }
114
115 fn set_horizontal_content_alignment(&self, value: HorizontalAlignment) {
116 ContentControl::set_horizontal_content_alignment(self, value)
117 }
118
119 fn vertical_content_alignment(&self) -> VerticalAlignment {
120 ContentControl::vertical_content_alignment(self)
121 }
122
123 fn set_vertical_content_alignment(&self, value: VerticalAlignment) {
124 ContentControl::set_vertical_content_alignment(self, value)
125 }
126}
127
128ferroui_base::ferro_properties! { impl ContentControl {
129 ferro_property!(
130 pub fn content_property() -> StyledProperty<Option<BoxedValue>> {
132 FerroProperty::register::<ContentControl, _>("Content", None)
133 }
134 );
135
136 ferro_property!(
137 pub fn content_template_property() -> StyledProperty<Option<Rc<dyn IDataTemplate>>> {
139 FerroProperty::register::<ContentControl, _>("ContentTemplate", None)
140 }
141 );
142
143 ferro_property!(
144 pub fn horizontal_content_alignment_property() -> StyledProperty<HorizontalAlignment> {
146 FerroProperty::register::<ContentControl, _>("HorizontalContentAlignment", HorizontalAlignment::Stretch)
147 }
148 );
149
150 ferro_property!(
151 pub fn vertical_content_alignment_property() -> StyledProperty<VerticalAlignment> {
153 FerroProperty::register::<ContentControl, _>("VerticalContentAlignment", VerticalAlignment::Stretch)
154 }
155 );
156} }
157
158impl ContentControl {
159 fn static_constructor() {
160 register_content_presenter_host::<ContentControl>();
161 crate::i_content_control::register_content_control::<ContentControl>();
162
163 let template: Rc<dyn IControlTemplate> = FuncControlTemplate::new(|_, ns| {
164 let presenter = ContentPresenter::new();
165 presenter.set_name(Some("PART_ContentPresenter".to_string()));
166
167 let bind = |property: &'static FerroProperty| {
168 presenter.bind_binding(property, &TemplateBinding::new(property));
169 };
170 bind(TemplatedControl::background_property().as_property());
171 bind(TemplatedControl::background_sizing_property().as_property());
172 bind(TemplatedControl::border_brush_property().as_property());
173 bind(TemplatedControl::border_thickness_property().as_property());
174 bind(TemplatedControl::corner_radius_property().as_property());
175 bind(ContentControl::content_template_property().as_property());
176 bind(ContentControl::content_property().as_property());
177 bind(TemplatedControl::padding_property().as_property());
178 bind(ContentControl::vertical_content_alignment_property().as_property());
179 bind(ContentControl::horizontal_content_alignment_property().as_property());
180
181 presenter.register_in_name_scope(&**ns).upcast()
182 });
183 TemplatedControl::template_property().override_default_value::<ContentControl>(Some(template));
184 }
185
186 pub fn construct() -> Self {
188 Self { base: TemplatedControl::construct(), presenter: RefCell::new(None) }
189 }
190
191 pub fn new() -> Ref<Self> {
192 instantiate(Self::construct())
193 }
194
195 pub fn content(&self) -> Option<BoxedValue> {
197 self.get_value(Self::content_property())
198 }
199
200 pub fn set_content(&self, value: Option<BoxedValue>) {
201 self.set_value(Self::content_property(), value)
202 }
203
204 pub fn content_template(&self) -> Option<Rc<dyn IDataTemplate>> {
206 self.get_value(Self::content_template_property())
207 }
208
209 pub fn set_content_template(&self, value: Option<Rc<dyn IDataTemplate>>) {
210 self.set_value(Self::content_template_property(), value)
211 }
212
213 pub fn presenter(&self) -> Option<Ref<ContentPresenter>> {
215 self.presenter.borrow().clone()
216 }
217
218 pub fn horizontal_content_alignment(&self) -> HorizontalAlignment {
220 self.get_value(Self::horizontal_content_alignment_property())
221 }
222
223 pub fn set_horizontal_content_alignment(&self, value: HorizontalAlignment) {
224 self.set_value(Self::horizontal_content_alignment_property(), value)
225 }
226
227 pub fn vertical_content_alignment(&self) -> VerticalAlignment {
229 self.get_value(Self::vertical_content_alignment_property())
230 }
231
232 pub fn set_vertical_content_alignment(&self, value: VerticalAlignment) {
233 self.set_value(Self::vertical_content_alignment_property(), value)
234 }
235
236 fn content_changed(&self, e: &FerroPropertyChangedEventArgs<'_>) {
237 let (old_value, new_value) = e.get_old_and_new_value::<Option<BoxedValue>>();
238
239 if let Some(old_child) = old_value.as_ref().and_then(Control::logical_from_boxed) {
240 StyledElement::logical_children(self).remove(&old_child);
241 }
242
243 if let Some(new_child) = new_value.as_ref().and_then(Control::logical_from_boxed) {
244 StyledElement::logical_children(self).add(new_child);
245 }
246
247 self.update_pseudo_classes();
248 }
249
250 fn update_pseudo_classes(&self) {
251 self.pseudo_classes().set(PC_EMPTY, self.content().is_none());
252 }
253}
254
255impl ContentControl {
256 pub(crate) fn build_debug_display(&self, builder: &mut String, include_content: bool) {
259 build_base_debug_display(self, builder);
260
261 if include_content {
262 append_optional_boxed_value(builder, "Content", self.content().as_ref(), include_content);
263 }
264 }
265}