Skip to content

Commit

Permalink
Use UiStack to figure out the default wrap-mode for ListItem labels
Browse files Browse the repository at this point in the history
  • Loading branch information
emilk committed Jun 5, 2024
1 parent 59069e2 commit 805811f
Show file tree
Hide file tree
Showing 4 changed files with 60 additions and 32 deletions.
6 changes: 3 additions & 3 deletions crates/re_time_panel/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -635,7 +635,7 @@ impl TimePanel {
ctx,
&InstancePath::from(tree.path.clone()),
))
.exact_width(true),
.truncate(false),
|_, ui| {
self.show_children(
ctx,
Expand Down Expand Up @@ -780,7 +780,7 @@ impl TimePanel {
} else {
&re_ui::icons::COMPONENT_TEMPORAL
})
.exact_width(true),
.truncate(false),
);

context_menu_ui_for_item(
Expand Down Expand Up @@ -826,7 +826,7 @@ impl TimePanel {
)
},
))
.exact_width(true)
.truncate(false)
.with_icon(if is_static {
&re_ui::icons::COMPONENT_STATIC
} else {
Expand Down
7 changes: 2 additions & 5 deletions crates/re_ui/examples/re_ui_example/right_panel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,13 @@ impl RightPanel {
format!("Some item {i}")
};

// Note: we use `exact_width(true)` here to force the item to allocate
// Note: we use `truncate(false)` here to force the item to allocate
// as much as needed for the label, which in turn will trigger the
// scroll area.
if re_ui
.list_item()
.selected(Some(i) == self.selected_list_item)
.show_flat(
ui,
list_item::LabelContent::new(&label).exact_width(true),
)
.show_flat(ui, list_item::LabelContent::new(&label).truncate(false))
.clicked()
{
self.selected_list_item = Some(i);
Expand Down
4 changes: 2 additions & 2 deletions crates/re_ui/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1327,9 +1327,9 @@ pub trait UiExt {
if node.has_visible_frame()
|| node.is_panel_ui()
|| node.is_root_ui()
|| node.kind == Some(egui::UiKind::TableCell)
|| node.kind() == Some(egui::UiKind::TableCell)
{
return (node.max_rect + node.frame.inner_margin).x_range();
return (node.max_rect + node.frame().inner_margin).x_range();
}
}

Expand Down
75 changes: 53 additions & 22 deletions crates/re_ui/src/list_item/label_content.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,26 @@ pub struct LabelContent<'a> {
buttons_fn: Option<Box<dyn FnOnce(&ReUi, &mut egui::Ui) -> egui::Response + 'a>>,
always_show_buttons: bool,

text_wrap_mode: Option<egui::TextWrapMode>,
min_desired_width: Option<f32>,
exact_width: bool,
}

impl<'a> LabelContent<'a> {
pub fn new(text: impl Into<egui::WidgetText>) -> Self {
Self {
text: text.into(),

subdued: false,
weak: false,
italics: false,

label_style: Default::default(),
icon_fn: None,
buttons_fn: None,
always_show_buttons: false,

text_wrap_mode: None,
min_desired_width: None,
exact_width: false,
}
}

Expand Down Expand Up @@ -79,16 +82,13 @@ impl<'a> LabelContent<'a> {
self
}

/// Allocate the exact width required for the label.
///
/// By default, [`LabelContent`] uses the available width. By setting `exact_width` to true,
/// the exact width required by the label (and the icon if any) is allocated instead. See
/// [`super::DesiredWidth::Exact`].
///
/// Note that if [`Self::min_desired_width`] is set, it is used as a minimum value.
#[inline]
pub fn exact_width(mut self, exact_width: bool) -> Self {
self.exact_width = exact_width;
/// Should we truncate text if it is too long?
pub fn truncate(mut self, truncate: bool) -> Self {
self.text_wrap_mode = Some(if truncate {
egui::TextWrapMode::Truncate
} else {
egui::TextWrapMode::Extend
});
self
}

Expand Down Expand Up @@ -148,10 +148,39 @@ impl<'a> LabelContent<'a> {
self.always_show_buttons = always_show_buttons;
self
}

fn get_text_wrap_mode(&self, ui: &egui::Ui) -> egui::TextWrapMode {
if let Some(text_wrap_mode) = self.text_wrap_mode {
text_wrap_mode
} else {
let mut is_in_side_panel = false;
for frame in ui.stack().iter() {
if let Some(kind) = frame.kind() {
if kind.is_area() {
// Our popups (tooltips etc) aren't resizable, so show all of the text
return egui::TextWrapMode::Extend;
}
if matches!(kind, egui::UiKind::LeftPanel | egui::UiKind::RightPanel) {
is_in_side_panel = true;
}
}
}

if is_in_side_panel {
// Our side-panels are resizable, so truncate the text if we don't fit.
egui::TextWrapMode::Truncate
} else {
// Safe fallback
egui::TextWrapMode::Extend
}
}
}
}

impl ListItemContent for LabelContent<'_> {
fn ui(self: Box<Self>, re_ui: &ReUi, ui: &mut Ui, context: &ContentContext<'_>) {
let text_wrap_mode = self.get_text_wrap_mode(ui);

let Self {
mut text,
subdued,
Expand All @@ -161,8 +190,8 @@ impl ListItemContent for LabelContent<'_> {
icon_fn,
buttons_fn,
always_show_buttons,
text_wrap_mode: _,
min_desired_width: _,
exact_width: _,
} = *self;

let icon_rect = egui::Rect::from_center_size(
Expand Down Expand Up @@ -235,7 +264,7 @@ impl ListItemContent for LabelContent<'_> {

let mut layout_job =
text.into_layout_job(ui.style(), egui::FontSelection::Default, Align::LEFT);
layout_job.wrap = TextWrapping::truncate_at_width(text_rect.width());
layout_job.wrap = TextWrapping::from_wrap_mode_and_width(text_wrap_mode, text_rect.width());

let galley = ui.fonts(|fonts| fonts.layout_job(layout_job));

Expand All @@ -256,6 +285,8 @@ impl ListItemContent for LabelContent<'_> {
}

fn desired_width(&self, _re_ui: &ReUi, ui: &Ui) -> DesiredWidth {
let text_wrap_mode = self.get_text_wrap_mode(ui);

let measured_width = {
//TODO(ab): ideally there wouldn't be as much code duplication with `Self::ui`
let mut text = self.text.clone();
Expand All @@ -279,16 +310,16 @@ impl ListItemContent for LabelContent<'_> {
desired_width.ceil()
};

// TODO(emilk): use the egui `UiStack` to check if we're somewhere resizable,
// and only then turn on text truncation.
// For instance: in a tooltip we must not truncate the text, because
// the user can't resize the tooltip to read the full text.
// But if we're in a panel, the user can resize the panel to read the full text.
let min_desired_width = self.min_desired_width.unwrap_or(measured_width);

if self.exact_width {
if text_wrap_mode == egui::TextWrapMode::Extend {
let min_desired_width = self.min_desired_width.unwrap_or(0.0);
DesiredWidth::Exact(measured_width.at_least(min_desired_width))
} else {
// If the user set an explicit min-width, use it.
// Otherwise, show at least `default_min_width`, unless the text is even short.
let default_min_width = 64.0;
let min_desired_width = self
.min_desired_width
.unwrap_or_else(|| measured_width.min(default_min_width));
DesiredWidth::AtLeast(min_desired_width)
}
}
Expand Down

0 comments on commit 805811f

Please sign in to comment.