1use emath::GuiRounding as _;
6
7use crate::{
8 emath, pos2, Align2, Context, Id, InnerResponse, LayerId, NumExt, Order, Pos2, Rect, Response,
9 Sense, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetRect, WidgetWithState,
10};
11
12#[derive(Clone, Copy, Debug)]
17#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
18pub struct AreaState {
19 pub pivot_pos: Option<Pos2>,
21
22 pub pivot: Align2,
24
25 #[cfg_attr(feature = "serde", serde(skip))]
32 pub size: Option<Vec2>,
33
34 pub interactable: bool,
36
37 #[cfg_attr(feature = "serde", serde(skip))]
41 pub last_became_visible_at: Option<f64>,
42}
43
44impl Default for AreaState {
45 fn default() -> Self {
46 Self {
47 pivot_pos: None,
48 pivot: Align2::LEFT_TOP,
49 size: None,
50 interactable: true,
51 last_became_visible_at: None,
52 }
53 }
54}
55
56impl AreaState {
57 pub fn load(ctx: &Context, id: Id) -> Option<Self> {
59 ctx.memory(|mem| mem.areas().get(id).copied())
61 }
62
63 pub fn left_top_pos(&self) -> Pos2 {
65 let pivot_pos = self.pivot_pos.unwrap_or_default();
66 let size = self.size.unwrap_or_default();
67 pos2(
68 pivot_pos.x - self.pivot.x().to_factor() * size.x,
69 pivot_pos.y - self.pivot.y().to_factor() * size.y,
70 )
71 .round_ui()
72 }
73
74 pub fn set_left_top_pos(&mut self, pos: Pos2) {
76 let size = self.size.unwrap_or_default();
77 self.pivot_pos = Some(pos2(
78 pos.x + self.pivot.x().to_factor() * size.x,
79 pos.y + self.pivot.y().to_factor() * size.y,
80 ));
81 }
82
83 pub fn rect(&self) -> Rect {
85 let size = self.size.unwrap_or_default();
86 Rect::from_min_size(self.left_top_pos(), size).round_ui()
87 }
88}
89
90#[must_use = "You should call .show()"]
106#[derive(Clone, Copy, Debug)]
107pub struct Area {
108 pub(crate) id: Id,
109 kind: UiKind,
110 sense: Option<Sense>,
111 movable: bool,
112 interactable: bool,
113 enabled: bool,
114 constrain: bool,
115 constrain_rect: Option<Rect>,
116 order: Order,
117 default_pos: Option<Pos2>,
118 default_size: Vec2,
119 pivot: Align2,
120 anchor: Option<(Align2, Vec2)>,
121 new_pos: Option<Pos2>,
122 fade_in: bool,
123}
124
125impl WidgetWithState for Area {
126 type State = AreaState;
127}
128
129impl Area {
130 pub fn new(id: Id) -> Self {
132 Self {
133 id,
134 kind: UiKind::GenericArea,
135 sense: None,
136 movable: true,
137 interactable: true,
138 constrain: true,
139 constrain_rect: None,
140 enabled: true,
141 order: Order::Middle,
142 default_pos: None,
143 default_size: Vec2::NAN,
144 new_pos: None,
145 pivot: Align2::LEFT_TOP,
146 anchor: None,
147 fade_in: true,
148 }
149 }
150
151 #[inline]
155 pub fn id(mut self, id: Id) -> Self {
156 self.id = id;
157 self
158 }
159
160 #[inline]
164 pub fn kind(mut self, kind: UiKind) -> Self {
165 self.kind = kind;
166 self
167 }
168
169 pub fn layer(&self) -> LayerId {
170 LayerId::new(self.order, self.id)
171 }
172
173 #[inline]
178 pub fn enabled(mut self, enabled: bool) -> Self {
179 self.enabled = enabled;
180 self
181 }
182
183 #[inline]
185 pub fn movable(mut self, movable: bool) -> Self {
186 self.movable = movable;
187 self.interactable |= movable;
188 self
189 }
190
191 pub fn is_enabled(&self) -> bool {
192 self.enabled
193 }
194
195 pub fn is_movable(&self) -> bool {
196 self.movable && self.enabled
197 }
198
199 #[inline]
205 pub fn interactable(mut self, interactable: bool) -> Self {
206 self.interactable = interactable;
207 self.movable &= interactable;
208 self
209 }
210
211 #[inline]
215 pub fn sense(mut self, sense: Sense) -> Self {
216 self.sense = Some(sense);
217 self
218 }
219
220 #[inline]
222 pub fn order(mut self, order: Order) -> Self {
223 self.order = order;
224 self
225 }
226
227 #[inline]
228 pub fn default_pos(mut self, default_pos: impl Into<Pos2>) -> Self {
229 self.default_pos = Some(default_pos.into());
230 self
231 }
232
233 #[inline]
243 pub fn default_size(mut self, default_size: impl Into<Vec2>) -> Self {
244 self.default_size = default_size.into();
245 self
246 }
247
248 #[inline]
250 pub fn default_width(mut self, default_width: f32) -> Self {
251 self.default_size.x = default_width;
252 self
253 }
254
255 #[inline]
257 pub fn default_height(mut self, default_height: f32) -> Self {
258 self.default_size.y = default_height;
259 self
260 }
261
262 #[inline]
264 pub fn fixed_pos(mut self, fixed_pos: impl Into<Pos2>) -> Self {
265 self.new_pos = Some(fixed_pos.into());
266 self.movable = false;
267 self
268 }
269
270 #[inline]
274 pub fn constrain(mut self, constrain: bool) -> Self {
275 self.constrain = constrain;
276 self
277 }
278
279 #[inline]
283 pub fn constrain_to(mut self, constrain_rect: Rect) -> Self {
284 self.constrain = true;
285 self.constrain_rect = Some(constrain_rect);
286 self
287 }
288
289 #[inline]
297 pub fn pivot(mut self, pivot: Align2) -> Self {
298 self.pivot = pivot;
299 self
300 }
301
302 #[inline]
304 pub fn current_pos(mut self, current_pos: impl Into<Pos2>) -> Self {
305 self.new_pos = Some(current_pos.into());
306 self
307 }
308
309 #[inline]
321 pub fn anchor(mut self, align: Align2, offset: impl Into<Vec2>) -> Self {
322 self.anchor = Some((align, offset.into()));
323 self.movable(false)
324 }
325
326 pub(crate) fn get_pivot(&self) -> Align2 {
327 if let Some((pivot, _)) = self.anchor {
328 pivot
329 } else {
330 Align2::LEFT_TOP
331 }
332 }
333
334 #[inline]
338 pub fn fade_in(mut self, fade_in: bool) -> Self {
339 self.fade_in = fade_in;
340 self
341 }
342}
343
344pub(crate) struct Prepared {
345 kind: UiKind,
346 layer_id: LayerId,
347 state: AreaState,
348 move_response: Response,
349 enabled: bool,
350 constrain: bool,
351 constrain_rect: Rect,
352
353 sizing_pass: bool,
359
360 fade_in: bool,
361}
362
363impl Area {
364 pub fn show<R>(
365 self,
366 ctx: &Context,
367 add_contents: impl FnOnce(&mut Ui) -> R,
368 ) -> InnerResponse<R> {
369 let prepared = self.begin(ctx);
370 let mut content_ui = prepared.content_ui(ctx);
371 let inner = add_contents(&mut content_ui);
372 let response = prepared.end(ctx, content_ui);
373 InnerResponse { inner, response }
374 }
375
376 pub(crate) fn begin(self, ctx: &Context) -> Prepared {
377 let Self {
378 id,
379 kind,
380 sense,
381 movable,
382 order,
383 interactable,
384 enabled,
385 default_pos,
386 default_size,
387 new_pos,
388 pivot,
389 anchor,
390 constrain,
391 constrain_rect,
392 fade_in,
393 } = self;
394
395 let constrain_rect = constrain_rect.unwrap_or_else(|| ctx.screen_rect());
396
397 let layer_id = LayerId::new(order, id);
398
399 let state = AreaState::load(ctx, id);
400 let mut sizing_pass = state.is_none();
401 let mut state = state.unwrap_or(AreaState {
402 pivot_pos: None,
403 pivot,
404 size: None,
405 interactable,
406 last_became_visible_at: None,
407 });
408 state.pivot = pivot;
409 state.interactable = interactable;
410 if let Some(new_pos) = new_pos {
411 state.pivot_pos = Some(new_pos);
412 }
413 state.pivot_pos.get_or_insert_with(|| {
414 default_pos.unwrap_or_else(|| automatic_area_position(ctx, layer_id))
415 });
416 state.interactable = interactable;
417
418 let size = *state.size.get_or_insert_with(|| {
419 sizing_pass = true;
420
421 let mut size = default_size;
423
424 let default_area_size = ctx.style().spacing.default_area_size;
425 if size.x.is_nan() {
426 size.x = default_area_size.x;
427 }
428 if size.y.is_nan() {
429 size.y = default_area_size.y;
430 }
431
432 if constrain {
433 size = size.at_most(constrain_rect.size());
434 }
435
436 size
437 });
438
439 let visible_last_frame = ctx.memory(|mem| mem.areas().visible_last_frame(&layer_id));
441
442 if !visible_last_frame || state.last_became_visible_at.is_none() {
443 state.last_became_visible_at = Some(ctx.input(|i| i.time));
444 }
445
446 if let Some((anchor, offset)) = anchor {
447 state.set_left_top_pos(
448 anchor
449 .align_size_within_rect(size, constrain_rect)
450 .left_top()
451 + offset,
452 );
453 }
454
455 let mut move_response = {
457 let interact_id = layer_id.id.with("move");
458 let sense = sense.unwrap_or_else(|| {
459 if movable {
460 Sense::drag()
461 } else if interactable {
462 Sense::click() } else {
464 Sense::hover()
465 }
466 });
467
468 let move_response = ctx.create_widget(
469 WidgetRect {
470 id: interact_id,
471 layer_id,
472 rect: state.rect(),
473 interact_rect: state.rect().intersect(constrain_rect),
474 sense,
475 enabled,
476 },
477 true,
478 );
479
480 if movable && move_response.dragged() {
481 if let Some(pivot_pos) = &mut state.pivot_pos {
482 *pivot_pos += move_response.drag_delta();
483 }
484 }
485
486 if (move_response.dragged() || move_response.clicked())
487 || pointer_pressed_on_area(ctx, layer_id)
488 || !ctx.memory(|m| m.areas().visible_last_frame(&layer_id))
489 {
490 ctx.memory_mut(|m| m.areas_mut().move_to_top(layer_id));
491 ctx.request_repaint();
492 }
493
494 move_response
495 };
496
497 if constrain {
498 state.set_left_top_pos(
499 Context::constrain_window_rect_to_area(state.rect(), constrain_rect).min,
500 );
501 }
502
503 state.set_left_top_pos(state.left_top_pos());
504
505 move_response.rect = state.rect();
507 move_response.interact_rect = state.rect();
508
509 Prepared {
510 kind,
511 layer_id,
512 state,
513 move_response,
514 enabled,
515 constrain,
516 constrain_rect,
517 sizing_pass,
518 fade_in,
519 }
520 }
521}
522
523impl Prepared {
524 pub(crate) fn state(&self) -> &AreaState {
525 &self.state
526 }
527
528 pub(crate) fn state_mut(&mut self) -> &mut AreaState {
529 &mut self.state
530 }
531
532 pub(crate) fn constrain(&self) -> bool {
533 self.constrain
534 }
535
536 pub(crate) fn constrain_rect(&self) -> Rect {
537 self.constrain_rect
538 }
539
540 pub(crate) fn content_ui(&self, ctx: &Context) -> Ui {
541 let max_rect = self.state.rect();
542
543 let mut ui_builder = UiBuilder::new()
544 .ui_stack_info(UiStackInfo::new(self.kind))
545 .layer_id(self.layer_id)
546 .max_rect(max_rect);
547
548 if !self.enabled {
549 ui_builder = ui_builder.disabled();
550 }
551 if self.sizing_pass {
552 ui_builder = ui_builder.sizing_pass().invisible();
553 }
554
555 let mut ui = Ui::new(ctx.clone(), self.layer_id.id, ui_builder);
556 ui.set_clip_rect(self.constrain_rect); if self.fade_in {
559 if let Some(last_became_visible_at) = self.state.last_became_visible_at {
560 let age =
561 ctx.input(|i| (i.time - last_became_visible_at) as f32 + i.predicted_dt / 2.0);
562 let opacity = crate::remap_clamp(age, 0.0..=ctx.style().animation_time, 0.0..=1.0);
563 let opacity = emath::easing::quadratic_out(opacity); ui.multiply_opacity(opacity);
565 if opacity < 1.0 {
566 ctx.request_repaint();
567 }
568 }
569 }
570
571 ui
572 }
573
574 pub(crate) fn with_widget_info(&self, make_info: impl Fn() -> crate::WidgetInfo) {
575 self.move_response.widget_info(make_info);
576 }
577
578 pub(crate) fn id(&self) -> Id {
579 self.move_response.id
580 }
581
582 #[allow(clippy::needless_pass_by_value)] pub(crate) fn end(self, ctx: &Context, content_ui: Ui) -> Response {
584 let Self {
585 kind: _,
586 layer_id,
587 mut state,
588 move_response: mut response,
589 sizing_pass,
590 ..
591 } = self;
592
593 state.size = Some(content_ui.min_size());
594
595 let final_rect = state.rect();
598 response.rect = final_rect;
599 response.interact_rect = final_rect;
600
601 ctx.memory_mut(|m| m.areas_mut().set_state(layer_id, state));
602
603 if sizing_pass {
604 ctx.request_repaint();
606 }
607
608 response
609 }
610}
611
612fn pointer_pressed_on_area(ctx: &Context, layer_id: LayerId) -> bool {
613 if let Some(pointer_pos) = ctx.pointer_interact_pos() {
614 let any_pressed = ctx.input(|i| i.pointer.any_pressed());
615 any_pressed && ctx.layer_id_at(pointer_pos) == Some(layer_id)
616 } else {
617 false
618 }
619}
620
621fn automatic_area_position(ctx: &Context, layer_id: LayerId) -> Pos2 {
622 let mut existing: Vec<Rect> = ctx.memory(|mem| {
623 mem.areas()
624 .visible_windows()
625 .filter(|(id, _)| id != &layer_id) .filter(|(_, state)| state.pivot_pos.is_some() && state.size.is_some())
627 .map(|(_, state)| state.rect())
628 .collect()
629 });
630 existing.sort_by_key(|r| r.left().round() as i32);
631
632 let available_rect = ctx.available_rect();
635
636 let spacing = 16.0;
637 let left = available_rect.left() + spacing;
638 let top = available_rect.top() + spacing;
639
640 if existing.is_empty() {
641 return pos2(left, top);
642 }
643
644 let mut column_bbs = vec![existing[0]];
646
647 for &rect in &existing {
648 let current_column_bb = column_bbs.last_mut().unwrap();
649 if rect.left() < current_column_bb.right() {
650 *current_column_bb = current_column_bb.union(rect);
652 } else {
653 column_bbs.push(rect);
655 }
656 }
657
658 {
659 let mut x = left;
661 for col_bb in &column_bbs {
662 let available = col_bb.left() - x;
663 if available >= 300.0 {
664 return pos2(x, top);
665 }
666 x = col_bb.right() + spacing;
667 }
668 }
669
670 for col_bb in &column_bbs {
672 if col_bb.bottom() < available_rect.center().y {
673 return pos2(col_bb.left(), col_bb.bottom() + spacing);
674 }
675 }
676
677 let rightmost = column_bbs.last().unwrap().right();
679 if rightmost + 200.0 < available_rect.right() {
680 return pos2(rightmost + spacing, top);
681 }
682
683 let mut best_pos = pos2(left, column_bbs[0].bottom() + spacing);
685 for col_bb in &column_bbs {
686 let col_pos = pos2(col_bb.left(), col_bb.bottom() + spacing);
687 if col_pos.y < best_pos.y {
688 best_pos = col_pos;
689 }
690 }
691 best_pos
692}