1use std::sync::Arc;
10use std::time::Duration;
11
12use ek_core::main_engine::MainEngine;
13use ek_core::plugin::Plugin;
14use ek_dm::plugin::DmConfig;
15use ek_station::{PlugKind, StationConfig, default_kinds};
16
17use crate::plugin::{UI_NAME, UiPlugin};
18use crate::state::UiShared;
19
20pub fn ui_kinds(shared: &Arc<UiShared>, dm_rt: bool) -> Vec<PlugKind> {
28 let mut kinds: Vec<PlugKind> = default_kinds()
29 .into_iter()
30 .filter(|k| k.name != "dm")
31 .collect();
32 kinds.push(PlugKind {
33 name: "dm",
34 doc: "数据管理器(KlineManager;real_time 更新门开,行情板/K线页取 eBarUpdate./eBar.)",
35 build: Arc::new(move || -> Arc<dyn Plugin> {
36 Arc::new(ek_dm::plugin::DmPlugin::new(DmConfig {
37 real_time: dm_rt,
38 ..DmConfig::minute("dm")
39 }))
40 }),
41 });
42 kinds.push(PlugKind {
43 name: UI_NAME,
44 doc: "常驻观测界面(eLog + eTimer 1s 快照;关窗即 stop)",
45 build: {
46 let shared = Arc::clone(shared);
47 Arc::new(move || -> Arc<dyn Plugin> { Arc::new(UiPlugin::new(Arc::clone(&shared))) })
48 },
49 });
50 kinds
51}
52
53pub fn spawn_host_watcher(
57 engine: &Arc<MainEngine>,
58 shared: &Arc<UiShared>,
59) -> std::thread::JoinHandle<()> {
60 let engine = Arc::clone(engine);
61 let shared = Arc::clone(shared);
62 std::thread::spawn(move || {
63 while !shared.is_stop() {
64 std::thread::sleep(Duration::from_millis(120));
65 }
66 shared.log_line("[host] 窗口关闭,close_all …");
67 engine.close_all(Duration::from_secs(3));
68 shared.log_line("[host] 已收场");
69 })
70}
71
72struct StationApp {
74 shared: Arc<UiShared>,
75}
76
77impl eframe::App for StationApp {
78 fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
79 crate::render_body(ctx, &self.shared);
81 ctx.request_repaint_after(Duration::from_secs(1));
82 }
83}
84
85#[derive(Debug, Clone)]
88pub struct RunOpts {
89 pub ready_marker: Option<std::path::PathBuf>,
91 pub dm_rt: bool,
93}
94
95impl Default for RunOpts {
96 fn default() -> Self {
99 Self {
100 ready_marker: None,
101 dm_rt: true,
102 }
103 }
104}
105
106impl RunOpts {
107 pub fn plain() -> Self {
109 Self::default()
110 }
111}
112
113pub fn run_station_ui(cfg: &StationConfig, opts: &RunOpts) -> Result<(), String> {
116 let engine = Arc::new(MainEngine::new(None));
117 let shared = Arc::new(UiShared::new());
118
119 ek_station::boot(&engine, &ui_kinds(&shared, opts.dm_rt), cfg)
120 .map_err(|e| format!("boot: {e:?}"))?;
121 engine.start().map_err(|e| format!("start: {e:?}"))?;
122
123 if let Some(marker) = &opts.ready_marker {
124 use std::io::Write;
125 if let Ok(mut f) = std::fs::File::create(marker) {
126 let _ = f.write_all(crate::launch::READY_TEXT.as_bytes());
127 } else {
128 eprintln!("ek-ui: ready marker write failed: {}", marker.display());
129 }
130 }
131
132 let watcher = spawn_host_watcher(&engine, &shared);
133
134 let opts = eframe::NativeOptions {
135 viewport: egui::ViewportBuilder::default()
136 .with_inner_size([1280.0, 860.0])
137 .with_title("ek-station"),
138 ..Default::default()
139 };
140 let result = eframe::run_native(
142 "ek-station",
143 opts,
144 Box::new(|cc| {
145 crate::fonts::apply_cjk_fonts(&cc.egui_ctx); Ok(Box::new(StationApp {
147 shared: Arc::clone(&shared),
148 }) as Box<dyn eframe::App>)
149 }),
150 );
151 shared.request_stop();
152 let _ = watcher.join();
153 result.map_err(|e| format!("ui: {e:?}"))
154}