Refactor panels into proper containers

This commit is contained in:
Emil Ernerfeldt 2020-10-21 22:10:55 +02:00
parent 8b51ae5dea
commit 35b949a2d8
9 changed files with 152 additions and 96 deletions

View file

@ -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

View file

@ -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

View file

@ -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,
};

View file

@ -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<R>(
self,
ctx: &Arc<Context>,
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<f32>,
}
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<R>(
self,
ctx: &Arc<Context>,
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)
}
}

View file

@ -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<R>(
self: &Arc<Context>,
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<R>(
self: &Arc<Context>,
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)
}
// ---------------------------------------------------------------------

View file

@ -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);
});

View file

@ -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`.

View file

@ -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<R>(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)
})
}

View file

@ -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()