Skip to main content

modde_ui/views/
diagnostics.rs

1use iced::widget::{button, column, container, row, scrollable, text};
2use iced::{color, Alignment, Element, Length};
3
4use crate::app::Message;
5
6/// A single diagnostic finding.
7#[derive(Debug, Clone)]
8pub struct DiagnosticEntry {
9    pub severity: DiagnosticSeverity,
10    pub message: String,
11}
12
13/// Severity levels for diagnostics.
14#[derive(Debug, Clone)]
15pub enum DiagnosticSeverity {
16    Info,
17    Warning,
18    Error,
19}
20
21/// State machine for the diagnostics view.
22#[derive(Debug, Clone, Default)]
23pub enum DiagnosticsState {
24    #[default]
25    Idle,
26    Running,
27    Complete(Vec<DiagnosticEntry>),
28}
29
30/// Render the diagnostics view.
31pub fn view(state: &DiagnosticsState) -> Element<'_, Message> {
32    let running = matches!(state, DiagnosticsState::Running);
33
34    let title_bar = row![
35        text("Diagnostics").size(20),
36        iced::widget::space::horizontal(),
37        button(
38            text(if running {
39                "Running..."
40            } else {
41                "Run Diagnostics"
42            })
43            .size(14)
44        )
45        .on_press_maybe(if running {
46            None
47        } else {
48            Some(Message::RunDiagnostics)
49        })
50        .style(button::primary)
51        .padding([6, 14]),
52    ]
53    .align_y(Alignment::Center);
54
55    let content: Element<Message> = match state {
56        DiagnosticsState::Idle | DiagnosticsState::Running => container(
57            text("Click 'Run Diagnostics' to scan for common modding issues.").size(14),
58        )
59        .padding(20)
60        .width(Length::Fill)
61        .center_x(Length::Fill)
62        .into(),
63
64        DiagnosticsState::Complete(entries) => {
65            if entries.is_empty() {
66                container(
67                    text("No issues found!")
68                        .size(14)
69                        .color(color!(0x88CC88)),
70                )
71                .padding(20)
72                .width(Length::Fill)
73                .center_x(Length::Fill)
74                .into()
75            } else {
76                let rows =
77                    entries
78                        .iter()
79                        .fold(column![].spacing(4), |col, entry| {
80                            let (icon, icon_color) = match entry.severity {
81                                DiagnosticSeverity::Info => ("INFO", color!(0x88AACC)),
82                                DiagnosticSeverity::Warning => ("WARN", color!(0xFFAA44)),
83                                DiagnosticSeverity::Error => ("ERR ", color!(0xFF4444)),
84                            };
85
86                            let entry_row = row![
87                                text(icon).size(12).color(icon_color).width(Length::Fixed(40.0)),
88                                text(&entry.message).size(13).width(Length::Fill),
89                            ]
90                            .spacing(8)
91                            .padding([4, 8]);
92
93                            col.push(entry_row)
94                        });
95
96                let summary = {
97                    let errors = entries
98                        .iter()
99                        .filter(|e| matches!(e.severity, DiagnosticSeverity::Error))
100                        .count();
101                    let warnings = entries
102                        .iter()
103                        .filter(|e| matches!(e.severity, DiagnosticSeverity::Warning))
104                        .count();
105                    let infos = entries
106                        .iter()
107                        .filter(|e| matches!(e.severity, DiagnosticSeverity::Info))
108                        .count();
109                    text(format!(
110                        "{} error(s), {} warning(s), {} info(s)",
111                        errors, warnings, infos
112                    ))
113                    .size(12)
114                };
115
116                column![
117                    summary,
118                    scrollable(rows.padding(8)).height(Length::Fill),
119                ]
120                .spacing(8)
121                .into()
122            }
123        }
124    };
125
126    column![title_bar, iced::widget::rule::horizontal(1), content]
127        .spacing(8)
128        .padding(16)
129        .width(Length::Fill)
130        .height(Length::Fill)
131        .into()
132}