Skip to content

Commit

Permalink
Allow resetting view property components from gui for all generically…
Browse files Browse the repository at this point in the history
… implemented property ui (#6417)

### What

* Part of #6237 

See title :)

This allows to reset the value either to the default blueprint or - via
context menu - to empty!



https://github.com/rerun-io/rerun/assets/1220815/2caa3a44-d06f-4828-8d4b-e424d8abe238



Future work:
* write this value to the default blueprint (via context menu as well
for now!)
* some visual language indicating the difference between
from-active-only/from-default/unset

### 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/6417?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/6417?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/6417)
- [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
Wumpf authored May 28, 2024
1 parent 832b12e commit b54e619
Show file tree
Hide file tree
Showing 11 changed files with 169 additions and 63 deletions.
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.")
.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
Loading

0 comments on commit b54e619

Please sign in to comment.