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 theme: crate::tokens::Theme,
95}
96
97impl PickApp {
98 pub fn new(program: PathBuf, shared: Arc<RunShared>) -> Self {
100 let kinds = pick_kinds();
101 let selected = vec![false; kinds.len()];
102 Self {
103 kinds,
104 selected,
105 program,
106 shared,
107 spawned: false,
108 cancel: false,
109 theme: crate::plugin::load_theme(),
110 }
111 }
112}
113
114impl eframe::App for PickApp {
115 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
116 crate::render::apply_ui_style(ctx, self.theme);
118 let kinds = self.kinds.clone();
119 let mut req_launch = false;
120 let mut req_cancel = false;
121 egui::CentralPanel::default().show(ctx, |ui| {
122 crate::launcher_ui::render_pick(
123 ui,
124 &mut self.selected,
125 &kinds,
126 &mut || req_launch = true,
127 &mut || req_cancel = true,
128 );
129 });
130
131 if req_cancel {
132 self.cancel = true;
133 }
134 if req_launch && !self.spawned {
135 let picked: Vec<String> = self
136 .kinds
137 .iter()
138 .zip(&self.selected)
139 .filter(|(_, ok)| **ok)
140 .map(|(k, _)| k.0.clone())
141 .collect();
142 if !picked.is_empty() {
143 self.spawned = true;
144 let shared = Arc::clone(&self.shared);
145 let program = self.program.clone();
146 let worker_names = picked;
147 let h = std::thread::spawn(move || {
148 launch_worker(&shared, &worker_names, program);
149 });
150 *self.shared.handle.lock().unwrap() = Some(h);
151 }
152 }
153
154 if self.spawned {
155 let done = self.shared.outcome.lock().unwrap().is_some();
156 if done {
157 ctx.send_viewport_cmd(egui::ViewportCommand::Close); } else {
159 ctx.request_repaint_after(POLL);
160 }
161 }
162 if self.cancel {
163 ctx.send_viewport_cmd(egui::ViewportCommand::Close);
164 }
165 if !self.spawned {
166 ctx.request_repaint_after(Duration::from_millis(500));
167 }
168 }
169}