Skip to content

Commit 1470937

Browse files
authored
Add share link button to time panel context menu (#11186)
### Related - Part of #10817 - Based on #11183 ### What Adds share buttons to timeline context menus (left click) as described in #10817 - [x] Look into adding keybind and keybind in ui (leave for future PR)
1 parent d7b6e57 commit 1470937

File tree

22 files changed

+506
-419
lines changed

22 files changed

+506
-419
lines changed

.typos.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ analogue = "analog"
3434
analyse = "analyze"
3535
appetiser = "appetizer"
3636
arbour = "arbor"
37-
ardour = "arbor"
37+
ardor = "arbor"
3838
armour = "armor"
3939
artefact = "artifact"
4040
authorise = "authorize"
@@ -175,4 +175,6 @@ extend-ignore-re = [
175175
"cros-codecs",
176176

177177
"muh_scalars", # Weird name introduced in `crates/viewer/re_view_time_series/tests/basic.rs` introduced in https://github.com/rerun-io/rerun/pull/10713
178+
179+
"isse", # Name
178180
]

Cargo.lock

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9071,7 +9071,6 @@ dependencies = [
90719071
"thiserror 1.0.69",
90729072
"tokio",
90739073
"url",
9074-
"vec1",
90759074
"wasm-bindgen",
90769075
"wasm-bindgen-futures",
90779076
"web-sys",
@@ -9110,10 +9109,12 @@ dependencies = [
91109109
"re_byte_size",
91119110
"re_chunk",
91129111
"re_chunk_store",
9112+
"re_data_source",
91139113
"re_entity_db",
91149114
"re_format",
91159115
"re_global_context",
91169116
"re_log",
9117+
"re_log_encoding",
91179118
"re_log_types",
91189119
"re_query",
91199120
"re_renderer",
@@ -9131,6 +9132,7 @@ dependencies = [
91319132
"static_assertions",
91329133
"thiserror 1.0.69",
91339134
"tokio",
9135+
"vec1",
91349136
"wasm-bindgen-futures",
91359137
"web-sys",
91369138
"wgpu",

crates/viewer/re_global_context/src/command_sender.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,10 @@ pub enum SystemCommand {
9696
fragment: re_uri::Fragment,
9797
},
9898

99-
/// Copies an url to the clipboard to the given a display mode, selection range, and url fragment.
100-
CopyUrlWithContext {
101-
display_mode: crate::DisplayMode,
102-
time_range: Option<re_uri::TimeSelection>,
103-
fragment: re_uri::Fragment,
104-
},
99+
/// Copies the given url to the clipboard.
100+
///
101+
/// On web this adds the viewer url as the base url.
102+
CopyViewerUrl(String),
105103

106104
/// Set the item selection.
107105
SetSelection(crate::ItemCollection),

crates/viewer/re_redap_browser/src/lib.rs

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,7 @@ pub use self::{
1616
use re_uri::Scheme;
1717
use std::sync::LazyLock;
1818

19-
/// Origin used to show the examples ui in the redap browser.
20-
///
21-
/// Not actually a valid origin.
22-
pub static EXAMPLES_ORIGIN: LazyLock<re_uri::Origin> = LazyLock::new(|| re_uri::Origin {
23-
scheme: Scheme::RerunHttps,
24-
host: url::Host::Domain(String::from("_examples.rerun.io")),
25-
port: 443,
26-
});
19+
pub use re_viewer_context::open_url::EXAMPLES_ORIGIN;
2720

2821
/// Origin used to show the local ui in the redap browser.
2922
///

crates/viewer/re_test_context/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -588,8 +588,8 @@ impl TestContext {
588588
// This adds new system commands, which will be handled later in the loop.
589589
self.go_to_dataset_data(store_id, fragment);
590590
}
591-
SystemCommand::CopyUrlWithContext { .. } => {
592-
// Ignore copying to clipboard here.
591+
SystemCommand::CopyViewerUrl(_) => {
592+
// Ignore this trying to copy to the clipboard.
593593
}
594594
SystemCommand::AppendToStore(store_id, chunks) => {
595595
let store_hub = self.store_hub.get_mut();

crates/viewer/re_time_panel/src/time_control_ui.rs

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ impl TimeControlUi {
2525
ui.visuals_mut().widgets.hovered.expansion = 0.0;
2626
ui.visuals_mut().widgets.open.expansion = 0.0;
2727

28-
egui::ComboBox::from_id_salt("timeline")
28+
let response = egui::ComboBox::from_id_salt("timeline")
2929
.selected_text(time_control.timeline().name().as_str())
3030
.show_ui(ui, |ui| {
3131
for timeline_stats in times_per_timeline.timelines_with_stats() {
@@ -75,6 +75,28 @@ You can also define your own timelines, e.g. for sensor time or camera frame num
7575
true,
7676
);
7777
});
78+
});
79+
// Sort of an inline of the `egui::Response::context_menu` function.
80+
// This is required to assign an id to the context menu, which would
81+
// otherwise conflict with the popup of this `ComboBox`'s popup menu.
82+
egui::Popup::menu(&response)
83+
.id(egui::Id::new("timeline select context menu"))
84+
.open_memory(if response.secondary_clicked() {
85+
Some(egui::SetOpenCommand::Bool(true))
86+
} else if response.clicked() {
87+
// Explicitly close the menu if the widget was clicked
88+
// Without this, the context menu would stay open if the user clicks the widget
89+
Some(egui::SetOpenCommand::Bool(false))
90+
} else {
91+
None
92+
})
93+
.at_pointer_fixed()
94+
.show(|ui| {
95+
if ui.button("Copy timeline name").clicked() {
96+
let timeline = format!("{}", time_control.timeline().name());
97+
re_log::info!("Copied timeline: {}", timeline);
98+
ui.ctx().copy_text(timeline);
99+
}
78100
})
79101
});
80102
}

crates/viewer/re_time_panel/src/time_panel.rs

Lines changed: 121 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ use re_types::reflection::ComponentDescriptorExt as _;
1818
use re_types_core::ComponentDescriptor;
1919
use re_ui::{ContextExt as _, DesignTokens, Help, UiExt as _, filter_widget, icons, list_item};
2020
use re_ui::{IconText, filter_widget::format_matching_text};
21+
use re_viewer_context::open_url::ViewerOpenUrl;
2122
use re_viewer_context::{
2223
CollapseScope, HoverHighlight, Item, ItemCollection, ItemContext, RecordingConfig,
2324
SystemCommand, SystemCommandSender as _, TimeControl, TimeView, UiLayout, ViewerContext,
@@ -567,6 +568,7 @@ impl TimePanel {
567568
&self.time_ranges_ui,
568569
time_ctrl,
569570
ui,
571+
ctx,
570572
Some(&time_area_response),
571573
&time_area_painter,
572574
&timeline_rect,
@@ -1388,6 +1390,7 @@ impl TimePanel {
13881390
&time_ranges_ui,
13891391
time_ctrl,
13901392
ui,
1393+
ctx,
13911394
None,
13921395
&painter,
13931396
&time_range_rect,
@@ -1404,7 +1407,9 @@ impl TimePanel {
14041407
ui: &mut egui::Ui,
14051408
time_ctrl: &mut TimeControl,
14061409
) {
1407-
if let Some(time_int) = time_ctrl.time_int() {
1410+
if let Some(time_int) = time_ctrl.time_int()
1411+
&& let Some(time) = time_ctrl.time()
1412+
{
14081413
let time_type = time_ctrl.time_type();
14091414

14101415
let mut time_str = self
@@ -1426,11 +1431,11 @@ impl TimePanel {
14261431
}
14271432
self.time_edit_string = None;
14281433
}
1429-
response
1430-
.on_hover_text(format!("Timestamp: {}", time_int.as_i64()))
1431-
.context_menu(|ui| {
1432-
copy_time_properties_context_menu(ui, time_ctrl, None);
1433-
});
1434+
let response = response.on_hover_text(format!("Timestamp: {}", time_int.as_i64()));
1435+
1436+
response.context_menu(|ui| {
1437+
copy_time_properties_context_menu(ui, time);
1438+
});
14341439
}
14351440
}
14361441
}
@@ -1776,29 +1781,79 @@ fn interact_with_streams_rect(
17761781
}
17771782

17781783
/// Context menu that shows up when interacting with the streams rect.
1779-
fn copy_time_properties_context_menu(
1784+
fn copy_timeline_properties_context_menu(
17801785
ui: &mut egui::Ui,
1786+
ctx: &ViewerContext<'_>,
17811787
time_ctrl: &TimeControl,
1782-
hovered_time: Option<TimeReal>,
1788+
hovered_time: TimeReal,
17831789
) {
1784-
if let Some(time) = hovered_time {
1785-
if ui.button("Copy hovered timestamp").clicked() {
1786-
let time = format!("{}", time.floor().as_i64());
1787-
re_log::info!("Copied hovered timestamp: {}", time);
1788-
ui.ctx().copy_text(time);
1789-
}
1790-
} else if let Some(time) = time_ctrl.time_int()
1791-
&& ui.button("Copy current timestamp").clicked()
1790+
let mut url = ViewerOpenUrl::from_context(ctx);
1791+
if let Some(selected_time_range) = time_ctrl.active_loop_selection()
1792+
&& selected_time_range.contains(hovered_time)
17921793
{
1793-
let time = format!("{}", time.as_i64());
1794-
re_log::info!("Copied current timestamp: {}", time);
1794+
let has_time_range = url.as_mut().is_ok_and(|url| url.fragment_mut().is_some());
1795+
let copy_command = url.and_then(|url| url.copy_url_command());
1796+
if ui
1797+
.add_enabled(
1798+
copy_command.is_ok() && has_time_range,
1799+
egui::Button::new("Copy link to trimmed range"),
1800+
)
1801+
.on_disabled_hover_text(if copy_command.is_err() {
1802+
"Can't share links to the current recording"
1803+
} else {
1804+
"The current recording doesn't support time range links"
1805+
})
1806+
.clicked()
1807+
&& let Ok(copy_command) = copy_command
1808+
{
1809+
ctx.command_sender().send_system(copy_command);
1810+
}
1811+
} else {
1812+
let has_fragment = url.as_mut().is_ok_and(|url| {
1813+
if let Some(fragment) = url.fragment_mut() {
1814+
fragment.when = Some((
1815+
*time_ctrl.timeline().name(),
1816+
re_log_types::TimeCell {
1817+
typ: time_ctrl.time_type(),
1818+
value: hovered_time.floor().into(),
1819+
},
1820+
));
1821+
true
1822+
} else {
1823+
false
1824+
}
1825+
});
1826+
let copy_command = url.and_then(|url| url.copy_url_command());
1827+
1828+
if ui
1829+
.add_enabled(
1830+
copy_command.is_ok() && has_fragment,
1831+
egui::Button::new("Copy link to timestamp"),
1832+
)
1833+
.on_disabled_hover_text(if copy_command.is_err() {
1834+
"Can't share links to the current recording"
1835+
} else {
1836+
"The current recording doesn't support time stamp links"
1837+
})
1838+
.clicked()
1839+
&& let Ok(copy_command) = copy_command
1840+
{
1841+
ctx.command_sender().send_system(copy_command);
1842+
}
1843+
}
1844+
1845+
if ui.button("Copy timestamp").clicked() {
1846+
let time = format!("{}", hovered_time.floor().as_i64());
1847+
re_log::info!("Copied hovered timestamp: {}", time);
17951848
ui.ctx().copy_text(time);
17961849
}
1850+
}
17971851

1798-
if ui.button("Copy current timeline name").clicked() {
1799-
let timeline = format!("{}", time_ctrl.timeline().name());
1800-
re_log::info!("Copied current timeline: {}", timeline);
1801-
ui.ctx().copy_text(timeline);
1852+
fn copy_time_properties_context_menu(ui: &mut egui::Ui, time: TimeReal) {
1853+
if ui.button("Copy timestamp").clicked() {
1854+
let time = format!("{}", time.floor().as_i64());
1855+
re_log::info!("Copied hovered timestamp: {}", time);
1856+
ui.ctx().copy_text(time);
18021857
}
18031858
}
18041859

@@ -1807,6 +1862,7 @@ fn time_marker_ui(
18071862
time_ranges_ui: &TimeRangesUi,
18081863
time_ctrl: &mut TimeControl,
18091864
ui: &egui::Ui,
1865+
ctx: &ViewerContext<'_>,
18101866
time_area_response: Option<&egui::Response>,
18111867
time_area_painter: &egui::Painter,
18121868
timeline_rect: &Rect,
@@ -1869,19 +1925,38 @@ fn time_marker_ui(
18691925
let is_pointer_in_timeline_rect =
18701926
ui.ui_contains_pointer() && timeline_rect.contains(pointer_pos);
18711927

1872-
// Show preview?
1873-
if !is_hovering_time_cursor
1928+
let hovered_ctx_id = egui::Id::new("hovered timestamp context");
1929+
1930+
let on_timeline = !is_hovering_time_cursor
18741931
&& !time_area_double_clicked
18751932
&& is_pointer_in_time_area_rect
18761933
&& !is_anything_being_dragged
1877-
&& !is_hovering_the_loop_selection
1934+
&& !is_hovering_the_loop_selection;
1935+
1936+
if on_timeline {
1937+
ui.ctx().set_cursor_icon(timeline_cursor_icon);
1938+
}
1939+
1940+
// Show a preview bar at this position, if we have right-clicked
1941+
// on the time panel we want to still draw the line at the
1942+
// original position.
1943+
let hovered_x_pos = if let Some(hovered_time) =
1944+
ui.ctx().memory(|mem| mem.data.get_temp(hovered_ctx_id))
1945+
&& let Some(x) = time_ranges_ui.x_from_time_f32(hovered_time)
18781946
{
1947+
Some(x)
1948+
} else if on_timeline {
1949+
Some(pointer_pos.x)
1950+
} else {
1951+
None
1952+
};
1953+
1954+
if let Some(x) = hovered_x_pos {
18791955
time_area_painter.vline(
1880-
pointer_pos.x,
1956+
x,
18811957
timeline_rect.top()..=ui.max_rect().bottom(),
18821958
ui.visuals().widgets.noninteractive.fg_stroke,
18831959
);
1884-
ui.ctx().set_cursor_icon(timeline_cursor_icon); // preview!
18851960
}
18861961

18871962
// Click to move time here:
@@ -1920,8 +1995,25 @@ fn time_marker_ui(
19201995
}
19211996
}
19221997

1923-
time_area_response
1924-
.context_menu(|ui| copy_time_properties_context_menu(ui, time_ctrl, hovered_time));
1998+
if let Some(hovered_time) = ui
1999+
.ctx()
2000+
.memory(|mem| mem.data.get_temp(hovered_ctx_id))
2001+
.or(hovered_time)
2002+
{
2003+
if egui::Popup::context_menu(&time_area_response)
2004+
.width(300.0)
2005+
.show(|ui| {
2006+
copy_timeline_properties_context_menu(ui, ctx, time_ctrl, hovered_time);
2007+
})
2008+
.is_some()
2009+
{
2010+
ui.ctx()
2011+
.memory_mut(|mem| mem.data.insert_temp(hovered_ctx_id, hovered_time));
2012+
} else {
2013+
ui.ctx()
2014+
.memory_mut(|mem| mem.data.remove::<TimeReal>(hovered_ctx_id));
2015+
}
2016+
}
19252017
}
19262018
}
19272019

crates/viewer/re_viewer/Cargo.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ serde-wasm-bindgen.workspace = true
140140
tap.workspace = true
141141
thiserror.workspace = true
142142
url.workspace = true
143-
vec1.workspace = true
144143
web-time.workspace = true
145144
wgpu.workspace = true
146145

0 commit comments

Comments
 (0)