diff --git a/Cargo.lock b/Cargo.lock index dfe3b0bcd4f3..5413cf5ac6ae 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1363,7 +1363,7 @@ checksum = "68b0cf012f1230e43cd00ebb729c6bb58707ecfa8ad08b52ef3a4ccd2697fc30" [[package]] name = "ecolor" version = "0.22.0" -source = "git+https://github.com/emilk/egui?rev=2338a854f932658ad0548aa4f00874b3427970c1#2338a854f932658ad0548aa4f00874b3427970c1" +source = "git+https://github.com/emilk/egui?rev=67a3fcae383044def7450b311ddc1f79e36eaae0#67a3fcae383044def7450b311ddc1f79e36eaae0" dependencies = [ "bytemuck", "serde", @@ -1372,7 +1372,7 @@ dependencies = [ [[package]] name = "eframe" version = "0.22.0" -source = "git+https://github.com/emilk/egui?rev=2338a854f932658ad0548aa4f00874b3427970c1#2338a854f932658ad0548aa4f00874b3427970c1" +source = "git+https://github.com/emilk/egui?rev=67a3fcae383044def7450b311ddc1f79e36eaae0#67a3fcae383044def7450b311ddc1f79e36eaae0" dependencies = [ "bytemuck", "cocoa", @@ -1405,7 +1405,7 @@ dependencies = [ [[package]] name = "egui" version = "0.22.0" -source = "git+https://github.com/emilk/egui?rev=2338a854f932658ad0548aa4f00874b3427970c1#2338a854f932658ad0548aa4f00874b3427970c1" +source = "git+https://github.com/emilk/egui?rev=67a3fcae383044def7450b311ddc1f79e36eaae0#67a3fcae383044def7450b311ddc1f79e36eaae0" dependencies = [ "accesskit", "ahash 0.8.3", @@ -1420,7 +1420,7 @@ dependencies = [ [[package]] name = "egui-wgpu" version = "0.22.0" -source = "git+https://github.com/emilk/egui?rev=2338a854f932658ad0548aa4f00874b3427970c1#2338a854f932658ad0548aa4f00874b3427970c1" +source = "git+https://github.com/emilk/egui?rev=67a3fcae383044def7450b311ddc1f79e36eaae0#67a3fcae383044def7450b311ddc1f79e36eaae0" dependencies = [ "bytemuck", "epaint", @@ -1435,7 +1435,7 @@ dependencies = [ [[package]] name = "egui-winit" version = "0.22.0" -source = "git+https://github.com/emilk/egui?rev=2338a854f932658ad0548aa4f00874b3427970c1#2338a854f932658ad0548aa4f00874b3427970c1" +source = "git+https://github.com/emilk/egui?rev=67a3fcae383044def7450b311ddc1f79e36eaae0#67a3fcae383044def7450b311ddc1f79e36eaae0" dependencies = [ "arboard", "egui", @@ -1452,12 +1452,13 @@ dependencies = [ [[package]] name = "egui_extras" version = "0.22.0" -source = "git+https://github.com/emilk/egui?rev=2338a854f932658ad0548aa4f00874b3427970c1#2338a854f932658ad0548aa4f00874b3427970c1" +source = "git+https://github.com/emilk/egui?rev=67a3fcae383044def7450b311ddc1f79e36eaae0#67a3fcae383044def7450b311ddc1f79e36eaae0" dependencies = [ "egui", "ehttp", "image", "log", + "mime_guess", "puffin", "serde", ] @@ -1465,7 +1466,7 @@ dependencies = [ [[package]] name = "egui_glow" version = "0.22.0" -source = "git+https://github.com/emilk/egui?rev=2338a854f932658ad0548aa4f00874b3427970c1#2338a854f932658ad0548aa4f00874b3427970c1" +source = "git+https://github.com/emilk/egui?rev=67a3fcae383044def7450b311ddc1f79e36eaae0#67a3fcae383044def7450b311ddc1f79e36eaae0" dependencies = [ "bytemuck", "egui", @@ -1481,7 +1482,7 @@ dependencies = [ [[package]] name = "egui_plot" version = "0.22.0" -source = "git+https://github.com/emilk/egui?rev=2338a854f932658ad0548aa4f00874b3427970c1#2338a854f932658ad0548aa4f00874b3427970c1" +source = "git+https://github.com/emilk/egui?rev=67a3fcae383044def7450b311ddc1f79e36eaae0#67a3fcae383044def7450b311ddc1f79e36eaae0" dependencies = [ "egui", ] @@ -1523,7 +1524,7 @@ checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" [[package]] name = "emath" version = "0.22.0" -source = "git+https://github.com/emilk/egui?rev=2338a854f932658ad0548aa4f00874b3427970c1#2338a854f932658ad0548aa4f00874b3427970c1" +source = "git+https://github.com/emilk/egui?rev=67a3fcae383044def7450b311ddc1f79e36eaae0#67a3fcae383044def7450b311ddc1f79e36eaae0" dependencies = [ "bytemuck", "serde", @@ -1604,7 +1605,7 @@ dependencies = [ [[package]] name = "epaint" version = "0.22.0" -source = "git+https://github.com/emilk/egui?rev=2338a854f932658ad0548aa4f00874b3427970c1#2338a854f932658ad0548aa4f00874b3427970c1" +source = "git+https://github.com/emilk/egui?rev=67a3fcae383044def7450b311ddc1f79e36eaae0#67a3fcae383044def7450b311ddc1f79e36eaae0" dependencies = [ "ab_glyph", "ahash 0.8.3", @@ -2833,6 +2834,22 @@ dependencies = [ "libmimalloc-sys", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "mime_guess" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +dependencies = [ + "mime", + "unicase", +] + [[package]] name = "minimal" version = "0.9.0-alpha.4" @@ -6075,6 +6092,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "unicase" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" +dependencies = [ + "version_check", +] + [[package]] name = "unicode-bidi" version = "0.3.13" diff --git a/Cargo.toml b/Cargo.toml index e5565cb512b1..04dfcde17711 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -82,17 +82,22 @@ crossbeam = "0.8" ecolor = "0.22.0" eframe = { version = "0.22.0", default-features = false, features = [ "default_fonts", + "puffin", "wayland", "x11", ] } -egui = { version = "0.22.0", features = ["extra_debug_asserts", "log"] } +egui = { version = "0.22.0", features = [ + "extra_debug_asserts", + "log", + "puffin", +] } egui_extras = { version = "0.22.0", features = [ "log", "image", "http", "puffin", ] } -egui_plot = { git = "https://github.com/emilk/egui", rev = "2338a854f932658ad0548aa4f00874b3427970c1" } +egui_plot = { git = "https://github.com/emilk/egui", rev = "67a3fcae383044def7450b311ddc1f79e36eaae0" } egui_tiles = { version = "0.2" } egui-wgpu = "0.22.0" ehttp = { version = "0.3" } @@ -165,14 +170,14 @@ debug = true # ALWAYS document what PR the commit hash is part of, or when it was merged into the upstream trunk. # Temporary patch until next egui release -ecolor = { git = "https://github.com/emilk/egui", rev = "2338a854f932658ad0548aa4f00874b3427970c1" } -eframe = { git = "https://github.com/emilk/egui", rev = "2338a854f932658ad0548aa4f00874b3427970c1" } -egui-wgpu = { git = "https://github.com/emilk/egui", rev = "2338a854f932658ad0548aa4f00874b3427970c1" } -egui-winit = { git = "https://github.com/emilk/egui", rev = "2338a854f932658ad0548aa4f00874b3427970c1" } -egui = { git = "https://github.com/emilk/egui", rev = "2338a854f932658ad0548aa4f00874b3427970c1" } -egui_extras = { git = "https://github.com/emilk/egui", rev = "2338a854f932658ad0548aa4f00874b3427970c1" } -emath = { git = "https://github.com/emilk/egui", rev = "2338a854f932658ad0548aa4f00874b3427970c1" } -epaint = { git = "https://github.com/emilk/egui", rev = "2338a854f932658ad0548aa4f00874b3427970c1" } +ecolor = { git = "https://github.com/emilk/egui", rev = "67a3fcae383044def7450b311ddc1f79e36eaae0" } +eframe = { git = "https://github.com/emilk/egui", rev = "67a3fcae383044def7450b311ddc1f79e36eaae0" } +egui-wgpu = { git = "https://github.com/emilk/egui", rev = "67a3fcae383044def7450b311ddc1f79e36eaae0" } +egui-winit = { git = "https://github.com/emilk/egui", rev = "67a3fcae383044def7450b311ddc1f79e36eaae0" } +egui = { git = "https://github.com/emilk/egui", rev = "67a3fcae383044def7450b311ddc1f79e36eaae0" } +egui_extras = { git = "https://github.com/emilk/egui", rev = "67a3fcae383044def7450b311ddc1f79e36eaae0" } +emath = { git = "https://github.com/emilk/egui", rev = "67a3fcae383044def7450b311ddc1f79e36eaae0" } +epaint = { git = "https://github.com/emilk/egui", rev = "67a3fcae383044def7450b311ddc1f79e36eaae0" } # Temporary patch until next egui_tiles release egui_tiles = { git = "https://github.com/rerun-io/egui_tiles", rev = "c66d6cba7ddb5b236be614d1816be4561260274e" } diff --git a/crates/re_ui/src/command.rs b/crates/re_ui/src/command.rs index 50d1db68747c..41b7cdb1438d 100644 --- a/crates/re_ui/src/command.rs +++ b/crates/re_ui/src/command.rs @@ -272,7 +272,7 @@ impl UICommand { response } - pub fn menu_button(self, egui_ctx: &egui::Context) -> egui::Button { + pub fn menu_button(self, egui_ctx: &egui::Context) -> egui::Button<'static> { let mut button = egui::Button::new(self.text()); if let Some(shortcut) = self.kb_shortcut() { button = button.shortcut_text(egui_ctx.format_shortcut(&shortcut)); diff --git a/crates/re_ui/src/design_tokens.rs b/crates/re_ui/src/design_tokens.rs index 199cfdd919e8..aba85e2a4dc0 100644 --- a/crates/re_ui/src/design_tokens.rs +++ b/crates/re_ui/src/design_tokens.rs @@ -188,6 +188,8 @@ fn apply_design_tokens(ctx: &egui::Context) -> DesignTokens { // don't color hyperlinks #2733 egui_style.visuals.hyperlink_color = default; + egui_style.image_loading_spinners = false; + ctx.set_style(egui_style); DesignTokens { diff --git a/crates/re_ui/src/icons.rs b/crates/re_ui/src/icons.rs index 1072661738b0..351209e7ed17 100644 --- a/crates/re_ui/src/icons.rs +++ b/crates/re_ui/src/icons.rs @@ -1,3 +1,5 @@ +use egui::{Image, ImageSource}; + #[derive(Clone, Copy, Debug)] pub struct Icon { /// Human readable unique id @@ -10,6 +12,10 @@ impl Icon { pub const fn new(id: &'static str, png_bytes: &'static [u8]) -> Self { Self { id, png_bytes } } + + pub fn as_image(&self) -> Image<'static> { + Image::new(ImageSource::Bytes(self.id.into(), self.png_bytes.into())) + } } pub const RERUN_MENU: Icon = diff --git a/crates/re_ui/src/lib.rs b/crates/re_ui/src/lib.rs index e6b423e80d6e..21a731a0e74a 100644 --- a/crates/re_ui/src/lib.rs +++ b/crates/re_ui/src/lib.rs @@ -7,7 +7,6 @@ pub mod egui_helpers; pub mod icons; mod layout_job_builder; pub mod list_item; -mod static_image_cache; pub mod toasts; mod toggle_switch; @@ -16,7 +15,6 @@ pub use command_palette::CommandPalette; pub use design_tokens::DesignTokens; pub use icons::Icon; pub use layout_job_builder::LayoutJobBuilder; -pub use static_image_cache::StaticImageCache; pub use toggle_switch::toggle_switch; // --------------------------------------------------------------------------- @@ -47,12 +45,10 @@ pub struct TopBarStyle { // ---------------------------------------------------------------------------- -use std::sync::Arc; - -use parking_lot::Mutex; - use crate::list_item::ListItem; use egui::emath::{Rangef, Rot2}; +use egui::epaint::util::FloatOrd; +use egui::load::TexturePoll; use egui::{pos2, Align2, Color32, Mesh, NumExt, Rect, Shape, Vec2}; #[derive(Clone)] @@ -61,8 +57,6 @@ pub struct ReUi { /// Colors, styles etc loaded from a design_tokens.json pub design_tokens: DesignTokens, - - pub static_image_cache: Arc>, } impl ReUi { @@ -70,24 +64,26 @@ impl ReUi { pub fn load_and_apply(egui_ctx: &egui::Context) -> Self { egui_extras::loaders::install(egui_ctx); + egui_ctx.include_bytes( + "logo_dark_mode", + include_bytes!("../data/logo_dark_mode.png"), + ); + egui_ctx.include_bytes( + "logo_light_mode", + include_bytes!("../data/logo_light_mode.png"), + ); + Self { egui_ctx: egui_ctx.clone(), design_tokens: DesignTokens::load_and_apply(egui_ctx), - static_image_cache: Arc::new(Mutex::new(StaticImageCache::default())), } } - pub fn rerun_logo(&self) -> Arc { + pub fn rerun_logo(&self) -> &'static str { if self.egui_ctx.style().visuals.dark_mode { - self.static_image_cache.lock().get( - "logo_dark_mode", - include_bytes!("../data/logo_dark_mode.png"), - ) + "logo_dark_mode" } else { - self.static_image_cache.lock().get( - "logo_light_mode", - include_bytes!("../data/logo_light_mode.png"), - ) + "logo_light_mode" } } @@ -269,16 +265,19 @@ impl ReUi { /// Paint a watermark pub fn paint_watermark(&self) { - let logo = self.rerun_logo(); - let screen_rect = self.egui_ctx.screen_rect(); - let size = logo.size_vec2(); - let rect = Align2::RIGHT_BOTTOM - .align_size_within_rect(size, screen_rect) - .translate(-Vec2::splat(16.0)); - let mut mesh = Mesh::with_texture(logo.texture_id(&self.egui_ctx)); - let uv = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)); - mesh.add_rect_with_uv(rect, uv, Color32::WHITE); - self.egui_ctx.debug_painter().add(Shape::mesh(mesh)); + if let Ok(egui::load::TexturePoll::Ready { texture }) = self.egui_ctx.try_load_texture( + self.rerun_logo(), + egui::TextureOptions::default(), + egui::SizeHint::Scale(1.0.ord()), + ) { + let rect = Align2::RIGHT_BOTTOM + .align_size_within_rect(texture.size, self.egui_ctx.screen_rect()) + .translate(-Vec2::splat(16.0)); + let mut mesh = Mesh::with_texture(texture.id); + let uv = Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)); + mesh.add_rect_with_uv(rect, uv, Color32::WHITE); + self.egui_ctx.debug_painter().add(Shape::mesh(mesh)); + } } pub fn top_bar_style( @@ -332,19 +331,16 @@ impl ReUi { TopBarStyle { height, indent } } - pub fn icon_image(&self, icon: &Icon) -> Arc { - self.static_image_cache.lock().get(icon.id, icon.png_bytes) - } - + #[allow(clippy::unused_self)] pub fn small_icon_button(&self, ui: &mut egui::Ui, icon: &Icon) -> egui::Response { - let size_points = Self::small_icon_size(); - let image = self.icon_image(icon); - let texture_id = image.texture_id(ui.ctx()); // TODO(emilk): change color and size on hover - let tint = ui.visuals().widgets.inactive.fg_stroke.color; - ui.add(egui::ImageButton::new(texture_id, size_points).tint(tint)) + ui.add( + egui::ImageButton::new(icon.as_image().fit_to_exact_size(Self::small_icon_size())) + .tint(ui.visuals().widgets.inactive.fg_stroke.color), + ) } + #[allow(clippy::unused_self)] pub fn medium_icon_toggle_button( &self, ui: &mut egui::Ui, @@ -353,14 +349,13 @@ impl ReUi { ) -> egui::Response { let size_points = egui::Vec2::splat(16.0); // TODO(emilk): get from design tokens - let image = self.icon_image(icon); - let texture_id = image.texture_id(ui.ctx()); let tint = if *selected { ui.visuals().widgets.inactive.fg_stroke.color } else { egui::Color32::from_gray(100) // TODO(emilk): get from design tokens }; - let mut response = ui.add(egui::ImageButton::new(texture_id, size_points).tint(tint)); + let mut response = ui + .add(egui::ImageButton::new(icon.as_image().fit_to_exact_size(size_points)).tint(tint)); if response.clicked() { *selected = !*selected; response.mark_changed(); @@ -368,6 +363,7 @@ impl ReUi { response } + #[allow(clippy::unused_self)] fn large_button_impl( &self, ui: &mut egui::Ui, @@ -387,9 +383,6 @@ impl ReUi { visuals.widgets.open.expansion = 0.0; } - let image = self.icon_image(icon); - let texture_id = image.texture_id(ui.ctx()); - let button_size = Vec2::splat(28.0); let icon_size = Vec2::splat(12.0); // centered inside the button let rounding = 6.0; @@ -397,6 +390,12 @@ impl ReUi { let (rect, response) = ui.allocate_exact_size(button_size, egui::Sense::click()); response.widget_info(|| egui::WidgetInfo::new(egui::WidgetType::ImageButton)); + let Ok(TexturePoll::Ready { texture }) = + icon.as_image().fit_to_exact_size(icon_size).load(ui) + else { + return response; + }; + if ui.is_rect_visible(rect) { let visuals = ui.style().interact(&response); let bg_fill = bg_fill.unwrap_or(visuals.bg_fill); @@ -408,7 +407,7 @@ impl ReUi { ui.painter() .rect_filled(rect.expand(visuals.expansion), rounding, bg_fill); - let mut mesh = egui::Mesh::with_texture(texture_id); + let mut mesh = egui::Mesh::with_texture(texture.id); let uv = egui::Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)); mesh.add_rect_with_uv(image_rect, uv, tint); ui.painter().add(egui::Shape::mesh(mesh)); @@ -759,6 +758,7 @@ impl ReUi { ListItem::new(self, text) } + #[allow(clippy::unused_self)] pub fn selectable_label_with_icon( &self, ui: &mut egui::Ui, @@ -802,23 +802,26 @@ impl ReUi { } // Draw icon - let image = self.icon_image(icon); - let texture_id = image.texture_id(ui.ctx()); - // TODO(emilk/andreas): change color and size on hover - let tint = ui.visuals().widgets.inactive.fg_stroke.color; + let image_size = Self::small_icon_size(); let image_rect = egui::Rect::from_min_size( ui.painter().round_pos_to_pixels(egui::pos2( rect.min.x.ceil(), (rect.center().y - 0.5 * ReUi::small_icon_size().y).ceil(), )), - Self::small_icon_size(), - ); - ui.painter().image( - texture_id, - image_rect, - egui::Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), - tint, + image_size, ); + if let Ok(TexturePoll::Ready { texture }) = + icon.as_image().fit_to_exact_size(image_size).load(ui) + { + // TODO(emilk/andreas): change color and size on hover + let tint = ui.visuals().widgets.inactive.fg_stroke.color; + ui.painter().image( + texture.id, + image_rect, + egui::Rect::from_min_max(pos2(0.0, 0.0), pos2(1.0, 1.0)), + tint, + ); + } // Draw text next to the icon. let mut text_rect = rect; diff --git a/crates/re_ui/src/list_item.rs b/crates/re_ui/src/list_item.rs index 9ac52e7c7076..2c320c647634 100644 --- a/crates/re_ui/src/list_item.rs +++ b/crates/re_ui/src/list_item.rs @@ -1,5 +1,6 @@ use crate::{Icon, ReUi}; use egui::epaint::text::TextWrapping; +use egui::load::TexturePoll; use egui::{Align, Align2, Response, Shape, Ui}; use std::default::Default; @@ -176,17 +177,19 @@ impl<'a> ListItem<'a> { /// Provide an [`Icon`] to be displayed on the left of the item. pub fn with_icon(self, icon: &'a Icon) -> Self { - self.with_icon_fn(|re_ui, ui, rect, visuals| { - let image = re_ui.icon_image(icon); - let texture_id = image.texture_id(ui.ctx()); - let tint = visuals.fg_stroke.color; - - ui.painter().image( - texture_id, - rect, - egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)), - tint, - ); + self.with_icon_fn(|_, ui, rect, visuals| { + if let Ok(TexturePoll::Ready { texture }) = + icon.as_image().fit_to_exact_size(rect.size()).load(ui) + { + let tint = visuals.fg_stroke.color; + + ui.painter().image( + texture.id, + rect, + egui::Rect::from_min_max(egui::pos2(0.0, 0.0), egui::pos2(1.0, 1.0)), + tint, + ); + } }) } diff --git a/crates/re_ui/src/static_image_cache.rs b/crates/re_ui/src/static_image_cache.rs deleted file mode 100644 index 9d3712a6b7c2..000000000000 --- a/crates/re_ui/src/static_image_cache.rs +++ /dev/null @@ -1,32 +0,0 @@ -use egui_extras::RetainedImage; -use std::sync::Arc; - -#[derive(Default)] -pub struct StaticImageCache { - images: std::collections::HashMap<&'static str, Arc>, -} - -impl StaticImageCache { - pub fn get(&mut self, id: &'static str, image_bytes: &'static [u8]) -> Arc { - self.images - .entry(id) - .or_insert_with(|| { - let color_image = load_image_bytes(image_bytes) - .unwrap_or_else(|err| panic!("Failed to load image {id:?}: {err}")); - let retained_img = RetainedImage::from_color_image(id, color_image); - Arc::new(retained_img) - }) - .clone() - } -} - -fn load_image_bytes(image_bytes: &[u8]) -> Result { - let image = image::load_from_memory(image_bytes).map_err(|err| err.to_string())?; - let image = image.into_rgba8(); - let size = [image.width() as _, image.height() as _]; - let pixels = image.as_flat_samples(); - Ok(egui::ColorImage::from_rgba_unmultiplied( - size, - pixels.as_slice(), - )) -} diff --git a/crates/re_viewer/src/app_state.rs b/crates/re_viewer/src/app_state.rs index e3a909aa4e2f..e3485efd9513 100644 --- a/crates/re_viewer/src/app_state.rs +++ b/crates/re_viewer/src/app_state.rs @@ -193,7 +193,7 @@ impl AppState { .frame(viewport_frame) .show_inside(ui, |ui| { if show_welcome { - welcome_screen.ui(re_ui, ui, rx, command_sender); + welcome_screen.ui(ui, rx, command_sender); } else { viewport.viewport_ui(ui, &mut ctx); } diff --git a/crates/re_viewer/src/ui/rerun_menu.rs b/crates/re_viewer/src/ui/rerun_menu.rs index 0257ae3f1536..43801e9acb5d 100644 --- a/crates/re_viewer/src/ui/rerun_menu.rs +++ b/crates/re_viewer/src/ui/rerun_menu.rs @@ -18,11 +18,10 @@ impl App { let desired_icon_height = ui.max_rect().height() - 4.0; // TODO(emilk): figure out this fudge let desired_icon_height = desired_icon_height.at_most(28.0); // figma size 2023-02-03 - let icon_image = self.re_ui().icon_image(&re_ui::icons::RERUN_MENU); - let image_size = icon_image.size_vec2() * (desired_icon_height / icon_image.size_vec2().y); - let texture_id = icon_image.texture_id(ui.ctx()); - - ui.menu_image_button(texture_id, image_size, |ui| { + let image = re_ui::icons::RERUN_MENU + .as_image() + .max_height(desired_icon_height); + ui.menu_image_button(image, |ui| { ui.set_min_width(220.0); let spacing = 12.0; @@ -84,15 +83,16 @@ impl App { // dont use `hyperlink_to` for styling reasons const HELP_URL: &str = "https://www.rerun.io/docs/getting-started/viewer-walkthrough"; - let texture_id = self - .re_ui() - .icon_image(&re_ui::icons::EXTERNAL_LINK) - .texture_id(ui.ctx()); - if egui::Button::image_and_text(texture_id, ReUi::small_icon_size(), "Help") - .ui(ui) - .on_hover_cursor(egui::CursorIcon::PointingHand) - .on_hover_text(HELP_URL) - .clicked() + if egui::Button::image_and_text( + re_ui::icons::EXTERNAL_LINK + .as_image() + .fit_to_exact_size(ReUi::small_icon_size()), + "Help", + ) + .ui(ui) + .on_hover_cursor(egui::CursorIcon::PointingHand) + .on_hover_text(HELP_URL) + .clicked() { ui.ctx().output_mut(|o| { o.open_url = Some(egui::output::OpenUrl { diff --git a/crates/re_viewer/src/ui/top_panel.rs b/crates/re_viewer/src/ui/top_panel.rs index ddd887ff9332..bdf7e6a2f279 100644 --- a/crates/re_viewer/src/ui/top_panel.rs +++ b/crates/re_viewer/src/ui/top_panel.rs @@ -74,7 +74,7 @@ fn top_bar_ui( app.rerun_menu_button_ui(store_context, ui, frame); ui.add_space(12.0); - website_link_ui(ui, app); + website_link_ui(ui); if app.app_options().show_metrics { ui.separator(); @@ -158,17 +158,17 @@ fn top_bar_ui( } /// Shows clickable website link as an image (text doesn't look as nice) -fn website_link_ui(ui: &mut egui::Ui, app: &mut App) { - let icon_image = app.re_ui().icon_image(&re_ui::icons::RERUN_IO_TEXT); - +fn website_link_ui(ui: &mut egui::Ui) { let desired_height = ui.max_rect().height(); let desired_height = desired_height.at_most(28.0); // figma size 2023-02-03 - let image_size = icon_image.size_vec2() * (desired_height / icon_image.size_vec2().y); - let texture_id = icon_image.texture_id(ui.ctx()); + let image = re_ui::icons::RERUN_IO_TEXT + .as_image() + .max_height(desired_height); + let url = "https://rerun.io/"; let response = ui - .add(egui::ImageButton::new(texture_id, image_size)) + .add(egui::ImageButton::new(image)) .on_hover_cursor(egui::CursorIcon::PointingHand) .on_hover_text(url); if response.clicked() { diff --git a/crates/re_viewer/src/ui/welcome_screen/example_page.rs b/crates/re_viewer/src/ui/welcome_screen/example_page.rs index e8dde8ad9922..a6876d883783 100644 --- a/crates/re_viewer/src/ui/welcome_screen/example_page.rs +++ b/crates/re_viewer/src/ui/welcome_screen/example_page.rs @@ -1,10 +1,11 @@ -use super::WelcomeScreenResponse; -use egui::load::TexturePoll; -use egui::{NumExt, TextureOptions, Ui}; +use egui::{NumExt as _, Ui}; + use re_log_types::LogMsg; use re_smart_channel::ReceiveSet; use re_viewer_context::SystemCommandSender; +use super::WelcomeScreenResponse; + #[derive(Debug, serde::Deserialize)] struct ExampleThumbnail { url: String, @@ -257,33 +258,11 @@ fn example_thumbnail( ) { let rounding = egui::Rounding::same(THUMBNAIL_RADIUS); - let resp = match ui.ctx().try_load_texture( - example.thumbnail.url.as_str(), - TextureOptions::LINEAR, - egui::SizeHint::from(size), - ) { - Ok(TexturePoll::Ready { texture }) => { - ui.add(egui::Image::new(texture.id, size).rounding(rounding)) - } - Ok(TexturePoll::Pending { .. }) => { - ui.allocate_ui_at_rect(egui::Rect::from_min_size(ui.cursor().min, size), |ui| { - // add some space before the spinner - ui.add_space(4.0); - ui.horizontal(|ui| { - ui.add_space(4.0); - ui.spinner() - .on_hover_text(format!("Loading thumbnail for {} example…", example.title)); - }); - - // Eat all available space so the spinner container has the same size as the - // thumbnail itself. - ui.allocate_exact_size(ui.max_rect().max - ui.cursor().min, egui::Sense::hover()); - }) - .response - } - - Err(err) => ui.colored_label(ui.visuals().error_fg_color, err.to_string()), - }; + let response = ui.add( + egui::Image::new(&example.thumbnail.url) + .rounding(rounding) + .fit_to_exact_size(size), + ); // TODO(ab): use design tokens let border_color = if hovered { @@ -293,19 +272,19 @@ fn example_thumbnail( }; ui.painter() - .rect_stroke(resp.rect, rounding, (1.0, border_color)); + .rect_stroke(response.rect, rounding, (1.0, border_color)); - // spinner overlay + // Show spinner overlay while loading the example: if is_loading(rx, example) { ui.painter().rect_filled( - resp.rect, + response.rect, rounding, egui::Color32::BLACK.gamma_multiply(0.75), ); - let spinner_size = resp.rect.size().min_elem().at_most(72.0); + let spinner_size = response.rect.size().min_elem().at_most(72.0); let spinner_rect = - egui::Rect::from_center_size(resp.rect.center(), egui::Vec2::splat(spinner_size)); + egui::Rect::from_center_size(response.rect.center(), egui::Vec2::splat(spinner_size)); ui.allocate_ui_at_rect(spinner_rect, |ui| { ui.add(egui::Spinner::new().size(spinner_size)); }); diff --git a/crates/re_viewer/src/ui/welcome_screen/mod.rs b/crates/re_viewer/src/ui/welcome_screen/mod.rs index 6b44c9149788..1bc44f2b0a1e 100644 --- a/crates/re_viewer/src/ui/welcome_screen/mod.rs +++ b/crates/re_viewer/src/ui/welcome_screen/mod.rs @@ -41,7 +41,6 @@ impl WelcomeScreen { /// Welcome screen shown in place of the viewport when no data is loaded. pub fn ui( &mut self, - re_ui: &re_ui::ReUi, ui: &mut egui::Ui, rx: &ReceiveSet, command_sender: &re_viewer_context::CommandSender, @@ -76,7 +75,7 @@ impl WelcomeScreen { .id_source(("welcome_screen_page", &self.current_page)) .auto_shrink([false, false]) .show(ui, |ui| match self.current_page { - WelcomeScreenPage::Welcome => welcome_page_ui(re_ui, ui, rx, command_sender), + WelcomeScreenPage::Welcome => welcome_page_ui(ui, rx, command_sender), WelcomeScreenPage::Examples => self.example_page.ui(ui, rx, command_sender), }) .inner; @@ -134,22 +133,19 @@ fn set_large_button_style(ui: &mut egui::Ui) { visuals.widgets.inactive.weak_bg_fill = visuals.widgets.inactive.bg_fill; } -fn url_large_text_button( - re_ui: &re_ui::ReUi, - ui: &mut egui::Ui, - text: impl Into, - url: &str, -) { +fn url_large_text_button(ui: &mut egui::Ui, text: impl Into, url: &str) { ui.scope(|ui| { set_large_button_style(ui); - let image = re_ui.icon_image(&re_ui::icons::EXTERNAL_LINK); - let texture_id = image.texture_id(ui.ctx()); - - if egui::Button::image_and_text(texture_id, ReUi::small_icon_size(), text) - .ui(ui) - .on_hover_cursor(egui::CursorIcon::PointingHand) - .clicked() + if egui::Button::image_and_text( + re_ui::icons::EXTERNAL_LINK + .as_image() + .fit_to_exact_size(ReUi::small_icon_size()), + text, + ) + .ui(ui) + .on_hover_cursor(egui::CursorIcon::PointingHand) + .clicked() { ui.ctx().output_mut(|o| { o.open_url = Some(egui::output::OpenUrl { diff --git a/crates/re_viewer/src/ui/welcome_screen/welcome_page.rs b/crates/re_viewer/src/ui/welcome_screen/welcome_page.rs index e68dae2b8d67..05768af56208 100644 --- a/crates/re_viewer/src/ui/welcome_screen/welcome_page.rs +++ b/crates/re_viewer/src/ui/welcome_screen/welcome_page.rs @@ -2,7 +2,7 @@ use super::{large_text_button, status_strings, url_large_text_button, WelcomeScr use egui::{NumExt, Ui}; use re_log_types::LogMsg; use re_smart_channel::ReceiveSet; -use re_ui::{ReUi, UICommandSender}; +use re_ui::UICommandSender; //const CPP_QUICKSTART: &str = "https://www.rerun.io/docs/getting-started/cpp"; const PYTHON_QUICKSTART: &str = "https://www.rerun.io/docs/getting-started/python"; @@ -13,7 +13,6 @@ const SPACE_VIEWS_HELP: &str = "https://www.rerun.io/docs/getting-started/viewer /// /// Return `true` if the user wants to switch to the example page. pub(super) fn welcome_page_ui( - re_ui: &re_ui::ReUi, ui: &mut egui::Ui, rx: &ReceiveSet, command_sender: &re_viewer_context::CommandSender, @@ -26,7 +25,7 @@ pub(super) fn welcome_page_ui( } .show(ui, |ui| { ui.vertical(|ui| { - let show_example = onboarding_content_ui(re_ui, ui, command_sender); + let show_example = onboarding_content_ui(ui, command_sender); for status_strings in status_strings(rx) { if status_strings.long_term { @@ -56,7 +55,6 @@ struct WelcomePagePanel<'a> { } fn onboarding_content_ui( - re_ui: &ReUi, ui: &mut Ui, command_sender: &re_viewer_context::CommandSender, ) -> WelcomeScreenResponse { @@ -70,9 +68,9 @@ fn onboarding_content_ui( image: &re_ui::icons::WELCOME_SCREEN_LIVE_DATA, add_buttons: Box::new(|ui: &mut egui::Ui| { // TODO(ab): activate when C++ is ready! - // url_large_text_button(re_ui, ui, "C++", CPP_QUICKSTART); - url_large_text_button(re_ui, ui, "Python", PYTHON_QUICKSTART); - url_large_text_button(re_ui, ui, "Rust", RUST_QUICKSTART); + // url_large_text_button(ui, "C++", CPP_QUICKSTART); + url_large_text_button(ui, "Python", PYTHON_QUICKSTART); + url_large_text_button(ui, "Rust", RUST_QUICKSTART); false }), @@ -97,7 +95,7 @@ fn onboarding_content_ui( interactively in the viewer or (coming soon) directly from code in the SDK.", image: &re_ui::icons::WELCOME_SCREEN_CONFIGURE, add_buttons: Box::new(|ui: &mut egui::Ui| { - url_large_text_button(re_ui, ui, "Learn about Views", SPACE_VIEWS_HELP); + url_large_text_button(ui, "Learn about Views", SPACE_VIEWS_HELP); false }), @@ -174,7 +172,7 @@ fn onboarding_content_ui( for panels in panels.chunks(column_count) { if column_count == panel_count { for panel in panels { - image_banner(re_ui, ui, panel.image, column_width); + image_banner(ui, panel.image, column_width); } } else { for _ in panels { @@ -225,12 +223,10 @@ fn onboarding_content_ui( .inner } -fn image_banner(re_ui: &re_ui::ReUi, ui: &mut egui::Ui, image: &re_ui::Icon, column_width: f32) { - let image = re_ui.icon_image(image); - let texture_id = image.texture_id(ui.ctx()); - let height = column_width * image.size()[1] as f32 / image.size()[0] as f32; +fn image_banner(ui: &mut egui::Ui, icon: &re_ui::Icon, column_width: f32) { ui.add( - egui::Image::new(texture_id, egui::vec2(column_width, height)) + icon.as_image() + .fit_to_exact_size(egui::Vec2::new(column_width, f32::INFINITY)) .rounding(egui::Rounding::same(8.)), ); } diff --git a/crates/re_viewport/src/viewport.rs b/crates/re_viewport/src/viewport.rs index dfc347937caa..f606405b0f06 100644 --- a/crates/re_viewport/src/viewport.rs +++ b/crates/re_viewport/src/viewport.rs @@ -5,10 +5,11 @@ use std::collections::BTreeMap; use ahash::HashMap; +use egui::load::TexturePoll; use egui_tiles::Behavior; use nohash_hasher::IntMap; -use re_ui::ReUi; +use re_ui::{Icon, ReUi}; use re_viewer_context::{ CommandSender, Item, SpaceViewClassName, SpaceViewClassRegistry, SpaceViewHighlights, SpaceViewId, SpaceViewState, ViewerContext, @@ -463,10 +464,10 @@ fn space_view_ui( } struct TabWidget { - texture_id: egui::TextureId, galley: egui::widget_text::WidgetTextGalley, rect: egui::Rect, galley_rect: egui::Rect, + icon: &'static Icon, icon_size: egui::Vec2, icon_rect: egui::Rect, bg_color: egui::Color32, @@ -494,14 +495,11 @@ impl TabWidget { let space_view_id = space_view.id; // tab icon + let icon_size = ReUi::small_icon_size(); + let icon_width_plus_padding = icon_size.x + ReUi::text_to_icon_padding(); let icon = space_view .class(tab_viewer.ctx.space_view_class_registry) .icon(); - let image = tab_viewer.ctx.re_ui.icon_image(icon); - let texture_id = image.texture_id(ui.ctx()); - - let icon_size = ReUi::small_icon_size(); - let icon_width_plus_padding = icon_size.x + ReUi::text_to_icon_padding(); // tab title let text = tab_viewer.tab_title_for_tile(tiles, tile_id); @@ -537,10 +535,10 @@ impl TabWidget { .gamma_multiply(gamma); Some(Self { - texture_id, galley, rect, galley_rect, + icon, icon_size, icon_rect, bg_color, @@ -552,9 +550,14 @@ impl TabWidget { ui.painter() .rect(self.rect, 0.0, self.bg_color, egui::Stroke::NONE); - let icon_image = - egui::widgets::Image::new(self.texture_id, self.icon_size).tint(self.text_color); - icon_image.paint_at(ui, self.icon_rect); + let icon_image = self + .icon + .as_image() + .fit_to_exact_size(self.icon_size) + .tint(self.text_color); + if let Ok(TexturePoll::Ready { texture }) = icon_image.load(ui) { + icon_image.paint_at(ui, self.icon_rect, &texture); + } ui.painter().galley_with_color( egui::Align2::CENTER_CENTER diff --git a/crates/re_viewport/src/viewport_blueprint_ui.rs b/crates/re_viewport/src/viewport_blueprint_ui.rs index ac7371f00275..96138ad44b50 100644 --- a/crates/re_viewport/src/viewport_blueprint_ui.rs +++ b/crates/re_viewport/src/viewport_blueprint_ui.rs @@ -357,37 +357,40 @@ impl ViewportBlueprint<'_> { ) { #![allow(clippy::collapsible_if)] - let icon_image = ctx.re_ui.icon_image(&re_ui::icons::ADD); - let texture_id = icon_image.texture_id(ui.ctx()); - ui.menu_image_button(texture_id, re_ui::ReUi::small_icon_size(), |ui| { - ui.style_mut().wrap = Some(false); - - let entities_per_system_per_class = identify_entities_per_system_per_class(ctx); - for space_view in - all_possible_space_views(ctx, spaces_info, &entities_per_system_per_class) - .into_iter() - .sorted_by_key(|space_view| space_view.space_origin.to_string()) - { - if ctx - .re_ui - .selectable_label_with_icon( - ui, - space_view.class(ctx.space_view_class_registry).icon(), - if space_view.space_origin.is_root() { - space_view.display_name.clone() - } else { - space_view.space_origin.to_string() - }, - false, - ) - .clicked() + ui.menu_image_button( + re_ui::icons::ADD + .as_image() + .fit_to_exact_size(re_ui::ReUi::small_icon_size()), + |ui| { + ui.style_mut().wrap = Some(false); + + let entities_per_system_per_class = identify_entities_per_system_per_class(ctx); + for space_view in + all_possible_space_views(ctx, spaces_info, &entities_per_system_per_class) + .into_iter() + .sorted_by_key(|space_view| space_view.space_origin.to_string()) { - ui.close_menu(); - let new_space_view_id = self.add_space_view(space_view); - ctx.set_single_selection(&Item::SpaceView(new_space_view_id)); + if ctx + .re_ui + .selectable_label_with_icon( + ui, + space_view.class(ctx.space_view_class_registry).icon(), + if space_view.space_origin.is_root() { + space_view.display_name.clone() + } else { + space_view.space_origin.to_string() + }, + false, + ) + .clicked() + { + ui.close_menu(); + let new_space_view_id = self.add_space_view(space_view); + ctx.set_single_selection(&Item::SpaceView(new_space_view_id)); + } } - } - }) + }, + ) .response .on_hover_text("Add new Space View"); }