Skip to content

Commit

Permalink
Expand CommandSender to support SystemCommand (#2344)
Browse files Browse the repository at this point in the history
Built on top of: #2330
Need to rebase after merging.

## Motivation
The new CommandSender introduced in
#2339 was limited to only
supporting Commands from `re_ui`, which has a very limited set of
dependencies. We needed a similar pattern for commands carrying
additional data outside the context of the command palette.

## Overview
 - Rename `Commmand` -> `UICommand`
 - Introduce `SystemCommand`
- Introduce a new `Command` as a union of `UICommand` and
`SystemCommand`
 - Introduce new traits for sending the respective types.
- Moves the implementation of the `CommanSender` to `re_viewer_context`
so we can access it from more places.
- Switches the business for setting recording id to use the new
`SystemCommand` interfaces.
- Adds `CommandSender` to `ViewerContext` so we can use it from the
Blueprint panel in the future.

### Checklist
* [x] I have read and agree to [Contributor
Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and
the [Code of
Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md)
* [ ] I've included a screenshot or gif (if applicable)

<!-- This line will get updated when the PR build summary job finishes.
-->
PR Build Summary: https://build.rerun.io/pr/2344

<!-- pr-link-docs:start -->
Docs preview: https://rerun.io/preview/af3f5f8/docs
Examples preview: https://rerun.io/preview/af3f5f8/examples
<!-- pr-link-docs:end -->
  • Loading branch information
jleibs authored Jun 9, 2023
1 parent a8acd6e commit 370e16f
Show file tree
Hide file tree
Showing 15 changed files with 268 additions and 175 deletions.
1 change: 0 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion crates/re_time_panel/src/time_control_ui.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ impl TimeControlUi {
}

fn toggle_playback_text(egui_ctx: &egui::Context) -> String {
if let Some(shortcut) = re_ui::Command::PlaybackTogglePlayPause.kb_shortcut() {
if let Some(shortcut) = re_ui::UICommand::PlaybackTogglePlayPause.kb_shortcut() {
format!(" Toggle with {}", egui_ctx.format_shortcut(&shortcut))
} else {
Default::default()
Expand Down
1 change: 0 additions & 1 deletion crates/re_ui/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ eframe = ["dep:eframe"]


[dependencies]
crossbeam.workspace = true
egui_extras.workspace = true
egui.workspace = true
image = { workspace = true, default-features = false, features = ["png"] }
Expand Down
49 changes: 39 additions & 10 deletions crates/re_ui/examples/re_ui_example.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,33 @@
use re_ui::{toasts, Command, CommandPalette, CommandReceiver, CommandSender};
use re_ui::{toasts, CommandPalette, UICommand, UICommandSender};

/// Sender that queues up the execution of a command.
pub struct CommandSender(std::sync::mpsc::Sender<UICommand>);

impl UICommandSender for CommandSender {
/// Send a command to be executed.
fn send_ui(&self, command: UICommand) {
// The only way this can fail is if the receiver has been dropped.
self.0.send(command).ok();
}
}

/// Receiver for the [`CommandSender`]
pub struct CommandReceiver(std::sync::mpsc::Receiver<UICommand>);

impl CommandReceiver {
/// Receive a command to be executed if any is queued.
pub fn recv(&self) -> Option<UICommand> {
// The only way this can fail (other than being empty)
// is if the sender has been dropped.
self.0.try_recv().ok()
}
}

/// Creates a new command channel.
fn command_channel() -> (CommandSender, CommandReceiver) {
let (sender, receiver) = std::sync::mpsc::channel();
(CommandSender(sender), CommandReceiver(receiver))
}

fn main() -> eframe::Result<()> {
re_log::setup_native_logging();
Expand Down Expand Up @@ -61,7 +90,7 @@ impl ExampleApp {

let tree = egui_tiles::Tree::new_tabs(vec![1, 2, 3]);

let (command_sender, command_receiver) = re_ui::command_channel();
let (command_sender, command_receiver) = command_channel();

Self {
re_ui,
Expand Down Expand Up @@ -209,18 +238,18 @@ impl eframe::App for ExampleApp {
});

if let Some(cmd) = self.cmd_palette.show(egui_ctx) {
self.command_sender.send(cmd);
self.command_sender.send_ui(cmd);
}
if let Some(cmd) = re_ui::Command::listen_for_kb_shortcut(egui_ctx) {
self.command_sender.send(cmd);
if let Some(cmd) = re_ui::UICommand::listen_for_kb_shortcut(egui_ctx) {
self.command_sender.send_ui(cmd);
}

while let Some(cmd) = self.command_receiver.recv() {
self.latest_cmd = cmd.text().to_owned();

#[allow(clippy::single_match)]
match cmd {
Command::ToggleCommandPalette => self.cmd_palette.toggle(),
UICommand::ToggleCommandPalette => self.cmd_palette.toggle(),
_ => {}
}
}
Expand Down Expand Up @@ -302,10 +331,10 @@ impl ExampleApp {
}

fn file_menu(ui: &mut egui::Ui, command_sender: &CommandSender) {
Command::Save.menu_button_ui(ui, command_sender);
Command::SaveSelection.menu_button_ui(ui, command_sender);
Command::Open.menu_button_ui(ui, command_sender);
Command::Quit.menu_button_ui(ui, command_sender);
UICommand::Save.menu_button_ui(ui, command_sender);
UICommand::SaveSelection.menu_button_ui(ui, command_sender);
UICommand::Open.menu_button_ui(ui, command_sender);
UICommand::Quit.menu_button_ui(ui, command_sender);
}

fn selection_buttons(ui: &mut egui::Ui) {
Expand Down
136 changes: 56 additions & 80 deletions crates/re_ui/src/command.rs
Original file line number Diff line number Diff line change
@@ -1,32 +1,8 @@
use egui::{Key, KeyboardShortcut, Modifiers};

/// Sender that queues up the execution of a command.
pub struct CommandSender(crossbeam::channel::Sender<Command>);

impl CommandSender {
/// Send a command to be executed.
pub fn send(&self, command: Command) {
// The only way this can fail is if the receiver has been dropped.
self.0.send(command).ok();
}
}

/// Receiver for the [`CommandSender`]
pub struct CommandReceiver(crossbeam::channel::Receiver<Command>);

impl CommandReceiver {
/// Receive a command to be executed if any is queued.
pub fn recv(&self) -> Option<Command> {
// The only way this can fail (other than being empty)
// is if the sender has been dropped.
self.0.try_recv().ok()
}
}

/// Creates a new command channel.
pub fn command_channel() -> (CommandSender, CommandReceiver) {
let (sender, receiver) = crossbeam::channel::unbounded();
(CommandSender(sender), CommandReceiver(receiver))
/// Interface for sending [`UICommand`] messages.
pub trait UICommandSender {
fn send_ui(&self, command: UICommand);
}

/// All the commands we support.
Expand All @@ -35,7 +11,7 @@ pub fn command_channel() -> (CommandSender, CommandReceiver) {
/// some have keyboard shortcuts,
/// and all are visible in the [`crate::CommandPalette`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, strum_macros::EnumIter)]
pub enum Command {
pub enum UICommand {
// Listed in the order they show up in the command palette by default!
#[cfg(not(target_arch = "wasm32"))]
Open,
Expand Down Expand Up @@ -82,7 +58,7 @@ pub enum Command {
ScreenshotWholeApp,
}

impl Command {
impl UICommand {
pub fn text(self) -> &'static str {
self.text_and_tooltip().0
}
Expand All @@ -94,76 +70,76 @@ impl Command {
pub fn text_and_tooltip(self) -> (&'static str, &'static str) {
match self {
#[cfg(not(target_arch = "wasm32"))]
Command::Save => ("Save…", "Save all data to a Rerun data file (.rrd)"),
UICommand::Save => ("Save…", "Save all data to a Rerun data file (.rrd)"),

#[cfg(not(target_arch = "wasm32"))]
Command::SaveSelection => (
UICommand::SaveSelection => (
"Save loop selection…",
"Save data for the current loop selection to a Rerun data file (.rrd)",
),

#[cfg(not(target_arch = "wasm32"))]
Command::Open => ("Open…", "Open a Rerun Data File (.rrd)"),
UICommand::Open => ("Open…", "Open a Rerun Data File (.rrd)"),

#[cfg(not(target_arch = "wasm32"))]
Command::Quit => ("Quit", "Close the Rerun Viewer"),
UICommand::Quit => ("Quit", "Close the Rerun Viewer"),

Command::ResetViewer => (
UICommand::ResetViewer => (
"Reset viewer",
"Reset the viewer to how it looked the first time you ran it",
),

#[cfg(not(target_arch = "wasm32"))]
Command::OpenProfiler => (
UICommand::OpenProfiler => (
"Open profiler",
"Starts a profiler, showing what makes the viewer run slow",
),

Command::ToggleMemoryPanel => (
UICommand::ToggleMemoryPanel => (
"Toggle memory panel",
"Investigate what is using up RAM in Rerun Viewer",
),
Command::ToggleBlueprintPanel => ("Toggle blueprint panel", "Toggle the left panel"),
Command::ToggleSelectionPanel => ("Toggle selection panel", "Toggle the right panel"),
Command::ToggleTimePanel => ("Toggle time panel", "Toggle the bottom time panel"),
UICommand::ToggleBlueprintPanel => ("Toggle blueprint panel", "Toggle the left panel"),
UICommand::ToggleSelectionPanel => ("Toggle selection panel", "Toggle the right panel"),
UICommand::ToggleTimePanel => ("Toggle time panel", "Toggle the bottom time panel"),

#[cfg(not(target_arch = "wasm32"))]
Command::ToggleFullscreen => (
UICommand::ToggleFullscreen => (
"Toggle fullscreen",
"Toggle between windowed and fullscreen viewer",
),
#[cfg(not(target_arch = "wasm32"))]
Command::ZoomIn => ("Zoom In", "Increases the ui scaling factor"),
UICommand::ZoomIn => ("Zoom In", "Increases the ui scaling factor"),
#[cfg(not(target_arch = "wasm32"))]
Command::ZoomOut => ("Zoom Out", "Decreases the ui scaling factor"),
UICommand::ZoomOut => ("Zoom Out", "Decreases the ui scaling factor"),
#[cfg(not(target_arch = "wasm32"))]
Command::ZoomReset => (
UICommand::ZoomReset => (
"Reset Zoom",
"Resets ui scaling factor to the OS provided default",
),

Command::SelectionPrevious => ("Previous selection", "Go to previous selection"),
Command::SelectionNext => ("Next selection", "Go to next selection"),
Command::ToggleCommandPalette => {
UICommand::SelectionPrevious => ("Previous selection", "Go to previous selection"),
UICommand::SelectionNext => ("Next selection", "Go to next selection"),
UICommand::ToggleCommandPalette => {
("Command palette…", "Toggle the command palette window")
}

Command::PlaybackTogglePlayPause => {
UICommand::PlaybackTogglePlayPause => {
("Toggle play/pause", "Either play or pause the time")
}
Command::PlaybackFollow => ("Follow", "Follow on from end of timeline"),
Command::PlaybackStepBack => (
UICommand::PlaybackFollow => ("Follow", "Follow on from end of timeline"),
UICommand::PlaybackStepBack => (
"Step time back",
"Move the time marker back to the previous point in time with any data",
),
Command::PlaybackStepForward => (
UICommand::PlaybackStepForward => (
"Step time forward",
"Move the time marker to the next point in time with any data",
),
Command::PlaybackRestart => ("Restart", "Restart from beginning of timeline"),
UICommand::PlaybackRestart => ("Restart", "Restart from beginning of timeline"),

#[cfg(not(target_arch = "wasm32"))]
Command::ScreenshotWholeApp => (
UICommand::ScreenshotWholeApp => (
"Screenshot",
"Copy screenshot of the whole app to clipboard",
),
Expand All @@ -190,52 +166,52 @@ impl Command {

match self {
#[cfg(not(target_arch = "wasm32"))]
Command::Save => Some(cmd(Key::S)),
UICommand::Save => Some(cmd(Key::S)),
#[cfg(not(target_arch = "wasm32"))]
Command::SaveSelection => Some(cmd_shift(Key::S)),
UICommand::SaveSelection => Some(cmd_shift(Key::S)),
#[cfg(not(target_arch = "wasm32"))]
Command::Open => Some(cmd(Key::O)),
UICommand::Open => Some(cmd(Key::O)),

#[cfg(all(not(target_arch = "wasm32"), target_os = "windows"))]
Command::Quit => Some(KeyboardShortcut::new(Modifiers::ALT, Key::F4)),
UICommand::Quit => Some(KeyboardShortcut::new(Modifiers::ALT, Key::F4)),

#[cfg(all(not(target_arch = "wasm32"), not(target_os = "windows")))]
Command::Quit => Some(cmd(Key::Q)),
UICommand::Quit => Some(cmd(Key::Q)),

Command::ResetViewer => Some(ctrl_shift(Key::R)),
UICommand::ResetViewer => Some(ctrl_shift(Key::R)),
#[cfg(not(target_arch = "wasm32"))]
Command::OpenProfiler => Some(ctrl_shift(Key::P)),
Command::ToggleMemoryPanel => Some(ctrl_shift(Key::M)),
Command::ToggleBlueprintPanel => Some(ctrl_shift(Key::B)),
Command::ToggleSelectionPanel => Some(ctrl_shift(Key::S)),
Command::ToggleTimePanel => Some(ctrl_shift(Key::T)),
UICommand::OpenProfiler => Some(ctrl_shift(Key::P)),
UICommand::ToggleMemoryPanel => Some(ctrl_shift(Key::M)),
UICommand::ToggleBlueprintPanel => Some(ctrl_shift(Key::B)),
UICommand::ToggleSelectionPanel => Some(ctrl_shift(Key::S)),
UICommand::ToggleTimePanel => Some(ctrl_shift(Key::T)),

#[cfg(not(target_arch = "wasm32"))]
Command::ToggleFullscreen => Some(key(Key::F11)),
UICommand::ToggleFullscreen => Some(key(Key::F11)),
#[cfg(not(target_arch = "wasm32"))]
Command::ZoomIn => Some(egui::gui_zoom::kb_shortcuts::ZOOM_IN),
UICommand::ZoomIn => Some(egui::gui_zoom::kb_shortcuts::ZOOM_IN),
#[cfg(not(target_arch = "wasm32"))]
Command::ZoomOut => Some(egui::gui_zoom::kb_shortcuts::ZOOM_OUT),
UICommand::ZoomOut => Some(egui::gui_zoom::kb_shortcuts::ZOOM_OUT),
#[cfg(not(target_arch = "wasm32"))]
Command::ZoomReset => Some(egui::gui_zoom::kb_shortcuts::ZOOM_RESET),
UICommand::ZoomReset => Some(egui::gui_zoom::kb_shortcuts::ZOOM_RESET),

Command::SelectionPrevious => Some(ctrl_shift(Key::ArrowLeft)),
Command::SelectionNext => Some(ctrl_shift(Key::ArrowRight)),
Command::ToggleCommandPalette => Some(cmd(Key::P)),
UICommand::SelectionPrevious => Some(ctrl_shift(Key::ArrowLeft)),
UICommand::SelectionNext => Some(ctrl_shift(Key::ArrowRight)),
UICommand::ToggleCommandPalette => Some(cmd(Key::P)),

Command::PlaybackTogglePlayPause => Some(key(Key::Space)),
Command::PlaybackFollow => Some(cmd(Key::ArrowRight)),
Command::PlaybackStepBack => Some(key(Key::ArrowLeft)),
Command::PlaybackStepForward => Some(key(Key::ArrowRight)),
Command::PlaybackRestart => Some(cmd(Key::ArrowLeft)),
UICommand::PlaybackTogglePlayPause => Some(key(Key::Space)),
UICommand::PlaybackFollow => Some(cmd(Key::ArrowRight)),
UICommand::PlaybackStepBack => Some(key(Key::ArrowLeft)),
UICommand::PlaybackStepForward => Some(key(Key::ArrowRight)),
UICommand::PlaybackRestart => Some(cmd(Key::ArrowLeft)),

#[cfg(not(target_arch = "wasm32"))]
Command::ScreenshotWholeApp => None,
UICommand::ScreenshotWholeApp => None,
}
}

#[must_use = "Returns the Command that was triggered by some keyboard shortcut"]
pub fn listen_for_kb_shortcut(egui_ctx: &egui::Context) -> Option<Command> {
pub fn listen_for_kb_shortcut(egui_ctx: &egui::Context) -> Option<UICommand> {
use strum::IntoEnumIterator as _;

let anything_has_focus = egui_ctx.memory(|mem| mem.focus().is_some());
Expand All @@ -244,7 +220,7 @@ impl Command {
}

egui_ctx.input_mut(|input| {
for command in Command::iter() {
for command in UICommand::iter() {
if let Some(kb_shortcut) = command.kb_shortcut() {
if input.consume_shortcut(&kb_shortcut) {
return Some(command);
Expand All @@ -261,12 +237,12 @@ impl Command {
pub fn menu_button_ui(
self,
ui: &mut egui::Ui,
command_sender: &CommandSender,
command_sender: &impl UICommandSender,
) -> egui::Response {
let button = self.menu_button(ui.ctx());
let response = ui.add(button).on_hover_text(self.tooltip());
if response.clicked() {
command_sender.send(self);
command_sender.send_ui(self);
ui.close_menu();
}
response
Expand Down
Loading

1 comment on commit 370e16f

@github-actions
Copy link

Choose a reason for hiding this comment

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

⚠️ Performance Alert ⚠️

Possible performance regression was detected for benchmark 'Rust Benchmark'.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold 1.25.

Benchmark suite Current: 370e16f Previous: a8acd6e Ratio
datastore/num_rows=1000/num_instances=1000/packed=false/latest_at/default 406 ns/iter (± 1) 305 ns/iter (± 8) 1.33
datastore/num_rows=1000/num_instances=1000/packed=false/latest_at_missing/primary/default 300 ns/iter (± 1) 221 ns/iter (± 4) 1.36
datastore/num_rows=1000/num_instances=1000/packed=false/latest_at_missing/secondaries/default 457 ns/iter (± 2) 346 ns/iter (± 7) 1.32
datastore/num_rows=1000/num_instances=1000/gc/default 2661373 ns/iter (± 13221) 1733174 ns/iter (± 8982) 1.54
mono_points_arrow/generate_message_bundles 38377223 ns/iter (± 1157064) 28737727 ns/iter (± 792245) 1.34
mono_points_arrow/decode_message_bundles 81090188 ns/iter (± 1514880) 59158669 ns/iter (± 817639) 1.37
mono_points_arrow_batched/generate_message_bundles 25702693 ns/iter (± 1621050) 18376769 ns/iter (± 387734) 1.40
mono_points_arrow_batched/generate_messages 5357422 ns/iter (± 245584) 3687140 ns/iter (± 65741) 1.45
mono_points_arrow_batched/encode_total 32655825 ns/iter (± 1815826) 23707693 ns/iter (± 145905) 1.38
mono_points_arrow_batched/decode_log_msg 530256 ns/iter (± 1944) 327507 ns/iter (± 584) 1.62
mono_points_arrow_batched/decode_message_bundles 9540767 ns/iter (± 230940) 7497323 ns/iter (± 8623) 1.27
mono_points_arrow_batched/decode_total 10056395 ns/iter (± 193135) 7818523 ns/iter (± 63396) 1.29
batch_points_arrow/decode_log_msg 71744 ns/iter (± 371) 47574 ns/iter (± 121) 1.51
batch_points_arrow/decode_total 78827 ns/iter (± 321) 53131 ns/iter (± 142) 1.48
arrow_mono_points/insert 2855433967 ns/iter (± 16990142) 1774794135 ns/iter (± 4602343) 1.61
arrow_mono_points/query 1342722 ns/iter (± 16368) 932112 ns/iter (± 2177) 1.44
arrow_batch_points/query 17059 ns/iter (± 126) 12094 ns/iter (± 2) 1.41
arrow_batch_vecs/query 477252 ns/iter (± 2097) 316298 ns/iter (± 185) 1.51

This comment was automatically generated by workflow using github-action-benchmark.

Please sign in to comment.