1use crate::*;
2
3#[derive(Clone, Copy, Debug)]
4#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
5pub(crate) struct State {
6 pub(crate) desired_size: Vec2,
11
12 last_content_size: Vec2,
14
15 pub(crate) requested_size: Option<Vec2>,
17}
18
19impl State {
20 pub fn load(ctx: &Context, id: Id) -> Option<Self> {
21 ctx.data_mut(|d| d.get_persisted(id))
22 }
23
24 pub fn store(self, ctx: &Context, id: Id) {
25 ctx.data_mut(|d| d.insert_persisted(id, self));
26 }
27}
28
29#[derive(Clone, Copy, Debug)]
31#[must_use = "You should call .show()"]
32pub struct Resize {
33 id: Option<Id>,
34 id_source: Option<Id>,
35
36 resizable: Vec2b,
38
39 pub(crate) min_size: Vec2,
40 pub(crate) max_size: Vec2,
41
42 default_size: Vec2,
43
44 with_stroke: bool,
45}
46
47impl Default for Resize {
48 fn default() -> Self {
49 Self {
50 id: None,
51 id_source: None,
52 resizable: Vec2b::TRUE,
53 min_size: Vec2::splat(16.0),
54 max_size: Vec2::splat(f32::INFINITY),
55 default_size: vec2(320.0, 128.0), with_stroke: true,
57 }
58 }
59}
60
61impl Resize {
62 #[inline]
64 pub fn id(mut self, id: Id) -> Self {
65 self.id = Some(id);
66 self
67 }
68
69 #[inline]
71 pub fn id_source(mut self, id_source: impl std::hash::Hash) -> Self {
72 self.id_source = Some(Id::new(id_source));
73 self
74 }
75
76 #[inline]
83 pub fn default_width(mut self, width: f32) -> Self {
84 self.default_size.x = width;
85 self
86 }
87
88 #[inline]
96 pub fn default_height(mut self, height: f32) -> Self {
97 self.default_size.y = height;
98 self
99 }
100
101 #[inline]
102 pub fn default_size(mut self, default_size: impl Into<Vec2>) -> Self {
103 self.default_size = default_size.into();
104 self
105 }
106
107 #[inline]
109 pub fn min_size(mut self, min_size: impl Into<Vec2>) -> Self {
110 self.min_size = min_size.into();
111 self
112 }
113
114 #[inline]
116 pub fn min_width(mut self, min_width: f32) -> Self {
117 self.min_size.x = min_width;
118 self
119 }
120
121 #[inline]
123 pub fn min_height(mut self, min_height: f32) -> Self {
124 self.min_size.y = min_height;
125 self
126 }
127
128 #[inline]
130 pub fn max_size(mut self, max_size: impl Into<Vec2>) -> Self {
131 self.max_size = max_size.into();
132 self
133 }
134
135 #[inline]
137 pub fn max_width(mut self, max_width: f32) -> Self {
138 self.max_size.x = max_width;
139 self
140 }
141
142 #[inline]
144 pub fn max_height(mut self, max_height: f32) -> Self {
145 self.max_size.y = max_height;
146 self
147 }
148
149 #[inline]
155 pub fn resizable(mut self, resizable: impl Into<Vec2b>) -> Self {
156 self.resizable = resizable.into();
157 self
158 }
159
160 #[inline]
161 pub fn is_resizable(&self) -> Vec2b {
162 self.resizable
163 }
164
165 pub fn auto_sized(self) -> Self {
168 self.min_size(Vec2::ZERO)
169 .default_size(Vec2::splat(f32::INFINITY))
170 .resizable(false)
171 }
172
173 #[inline]
174 pub fn fixed_size(mut self, size: impl Into<Vec2>) -> Self {
175 let size = size.into();
176 self.default_size = size;
177 self.min_size = size;
178 self.max_size = size;
179 self.resizable = Vec2b::FALSE;
180 self
181 }
182
183 #[inline]
184 pub fn with_stroke(mut self, with_stroke: bool) -> Self {
185 self.with_stroke = with_stroke;
186 self
187 }
188}
189
190struct Prepared {
191 id: Id,
192 corner_id: Option<Id>,
193 state: State,
194 content_ui: Ui,
195}
196
197impl Resize {
198 fn begin(&mut self, ui: &mut Ui) -> Prepared {
199 let position = ui.available_rect_before_wrap().min;
200 let id = self.id.unwrap_or_else(|| {
201 let id_source = self.id_source.unwrap_or_else(|| Id::new("resize"));
202 ui.make_persistent_id(id_source)
203 });
204
205 let mut state = State::load(ui.ctx(), id).unwrap_or_else(|| {
206 ui.ctx().request_repaint(); let default_size = self
209 .default_size
210 .at_least(self.min_size)
211 .at_most(self.max_size)
212 .at_most(
213 ui.ctx().screen_rect().size() - ui.spacing().window_margin.sum(), );
215
216 State {
217 desired_size: default_size,
218 last_content_size: vec2(0.0, 0.0),
219 requested_size: None,
220 }
221 });
222
223 state.desired_size = state
224 .desired_size
225 .at_least(self.min_size)
226 .at_most(self.max_size);
227
228 let mut user_requested_size = state.requested_size.take();
229
230 let corner_id = self.resizable.any().then(|| id.with("__resize_corner"));
231
232 if let Some(corner_id) = corner_id {
233 if let Some(corner_response) = ui.ctx().read_response(corner_id) {
234 if let Some(pointer_pos) = corner_response.interact_pointer_pos() {
235 user_requested_size =
237 Some(pointer_pos - position + 0.5 * corner_response.rect.size());
238 }
239 }
240 }
241
242 if let Some(user_requested_size) = user_requested_size {
243 state.desired_size = user_requested_size;
244 } else {
245 state.desired_size = state.desired_size.max(state.last_content_size);
249 }
250
251 state.desired_size = state
252 .desired_size
253 .at_least(self.min_size)
254 .at_most(self.max_size);
255
256 let inner_rect = Rect::from_min_size(position, state.desired_size);
259
260 let mut content_clip_rect = inner_rect.expand(ui.visuals().clip_rect_margin);
261
262 content_clip_rect.max = content_clip_rect.max.max(
268 inner_rect.min + state.last_content_size + Vec2::splat(ui.visuals().clip_rect_margin),
269 );
270
271 content_clip_rect = content_clip_rect.intersect(ui.clip_rect()); let mut content_ui = ui.child_ui(
274 inner_rect,
275 *ui.layout(),
276 Some(UiStackInfo::new(UiKind::Resize)),
277 );
278 content_ui.set_clip_rect(content_clip_rect);
279
280 Prepared {
281 id,
282 corner_id,
283 state,
284 content_ui,
285 }
286 }
287
288 pub fn show<R>(mut self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R {
289 let mut prepared = self.begin(ui);
290 let ret = add_contents(&mut prepared.content_ui);
291 self.end(ui, prepared);
292 ret
293 }
294
295 fn end(self, ui: &mut Ui, prepared: Prepared) {
296 let Prepared {
297 id,
298 corner_id,
299 mut state,
300 content_ui,
301 } = prepared;
302
303 state.last_content_size = content_ui.min_size();
304
305 let mut size = state.last_content_size;
308 for d in 0..2 {
309 if self.with_stroke || self.resizable[d] {
310 state.desired_size[d] = state.desired_size[d].max(state.last_content_size[d]);
314
315 size[d] = state.desired_size[d];
317 } else {
318 size[d] = state.last_content_size[d];
320 }
321 }
322 ui.advance_cursor_after_rect(Rect::from_min_size(content_ui.min_rect().min, size));
323
324 let corner_response = if let Some(corner_id) = corner_id {
327 let corner_size = Vec2::splat(ui.visuals().resize_corner_size);
329 let corner_rect = Rect::from_min_size(
330 content_ui.min_rect().left_top() + size - corner_size,
331 corner_size,
332 );
333 Some(ui.interact(corner_rect, corner_id, Sense::drag()))
334 } else {
335 None
336 };
337
338 if self.with_stroke && corner_response.is_some() {
341 let rect = Rect::from_min_size(content_ui.min_rect().left_top(), state.desired_size);
342 let rect = rect.expand(2.0); ui.painter().add(Shape::rect_stroke(
344 rect,
345 3.0,
346 ui.visuals().widgets.noninteractive.bg_stroke,
347 ));
348 }
349
350 if let Some(corner_response) = corner_response {
351 paint_resize_corner(ui, &corner_response);
352
353 if corner_response.hovered() || corner_response.dragged() {
354 ui.ctx().set_cursor_icon(CursorIcon::ResizeNwSe);
355 }
356 }
357
358 state.store(ui.ctx(), id);
359
360 #[cfg(debug_assertions)]
361 if ui.ctx().style().debug.show_resize {
362 ui.ctx().debug_painter().debug_rect(
363 Rect::from_min_size(content_ui.min_rect().left_top(), state.desired_size),
364 Color32::GREEN,
365 "desired_size",
366 );
367 ui.ctx().debug_painter().debug_rect(
368 Rect::from_min_size(content_ui.min_rect().left_top(), state.last_content_size),
369 Color32::LIGHT_BLUE,
370 "last_content_size",
371 );
372 }
373 }
374}
375
376use epaint::Stroke;
377
378pub fn paint_resize_corner(ui: &Ui, response: &Response) {
379 let stroke = ui.style().interact(response).fg_stroke;
380 paint_resize_corner_with_style(ui, &response.rect, stroke.color, Align2::RIGHT_BOTTOM);
381}
382
383pub fn paint_resize_corner_with_style(
384 ui: &Ui,
385 rect: &Rect,
386 color: impl Into<Color32>,
387 corner: Align2,
388) {
389 let painter = ui.painter();
390 let cp = painter.round_pos_to_pixels(corner.pos_in_rect(rect));
391 let mut w = 2.0;
392 let stroke = Stroke {
393 width: 1.0, color: color.into(),
395 };
396
397 while w <= rect.width() && w <= rect.height() {
398 painter.line_segment(
399 [
400 pos2(cp.x - w * corner.x().to_sign(), cp.y),
401 pos2(cp.x, cp.y - w * corner.y().to_sign()),
402 ],
403 stroke,
404 );
405 w += 4.0;
406 }
407}