1use std::sync::Arc;
4
5use crate::collapsing_header::CollapsingState;
6use crate::*;
7use epaint::*;
8
9use super::*;
10
11#[must_use = "You should call .show()"]
33pub struct Window<'open> {
34 title: WidgetText,
35 open: Option<&'open mut bool>,
36 area: Area,
37 frame: Option<Frame>,
38 resize: Resize,
39 scroll: ScrollArea,
40 collapsible: bool,
41 default_open: bool,
42 with_title_bar: bool,
43 fade_out: bool,
44}
45
46impl<'open> Window<'open> {
47 pub fn new(title: impl Into<WidgetText>) -> Self {
51 let title = title.into().fallback_text_style(TextStyle::Heading);
52 let area = Area::new(Id::new(title.text())).kind(UiKind::Window);
53 Self {
54 title,
55 open: None,
56 area,
57 frame: None,
58 resize: Resize::default()
59 .with_stroke(false)
60 .min_size([96.0, 32.0])
61 .default_size([340.0, 420.0]), scroll: ScrollArea::neither().auto_shrink(false),
63 collapsible: true,
64 default_open: true,
65 with_title_bar: true,
66 fade_out: true,
67 }
68 }
69
70 #[inline]
72 pub fn id(mut self, id: Id) -> Self {
73 self.area = self.area.id(id);
74 self
75 }
76
77 #[inline]
83 pub fn open(mut self, open: &'open mut bool) -> Self {
84 self.open = Some(open);
85 self
86 }
87
88 #[inline]
90 pub fn enabled(mut self, enabled: bool) -> Self {
91 self.area = self.area.enabled(enabled);
92 self
93 }
94
95 #[inline]
101 pub fn interactable(mut self, interactable: bool) -> Self {
102 self.area = self.area.interactable(interactable);
103 self
104 }
105
106 #[inline]
108 pub fn movable(mut self, movable: bool) -> Self {
109 self.area = self.area.movable(movable);
110 self
111 }
112
113 #[inline]
115 pub fn order(mut self, order: Order) -> Self {
116 self.area = self.area.order(order);
117 self
118 }
119
120 #[inline]
124 pub fn fade_in(mut self, fade_in: bool) -> Self {
125 self.area = self.area.fade_in(fade_in);
126 self
127 }
128
129 #[inline]
135 pub fn fade_out(mut self, fade_out: bool) -> Self {
136 self.fade_out = fade_out;
137 self
138 }
139
140 #[inline]
143 pub fn mutate(mut self, mutate: impl Fn(&mut Self)) -> Self {
144 mutate(&mut self);
145 self
146 }
147
148 #[inline]
151 pub fn resize(mut self, mutate: impl Fn(Resize) -> Resize) -> Self {
152 self.resize = mutate(self.resize);
153 self
154 }
155
156 #[inline]
158 pub fn frame(mut self, frame: Frame) -> Self {
159 self.frame = Some(frame);
160 self
161 }
162
163 #[inline]
165 pub fn min_width(mut self, min_width: f32) -> Self {
166 self.resize = self.resize.min_width(min_width);
167 self
168 }
169
170 #[inline]
172 pub fn min_height(mut self, min_height: f32) -> Self {
173 self.resize = self.resize.min_height(min_height);
174 self
175 }
176
177 #[inline]
179 pub fn min_size(mut self, min_size: impl Into<Vec2>) -> Self {
180 self.resize = self.resize.min_size(min_size);
181 self
182 }
183
184 #[inline]
186 pub fn max_width(mut self, max_width: f32) -> Self {
187 self.resize = self.resize.max_width(max_width);
188 self
189 }
190
191 #[inline]
193 pub fn max_height(mut self, max_height: f32) -> Self {
194 self.resize = self.resize.max_height(max_height);
195 self
196 }
197
198 #[inline]
200 pub fn max_size(mut self, max_size: impl Into<Vec2>) -> Self {
201 self.resize = self.resize.max_size(max_size);
202 self
203 }
204
205 #[inline]
208 pub fn current_pos(mut self, current_pos: impl Into<Pos2>) -> Self {
209 self.area = self.area.current_pos(current_pos);
210 self
211 }
212
213 #[inline]
215 pub fn default_pos(mut self, default_pos: impl Into<Pos2>) -> Self {
216 self.area = self.area.default_pos(default_pos);
217 self
218 }
219
220 #[inline]
222 pub fn fixed_pos(mut self, pos: impl Into<Pos2>) -> Self {
223 self.area = self.area.fixed_pos(pos);
224 self
225 }
226
227 #[inline]
233 pub fn constrain(mut self, constrain: bool) -> Self {
234 self.area = self.area.constrain(constrain);
235 self
236 }
237
238 #[inline]
242 pub fn constrain_to(mut self, constrain_rect: Rect) -> Self {
243 self.area = self.area.constrain_to(constrain_rect);
244 self
245 }
246
247 #[inline]
255 pub fn pivot(mut self, pivot: Align2) -> Self {
256 self.area = self.area.pivot(pivot);
257 self
258 }
259
260 #[inline]
272 pub fn anchor(mut self, align: Align2, offset: impl Into<Vec2>) -> Self {
273 self.area = self.area.anchor(align, offset);
274 self
275 }
276
277 #[inline]
279 pub fn default_open(mut self, default_open: bool) -> Self {
280 self.default_open = default_open;
281 self
282 }
283
284 #[inline]
286 pub fn default_size(mut self, default_size: impl Into<Vec2>) -> Self {
287 self.resize = self.resize.default_size(default_size);
288 self
289 }
290
291 #[inline]
293 pub fn default_width(mut self, default_width: f32) -> Self {
294 self.resize = self.resize.default_width(default_width);
295 self
296 }
297
298 #[inline]
300 pub fn default_height(mut self, default_height: f32) -> Self {
301 self.resize = self.resize.default_height(default_height);
302 self
303 }
304
305 #[inline]
307 pub fn fixed_size(mut self, size: impl Into<Vec2>) -> Self {
308 self.resize = self.resize.fixed_size(size);
309 self
310 }
311
312 pub fn default_rect(self, rect: Rect) -> Self {
314 self.default_pos(rect.min).default_size(rect.size())
315 }
316
317 pub fn fixed_rect(self, rect: Rect) -> Self {
319 self.fixed_pos(rect.min).fixed_size(rect.size())
320 }
321
322 #[inline]
332 pub fn resizable(mut self, resizable: impl Into<Vec2b>) -> Self {
333 let resizable = resizable.into();
334 self.resize = self.resize.resizable(resizable);
335 self
336 }
337
338 #[inline]
340 pub fn collapsible(mut self, collapsible: bool) -> Self {
341 self.collapsible = collapsible;
342 self
343 }
344
345 #[inline]
348 pub fn title_bar(mut self, title_bar: bool) -> Self {
349 self.with_title_bar = title_bar;
350 self
351 }
352
353 #[inline]
357 pub fn auto_sized(mut self) -> Self {
358 self.resize = self.resize.auto_sized();
359 self.scroll = ScrollArea::neither();
360 self
361 }
362
363 #[inline]
367 pub fn scroll(mut self, scroll: impl Into<Vec2b>) -> Self {
368 self.scroll = self.scroll.scroll(scroll);
369 self
370 }
371
372 #[deprecated = "Renamed to `scroll`"]
374 #[inline]
375 pub fn scroll2(mut self, scroll: impl Into<Vec2b>) -> Self {
376 self.scroll = self.scroll.scroll(scroll);
377 self
378 }
379
380 #[inline]
382 pub fn hscroll(mut self, hscroll: bool) -> Self {
383 self.scroll = self.scroll.hscroll(hscroll);
384 self
385 }
386
387 #[inline]
389 pub fn vscroll(mut self, vscroll: bool) -> Self {
390 self.scroll = self.scroll.vscroll(vscroll);
391 self
392 }
393
394 #[inline]
398 pub fn drag_to_scroll(mut self, drag_to_scroll: bool) -> Self {
399 self.scroll = self.scroll.drag_to_scroll(drag_to_scroll);
400 self
401 }
402}
403
404impl<'open> Window<'open> {
405 #[inline]
408 pub fn show<R>(
409 self,
410 ctx: &Context,
411 add_contents: impl FnOnce(&mut Ui) -> R,
412 ) -> Option<InnerResponse<Option<R>>> {
413 self.show_dyn(ctx, Box::new(add_contents))
414 }
415
416 fn show_dyn<'c, R>(
417 self,
418 ctx: &Context,
419 add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
420 ) -> Option<InnerResponse<Option<R>>> {
421 let Window {
422 title,
423 open,
424 area,
425 frame,
426 resize,
427 scroll,
428 collapsible,
429 default_open,
430 with_title_bar,
431 fade_out,
432 } = self;
433
434 let header_color =
435 frame.map_or_else(|| ctx.style().visuals.widgets.open.weak_bg_fill, |f| f.fill);
436 let mut window_frame = frame.unwrap_or_else(|| Frame::window(&ctx.style()));
437 let window_margin = window_frame.inner_margin;
439 let border_padding = window_frame.stroke.width / 2.0;
440 window_frame.inner_margin += border_padding;
442
443 let is_explicitly_closed = matches!(open, Some(false));
444 let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible());
445 let opacity = ctx.animate_bool_with_easing(
446 area.id.with("fade-out"),
447 is_open,
448 emath::easing::cubic_out,
449 );
450 if opacity <= 0.0 {
451 return None;
452 }
453
454 let area_id = area.id;
455 let area_layer_id = area.layer();
456 let resize_id = area_id.with("resize");
457 let mut collapsing =
458 CollapsingState::load_with_default_open(ctx, area_id.with("collapsing"), default_open);
459
460 let is_collapsed = with_title_bar && !collapsing.is_open();
461 let possible = PossibleInteractions::new(&area, &resize, is_collapsed);
462
463 let resize = resize.resizable(false); let mut resize = resize.id(resize_id);
465
466 let on_top = Some(area_layer_id) == ctx.top_layer_id();
467 let mut area = area.begin(ctx);
468
469 let (title_bar_height, title_content_spacing) = if with_title_bar {
471 let style = ctx.style();
472 let spacing = window_margin.top + window_margin.bottom;
473 let height = ctx.fonts(|f| title.font_height(f, &style)) + spacing;
474 window_frame.rounding.ne = window_frame.rounding.ne.clamp(0.0, height / 2.0);
475 window_frame.rounding.nw = window_frame.rounding.nw.clamp(0.0, height / 2.0);
476 (height, spacing)
477 } else {
478 (0.0, 0.0)
479 };
480
481 {
482 let constrain_rect = area.constrain_rect();
484 let max_width = constrain_rect.width();
485 let max_height = constrain_rect.height() - title_bar_height;
486 resize.max_size.x = resize.max_size.x.min(max_width);
487 resize.max_size.y = resize.max_size.y.min(max_height);
488 }
489
490 let last_frame_outer_rect = area.state().rect();
492 let resize_interaction =
493 resize_interaction(ctx, possible, area_layer_id, last_frame_outer_rect);
494
495 let margins = window_frame.outer_margin.sum()
496 + window_frame.inner_margin.sum()
497 + vec2(0.0, title_bar_height);
498
499 resize_response(
500 resize_interaction,
501 ctx,
502 margins,
503 area_layer_id,
504 &mut area,
505 resize_id,
506 );
507
508 let mut area_content_ui = area.content_ui(ctx);
509 if is_open {
510 } else if fade_out {
513 area_content_ui.multiply_opacity(opacity);
514 }
515
516 let content_inner = {
517 let frame_stroke = window_frame.stroke;
519 let mut frame = window_frame.begin(&mut area_content_ui);
520
521 let show_close_button = open.is_some();
522
523 let where_to_put_header_background = &area_content_ui.painter().add(Shape::Noop);
524
525 let item_spacing = frame.content_ui.spacing().item_spacing;
527 frame.content_ui.spacing_mut().item_spacing.y = title_content_spacing;
529
530 let title_bar = if with_title_bar {
531 let title_bar = show_title_bar(
532 &mut frame.content_ui,
533 title,
534 show_close_button,
535 &mut collapsing,
536 collapsible,
537 );
538 resize.min_size.x = resize.min_size.x.at_least(title_bar.rect.width()); Some(title_bar)
540 } else {
541 None
542 };
543
544 frame.content_ui.spacing_mut().item_spacing.y = 0.0;
546
547 let (content_inner, mut content_response) = collapsing
548 .show_body_unindented(&mut frame.content_ui, |ui| {
549 ui.spacing_mut().item_spacing.y = item_spacing.y;
551
552 resize.show(ui, |ui| {
553 if scroll.is_any_scroll_enabled() {
554 scroll.show(ui, add_contents).inner
555 } else {
556 add_contents(ui)
557 }
558 })
559 })
560 .map_or((None, None), |ir| (Some(ir.inner), Some(ir.response)));
561
562 let outer_rect = frame.end(&mut area_content_ui).rect;
563 paint_resize_corner(
564 &area_content_ui,
565 &possible,
566 outer_rect,
567 frame_stroke,
568 window_frame.rounding,
569 );
570
571 if let Some(title_bar) = title_bar {
574 let mut title_rect = Rect::from_min_size(
575 outer_rect.min + vec2(border_padding, border_padding),
576 Vec2 {
577 x: outer_rect.size().x - border_padding * 2.0,
578 y: title_bar_height,
579 },
580 );
581
582 title_rect = area_content_ui.painter().round_rect_to_pixels(title_rect);
583
584 if on_top && area_content_ui.visuals().window_highlight_topmost {
585 let mut round = window_frame.rounding;
586
587 round -= border_padding;
589
590 if !is_collapsed {
591 round.se = 0.0;
592 round.sw = 0.0;
593 }
594
595 area_content_ui.painter().set(
596 *where_to_put_header_background,
597 RectShape::filled(title_rect, round, header_color),
598 );
599 };
600
601 if let Some(response) = &mut content_response {
603 response.rect.min.y = outer_rect.min.y + title_bar_height + border_padding;
604 }
605
606 title_bar.ui(
607 &mut area_content_ui,
608 title_rect,
609 &content_response,
610 open,
611 &mut collapsing,
612 collapsible,
613 );
614 }
615
616 collapsing.store(ctx);
617
618 paint_frame_interaction(&area_content_ui, outer_rect, resize_interaction);
619
620 content_inner
621 };
622
623 let full_response = area.end(ctx, area_content_ui);
624
625 let inner_response = InnerResponse {
626 inner: content_inner,
627 response: full_response,
628 };
629 Some(inner_response)
630 }
631}
632
633fn paint_resize_corner(
634 ui: &Ui,
635 possible: &PossibleInteractions,
636 outer_rect: Rect,
637 stroke: impl Into<Stroke>,
638 rounding: impl Into<Rounding>,
639) {
640 let stroke = stroke.into();
641 let rounding = rounding.into();
642 let (corner, radius) = if possible.resize_right && possible.resize_bottom {
643 (Align2::RIGHT_BOTTOM, rounding.se)
644 } else if possible.resize_left && possible.resize_bottom {
645 (Align2::LEFT_BOTTOM, rounding.sw)
646 } else if possible.resize_left && possible.resize_top {
647 (Align2::LEFT_TOP, rounding.nw)
648 } else if possible.resize_right && possible.resize_top {
649 (Align2::RIGHT_TOP, rounding.ne)
650 } else {
651 if possible.resize_right || possible.resize_bottom {
655 (Align2::RIGHT_BOTTOM, rounding.se)
656 } else if possible.resize_left || possible.resize_bottom {
657 (Align2::LEFT_BOTTOM, rounding.sw)
658 } else if possible.resize_left || possible.resize_top {
659 (Align2::LEFT_TOP, rounding.nw)
660 } else if possible.resize_right || possible.resize_top {
661 (Align2::RIGHT_TOP, rounding.ne)
662 } else {
663 return;
664 }
665 };
666
667 let offset = if radius <= 2.0 && stroke.width < 2.0 {
669 2.0
670 } else {
671 (2.0_f32.sqrt() * (1.0 + radius + stroke.width / 2.0) - radius)
673 * 45.0_f32.to_radians().cos()
674 };
675 let corner_size = Vec2::splat(ui.visuals().resize_corner_size);
676 let corner_rect = corner.align_size_within_rect(corner_size, outer_rect);
677 let corner_rect = corner_rect.translate(-offset * corner.to_sign()); crate::resize::paint_resize_corner_with_style(ui, &corner_rect, stroke.color, corner);
679}
680
681#[derive(Clone, Copy, Debug)]
685struct PossibleInteractions {
686 resize_left: bool,
688 resize_right: bool,
689 resize_top: bool,
690 resize_bottom: bool,
691}
692
693impl PossibleInteractions {
694 fn new(area: &Area, resize: &Resize, is_collapsed: bool) -> Self {
695 let movable = area.is_enabled() && area.is_movable();
696 let resizable = resize
697 .is_resizable()
698 .and(area.is_enabled() && !is_collapsed);
699 let pivot = area.get_pivot();
700 Self {
701 resize_left: resizable.x && (movable || pivot.x() != Align::LEFT),
702 resize_right: resizable.x && (movable || pivot.x() != Align::RIGHT),
703 resize_top: resizable.y && (movable || pivot.y() != Align::TOP),
704 resize_bottom: resizable.y && (movable || pivot.y() != Align::BOTTOM),
705 }
706 }
707
708 pub fn resizable(&self) -> bool {
709 self.resize_left || self.resize_right || self.resize_top || self.resize_bottom
710 }
711}
712
713#[derive(Clone, Copy, Debug)]
715struct ResizeInteraction {
716 start_rect: Rect,
717 left: SideResponse,
718 right: SideResponse,
719 top: SideResponse,
720 bottom: SideResponse,
721}
722
723#[derive(Clone, Copy, Debug, Default)]
725struct SideResponse {
726 hover: bool,
727 drag: bool,
728}
729
730impl SideResponse {
731 pub fn any(&self) -> bool {
732 self.hover || self.drag
733 }
734}
735
736impl std::ops::BitOrAssign for SideResponse {
737 fn bitor_assign(&mut self, rhs: Self) {
738 *self = Self {
739 hover: self.hover || rhs.hover,
740 drag: self.drag || rhs.drag,
741 };
742 }
743}
744
745impl ResizeInteraction {
746 pub fn set_cursor(&self, ctx: &Context) {
747 let left = self.left.any();
748 let right = self.right.any();
749 let top = self.top.any();
750 let bottom = self.bottom.any();
751
752 if (left && top) || (right && bottom) {
754 ctx.set_cursor_icon(CursorIcon::ResizeNwSe);
755 } else if (right && top) || (left && bottom) {
756 ctx.set_cursor_icon(CursorIcon::ResizeNeSw);
757 } else if left || right {
758 ctx.set_cursor_icon(CursorIcon::ResizeHorizontal);
759 } else if bottom || top {
760 ctx.set_cursor_icon(CursorIcon::ResizeVertical);
761 }
762 }
763
764 pub fn any_hovered(&self) -> bool {
765 self.left.hover || self.right.hover || self.top.hover || self.bottom.hover
766 }
767
768 pub fn any_dragged(&self) -> bool {
769 self.left.drag || self.right.drag || self.top.drag || self.bottom.drag
770 }
771}
772
773fn resize_response(
774 resize_interaction: ResizeInteraction,
775 ctx: &Context,
776 margins: Vec2,
777 area_layer_id: LayerId,
778 area: &mut area::Prepared,
779 resize_id: Id,
780) {
781 let Some(new_rect) = move_and_resize_window(ctx, &resize_interaction) else {
782 return;
783 };
784 let mut new_rect = ctx.round_rect_to_pixels(new_rect);
785
786 if area.constrain() {
787 new_rect = ctx.constrain_window_rect_to_area(new_rect, area.constrain_rect());
788 }
789
790 area.state_mut().set_left_top_pos(new_rect.left_top());
792
793 if resize_interaction.any_dragged() {
794 if let Some(mut state) = resize::State::load(ctx, resize_id) {
795 state.requested_size = Some(new_rect.size() - margins);
796 state.store(ctx, resize_id);
797 }
798 }
799
800 ctx.memory_mut(|mem| mem.areas_mut().move_to_top(area_layer_id));
801}
802
803fn move_and_resize_window(ctx: &Context, interaction: &ResizeInteraction) -> Option<Rect> {
804 if !interaction.any_dragged() {
805 return None;
806 }
807
808 let pointer_pos = ctx.input(|i| i.pointer.interact_pos())?;
809 let mut rect = interaction.start_rect; if interaction.left.drag {
812 rect.min.x = ctx.round_to_pixel(pointer_pos.x);
813 } else if interaction.right.drag {
814 rect.max.x = ctx.round_to_pixel(pointer_pos.x);
815 }
816
817 if interaction.top.drag {
818 rect.min.y = ctx.round_to_pixel(pointer_pos.y);
819 } else if interaction.bottom.drag {
820 rect.max.y = ctx.round_to_pixel(pointer_pos.y);
821 }
822
823 Some(rect)
824}
825
826fn resize_interaction(
827 ctx: &Context,
828 possible: PossibleInteractions,
829 layer_id: LayerId,
830 rect: Rect,
831) -> ResizeInteraction {
832 if !possible.resizable() {
833 return ResizeInteraction {
834 start_rect: rect,
835 left: Default::default(),
836 right: Default::default(),
837 top: Default::default(),
838 bottom: Default::default(),
839 };
840 }
841
842 let is_dragging = |rect, id| {
843 let response = ctx.create_widget(WidgetRect {
844 layer_id,
845 id,
846 rect,
847 interact_rect: rect,
848 sense: Sense::drag(),
849 enabled: true,
850 });
851 SideResponse {
852 hover: response.hovered(),
853 drag: response.dragged(),
854 }
855 };
856
857 let id = Id::new(layer_id).with("edge_drag");
858
859 let side_grab_radius = ctx.style().interaction.resize_grab_radius_side;
860 let corner_grab_radius = ctx.style().interaction.resize_grab_radius_corner;
861
862 let corner_rect =
863 |center: Pos2| Rect::from_center_size(center, Vec2::splat(2.0 * corner_grab_radius));
864
865 let [mut left, mut right, mut top, mut bottom] = [SideResponse::default(); 4];
867
868 if possible.resize_right {
872 let response = is_dragging(
873 Rect::from_min_max(rect.right_top(), rect.right_bottom()).expand(side_grab_radius),
874 id.with("right"),
875 );
876 right |= response;
877 }
878 if possible.resize_left {
879 let response = is_dragging(
880 Rect::from_min_max(rect.left_top(), rect.left_bottom()).expand(side_grab_radius),
881 id.with("left"),
882 );
883 left |= response;
884 }
885 if possible.resize_bottom {
886 let response = is_dragging(
887 Rect::from_min_max(rect.left_bottom(), rect.right_bottom()).expand(side_grab_radius),
888 id.with("bottom"),
889 );
890 bottom |= response;
891 }
892 if possible.resize_top {
893 let response = is_dragging(
894 Rect::from_min_max(rect.left_top(), rect.right_top()).expand(side_grab_radius),
895 id.with("top"),
896 );
897 top |= response;
898 }
899
900 if possible.resize_right && possible.resize_bottom {
904 let response = is_dragging(corner_rect(rect.right_bottom()), id.with("right_bottom"));
905 right |= response;
906 bottom |= response;
907 }
908
909 if possible.resize_right && possible.resize_top {
910 let response = is_dragging(corner_rect(rect.right_top()), id.with("right_top"));
911 right |= response;
912 top |= response;
913 }
914
915 if possible.resize_left && possible.resize_bottom {
916 let response = is_dragging(corner_rect(rect.left_bottom()), id.with("left_bottom"));
917 left |= response;
918 bottom |= response;
919 }
920
921 if possible.resize_left && possible.resize_top {
922 let response = is_dragging(corner_rect(rect.left_top()), id.with("left_top"));
923 left |= response;
924 top |= response;
925 }
926
927 let interaction = ResizeInteraction {
928 start_rect: rect,
929 left,
930 right,
931 top,
932 bottom,
933 };
934 interaction.set_cursor(ctx);
935 interaction
936}
937
938fn paint_frame_interaction(ui: &Ui, rect: Rect, interaction: ResizeInteraction) {
940 use epaint::tessellator::path::add_circle_quadrant;
941
942 let visuals = if interaction.any_dragged() {
943 ui.style().visuals.widgets.active
944 } else if interaction.any_hovered() {
945 ui.style().visuals.widgets.hovered
946 } else {
947 return;
948 };
949
950 let [left, right, top, bottom]: [bool; 4];
951
952 if interaction.any_dragged() {
953 left = interaction.left.drag;
954 right = interaction.right.drag;
955 top = interaction.top.drag;
956 bottom = interaction.bottom.drag;
957 } else {
958 left = interaction.left.hover;
959 right = interaction.right.hover;
960 top = interaction.top.hover;
961 bottom = interaction.bottom.hover;
962 }
963
964 let rounding = ui.visuals().window_rounding;
965 let Rect { min, max } = rect;
966
967 let mut points = Vec::new();
968
969 if right && !bottom && !top {
970 points.push(pos2(max.x, min.y + rounding.ne));
971 points.push(pos2(max.x, max.y - rounding.se));
972 }
973 if right && bottom {
974 points.push(pos2(max.x, min.y + rounding.ne));
975 points.push(pos2(max.x, max.y - rounding.se));
976 add_circle_quadrant(
977 &mut points,
978 pos2(max.x - rounding.se, max.y - rounding.se),
979 rounding.se,
980 0.0,
981 );
982 }
983 if bottom {
984 points.push(pos2(max.x - rounding.se, max.y));
985 points.push(pos2(min.x + rounding.sw, max.y));
986 }
987 if left && bottom {
988 add_circle_quadrant(
989 &mut points,
990 pos2(min.x + rounding.sw, max.y - rounding.sw),
991 rounding.sw,
992 1.0,
993 );
994 }
995 if left {
996 points.push(pos2(min.x, max.y - rounding.sw));
997 points.push(pos2(min.x, min.y + rounding.nw));
998 }
999 if left && top {
1000 add_circle_quadrant(
1001 &mut points,
1002 pos2(min.x + rounding.nw, min.y + rounding.nw),
1003 rounding.nw,
1004 2.0,
1005 );
1006 }
1007 if top {
1008 points.push(pos2(min.x + rounding.nw, min.y));
1009 points.push(pos2(max.x - rounding.ne, min.y));
1010 }
1011 if right && top {
1012 add_circle_quadrant(
1013 &mut points,
1014 pos2(max.x - rounding.ne, min.y + rounding.ne),
1015 rounding.ne,
1016 3.0,
1017 );
1018 points.push(pos2(max.x, min.y + rounding.ne));
1019 points.push(pos2(max.x, max.y - rounding.se));
1020 }
1021 ui.painter().add(Shape::line(points, visuals.bg_stroke));
1022}
1023
1024struct TitleBar {
1027 id: Id,
1029
1030 title_galley: Arc<Galley>,
1032
1033 min_rect: Rect,
1037
1038 rect: Rect,
1041}
1042
1043fn show_title_bar(
1044 ui: &mut Ui,
1045 title: WidgetText,
1046 show_close_button: bool,
1047 collapsing: &mut CollapsingState,
1048 collapsible: bool,
1049) -> TitleBar {
1050 let inner_response = ui.horizontal(|ui| {
1051 let height = ui
1052 .fonts(|fonts| title.font_height(fonts, ui.style()))
1053 .max(ui.spacing().interact_size.y);
1054 ui.set_min_height(height);
1055
1056 let item_spacing = ui.spacing().item_spacing;
1057 let button_size = Vec2::splat(ui.spacing().icon_width);
1058
1059 let pad = (height - button_size.y) / 2.0; if collapsible {
1062 ui.add_space(pad);
1063 collapsing.show_default_button_with_size(ui, button_size);
1064 }
1065
1066 let title_galley = title.into_galley(
1067 ui,
1068 Some(crate::TextWrapMode::Extend),
1069 f32::INFINITY,
1070 TextStyle::Heading,
1071 );
1072
1073 let minimum_width = if collapsible || show_close_button {
1074 2.0 * (pad + button_size.x + item_spacing.x) + title_galley.size().x
1076 } else {
1077 pad + title_galley.size().x + pad
1078 };
1079 let min_rect = Rect::from_min_size(ui.min_rect().min, vec2(minimum_width, height));
1080 let id = ui.advance_cursor_after_rect(min_rect);
1081
1082 TitleBar {
1083 id,
1084 title_galley,
1085 min_rect,
1086 rect: Rect::NAN, }
1088 });
1089
1090 let title_bar = inner_response.inner;
1091 let rect = inner_response.response.rect;
1092
1093 TitleBar { rect, ..title_bar }
1094}
1095
1096impl TitleBar {
1097 fn ui(
1112 mut self,
1113 ui: &mut Ui,
1114 outer_rect: Rect,
1115 content_response: &Option<Response>,
1116 open: Option<&mut bool>,
1117 collapsing: &mut CollapsingState,
1118 collapsible: bool,
1119 ) {
1120 if let Some(content_response) = &content_response {
1121 self.rect.max.x = self.rect.max.x.max(content_response.rect.max.x);
1123 }
1124
1125 if let Some(open) = open {
1126 if self.close_button_ui(ui).clicked() {
1128 *open = false;
1129 }
1130 }
1131
1132 let full_top_rect = Rect::from_x_y_ranges(self.rect.x_range(), self.min_rect.y_range());
1133 let text_pos =
1134 emath::align::center_size_in_rect(self.title_galley.size(), full_top_rect).left_top();
1135 let text_pos = text_pos - self.title_galley.rect.min.to_vec2();
1136 let text_pos = text_pos - 1.5 * Vec2::Y; ui.painter().galley(
1138 text_pos,
1139 self.title_galley.clone(),
1140 ui.visuals().text_color(),
1141 );
1142
1143 if let Some(content_response) = &content_response {
1144 let y = content_response.rect.top();
1146 let stroke = ui.visuals().widgets.noninteractive.bg_stroke;
1148 let x_range = outer_rect.x_range().shrink(0.1);
1151 ui.painter().hline(x_range, y, stroke);
1152 }
1153
1154 let double_click_rect = self.rect.shrink2(vec2(32.0, 0.0));
1156
1157 if ui
1158 .interact(double_click_rect, self.id, Sense::click())
1159 .double_clicked()
1160 && collapsible
1161 {
1162 collapsing.toggle(ui);
1163 }
1164 }
1165
1166 fn close_button_ui(&self, ui: &mut Ui) -> Response {
1172 let button_size = Vec2::splat(ui.spacing().icon_width);
1173 let pad = (self.rect.height() - button_size.y) / 2.0; let button_rect = Rect::from_min_size(
1175 pos2(
1176 self.rect.right() - pad - button_size.x,
1177 self.rect.center().y - 0.5 * button_size.y,
1178 ),
1179 button_size,
1180 );
1181
1182 close_button(ui, button_rect)
1183 }
1184}
1185
1186fn close_button(ui: &mut Ui, rect: Rect) -> Response {
1197 let close_id = ui.auto_id_with("window_close_button");
1198 let response = ui.interact(rect, close_id, Sense::click());
1199 ui.expand_to_include_rect(response.rect);
1200
1201 let visuals = ui.style().interact(&response);
1202 let rect = rect.shrink(2.0).expand(visuals.expansion);
1203 let stroke = visuals.fg_stroke;
1204 ui.painter() .line_segment([rect.left_top(), rect.right_bottom()], stroke);
1206 ui.painter() .line_segment([rect.right_top(), rect.left_bottom()], stroke);
1208 response
1209}