diff --git a/crates/re_selection_panel/src/selection_panel.rs b/crates/re_selection_panel/src/selection_panel.rs index 9a8109307d8f..a2b2fd6ddf58 100644 --- a/crates/re_selection_panel/src/selection_panel.rs +++ b/crates/re_selection_panel/src/selection_panel.rs @@ -11,7 +11,7 @@ use re_log_types::EntityPathFilter; use re_types::blueprint::components::Interactive; use re_ui::{ icons, - list_item::{self, LabelContent, PropertyContent}, + list_item::{self, PropertyContent}, ContextExt as _, DesignTokens, SyntaxHighlighting as _, UiExt, }; use re_viewer_context::{ @@ -454,19 +454,17 @@ fn clone_space_view_button_ui( blueprint: &ViewportBlueprint, view_id: SpaceViewId, ) { - //TODO(#6707): use `ButtonContent` when it exists - if ui - .list_item() - .show_flat(ui, LabelContent::new("Clone this view")) - .on_hover_text("Create an exact duplicate of this view including all blueprint settings") - .clicked() - { - if let Some(new_space_view_id) = blueprint.duplicate_space_view(&view_id, ctx) { - ctx.selection_state() - .set_selection(Item::SpaceView(new_space_view_id)); - blueprint.mark_user_interaction(ctx); - } - } + ui.list_item_flat_noninteractive( + list_item::ButtonContent::new("Clone this view") + .on_click(|| { + if let Some(new_space_view_id) = blueprint.duplicate_space_view(&view_id, ctx) { + ctx.selection_state() + .set_selection(Item::SpaceView(new_space_view_id)); + blueprint.mark_user_interaction(ctx); + } + }) + .hover_text("Create an exact duplicate of this view including all blueprint settings"), + ); } /// Returns a new filter when the editing is done, and there has been a change. @@ -971,25 +969,23 @@ fn container_top_level_properties( })); } - //TODO(#6707): use `ButtonContent` when it exists - if ui - .list_item() - .show_flat(ui, LabelContent::new("Simplify hierarchy")) - .on_hover_text("Simplify this container and its children") - .clicked() - { - blueprint.simplify_container( - container_id, - egui_tiles::SimplificationOptions { - prune_empty_tabs: true, - prune_empty_containers: true, - prune_single_child_tabs: false, - prune_single_child_containers: false, - all_panes_must_have_tabs: true, - join_nested_linear_containers: true, - }, - ); - } + ui.list_item_flat_noninteractive( + list_item::ButtonContent::new("Simplify hierarchy") + .on_click(|| { + blueprint.simplify_container( + container_id, + egui_tiles::SimplificationOptions { + prune_empty_tabs: true, + prune_empty_containers: true, + prune_single_child_tabs: false, + prune_single_child_containers: false, + all_panes_must_have_tabs: true, + join_nested_linear_containers: true, + }, + ); + }) + .hover_text("Simplify this container and its children"), + ); fn equal_shares(shares: &[f32]) -> bool { shares.iter().all(|&share| share == shares[0]) @@ -998,20 +994,20 @@ fn container_top_level_properties( let all_shares_are_equal = equal_shares(&container.col_shares) && equal_shares(&container.row_shares); - //TODO(#6707): use `ButtonContent` when it exists if container.contents.len() > 1 && match container.container_kind { ContainerKind::Tabs => false, ContainerKind::Horizontal | ContainerKind::Vertical | ContainerKind::Grid => true, } - && ui - .list_item() - .interactive(!all_shares_are_equal) - .show_flat(ui, LabelContent::new("Distribute content equally")) - .on_hover_text("Make all children the same size") - .clicked() { - blueprint.make_all_children_same_size(container_id); + ui.list_item_flat_noninteractive( + list_item::ButtonContent::new("Distribute content equally") + .on_click(|| { + blueprint.make_all_children_same_size(container_id); + }) + .enabled(!all_shares_are_equal) + .hover_text("Make all children the same size"), + ); } } diff --git a/crates/re_ui/src/list_item/button_content.rs b/crates/re_ui/src/list_item/button_content.rs new file mode 100644 index 000000000000..47e8a4205491 --- /dev/null +++ b/crates/re_ui/src/list_item/button_content.rs @@ -0,0 +1,74 @@ +use crate::list_item::{ContentContext, ListItemContent}; + +/// Simple [`ListItemContent`] to easily display a button in a [`crate::list_item::ListItem`]-based UI. +pub struct ButtonContent<'a> { + label: egui::WidgetText, + enabled: bool, + on_click: Option>, + hover_text: Option, +} + +impl<'a> ButtonContent<'a> { + #[must_use] + pub fn new(label: impl Into) -> Self { + Self { + label: label.into(), + enabled: true, + on_click: None, + hover_text: None, + } + } + + /// Sets whether the button is enabled. + #[inline] + pub fn enabled(mut self, enabled: bool) -> Self { + self.enabled = enabled; + self + } + + /// Called when the button is clicked. + #[inline] + pub fn on_click(mut self, on_click: impl FnOnce() + 'a) -> Self { + self.on_click = Some(Box::new(on_click)); + self + } + + /// Sets the hover text of the button. + #[inline] + pub fn hover_text(mut self, hover_text: impl Into) -> Self { + self.hover_text = Some(hover_text.into()); + self + } +} + +impl ListItemContent for ButtonContent<'_> { + fn ui(self: Box, ui: &mut egui::Ui, context: &ContentContext<'_>) { + let Self { + label, + enabled, + on_click, + hover_text, + } = *self; + + let mut ui = ui.child_ui( + context.rect, + egui::Layout::left_to_right(egui::Align::Center), + None, + ); + + // Compensate for the button padding such that the button text is aligned with other list + // item contents. + ui.add_space(-ui.spacing().button_padding.x); + + let response = ui.add_enabled(enabled, egui::Button::new(label)); + if let Some(on_click) = on_click { + if response.clicked() { + on_click(); + } + } + + if let Some(hover_text) = hover_text { + response.on_hover_text(hover_text); + } + } +} diff --git a/crates/re_ui/src/list_item/mod.rs b/crates/re_ui/src/list_item/mod.rs index 13829488979b..80178875716f 100644 --- a/crates/re_ui/src/list_item/mod.rs +++ b/crates/re_ui/src/list_item/mod.rs @@ -2,6 +2,7 @@ //! //! TODO(ab): provide some top-level documentation here. +mod button_content; mod item_button; mod label_content; #[allow(clippy::module_inception)] @@ -10,6 +11,7 @@ mod other_contents; mod property_content; mod scope; +pub use button_content::*; pub use item_button::*; pub use label_content::*; pub use list_item::*;