Skip to content

Commit

Permalink
Filter entities in the UI (part 0): Make CustomContent more useful (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
abey79 authored Jan 11, 2025
1 parent cdf0181 commit 38cc75b
Show file tree
Hide file tree
Showing 10 changed files with 249 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,10 @@ pub fn item_title_list_item(
.interactive(true)
.show_flat(
ui,
list_item::CustomContent::new(|ui, context| {
ui.allocate_new_ui(
egui::UiBuilder::new()
.max_rect(context.rect)
.layout(egui::Layout::left_to_right(egui::Align::Center)),
|ui| {
ui.spacing_mut().item_spacing.x = 4.0;
ui.style_mut().interaction.selectable_labels = false;
item_heading_no_breadcrumbs(ctx, viewport, ui, item);
},
);
list_item::CustomContent::new(|ui, _| {
ui.spacing_mut().item_spacing.x = 4.0;
ui.style_mut().interaction.selectable_labels = false;
item_heading_no_breadcrumbs(ctx, viewport, ui, item);
}),
);
cursor_interact_with_selectable(ctx, response, item.clone());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,37 +37,30 @@ pub fn item_heading_with_breadcrumbs(
.selected(true)
.show_flat(
ui,
list_item::CustomContent::new(|ui, context| {
ui.allocate_new_ui(
egui::UiBuilder::new()
.max_rect(context.rect)
.layout(egui::Layout::left_to_right(egui::Align::Center)),
|ui| {
ui.spacing_mut().item_spacing.x = 4.0;
list_item::CustomContent::new(|ui, _| {
ui.spacing_mut().item_spacing.x = 4.0;

{
// No background rectangles, even for hovered items
let visuals = ui.visuals_mut();
visuals.widgets.active.bg_fill = egui::Color32::TRANSPARENT;
visuals.widgets.active.weak_bg_fill = egui::Color32::TRANSPARENT;
visuals.widgets.hovered.bg_fill = egui::Color32::TRANSPARENT;
visuals.widgets.hovered.weak_bg_fill = egui::Color32::TRANSPARENT;
}
{
// No background rectangles, even for hovered items
let visuals = ui.visuals_mut();
visuals.widgets.active.bg_fill = egui::Color32::TRANSPARENT;
visuals.widgets.active.weak_bg_fill = egui::Color32::TRANSPARENT;
visuals.widgets.hovered.bg_fill = egui::Color32::TRANSPARENT;
visuals.widgets.hovered.weak_bg_fill = egui::Color32::TRANSPARENT;
}

// First the C>R>U>M>B>S>
{
let previous_style = ui.style().clone();
// Dimmer colors for breadcrumbs
let visuals = ui.visuals_mut();
visuals.widgets.inactive.fg_stroke.color = egui::hex_color!("#6A8CD0"); // TODO(#3133): use design tokens
item_bread_crumbs_ui(ctx, viewport, ui, item);
ui.set_style(previous_style);
}
// First the C>R>U>M>B>S>
{
let previous_style = ui.style().clone();
// Dimmer colors for breadcrumbs
let visuals = ui.visuals_mut();
visuals.widgets.inactive.fg_stroke.color = egui::hex_color!("#6A8CD0"); // TODO(#3133): use design tokens
item_bread_crumbs_ui(ctx, viewport, ui, item);
ui.set_style(previous_style);
}

// Then the full name of the main item:
last_part_of_item_heading(ctx, viewport, ui, item);
},
);
// Then the full name of the main item:
last_part_of_item_heading(ctx, viewport, ui, item);
}),
);
}
Expand Down
10 changes: 3 additions & 7 deletions crates/viewer/re_selection_panel/src/view_entity_picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,15 +90,10 @@ fn add_entities_tree_ui(
entity_path_filter: &ResolvedEntityPathFilter,
entities_add_info: &IntMap<EntityPath, EntityAddInfo>,
) {
let item_content = list_item::CustomContent::new(|ui, content_ctx| {
let mut child_ui = ui.new_child(
egui::UiBuilder::new()
.max_rect(content_ctx.rect)
.layout(egui::Layout::left_to_right(egui::Align::Center)),
);
let item_content = list_item::CustomContent::new(|ui, _| {
add_entities_line_ui(
ctx,
&mut child_ui,
ui,
name,
tree,
view,
Expand Down Expand Up @@ -191,6 +186,7 @@ fn add_entities_line_ui(
}
});

//TODO(ab): use `CustomContent` support for action button to implement this.
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if entity_path_filter.contains_rule_for_exactly(entity_path) {
// Reset-button
Expand Down
20 changes: 17 additions & 3 deletions crates/viewer/re_ui/examples/re_ui_example/right_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,14 +324,28 @@ impl RightPanel {

ui.list_item().show_hierarchical(
ui,
list_item::CustomContent::new(|ui, context| {
list_item::CustomContent::new(|ui, _| {
ui.ctx().debug_painter().debug_rect(
context.rect,
ui.max_rect(),
egui::Color32::LIGHT_RED,
"CustomContent delegates to a closure",
);
}),
)
);

ui.list_item().show_hierarchical(
ui,
list_item::CustomContent::new(|ui, _| {
ui.ctx().debug_painter().debug_rect(
ui.max_rect(),
egui::Color32::LIGHT_RED,
"CustomContent with an action button",
);
})
.action_button(&re_ui::icons::ADD, || {
re_log::warn!("Add button clicked");
}),
);
},
);
}
Expand Down
179 changes: 179 additions & 0 deletions crates/viewer/re_ui/src/list_item/custom_content.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
use egui::{NumExt as _, Ui};

use crate::list_item::{ContentContext, DesiredWidth, ListItemContent};
use crate::DesignTokens;

/// Control how the [`CustomContent`] advertises its width.
#[derive(Debug, Clone, Copy)]
enum CustomContentDesiredWidth {
/// Use the provided [`DesiredWidth`].
DesiredWidth(DesiredWidth),

/// Use [`DesiredWidth::AtLeast`] with a width computed from the provided content, plus any
/// extras such as a button.
ContentWidth(f32),
}

impl Default for CustomContentDesiredWidth {
fn default() -> Self {
Self::DesiredWidth(Default::default())
}
}

/// [`ListItemContent`] that mostly delegates to a closure.
#[expect(clippy::type_complexity)]
pub struct CustomContent<'a> {
ui: Box<dyn FnOnce(&mut egui::Ui, &ContentContext<'_>) + 'a>,
desired_width: CustomContentDesiredWidth,

//TODO(ab): in the future, that should be a `Vec`, with some auto expanding mini-toolbar
button: Option<Box<dyn super::ItemButton + 'a>>,
}

impl<'a> CustomContent<'a> {
/// Create a content with a custom UI closure.
///
/// The closure will be called from within a [`egui::Ui`] with its maximum width set as per the
/// list item geometry. Note that this may differ from [`ContentContext::rect`] if a button is
/// set.
pub fn new(ui: impl FnOnce(&mut egui::Ui, &ContentContext<'_>) + 'a) -> Self {
Self {
ui: Box::new(ui),
desired_width: Default::default(),
button: None,
}
}

/// Set the desired width for the entire content.
#[inline]
pub fn with_desired_width(mut self, desired_width: DesiredWidth) -> Self {
self.desired_width = CustomContentDesiredWidth::DesiredWidth(desired_width);
self
}

/// Set the desired width based on the provided content width. If a button is set, its width
/// will be taken into account and added to the content width.
#[inline]
pub fn with_content_width(mut self, desired_content_width: f32) -> Self {
self.desired_width = CustomContentDesiredWidth::ContentWidth(desired_content_width);
self
}

/// Add a right-aligned [`super::ItemButton`].
///
/// Note: for aesthetics, space is always reserved for the action button.
// TODO(#6191): accept multiple calls for this function for multiple actions.
#[inline]
pub fn button(mut self, button: impl super::ItemButton + 'a) -> Self {
// TODO(#6191): support multiple action buttons
assert!(
self.button.is_none(),
"Only one action button is supported right now"
);

self.button = Some(Box::new(button));
self
}

/// Helper to add an [`super::ItemActionButton`] to the right of the item.
///
/// See [`Self::button`] for more information.
#[inline]
pub fn action_button(
self,
icon: &'static crate::icons::Icon,
on_click: impl FnOnce() + 'a,
) -> Self {
self.action_button_with_enabled(icon, true, on_click)
}

/// Helper to add an enabled/disabled [`super::ItemActionButton`] to the right of the item.
///
/// See [`Self::button`] for more information.
#[inline]
pub fn action_button_with_enabled(
self,
icon: &'static crate::icons::Icon,
enabled: bool,
on_click: impl FnOnce() + 'a,
) -> Self {
self.button(super::ItemActionButton::new(icon, on_click).enabled(enabled))
}

/// Helper to add a [`super::ItemMenuButton`] to the right of the item.
///
/// See [`Self::button`] for more information.
#[inline]
pub fn menu_button(
self,
icon: &'static crate::icons::Icon,
add_contents: impl FnOnce(&mut egui::Ui) + 'a,
) -> Self {
self.button(super::ItemMenuButton::new(icon, add_contents))
}
}

impl ListItemContent for CustomContent<'_> {
fn ui(self: Box<Self>, ui: &mut egui::Ui, context: &ContentContext<'_>) {
let Self {
ui: content_ui,
desired_width: _,
button,
} = *self;

let button_dimension =
DesignTokens::small_icon_size().x + 2.0 * ui.spacing().button_padding.x;

let content_width = if button.is_some() {
(context.rect.width() - button_dimension - DesignTokens::text_to_icon_padding())
.at_least(0.0)
} else {
context.rect.width()
};

let content_rect = egui::Rect::from_min_size(
context.rect.min,
egui::vec2(content_width, context.rect.height()),
);

ui.allocate_new_ui(
egui::UiBuilder::new()
.max_rect(content_rect)
.layout(egui::Layout::left_to_right(egui::Align::Center)),
|ui| {
content_ui(ui, context);
},
);

if let Some(button) = button {
let action_button_rect = egui::Rect::from_center_size(
context.rect.right_center() - egui::vec2(button_dimension / 2.0, 0.0),
egui::Vec2::splat(button_dimension),
);

// the right to left layout is used to mimic LabelContent's buttons behavior and get a
// better alignment
let mut child_ui = ui.new_child(
egui::UiBuilder::new()
.max_rect(action_button_rect)
.layout(egui::Layout::right_to_left(egui::Align::Center)),
);

button.ui(&mut child_ui);
}
}

fn desired_width(&self, ui: &Ui) -> DesiredWidth {
match self.desired_width {
CustomContentDesiredWidth::DesiredWidth(desired_width) => desired_width,
CustomContentDesiredWidth::ContentWidth(mut content_width) => {
if self.button.is_some() {
content_width += DesignTokens::small_icon_size().x
+ 2.0 * ui.spacing().button_padding.x
+ DesignTokens::text_to_icon_padding();
}
DesiredWidth::AtLeast(content_width)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,6 @@ use egui::Ui;

use crate::list_item::{ContentContext, DesiredWidth, ListItemContent};

/// [`ListItemContent`] that delegates to a closure.
#[allow(clippy::type_complexity)]
pub struct CustomContent<'a> {
ui: Box<dyn FnOnce(&mut egui::Ui, &ContentContext<'_>) + 'a>,
desired_width: DesiredWidth,
}

impl<'a> CustomContent<'a> {
pub fn new(ui: impl FnOnce(&mut egui::Ui, &ContentContext<'_>) + 'a) -> Self {
Self {
ui: Box::new(ui),
desired_width: Default::default(),
}
}

#[inline]
pub fn with_desired_width(mut self, desired_width: DesiredWidth) -> Self {
self.desired_width = desired_width;
self
}
}

impl ListItemContent for CustomContent<'_> {
fn ui(self: Box<Self>, ui: &mut egui::Ui, context: &ContentContext<'_>) {
(self.ui)(ui, context);
}

fn desired_width(&self, _ui: &Ui) -> DesiredWidth {
self.desired_width
}
}

/// [`ListItemContent`] that displays the content rect.
#[derive(Debug, Clone, Default)]
pub struct DebugContent {
Expand Down
6 changes: 4 additions & 2 deletions crates/viewer/re_ui/src/list_item/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,21 @@
//! TODO(ab): provide some top-level documentation here.
mod button_content;
mod custom_content;
mod debug_content;
mod item_button;
mod label_content;
#[allow(clippy::module_inception)]
mod list_item;
mod other_contents;
mod property_content;
mod scope;

pub use button_content::*;
pub use custom_content::*;
pub use debug_content::*;
pub use item_button::*;
pub use label_content::*;
pub use list_item::*;
pub use other_contents::*;
pub use property_content::*;
pub use scope::*;

Expand Down
4 changes: 2 additions & 2 deletions crates/viewer/re_ui/src/list_item/property_content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ impl<'a> PropertyContent<'a> {

/// Set the minimum desired width for the entire content.
///
/// Since there is no possibly way to meaningfully collapse two to three columns worth of
/// Since there is no possible way to meaningfully collapse two to three columns worth of
/// content, this is set to 200.0 by default.
#[inline]
pub fn min_desired_width(mut self, min_desired_width: f32) -> Self {
Expand Down Expand Up @@ -78,7 +78,7 @@ impl<'a> PropertyContent<'a> {
// TODO(#6191): support multiple action buttons
assert!(
self.button.is_none(),
"Only one action button supported right now"
"Only one action button is supported right now"
);

self.button = Some(Box::new(button));
Expand Down
Loading

0 comments on commit 38cc75b

Please sign in to comment.