1use std::ops::Range;
2
3use json::ToJson;
4use serde_json::json;
5
6use crate::{
7 geometry::{rect::RectF, vector::Vector2F},
8 json,
9 presenter::MeasurementContext,
10 DebugContext, Element, ElementBox, LayoutContext, PaintContext, SizeConstraint,
11};
12
13pub struct ConstrainedBox {
14 child: ElementBox,
15 constraint: Constraint,
16}
17
18pub enum Constraint {
19 Static(SizeConstraint),
20 Dynamic(Box<dyn FnMut(SizeConstraint, &mut LayoutContext) -> SizeConstraint>),
21}
22
23impl ToJson for Constraint {
24 fn to_json(&self) -> serde_json::Value {
25 match self {
26 Constraint::Static(constraint) => constraint.to_json(),
27 Constraint::Dynamic(_) => "dynamic".into(),
28 }
29 }
30}
31
32impl ConstrainedBox {
33 pub fn new(child: ElementBox) -> Self {
34 Self {
35 child,
36 constraint: Constraint::Static(Default::default()),
37 }
38 }
39
40 pub fn dynamically(
41 mut self,
42 constraint: impl 'static + FnMut(SizeConstraint, &mut LayoutContext) -> 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 cx: &mut LayoutContext,
124 ) -> SizeConstraint {
125 match &mut self.constraint {
126 Constraint::Static(constraint) => *constraint,
127 Constraint::Dynamic(compute_constraint) => compute_constraint(input_constraint, cx),
128 }
129 }
130}
131
132impl Element for ConstrainedBox {
133 type LayoutState = ();
134 type PaintState = ();
135
136 fn layout(
137 &mut self,
138 mut parent_constraint: SizeConstraint,
139 cx: &mut LayoutContext,
140 ) -> (Vector2F, Self::LayoutState) {
141 let constraint = self.constraint(parent_constraint, cx);
142 parent_constraint.min = parent_constraint.min.max(constraint.min);
143 parent_constraint.max = parent_constraint.max.min(constraint.max);
144 parent_constraint.max = parent_constraint.max.max(parent_constraint.min);
145 let size = self.child.layout(parent_constraint, cx);
146 (size, ())
147 }
148
149 fn paint(
150 &mut self,
151 bounds: RectF,
152 visible_bounds: RectF,
153 _: &mut Self::LayoutState,
154 cx: &mut PaintContext,
155 ) -> Self::PaintState {
156 cx.paint_layer(Some(visible_bounds), |cx| {
157 self.child.paint(bounds.origin(), visible_bounds, cx);
158 })
159 }
160
161 fn rect_for_text_range(
162 &self,
163 range_utf16: Range<usize>,
164 _: RectF,
165 _: RectF,
166 _: &Self::LayoutState,
167 _: &Self::PaintState,
168 cx: &MeasurementContext,
169 ) -> Option<RectF> {
170 self.child.rect_for_text_range(range_utf16, cx)
171 }
172
173 fn debug(
174 &self,
175 _: RectF,
176 _: &Self::LayoutState,
177 _: &Self::PaintState,
178 cx: &DebugContext,
179 ) -> json::Value {
180 json!({"type": "ConstrainedBox", "assigned_constraint": self.constraint.to_json(), "child": self.child.debug(cx)})
181 }
182}