use egui::{NumExt as _, Widget};
use re_log_types::TimeZone;
use re_ui::{ReUi, UICommand};
use re_viewer_context::StoreContext;
use crate::App;
impl App {
pub fn rerun_menu_button_ui(
&mut self,
_store_context: Option<&StoreContext<'_>>,
ui: &mut egui::Ui,
frame: &mut eframe::Frame,
) {
let desired_icon_height = ui.max_rect().height() - 4.0; let desired_icon_height = desired_icon_height.at_most(28.0);
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;
ui.menu_button("About", |ui| self.about_rerun_ui(ui));
ui.add_space(spacing);
UICommand::ToggleCommandPalette.menu_button_ui(ui, &self.command_sender);
ui.add_space(spacing);
UICommand::Open.menu_button_ui(ui, &self.command_sender);
#[cfg(not(target_arch = "wasm32"))]
{
self.save_buttons_ui(ui, _store_context);
UICommand::CloseCurrentRecording.menu_button_ui(ui, &self.command_sender);
ui.add_space(spacing);
let zoom_factor = self.app_options().zoom_factor;
ui.weak(format!("Zoom {:.0}%", zoom_factor * 100.0))
.on_hover_text(
"The UI zoom level on top of the operating system's default value",
);
UICommand::ZoomIn.menu_button_ui(ui, &self.command_sender);
UICommand::ZoomOut.menu_button_ui(ui, &self.command_sender);
ui.add_enabled_ui(zoom_factor != 1.0, |ui| {
UICommand::ZoomReset.menu_button_ui(ui, &self.command_sender)
});
UICommand::ToggleFullscreen.menu_button_ui(ui, &self.command_sender);
ui.add_space(spacing);
}
{
UICommand::ResetViewer.menu_button_ui(ui, &self.command_sender);
#[cfg(not(target_arch = "wasm32"))]
UICommand::OpenProfiler.menu_button_ui(ui, &self.command_sender);
UICommand::ToggleMemoryPanel.menu_button_ui(ui, &self.command_sender);
#[cfg(debug_assertions)]
UICommand::ToggleStylePanel.menu_button_ui(ui, &self.command_sender);
}
ui.add_space(spacing);
ui.menu_button("Options", |ui| {
self.options_menu_ui(ui, frame);
});
ui.add_space(spacing);
const HELP_URL: &str = "https://www.rerun.io/docs/getting-started/viewer-walkthrough";
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 {
url: HELP_URL.to_owned(),
new_tab: true,
});
});
}
#[cfg(not(target_arch = "wasm32"))]
{
ui.add_space(spacing);
UICommand::Quit.menu_button_ui(ui, &self.command_sender);
}
});
}
fn about_rerun_ui(&self, ui: &mut egui::Ui) {
let re_build_info::BuildInfo {
crate_name,
version,
rustc_version,
llvm_version,
git_hash,
git_branch: _,
is_in_rerun_workspace: _,
target_triple,
datetime,
} = *self.build_info();
ui.style_mut().wrap = Some(false);
let rustc_version = if rustc_version.is_empty() {
"unknown"
} else {
rustc_version
};
let llvm_version = if llvm_version.is_empty() {
"unknown"
} else {
llvm_version
};
let short_git_hash = &git_hash[..std::cmp::min(git_hash.len(), 7)];
ui.label(format!(
"{crate_name} {version} ({short_git_hash})\n\
{target_triple}\n\
rustc {rustc_version}\n\
LLVM {llvm_version}\n\
Built {datetime}",
));
}
fn options_menu_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
ui.style_mut().wrap = Some(false);
if self
.re_ui
.checkbox(
ui,
&mut self.state.app_options.show_metrics,
"Show performance metrics",
)
.on_hover_text("Show metrics for milliseconds/frame and RAM usage in the top bar")
.clicked()
{
ui.close_menu();
}
ui.horizontal(|ui| {
if self
.re_ui
.radio_value(
ui,
&mut self.state.app_options.time_zone_for_timestamps,
TimeZone::Utc,
"UTC",
)
.on_hover_text("Display timestamps in UTC")
.clicked()
{
ui.close_menu();
}
if self
.re_ui
.radio_value(
ui,
&mut self.state.app_options.time_zone_for_timestamps,
TimeZone::Local,
"Local",
)
.on_hover_text("Display timestamps in the local timezone")
.clicked()
{
ui.close_menu();
}
});
#[cfg(not(target_arch = "wasm32"))]
{
if self.re_ui
.checkbox(ui, &mut self.state.app_options.experimental_space_view_screenshots, "(experimental) Space View screenshots")
.on_hover_text("Allow taking screenshots of 2D and 3D Space Views via their context menu. Does not contain labels.")
.clicked()
{
ui.close_menu();
}
}
#[cfg(debug_assertions)]
{
ui.separator();
ui.label("Debug:");
self.egui_debug_options_ui(ui);
ui.separator();
self.debug_menu_options_ui(ui, _frame);
}
}
#[cfg(not(target_arch = "wasm32"))]
fn save_buttons_ui(&mut self, ui: &mut egui::Ui, store_view: Option<&StoreContext<'_>>) {
use re_ui::UICommandSender;
let file_save_in_progress = self.background_tasks.is_file_save_in_progress();
let save_button = UICommand::Save.menu_button(ui.ctx());
let save_selection_button = UICommand::SaveSelection.menu_button(ui.ctx());
if file_save_in_progress {
ui.add_enabled_ui(false, |ui| {
ui.horizontal(|ui| {
ui.add(save_button);
ui.spinner();
});
ui.horizontal(|ui| {
ui.add(save_selection_button);
ui.spinner();
});
});
} else {
let store_db_is_nonempty = store_view
.and_then(|view| view.recording)
.map_or(false, |recording| !recording.is_empty());
ui.add_enabled_ui(store_db_is_nonempty, |ui| {
if ui
.add(save_button)
.on_hover_text("Save all data to a Rerun data file (.rrd)")
.clicked()
{
ui.close_menu();
self.command_sender.send_ui(UICommand::Save);
}
let loop_selection = self.state.loop_selection(store_view);
if ui
.add_enabled(loop_selection.is_some(), save_selection_button)
.on_hover_text(
"Save data for the current loop selection to a Rerun data file (.rrd)",
)
.clicked()
{
ui.close_menu();
self.command_sender.send_ui(UICommand::SaveSelection);
}
});
}
}
#[cfg(debug_assertions)]
fn egui_debug_options_ui(&self, ui: &mut egui::Ui) {
let mut debug = ui.style().debug;
let mut any_clicked = false;
let re_ui = self.re_ui();
any_clicked |= re_ui
.checkbox(ui, &mut debug.debug_on_hover, "Ui debug on hover")
.on_hover_text("However over widgets to see their rectangles")
.changed();
any_clicked |= re_ui
.checkbox(ui, &mut debug.show_expand_width, "Show expand width")
.on_hover_text("Show which widgets make their parent wider")
.changed();
any_clicked |= re_ui
.checkbox(ui, &mut debug.show_expand_height, "Show expand height")
.on_hover_text("Show which widgets make their parent higher")
.changed();
any_clicked |= re_ui
.checkbox(ui, &mut debug.show_resize, "Show resize")
.changed();
any_clicked |= re_ui
.checkbox(
ui,
&mut debug.show_interactive_widgets,
"Show interactive widgets",
)
.on_hover_text("Show an overlay on all interactive widgets")
.changed();
any_clicked |= re_ui
.checkbox(ui, &mut debug.show_blocking_widget, "Show blocking widgets")
.on_hover_text("Show what widget blocks the interaction of another widget")
.changed();
if any_clicked {
let mut style = (*ui.ctx().style()).clone();
style.debug = debug;
ui.ctx().set_style(style);
}
}
#[cfg(debug_assertions)]
fn debug_menu_options_ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
#[cfg(not(target_arch = "wasm32"))]
{
if ui.button("Mobile size").clicked() {
_frame.set_window_size(egui::vec2(375.0, 667.0)); _frame.set_fullscreen(false);
ui.close_menu();
}
ui.separator();
}
if ui.button("Log info").clicked() {
re_log::info!("Logging some info");
}
self.re_ui.checkbox(ui,
&mut self.state.app_options.show_picking_debug_overlay,
"Picking Debug Overlay",
)
.on_hover_text("Show a debug overlay that renders the picking layer information using the `debug_overlay.wgsl` shader.");
ui.menu_button("Crash", |ui| {
#[allow(clippy::manual_assert)]
if ui.button("panic!").clicked() {
panic!("Intentional panic");
}
if ui.button("panic! during unwind").clicked() {
struct PanicOnDrop {}
impl Drop for PanicOnDrop {
fn drop(&mut self) {
panic!("Second intentional panic in Drop::drop");
}
}
let _this_will_panic_when_dropped = PanicOnDrop {};
panic!("First intentional panic");
}
if ui.button("SEGFAULT").clicked() {
#[cfg(target_pointer_width = "64")]
pub const SEGFAULT_ADDRESS: u64 = u32::MAX as u64 + 0x42;
#[cfg(target_pointer_width = "32")]
pub const SEGFAULT_ADDRESS: u32 = 0x42;
let bad_ptr: *mut u8 = SEGFAULT_ADDRESS as _;
#[allow(unsafe_code)]
unsafe {
std::ptr::write_volatile(bad_ptr, 1);
}
}
if ui.button("Stack overflow").clicked() {
fn recurse(data: u64) -> u64 {
let mut buff = [0u8; 256];
buff[..9].copy_from_slice(b"junk data");
let mut result = data;
for c in buff {
result += c as u64;
}
if result == 0 {
result
} else {
recurse(result) + 1
}
}
recurse(42);
}
});
}
}