Add possibility of panels inside UI (i.e. windows) (#624)

* Adding possibility to have panels inside UI

* Adding window with panels demo
This commit is contained in:
gents83 2021-08-16 21:32:44 +02:00 committed by GitHub
parent d6299bcd91
commit ff8c4c0d38
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 193 additions and 62 deletions

View file

@ -137,9 +137,9 @@ impl SidePanel {
}
impl SidePanel {
pub fn show<R>(
pub fn show_inside<R>(
self,
ctx: &CtxRef,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let Self {
@ -151,13 +151,11 @@ impl SidePanel {
width_range,
} = self;
let layer_id = LayerId::background();
let available_rect = ctx.available_rect();
let available_rect = ui.max_rect();
let mut panel_rect = available_rect;
{
let mut width = default_width;
if let Some(state) = ctx.memory().id_data.get::<PanelState>(&id) {
if let Some(state) = ui.memory().id_data.get::<PanelState>(&id) {
width = state.rect.width();
}
width = clamp_to_range(width, width_range.clone()).at_most(available_rect.width());
@ -168,24 +166,25 @@ impl SidePanel {
let mut is_resizing = false;
if resizable {
let resize_id = id.with("__resize");
if let Some(pointer) = ctx.input().pointer.latest_pos() {
let we_are_on_top = ctx
if let Some(pointer) = ui.input().pointer.latest_pos() {
let we_are_on_top = ui
.ctx()
.layer_id_at(pointer)
.map_or(true, |top_layer_id| top_layer_id == layer_id);
.map_or(true, |top_layer_id| top_layer_id == ui.layer_id());
let resize_x = side.opposite().side_x(panel_rect);
let mouse_over_resize_line = we_are_on_top
&& panel_rect.y_range().contains(&pointer.y)
&& (resize_x - pointer.x).abs()
<= ctx.style().interaction.resize_grab_radius_side;
<= ui.style().interaction.resize_grab_radius_side;
if ctx.input().pointer.any_pressed()
&& ctx.input().pointer.any_down()
if ui.input().pointer.any_pressed()
&& ui.input().pointer.any_down()
&& mouse_over_resize_line
{
ctx.memory().interaction.drag_id = Some(resize_id);
ui.memory().interaction.drag_id = Some(resize_id);
}
is_resizing = ctx.memory().interaction.drag_id == Some(resize_id);
is_resizing = ui.memory().interaction.drag_id == Some(resize_id);
if is_resizing {
let width = (pointer.x - side.side_x(panel_rect)).abs();
let width = clamp_to_range(width, width_range).at_most(available_rect.width());
@ -193,42 +192,58 @@ impl SidePanel {
}
let dragging_something_else =
ctx.input().pointer.any_down() || ctx.input().pointer.any_pressed();
ui.input().pointer.any_down() || ui.input().pointer.any_pressed();
resize_hover = mouse_over_resize_line && !dragging_something_else;
if resize_hover || is_resizing {
ctx.output().cursor_icon = CursorIcon::ResizeHorizontal;
ui.output().cursor_icon = CursorIcon::ResizeHorizontal;
}
}
}
let clip_rect = ctx.input().screen_rect();
let mut panel_ui = Ui::new(ctx.clone(), layer_id, id, panel_rect, clip_rect);
let frame = frame.unwrap_or_else(|| Frame::side_top_panel(&ctx.style()));
let mut panel_ui = ui.child_ui(panel_rect, Layout::top_down(Align::Min));
panel_ui.expand_to_include_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_finite().height()); // Make sure the frame fills the full height
add_contents(ui)
});
let rect = inner_response.response.rect;
ctx.memory().id_data.insert(id, PanelState { rect });
ui.memory().id_data.insert(id, PanelState { rect });
if resize_hover || is_resizing {
let stroke = if is_resizing {
ctx.style().visuals.widgets.active.bg_stroke
ui.style().visuals.widgets.active.bg_stroke
} else {
ctx.style().visuals.widgets.hovered.bg_stroke
ui.style().visuals.widgets.hovered.bg_stroke
};
// draw on top of ALL panels so that the resize line won't be covered by subsequent panels
let resize_layer = LayerId::new(Order::PanelResizeLine, Id::new("panel_resize"));
let resize_layer = LayerId::new(Order::Foreground, Id::new("panel_resize"));
let resize_x = side.opposite().side_x(rect);
let top = pos2(resize_x, rect.top());
let bottom = pos2(resize_x, rect.bottom());
ctx.layer_painter(resize_layer)
ui.ctx()
.layer_painter(resize_layer)
.line_segment([top, bottom], stroke);
}
inner_response
}
pub fn show<R>(
self,
ctx: &CtxRef,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let layer_id = LayerId::background();
let side = self.side;
let available_rect = ctx.available_rect();
let clip_rect = ctx.input().screen_rect();
let mut panel_ui = Ui::new(ctx.clone(), layer_id, self.id, available_rect, clip_rect);
let inner_response = self.show_inside(&mut panel_ui, add_contents);
let rect = inner_response.response.rect;
match side {
Side::Left => ctx
.frame_state()
@ -237,7 +252,6 @@ impl SidePanel {
.frame_state()
.allocate_right_panel(Rect::from_min_max(rect.min, available_rect.max)),
}
inner_response
}
}
@ -362,9 +376,9 @@ impl TopBottomPanel {
}
impl TopBottomPanel {
pub fn show<R>(
pub fn show_inside<R>(
self,
ctx: &CtxRef,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let Self {
@ -376,16 +390,14 @@ impl TopBottomPanel {
height_range,
} = self;
let layer_id = LayerId::background();
let available_rect = ctx.available_rect();
let available_rect = ui.max_rect();
let mut panel_rect = available_rect;
{
let state = ctx.memory().id_data.get::<PanelState>(&id).copied();
let state = ui.memory().id_data.get::<PanelState>(&id).copied();
let mut height = if let Some(state) = state {
state.rect.height()
} else {
default_height.unwrap_or_else(|| ctx.style().spacing.interact_size.y)
default_height.unwrap_or_else(|| ui.style().spacing.interact_size.y)
};
height = clamp_to_range(height, height_range.clone()).at_most(available_rect.height());
side.set_rect_height(&mut panel_rect, height);
@ -395,24 +407,25 @@ impl TopBottomPanel {
let mut is_resizing = false;
if resizable {
let resize_id = id.with("__resize");
if let Some(pointer) = ctx.input().pointer.latest_pos() {
let we_are_on_top = ctx
if let Some(pointer) = ui.input().pointer.latest_pos() {
let we_are_on_top = ui
.ctx()
.layer_id_at(pointer)
.map_or(true, |top_layer_id| top_layer_id == layer_id);
.map_or(true, |top_layer_id| top_layer_id == ui.layer_id());
let resize_y = side.opposite().side_y(panel_rect);
let mouse_over_resize_line = we_are_on_top
&& panel_rect.x_range().contains(&pointer.x)
&& (resize_y - pointer.y).abs()
<= ctx.style().interaction.resize_grab_radius_side;
<= ui.style().interaction.resize_grab_radius_side;
if ctx.input().pointer.any_pressed()
&& ctx.input().pointer.any_down()
if ui.input().pointer.any_pressed()
&& ui.input().pointer.any_down()
&& mouse_over_resize_line
{
ctx.memory().interaction.drag_id = Some(resize_id);
ui.memory().interaction.drag_id = Some(resize_id);
}
is_resizing = ctx.memory().interaction.drag_id == Some(resize_id);
is_resizing = ui.memory().interaction.drag_id == Some(resize_id);
if is_resizing {
let height = (pointer.y - side.side_y(panel_rect)).abs();
let height =
@ -421,42 +434,60 @@ impl TopBottomPanel {
}
let dragging_something_else =
ctx.input().pointer.any_down() || ctx.input().pointer.any_pressed();
ui.input().pointer.any_down() || ui.input().pointer.any_pressed();
resize_hover = mouse_over_resize_line && !dragging_something_else;
if resize_hover || is_resizing {
ctx.output().cursor_icon = CursorIcon::ResizeVertical;
ui.output().cursor_icon = CursorIcon::ResizeVertical;
}
}
}
let clip_rect = ctx.input().screen_rect();
let mut panel_ui = Ui::new(ctx.clone(), layer_id, id, panel_rect, clip_rect);
let frame = frame.unwrap_or_else(|| Frame::side_top_panel(&ctx.style()));
let mut panel_ui = ui.child_ui(panel_rect, Layout::top_down(Align::Min));
panel_ui.expand_to_include_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_width(ui.max_rect_finite().width()); // Make the frame fill full width
add_contents(ui)
});
let rect = inner_response.response.rect;
ctx.memory().id_data.insert(id, PanelState { rect });
ui.memory().id_data.insert(id, PanelState { rect });
if resize_hover || is_resizing {
let stroke = if is_resizing {
ctx.style().visuals.widgets.active.bg_stroke
ui.style().visuals.widgets.active.bg_stroke
} else {
ctx.style().visuals.widgets.hovered.bg_stroke
ui.style().visuals.widgets.hovered.bg_stroke
};
// draw on top of ALL panels so that the resize line won't be covered by subsequent panels
let resize_layer = LayerId::new(Order::PanelResizeLine, Id::new("panel_resize"));
let resize_layer = LayerId::new(Order::Foreground, Id::new("panel_resize"));
let resize_y = side.opposite().side_y(rect);
let left = pos2(rect.left(), resize_y);
let right = pos2(rect.right(), resize_y);
ctx.layer_painter(resize_layer)
ui.ctx()
.layer_painter(resize_layer)
.line_segment([left, right], stroke);
}
inner_response
}
pub fn show<R>(
self,
ctx: &CtxRef,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let layer_id = LayerId::background();
let available_rect = ctx.available_rect();
let side = self.side;
let clip_rect = ctx.input().screen_rect();
let mut panel_ui = Ui::new(ctx.clone(), layer_id, self.id, available_rect, clip_rect);
let inner_response = self.show_inside(&mut panel_ui, add_contents);
let rect = inner_response.response.rect;
match side {
TopBottomSide::Top => {
ctx.frame_state()
@ -516,26 +547,35 @@ impl CentralPanel {
}
impl CentralPanel {
pub fn show_inside<R>(
self,
ui: &mut Ui,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let Self { frame } = self;
let panel_rect = ui.available_rect_before_wrap_finite();
let mut panel_ui = ui.child_ui(panel_rect, Layout::top_down(Align::Min));
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()); // Expand frame to include it all
add_contents(ui)
})
}
pub fn show<R>(
self,
ctx: &CtxRef,
add_contents: impl FnOnce(&mut Ui) -> R,
) -> InnerResponse<R> {
let Self { frame } = self;
let panel_rect = ctx.available_rect();
let available_rect = ctx.available_rect();
let layer_id = LayerId::background();
let id = Id::new("central_panel");
let clip_rect = ctx.input().screen_rect();
let mut panel_ui = Ui::new(ctx.clone(), layer_id, id, panel_rect, clip_rect);
let mut panel_ui = Ui::new(ctx.clone(), layer_id, id, available_rect, clip_rect);
let frame = frame.unwrap_or_else(|| Frame::central_panel(&ctx.style()));
let inner_response = frame.show(&mut panel_ui, |ui| {
ui.expand_to_include_rect(ui.max_rect()); // Expand frame to include it all
add_contents(ui)
});
let inner_response = self.show_inside(&mut panel_ui, add_contents);
// Only inform ctx about what we actually used, so we can shrink the native window to fit.
ctx.frame_state()

View file

@ -28,6 +28,7 @@ impl Default for Demos {
Box::new(super::widget_gallery::WidgetGallery::default()),
Box::new(super::window_options::WindowOptions::default()),
Box::new(super::tests::WindowResizeTest::default()),
Box::new(super::window_with_panels::WindowWithPanels::default()),
])
}
}

View file

@ -21,6 +21,7 @@ pub mod tests;
pub mod toggle_switch;
pub mod widget_gallery;
pub mod window_options;
pub mod window_with_panels;
pub use {
app::DemoApp, demo_app_windows::DemoWindows, misc_demo_window::MiscDemoWindow,

View file

@ -0,0 +1,89 @@
use egui::{menu, Align, CentralPanel, Layout, ScrollArea, SidePanel, TopBottomPanel};
#[derive(Clone, PartialEq, Default)]
#[cfg_attr(feature = "persistence", derive(serde::Deserialize, serde::Serialize))]
pub struct WindowWithPanels {}
impl super::Demo for WindowWithPanels {
fn name(&self) -> &'static str {
"🗖 Window With Panels"
}
fn show(&mut self, ctx: &egui::CtxRef, open: &mut bool) {
use super::View;
let window = egui::Window::new("Window with Panels")
.scroll(false)
.title_bar(true)
.resizable(true)
.collapsible(false)
.enabled(true)
.open(open);
window.show(ctx, |ui| self.ui(ui));
}
}
impl super::View for WindowWithPanels {
fn ui(&mut self, ui: &mut egui::Ui) {
let left_panel_min_width = 100.;
let left_panel_max_width = left_panel_min_width * 4.;
let bottom_height = 25.;
ui.expand_to_include_rect(ui.max_rect()); // Expand frame to include it all
let mut top_rect = ui.available_rect_before_wrap_finite();
top_rect.min.y += ui.spacing().item_spacing.y;
let mut top_ui = ui.child_ui(top_rect, Layout::top_down(Align::Max));
let top_response = TopBottomPanel::top("window_menu")
.resizable(false)
.show_inside(&mut top_ui, |ui| {
menu::bar(ui, |ui| {
menu::menu(ui, "Menu", |ui| {
if ui.button("Option 1").clicked() {}
if ui.button("Option 2").clicked() {}
if ui.button("Option 3").clicked() {}
});
});
});
let mut left_rect = ui.available_rect_before_wrap_finite();
left_rect.min.y = top_response.response.rect.max.y + ui.spacing().item_spacing.y;
let mut left_ui = ui.child_ui(left_rect, Layout::top_down(Align::Max));
let left_response = SidePanel::left("Folders")
.resizable(true)
.min_width(left_panel_min_width)
.max_width(left_panel_max_width)
.show_inside(&mut left_ui, |ui| {
ScrollArea::auto_sized().show(ui, |ui| {
ui.vertical(|ui| {
ui.label("Left Panel");
})
})
});
let mut right_rect = ui.available_rect_before_wrap_finite();
right_rect.min.x = left_response.response.rect.max.x;
right_rect.min.y = top_response.response.rect.max.y + ui.spacing().item_spacing.y;
let mut right_ui = ui.child_ui(right_rect, Layout::top_down(Align::Max));
CentralPanel::default().show_inside(&mut right_ui, |ui| {
let mut rect = ui.min_rect();
let mut bottom_rect = rect;
bottom_rect.min.y = ui.max_rect_finite().max.y - bottom_height;
rect.max.y = bottom_rect.min.y - ui.spacing().indent;
let mut child_ui = ui.child_ui(rect, Layout::top_down(Align::Min));
let mut bottom_ui = ui.child_ui(bottom_rect, Layout::bottom_up(Align::Max));
ScrollArea::auto_sized().show(&mut child_ui, |ui| {
ui.vertical(|ui| {
ui.label("Central Panel");
})
});
bottom_ui.vertical(|ui| {
ui.separator();
ui.horizontal(|ui| {
ui.label("Bottom Content");
});
});
});
}
}