ferroui_controls/
panel.rs1use crate::{Border, Control, ControlImpl, Controls};
2use ferroui_base::collections::{NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs};
3use ferroui_base::input::InputElementImpl;
4use ferroui_base::interactivity::InteractiveImpl;
5use ferroui_base::layout::LayoutableImpl;
6use ferroui_base::logical_tree::{ChildIndexChangedEventArgs, IChildIndexProvider};
7use ferroui_base::media::{DrawingContext, IBrush};
8use ferroui_base::reactive::{Disposable, IDisposable};
9use ferroui_base::utilities::HandlerList;
10use ferroui_base::{
11 ferro_class, ferro_impl_classes, ferro_property, instantiate, FerroObjectImpl, FerroObjectImplExt,
12 FerroProperty, ObjectType, Rect, Ref, StyledElement, StyledElementImpl, StyledProperty, Upcast, Visual, VisualImpl, VisualImplExt,
13 WeakRef,
14};
15use std::cell::{Cell, OnceCell};
16use std::rc::Rc;
17
18type ChildIndexChangedHandlers = HandlerList<dyn Fn(&ChildIndexChangedEventArgs)>;
19
20#[repr(C)]
25pub struct Panel {
26 base: Control,
27 children: Controls,
28 is_items_host: Cell<bool>,
29 child_index_changed: Rc<ChildIndexChangedHandlers>,
30 child_index_provider: OnceCell<Rc<PanelChildIndexProvider>>,
31}
32
33ferro_class! {
34 Panel: Control, virtuals PanelImpl: ControlImpl {
35 fn children_changed(this, e: &NotifyCollectionChangedEventArgs<'_, Ref<Control>>);
37 fn invalidate_measure_on_children_changed(this);
39 }
40}
41ferroui_base::ferro_class_info!(Panel { new: Panel::new });
42
43ferro_impl_classes!(Panel: LayoutableImpl, InteractiveImpl, InputElementImpl, ControlImpl);
44
45impl VisualImpl for Panel {
46 fn render(this: &Self, context: &mut DrawingContext) {
48 if let Some(background) = this.background() {
49 let render_size = this.bounds().size();
50 context.fill_rectangle(&background, Rect::from_size(render_size), 0.0);
51 }
52
53 Self::parent_render(this, context);
54 }
55}
56
57ferroui_base::ferro_overrides! { impl FerroObjectImpl for Panel {
58 fn constructed(this: &Self) {
59 Self::parent_constructed(this);
60
61 let weak = this.to_ref().downgrade();
62 this.children.add_collection_changed(Rc::new(
63 move |e: &NotifyCollectionChangedEventArgs<'_, Ref<Control>>| {
64 if let Some(this) = weak.upgrade() {
65 this.children_changed(e);
66
67 if matches!(
70 e.action,
71 NotifyCollectionChangedAction::Add
72 | NotifyCollectionChangedAction::Remove
73 | NotifyCollectionChangedAction::Reset
74 ) {
75 this.raise_child_index_changed(&ChildIndexChangedEventArgs::total_count_changed());
76 }
77 }
78 },
79 ));
80 }
81} }
82
83impl StyledElementImpl for Panel {
84 fn child_index_provider(this: &Self) -> Option<Rc<dyn IChildIndexProvider>> {
85 let provider = this
86 .child_index_provider
87 .get_or_init(|| Rc::new(PanelChildIndexProvider { owner: this.to_ref().downgrade() }))
88 .clone();
89 Some(provider)
90 }
91}
92
93impl PanelImpl for Panel {
94 fn children_changed(this: &Self, e: &NotifyCollectionChangedEventArgs<'_, Ref<Control>>) {
95 match e.action {
96 NotifyCollectionChangedAction::Add => {
97 if !this.is_items_host() {
98 this.logical_children().insert_range(
99 e.new_starting_index as usize,
100 e.new_items.iter().map(|c| c.clone().upcast::<StyledElement>()),
101 );
102 }
103 this.visual_children().insert_range(
104 e.new_starting_index as usize,
105 e.new_items.iter().map(|c| c.clone().upcast::<Visual>()),
106 );
107 }
108 NotifyCollectionChangedAction::Move => {
109 if !this.is_items_host() {
110 this.logical_children().move_range(
111 e.old_starting_index as usize,
112 e.old_items.len(),
113 e.new_starting_index as usize,
114 );
115 }
116 this.visual_children().move_range(
117 e.old_starting_index as usize,
118 e.old_items.len(),
119 e.new_starting_index as usize,
120 );
121 }
122 NotifyCollectionChangedAction::Remove => {
123 if !this.is_items_host() {
124 this.logical_children()
125 .remove_all(e.old_items.iter().map(|c| c.clone().upcast::<StyledElement>()));
126 }
127 this.visual_children().remove_all(e.old_items.iter().map(|c| c.clone().upcast::<Visual>()));
128 }
129 NotifyCollectionChangedAction::Replace => {
130 for i in 0..e.old_items.len() {
131 let index = i + e.old_starting_index as usize;
132 let child = e.new_items[i].clone();
133 if !this.is_items_host() {
134 this.logical_children().set(index, child.clone().upcast());
135 }
136 this.visual_children().set(index, child.upcast());
137 }
138 }
139 NotifyCollectionChangedAction::Reset => panic!("Specified method is not supported."),
140 }
141
142 this.raise_child_index_changed(&ChildIndexChangedEventArgs::child_indexes_reset());
143 this.invalidate_measure_on_children_changed();
144 }
145
146 fn invalidate_measure_on_children_changed(this: &Self) {
147 this.invalidate_measure();
148 }
149}
150
151ferroui_base::ferro_properties! { impl Panel {
152 ferro_property!(
153 pub fn background_property() -> StyledProperty<Option<Rc<dyn IBrush>>> {
155 Border::background_property().add_owner::<Panel>()
156 }
157 );
158} }
159
160impl Panel {
161 fn static_constructor() {
162 Visual::affects_render::<Panel>(&[Self::background_property().as_property()]);
163 }
164
165 pub fn construct() -> Self {
167 Self {
168 base: Control::construct(),
169 children: Controls::new(),
170 is_items_host: Cell::new(false),
171 child_index_changed: Rc::new(HandlerList::new()),
172 child_index_provider: OnceCell::new(),
173 }
174 }
175
176 pub fn new() -> Ref<Self> {
177 instantiate(Self::construct())
178 }
179
180 pub fn children(&self) -> Controls {
182 self.children.clone()
183 }
184
185 pub fn background(&self) -> Option<Rc<dyn IBrush>> {
187 self.get_value(Self::background_property())
188 }
189
190 pub fn set_background(&self, value: Option<Rc<dyn IBrush>>) {
191 self.set_value(Self::background_property(), value)
192 }
193
194 pub fn is_items_host(&self) -> bool {
196 self.is_items_host.get()
197 }
198
199 pub fn set_is_items_host(&self, value: bool) {
202 self.is_items_host.set(value)
203 }
204
205 pub fn affects_parent_arrange<TPanel: ObjectType + Upcast<Panel>>(properties: &[&'static FerroProperty]) {
210 for property in properties {
211 property.changed().subscribe(|e| {
212 let control = e.sender().downcast_ref::<Control>();
213 let parent = control.and_then(|c| c.visual_parent());
214 if let Some(panel) = parent.as_ref().and_then(|p| p.downcast_ref::<TPanel>()) {
215 let panel: &Panel = panel.upcast();
216 panel.invalidate_arrange();
217 }
218 });
219 }
220 }
221
222 pub fn affects_parent_measure<TPanel: ObjectType + Upcast<Panel>>(properties: &[&'static FerroProperty]) {
227 for property in properties {
228 property.changed().subscribe(|e| {
229 let control = e.sender().downcast_ref::<Control>();
230 let parent = control.and_then(|c| c.visual_parent());
231 if let Some(panel) = parent.as_ref().and_then(|p| p.downcast_ref::<TPanel>()) {
232 let panel: &Panel = panel.upcast();
233 panel.invalidate_measure();
234 }
235 });
236 }
237 }
238
239 fn raise_child_index_changed(&self, e: &ChildIndexChangedEventArgs) {
240 if !self.child_index_changed.is_empty() {
241 for (_, handler) in self.child_index_changed.snapshot().iter() {
242 handler(e);
243 }
244 }
245 }
246}
247
248struct PanelChildIndexProvider {
250 owner: WeakRef<Panel>,
251}
252
253impl IChildIndexProvider for PanelChildIndexProvider {
254 fn get_child_index(&self, child: &StyledElement) -> i32 {
255 let Some(owner) = self.owner.upgrade() else { return -1 };
256 match child.downcast_ref::<Control>() {
257 Some(control) => owner.children.index_of(&control.to_ref()).map_or(-1, |index| index as i32),
258 None => -1,
259 }
260 }
261
262 fn try_get_total_count(&self) -> Option<i32> {
263 let owner = self.owner.upgrade()?;
264 Some(owner.children.count() as i32)
265 }
266
267 fn child_index_changed(&self, handler: Rc<dyn Fn(&ChildIndexChangedEventArgs)>) -> Rc<dyn IDisposable> {
268 let Some(owner) = self.owner.upgrade() else { return Disposable::empty() };
269 let token = owner.child_index_changed.add(handler);
270 let handlers = Rc::downgrade(&owner.child_index_changed);
271 Disposable::create(move || {
272 if let Some(handlers) = handlers.upgrade() {
273 handlers.remove(token);
274 }
275 })
276 }
277}