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

Add an experimental text-box component and logtype #2011

Merged
merged 16 commits into from
May 10, 2023
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,
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't it make more sense for word wrapping to be on by default?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, that's a fair point. I guess I'm so used to text-editor contexts where I self-wrap that I forgot how bad an experience it is to have non-wrapped lines not end up wrapping.

}

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