diff --git a/crates/egui/src/containers/group.rs b/crates/egui/src/containers/group.rs new file mode 100644 index 000000000..a4a7d5e35 --- /dev/null +++ b/crates/egui/src/containers/group.rs @@ -0,0 +1,78 @@ +use emath::NumExt; +use crate::{Align2, Id, Rect, Ui, UiBuilder, Vec2}; + +pub struct Group { + id: Id, + rect: Option, + size: Option, + align2: Align2, +} + +impl Group { + pub fn new(id: impl Into) -> Self { + Self { + id: id.into(), + rect: None, + size: None, + align2: Align2::CENTER_CENTER, + } + } + + pub fn align2(mut self, align2: Align2) -> Self { + self.align2 = align2; + self + } + + pub fn rect(mut self, rect: Rect) -> Self { + self.rect = Some(rect); + self + } + + pub fn ui(self, ui: &mut Ui, content: impl FnOnce(&mut Ui) -> T) -> T { + let id = ui.id().with(self.id); + let data_id = id.with("group"); + + let rect = if let Some(rect) = self.rect { + rect + } else if let Some(size) = self.size { + let pos = ui.next_widget_position(); + Rect::from_min_size(pos, size) + } else { + ui.available_rect_before_wrap() + }; + + let last_size = ui.ctx().data(|mem| mem.get_temp(data_id)); + + let mut content_rect = if let Some(size) = last_size { + let left_top = self.align2.align_size_within_rect(size, rect).left_top(); + Rect::from_min_size(left_top, rect.size()) + } else { + rect + }; + + // Clamp the content_rect so it doesn't exceed the top left corner + let offset = (rect.min - content_rect.min).at_least(Vec2::ZERO); + content_rect = content_rect.translate(offset); + + let mut builder = UiBuilder::new().id_salt(id); + + if last_size.is_none() { + builder = builder.invisible(); + } + + let response = ui.scope_builder(builder.max_rect(content_rect), content); + + let size = response.response.rect.size(); + + if last_size != Some(size) { + ui.ctx().request_discard("Group size changed"); + ui.ctx().request_repaint(); + } + + ui.ctx().data_mut(|mem| { + mem.insert_temp(data_id, size); + }); + + response.inner + } +} diff --git a/crates/egui/src/containers/mod.rs b/crates/egui/src/containers/mod.rs index a8f3306e9..aefeb644f 100644 --- a/crates/egui/src/containers/mod.rs +++ b/crates/egui/src/containers/mod.rs @@ -18,7 +18,9 @@ pub mod scroll_area; mod sides; mod tooltip; pub(crate) mod window; +mod group; +pub use group::Group; pub use { area::{Area, AreaState}, close_tag::ClosableTag, diff --git a/crates/egui_demo_lib/src/demo/demo_app_windows.rs b/crates/egui_demo_lib/src/demo/demo_app_windows.rs index d2cc17448..bbe2794d5 100644 --- a/crates/egui_demo_lib/src/demo/demo_app_windows.rs +++ b/crates/egui_demo_lib/src/demo/demo_app_windows.rs @@ -80,6 +80,7 @@ impl Default for DemoGroups { Box::::default(), Box::::default(), Box::::default(), + Box::::default(), Box::::default(), Box::::default(), Box::::default(), diff --git a/crates/egui_demo_lib/src/demo/group_demo.rs b/crates/egui_demo_lib/src/demo/group_demo.rs new file mode 100644 index 000000000..68b9465d7 --- /dev/null +++ b/crates/egui_demo_lib/src/demo/group_demo.rs @@ -0,0 +1,76 @@ +use egui::{Align, Align2, Group, ScrollArea}; + +#[derive(PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))] +#[cfg_attr(feature = "serde", serde(default))] +pub struct GroupDemo { + align_x: Align, + align_y: Align, +} + +impl Default for GroupDemo { + fn default() -> Self { + Self { + align_x: Align::Center, + align_y: Align::Center, + } + } +} + +impl crate::Demo for GroupDemo { + fn name(&self) -> &'static str { + "Group" + } + + fn show(&mut self, ui: &mut egui::Ui, open: &mut bool) { + egui::Window::new(self.name()) + .open(open) + .default_width(300.0) + .default_height(300.0) + .resizable(true) + .constrain_to(ui.available_rect_before_wrap()) + .show(ui, |ui| { + use crate::View as _; + self.ui(ui); + }); + } +} + +impl crate::View for GroupDemo { + fn ui(&mut self, ui: &mut egui::Ui) { + ui.vertical_centered(|ui| { + ui.add(crate::egui_github_link_file!()); + }); + + ui.horizontal(|ui| { + ui.label("Horizontal:"); + ui.selectable_value(&mut self.align_x, Align::Min, "Left"); + ui.selectable_value(&mut self.align_x, Align::Center, "Center"); + ui.selectable_value(&mut self.align_x, Align::Max, "Right"); + }); + + ui.horizontal(|ui| { + ui.label("Vertical:"); + ui.selectable_value(&mut self.align_y, Align::Min, "Top"); + ui.selectable_value(&mut self.align_y, Align::Center, "Center"); + ui.selectable_value(&mut self.align_y, Align::Max, "Bottom"); + }); + + ui.separator(); + + let align2 = Align2([self.align_x, self.align_y]); + + + Group::new("demo_group").align2(align2).ui(ui, |ui| { + ui.label("Hello!"); + let _ = ui.button("A button"); + ui.label("More text"); + ScrollArea::vertical().max_height(50.0).show(ui, |ui| { + for _ in 0..100 { + ui.label("Even more text"); + } + }); + }); + ui.set_height(ui.available_height()); + } +} diff --git a/crates/egui_demo_lib/src/demo/mod.rs b/crates/egui_demo_lib/src/demo/mod.rs index 6d1906166..8edf9b265 100644 --- a/crates/egui_demo_lib/src/demo/mod.rs +++ b/crates/egui_demo_lib/src/demo/mod.rs @@ -13,6 +13,7 @@ pub mod drag_and_drop; pub mod extra_viewport; pub mod font_book; pub mod frame_demo; +pub mod group_demo; pub mod highlighting; pub mod interactive_container; pub mod misc_demo_window;