diff --git a/egui/src/containers/panel.rs b/egui/src/containers/panel.rs index 0e60cf16..51830a53 100644 --- a/egui/src/containers/panel.rs +++ b/egui/src/containers/panel.rs @@ -137,9 +137,9 @@ impl SidePanel { } impl SidePanel { - pub fn show( + pub fn show_inside( self, - ctx: &CtxRef, + ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R, ) -> InnerResponse { 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::(&id) { + if let Some(state) = ui.memory().id_data.get::(&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( + self, + ctx: &CtxRef, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> InnerResponse { + 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( + pub fn show_inside( self, - ctx: &CtxRef, + ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R, ) -> InnerResponse { 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::(&id).copied(); + let state = ui.memory().id_data.get::(&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( + self, + ctx: &CtxRef, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> InnerResponse { + 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( + self, + ui: &mut Ui, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> InnerResponse { + 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( self, ctx: &CtxRef, add_contents: impl FnOnce(&mut Ui) -> R, ) -> InnerResponse { - 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() diff --git a/egui_demo_lib/src/apps/demo/demo_app_windows.rs b/egui_demo_lib/src/apps/demo/demo_app_windows.rs index 094154e7..83ab40e2 100644 --- a/egui_demo_lib/src/apps/demo/demo_app_windows.rs +++ b/egui_demo_lib/src/apps/demo/demo_app_windows.rs @@ -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()), ]) } } diff --git a/egui_demo_lib/src/apps/demo/mod.rs b/egui_demo_lib/src/apps/demo/mod.rs index 9a89160d..b607ff05 100644 --- a/egui_demo_lib/src/apps/demo/mod.rs +++ b/egui_demo_lib/src/apps/demo/mod.rs @@ -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, diff --git a/egui_demo_lib/src/apps/demo/window_with_panels.rs b/egui_demo_lib/src/apps/demo/window_with_panels.rs new file mode 100644 index 00000000..538d1d9e --- /dev/null +++ b/egui_demo_lib/src/apps/demo/window_with_panels.rs @@ -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"); + }); + }); + }); + } +}