use crate::{
lerp, vec2, Align, Context, CursorIcon, Frame, Id, InnerResponse, LayerId, Layout, NumExt,
Rangef, Rect, Sense, Stroke, Ui, UiBuilder, UiKind, UiStackInfo, Vec2,
};
fn animate_expansion(ctx: &Context, id: Id, is_expanded: bool) -> f32 {
ctx.animate_bool_responsive(id, is_expanded)
}
#[derive(Clone, Copy, Debug)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct PanelState {
pub rect: Rect,
}
impl PanelState {
pub fn load(ctx: &Context, bar_id: Id) -> Option<Self> {
ctx.data_mut(|d| d.get_persisted(bar_id))
}
pub fn size(&self) -> Vec2 {
self.rect.size()
}
fn store(self, ctx: &Context, bar_id: Id) {
ctx.data_mut(|d| d.insert_persisted(bar_id, self));
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Side {
Left,
Right,
}
impl Side {
fn opposite(self) -> Self {
match self {
Self::Left => Self::Right,
Self::Right => Self::Left,
}
}
fn set_rect_width(self, rect: &mut Rect, width: f32) {
match self {
Self::Left => rect.max.x = rect.min.x + width,
Self::Right => rect.min.x = rect.max.x - width,
}
}
fn side_x(self, rect: Rect) -> f32 {
match self {
Self::Left => rect.left(),
Self::Right => rect.right(),
}
}
}
#[must_use = "You should call .show()"]
pub struct SidePanel {
side: Side,
id: Id,
frame: Option<Frame>,
resizable: bool,
show_separator_line: bool,
default_width: f32,
width_range: Rangef,
}
impl SidePanel {
pub fn left(id: impl Into<Id>) -> Self {
Self::new(Side::Left, id)
}
pub fn right(id: impl Into<Id>) -> Self {
Self::new(Side::Right, id)
}
pub fn new(side: Side, id: impl Into<Id>) -> Self {
Self {
side,
id: id.into(),
frame: None,
resizable: true,
show_separator_line: true,
default_width: 200.0,
width_range: Rangef::new(96.0, f32::INFINITY),
}
}
#[inline]
pub fn resizable(mut self, resizable: bool) -> Self {
self.resizable = resizable;
self
}
#[inline]
pub fn show_separator_line(mut self, show_separator_line: bool) -> Self {
self.show_separator_line = show_separator_line;
self
}
#[inline]
pub fn default_width(mut self, default_width: f32) -> Self {
self.default_width = default_width;
self.width_range = Rangef::new(
self.width_range.min.at_most(default_width),
self.width_range.max.at_least(default_width),
);
self
}
#[inline]
pub fn min_width(mut self, min_width: f32) -> Self {
self.width_range = Rangef::new(min_width, self.width_range.max.at_least(min_width));
self
}
#[inline]
pub fn max_width(mut self, max_width: f32) -> Self {
self.width_range = Rangef::new(self.width_range.min.at_most(max_width), max_width);
self
}
#[inline]
pub fn width_range(mut self, width_range: impl Into<Rangef>) -> Self {
let width_range = width_range.into();
self.default_width = clamp_to_range(self.default_width, width_range);
self.width_range = width_range;
self
}
#[inline]
pub fn exact_width(mut self, width: f32) -> Self {
self.default_width = width;
self.width_range = Rangef::point(width);
self
}
#[inline]
pub fn frame(mut self, frame: Frame) -> Self {
self.frame = Some(frame);
self
}
}
impl SidePanel {
pub fn show_inside<R>(
self,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.show_inside_dyn(ui, Box::new(add_contents))
}
fn show_inside_dyn<'c, R>(
self,
ui: &mut Ui,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<R> {
let Self {
side,
id,
frame,
resizable,
show_separator_line,
default_width,
width_range,
} = self;
let available_rect = ui.available_rect_before_wrap();
let mut panel_rect = available_rect;
let mut width = default_width;
{
if let Some(state) = PanelState::load(ui.ctx(), id) {
width = state.rect.width();
}
width = clamp_to_range(width, width_range).at_most(available_rect.width());
side.set_rect_width(&mut panel_rect, width);
ui.ctx().check_for_id_clash(id, panel_rect, "SidePanel");
}
let resize_id = id.with("__resize");
let mut resize_hover = false;
let mut is_resizing = false;
if resizable {
if let Some(resize_response) = ui.ctx().read_response(resize_id) {
resize_hover = resize_response.hovered();
is_resizing = resize_response.dragged();
if is_resizing {
if let Some(pointer) = resize_response.interact_pointer_pos() {
width = (pointer.x - side.side_x(panel_rect)).abs();
width = clamp_to_range(width, width_range).at_most(available_rect.width());
side.set_rect_width(&mut panel_rect, width);
}
}
}
}
let mut panel_ui = ui.new_child(
UiBuilder::new()
.id_salt(id)
.ui_stack_info(UiStackInfo::new(match side {
Side::Left => UiKind::LeftPanel,
Side::Right => UiKind::RightPanel,
}))
.max_rect(panel_rect)
.layout(Layout::top_down(Align::Min)),
);
panel_ui.expand_to_include_rect(panel_rect);
panel_ui.set_clip_rect(panel_rect); let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style()));
let inner_response = frame.show(&mut panel_ui, |ui| {
ui.set_min_height(ui.max_rect().height()); ui.set_min_width((width_range.min - frame.inner_margin.sum().x).at_least(0.0));
add_contents(ui)
});
let rect = inner_response.response.rect;
{
let mut cursor = ui.cursor();
match side {
Side::Left => {
cursor.min.x = rect.max.x;
}
Side::Right => {
cursor.max.x = rect.min.x;
}
}
ui.set_cursor(cursor);
}
ui.expand_to_include_rect(rect);
if resizable {
let resize_x = side.opposite().side_x(panel_rect);
let resize_rect = Rect::from_x_y_ranges(resize_x..=resize_x, panel_rect.y_range())
.expand2(vec2(ui.style().interaction.resize_grab_radius_side, 0.0));
let resize_response = ui.interact(resize_rect, resize_id, Sense::drag());
resize_hover = resize_response.hovered();
is_resizing = resize_response.dragged();
}
if resize_hover || is_resizing {
let cursor_icon = if width <= width_range.min {
match self.side {
Side::Left => CursorIcon::ResizeEast,
Side::Right => CursorIcon::ResizeWest,
}
} else if width < width_range.max {
CursorIcon::ResizeHorizontal
} else {
match self.side {
Side::Left => CursorIcon::ResizeWest,
Side::Right => CursorIcon::ResizeEast,
}
};
ui.ctx().set_cursor_icon(cursor_icon);
}
PanelState { rect }.store(ui.ctx(), id);
{
let stroke = if is_resizing {
ui.style().visuals.widgets.active.fg_stroke } else if resize_hover {
ui.style().visuals.widgets.hovered.fg_stroke } else if show_separator_line {
ui.style().visuals.widgets.noninteractive.bg_stroke } else {
Stroke::NONE
};
let resize_x = side.opposite().side_x(rect);
let resize_x = ui.painter().round_to_pixel_center(resize_x);
let resize_x = resize_x - if side == Side::Left { 1.0 } else { 0.0 };
ui.painter().vline(resize_x, panel_rect.y_range(), stroke);
}
inner_response
}
pub fn show<R>(
self,
ctx: &Context,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.show_dyn(ctx, Box::new(add_contents))
}
fn show_dyn<'c, R>(
self,
ctx: &Context,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<R> {
let layer_id = LayerId::background();
let side = self.side;
let available_rect = ctx.available_rect();
let mut panel_ui = Ui::new(
ctx.clone(),
layer_id,
self.id,
UiBuilder::new().max_rect(available_rect),
);
panel_ui.set_clip_rect(ctx.screen_rect());
let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
let rect = inner_response.response.rect;
match side {
Side::Left => ctx.pass_state_mut(|state| {
state.allocate_left_panel(Rect::from_min_max(available_rect.min, rect.max));
}),
Side::Right => ctx.pass_state_mut(|state| {
state.allocate_right_panel(Rect::from_min_max(rect.min, available_rect.max));
}),
}
inner_response
}
pub fn show_animated<R>(
self,
ctx: &Context,
is_expanded: bool,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<InnerResponse<R>> {
let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded);
if 0.0 == how_expanded {
None
} else if how_expanded < 1.0 {
let expanded_width = PanelState::load(ctx, self.id)
.map_or(self.default_width, |state| state.rect.width());
let fake_width = how_expanded * expanded_width;
Self {
id: self.id.with("animating_panel"),
..self
}
.resizable(false)
.exact_width(fake_width)
.show(ctx, |_ui| {});
None
} else {
Some(self.show(ctx, add_contents))
}
}
pub fn show_animated_inside<R>(
self,
ui: &mut Ui,
is_expanded: bool,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<InnerResponse<R>> {
let how_expanded = animate_expansion(ui.ctx(), self.id.with("animation"), is_expanded);
if 0.0 == how_expanded {
None
} else if how_expanded < 1.0 {
let expanded_width = PanelState::load(ui.ctx(), self.id)
.map_or(self.default_width, |state| state.rect.width());
let fake_width = how_expanded * expanded_width;
Self {
id: self.id.with("animating_panel"),
..self
}
.resizable(false)
.exact_width(fake_width)
.show_inside(ui, |_ui| {});
None
} else {
Some(self.show_inside(ui, add_contents))
}
}
pub fn show_animated_between<R>(
ctx: &Context,
is_expanded: bool,
collapsed_panel: Self,
expanded_panel: Self,
add_contents: impl FnOnce(&mut Ui, f32) -> R,
) -> Option<InnerResponse<R>> {
let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded);
if 0.0 == how_expanded {
Some(collapsed_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
} else if how_expanded < 1.0 {
let collapsed_width = PanelState::load(ctx, collapsed_panel.id)
.map_or(collapsed_panel.default_width, |state| state.rect.width());
let expanded_width = PanelState::load(ctx, expanded_panel.id)
.map_or(expanded_panel.default_width, |state| state.rect.width());
let fake_width = lerp(collapsed_width..=expanded_width, how_expanded);
Self {
id: expanded_panel.id.with("animating_panel"),
..expanded_panel
}
.resizable(false)
.exact_width(fake_width)
.show(ctx, |ui| add_contents(ui, how_expanded));
None
} else {
Some(expanded_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
}
}
pub fn show_animated_between_inside<R>(
ui: &mut Ui,
is_expanded: bool,
collapsed_panel: Self,
expanded_panel: Self,
add_contents: impl FnOnce(&mut Ui, f32) -> R,
) -> InnerResponse<R> {
let how_expanded =
animate_expansion(ui.ctx(), expanded_panel.id.with("animation"), is_expanded);
if 0.0 == how_expanded {
collapsed_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
} else if how_expanded < 1.0 {
let collapsed_width = PanelState::load(ui.ctx(), collapsed_panel.id)
.map_or(collapsed_panel.default_width, |state| state.rect.width());
let expanded_width = PanelState::load(ui.ctx(), expanded_panel.id)
.map_or(expanded_panel.default_width, |state| state.rect.width());
let fake_width = lerp(collapsed_width..=expanded_width, how_expanded);
Self {
id: expanded_panel.id.with("animating_panel"),
..expanded_panel
}
.resizable(false)
.exact_width(fake_width)
.show_inside(ui, |ui| add_contents(ui, how_expanded))
} else {
expanded_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TopBottomSide {
Top,
Bottom,
}
impl TopBottomSide {
fn opposite(self) -> Self {
match self {
Self::Top => Self::Bottom,
Self::Bottom => Self::Top,
}
}
fn set_rect_height(self, rect: &mut Rect, height: f32) {
match self {
Self::Top => rect.max.y = rect.min.y + height,
Self::Bottom => rect.min.y = rect.max.y - height,
}
}
fn side_y(self, rect: Rect) -> f32 {
match self {
Self::Top => rect.top(),
Self::Bottom => rect.bottom(),
}
}
}
#[must_use = "You should call .show()"]
pub struct TopBottomPanel {
side: TopBottomSide,
id: Id,
frame: Option<Frame>,
resizable: bool,
show_separator_line: bool,
default_height: Option<f32>,
height_range: Rangef,
}
impl TopBottomPanel {
pub fn top(id: impl Into<Id>) -> Self {
Self::new(TopBottomSide::Top, id)
}
pub fn bottom(id: impl Into<Id>) -> Self {
Self::new(TopBottomSide::Bottom, id)
}
pub fn new(side: TopBottomSide, id: impl Into<Id>) -> Self {
Self {
side,
id: id.into(),
frame: None,
resizable: false,
show_separator_line: true,
default_height: None,
height_range: Rangef::new(20.0, f32::INFINITY),
}
}
#[inline]
pub fn resizable(mut self, resizable: bool) -> Self {
self.resizable = resizable;
self
}
#[inline]
pub fn show_separator_line(mut self, show_separator_line: bool) -> Self {
self.show_separator_line = show_separator_line;
self
}
#[inline]
pub fn default_height(mut self, default_height: f32) -> Self {
self.default_height = Some(default_height);
self.height_range = Rangef::new(
self.height_range.min.at_most(default_height),
self.height_range.max.at_least(default_height),
);
self
}
#[inline]
pub fn min_height(mut self, min_height: f32) -> Self {
self.height_range = Rangef::new(min_height, self.height_range.max.at_least(min_height));
self
}
#[inline]
pub fn max_height(mut self, max_height: f32) -> Self {
self.height_range = Rangef::new(self.height_range.min.at_most(max_height), max_height);
self
}
#[inline]
pub fn height_range(mut self, height_range: impl Into<Rangef>) -> Self {
let height_range = height_range.into();
self.default_height = self
.default_height
.map(|default_height| clamp_to_range(default_height, height_range));
self.height_range = height_range;
self
}
#[inline]
pub fn exact_height(mut self, height: f32) -> Self {
self.default_height = Some(height);
self.height_range = Rangef::point(height);
self
}
#[inline]
pub fn frame(mut self, frame: Frame) -> Self {
self.frame = Some(frame);
self
}
}
impl TopBottomPanel {
pub fn show_inside<R>(
self,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.show_inside_dyn(ui, Box::new(add_contents))
}
fn show_inside_dyn<'c, R>(
self,
ui: &mut Ui,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<R> {
let Self {
side,
id,
frame,
resizable,
show_separator_line,
default_height,
height_range,
} = self;
let frame = frame.unwrap_or_else(|| Frame::side_top_panel(ui.style()));
let available_rect = ui.available_rect_before_wrap();
let mut panel_rect = available_rect;
let mut height = if let Some(state) = PanelState::load(ui.ctx(), id) {
state.rect.height()
} else {
default_height
.unwrap_or_else(|| ui.style().spacing.interact_size.y + frame.inner_margin.sum().y)
};
{
height = clamp_to_range(height, height_range).at_most(available_rect.height());
side.set_rect_height(&mut panel_rect, height);
ui.ctx()
.check_for_id_clash(id, panel_rect, "TopBottomPanel");
}
let resize_id = id.with("__resize");
let mut resize_hover = false;
let mut is_resizing = false;
if resizable {
if let Some(resize_response) = ui.ctx().read_response(resize_id) {
resize_hover = resize_response.hovered();
is_resizing = resize_response.dragged();
if is_resizing {
if let Some(pointer) = resize_response.interact_pointer_pos() {
height = (pointer.y - side.side_y(panel_rect)).abs();
height =
clamp_to_range(height, height_range).at_most(available_rect.height());
side.set_rect_height(&mut panel_rect, height);
}
}
}
}
let mut panel_ui = ui.new_child(
UiBuilder::new()
.id_salt(id)
.ui_stack_info(UiStackInfo::new(match side {
TopBottomSide::Top => UiKind::TopPanel,
TopBottomSide::Bottom => UiKind::BottomPanel,
}))
.max_rect(panel_rect)
.layout(Layout::top_down(Align::Min)),
);
panel_ui.expand_to_include_rect(panel_rect);
panel_ui.set_clip_rect(panel_rect); let inner_response = frame.show(&mut panel_ui, |ui| {
ui.set_min_width(ui.max_rect().width()); ui.set_min_height((height_range.min - frame.inner_margin.sum().y).at_least(0.0));
add_contents(ui)
});
let rect = inner_response.response.rect;
{
let mut cursor = ui.cursor();
match side {
TopBottomSide::Top => {
cursor.min.y = rect.max.y;
}
TopBottomSide::Bottom => {
cursor.max.y = rect.min.y;
}
}
ui.set_cursor(cursor);
}
ui.expand_to_include_rect(rect);
if resizable {
let resize_y = side.opposite().side_y(panel_rect);
let resize_rect = Rect::from_x_y_ranges(panel_rect.x_range(), resize_y..=resize_y)
.expand2(vec2(0.0, ui.style().interaction.resize_grab_radius_side));
let resize_response = ui.interact(resize_rect, resize_id, Sense::drag());
resize_hover = resize_response.hovered();
is_resizing = resize_response.dragged();
}
if resize_hover || is_resizing {
let cursor_icon = if height <= height_range.min {
match self.side {
TopBottomSide::Top => CursorIcon::ResizeSouth,
TopBottomSide::Bottom => CursorIcon::ResizeNorth,
}
} else if height < height_range.max {
CursorIcon::ResizeVertical
} else {
match self.side {
TopBottomSide::Top => CursorIcon::ResizeNorth,
TopBottomSide::Bottom => CursorIcon::ResizeSouth,
}
};
ui.ctx().set_cursor_icon(cursor_icon);
}
PanelState { rect }.store(ui.ctx(), id);
{
let stroke = if is_resizing {
ui.style().visuals.widgets.active.fg_stroke } else if resize_hover {
ui.style().visuals.widgets.hovered.fg_stroke } else if show_separator_line {
ui.style().visuals.widgets.noninteractive.bg_stroke } else {
Stroke::NONE
};
let resize_y = side.opposite().side_y(rect);
let resize_y = ui.painter().round_to_pixel_center(resize_y);
let resize_y = resize_y - if side == TopBottomSide::Top { 1.0 } else { 0.0 };
ui.painter().hline(panel_rect.x_range(), resize_y, stroke);
}
inner_response
}
pub fn show<R>(
self,
ctx: &Context,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.show_dyn(ctx, Box::new(add_contents))
}
fn show_dyn<'c, R>(
self,
ctx: &Context,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<R> {
let layer_id = LayerId::background();
let available_rect = ctx.available_rect();
let side = self.side;
let mut panel_ui = Ui::new(
ctx.clone(),
layer_id,
self.id,
UiBuilder::new().max_rect(available_rect),
);
panel_ui.set_clip_rect(ctx.screen_rect());
let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
let rect = inner_response.response.rect;
match side {
TopBottomSide::Top => {
ctx.pass_state_mut(|state| {
state.allocate_top_panel(Rect::from_min_max(available_rect.min, rect.max));
});
}
TopBottomSide::Bottom => {
ctx.pass_state_mut(|state| {
state.allocate_bottom_panel(Rect::from_min_max(rect.min, available_rect.max));
});
}
}
inner_response
}
pub fn show_animated<R>(
self,
ctx: &Context,
is_expanded: bool,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<InnerResponse<R>> {
let how_expanded = animate_expansion(ctx, self.id.with("animation"), is_expanded);
if 0.0 == how_expanded {
None
} else if how_expanded < 1.0 {
let expanded_height = PanelState::load(ctx, self.id)
.map(|state| state.rect.height())
.or(self.default_height)
.unwrap_or_else(|| ctx.style().spacing.interact_size.y);
let fake_height = how_expanded * expanded_height;
Self {
id: self.id.with("animating_panel"),
..self
}
.resizable(false)
.exact_height(fake_height)
.show(ctx, |_ui| {});
None
} else {
Some(self.show(ctx, add_contents))
}
}
pub fn show_animated_inside<R>(
self,
ui: &mut Ui,
is_expanded: bool,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> Option<InnerResponse<R>> {
let how_expanded = animate_expansion(ui.ctx(), self.id.with("animation"), is_expanded);
if 0.0 == how_expanded {
None
} else if how_expanded < 1.0 {
let expanded_height = PanelState::load(ui.ctx(), self.id)
.map(|state| state.rect.height())
.or(self.default_height)
.unwrap_or_else(|| ui.style().spacing.interact_size.y);
let fake_height = how_expanded * expanded_height;
Self {
id: self.id.with("animating_panel"),
..self
}
.resizable(false)
.exact_height(fake_height)
.show_inside(ui, |_ui| {});
None
} else {
Some(self.show_inside(ui, add_contents))
}
}
pub fn show_animated_between<R>(
ctx: &Context,
is_expanded: bool,
collapsed_panel: Self,
expanded_panel: Self,
add_contents: impl FnOnce(&mut Ui, f32) -> R,
) -> Option<InnerResponse<R>> {
let how_expanded = animate_expansion(ctx, expanded_panel.id.with("animation"), is_expanded);
if 0.0 == how_expanded {
Some(collapsed_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
} else if how_expanded < 1.0 {
let collapsed_height = PanelState::load(ctx, collapsed_panel.id)
.map(|state| state.rect.height())
.or(collapsed_panel.default_height)
.unwrap_or_else(|| ctx.style().spacing.interact_size.y);
let expanded_height = PanelState::load(ctx, expanded_panel.id)
.map(|state| state.rect.height())
.or(expanded_panel.default_height)
.unwrap_or_else(|| ctx.style().spacing.interact_size.y);
let fake_height = lerp(collapsed_height..=expanded_height, how_expanded);
Self {
id: expanded_panel.id.with("animating_panel"),
..expanded_panel
}
.resizable(false)
.exact_height(fake_height)
.show(ctx, |ui| add_contents(ui, how_expanded));
None
} else {
Some(expanded_panel.show(ctx, |ui| add_contents(ui, how_expanded)))
}
}
pub fn show_animated_between_inside<R>(
ui: &mut Ui,
is_expanded: bool,
collapsed_panel: Self,
expanded_panel: Self,
add_contents: impl FnOnce(&mut Ui, f32) -> R,
) -> InnerResponse<R> {
let how_expanded =
animate_expansion(ui.ctx(), expanded_panel.id.with("animation"), is_expanded);
if 0.0 == how_expanded {
collapsed_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
} else if how_expanded < 1.0 {
let collapsed_height = PanelState::load(ui.ctx(), collapsed_panel.id)
.map(|state| state.rect.height())
.or(collapsed_panel.default_height)
.unwrap_or_else(|| ui.style().spacing.interact_size.y);
let expanded_height = PanelState::load(ui.ctx(), expanded_panel.id)
.map(|state| state.rect.height())
.or(expanded_panel.default_height)
.unwrap_or_else(|| ui.style().spacing.interact_size.y);
let fake_height = lerp(collapsed_height..=expanded_height, how_expanded);
Self {
id: expanded_panel.id.with("animating_panel"),
..expanded_panel
}
.resizable(false)
.exact_height(fake_height)
.show_inside(ui, |ui| add_contents(ui, how_expanded))
} else {
expanded_panel.show_inside(ui, |ui| add_contents(ui, how_expanded))
}
}
}
#[must_use = "You should call .show()"]
#[derive(Default)]
pub struct CentralPanel {
frame: Option<Frame>,
}
impl CentralPanel {
#[inline]
pub fn frame(mut self, frame: Frame) -> Self {
self.frame = Some(frame);
self
}
}
impl CentralPanel {
pub fn show_inside<R>(
self,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.show_inside_dyn(ui, Box::new(add_contents))
}
fn show_inside_dyn<'c, R>(
self,
ui: &mut Ui,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<R> {
let Self { frame } = self;
let panel_rect = ui.available_rect_before_wrap();
let mut panel_ui = ui.new_child(
UiBuilder::new()
.ui_stack_info(UiStackInfo::new(UiKind::CentralPanel))
.max_rect(panel_rect)
.layout(Layout::top_down(Align::Min)),
);
panel_ui.set_clip_rect(panel_rect); let frame = frame.unwrap_or_else(|| Frame::central_panel(ui.style()));
frame.show(&mut panel_ui, |ui| {
ui.expand_to_include_rect(ui.max_rect()); add_contents(ui)
})
}
pub fn show<R>(
self,
ctx: &Context,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
self.show_dyn(ctx, Box::new(add_contents))
}
fn show_dyn<'c, R>(
self,
ctx: &Context,
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
) -> InnerResponse<R> {
let available_rect = ctx.available_rect();
let layer_id = LayerId::background();
let id = Id::new((ctx.viewport_id(), "central_panel"));
let mut panel_ui = Ui::new(
ctx.clone(),
layer_id,
id,
UiBuilder::new().max_rect(available_rect),
);
panel_ui.set_clip_rect(ctx.screen_rect());
let inner_response = self.show_inside_dyn(&mut panel_ui, add_contents);
ctx.pass_state_mut(|state| state.allocate_central_panel(inner_response.response.rect));
inner_response
}
}
fn clamp_to_range(x: f32, range: Rangef) -> f32 {
let range = range.as_positive();
x.clamp(range.min, range.max)
}