Skip to main content

guinea_core/observability/
panels.rs

1//! Panels: what a backend or a plugin knows that the rest of guinea does not,
2//! offered for a window or for the whole application, and drawn by a tool
3//! without being interpreted.
4
5use std::cell::RefCell;
6use std::rc::Rc;
7
8/// Something a backend knows about a window that the rest of guinea does not:
9/// its component tree, say. A tool draws it without interpreting it.
10#[derive(Clone, Debug, Default, PartialEq)]
11pub struct Panel {
12    pub id: &'static str,
13    pub title: &'static str,
14    pub nodes: Vec<PanelNode>,
15}
16
17#[derive(Clone, Debug, Default, PartialEq)]
18pub struct PanelNode {
19    pub label: String,
20    pub kind: String,
21    pub properties: Vec<(String, String)>,
22    pub children: Vec<PanelNode>,
23}
24
25type Provider = Rc<dyn Fn() -> Option<Panel>>;
26
27thread_local! {
28    static PANELS: RefCell<Vec<(u64, Option<u64>, Provider)>> = const { RefCell::new(Vec::new()) };
29    static NEXT_PANEL: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
30}
31
32fn offer(root: Option<u64>, provider: impl Fn() -> Option<Panel> + 'static) -> PanelGuard {
33    let id = NEXT_PANEL.with(|next| {
34        let id = next.get();
35        next.set(id + 1);
36        id
37    });
38    PANELS.with(|panels| panels.borrow_mut().push((id, root, Rc::new(provider))));
39    PanelGuard { id }
40}
41
42fn built(root: Option<u64>) -> Vec<Panel> {
43    let providers: Vec<Provider> = PANELS.with(|panels| {
44        panels
45            .borrow()
46            .iter()
47            .filter(|(_, owner, _)| *owner == root)
48            .map(|(_, _, provider)| provider.clone())
49            .collect()
50    });
51    providers.iter().filter_map(|provider| provider()).collect()
52}
53
54/// Offers a panel for the root numbered `root`, for as long as the guard
55/// lives. `provider` is asked only while something is reading.
56#[must_use = "the panel is withdrawn when the guard is dropped"]
57pub fn contribute(root: u64, provider: impl Fn() -> Option<Panel> + 'static) -> PanelGuard {
58    offer(Some(root), provider)
59}
60
61/// Offers a panel about the whole application rather than one window: what a
62/// plugin holds, say.
63#[must_use = "the panel is withdrawn when the guard is dropped"]
64pub fn contribute_to_app(provider: impl Fn() -> Option<Panel> + 'static) -> PanelGuard {
65    offer(None, provider)
66}
67
68/// Every panel offered for `root`, built now.
69pub fn for_root(root: u64) -> Vec<Panel> {
70    built(Some(root))
71}
72
73/// Every panel offered for the application, built now.
74pub fn for_app() -> Vec<Panel> {
75    built(None)
76}
77
78pub struct PanelGuard {
79    id: u64,
80}
81
82impl Drop for PanelGuard {
83    fn drop(&mut self) {
84        let id = self.id;
85        let _ = PANELS.try_with(|panels| panels.borrow_mut().retain(|(own, _, _)| *own != id));
86    }
87}