Skip to content

Commit

Permalink
ListItem 2.0 (part 6): split full-span range management to a dedica…
Browse files Browse the repository at this point in the history
…ted module (#6211)

### What

So far, the full span x coordinate rang needed for highlighting was
maintained by `LayoutInfo` and `list_item_scope`. However, other widgets
also need this mechanism. This PR splits of full-span management to a
dedicated module, and deploys it both in `re_ui_example` and where
`list_item2` is currently used.

Other changes:
- This PR also improves the sanity checks regarding both
`list_item_scope` and `full_span_scope`. When missing, debug build will
panic and release build will emit warnings.
- Both scopes are now `ui.scope()`, so it's safe to modify `ui.style()`
from the closure.
- `list_item_scope` now sets `ui.spacing().item_spacing.y = 0`, since
this is required anyway for `ListItem`s to look good.

Follow-up PR will be needed to deploy `full_span` to all relevant
widgets and their use in the viewer (#6156).

- Part of #6075 
- Part of #6156
- Follow-up to #6184

### 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 examples from latest `main` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/6211?manifest_url=https://app.rerun.io/version/main/examples_manifest.json)
* Using full set of examples from `nightly` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/6211?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
* [x] If applicable, add a new check to the [release
checklist](https://github.com/rerun-io/rerun/blob/main/tests/python/release_checklist)!

- [PR Build Summary](https://build.rerun.io/pr/6211)
- [Recent benchmark results](https://build.rerun.io/graphs/crates.html)
- [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)

To run all checks from `main`, comment on the PR with `@rerun-bot
full-check`.
  • Loading branch information
abey79 authored May 6, 2024
1 parent e25e1e2 commit 0dfe602
Show file tree
Hide file tree
Showing 10 changed files with 210 additions and 176 deletions.
61 changes: 30 additions & 31 deletions crates/re_data_ui/src/editors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -255,50 +255,49 @@ fn edit_marker_shape_ui(

let marker_text = edit_marker.to_string();

let item_width = 100.0;

egui::ComboBox::from_id_source("marker_shape")
.selected_text(marker_text) // TODO(emilk): Show marker shape in the selected text
.width(ui.available_width().at_most(100.0))
.width(
ui.available_width()
.at_most(item_width + ui.spacing().menu_margin.sum().x),
)
.height(320.0)
.show_ui(ui, |ui| {
// no spacing between list items
ui.spacing_mut().item_spacing.y = 0.0;

let item_width = 100.0;

// workaround to force `ui.max_rect()` to reflect the content size
ui.allocate_space(egui::vec2(item_width, 0.0));
ui.set_width(item_width);

let background_x_range = ui
.spacing()
.menu_margin
.expand_rect(ui.max_rect())
.x_range();

re_ui::list_item2::list_item_scope(
ui,
"marker_shape",
Some(background_x_range),
|ui| {
for marker in MarkerShape::ALL {
let response = ctx
.re_ui
.list_item2()
.selected(edit_marker == marker)
.show_flat(
ui,
re_ui::list_item2::LabelContent::new(marker.to_string())
.min_desired_width(item_width)
.with_icon_fn(|_re_ui, ui, rect, visuals| {
paint_marker(ui, marker.into(), rect, visuals.text_color());
}),
);

if response.clicked() {
edit_marker = marker;
}
let list_ui = |ui: &mut egui::Ui| {
for marker in MarkerShape::ALL {
let response = ctx
.re_ui
.list_item2()
.selected(edit_marker == marker)
.show_flat(
ui,
re_ui::list_item2::LabelContent::new(marker.to_string())
.min_desired_width(item_width)
.with_icon_fn(|_re_ui, ui, rect, visuals| {
paint_marker(ui, marker.into(), rect, visuals.text_color());
}),
);

if response.clicked() {
edit_marker = marker;
}
},
);
}
};

re_ui::full_span::full_span_scope(ui, background_x_range, |ui| {
re_ui::list_item2::list_item_scope(ui, "marker_shape", list_ui);
});
});

if edit_marker != current_marker {
Expand Down
16 changes: 4 additions & 12 deletions crates/re_ui/examples/re_ui_example/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -312,19 +312,11 @@ impl eframe::App for ExampleApp {
.frame(panel_frame)
.min_width(0.0)
.show_animated(egui_ctx, self.show_right_panel, |ui| {
// TODO(#6156): this is still needed for some full-span widgets
ui.set_clip_rect(ui.max_rect());

// define the hover/selection background highlight range for all nested `ListItem`s
re_ui::list_item2::list_item_scope(
ui,
"right_panel_list_item_scope",
Some(ui.max_rect().x_range()),
|ui| {
ui.spacing_mut().item_spacing.y = 0.0;
self.right_panel.ui(&self.re_ui, ui);
},
);
re_ui::full_span::full_span_scope(ui, ui.max_rect().x_range(), |ui| {
ui.spacing_mut().item_spacing.y = 0.0;
self.right_panel.ui(&self.re_ui, ui);
});
});

egui::CentralPanel::default()
Expand Down
62 changes: 36 additions & 26 deletions crates/re_ui/examples/re_ui_example/right_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,11 +47,13 @@ impl RightPanel {
ui.label("Hierarchical:");
});

if self.show_hierarchical_demo {
self.hierarchical_drag_and_drop.ui(re_ui, ui);
} else {
self.drag_and_drop.ui(re_ui, ui);
}
list_item2::list_item_scope(ui, "drag_and_drop", |ui| {
if self.show_hierarchical_demo {
self.hierarchical_drag_and_drop.ui(re_ui, ui);
} else {
self.drag_and_drop.ui(re_ui, ui);
}
});
});

ui.add_space(20.0);
Expand All @@ -62,7 +64,10 @@ impl RightPanel {

re_ui.panel_content(ui, |re_ui, ui| {
re_ui.panel_title_bar(ui, "Demo: ListItem APIs", None);
self.list_item_api_demo(re_ui, ui);

list_item2::list_item_scope(ui, "list_item_api", |ui| {
self.list_item_api_demo(re_ui, ui);
});
});

ui.add_space(20.0);
Expand All @@ -81,25 +86,30 @@ impl RightPanel {
.auto_shrink([false, true])
.show(ui, |ui| {
re_ui.panel_content(ui, |re_ui, ui| {
for i in 0..10 {
let label = if i == 4 {
"That's one heck of a loooooooong label!".to_owned()
} else {
format!("Some item {i}")
};

// Note: we use `exact_width(true)` here to force the item to allocate
// as much as needed for the label, which in turn will trigger the
// scroll area.
if re_ui
.list_item2()
.selected(Some(i) == self.selected_list_item)
.show_flat(ui, list_item2::LabelContent::new(&label).exact_width(true))
.clicked()
{
self.selected_list_item = Some(i);
list_item2::list_item_scope(ui, "scroll_area_demo", |ui| {
for i in 0..10 {
let label = if i == 4 {
"That's one heck of a loooooooong label!".to_owned()
} else {
format!("Some item {i}")
};

// Note: we use `exact_width(true)` here to force the item to allocate
// as much as needed for the label, which in turn will trigger the
// scroll area.
if re_ui
.list_item2()
.selected(Some(i) == self.selected_list_item)
.show_flat(
ui,
list_item2::LabelContent::new(&label).exact_width(true),
)
.clicked()
{
self.selected_list_item = Some(i);
}
}
}
});
});
});
}
Expand Down Expand Up @@ -200,7 +210,7 @@ impl RightPanel {
|re_ui, ui| {
// By using an inner scope, we allow the nested properties to not align themselves
// to the parent property, which in this particular case looks better.
list_item2::list_item_scope(ui, "inner_scope", None, |ui| {
list_item2::list_item_scope(ui, "inner_scope", |ui| {
re_ui.list_item2().show_hierarchical(
ui,
list_item2::PropertyContent::new("Bool").value_bool(self.boolean),
Expand Down Expand Up @@ -255,7 +265,7 @@ impl RightPanel {
|re_ui, ui| {
// By using an inner scope, we allow the nested properties to not align themselves
// to the parent property, which in this particular case looks better.
list_item2::list_item_scope(ui, "inner_scope", None, |ui| {
list_item2::list_item_scope(ui, "inner_scope", |ui| {
fn demo_item(re_ui: &ReUi, ui: &mut egui::Ui) {
re_ui.list_item2().show_hierarchical(
ui,
Expand Down
60 changes: 60 additions & 0 deletions crates/re_ui/src/full_span.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//! Support for full-span widgets.
//!
//! Full-span widgets are widgets which draw beyond the boundaries of `ui.max_rect()`, e.g. to
//! provide highlighting without margin, like [`crate::list_item2`]. Typically, in the context of a
//! side panel, the full span is the entire width of the panel, excluding any margin.
//!
//! This module maintains a stack of full span values (effectively[`egui::Rangef`]) using nestable
//! scopes (via [`full_span_scope`]) and makes them available to widgets (via [`get_full_span`]).
#[derive(Clone, Default)]
struct FullSpanStack(Vec<egui::Rangef>);

/// Set up a full-span scope.
///
/// Note:
/// - Uses [`egui::Ui::scope`] internally, so it's safe to modify `ui` in the closure.
/// - Can be nested since the full-span range is stored in a stack.
pub fn full_span_scope<R>(
ui: &mut egui::Ui,
background_x_range: egui::Rangef,
content: impl FnOnce(&mut egui::Ui) -> R,
) -> R {
// push
ui.ctx().data_mut(|writer| {
let stack: &mut FullSpanStack = writer.get_temp_mut_or_default(egui::Id::NULL);
stack.0.push(background_x_range);
});

//
let result = ui.scope(content).inner;

// pop
ui.ctx().data_mut(|writer| {
let stack: &mut FullSpanStack = writer.get_temp_mut_or_default(egui::Id::NULL);
stack.0.pop();
});

result
}

/// Retrieve the current full-span scope.
///
/// If called outside a [`full_span_scope`], this function emits a warning and returns the clip
/// rectangle width. In debug build, it panics.
pub fn get_full_span(ui: &egui::Ui) -> egui::Rangef {
let range = ui.ctx().data_mut(|writer| {
let stack: &mut FullSpanStack = writer.get_temp_mut_or_default(egui::Id::NULL);
stack.0.last().copied()
});

if range.is_none() {
re_log::warn_once!("Full span requested outside a `full_span_scope()`");
}
debug_assert!(
range.is_some(),
"Full span requested outside a `full_span_scope()`"
);

range.unwrap_or(ui.clip_rect().x_range())
}
1 change: 1 addition & 0 deletions crates/re_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod syntax_highlighting;
mod toggle_switch;

pub mod drag_and_drop;
pub mod full_span;
pub mod icons;
pub mod list_item;
pub mod list_item2;
Expand Down
11 changes: 8 additions & 3 deletions crates/re_ui/src/list_item2/list_item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,8 @@ impl<'a> ListItem<'a> {
}

/// Draw the item as part of a flat list.
///
/// *Important*: must be called while nested in a [`super::list_item_scope`].
pub fn show_flat(self, ui: &mut Ui, content: impl ListItemContent + 'a) -> Response {
// Note: the purpose of the scope is to minimise interferences on subsequent items' id
ui.scope(|ui| self.ui(ui, None, 0.0, Box::new(content)))
Expand All @@ -126,6 +128,8 @@ impl<'a> ListItem<'a> {
}

/// Draw the item as a leaf node from a hierarchical list.
///
/// *Important*: must be called while nested in a [`super::list_item_scope`].
pub fn show_hierarchical(self, ui: &mut Ui, content: impl ListItemContent + 'a) -> Response {
// Note: the purpose of the scope is to minimise interferences on subsequent items' id
ui.scope(|ui| {
Expand All @@ -141,6 +145,8 @@ impl<'a> ListItem<'a> {
}

/// Draw the item as a non-leaf node from a hierarchical list.
///
/// *Important*: must be called while nested in a [`super::list_item_scope`].
pub fn show_hierarchical_with_children<R>(
mut self,
ui: &mut Ui,
Expand Down Expand Up @@ -239,9 +245,8 @@ impl<'a> ListItem<'a> {
// We use the state set by ListItemContainer to determine how far the background should
// extend.
let layout_info = LayoutInfoStack::top(ui.ctx());
let mut bg_rect = rect;
bg_rect.set_left(layout_info.background_x_range.min);
bg_rect.set_right(layout_info.background_x_range.max);
let bg_rect =
egui::Rect::from_x_y_ranges(crate::full_span::get_full_span(ui), rect.y_range());

// Record the max allocated width.
layout_info.register_max_item_width(ui.ctx(), rect.right() - layout_info.left_x);
Expand Down
2 changes: 1 addition & 1 deletion crates/re_ui/src/list_item2/property_content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ impl ListItemContent for PropertyContent<'_> {
} = *self;

// │ │
// │◀─────────────────────layout_info.background_x_range─────────────────────────▶│
// │◀─────────────────────────────get_full_span()────────────────────────────────▶│
// │ │
// │ ◀────────layout_info.left_column_width─────────▶│┌──COLUMN_SPACING │
// │ ▼ │
Expand Down
Loading

0 comments on commit 0dfe602

Please sign in to comment.