Skip to main content

ek_ui/
dock.rs

1//! 0.3.0 Wave C(BD-UI-48):下带四组监控格换 `egui_dock` 树。
2//!
3//! 框架形状按交接报告 §6 的定案:**菜单条 / 行情带 / 下单侧保持 egui 原生面板,不进 dock**;
4//! 只有下带右侧数据区(委托|未成交 / 成交 / 持仓 / 资金|日志)换成 dock 树,
5//! 在 `CentralPanel` 里用 `DockArea::show_inside`(`DockArea::show` 与 `CentralPanel::show` 互斥)。
6//!
7//! spike 验收(`.dsh/plans/ek-ui-WaveC-测试方案.md` §1,commit `52104cd`)留下的三条硬口径:
8//! 1. dock 的 tab 节点在 AccessKit 里**查得到但精确标签查不到**(标签比标题长)——判据一律 `contains`;
9//! 2. 默认树必须显式 `split`(`DockState::new` 只会把所有 tab 塞进一个叶子),
10//!    `Right 0.5` + 两半各 `Below 0.5` 才是 Python `right_splitter` 的 50:50;
11//! 3. 核心区不能被关:`show_close_buttons(false)` + `is_closeable=false` + `on_close=Ignore`。
12
13use egui_dock::tab_viewer::OnCloseResponse;
14use egui_dock::{
15    AllowedSplits, DockArea, DockState, Node, NodeIndex, Split, Style, SurfaceIndex, TabViewer,
16};
17
18use crate::state::UiShared;
19
20/// 下带四组(对应 Python `mainwindow.py:253-254` `right_splitter` 里的四个 widget)。
21#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
22pub enum DataGroup {
23    /// 左上:委托 / 未成交两 tab。
24    Orders,
25    /// 右上:成交。
26    Trades,
27    /// 左下:持仓。
28    Positions,
29    /// 右下:资金 / 日志两 tab。
30    Funds,
31}
32
33impl DataGroup {
34    /// 固定顺序 = 默认 2×2 的读序(左上 → 右上 → 左下 → 右下)。
35    pub const ALL: [DataGroup; 4] = [
36        DataGroup::Orders,
37        DataGroup::Trades,
38        DataGroup::Positions,
39        DataGroup::Funds,
40    ];
41
42    /// tab 标题 = Python 四块的表题语义(BD-UI-31「表题文字撤除,带位即身份」仍然成立:
43    /// 标题只出现在 tab 上,格内不再重复大字表题)。
44    pub fn title(&self) -> &'static str {
45        match self {
46            DataGroup::Orders => "委托",
47            DataGroup::Trades => "成交",
48            DataGroup::Positions => "持仓",
49            DataGroup::Funds => "资金",
50        }
51    }
52}
53
54/// 默认布局:先左右 50:50 一刀,再左右两半各自上下 50:50 一刀 = 2×2。
55///
56/// 必须显式 `split`:`DockState::new(vec![..])` 只会把所有 tab 塞进**同一个叶子**
57/// (spike 实测:不 split 时四组挤成一列,S4 直接红)。
58pub fn default_tree() -> DockState<DataGroup> {
59    let mut tree = DockState::new(vec![DataGroup::Orders]);
60    let main = SurfaceIndex::main();
61    let [left, right] = tree.split(
62        (main, NodeIndex::from(0usize)),
63        Split::Right,
64        0.5,
65        Node::leaf(DataGroup::Trades),
66    );
67    tree.split(
68        (main, left),
69        Split::Below,
70        0.5,
71        Node::leaf(DataGroup::Positions),
72    );
73    tree.split(
74        (main, right),
75        Split::Below,
76        0.5,
77        Node::leaf(DataGroup::Funds),
78    );
79    tree
80}
81
82/// `TabViewer`:格内容直接复用 0.2.29 的四个 group 渲染函数,**不改一行业务渲染代码**。
83pub struct EkTabViewer<'a> {
84    shared: &'a UiShared,
85}
86
87impl<'a> EkTabViewer<'a> {
88    pub fn new(shared: &'a UiShared) -> Self {
89        Self { shared }
90    }
91}
92
93impl TabViewer for EkTabViewer<'_> {
94    type Tab = DataGroup;
95
96    fn title(&mut self, tab: &mut Self::Tab) -> egui::WidgetText {
97        tab.title().to_string().into()
98    }
99
100    fn ui(&mut self, ui: &mut egui::Ui, tab: &mut Self::Tab) {
101        match tab {
102            DataGroup::Orders => crate::render::render_topleft_group(ui, self.shared),
103            DataGroup::Trades => crate::render::render_trades_group(ui, self.shared),
104            DataGroup::Positions => crate::render::render_positions_group(ui, self.shared),
105            DataGroup::Funds => crate::render::render_bottomright_group(ui, self.shared),
106        }
107    }
108
109    fn id(&mut self, tab: &mut Self::Tab) -> egui::Id {
110        egui::Id::new(*tab)
111    }
112
113    // 核心区不能被关(S5):三处一起关死,不留「关掉就没了」的入口。
114    fn is_closeable(&self, _tab: &Self::Tab) -> bool {
115        false
116    }
117
118    fn closeable(&mut self, _tab: &mut Self::Tab) -> bool {
119        false
120    }
121
122    fn on_close(&mut self, _tab: &mut Self::Tab) -> OnCloseResponse {
123        OnCloseResponse::Ignore
124    }
125
126    // 四组都是数据区常驻件,允许被 detach 成独立窗(用户裁定:tab 可见 + 允许 detach)。
127    fn allowed_in_windows(&self, _tab: &mut Self::Tab) -> bool {
128        true
129    }
130}
131
132/// 画下带数据区(`CentralPanel` 内)。样式**只从 egui `Style` 派生**(egui `Style` 已由
133/// `tokens::apply` 填好)——这里不出现任何字面量颜色(V5 机械钉扫的就是这一点)。
134pub fn show_monitor_dock(ui: &mut egui::Ui, shared: &UiShared) {
135    let mut guard = shared.dock_tree();
136    if guard.is_none() {
137        *guard = Some(default_tree());
138    }
139    let tree = guard.as_mut().expect("dock 树已初始化");
140    let style = Style::from_egui(ui.style().as_ref());
141    DockArea::new(tree)
142        .id(egui::Id::new("ek_monitor_dock"))
143        .style(style)
144        .show_close_buttons(false)
145        .show_add_buttons(false)
146        // 实跑截图(0.3.0 尾巴)实锤:`DockArea::new` 的这两个开关默认 true,每个叶子里会画
147        // 「×」(全关)与「▼」(折叠)两颗钮 —— 数据区四组不能被关,这两颗钮就是漏口。
148        .show_leaf_close_all_buttons(false)
149        .show_leaf_collapse_buttons(false)
150        .draggable_tabs(true)
151        .allowed_splits(AllowedSplits::All)
152        .show_inside(ui, &mut EkTabViewer::new(shared));
153}