Skip to content

Commit

Permalink
Add a text-box component and logtype (#2011)
Browse files Browse the repository at this point in the history
* New UI for displaying a textbox
* Add new TextBox component to simplify view-category creation
* Make module experimental
* Move to experimental designation
* Add experimental APIs to docs
* Python import cleanup
* Default word_wrap to true and set autoshrink to false for the scroll area
  • Loading branch information
jleibs authored and jprochazk committed May 11, 2023
1 parent 9b84a7b commit 9aa2556
Show file tree
Hide file tree
Showing 20 changed files with 322 additions and 6 deletions.
5 changes: 4 additions & 1 deletion crates/re_log_types/src/component_types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ mod rect;
mod scalar;
mod size;
mod tensor;
mod text_box;
mod text_entry;
mod transform;
mod vec;
Expand Down Expand Up @@ -65,13 +66,14 @@ pub use tensor::{
};
#[cfg(feature = "image")]
pub use tensor::{TensorImageLoadError, TensorImageSaveError};
pub use text_box::TextBox;
pub use text_entry::TextEntry;
pub use transform::{Pinhole, Rigid3, Transform};
pub use vec::{Vec2D, Vec3D, Vec4D};

lazy_static! {
//TODO(john): use a run-time type registry
static ref FIELDS: [Field; 25] = [
static ref FIELDS: [Field; 26] = [
<AnnotationContext as Component>::field(),
<Arrow3D as Component>::field(),
<Box3D as Component>::field(),
Expand All @@ -93,6 +95,7 @@ lazy_static! {
<Size3D as Component>::field(),
<Tensor as Component>::field(),
<TextEntry as Component>::field(),
<TextBox as Component>::field(),
<Transform as Component>::field(),
<Vec2D as Component>::field(),
<Vec3D as Component>::field(),
Expand Down
43 changes: 43 additions & 0 deletions crates/re_log_types/src/component_types/text_box.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
use arrow2_convert::{ArrowDeserialize, ArrowField, ArrowSerialize};

use crate::Component;

/// A text element intended to be displayed in a text-box
///
/// ```
/// use re_log_types::component_types::TextBox;
/// use arrow2_convert::field::ArrowField;
/// use arrow2::datatypes::{DataType, Field};
///
/// assert_eq!(
/// TextBox::data_type(),
/// DataType::Struct(vec![
/// Field::new("body", DataType::Utf8, false),
/// ])
/// );
/// ```
// TODO(jleibs): Should this be reconciled with the `TextEntry` component?
#[derive(Clone, Debug, ArrowField, ArrowSerialize, ArrowDeserialize, PartialEq, Eq)]
pub struct TextBox {
// TODO(jleibs): Support options for advanced styling. HTML? Markdown?
pub body: String, // TODO(#1887): avoid allocations
}

impl TextBox {
#[inline]
pub fn new(body: impl Into<String>) -> Self {
Self { body: body.into() }
}

#[inline]
pub fn from_body(body: impl Into<String>) -> Self {
Self { body: body.into() }
}
}

impl Component for TextBox {
#[inline]
fn name() -> crate::ComponentName {
"rerun.text_box".into()
}
}
5 changes: 5 additions & 0 deletions crates/re_ui/src/icons.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ pub const SPACE_VIEW_TEXT: Icon = Icon::new(
"spaceview_text",
include_bytes!("../data/icons/spaceview_text.png"),
);
// TODO(jleibs): Differentiate icon?
pub const SPACE_VIEW_TEXTBOX: Icon = Icon::new(
"spaceview_text",
include_bytes!("../data/icons/spaceview_text.png"),
);
pub const SPACE_VIEW_3D: Icon = Icon::new(
"spaceview_3d",
include_bytes!("../data/icons/spaceview_3d.png"),
Expand Down
6 changes: 4 additions & 2 deletions crates/re_viewer/src/ui/auto_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,10 @@ pub(crate) fn tree_from_space_views(
super::view_spatial::SpatialNavigationMode::ThreeD => None,
}
}
ViewCategory::Tensor | ViewCategory::TimeSeries => Some(1.0), // Not sure if we should do `None` here.
ViewCategory::Text => Some(2.0), // Make text logs wide
ViewCategory::TextBox | ViewCategory::Tensor | ViewCategory::TimeSeries => {
Some(1.0)
} // Not sure if we should do `None` here.
ViewCategory::Text => Some(2.0), // Make text logs wide
ViewCategory::BarChart => None,
};

Expand Down
1 change: 1 addition & 0 deletions crates/re_viewer/src/ui/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ mod view_bar_chart;
mod view_category;
mod view_tensor;
mod view_text;
mod view_text_box;
mod view_time_series;
mod viewport;

Expand Down
27 changes: 26 additions & 1 deletion crates/re_viewer/src/ui/space_view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use crate::{
use super::{
data_blueprint::DataBlueprintTree, space_view_heuristics::default_queried_entities,
view_bar_chart, view_category::ViewCategory, view_spatial, view_tensor, view_text,
view_time_series,
view_text_box, view_time_series,
};

// ----------------------------------------------------------------------------
Expand Down Expand Up @@ -154,6 +154,9 @@ impl SpaceView {
ViewCategory::Text => {
self.view_state.state_text.selection_ui(ctx.re_ui, ui);
}
ViewCategory::TextBox => {
self.view_state.state_textbox.selection_ui(ctx.re_ui, ui);
}
ViewCategory::TimeSeries => {}
ViewCategory::BarChart => {}
ViewCategory::Spatial => {
Expand Down Expand Up @@ -205,6 +208,12 @@ impl SpaceView {
self.view_state.ui_text(ctx, ui, &scene);
}

ViewCategory::TextBox => {
let mut scene = view_text_box::SceneTextBox::default();
scene.load(ctx, &query);
self.view_state.ui_textbox(ctx, ui, &scene);
}

ViewCategory::TimeSeries => {
let mut scene = view_time_series::SceneTimeSeries::default();
scene.load(ctx, &query);
Expand Down Expand Up @@ -303,6 +312,7 @@ pub struct ViewState {
selected_tensor: Option<InstancePath>,

state_text: view_text::ViewTextState,
state_textbox: view_text_box::ViewTextBoxState,
state_time_series: view_time_series::ViewTimeSeriesState,
state_bar_chart: view_bar_chart::BarChartState,
pub state_spatial: view_spatial::ViewSpatialState,
Expand Down Expand Up @@ -393,6 +403,21 @@ impl ViewState {
});
}

fn ui_textbox(
&mut self,
ctx: &mut ViewerContext<'_>,
ui: &mut egui::Ui,
scene: &view_text_box::SceneTextBox,
) {
egui::Frame {
inner_margin: re_ui::ReUi::view_padding().into(),
..egui::Frame::default()
}
.show(ui, |ui| {
view_text_box::view_text_box(ctx, ui, &mut self.state_textbox, scene);
});
}

fn ui_bar_chart(
&mut self,
ctx: &mut ViewerContext<'_>,
Expand Down
10 changes: 9 additions & 1 deletion crates/re_viewer/src/ui/view_category.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ use re_arrow_store::{LatestAtQuery, TimeInt};
use re_data_store::{EntityPath, LogDb, Timeline};
use re_log_types::{
component_types::{
Box3D, LineStrip2D, LineStrip3D, Point2D, Point3D, Rect2D, Scalar, Tensor, TextEntry,
Box3D, LineStrip2D, LineStrip3D, Point2D, Point3D, Rect2D, Scalar, Tensor, TextBox,
TextEntry,
},
Arrow3D, Component, Mesh3D, Transform,
};
Expand All @@ -17,6 +18,9 @@ pub enum ViewCategory {
/// Text log view (text over time)
Text,

/// Single textbox element
TextBox,

/// Time series plot (scalar over time)
TimeSeries,

Expand All @@ -35,6 +39,7 @@ impl ViewCategory {
pub fn icon(self) -> &'static re_ui::Icon {
match self {
ViewCategory::Text => &re_ui::icons::SPACE_VIEW_TEXT,
ViewCategory::TextBox => &re_ui::icons::SPACE_VIEW_TEXTBOX,
ViewCategory::TimeSeries => &re_ui::icons::SPACE_VIEW_SCATTERPLOT,
ViewCategory::BarChart => &re_ui::icons::SPACE_VIEW_HISTOGRAM,
ViewCategory::Spatial => &re_ui::icons::SPACE_VIEW_3D,
Expand All @@ -47,6 +52,7 @@ impl std::fmt::Display for ViewCategory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
ViewCategory::Text => "Text",
ViewCategory::TextBox => "Text Box",
ViewCategory::TimeSeries => "Time Series",
ViewCategory::BarChart => "Bar Chart",
ViewCategory::Spatial => "Spatial",
Expand Down Expand Up @@ -77,6 +83,8 @@ pub fn categorize_entity_path(
{
if component == TextEntry::name() {
set.insert(ViewCategory::Text);
} else if component == TextBox::name() {
set.insert(ViewCategory::TextBox);
} else if component == Scalar::name() {
set.insert(ViewCategory::TimeSeries);
} else if component == Point2D::name()
Expand Down
5 changes: 5 additions & 0 deletions crates/re_viewer/src/ui/view_text_box/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod scene;
pub(crate) use self::scene::SceneTextBox;

mod ui;
pub(crate) use self::ui::{view_text_box, ViewTextBoxState};
53 changes: 53 additions & 0 deletions crates/re_viewer/src/ui/view_text_box/scene.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use re_arrow_store::LatestAtQuery;
use re_log::warn_once;
use re_log_types::component_types::{self};
use re_query::{query_entity_with_primary, QueryError};
use re_viewer_context::{SceneQuery, ViewerContext};

// ---

#[derive(Debug, Clone)]
pub struct TextBoxEntry {
pub body: String,
}

/// A text scene, with everything needed to render it.
#[derive(Default)]
pub struct SceneTextBox {
pub text_entries: Vec<TextBoxEntry>,
}

impl SceneTextBox {
/// Loads all text components into the scene according to the given query.
pub(crate) fn load(&mut self, ctx: &ViewerContext<'_>, query: &SceneQuery<'_>) {
crate::profile_function!();

let store = &ctx.log_db.entity_db.data_store;

for (ent_path, props) in query.iter_entities() {
if !props.visible {
continue;
}

let query = LatestAtQuery::new(query.timeline, query.latest_at);
match query_entity_with_primary::<component_types::TextBox>(
store,
&query,
ent_path,
&[],
)
.and_then(|ent_view| {
for text_entry in ent_view.iter_primary()?.flatten() {
let component_types::TextBox { body } = text_entry;
self.text_entries.push(TextBoxEntry { body });
}
Ok(())
}) {
Ok(_) | Err(QueryError::PrimaryNotFound) => {}
Err(_) => {
warn_once!("text-box query failed for {ent_path:?}");
}
}
}
}
}
69 changes: 69 additions & 0 deletions crates/re_viewer/src/ui/view_text_box/ui.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use egui::Label;
use re_viewer_context::ViewerContext;

use super::SceneTextBox;

// --- Main view ---

#[derive(Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize)]
pub struct ViewTextBoxState {
monospace: bool,
word_wrap: bool,
}

impl Default for ViewTextBoxState {
fn default() -> Self {
Self {
monospace: false,
word_wrap: true,
}
}
}

impl ViewTextBoxState {
pub fn selection_ui(&mut self, re_ui: &re_ui::ReUi, ui: &mut egui::Ui) {
crate::profile_function!();

re_ui.selection_grid(ui, "text_config").show(ui, |ui| {
re_ui.grid_left_hand_label(ui, "Text style");
ui.vertical(|ui| {
ui.radio_value(&mut self.monospace, false, "Proportional");
ui.radio_value(&mut self.monospace, true, "Monospace");
ui.checkbox(&mut self.word_wrap, "Word Wrap");
});
ui.end_row();
});
}
}

pub(crate) fn view_text_box(
_ctx: &mut ViewerContext<'_>,
ui: &mut egui::Ui,
state: &mut ViewTextBoxState,
scene: &SceneTextBox,
) -> egui::Response {
crate::profile_function!();

ui.with_layout(egui::Layout::top_down(egui::Align::LEFT), |ui| {
egui::ScrollArea::both()
.auto_shrink([false, false])
.show(ui, |ui| {
// TODO(jleibs): better handling for multiple results
if scene.text_entries.len() == 1 {
let mut text = egui::RichText::new(&scene.text_entries[0].body);

if state.monospace {
text = text.monospace();
}

ui.add(Label::new(text).wrap(state.word_wrap));
} else {
ui.label(format!(
"Unexpected number of text entries: {}. Limit your query to 1.",
scene.text_entries.len()
));
}
})
})
.response
}
2 changes: 1 addition & 1 deletion crates/re_viewer/src/ui/viewport.rs
Original file line number Diff line number Diff line change
Expand Up @@ -709,7 +709,7 @@ fn help_text_ui(ui: &mut egui::Ui, space_view: &SpaceView) {
ViewCategory::TimeSeries => Some(crate::ui::view_time_series::HELP_TEXT),
ViewCategory::BarChart => Some(crate::ui::view_bar_chart::HELP_TEXT),
ViewCategory::Spatial => Some(space_view.view_state.state_spatial.help_text()),
ViewCategory::Text | ViewCategory::Tensor => None,
ViewCategory::Text | ViewCategory::TextBox | ViewCategory::Tensor => None,
};

if let Some(help_text) = help_text {
Expand Down
5 changes: 5 additions & 0 deletions rerun_py/docs/gen_common_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,11 @@ class Section:
module_summary="script_helpers",
func_list=["script_add_args", "script_setup", "script_teardown"],
),
Section(
title="Experimental",
module_summary="experimental",
func_list=["experimental.log_text_box"],
),
]

# Virtual folder where we will generate the md files
Expand Down
2 changes: 2 additions & 0 deletions rerun_py/rerun_sdk/rerun/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import rerun_bindings as bindings # type: ignore[attr-defined]

from rerun import experimental
from rerun.log import log_cleared
from rerun.log.annotation import AnnotationInfo, ClassDescription, log_annotation_context
from rerun.log.arrow import log_arrow
Expand All @@ -31,6 +32,7 @@
"LoggingHandler",
"bindings",
"ImageFormat",
"experimental",
"log_annotation_context",
"log_arrow",
"log_cleared",
Expand Down
1 change: 1 addition & 0 deletions rerun_py/rerun_sdk/rerun/components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"arrow",
"box",
"color",
"experimental",
"label",
"point",
"quaternion",
Expand Down
Empty file.
Loading

0 comments on commit 9aa2556

Please sign in to comment.