diff --git a/CHANGELOG.md b/CHANGELOG.md index fa9f3eb9..fc0b7595 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ * Refactored the interface for `egui::app::App` * Demo App: Add slider to scale all of Egui * Windows are now constrained to the screen -* Panels: you can now add side panels using `Context::panel_left` and `Context::panel_top`. +* Panels: you can now create panels using `SidePanel` and `TopPanel`. * Fix a bug where some regions would slowly grow for non-integral scales (`pixels_per_point`). ## 0.2.0 - 2020-10-10 diff --git a/egui/src/containers/frame.rs b/egui/src/containers/frame.rs index fdb7380b..33338c04 100644 --- a/egui/src/containers/frame.rs +++ b/egui/src/containers/frame.rs @@ -31,9 +31,9 @@ impl Frame { } } - pub fn menu_bar(style: &Style) -> Self { + pub(crate) fn panel(style: &Style) -> Self { Self { - margin: Vec2::splat(1.0), + margin: Vec2::new(8.0, 2.0), corner_radius: 0.0, fill: style.visuals.widgets.noninteractive.bg_fill, stroke: style.visuals.widgets.noninteractive.bg_stroke, @@ -58,10 +58,6 @@ impl Frame { } } - pub fn panel(style: &Style) -> Self { - Self::popup(style) - } - pub fn fill(mut self, fill: Srgba) -> Self { self.fill = fill; self diff --git a/egui/src/containers/mod.rs b/egui/src/containers/mod.rs index b636292d..3e357311 100644 --- a/egui/src/containers/mod.rs +++ b/egui/src/containers/mod.rs @@ -6,12 +6,20 @@ pub(crate) mod area; pub(crate) mod collapsing_header; mod combo_box; pub(crate) mod frame; +pub(crate) mod panel; pub(crate) mod popup; pub(crate) mod resize; pub(crate) mod scroll_area; pub(crate) mod window; pub use { - area::Area, collapsing_header::*, combo_box::*, frame::Frame, popup::*, resize::Resize, - scroll_area::ScrollArea, window::Window, + area::Area, + collapsing_header::*, + combo_box::*, + frame::Frame, + panel::{SidePanel, TopPanel}, + popup::*, + resize::Resize, + scroll_area::ScrollArea, + window::Window, }; diff --git a/egui/src/containers/panel.rs b/egui/src/containers/panel.rs new file mode 100644 index 00000000..58e10d7b --- /dev/null +++ b/egui/src/containers/panel.rs @@ -0,0 +1,105 @@ +//! Panels + +use crate::*; +use std::sync::Arc; + +// ---------------------------------------------------------------------------- + +/// A panel that covers the entire left side of the screen. +/// +/// Panels should be added before adding any `Window`s. +pub struct SidePanel { + id: Id, + max_width: f32, +} + +impl SidePanel { + /// The given `max_width` is a soft maximum (as always), and the actual panel may be smaller or larger. + pub fn left(id: Id, max_width: f32) -> Self { + Self { id, max_width } + } +} + +impl SidePanel { + pub fn show( + self, + ctx: &Arc, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> (R, Response) { + let Self { id, max_width } = self; + + let mut panel_rect = ctx.available_rect(); + panel_rect.max.x = panel_rect.max.x.at_most(panel_rect.min.x + max_width); + + let layer_id = LayerId::background(); + + 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::panel(&ctx.style()); + let r = frame.show(&mut panel_ui, |ui| { + ui.set_min_height(ui.max_rect_finite().height()); // fill full height + add_contents(ui) + }); + + let panel_rect = panel_ui.min_rect(); + let response = panel_ui.interact_hover(panel_rect); + + ctx.allocate_left_panel(panel_rect); + + (r, response) + } +} + +// ---------------------------------------------------------------------------- + +/// A panel that covers the entire top side of the screen. +/// +/// Panels should be added before adding any `Window`s. +pub struct TopPanel { + id: Id, + max_height: Option, +} + +impl TopPanel { + /// Default height is that of `interact_size.y` (i.e. a button), + /// but the panel will expand as needed. + pub fn top(id: Id) -> Self { + Self { + id, + max_height: None, + } + } +} + +impl TopPanel { + pub fn show( + self, + ctx: &Arc, + add_contents: impl FnOnce(&mut Ui) -> R, + ) -> (R, Response) { + let Self { id, max_height } = self; + let max_height = max_height.unwrap_or_else(|| ctx.style().spacing.interact_size.y); + + let mut panel_rect = ctx.available_rect(); + panel_rect.max.y = panel_rect.max.y.at_most(panel_rect.min.y + max_height); + + let layer_id = LayerId::background(); + + 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::panel(&ctx.style()); + let r = frame.show(&mut panel_ui, |ui| { + ui.set_min_width(ui.max_rect_finite().width()); // fill full width + add_contents(ui) + }); + + let panel_rect = panel_ui.min_rect(); + let response = panel_ui.interact_hover(panel_rect); + + ctx.allocate_top_panel(panel_rect); + + (r, response) + } +} diff --git a/egui/src/context.rs b/egui/src/context.rs index 215e98f8..a120ed0b 100644 --- a/egui/src/context.rs +++ b/egui/src/context.rs @@ -287,77 +287,18 @@ impl Context { // --------------------------------------------------------------------- - /// Create a panel that covers the entire left side of the screen. - /// The given `max_width` is a soft maximum (as always), and the actual panel may be smaller or larger. - /// You should call this *before* adding windows to Egui. - pub fn panel_left( - self: &Arc, - max_width: f32, - frame: Frame, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> (R, Response) { - let mut panel_rect = self.available_rect(); - panel_rect.max.x = panel_rect.max.x.at_most(panel_rect.min.x + max_width); - - let id = Id::background(); - let layer_id = LayerId { - order: Order::Background, - id, - }; - - let clip_rect = self.input.screen_rect(); - let mut panel_ui = Ui::new(self.clone(), layer_id, id, panel_rect, clip_rect); - let r = frame.show(&mut panel_ui, |ui| { - ui.set_min_height(ui.max_rect_finite().height()); // fill full height - add_contents(ui) - }); - - let panel_rect = panel_ui.min_rect(); - let response = panel_ui.interact_hover(panel_rect); - - // Shrink out `available_rect`: + /// Shrink `available_rect()`. + pub(crate) fn allocate_left_panel(&self, panel_rect: Rect) { let mut remainder = self.available_rect(); remainder.min.x = panel_rect.max.x; *self.available_rect.lock() = Some(remainder); - - (r, response) } - /// Create a panel that covers the entire top side of the screen. - /// This can be useful to add a menu bar to the whole window. - /// The given `max_height` is a soft maximum (as always), and the actual panel may be smaller or larger. - /// You should call this *before* adding windows to Egui. - pub fn panel_top( - self: &Arc, - max_height: f32, - frame: Frame, - add_contents: impl FnOnce(&mut Ui) -> R, - ) -> (R, Response) { - let mut panel_rect = self.available_rect(); - panel_rect.max.y = panel_rect.max.y.at_most(panel_rect.min.y + max_height); - - let id = Id::background(); - let layer_id = LayerId { - order: Order::Background, - id, - }; - - let clip_rect = self.input.screen_rect(); - let mut panel_ui = Ui::new(self.clone(), layer_id, id, panel_rect, clip_rect); - let r = frame.show(&mut panel_ui, |ui| { - ui.set_min_width(ui.max_rect_finite().width()); // fill full width - add_contents(ui) - }); - - let panel_rect = panel_ui.min_rect(); - let response = panel_ui.interact_hover(panel_rect); - - // Shrink out `available_rect`: + /// Shrink `available_rect()`. + pub(crate) fn allocate_top_panel(&self, panel_rect: Rect) { let mut remainder = self.available_rect(); remainder.min.y = panel_rect.max.y; *self.available_rect.lock() = Some(remainder); - - (r, response) } // --------------------------------------------------------------------- diff --git a/egui/src/demos/demo_windows.rs b/egui/src/demos/demo_windows.rs index 21629e1b..52ac7037 100644 --- a/egui/src/demos/demo_windows.rs +++ b/egui/src/demos/demo_windows.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use crate::{app, demos, Context, Resize, ScrollArea, Ui, Window}; +use crate::{app, demos, Context, Id, Resize, ScrollArea, Ui, Window}; // ---------------------------------------------------------------------------- @@ -63,8 +63,7 @@ impl DemoWindows { self.previous_link = env.link; } - let frame = crate::Frame::panel(ui.style()); - ctx.panel_left(240.0, frame, |ui| { + crate::SidePanel::left(Id::new("side_panel"), 200.0).show(ctx, |ui| { ui.heading("Egui Demo"); ui.label("Egui is an immediate mode GUI library written in Rust."); ui.add(crate::Hyperlink::new("https://github.com/emilk/egui").text("Egui home page")); @@ -83,7 +82,8 @@ impl DemoWindows { self.open_windows.ui(ui); }); }); - ctx.panel_top(0.0, crate::Frame::none(), |ui| { + + crate::TopPanel::top(Id::new("menu_bar")).show(ctx, |ui| { show_menu_bar(ui, &mut self.open_windows, env.seconds_since_midnight); }); diff --git a/egui/src/layers.rs b/egui/src/layers.rs index 97c036f0..0446bbfa 100644 --- a/egui/src/layers.rs +++ b/egui/src/layers.rs @@ -34,6 +34,13 @@ impl LayerId { id: Id::new("debug"), } } + + pub fn background() -> Self { + Self { + order: Order::Background, + id: Id::background(), + } + } } /// A unique identifier of a specific `PaintCmd` in a `PaintList`. diff --git a/egui/src/menu.rs b/egui/src/menu.rs index 1a16b87c..8ebb228c 100644 --- a/egui/src/menu.rs +++ b/egui/src/menu.rs @@ -37,25 +37,26 @@ impl BarState { } } +/// The menu bar goes well in `TopPanel`, +/// but can also be placed in a `Window`. +/// In the latter case you may want to wrap it in `Frame`. pub fn bar(ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> (R, Response) { - Frame::menu_bar(ui.style()).show(ui, |ui| { - ui.horizontal(|ui| { - let mut style = ui.style().clone(); - style.spacing.button_padding = vec2(2.0, 0.0); - // style.visuals.widgets.active.bg_fill = TRANSPARENT; - style.visuals.widgets.active.bg_stroke = Stroke::none(); - // style.visuals.widgets.hovered.bg_fill = TRANSPARENT; - style.visuals.widgets.hovered.bg_stroke = Stroke::none(); - style.visuals.widgets.inactive.bg_fill = TRANSPARENT; - style.visuals.widgets.inactive.bg_stroke = Stroke::none(); - ui.set_style(style); + ui.horizontal(|ui| { + let mut style = ui.style().clone(); + style.spacing.button_padding = vec2(2.0, 0.0); + // style.visuals.widgets.active.bg_fill = TRANSPARENT; + style.visuals.widgets.active.bg_stroke = Stroke::none(); + // style.visuals.widgets.hovered.bg_fill = TRANSPARENT; + style.visuals.widgets.hovered.bg_stroke = Stroke::none(); + style.visuals.widgets.inactive.bg_fill = TRANSPARENT; + style.visuals.widgets.inactive.bg_stroke = Stroke::none(); + ui.set_style(style); - // Take full width and fixed height: - let height = ui.style().spacing.interact_size.y; - ui.set_min_size(vec2(ui.available().width(), height)); + // Take full width and fixed height: + let height = ui.style().spacing.interact_size.y; + ui.set_min_size(vec2(ui.available().width(), height)); - add_contents(ui) - }) + add_contents(ui) }) } diff --git a/example_glium/src/main.rs b/example_glium/src/main.rs index 923acd26..5ca5bc9b 100644 --- a/example_glium/src/main.rs +++ b/example_glium/src/main.rs @@ -3,8 +3,6 @@ #![deny(warnings)] #![warn(clippy::all)] -use egui::{Slider, Window}; - /// We derive Deserialize/Serialize so we can persist app state on shutdown. #[derive(Default, serde::Deserialize, serde::Serialize)] struct MyApp { @@ -24,13 +22,13 @@ impl egui::app::App for MyApp { let MyApp { my_string, value } = self; // Example used in `README.md`. - Window::new("Debug").show(ui.ctx(), |ui| { + egui::Window::new("Debug").show(ui.ctx(), |ui| { ui.label(format!("Hello, world {}", 123)); if ui.button("Save").clicked { my_save_function(); } ui.text_edit(my_string); - ui.add(Slider::f32(value, 0.0..=1.0).text("float")); + ui.add(egui::Slider::f32(value, 0.0..=1.0).text("float")); }); Default::default()