use std::collections::{BTreeMap, BTreeSet};
use egui::NumExt as _;
use re_data_store::TimesPerTimeline;
use re_log_types::{Duration, TimeInt, TimeRange, TimeRangeF, TimeReal, TimeType, Timeline};
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)]
pub struct TimeView {
pub min: TimeReal,
pub time_spanned: f64,
}
#[derive(Clone, Copy, Debug, serde::Deserialize, serde::Serialize)]
struct TimeState {
time: TimeReal,
fps: f32,
#[serde(default)]
loop_selection: Option<TimeRangeF>,
#[serde(default)]
view: Option<TimeView>,
}
impl TimeState {
fn new(time: impl Into<TimeReal>) -> Self {
Self {
time: time.into(),
fps: 30.0, loop_selection: Default::default(),
view: None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)]
pub enum Looping {
Off,
Selection,
All,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Deserialize, serde::Serialize)]
pub enum PlayState {
Paused,
Playing,
Following,
}
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(default)]
pub struct TimeControl {
timeline: Timeline,
states: BTreeMap<Timeline, TimeState>,
playing: bool,
following: bool,
speed: f32,
looping: Looping,
}
impl Default for TimeControl {
fn default() -> Self {
Self {
timeline: Default::default(),
states: Default::default(),
playing: true,
following: true,
speed: 1.0,
looping: Looping::Off,
}
}
}
impl TimeControl {
pub fn move_time(&mut self, egui_ctx: &egui::Context, times_per_timeline: &TimesPerTimeline) {
self.select_a_valid_timeline(times_per_timeline);
let Some(full_range) = self.full_range(times_per_timeline) else {
return;
};
match self.play_state() {
PlayState::Paused => {}
PlayState::Playing => {
let dt = egui_ctx.input(|i| i.stable_dt).at_most(0.1) * self.speed;
let state = self
.states
.entry(self.timeline)
.or_insert_with(|| TimeState::new(full_range.min));
if self.looping == Looping::Off && state.time >= full_range.max {
return;
}
let loop_range = match self.looping {
Looping::Off => None,
Looping::Selection => state.loop_selection,
Looping::All => Some(full_range.into()),
};
if let Some(loop_range) = loop_range {
state.time = state.time.max(loop_range.min);
}
match self.timeline.typ() {
TimeType::Sequence => {
state.time += TimeReal::from(state.fps * dt);
}
TimeType::Time => state.time += TimeReal::from(Duration::from_secs(dt)),
}
egui_ctx.request_repaint();
if let Some(loop_range) = loop_range {
if state.time > loop_range.max {
state.time = loop_range.min;
}
}
}
PlayState::Following => {
match self.states.entry(self.timeline) {
std::collections::btree_map::Entry::Vacant(entry) => {
entry.insert(TimeState::new(full_range.max));
}
std::collections::btree_map::Entry::Occupied(mut entry) => {
entry.get_mut().time = full_range.max.into();
}
}
}
}
}
pub fn play_state(&self) -> PlayState {
if self.playing {
if self.following {
PlayState::Following
} else {
PlayState::Playing
}
} else {
PlayState::Paused
}
}
pub fn looping(&self) -> Looping {
if self.play_state() == PlayState::Following {
Looping::Off
} else {
self.looping
}
}
pub fn set_looping(&mut self, looping: Looping) {
self.looping = looping;
if self.looping != Looping::Off {
self.following = false;
}
}
pub fn set_play_state(&mut self, times_per_timeline: &TimesPerTimeline, play_state: PlayState) {
match play_state {
PlayState::Paused => {
self.playing = false;
}
PlayState::Playing => {
self.playing = true;
self.following = false;
if let Some(time_points) = times_per_timeline.get(&self.timeline) {
if let Some(state) = self.states.get_mut(&self.timeline) {
if max(time_points) <= state.time {
state.time = min(time_points).into();
}
} else {
self.states
.insert(self.timeline, TimeState::new(min(time_points)));
}
}
}
PlayState::Following => {
self.playing = true;
self.following = true;
if let Some(time_points) = times_per_timeline.get(&self.timeline) {
match self.states.entry(self.timeline) {
std::collections::btree_map::Entry::Vacant(entry) => {
entry.insert(TimeState::new(max(time_points)));
}
std::collections::btree_map::Entry::Occupied(mut entry) => {
entry.get_mut().time = max(time_points).into();
}
}
}
}
}
}
pub fn pause(&mut self) {
self.playing = false;
}
pub fn step_time_back(&mut self, times_per_timeline: &TimesPerTimeline) {
let Some(time_values) = times_per_timeline.get(self.timeline()) else { return; };
self.pause();
if let Some(time) = self.time() {
#[allow(clippy::collapsible_else_if)]
let new_time = if let Some(loop_range) = self.active_loop_selection() {
step_back_time_looped(time, time_values, &loop_range)
} else {
step_back_time(time, time_values).into()
};
self.set_time(new_time);
}
}
pub fn step_time_fwd(&mut self, times_per_timeline: &TimesPerTimeline) {
let Some(time_values) = times_per_timeline.get(self.timeline()) else { return; };
self.pause();
if let Some(time) = self.time() {
#[allow(clippy::collapsible_else_if)]
let new_time = if let Some(loop_range) = self.active_loop_selection() {
step_fwd_time_looped(time, time_values, &loop_range)
} else {
step_fwd_time(time, time_values).into()
};
self.set_time(new_time);
}
}
pub fn toggle_play_pause(&mut self, times_per_timeline: &TimesPerTimeline) {
#[allow(clippy::collapsible_else_if)]
if self.playing {
self.pause();
} else {
if let Some(time_points) = times_per_timeline.get(&self.timeline) {
if let Some(state) = self.states.get_mut(&self.timeline) {
if max(time_points) <= state.time {
state.time = min(time_points).into();
self.playing = true;
self.following = false;
return;
}
}
}
if self.following {
self.set_play_state(times_per_timeline, PlayState::Following);
} else {
self.set_play_state(times_per_timeline, PlayState::Playing);
}
}
}
pub fn speed(&self) -> f32 {
self.speed
}
pub fn set_speed(&mut self, speed: f32) {
self.speed = speed;
}
pub fn fps(&self) -> Option<f32> {
self.states.get(&self.timeline).map(|state| state.fps)
}
pub fn set_fps(&mut self, fps: f32) {
if let Some(state) = self.states.get_mut(&self.timeline) {
state.fps = fps;
}
}
pub fn select_a_valid_timeline(&mut self, times_per_timeline: &TimesPerTimeline) {
for timeline in times_per_timeline.timelines() {
if &self.timeline == timeline {
return; }
}
if let Some(timeline) = default_time_line(times_per_timeline.timelines()) {
self.timeline = *timeline;
} else {
self.timeline = Default::default();
}
}
pub fn timeline(&self) -> &Timeline {
&self.timeline
}
pub fn time_type(&self) -> TimeType {
self.timeline.typ()
}
pub fn set_timeline(&mut self, timeline: Timeline) {
self.timeline = timeline;
}
pub fn time(&self) -> Option<TimeReal> {
self.states.get(&self.timeline).map(|state| state.time)
}
pub fn time_int(&self) -> Option<TimeInt> {
self.time().map(|t| t.floor())
}
pub fn time_i64(&self) -> Option<i64> {
self.time().map(|t| t.floor().as_i64())
}
pub fn current_query(&self) -> re_arrow_store::LatestAtQuery {
re_arrow_store::LatestAtQuery::new(
self.timeline,
self.time().map_or(TimeInt::MAX, |t| t.floor()),
)
}
pub fn active_loop_selection(&self) -> Option<TimeRangeF> {
if self.looping == Looping::Selection {
self.states.get(&self.timeline)?.loop_selection
} else {
None
}
}
pub fn full_range(&self, times_per_timeline: &TimesPerTimeline) -> Option<TimeRange> {
times_per_timeline.get(&self.timeline).map(range)
}
pub fn loop_selection(&self) -> Option<TimeRangeF> {
self.states.get(&self.timeline)?.loop_selection
}
pub fn set_loop_selection(&mut self, selection: TimeRangeF) {
self.states
.entry(self.timeline)
.or_insert_with(|| TimeState::new(selection.min))
.loop_selection = Some(selection);
}
pub fn remove_loop_selection(&mut self) {
if let Some(state) = self.states.get_mut(&self.timeline) {
state.loop_selection = None;
}
if self.looping() == Looping::Selection {
self.set_looping(Looping::Off);
}
}
pub fn is_time_selected(&self, timeline: &Timeline, needle: TimeInt) -> bool {
if timeline != &self.timeline {
return false;
}
if let Some(state) = self.states.get(&self.timeline) {
state.time.floor() == needle
} else {
false
}
}
pub fn set_timeline_and_time(&mut self, timeline: Timeline, time: impl Into<TimeReal>) {
self.timeline = timeline;
self.set_time(time);
}
pub fn set_time(&mut self, time: impl Into<TimeReal>) {
let time = time.into();
self.states
.entry(self.timeline)
.or_insert_with(|| TimeState::new(time))
.time = time;
}
pub(crate) fn time_view(&self) -> Option<TimeView> {
self.states.get(&self.timeline).and_then(|state| state.view)
}
pub(crate) fn set_time_view(&mut self, view: TimeView) {
self.states
.entry(self.timeline)
.or_insert_with(|| TimeState::new(view.min))
.view = Some(view);
}
pub fn reset_time_view(&mut self) {
if let Some(state) = self.states.get_mut(&self.timeline) {
state.view = None;
}
}
}
fn min(values: &BTreeSet<TimeInt>) -> TimeInt {
*values.iter().next().unwrap()
}
fn max(values: &BTreeSet<TimeInt>) -> TimeInt {
*values.iter().rev().next().unwrap()
}
fn range(values: &BTreeSet<TimeInt>) -> TimeRange {
TimeRange::new(min(values), max(values))
}
fn default_time_line<'a>(timelines: impl Iterator<Item = &'a Timeline>) -> Option<&'a Timeline> {
let mut log_time_timeline = None;
for timeline in timelines {
if timeline.name().as_str() == "log_time" {
log_time_timeline = Some(timeline);
} else {
return Some(timeline); }
}
log_time_timeline
}
fn step_fwd_time(time: TimeReal, values: &BTreeSet<TimeInt>) -> TimeInt {
if let Some(next) = values
.range((
std::ops::Bound::Excluded(time.floor()),
std::ops::Bound::Unbounded,
))
.next()
{
*next
} else {
min(values)
}
}
fn step_back_time(time: TimeReal, values: &BTreeSet<TimeInt>) -> TimeInt {
if let Some(previous) = values.range(..time.ceil()).rev().next() {
*previous
} else {
max(values)
}
}
fn step_fwd_time_looped(
time: TimeReal,
values: &BTreeSet<TimeInt>,
loop_range: &TimeRangeF,
) -> TimeReal {
if time < loop_range.min || loop_range.max <= time {
loop_range.min
} else if let Some(next) = values
.range((
std::ops::Bound::Excluded(time.floor()),
std::ops::Bound::Included(loop_range.max.floor()),
))
.next()
{
TimeReal::from(*next)
} else {
step_fwd_time(time, values).into()
}
}
fn step_back_time_looped(
time: TimeReal,
values: &BTreeSet<TimeInt>,
loop_range: &TimeRangeF,
) -> TimeReal {
if time <= loop_range.min || loop_range.max < time {
loop_range.max
} else if let Some(previous) = values
.range(loop_range.min.ceil()..time.ceil())
.rev()
.next()
{
TimeReal::from(*previous)
} else {
step_back_time(time, values).into()
}
}