1use crate::{LayoutPositionState, Transition};
3
4use gpui::{
5 AnyElement, App, AvailableSpace, Bounds, ContentMask, Element, ElementId, GlobalElementId,
6 InspectorElementId, IntoElement, LayoutId, Pixels, Style, Window, px, size,
7};
8
9use std::time::Instant;
10
11#[derive(Clone, Copy, Debug, Default)]
13pub enum SizeAxis {
14 Width,
16 #[default]
18 Height,
19 Both,
21}
22
23pub struct AutoSize<E> {
28 id: ElementId,
29 child: Option<E>,
30 width: Option<f32>,
31 height: Option<f32>,
32 axis: SizeAxis,
33 transition: Transition,
34 initial: Option<[f32; 2]>,
35}
36
37impl<E> AutoSize<E> {
38 pub fn new(id: impl Into<ElementId>, child: E) -> Self {
46 Self {
47 id: id.into(),
48 child: Some(child),
49 width: None,
50 height: None,
51 axis: SizeAxis::Height,
52 transition: Transition::default(),
53 initial: None,
54 }
55 }
56
57 pub fn axis(mut self, axis: SizeAxis) -> Self {
59 self.axis = axis;
60
61 self
62 }
63
64 pub fn width(mut self, width: f32) -> Self {
66 assert!(width.is_finite() && width >= 0.);
67 self.width = Some(width);
68
69 self
70 }
71
72 pub fn height(mut self, height: f32) -> Self {
74 assert!(height.is_finite() && height >= 0.);
75 self.height = Some(height);
76
77 self
78 }
79
80 pub fn transition(mut self, transition: Transition) -> Self {
82 self.transition = transition;
83
84 self
85 }
86
87 pub fn initial(mut self, value: [f32; 2]) -> Self {
89 assert!(value.iter().all(|v| v.is_finite() && *v >= 0.));
90 self.initial = Some(value);
91
92 self
93 }
94}
95
96struct SizeHistory {
97 state: LayoutPositionState,
98 epoch: Instant,
99}
100
101#[allow(clippy::needless_update)]
103fn content_mask(bounds: Bounds<Pixels>) -> ContentMask<Pixels> {
104 ContentMask {
105 bounds,
106 ..Default::default()
107 }
108}
109
110impl<E: IntoElement + 'static> IntoElement for AutoSize<E> {
111 type Element = Self;
112
113 fn into_element(self) -> Self {
114 self
115 }
116}
117
118impl<E: IntoElement + 'static> Element for AutoSize<E> {
119 type RequestLayoutState = AnyElement;
120 type PrepaintState = Bounds<Pixels>;
121
122 fn id(&self) -> Option<ElementId> {
123 Some(self.id.clone())
124 }
125
126 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
127 None
128 }
129
130 fn request_layout(
131 &mut self,
132 id: Option<&GlobalElementId>,
133 _: Option<&InspectorElementId>,
134 window: &mut Window,
135 cx: &mut App,
136 ) -> (LayoutId, AnyElement) {
137 let mut child = self.child.take().expect("one layout").into_any_element();
138
139 let available = |value: Option<f32>| {
140 value.map_or(AvailableSpace::MaxContent, |v| {
141 AvailableSpace::Definite(px(v))
142 })
143 };
144
145 let measured = child.layout_as_root(
146 size(available(self.width), available(self.height)),
147 window,
148 cx,
149 );
150
151 let target = [f64::from(measured.width), f64::from(measured.height)];
152 let now = cx.background_executor().now();
153
154 let transitions = match self.axis {
155 SizeAxis::Width => [self.transition, Transition::Instant],
156 SizeAxis::Height => [Transition::Instant, self.transition],
157 SizeAxis::Both => [self.transition; 2],
158 };
159
160 let frame = window.with_element_state(
161 id.expect("size ID"),
162 |state: Option<SizeHistory>, window| {
163 let mut state = state.unwrap_or_else(|| {
164 let mut state = LayoutPositionState::default();
165
166 if let Some(initial) = self.initial {
167 state.sample(
168 initial.map(f64::from),
169 self.transition,
170 std::time::Duration::ZERO,
171 cx.reduce_motion(),
172 );
173 }
174
175 SizeHistory { state, epoch: now }
176 });
177
178 let frame = state.state.sample_axes(
179 target,
180 transitions,
181 now.saturating_duration_since(state.epoch),
182 cx.reduce_motion(),
183 );
184
185 if frame.is_animating() {
186 window.request_animation_frame();
187 }
188
189 (frame, state)
190 },
191 );
192
193 let [mut width, mut height] = frame
194 .position()
195 .map(|v| px(v.clamp(0., f64::from(f32::MAX)) as f32));
196
197 if matches!(self.axis, SizeAxis::Height) {
198 width = measured.width;
199 }
200
201 if matches!(self.axis, SizeAxis::Width) {
202 height = measured.height;
203 }
204
205 let style = Style {
206 size: size(width.into(), height.into()),
207 flex_shrink: 0.,
208 ..Default::default()
209 };
210
211 (window.request_layout(style, [], cx), child)
212 }
213
214 fn prepaint(
215 &mut self,
216 _: Option<&GlobalElementId>,
217 _: Option<&InspectorElementId>,
218 bounds: Bounds<Pixels>,
219 child: &mut AnyElement,
220 window: &mut Window,
221 cx: &mut App,
222 ) -> Bounds<Pixels> {
223 window.with_content_mask(Some(content_mask(bounds)), |w| {
224 child.prepaint_at(bounds.origin, w, cx);
225 });
226
227 bounds
228 }
229
230 fn paint(
231 &mut self,
232 _: Option<&GlobalElementId>,
233 _: Option<&InspectorElementId>,
234 _: Bounds<Pixels>,
235 child: &mut AnyElement,
236 bounds: &mut Bounds<Pixels>,
237 window: &mut Window,
238 cx: &mut App,
239 ) {
240 window.with_content_mask(Some(content_mask(*bounds)), |w| child.paint(w, cx));
241 }
242}