mirror of
https://github.com/emilk/egui.git
synced 2026-09-02 06:40:06 -04:00
Experiment with a new Group container
This commit is contained in:
@@ -1913,6 +1913,14 @@ dependencies = [
|
|||||||
"bitflags 2.4.0",
|
"bitflags 2.4.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "group_layout"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"eframe",
|
||||||
|
"env_logger",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gtk-sys"
|
name = "gtk-sys"
|
||||||
version = "0.18.0"
|
version = "0.18.0"
|
||||||
|
|||||||
159
crates/egui/src/containers/group.rs
Normal file
159
crates/egui/src/containers/group.rs
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
//! Frame container
|
||||||
|
|
||||||
|
use crate::{layers::ShapeIdx, *};
|
||||||
|
use epaint::*;
|
||||||
|
|
||||||
|
/// A group of widgets with a unique id
|
||||||
|
/// that can be used in centered layouts.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
|
||||||
|
#[must_use = "You should call .show()"]
|
||||||
|
pub struct Group {
|
||||||
|
id_source: Id,
|
||||||
|
frame: Frame,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Group {
|
||||||
|
pub fn new(id_source: impl Into<Id>) -> Self {
|
||||||
|
Self {
|
||||||
|
id_source: id_source.into(),
|
||||||
|
frame: Frame::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn frame(mut self, frame: Frame) -> Self {
|
||||||
|
self.frame = frame;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
pub struct Prepared {
|
||||||
|
id: Id,
|
||||||
|
|
||||||
|
/// The frame that was prepared.
|
||||||
|
///
|
||||||
|
/// The margin has already been read and used,
|
||||||
|
/// but the rest of the fields may be modified.
|
||||||
|
pub frame: Frame,
|
||||||
|
|
||||||
|
/// This is where we will insert the frame shape so it ends up behind the content.
|
||||||
|
where_to_put_background: ShapeIdx,
|
||||||
|
|
||||||
|
/// Add your widgets to this UI so it ends up within the frame.
|
||||||
|
pub content_ui: Ui,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Group {
|
||||||
|
/// Begin a dynamically colored frame.
|
||||||
|
///
|
||||||
|
/// This is a more advanced API.
|
||||||
|
/// Usually you want to use [`Self::show`] instead.
|
||||||
|
///
|
||||||
|
/// See docs for [`Group`] for an example.
|
||||||
|
pub fn begin(self, ui: &mut Ui) -> Prepared {
|
||||||
|
let Self { id_source, frame } = self;
|
||||||
|
let id = ui.make_persistent_id(id_source);
|
||||||
|
|
||||||
|
let where_to_put_background = ui.painter().add(Shape::Noop);
|
||||||
|
|
||||||
|
let prev_inner_size: Option<Vec2> = ui.data(|data| data.get_temp(id));
|
||||||
|
|
||||||
|
let mut inner_rect = if let Some(prev_inner_size) = prev_inner_size {
|
||||||
|
let (_, outer_rect) = ui.allocate_space(prev_inner_size + frame.total_margin().sum());
|
||||||
|
outer_rect - frame.total_margin()
|
||||||
|
} else {
|
||||||
|
// Invisible sizing pass
|
||||||
|
let outer_rect_bounds = ui.available_rect_before_wrap();
|
||||||
|
outer_rect_bounds - frame.total_margin()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Make sure we don't shrink to the negative:
|
||||||
|
inner_rect.max.x = inner_rect.max.x.max(inner_rect.min.x);
|
||||||
|
inner_rect.max.y = inner_rect.max.y.max(inner_rect.min.y);
|
||||||
|
|
||||||
|
let sizing_pass = prev_inner_size.is_none();
|
||||||
|
|
||||||
|
let mut layout = *ui.layout();
|
||||||
|
|
||||||
|
if sizing_pass {
|
||||||
|
// TODO(emilk): this code is duplicated from `ui.rs`
|
||||||
|
// During the sizing pass we want widgets to use up as little space as possible,
|
||||||
|
// so that we measure the only the space we _need_.
|
||||||
|
layout.cross_justify = false;
|
||||||
|
if layout.cross_align == Align::Center {
|
||||||
|
layout.cross_align = Align::Min;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut content_ui = ui.child_ui(
|
||||||
|
inner_rect,
|
||||||
|
layout,
|
||||||
|
Some(UiStackInfo::new(UiKind::Frame).with_frame(frame)),
|
||||||
|
);
|
||||||
|
|
||||||
|
if sizing_pass {
|
||||||
|
content_ui.set_sizing_pass();
|
||||||
|
}
|
||||||
|
|
||||||
|
Prepared {
|
||||||
|
id,
|
||||||
|
frame,
|
||||||
|
where_to_put_background,
|
||||||
|
content_ui,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show the given ui surrounded by this frame.
|
||||||
|
pub fn show<R>(self, ui: &mut Ui, add_contents: impl FnOnce(&mut Ui) -> R) -> InnerResponse<R> {
|
||||||
|
self.show_dyn(ui, Box::new(add_contents))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show using dynamic dispatch.
|
||||||
|
pub fn show_dyn<'c, R>(
|
||||||
|
self,
|
||||||
|
ui: &mut Ui,
|
||||||
|
add_contents: Box<dyn FnOnce(&mut Ui) -> R + 'c>,
|
||||||
|
) -> InnerResponse<R> {
|
||||||
|
let mut prepared = self.begin(ui);
|
||||||
|
let ret = add_contents(&mut prepared.content_ui);
|
||||||
|
let response = prepared.end(ui);
|
||||||
|
InnerResponse::new(ret, response)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Prepared {
|
||||||
|
/// Allocate the space that was used by [`Self::content_ui`].
|
||||||
|
///
|
||||||
|
/// This MUST be called, or the parent ui will not know how much space this widget used.
|
||||||
|
///
|
||||||
|
/// This can be called before or after [`Self::paint`].
|
||||||
|
pub fn allocate_space(&self, ui: &mut Ui) -> Response {
|
||||||
|
let inner_rect = self.content_ui.min_rect();
|
||||||
|
let outer_rect = inner_rect + self.frame.total_margin();
|
||||||
|
|
||||||
|
// Remember size to next frame
|
||||||
|
ui.data_mut(|data| data.insert_temp(self.id, inner_rect.size()));
|
||||||
|
|
||||||
|
ui.allocate_rect(outer_rect, Sense::hover())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Paint the frame.
|
||||||
|
///
|
||||||
|
/// This can be called before or after [`Self::allocate_space`].
|
||||||
|
pub fn paint(&self, ui: &Ui) {
|
||||||
|
let paint_rect = self.content_ui.min_rect() + self.frame.inner_margin;
|
||||||
|
|
||||||
|
if ui.is_rect_visible(paint_rect) {
|
||||||
|
let shape = self.frame.paint(paint_rect);
|
||||||
|
ui.painter().set(self.where_to_put_background, shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience for calling [`Self::allocate_space`] and [`Self::paint`].
|
||||||
|
pub fn end(self, ui: &mut Ui) -> Response {
|
||||||
|
self.paint(ui);
|
||||||
|
self.allocate_space(ui)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@ pub(crate) mod area;
|
|||||||
pub mod collapsing_header;
|
pub mod collapsing_header;
|
||||||
mod combo_box;
|
mod combo_box;
|
||||||
pub(crate) mod frame;
|
pub(crate) mod frame;
|
||||||
|
pub mod group;
|
||||||
pub mod panel;
|
pub mod panel;
|
||||||
pub mod popup;
|
pub mod popup;
|
||||||
pub(crate) mod resize;
|
pub(crate) mod resize;
|
||||||
@@ -17,6 +18,7 @@ pub use {
|
|||||||
collapsing_header::{CollapsingHeader, CollapsingResponse},
|
collapsing_header::{CollapsingHeader, CollapsingResponse},
|
||||||
combo_box::*,
|
combo_box::*,
|
||||||
frame::Frame,
|
frame::Frame,
|
||||||
|
group::Group,
|
||||||
panel::{CentralPanel, SidePanel, TopBottomPanel},
|
panel::{CentralPanel, SidePanel, TopBottomPanel},
|
||||||
popup::*,
|
popup::*,
|
||||||
resize::Resize,
|
resize::Resize,
|
||||||
|
|||||||
22
examples/group_layout/Cargo.toml
Normal file
22
examples/group_layout/Cargo.toml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[package]
|
||||||
|
name = "group_layout"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||||
|
license = "MIT OR Apache-2.0"
|
||||||
|
edition = "2021"
|
||||||
|
rust-version = "1.76"
|
||||||
|
publish = false
|
||||||
|
|
||||||
|
[lints]
|
||||||
|
workspace = true
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
eframe = { workspace = true, features = [
|
||||||
|
"default",
|
||||||
|
"__screenshot", # __screenshot is so we can dump a screenshot using EFRAME_SCREENSHOT_TO
|
||||||
|
] }
|
||||||
|
env_logger = { version = "0.10", default-features = false, features = [
|
||||||
|
"auto-color",
|
||||||
|
"humantime",
|
||||||
|
] }
|
||||||
32
examples/group_layout/src/main.rs
Normal file
32
examples/group_layout/src/main.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] // hide console window on Windows in release
|
||||||
|
#![allow(rustdoc::missing_crate_level_docs)] // it's an example
|
||||||
|
|
||||||
|
use eframe::egui;
|
||||||
|
|
||||||
|
fn main() -> Result<(), eframe::Error> {
|
||||||
|
env_logger::init(); // Log to stderr (if you run with `RUST_LOG=debug`).
|
||||||
|
|
||||||
|
let options = eframe::NativeOptions {
|
||||||
|
viewport: egui::ViewportBuilder::default().with_inner_size([640.0, 480.0]),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
eframe::run_simple_native("My egui App", options, move |ctx, _frame| {
|
||||||
|
egui::CentralPanel::default().show(ctx, |ui| {
|
||||||
|
ui.vertical_centered(|ui| {
|
||||||
|
egui::Group::new("my_group")
|
||||||
|
.frame(egui::Frame::group(ui.style()))
|
||||||
|
.show(ui, |ui| {
|
||||||
|
ui.horizontal(|ui| {
|
||||||
|
ui.label("Hello");
|
||||||
|
ui.code("world!");
|
||||||
|
});
|
||||||
|
|
||||||
|
if ui.button("Reset egui").clicked() {
|
||||||
|
ui.memory_mut(|mem| *mem = Default::default());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user