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