1use emath::GuiRounding as _;
19
20use crate::{
21 lerp, vec2, Align, Context, CursorIcon, Frame, Id, InnerResponse, LayerId, Layout, NumExt,
22 Rangef, Rect, Sense, Stroke, Ui, UiBuilder, UiKind, UiStackInfo, Vec2,
23};
24
25fn animate_expansion(ctx: &Context, id: Id, is_expanded: bool) -> f32 {
26 ctx.animate_bool_responsive(id, is_expanded)
27}
28
29#[derive(Clone, Copy, Debug)]
31#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
32pub struct PanelState {
33 pub rect: Rect,
34}
35
36impl PanelState {
37 pub fn load(ctx: &Context, bar_id: Id) -> Option<Self> {
38 ctx.data_mut(|d| d.get_persisted(bar_id))
39 }
40
41 pub fn size(&self) -> Vec2 {
43 self.rect.size()
44 }
45
46 fn store(self, ctx: &Context, bar_id: Id) {
47 ctx.data_mut(|d| d.insert_persisted(bar_id, self));
48 }
49}
50
51#[derive(Clone, Copy, Debug, PartialEq, Eq)]
55pub enum Side {
56 Left,
57 Right,
58}
59
60impl Side {
61 fn opposite(self) -> Self {
62 match self {
63 Self::Left => Self::Right,
64 Self::Right => Self::Left,
65 }
66 }
67
68 fn set_rect_width(self, rect: &mut Rect, width: f32) {
69 match self {
70 Self::Left => rect.max.x = rect.min.x + width,
71 Self::Right => rect.min.x = rect.max.x - width,
72 }
73 }
74
75 fn side_x(self, rect: Rect) -> f32 {
76 match self {
77 Self::Left => rect.left(),
78 Self::Right => rect.right(),
79 }
80 }
81
82 fn sign(self) -> f32 {
83 match self {
84 Self::Left => -1.0,
85 Self::Right => 1.0,
86 }
87 }
88}
89
90#[must_use = "You should call .show()"]
109pub struct SidePanel {
110 side: Side,
111 id: Id,
112 frame: Option<Frame>,
113 resizable: bool,
114 show_separator_line: bool,
115 default_width: f32,
116 width_range: Rangef,
117}
118
119impl SidePanel {
120 pub fn left(id: impl Into<Id>) -> Self {
122 Self::new(Side::Left, id)
123 }
124
125 pub fn right(id: impl Into<Id>) -> Self {
127 Self::new(Side::Right, id)
128 }
129
130 pub fn new(side: Side, id: impl Into<Id>) -> Self {
132 Self {
133 side,
134 id: id.into(),
135 frame: None,
136 resizable: true,
137 show_separator_line: true,
138 default_width: 200.0,
139 width_range: Rangef::new(96.0, f32::INFINITY),
140 }
141 }
142
143 #[inline]
155 pub fn resizable(mut self, resizable: bool) -> Self {
156 self.resizable = resizable;
157 self
158 }
159
160 #[inline]
164 pub fn show_separator_line(mut self, show_separator_line: bool) -> Self {
165 self.show_separator_line = show_separator_line;
166 self
167 }
168
169 #[inline]
171 pub fn default_width(mut self, default_width: f32) -> Self {
172 self.default_width = default_width;
173 self.width_range = Rangef::new(
174 self.width_range.min.at_most(default_width),
175 self.width_range.max.at_least(default_width),
176 );
177 self
178 }
179
180 #[inline]
182 pub fn min_width(mut self, min_width: f32) -> Self {
183 self.width_range = Rangef::new(min_width, self.width_range.max.at_least(min_width));
184 self
185 }
186
187 #[inline]
189 pub fn max_width(mut self, max_width: f32) -> Self {
190 self.width_range = Rangef::new(self.width_range.min.at_most(max_width), max_width);
191 self
192 }
193
194 #[inline]
196 pub fn width_range(mut self, width_range: impl Into<Rangef>) -> Self {
197 let width_range = width_range.into();
198 self.default_width = clamp_to_range(self.default_width, width_range);
199 self.width_range = width_range;
200 self
201 }
202
203 #[inline]
205 pub fn exact_width(mut self, width: f32) -> Self {
206 self.default_width = width;
207 self.width_range = Rangef::point(width);
208 self
209 }
210
211 #[inline]
213 pub fn frame(mut self, frame: Frame) -> Self {
214 self.frame = Some(frame);
215 self
216 }
217}
218
219impl SidePanel {
220 pub fn show_inside<R>(
222 self,
223 ui: &mut Ui,
224 add_contents: impl FnOnce(&mut Ui) -> R,
225 ) -> InnerResponse<R> {
226 self.show_inside_dyn(ui, Box::new(add_contents))
227 }
228
229 fn show_inside_dyn<'c, R>(
231 self,
232 ui: &mut Ui,
233 add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
234 ) -> InnerResponse<R> {
235 let Self {
236 side,
237 id,
238 frame,
239 resizable,
240 show_separator_line,
241 default_width,
242 width_range,
243 } = self;
244
245 let available_rect = ui.available_rect_before_wrap();
246 let mut panel_rect = available_rect;
247 let mut width = default_width;
248 {
249 if let Some(state) = PanelState::load(ui.ctx(), id) {
250 width = state.rect.width();
251 }
252 width = clamp_to_range(width, width_range).at_most(available_rect.width());
253 side.set_rect_width(&mut panel_rect, width);
254 ui.ctx().check_for_id_clash(id, panel_rect, "SidePanel");
255 }
256
257 let resize_id = id.with("__resize");
258 let mut resize_hover = false;
259 let mut is_resizing = false;
260 if resizable {
261 if let Some(resize_response) = ui.ctx().read_response(resize_id) {
263 resize_hover = resize_response.hovered();
264 is_resizing = resize_response.dragged();
265
266 if is_resizing {
267 if let Some(pointer) = resize_response.interact_pointer_pos() {
268 width = (pointer.x - side.side_x(panel_rect)).abs();
269 width = clamp_to_range(width, width_range).at_most(available_rect.width());
270 side.set_rect_width(&mut panel_rect, width);
271 }
272 }
273 }
274 }
275
276 panel_rect = panel_rect.round_ui();
277
278 let mut panel_ui = ui.new_child(
279 UiBuilder::new()
280 .id_salt(id)
281 .ui_stack_info(UiStackInfo::new(match side {
282 Side::Left => UiKind::LeftPanel,
283 Side::Right => UiKind::RightPanel,
284 }))
285 .max_rect(panel_rect)
286 .layout(Layout::top_down(Align::Min)),
287 );
288 panel_ui.expand_to_include_rect(panel_rect);
289 panel_ui.set_clip_rect(panel_rect); let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style()));
292 let inner_response = frame.show(&mut panel_ui, |ui| {
293 ui.set_min_height(ui.max_rect().height()); ui.set_min_width((width_range.min - frame.inner_margin.sum().x).at_least(0.0));
295 add_contents(ui)
296 });
297
298 let rect = inner_response.response.rect;
299
300 {
301 let mut cursor = ui.cursor();
302 match side {
303 Side::Left => {
304 cursor.min.x = rect.max.x;
305 }
306 Side::Right => {
307 cursor.max.x = rect.min.x;
308 }
309 }
310 ui.set_cursor(cursor);
311 }
312 ui.expand_to_include_rect(rect);
313
314 if resizable {
315 let resize_x = side.opposite().side_x(panel_rect);
319 let resize_rect = Rect::from_x_y_ranges(resize_x..=resize_x, panel_rect.y_range())
320 .expand2(vec2(ui.style().interaction.resize_grab_radius_side, 0.0));
321 let resize_response = ui.interact(resize_rect, resize_id, Sense::drag());
322 resize_hover = resize_response.hovered();
323 is_resizing = resize_response.dragged();
324 }
325
326 if resize_hover || is_resizing {
327 let cursor_icon = if width <= width_range.min {
328 match self.side {
329 Side::Left => CursorIcon::ResizeEast,
330 Side::Right => CursorIcon::ResizeWest,
331 }
332 } else if width < width_range.max {
333 CursorIcon::ResizeHorizontal
334 } else {
335 match self.side {
336 Side::Left => CursorIcon::ResizeWest,
337 Side::Right => CursorIcon::ResizeEast,
338 }
339 };
340 ui.ctx().set_cursor_icon(cursor_icon);
341 }
342
343 PanelState { rect }.store(ui.ctx(), id);
344
345 {
346 let stroke = if is_resizing {
347 ui.style().visuals.widgets.active.fg_stroke } else if resize_hover {
349 ui.style().visuals.widgets.hovered.fg_stroke } else if show_separator_line {
351 ui.style().visuals.widgets.noninteractive.bg_stroke } else {
354 Stroke::NONE
355 };
356 let resize_x = side.opposite().side_x(rect);
358
359 let resize_x = resize_x + 0.5 * side.sign() * stroke.width;
361 ui.painter().vline(resize_x, panel_rect.y_range(), stroke);
362 }
363
364 inner_response
365 }
366
367 pub fn show<R>(
369 self,
370 ctx: &Context,
371 add_contents: impl FnOnce(&mut Ui) -> R,
372 ) -> InnerResponse<R> {
373 self.show_dyn(ctx, Box::new(add_contents))
374 }
375
376 fn show_dyn<'c, R>(
378 self,
379 ctx: &Context,
380 add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
381 ) -> InnerResponse<R> {
382 let side = self.side;
383 let available_rect = ctx.available_rect();
384 let mut panel_ui = Ui::new(
385 ctx.clone(),
386 self.id,
387 UiBuilder::new()
388 .layer_id(LayerId::background())
389 .max_rect(available_rect),
390 );
391 panel_ui.set_clip_rect(ctx.screen_rect());
392
393 let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
394 let rect = inner_response.response.rect;
395
396 match side {
397 Side::Left => ctx.pass_state_mut(|state| {
398 state.allocate_left_panel(Rect::from_min_max(available_rect.min, rect.max));
399 }),
400 Side::Right => ctx.pass_state_mut(|state| {
401 state.allocate_right_panel(Rect::from_min_max(rect.min, available_rect.max));
402 }),
403 }
404 inner_response
405 }
406
407 pub fn show_animated<R>(
410 self,
411 ctx: &Context,
412 is_expanded: bool,
413 add_contents: impl FnOnce(&mut Ui) -> R,
414 ) -> Option<InnerResponse<R>> {
415 let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded);
416
417 if 0.0 == how_expanded {
418 None
419 } else if how_expanded < 1.0 {
420 let expanded_width = PanelState::load(ctx, self.id)
424 .map_or(self.default_width, |state| state.rect.width());
425 let fake_width = how_expanded * expanded_width;
426 Self {
427 id: self.id.with("animating_panel"),
428 ..self
429 }
430 .resizable(false)
431 .exact_width(fake_width)
432 .show(ctx, |_ui| {});
433 None
434 } else {
435 Some(self.show(ctx, add_contents))
437 }
438 }
439
440 pub fn show_animated_inside<R>(
443 self,
444 ui: &mut Ui,
445 is_expanded: bool,
446 add_contents: impl FnOnce(&mut Ui) -> R,
447 ) -> Option<InnerResponse<R>> {
448 let how_expanded = animate_expansion(ui.ctx(), self.id.with("animation"), is_expanded);
449
450 if 0.0 == how_expanded {
451 None
452 } else if how_expanded < 1.0 {
453 let expanded_width = PanelState::load(ui.ctx(), self.id)
457 .map_or(self.default_width, |state| state.rect.width());
458 let fake_width = how_expanded * expanded_width;
459 Self {
460 id: self.id.with("animating_panel"),
461 ..self
462 }
463 .resizable(false)
464 .exact_width(fake_width)
465 .show_inside(ui, |_ui| {});
466 None
467 } else {
468 Some(self.show_inside(ui, add_contents))
470 }
471 }
472
473 pub fn show_animated_between<R>(
475 ctx: &Context,
476 is_expanded: bool,
477 collapsed_panel: Self,
478 expanded_panel: Self,
479 add_contents: impl FnOnce(&mut Ui, f32) -> R,
480 ) -> Option<InnerResponse<R>> {
481 let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded);
482
483 if 0.0 == how_expanded {
484 Some(collapsed_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
485 } else if how_expanded < 1.0 {
486 let collapsed_width = PanelState::load(ctx, collapsed_panel.id)
488 .map_or(collapsed_panel.default_width, |state| state.rect.width());
489 let expanded_width = PanelState::load(ctx, expanded_panel.id)
490 .map_or(expanded_panel.default_width, |state| state.rect.width());
491 let fake_width = lerp(collapsed_width..=expanded_width, how_expanded);
492 Self {
493 id: expanded_panel.id.with("animating_panel"),
494 ..expanded_panel
495 }
496 .resizable(false)
497 .exact_width(fake_width)
498 .show(ctx, |ui| add_contents(ui, how_expanded));
499 None
500 } else {
501 Some(expanded_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
502 }
503 }
504
505 pub fn show_animated_between_inside<R>(
507 ui: &mut Ui,
508 is_expanded: bool,
509 collapsed_panel: Self,
510 expanded_panel: Self,
511 add_contents: impl FnOnce(&mut Ui, f32) -> R,
512 ) -> InnerResponse<R> {
513 let how_expanded =
514 animate_expansion(ui.ctx(), expanded_panel.id.with("animation"), is_expanded);
515
516 if 0.0 == how_expanded {
517 collapsed_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
518 } else if how_expanded < 1.0 {
519 let collapsed_width = PanelState::load(ui.ctx(), collapsed_panel.id)
521 .map_or(collapsed_panel.default_width, |state| state.rect.width());
522 let expanded_width = PanelState::load(ui.ctx(), expanded_panel.id)
523 .map_or(expanded_panel.default_width, |state| state.rect.width());
524 let fake_width = lerp(collapsed_width..=expanded_width, how_expanded);
525 Self {
526 id: expanded_panel.id.with("animating_panel"),
527 ..expanded_panel
528 }
529 .resizable(false)
530 .exact_width(fake_width)
531 .show_inside(ui, |ui| add_contents(ui, how_expanded))
532 } else {
533 expanded_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
534 }
535 }
536}
537
538#[derive(Clone, Copy, Debug, PartialEq, Eq)]
542pub enum TopBottomSide {
543 Top,
544 Bottom,
545}
546
547impl TopBottomSide {
548 fn opposite(self) -> Self {
549 match self {
550 Self::Top => Self::Bottom,
551 Self::Bottom => Self::Top,
552 }
553 }
554
555 fn set_rect_height(self, rect: &mut Rect, height: f32) {
556 match self {
557 Self::Top => rect.max.y = rect.min.y + height,
558 Self::Bottom => rect.min.y = rect.max.y - height,
559 }
560 }
561
562 fn side_y(self, rect: Rect) -> f32 {
563 match self {
564 Self::Top => rect.top(),
565 Self::Bottom => rect.bottom(),
566 }
567 }
568
569 fn sign(self) -> f32 {
570 match self {
571 Self::Top => -1.0,
572 Self::Bottom => 1.0,
573 }
574 }
575}
576
577#[must_use = "You should call .show()"]
596pub struct TopBottomPanel {
597 side: TopBottomSide,
598 id: Id,
599 frame: Option<Frame>,
600 resizable: bool,
601 show_separator_line: bool,
602 default_height: Option<f32>,
603 height_range: Rangef,
604}
605
606impl TopBottomPanel {
607 pub fn top(id: impl Into<Id>) -> Self {
609 Self::new(TopBottomSide::Top, id)
610 }
611
612 pub fn bottom(id: impl Into<Id>) -> Self {
614 Self::new(TopBottomSide::Bottom, id)
615 }
616
617 pub fn new(side: TopBottomSide, id: impl Into<Id>) -> Self {
619 Self {
620 side,
621 id: id.into(),
622 frame: None,
623 resizable: false,
624 show_separator_line: true,
625 default_height: None,
626 height_range: Rangef::new(20.0, f32::INFINITY),
627 }
628 }
629
630 #[inline]
642 pub fn resizable(mut self, resizable: bool) -> Self {
643 self.resizable = resizable;
644 self
645 }
646
647 #[inline]
651 pub fn show_separator_line(mut self, show_separator_line: bool) -> Self {
652 self.show_separator_line = show_separator_line;
653 self
654 }
655
656 #[inline]
659 pub fn default_height(mut self, default_height: f32) -> Self {
660 self.default_height = Some(default_height);
661 self.height_range = Rangef::new(
662 self.height_range.min.at_most(default_height),
663 self.height_range.max.at_least(default_height),
664 );
665 self
666 }
667
668 #[inline]
670 pub fn min_height(mut self, min_height: f32) -> Self {
671 self.height_range = Rangef::new(min_height, self.height_range.max.at_least(min_height));
672 self
673 }
674
675 #[inline]
677 pub fn max_height(mut self, max_height: f32) -> Self {
678 self.height_range = Rangef::new(self.height_range.min.at_most(max_height), max_height);
679 self
680 }
681
682 #[inline]
684 pub fn height_range(mut self, height_range: impl Into<Rangef>) -> Self {
685 let height_range = height_range.into();
686 self.default_height = self
687 .default_height
688 .map(|default_height| clamp_to_range(default_height, height_range));
689 self.height_range = height_range;
690 self
691 }
692
693 #[inline]
695 pub fn exact_height(mut self, height: f32) -> Self {
696 self.default_height = Some(height);
697 self.height_range = Rangef::point(height);
698 self
699 }
700
701 #[inline]
703 pub fn frame(mut self, frame: Frame) -> Self {
704 self.frame = Some(frame);
705 self
706 }
707}
708
709impl TopBottomPanel {
710 pub fn show_inside<R>(
712 self,
713 ui: &mut Ui,
714 add_contents: impl FnOnce(&mut Ui) -> R,
715 ) -> InnerResponse<R> {
716 self.show_inside_dyn(ui, Box::new(add_contents))
717 }
718
719 fn show_inside_dyn<'c, R>(
721 self,
722 ui: &mut Ui,
723 add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
724 ) -> InnerResponse<R> {
725 let Self {
726 side,
727 id,
728 frame,
729 resizable,
730 show_separator_line,
731 default_height,
732 height_range,
733 } = self;
734
735 let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style()));
736
737 let available_rect = ui.available_rect_before_wrap();
738 let mut panel_rect = available_rect;
739
740 let mut height = if let Some(state) = PanelState::load(ui.ctx(), id) {
741 state.rect.height()
742 } else {
743 default_height
744 .unwrap_or_else(|| ui.style().spacing.interact_size.y + frame.inner_margin.sum().y)
745 };
746 {
747 height = clamp_to_range(height, height_range).at_most(available_rect.height());
748 side.set_rect_height(&mut panel_rect, height);
749 ui.ctx()
750 .check_for_id_clash(id, panel_rect, "TopBottomPanel");
751 }
752
753 let resize_id = id.with("__resize");
754 let mut resize_hover = false;
755 let mut is_resizing = false;
756 if resizable {
757 if let Some(resize_response) = ui.ctx().read_response(resize_id) {
759 resize_hover = resize_response.hovered();
760 is_resizing = resize_response.dragged();
761
762 if is_resizing {
763 if let Some(pointer) = resize_response.interact_pointer_pos() {
764 height = (pointer.y - side.side_y(panel_rect)).abs();
765 height =
766 clamp_to_range(height, height_range).at_most(available_rect.height());
767 side.set_rect_height(&mut panel_rect, height);
768 }
769 }
770 }
771 }
772
773 panel_rect = panel_rect.round_ui();
774
775 let mut panel_ui = ui.new_child(
776 UiBuilder::new()
777 .id_salt(id)
778 .ui_stack_info(UiStackInfo::new(match side {
779 TopBottomSide::Top => UiKind::TopPanel,
780 TopBottomSide::Bottom => UiKind::BottomPanel,
781 }))
782 .max_rect(panel_rect)
783 .layout(Layout::top_down(Align::Min)),
784 );
785 panel_ui.expand_to_include_rect(panel_rect);
786 panel_ui.set_clip_rect(panel_rect); let inner_response = frame.show(&mut panel_ui, |ui| {
789 ui.set_min_width(ui.max_rect().width()); ui.set_min_height((height_range.min - frame.inner_margin.sum().y).at_least(0.0));
791 add_contents(ui)
792 });
793
794 let rect = inner_response.response.rect;
795
796 {
797 let mut cursor = ui.cursor();
798 match side {
799 TopBottomSide::Top => {
800 cursor.min.y = rect.max.y;
801 }
802 TopBottomSide::Bottom => {
803 cursor.max.y = rect.min.y;
804 }
805 }
806 ui.set_cursor(cursor);
807 }
808 ui.expand_to_include_rect(rect);
809
810 if resizable {
811 let resize_y = side.opposite().side_y(panel_rect);
816 let resize_rect = Rect::from_x_y_ranges(panel_rect.x_range(), resize_y..=resize_y)
817 .expand2(vec2(0.0, ui.style().interaction.resize_grab_radius_side));
818 let resize_response = ui.interact(resize_rect, resize_id, Sense::drag());
819 resize_hover = resize_response.hovered();
820 is_resizing = resize_response.dragged();
821 }
822
823 if resize_hover || is_resizing {
824 let cursor_icon = if height <= height_range.min {
825 match self.side {
826 TopBottomSide::Top => CursorIcon::ResizeSouth,
827 TopBottomSide::Bottom => CursorIcon::ResizeNorth,
828 }
829 } else if height < height_range.max {
830 CursorIcon::ResizeVertical
831 } else {
832 match self.side {
833 TopBottomSide::Top => CursorIcon::ResizeNorth,
834 TopBottomSide::Bottom => CursorIcon::ResizeSouth,
835 }
836 };
837 ui.ctx().set_cursor_icon(cursor_icon);
838 }
839
840 PanelState { rect }.store(ui.ctx(), id);
841
842 {
843 let stroke = if is_resizing {
844 ui.style().visuals.widgets.active.fg_stroke } else if resize_hover {
846 ui.style().visuals.widgets.hovered.fg_stroke } else if show_separator_line {
848 ui.style().visuals.widgets.noninteractive.bg_stroke } else {
851 Stroke::NONE
852 };
853 let resize_y = side.opposite().side_y(rect);
855
856 let resize_y = resize_y + 0.5 * side.sign() * stroke.width;
858 ui.painter().hline(panel_rect.x_range(), resize_y, stroke);
859 }
860
861 inner_response
862 }
863
864 pub fn show<R>(
866 self,
867 ctx: &Context,
868 add_contents: impl FnOnce(&mut Ui) -> R,
869 ) -> InnerResponse<R> {
870 self.show_dyn(ctx, Box::new(add_contents))
871 }
872
873 fn show_dyn<'c, R>(
875 self,
876 ctx: &Context,
877 add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
878 ) -> InnerResponse<R> {
879 let available_rect = ctx.available_rect();
880 let side = self.side;
881
882 let mut panel_ui = Ui::new(
883 ctx.clone(),
884 self.id,
885 UiBuilder::new()
886 .layer_id(LayerId::background())
887 .max_rect(available_rect),
888 );
889 panel_ui.set_clip_rect(ctx.screen_rect());
890
891 let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
892 let rect = inner_response.response.rect;
893
894 match side {
895 TopBottomSide::Top => {
896 ctx.pass_state_mut(|state| {
897 state.allocate_top_panel(Rect::from_min_max(available_rect.min, rect.max));
898 });
899 }
900 TopBottomSide::Bottom => {
901 ctx.pass_state_mut(|state| {
902 state.allocate_bottom_panel(Rect::from_min_max(rect.min, available_rect.max));
903 });
904 }
905 }
906
907 inner_response
908 }
909
910 pub fn show_animated<R>(
913 self,
914 ctx: &Context,
915 is_expanded: bool,
916 add_contents: impl FnOnce(&mut Ui) -> R,
917 ) -> Option<InnerResponse<R>> {
918 let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded);
919
920 if 0.0 == how_expanded {
921 None
922 } else if how_expanded < 1.0 {
923 let expanded_height = PanelState::load(ctx, self.id)
927 .map(|state| state.rect.height())
928 .or(self.default_height)
929 .unwrap_or_else(|| ctx.style().spacing.interact_size.y);
930 let fake_height = how_expanded * expanded_height;
931 Self {
932 id: self.id.with("animating_panel"),
933 ..self
934 }
935 .resizable(false)
936 .exact_height(fake_height)
937 .show(ctx, |_ui| {});
938 None
939 } else {
940 Some(self.show(ctx, add_contents))
942 }
943 }
944
945 pub fn show_animated_inside<R>(
948 self,
949 ui: &mut Ui,
950 is_expanded: bool,
951 add_contents: impl FnOnce(&mut Ui) -> R,
952 ) -> Option<InnerResponse<R>> {
953 let how_expanded = animate_expansion(ui.ctx(), self.id.with("animation"), is_expanded);
954
955 if 0.0 == how_expanded {
956 None
957 } else if how_expanded < 1.0 {
958 let expanded_height = PanelState::load(ui.ctx(), self.id)
962 .map(|state| state.rect.height())
963 .or(self.default_height)
964 .unwrap_or_else(|| ui.style().spacing.interact_size.y);
965 let fake_height = how_expanded * expanded_height;
966 Self {
967 id: self.id.with("animating_panel"),
968 ..self
969 }
970 .resizable(false)
971 .exact_height(fake_height)
972 .show_inside(ui, |_ui| {});
973 None
974 } else {
975 Some(self.show_inside(ui, add_contents))
977 }
978 }
979
980 pub fn show_animated_between<R>(
982 ctx: &Context,
983 is_expanded: bool,
984 collapsed_panel: Self,
985 expanded_panel: Self,
986 add_contents: impl FnOnce(&mut Ui, f32) -> R,
987 ) -> Option<InnerResponse<R>> {
988 let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded);
989
990 if 0.0 == how_expanded {
991 Some(collapsed_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
992 } else if how_expanded < 1.0 {
993 let collapsed_height = PanelState::load(ctx, collapsed_panel.id)
995 .map(|state| state.rect.height())
996 .or(collapsed_panel.default_height)
997 .unwrap_or_else(|| ctx.style().spacing.interact_size.y);
998
999 let expanded_height = PanelState::load(ctx, expanded_panel.id)
1000 .map(|state| state.rect.height())
1001 .or(expanded_panel.default_height)
1002 .unwrap_or_else(|| ctx.style().spacing.interact_size.y);
1003
1004 let fake_height = lerp(collapsed_height..=expanded_height, how_expanded);
1005 Self {
1006 id: expanded_panel.id.with("animating_panel"),
1007 ..expanded_panel
1008 }
1009 .resizable(false)
1010 .exact_height(fake_height)
1011 .show(ctx, |ui| add_contents(ui, how_expanded));
1012 None
1013 } else {
1014 Some(expanded_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
1015 }
1016 }
1017
1018 pub fn show_animated_between_inside<R>(
1020 ui: &mut Ui,
1021 is_expanded: bool,
1022 collapsed_panel: Self,
1023 expanded_panel: Self,
1024 add_contents: impl FnOnce(&mut Ui, f32) -> R,
1025 ) -> InnerResponse<R> {
1026 let how_expanded =
1027 animate_expansion(ui.ctx(), expanded_panel.id.with("animation"), is_expanded);
1028
1029 if 0.0 == how_expanded {
1030 collapsed_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
1031 } else if how_expanded < 1.0 {
1032 let collapsed_height = PanelState::load(ui.ctx(), collapsed_panel.id)
1034 .map(|state| state.rect.height())
1035 .or(collapsed_panel.default_height)
1036 .unwrap_or_else(|| ui.style().spacing.interact_size.y);
1037
1038 let expanded_height = PanelState::load(ui.ctx(), expanded_panel.id)
1039 .map(|state| state.rect.height())
1040 .or(expanded_panel.default_height)
1041 .unwrap_or_else(|| ui.style().spacing.interact_size.y);
1042
1043 let fake_height = lerp(collapsed_height..=expanded_height, how_expanded);
1044 Self {
1045 id: expanded_panel.id.with("animating_panel"),
1046 ..expanded_panel
1047 }
1048 .resizable(false)
1049 .exact_height(fake_height)
1050 .show_inside(ui, |ui| add_contents(ui, how_expanded))
1051 } else {
1052 expanded_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
1053 }
1054 }
1055}
1056
1057#[must_use = "You should call .show()"]
1082#[derive(Default)]
1083pub struct CentralPanel {
1084 frame: Option<Frame>,
1085}
1086
1087impl CentralPanel {
1088 #[inline]
1090 pub fn frame(mut self, frame: Frame) -> Self {
1091 self.frame = Some(frame);
1092 self
1093 }
1094}
1095
1096impl CentralPanel {
1097 pub fn show_inside<R>(
1099 self,
1100 ui: &mut Ui,
1101 add_contents: impl FnOnce(&mut Ui) -> R,
1102 ) -> InnerResponse<R> {
1103 self.show_inside_dyn(ui, Box::new(add_contents))
1104 }
1105
1106 fn show_inside_dyn<'c, R>(
1108 self,
1109 ui: &mut Ui,
1110 add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
1111 ) -> InnerResponse<R> {
1112 let Self { frame } = self;
1113
1114 let panel_rect = ui.available_rect_before_wrap();
1115 let mut panel_ui = ui.new_child(
1116 UiBuilder::new()
1117 .ui_stack_info(UiStackInfo::new(UiKind::CentralPanel))
1118 .max_rect(panel_rect)
1119 .layout(Layout::top_down(Align::Min)),
1120 );
1121 panel_ui.set_clip_rect(panel_rect); let frame = frame.unwrap_or_else(|| Frame::central_panel(ui.style()));
1124 frame.show(&mut panel_ui, |ui| {
1125 ui.expand_to_include_rect(ui.max_rect()); add_contents(ui)
1127 })
1128 }
1129
1130 pub fn show<R>(
1132 self,
1133 ctx: &Context,
1134 add_contents: impl FnOnce(&mut Ui) -> R,
1135 ) -> InnerResponse<R> {
1136 self.show_dyn(ctx, Box::new(add_contents))
1137 }
1138
1139 fn show_dyn<'c, R>(
1141 self,
1142 ctx: &Context,
1143 add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
1144 ) -> InnerResponse<R> {
1145 let id = Id::new((ctx.viewport_id(), "central_panel"));
1146
1147 let mut panel_ui = Ui::new(
1148 ctx.clone(),
1149 id,
1150 UiBuilder::new()
1151 .layer_id(LayerId::background())
1152 .max_rect(ctx.available_rect().round_ui()),
1153 );
1154 panel_ui.set_clip_rect(ctx.screen_rect());
1155
1156 let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
1157
1158 ctx.pass_state_mut(|state| state.allocate_central_panel(inner_response.response.rect));
1160
1161 inner_response
1162 }
1163}
1164
1165fn clamp_to_range(x: f32, range: Rangef) -> f32 {
1166 let range = range.as_positive();
1167 x.clamp(range.min, range.max)
1168}