re_viewer_context/command_sender.rs
1use re_chunk::EntityPath;
2use re_chunk_store::external::re_chunk::Chunk;
3use re_data_source::LogDataSource;
4use re_log_types::StoreId;
5use re_ui::{UICommand, UICommandSender};
6
7use crate::{RecordingOrTable, time_control::TimeControlCommand};
8
9// ----------------------------------------------------------------------------
10
11/// Commands used by internal system components
12// TODO(jleibs): Is there a better crate for this?
13#[derive(strum_macros::IntoStaticStr)]
14pub enum SystemCommand {
15    /// Make this the active application.
16    ActivateApp(re_log_types::ApplicationId),
17
18    /// Close this app and all its recordings.
19    CloseApp(re_log_types::ApplicationId),
20
21    /// Load data from a given data source.
22    ///
23    /// Will not load any new data if the source is already one of the active data sources.
24    LoadDataSource(LogDataSource),
25
26    /// Add a new receiver for log messages.
27    AddReceiver(re_smart_channel::Receiver<re_log_types::DataSourceMessage>),
28
29    /// Add a new server to the redap browser.
30    AddRedapServer(re_uri::Origin),
31
32    ChangeDisplayMode(crate::DisplayMode),
33
34    /// Sets the display mode to what it is at startup.
35    ResetDisplayMode,
36
37    /// Reset the `Viewer` to the default state
38    ResetViewer,
39
40    /// Clear the active blueprint.
41    ///
42    /// This may have two outcomes:
43    /// - If a default blueprint is set, it will be used.
44    /// - Otherwise, the heuristics will be enabled.
45    ///
46    /// To force using the heuristics, use [`Self::ClearActiveBlueprintAndEnableHeuristics`].
47    ///
48    /// UI note: because of the above ambiguity, controls for this command should only be enabled if
49    /// a default blueprint is set or the behavior is explicitly explained.
50    ClearActiveBlueprint,
51
52    /// Clear the active blueprint and enable heuristics.
53    ///
54    /// The final outcome of this is to set the active blueprint to the heuristics. This command
55    /// does not affect the default blueprint if any was set.
56    ClearActiveBlueprintAndEnableHeuristics,
57
58    /// Switch to this [`RecordingOrTable`].
59    ActivateRecordingOrTable(RecordingOrTable),
60
61    /// Close an [`RecordingOrTable`] and free its memory.
62    CloseRecordingOrTable(RecordingOrTable),
63
64    /// Close all stores and show the welcome screen again.
65    CloseAllEntries,
66
67    /// Add more data to a store (blueprint or recording).
68    ///
69    /// Edit recordings with case: we generally regard recordings as immutable.
70    ///
71    /// For blueprints,the [`StoreId`] should generally be the currently selected blueprint.
72    ///
73    /// Instead of using this directly, consider using `save_blueprint_archetype` or similar.
74    AppendToStore(StoreId, Vec<Chunk>),
75
76    UndoBlueprint {
77        blueprint_id: StoreId,
78    },
79    RedoBlueprint {
80        blueprint_id: StoreId,
81    },
82
83    /// Drop a specific entity from a store.
84    ///
85    /// Also drops all recursive children.
86    ///
87    /// The [`StoreId`] should generally be the currently selected blueprint
88    /// but is tracked manually to ensure self-consistency if the blueprint
89    /// is both modified and changed in the same frame.
90    DropEntity(StoreId, EntityPath),
91
92    /// Show a timeline of the blueprint data.
93    #[cfg(debug_assertions)]
94    EnableInspectBlueprintTimeline(bool),
95
96    /// Navigate to time/entities/anchors/etc. that are set in a [`re_uri::Fragment`].
97    SetUrlFragment {
98        store_id: StoreId,
99        fragment: re_uri::Fragment,
100    },
101
102    /// Copies the given url to the clipboard.
103    ///
104    /// On web this adds the viewer url as the base url.
105    CopyViewerUrl(String),
106
107    /// Set the item selection.
108    SetSelection(crate::ItemCollection),
109
110    TimeControlCommands {
111        store_id: StoreId,
112        time_commands: Vec<TimeControlCommand>,
113    },
114
115    /// Sets the focus to the given item.
116    ///
117    /// The focused item is cleared out every frame.
118    /// Focusing is triggered either explicitly by ui-elements saying so
119    /// or by double-clicking on a button representing an item.
120    ///
121    /// Unlike item selection, item focusing is not global state.
122    /// It may however have stateful effects in certain views,
123    /// e.g. the 3D view may follow the last focused item as it moves,
124    /// or a frame may be highlighted for a few frames.
125    ///
126    /// Just like selection highlighting, the exact behavior of focusing is up to the receiving views.
127    SetFocus(crate::Item),
128
129    /// Show a notification to the user
130    ShowNotification(re_ui::notifications::Notification),
131
132    /// Add a task, run on a background thread, that saves something to disk.
133    #[cfg(not(target_arch = "wasm32"))]
134    FileSaver(Box<dyn FnOnce() -> anyhow::Result<std::path::PathBuf> + Send + 'static>),
135}
136
137impl SystemCommand {
138    pub fn clear_selection() -> Self {
139        Self::SetSelection(crate::ItemCollection::default())
140    }
141}
142
143impl std::fmt::Debug for SystemCommand {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        // not all variant contents can be made `Debug`, so we only output the variant name
146        f.write_str(self.into())
147    }
148}
149
150/// Interface for sending [`SystemCommand`] messages.
151pub trait SystemCommandSender {
152    fn send_system(&self, command: SystemCommand);
153}
154
155// ----------------------------------------------------------------------------
156
157/// Sender that queues up the execution of commands.
158#[derive(Clone)]
159pub struct CommandSender {
160    system_sender: std::sync::mpsc::Sender<SystemCommand>,
161    ui_sender: std::sync::mpsc::Sender<UICommand>,
162}
163
164/// Receiver for the [`CommandSender`]
165pub struct CommandReceiver {
166    system_receiver: std::sync::mpsc::Receiver<SystemCommand>,
167    ui_receiver: std::sync::mpsc::Receiver<UICommand>,
168}
169
170impl CommandReceiver {
171    /// Receive a [`SystemCommand`] to be executed if any is queued.
172    pub fn recv_system(&self) -> Option<SystemCommand> {
173        // The only way this can fail (other than being empty)
174        // is if the sender has been dropped.
175        self.system_receiver.try_recv().ok()
176    }
177
178    /// Receive a [`UICommand`] to be executed if any is queued.
179    pub fn recv_ui(&self) -> Option<UICommand> {
180        // The only way this can fail (other than being empty)
181        // is if the sender has been dropped.
182        self.ui_receiver.try_recv().ok()
183    }
184}
185
186/// Creates a new command channel.
187pub fn command_channel() -> (CommandSender, CommandReceiver) {
188    let (system_sender, system_receiver) = std::sync::mpsc::channel();
189    let (ui_sender, ui_receiver) = std::sync::mpsc::channel();
190    (
191        CommandSender {
192            system_sender,
193            ui_sender,
194        },
195        CommandReceiver {
196            system_receiver,
197            ui_receiver,
198        },
199    )
200}
201
202// ----------------------------------------------------------------------------
203
204impl SystemCommandSender for CommandSender {
205    /// Send a command to be executed.
206    fn send_system(&self, command: SystemCommand) {
207        // The only way this can fail is if the receiver has been dropped.
208        self.system_sender.send(command).ok();
209    }
210}
211
212impl UICommandSender for CommandSender {
213    /// Send a command to be executed.
214    fn send_ui(&self, command: UICommand) {
215        // The only way this can fail is if the receiver has been dropped.
216        self.ui_sender.send(command).ok();
217    }
218}