use crate::{
site::{Change, FilteredIssueKinds, FilteredIssues, IssueKey},
widgets::{prelude::*, SelectorWidget},
CurrentWorkspace, Icons, Issue, IssueDictionary, ValidateWorkspace,
};
use bevy::{
ecs::{hierarchy::ChildOf, system::SystemParam},
prelude::*,
};
use bevy_egui::egui::{self, Button, Checkbox, Grid, ImageButton, ScrollArea, Ui};
use rmf_site_egui::*;
#[derive(Default)]
pub struct DiagnosticsPlugin {}
impl Plugin for DiagnosticsPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<IssueDictionary>()
.init_resource::<IssueMenu>()
.init_resource::<DiagnosticsDisplay>()
.add_systems(Update, handle_diagnostic_panel_visibility);
let panel = PanelWidget::new(diagnostics_panel, app.world_mut());
let widget = Widget::new::<Diagnostics>(app.world_mut());
app.world_mut().spawn((panel, widget));
}
}
fn diagnostics_panel(In(input): In<PanelWidgetInput>, world: &mut World) {
if world.resource::<DiagnosticsDisplay>().show {
egui::SidePanel::left("diagnostics")
.resizable(true)
.min_width(320.0)
.show(&input.context, |ui| {
if let Err(err) = world.try_show(input.id, ui) {
error!("Unable to display diagnostics panel: {err:?}");
}
});
}
}
#[derive(SystemParam)]
pub struct Diagnostics<'w, 's> {
commands: Commands<'w, 's>,
icons: Res<'w, Icons>,
filters: Query<'w, 's, (&'static FilteredIssues<Entity>, &'static FilteredIssueKinds)>,
issue_dictionary: Res<'w, IssueDictionary>,
issues: Query<'w, 's, (&'static Issue, &'static ChildOf)>,
display_diagnostics: ResMut<'w, DiagnosticsDisplay>,
current_workspace: ResMut<'w, CurrentWorkspace>,
validate_workspace: EventWriter<'w, ValidateWorkspace>,
selector: SelectorWidget<'w, 's>,
}
impl<'w, 's> WidgetSystem for Diagnostics<'w, 's> {
fn show(_: (), ui: &mut Ui, state: &mut SystemState<Self>, world: &mut World) -> () {
let mut params = state.get_mut(world);
params.show_widget(ui);
}
}
impl<'w, 's> Diagnostics<'w, 's> {
pub fn show_widget(&mut self, ui: &mut Ui) {
let mut state = (*self.display_diagnostics).clone();
let Some(root) = self.current_workspace.root else {
return;
};
let Ok((filtered_issues, filtered_issue_kinds)) = self.filters.get(root) else {
return;
};
let mut new_filtered_issues = filtered_issues.clone();
let mut new_filtered_issue_kinds = filtered_issue_kinds.clone();
ui.vertical(|ui| {
ui.collapsing("Filters", |ui| {
for (uuid, name) in self.issue_dictionary.iter() {
let mut show_category = !new_filtered_issue_kinds.contains(uuid);
if ui.add(Checkbox::new(&mut show_category, name)).clicked() {
match show_category {
true => new_filtered_issue_kinds.remove(uuid),
false => new_filtered_issue_kinds.insert(*uuid),
};
}
}
});
ui.collapsing("Suppressed issues", |ui| {
let mut clear_suppressions = Vec::new();
for (idx, issue) in new_filtered_issues.iter().enumerate() {
ui.horizontal(|ui| {
let issue_type = self
.issue_dictionary
.get(&issue.kind)
.cloned()
.unwrap_or("Unknown Type".to_owned());
ui.label(issue_type);
if ui
.add(ImageButton::new(self.icons.trash.egui()))
.on_hover_text("Remove this suppression")
.clicked()
{
clear_suppressions.push(issue.clone());
}
});
Grid::new(format!("diagnostic_suppressed_affected_entities_{}", idx)).show(
ui,
|ui| {
for e in &issue.entities {
self.selector.show_widget(*e, ui);
}
},
);
ui.add_space(10.0);
}
for c in clear_suppressions.iter() {
new_filtered_issues.remove(c);
}
});
ui.label("Active issues");
ScrollArea::vertical()
.max_height(600.0)
.auto_shrink([false, false])
.show(ui, |ui| {
let mut issue_still_exists = false;
if self.issues.is_empty() {
ui.label("No issues found");
}
for (issue, child_of) in &self.issues {
if new_filtered_issue_kinds.contains(&issue.key.kind)
|| new_filtered_issues.contains(&issue.key)
|| child_of.parent() != root
{
continue;
}
let mut sel = state.selected.as_ref().is_some_and(|k| *k == issue.key);
issue_still_exists |= sel;
ui.horizontal(|ui| {
if ui
.add(ImageButton::new(self.icons.hide.egui()))
.on_hover_text("Suppress this issue")
.clicked()
{
new_filtered_issues.insert(issue.key.clone());
issue_still_exists = false;
}
if ui
.toggle_value(&mut sel, &issue.brief)
.on_hover_text(&issue.hint)
.clicked()
{
state.selected = sel.then(|| issue.key.clone());
issue_still_exists = sel;
}
});
}
if !issue_still_exists {
state.selected = None;
}
});
ui.add_space(10.0);
if let Some(sel) = &state.selected {
ui.label("Affected entities");
Grid::new("diagnostic_affected_entities").show(ui, |ui| {
for e in &sel.entities {
self.selector.show_widget(*e, ui);
}
});
}
if ui.add(Button::new("Validate")).clicked() {
self.validate_workspace.write(ValidateWorkspace(root));
}
if ui.add(Button::new("Close")).clicked() {
state.show = false;
}
});
if new_filtered_issues != *filtered_issues {
self.commands
.trigger(Change::new(new_filtered_issues, root));
}
if new_filtered_issue_kinds != *filtered_issue_kinds {
self.commands
.trigger(Change::new(new_filtered_issue_kinds, root));
}
*self.display_diagnostics = state;
}
}
#[derive(Resource, Debug, Clone, Default)]
pub struct DiagnosticsDisplay {
pub show: bool,
pub selected: Option<IssueKey<Entity>>,
}
fn handle_diagnostic_panel_visibility(
mut menu_events: EventReader<MenuEvent>,
issue_menu: Res<IssueMenu>,
mut diagnostic_window: ResMut<DiagnosticsDisplay>,
) {
for event in menu_events.read() {
if event.clicked() && event.source() == issue_menu.diagnostic_tool {
diagnostic_window.show = true;
}
}
}
#[derive(Resource)]
pub struct IssueMenu {
diagnostic_tool: Entity,
}
impl FromWorld for IssueMenu {
fn from_world(world: &mut World) -> Self {
let tool_header = world.resource::<ToolMenu>().get();
let diagnostic_tool = world
.spawn(MenuItem::Text("Diagnostic Tool".into()))
.insert(ChildOf(tool_header))
.id();
IssueMenu { diagnostic_tool }
}
}