Skip to content

Space view screenshotting in native viewer #8258

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

Merged
merged 16 commits into from
Dec 3, 2024
24 changes: 11 additions & 13 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1923,7 +1923,7 @@ checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125"
[[package]]
name = "ecolor"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3#1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3"
dependencies = [
"bytemuck",
"color-hex",
Expand All @@ -1940,7 +1940,7 @@ checksum = "18aade80d5e09429040243ce1143ddc08a92d7a22820ac512610410a4dd5214f"
[[package]]
name = "eframe"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3#1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3"
dependencies = [
"ahash",
"bytemuck",
Expand Down Expand Up @@ -1979,7 +1979,7 @@ dependencies = [
[[package]]
name = "egui"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3#1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3"
dependencies = [
"accesskit",
"ahash",
Expand All @@ -1996,7 +1996,7 @@ dependencies = [
[[package]]
name = "egui-wgpu"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3#1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3"
dependencies = [
"ahash",
"bytemuck",
Expand All @@ -2015,7 +2015,7 @@ dependencies = [
[[package]]
name = "egui-winit"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3#1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3"
dependencies = [
"accesskit_winit",
"ahash",
Expand Down Expand Up @@ -2057,7 +2057,7 @@ dependencies = [
[[package]]
name = "egui_extras"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3#1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3"
dependencies = [
"ahash",
"egui",
Expand All @@ -2074,7 +2074,7 @@ dependencies = [
[[package]]
name = "egui_glow"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3#1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3"
dependencies = [
"ahash",
"bytemuck",
Expand All @@ -2092,7 +2092,7 @@ dependencies = [
[[package]]
name = "egui_kittest"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3#1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3"
dependencies = [
"dify",
"egui",
Expand Down Expand Up @@ -2161,7 +2161,7 @@ checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "emath"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3#1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3"
dependencies = [
"bytemuck",
"serde",
Expand Down Expand Up @@ -2277,7 +2277,7 @@ dependencies = [
[[package]]
name = "epaint"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3#1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3"
dependencies = [
"ab_glyph",
"ahash",
Expand All @@ -2296,7 +2296,7 @@ dependencies = [
[[package]]
name = "epaint_default_fonts"
version = "0.29.1"
source = "git+https://github.com/emilk/egui.git?rev=84cc1572b175d49a64f1b323a6d7e56b1f1fba66#84cc1572b175d49a64f1b323a6d7e56b1f1fba66"
source = "git+https://github.com/emilk/egui.git?rev=1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3#1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3"

[[package]]
name = "equivalent"
Expand Down Expand Up @@ -6765,8 +6765,6 @@ dependencies = [
"ahash",
"egui",
"egui_tiles",
"glam",
"image",
"itertools 0.13.0",
"nohash-hasher",
"rayon",
Expand Down
16 changes: 8 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -559,20 +559,20 @@ significant_drop_tightening = "allow" # An update of parking_lot made this trigg
# As a last resport, patch with a commit to our own repository.
# ALWAYS document what PR the commit hash is part of, or when it was merged into the upstream trunk.

ecolor = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27
eframe = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27
egui = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27
egui_extras = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27
egui-wgpu = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27
emath = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27

egui_kittest = { git = "https://github.com/emilk/egui.git", rev = "84cc1572b175d49a64f1b323a6d7e56b1f1fba66" } # egui master 2024-11-27
ecolor = { git = "https://github.com/emilk/egui.git", rev = "1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3" } # this is from a PR. TODO: update to egui master
eframe = { git = "https://github.com/emilk/egui.git", rev = "1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3" } # this is from a PR. TODO: update to egui master
egui = { git = "https://github.com/emilk/egui.git", rev = "1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3" } # this is from a PR. TODO: update to egui master
egui_extras = { git = "https://github.com/emilk/egui.git", rev = "1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3" } # this is from a PR. TODO: update to egui master
egui_kittest = { git = "https://github.com/emilk/egui.git", rev = "1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3" } # this is from a PR. TODO: update to egui master
egui-wgpu = { git = "https://github.com/emilk/egui.git", rev = "1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3" } # this is from a PR. TODO: update to egui master
emath = { git = "https://github.com/emilk/egui.git", rev = "1d6153b56b6e203ce5e5abeaa0e2a04f4a45b7b3" } # this is from a PR. TODO: update to egui master

# Useful while developing:
# ecolor = { path = "../../egui/crates/ecolor" }
# eframe = { path = "../../egui/crates/eframe" }
# egui = { path = "../../egui/crates/egui" }
# egui_extras = { path = "../../egui/crates/egui_extras" }
# egui_kittest = { path = "../../egui/crates/egui_kittest" }
# egui-wgpu = { path = "../../egui/crates/egui-wgpu" }
# emath = { path = "../../egui/crates/emath" }

Expand Down
6 changes: 6 additions & 0 deletions crates/viewer/re_context_menu/src/actions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@ pub(super) mod collapse_expand_all;
pub(super) mod move_contents_to_new_container;
pub(super) mod remove;
pub(super) mod show_hide;

#[cfg(not(target_arch = "wasm32"))] // TODO(#8264): screenshotting on web
mod screenshot_action;

#[cfg(not(target_arch = "wasm32"))] // TODO(#8264): screenshotting on web
pub use screenshot_action::ScreenshotAction;
78 changes: 78 additions & 0 deletions crates/viewer/re_context_menu/src/actions/screenshot_action.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use re_viewer_context::{
Item, PublishedSpaceViewInfo, ScreenshotTarget, SpaceViewId, SpaceViewRectPublisher,
};

use crate::{ContextMenuAction, ContextMenuContext};

/// Space view screenshot action.
#[cfg(not(target_arch = "wasm32"))]
pub enum ScreenshotAction {
/// Screenshot the space view, and copy the results to clipboard.
CopyScreenshot,

/// Screenshot the space view, and save the results to disk.
SaveScreenshot,
}

impl ContextMenuAction for ScreenshotAction {
/// Do we have a context menu for this selection?
fn supports_selection(&self, ctx: &ContextMenuContext<'_>) -> bool {
// Allow if there is a single space view selected.
ctx.selection.len() == 1
&& ctx
.selection
.iter()
.all(|(item, _)| self.supports_item(ctx, item))
}

/// Do we have a context menu for this item?
fn supports_item(&self, ctx: &ContextMenuContext<'_>, item: &Item) -> bool {
let Item::SpaceView(space_view_id) = item else {
return false;
};

ctx.egui_context.memory_mut(|mem| {
mem.caches
.cache::<SpaceViewRectPublisher>()
.get(space_view_id)
.is_some()
})
}

fn label(&self, _ctx: &ContextMenuContext<'_>) -> String {
match self {
Self::CopyScreenshot => "Copy screenshot".to_owned(),
Self::SaveScreenshot => "Save screenshot…".to_owned(),
}
}

fn process_space_view(&self, ctx: &ContextMenuContext<'_>, space_view_id: &SpaceViewId) {
let Some(space_view_info) = ctx.egui_context.memory_mut(|mem| {
mem.caches
.cache::<SpaceViewRectPublisher>()
.get(space_view_id)
.cloned()
}) else {
return;
};

let PublishedSpaceViewInfo { name, rect } = space_view_info;

let rect = rect.shrink(1.75); // Hacky: Shrink so we don't accidentally include the border of the space-view.

let target = match self {
Self::CopyScreenshot => ScreenshotTarget::CopyToClipboard,
Self::SaveScreenshot => ScreenshotTarget::SaveToDisk,
};

ctx.egui_context
.send_viewport_cmd(egui::ViewportCommand::Screenshot(egui::UserData::new(
re_viewer_context::ScreenshotInfo {
ui_rect: Some(rect),
pixels_per_point: ctx.egui_context.pixels_per_point(),
name,
target,
},
)));
}
}
5 changes: 5 additions & 0 deletions crates/viewer/re_context_menu/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ fn action_list(
Box::new(HideAction),
Box::new(RemoveAction),
],
#[cfg(not(target_arch = "wasm32"))] // TODO(#8264): screenshotting on web
vec![
Box::new(actions::ScreenshotAction::CopyScreenshot),
Box::new(actions::ScreenshotAction::SaveScreenshot),
],
vec![
Box::new(CollapseExpandAllAction::ExpandAll),
Box::new(CollapseExpandAllAction::CollapseAll),
Expand Down
6 changes: 5 additions & 1 deletion crates/viewer/re_data_ui/src/blob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,11 @@ pub fn blob_preview_and_save_ui(
file_name.push_str(file_extension);
}

ctx.save_file_dialog(file_name, "Save blob".to_owned(), blob.to_vec());
ctx.command_sender.save_file_dialog(
&file_name,
"Save blob".to_owned(),
blob.to_vec(),
);
}

#[cfg(not(target_arch = "wasm32"))]
Expand Down
3 changes: 2 additions & 1 deletion crates/viewer/re_data_ui/src/instance_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,7 +384,8 @@ fn image_download_button_ui(
.map_or("image", |name| name.unescaped_str())
.to_owned()
);
ctx.save_file_dialog(file_name, "Save image".to_owned(), png_bytes);
ctx.command_sender
.save_file_dialog(&file_name, "Save image".to_owned(), png_bytes);
}
Err(err) => {
re_log::error!("{err}");
Expand Down
2 changes: 0 additions & 2 deletions crates/viewer/re_space_view/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ mod instance_hash_conversions;
mod outlines;
mod query;
mod results_ext;
mod screenshot;
mod view_property_ui;

pub use annotation_context_utils::{
Expand All @@ -31,7 +30,6 @@ pub use query::{
pub use results_ext::{
HybridLatestAtResults, HybridResults, HybridResultsChunkIter, RangeResultsExt,
};
pub use screenshot::ScreenshotMode;
pub use view_property_ui::view_property_ui;

pub mod external {
Expand Down
9 changes: 0 additions & 9 deletions crates/viewer/re_space_view/src/screenshot.rs

This file was deleted.

34 changes: 1 addition & 33 deletions crates/viewer/re_space_view_spatial/src/ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,12 @@ use egui::{epaint::util::OrderedFloat, text::TextWrapping, NumExt as _, WidgetTe

use re_format::format_f32;
use re_math::BoundingBox;
use re_space_view::ScreenshotMode;
use re_types::{
archetypes::Pinhole, blueprint::components::VisualBounds2D, components::ViewCoordinates,
image::ImageKind,
};
use re_ui::UiExt as _;
use re_viewer_context::{
HoverHighlight, SelectionHighlight, SpaceViewHighlights, SpaceViewState, ViewerContext,
};
use re_viewer_context::{HoverHighlight, SelectionHighlight, SpaceViewHighlights, SpaceViewState};

use crate::{
eye::EyeMode,
Expand Down Expand Up @@ -341,35 +338,6 @@ pub fn paint_loading_spinners(
}
}

pub fn screenshot_context_menu(
_ctx: &ViewerContext<'_>,
_response: &egui::Response,
) -> Option<ScreenshotMode> {
#[cfg(not(target_arch = "wasm32"))]
{
if _ctx.app_options.experimental_space_view_screenshots {
let mut take_screenshot = None;
_response.context_menu(|ui| {
ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
if ui.button("Save screenshot to disk").clicked() {
take_screenshot = Some(ScreenshotMode::SaveAndCopyToClipboard);
ui.close_menu();
} else if ui.button("Copy screenshot to clipboard").clicked() {
take_screenshot = Some(ScreenshotMode::CopyToClipboard);
ui.close_menu();
}
});
take_screenshot
} else {
None
}
}
#[cfg(target_arch = "wasm32")]
{
None
}
}

pub fn format_vector(v: glam::Vec3) -> String {
use glam::Vec3;

Expand Down
15 changes: 3 additions & 12 deletions crates/viewer/re_space_view_spatial/src/ui_2d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ use re_viewer_context::{
};
use re_viewport_blueprint::ViewProperty;

use super::{
eye::Eye,
ui::{create_labels, screenshot_context_menu},
};
use super::{eye::Eye, ui::create_labels};
use crate::{
query_pinhole_legacy, ui::SpatialSpaceViewState, view_kind::SpatialSpaceViewKind,
visualizers::collect_ui_labels, SpatialSpaceView2D,
Expand Down Expand Up @@ -169,7 +166,7 @@ impl SpatialSpaceView2D {
state.pinhole_at_origin =
query_pinhole_legacy(ctx, &ctx.current_query(), query.space_origin);

let (mut response, painter) =
let (response, painter) =
ui.allocate_painter(ui.available_size(), egui::Sense::click_and_drag());

// Convert ui coordinates to/from scene coordinates.
Expand Down Expand Up @@ -216,7 +213,7 @@ impl SpatialSpaceView2D {
ui.ctx().pixels_per_point(),
&eye,
);
response = crate::picking_ui::picking(
crate::picking_ui::picking(
ctx,
&picking_context,
ui,
Expand Down Expand Up @@ -249,12 +246,6 @@ impl SpatialSpaceView2D {

// ------------------------------------------------------------------------
Copy link
Member

Choose a reason for hiding this comment

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

for later consideration: it was actually nice to have a context menu inside the view.


if let Some(mode) = screenshot_context_menu(ctx, &response) {
view_builder
.schedule_screenshot(render_ctx, query.space_view_id.gpu_readback_id(), mode)
.ok();
}

// Draw a re_renderer driven view.
// Camera & projection are configured to ingest space coordinates directly.
painter.add(gpu_bridge::new_renderer_callback(
Expand Down
Loading
Loading