ek-ui 0.6.1

ek 生态常驻主界面(egui/eframe):插件即界面——观测页 + 日志页,车头与 UI 同插件
Documentation
//! UiExt(0.2.30,BD-UI-47):语义排版助手。
//!
//! 为什么要有这一层:毛病 7「无视觉层级」的根因不是缺标题,是**同一档文字被用在
//! 不同语义上**——口径说明句(「代码回车 = 订阅」)与正文同字号同色,空态提示与
//! 表内数据同字号同色,于是屏幕上没有主次。Python 侧的层级来自 Qt 控件类型
//! (QTabBar / QHeaderView / QLabel / QLineEdit 各有 qss 规格),egui 里控件类型少,
//! 层级必须由这一层显式给。
//!
//! 纪律:这一层只**派生**自 [`crate::tokens`],不新造颜色、不新造字号。
//! 裁定边界(BD-UI-31「表题文字撤除,带位即身份」)仍然有效:主界面四张监控格
//! **不加**大标题,`section_title` 只用在有 windowTitle 的浮动窗里(Python 侧那些窗
//! 正是有标题的:`widget.py:1659 / 2407 / 2605`)。

use crate::tokens;
use egui::{Align, Layout, Response, RichText, TextStyle, Ui};

/// 区块标题(Heading 档)。只用在浮动窗:运维窗 / 账户窗 / 合约查询窗。
pub fn section_title(ui: &mut Ui, text: impl Into<String>) -> Response {
    ui.label(
        RichText::new(text.into())
            .font(ui.style().text_styles[&TextStyle::Heading].clone())
            .color(ui.visuals().widgets.noninteractive.fg_stroke.color),
    )
}

/// 次级说明:口径句、空态提示、字段旁的一次性提示。Small 档 + 弱化色。
/// 与正文的差别必须**肉眼可分**(字号 + 色两档都降),否则等于没层级。
pub fn muted_label(ui: &mut Ui, text: impl Into<String>) -> Response {
    ui.label(
        RichText::new(text.into())
            .font(ui.style().text_styles[&TextStyle::Small].clone())
            .color(tokens::shadcn::TEXT3),
    )
}

/// 区块之间的呼吸位(比缺省行距大一档,`tokens::GROUP_GAP`)。
pub fn space_group(ui: &mut Ui) {
    ui.add_space(tokens::GROUP_GAP - ui.spacing().item_spacing.y);
}

/// 主操作钮(委托 / 全撤这类)。取色板里 pressed/checked 那一档(`widgets.active`,
/// Nova 下 = `#525252`)—— 色板内合法,不新造颜色;文字加粗。Python 侧下单钮是普通
/// QPushButton,这里是**有意**的层级加强(毛病 7),加强只用色板既有档。
pub fn primary_button(ui: &mut Ui, text: impl Into<String>) -> Response {
    ui.add(
        egui::Button::new(RichText::new(text.into()).strong())
            .fill(ui.visuals().widgets.active.bg_fill)
            .stroke(ui.visuals().widgets.active.bg_stroke),
    )
}

/// 数字格:右对齐 + 不换行。0.2.30 起 `render.rs::tcell` 的右对齐臂就是调它 ——
/// 「数字怎么贴右」只有一处实现,不在每个调用点重抄一遍
/// `with_layout(right_to_left)`(毛病 3「数字不成列」的实现面)。
pub fn num_cell<R>(ui: &mut Ui, add: impl FnOnce(&mut Ui) -> R) -> R {
    ui.with_layout(Layout::right_to_left(Align::Center), add)
        .inner
}

/// Grid 短表的列宽下限(0.4.1 七期 BD-UI-60,用户裁定「表宽默认就是你所在区域的 100%,
/// 字段什么都应该会压缩」)。
///
/// `min_col_width` 是**下限**不是固定值:内容更宽时列照样往上长(grid.rs:231
/// `max(内容宽, min_cell_size.x)`)→ 填不满时铺满、装不下时出横向滚动,与 Python
/// `ResizeToContents` + `stretchLastSection` 同一口径,不截断任何文字。
///
/// 目标宽**第一帧钉死**,之后只跟着容器缩、不跟着容器长:浮动窗是自动尺寸的,列宽若取
/// 「当帧可用宽」,窗就会跟着变宽的内容再长 → 下一帧可用宽更大 → 列再长,`grid.rs:276-278`
/// 每帧 `curr_state != prev_state` → `request_repaint` 死循环(0.4.1 七期实撞:15 条判据
/// 全撞在 `Harness::run exceeded max_steps`,Repaint causes 全是 grid.rs:278)。
pub fn grid_col_width(ui: &Ui, salt: &str, n_cols: usize) -> f32 {
    let id = egui::Id::new(("grid_fill", salt));
    let avail = ui.available_width();
    let stored = ui.ctx().data(|d| d.get_temp::<f32>(id)).unwrap_or(0.0);
    let target = if stored > 0.0 {
        stored.min(avail)
    } else {
        avail
    };
    ui.ctx().data_mut(|d| d.insert_temp(id, target.max(1.0)));
    let gaps = ui.spacing().item_spacing.x * (n_cols as f32 - 1.0);
    ((target - gaps) / n_cols as f32).max(24.0)
}