1use std::ops::Range;
2
3use json::ToJson;
4use serde_json::json;
5
6use crate::{
7 geometry::{rect::RectF, vector::Vector2F},
8 json, AnyElement, Element, LayoutContext, SceneBuilder, SizeConstraint, View, ViewContext,
9};
10
11pub struct ConstrainedBox<V: View> {
12 child: AnyElement<V>,
13 constraint: Constraint<V>,
14}
15
16pub enum Constraint<V: View> {
17 Static(SizeConstraint),
18 Dynamic(Box<dyn FnMut(SizeConstraint, &mut V, &mut LayoutContext<V>) -> SizeConstraint>),
19}
20
21impl<V: View> ToJson for Constraint<V> {
22 fn to_json(&self) -> serde_json::Value {
23 match self {
24 Constraint::Static(constraint) => constraint.to_json(),
25 Constraint::Dynamic(_) => "dynamic".into(),
26 }
27 }
28}
29
30impl<V: View> ConstrainedBox<V> {
31 pub fn new(child: impl Element<V>) -> Self {
32 Self {
33 child: child.into_any(),
34 constraint: Constraint::Static(Default::default()),
35 }
36 }
37
38 pub fn dynamically(
39 mut self,
40 constraint: impl 'static
41 + FnMut(SizeConstraint, &mut V, &mut LayoutContext<V>) -> SizeConstraint,
42 ) -> Self {
43 self.constraint = Constraint::Dynamic(Box::new(constraint));
44 self
45 }
46
47 pub fn with_min_width(mut self, min_width: f32) -> Self {
48 if let Constraint::Dynamic(_) = self.constraint {
49 self.constraint = Constraint::Static(Default::default());
50 }
51
52 if let Constraint::Static(constraint) = &mut self.constraint {
53 constraint.min.set_x(min_width);
54 } else {
55 unreachable!()
56 }
57
58 self
59 }
60
61 pub fn with_max_width(mut self, max_width: f32) -> Self {
62 if let Constraint::Dynamic(_) = self.constraint {
63 self.constraint = Constraint::Static(Default::default());
64 }
65
66 if let Constraint::Static(constraint) = &mut self.constraint {
67 constraint.max.set_x(max_width);
68 } else {
69 unreachable!()
70 }
71
72 self
73 }
74
75 pub fn with_max_height(mut self, max_height: f32) -> Self {
76 if let Constraint::Dynamic(_) = self.constraint {
77 self.constraint = Constraint::Static(Default::default());
78 }
79
80 if let Constraint::Static(constraint) = &mut self.constraint {
81 constraint.max.set_y(max_height);
82 } else {
83 unreachable!()
84 }
85
86 self
87 }
88
89 pub fn with_width(mut self, width: f32) -> Self {
90 if let Constraint::Dynamic(_) = self.constraint {
91 self.constraint = Constraint::Static(Default::default());
92 }
93
94 if let Constraint::Static(constraint) = &mut self.constraint {
95 constraint.min.set_x(width);
96 constraint.max.set_x(width);
97 } else {
98 unreachable!()
99 }
100
101 self
102 }
103
104 pub fn with_height(mut self, height: f32) -> Self {
105 if let Constraint::Dynamic(_) = self.constraint {
106 self.constraint = Constraint::Static(Default::default());
107 }
108
109 if let Constraint::Static(constraint) = &mut self.constraint {
110 constraint.min.set_y(height);
111 constraint.max.set_y(height);
112 } else {
113 unreachable!()
114 }
115
116 self
117 }
118
119 fn constraint(
120 &mut self,
121 input_constraint: SizeConstraint,
122 view: &mut V,
123 cx: &mut LayoutContext<V>,
124 ) -> SizeConstraint {
125 match &mut self.constraint {
126 Constraint::Static(constraint) => *constraint,
127 Constraint::Dynamic(compute_constraint) => {
128 compute_constraint(input_constraint, view, cx)
129 }
130 }
131 }
132}
133
134impl<V: View> Element<V> for ConstrainedBox<V> {
135 type LayoutState = ();
136 type PaintState = ();
137
138 fn layout(
139 &mut self,
140 mut parent_constraint: SizeConstraint,
141 view: &mut V,
142 cx: &mut LayoutContext<V>,
143 ) -> (Vector2F, Self::LayoutState) {
144 let constraint = self.constraint(parent_constraint, view, cx);
145 parent_constraint.min = parent_constraint.min.max(constraint.min);
146 parent_constraint.max = parent_constraint.max.min(constraint.max);
147 parent_constraint.max = parent_constraint.max.max(parent_constraint.min);
148 let size = self.child.layout(parent_constraint, view, cx);
149 (size, ())
150 }
151
152 fn paint(
153 &mut self,
154 scene: &mut SceneBuilder,
155 bounds: RectF,
156 visible_bounds: RectF,
157 _: &mut Self::LayoutState,
158 view: &mut V,
159 cx: &mut ViewContext<V>,
160 ) -> Self::PaintState {
161 scene.paint_layer(Some(visible_bounds), |scene| {
162 self.child
163 .paint(scene, bounds.origin(), visible_bounds, view, cx);
164 })
165 }
166
167 fn rect_for_text_range(
168 &self,
169 range_utf16: Range<usize>,
170 _: RectF,
171 _: RectF,
172 _: &Self::LayoutState,
173 _: &Self::PaintState,
174 view: &V,
175 cx: &ViewContext<V>,
176 ) -> Option<RectF> {
177 self.child.rect_for_text_range(range_utf16, view, cx)
178 }
179
180 fn debug(
181 &self,
182 _: RectF,
183 _: &Self::LayoutState,
184 _: &Self::PaintState,
185 view: &V,
186 cx: &ViewContext<V>,
187 ) -> json::Value {
188 json!({"type": "ConstrainedBox", "assigned_constraint": self.constraint.to_json(), "child": self.child.debug(view, cx)})
189 }
190}