1use std::path::PathBuf;
17
18use clap::Parser;
19use eframe::egui;
20use panels::tabs::{create_tree, TreeBehavior};
21use strum::EnumIter;
22
23use rafia_stpa::stpa::{
24 ca_analysis::CaAnalyses, constraints::Constraints,
25 control_loop_sequences::ControlLoopSequences, control_loops::ControlLoops, elements::Elements,
26 hazards::Hazards, interactions::Interactions, losses::Losses, scenarios::Scenarios,
27 uca_contexts::UcaContexts, ucas::Ucas, StpaData, StpaProject,
28};
29
30pub mod panels;
31
32#[derive(EnumIter, Debug, PartialEq, Eq)]
33pub enum StpaView {
34 IoOptions,
35 Diagram,
36 Losses,
37 Hazards,
38 Elements,
39 Interactions,
40 UcaContexts,
41 CaAnalyses,
42 Constraints,
43 Ucas,
44 ControlLoops,
45 ControlLoopSequences,
46 Scenarios,
47}
48impl StpaView {
49 fn tab_name(&self) -> &'static str {
50 match self {
51 StpaView::IoOptions => "Import / Export",
52 StpaView::Diagram => "Scope / Diagram",
53 StpaView::Losses => "Losses",
54 StpaView::Hazards => "Hazards",
55 StpaView::Elements => "Elements",
56 StpaView::Interactions => "Interactions",
57 StpaView::UcaContexts => "Contexts",
58 StpaView::CaAnalyses => "Control Action Analyses",
59 StpaView::Constraints => "Constraints",
60 StpaView::Ucas => "UCAs",
61 StpaView::ControlLoops => "Control Loops",
62 StpaView::ControlLoopSequences => "Control Loop Sequences",
63 StpaView::Scenarios => "Scenarios",
64 }
65 }
66}
67
68#[derive(Parser)]
69#[command(version, about, long_about = None)]
70pub struct Cli {
71 #[arg(long)]
72 losses: Option<PathBuf>,
73 #[arg(long)]
74 hazards: Option<PathBuf>,
75 #[arg(long)]
76 elements: Option<PathBuf>,
77 #[arg(long)]
78 interactions: Option<PathBuf>,
79 #[arg(long)]
80 constraints: Option<PathBuf>,
81 #[arg(long)]
82 ca_analysis: Option<PathBuf>,
83 #[arg(long)]
84 uca_contexts: Option<PathBuf>,
85 #[arg(long)]
86 uca: Option<PathBuf>,
87 #[arg(long)]
88 control_loops: Option<PathBuf>,
89 #[arg(long)]
90 cl_sequences: Option<PathBuf>,
91 #[arg(long)]
92 scenarios: Option<PathBuf>,
93 #[arg(long)]
94 project: Option<PathBuf>,
95 #[arg(long)]
96 diagram: Option<PathBuf>,
97}
98
99pub struct StpaApp {
100 tree: TreeBehavior,
101 panels: egui_tiles::Tree<StpaView>,
102}
103
104impl StpaApp {
105 pub fn new(cli: Cli, _cc: &eframe::CreationContext<'_>) -> Self {
106 let (mut stpa_data, project) = if let Some(project_file) = cli.project {
107 let project = StpaProject::from_file(&project_file).unwrap();
108 (project.clone().try_into().unwrap(), Some(project))
109 } else {
110 (StpaData::default(), None)
111 };
112
113 if let Some(losses) = cli.losses {
114 stpa_data.losses = Losses::from_file(&losses).unwrap();
115 }
116 if let Some(elements) = cli.elements {
117 stpa_data.elements = Elements::from_file(&elements).unwrap();
118 };
119 if let Some(interactions) = cli.interactions {
120 stpa_data.interactions = Interactions::from_file(&interactions).unwrap()
121 };
122 if let Some(hazards) = cli.hazards {
123 stpa_data.hazards = Hazards::from_file(&hazards).unwrap();
124 };
125 if let Some(constraints) = cli.constraints {
126 stpa_data.constraints = Constraints::from_file(&constraints).unwrap();
127 };
128 if let Some(ca_analyses) = cli.ca_analysis {
129 stpa_data.ca_analyses = CaAnalyses::from_file(&ca_analyses).unwrap();
130 };
131 if let Some(uca_contexts) = cli.uca_contexts {
132 stpa_data.uca_contexts = UcaContexts::from_file(&uca_contexts).unwrap();
133 };
134 if let Some(ucas) = cli.uca {
135 stpa_data.ucas = Ucas::from_file(&ucas).unwrap();
136 };
137 if let Some(control_loops) = cli.control_loops {
138 stpa_data.control_loops = ControlLoops::from_file(&control_loops).unwrap();
139 };
140 if let Some(control_loop_sequences) = cli.cl_sequences {
141 stpa_data.control_loop_sequences =
142 ControlLoopSequences::from_file(&control_loop_sequences).unwrap();
143 };
144 if let Some(scenarios) = cli.scenarios {
145 stpa_data.scenarios = Scenarios::from_file(&scenarios).unwrap();
146 };
147 StpaApp {
148 tree: TreeBehavior {
149 redo: vec![],
150 stpa_data: stpa_data.clone(),
151 project,
152 undo: vec![stpa_data],
153 },
154 panels: create_tree(),
155 }
156 }
157}
158
159impl eframe::App for StpaApp {
160 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
161 egui::TopBottomPanel::top("title_bar").show(ctx, |ui| {
162 ui.horizontal(|ui| {
163 ui.label("Rafia Stpa");
164 ui.separator();
165
166 egui::global_theme_preference_switch(ui);
167 ui.separator();
168
169 let undo_button = egui::Button::new("Undo");
170 if ui
171 .add_enabled(self.tree.undo.len() > 1, undo_button)
172 .clicked()
173 {
174 let redo = self.tree.undo.pop().unwrap();
175 self.tree.stpa_data = self.tree.undo.last().unwrap().clone();
176 self.tree.redo.push(redo);
177 }
178 let redo_button = egui::Button::new("Redo");
179 if ui
180 .add_enabled(!self.tree.redo.is_empty(), redo_button)
181 .clicked()
182 {
183 let redo = self.tree.redo.pop().unwrap();
184 self.tree.undo.push(redo.clone());
185 self.tree.stpa_data = redo;
186 }
187 });
188 });
189
190 egui::CentralPanel::default().show(ctx, |ui| {
191 self.panels.ui(&mut self.tree, ui);
192 });
193
194 if &self.tree.stpa_data != self.tree.undo.last().unwrap() {
195 self.tree.undo.push(self.tree.stpa_data.clone());
196 self.tree.redo.clear();
197 }
198 }
199}