Skip to content

Commit

Permalink
Improve tensor view margins (#1091)
Browse files Browse the repository at this point in the history
* Remove margins from the tensor view

* refactor: remove right-ward drift

* Refactor again: make helper function

* Paint tensor labels on top of the tensor (again)

* Add more rounding and margin to the axis labels

* Improve spacing around sliders

* Show tensor axes _unless_ hovered

* Remove deprecated warning

* Clarify the rounding and margins
  • Loading branch information
emilk committed Feb 4, 2023
1 parent b77bd52 commit 3bf2aeb
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 159 deletions.
64 changes: 29 additions & 35 deletions crates/re_viewer/src/ui/space_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,47 +357,41 @@ impl ViewState {
ui: &mut egui::Ui,
scene: &view_tensor::SceneTensor,
) {
egui::Frame {
inner_margin: re_ui::ReUi::view_padding().into(),
..egui::Frame::default()
}
.show(ui, |ui| {
if scene.tensors.is_empty() {
ui.centered_and_justified(|ui| ui.label("(empty)"));
self.selected_tensor = None;
} else {
if let Some(selected_tensor) = &self.selected_tensor {
if !scene.tensors.contains_key(selected_tensor) {
self.selected_tensor = None;
}
}
if self.selected_tensor.is_none() {
self.selected_tensor = Some(scene.tensors.iter().next().unwrap().0.clone());
if scene.tensors.is_empty() {
ui.centered_and_justified(|ui| ui.label("(empty)"));
self.selected_tensor = None;
} else {
if let Some(selected_tensor) = &self.selected_tensor {
if !scene.tensors.contains_key(selected_tensor) {
self.selected_tensor = None;
}
}
if self.selected_tensor.is_none() {
self.selected_tensor = Some(scene.tensors.iter().next().unwrap().0.clone());
}

if scene.tensors.len() > 1 {
// Show radio buttons for the different tensors we have in this view - better than nothing!
ui.horizontal(|ui| {
for instance_path in scene.tensors.keys() {
let is_selected = self.selected_tensor.as_ref() == Some(instance_path);
if ui.radio(is_selected, instance_path.to_string()).clicked() {
self.selected_tensor = Some(instance_path.clone());
}
if scene.tensors.len() > 1 {
// Show radio buttons for the different tensors we have in this view - better than nothing!
ui.horizontal(|ui| {
for instance_path in scene.tensors.keys() {
let is_selected = self.selected_tensor.as_ref() == Some(instance_path);
if ui.radio(is_selected, instance_path.to_string()).clicked() {
self.selected_tensor = Some(instance_path.clone());
}
});
}

if let Some(selected_tensor) = &self.selected_tensor {
if let Some(tensor) = scene.tensors.get(selected_tensor) {
let state_tensor = self
.state_tensors
.entry(selected_tensor.clone())
.or_insert_with(|| view_tensor::ViewTensorState::create(tensor));
view_tensor::view_tensor(ctx, ui, state_tensor, tensor);
}
});
}

if let Some(selected_tensor) = &self.selected_tensor {
if let Some(tensor) = scene.tensors.get(selected_tensor) {
let state_tensor = self
.state_tensors
.entry(selected_tensor.clone())
.or_insert_with(|| view_tensor::ViewTensorState::create(tensor));
view_tensor::view_tensor(ctx, ui, state_tensor, tensor);
}
}
});
}
}

fn ui_text(
Expand Down
281 changes: 157 additions & 124 deletions crates/re_viewer/src/ui/view_tensor/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,24 @@ pub(crate) fn view_tensor(
state.dimension_mapping = DimensionMapping::create(tensor.shape());
}

selectors_ui(ui, state, tensor);
let default_item_spacing = ui.spacing_mut().item_spacing;
ui.spacing_mut().item_spacing.y = 0.0; // No extra spacing between sliders and tensor

if state
.dimension_mapping
.selectors
.iter()
.any(|selector| selector.visible)
{
egui::Frame {
inner_margin: egui::Margin::symmetric(16.0, 8.0),
..Default::default()
}
.show(ui, |ui| {
ui.spacing_mut().item_spacing = default_item_spacing; // keep the default spacing between sliders
selectors_ui(ui, state, tensor);
});
}

tensor_ui(ctx, ui, state, tensor);
}
Expand Down Expand Up @@ -621,7 +638,9 @@ fn slice_ui<T: Copy>(
};

let image = into_image(&slice, color_from_value);
image_ui(ui, view_state, image, dimension_labels);
egui::ScrollArea::both().show(ui, |ui| {
image_ui(ui, view_state, image, dimension_labels);
});
} else {
ui.label(ctx.re_ui.error_text(format!(
"Only 2D slices supported at the moment, but slice ndim {ndims}"
Expand Down Expand Up @@ -669,129 +688,143 @@ fn image_ui(
) {
crate::profile_function!();

egui::ScrollArea::both().show(ui, |ui| {
let font_id = egui::TextStyle::Body.resolve(ui.style());

let margin = egui::vec2(0.0, 12.0); // Add some margin for the arrow overlay.

let (response, mut painter, image_rect) =
view_state.texture_settings.paint_image(ui, margin, image);

let is_anything_being_dragged = ui.memory(|mem| mem.is_anything_being_dragged());
if response.hovered() && !is_anything_being_dragged {
// Show axis names etc:
let [(width_name, invert_width), (height_name, invert_height)] = dimension_labels;
let text_color = ui.visuals().text_color();

painter.set_clip_rect(egui::Rect::EVERYTHING); // Allow painting axis names outside of our bounds!

// We make sure that the label for the X axis is always at Y=0,
// and that the label for the Y axis is always at X=0, no matter what inversions.
//
// For instance, with origin in the top right:
//
// foo ⬅
// ..........
// ..........
// ..........
// .......... ↓
// .......... b
// .......... a
// .......... r

// TODO(emilk): draw actual arrows behind the text instead of the ugly emoji arrows

// Label for X axis:
{
let text_background = painter.add(egui::Shape::Noop);
let text_rect = if invert_width {
// On left, pointing left:
let (pos, align) = if invert_height {
(image_rect.left_bottom(), Align2::LEFT_TOP)
} else {
(image_rect.left_top(), Align2::LEFT_BOTTOM)
};
painter.text(
pos,
align,
format!("{width_name} ⬅"),
font_id.clone(),
text_color,
)
} else {
// On right, pointing right:
let (pos, align) = if invert_height {
(image_rect.right_bottom(), Align2::RIGHT_TOP)
} else {
(image_rect.right_top(), Align2::RIGHT_BOTTOM)
};
painter.text(
pos,
align,
format!("➡ {width_name}"),
font_id.clone(),
text_color,
)
};
painter.set(
text_background,
egui::Shape::rect_filled(text_rect, 2.0, ui.visuals().panel_fill),
);
}
let font_id = egui::TextStyle::Body.resolve(ui.style());

// Label for Y axis:
{
let text_background = painter.add(egui::Shape::Noop);
let text_rect = if invert_height {
// On top, pointing up:
let galley =
painter.layout_no_wrap(format!("➡ {height_name}"), font_id, text_color);
let galley_size = galley.size();
let pos = if invert_width {
image_rect.right_top() - egui::vec2(0.0, -galley_size.x)
} else {
image_rect.left_top() - egui::vec2(galley_size.y, -galley_size.x)
};
painter.add(TextShape {
pos,
galley,
angle: -std::f32::consts::TAU / 4.0,
underline: Default::default(),
override_text_color: None,
});
egui::Rect::from_min_size(
pos - galley_size.x * egui::Vec2::Y,
egui::vec2(galley_size.y, galley_size.x),
)
} else {
// On bottom, pointing down:
let galley =
painter.layout_no_wrap(format!("{height_name} ⬅"), font_id, text_color);
let galley_size = galley.size();
let pos = if invert_width {
image_rect.right_bottom()
} else {
image_rect.left_bottom() - egui::vec2(galley_size.y, 0.0)
};
painter.add(TextShape {
pos,
galley,
angle: -std::f32::consts::TAU / 4.0,
underline: Default::default(),
override_text_color: None,
});
egui::Rect::from_min_size(
pos - galley_size.x * egui::Vec2::Y,
egui::vec2(galley_size.y, galley_size.x),
)
};
painter.set(
text_background,
egui::Shape::rect_filled(text_rect, 2.0, ui.visuals().panel_fill),
);
}
}
});
let margin = egui::vec2(0.0, 0.0);

let (response, painter, image_rect) =
view_state.texture_settings.paint_image(ui, margin, image);

if !response.hovered() {
paint_axis_names(ui, &painter, image_rect, font_id, dimension_labels);
}
}

fn paint_axis_names(
ui: &mut egui::Ui,
painter: &egui::Painter,
rect: egui::Rect,
font_id: egui::FontId,
dimension_labels: [(String, bool); 2],
) {
// Show axis names etc:
let [(width_name, invert_width), (height_name, invert_height)] = dimension_labels;
let text_color = ui.visuals().text_color();

let rounding = re_ui::ReUi::normal_rounding();
let inner_margin = rounding;
let outer_margin = 8.0;

let rect = rect.shrink(outer_margin + inner_margin);

// We make sure that the label for the X axis is always at Y=0,
// and that the label for the Y axis is always at X=0, no matter what inversions.
//
// For instance, with origin in the top right:
//
// foo ⬅
// ..........
// ..........
// ..........
// .......... ↓
// .......... b
// .......... a
// .......... r

// TODO(emilk): draw actual arrows behind the text instead of the ugly emoji arrows

let paint_text_bg = |text_background, text_rect: egui::Rect| {
painter.set(
text_background,
egui::Shape::rect_filled(
text_rect.expand(inner_margin),
rounding,
ui.visuals().panel_fill,
),
);
};

// Label for X axis:
{
let text_background = painter.add(egui::Shape::Noop);
let text_rect = if invert_width {
// On left, pointing left:
let (pos, align) = if invert_height {
(rect.left_bottom(), Align2::LEFT_BOTTOM)
} else {
(rect.left_top(), Align2::LEFT_TOP)
};
painter.text(
pos,
align,
format!("{width_name} ⬅"),
font_id.clone(),
text_color,
)
} else {
// On right, pointing right:
let (pos, align) = if invert_height {
(rect.right_bottom(), Align2::RIGHT_BOTTOM)
} else {
(rect.right_top(), Align2::RIGHT_TOP)
};
painter.text(
pos,
align,
format!("➡ {width_name}"),
font_id.clone(),
text_color,
)
};
paint_text_bg(text_background, text_rect);
}

// Label for Y axis:
{
let text_background = painter.add(egui::Shape::Noop);
let text_rect = if invert_height {
// On top, pointing up:
let galley = painter.layout_no_wrap(format!("➡ {height_name}"), font_id, text_color);
let galley_size = galley.size();
let pos = if invert_width {
rect.right_top() + egui::vec2(-galley_size.y, galley_size.x)
} else {
rect.left_top() + egui::vec2(0.0, galley_size.x)
};
painter.add(TextShape {
pos,
galley,
angle: -std::f32::consts::TAU / 4.0,
underline: Default::default(),
override_text_color: None,
});
egui::Rect::from_min_size(
pos - galley_size.x * egui::Vec2::Y,
egui::vec2(galley_size.y, galley_size.x),
)
} else {
// On bottom, pointing down:
let galley = painter.layout_no_wrap(format!("{height_name} ⬅"), font_id, text_color);
let galley_size = galley.size();
let pos = if invert_width {
rect.right_bottom() - egui::vec2(galley_size.y, 0.0)
} else {
rect.left_bottom()
};
painter.add(TextShape {
pos,
galley,
angle: -std::f32::consts::TAU / 4.0,
underline: Default::default(),
override_text_color: None,
});
egui::Rect::from_min_size(
pos - galley_size.x * egui::Vec2::Y,
egui::vec2(galley_size.y, galley_size.x),
)
};
paint_text_bg(text_background, text_rect);
}
}

fn selectors_ui(ui: &mut egui::Ui, state: &mut ViewTensorState, tensor: &ClassicTensor) {
Expand Down

1 comment on commit 3bf2aeb

@github-actions
Copy link

Choose a reason for hiding this comment

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

Rust Benchmark

Benchmark suite Current: 3bf2aeb Previous: b77bd52 Ratio
datastore/insert/batch/rects/insert 581325 ns/iter (± 5161) 577692 ns/iter (± 4718) 1.01
datastore/latest_at/batch/rects/query 1794 ns/iter (± 5) 1780 ns/iter (± 9) 1.01
datastore/latest_at/missing_components/primary 322 ns/iter (± 2) 304 ns/iter (± 4) 1.06
datastore/latest_at/missing_components/secondaries 386 ns/iter (± 3) 377 ns/iter (± 4) 1.02
datastore/range/batch/rects/query 157340 ns/iter (± 919) 155478 ns/iter (± 1390) 1.01
mono_points_arrow/generate_message_bundles 47267450 ns/iter (± 976710) 46226300 ns/iter (± 1130829) 1.02
mono_points_arrow/generate_messages 123994764 ns/iter (± 1485638) 124995848 ns/iter (± 1294355) 0.99
mono_points_arrow/encode_log_msg 149905578 ns/iter (± 803083) 153122266 ns/iter (± 1938590) 0.98
mono_points_arrow/encode_total 322292104 ns/iter (± 1704744) 325530957 ns/iter (± 2346703) 0.99
mono_points_arrow/decode_log_msg 173562763 ns/iter (± 1195564) 174644642 ns/iter (± 880729) 0.99
mono_points_arrow/decode_message_bundles 63188689 ns/iter (± 753667) 64156979 ns/iter (± 870293) 0.98
mono_points_arrow/decode_total 235082781 ns/iter (± 1774520) 238108450 ns/iter (± 1578861) 0.99
batch_points_arrow/generate_message_bundles 319398 ns/iter (± 1240) 318397 ns/iter (± 3040) 1.00
batch_points_arrow/generate_messages 6073 ns/iter (± 45) 6051 ns/iter (± 43) 1.00
batch_points_arrow/encode_log_msg 350305 ns/iter (± 1690) 352185 ns/iter (± 1171) 0.99
batch_points_arrow/encode_total 701033 ns/iter (± 4096) 704090 ns/iter (± 10024) 1.00
batch_points_arrow/decode_log_msg 344068 ns/iter (± 1575) 347229 ns/iter (± 826) 0.99
batch_points_arrow/decode_message_bundles 2006 ns/iter (± 22) 2043 ns/iter (± 13) 0.98
batch_points_arrow/decode_total 354136 ns/iter (± 2263) 352229 ns/iter (± 1367) 1.01
arrow_mono_points/insert 5953385649 ns/iter (± 22982376) 6000266740 ns/iter (± 31398025) 0.99
arrow_mono_points/query 1697212 ns/iter (± 13312) 1701472 ns/iter (± 12585) 1.00
arrow_batch_points/insert 2661479 ns/iter (± 13050) 2657662 ns/iter (± 14765) 1.00
arrow_batch_points/query 17082 ns/iter (± 38) 17074 ns/iter (± 52) 1.00
tuid/Tuid::random 34 ns/iter (± 0) 34 ns/iter (± 0) 1

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.