1use 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#[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 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#[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 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#[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 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#[derive(Clone, Copy, Debug, PartialEq, Eq)]
141enum PanelSide {
142 Vertical(VerticalSide),
144
145 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 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
189struct 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#[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 default_size: Option<f32>,
309
310 size_range: Rangef,
313}
314
315impl Panel {
316 pub fn left(id: impl Into<Id>) -> Self {
320 Self::new(PanelSide::LEFT, id)
321 }
322
323 pub fn right(id: impl Into<Id>) -> Self {
327 Self::new(PanelSide::RIGHT, id)
328 }
329
330 pub fn top(id: impl Into<Id>) -> Self {
336 Self::new(PanelSide::TOP, id).resizable(false)
337 }
338
339 pub fn bottom(id: impl Into<Id>) -> Self {
345 Self::new(PanelSide::BOTTOM, id).resizable(false)
346 }
347
348 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 #[inline]
388 pub fn resizable(mut self, resizable: bool) -> Self {
389 self.resizable = resizable;
390 self
391 }
392
393 #[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 #[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 #[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 #[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 #[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 #[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 #[inline]
448 pub fn frame(mut self, frame: Frame) -> Self {
449 self.frame = Some(frame);
450 self
451 }
452}
453
454impl 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
507impl Panel {
509 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 #[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 #[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 animated_panel.show(ctx, |_ui| {});
546 None
547 } else {
548 Some(animated_panel.show(ctx, add_contents))
550 }
551 }
552
553 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 let Some(animated_panel) = self.get_animated_panel(ui.ctx(), is_expanded) else {
565 ui.skip_ahead_auto_ids(1);
567 return None;
568 };
569
570 if how_expanded < 1.0 {
571 animated_panel.show_inside(ui, |_ui| {});
573 None
574 } else {
575 Some(animated_panel.show_inside(ui, add_contents))
577 }
578 }
579
580 #[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 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 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 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 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
637impl Panel {
639 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 let mut panel_sizer = PanelSizer::new(&self, ui);
653
654 ui.ctx()
656 .check_for_id_clash(id, panel_sizer.panel_rect, "Panel");
657
658 if self.resizable {
659 self.prepare_resizable_panel(&mut panel_sizer, ui);
661 }
662
663 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); 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()); 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()); 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 (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 } else if resize_hover {
734 ui.style().visuals.widgets.hovered.fg_stroke } else if show_separator_line {
736 ui.style().visuals.widgets.noninteractive.bg_stroke } else {
739 Stroke::NONE
740 };
741 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 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 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 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 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 Some(self)
908 }
909 }
910
911 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#[must_use = "You should call .show_inside()"]
985#[derive(Default)]
986pub struct CentralPanel {
987 frame: Option<Frame>,
988}
989
990impl CentralPanel {
991 pub fn no_frame() -> Self {
993 Self {
994 frame: Some(Frame::NONE),
995 }
996 }
997
998 pub fn default_margins() -> Self {
1000 Self { frame: None }
1001 }
1002
1003 #[inline]
1005 pub fn frame(mut self, frame: Frame) -> Self {
1006 self.frame = Some(frame);
1007 self
1008 }
1009
1010 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 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); 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()); add_contents(ui)
1040 });
1041
1042 ui.advance_cursor_after_rect(response.response.rect);
1044
1045 response
1046 }
1047
1048 #[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 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 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 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#[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;