From e6958004e3640e72c4419e4f271cc49e015d7967 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Mon, 14 Aug 2023 14:43:27 +0200 Subject: [PATCH 1/5] Add delete buttons in the Recordings UI --- crates/re_viewer/src/store_hub.rs | 12 ++++--- crates/re_viewer/src/ui/recordings_panel.rs | 35 +++++++++++++++++++-- 2 files changed, 39 insertions(+), 8 deletions(-) diff --git a/crates/re_viewer/src/store_hub.rs b/crates/re_viewer/src/store_hub.rs index 537fcb1d22c4..aa5a7dec67ce 100644 --- a/crates/re_viewer/src/store_hub.rs +++ b/crates/re_viewer/src/store_hub.rs @@ -109,11 +109,13 @@ impl StoreHub { } pub fn remove_recording_id(&mut self, recording_id: &StoreId) { - if let Some(new_selection) = self.store_dbs.find_closest_recording(recording_id) { - self.set_recording_id(new_selection.clone()); - } else { - self.application_id = None; - self.selected_rec_id = None; + if self.selected_rec_id.as_ref() == Some(recording_id) { + if let Some(new_selection) = self.store_dbs.find_closest_recording(recording_id) { + self.set_recording_id(new_selection.clone()); + } else { + self.application_id = None; + self.selected_rec_id = None; + } } self.store_dbs.remove(recording_id); diff --git a/crates/re_viewer/src/ui/recordings_panel.rs b/crates/re_viewer/src/ui/recordings_panel.rs index fecce8d07874..d787c10fd769 100644 --- a/crates/re_viewer/src/ui/recordings_panel.rs +++ b/crates/re_viewer/src/ui/recordings_panel.rs @@ -1,4 +1,4 @@ -use re_viewer_context::{SystemCommand, SystemCommandSender, ViewerContext}; +use re_viewer_context::{CommandSender, SystemCommand, SystemCommandSender, ViewerContext}; use std::collections::BTreeMap; use time::macros::format_description; @@ -59,7 +59,16 @@ fn recording_list_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) { for (app_id, store_dbs) in store_dbs_map { if store_dbs.len() == 1 { let store_db = store_dbs[0]; - if recording_ui(ctx.re_ui, ui, store_db, Some(app_id), active_recording).clicked() { + if recording_ui( + ctx.re_ui, + ui, + store_db, + Some(app_id), + active_recording, + command_sender, + ) + .clicked() + { command_sender .send_system(SystemCommand::SetRecordingId(store_db.store_id().clone())); } @@ -69,7 +78,16 @@ fn recording_list_ui(ctx: &mut ViewerContext<'_>, ui: &mut egui::Ui) { .active(false) .show_collapsing(ui, true, |_, ui| { for store_db in store_dbs { - if recording_ui(ctx.re_ui, ui, store_db, None, active_recording).clicked() { + if recording_ui( + ctx.re_ui, + ui, + store_db, + None, + active_recording, + command_sender, + ) + .clicked() + { command_sender.send_system(SystemCommand::SetRecordingId( store_db.store_id().clone(), )); @@ -89,6 +107,7 @@ fn recording_ui( store_db: &re_data_store::StoreDb, app_id_label: Option<&str>, active_recording: Option<&re_log_types::StoreId>, + command_sender: &CommandSender, ) -> egui::Response { let prefix = if let Some(app_id_label) = app_id_label { format!("{app_id_label} - ") @@ -107,6 +126,16 @@ fn recording_ui( re_ui .list_item(format!("{prefix}{name}")) + .with_buttons(|re_ui, ui| { + let resp = re_ui + .small_icon_button(ui, &re_ui::icons::REMOVE) + .on_hover_text("Close this Recording (unsaved data will be lost)"); + if resp.clicked() { + command_sender + .send_system(SystemCommand::CloseRecordingId(store_db.store_id().clone())); + } + resp + }) .with_icon_fn(|_re_ui, ui, rect, visuals| { let color = if active_recording == Some(store_db.store_id()) { visuals.fg_stroke.color From 0fd6a71b0254a51daad04cddf0d6f88f6f11d29e Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Mon, 14 Aug 2023 15:51:00 +0200 Subject: [PATCH 2/5] Refactored `ListItem::ui()` to compute the *actual* available width for text --- crates/re_ui/src/list_item.rs | 68 +++++++++++++++++------------------ 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/crates/re_ui/src/list_item.rs b/crates/re_ui/src/list_item.rs index ec2980dac36f..f28f629b3446 100644 --- a/crates/re_ui/src/list_item.rs +++ b/crates/re_ui/src/list_item.rs @@ -1,5 +1,5 @@ use crate::{Icon, ReUi}; -use egui::{Align2, NumExt, Response, Shape, Ui}; +use egui::{Align2, Response, Shape, Ui}; struct ListItemResponse { response: Response, @@ -117,6 +117,7 @@ impl<'a> ListItem<'a> { self.ui(ui).response } + /// Draw the item as a collapsing header. pub fn show_collapsing( mut self, ui: &mut Ui, @@ -147,7 +148,6 @@ impl<'a> ListItem<'a> { } fn ui(self, ui: &mut Ui) -> ListItemResponse { - let button_padding = ui.spacing().button_padding; let collapse_extra = if self.collapse_openness.is_some() { ReUi::collapsing_triangle_size().x + ReUi::text_to_icon_padding() } else { @@ -159,27 +159,9 @@ impl<'a> ListItem<'a> { 0.0 }; - let padding_extra = button_padding + button_padding; - let wrap_width = ui.available_width() - padding_extra.x - collapse_extra - icon_extra; - - let text = - self.text - .clone() - .into_galley(ui, Some(false), wrap_width, egui::TextStyle::Button); - - let desired_size = - (padding_extra + egui::vec2(collapse_extra + icon_extra, 0.0) + text.size()) - .at_least(egui::vec2(ui.available_width(), ReUi::list_item_height())); + let desired_size = egui::vec2(ui.available_width(), ReUi::list_item_height()); let (rect, response) = ui.allocate_at_least(desired_size, egui::Sense::click()); - response.widget_info(|| { - egui::WidgetInfo::selected( - egui::WidgetType::SelectableLabel, - self.selected, - text.text(), - ) - }); - let mut collapse_response = None; if ui.is_rect_visible(rect) { @@ -217,30 +199,48 @@ impl<'a> ListItem<'a> { icon_fn(self.re_ui, ui, icon_rect, visuals); } - // Draw text next to the icon. - let mut text_rect = rect; - text_rect.min.x += collapse_extra + icon_extra; - let text_pos = Align2::LEFT_CENTER - .align_size_within_rect(text.size(), text_rect) - .min; - text.paint_with_visuals(ui.painter(), text_pos, &visuals); - // Handle buttons - let button_hovered = + let button_response = if self.active && ui.interact(rect, ui.id(), egui::Sense::hover()).hovered() { if let Some(buttons) = self.buttons_fn { let mut ui = ui.child_ui(rect, egui::Layout::right_to_left(egui::Align::Center)); - buttons(self.re_ui, &mut ui).hovered() + Some(buttons(self.re_ui, &mut ui)) } else { - false + None } } else { - false + None }; + // Draw text next to the icon. + let mut text_rect = rect; + text_rect.min.x += collapse_extra + icon_extra; + if let Some(ref button_response) = button_response { + text_rect.max.x -= button_response.rect.width() + ReUi::text_to_icon_padding(); + } + + let text = + self.text + .into_galley(ui, Some(false), text_rect.width(), egui::TextStyle::Button); + + // this happens here to avoid cloning the text + response.widget_info(|| { + egui::WidgetInfo::selected( + egui::WidgetType::SelectableLabel, + self.selected, + text.text(), + ) + }); + + let text_pos = Align2::LEFT_CENTER + .align_size_within_rect(text.size(), text_rect) + .min; + + text.paint_with_visuals(ui.painter(), text_pos, &visuals); + // Draw background on interaction. - let bg_fill = if button_hovered { + let bg_fill = if button_response.map_or(false, |r| r.hovered()) { Some(visuals.bg_fill) } else if self.selected || response.hovered() From 3c3a23ed7804ec3f3b9b4df3b448b77c3af37bc9 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Wed, 16 Aug 2023 11:26:41 +0200 Subject: [PATCH 3/5] Add text truncation :tada: --- crates/re_ui/src/list_item.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/re_ui/src/list_item.rs b/crates/re_ui/src/list_item.rs index f28f629b3446..2b0be173a950 100644 --- a/crates/re_ui/src/list_item.rs +++ b/crates/re_ui/src/list_item.rs @@ -1,5 +1,5 @@ use crate::{Icon, ReUi}; -use egui::{Align2, Response, Shape, Ui}; +use egui::{Align, Align2, Response, Shape, Ui}; struct ListItemResponse { response: Response, @@ -220,9 +220,14 @@ impl<'a> ListItem<'a> { text_rect.max.x -= button_response.rect.width() + ReUi::text_to_icon_padding(); } - let text = + let mut text_job = self.text - .into_galley(ui, Some(false), text_rect.width(), egui::TextStyle::Button); + .into_text_job(ui.style(), egui::FontSelection::Default, Align::LEFT); + text_job.job.wrap.max_width = text_rect.width(); + text_job.job.wrap.max_rows = 1; + text_job.job.wrap.break_anywhere = true; + + let text = ui.fonts(|f| text_job.into_galley(f)); // this happens here to avoid cloning the text response.widget_info(|| { From 82b8ec86bec1f774da15a05d3551539bd03fceac Mon Sep 17 00:00:00 2001 From: Antoine Beyeler <49431240+abey79@users.noreply.github.com> Date: Wed, 16 Aug 2023 13:30:13 +0200 Subject: [PATCH 4/5] Update crates/re_ui/src/list_item.rs Co-authored-by: Emil Ernerfeldt --- crates/re_ui/src/list_item.rs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/crates/re_ui/src/list_item.rs b/crates/re_ui/src/list_item.rs index 2b0be173a950..aad742e2b057 100644 --- a/crates/re_ui/src/list_item.rs +++ b/crates/re_ui/src/list_item.rs @@ -223,9 +223,7 @@ impl<'a> ListItem<'a> { let mut text_job = self.text .into_text_job(ui.style(), egui::FontSelection::Default, Align::LEFT); - text_job.job.wrap.max_width = text_rect.width(); - text_job.job.wrap.max_rows = 1; - text_job.job.wrap.break_anywhere = true; + text_job.job.wrap = egui::text::TextWrapping::elide_at_width(text_rect.width()); let text = ui.fonts(|f| text_job.into_galley(f)); From b0bec064c6dda02409c49462c0a11571b9412ab6 Mon Sep 17 00:00:00 2001 From: Antoine Beyeler Date: Wed, 16 Aug 2023 13:48:30 +0200 Subject: [PATCH 5/5] Fix TextWrapping --- crates/re_ui/src/list_item.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/re_ui/src/list_item.rs b/crates/re_ui/src/list_item.rs index aad742e2b057..04e94f337b59 100644 --- a/crates/re_ui/src/list_item.rs +++ b/crates/re_ui/src/list_item.rs @@ -1,4 +1,5 @@ use crate::{Icon, ReUi}; +use egui::epaint::text::TextWrapping; use egui::{Align, Align2, Response, Shape, Ui}; struct ListItemResponse { @@ -223,7 +224,7 @@ impl<'a> ListItem<'a> { let mut text_job = self.text .into_text_job(ui.style(), egui::FontSelection::Default, Align::LEFT); - text_job.job.wrap = egui::text::TextWrapping::elide_at_width(text_rect.width()); + text_job.job.wrap = TextWrapping::elide_at_width(text_rect.width()); let text = ui.fonts(|f| text_job.into_galley(f));