use gpui_kit::{AppContext as _, Context, Entity, EventEmitter, Subscription};
use crate::screens::explore::trace::renderer::TimeViewKind;
use crate::screens::explore::trace::view::ViewId;
use crate::data::store::{AppEvent, Store};
use super::trace::trace_vm::{TraceEvent, TraceVm};
pub enum ExploreEvent {
Reset(Vec<Entity<TraceVm>>),
Added { vm: Entity<TraceVm>, below: bool },
Focus,
Close(ViewId),
}
pub struct ExploreVm {
store: Entity<Store>,
pub views: Vec<Entity<TraceVm>>,
pub focused: Option<Entity<TraceVm>>,
next_id: ViewId,
subs: Vec<(ViewId, Subscription)>,
_store: Subscription,
}
impl EventEmitter<ExploreEvent> for ExploreVm {}
impl ExploreVm {
pub fn new(store: Entity<Store>, cx: &mut Context<Self>) -> Self {
let sub = cx.subscribe(&store, |vm, _, event, cx| {
if *event == AppEvent::RecordingChanged {
vm.reset(cx);
}
});
let mut vm = Self { store, views: Vec::new(), focused: None, next_id: 1, subs: Vec::new(), _store: sub };
if vm.store.read(cx).recording.is_some() {
vm.reset(cx);
}
vm
}
pub fn store(&self) -> &Entity<Store> {
&self.store
}
fn make(&mut self, kind: TimeViewKind, source: &str, saved: Option<&crate::data::preferences::SavedTimeView>, cx: &mut Context<Self>) -> Entity<TraceVm> {
let id = self.next_id;
self.next_id += 1;
let store = self.store.clone();
let source = source.to_string();
let saved = saved.cloned();
let vm = cx.new(|cx| {
let mut vm = TraceVm::new(store, id, kind, &source, cx);
if let Some(s) = saved {
s.apply(&mut vm.view);
}
vm
});
let sub = cx.subscribe(&vm, |this, vm, event, cx| match event {
TraceEvent::Pressed => this.focus(vm, cx),
TraceEvent::Released(id) => this.release(id.clone(), cx),
TraceEvent::Changed => {
this.persist_views(cx);
if this.focused.as_ref() == Some(&vm) {
this.select_view_of(&vm, cx);
cx.emit(ExploreEvent::Focus);
}
}
});
self.subs.push((id, sub));
self.views.push(vm.clone());
vm
}
fn persist_views(&self, cx: &mut Context<Self>) {
let saved: Vec<crate::data::preferences::SavedTimeView> = self.views.iter().map(|v| crate::data::preferences::SavedTimeView::from_view(&v.read(cx).view)).collect();
self.store.update(cx, |s, _| s.set_explore_views(saved));
}
fn reset(&mut self, cx: &mut Context<Self>) {
for vm in std::mem::take(&mut self.views) {
vm.update(cx, |vm, cx| vm.close(cx));
}
self.subs.clear();
let Some(sources) = self.store.read(cx).sources().cloned() else {
self.focused = None;
cx.emit(ExploreEvent::Reset(Vec::new()));
cx.emit(ExploreEvent::Focus);
return;
};
let default_source = sources.default_entry().id.clone();
let known = |id: &str| sources.entries().iter().any(|e| e.id == id);
let saved: Vec<_> = self.store.read(cx).session.explore.views.clone();
let created: Vec<Entity<TraceVm>> = if saved.is_empty() {
vec![self.make(TimeViewKind::Traces, &default_source, None, cx), self.make(TimeViewKind::Heatmap, &default_source, None, cx)]
} else {
saved
.iter()
.map(|s| {
let src = if known(&s.source) { &s.source } else { &default_source };
self.make(s.kind, src, Some(s), cx)
})
.collect()
};
self.focused = created.first().cloned();
self.persist_views(cx);
cx.emit(ExploreEvent::Reset(created));
cx.emit(ExploreEvent::Focus);
}
pub fn add(&mut self, kind: TimeViewKind, cx: &mut Context<Self>) -> Option<Entity<TraceVm>> {
let sources = self.store.read(cx).sources().cloned()?;
let source = self.focused.as_ref().map(|f| f.read(cx).view.source.clone()).unwrap_or_else(|| sources.default_entry().id.clone());
Some(self.add_on(kind, &source, cx))
}
pub fn add_on(&mut self, kind: TimeViewKind, source: &str, cx: &mut Context<Self>) -> Entity<TraceVm> {
let vm = self.make(kind, source, None, cx);
self.persist_views(cx);
cx.emit(ExploreEvent::Added { vm: vm.clone(), below: kind == TimeViewKind::Heatmap });
self.focus(vm.clone(), cx);
vm
}
fn select_view_of(&self, vm: &Entity<TraceVm>, cx: &mut Context<Self>) {
let doc_view = vm.read(cx).view.doc_view.clone();
if !doc_view.is_empty() && self.store.read(cx).doc.active_view.as_deref() != Some(doc_view.as_str()) {
self.store.update(cx, |s, cx| {
let _ = s.execute(crate::domain::command::SessionCommand::SelectView { view: doc_view }, cx);
});
}
}
pub fn focus(&mut self, vm: Entity<TraceVm>, cx: &mut Context<Self>) {
self.select_view_of(&vm, cx);
if self.store.read(cx).inspected.is_some() {
self.store.update(cx, |s, cx| s.inspect(None, cx));
}
if self.focused.as_ref() != Some(&vm) {
self.focused = Some(vm);
cx.emit(ExploreEvent::Focus);
cx.notify();
}
}
pub fn reset_layout(&mut self, cx: &mut Context<Self>) {
self.store.update(cx, |s, _| s.set_explore_views(Vec::new()));
self.reset(cx);
}
pub fn close(&mut self, id: ViewId, cx: &mut Context<Self>) {
cx.emit(ExploreEvent::Close(id));
}
pub fn removed(&mut self, id: ViewId, cx: &mut Context<Self>) {
let Some(at) = self.views.iter().position(|v| v.read(cx).id() == id) else { return };
let vm = self.views.remove(at);
let doc_view = vm.read(cx).view.doc_view.clone();
vm.update(cx, |vm, cx| vm.close(cx));
if !doc_view.is_empty() {
self.release(doc_view, cx);
}
self.subs.retain(|(v, _)| *v != id);
if self.focused.as_ref() == Some(&vm) {
self.focused = self.views.first().cloned();
cx.emit(ExploreEvent::Focus);
}
self.persist_views(cx);
cx.notify();
}
fn release(&mut self, doc_view: String, cx: &mut Context<Self>) {
if self.views.iter().any(|v| v.read(cx).view.doc_view == doc_view) {
return;
}
self.store.update(cx, |s, cx| s.close_view_if_untouched(&doc_view, cx));
}
pub fn reveal_in_traces(&mut self, from: &Entity<TraceVm>, channel: usize, cx: &mut Context<Self>) {
let source = from.read(cx).view.source.clone();
let target = self.views.iter().find(|v| {
let v = &v.read(cx).view;
v.kind == TimeViewKind::Traces && v.source == source && v.selection.contains(&channel)
});
let target = match target {
Some(t) => t.clone(),
None => self.add_on(TimeViewKind::Traces, &source, cx),
};
target.update(cx, |vm, cx| vm.reveal(channel, cx));
self.focus(target, cx);
}
}