use std::ops::RangeInclusive;
use egui::{NumExt as _, Response};
use re_entity_db::TimeHistogram;
use re_log_types::{TimeInt, TimeType, TimeZone};
#[derive(Debug)]
pub struct TimeDragValue {
pub range: RangeInclusive<i64>,
base_time: Option<i64>,
unit_factor: i64,
unit_symbol: &'static str,
abs_range: RangeInclusive<i64>,
rel_range: RangeInclusive<i64>,
}
impl TimeDragValue {
pub fn from_time_histogram(times: &TimeHistogram) -> Self {
Self::from_time_range(
times.min_key().unwrap_or_default()..=times.max_key().unwrap_or_default(),
)
}
pub fn from_time_range(range: RangeInclusive<i64>) -> Self {
let span = range.end() - range.start();
let base_time = time_range_base_time(*range.start(), span);
let (unit_symbol, unit_factor) = unit_from_span(span);
let abs_range =
round_down(*range.start(), unit_factor)..=round_up(*range.end(), unit_factor);
let rel_range = round_down(-span, unit_factor)..=round_up(2 * span, unit_factor);
Self {
range,
base_time,
unit_factor,
unit_symbol,
abs_range,
rel_range,
}
}
pub fn min_time(&self) -> TimeInt {
TimeInt::new_temporal(*self.range.start())
}
pub fn max_time(&self) -> TimeInt {
TimeInt::new_temporal(*self.range.end())
}
pub fn sequence_drag_value_ui(
&self,
ui: &mut egui::Ui,
value: &mut TimeInt,
absolute: bool,
low_bound_override: Option<TimeInt>,
) -> Response {
let mut time_range = if absolute {
self.abs_range.clone()
} else {
self.rel_range.clone()
};
let span = time_range.end() - time_range.start();
let speed = (span as f32 * 0.005).at_least(1.0);
if let Some(low_bound_override) = low_bound_override {
time_range =
low_bound_override.as_i64().at_least(*time_range.start())..=*time_range.end();
}
let mut value_i64 = value.as_i64();
let response = ui.add(
egui::DragValue::new(&mut value_i64)
.range(time_range)
.speed(speed),
);
*value = TimeInt::new_temporal(value_i64);
response
}
pub fn temporal_drag_value_ui(
&self,
ui: &mut egui::Ui,
value: &mut TimeInt,
absolute: bool,
low_bound_override: Option<TimeInt>,
time_zone_for_timestamps: TimeZone,
) -> (Response, Option<Response>) {
let mut time_range = if absolute {
self.abs_range.clone()
} else {
self.rel_range.clone()
};
let factor = self.unit_factor as f32;
let offset = if absolute {
self.base_time.unwrap_or(0)
} else {
0
};
let speed = (time_range.end() - time_range.start()) as f32 / factor * 0.005;
if let Some(low_bound_override) = low_bound_override {
time_range =
low_bound_override.as_i64().at_least(*time_range.start())..=*time_range.end();
}
let mut time_unit = (value.as_i64().saturating_sub(offset)) as f32 / factor;
let time_range = (*time_range.start() - offset) as f32 / factor
..=(*time_range.end() - offset) as f32 / factor;
let base_time_response = if absolute {
self.base_time.map(|base_time| {
ui.label(format!(
"{} + ",
TimeType::Time
.format(TimeInt::new_temporal(base_time), time_zone_for_timestamps)
))
})
} else {
None
};
let drag_value_response = ui.add(
egui::DragValue::new(&mut time_unit)
.range(time_range)
.speed(speed)
.suffix(self.unit_symbol),
);
*value = TimeInt::new_temporal((time_unit * factor).round() as i64 + offset);
(drag_value_response, base_time_response)
}
}
fn unit_from_span(span: i64) -> (&'static str, i64) {
if span / 1_000_000_000 > 0 {
("s", 1_000_000_000)
} else if span / 1_000_000 > 0 {
("ms", 1_000_000)
} else if span / 1_000 > 0 {
("μs", 1_000)
} else {
("ns", 1)
}
}
static SPAN_TO_START_TIME_OFFSET_THRESHOLD: i64 = 10;
fn time_range_base_time(min_time: i64, span: i64) -> Option<i64> {
if min_time <= 0 {
return None;
}
if span.saturating_mul(SPAN_TO_START_TIME_OFFSET_THRESHOLD) < min_time {
let factor = if span / 1_000_000 > 0 {
1_000_000_000
} else if span / 1_000 > 0 {
1_000_000
} else {
1_000
};
Some(min_time - (min_time % factor))
} else {
None
}
}
fn round_down(value: i64, factor: i64) -> i64 {
value - (value.rem_euclid(factor))
}
fn round_up(value: i64, factor: i64) -> i64 {
let val = round_down(value, factor);
if val == value {
val
} else {
val + factor
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_round_down() {
assert_eq!(round_down(2200, 1000), 2000);
assert_eq!(round_down(2000, 1000), 2000);
assert_eq!(round_down(-2200, 1000), -3000);
assert_eq!(round_down(-3000, 1000), -3000);
assert_eq!(round_down(0, 1000), 0);
}
#[test]
fn test_round_up() {
assert_eq!(round_up(2200, 1000), 3000);
assert_eq!(round_up(2000, 1000), 2000);
assert_eq!(round_up(-2200, 1000), -2000);
assert_eq!(round_up(-3000, 1000), -3000);
assert_eq!(round_up(0, 1000), 0);
}
}