diff --git a/Cargo.lock b/Cargo.lock index afbe68d..a3d08f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1669,7 +1669,7 @@ dependencies = [ "bytemuck", "byteorder", "color_quant", - "num-rational", + "num-rational 0.4.1", "num-traits", "png", ] @@ -1803,6 +1803,7 @@ dependencies = [ "env_logger", "getrandom", "log", + "percentage", "rand", "rayon", "reqwest", @@ -2066,6 +2067,41 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8536030f9fea7127f841b45bb6243b27255787fb4eb83958aa1ef9d2fdc0c36" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational 0.2.4", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" +dependencies = [ + "autocfg", + "num-traits", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -2076,6 +2112,29 @@ dependencies = [ "num-traits", ] +[[package]] +name = "num-iter" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" +dependencies = [ + "autocfg", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "num-rational" version = "0.4.1" @@ -2301,6 +2360,15 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94" +[[package]] +name = "percentage" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fd23b938276f14057220b707937bcb42fa76dda7560e57a2da30cb52d557937" +dependencies = [ + "num", +] + [[package]] name = "pin-project-lite" version = "0.2.9" diff --git a/Cargo.toml b/Cargo.toml index c2ba4d5..ca10ba1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,7 @@ client = ["dep:reqwest", "dep:url"] server = ["dep:actix-cors", "dep:actix-web"] [dependencies] +percentage = "0.1.0" egui = "0.22.0" egui_extras = "0.22.0" eframe = { version = "0.22.0", default-features = false, features = [ diff --git a/src/app.rs b/src/app.rs index c24d166..f2bb8aa 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,6 +8,7 @@ use egui::{ Align2, Color32, NumExt, Pos2, Rect, RichText, ScrollArea, Slider, Stroke, TextStyle, Vec2, }; use egui_extras::{Column, TableBuilder}; +use percentage::{Percentage, PercentageInteger}; use serde::{Deserialize, Serialize}; use crate::data::{ @@ -1669,6 +1670,11 @@ impl Window { } } +enum PanDirection { + Left, + Right, +} + impl ProfApp { /// Called once before the first frame. pub fn new( @@ -1711,6 +1717,19 @@ impl ProfApp { result } + fn pan(cx: &mut Context, percent: PercentageInteger, dir: PanDirection) { + if percent.value() == 0 { + return; + } + + let duration: i64 = percent.apply_to(cx.view_interval.duration_ns()); + let sign: i64 = match dir { + PanDirection::Left => -1, + PanDirection::Right => 1, + }; + cx.view_interval = cx.view_interval.translate(duration * sign) + } + fn zoom(cx: &mut Context, interval: Interval) { if cx.view_interval == interval { return; @@ -1783,6 +1802,7 @@ impl ProfApp { UndoZoom, RedoZoom, ResetZoom, + Pan(PercentageInteger, PanDirection), ExpandVertical, ShrinkVertical, ResetVertical, @@ -1814,8 +1834,20 @@ impl ProfApp { } else { Actions::NoAction } + } else if i.modifiers.shift { + if i.key_pressed(egui::Key::ArrowLeft) { + Actions::Pan(Percentage::from(1), PanDirection::Left) + } else if i.key_pressed(egui::Key::ArrowRight) { + Actions::Pan(Percentage::from(1), PanDirection::Right) + } else { + Actions::NoAction + } } else if i.key_pressed(egui::Key::H) { Actions::ToggleControls + } else if i.key_pressed(egui::Key::ArrowLeft) { + Actions::Pan(Percentage::from(5), PanDirection::Left) + } else if i.key_pressed(egui::Key::ArrowRight) { + Actions::Pan(Percentage::from(5), PanDirection::Right) } else { Actions::NoAction } @@ -1826,6 +1858,7 @@ impl ProfApp { Actions::UndoZoom => ProfApp::undo_zoom(cx), Actions::RedoZoom => ProfApp::redo_zoom(cx), Actions::ResetZoom => ProfApp::zoom(cx, cx.total_interval), + Actions::Pan(percent, dir) => ProfApp::pan(cx, percent, dir), Actions::ExpandVertical => ProfApp::multiply_scale_factor(cx, 2.0), Actions::ShrinkVertical => ProfApp::multiply_scale_factor(cx, 0.5), Actions::ResetVertical => ProfApp::reset_scale_factor(cx), @@ -1966,6 +1999,8 @@ impl ProfApp { }); }; show_row("Zoom to Interval", "Click and Drag"); + show_row("Pan 5%", "Left/Right Arrow"); + show_row("Pan 1%", "Shift + Left/Right Arrow"); show_row("Zoom In", "Ctrl + Plus/Equals"); show_row("Zoom Out", "Ctrl + Minus"); show_row("Undo Zoom", "Ctrl + Left Arrow"); diff --git a/src/timestamp.rs b/src/timestamp.rs index 0d470e8..c492d54 100644 --- a/src/timestamp.rs +++ b/src/timestamp.rs @@ -164,148 +164,187 @@ impl Interval { stop: Timestamp(self.stop.0 + duration_ns), } } + // Translate interval by duration_ns on both sides. + pub fn translate(self, duration_ns: i64) -> Self { + Self { + start: Timestamp(self.start.0 + duration_ns), + stop: Timestamp(self.stop.0 + duration_ns), + } + } } #[cfg(test)] mod tests { use super::*; - #[test] - fn test_s() { - assert_eq!(Timestamp::parse("123.4 s"), Ok(Timestamp(123_400_000_000))); - } + mod timestamp { + use super::*; - #[test] - fn test_ms() { - assert_eq!(Timestamp::parse("234.5 ms"), Ok(Timestamp(234_500_000))); - } + #[test] + fn test_s() { + assert_eq!(Timestamp::parse("123.4 s"), Ok(Timestamp(123_400_000_000))); + } - #[test] - fn test_us() { - assert_eq!(Timestamp::parse("345.6 us"), Ok(Timestamp(345_600))); - } + #[test] + fn test_ms() { + assert_eq!(Timestamp::parse("234.5 ms"), Ok(Timestamp(234_500_000))); + } - #[test] - fn test_ns() { - assert_eq!(Timestamp::parse("567.0 ns"), Ok(Timestamp(567))); - } + #[test] + fn test_us() { + assert_eq!(Timestamp::parse("345.6 us"), Ok(Timestamp(345_600))); + } - #[test] - fn test_s_upper() { - assert_eq!(Timestamp::parse("123.4 S"), Ok(Timestamp(123_400_000_000))); - } + #[test] + fn test_ns() { + assert_eq!(Timestamp::parse("567.0 ns"), Ok(Timestamp(567))); + } - #[test] - fn test_ms_upper() { - assert_eq!(Timestamp::parse("234.5 MS"), Ok(Timestamp(234_500_000))); - } + #[test] + fn test_s_upper() { + assert_eq!(Timestamp::parse("123.4 S"), Ok(Timestamp(123_400_000_000))); + } - #[test] - fn test_us_upper() { - assert_eq!(Timestamp::parse("345.6 US"), Ok(Timestamp(345_600))); - } + #[test] + fn test_ms_upper() { + assert_eq!(Timestamp::parse("234.5 MS"), Ok(Timestamp(234_500_000))); + } - #[test] - fn test_ns_upper() { - assert_eq!(Timestamp::parse("567.0 NS"), Ok(Timestamp(567))); - } + #[test] + fn test_us_upper() { + assert_eq!(Timestamp::parse("345.6 US"), Ok(Timestamp(345_600))); + } - #[test] - fn test_s_nospace() { - assert_eq!(Timestamp::parse("123.4s"), Ok(Timestamp(123_400_000_000))); - } + #[test] + fn test_ns_upper() { + assert_eq!(Timestamp::parse("567.0 NS"), Ok(Timestamp(567))); + } - #[test] - fn test_ms_nospace() { - assert_eq!(Timestamp::parse("234.5ms"), Ok(Timestamp(234_500_000))); - } + #[test] + fn test_s_nospace() { + assert_eq!(Timestamp::parse("123.4s"), Ok(Timestamp(123_400_000_000))); + } - #[test] - fn test_us_nospace() { - assert_eq!(Timestamp::parse("345.6us"), Ok(Timestamp(345_600))); - } + #[test] + fn test_ms_nospace() { + assert_eq!(Timestamp::parse("234.5ms"), Ok(Timestamp(234_500_000))); + } - #[test] - fn test_ns_nospace() { - assert_eq!(Timestamp::parse("567.0ns"), Ok(Timestamp(567))); - } + #[test] + fn test_us_nospace() { + assert_eq!(Timestamp::parse("345.6us"), Ok(Timestamp(345_600))); + } - #[test] - fn test_s_spaces() { - assert_eq!( - Timestamp::parse(" 123.4 s "), - Ok(Timestamp(123_400_000_000)) - ); - } + #[test] + fn test_ns_nospace() { + assert_eq!(Timestamp::parse("567.0ns"), Ok(Timestamp(567))); + } - #[test] - fn test_ms_spaces() { - assert_eq!( - Timestamp::parse(" 234.5 ms "), - Ok(Timestamp(234_500_000)) - ); - } + #[test] + fn test_s_spaces() { + assert_eq!( + Timestamp::parse(" 123.4 s "), + Ok(Timestamp(123_400_000_000)) + ); + } - #[test] - fn test_us_spaces() { - assert_eq!(Timestamp::parse(" 345.6 us "), Ok(Timestamp(345_600))); - } + #[test] + fn test_ms_spaces() { + assert_eq!( + Timestamp::parse(" 234.5 ms "), + Ok(Timestamp(234_500_000)) + ); + } - #[test] - fn test_ns_spaces() { - assert_eq!(Timestamp::parse(" 567.0 ns "), Ok(Timestamp(567))); - } + #[test] + fn test_us_spaces() { + assert_eq!(Timestamp::parse(" 345.6 us "), Ok(Timestamp(345_600))); + } - #[test] - fn test_no_unit() { - assert_eq!(Timestamp::parse("500.0"), Err(TimestampParseError::NoUnit)); - } + #[test] + fn test_ns_spaces() { + assert_eq!(Timestamp::parse(" 567.0 ns "), Ok(Timestamp(567))); + } - #[test] - fn test_no_value() { - assert_eq!( - Timestamp::parse("ms"), - Err(TimestampParseError::InvalidValue) - ); - } + #[test] + fn test_no_unit() { + assert_eq!(Timestamp::parse("500.0"), Err(TimestampParseError::NoUnit)); + } - #[test] - fn test_invalid_unit() { - assert_eq!( - Timestamp::parse("500.0 foo"), - Err(TimestampParseError::InvalidUnit) - ); - } + #[test] + fn test_no_value() { + assert_eq!( + Timestamp::parse("ms"), + Err(TimestampParseError::InvalidValue) + ); + } - #[test] - fn test_invalid_value() { - assert_eq!( - Timestamp::parse("foo ms"), - Err(TimestampParseError::InvalidValue) - ); - } + #[test] + fn test_invalid_unit() { + assert_eq!( + Timestamp::parse("500.0 foo"), + Err(TimestampParseError::InvalidUnit) + ); + } - #[test] - fn test_invalid_value2() { - assert_eq!( - Timestamp::parse("500.0.0 ms"), - Err(TimestampParseError::InvalidValue) - ); - } + #[test] + fn test_invalid_value() { + assert_eq!( + Timestamp::parse("foo ms"), + Err(TimestampParseError::InvalidValue) + ); + } + + #[test] + fn test_invalid_value2() { + assert_eq!( + Timestamp::parse("500.0.0 ms"), + Err(TimestampParseError::InvalidValue) + ); + } - #[test] - fn test_invalid_value3() { - assert_eq!( - Timestamp::parse("500.0.0"), - Err(TimestampParseError::NoUnit) - ); + #[test] + fn test_invalid_value3() { + assert_eq!( + Timestamp::parse("500.0.0"), + Err(TimestampParseError::NoUnit) + ); + } + + #[test] + fn test_extra() { + assert_eq!( + Timestamp::parse("500.0 ms asdfadf"), + Err(TimestampParseError::InvalidUnit) + ); + } } - #[test] - fn test_extra() { - assert_eq!( - Timestamp::parse("500.0 ms asdfadf"), - Err(TimestampParseError::InvalidUnit) - ); + mod interval { + use super::*; + + #[test] + fn test_translate_positive() { + let start: Timestamp = Timestamp::parse("234.5 ms").unwrap(); + let end: Timestamp = Timestamp::parse("235.5 ms").unwrap(); + let expected_start: Timestamp = Timestamp(start.0 + 250); + let expected_end: Timestamp = Timestamp(end.0 + 250); + assert_eq!( + Interval::new(start, end).translate(250), + Interval::new(expected_start, expected_end) + ) + } + + #[test] + fn test_translate_negative() { + let start: Timestamp = Timestamp::parse("234.5 ms").unwrap(); + let end: Timestamp = Timestamp::parse("235.5 ms").unwrap(); + let expected_start: Timestamp = Timestamp(start.0 - 250); + let expected_end: Timestamp = Timestamp(end.0 - 250); + assert_eq!( + Interval::new(start, end).translate(-250), + Interval::new(expected_start, expected_end) + ) + } } }