Panels: Added Context::panel_left and panel_top
Context will keep track of the avilable space left after panels. Windows will be constrained to that available space. So add panels first, THEN add windows.
This commit is contained in:
parent
15c5e0b04d
commit
8b51ae5dea
9 changed files with 188 additions and 62 deletions
|
@ -7,6 +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`.
|
||||
* Fix a bug where some regions would slowly grow for non-integral scales (`pixels_per_point`).
|
||||
|
||||
## 0.2.0 - 2020-10-10
|
||||
|
|
|
@ -158,11 +158,14 @@ impl Prepared {
|
|||
}
|
||||
|
||||
pub(crate) fn content_ui(&self, ctx: &Arc<Context>) -> Ui {
|
||||
let max_rect = Rect::from_min_size(self.state.pos, Vec2::infinity());
|
||||
let clip_rect = max_rect.expand(ctx.style().visuals.clip_rect_margin);
|
||||
Ui::new(
|
||||
ctx.clone(),
|
||||
self.layer_id,
|
||||
self.layer_id.id,
|
||||
Rect::from_min_size(self.state.pos, Vec2::infinity()),
|
||||
max_rect,
|
||||
clip_rect,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -176,7 +179,6 @@ impl Prepared {
|
|||
state.size = content_ui.min_rect().size();
|
||||
|
||||
let area_rect = Rect::from_min_size(state.pos, state.size);
|
||||
let clip_rect = ctx.rect();
|
||||
|
||||
let interact_id = if movable {
|
||||
Some(layer_id.id.with("move"))
|
||||
|
@ -185,7 +187,7 @@ impl Prepared {
|
|||
};
|
||||
let move_response = ctx.interact(
|
||||
layer_id,
|
||||
clip_rect,
|
||||
Rect::everything(),
|
||||
area_rect,
|
||||
interact_id,
|
||||
Sense::click_and_drag(),
|
||||
|
@ -243,9 +245,11 @@ fn automatic_area_position(ctx: &Context) -> Pos2 {
|
|||
.collect();
|
||||
existing.sort_by_key(|r| r.left().round() as i32);
|
||||
|
||||
let left = 16.0;
|
||||
let top = 32.0; // allow existence of menu bar. TODO: get from ui.available()
|
||||
let available_rect = ctx.available_rect();
|
||||
|
||||
let spacing = 16.0;
|
||||
let left = available_rect.left() + spacing;
|
||||
let top = available_rect.top() + spacing;
|
||||
|
||||
if existing.is_empty() {
|
||||
return pos2(left, top);
|
||||
|
@ -279,14 +283,14 @@ fn automatic_area_position(ctx: &Context) -> Pos2 {
|
|||
|
||||
// Find first column with some available space at the bottom of it:
|
||||
for col_bb in &column_bbs {
|
||||
if col_bb.bottom() < ctx.input().screen_size.y * 0.5 {
|
||||
if col_bb.bottom() < available_rect.center().y {
|
||||
return pos2(col_bb.left(), col_bb.bottom() + spacing);
|
||||
}
|
||||
}
|
||||
|
||||
// Maybe we can fit a new column?
|
||||
let rightmost = column_bbs.last().unwrap().right();
|
||||
if rightmost < ctx.input().screen_size.x - 200.0 {
|
||||
if rightmost + 200.0 < available_rect.right() {
|
||||
return pos2(rightmost + spacing, top);
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
use crate::{layers::PaintCmdIdx, paint::*, *};
|
||||
|
||||
/// Adds a rectangular frame and background to some `Ui`.
|
||||
#[derive(Clone, Debug)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub struct Frame {
|
||||
// On each side
|
||||
pub margin: Vec2,
|
||||
|
@ -58,6 +58,10 @@ impl Frame {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn panel(style: &Style) -> Self {
|
||||
Self::popup(style)
|
||||
}
|
||||
|
||||
pub fn fill(mut self, fill: Srgba) -> Self {
|
||||
self.fill = fill;
|
||||
self
|
||||
|
|
|
@ -42,6 +42,9 @@ pub struct Context {
|
|||
|
||||
input: InputState,
|
||||
|
||||
/// Starts off as the screen_rect, shrinks as panels are added.
|
||||
available_rect: Mutex<Option<Rect>>,
|
||||
|
||||
// The output of a frame:
|
||||
graphics: Mutex<GraphicLayers>,
|
||||
output: Mutex<Output>,
|
||||
|
@ -62,6 +65,7 @@ impl Clone for Context {
|
|||
memory: self.memory.clone(),
|
||||
animation_manager: self.animation_manager.clone(),
|
||||
input: self.input.clone(),
|
||||
available_rect: self.available_rect.clone(),
|
||||
graphics: self.graphics.clone(),
|
||||
output: self.output.clone(),
|
||||
used_ids: self.used_ids.clone(),
|
||||
|
@ -76,8 +80,13 @@ impl Context {
|
|||
Arc::new(Self::default())
|
||||
}
|
||||
|
||||
pub fn rect(&self) -> Rect {
|
||||
Rect::from_min_size(pos2(0.0, 0.0), self.input.screen_size)
|
||||
/// How much space is still available after panels has been added.
|
||||
/// This is the "background" area, what Egui doesn't cover with panels (but may cover with windows).
|
||||
/// This is also the area to which windows are constrained.
|
||||
pub fn available_rect(&self) -> Rect {
|
||||
self.available_rect
|
||||
.lock()
|
||||
.expect("Called `avaiblable_rect()` before `begin_frame()`")
|
||||
}
|
||||
|
||||
pub fn memory(&self) -> MutexGuard<'_, Memory> {
|
||||
|
@ -168,7 +177,7 @@ impl Context {
|
|||
/// Constraint the position of a window/area
|
||||
/// so it fits within the screen.
|
||||
pub(crate) fn constrain_window_rect(&self, window: Rect) -> Rect {
|
||||
let screen = self.rect();
|
||||
let screen = self.available_rect();
|
||||
|
||||
let mut pos = window.min;
|
||||
|
||||
|
@ -203,6 +212,8 @@ impl Context {
|
|||
self.used_ids.lock().clear();
|
||||
|
||||
self.input = std::mem::take(&mut self.input).begin_frame(new_raw_input);
|
||||
*self.available_rect.lock() = Some(self.input.screen_rect());
|
||||
|
||||
let mut font_definitions = self.options.lock().font_definitions.clone();
|
||||
font_definitions.pixels_per_point = self.input.pixels_per_point();
|
||||
let same_as_current = match &self.fonts {
|
||||
|
@ -255,7 +266,7 @@ impl Context {
|
|||
|
||||
/// A `Ui` for the entire screen, behind any windows.
|
||||
fn fullscreen_ui(self: &Arc<Self>) -> Ui {
|
||||
let rect = Rect::from_min_size(Default::default(), self.input().screen_size);
|
||||
let rect = self.input.screen_rect();
|
||||
let id = Id::background();
|
||||
let layer_id = LayerId {
|
||||
order: Order::Background,
|
||||
|
@ -271,7 +282,82 @@ impl Context {
|
|||
vel: Default::default(),
|
||||
},
|
||||
);
|
||||
Ui::new(self.clone(), layer_id, id, rect)
|
||||
Ui::new(self.clone(), layer_id, id, rect, rect)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
||||
/// 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`:
|
||||
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`:
|
||||
let mut remainder = self.available_rect();
|
||||
remainder.min.y = panel_rect.max.y;
|
||||
*self.available_rect.lock() = Some(remainder);
|
||||
|
||||
(r, response)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------
|
||||
|
@ -328,9 +414,16 @@ impl Context {
|
|||
pub fn is_mouse_over_area(&self) -> bool {
|
||||
if let Some(mouse_pos) = self.input.mouse.pos {
|
||||
if let Some(layer) = self.layer_id_at(mouse_pos) {
|
||||
// TODO: this currently returns false for hovering the menu bar.
|
||||
// We should probably move the menu bar to its own area to fix this.
|
||||
layer.order != Order::Background
|
||||
if layer.order == Order::Background {
|
||||
if let Some(available_rect) = *self.available_rect.lock() {
|
||||
// "available_rect" is the area that Egui is NOT using.
|
||||
!available_rect.contains(mouse_pos)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
@ -521,7 +614,7 @@ impl Context {
|
|||
/// ## Painting
|
||||
impl Context {
|
||||
pub fn debug_painter(self: &Arc<Self>) -> Painter {
|
||||
Painter::new(self.clone(), LayerId::debug(), self.rect())
|
||||
Painter::new(self.clone(), LayerId::debug(), self.input.screen_rect())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -278,20 +278,12 @@ impl app::App for DemoApp {
|
|||
info: &app::BackendInfo,
|
||||
tex_allocator: Option<&mut dyn app::TextureAllocator>,
|
||||
) -> app::AppOutput {
|
||||
let mut output = app::AppOutput::default();
|
||||
|
||||
crate::Window::new("Backend")
|
||||
.min_width(360.0)
|
||||
.scroll(false)
|
||||
.show(ui.ctx(), |ui| {
|
||||
output = self.backend_ui(ui, info);
|
||||
});
|
||||
|
||||
let web_location_hash = info
|
||||
.web_info
|
||||
.as_ref()
|
||||
.map(|info| info.web_location_hash.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
let link = if web_location_hash == "clock" {
|
||||
Some(demos::DemoLink::Clock)
|
||||
} else {
|
||||
|
@ -305,6 +297,15 @@ impl app::App for DemoApp {
|
|||
|
||||
self.demo_windows.ui(ui, &demo_environment, tex_allocator);
|
||||
|
||||
let mut output = app::AppOutput::default();
|
||||
|
||||
crate::Window::new("Backend")
|
||||
.min_width(360.0)
|
||||
.scroll(false)
|
||||
.show(ui.ctx(), |ui| {
|
||||
output = self.backend_ui(ui, info);
|
||||
});
|
||||
|
||||
if self.run_mode == RunMode::Continuous {
|
||||
// Tell the backend to repaint as soon as possible
|
||||
ui.ctx().request_repaint();
|
||||
|
|
|
@ -31,15 +31,6 @@ impl Default for DemoWindow {
|
|||
|
||||
impl DemoWindow {
|
||||
pub fn ui(&mut self, ui: &mut Ui) {
|
||||
ui.collapsing("About Egui", |ui| {
|
||||
ui.label("Egui is an experimental immediate mode GUI written in Rust.");
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Project home page:");
|
||||
ui.hyperlink("https://github.com/emilk/egui");
|
||||
});
|
||||
});
|
||||
|
||||
CollapsingHeader::new("Widgets")
|
||||
.default_open(true)
|
||||
.show(ui, |ui| {
|
||||
|
|
|
@ -48,6 +48,8 @@ impl DemoWindows {
|
|||
env: &DemoEnvironment,
|
||||
tex_allocator: Option<&mut dyn app::TextureAllocator>,
|
||||
) {
|
||||
let ctx = ui.ctx();
|
||||
|
||||
if self.previous_link != env.link {
|
||||
match env.link {
|
||||
None => {}
|
||||
|
@ -61,8 +63,31 @@ impl DemoWindows {
|
|||
self.previous_link = env.link;
|
||||
}
|
||||
|
||||
show_menu_bar(ui, &mut self.open_windows, env.seconds_since_midnight);
|
||||
self.windows(ui.ctx(), env, tex_allocator);
|
||||
let frame = crate::Frame::panel(ui.style());
|
||||
ctx.panel_left(240.0, frame, |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"));
|
||||
|
||||
ui.separator();
|
||||
ui.label(
|
||||
"This is an example of a panel. Windows are constrained to the area that remain.",
|
||||
);
|
||||
if ui.button("Organize windows").clicked {
|
||||
ui.ctx().memory().reset_areas();
|
||||
}
|
||||
ui.separator();
|
||||
|
||||
ui.heading("Windows:");
|
||||
ui.indent("windows", |ui| {
|
||||
self.open_windows.ui(ui);
|
||||
});
|
||||
});
|
||||
ctx.panel_top(0.0, crate::Frame::none(), |ui| {
|
||||
show_menu_bar(ui, &mut self.open_windows, env.seconds_since_midnight);
|
||||
});
|
||||
|
||||
self.windows(ctx, env, tex_allocator);
|
||||
}
|
||||
|
||||
/// Show the open windows.
|
||||
|
@ -218,6 +243,28 @@ impl OpenWindows {
|
|||
color_test: false,
|
||||
}
|
||||
}
|
||||
|
||||
fn ui(&mut self, ui: &mut Ui) {
|
||||
let Self {
|
||||
demo,
|
||||
fractal_clock,
|
||||
settings,
|
||||
inspection,
|
||||
memory,
|
||||
resize,
|
||||
color_test,
|
||||
} = self;
|
||||
ui.checkbox(demo, "Demo");
|
||||
ui.checkbox(fractal_clock, "Fractal Clock");
|
||||
ui.separator();
|
||||
ui.checkbox(settings, "Settings");
|
||||
ui.checkbox(inspection, "Inspection");
|
||||
ui.checkbox(memory, "Memory");
|
||||
ui.checkbox(resize, "Resize examples");
|
||||
ui.separator();
|
||||
ui.checkbox(color_test, "Color test")
|
||||
.on_hover_text("For testing the integrations painter");
|
||||
}
|
||||
}
|
||||
|
||||
fn show_menu_bar(ui: &mut Ui, windows: &mut OpenWindows, seconds_since_midnight: Option<f64>) {
|
||||
|
@ -225,7 +272,7 @@ fn show_menu_bar(ui: &mut Ui, windows: &mut OpenWindows, seconds_since_midnight:
|
|||
|
||||
menu::bar(ui, |ui| {
|
||||
menu::menu(ui, "File", |ui| {
|
||||
if ui.button("Reorganize windows").clicked {
|
||||
if ui.button("Organize windows").clicked {
|
||||
ui.ctx().memory().reset_areas();
|
||||
}
|
||||
if ui
|
||||
|
@ -236,27 +283,7 @@ fn show_menu_bar(ui: &mut Ui, windows: &mut OpenWindows, seconds_since_midnight:
|
|||
*ui.ctx().memory() = Default::default();
|
||||
}
|
||||
});
|
||||
menu::menu(ui, "Windows", |ui| {
|
||||
let OpenWindows {
|
||||
demo,
|
||||
fractal_clock,
|
||||
settings,
|
||||
inspection,
|
||||
memory,
|
||||
resize,
|
||||
color_test,
|
||||
} = windows;
|
||||
ui.checkbox(demo, "Demo");
|
||||
ui.checkbox(fractal_clock, "Fractal Clock");
|
||||
ui.separator();
|
||||
ui.checkbox(settings, "Settings");
|
||||
ui.checkbox(inspection, "Inspection");
|
||||
ui.checkbox(memory, "Memory");
|
||||
ui.checkbox(resize, "Resize examples");
|
||||
ui.separator();
|
||||
ui.checkbox(color_test, "Color test")
|
||||
.on_hover_text("For testing the integrations painter");
|
||||
});
|
||||
menu::menu(ui, "Windows", |ui| windows.ui(ui));
|
||||
menu::menu(ui, "About", |ui| {
|
||||
ui.label("This is Egui");
|
||||
ui.add(Hyperlink::new("https://github.com/emilk/egui").text("Egui home page"));
|
||||
|
|
|
@ -39,7 +39,7 @@ impl FractalClock {
|
|||
) {
|
||||
Window::new("FractalClock")
|
||||
.open(open)
|
||||
.default_rect(ctx.rect().expand(-42.0))
|
||||
.default_rect(ctx.available_rect().expand(-42.0))
|
||||
.scroll(false)
|
||||
// Dark background frame to make it pop:
|
||||
.frame(Frame::window(&ctx.style()).fill(Srgba::black_alpha(250)))
|
||||
|
|
|
@ -60,9 +60,14 @@ impl Ui {
|
|||
// ------------------------------------------------------------------------
|
||||
// Creation:
|
||||
|
||||
pub fn new(ctx: Arc<Context>, layer_id: LayerId, id: Id, max_rect: Rect) -> Self {
|
||||
pub fn new(
|
||||
ctx: Arc<Context>,
|
||||
layer_id: LayerId,
|
||||
id: Id,
|
||||
max_rect: Rect,
|
||||
clip_rect: Rect,
|
||||
) -> Self {
|
||||
let style = ctx.style();
|
||||
let clip_rect = max_rect.expand(style.visuals.clip_rect_margin);
|
||||
let layout = Layout::default();
|
||||
let cursor = layout.initial_cursor(max_rect);
|
||||
let min_size = Vec2::zero(); // TODO: From Style
|
||||
|
|
Loading…
Reference in a new issue