Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for full span highlighting to modal and use it in the "Add Space View or Container" modal #4822

Merged
merged 7 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions crates/re_ui/examples/re_ui_example.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use re_ui::list_item::ListItem;
use re_ui::{toasts, CommandPalette, ReUi, UICommand, UICommandSender};

/// Sender that queues up the execution of a command.
Expand Down Expand Up @@ -68,8 +69,12 @@ pub struct ExampleApp {

tree: egui_tiles::Tree<Tab>,

/// regular modal
modal_handler: re_ui::modal::ModalHandler,

/// modal with full span mode
full_span_modal_handler: re_ui::modal::ModalHandler,

left_panel: bool,
right_panel: bool,
bottom_panel: bool,
Expand Down Expand Up @@ -102,6 +107,7 @@ impl ExampleApp {

tree,
modal_handler: Default::default(),
full_span_modal_handler: Default::default(),

left_panel: true,
right_panel: true,
Expand Down Expand Up @@ -236,6 +242,23 @@ impl eframe::App for ExampleApp {

// ---

if ui.button("Open full span modal").clicked() {
self.full_span_modal_handler.open();
}

self.full_span_modal_handler.ui(
&self.re_ui,
ui,
|| re_ui::modal::Modal::new("Modal window").full_span_content(true),
|_, ui, _| {
for idx in 0..10 {
ListItem::new(&self.re_ui, format!("Item {idx}")).show(ui);
}
},
);

// ---

self.re_ui.large_collapsing_header(ui, "Data", true, |ui| {
ui.label("Some data here");
});
Expand Down
27 changes: 27 additions & 0 deletions crates/re_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,33 @@ impl ReUi {
response
}

/// Create a separator similar to [`egui::Separator`] but with the full span behavior.
///
/// The span is determined by the current clip rectangle. Contrary to [`egui::Separator`], this separator allocates
/// a single pixel in height, as spacing is typically handled by content when full span highlighting is used.
pub fn full_span_separator(ui: &mut egui::Ui) -> egui::Response {
let height = 1.0;

let available_space = ui.available_size_before_wrap();
let size = egui::vec2(available_space.x, height);

let (rect, response) = ui.allocate_at_least(size, egui::Sense::hover());
let clip_rect = ui.clip_rect();

if ui.is_rect_visible(response.rect) {
let stroke = ui.visuals().widgets.noninteractive.bg_stroke;
let painter = ui.painter();

painter.hline(
clip_rect.left()..=clip_rect.right(),
painter.round_to_pixel(rect.center().y),
stroke,
);
}

response
}

pub fn panel_content<R>(
&self,
ui: &mut egui::Ui,
Expand Down
89 changes: 83 additions & 6 deletions crates/re_ui/src/modal.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::ReUi;
use egui::NumExt;

/// Helper object to handle a [`Modal`] window.
Expand Down Expand Up @@ -95,28 +96,65 @@ pub struct ModalResponse<R> {
/// └──────────────────▼─────────────────────┘
/// ```
///
/// The modal sets the clip rect such as to allow full-span highlighting behavior (e.g. with [`crate::ListItem`]).
/// Consider using [`crate::ReUi::full_span_separator`] to draw a separator that spans the full width of the modal
/// instead of the usual [`egui::Ui::separator`] method.
///
/// Note that [`Modal`] are typically used via the [`ModalHandler`] helper object to reduce boilerplate.
pub struct Modal {
title: String,
min_width: Option<f32>,
min_height: Option<f32>,
default_height: Option<f32>,
full_span_content: bool,
}

impl Modal {
/// Create a new modal with the given title.
pub fn new(title: &str) -> Self {
Self {
title: title.to_owned(),
min_width: None,
min_height: None,
default_height: None,
full_span_content: false,
}
}

/// Set the minimum width of the modal window.
#[inline]
pub fn min_width(mut self, min_width: f32) -> Self {
self.min_width = Some(min_width);
self
}

/// Set the minimum height of the modal window.
#[inline]
pub fn min_height(mut self, min_height: f32) -> Self {
self.min_height = Some(min_height);
self
}

/// Set the default height of the modal window.
#[inline]
pub fn default_height(mut self, default_height: f32) -> Self {
self.default_height = Some(default_height);
self
}

/// Configure the content area of the modal for full span highlighting.
///
/// This includes:
/// - setting the vertical spacing to 0.0
/// - removing any padding at the bottom of the area
///
/// In this mode, the user code is responsible for adding spacing between items.
#[inline]
pub fn full_span_content(mut self, full_span_content: bool) -> Self {
self.full_span_content = full_span_content;
self
}

/// Show the modal window.
///
/// Typically called by [`ModalHandler::ui`].
Expand All @@ -134,8 +172,6 @@ impl Modal {
let modal_vertical_margins = (75.0).at_most(screen_height * 0.1);

let mut window = egui::Window::new(&self.title)
//TODO(ab): workaround for https://github.com/emilk/egui/pull/3721 until we make a new egui release
.id(egui::Id::new(("modal", &self.title)))
.pivot(egui::Align2::CENTER_TOP)
.fixed_pos(
ui.ctx().screen_rect().center_top() + egui::vec2(0.0, modal_vertical_margins),
Expand All @@ -145,19 +181,61 @@ impl Modal {
.collapsible(false)
.resizable(true)
.frame(egui::Frame {
// Note: inner margin are kept to zero so the clip rect is set to the same size as the modal itself,
// which is needed for the full-span highlighting behavior.
fill: ui.visuals().panel_fill,
inner_margin: crate::ReUi::view_padding().into(),
..Default::default()
})
.title_bar(false);

if let Some(min_width) = self.min_width {
window = window.min_width(min_width);
}

if let Some(min_height) = self.min_height {
window = window.min_height(min_height);
}

if let Some(default_height) = self.default_height {
window = window.default_height(default_height);
}

let response = window.show(ui.ctx(), |ui| {
Self::title_bar(re_ui, ui, &self.title, &mut open);
content_ui(re_ui, ui, &mut open)
let item_spacing_y = ui.spacing().item_spacing.y;
ui.spacing_mut().item_spacing.y = 0.0;

egui::Frame {
inner_margin: egui::Margin::symmetric(ReUi::view_padding(), 0.0),
..Default::default()
}
.show(ui, |ui| {
ui.add_space(ReUi::view_padding());
Self::title_bar(re_ui, ui, &self.title, &mut open);
ui.add_space(ReUi::view_padding());
crate::ReUi::full_span_separator(ui);

if self.full_span_content {
// no further spacing for the content UI
content_ui(re_ui, ui, &mut open)
} else {
// we must restore vertical spacing and add view padding at the bottom
ui.add_space(item_spacing_y);

egui::Frame {
inner_margin: egui::Margin {
bottom: ReUi::view_padding(),
..Default::default()
},
..Default::default()
}
.show(ui, |ui| {
ui.spacing_mut().item_spacing.y = item_spacing_y;
content_ui(re_ui, ui, &mut open)
})
.inner
}
})
.inner
});

// Any click outside causes the window to close.
Expand Down Expand Up @@ -211,6 +289,5 @@ impl Modal {
*open = false;
}
});
ui.separator();
}
}
Loading
Loading