egui/containers/
panel.rs

1//! Panels are [`Ui`] regions taking up e.g. the left side of a [`Ui`] or screen.
2//!
3//! Panels can either be a child of a [`Ui`] (taking up a portion of the parent)
4//! or be top-level (taking up a portion of the whole screen).
5//!
6//! Together with [`crate::Window`] and [`crate::Area`]:s, top-level panels are
7//! the only places where you can put you widgets.
8//!
9//! The order in which you add panels matter!
10//! The first panel you add will always be the outermost, and the last you add will always be the innermost.
11//!
12//! You must never open one top-level panel from within another panel. Add one panel, then the next.
13//!
14//! ⚠ Always add any [`CentralPanel`] last.
15//!
16//! Add your [`crate::Window`]:s after any top-level panels.
17
18use emath::{GuiRounding as _, Pos2};
19
20use crate::{
21    Align, Context, CursorIcon, Frame, Id, InnerResponse, LayerId, Layout, NumExt as _, Rangef,
22    Rect, Sense, Stroke, Ui, UiBuilder, UiKind, UiStackInfo, Vec2, WidgetInfo, WidgetType, lerp,
23    vec2,
24};
25
26fn animate_expansion(ctx: &Context, id: Id, is_expanded: bool) -> f32 {
27    ctx.animate_bool_responsive(id, is_expanded)
28}
29
30/// State regarding panels.
31#[derive(Clone, Copy, Debug)]
32#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
33pub struct PanelState {
34    pub rect: Rect,
35}
36
37impl PanelState {
38    pub fn load(ctx: &Context, bar_id: Id) -> Option<Self> {
39        ctx.data_mut(|d| d.get_persisted(bar_id))
40    }
41
42    /// The size of the panel (from previous frame).
43    pub fn size(&self) -> Vec2 {
44        self.rect.size()
45    }
46
47    fn store(self, ctx: &Context, bar_id: Id) {
48        ctx.data_mut(|d| d.insert_persisted(bar_id, self));
49    }
50}
51
52// ----------------------------------------------------------------------------
53
54/// [`Left`](VerticalSide::Left) or [`Right`](VerticalSide::Right)
55#[derive(Clone, Copy, Debug, PartialEq, Eq)]
56enum VerticalSide {
57    Left,
58    Right,
59}
60
61impl VerticalSide {
62    pub fn opposite(self) -> Self {
63        match self {
64            Self::Left => Self::Right,
65            Self::Right => Self::Left,
66        }
67    }
68
69    /// `self` is the _fixed_ side.
70    ///
71    /// * Left panels are resized on their right side
72    /// * Right panels are resized on their left side
73    fn set_rect_width(self, rect: &mut Rect, width: f32) {
74        match self {
75            Self::Left => rect.max.x = rect.min.x + width,
76            Self::Right => rect.min.x = rect.max.x - width,
77        }
78    }
79
80    fn sign(self) -> f32 {
81        match self {
82            Self::Left => -1.0,
83            Self::Right => 1.0,
84        }
85    }
86
87    fn side_x(self, rect: Rect) -> f32 {
88        match self {
89            Self::Left => rect.left(),
90            Self::Right => rect.right(),
91        }
92    }
93}
94
95/// [`Top`](HorizontalSide::Top) or [`Bottom`](HorizontalSide::Bottom)
96#[derive(Clone, Copy, Debug, PartialEq, Eq)]
97enum HorizontalSide {
98    Top,
99    Bottom,
100}
101
102impl HorizontalSide {
103    pub fn opposite(self) -> Self {
104        match self {
105            Self::Top => Self::Bottom,
106            Self::Bottom => Self::Top,
107        }
108    }
109
110    /// `self` is the _fixed_ side.
111    ///
112    /// * Top panels are resized on their bottom side
113    /// * Bottom panels are resized upwards
114    fn set_rect_height(self, rect: &mut Rect, height: f32) {
115        match self {
116            Self::Top => rect.max.y = rect.min.y + height,
117            Self::Bottom => rect.min.y = rect.max.y - height,
118        }
119    }
120
121    fn sign(self) -> f32 {
122        match self {
123            Self::Top => -1.0,
124            Self::Bottom => 1.0,
125        }
126    }
127
128    fn side_y(self, rect: Rect) -> f32 {
129        match self {
130            Self::Top => rect.top(),
131            Self::Bottom => rect.bottom(),
132        }
133    }
134}
135
136// Intentionally private because I'm not sure of the naming.
137// TODO(emilk): decide on good names and make public.
138// "VerticalSide" and "HorizontalSide" feels inverted to me.
139/// [`Horizontal`](PanelSide::Horizontal) or [`Vertical`](PanelSide::Vertical)
140#[derive(Clone, Copy, Debug, PartialEq, Eq)]
141enum PanelSide {
142    /// Left or right.
143    Vertical(VerticalSide),
144
145    /// Top or bottom
146    Horizontal(HorizontalSide),
147}
148
149impl From<HorizontalSide> for PanelSide {
150    fn from(side: HorizontalSide) -> Self {
151        Self::Horizontal(side)
152    }
153}
154
155impl From<VerticalSide> for PanelSide {
156    fn from(side: VerticalSide) -> Self {
157        Self::Vertical(side)
158    }
159}
160
161impl PanelSide {
162    pub const LEFT: Self = Self::Vertical(VerticalSide::Left);
163    pub const RIGHT: Self = Self::Vertical(VerticalSide::Right);
164    pub const TOP: Self = Self::Horizontal(HorizontalSide::Top);
165    pub const BOTTOM: Self = Self::Horizontal(HorizontalSide::Bottom);
166
167    /// Resize by keeping the [`self`] side fixed, and moving the opposite side.
168    fn set_rect_size(self, rect: &mut Rect, size: f32) {
169        match self {
170            Self::Vertical(side) => side.set_rect_width(rect, size),
171            Self::Horizontal(side) => side.set_rect_height(rect, size),
172        }
173    }
174
175    fn ui_kind(self) -> UiKind {
176        match self {
177            Self::Vertical(side) => match side {
178                VerticalSide::Left => UiKind::LeftPanel,
179                VerticalSide::Right => UiKind::RightPanel,
180            },
181            Self::Horizontal(side) => match side {
182                HorizontalSide::Top => UiKind::TopPanel,
183                HorizontalSide::Bottom => UiKind::BottomPanel,
184            },
185        }
186    }
187}
188
189// ----------------------------------------------------------------------------
190
191/// Intermediate structure to abstract some portion of [`Panel::show_inside`](Panel::show_inside).
192struct PanelSizer<'a> {
193    panel: &'a Panel,
194    frame: Frame,
195    available_rect: Rect,
196    size: f32,
197    panel_rect: Rect,
198}
199
200impl<'a> PanelSizer<'a> {
201    fn new(panel: &'a Panel, ui: &Ui) -> Self {
202        let frame = panel
203            .frame
204            .unwrap_or_else(|| Frame::side_top_panel(ui.style()));
205        let available_rect = ui.available_rect_before_wrap();
206        let size = PanelSizer::get_size_from_state_or_default(panel, ui, frame);
207        let panel_rect = PanelSizer::panel_rect(panel, available_rect, size);
208
209        Self {
210            panel,
211            frame,
212            available_rect,
213            size,
214            panel_rect,
215        }
216    }
217
218    fn get_size_from_state_or_default(panel: &Panel, ui: &Ui, frame: Frame) -> f32 {
219        if let Some(state) = PanelState::load(ui.ctx(), panel.id) {
220            match panel.side {
221                PanelSide::Vertical(_) => state.rect.width(),
222                PanelSide::Horizontal(_) => state.rect.height(),
223            }
224        } else {
225            match panel.side {
226                PanelSide::Vertical(_) => panel.default_size.unwrap_or_else(|| {
227                    ui.style().spacing.interact_size.x + frame.inner_margin.sum().x
228                }),
229                PanelSide::Horizontal(_) => panel.default_size.unwrap_or_else(|| {
230                    ui.style().spacing.interact_size.y + frame.inner_margin.sum().y
231                }),
232            }
233        }
234    }
235
236    fn panel_rect(panel: &Panel, available_rect: Rect, mut size: f32) -> Rect {
237        let side = panel.side;
238        let size_range = panel.size_range;
239
240        let mut panel_rect = available_rect;
241
242        match side {
243            PanelSide::Vertical(_) => {
244                size = clamp_to_range(size, size_range).at_most(available_rect.width());
245            }
246            PanelSide::Horizontal(_) => {
247                size = clamp_to_range(size, size_range).at_most(available_rect.height());
248            }
249        }
250        side.set_rect_size(&mut panel_rect, size);
251        panel_rect
252    }
253
254    fn prepare_resizing_response(&mut self, is_resizing: bool, pointer: Option<Pos2>) {
255        let side = self.panel.side;
256        let size_range = self.panel.size_range;
257
258        if is_resizing && let Some(pointer) = pointer {
259            match side {
260                PanelSide::Vertical(side) => {
261                    self.size = (pointer.x - side.side_x(self.panel_rect)).abs();
262                    self.size =
263                        clamp_to_range(self.size, size_range).at_most(self.available_rect.width());
264                }
265                PanelSide::Horizontal(side) => {
266                    self.size = (pointer.y - side.side_y(self.panel_rect)).abs();
267                    self.size =
268                        clamp_to_range(self.size, size_range).at_most(self.available_rect.height());
269                }
270            }
271
272            side.set_rect_size(&mut self.panel_rect, self.size);
273        }
274    }
275}
276
277// ----------------------------------------------------------------------------
278
279/// A panel that covers an entire side
280/// ([`left`](Panel::left), [`right`](Panel::right),
281/// [`top`](Panel::top) or [`bottom`](Panel::bottom))
282/// of a [`Ui`] or screen.
283///
284/// The order in which you add panels matter!
285/// The first panel you add will always be the outermost, and the last you add will always be the innermost.
286///
287/// ⚠ Always add any [`CentralPanel`] last.
288///
289/// See the [module level docs](crate::containers::panel) for more details.
290///
291/// ```
292/// # egui::__run_test_ui(|ui| {
293/// egui::Panel::left("my_left_panel").show_inside(ui, |ui| {
294///    ui.label("Hello World!");
295/// });
296/// # });
297/// ```
298#[must_use = "You should call .show_inside()"]
299pub struct Panel {
300    side: PanelSide,
301    id: Id,
302    frame: Option<Frame>,
303    resizable: bool,
304    show_separator_line: bool,
305
306    /// The size is defined as being either the width for a Vertical Panel
307    /// or the height for a Horizontal Panel.
308    default_size: Option<f32>,
309
310    /// The size is defined as being either the width for a Vertical Panel
311    /// or the height for a Horizontal Panel.
312    size_range: Rangef,
313}
314
315impl Panel {
316    /// Create a left panel.
317    ///
318    /// The id should be globally unique, e.g. `Id::new("my_left_panel")`.
319    pub fn left(id: impl Into<Id>) -> Self {
320        Self::new(PanelSide::LEFT, id)
321    }
322
323    /// Create a right panel.
324    ///
325    /// The id should be globally unique, e.g. `Id::new("my_right_panel")`.
326    pub fn right(id: impl Into<Id>) -> Self {
327        Self::new(PanelSide::RIGHT, id)
328    }
329
330    /// Create a top panel.
331    ///
332    /// The id should be globally unique, e.g. `Id::new("my_top_panel")`.
333    ///
334    /// By default this is NOT resizable.
335    pub fn top(id: impl Into<Id>) -> Self {
336        Self::new(PanelSide::TOP, id).resizable(false)
337    }
338
339    /// Create a bottom panel.
340    ///
341    /// The id should be globally unique, e.g. `Id::new("my_bottom_panel")`.
342    ///
343    /// By default this is NOT resizable.
344    pub fn bottom(id: impl Into<Id>) -> Self {
345        Self::new(PanelSide::BOTTOM, id).resizable(false)
346    }
347
348    /// Create a panel.
349    ///
350    /// The id should be globally unique, e.g. `Id::new("my_panel")`.
351    fn new(side: PanelSide, id: impl Into<Id>) -> Self {
352        let default_size: Option<f32> = match side {
353            PanelSide::Vertical(_) => Some(200.0),
354            PanelSide::Horizontal(_) => None,
355        };
356
357        let size_range: Rangef = match side {
358            PanelSide::Vertical(_) => Rangef::new(96.0, f32::INFINITY),
359            PanelSide::Horizontal(_) => Rangef::new(20.0, f32::INFINITY),
360        };
361
362        Self {
363            side,
364            id: id.into(),
365            frame: None,
366            resizable: true,
367            show_separator_line: true,
368            default_size,
369            size_range,
370        }
371    }
372
373    /// Can panel be resized by dragging the edge of it?
374    ///
375    /// Default is `true`.
376    ///
377    /// If you want your panel to be resizable you also need to make the ui use
378    /// the available space.
379    ///
380    /// This can be done by using [`Ui::take_available_space`], or using a
381    /// widget in it that takes up more space as you resize it, such as:
382    /// * Wrapping text ([`Ui::horizontal_wrapped`]).
383    /// * A [`crate::ScrollArea`].
384    /// * A [`crate::Separator`].
385    /// * A [`crate::TextEdit`].
386    /// * …
387    #[inline]
388    pub fn resizable(mut self, resizable: bool) -> Self {
389        self.resizable = resizable;
390        self
391    }
392
393    /// Show a separator line, even when not interacting with it?
394    ///
395    /// Default: `true`.
396    #[inline]
397    pub fn show_separator_line(mut self, show_separator_line: bool) -> Self {
398        self.show_separator_line = show_separator_line;
399        self
400    }
401
402    /// The initial wrapping width of the [`Panel`], including margins.
403    #[inline]
404    pub fn default_size(mut self, default_size: f32) -> Self {
405        self.default_size = Some(default_size);
406        self.size_range = Rangef::new(
407            self.size_range.min.at_most(default_size),
408            self.size_range.max.at_least(default_size),
409        );
410        self
411    }
412
413    /// Minimum size of the panel, including margins.
414    #[inline]
415    pub fn min_size(mut self, min_size: f32) -> Self {
416        self.size_range = Rangef::new(min_size, self.size_range.max.at_least(min_size));
417        self
418    }
419
420    /// Maximum size of the panel, including margins.
421    #[inline]
422    pub fn max_size(mut self, max_size: f32) -> Self {
423        self.size_range = Rangef::new(self.size_range.min.at_most(max_size), max_size);
424        self
425    }
426
427    /// The allowable size range for the panel, including margins.
428    #[inline]
429    pub fn size_range(mut self, size_range: impl Into<Rangef>) -> Self {
430        let size_range = size_range.into();
431        self.default_size = self
432            .default_size
433            .map(|default_size| clamp_to_range(default_size, size_range));
434        self.size_range = size_range;
435        self
436    }
437
438    /// Enforce this exact size, including margins.
439    #[inline]
440    pub fn exact_size(mut self, size: f32) -> Self {
441        self.default_size = Some(size);
442        self.size_range = Rangef::point(size);
443        self
444    }
445
446    /// Change the background color, margins, etc.
447    #[inline]
448    pub fn frame(mut self, frame: Frame) -> Self {
449        self.frame = Some(frame);
450        self
451    }
452}
453
454// Deprecated
455impl Panel {
456    #[deprecated = "Renamed default_size"]
457    pub fn default_width(self, default_size: f32) -> Self {
458        self.default_size(default_size)
459    }
460
461    #[deprecated = "Renamed min_size"]
462    pub fn min_width(self, min_size: f32) -> Self {
463        self.min_size(min_size)
464    }
465
466    #[deprecated = "Renamed max_size"]
467    pub fn max_width(self, max_size: f32) -> Self {
468        self.max_size(max_size)
469    }
470
471    #[deprecated = "Renamed size_range"]
472    pub fn width_range(self, size_range: impl Into<Rangef>) -> Self {
473        self.size_range(size_range)
474    }
475
476    #[deprecated = "Renamed exact_size"]
477    pub fn exact_width(self, size: f32) -> Self {
478        self.exact_size(size)
479    }
480
481    #[deprecated = "Renamed default_size"]
482    pub fn default_height(self, default_size: f32) -> Self {
483        self.default_size(default_size)
484    }
485
486    #[deprecated = "Renamed min_size"]
487    pub fn min_height(self, min_size: f32) -> Self {
488        self.min_size(min_size)
489    }
490
491    #[deprecated = "Renamed max_size"]
492    pub fn max_height(self, max_size: f32) -> Self {
493        self.max_size(max_size)
494    }
495
496    #[deprecated = "Renamed size_range"]
497    pub fn height_range(self, size_range: impl Into<Rangef>) -> Self {
498        self.size_range(size_range)
499    }
500
501    #[deprecated = "Renamed exact_size"]
502    pub fn exact_height(self, size: f32) -> Self {
503        self.exact_size(size)
504    }
505}
506
507// Public showing methods
508impl Panel {
509    /// Show the panel inside a [`Ui`].
510    pub fn show_inside<R>(
511        self,
512        ui: &mut Ui,
513        add_contents: impl FnOnce(&mut Ui) -> R,
514    ) -> InnerResponse<R> {
515        self.show_inside_dyn(ui, Box::new(add_contents))
516    }
517
518    /// Show the panel at the top level.
519    #[deprecated = "Use show_inside() instead"]
520    pub fn show<R>(
521        self,
522        ctx: &Context,
523        add_contents: impl FnOnce(&mut Ui) -> R,
524    ) -> InnerResponse<R> {
525        self.show_dyn(ctx, Box::new(add_contents))
526    }
527
528    /// Show the panel if `is_expanded` is `true`,
529    /// otherwise don't show it, but with a nice animation between collapsed and expanded.
530    #[deprecated = "Use show_animated_inside() instead"]
531    pub fn show_animated<R>(
532        self,
533        ctx: &Context,
534        is_expanded: bool,
535        add_contents: impl FnOnce(&mut Ui) -> R,
536    ) -> Option<InnerResponse<R>> {
537        #![expect(deprecated)]
538
539        let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded);
540
541        let animated_panel = self.get_animated_panel(ctx, is_expanded)?;
542
543        if how_expanded < 1.0 {
544            // Show a fake panel in this in-between animation state:
545            animated_panel.show(ctx, |_ui| {});
546            None
547        } else {
548            // Show the real panel:
549            Some(animated_panel.show(ctx, add_contents))
550        }
551    }
552
553    /// Show the panel if `is_expanded` is `true`,
554    /// otherwise don't show it, but with a nice animation between collapsed and expanded.
555    pub fn show_animated_inside<R>(
556        self,
557        ui: &mut Ui,
558        is_expanded: bool,
559        add_contents: impl FnOnce(&mut Ui) -> R,
560    ) -> Option<InnerResponse<R>> {
561        let how_expanded = animate_expansion(ui.ctx(), self.id.with("animation"), is_expanded);
562
563        // Get either the fake or the real panel to animate
564        let Some(animated_panel) = self.get_animated_panel(ui.ctx(), is_expanded) else {
565            // Make sure the ids of the next widgets are the same whether we show the panel or not:
566            ui.skip_ahead_auto_ids(1);
567            return None;
568        };
569
570        if how_expanded < 1.0 {
571            // Show a fake panel in this in-between animation state:
572            animated_panel.show_inside(ui, |_ui| {});
573            None
574        } else {
575            // Show the real panel:
576            Some(animated_panel.show_inside(ui, add_contents))
577        }
578    }
579
580    /// Show either a collapsed or a expanded panel, with a nice animation between.
581    #[deprecated = "Use show_animated_between_inside() instead"]
582    pub fn show_animated_between<R>(
583        ctx: &Context,
584        is_expanded: bool,
585        collapsed_panel: Self,
586        expanded_panel: Self,
587        add_contents: impl FnOnce(&mut Ui, f32) -> R,
588    ) -> Option<InnerResponse<R>> {
589        #![expect(deprecated)]
590
591        let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded);
592
593        // Get either the fake or the real panel to animate
594        let animated_between_panel =
595            Self::get_animated_between_panel(ctx, is_expanded, collapsed_panel, expanded_panel);
596
597        if 0.0 == how_expanded {
598            Some(animated_between_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
599        } else if how_expanded < 1.0 {
600            // Show animation:
601            animated_between_panel.show(ctx, |ui| add_contents(ui, how_expanded));
602            None
603        } else {
604            Some(animated_between_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
605        }
606    }
607
608    /// Show either a collapsed or a expanded panel, with a nice animation between.
609    pub fn show_animated_between_inside<R>(
610        ui: &mut Ui,
611        is_expanded: bool,
612        collapsed_panel: Self,
613        expanded_panel: Self,
614        add_contents: impl FnOnce(&mut Ui, f32) -> R,
615    ) -> InnerResponse<R> {
616        let how_expanded =
617            animate_expansion(ui.ctx(), expanded_panel.id.with("animation"), is_expanded);
618
619        let animated_between_panel = Self::get_animated_between_panel(
620            ui.ctx(),
621            is_expanded,
622            collapsed_panel,
623            expanded_panel,
624        );
625
626        if 0.0 == how_expanded {
627            animated_between_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
628        } else if how_expanded < 1.0 {
629            // Show animation:
630            animated_between_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
631        } else {
632            animated_between_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
633        }
634    }
635}
636
637// Private methods to support the various show methods
638impl Panel {
639    /// Show the panel inside a [`Ui`].
640    fn show_inside_dyn<'c, R>(
641        self,
642        ui: &mut Ui,
643        add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
644    ) -> InnerResponse<R> {
645        let side = self.side;
646        let id = self.id;
647        let resizable = self.resizable;
648        let show_separator_line = self.show_separator_line;
649        let size_range = self.size_range;
650
651        // Define the sizing of the panel.
652        let mut panel_sizer = PanelSizer::new(&self, ui);
653
654        // Check for duplicate id
655        ui.ctx()
656            .check_for_id_clash(id, panel_sizer.panel_rect, "Panel");
657
658        if self.resizable {
659            // Prepare the resizable panel to avoid frame latency in the resize
660            self.prepare_resizable_panel(&mut panel_sizer, ui);
661        }
662
663        // NOTE(shark98): This must be **after** the resizable preparation, as the size
664        // may change and round_ui() uses the size.
665        panel_sizer.panel_rect = panel_sizer.panel_rect.round_ui();
666
667        let mut panel_ui = ui.new_child(
668            UiBuilder::new()
669                .id_salt(id)
670                .ui_stack_info(UiStackInfo::new(side.ui_kind()))
671                .max_rect(panel_sizer.panel_rect)
672                .layout(Layout::top_down(Align::Min)),
673        );
674        panel_ui.expand_to_include_rect(panel_sizer.panel_rect);
675        panel_ui.set_clip_rect(panel_sizer.panel_rect); // If we overflow, don't do so visibly (#4475)
676
677        let inner_response = panel_sizer.frame.show(&mut panel_ui, |ui| {
678            match side {
679                PanelSide::Vertical(_) => {
680                    ui.set_min_height(ui.max_rect().height()); // Make sure the frame fills the full height
681                    ui.set_min_width(
682                        (size_range.min - panel_sizer.frame.inner_margin.sum().x).at_least(0.0),
683                    );
684                }
685                PanelSide::Horizontal(_) => {
686                    ui.set_min_width(ui.max_rect().width()); // Make the frame fill full width
687                    ui.set_min_height(
688                        (size_range.min - panel_sizer.frame.inner_margin.sum().y).at_least(0.0),
689                    );
690                }
691            }
692
693            add_contents(ui)
694        });
695
696        let rect = inner_response.response.rect;
697
698        {
699            let mut cursor = ui.cursor();
700            match side {
701                PanelSide::Vertical(side) => match side {
702                    VerticalSide::Left => cursor.min.x = rect.max.x,
703                    VerticalSide::Right => cursor.max.x = rect.min.x,
704                },
705                PanelSide::Horizontal(side) => match side {
706                    HorizontalSide::Top => cursor.min.y = rect.max.y,
707                    HorizontalSide::Bottom => cursor.max.y = rect.min.y,
708                },
709            }
710            ui.set_cursor(cursor);
711        }
712
713        ui.expand_to_include_rect(rect);
714
715        let mut resize_hover = false;
716        let mut is_resizing = false;
717        if resizable {
718            // Now we do the actual resize interaction, on top of all the contents,
719            // otherwise its input could be eaten by the contents, e.g. a
720            // `ScrollArea` on either side of the panel boundary.
721            (resize_hover, is_resizing) = self.resize_panel(&panel_sizer, ui);
722        }
723
724        if resize_hover || is_resizing {
725            ui.set_cursor_icon(self.cursor_icon(&panel_sizer));
726        }
727
728        PanelState { rect }.store(ui.ctx(), id);
729
730        {
731            let stroke = if is_resizing {
732                ui.style().visuals.widgets.active.fg_stroke // highly visible
733            } else if resize_hover {
734                ui.style().visuals.widgets.hovered.fg_stroke // highly visible
735            } else if show_separator_line {
736                // TODO(emilk): distinguish resizable from non-resizable
737                ui.style().visuals.widgets.noninteractive.bg_stroke // dim
738            } else {
739                Stroke::NONE
740            };
741            // TODO(emilk): draw line on top of all panels in this ui when https://github.com/emilk/egui/issues/1516 is done
742            match side {
743                PanelSide::Vertical(side) => {
744                    let x = side.opposite().side_x(rect) + 0.5 * side.sign() * stroke.width;
745                    ui.painter()
746                        .vline(x, panel_sizer.panel_rect.y_range(), stroke);
747                }
748                PanelSide::Horizontal(side) => {
749                    let y = side.opposite().side_y(rect) + 0.5 * side.sign() * stroke.width;
750                    ui.painter()
751                        .hline(panel_sizer.panel_rect.x_range(), y, stroke);
752                }
753            }
754        }
755
756        inner_response
757    }
758
759    /// Show the panel at the top level.
760    fn show_dyn<'c, R>(
761        self,
762        ctx: &Context,
763        add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
764    ) -> InnerResponse<R> {
765        #![expect(deprecated)]
766
767        let side = self.side;
768        let available_rect = ctx.available_rect();
769        let mut panel_ui = Ui::new(
770            ctx.clone(),
771            self.id,
772            UiBuilder::new()
773                .layer_id(LayerId::background())
774                .max_rect(available_rect),
775        );
776        panel_ui.set_clip_rect(ctx.content_rect());
777        panel_ui
778            .response()
779            .widget_info(|| WidgetInfo::new(WidgetType::Panel));
780
781        let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
782        let rect = inner_response.response.rect;
783
784        match side {
785            PanelSide::Vertical(side) => match side {
786                VerticalSide::Left => ctx.pass_state_mut(|state| {
787                    state.allocate_left_panel(Rect::from_min_max(available_rect.min, rect.max));
788                }),
789                VerticalSide::Right => ctx.pass_state_mut(|state| {
790                    state.allocate_right_panel(Rect::from_min_max(rect.min, available_rect.max));
791                }),
792            },
793            PanelSide::Horizontal(side) => match side {
794                HorizontalSide::Top => {
795                    ctx.pass_state_mut(|state| {
796                        state.allocate_top_panel(Rect::from_min_max(available_rect.min, rect.max));
797                    });
798                }
799                HorizontalSide::Bottom => {
800                    ctx.pass_state_mut(|state| {
801                        state.allocate_bottom_panel(Rect::from_min_max(
802                            rect.min,
803                            available_rect.max,
804                        ));
805                    });
806                }
807            },
808        }
809        inner_response
810    }
811
812    fn prepare_resizable_panel(&self, panel_sizer: &mut PanelSizer<'_>, ui: &Ui) {
813        let resize_id = self.id.with("__resize");
814        let resize_response = ui.ctx().read_response(resize_id);
815
816        if let Some(resize_response) = resize_response {
817            // NOTE(sharky98): The original code was initializing to
818            // false first, but it doesn't seem necessary.
819            let is_resizing = resize_response.dragged();
820            let pointer = resize_response.interact_pointer_pos();
821            panel_sizer.prepare_resizing_response(is_resizing, pointer);
822        }
823    }
824
825    fn resize_panel(&self, panel_sizer: &PanelSizer<'_>, ui: &Ui) -> (bool, bool) {
826        let (resize_x, resize_y, amount): (Rangef, Rangef, Vec2) = match self.side {
827            PanelSide::Vertical(side) => {
828                let resize_x = side.opposite().side_x(panel_sizer.panel_rect);
829                let resize_y = panel_sizer.panel_rect.y_range();
830                (
831                    Rangef::from(resize_x..=resize_x),
832                    resize_y,
833                    vec2(ui.style().interaction.resize_grab_radius_side, 0.0),
834                )
835            }
836            PanelSide::Horizontal(side) => {
837                let resize_x = panel_sizer.panel_rect.x_range();
838                let resize_y = side.opposite().side_y(panel_sizer.panel_rect);
839                (
840                    resize_x,
841                    Rangef::from(resize_y..=resize_y),
842                    vec2(0.0, ui.style().interaction.resize_grab_radius_side),
843                )
844            }
845        };
846
847        let resize_id = self.id.with("__resize");
848        let resize_rect = Rect::from_x_y_ranges(resize_x, resize_y).expand2(amount);
849        let resize_response = ui.interact(resize_rect, resize_id, Sense::drag());
850
851        (resize_response.hovered(), resize_response.dragged())
852    }
853
854    fn cursor_icon(&self, panel_sizer: &PanelSizer<'_>) -> CursorIcon {
855        if panel_sizer.size <= self.size_range.min {
856            match self.side {
857                PanelSide::Vertical(side) => match side {
858                    VerticalSide::Left => CursorIcon::ResizeEast,
859                    VerticalSide::Right => CursorIcon::ResizeWest,
860                },
861                PanelSide::Horizontal(side) => match side {
862                    HorizontalSide::Top => CursorIcon::ResizeSouth,
863                    HorizontalSide::Bottom => CursorIcon::ResizeNorth,
864                },
865            }
866        } else if panel_sizer.size < self.size_range.max {
867            match self.side {
868                PanelSide::Vertical(_) => CursorIcon::ResizeHorizontal,
869                PanelSide::Horizontal(_) => CursorIcon::ResizeVertical,
870            }
871        } else {
872            match self.side {
873                PanelSide::Vertical(side) => match side {
874                    VerticalSide::Left => CursorIcon::ResizeWest,
875                    VerticalSide::Right => CursorIcon::ResizeEast,
876                },
877                PanelSide::Horizontal(side) => match side {
878                    HorizontalSide::Top => CursorIcon::ResizeNorth,
879                    HorizontalSide::Bottom => CursorIcon::ResizeSouth,
880                },
881            }
882        }
883    }
884
885    /// Get the real or fake panel to animate if `is_expanded` is `true`.
886    fn get_animated_panel(self, ctx: &Context, is_expanded: bool) -> Option<Self> {
887        let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded);
888
889        if 0.0 == how_expanded {
890            None
891        } else if how_expanded < 1.0 {
892            // Show a fake panel in this in-between animation state:
893            // TODO(emilk): move the panel out-of-screen instead of changing its width.
894            // Then we can actually paint it as it animates.
895            let expanded_size = Self::animated_size(ctx, &self);
896            let fake_size = how_expanded * expanded_size;
897            Some(
898                Self {
899                    id: self.id.with("animating_panel"),
900                    ..self
901                }
902                .resizable(false)
903                .exact_size(fake_size),
904            )
905        } else {
906            // Show the real panel:
907            Some(self)
908        }
909    }
910
911    /// Get either the collapsed or expended panel to animate.
912    fn get_animated_between_panel(
913        ctx: &Context,
914        is_expanded: bool,
915        collapsed_panel: Self,
916        expanded_panel: Self,
917    ) -> Self {
918        let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded);
919
920        if 0.0 == how_expanded {
921            collapsed_panel
922        } else if how_expanded < 1.0 {
923            let collapsed_size = Self::animated_size(ctx, &collapsed_panel);
924            let expanded_size = Self::animated_size(ctx, &expanded_panel);
925
926            let fake_size = lerp(collapsed_size..=expanded_size, how_expanded);
927
928            Self {
929                id: expanded_panel.id.with("animating_panel"),
930                ..expanded_panel
931            }
932            .resizable(false)
933            .exact_size(fake_size)
934        } else {
935            expanded_panel
936        }
937    }
938
939    fn animated_size(ctx: &Context, panel: &Self) -> f32 {
940        let get_rect_state_size = |state: PanelState| match panel.side {
941            PanelSide::Vertical(_) => state.rect.width(),
942            PanelSide::Horizontal(_) => state.rect.height(),
943        };
944
945        let get_spacing_size = || match panel.side {
946            PanelSide::Vertical(_) => ctx.global_style().spacing.interact_size.x,
947            PanelSide::Horizontal(_) => ctx.global_style().spacing.interact_size.y,
948        };
949
950        PanelState::load(ctx, panel.id)
951            .map(get_rect_state_size)
952            .or(panel.default_size)
953            .unwrap_or_else(get_spacing_size)
954    }
955}
956
957// ----------------------------------------------------------------------------
958
959/// A panel that covers the remainder of the screen,
960/// i.e. whatever area is left after adding other panels.
961///
962/// This acts very similar to [`Frame::central_panel`], but always expands
963/// to use up all available space.
964///
965/// The order in which you add panels matter!
966/// The first panel you add will always be the outermost, and the last you add will always be the innermost.
967///
968/// ⚠ [`CentralPanel`] must be added after all other panels!
969///
970/// NOTE: Any [`crate::Window`]s and [`crate::Area`]s will cover the top-level [`CentralPanel`].
971///
972/// See the [module level docs](crate::containers::panel) for more details.
973///
974/// ```
975/// # egui::__run_test_ui(|ui| {
976/// egui::Panel::top("my_panel").show_inside(ui, |ui| {
977///    ui.label("Hello World! From `Panel`, that must be before `CentralPanel`!");
978/// });
979/// egui::CentralPanel::default().show_inside(ui, |ui| {
980///    ui.label("Hello World!");
981/// });
982/// # });
983/// ```
984#[must_use = "You should call .show_inside()"]
985#[derive(Default)]
986pub struct CentralPanel {
987    frame: Option<Frame>,
988}
989
990impl CentralPanel {
991    /// A central panel with no margin or background color
992    pub fn no_frame() -> Self {
993        Self {
994            frame: Some(Frame::NONE),
995        }
996    }
997
998    /// A central panel with a background color and some inner margins
999    pub fn default_margins() -> Self {
1000        Self { frame: None }
1001    }
1002
1003    /// Change the background color, margins, etc.
1004    #[inline]
1005    pub fn frame(mut self, frame: Frame) -> Self {
1006        self.frame = Some(frame);
1007        self
1008    }
1009
1010    /// Show the panel inside a [`Ui`].
1011    pub fn show_inside<R>(
1012        self,
1013        ui: &mut Ui,
1014        add_contents: impl FnOnce(&mut Ui) -> R,
1015    ) -> InnerResponse<R> {
1016        self.show_inside_dyn(ui, Box::new(add_contents))
1017    }
1018
1019    /// Show the panel inside a [`Ui`].
1020    fn show_inside_dyn<'c, R>(
1021        self,
1022        ui: &mut Ui,
1023        add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
1024    ) -> InnerResponse<R> {
1025        let Self { frame } = self;
1026
1027        let panel_rect = ui.available_rect_before_wrap();
1028        let mut panel_ui = ui.new_child(
1029            UiBuilder::new()
1030                .ui_stack_info(UiStackInfo::new(UiKind::CentralPanel))
1031                .max_rect(panel_rect)
1032                .layout(Layout::top_down(Align::Min)),
1033        );
1034        panel_ui.set_clip_rect(panel_rect); // If we overflow, don't do so visibly (#4475)
1035
1036        let frame = frame.unwrap_or_else(|| Frame::central_panel(ui.style()));
1037        let response = frame.show(&mut panel_ui, |ui| {
1038            ui.expand_to_include_rect(ui.max_rect()); // Expand frame to include it all
1039            add_contents(ui)
1040        });
1041
1042        // Use up space in the parent:
1043        ui.advance_cursor_after_rect(response.response.rect);
1044
1045        response
1046    }
1047
1048    /// Show the panel at the top level.
1049    #[deprecated = "Use show_inside() instead"]
1050    pub fn show<R>(
1051        self,
1052        ctx: &Context,
1053        add_contents: impl FnOnce(&mut Ui) -> R,
1054    ) -> InnerResponse<R> {
1055        self.show_dyn(ctx, Box::new(add_contents))
1056    }
1057
1058    /// Show the panel at the top level.
1059    fn show_dyn<'c, R>(
1060        self,
1061        ctx: &Context,
1062        add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
1063    ) -> InnerResponse<R> {
1064        #![expect(deprecated)]
1065
1066        let id = Id::new((ctx.viewport_id(), "central_panel"));
1067
1068        let mut panel_ui = Ui::new(
1069            ctx.clone(),
1070            id,
1071            UiBuilder::new()
1072                .layer_id(LayerId::background())
1073                .max_rect(ctx.available_rect()),
1074        );
1075        panel_ui.set_clip_rect(ctx.content_rect());
1076
1077        if false {
1078            // TODO(emilk): @lucasmerlin shouldn't we enable this?
1079            panel_ui
1080                .response()
1081                .widget_info(|| WidgetInfo::new(WidgetType::Panel));
1082        }
1083
1084        let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
1085
1086        // Only inform ctx about what we actually used, so we can shrink the native window to fit.
1087        ctx.pass_state_mut(|state| state.allocate_central_panel(inner_response.response.rect));
1088
1089        inner_response
1090    }
1091}
1092
1093fn clamp_to_range(x: f32, range: Rangef) -> f32 {
1094    let range = range.as_positive();
1095    x.clamp(range.min, range.max)
1096}
1097
1098// ----------------------------------------------------------------------------
1099
1100#[deprecated = "Use Panel::left or Panel::right instead"]
1101pub type SidePanel = super::Panel;
1102
1103#[deprecated = "Use Panel::top or Panel::bottom instead"]
1104pub type TopBottomPanel = super::Panel;