use std::ops::Range;
use std::path::Path;
use std::sync::{Arc, OnceLock};
use dspk_core::{DspResult, RecordingSource};
use dspk_io::SourceEntry;
use gpui_kit::{AnyElement, AnyView, App, Entity, Window};
use crate::data::store::Store;
use crate::domain::session::ViewDoc;
use crate::navigation::route::Workspace;
use crate::ui::plot::canvas::Pixel;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ChannelLayout {
pub group_kind: String,
pub groups: Vec<ChannelGroup>,
pub labels: Vec<Option<String>>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ChannelGroup {
pub name: String,
pub channels: Vec<usize>,
}
impl ChannelLayout {
pub fn label(&self, channel: usize) -> Option<&str> {
self.labels.get(channel)?.as_deref()
}
}
pub trait SourceOpener: Send + Sync {
fn reads(&self, path: &Path) -> bool;
fn sources(&self, path: &Path) -> DspResult<Vec<SourceEntry>>;
fn open(&self, path: &Path, id: &str) -> DspResult<Box<dyn RecordingSource>>;
fn layout(&self, _path: &Path, _id: &str) -> Option<ChannelLayout> {
None
}
}
#[derive(Debug, Clone, Default)]
pub struct TraceOverlay {
pub dim: bool,
pub units: Vec<OverlayUnit>,
pub marks: Vec<OverlayMark>,
}
#[derive(Debug, Clone)]
pub struct OverlayUnit {
pub color: Pixel,
pub channels: Vec<usize>,
}
#[derive(Debug, Clone, Copy)]
pub struct OverlayMark {
pub start_sec: f64,
pub end_sec: f64,
pub unit: u32,
}
#[derive(Debug, Clone, Default)]
pub struct Raster {
pub rows: Vec<RasterRow>,
}
#[derive(Debug, Clone)]
pub struct RasterRow {
pub color: Pixel,
pub label: String,
pub times: Vec<f64>,
}
pub trait AppPlugin: Send + Sync + 'static {
fn id(&self) -> &'static str;
fn name(&self) -> &'static str;
fn opener(&self) -> Option<&dyn SourceOpener> {
None
}
fn synthetic(&self, _channels: usize, _sample_rate: f64, _duration_sec: f64) -> Option<anyhow::Result<Arc<dyn RecordingSource>>> {
None
}
fn install(&self, _store: &Entity<Store>, _cx: &mut App) {}
fn trace_overlay(&self, _store: &Store, _source: &str, _window_sec: Range<f64>, _cx: &App) -> Option<TraceOverlay> {
None
}
fn raster(&self, _store: &Store, _source: &str, _window_sec: Range<f64>, _cx: &App) -> Option<Raster> {
None
}
fn workspace(&self, _ws: Workspace, _store: &Entity<Store>, _window: &mut Window, _cx: &mut App) -> Option<AnyView> {
None
}
fn provides(&self, _ws: Workspace) -> bool {
false
}
fn panel(&self) -> Option<(&'static str, std::rc::Rc<dyn Fn() -> gpui_kit::component::Icon>)> {
None
}
fn view_section(&self, _store: &Entity<Store>, _view: &ViewDoc, _window: &mut Window, _cx: &mut App) -> Option<AnyElement> {
None
}
}
static PLUGINS: OnceLock<Vec<Arc<dyn AppPlugin>>> = OnceLock::new();
pub fn register(plugins: Vec<Arc<dyn AppPlugin>>) {
if PLUGINS.set(plugins).is_err() {
tracing::warn!("plugins are registered once");
}
}
pub fn plugins() -> &'static [Arc<dyn AppPlugin>] {
PLUGINS.get().map_or(&[], Vec::as_slice)
}
pub fn sources(path: &Path) -> DspResult<Vec<SourceEntry>> {
match opener_of(path) {
Some(o) => o.sources(path),
None => dspk_io::sources(path),
}
}
fn opener_of(path: &Path) -> Option<&'static dyn SourceOpener> {
plugins().iter().filter_map(|p| p.opener()).find(|o| o.reads(path))
}
pub fn open_source(path: &Path, id: &str) -> DspResult<Box<dyn RecordingSource>> {
match opener_of(path) {
Some(o) => o.open(path, id),
None => dspk_io::open_source(path, id),
}
}
pub fn open(path: &Path, id: Option<&str>) -> DspResult<Box<dyn RecordingSource>> {
match id {
Some(id) => open_source(path, id),
None => {
let list = sources(path)?;
let id = dspk_io::default_source(&list).map(|e| e.id.clone()).unwrap_or_default();
open_source(path, &id)
}
}
}
pub fn layout(path: &Path, id: &str) -> Option<ChannelLayout> {
opener_of(path)?.layout(path, id)
}
pub fn synthetic(channels: usize, sample_rate: f64, duration_sec: f64) -> Option<anyhow::Result<Arc<dyn RecordingSource>>> {
plugins().iter().find_map(|p| p.synthetic(channels, sample_rate, duration_sec))
}