Skip to main content

egui/containers/
window.rs

1// WARNING: the code in here is horrible. It is a behemoth that needs breaking up into simpler parts.
2
3use emath::GuiRounding as _;
4use epaint::CornerRadiusF32;
5
6use crate::collapsing_header::CollapsingState;
7use crate::*;
8
9use super::scroll_area::{DragScroll, ScrollBarVisibility, ScrollSource};
10use super::{Area, Frame, Resize, ScrollArea, area, resize};
11
12/// Where the user can drag to move a [`Window`].
13///
14/// See [`Window::drag_area`].
15#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
16#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
17pub enum WindowDrag {
18    /// Window cannot be moved by dragging.
19    ///
20    /// [`Window::movable(false)`](Window::movable) forces this regardless of
21    /// what was passed to [`Window::drag_area`].
22    Off,
23
24    /// The user can drag the window from anywhere on its surface.
25    ///
26    /// Good for touch screens, but can interfere with selecting / dragging
27    /// content inside the window when used with a mouse.
28    Anywhere,
29
30    /// Only the title bar accepts the move-drag gesture.
31    ///
32    /// Windows without a title bar (see [`Window::title_bar`]) silently fall
33    /// back to [`Self::Anywhere`] — otherwise they'd be unmovable.
34    TitleBar,
35
36    /// [`Self::Anywhere`] when a touch screen is detected (see
37    /// [`crate::InputState::has_touch_screen`]); [`Self::TitleBar`] otherwise.
38    /// The recommended default.
39    #[default]
40    OnTouch,
41}
42
43impl WindowDrag {
44    /// Resolve [`Self::OnTouch`] to either [`Self::Anywhere`] or [`Self::TitleBar`]
45    /// based on whether a touch screen was detected.
46    fn resolve(self, ctx: &Context) -> Self {
47        match self {
48            Self::OnTouch => {
49                if ctx.input(|i| i.has_touch_screen()) {
50                    Self::Anywhere
51                } else {
52                    Self::TitleBar
53                }
54            }
55            other => other,
56        }
57    }
58}
59
60/// Builder for a floating window which can be dragged, closed, collapsed, resized and scrolled (off by default).
61///
62/// You can customize:
63/// * title
64/// * default, minimum, maximum and/or fixed size, collapsed/expanded
65/// * if the window has a scroll area (off by default)
66/// * if the window can be collapsed (minimized) to just the title bar (yes, by default)
67/// * if there should be a close button (none by default)
68///
69/// ```
70/// # egui::__run_test_ctx(|ctx| {
71/// egui::Window::new("My Window").show(ctx, |ui| {
72///    ui.label("Hello World!");
73/// });
74/// # });
75/// ```
76///
77/// The previous rectangle used by this window can be obtained through [`crate::Memory::area_rect()`].
78///
79/// Note that this is NOT a native OS window.
80/// To create a new native OS window, use [`crate::Context::show_viewport_deferred`].
81#[must_use = "You should call .show()"]
82pub struct Window<'a> {
83    title: Atoms<'a>,
84    open: Option<&'a mut bool>,
85    area: Area,
86    frame: Option<Frame>,
87    title_frame: Option<Frame>,
88    resize: Resize,
89    scroll: ScrollArea,
90    collapsible: bool,
91    default_open: bool,
92    with_title_bar: bool,
93    fade_out: bool,
94    auto_sized: bool,
95    drag_area: WindowDrag,
96}
97
98impl<'a> Window<'a> {
99    /// The window title is used as a unique [`Id`] and must be unique, and should not change.
100    /// This is true even if you disable the title bar with `.title_bar(false)`.
101    /// If you need a changing title, you must call `window.id(…)` with a fixed id.
102    pub fn new(title: impl IntoAtoms<'a>) -> Self {
103        let title: Atoms<'_> = title.into_atoms();
104        let area = Area::new(Id::new(title.text())).kind(UiKind::Window);
105        Self {
106            title,
107            open: None,
108            area,
109            frame: None,
110            title_frame: None,
111            resize: Resize::default()
112                .with_stroke(false)
113                .min_size([96.0, 32.0])
114                .default_size([340.0, 420.0]), // Default outer size of a window (includes frame margins, stroke, and title bar)
115            scroll: ScrollArea::neither().auto_shrink(false).content_margin(0.0),
116            collapsible: true,
117            default_open: true,
118            with_title_bar: true,
119            fade_out: true,
120            auto_sized: false,
121            drag_area: WindowDrag::default(),
122        }
123    }
124
125    /// Construct a [`Window`] that follows the given viewport.
126    pub fn from_viewport(id: ViewportId, viewport: ViewportBuilder) -> Self {
127        let ViewportBuilder {
128            title,
129            app_id,
130            inner_size,
131            min_inner_size,
132            max_inner_size,
133            resizable,
134            decorations,
135            title_shown,
136            minimize_button,
137            .. // A lot of things not implemented yet
138        } = viewport;
139
140        let mut window = Self::new(title.or(app_id).unwrap_or_else(String::new)).id(Id::new(id));
141
142        if let Some(inner_size) = inner_size {
143            window = window.default_size(inner_size);
144        }
145        if let Some(min_inner_size) = min_inner_size {
146            window = window.min_size(min_inner_size);
147        }
148        if let Some(max_inner_size) = max_inner_size {
149            window = window.max_size(max_inner_size);
150        }
151        if let Some(resizable) = resizable {
152            window = window.resizable(resizable);
153        }
154        window = window.title_bar(decorations.unwrap_or(true) && title_shown.unwrap_or(true));
155        window = window.collapsible(minimize_button.unwrap_or(true));
156
157        window
158    }
159
160    /// Assign a unique id to the Window. Required if the title changes, or is shared with another window.
161    #[inline]
162    pub fn id(mut self, id: Id) -> Self {
163        self.area = self.area.id(id);
164        self
165    }
166
167    /// Call this to add a close-button to the window title bar.
168    ///
169    /// * If `*open == false`, the window will not be visible.
170    /// * If `*open == true`, the window will have a close button.
171    /// * If the close button is pressed, `*open` will be set to `false`.
172    #[inline]
173    pub fn open(mut self, open: &'a mut bool) -> Self {
174        self.open = Some(open);
175        self
176    }
177
178    /// If `false` the window will be grayed out and non-interactive.
179    #[inline]
180    pub fn enabled(mut self, enabled: bool) -> Self {
181        self.area = self.area.enabled(enabled);
182        self
183    }
184
185    /// If false, clicks goes straight through to what is behind us.
186    ///
187    /// Can be used for semi-invisible areas that the user should be able to click through.
188    ///
189    /// Default: `true`.
190    #[inline]
191    pub fn interactable(mut self, interactable: bool) -> Self {
192        self.area = self.area.interactable(interactable);
193        self
194    }
195
196    /// If `false` the window will be immovable.
197    ///
198    /// If `true`, you can move the window by dragging it.
199    /// Where you can drag to move the window is determined by [`Self::drag_area`].
200    #[inline]
201    pub fn movable(mut self, movable: bool) -> Self {
202        self.area = self.area.movable(movable);
203        self
204    }
205
206    /// Where the user can grab the window to move it.
207    ///
208    /// Defaults to [`WindowDrag::OnTouch`]: drag anywhere on touch screens,
209    /// title bar only otherwise. See [`WindowDrag`] for details.
210    ///
211    /// [`Self::movable(false)`](Self::movable) forces [`WindowDrag::Off`]
212    /// regardless of this setting. Windows without a title bar (see
213    /// [`Self::title_bar`]) fall back to [`WindowDrag::Anywhere`].
214    #[inline]
215    pub fn drag_area(mut self, drag_area: WindowDrag) -> Self {
216        self.drag_area = drag_area;
217        self
218    }
219
220    /// `order(Order::Foreground)` for a Window that should always be on top
221    #[inline]
222    pub fn order(mut self, order: Order) -> Self {
223        self.area = self.area.order(order);
224        self
225    }
226
227    /// If `true`, quickly fade in the `Window` when it first appears.
228    ///
229    /// Default: `true`.
230    #[inline]
231    pub fn fade_in(mut self, fade_in: bool) -> Self {
232        self.area = self.area.fade_in(fade_in);
233        self
234    }
235
236    /// If `true`, quickly fade out the `Window` when it closes.
237    ///
238    /// This only works if you use [`Self::open`] to close the window.
239    ///
240    /// Default: `true`.
241    #[inline]
242    pub fn fade_out(mut self, fade_out: bool) -> Self {
243        self.fade_out = fade_out;
244        self
245    }
246
247    /// Usage: `Window::new(…).mutate(|w| w.resize = w.resize.auto_expand_width(true))`
248    // TODO(emilk): I'm not sure this is a good interface for this.
249    #[inline]
250    pub fn mutate(mut self, mutate: impl Fn(&mut Self)) -> Self {
251        mutate(&mut self);
252        self
253    }
254
255    /// Usage: `Window::new(…).resize(|r| r.auto_expand_width(true))`
256    // TODO(emilk): I'm not sure this is a good interface for this.
257    #[inline]
258    pub fn resize(mut self, mutate: impl Fn(Resize) -> Resize) -> Self {
259        self.resize = mutate(self.resize);
260        self
261    }
262
263    /// Change the background color, margins, etc.
264    #[inline]
265    pub fn frame(mut self, frame: Frame) -> Self {
266        self.frame = Some(frame);
267        self
268    }
269
270    /// Change the background color, margins, etc. of the title
271    #[inline]
272    pub fn title_frame(mut self, frame: Frame) -> Self {
273        self.title_frame = Some(frame);
274        self
275    }
276
277    /// Set minimum width of the window.
278    #[inline]
279    pub fn min_width(mut self, min_width: f32) -> Self {
280        self.resize = self.resize.min_width(min_width);
281        self
282    }
283
284    /// Set minimum height of the window.
285    #[inline]
286    pub fn min_height(mut self, min_height: f32) -> Self {
287        self.resize = self.resize.min_height(min_height);
288        self
289    }
290
291    /// Set minimum size of the window, equivalent to calling both `min_width` and `min_height`.
292    ///
293    /// The size refers to the *outer* window size, including the frame's `inner_margin`,
294    /// `outer_margin`, `stroke`, and the title bar.
295    #[inline]
296    pub fn min_size(mut self, min_size: impl Into<Vec2>) -> Self {
297        self.resize = self.resize.min_size(min_size);
298        self
299    }
300
301    /// Set maximum width of the window.
302    #[inline]
303    pub fn max_width(mut self, max_width: f32) -> Self {
304        self.resize = self.resize.max_width(max_width);
305        self
306    }
307
308    /// Set maximum height of the window.
309    #[inline]
310    pub fn max_height(mut self, max_height: f32) -> Self {
311        self.resize = self.resize.max_height(max_height);
312        self
313    }
314
315    /// Set maximum size of the window, equivalent to calling both `max_width` and `max_height`.
316    ///
317    /// The size refers to the *outer* window size, including the frame's `inner_margin`,
318    /// `outer_margin`, `stroke`, and the title bar.
319    #[inline]
320    pub fn max_size(mut self, max_size: impl Into<Vec2>) -> Self {
321        self.resize = self.resize.max_size(max_size);
322        self
323    }
324
325    /// Set current position of the window.
326    /// If the window is movable it is up to you to keep track of where it moved to!
327    #[inline]
328    pub fn current_pos(mut self, current_pos: impl Into<Pos2>) -> Self {
329        self.area = self.area.current_pos(current_pos);
330        self
331    }
332
333    /// Set initial position of the window.
334    #[inline]
335    pub fn default_pos(mut self, default_pos: impl Into<Pos2>) -> Self {
336        self.area = self.area.default_pos(default_pos);
337        self
338    }
339
340    /// Sets the window position and prevents it from being dragged around.
341    #[inline]
342    pub fn fixed_pos(mut self, pos: impl Into<Pos2>) -> Self {
343        self.area = self.area.fixed_pos(pos);
344        self
345    }
346
347    /// Constrains this window to [`Context::content_rect`].
348    ///
349    /// To change the area to constrain to, use [`Self::constrain_to`].
350    ///
351    /// Default: `true`.
352    #[inline]
353    pub fn constrain(mut self, constrain: bool) -> Self {
354        self.area = self.area.constrain(constrain);
355        self
356    }
357
358    /// Constrain the movement of the window to the given rectangle.
359    ///
360    /// For instance: `.constrain_to(ctx.content_rect())`.
361    #[inline]
362    pub fn constrain_to(mut self, constrain_rect: Rect) -> Self {
363        self.area = self.area.constrain_to(constrain_rect);
364        self
365    }
366
367    /// Where the "root" of the window is.
368    ///
369    /// For instance, if you set this to [`Align2::RIGHT_TOP`]
370    /// then [`Self::fixed_pos`] will set the position of the right-top
371    /// corner of the window.
372    ///
373    /// Default: [`Align2::LEFT_TOP`].
374    #[inline]
375    pub fn pivot(mut self, pivot: Align2) -> Self {
376        self.area = self.area.pivot(pivot);
377        self
378    }
379
380    /// Set anchor and distance.
381    ///
382    /// An anchor of `Align2::RIGHT_TOP` means "put the right-top corner of the window
383    /// in the right-top corner of the screen".
384    ///
385    /// The offset is added to the position, so e.g. an offset of `[-5.0, 5.0]`
386    /// would move the window left and down from the given anchor.
387    ///
388    /// Anchoring also makes the window immovable.
389    ///
390    /// It is an error to set both an anchor and a position.
391    #[inline]
392    pub fn anchor(mut self, align: Align2, offset: impl Into<Vec2>) -> Self {
393        self.area = self.area.anchor(align, offset);
394        self
395    }
396
397    /// Set initial collapsed state of the window
398    #[inline]
399    pub fn default_open(mut self, default_open: bool) -> Self {
400        self.default_open = default_open;
401        self
402    }
403
404    /// Set initial size of the window.
405    ///
406    /// The size refers to the *outer* window size, including frame margins, stroke,
407    /// and the title bar.
408    #[inline]
409    pub fn default_size(mut self, default_size: impl Into<Vec2>) -> Self {
410        let default_size: Vec2 = default_size.into();
411        self.resize = self.resize.default_size(default_size);
412        self.area = self.area.default_size(default_size);
413        self
414    }
415
416    /// Set initial width of the window.
417    #[inline]
418    pub fn default_width(mut self, default_width: f32) -> Self {
419        self.resize = self.resize.default_width(default_width);
420        self.area = self.area.default_width(default_width);
421        self
422    }
423
424    /// Set initial height of the window.
425    #[inline]
426    pub fn default_height(mut self, default_height: f32) -> Self {
427        self.resize = self.resize.default_height(default_height);
428        self.area = self.area.default_height(default_height);
429        self
430    }
431
432    /// Sets the window size and prevents it from being resized by dragging its edges.
433    ///
434    /// The size refers to the *outer* window size, including the frame's `inner_margin`,
435    /// `outer_margin`, `stroke`, and the title bar.
436    #[inline]
437    pub fn fixed_size(mut self, size: impl Into<Vec2>) -> Self {
438        self.resize = self.resize.fixed_size(size);
439        self
440    }
441
442    /// Set initial position and size of the window.
443    pub fn default_rect(self, rect: Rect) -> Self {
444        self.default_pos(rect.min).default_size(rect.size())
445    }
446
447    /// Sets the window pos and size and prevents it from being moved and resized by dragging its edges.
448    pub fn fixed_rect(self, rect: Rect) -> Self {
449        self.fixed_pos(rect.min).fixed_size(rect.size())
450    }
451
452    /// Can the user resize the window by dragging its edges?
453    ///
454    /// Note that even if you set this to `false` the window may still auto-resize.
455    ///
456    /// You can set the window to only be resizable in one direction by using
457    /// e.g. `[true, false]` as the argument,
458    /// making the window only resizable in the x-direction.
459    ///
460    /// Default is `true`.
461    #[inline]
462    pub fn resizable(mut self, resizable: impl Into<Vec2b>) -> Self {
463        let resizable = resizable.into();
464        self.resize = self.resize.resizable(resizable);
465        self
466    }
467
468    /// Can the window be collapsed by clicking on its title?
469    #[inline]
470    pub fn collapsible(mut self, collapsible: bool) -> Self {
471        self.collapsible = collapsible;
472        self
473    }
474
475    /// Show title bar on top of the window?
476    /// If `false`, the window will not be collapsible nor have a close-button.
477    #[inline]
478    pub fn title_bar(mut self, title_bar: bool) -> Self {
479        self.with_title_bar = title_bar;
480        self
481    }
482
483    /// Not resizable, just takes the size of its contents.
484    /// Also disabled scrolling.
485    /// Text will not wrap, but will instead make your window width expand.
486    #[inline]
487    pub fn auto_sized(mut self) -> Self {
488        self.resize = self.resize.auto_sized();
489        self.scroll = ScrollArea::neither();
490        self.auto_sized = true;
491        self
492    }
493
494    /// Enable/disable horizontal/vertical scrolling. `false` by default.
495    ///
496    /// You can pass in `false`, `true`, `[false, true]` etc.
497    #[inline]
498    pub fn scroll(mut self, scroll: impl Into<Vec2b>) -> Self {
499        self.scroll = self.scroll.scroll(scroll);
500        self
501    }
502
503    /// Enable/disable horizontal scrolling. `false` by default.
504    #[inline]
505    pub fn hscroll(mut self, hscroll: bool) -> Self {
506        self.scroll = self.scroll.hscroll(hscroll);
507        self
508    }
509
510    /// Enable/disable vertical scrolling. `false` by default.
511    #[inline]
512    pub fn vscroll(mut self, vscroll: bool) -> Self {
513        self.scroll = self.scroll.vscroll(vscroll);
514        self
515    }
516
517    /// Controls scrolling the window by dragging the contents with the pointer.
518    ///
519    /// Defaults to [`DragScroll::OnTouch`] — only active when a touch screen is detected.
520    ///
521    /// See [`ScrollArea::scroll_source`] and [`DragScroll`] for more.
522    #[inline]
523    pub fn drag_to_scroll(mut self, drag_to_scroll: DragScroll) -> Self {
524        self.scroll = self.scroll.scroll_source(ScrollSource {
525            drag: drag_to_scroll,
526            ..Default::default()
527        });
528        self
529    }
530
531    /// Sets the [`ScrollBarVisibility`] of the window.
532    #[inline]
533    pub fn scroll_bar_visibility(mut self, visibility: ScrollBarVisibility) -> Self {
534        self.scroll = self.scroll.scroll_bar_visibility(visibility);
535        self
536    }
537}
538
539impl Window<'_> {
540    /// Returns `None` if the window is not open (if [`Window::open`] was called with `&mut false`).
541    /// Returns `Some(InnerResponse { inner: None })` if the window is collapsed.
542    #[inline]
543    pub fn show<R>(
544        self,
545        ctx: &Context,
546        add_contents: impl FnOnce(&mut Ui) -> R,
547    ) -> Option<InnerResponse<Option<R>>> {
548        self.show_dyn(ctx, Box::new(add_contents))
549    }
550
551    fn show_dyn<'c, R>(
552        self,
553        ctx: &Context,
554        add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
555    ) -> Option<InnerResponse<Option<R>>> {
556        let Window {
557            title,
558            mut open,
559            area,
560            frame,
561            title_frame,
562            resize,
563            scroll,
564            collapsible,
565            default_open,
566            with_title_bar,
567            fade_out,
568            auto_sized,
569            drag_area: drag_area_setting,
570        } = self;
571
572        // `Window::movable(false)` (and `Area::movable(false)`) and
573        // `WindowDrag::Off` both mean "this window cannot be moved by
574        // dragging". Without a title bar, `TitleBar` mode would leave the
575        // window unmovable, so silently fall back to drag-anywhere instead.
576        let effective_drag = if !area.is_movable() || drag_area_setting == WindowDrag::Off {
577            WindowDrag::Off
578        } else if !with_title_bar {
579            WindowDrag::Anywhere
580        } else {
581            drag_area_setting.resolve(ctx)
582        };
583
584        // Make the area itself agree: keep its movable flag in sync with
585        // the resolved drag mode so resize behavior and `Area::begin`'s
586        // drag-from-anywhere handling don't disagree with the title-bar
587        // path. (Builder order shouldn't matter — `.drag_area(Off)` after
588        // `.movable(true)` and vice versa both end up here.)
589        let area = if effective_drag == WindowDrag::Off {
590            area.movable(false)
591        } else {
592            area
593        };
594
595        // Apply the previous frame's title-bar drag _before_ `Area::begin`
596        // loads the state. We can't apply it inside the content closure because
597        // `Area::end` writes the locally-captured `AreaState` back, overwriting
598        // any in-frame mutation.
599        //
600        // We deliberately leave `Area` with its normal `Sense::DRAG`: that way
601        // the area's widget still absorbs drag hit-tests over the body, so the
602        // resize-edge widgets aren't picked as the "closest drag" target when
603        // hovering anywhere in the window. The drag-from-anywhere move that
604        // `Area::begin` would then apply is undone right after `begin` for
605        // `WindowDrag::TitleBar`.
606        let title_drag_mode = effective_drag == WindowDrag::TitleBar;
607        let pivot_pos_before_begin = if title_drag_mode {
608            if let Some(resp) = ctx.read_response(area.id.with("__title_click"))
609                && resp.dragged()
610            {
611                let delta = ctx.input(|i| i.pointer.delta());
612                if delta != Vec2::ZERO {
613                    ctx.memory_mut(|mem| {
614                        if let Some(state) = mem.areas_mut().get_mut(area.id)
615                            && let Some(pivot_pos) = state.pivot_pos.as_mut()
616                        {
617                            *pivot_pos += delta;
618                        }
619                    });
620                }
621            }
622            area::AreaState::load(ctx, area.id).and_then(|s| s.pivot_pos)
623        } else {
624            None
625        };
626
627        let style = ctx.global_style();
628
629        // We get or create the Frame for the title and content
630        let window_frame = frame.unwrap_or_else(|| Frame::window(&style));
631        let window_title_frame = title_frame.unwrap_or(window_frame);
632
633        // We apply the window margin by using the `ScrollArea::content_margin`.
634        let window_content_margin = window_frame.inner_margin;
635        let window_frame = window_frame.inner_margin(0.0);
636
637        let is_explicitly_closed = matches!(open, Some(false));
638        let is_open = !is_explicitly_closed || ctx.memory(|mem| mem.everything_is_visible());
639        let opacity = ctx.animate_bool_with_easing(
640            area.id.with("fade-out"),
641            is_open,
642            emath::easing::cubic_out,
643        );
644        if opacity <= 0.0 {
645            return None;
646        }
647
648        let area_id = area.id;
649        let area_layer_id = area.layer();
650        let resize_id = area_id.with("resize");
651        let mut collapsing =
652            CollapsingState::load_with_default_open(ctx, area_id.with("collapsing"), default_open);
653
654        let is_collapsed = with_title_bar && !collapsing.is_open();
655        let possible = PossibleInteractions::new(&area, &resize, is_collapsed);
656
657        let resize = resize.resizable(false); // We resize it manually
658        let mut resize = resize.id(resize_id);
659
660        let on_top = Some(area_layer_id) == ctx.top_layer_id();
661        let mut area = area.begin(ctx);
662
663        // Title-bar-drag mode: throw away any drag-from-anywhere movement
664        // `Area::begin` may have applied. The title-bar pre-begin step above
665        // already accounted for the title drag. We then re-run the same
666        // constrain+round step `Area::begin` does so the title-bar drag
667        // can't escape `constrain_rect` or reintroduce sub-pixel jitter.
668        if let Some(pre_begin_pivot) = pivot_pos_before_begin {
669            let constrain = area.constrain();
670            let constrain_rect = area.constrain_rect();
671            let state = area.state_mut();
672            state.pivot_pos = Some(pre_begin_pivot);
673            if constrain {
674                state.set_left_top_pos(
675                    Context::constrain_window_rect_to_area(state.rect(), constrain_rect).min,
676                );
677            }
678            state.set_left_top_pos(area::round_area_position(ctx, state.left_top_pos()));
679        }
680
681        area.with_widget_info(|| {
682            WidgetInfo::labeled(
683                WidgetType::Window,
684                true,
685                title.text().as_deref().unwrap_or(""),
686            )
687        });
688
689        {
690            // Prevent window from becoming larger than the constrain rect.
691            // `resize.max_size` is still in outer-window coordinates here, matching `constrain_rect`.
692            let constrain_rect = area.constrain_rect();
693            let max_width = constrain_rect.width();
694            let max_height = constrain_rect.height();
695            resize.max_size.x = resize.max_size.x.min(max_width);
696            resize.max_size.y = resize.max_size.y.min(max_height);
697        }
698
699        // The user-supplied min/max/default sizes on `Window` refer to the *outer* window size
700        // (the total footprint, including frame margins, stroke, and title bar). `Resize` sizes
701        // the title bar + inner content area, so we subtract the extra frame margin (the part
702        // outside of `Resize`).
703        {
704            let frame_margin = window_frame.total_margin().sum();
705            resize.min_size = (resize.min_size - frame_margin).at_least(Vec2::ZERO);
706            resize.max_size = (resize.max_size - frame_margin).at_least(Vec2::ZERO);
707            resize.default_size = (resize.default_size - frame_margin).at_least(Vec2::ZERO);
708        }
709
710        let mut area_content_ui = area.content_ui(ctx);
711
712        if is_open {
713            // `Area` already takes care of fade-in animations,
714            // so we only need to handle fade-out animations here.
715        } else if fade_out {
716            area_content_ui.multiply_opacity(opacity);
717        }
718
719        let content_inner = {
720            let outer_response = window_frame.show(&mut area_content_ui, |ui| {
721                resize.show(ui, |ui| {
722                    if with_title_bar {
723                        title_ui(
724                            ui,
725                            title,
726                            window_title_frame,
727                            &mut collapsing,
728                            collapsible,
729                            on_top,
730                            open.as_deref_mut(),
731                            auto_sized,
732                            effective_drag == WindowDrag::TitleBar,
733                            area_id,
734                        );
735                    }
736                    collapsing
737                        .show_body_unindented(ui, |ui| {
738                            if scroll.is_any_scroll_enabled() {
739                                scroll
740                                    .content_margin(window_content_margin)
741                                    .show(ui, add_contents)
742                                    .inner
743                            } else {
744                                crate::Frame::NONE
745                                    .inner_margin(window_content_margin)
746                                    .show(ui, add_contents)
747                                    .inner
748                            }
749                        })
750                        .map(|inner| inner.inner)
751                })
752            });
753
754            let outer_rect = outer_response.response.rect;
755
756            // Do resize interaction _again_, to move their widget rectangles on TOP of the rest of the window.
757            let resize_interaction = do_resize_interaction(
758                ctx,
759                possible,
760                area.id(),
761                area_layer_id,
762                outer_rect,
763                window_frame,
764            );
765
766            paint_resize_corner(
767                &area_content_ui,
768                &possible,
769                outer_rect,
770                &window_frame,
771                resize_interaction,
772            );
773
774            {
775                let margins = window_frame.total_margin().sum();
776
777                resize_response(
778                    resize_interaction,
779                    ctx,
780                    margins,
781                    area_layer_id,
782                    &mut area,
783                    resize_id,
784                );
785            }
786            // END FRAME --------------------------------
787
788            collapsing.store(ctx);
789
790            paint_frame_interaction(&area_content_ui, outer_rect, resize_interaction);
791
792            outer_response.inner
793        };
794
795        let full_response = area.end(ctx, area_content_ui);
796
797        if full_response.should_close()
798            && let Some(open) = open
799        {
800            *open = false;
801        }
802
803        let inner_response = InnerResponse {
804            inner: content_inner,
805            response: full_response,
806        };
807        Some(inner_response)
808    }
809}
810
811fn paint_resize_corner(
812    ui: &Ui,
813    possible: &PossibleInteractions,
814    outer_rect: Rect,
815    window_frame: &Frame,
816    i: ResizeInteraction,
817) {
818    let cr = window_frame.corner_radius;
819
820    let (corner, radius, corner_response) = if possible.resize_right && possible.resize_bottom {
821        (Align2::RIGHT_BOTTOM, cr.se, i.right & i.bottom)
822    } else if possible.resize_left && possible.resize_bottom {
823        (Align2::LEFT_BOTTOM, cr.sw, i.left & i.bottom)
824    } else if possible.resize_left && possible.resize_top {
825        (Align2::LEFT_TOP, cr.nw, i.left & i.top)
826    } else if possible.resize_right && possible.resize_top {
827        (Align2::RIGHT_TOP, cr.ne, i.right & i.top)
828    } else {
829        // We're not in two directions, but it is still nice to tell the user
830        // we're resizable by painting the resize corner in the expected place
831        // (i.e. for windows only resizable in one direction):
832        if possible.resize_right || possible.resize_bottom {
833            (Align2::RIGHT_BOTTOM, cr.se, i.right & i.bottom)
834        } else if possible.resize_left || possible.resize_bottom {
835            (Align2::LEFT_BOTTOM, cr.sw, i.left & i.bottom)
836        } else if possible.resize_left || possible.resize_top {
837            (Align2::LEFT_TOP, cr.nw, i.left & i.top)
838        } else if possible.resize_right || possible.resize_top {
839            (Align2::RIGHT_TOP, cr.ne, i.right & i.top)
840        } else {
841            return;
842        }
843    };
844
845    // Adjust the corner offset to accommodate for window rounding
846    let radius = radius as f32;
847    let offset =
848        ((2.0_f32.sqrt() * (1.0 + radius) - radius) * 45.0_f32.to_radians().cos()).max(2.0);
849
850    let stroke = if corner_response.drag {
851        ui.visuals().widgets.active.fg_stroke
852    } else if corner_response.hover {
853        ui.visuals().widgets.hovered.fg_stroke
854    } else {
855        window_frame.stroke
856    };
857
858    let fill_rect = outer_rect.shrink(window_frame.stroke.width);
859    let corner_size = Vec2::splat(ui.visuals().resize_corner_size);
860    let corner_rect = corner.align_size_within_rect(corner_size, fill_rect);
861    let corner_rect = corner_rect.translate(-offset * corner.to_sign()); // move away from corner
862    crate::resize::paint_resize_corner_with_style(ui, &corner_rect, stroke.color, corner);
863}
864
865// ----------------------------------------------------------------------------
866
867/// Which sides can be resized?
868#[derive(Clone, Copy, Debug)]
869struct PossibleInteractions {
870    // Which sides can we drag to resize or move?
871    resize_left: bool,
872    resize_right: bool,
873    resize_top: bool,
874    resize_bottom: bool,
875}
876
877impl PossibleInteractions {
878    fn new(area: &Area, resize: &Resize, is_collapsed: bool) -> Self {
879        let movable = area.is_enabled() && area.is_movable();
880        let resizable = resize
881            .is_resizable()
882            .and(area.is_enabled() && !is_collapsed);
883        let pivot = area.get_pivot();
884        Self {
885            resize_left: resizable.x && (movable || pivot.x() != Align::LEFT),
886            resize_right: resizable.x && (movable || pivot.x() != Align::RIGHT),
887            resize_top: resizable.y && (movable || pivot.y() != Align::TOP),
888            resize_bottom: resizable.y && (movable || pivot.y() != Align::BOTTOM),
889        }
890    }
891
892    pub fn resizable(&self) -> bool {
893        self.resize_left || self.resize_right || self.resize_top || self.resize_bottom
894    }
895}
896
897/// Resizing the window edges.
898#[derive(Clone, Copy, Debug)]
899struct ResizeInteraction {
900    /// Outer rect (outside the stroke)
901    outer_rect: Rect,
902
903    window_frame: Frame,
904
905    left: SideResponse,
906    right: SideResponse,
907    top: SideResponse,
908    bottom: SideResponse,
909}
910
911/// A miniature version of `Response`, for each side of the window.
912#[derive(Clone, Copy, Debug, Default)]
913struct SideResponse {
914    hover: bool,
915    drag: bool,
916}
917
918impl SideResponse {
919    pub fn any(&self) -> bool {
920        self.hover || self.drag
921    }
922}
923
924impl core::ops::BitAnd for SideResponse {
925    type Output = Self;
926
927    fn bitand(self, rhs: Self) -> Self::Output {
928        Self {
929            hover: self.hover && rhs.hover,
930            drag: self.drag && rhs.drag,
931        }
932    }
933}
934
935impl core::ops::BitOrAssign for SideResponse {
936    fn bitor_assign(&mut self, rhs: Self) {
937        *self = Self {
938            hover: self.hover || rhs.hover,
939            drag: self.drag || rhs.drag,
940        };
941    }
942}
943
944impl ResizeInteraction {
945    pub fn set_cursor(&self, ctx: &Context) {
946        let left = self.left.any();
947        let right = self.right.any();
948        let top = self.top.any();
949        let bottom = self.bottom.any();
950
951        // TODO(emilk): use one-sided cursors for when we reached the min/max size.
952        if (left && top) || (right && bottom) {
953            ctx.set_cursor_icon(CursorIcon::ResizeNwSe);
954        } else if (right && top) || (left && bottom) {
955            ctx.set_cursor_icon(CursorIcon::ResizeNeSw);
956        } else if left || right {
957            ctx.set_cursor_icon(CursorIcon::ResizeHorizontal);
958        } else if bottom || top {
959            ctx.set_cursor_icon(CursorIcon::ResizeVertical);
960        }
961    }
962
963    pub fn any_hovered(&self) -> bool {
964        self.left.hover || self.right.hover || self.top.hover || self.bottom.hover
965    }
966
967    pub fn any_dragged(&self) -> bool {
968        self.left.drag || self.right.drag || self.top.drag || self.bottom.drag
969    }
970}
971
972fn resize_response(
973    resize_interaction: ResizeInteraction,
974    ctx: &Context,
975    margins: Vec2,
976    area_layer_id: LayerId,
977    area: &mut area::Prepared,
978    resize_id: Id,
979) {
980    let Some(mut new_rect) = move_and_resize_window(ctx, resize_id, &resize_interaction) else {
981        return;
982    };
983
984    if area.constrain() {
985        new_rect = Context::constrain_window_rect_to_area(new_rect, area.constrain_rect());
986    }
987
988    // TODO(emilk): add this to a Window state instead as a command "move here next frame"
989    area.state_mut().set_left_top_pos(new_rect.left_top());
990
991    if resize_interaction.any_dragged()
992        && let Some(mut state) = resize::State::load(ctx, resize_id)
993    {
994        state.requested_size = Some(new_rect.size() - margins);
995        state.store(ctx, resize_id);
996    }
997
998    ctx.memory_mut(|mem| mem.areas_mut().move_to_top(area_layer_id));
999}
1000
1001/// Acts on outer rect (outside the stroke)
1002fn move_and_resize_window(ctx: &Context, id: Id, interaction: &ResizeInteraction) -> Option<Rect> {
1003    // Used to prevent drift
1004    let rect_at_start_of_drag_id = id.with("window_rect_at_drag_start");
1005
1006    if !interaction.any_dragged() {
1007        ctx.data_mut(|data| {
1008            data.remove::<Rect>(rect_at_start_of_drag_id);
1009        });
1010        return None;
1011    }
1012
1013    let total_drag_delta = ctx.input(|i| i.pointer.total_drag_delta())?;
1014
1015    let rect_at_start_of_drag = ctx.data_mut(|data| {
1016        *data.get_temp_mut_or::<Rect>(rect_at_start_of_drag_id, interaction.outer_rect)
1017    });
1018
1019    let mut rect = rect_at_start_of_drag; // prevent drift
1020
1021    // Put the rect in the center of the stroke:
1022    rect = rect.shrink(interaction.window_frame.stroke.width / 2.0);
1023
1024    if interaction.left.drag {
1025        rect.min.x += total_drag_delta.x;
1026    } else if interaction.right.drag {
1027        rect.max.x += total_drag_delta.x;
1028    }
1029
1030    if interaction.top.drag {
1031        rect.min.y += total_drag_delta.y;
1032    } else if interaction.bottom.drag {
1033        rect.max.y += total_drag_delta.y;
1034    }
1035
1036    // Return to having the rect outside the stroke:
1037    rect = rect.expand(interaction.window_frame.stroke.width / 2.0);
1038
1039    Some(rect.round_ui())
1040}
1041
1042fn do_resize_interaction(
1043    ctx: &Context,
1044    possible: PossibleInteractions,
1045    accessibility_parent: Id,
1046    layer_id: LayerId,
1047    outer_rect: Rect,
1048    window_frame: Frame,
1049) -> ResizeInteraction {
1050    if !possible.resizable() {
1051        return ResizeInteraction {
1052            outer_rect,
1053            window_frame,
1054            left: Default::default(),
1055            right: Default::default(),
1056            top: Default::default(),
1057            bottom: Default::default(),
1058        };
1059    }
1060
1061    // The rect that is in the middle of the stroke:
1062    let rect = outer_rect.shrink(window_frame.stroke.width / 2.0);
1063
1064    let side_response = |rect, id| {
1065        ctx.register_accesskit_parent(id, accessibility_parent);
1066        let response = ctx.create_widget(
1067            WidgetRect {
1068                layer_id,
1069                id,
1070                parent_id: layer_id.id,
1071                rect,
1072                interact_rect: rect,
1073                sense: Sense::DRAG, // Don't use Sense::drag() since we don't want these to be focusable
1074                enabled: true,
1075            },
1076            true,
1077            InteractOptions {
1078                // We call this multiple times.
1079                // First to read the result (to avoid frame delay)
1080                // and the second time to move it to the top, above the window contents.
1081                move_to_top: true,
1082            },
1083        );
1084
1085        response.widget_info(|| WidgetInfo::new(crate::WidgetType::ResizeHandle));
1086
1087        SideResponse {
1088            hover: response.hovered(),
1089            drag: response.dragged(),
1090        }
1091    };
1092
1093    let id = Id::new(layer_id).with("edge_drag");
1094
1095    let style = ctx.global_style();
1096
1097    let side_grab_radius = style.interaction.resize_grab_radius_side;
1098    let corner_grab_radius = style.interaction.resize_grab_radius_corner;
1099
1100    let vertical_rect = |a: Pos2, b: Pos2| {
1101        Rect::from_min_max(a, b).expand2(vec2(side_grab_radius, -corner_grab_radius))
1102    };
1103    let horizontal_rect = |a: Pos2, b: Pos2| {
1104        Rect::from_min_max(a, b).expand2(vec2(-corner_grab_radius, side_grab_radius))
1105    };
1106    let corner_rect =
1107        |center: Pos2| Rect::from_center_size(center, Vec2::splat(2.0 * corner_grab_radius));
1108
1109    // What are we dragging/hovering?
1110    let [mut left, mut right, mut top, mut bottom] = [SideResponse::default(); 4];
1111
1112    // ----------------------------------------
1113    // Check sides first, so that corners are on top, covering the sides (i.e. corners have priority)
1114
1115    if possible.resize_right {
1116        let response = side_response(
1117            vertical_rect(rect.right_top(), rect.right_bottom()),
1118            id.with("right"),
1119        );
1120        right |= response;
1121    }
1122    if possible.resize_left {
1123        let response = side_response(
1124            vertical_rect(rect.left_top(), rect.left_bottom()),
1125            id.with("left"),
1126        );
1127        left |= response;
1128    }
1129    if possible.resize_bottom {
1130        let response = side_response(
1131            horizontal_rect(rect.left_bottom(), rect.right_bottom()),
1132            id.with("bottom"),
1133        );
1134        bottom |= response;
1135    }
1136    if possible.resize_top {
1137        let response = side_response(
1138            horizontal_rect(rect.left_top(), rect.right_top()),
1139            id.with("top"),
1140        );
1141        top |= response;
1142    }
1143
1144    // ----------------------------------------
1145    // Now check corners.
1146    // We check any corner that has either side resizable,
1147    // because we shrink the side resize handled by the corner width.
1148    // Also, even if we can only change the width (or height) of a window,
1149    // we show one of the corners as a grab-handle, so it makes sense that
1150    // the whole corner is grabbable:
1151
1152    if possible.resize_right || possible.resize_bottom {
1153        let response = side_response(corner_rect(rect.right_bottom()), id.with("right_bottom"));
1154        if possible.resize_right {
1155            right |= response;
1156        }
1157        if possible.resize_bottom {
1158            bottom |= response;
1159        }
1160    }
1161
1162    if possible.resize_right || possible.resize_top {
1163        let response = side_response(corner_rect(rect.right_top()), id.with("right_top"));
1164        if possible.resize_right {
1165            right |= response;
1166        }
1167        if possible.resize_top {
1168            top |= response;
1169        }
1170    }
1171
1172    if possible.resize_left || possible.resize_bottom {
1173        let response = side_response(corner_rect(rect.left_bottom()), id.with("left_bottom"));
1174        if possible.resize_left {
1175            left |= response;
1176        }
1177        if possible.resize_bottom {
1178            bottom |= response;
1179        }
1180    }
1181
1182    if possible.resize_left || possible.resize_top {
1183        let response = side_response(corner_rect(rect.left_top()), id.with("left_top"));
1184        if possible.resize_left {
1185            left |= response;
1186        }
1187        if possible.resize_top {
1188            top |= response;
1189        }
1190    }
1191
1192    let interaction = ResizeInteraction {
1193        outer_rect,
1194        window_frame,
1195        left,
1196        right,
1197        top,
1198        bottom,
1199    };
1200    interaction.set_cursor(ctx);
1201    interaction
1202}
1203
1204/// Fill in parts of the window frame when we resize by dragging that part
1205fn paint_frame_interaction(ui: &Ui, rect: Rect, interaction: ResizeInteraction) {
1206    use epaint::tessellator::path::add_circle_quadrant;
1207
1208    let visuals = if interaction.any_dragged() {
1209        ui.style().visuals.widgets.active
1210    } else if interaction.any_hovered() {
1211        ui.style().visuals.widgets.hovered
1212    } else {
1213        return;
1214    };
1215
1216    let [left, right, top, bottom]: [bool; 4];
1217
1218    if interaction.any_dragged() {
1219        left = interaction.left.drag;
1220        right = interaction.right.drag;
1221        top = interaction.top.drag;
1222        bottom = interaction.bottom.drag;
1223    } else {
1224        left = interaction.left.hover;
1225        right = interaction.right.hover;
1226        top = interaction.top.hover;
1227        bottom = interaction.bottom.hover;
1228    }
1229
1230    let cr = CornerRadiusF32::from(ui.visuals().window_corner_radius);
1231
1232    // Put the rect in the center of the fixed window stroke:
1233    let rect = rect.shrink(interaction.window_frame.stroke.width / 2.0);
1234
1235    // Make sure the inner part of the stroke is at a pixel boundary:
1236    let stroke = visuals.bg_stroke;
1237    let half_stroke = stroke.width / 2.0;
1238    let rect = rect
1239        .shrink(half_stroke)
1240        .round_to_pixels(ui.pixels_per_point())
1241        .expand(half_stroke);
1242
1243    let Rect { min, max } = rect;
1244
1245    let mut points = Vec::new();
1246
1247    if right && !bottom && !top {
1248        points.push(pos2(max.x, min.y + cr.ne));
1249        points.push(pos2(max.x, max.y - cr.se));
1250    }
1251    if right && bottom {
1252        points.push(pos2(max.x, min.y + cr.ne));
1253        points.push(pos2(max.x, max.y - cr.se));
1254        add_circle_quadrant(&mut points, pos2(max.x - cr.se, max.y - cr.se), cr.se, 0.0);
1255    }
1256    if bottom {
1257        points.push(pos2(max.x - cr.se, max.y));
1258        points.push(pos2(min.x + cr.sw, max.y));
1259    }
1260    if left && bottom {
1261        add_circle_quadrant(&mut points, pos2(min.x + cr.sw, max.y - cr.sw), cr.sw, 1.0);
1262    }
1263    if left {
1264        points.push(pos2(min.x, max.y - cr.sw));
1265        points.push(pos2(min.x, min.y + cr.nw));
1266    }
1267    if left && top {
1268        add_circle_quadrant(&mut points, pos2(min.x + cr.nw, min.y + cr.nw), cr.nw, 2.0);
1269    }
1270    if top {
1271        points.push(pos2(min.x + cr.nw, min.y));
1272        points.push(pos2(max.x - cr.ne, min.y));
1273    }
1274    if right && top {
1275        add_circle_quadrant(&mut points, pos2(max.x - cr.ne, min.y + cr.ne), cr.ne, 3.0);
1276        points.push(pos2(max.x, min.y + cr.ne));
1277        points.push(pos2(max.x, max.y - cr.se));
1278    }
1279
1280    ui.painter().add(Shape::line(points, stroke));
1281}
1282
1283// ----------------------------------------------------------------------------
1284
1285/// Show the window titlebar.
1286///
1287/// Should be placed inside a `Frame::window`. The [`Frame`] it was placed inside should be passed as
1288/// an arg and will be used to paint the divider line at the bottom and the highlighted background
1289/// when `active` is true.
1290#[expect(clippy::too_many_arguments, clippy::fn_params_excessive_bools)]
1291fn title_ui(
1292    ui: &mut Ui,
1293    mut title: Atoms<'_>,
1294    frame: Frame,
1295    collapsing: &mut CollapsingState,
1296    collapsible: bool,
1297    active: bool,
1298    open: Option<&mut bool>,
1299    auto_sized: bool,
1300    drag_to_move: bool,
1301    area_id: Id,
1302) -> Response {
1303    let shape_idx = ui.painter().add(Shape::Noop);
1304
1305    let mut atoms = Atoms::default();
1306
1307    let button_size = Vec2::splat(ui.spacing().icon_width);
1308
1309    // Since the heading height is higher than the button size, we need to allocate the buttons
1310    // with the headers height as size, otherwise they'd look slightly off-center.
1311    // The shrink is then used to render the buttons with the right size.
1312    let heading_font_height =
1313        ui.fonts_mut(|f| f.row_height(&TextStyle::Heading.resolve(ui.style())));
1314    let button_allocation_size = Vec2::splat(heading_font_height);
1315    let button_shrink = (button_allocation_size - button_size) / 2.0;
1316
1317    let collapse_atom_id = Id::new("__window_collapse_button");
1318    let close_atom_id = Id::new("__window_close_button");
1319
1320    let expanded = collapsing.openness(ui.ctx()) > 0.0;
1321
1322    if collapsible {
1323        atoms.push_right(Atom::custom(collapse_atom_id, button_allocation_size));
1324    }
1325
1326    atoms.push_right(Atom::grow());
1327
1328    if !auto_sized
1329        && !title.any_shrink()
1330        && let Some(first_text) = title
1331            .iter_mut()
1332            .find(|a| matches!(a.kind, AtomKind::Text(..)))
1333    {
1334        first_text.shrink = true;
1335    }
1336    atoms.extend_right(title);
1337
1338    atoms.push_right(Atom::grow());
1339
1340    if open.is_some() {
1341        atoms.push_right(Atom::custom(close_atom_id, button_allocation_size));
1342    }
1343
1344    let spacing = ui.spacing().item_spacing.x;
1345
1346    let mut child_ui = ui.new_child(UiBuilder::new());
1347
1348    let mut layout = AtomLayout::new(atoms)
1349        .gap(spacing)
1350        .fallback_font(TextStyle::Heading)
1351        .wrap_mode(TextWrapMode::Truncate)
1352        .frame(Frame::NONE.inner_margin(frame.inner_margin));
1353
1354    let frame = frame.inner_margin(0); // Only applied to the atoms; done above.
1355
1356    if expanded {
1357        let min_width = if auto_sized {
1358            // During auto size, the resize is essentially disabled, meaning we don't get an
1359            // available_width we can rely on. Instead, check of large the content grew last frame
1360            // and use that for sizing the title bar. Unfortunately this adds a frame delay.
1361            ui.response().rect.width()
1362        } else {
1363            child_ui.available_width()
1364        };
1365
1366        layout = layout.min_size(Vec2::new(min_width, 0.0));
1367    }
1368
1369    let layout_response = layout.show(&mut child_ui);
1370
1371    let mut title_click_rect = layout_response.response.rect + frame.total_margin();
1372
1373    // Collapse triangle icon
1374    if collapsible && let Some(rect) = layout_response.rect(collapse_atom_id) {
1375        let rect = rect.shrink2(button_shrink);
1376        title_click_rect = title_click_rect.with_min_x(rect.max.x);
1377        let icon_response = child_ui.interact(
1378            rect,
1379            child_ui.auto_id_with("collapse_button"),
1380            Sense::click(),
1381        );
1382        icon_response.widget_info(|| {
1383            WidgetInfo::labeled(
1384                WidgetType::Button,
1385                child_ui.is_enabled(),
1386                if collapsing.is_open() { "Hide" } else { "Show" },
1387            )
1388        });
1389        if icon_response.clicked() {
1390            collapsing.toggle(&child_ui);
1391        }
1392        let openness = collapsing.openness(child_ui.ctx());
1393        crate::collapsing_header::paint_default_icon(&mut child_ui, openness, &icon_response);
1394    }
1395
1396    // Close button
1397    if let Some(open) = open
1398        && let Some(rect) = layout_response.rect(close_atom_id)
1399    {
1400        let rect = rect.shrink2(button_shrink);
1401        title_click_rect = title_click_rect.with_max_x(rect.min.x);
1402        if close_button(&mut child_ui, rect).clicked() {
1403            *open = false;
1404        }
1405    }
1406
1407    if collapsible || drag_to_move {
1408        // Single widget covers double-click-to-toggle (when collapsible) and
1409        // drag-to-move (in title-bar-drag mode). The move itself is applied in
1410        // `Window::show_dyn` _before_ `Area::begin` next frame, since
1411        // `Area::end` overwrites any in-frame mutation of `AreaState`.
1412        let sense = if drag_to_move {
1413            Sense::click_and_drag()
1414        } else {
1415            Sense::click()
1416        };
1417        let response = child_ui.interact(title_click_rect, area_id.with("__title_click"), sense);
1418
1419        if collapsible && response.double_clicked() {
1420            collapsing.toggle(&child_ui);
1421        }
1422    }
1423
1424    {
1425        let mut header_frame = frame.shadow(Shadow::NONE);
1426        if active {
1427            header_frame = header_frame.fill(ui.visuals().widgets.open.weak_bg_fill);
1428        }
1429        if expanded {
1430            header_frame.corner_radius.sw = 0;
1431            header_frame.corner_radius.se = 0;
1432        }
1433        ui.painter()
1434            .set(shape_idx, header_frame.paint(layout_response.rect));
1435    }
1436
1437    let mut advance_rect = child_ui.min_rect();
1438
1439    if auto_sized {
1440        // We may not allocate in the horizontal direction as that would break auto sizing.
1441        // Allocate a rect with 0 width:
1442        advance_rect = advance_rect.with_max_x(advance_rect.min.x);
1443    }
1444    if expanded {
1445        // Account for the margin of the title frame + the margin of the window contents
1446        // - the default ui spacing egui would add on this call
1447        advance_rect.max.y += frame.total_margin().bottom + frame.inner_margin.top as f32
1448            - child_ui.spacing().item_spacing.y;
1449    }
1450
1451    ui.advance_cursor_after_rect(advance_rect);
1452
1453    layout_response.response
1454}
1455
1456/// Paints the "Close" button of the window and processes clicks on it.
1457///
1458/// The close button is just an `X` symbol painted by a current stroke
1459/// for foreground elements (such as a label text).
1460///
1461/// # Parameters
1462/// - `ui`:
1463/// - `rect`: The rectangular area to fit the button in
1464///
1465/// Returns the result of a click on a button if it was pressed
1466fn close_button(ui: &mut Ui, rect: Rect) -> Response {
1467    let close_id = ui.auto_id_with("window_close_button");
1468    let response = ui.interact(rect, close_id, Sense::click());
1469    response
1470        .widget_info(|| WidgetInfo::labeled(WidgetType::Button, ui.is_enabled(), "Close window"));
1471
1472    ui.expand_to_include_rect(response.rect);
1473
1474    let visuals = ui.style().interact(&response);
1475    let rect = rect.shrink(2.0).expand(visuals.expansion);
1476    let stroke = visuals.fg_stroke;
1477    ui.painter() // paints \
1478        .line_segment([rect.left_top(), rect.right_bottom()], stroke);
1479    ui.painter() // paints /
1480        .line_segment([rect.right_top(), rect.left_bottom()], stroke);
1481    response
1482}