1use crate::*;
6
7#[derive(Clone, Copy, Debug)]
12#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
13pub struct AreaState {
14 pub pivot_pos: Option<Pos2>,
16
17 pub pivot: Align2,
19
20 #[cfg_attr(feature = "serde", serde(skip))]
27 pub size: Option<Vec2>,
28
29 pub interactable: bool,
31
32 #[cfg_attr(feature = "serde", serde(skip))]
36 pub last_became_visible_at: Option<f64>,
37}
38
39impl Default for AreaState {
40 fn default() -> Self {
41 Self {
42 pivot_pos: None,
43 pivot: Align2::LEFT_TOP,
44 size: None,
45 interactable: true,
46 last_became_visible_at: None,
47 }
48 }
49}
50
51impl AreaState {
52 pub fn load(ctx: &Context, id: Id) -> Option<Self> {
54 ctx.memory(|mem| mem.areas().get(id).copied())
56 }
57
58 pub fn left_top_pos(&self) -> Pos2 {
60 let pivot_pos = self.pivot_pos.unwrap_or_default();
61 let size = self.size.unwrap_or_default();
62 pos2(
63 pivot_pos.x - self.pivot.x().to_factor() * size.x,
64 pivot_pos.y - self.pivot.y().to_factor() * size.y,
65 )
66 }
67
68 pub fn set_left_top_pos(&mut self, pos: Pos2) {
70 let size = self.size.unwrap_or_default();
71 self.pivot_pos = Some(pos2(
72 pos.x + self.pivot.x().to_factor() * size.x,
73 pos.y + self.pivot.y().to_factor() * size.y,
74 ));
75 }
76
77 pub fn rect(&self) -> Rect {
79 let size = self.size.unwrap_or_default();
80 Rect::from_min_size(self.left_top_pos(), size)
81 }
82}
83
84#[must_use = "You should call .show()"]
100#[derive(Clone, Copy, Debug)]
101pub struct Area {
102 pub(crate) id: Id,
103 kind: UiKind,
104 sense: Option<Sense>,
105 movable: bool,
106 interactable: bool,
107 enabled: bool,
108 constrain: bool,
109 constrain_rect: Option<Rect>,
110 order: Order,
111 default_pos: Option<Pos2>,
112 default_size: Vec2,
113 pivot: Align2,
114 anchor: Option<(Align2, Vec2)>,
115 new_pos: Option<Pos2>,
116 fade_in: bool,
117}
118
119impl WidgetWithState for Area {
120 type State = AreaState;
121}
122
123impl Area {
124 pub fn new(id: Id) -> Self {
126 Self {
127 id,
128 kind: UiKind::GenericArea,
129 sense: None,
130 movable: true,
131 interactable: true,
132 constrain: true,
133 constrain_rect: None,
134 enabled: true,
135 order: Order::Middle,
136 default_pos: None,
137 default_size: Vec2::NAN,
138 new_pos: None,
139 pivot: Align2::LEFT_TOP,
140 anchor: None,
141 fade_in: true,
142 }
143 }
144
145 #[inline]
149 pub fn id(mut self, id: Id) -> Self {
150 self.id = id;
151 self
152 }
153
154 #[inline]
158 pub fn kind(mut self, kind: UiKind) -> Self {
159 self.kind = kind;
160 self
161 }
162
163 pub fn layer(&self) -> LayerId {
164 LayerId::new(self.order, self.id)
165 }
166
167 #[inline]
172 pub fn enabled(mut self, enabled: bool) -> Self {
173 self.enabled = enabled;
174 self
175 }
176
177 #[inline]
179 pub fn movable(mut self, movable: bool) -> Self {
180 self.movable = movable;
181 self.interactable |= movable;
182 self
183 }
184
185 pub fn is_enabled(&self) -> bool {
186 self.enabled
187 }
188
189 pub fn is_movable(&self) -> bool {
190 self.movable && self.enabled
191 }
192
193 #[inline]
199 pub fn interactable(mut self, interactable: bool) -> Self {
200 self.interactable = interactable;
201 self.movable &= interactable;
202 self
203 }
204
205 #[inline]
209 pub fn sense(mut self, sense: Sense) -> Self {
210 self.sense = Some(sense);
211 self
212 }
213
214 #[inline]
216 pub fn order(mut self, order: Order) -> Self {
217 self.order = order;
218 self
219 }
220
221 #[inline]
222 pub fn default_pos(mut self, default_pos: impl Into<Pos2>) -> Self {
223 self.default_pos = Some(default_pos.into());
224 self
225 }
226
227 #[inline]
237 pub fn default_size(mut self, default_size: impl Into<Vec2>) -> Self {
238 self.default_size = default_size.into();
239 self
240 }
241
242 #[inline]
244 pub fn default_width(mut self, default_width: f32) -> Self {
245 self.default_size.x = default_width;
246 self
247 }
248
249 #[inline]
251 pub fn default_height(mut self, default_height: f32) -> Self {
252 self.default_size.y = default_height;
253 self
254 }
255
256 #[inline]
258 pub fn fixed_pos(mut self, fixed_pos: impl Into<Pos2>) -> Self {
259 self.new_pos = Some(fixed_pos.into());
260 self.movable = false;
261 self
262 }
263
264 #[inline]
268 pub fn constrain(mut self, constrain: bool) -> Self {
269 self.constrain = constrain;
270 self
271 }
272
273 #[inline]
277 pub fn constrain_to(mut self, constrain_rect: Rect) -> Self {
278 self.constrain = true;
279 self.constrain_rect = Some(constrain_rect);
280 self
281 }
282
283 #[inline]
291 pub fn pivot(mut self, pivot: Align2) -> Self {
292 self.pivot = pivot;
293 self
294 }
295
296 #[inline]
298 pub fn current_pos(mut self, current_pos: impl Into<Pos2>) -> Self {
299 self.new_pos = Some(current_pos.into());
300 self
301 }
302
303 #[inline]
315 pub fn anchor(mut self, align: Align2, offset: impl Into<Vec2>) -> Self {
316 self.anchor = Some((align, offset.into()));
317 self.movable(false)
318 }
319
320 pub(crate) fn get_pivot(&self) -> Align2 {
321 if let Some((pivot, _)) = self.anchor {
322 pivot
323 } else {
324 Align2::LEFT_TOP
325 }
326 }
327
328 #[inline]
332 pub fn fade_in(mut self, fade_in: bool) -> Self {
333 self.fade_in = fade_in;
334 self
335 }
336}
337
338pub(crate) struct Prepared {
339 kind: UiKind,
340 layer_id: LayerId,
341 state: AreaState,
342 move_response: Response,
343 enabled: bool,
344 constrain: bool,
345 constrain_rect: Rect,
346
347 sizing_pass: bool,
353
354 fade_in: bool,
355}
356
357impl Area {
358 pub fn show<R>(
359 self,
360 ctx: &Context,
361 add_contents: impl FnOnce(&mut Ui) -> R,
362 ) -> InnerResponse<R> {
363 let prepared = self.begin(ctx);
364 let mut content_ui = prepared.content_ui(ctx);
365 let inner = add_contents(&mut content_ui);
366 let response = prepared.end(ctx, content_ui);
367 InnerResponse { inner, response }
368 }
369
370 pub(crate) fn begin(self, ctx: &Context) -> Prepared {
371 let Self {
372 id,
373 kind,
374 sense,
375 movable,
376 order,
377 interactable,
378 enabled,
379 default_pos,
380 default_size,
381 new_pos,
382 pivot,
383 anchor,
384 constrain,
385 constrain_rect,
386 fade_in,
387 } = self;
388
389 let constrain_rect = constrain_rect.unwrap_or_else(|| ctx.screen_rect());
390
391 let layer_id = LayerId::new(order, id);
392
393 let state = AreaState::load(ctx, id);
394 let mut sizing_pass = state.is_none();
395 let mut state = state.unwrap_or(AreaState {
396 pivot_pos: None,
397 pivot,
398 size: None,
399 interactable,
400 last_became_visible_at: None,
401 });
402 state.pivot = pivot;
403 state.interactable = interactable;
404 if let Some(new_pos) = new_pos {
405 state.pivot_pos = Some(new_pos);
406 }
407 state.pivot_pos.get_or_insert_with(|| {
408 default_pos.unwrap_or_else(|| automatic_area_position(ctx, layer_id))
409 });
410 state.interactable = interactable;
411
412 let size = *state.size.get_or_insert_with(|| {
413 sizing_pass = true;
414
415 let mut size = default_size;
417
418 let default_area_size = ctx.style().spacing.default_area_size;
419 if size.x.is_nan() {
420 size.x = default_area_size.x;
421 }
422 if size.y.is_nan() {
423 size.y = default_area_size.y;
424 }
425
426 if constrain {
427 size = size.at_most(constrain_rect.size());
428 }
429
430 size
431 });
432
433 let visible_last_frame = ctx.memory(|mem| mem.areas().visible_last_frame(&layer_id));
435
436 if !visible_last_frame || state.last_became_visible_at.is_none() {
437 state.last_became_visible_at = Some(ctx.input(|i| i.time));
438 }
439
440 if let Some((anchor, offset)) = anchor {
441 state.set_left_top_pos(
442 anchor
443 .align_size_within_rect(size, constrain_rect)
444 .left_top()
445 + offset,
446 );
447 }
448
449 let mut move_response = {
451 let interact_id = layer_id.id.with("move");
452 let sense = sense.unwrap_or_else(|| {
453 if movable {
454 Sense::drag()
455 } else if interactable {
456 Sense::click() } else {
458 Sense::hover()
459 }
460 });
461
462 let move_response = ctx.create_widget(WidgetRect {
463 id: interact_id,
464 layer_id,
465 rect: state.rect(),
466 interact_rect: state.rect(),
467 sense,
468 enabled,
469 });
470
471 if movable && move_response.dragged() {
472 if let Some(pivot_pos) = &mut state.pivot_pos {
473 *pivot_pos += move_response.drag_delta();
474 }
475 }
476
477 if (move_response.dragged() || move_response.clicked())
478 || pointer_pressed_on_area(ctx, layer_id)
479 || !ctx.memory(|m| m.areas().visible_last_frame(&layer_id))
480 {
481 ctx.memory_mut(|m| m.areas_mut().move_to_top(layer_id));
482 ctx.request_repaint();
483 }
484
485 move_response
486 };
487
488 if constrain {
489 state.set_left_top_pos(
490 ctx.constrain_window_rect_to_area(state.rect(), constrain_rect)
491 .min,
492 );
493 }
494
495 state.set_left_top_pos(ctx.round_pos_to_pixels(state.left_top_pos()));
496
497 move_response.rect = state.rect();
499 move_response.interact_rect = state.rect();
500
501 Prepared {
502 kind,
503 layer_id,
504 state,
505 move_response,
506 enabled,
507 constrain,
508 constrain_rect,
509 sizing_pass,
510 fade_in,
511 }
512 }
513}
514
515impl Prepared {
516 pub(crate) fn state(&self) -> &AreaState {
517 &self.state
518 }
519
520 pub(crate) fn state_mut(&mut self) -> &mut AreaState {
521 &mut self.state
522 }
523
524 pub(crate) fn constrain(&self) -> bool {
525 self.constrain
526 }
527
528 pub(crate) fn constrain_rect(&self) -> Rect {
529 self.constrain_rect
530 }
531
532 pub(crate) fn content_ui(&self, ctx: &Context) -> Ui {
533 let max_rect = self.state.rect();
534
535 let clip_rect = self.constrain_rect; let mut ui = Ui::new(
538 ctx.clone(),
539 self.layer_id,
540 self.layer_id.id,
541 max_rect,
542 clip_rect,
543 UiStackInfo::new(self.kind),
544 );
545
546 if self.fade_in {
547 if let Some(last_became_visible_at) = self.state.last_became_visible_at {
548 let age =
549 ctx.input(|i| (i.time - last_became_visible_at) as f32 + i.predicted_dt / 2.0);
550 let opacity = crate::remap_clamp(age, 0.0..=ctx.style().animation_time, 0.0..=1.0);
551 let opacity = emath::easing::quadratic_out(opacity); ui.multiply_opacity(opacity);
553 if opacity < 1.0 {
554 ctx.request_repaint();
555 }
556 }
557 }
558
559 if !self.enabled {
560 ui.disable();
561 }
562 if self.sizing_pass {
563 ui.set_sizing_pass();
564 }
565 ui
566 }
567
568 #[allow(clippy::needless_pass_by_value)] pub(crate) fn end(self, ctx: &Context, content_ui: Ui) -> Response {
570 let Self {
571 kind: _,
572 layer_id,
573 mut state,
574 move_response: mut response,
575 sizing_pass,
576 ..
577 } = self;
578
579 state.size = Some(content_ui.min_size());
580
581 let final_rect = state.rect();
584 response.rect = final_rect;
585 response.interact_rect = final_rect;
586
587 ctx.memory_mut(|m| m.areas_mut().set_state(layer_id, state));
588
589 if sizing_pass {
590 ctx.request_repaint();
592 }
593
594 response
595 }
596}
597
598fn pointer_pressed_on_area(ctx: &Context, layer_id: LayerId) -> bool {
599 if let Some(pointer_pos) = ctx.pointer_interact_pos() {
600 let any_pressed = ctx.input(|i| i.pointer.any_pressed());
601 any_pressed && ctx.layer_id_at(pointer_pos) == Some(layer_id)
602 } else {
603 false
604 }
605}
606
607fn automatic_area_position(ctx: &Context, layer_id: LayerId) -> Pos2 {
608 let mut existing: Vec<Rect> = ctx.memory(|mem| {
609 mem.areas()
610 .visible_windows()
611 .filter(|(id, _)| id != &layer_id) .filter(|(_, state)| state.pivot_pos.is_some() && state.size.is_some())
613 .map(|(_, state)| state.rect())
614 .collect()
615 });
616 existing.sort_by_key(|r| r.left().round() as i32);
617
618 let available_rect = ctx.available_rect();
621
622 let spacing = 16.0;
623 let left = available_rect.left() + spacing;
624 let top = available_rect.top() + spacing;
625
626 if existing.is_empty() {
627 return pos2(left, top);
628 }
629
630 let mut column_bbs = vec![existing[0]];
632
633 for &rect in &existing {
634 let current_column_bb = column_bbs.last_mut().unwrap();
635 if rect.left() < current_column_bb.right() {
636 *current_column_bb = current_column_bb.union(rect);
638 } else {
639 column_bbs.push(rect);
641 }
642 }
643
644 {
645 let mut x = left;
647 for col_bb in &column_bbs {
648 let available = col_bb.left() - x;
649 if available >= 300.0 {
650 return pos2(x, top);
651 }
652 x = col_bb.right() + spacing;
653 }
654 }
655
656 for col_bb in &column_bbs {
658 if col_bb.bottom() < available_rect.center().y {
659 return pos2(col_bb.left(), col_bb.bottom() + spacing);
660 }
661 }
662
663 let rightmost = column_bbs.last().unwrap().right();
665 if rightmost + 200.0 < available_rect.right() {
666 return pos2(rightmost + spacing, top);
667 }
668
669 let mut best_pos = pos2(left, column_bbs[0].bottom() + spacing);
671 for col_bb in &column_bbs {
672 let col_pos = pos2(col_bb.left(), col_bb.bottom() + spacing);
673 if col_pos.y < best_pos.y {
674 best_pos = col_pos;
675 }
676 }
677 best_pos
678}