Skip to main content

ek_ui/
faces.rs

1//! faces —— 插件界面挂载表(「插件即 App」波 P4)。
2//!
3//! 挂载 = **装配名单驱动**(判据 U-W1:装配了谁挂谁的面,没装配的不出现):
4//! 插件自带界面住在插件 crate(feature `ui` 门控,daemon 根本不编译),本模块
5//! 只做**派发 + 宿主桥**:
6//! - [`CfgBridge`] = [`CfgHost`] 的 ek-ui 实现(写档 = ek-cfg 托底根;显形 =
7//!   UiShared 日志行)—— 插件面经它交回宿主,插件本体永不碰文件与凭据库;
8//! - 派发按注册名走 `MainEngine::get_typed`(类型安全取壳;tq/ctp 的注册名是
9//!   派生网关名,所以逐类型试取,命中即定面);dm/oms/tq 吃各自 `ui_face::render`,
10//!   ctp(自带键位、无 schema)走 [`crate::pluginwin::render_window`] 视图驱动形态。
11
12use std::sync::Arc;
13
14use ek_cfg::LoadOutcome;
15use ek_cfg::Store as CfgStore;
16use ek_core::cfg_face::CfgHost;
17use ek_core::main_engine::MainEngine;
18use ek_core::plugin::Plugin;
19use ek_core::section::SectionMap;
20
21use crate::pluginwin::WinState;
22use crate::state::UiShared;
23
24/// 插件面出口的稳定注册名(0.3.19 惯例:消费方零字面量,提供方导出口)。
25/// ek-tq / ek-ctp 的注册名是派生网关名,故此处只作缺省参考,派发按类型试取。
26const NS_DM: &str = "dm";
27const NS_OMS: &str = "oms";
28
29/// 命中面 kinds(挂载表与派发共用同一张判定)。
30#[derive(Debug, Clone, Copy, PartialEq, Eq)]
31pub enum FaceKind {
32    /// dm 自带面。
33    Dm,
34    /// oms 自带面。
35    Oms,
36    /// tq 自带面。
37    Tq,
38    /// ctp 走通用窗口(视图驱动)。
39    Ctp,
40}
41
42/// 宿主桥:插件面的 `cfg_save`/`cfg_load`/`log` 落点(写档 + 日志显形)。
43pub struct CfgBridge {
44    /// ek-cfg 托底根(`Store::eco()` 成功时才有;失败 = 界面只显形不落盘)。
45    pub cfg: Option<CfgStore>,
46    /// 宿主日志面(窗口右下日志区)。
47    pub shared: Arc<UiShared>,
48}
49
50impl CfgBridge {
51    /// 用当前托底根 + 宿主日志面建桥(`eco()` 失败 = 只显形不落盘)。
52    #[must_use]
53    pub fn new(shared: &Arc<UiShared>) -> Self {
54        Self {
55            cfg: CfgStore::eco().ok(),
56            shared: Arc::clone(shared),
57        }
58    }
59}
60
61impl CfgHost for CfgBridge {
62    fn cfg_save(&self, ns: &str, map: &SectionMap) -> Result<(), String> {
63        match &self.cfg {
64            Some(c) => c.store(ns, map).map_err(|e| e.to_string()),
65            None => Err("档案托底根不可用(Store::eco 失败)".to_string()),
66        }
67    }
68
69    fn cfg_load(&self, ns: &str) -> Result<Option<SectionMap>, String> {
70        match &self.cfg {
71            Some(c) => match c.load(ns) {
72                Ok(LoadOutcome::Loaded { map, .. }) => Ok(Some(map)),
73                Ok(LoadOutcome::Fresh) => Ok(None),
74                Err(e) => Err(e.to_string()),
75            },
76            None => Err("档案托底根不可用(Store::eco 失败)".to_string()),
77        }
78    }
79
80    fn log(&self, line: &str) {
81        self.shared.log_line(line);
82    }
83}
84
85/// 一帧宿主的状态包(各插件面自带态 + 通用窗口态表)。
86#[derive(Default)]
87pub struct FaceStates {
88    /// dm 面态。
89    pub dm: ek_dm::ui_face::DmUiState,
90    /// oms 面态。
91    pub oms: ek_oms::ui_face::OmsUiState,
92    /// tq 面态。
93    pub tq: ek_tq::ui_face::TqUiState,
94    /// 通用窗口态表(注册名 → 窗口态)。
95    pub wins: std::collections::HashMap<String, WinState>,
96}
97
98impl FaceStates {
99    /// 空态表。
100    #[must_use]
101    pub fn new() -> Self {
102        Self::default()
103    }
104}
105
106/// 按注册名定面(逐类型试取:dm/oms 是定名,tq/ctp 是派生网关名)。
107#[must_use]
108pub fn face_of(engine: &MainEngine, name: &str) -> Option<FaceKind> {
109    if engine
110        .get_typed::<ek_dm::plugin::DmPlugin>(name)
111        .is_some_and(|p| p.as_configurable().is_some())
112    {
113        return Some(FaceKind::Dm);
114    }
115    if engine
116        .get_typed::<ek_oms::plugin::OmsPlugin>(name)
117        .is_some_and(|p| p.as_configurable().is_some())
118    {
119        return Some(FaceKind::Oms);
120    }
121    if engine
122        .get_typed::<ek_tq::plugin::TqPlugin>(name)
123        .is_some_and(|p| p.as_configurable().is_some())
124    {
125        return Some(FaceKind::Tq);
126    }
127    if engine
128        .get_typed::<ek_ctp::plugin::CtpPlugin>(name)
129        .is_some_and(|p| p.as_configurable().is_some())
130    {
131        return Some(FaceKind::Ctp);
132    }
133    None
134}
135
136/// 挂载表:在册且有面的插件(注册名, 窗口标题)。未装配的不出现(判据 U-W1)。
137#[must_use]
138pub fn mounted(engine: &MainEngine) -> Vec<(String, String)> {
139    let mut out: Vec<(String, String)> = Vec::new();
140    for name in engine.plugin_names() {
141        let title = match face_of(engine, &name) {
142            Some(FaceKind::Dm) => "数据管理器 dm",
143            Some(FaceKind::Oms) => "统一受理 oms",
144            Some(FaceKind::Tq) => "天勤桥 tq",
145            Some(FaceKind::Ctp) => "行情网关 ctp",
146            None => continue,
147        };
148        out.push((name, title.to_string()));
149    }
150    out
151}
152
153/// 按段派发受理(档案包导入用):段名 → 在册插件壳 → `apply_config` 回执。
154/// 取不到壳 = `None`(只收编不受理,导入不炸)。
155pub fn apply_section(engine: &MainEngine, ns: &str, section: &SectionMap) -> Option<String> {
156    // 定名段直取;派生网关名段(ctp/tq)按 ns 扫在册壳的 config_ns 对上号。
157    let rep = match ns {
158        NS_DM => {
159            let p = engine.get_typed::<ek_dm::plugin::DmPlugin>(ns)?;
160            p.as_configurable()?.apply_config(section).ok()?
161        }
162        NS_OMS => {
163            let p = engine.get_typed::<ek_oms::plugin::OmsPlugin>(ns)?;
164            p.as_configurable()?.apply_config(section).ok()?
165        }
166        _ => {
167            // 扫在册插件:谁的 config_ns == ns 就派谁(tq/ctp 的派生名段)
168            let mut found = None;
169            for name in engine.plugin_names() {
170                if let Some(kind) = face_of(engine, &name) {
171                    let hit = match kind {
172                        FaceKind::Tq => engine
173                            .get_typed::<ek_tq::plugin::TqPlugin>(&name)
174                            .and_then(|p| {
175                                let c = p.as_configurable()?;
176                                (c.config_ns() == ns).then(|| c.apply_config(section).ok())
177                            }),
178                        FaceKind::Ctp => engine
179                            .get_typed::<ek_ctp::plugin::CtpPlugin>(&name)
180                            .and_then(|p| {
181                                let c = p.as_configurable()?;
182                                (c.config_ns() == ns).then(|| c.apply_config(section).ok())
183                            }),
184                        _ => None,
185                    };
186                    if let Some(Some(r)) = hit {
187                        found = Some(r);
188                        break;
189                    }
190                }
191            }
192            found?
193        }
194    };
195    Some(format!(
196        "受理 采纳{}/待重启{}",
197        rep.adopted.len(),
198        rep.deferred.len()
199    ))
200}
201
202/// 派发一个插件的面。返回 true = 本帧有交互(宿主按需重绘)。
203pub fn render_face(
204    ui: &mut egui::Ui,
205    engine: &MainEngine,
206    name: &str,
207    host: &dyn CfgHost,
208    fs: &mut FaceStates,
209) -> bool {
210    match face_of(engine, name) {
211        Some(FaceKind::Dm) => {
212            let Some(p) = engine.get_typed::<ek_dm::plugin::DmPlugin>(name) else {
213                ui.label("dm 壳取不到(未装配?)");
214                return false;
215            };
216            ek_dm::ui_face::render(ui, &p, host, &mut fs.dm)
217        }
218        Some(FaceKind::Oms) => {
219            let Some(p) = engine.get_typed::<ek_oms::plugin::OmsPlugin>(name) else {
220                ui.label("oms 壳取不到(未装配?)");
221                return false;
222            };
223            ek_oms::ui_face::render(ui, &p, host, &mut fs.oms)
224        }
225        Some(FaceKind::Tq) => {
226            let Some(p) = engine.get_typed::<ek_tq::plugin::TqPlugin>(name) else {
227                ui.label("tq 壳取不到(未装配?)");
228                return false;
229            };
230            ek_tq::ui_face::render(ui, &p, host, &mut fs.tq)
231        }
232        Some(FaceKind::Ctp) => {
233            let Some(p) = engine.get_typed::<ek_ctp::plugin::CtpPlugin>(name) else {
234                ui.label("ctp 壳取不到(未装配?)");
235                return false;
236            };
237            let st = fs.wins.entry(name.to_string()).or_default();
238            crate::pluginwin::render_window(ui, p.as_ref(), host, st)
239        }
240        None => {
241            ui.label(format!("{name}:无自带面(不在挂载表)"));
242            false
243        }
244    }
245}