Skip to content

Commit

Permalink
Add support for full span highlighting to modal and use it in the "Ad…
Browse files Browse the repository at this point in the history
…d Space View or Container" modal (#4822)

### What

This PR does the following:
- Make sure the modal's clip rect is the same size as the modal itself.
- Add a `full_span_separator()` helper function that's similar to
`ui.separator()` but draws a full-span line and doesn't add vertical
spacing.
- Add a "full span" mode to our modal. In this mode, the vertical
spacing is entirely left up to the user code.
- Use the above in the "Add Space View or Container" modal for a nice,
full span hover highlighting.
- Add an example in `re_ui_example` showcasing how full span modal and
`ListItem` work together.
- Fixes #4673

`re_ui_example` with `ListItem`:

![Export-1705396140814](https://github.com/rerun-io/rerun/assets/49431240/9b6af286-d17f-4b2c-b47d-983ce3da099c)

Add SV or Container:


![Export-1705397228282](https://github.com/rerun-io/rerun/assets/49431240/8bb5f447-0d05-493d-8d7a-a3a85f335a7a)



### Checklist
* [x] I have read and agree to [Contributor
Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and
the [Code of
Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md)
* [x] I've included a screenshot or gif (if applicable)
* [x] I have tested the web demo (if applicable):
* Using newly built examples:
[app.rerun.io](https://app.rerun.io/pr/4822/index.html)
* Using examples from latest `main` build:
[app.rerun.io](https://app.rerun.io/pr/4822/index.html?manifest_url=https://app.rerun.io/version/main/examples_manifest.json)
* Using full set of examples from `nightly` build:
[app.rerun.io](https://app.rerun.io/pr/4822/index.html?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json)
* [x] The PR title and labels are set such as to maximize their
usefulness for the next release's CHANGELOG

- [PR Build Summary](https://build.rerun.io/pr/4822)
- [Docs
preview](https://rerun.io/preview/c76b1a5e8cf8acbb01c89d1741ab36d2f942189a/docs)
<!--DOCS-PREVIEW-->
- [Examples
preview](https://rerun.io/preview/c76b1a5e8cf8acbb01c89d1741ab36d2f942189a/examples)
<!--EXAMPLES-PREVIEW-->
- [Recent benchmark results](https://build.rerun.io/graphs/crates.html)
- [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)
  • Loading branch information
abey79 authored Jan 16, 2024
1 parent fcc1e51 commit 6a76436
Show file tree
Hide file tree
Showing 4 changed files with 247 additions and 68 deletions.
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

0 comments on commit 6a76436

Please sign in to comment.