1use crate::{Control, ControlImpl, Panel, PanelImpl};
6use ferroui_base::input::InputElementImpl;
7use ferroui_base::interactivity::InteractiveImpl;
8use ferroui_base::layout::{Layoutable, LayoutableImpl};
9use ferroui_base::utilities::math_utilities::{max, min};
10use ferroui_base::{
11 ferro_class, ferro_impl_classes, ferro_property, instantiate, AttachedProperty, FerroObjectImpl, FerroProperty, Rect, Ref, Size, StyledElementImpl, StyledProperty, VisualImpl,
12};
13
14#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
16#[repr(i32)]
17pub enum Dock {
18 #[default]
20 Left = 0,
21 Bottom = 1,
23 Right = 2,
25 Top = 3,
27}
28
29#[repr(C)]
32pub struct DockPanel {
33 base: Panel,
34}
35
36ferro_class!(DockPanel: Panel);
37ferroui_base::ferro_class_info!(DockPanel { new: DockPanel::new });
38ferro_impl_classes!(
39 DockPanel: StyledElementImpl,
40 VisualImpl,
41 InteractiveImpl,
42 InputElementImpl,
43 ControlImpl,
44 PanelImpl
45);
46
47ferroui_base::ferro_impl_classes!(DockPanel: FerroObjectImpl);
48
49impl LayoutableImpl for DockPanel {
50 fn measure_override(this: &Self, available_size: Size) -> Size {
59 let mut parent_width = 0.0;
60 let mut parent_height = 0.0;
61 let mut accumulated_width = 0.0;
62 let mut accumulated_height = 0.0;
63
64 let mut horizontal_spacing = false;
65 let mut vertical_spacing = false;
66 let children = this.children().snapshot();
67 let last_child_fill = this.last_child_fill();
68 let children_count = if last_child_fill { children.len().saturating_sub(1) } else { children.len() };
69
70 for child in children.iter().take(children_count) {
71 let child_constraint = Size::new(
72 max(0.0, available_size.width - accumulated_width),
73 max(0.0, available_size.height - accumulated_height),
74 );
75
76 child.measure(child_constraint);
77 let child_desired_size = child.desired_size();
78
79 match child.get_value(Self::dock_property()) {
95 Dock::Left | Dock::Right => {
96 parent_height = max(parent_height, accumulated_height + child_desired_size.height);
97 if child.is_visible() {
98 accumulated_width += this.horizontal_spacing();
99 horizontal_spacing = true;
100 }
101 accumulated_width += child_desired_size.width;
102 }
103 Dock::Top | Dock::Bottom => {
104 parent_width = max(parent_width, accumulated_width + child_desired_size.width);
105 if child.is_visible() {
106 accumulated_height += this.vertical_spacing();
107 vertical_spacing = true;
108 }
109 accumulated_height += child_desired_size.height;
110 }
111 }
112 }
113
114 if last_child_fill && !children.is_empty() {
115 let child = &children[children.len() - 1];
116 let child_constraint = Size::new(
117 max(0.0, available_size.width - accumulated_width),
118 max(0.0, available_size.height - accumulated_height),
119 );
120
121 child.measure(child_constraint);
122 let child_desired_size = child.desired_size();
123 parent_height = max(parent_height, accumulated_height + child_desired_size.height);
124 parent_width = max(parent_width, accumulated_width + child_desired_size.width);
125 accumulated_height += child_desired_size.height;
126 accumulated_width += child_desired_size.width;
127 } else {
128 if horizontal_spacing {
129 accumulated_width -= this.horizontal_spacing();
130 }
131 if vertical_spacing {
132 accumulated_height -= this.vertical_spacing();
133 }
134 }
135
136 parent_width = max(parent_width, accumulated_width);
138 parent_height = max(parent_height, accumulated_height);
139 Size::new(parent_width, parent_height)
140 }
141
142 fn arrange_override(this: &Self, final_size: Size) -> Size {
145 let children = this.children().snapshot();
146 if children.is_empty() {
147 return final_size;
148 }
149
150 let mut current_bounds = Rect::from_size(final_size);
151 let last_child_fill = this.last_child_fill();
152 let children_count = if last_child_fill { children.len() - 1 } else { children.len() };
153
154 for child in children.iter().take(children_count) {
155 if !child.is_visible() {
156 continue;
157 }
158
159 let dock = child.get_value(Self::dock_property());
160 match dock {
161 Dock::Left => {
162 let mut width = min(child.desired_size().width, current_bounds.width);
163 child.arrange(current_bounds.with_width(width));
164 width += this.horizontal_spacing();
165 current_bounds = Rect::new(
166 current_bounds.x + width,
167 current_bounds.y,
168 max(0.0, current_bounds.width - width),
169 current_bounds.height,
170 );
171 }
172 Dock::Top => {
173 let mut height = min(child.desired_size().height, current_bounds.height);
174 child.arrange(current_bounds.with_height(height));
175 height += this.vertical_spacing();
176 current_bounds = Rect::new(
177 current_bounds.x,
178 current_bounds.y + height,
179 current_bounds.width,
180 max(0.0, current_bounds.height - height),
181 );
182 }
183 Dock::Right => {
184 let mut width = min(child.desired_size().width, current_bounds.width);
185 child.arrange(Rect::new(
186 current_bounds.x + current_bounds.width - width,
187 current_bounds.y,
188 width,
189 current_bounds.height,
190 ));
191 width += this.horizontal_spacing();
192 current_bounds = current_bounds.with_width(max(0.0, current_bounds.width - width));
193 }
194 Dock::Bottom => {
195 let mut height = min(child.desired_size().height, current_bounds.height);
196 child.arrange(Rect::new(
197 current_bounds.x,
198 current_bounds.y + current_bounds.height - height,
199 current_bounds.width,
200 height,
201 ));
202 height += this.vertical_spacing();
203 current_bounds = current_bounds.with_height(max(0.0, current_bounds.height - height));
204 }
205 }
206 }
207
208 if last_child_fill {
209 let child = &children[children.len() - 1];
210 child.arrange(Rect::new(current_bounds.x, current_bounds.y, current_bounds.width, current_bounds.height));
211 }
212
213 final_size
214 }
215}
216
217ferroui_base::ferro_properties! { impl DockPanel {
218 ferro_property!(
219 pub fn dock_property() -> AttachedProperty<Dock> {
221 FerroProperty::register_attached::<DockPanel, Control, _>("Dock", Dock::Left)
222 }
223 );
224
225 ferro_property!(
226 pub fn last_child_fill_property() -> StyledProperty<bool> {
228 FerroProperty::register::<DockPanel, _>("LastChildFill", true)
229 }
230 );
231
232 ferro_property!(
233 pub fn horizontal_spacing_property() -> StyledProperty<f64> {
235 FerroProperty::register::<DockPanel, _>("HorizontalSpacing", 0.0)
236 }
237 );
238
239 ferro_property!(
240 pub fn vertical_spacing_property() -> StyledProperty<f64> {
242 FerroProperty::register::<DockPanel, _>("VerticalSpacing", 0.0)
243 }
244 );
245} }
246
247impl DockPanel {
248 fn static_constructor() {
249 Panel::affects_parent_measure::<DockPanel>(&[Self::dock_property().as_property()]);
250 Layoutable::affects_measure::<DockPanel>(&[
251 Self::last_child_fill_property().as_property(),
252 Self::horizontal_spacing_property().as_property(),
253 Self::vertical_spacing_property().as_property(),
254 ]);
255 }
256
257 pub fn construct() -> Self {
259 Self { base: Panel::construct() }
260 }
261
262 pub fn new() -> Ref<Self> {
263 instantiate(Self::construct())
264 }
265
266 pub fn get_dock(control: &Control) -> Dock {
269 control.get_value(Self::dock_property())
270 }
271
272 pub fn set_dock(control: &Control, value: Dock) {
275 control.set_value(Self::dock_property(), value)
276 }
277
278 pub fn last_child_fill(&self) -> bool {
281 self.get_value(Self::last_child_fill_property())
282 }
283
284 pub fn set_last_child_fill(&self, value: bool) {
285 self.set_value(Self::last_child_fill_property(), value)
286 }
287
288 pub fn horizontal_spacing(&self) -> f64 {
290 self.get_value(Self::horizontal_spacing_property())
291 }
292
293 pub fn set_horizontal_spacing(&self, value: f64) {
294 self.set_value(Self::horizontal_spacing_property(), value)
295 }
296
297 pub fn vertical_spacing(&self) -> f64 {
299 self.get_value(Self::vertical_spacing_property())
300 }
301
302 pub fn set_vertical_spacing(&self, value: f64) {
303 self.set_value(Self::vertical_spacing_property(), value)
304 }
305}