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