1
0
mirror of https://github.com/emilk/egui.git synced 2026-09-01 06:10:06 -04:00

Make it easy to panels inside of Ui:s (#629)

* Allow using the layout cursor to restrict available area

* Avoid id clashes when putting panels inside a Ui

* Panels: Propagate height/width range to inner Ui

* Allow easy placement of panels inside of Ui:s

* demo: simplify Windows with Panels demo
This commit is contained in:
Emil Ernerfeldt
2021-08-20 00:10:06 +02:00
committed by GitHub
parent ee50cca696
commit 3e2746a288
6 changed files with 164 additions and 83 deletions

View File

@@ -151,7 +151,7 @@ impl SidePanel {
width_range,
} = self;
let available_rect = ui.max_rect();
let available_rect = ui.available_rect_before_wrap();
let mut panel_rect = available_rect;
{
let mut width = default_width;
@@ -187,7 +187,8 @@ impl SidePanel {
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());
let width =
clamp_to_range(width, width_range.clone()).at_most(available_rect.width());
side.set_rect_width(&mut panel_rect, width);
}
@@ -201,15 +202,31 @@ impl SidePanel {
}
}
let mut panel_ui = ui.child_ui(panel_rect, Layout::top_down(Align::Min));
let mut panel_ui = ui.child_ui_with_id_source(panel_rect, Layout::top_down(Align::Min), id);
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
ui.set_width_range(width_range);
add_contents(ui)
});
let rect = inner_response.response.rect;
{
let mut cursor = ui.cursor();
match side {
Side::Left => {
cursor.min.x = rect.max.x + ui.spacing().item_spacing.x;
}
Side::Right => {
cursor.max.x = rect.min.x - ui.spacing().item_spacing.x;
}
}
ui.set_cursor(cursor);
}
ui.expand_to_include_rect(rect);
ui.memory().id_data.insert(id, PanelState { rect });
if resize_hover || is_resizing {
@@ -230,6 +247,7 @@ impl SidePanel {
inner_response
}
pub fn show<R>(
self,
ctx: &CtxRef,
@@ -390,7 +408,7 @@ impl TopBottomPanel {
height_range,
} = self;
let available_rect = ui.max_rect();
let available_rect = ui.available_rect_before_wrap();
let mut panel_rect = available_rect;
{
let state = ui.memory().id_data.get::<PanelState>(&id).copied();
@@ -428,8 +446,8 @@ impl TopBottomPanel {
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 =
clamp_to_range(height, height_range).at_most(available_rect.height());
let height = clamp_to_range(height, height_range.clone())
.at_most(available_rect.height());
side.set_rect_height(&mut panel_rect, height);
}
@@ -443,15 +461,31 @@ impl TopBottomPanel {
}
}
let mut panel_ui = ui.child_ui(panel_rect, Layout::top_down(Align::Min));
let mut panel_ui = ui.child_ui_with_id_source(panel_rect, Layout::top_down(Align::Min), id);
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
ui.set_height_range(height_range);
add_contents(ui)
});
let rect = inner_response.response.rect;
{
let mut cursor = ui.cursor();
match side {
TopBottomSide::Top => {
cursor.min.y = rect.max.y + ui.spacing().item_spacing.y;
}
TopBottomSide::Bottom => {
cursor.max.y = rect.min.y - ui.spacing().item_spacing.y;
}
}
ui.set_cursor(cursor);
}
ui.expand_to_include_rect(rect);
ui.memory().id_data.insert(id, PanelState { rect });
if resize_hover || is_resizing {
@@ -563,6 +597,7 @@ impl CentralPanel {
add_contents(ui)
})
}
pub fn show<R>(
self,
ctx: &CtxRef,

View File

@@ -37,7 +37,7 @@ pub(crate) struct Region {
///
/// So one can think of `cursor` as a constraint on the available region.
///
/// If something has already been added, this will point ot `style.spacing.item_spacing` beyond the latest child.
/// If something has already been added, this will point to `style.spacing.item_spacing` beyond the latest child.
/// The cursor can thus be `style.spacing.item_spacing` pixels outside of the min_rect.
pub(crate) cursor: Rect,
}
@@ -409,7 +409,7 @@ impl Layout {
// NOTE: in normal top-down layout the cursor has moved below the current max_rect,
// but the available shouldn't be negative.
// ALSO: with wrapping layouts, cursor jumps to new row before expanding max_rect
// ALSO: with wrapping layouts, cursor jumps to new row before expanding max_rect.
let mut avail = max_rect;
@@ -417,45 +417,47 @@ impl Layout {
Direction::LeftToRight => {
avail.min.x = cursor.min.x;
avail.max.x = avail.max.x.max(cursor.min.x);
if self.main_wrap {
avail.min.y = cursor.min.y;
avail.max.y = cursor.max.y;
}
avail.max.x = avail.max.x.max(avail.min.x);
avail.max.y = avail.max.y.max(avail.min.y);
}
Direction::RightToLeft => {
avail.max.x = cursor.max.x;
avail.min.x = avail.min.x.min(cursor.max.x);
if self.main_wrap {
avail.min.y = cursor.min.y;
avail.max.y = cursor.max.y;
}
avail.min.x = avail.min.x.min(avail.max.x);
avail.max.y = avail.max.y.max(avail.min.y);
}
Direction::TopDown => {
avail.min.y = cursor.min.y;
avail.max.y = avail.max.y.max(cursor.min.y);
if self.main_wrap {
avail.min.x = cursor.min.x;
avail.max.x = cursor.max.x;
}
avail.max.x = avail.max.x.max(avail.min.x);
avail.max.y = avail.max.y.max(avail.min.y);
}
Direction::BottomUp => {
avail.max.y = cursor.max.y;
avail.min.y = avail.min.y.min(cursor.max.y);
if self.main_wrap {
avail.min.x = cursor.min.x;
avail.max.x = cursor.max.x;
}
avail.max.x = avail.max.x.max(avail.min.x);
avail.min.y = avail.min.y.min(avail.max.y);
}
}
// We can use the cursor to restrict the available region.
// For instance, we use this to restrict the available space of a parent Ui
// after adding a panel to it.
// We also use it for wrapping layouts.
avail = avail.intersect(cursor);
// Make sure it isn't negative:
if avail.max.x < avail.min.x {
let x = 0.5 * (avail.min.x + avail.max.x);
avail.min.x = x;
avail.max.x = x;
}
if avail.max.y < avail.min.y {
let y = 0.5 * (avail.min.y + avail.max.y);
avail.min.y = y;
avail.max.y = y;
}
avail
}
@@ -600,7 +602,9 @@ impl Layout {
) -> Rect {
let frame = self.next_frame_ignore_wrap(region, size);
let rect = self.align_size_within_rect(size, frame);
crate::egui_assert!((rect.size() - size).length() < 1.0);
crate::egui_assert!(!rect.any_nan());
crate::egui_assert!((rect.width() - size.x).abs() < 1.0 || size.x == f32::INFINITY);
crate::egui_assert!((rect.height() - size.y).abs() < 1.0 || size.y == f32::INFINITY);
rect
}

View File

@@ -72,6 +72,11 @@ impl Placer {
pub(crate) fn cursor(&self) -> Rect {
self.region.cursor
}
#[inline(always)]
pub(crate) fn set_cursor(&mut self, cursor: Rect) {
self.region.cursor = cursor
}
}
impl Placer {

View File

@@ -78,12 +78,22 @@ impl Ui {
/// Create a new `Ui` at a specific region.
pub fn child_ui(&mut self, max_rect: Rect, layout: Layout) -> Self {
self.child_ui_with_id_source(max_rect, layout, "child")
}
/// Create a new `Ui` at a specific region with a specific id.
pub fn child_ui_with_id_source(
&mut self,
max_rect: Rect,
layout: Layout,
id_source: impl Hash,
) -> Self {
crate::egui_assert!(!max_rect.any_nan());
let next_auto_id_source = Id::new(self.next_auto_id_source).with("child").value();
self.next_auto_id_source = self.next_auto_id_source.wrapping_add(1);
Ui {
id: self.id.with("child"),
id: self.id.with(id_source),
next_auto_id_source,
painter: self.painter.clone(),
style: self.style.clone(),
@@ -436,6 +446,12 @@ impl Ui {
self.set_max_width(*width.end());
}
/// `ui.set_height_range(min..=max);` is equivalent to `ui.set_min_height(min); ui.set_max_height(max);`.
pub fn set_height_range(&mut self, height: std::ops::RangeInclusive<f32>) {
self.set_min_height(*height.start());
self.set_max_height(*height.end());
}
/// Set both the minimum and maximum width.
pub fn set_width(&mut self, width: f32) {
self.set_min_width(width);
@@ -734,6 +750,10 @@ impl Ui {
self.placer.cursor()
}
pub(crate) fn set_cursor(&mut self, cursor: Rect) {
self.placer.set_cursor(cursor)
}
/// Where do we expect a zero-sized widget to be placed?
pub(crate) fn next_widget_position(&self) -> Pos2 {
self.placer.next_widget_position()