use crate::data::data::LivePlotData;
use crate::data::scope::AxisSettings;
use crate::data::trace_look::TraceLook;
use crate::data::traces::TraceRef;
use egui_plot::LineStyle;
pub struct Trigger {
pub name: String,
pub target: TraceRef,
pub enabled: bool,
pub level: f64,
pub slope: TriggerSlope,
pub single_shot: bool,
pub trigger_position: f64,
pub look: TraceLook,
pub holdoff_secs: f64,
start_trigger: bool,
last_triggered: Option<f64>,
trigger_pending: Option<f64>,
}
impl Default for Trigger {
fn default() -> Self {
Self {
name: String::new(),
target: TraceRef(String::new()),
enabled: true,
level: 0.0,
slope: TriggerSlope::Rising,
single_shot: true,
trigger_position: 0.5,
look: TraceLook {
style: LineStyle::Dotted { spacing: 4.0 },
..TraceLook::default()
},
holdoff_secs: 0.0,
start_trigger: false,
last_triggered: None,
trigger_pending: None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TriggerSlope {
Rising,
Falling,
Any,
}
impl Trigger {
pub fn reset(&mut self) {
self.last_triggered = None;
self.trigger_pending = None;
}
pub fn reset_runtime_state(&mut self) {
self.last_triggered = None;
self.trigger_pending = None;
self.start_trigger = false;
}
pub fn start(&mut self) {
self.last_triggered = None;
self.trigger_pending = None;
if self.enabled {
self.start_trigger = true;
}
}
pub fn stop(&mut self) {
self.start_trigger = false;
self.trigger_pending = None;
}
pub fn get_info(&self, axis: &AxisSettings) -> String {
let slope_txt = match self.slope {
TriggerSlope::Rising => "rising",
TriggerSlope::Falling => "falling",
TriggerSlope::Any => "any",
};
let dec_pl = 4usize;
let step = if self.level.abs() > 0.0 {
self.level.abs()
} else {
1.0
};
let lvl_fmt = axis.format_value(self.level, dec_pl, step);
let mut s = format!("{}: {} @ {}", self.target.0, slope_txt, lvl_fmt);
if self.single_shot {
s.push_str(" • Single");
} else {
s.push_str(" • Auto");
}
s
}
pub fn last_trigger_time(&self) -> Option<f64> {
self.last_triggered
}
pub fn is_triggered(&self) -> bool {
self.last_triggered.is_some() && self.enabled
}
pub fn is_active(&self) -> bool {
self.enabled && self.start_trigger && (self.trigger_pending.is_none() || !self.single_shot)
}
pub fn is_trigger_pending(&self) -> bool {
self.trigger_pending.is_some()
}
pub fn check_trigger(&mut self, data: &mut LivePlotData<'_>) -> bool {
let livedata = if let Some(data) = data.traces.get_points(&self.target, false) {
data
} else {
self.enabled = false;
if let Some(first) = data.traces.all_trace_names().first() {
self.target = first.clone();
}
return false;
};
if !self.enabled {
self.start_trigger = false;
return false;
}
if !self.start_trigger && self.trigger_pending.is_none() {
return false;
}
if self.start_trigger && self.trigger_pending.is_none() {
let new_trigger_time: Option<f64> = {
let len = livedata.len();
if len < 2 {
None
} else {
let window_start = len.saturating_sub(data.traces.max_points);
let pos = self.trigger_position.clamp(0.0, 1.0);
let offset = (pos * (data.traces.max_points as f64)).round() as usize;
let mut i0 = window_start.saturating_add(offset);
if i0 >= len {
i0 = len - 1;
}
if i0 < 1 {
i0 = 1;
}
let mut found: Option<f64> = None;
for i in i0..len {
let p0 = livedata.get(i - 1).unwrap();
let p1 = livedata.get(i).unwrap();
let (v0, v1) = (p0[1], p1[1]);
let t1 = p1[0];
let crossed = match self.slope {
TriggerSlope::Rising => v0 < self.level && v1 >= self.level,
TriggerSlope::Falling => v0 > self.level && v1 <= self.level,
TriggerSlope::Any => {
(v0 < self.level && v1 >= self.level)
|| (v0 > self.level && v1 <= self.level)
}
};
if crossed {
if let Some(last_t) = self.last_triggered {
let holdoff = if self.holdoff_secs > 0.0 {
self.holdoff_secs
} else {
f64::EPSILON
};
if (t1 - last_t) < holdoff {
continue;
}
}
found = Some(t1);
break;
}
}
found
}
};
if let Some(_t_trig) = new_trigger_time {
self.trigger_pending = new_trigger_time;
if self.single_shot {
self.start_trigger = false;
}
}
}
if let Some(t_trig) = self.trigger_pending {
let pos = self.trigger_position.clamp(0.0, 1.0);
let needed: usize = (data.traces.max_points as f64 * pos).round() as usize;
if needed == 0 {
data.pause_all();
self.last_triggered = self.trigger_pending;
self.trigger_pending = None;
} else {
let have = livedata.iter().filter(|p| p[0] > t_trig).count();
if have >= needed {
data.pause_all();
self.last_triggered = self.trigger_pending;
self.trigger_pending = None;
}
}
return true;
}
false
}
}