1use crate::{
2 pos2, vec2, Align2, Color32, Context, CursorIcon, Id, NumExt, Rect, Response, Sense, Shape, Ui,
3 UiBuilder, UiKind, UiStackInfo, Vec2, Vec2b,
4};
5
6#[derive(Clone, Copy, Debug)]
7#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
8pub(crate) struct State {
9 pub(crate) desired_size: Vec2,
14
15 last_content_size: Vec2,
17
18 pub(crate) requested_size: Option<Vec2>,
20}
21
22impl State {
23 pub fn load(ctx: &Context, id: Id) -> Option<Self> {
24 ctx.data_mut(|d| d.get_persisted(id))
25 }
26
27 pub fn store(self, ctx: &Context, id: Id) {
28 ctx.data_mut(|d| d.insert_persisted(id, self));
29 }
30}
31
32#[derive(Clone, Copy, Debug)]
34#[must_use = "You should call .show()"]
35pub struct Resize {
36 id: Option<Id>,
37 id_salt: Option<Id>,
38
39 resizable: Vec2b,
41
42 pub(crate) min_size: Vec2,
43 pub(crate) max_size: Vec2,
44
45 default_size: Vec2,
46
47 with_stroke: bool,
48}
49
50impl Default for Resize {
51 fn default() -> Self {
52 Self {
53 id: None,
54 id_salt: None,
55 resizable: Vec2b::TRUE,
56 min_size: Vec2::splat(16.0),
57 max_size: Vec2::splat(f32::INFINITY),
58 default_size: vec2(320.0, 128.0), with_stroke: true,
60 }
61 }
62}
63
64impl Resize {
65 #[inline]
67 pub fn id(mut self, id: Id) -> Self {
68 self.id = Some(id);
69 self
70 }
71
72 #[inline]
74 #[deprecated = "Renamed id_salt"]
75 pub fn id_source(self, id_salt: impl std::hash::Hash) -> Self {
76 self.id_salt(id_salt)
77 }
78
79 #[inline]
81 pub fn id_salt(mut self, id_salt: impl std::hash::Hash) -> Self {
82 self.id_salt = Some(Id::new(id_salt));
83 self
84 }
85
86 #[inline]
93 pub fn default_width(mut self, width: f32) -> Self {
94 self.default_size.x = width;
95 self
96 }
97
98 #[inline]
106 pub fn default_height(mut self, height: f32) -> Self {
107 self.default_size.y = height;
108 self
109 }
110
111 #[inline]
112 pub fn default_size(mut self, default_size: impl Into<Vec2>) -> Self {
113 self.default_size = default_size.into();
114 self
115 }
116
117 #[inline]
119 pub fn min_size(mut self, min_size: impl Into<Vec2>) -> Self {
120 self.min_size = min_size.into();
121 self
122 }
123
124 #[inline]
126 pub fn min_width(mut self, min_width: f32) -> Self {
127 self.min_size.x = min_width;
128 self
129 }
130
131 #[inline]
133 pub fn min_height(mut self, min_height: f32) -> Self {
134 self.min_size.y = min_height;
135 self
136 }
137
138 #[inline]
140 pub fn max_size(mut self, max_size: impl Into<Vec2>) -> Self {
141 self.max_size = max_size.into();
142 self
143 }
144
145 #[inline]
147 pub fn max_width(mut self, max_width: f32) -> Self {
148 self.max_size.x = max_width;
149 self
150 }
151
152 #[inline]
154 pub fn max_height(mut self, max_height: f32) -> Self {
155 self.max_size.y = max_height;
156 self
157 }
158
159 #[inline]
165 pub fn resizable(mut self, resizable: impl Into<Vec2b>) -> Self {
166 self.resizable = resizable.into();
167 self
168 }
169
170 #[inline]
171 pub fn is_resizable(&self) -> Vec2b {
172 self.resizable
173 }
174
175 pub fn auto_sized(self) -> Self {
178 self.min_size(Vec2::ZERO)
179 .default_size(Vec2::splat(f32::INFINITY))
180 .resizable(false)
181 }
182
183 #[inline]
184 pub fn fixed_size(mut self, size: impl Into<Vec2>) -> Self {
185 let size = size.into();
186 self.default_size = size;
187 self.min_size = size;
188 self.max_size = size;
189 self.resizable = Vec2b::FALSE;
190 self
191 }
192
193 #[inline]
194 pub fn with_stroke(mut self, with_stroke: bool) -> Self {
195 self.with_stroke = with_stroke;
196 self
197 }
198}
199
200struct Prepared {
201 id: Id,
202 corner_id: Option<Id>,
203 state: State,
204 content_ui: Ui,
205}
206
207impl Resize {
208 fn begin(&self, ui: &mut Ui) -> Prepared {
209 let position = ui.available_rect_before_wrap().min;
210 let id = self.id.unwrap_or_else(|| {
211 let id_salt = self.id_salt.unwrap_or_else(|| Id::new("resize"));
212 ui.make_persistent_id(id_salt)
213 });
214
215 let mut state = State::load(ui.ctx(), id).unwrap_or_else(|| {
216 ui.ctx().request_repaint(); let default_size = self
219 .default_size
220 .at_least(self.min_size)
221 .at_most(self.max_size)
222 .at_most(
223 ui.ctx().screen_rect().size() - ui.spacing().window_margin.sum(), )
225 .round_ui();
226
227 State {
228 desired_size: default_size,
229 last_content_size: vec2(0.0, 0.0),
230 requested_size: None,
231 }
232 });
233
234 state.desired_size = state
235 .desired_size
236 .at_least(self.min_size)
237 .at_most(self.max_size)
238 .round_ui();
239
240 let mut user_requested_size = state.requested_size.take();
241
242 let corner_id = self.resizable.any().then(|| id.with("__resize_corner"));
243
244 if let Some(corner_id) = corner_id {
245 if let Some(corner_response) = ui.ctx().read_response(corner_id) {
246 if let Some(pointer_pos) = corner_response.interact_pointer_pos() {
247 user_requested_size =
249 Some(pointer_pos - position + 0.5 * corner_response.rect.size());
250 }
251 }
252 }
253
254 if let Some(user_requested_size) = user_requested_size {
255 state.desired_size = user_requested_size;
256 } else {
257 state.desired_size = state.desired_size.max(state.last_content_size);
261 }
262
263 state.desired_size = state
264 .desired_size
265 .at_least(self.min_size)
266 .at_most(self.max_size);
267
268 let inner_rect = Rect::from_min_size(position, state.desired_size);
271
272 let mut content_clip_rect = inner_rect.expand(ui.visuals().clip_rect_margin);
273
274 content_clip_rect.max = content_clip_rect.max.max(
280 inner_rect.min + state.last_content_size + Vec2::splat(ui.visuals().clip_rect_margin),
281 );
282
283 content_clip_rect = content_clip_rect.intersect(ui.clip_rect()); let mut content_ui = ui.new_child(
286 UiBuilder::new()
287 .ui_stack_info(UiStackInfo::new(UiKind::Resize))
288 .max_rect(inner_rect),
289 );
290 content_ui.set_clip_rect(content_clip_rect);
291
292 Prepared {
293 id,
294 corner_id,
295 state,
296 content_ui,
297 }
298 }
299
300 pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> R {
301 let mut prepared = self.begin(ui);
302 let ret = add_contents(&mut prepared.content_ui);
303 self.end(ui, prepared);
304 ret
305 }
306
307 fn end(self, ui: &mut Ui, prepared: Prepared) {
308 let Prepared {
309 id,
310 corner_id,
311 mut state,
312 content_ui,
313 } = prepared;
314
315 state.last_content_size = content_ui.min_size();
316
317 let mut size = state.last_content_size;
320 for d in 0..2 {
321 if self.with_stroke || self.resizable[d] {
322 state.desired_size[d] = state.desired_size[d].max(state.last_content_size[d]);
326
327 size[d] = state.desired_size[d];
329 } else {
330 size[d] = state.last_content_size[d];
332 }
333 }
334 ui.advance_cursor_after_rect(Rect::from_min_size(content_ui.min_rect().min, size));
335
336 let corner_response = if let Some(corner_id) = corner_id {
339 let corner_size = Vec2::splat(ui.visuals().resize_corner_size);
341 let corner_rect = Rect::from_min_size(
342 content_ui.min_rect().left_top() + size - corner_size,
343 corner_size,
344 );
345 Some(ui.interact(corner_rect, corner_id, Sense::drag()))
346 } else {
347 None
348 };
349
350 if self.with_stroke && corner_response.is_some() {
353 let rect = Rect::from_min_size(content_ui.min_rect().left_top(), state.desired_size);
354 let rect = rect.expand(2.0); ui.painter().add(Shape::rect_stroke(
356 rect,
357 3.0,
358 ui.visuals().widgets.noninteractive.bg_stroke,
359 epaint::StrokeKind::Inside,
360 ));
361 }
362
363 if let Some(corner_response) = corner_response {
364 paint_resize_corner(ui, &corner_response);
365
366 if corner_response.hovered() || corner_response.dragged() {
367 ui.ctx().set_cursor_icon(CursorIcon::ResizeNwSe);
368 }
369 }
370
371 state.store(ui.ctx(), id);
372
373 #[cfg(debug_assertions)]
374 if ui.ctx().style().debug.show_resize {
375 ui.ctx().debug_painter().debug_rect(
376 Rect::from_min_size(content_ui.min_rect().left_top(), state.desired_size),
377 Color32::GREEN,
378 "desired_size",
379 );
380 ui.ctx().debug_painter().debug_rect(
381 Rect::from_min_size(content_ui.min_rect().left_top(), state.last_content_size),
382 Color32::LIGHT_BLUE,
383 "last_content_size",
384 );
385 }
386 }
387}
388
389use emath::GuiRounding as _;
390use epaint::Stroke;
391
392pub fn paint_resize_corner(ui: &Ui, response: &Response) {
393 let stroke = ui.style().interact(response).fg_stroke;
394 paint_resize_corner_with_style(ui, &response.rect, stroke.color, Align2::RIGHT_BOTTOM);
395}
396
397pub fn paint_resize_corner_with_style(
398 ui: &Ui,
399 rect: &Rect,
400 color: impl Into<Color32>,
401 corner: Align2,
402) {
403 let painter = ui.painter();
404 let cp = corner
405 .pos_in_rect(rect)
406 .round_to_pixels(ui.pixels_per_point());
407 let mut w = 2.0;
408 let stroke = Stroke {
409 width: 1.0, color: color.into(),
411 };
412
413 while w <= rect.width() && w <= rect.height() {
414 painter.line_segment(
415 [
416 pos2(cp.x - w * corner.x().to_sign(), cp.y),
417 pos2(cp.x, cp.y - w * corner.y().to_sign()),
418 ],
419 stroke,
420 );
421 w += 4.0;
422 }
423}