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

Allow resetting view property components from gui for all generically implemented property ui #6417

Merged
merged 10 commits into from
May 28, 2024
2 changes: 1 addition & 1 deletion crates/re_data_ui/src/editors/corner2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub fn edit_corner2d(
) {
let corner = component
// TODO(#5607): what should happen if the promise is still pending?
.instance::<Corner2D>(db.resolver(), instance.get() as _)
.try_instance::<Corner2D>(db.resolver(), instance.get() as _)
.unwrap_or_else(|| default_corner2d(ctx, query, db, entity_path));
let mut edit_corner = corner;

Expand Down
16 changes: 8 additions & 8 deletions crates/re_data_ui/src/editors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ fn edit_color_ui(
) {
let current_color = component
// TODO(#5607): what should happen if the promise is still pending?
.instance::<Color>(db.resolver(), instance.get() as _)
.try_instance::<Color>(db.resolver(), instance.get() as _)
.unwrap_or_else(|| default_color(ctx, query, db, entity_path));

let current_color = current_color.into();
Expand Down Expand Up @@ -75,7 +75,7 @@ fn edit_text_ui(
) {
let current_text = component
// TODO(#5607): what should happen if the promise is still pending?
.instance::<Text>(db.resolver(), instance.get() as _)
.try_instance::<Text>(db.resolver(), instance.get() as _)
.unwrap_or_else(|| default_text(ctx, query, db, entity_path));

let current_text = current_text.to_string();
Expand Down Expand Up @@ -115,7 +115,7 @@ fn edit_name_ui(
) {
let current_text = component
// TODO(#5607): what should happen if the promise is still pending?
.instance::<Name>(db.resolver(), instance.get() as _)
.try_instance::<Name>(db.resolver(), instance.get() as _)
.unwrap_or_else(|| default_name(ctx, query, db, entity_path));

let current_text = current_text.to_string();
Expand Down Expand Up @@ -156,7 +156,7 @@ fn edit_scatter_ui(
) {
let current_scatter = component
// TODO(#5607): what should happen if the promise is still pending?
.instance::<ScalarScattering>(db.resolver(), instance.get() as _)
.try_instance::<ScalarScattering>(db.resolver(), instance.get() as _)
.unwrap_or_else(|| default_scatter(ctx, query, db, entity_path));

let current_scatter = current_scatter.0;
Expand Down Expand Up @@ -205,7 +205,7 @@ fn edit_radius_ui(
) {
let current_radius = component
// TODO(#5607): what should happen if the promise is still pending?
.instance::<Radius>(db.resolver(), instance.get() as _)
.try_instance::<Radius>(db.resolver(), instance.get() as _)
.unwrap_or_else(|| default_radius(ctx, query, db, entity_path));

let current_radius = current_radius.0;
Expand Down Expand Up @@ -252,7 +252,7 @@ fn edit_marker_shape_ui(
) {
let current_marker = component
// TODO(#5607): what should happen if the promise is still pending?
.instance::<MarkerShape>(db.resolver(), instance.get() as _)
.try_instance::<MarkerShape>(db.resolver(), instance.get() as _)
.unwrap_or_else(|| default_marker_shape(ctx, query, db, entity_path));

let mut edit_marker = current_marker;
Expand Down Expand Up @@ -357,7 +357,7 @@ fn edit_stroke_width_ui(
) {
let current_stroke_width = component
// TODO(#5607): what should happen if the promise is still pending?
.instance::<StrokeWidth>(db.resolver(), instance.get() as _)
.try_instance::<StrokeWidth>(db.resolver(), instance.get() as _)
.unwrap_or_else(|| default_stroke_width(ctx, query, db, entity_path));

let current_stroke_width = current_stroke_width.0;
Expand Down Expand Up @@ -404,7 +404,7 @@ fn edit_marker_size_ui(
) {
let current_marker_size = component
// TODO(#5607): what should happen if the promise is still pending?
.instance::<MarkerSize>(db.resolver(), instance.get() as _)
.try_instance::<MarkerSize>(db.resolver(), instance.get() as _)
.unwrap_or_else(|| default_marker_size(ctx, query, db, entity_path));

let current_marker_size = current_marker_size.0;
Expand Down
2 changes: 1 addition & 1 deletion crates/re_data_ui/src/editors/visible.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ pub fn edit_visible(
) {
let visible = component
// TODO(#5607): what should happen if the promise is still pending?
.instance::<Visible>(db.resolver(), instance.get() as _)
.try_instance::<Visible>(db.resolver(), instance.get() as _)
.unwrap_or_else(|| default_visible(ctx, query, db, entity_path));
let mut edit_visible = visible;

Expand Down
39 changes: 39 additions & 0 deletions crates/re_query/src/latest_at/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,45 @@ impl LatestAtComponentResults {
}
}

/// Returns the component data of the specified instance if there's any ready data for this index.
///
/// Returns None both for pending promises and if the index is out of bounds.
/// Logs an error only in case of deserialization failure.
#[inline]
pub fn try_instance<C: Component>(
&self,
resolver: &PromiseResolver,
index: usize,
) -> Option<C> {
let component_name = C::name();
match self.to_dense::<C>(resolver).flatten() {
PromiseResult::Pending => None,

PromiseResult::Ready(data) => {
// TODO(#5259): Figure out if/how we'd like to integrate clamping semantics into the
// selection panel.
//
// For now, we simply always clamp, which is the closest to the legacy behavior that the UI
// expects.
let index = usize::min(index, data.len().saturating_sub(1));

if data.len() > index {
Some(data[index].clone())
} else {
None
}
}

PromiseResult::Error(err) => {
re_log::warn_once!(
"Couldn't deserialize {component_name}: {}",
re_error::format_ref(&*err),
);
None
}
}
}

/// Returns the component data of the specified instance.
///
/// Logs a warning and returns `None` if the component is missing or cannot be deserialized, or
Expand Down
5 changes: 5 additions & 0 deletions crates/re_query/src/latest_at/results.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,11 @@ impl LatestAtResults {
self.components.contains_key(&component_name.into())
}

pub fn contains_non_empty(&self, component_name: impl Into<ComponentName>) -> bool {
self.get(component_name)
.map_or(false, |result| result.num_instances() != 0)
}

/// Returns the [`LatestAtComponentResults`] for the specified [`Component`].
#[inline]
pub fn get(
Expand Down
2 changes: 1 addition & 1 deletion crates/re_selection_panel/src/override_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ pub fn override_ui(
re_ui::list_item::PropertyContent::new(component_name.short_name())
.min_desired_width(150.0)
.action_button(&re_ui::icons::CLOSE, || {
ctx.save_empty_blueprint_component_name(
ctx.save_empty_blueprint_component_by_name(
&overrides.individual_override_path,
*component_name,
);
Expand Down
59 changes: 43 additions & 16 deletions crates/re_space_view/src/view_property_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,29 +43,56 @@ pub fn view_property_ui<A: Archetype>(
let display_name =
field_info.map_or_else(|| component_name.short_name(), |info| info.display_name);

let list_item_response = list_item::ListItem::new(re_ui)
let mut list_item_response = list_item::ListItem::new(re_ui)
.interactive(false)
.show_flat(
ui,
list_item::PropertyContent::new(display_name).value_fn(|_, ui, _| {
ctx.component_ui_registry.edit_ui(
ctx,
ui,
re_viewer_context::UiLayout::List,
blueprint_query,
blueprint_db,
&blueprint_path,
&blueprint_path,
component_results.get_or_empty(*component_name),
component_name,
&0.into(),
);
}),
list_item::PropertyContent::new(display_name)
.action_button(&re_ui::icons::RESET, || {
ctx.reset_blueprint_component_by_name(&blueprint_path, *component_name);
})
.value_fn(|_, ui, _| {
ctx.component_ui_registry.edit_ui(
ctx,
ui,
re_viewer_context::UiLayout::List,
blueprint_query,
blueprint_db,
&blueprint_path,
&blueprint_path,
component_results.get_or_empty(*component_name),
component_name,
&0.into(),
);
}),
);

if let Some(tooltip) = field_info.map(|info| info.documentation) {
list_item_response.on_hover_text(tooltip);
list_item_response = list_item_response.on_hover_text(tooltip);
}

list_item_response.context_menu(|ui| {
if ui.button("Reset to default blueprint.")
.on_hover_text("Resets this property to the value in the default blueprint.\n
If no default blueprint was set or it didn't set any value for this field, this is the same as resetting to empty.")
.clicked() {
ctx.reset_blueprint_component_by_name(&blueprint_path, *component_name);
ui.close_menu();
}
ui.add_enabled_ui(component_results.contains_non_empty(*component_name), |ui| {
if ui.button("Reset to empty.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: we need a better name for this

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agreed, but don't know what to pick :/

.on_hover_text("Resets this property to an unset value, meaning that a heuristically determined value will be used instead.\n
This has the same effect as not setting the value in the blueprint at all.")
.on_disabled_hover_text("The property is already unset.")
.clicked() {
ctx.save_empty_blueprint_component_by_name(&blueprint_path, *component_name);
ui.close_menu();
}
});

// TODO(andreas): The next logical thing here is now to save it to the default blueprint!
// This should be fairly straight forward except that we need to make sure that a default blueprint exists in the first place.
});
}
};

Expand Down
27 changes: 23 additions & 4 deletions crates/re_ui/src/list_item/property_content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ type PropertyValueFn<'a> = dyn FnOnce(&ReUi, &mut egui::Ui, egui::style::WidgetV

struct PropertyActionButton<'a> {
icon: &'static crate::icons::Icon,
enabled: bool,
on_click: Box<dyn FnOnce() + 'a>,
}

Expand Down Expand Up @@ -93,8 +94,22 @@ impl<'a> PropertyContent<'a> {
// TODO(#6191): accept multiple calls for this function for multiple actions.
#[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)
}

/// Right aligned action button.
///
/// 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 action_button_with_enabled(
mut self,
icon: &'static crate::icons::Icon,
enabled: bool,
on_click: impl FnOnce() + 'a,
) -> Self {
// TODO(#6191): support multiple action buttons
Expand All @@ -104,6 +119,7 @@ impl<'a> PropertyContent<'a> {
);
self.action_buttons = Some(PropertyActionButton {
icon,
enabled,
on_click: Box::new(on_click),
});
self
Expand Down Expand Up @@ -349,10 +365,13 @@ impl ListItemContent for PropertyContent<'_> {
action_button_rect,
egui::Layout::right_to_left(egui::Align::Center),
);
let button_response = re_ui.small_icon_button(&mut child_ui, action_button.icon);
if button_response.clicked() {
(action_button.on_click)();
}

child_ui.add_enabled_ui(action_button.enabled, |ui| {
let button_response = re_ui.small_icon_button(ui, action_button.icon);
if button_response.clicked() {
(action_button.on_click)();
}
});
}
}

Expand Down
51 changes: 45 additions & 6 deletions crates/re_viewer_context/src/blueprint_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ impl ViewerContext<'_> {
entity_path: &EntityPath,
components: &dyn ComponentBatch,
) {
let mut data_cell = match DataCell::from_component_batch(components) {
let data_cell = match DataCell::from_component_batch(components) {
Ok(data_cell) => data_cell,
Err(err) => {
re_log::error_once!(
Expand All @@ -70,15 +70,20 @@ impl ViewerContext<'_> {
return;
}
};

self.save_blueprint_data_cell(entity_path, data_cell);
}

/// Helper to save a data cell to the blueprint store.
pub fn save_blueprint_data_cell(&self, entity_path: &EntityPath, mut data_cell: DataCell) {
data_cell.compute_size_bytes();

let num_instances = components.num_instances() as u32;
let timepoint = self.store_context.blueprint_timepoint_for_writes();

re_log::trace!(
"Writing {} components of type {:?} to {:?}",
num_instances,
components.name(),
data_cell.num_instances(),
data_cell.component_name(),
entity_path
);

Expand Down Expand Up @@ -111,15 +116,49 @@ impl ViewerContext<'_> {
self.save_blueprint_component(entity_path, &empty);
}

/// Resets a blueprint component to the value it had in the default blueprint.
pub fn reset_blueprint_component_by_name(
&self,
entity_path: &EntityPath,
component_name: ComponentName,
) {
let default_blueprint = self.store_context.default_blueprint;

if let Some(default_value) = default_blueprint.and_then(|default_blueprint| {
default_blueprint
.latest_at(self.blueprint_query, entity_path, [component_name])
.get(component_name)
.and_then(|default_value| {
default_value.raw(default_blueprint.resolver(), component_name)
})
}) {
self.save_blueprint_data_cell(
entity_path,
DataCell::from_arrow(component_name, default_value),
);
} else {
self.save_empty_blueprint_component_by_name(entity_path, component_name);
}
}

/// Helper to save a component to the blueprint store.
pub fn save_empty_blueprint_component_name(
pub fn save_empty_blueprint_component_by_name(
&self,
entity_path: &EntityPath,
component_name: ComponentName,
) {
let blueprint = &self.store_context.blueprint;

// Don't do anything if the component does not exist (if we don't the datatype lookup may fail).
if !blueprint
.latest_at(self.blueprint_query, entity_path, [component_name])
.contains(component_name)
{
return;
}

let Some(datatype) = blueprint.store().lookup_datatype(&component_name) else {
re_log::error_once!(
re_log::error!(
"Tried to clear a component with unknown type: {}",
component_name
);
Expand Down
4 changes: 2 additions & 2 deletions crates/re_viewport_blueprint/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ pub use space_view::SpaceViewBlueprint;
pub use space_view_contents::SpaceViewContents;
pub use tree_actions::TreeAction;
pub use view_properties::{
edit_blueprint_component, entity_path_for_view_property, get_blueprint_component,
query_view_property, query_view_property_or_default, view_property,
edit_blueprint_component, entity_path_for_view_property, query_view_property,
query_view_property_or_default, view_property,
};
pub use viewport_blueprint::ViewportBlueprint;

Expand Down