use egui::{Context, Ui};
use egui_plot::PlotUi;
use crate::data::data::LivePlotData;
use crate::data::hotkeys::HotkeyName;
use crate::data::scope::ScopeData;
use crate::data::traces::TracesCollection;
use downcast_rs::{impl_downcast, Downcast};
#[derive(Debug, Clone, Copy, Default)]
pub struct PanelState {
pub title: &'static str,
pub icon: Option<&'static str>,
pub visible: bool,
pub detached: bool,
pub request_docket: bool,
pub request_focus: bool,
pub window_pos: Option<[f32; 2]>,
pub window_size: Option<[f32; 2]>,
pub viewport_id: Option<egui::ViewportId>,
}
impl PanelState {
pub fn new(title: &'static str, icon: &'static str) -> Self {
Self {
title,
icon: Some(icon),
visible: false,
detached: false,
request_docket: false,
request_focus: false,
window_pos: None,
window_size: None,
viewport_id: None,
}
}
}
pub trait Panel: Downcast {
fn title(&self) -> &'static str {
self.state().title
}
fn icon_only(&self) -> Option<&'static str> {
self.state().icon
}
fn title_and_icon(&self) -> String {
if let Some(ic) = self.state().icon {
format!("{} {}", ic, self.state().title)
} else {
self.state().title.to_string()
}
}
fn state(&self) -> &PanelState;
fn state_mut(&mut self) -> &mut PanelState;
fn hotkey_name(&self) -> Option<HotkeyName> {
None
}
fn render_menu(
&mut self,
_ui: &mut Ui,
_data: &mut LivePlotData<'_>,
_collapsed: bool,
_tooltip: &str,
) {
}
fn render_panel(&mut self, _ui: &mut Ui, _data: &mut LivePlotData<'_>) {}
fn draw(&mut self, _plot_ui: &mut PlotUi, _scope: &ScopeData, _traces: &TracesCollection) {}
fn update_data(&mut self, _data: &mut LivePlotData<'_>) {}
fn clear_all(&mut self) {}
fn settings_snapshot(&self, _data: &LivePlotData<'_>) -> Option<String> {
None
}
fn show_detached_dialog(&mut self, ctx: &Context, data: &mut LivePlotData<'_>) {
let (title, vis, pos, size, vid_opt) = {
let st = self.state();
(
st.title,
st.visible,
st.window_pos,
st.window_size,
st.viewport_id,
)
};
let vid = vid_opt.unwrap_or_else(|| egui::ViewportId::from_hash_of(&(title, "panel")));
{
let st = self.state_mut();
st.viewport_id = Some(vid);
}
let mut builder = egui::ViewportBuilder::default().with_title(title);
if let Some(sz) = size {
builder = builder.with_inner_size([sz[0], sz[1]]);
}
if let Some(p) = pos {
builder = builder.with_position([p[0], p[1]]);
}
ctx.show_viewport_immediate(vid, builder, |vctx, class| {
let close = vctx.input(|i| i.viewport().close_requested());
if close {
let st = self.state_mut();
st.detached = false;
st.visible = false;
st.request_focus = false;
return;
}
let should_focus = {
let st = self.state();
st.request_focus
};
if should_focus {
let already_focused = vctx.input(|i| i.viewport().focused.unwrap_or(false));
if !already_focused {
vctx.send_viewport_cmd(egui::ViewportCommand::Focus);
}
self.state_mut().request_focus = false;
}
let mut dock_clicked = false;
let mut draw_ui = |ui: &mut Ui| {
ui.horizontal(|ui| {
ui.strong(title);
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
if ui.button("Dock").clicked() {
dock_clicked = true;
}
});
});
ui.separator();
let snap = self.settings_snapshot(data);
detect_settings_change(ui.ctx(), self.title(), snap);
self.render_panel(ui, data);
detect_settings_change_after(
ui.ctx(),
self.title(),
self.settings_snapshot(data),
data,
);
};
match class {
egui::ViewportClass::Root => {
let mut show_flag = vis;
let mut win = egui::Window::new(title).open(&mut show_flag);
if let Some(p) = pos {
win = win.default_pos(egui::pos2(p[0], p[1]));
}
if let Some(sz) = size {
win = win.default_size(egui::vec2(sz[0], sz[1]));
}
let resp = win.show(vctx, |ui| draw_ui(ui));
let st = self.state_mut();
if let Some(ir) = &resp {
let rect = ir.response.rect;
st.window_pos = Some([rect.min.x, rect.min.y]);
st.window_size = Some([rect.size().x, rect.size().y]);
}
if dock_clicked {
st.detached = false;
st.visible = true;
st.request_docket = true;
} else {
if !show_flag {
st.detached = false;
}
st.visible = show_flag;
}
}
_ => {
egui::CentralPanel::default().show(vctx, |ui| draw_ui(ui));
if dock_clicked {
let st = self.state_mut();
st.detached = false;
st.visible = true;
st.request_docket = true;
}
}
}
});
}
}
impl_downcast!(Panel);
pub fn detect_settings_change(ctx: &egui::Context, panel_title: &str, snapshot: Option<String>) {
let id = egui::Id::new(("settings_snapshot", panel_title));
let existing = ctx.data(|d| d.get_temp::<String>(id));
if existing.is_none() {
if let Some(snap) = snapshot {
ctx.data_mut(|d| d.insert_temp(id, snap));
}
}
}
pub fn detect_settings_change_after(
ctx: &egui::Context,
panel_title: &str,
snapshot: Option<String>,
data: &mut LivePlotData<'_>,
) {
let pointer_released = ctx.input(|i| i.pointer.any_released())
|| ctx.input(|i| {
i.events
.iter()
.any(|e| matches!(e, egui::Event::Key { pressed: true, .. }))
});
let id = egui::Id::new(("settings_snapshot", panel_title));
if pointer_released {
let before = ctx.data(|d| d.get_temp::<String>(id));
let after = snapshot;
let changed = matches!((&before, &after), (Some(b), Some(a)) if b != a);
if changed {
data.settings_changed = true;
}
if let Some(after) = after {
ctx.data_mut(|d| d.insert_temp(id, after));
}
}
}