1use crate::core::{LayoutPositionState, Transition};
3
4use gpui_kit::{
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
101fn content_mask(bounds: Bounds<Pixels>) -> ContentMask<Pixels> {
102 ContentMask { bounds }
103}
104
105impl<E: IntoElement + 'static> IntoElement for AutoSize<E> {
106 type Element = Self;
107
108 fn into_element(self) -> Self {
109 self
110 }
111}
112
113impl<E: IntoElement + 'static> Element for AutoSize<E> {
114 type RequestLayoutState = AnyElement;
115 type PrepaintState = Bounds<Pixels>;
116
117 fn id(&self) -> Option<ElementId> {
118 Some(self.id.clone())
119 }
120
121 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
122 None
123 }
124
125 fn request_layout(
126 &mut self,
127 id: Option<&GlobalElementId>,
128 _: Option<&InspectorElementId>,
129 window: &mut Window,
130 cx: &mut App,
131 ) -> (LayoutId, AnyElement) {
132 let mut child = self.child.take().expect("one layout").into_any_element();
133
134 let available = |value: Option<f32>| {
135 value.map_or(AvailableSpace::MaxContent, |v| {
136 AvailableSpace::Definite(px(v))
137 })
138 };
139
140 let measured = child.layout_as_root(
141 size(available(self.width), available(self.height)),
142 window,
143 cx,
144 );
145
146 let target = [f64::from(measured.width), f64::from(measured.height)];
147 let now = cx.background_executor().now();
148
149 let transitions = match self.axis {
150 SizeAxis::Width => [self.transition, Transition::Instant],
151 SizeAxis::Height => [Transition::Instant, self.transition],
152 SizeAxis::Both => [self.transition; 2],
153 };
154
155 let frame = window.with_element_state(
156 id.expect("size ID"),
157 |state: Option<SizeHistory>, window| {
158 let mut state = state.unwrap_or_else(|| {
159 let mut state = LayoutPositionState::default();
160
161 if let Some(initial) = self.initial {
162 state.sample(
163 initial.map(f64::from),
164 self.transition,
165 std::time::Duration::ZERO,
166 cx.reduce_motion(),
167 );
168 }
169
170 SizeHistory { state, epoch: now }
171 });
172
173 let frame = state.state.sample_axes(
174 target,
175 transitions,
176 now.saturating_duration_since(state.epoch),
177 cx.reduce_motion(),
178 );
179
180 if frame.is_animating() {
181 window.request_animation_frame();
182 }
183
184 (frame, state)
185 },
186 );
187
188 let [mut width, mut height] = frame
189 .position()
190 .map(|v| px(v.clamp(0., f64::from(f32::MAX)) as f32));
191
192 if matches!(self.axis, SizeAxis::Height) {
193 width = measured.width;
194 }
195
196 if matches!(self.axis, SizeAxis::Width) {
197 height = measured.height;
198 }
199
200 let style = Style {
201 size: size(width.into(), height.into()),
202 flex_shrink: 0.,
203 ..Default::default()
204 };
205
206 (window.request_layout(style, [], cx), child)
207 }
208
209 fn prepaint(
210 &mut self,
211 _: Option<&GlobalElementId>,
212 _: Option<&InspectorElementId>,
213 bounds: Bounds<Pixels>,
214 child: &mut AnyElement,
215 window: &mut Window,
216 cx: &mut App,
217 ) -> Bounds<Pixels> {
218 window.with_content_mask(Some(content_mask(bounds)), |w| {
219 child.prepaint_at(bounds.origin, w, cx);
220 });
221
222 bounds
223 }
224
225 fn paint(
226 &mut self,
227 _: Option<&GlobalElementId>,
228 _: Option<&InspectorElementId>,
229 _: Bounds<Pixels>,
230 child: &mut AnyElement,
231 bounds: &mut Bounds<Pixels>,
232 window: &mut Window,
233 cx: &mut App,
234 ) {
235 window.with_content_mask(Some(content_mask(*bounds)), |w| child.paint(w, cx));
236 }
237}