use std::sync::mpsc::{Receiver, Sender};
use std::sync::{Arc, Mutex};
use crate::data::traces::TraceRef;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct EventKind(pub u64);
impl EventKind {
pub const CLICK: Self = Self(1 << 0);
pub const DOUBLE_CLICK: Self = Self(1 << 1);
pub const CLICK_ON_TRACE: Self = Self(1 << 2);
pub const PAUSE: Self = Self(1 << 3);
pub const RESUME: Self = Self(1 << 4);
pub const MEASUREMENT_POINT: Self = Self(1 << 5);
pub const MEASUREMENT_COMPLETE: Self = Self(1 << 6);
pub const MEASUREMENT_CLEARED: Self = Self(1 << 7);
pub const TRACE_SHOWN: Self = Self(1 << 8);
pub const TRACE_HIDDEN: Self = Self(1 << 9);
pub const TRACE_COLOR_CHANGED: Self = Self(1 << 10);
pub const MATH_TRACE_ADDED: Self = Self(1 << 11);
pub const MATH_TRACE_REMOVED: Self = Self(1 << 12);
pub const ZOOM: Self = Self(1 << 13);
pub const FIT_TO_VIEW: Self = Self(1 << 14);
pub const PAN: Self = Self(1 << 15);
pub const RESIZE: Self = Self(1 << 16);
pub const DATA_UPDATED: Self = Self(1 << 17);
pub const DATA_CLEARED: Self = Self(1 << 18);
pub const THRESHOLD_EXCEEDED: Self = Self(1 << 19);
pub const THRESHOLD_ADDED: Self = Self(1 << 20);
pub const THRESHOLD_REMOVED: Self = Self(1 << 21);
pub const KEY_PRESSED: Self = Self(1 << 22);
pub const EXPORT: Self = Self(1 << 23);
pub const SCREENSHOT: Self = Self(1 << 24);
pub const SCOPE_ADDED: Self = Self(1 << 25);
pub const SCOPE_REMOVED: Self = Self(1 << 26);
pub const TRIGGER_FIRED: Self = Self(1 << 27);
pub const TRACE_OFFSET_CHANGED: Self = Self(1 << 28);
pub const Y_LOG_CHANGED: Self = Self(1 << 29);
pub const Y_UNIT_CHANGED: Self = Self(1 << 30);
pub const ALL: Self = Self(u64::MAX);
#[inline]
pub const fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
#[inline]
pub const fn contains(self, other: Self) -> bool {
(self.0 & other.0) == other.0
}
#[inline]
pub const fn intersects(self, other: Self) -> bool {
(self.0 & other.0) != 0
}
#[inline]
pub const fn is_empty(self) -> bool {
self.0 == 0
}
}
impl std::ops::BitOr for EventKind {
type Output = Self;
#[inline]
fn bitor(self, rhs: Self) -> Self {
Self(self.0 | rhs.0)
}
}
impl std::ops::BitOrAssign for EventKind {
#[inline]
fn bitor_assign(&mut self, rhs: Self) {
self.0 |= rhs.0;
}
}
impl std::ops::BitAnd for EventKind {
type Output = Self;
#[inline]
fn bitand(self, rhs: Self) -> Self {
Self(self.0 & rhs.0)
}
}
impl std::ops::Not for EventKind {
type Output = Self;
#[inline]
fn not(self) -> Self {
Self(!self.0)
}
}
impl std::fmt::Display for EventKind {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_empty() {
return write!(f, "EMPTY");
}
if *self == EventKind::ALL {
return write!(f, "ALL");
}
let pairs: &[(EventKind, &str)] = &[
(EventKind::CLICK, "CLICK"),
(EventKind::DOUBLE_CLICK, "DOUBLE_CLICK"),
(EventKind::CLICK_ON_TRACE, "CLICK_ON_TRACE"),
(EventKind::PAUSE, "PAUSE"),
(EventKind::RESUME, "RESUME"),
(EventKind::MEASUREMENT_POINT, "MEASUREMENT_POINT"),
(EventKind::MEASUREMENT_COMPLETE, "MEASUREMENT_COMPLETE"),
(EventKind::MEASUREMENT_CLEARED, "MEASUREMENT_CLEARED"),
(EventKind::TRACE_SHOWN, "TRACE_SHOWN"),
(EventKind::TRACE_HIDDEN, "TRACE_HIDDEN"),
(EventKind::TRACE_COLOR_CHANGED, "TRACE_COLOR_CHANGED"),
(EventKind::MATH_TRACE_ADDED, "MATH_TRACE_ADDED"),
(EventKind::MATH_TRACE_REMOVED, "MATH_TRACE_REMOVED"),
(EventKind::ZOOM, "ZOOM"),
(EventKind::FIT_TO_VIEW, "FIT_TO_VIEW"),
(EventKind::PAN, "PAN"),
(EventKind::RESIZE, "RESIZE"),
(EventKind::DATA_UPDATED, "DATA_UPDATED"),
(EventKind::DATA_CLEARED, "DATA_CLEARED"),
(EventKind::THRESHOLD_EXCEEDED, "THRESHOLD_EXCEEDED"),
(EventKind::THRESHOLD_ADDED, "THRESHOLD_ADDED"),
(EventKind::THRESHOLD_REMOVED, "THRESHOLD_REMOVED"),
(EventKind::KEY_PRESSED, "KEY_PRESSED"),
(EventKind::EXPORT, "EXPORT"),
(EventKind::SCREENSHOT, "SCREENSHOT"),
(EventKind::SCOPE_ADDED, "SCOPE_ADDED"),
(EventKind::SCOPE_REMOVED, "SCOPE_REMOVED"),
(EventKind::TRIGGER_FIRED, "TRIGGER_FIRED"),
(EventKind::TRACE_OFFSET_CHANGED, "TRACE_OFFSET_CHANGED"),
(EventKind::Y_LOG_CHANGED, "Y_LOG_CHANGED"),
(EventKind::Y_UNIT_CHANGED, "Y_UNIT_CHANGED"),
];
let mut names = Vec::new();
let mut known_bits: u64 = 0;
for (kind, name) in pairs {
known_bits |= kind.0;
if self.contains(*kind) {
names.push((*name).to_string());
}
}
let extra = self.0 & !known_bits;
if extra != 0 {
names.push(format!("0x{:x}", extra));
}
if names.is_empty() {
write!(f, "0x{:x}", self.0)
} else {
write!(f, "{}", names.join("|"))
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct ScreenPos {
pub x: f32,
pub y: f32,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct PlotPos {
pub x: f64,
pub y: f64,
}
#[derive(Debug, Clone)]
pub struct ClickMeta {
pub screen_pos: Option<ScreenPos>,
pub plot_pos: Option<PlotPos>,
pub trace: Option<TraceRef>,
pub scope_id: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct MeasurementMeta {
pub point_index: usize,
pub point: [f64; 2],
pub p1: Option<[f64; 2]>,
pub p2: Option<[f64; 2]>,
pub delta_x: Option<f64>,
pub delta_y: Option<f64>,
pub slope: Option<f64>,
pub distance: Option<f64>,
pub measurement_name: Option<String>,
pub trace: Option<TraceRef>,
}
#[derive(Debug, Clone)]
pub struct ViewChangeMeta {
pub x_range: Option<(f64, f64)>,
pub y_range: Option<(f64, f64)>,
pub scope_id: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct TraceMeta {
pub trace: TraceRef,
pub visible: Option<bool>,
pub color_rgb: Option<[u8; 3]>,
pub offset: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct MathTraceMeta {
pub name: String,
pub formula: Option<String>,
}
#[derive(Debug, Clone, Copy)]
pub struct ResizeMeta {
pub width: f32,
pub height: f32,
}
#[derive(Debug, Clone)]
pub struct DataUpdateMeta {
pub traces: Vec<TraceRef>,
pub new_point_count: usize,
}
#[derive(Debug, Clone)]
pub struct ThresholdMeta {
pub threshold_name: String,
pub trace: Option<TraceRef>,
pub start_t: Option<f64>,
pub end_t: Option<f64>,
pub duration: Option<f64>,
pub area: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct KeyPressMeta {
pub key: String,
pub modifiers: KeyModifiers,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct KeyModifiers {
pub ctrl: bool,
pub alt: bool,
pub shift: bool,
pub command: bool,
}
#[derive(Debug, Clone)]
pub struct ExportMeta {
pub format: String,
pub path: Option<String>,
}
#[derive(Debug, Clone)]
pub struct ScopeManageMeta {
pub scope_id: usize,
pub scope_name: Option<String>,
}
#[derive(Debug, Clone)]
pub struct TriggerMeta {
pub trigger_name: String,
pub trace: Option<TraceRef>,
pub timestamp: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct YAxisMeta {
pub y_log: Option<bool>,
pub y_unit: Option<Option<String>>,
}
#[derive(Debug, Clone)]
pub struct PauseMeta {
pub scope_id: Option<usize>,
}
#[derive(Debug, Clone)]
pub struct PlotEvent {
pub kinds: EventKind,
pub timestamp: f64,
pub click: Option<ClickMeta>,
pub measurement: Option<MeasurementMeta>,
pub view_change: Option<ViewChangeMeta>,
pub trace: Option<TraceMeta>,
pub math_trace: Option<MathTraceMeta>,
pub resize: Option<ResizeMeta>,
pub data_update: Option<DataUpdateMeta>,
pub threshold: Option<ThresholdMeta>,
pub key_press: Option<KeyPressMeta>,
pub export: Option<ExportMeta>,
pub scope_manage: Option<ScopeManageMeta>,
pub trigger: Option<TriggerMeta>,
pub y_axis: Option<YAxisMeta>,
pub pause: Option<PauseMeta>,
}
impl PlotEvent {
pub fn new(kinds: EventKind) -> Self {
Self {
kinds,
timestamp: 0.0, click: None,
measurement: None,
view_change: None,
trace: None,
math_trace: None,
resize: None,
data_update: None,
threshold: None,
key_press: None,
export: None,
scope_manage: None,
trigger: None,
y_axis: None,
pause: None,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct EventFilter {
pub mask: EventKind,
}
impl EventFilter {
pub const fn all() -> Self {
Self {
mask: EventKind::ALL,
}
}
pub const fn only(mask: EventKind) -> Self {
Self { mask }
}
#[inline]
pub fn matches(&self, event: &PlotEvent) -> bool {
event.kinds.intersects(self.mask)
}
}
impl Default for EventFilter {
fn default() -> Self {
Self::all()
}
}
pub(crate) struct Subscriber {
filter: EventFilter,
sender: Sender<PlotEvent>,
}
#[derive(Clone)]
pub struct EventController {
pub(crate) inner: Arc<Mutex<EventCtrlInner>>,
}
pub(crate) struct EventCtrlInner {
pub(crate) subscribers: Vec<Subscriber>,
pub(crate) start_instant: std::time::Instant,
pub(crate) last_size: Option<[f32; 2]>,
}
impl EventController {
pub fn new() -> Self {
Self {
inner: Arc::new(Mutex::new(EventCtrlInner {
subscribers: Vec::new(),
start_instant: std::time::Instant::now(),
last_size: None,
})),
}
}
pub fn subscribe(&self, filter: EventFilter) -> Receiver<PlotEvent> {
let (tx, rx) = std::sync::mpsc::channel();
let mut inner = self.inner.lock().unwrap();
inner.subscribers.push(Subscriber { filter, sender: tx });
rx
}
pub fn subscribe_all(&self) -> Receiver<PlotEvent> {
self.subscribe(EventFilter::all())
}
pub fn emit(&self, mut event: PlotEvent) {
let mut inner = self.inner.lock().unwrap();
event.timestamp = inner.start_instant.elapsed().as_secs_f64();
inner.subscribers.retain(|sub| {
if sub.filter.matches(&event) {
sub.sender.send(event.clone()).is_ok()
} else {
!sub.sender.send(event.clone()).is_err() || true
}
});
}
pub(crate) fn emit_filtered(&self, mut event: PlotEvent) {
let mut inner = self.inner.lock().unwrap();
event.timestamp = inner.start_instant.elapsed().as_secs_f64();
inner.subscribers.retain(|sub| {
if sub.filter.matches(&event) {
sub.sender.send(event.clone()).is_ok()
} else {
true
}
});
}
}
impl Default for EventController {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn event_kind_union_and_intersection() {
let click = EventKind::CLICK;
let dbl = EventKind::DOUBLE_CLICK;
let combined = click | dbl;
assert!(combined.contains(click));
assert!(combined.contains(dbl));
assert!(combined.intersects(click));
assert!(!EventKind::PAUSE.intersects(click));
}
#[test]
fn event_kind_all_matches_everything() {
assert!(EventKind::ALL.contains(EventKind::CLICK));
assert!(EventKind::ALL.contains(EventKind::ZOOM));
assert!(EventKind::ALL.contains(EventKind::THRESHOLD_EXCEEDED));
}
#[test]
fn event_filter_matches() {
let filter = EventFilter::only(EventKind::CLICK | EventKind::DOUBLE_CLICK);
let mut evt = PlotEvent::new(EventKind::CLICK);
evt.timestamp = 1.0;
assert!(filter.matches(&evt));
let evt2 = PlotEvent::new(EventKind::ZOOM);
assert!(!filter.matches(&evt2));
let evt3 = PlotEvent::new(EventKind::CLICK | EventKind::MEASUREMENT_POINT);
assert!(filter.matches(&evt3));
}
#[test]
fn event_filter_all_matches_everything() {
let filter = EventFilter::all();
let evt = PlotEvent::new(EventKind::THRESHOLD_EXCEEDED);
assert!(filter.matches(&evt));
}
#[test]
fn event_controller_subscribe_and_emit() {
let ctrl = EventController::new();
let rx_all = ctrl.subscribe_all();
let rx_clicks = ctrl.subscribe(EventFilter::only(EventKind::CLICK));
let rx_zoom = ctrl.subscribe(EventFilter::only(EventKind::ZOOM));
let evt = PlotEvent::new(EventKind::CLICK);
ctrl.emit_filtered(evt);
assert!(rx_all.try_recv().is_ok());
assert!(rx_clicks.try_recv().is_ok());
assert!(rx_zoom.try_recv().is_err());
}
#[test]
fn event_controller_combined_kinds() {
let ctrl = EventController::new();
let rx_click = ctrl.subscribe(EventFilter::only(EventKind::CLICK));
let rx_meas = ctrl.subscribe(EventFilter::only(EventKind::MEASUREMENT_POINT));
let evt = PlotEvent::new(EventKind::CLICK | EventKind::MEASUREMENT_POINT);
ctrl.emit_filtered(evt);
assert!(rx_click.try_recv().is_ok());
assert!(rx_meas.try_recv().is_ok());
}
#[test]
fn event_controller_timestamp_set_on_emit() {
let ctrl = EventController::new();
let rx = ctrl.subscribe_all();
std::thread::sleep(std::time::Duration::from_millis(10));
ctrl.emit_filtered(PlotEvent::new(EventKind::CLICK));
let evt = rx.try_recv().unwrap();
assert!(evt.timestamp > 0.0);
}
#[test]
fn event_kind_display() {
assert_eq!(format!("{}", EventKind::CLICK), "CLICK");
assert_eq!(format!("{}", EventKind::DOUBLE_CLICK), "DOUBLE_CLICK");
let combo = EventKind::CLICK | EventKind::DOUBLE_CLICK;
assert_eq!(format!("{}", combo), "CLICK|DOUBLE_CLICK");
assert_eq!(format!("{}", EventKind::ALL), "ALL");
let unknown = EventKind(1 << 63);
assert!(format!("{}", unknown).starts_with("0x"));
}
#[test]
fn event_kinds_do_not_overlap() {
let all_kinds = [
EventKind::CLICK,
EventKind::DOUBLE_CLICK,
EventKind::CLICK_ON_TRACE,
EventKind::PAUSE,
EventKind::RESUME,
EventKind::MEASUREMENT_POINT,
EventKind::MEASUREMENT_COMPLETE,
EventKind::MEASUREMENT_CLEARED,
EventKind::TRACE_SHOWN,
EventKind::TRACE_HIDDEN,
EventKind::TRACE_COLOR_CHANGED,
EventKind::MATH_TRACE_ADDED,
EventKind::MATH_TRACE_REMOVED,
EventKind::ZOOM,
EventKind::FIT_TO_VIEW,
EventKind::PAN,
EventKind::RESIZE,
EventKind::DATA_UPDATED,
EventKind::DATA_CLEARED,
EventKind::THRESHOLD_EXCEEDED,
EventKind::THRESHOLD_ADDED,
EventKind::THRESHOLD_REMOVED,
EventKind::KEY_PRESSED,
EventKind::EXPORT,
EventKind::SCREENSHOT,
EventKind::SCOPE_ADDED,
EventKind::SCOPE_REMOVED,
EventKind::TRIGGER_FIRED,
EventKind::TRACE_OFFSET_CHANGED,
EventKind::Y_LOG_CHANGED,
EventKind::Y_UNIT_CHANGED,
];
for (i, a) in all_kinds.iter().enumerate() {
for (j, b) in all_kinds.iter().enumerate() {
if i != j {
assert!(
!a.intersects(*b),
"EventKind bits {} and {} overlap: {:b} & {:b}",
i,
j,
a.0,
b.0
);
}
}
}
}
#[test]
fn dropped_receiver_is_cleaned_up() {
let ctrl = EventController::new();
let rx1 = ctrl.subscribe_all();
let rx2 = ctrl.subscribe_all();
drop(rx1);
ctrl.emit_filtered(PlotEvent::new(EventKind::CLICK));
assert!(rx2.try_recv().is_ok());
ctrl.emit_filtered(PlotEvent::new(EventKind::ZOOM));
assert!(rx2.try_recv().is_ok());
}
#[test]
fn plot_event_carries_metadata() {
let mut evt = PlotEvent::new(EventKind::CLICK | EventKind::MEASUREMENT_POINT);
evt.click = Some(ClickMeta {
screen_pos: Some(ScreenPos { x: 100.0, y: 200.0 }),
plot_pos: Some(PlotPos { x: 1.5, y: 2.5 }),
trace: Some(TraceRef("signal".into())),
scope_id: Some(0),
});
evt.measurement = Some(MeasurementMeta {
point_index: 0,
point: [1.5, 2.5],
p1: Some([1.5, 2.5]),
p2: None,
delta_x: None,
delta_y: None,
slope: None,
distance: None,
measurement_name: Some("M1".into()),
trace: Some(TraceRef("signal".into())),
});
assert!(evt.kinds.contains(EventKind::CLICK));
assert!(evt.click.is_some());
assert!(evt.measurement.is_some());
assert_eq!(evt.click.as_ref().unwrap().plot_pos.unwrap().x, 1.5);
}
}