1use crate::*;
19
20fn animate_expansion(ctx: &Context, id: Id, is_expanded: bool) -> f32 {
21 ctx.animate_bool_responsive(id, is_expanded)
22}
23
24#[derive(Clone, Copy, Debug)]
26#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
27pub struct PanelState {
28 pub rect: Rect,
29}
30
31impl PanelState {
32 pub fn load(ctx: &Context, bar_id: Id) -> Option<Self> {
33 ctx.data_mut(|d| d.get_persisted(bar_id))
34 }
35
36 pub fn size(&self) -> Vec2 {
38 self.rect.size()
39 }
40
41 fn store(self, ctx: &Context, bar_id: Id) {
42 ctx.data_mut(|d| d.insert_persisted(bar_id, self));
43 }
44}
45
46#[derive(Clone, Copy, Debug, PartialEq, Eq)]
50pub enum Side {
51 Left,
52 Right,
53}
54
55impl Side {
56 fn opposite(self) -> Self {
57 match self {
58 Self::Left => Self::Right,
59 Self::Right => Self::Left,
60 }
61 }
62
63 fn set_rect_width(self, rect: &mut Rect, width: f32) {
64 match self {
65 Self::Left => rect.max.x = rect.min.x + width,
66 Self::Right => rect.min.x = rect.max.x - width,
67 }
68 }
69
70 fn side_x(self, rect: Rect) -> f32 {
71 match self {
72 Self::Left => rect.left(),
73 Self::Right => rect.right(),
74 }
75 }
76}
77
78#[must_use = "You should call .show()"]
97pub struct SidePanel {
98 side: Side,
99 id: Id,
100 frame: Option<Frame>,
101 resizable: bool,
102 show_separator_line: bool,
103 default_width: f32,
104 width_range: Rangef,
105}
106
107impl SidePanel {
108 pub fn left(id: impl Into<Id>) -> Self {
110 Self::new(Side::Left, id)
111 }
112
113 pub fn right(id: impl Into<Id>) -> Self {
115 Self::new(Side::Right, id)
116 }
117
118 pub fn new(side: Side, id: impl Into<Id>) -> Self {
120 Self {
121 side,
122 id: id.into(),
123 frame: None,
124 resizable: true,
125 show_separator_line: true,
126 default_width: 200.0,
127 width_range: Rangef::new(96.0, f32::INFINITY),
128 }
129 }
130
131 #[inline]
143 pub fn resizable(mut self, resizable: bool) -> Self {
144 self.resizable = resizable;
145 self
146 }
147
148 #[inline]
152 pub fn show_separator_line(mut self, show_separator_line: bool) -> Self {
153 self.show_separator_line = show_separator_line;
154 self
155 }
156
157 #[inline]
159 pub fn default_width(mut self, default_width: f32) -> Self {
160 self.default_width = default_width;
161 self.width_range = Rangef::new(
162 self.width_range.min.at_most(default_width),
163 self.width_range.max.at_least(default_width),
164 );
165 self
166 }
167
168 #[inline]
170 pub fn min_width(mut self, min_width: f32) -> Self {
171 self.width_range = Rangef::new(min_width, self.width_range.max.at_least(min_width));
172 self
173 }
174
175 #[inline]
177 pub fn max_width(mut self, max_width: f32) -> Self {
178 self.width_range = Rangef::new(self.width_range.min.at_most(max_width), max_width);
179 self
180 }
181
182 #[inline]
184 pub fn width_range(mut self, width_range: impl Into<Rangef>) -> Self {
185 let width_range = width_range.into();
186 self.default_width = clamp_to_range(self.default_width, width_range);
187 self.width_range = width_range;
188 self
189 }
190
191 #[inline]
193 pub fn exact_width(mut self, width: f32) -> Self {
194 self.default_width = width;
195 self.width_range = Rangef::point(width);
196 self
197 }
198
199 #[inline]
201 pub fn frame(mut self, frame: Frame) -> Self {
202 self.frame = Some(frame);
203 self
204 }
205}
206
207impl SidePanel {
208 pub fn show_inside<R>(
210 self,
211 ui: &mut Ui,
212 add_contents: impl FnOnce(&mut Ui) -> R,
213 ) -> InnerResponse<R> {
214 self.show_inside_dyn(ui, Box::new(add_contents))
215 }
216
217 fn show_inside_dyn<'c, R>(
219 self,
220 ui: &mut Ui,
221 add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
222 ) -> InnerResponse<R> {
223 let Self {
224 side,
225 id,
226 frame,
227 resizable,
228 show_separator_line,
229 default_width,
230 width_range,
231 } = self;
232
233 let available_rect = ui.available_rect_before_wrap();
234 let mut panel_rect = available_rect;
235 let mut width = default_width;
236 {
237 if let Some(state) = PanelState::load(ui.ctx(), id) {
238 width = state.rect.width();
239 }
240 width = clamp_to_range(width, width_range).at_most(available_rect.width());
241 side.set_rect_width(&mut panel_rect, width);
242 ui.ctx().check_for_id_clash(id, panel_rect, "SidePanel");
243 }
244
245 let resize_id = id.with("__resize");
246 let mut resize_hover = false;
247 let mut is_resizing = false;
248 if resizable {
249 if let Some(resize_response) = ui.ctx().read_response(resize_id) {
251 resize_hover = resize_response.hovered();
252 is_resizing = resize_response.dragged();
253
254 if is_resizing {
255 if let Some(pointer) = resize_response.interact_pointer_pos() {
256 width = (pointer.x - side.side_x(panel_rect)).abs();
257 width = clamp_to_range(width, width_range).at_most(available_rect.width());
258 side.set_rect_width(&mut panel_rect, width);
259 }
260 }
261 }
262 }
263
264 let mut panel_ui = ui.child_ui_with_id_source(
265 panel_rect,
266 Layout::top_down(Align::Min),
267 id,
268 Some(UiStackInfo::new(match side {
269 Side::Left => UiKind::LeftPanel,
270 Side::Right => UiKind::RightPanel,
271 })),
272 );
273 panel_ui.expand_to_include_rect(panel_rect);
274 panel_ui.set_clip_rect(panel_rect); let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style()));
277 let inner_response = frame.show(&mut panel_ui, |ui| {
278 ui.set_min_height(ui.max_rect().height()); ui.set_min_width((width_range.min - frame.inner_margin.sum().x).at_least(0.0));
280 add_contents(ui)
281 });
282
283 let rect = inner_response.response.rect;
284
285 {
286 let mut cursor = ui.cursor();
287 match side {
288 Side::Left => {
289 cursor.min.x = rect.max.x;
290 }
291 Side::Right => {
292 cursor.max.x = rect.min.x;
293 }
294 }
295 ui.set_cursor(cursor);
296 }
297 ui.expand_to_include_rect(rect);
298
299 if resizable {
300 let resize_x = side.opposite().side_x(panel_rect);
304 let resize_rect = Rect::from_x_y_ranges(resize_x..=resize_x, panel_rect.y_range())
305 .expand2(vec2(ui.style().interaction.resize_grab_radius_side, 0.0));
306 let resize_response = ui.interact(resize_rect, resize_id, Sense::drag());
307 resize_hover = resize_response.hovered();
308 is_resizing = resize_response.dragged();
309 }
310
311 if resize_hover || is_resizing {
312 let cursor_icon = if width <= width_range.min {
313 match self.side {
314 Side::Left => CursorIcon::ResizeEast,
315 Side::Right => CursorIcon::ResizeWest,
316 }
317 } else if width < width_range.max {
318 CursorIcon::ResizeHorizontal
319 } else {
320 match self.side {
321 Side::Left => CursorIcon::ResizeWest,
322 Side::Right => CursorIcon::ResizeEast,
323 }
324 };
325 ui.ctx().set_cursor_icon(cursor_icon);
326 }
327
328 PanelState { rect }.store(ui.ctx(), id);
329
330 {
331 let stroke = if is_resizing {
332 ui.style().visuals.widgets.active.fg_stroke } else if resize_hover {
334 ui.style().visuals.widgets.hovered.fg_stroke } else if show_separator_line {
336 ui.style().visuals.widgets.noninteractive.bg_stroke } else {
339 Stroke::NONE
340 };
341 let resize_x = side.opposite().side_x(rect.shrink(1.0));
345 let resize_x = ui.painter().round_to_pixel(resize_x);
346 ui.painter().vline(resize_x, panel_rect.y_range(), stroke);
347 }
348
349 inner_response
350 }
351
352 pub fn show<R>(
354 self,
355 ctx: &Context,
356 add_contents: impl FnOnce(&mut Ui) -> R,
357 ) -> InnerResponse<R> {
358 self.show_dyn(ctx, Box::new(add_contents))
359 }
360
361 fn show_dyn<'c, R>(
363 self,
364 ctx: &Context,
365 add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
366 ) -> InnerResponse<R> {
367 let layer_id = LayerId::background();
368 let side = self.side;
369 let available_rect = ctx.available_rect();
370 let clip_rect = ctx.screen_rect();
371 let mut panel_ui = Ui::new(
372 ctx.clone(),
373 layer_id,
374 self.id,
375 available_rect,
376 clip_rect,
377 UiStackInfo::default(),
378 );
379
380 let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
381 let rect = inner_response.response.rect;
382
383 match side {
384 Side::Left => ctx.frame_state_mut(|state| {
385 state.allocate_left_panel(Rect::from_min_max(available_rect.min, rect.max));
386 }),
387 Side::Right => ctx.frame_state_mut(|state| {
388 state.allocate_right_panel(Rect::from_min_max(rect.min, available_rect.max));
389 }),
390 }
391 inner_response
392 }
393
394 pub fn show_animated<R>(
397 self,
398 ctx: &Context,
399 is_expanded: bool,
400 add_contents: impl FnOnce(&mut Ui) -> R,
401 ) -> Option<InnerResponse<R>> {
402 let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded);
403
404 if 0.0 == how_expanded {
405 None
406 } else if how_expanded < 1.0 {
407 let expanded_width = PanelState::load(ctx, self.id)
411 .map_or(self.default_width, |state| state.rect.width());
412 let fake_width = how_expanded * expanded_width;
413 Self {
414 id: self.id.with("animating_panel"),
415 ..self
416 }
417 .resizable(false)
418 .exact_width(fake_width)
419 .show(ctx, |_ui| {});
420 None
421 } else {
422 Some(self.show(ctx, add_contents))
424 }
425 }
426
427 pub fn show_animated_inside<R>(
430 self,
431 ui: &mut Ui,
432 is_expanded: bool,
433 add_contents: impl FnOnce(&mut Ui) -> R,
434 ) -> Option<InnerResponse<R>> {
435 let how_expanded = animate_expansion(ui.ctx(), self.id.with("animation"), is_expanded);
436
437 if 0.0 == how_expanded {
438 None
439 } else if how_expanded < 1.0 {
440 let expanded_width = PanelState::load(ui.ctx(), self.id)
444 .map_or(self.default_width, |state| state.rect.width());
445 let fake_width = how_expanded * expanded_width;
446 Self {
447 id: self.id.with("animating_panel"),
448 ..self
449 }
450 .resizable(false)
451 .exact_width(fake_width)
452 .show_inside(ui, |_ui| {});
453 None
454 } else {
455 Some(self.show_inside(ui, add_contents))
457 }
458 }
459
460 pub fn show_animated_between<R>(
462 ctx: &Context,
463 is_expanded: bool,
464 collapsed_panel: Self,
465 expanded_panel: Self,
466 add_contents: impl FnOnce(&mut Ui, f32) -> R,
467 ) -> Option<InnerResponse<R>> {
468 let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded);
469
470 if 0.0 == how_expanded {
471 Some(collapsed_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
472 } else if how_expanded < 1.0 {
473 let collapsed_width = PanelState::load(ctx, collapsed_panel.id)
475 .map_or(collapsed_panel.default_width, |state| state.rect.width());
476 let expanded_width = PanelState::load(ctx, expanded_panel.id)
477 .map_or(expanded_panel.default_width, |state| state.rect.width());
478 let fake_width = lerp(collapsed_width..=expanded_width, how_expanded);
479 Self {
480 id: expanded_panel.id.with("animating_panel"),
481 ..expanded_panel
482 }
483 .resizable(false)
484 .exact_width(fake_width)
485 .show(ctx, |ui| add_contents(ui, how_expanded));
486 None
487 } else {
488 Some(expanded_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
489 }
490 }
491
492 pub fn show_animated_between_inside<R>(
494 ui: &mut Ui,
495 is_expanded: bool,
496 collapsed_panel: Self,
497 expanded_panel: Self,
498 add_contents: impl FnOnce(&mut Ui, f32) -> R,
499 ) -> InnerResponse<R> {
500 let how_expanded =
501 animate_expansion(ui.ctx(), expanded_panel.id.with("animation"), is_expanded);
502
503 if 0.0 == how_expanded {
504 collapsed_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
505 } else if how_expanded < 1.0 {
506 let collapsed_width = PanelState::load(ui.ctx(), collapsed_panel.id)
508 .map_or(collapsed_panel.default_width, |state| state.rect.width());
509 let expanded_width = PanelState::load(ui.ctx(), expanded_panel.id)
510 .map_or(expanded_panel.default_width, |state| state.rect.width());
511 let fake_width = lerp(collapsed_width..=expanded_width, how_expanded);
512 Self {
513 id: expanded_panel.id.with("animating_panel"),
514 ..expanded_panel
515 }
516 .resizable(false)
517 .exact_width(fake_width)
518 .show_inside(ui, |ui| add_contents(ui, how_expanded))
519 } else {
520 expanded_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
521 }
522 }
523}
524
525#[derive(Clone, Copy, Debug, PartialEq, Eq)]
529pub enum TopBottomSide {
530 Top,
531 Bottom,
532}
533
534impl TopBottomSide {
535 fn opposite(self) -> Self {
536 match self {
537 Self::Top => Self::Bottom,
538 Self::Bottom => Self::Top,
539 }
540 }
541
542 fn set_rect_height(self, rect: &mut Rect, height: f32) {
543 match self {
544 Self::Top => rect.max.y = rect.min.y + height,
545 Self::Bottom => rect.min.y = rect.max.y - height,
546 }
547 }
548
549 fn side_y(self, rect: Rect) -> f32 {
550 match self {
551 Self::Top => rect.top(),
552 Self::Bottom => rect.bottom(),
553 }
554 }
555}
556
557#[must_use = "You should call .show()"]
576pub struct TopBottomPanel {
577 side: TopBottomSide,
578 id: Id,
579 frame: Option<Frame>,
580 resizable: bool,
581 show_separator_line: bool,
582 default_height: Option<f32>,
583 height_range: Rangef,
584}
585
586impl TopBottomPanel {
587 pub fn top(id: impl Into<Id>) -> Self {
589 Self::new(TopBottomSide::Top, id)
590 }
591
592 pub fn bottom(id: impl Into<Id>) -> Self {
594 Self::new(TopBottomSide::Bottom, id)
595 }
596
597 pub fn new(side: TopBottomSide, id: impl Into<Id>) -> Self {
599 Self {
600 side,
601 id: id.into(),
602 frame: None,
603 resizable: false,
604 show_separator_line: true,
605 default_height: None,
606 height_range: Rangef::new(20.0, f32::INFINITY),
607 }
608 }
609
610 #[inline]
622 pub fn resizable(mut self, resizable: bool) -> Self {
623 self.resizable = resizable;
624 self
625 }
626
627 #[inline]
631 pub fn show_separator_line(mut self, show_separator_line: bool) -> Self {
632 self.show_separator_line = show_separator_line;
633 self
634 }
635
636 #[inline]
639 pub fn default_height(mut self, default_height: f32) -> Self {
640 self.default_height = Some(default_height);
641 self.height_range = Rangef::new(
642 self.height_range.min.at_most(default_height),
643 self.height_range.max.at_least(default_height),
644 );
645 self
646 }
647
648 #[inline]
650 pub fn min_height(mut self, min_height: f32) -> Self {
651 self.height_range = Rangef::new(min_height, self.height_range.max.at_least(min_height));
652 self
653 }
654
655 #[inline]
657 pub fn max_height(mut self, max_height: f32) -> Self {
658 self.height_range = Rangef::new(self.height_range.min.at_most(max_height), max_height);
659 self
660 }
661
662 #[inline]
664 pub fn height_range(mut self, height_range: impl Into<Rangef>) -> Self {
665 let height_range = height_range.into();
666 self.default_height = self
667 .default_height
668 .map(|default_height| clamp_to_range(default_height, height_range));
669 self.height_range = height_range;
670 self
671 }
672
673 #[inline]
675 pub fn exact_height(mut self, height: f32) -> Self {
676 self.default_height = Some(height);
677 self.height_range = Rangef::point(height);
678 self
679 }
680
681 #[inline]
683 pub fn frame(mut self, frame: Frame) -> Self {
684 self.frame = Some(frame);
685 self
686 }
687}
688
689impl TopBottomPanel {
690 pub fn show_inside<R>(
692 self,
693 ui: &mut Ui,
694 add_contents: impl FnOnce(&mut Ui) -> R,
695 ) -> InnerResponse<R> {
696 self.show_inside_dyn(ui, Box::new(add_contents))
697 }
698
699 fn show_inside_dyn<'c, R>(
701 self,
702 ui: &mut Ui,
703 add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
704 ) -> InnerResponse<R> {
705 let Self {
706 side,
707 id,
708 frame,
709 resizable,
710 show_separator_line,
711 default_height,
712 height_range,
713 } = self;
714
715 let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style()));
716
717 let available_rect = ui.available_rect_before_wrap();
718 let mut panel_rect = available_rect;
719
720 let mut height = if let Some(state) = PanelState::load(ui.ctx(), id) {
721 state.rect.height()
722 } else {
723 default_height
724 .unwrap_or_else(|| ui.style().spacing.interact_size.y + frame.inner_margin.sum().y)
725 };
726 {
727 height = clamp_to_range(height, height_range).at_most(available_rect.height());
728 side.set_rect_height(&mut panel_rect, height);
729 ui.ctx()
730 .check_for_id_clash(id, panel_rect, "TopBottomPanel");
731 }
732
733 let resize_id = id.with("__resize");
734 let mut resize_hover = false;
735 let mut is_resizing = false;
736 if resizable {
737 if let Some(resize_response) = ui.ctx().read_response(resize_id) {
739 resize_hover = resize_response.hovered();
740 is_resizing = resize_response.dragged();
741
742 if is_resizing {
743 if let Some(pointer) = resize_response.interact_pointer_pos() {
744 height = (pointer.y - side.side_y(panel_rect)).abs();
745 height =
746 clamp_to_range(height, height_range).at_most(available_rect.height());
747 side.set_rect_height(&mut panel_rect, height);
748 }
749 }
750 }
751 }
752
753 let mut panel_ui = ui.child_ui_with_id_source(
754 panel_rect,
755 Layout::top_down(Align::Min),
756 id,
757 Some(UiStackInfo::new(match side {
758 TopBottomSide::Top => UiKind::TopPanel,
759 TopBottomSide::Bottom => UiKind::BottomPanel,
760 })),
761 );
762 panel_ui.expand_to_include_rect(panel_rect);
763 panel_ui.set_clip_rect(panel_rect); let inner_response = frame.show(&mut panel_ui, |ui| {
766 ui.set_min_width(ui.max_rect().width()); ui.set_min_height((height_range.min - frame.inner_margin.sum().y).at_least(0.0));
768 add_contents(ui)
769 });
770
771 let rect = inner_response.response.rect;
772
773 {
774 let mut cursor = ui.cursor();
775 match side {
776 TopBottomSide::Top => {
777 cursor.min.y = rect.max.y;
778 }
779 TopBottomSide::Bottom => {
780 cursor.max.y = rect.min.y;
781 }
782 }
783 ui.set_cursor(cursor);
784 }
785 ui.expand_to_include_rect(rect);
786
787 if resizable {
788 let resize_y = side.opposite().side_y(panel_rect);
793 let resize_rect = Rect::from_x_y_ranges(panel_rect.x_range(), resize_y..=resize_y)
794 .expand2(vec2(0.0, ui.style().interaction.resize_grab_radius_side));
795 let resize_response = ui.interact(resize_rect, resize_id, Sense::drag());
796 resize_hover = resize_response.hovered();
797 is_resizing = resize_response.dragged();
798 }
799
800 if resize_hover || is_resizing {
801 let cursor_icon = if height <= height_range.min {
802 match self.side {
803 TopBottomSide::Top => CursorIcon::ResizeSouth,
804 TopBottomSide::Bottom => CursorIcon::ResizeNorth,
805 }
806 } else if height < height_range.max {
807 CursorIcon::ResizeVertical
808 } else {
809 match self.side {
810 TopBottomSide::Top => CursorIcon::ResizeNorth,
811 TopBottomSide::Bottom => CursorIcon::ResizeSouth,
812 }
813 };
814 ui.ctx().set_cursor_icon(cursor_icon);
815 }
816
817 PanelState { rect }.store(ui.ctx(), id);
818
819 {
820 let stroke = if is_resizing {
821 ui.style().visuals.widgets.active.fg_stroke } else if resize_hover {
823 ui.style().visuals.widgets.hovered.fg_stroke } else if show_separator_line {
825 ui.style().visuals.widgets.noninteractive.bg_stroke } else {
828 Stroke::NONE
829 };
830 let resize_y = side.opposite().side_y(rect.shrink(1.0));
834 let resize_y = ui.painter().round_to_pixel(resize_y);
835 ui.painter().hline(panel_rect.x_range(), resize_y, stroke);
836 }
837
838 inner_response
839 }
840
841 pub fn show<R>(
843 self,
844 ctx: &Context,
845 add_contents: impl FnOnce(&mut Ui) -> R,
846 ) -> InnerResponse<R> {
847 self.show_dyn(ctx, Box::new(add_contents))
848 }
849
850 fn show_dyn<'c, R>(
852 self,
853 ctx: &Context,
854 add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
855 ) -> InnerResponse<R> {
856 let layer_id = LayerId::background();
857 let available_rect = ctx.available_rect();
858 let side = self.side;
859
860 let clip_rect = ctx.screen_rect();
861 let mut panel_ui = Ui::new(
862 ctx.clone(),
863 layer_id,
864 self.id,
865 available_rect,
866 clip_rect,
867 UiStackInfo::default(), );
869
870 let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
871 let rect = inner_response.response.rect;
872
873 match side {
874 TopBottomSide::Top => {
875 ctx.frame_state_mut(|state| {
876 state.allocate_top_panel(Rect::from_min_max(available_rect.min, rect.max));
877 });
878 }
879 TopBottomSide::Bottom => {
880 ctx.frame_state_mut(|state| {
881 state.allocate_bottom_panel(Rect::from_min_max(rect.min, available_rect.max));
882 });
883 }
884 }
885
886 inner_response
887 }
888
889 pub fn show_animated<R>(
892 self,
893 ctx: &Context,
894 is_expanded: bool,
895 add_contents: impl FnOnce(&mut Ui) -> R,
896 ) -> Option<InnerResponse<R>> {
897 let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded);
898
899 if 0.0 == how_expanded {
900 None
901 } else if how_expanded < 1.0 {
902 let expanded_height = PanelState::load(ctx, self.id)
906 .map(|state| state.rect.height())
907 .or(self.default_height)
908 .unwrap_or_else(|| ctx.style().spacing.interact_size.y);
909 let fake_height = how_expanded * expanded_height;
910 Self {
911 id: self.id.with("animating_panel"),
912 ..self
913 }
914 .resizable(false)
915 .exact_height(fake_height)
916 .show(ctx, |_ui| {});
917 None
918 } else {
919 Some(self.show(ctx, add_contents))
921 }
922 }
923
924 pub fn show_animated_inside<R>(
927 self,
928 ui: &mut Ui,
929 is_expanded: bool,
930 add_contents: impl FnOnce(&mut Ui) -> R,
931 ) -> Option<InnerResponse<R>> {
932 let how_expanded = animate_expansion(ui.ctx(), self.id.with("animation"), is_expanded);
933
934 if 0.0 == how_expanded {
935 None
936 } else if how_expanded < 1.0 {
937 let expanded_height = PanelState::load(ui.ctx(), self.id)
941 .map(|state| state.rect.height())
942 .or(self.default_height)
943 .unwrap_or_else(|| ui.style().spacing.interact_size.y);
944 let fake_height = how_expanded * expanded_height;
945 Self {
946 id: self.id.with("animating_panel"),
947 ..self
948 }
949 .resizable(false)
950 .exact_height(fake_height)
951 .show_inside(ui, |_ui| {});
952 None
953 } else {
954 Some(self.show_inside(ui, add_contents))
956 }
957 }
958
959 pub fn show_animated_between<R>(
961 ctx: &Context,
962 is_expanded: bool,
963 collapsed_panel: Self,
964 expanded_panel: Self,
965 add_contents: impl FnOnce(&mut Ui, f32) -> R,
966 ) -> Option<InnerResponse<R>> {
967 let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded);
968
969 if 0.0 == how_expanded {
970 Some(collapsed_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
971 } else if how_expanded < 1.0 {
972 let collapsed_height = PanelState::load(ctx, collapsed_panel.id)
974 .map(|state| state.rect.height())
975 .or(collapsed_panel.default_height)
976 .unwrap_or_else(|| ctx.style().spacing.interact_size.y);
977
978 let expanded_height = PanelState::load(ctx, expanded_panel.id)
979 .map(|state| state.rect.height())
980 .or(expanded_panel.default_height)
981 .unwrap_or_else(|| ctx.style().spacing.interact_size.y);
982
983 let fake_height = lerp(collapsed_height..=expanded_height, how_expanded);
984 Self {
985 id: expanded_panel.id.with("animating_panel"),
986 ..expanded_panel
987 }
988 .resizable(false)
989 .exact_height(fake_height)
990 .show(ctx, |ui| add_contents(ui, how_expanded));
991 None
992 } else {
993 Some(expanded_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
994 }
995 }
996
997 pub fn show_animated_between_inside<R>(
999 ui: &mut Ui,
1000 is_expanded: bool,
1001 collapsed_panel: Self,
1002 expanded_panel: Self,
1003 add_contents: impl FnOnce(&mut Ui, f32) -> R,
1004 ) -> InnerResponse<R> {
1005 let how_expanded =
1006 animate_expansion(ui.ctx(), expanded_panel.id.with("animation"), is_expanded);
1007
1008 if 0.0 == how_expanded {
1009 collapsed_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
1010 } else if how_expanded < 1.0 {
1011 let collapsed_height = PanelState::load(ui.ctx(), collapsed_panel.id)
1013 .map(|state| state.rect.height())
1014 .or(collapsed_panel.default_height)
1015 .unwrap_or_else(|| ui.style().spacing.interact_size.y);
1016
1017 let expanded_height = PanelState::load(ui.ctx(), expanded_panel.id)
1018 .map(|state| state.rect.height())
1019 .or(expanded_panel.default_height)
1020 .unwrap_or_else(|| ui.style().spacing.interact_size.y);
1021
1022 let fake_height = lerp(collapsed_height..=expanded_height, how_expanded);
1023 Self {
1024 id: expanded_panel.id.with("animating_panel"),
1025 ..expanded_panel
1026 }
1027 .resizable(false)
1028 .exact_height(fake_height)
1029 .show_inside(ui, |ui| add_contents(ui, how_expanded))
1030 } else {
1031 expanded_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
1032 }
1033 }
1034}
1035
1036#[must_use = "You should call .show()"]
1061#[derive(Default)]
1062pub struct CentralPanel {
1063 frame: Option<Frame>,
1064}
1065
1066impl CentralPanel {
1067 #[inline]
1069 pub fn frame(mut self, frame: Frame) -> Self {
1070 self.frame = Some(frame);
1071 self
1072 }
1073}
1074
1075impl CentralPanel {
1076 pub fn show_inside<R>(
1078 self,
1079 ui: &mut Ui,
1080 add_contents: impl FnOnce(&mut Ui) -> R,
1081 ) -> InnerResponse<R> {
1082 self.show_inside_dyn(ui, Box::new(add_contents))
1083 }
1084
1085 fn show_inside_dyn<'c, R>(
1087 self,
1088 ui: &mut Ui,
1089 add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
1090 ) -> InnerResponse<R> {
1091 let Self { frame } = self;
1092
1093 let panel_rect = ui.available_rect_before_wrap();
1094 let mut panel_ui = ui.child_ui(
1095 panel_rect,
1096 Layout::top_down(Align::Min),
1097 Some(UiStackInfo::new(UiKind::CentralPanel)),
1098 );
1099 panel_ui.set_clip_rect(panel_rect); let frame = frame.unwrap_or_else(|| Frame::central_panel(ui.style()));
1102 frame.show(&mut panel_ui, |ui| {
1103 ui.expand_to_include_rect(ui.max_rect()); add_contents(ui)
1105 })
1106 }
1107
1108 pub fn show<R>(
1110 self,
1111 ctx: &Context,
1112 add_contents: impl FnOnce(&mut Ui) -> R,
1113 ) -> InnerResponse<R> {
1114 self.show_dyn(ctx, Box::new(add_contents))
1115 }
1116
1117 fn show_dyn<'c, R>(
1119 self,
1120 ctx: &Context,
1121 add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
1122 ) -> InnerResponse<R> {
1123 let available_rect = ctx.available_rect();
1124 let layer_id = LayerId::background();
1125 let id = Id::new((ctx.viewport_id(), "central_panel"));
1126
1127 let clip_rect = ctx.screen_rect();
1128 let mut panel_ui = Ui::new(
1129 ctx.clone(),
1130 layer_id,
1131 id,
1132 available_rect,
1133 clip_rect,
1134 UiStackInfo::default(), );
1136
1137 let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
1138
1139 ctx.frame_state_mut(|state| state.allocate_central_panel(inner_response.response.rect));
1141
1142 inner_response
1143 }
1144}
1145
1146fn clamp_to_range(x: f32, range: Rangef) -> f32 {
1147 let range = range.as_positive();
1148 x.clamp(range.min, range.max)
1149}