1use std::path::PathBuf;
8use std::sync::Arc;
9use std::time::Duration;
10
11use ek_station::default_kinds;
12
13use crate::launcher::{POLL, RunShared, launch_worker};
14use crate::plugin::UI_NAME;
15
16pub const BTN_LAUNCH: &str = "启动";
18pub const BTN_CANCEL: &str = "取消";
20const PICKED_NONE: &str = "已选: (无)";
22
23#[must_use]
25pub fn pick_kinds() -> Vec<(String, String)> {
26 let mut list = default_kinds()
27 .into_iter()
28 .map(|k| (k.name.to_string(), k.doc.to_string()))
29 .collect::<Vec<_>>();
30 list.push((UI_NAME.to_string(), "常驻观测界面(加载即开窗)".to_string()));
31 list
32}
33
34pub fn render_pick(
38 ui: &mut egui::Ui,
39 selected: &mut [bool],
40 kinds: &[(String, String)],
41 on_launch: &mut dyn FnMut(),
42 on_cancel: &mut dyn FnMut(),
43) {
44 ui.heading("ek-station 启动选择");
45 ui.label("勾选要装载的插件;启动写入清单并拉起 ek-ui(ui 自动补上)。");
46 ui.separator();
47
48 egui::ScrollArea::vertical()
49 .max_height(240.0)
50 .show(ui, |ui| {
51 for (i, (name, doc)) in kinds.iter().enumerate() {
52 ui.horizontal(|ui| {
53 ui.checkbox(&mut selected[i], name);
54 let _ = ui.label(doc.clone());
55 });
56 }
57 });
58
59 ui.separator();
60 let picked: Vec<String> = kinds
61 .iter()
62 .zip(selected)
63 .filter(|(_, ok)| **ok)
64 .map(|(k, _)| k.0.clone())
65 .collect();
66 if picked.is_empty() {
67 ui.label(PICKED_NONE);
68 } else {
69 ui.label(format!("已选: {}", picked.join(", ")));
70 }
71 ui.horizontal(|ui| {
72 if ui
73 .add_enabled(!picked.is_empty(), egui::Button::new(BTN_LAUNCH))
74 .clicked()
75 {
76 on_launch();
77 }
78 if ui.button(BTN_CANCEL).clicked() {
79 on_cancel();
80 }
81 });
82}
83
84pub struct PickApp {
86 kinds: Vec<(String, String)>,
87 selected: Vec<bool>,
88 program: PathBuf,
89 shared: Arc<RunShared>,
90 spawned: bool,
91 cancel: bool,
92}
93
94impl PickApp {
95 pub fn new(program: PathBuf, shared: Arc<RunShared>) -> Self {
97 let kinds = pick_kinds();
98 let selected = vec![false; kinds.len()];
99 Self {
100 kinds,
101 selected,
102 program,
103 shared,
104 spawned: false,
105 cancel: false,
106 }
107 }
108}
109
110impl eframe::App for PickApp {
111 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
112 crate::render::apply_ui_style(ctx); let kinds = self.kinds.clone();
114 let mut req_launch = false;
115 let mut req_cancel = false;
116 egui::CentralPanel::default().show(ctx, |ui| {
117 crate::launcher_ui::render_pick(
118 ui,
119 &mut self.selected,
120 &kinds,
121 &mut || req_launch = true,
122 &mut || req_cancel = true,
123 );
124 });
125
126 if req_cancel {
127 self.cancel = true;
128 }
129 if req_launch && !self.spawned {
130 let picked: Vec<String> = self
131 .kinds
132 .iter()
133 .zip(&self.selected)
134 .filter(|(_, ok)| **ok)
135 .map(|(k, _)| k.0.clone())
136 .collect();
137 if !picked.is_empty() {
138 self.spawned = true;
139 let shared = Arc::clone(&self.shared);
140 let program = self.program.clone();
141 let worker_names = picked;
142 let h = std::thread::spawn(move || {
143 launch_worker(&shared, &worker_names, program);
144 });
145 *self.shared.handle.lock().unwrap() = Some(h);
146 }
147 }
148
149 if self.spawned {
150 let done = self.shared.outcome.lock().unwrap().is_some();
151 if done {
152 ctx.send_viewport_cmd(egui::ViewportCommand::Close); } else {
154 ctx.request_repaint_after(POLL);
155 }
156 }
157 if self.cancel {
158 ctx.send_viewport_cmd(egui::ViewportCommand::Close);
159 }
160 if !self.spawned {
161 ctx.request_repaint_after(Duration::from_millis(500));
162 }
163 }
164}