mcpField
M
makepad-2.0-theme

makepad-2.0-theme

github.com/zhanghandong设计与 UI75252 次安装29 周前更新
代码仓库

关于 makepad-2.0-theme

为 Makepad 2.0 应用提供 Splash 脚本可调用的设计令牌系统,涵盖颜色、字体、间距和控件状态。内置深色、浅色与骨架主题,便于统一界面样式并切换主题。

主要功能

  1. 01内置深色、浅色和骨架主题
  2. 02提供颜色与控件状态令牌
  3. 03提供字体样式和字号变量
  4. 04提供间距及尺寸预设
  5. 05支持调整主题全局参数

适用场景

  1. 01为应用统一颜色、字体与间距
  2. 02在深色和浅色主题间切换

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/zhanghandong/makepad-skills --skill makepad-2.0-theme

安装后在 Claude Code 中输入/makepad-2.0-theme确认已加载。

附带文件references/theme-variables.md

详细介绍

Makepad 2.0 主题系统

概述

Makepad 2.0 主题系统提供了一套全面的设计令牌,可通过 Splash 脚本中的 theme.* 变量访问。它能为整个应用的颜色、排版、间距和控件状态提供一致的样式。

提供三种内置主题:

  • mod.themes.dark -- 深色桌面主题(默认)
  • mod.themes.light -- 浅色桌面主题
  • mod.themes.skeleton -- 使用硬编码值的极简骨架主题

黄金法则:在生产环境的 UI 中,颜色、字号或间距始终使用 theme.* 变量,而不要使用硬编码值。这样可确保应用自动支持主题切换,并保持视觉一致性。

在 App::run 中设置主题

必须在加载控件之前加载主题。标准模式如下:

impl App {
    fn run(vm: &mut ScriptVm) -> Self {
        // Step 1: Load theme definitions (dark, light, skeleton)
        crate::makepad_widgets::theme_mod(vm);

        // Step 2: Select active theme (MUST come before widgets_mod)
        script_eval!(vm, {
            mod.theme = mod.themes.light  // or mod.themes.dark
        });

        // Step 3: Load widget definitions (they reference mod.theme)
        crate::makepad_widgets::widgets_mod(vm);

        // Step 4: Load your app's script_mod
        App::from_script_mod(vm, self::script_mod)
    }
}

如果跳过步骤 1-2,默认主题将是深色主题(在 theme_mod 中设置)。 计数器示例使用简化的单行写法 crate::makepad_widgets::script_mod(vm),它会将步骤 1-3 与默认深色主题一并加载。

内部工作原理

theme_mod() 会执行以下操作:

  1. 调用 makepad_draw::script_mod(vm) 加载绘图基元
  2. 创建 mod.themes 模块
  3. 加载 theme_desktop_dark、theme_desktop_light 和 theme_desktop_skeleton
  4. 将 mod.theme = mod.themes.dark 设为默认值

随后,widgets_mod() 会创建 mod.prelude.widgets_internal,并设置 theme: mod.theme,让所有 use mod.prelude.widgets.* 的控件脚本都能使用 theme.*。

主题全局参数

每个主题都定义了可调整的全局参数,用于控制整体观感:

参数用途默认值(深色/浅色)
color_contrast控制调色板范围1.0
color_tint应用于背景的色调#0000ff
color_tint_amount色调的应用程度(0-1)0.0
space_factor基础间距倍数6.0
corner_radius基础圆角半径2.5
beveling斜面强度0.75
font_size_base基础字号(px)10.0
font_size_contrast各级字号之间的步长2.5

主题颜色变量——主要颜色

这些是你在应用代码中最常用的颜色:

变量用途浅色外观深色外观
theme.color_bg_app应用背景浅灰色(约 #DDD)深灰色(约 #333)
theme.color_fg_app前景层稍深稍浅
theme.color_bg_container卡片/容器背景半透明浅色半透明深色
theme.color_bg_even交替行(偶数行)较浅较深
theme.color_bg_odd交替行(奇数行)较深较浅
theme.color_bg_highlight高亮背景类白色 #FFFFFF22低不透明度的类白色
theme.color_bg_highlight_inline行内高亮color_d_1color_d_3
theme.color_bg_unfocussed未聚焦高亮bg_highlight 的 85%bg_highlight 的 85%
theme.color_app_caption_bar标题栏背景透明透明
theme.color_white纯白色#FFFFFF#FFFFFF
theme.color_makepadMakepad 品牌色#FF5C39#FF5C39

主题颜色变量 -- 文本和标签

变量用途
theme.color_label_inner内部元素(按钮、标签)上的主要文本
theme.color_label_inner_hover悬停时的文本
theme.color_label_inner_down按下时的文本
theme.color_label_inner_focus聚焦时的文本
theme.color_label_inner_active激活/选中时的文本
theme.color_label_inner_inactive次要/弱化文本
theme.color_label_inner_disabled禁用状态的文本
theme.color_label_outer外部元素(选项卡、标题)上的主要文本
theme.color_label_outer_off关闭状态的外部文本
theme.color_label_outer_disabled禁用状态的外部文本
theme.color_text通用文本颜色
theme.color_text_hover悬停时的文本
theme.color_text_focus聚焦时的文本
theme.color_text_disabled禁用状态的文本
theme.color_text_placeholder占位文本
theme.color_text_meta元数据文本
theme.color_text_cursor文本光标颜色

主题颜色变量 -- 控件状态

凸起颜色(按钮、凸起元素):

变量用途
theme.color_outset默认按钮背景
theme.color_outset_hover悬停时的按钮
theme.color_outset_down按下时的按钮
theme.color_outset_active切换控件的激活状态
theme.color_outset_focus聚焦的按钮
theme.color_outset_disabled禁用的按钮
theme.color_outset_inactive未激活的按钮

凹陷颜色(文本输入框、复选框、单选按钮):

变量用途
theme.color_inset默认输入框背景
theme.color_inset_hover悬停时的输入框
theme.color_inset_focus聚焦时的输入框
theme.color_inset_disabled禁用的输入框
theme.color_inset_empty空输入框

选择和高亮:

变量用途
theme.color_selection_focus文本选择高亮
theme.color_selection_hover悬停时的选择区域
theme.color_highlight通用强调/高亮
theme.color_cursor光标颜色
theme.color_cursor_focus聚焦状态的光标

主题颜色变量 -- 语义/状态

变量用途值
theme.color_error错误状态红色(#C00)
theme.color_warning警告状态橙色(#FA0)
theme.color_high高严重性红色(#C00)
theme.color_mid中等严重性橙色(#FA0)
theme.color_low低严重性黄绿色(#8A0)
theme.color_panic恐慌/危急状态洋红色(#f0f)

主题颜色变量 -- 斜面系统

主题采用分层斜面系统,以实现控件的 3D 效果:

组变量用途
color_bevel*_hover、_focus、_active、_down、_disabled平面斜面
color_bevel_inset_1*相同后缀内阴影(第 1 层)
color_bevel_inset_2*相同后缀内高光(第 2 层)
color_bevel_outset_1*相同后缀外高光(第 1 层)
color_bevel_outset_2*相同后缀外阴影(第 2 层)

主题颜色变量 -- 其他控件颜色

变量组用途
theme.color_icon*图标颜色(默认、未激活、已激活、禁用)
theme.color_mark*复选标记/单选标记颜色
theme.color_val*进度条和滑块填充颜色
theme.color_handle*滑块手柄颜色
theme.color_shadow*阴影效果
theme.color_drag_quad拖动预览叠加层
theme.color_dock_tab_active活动停靠选项卡背景

主题字体变量

字体大小

字体大小根据 font_size_base 和 font_size_contrast 计算:

变量公式默认值
theme.font_size_1base + 8 * contrast30.0(最大标题)
theme.font_size_2base + 4 * contrast20.0(中号标题)
theme.font_size_3base + 2 * contrast15.0(小号标题)
theme.font_size_4base + 1 * contrast12.5(副标题)
theme.font_size_pbase10.0(正文)
theme.font_size_code固定值9.0(等宽代码)
字体样式(TextStyle 对象)
变量描述字体文件
theme.font_regular常规字重正文IBMPlexSans-Text.ttf
theme.font_bold粗体/半粗体文本IBMPlexSans-SemiBold.ttf
theme.font_italic斜体文本IBMPlexSans-Italic.ttf
theme.font_bold_italic粗斜体文本IBMPlexSans-BoldItalic.ttf
theme.font_code等宽代码字体LiberationMono-Regular.ttf
theme.font_label标签文本(旧版)IBMPlexSans-Text.ttf
theme.font_icons图标字体(FontAwesome)fa-solid-900.ttf

每种字体样式均支持多种语言:

  • 拉丁文:IBM Plex Sans 系列
  • 中文:LXGW WenKai 系列
  • Emoji:Noto Color Emoji
行间距常量
变量值用途
theme.font_wdgt_line_spacing1.2组件文本
theme.font_hl_line_spacing1.05标题文本
theme.font_longform_line_spacing1.2长篇文本

主题间距变量

基础间距

间距根据 space_factor(默认值为 6.0)计算:

变量公式默认值
theme.space_10.5 * space_factor3.0(超小)
theme.space_21.0 * space_factor6.0(小/标准)
theme.space_31.5 * space_factor9.0(中)
外边距/内边距预设(Inset 对象)

四边预设:

变量描述值
theme.mspace_1四边 XS 内边距每边 3px
theme.mspace_2四边 SM 内边距每边 6px
theme.mspace_3四边 MD 内边距每边 9px

仅水平方向的预设:

变量描述值
theme.mspace_h_1XS 水平内边距左/右:3px,上/下:0
theme.mspace_h_2SM 水平内边距左/右:6px,上/下:0
theme.mspace_h_3MD 水平内边距左/右:9px,上/下:0

仅垂直方向的预设:

变量描述值
theme.mspace_v_1XS 垂直内边距上/下:3px,左/右:0
theme.mspace_v_2SM 垂直内边距上/下:6px,左/右:0
theme.mspace_v_3MD 垂直内边距上/下:9px,左/右:0
尺寸变量
变量用途默认值
theme.data_item_height标准数据行高度~23px
theme.data_icon_width标准图标宽度~16px
theme.data_icon_height标准图标高度~22px
theme.container_corner_radius容器边框圆角半径5.0
theme.textselection_corner_radius文本选区圆角半径1.25
theme.tab_height标签栏高度36.0

在 Splash 中使用主题变量

颜色
// Background color
draw_bg.color: theme.color_bg_container

// Text color
draw_text.color: theme.color_label_inner

// Muted/secondary text
draw_text.color: theme.color_label_inner_inactive

// Color math -- multiply for opacity
draw_text.color: theme.color_label_inner_inactive * 0.8

// Semantic colors
draw_bg.color: theme.color_warning
draw_bg.color: theme.color_error
字体大小
// Body text size
draw_text.text_style.font_size: theme.font_size_p

// Heading sizes
draw_text.text_style.font_size: theme.font_size_1   // Largest
draw_text.text_style.font_size: theme.font_size_2   // Medium
draw_text.text_style.font_size: theme.font_size_3   // Small
draw_text.text_style.font_size: theme.font_size_4   // Sub-heading

// Code font size
draw_text.text_style.font_size: theme.font_size_code
字体样式
// Bold text with custom size (override with {} syntax)
draw_text.text_style: theme.font_bold{font_size: theme.font_size_2}

// Bold text keeping default size
draw_text.text_style: theme.font_bold{}

// Code font
draw_text.text_style: theme.font_code{}

// Override font size using +: merge syntax
draw_text +: {text_style +: {font_size: theme.font_size_3}}
间距
// Uniform padding
padding: theme.mspace_2

// Padding with overrides
padding: theme.mspace_2{left: theme.space_3, right: theme.space_3}

// Horizontal-only padding with custom values
padding: theme.mspace_h_1{left: theme.space_2, right: theme.space_2}

// Spacing between children
spacing: theme.space_2

// Computed spacing
width: Fill height: 9. * theme.space_1
padding: theme.mspace_3{left: theme.space_3 * 2, right: theme.space_3 * 2}

颜色语法参考

Splash 支持多种颜色格式(并非主题专属,但必不可少):

语法示例描述
#RGB#f00短十六进制(红色)
#RRGGBB#ff0000完整十六进制
#RRGGBBAA#ff000080带 Alpha 通道的十六进制
#xRRGGBB#x2ecc71以字母 e 开头的十六进制(前缀为 #x)
#N#D灰度简写
vec4(r,g,b,a)vec4(1.0, 0.0, 0.0, 1.0)RGBA 浮点值(0.0-1.0)
theme.*theme.color_highlight主题变量

重要:十六进制颜色以字母 e 开头时,请使用 #x 前缀, 以避免与科学计数法混淆。例如,使用 #x2ecc71 而非 #2ecc71。

支持颜色运算:

// Multiply for opacity
theme.color_label_inner_inactive * 0.8

// Mix two colors
mix(theme.color_w, theme.color_b, 0.5)

运行时切换主题

你可以在 Rust 事件处理器中动态切换主题:

impl MatchEvent for App {
    fn handle_actions(&mut self, cx: &mut Cx, actions: &Actions) {
        if self.ui.button(cx, ids!(toggle_theme)).clicked(actions) {
            script_eval!(cx, {
                mod.theme = mod.themes.dark  // or mod.themes.light
            });
            // All widgets using theme.* will pick up new values
            // You may need to trigger a re-render
        }
    }
}

也可以在加载 widget 之前切换主题(在 App::run 中),以静态选择主题:

fn run(vm: &mut ScriptVm) -> Self {
    crate::makepad_widgets::theme_mod(vm);
    script_eval!(vm, {
        mod.theme = mod.themes.light
    });
    crate::makepad_widgets::widgets_mod(vm);
    App::from_script_mod(vm, self::script_mod)
}

完整的主题感知 UI 示例

此示例演示如何仅使用主题变量创建主题化卡片列表:

script_mod! {
    use mod.prelude.widgets.*

    let CardItem = RoundedView{
        width: Fill height: Fit
        padding: theme.mspace_2{left: theme.space_3, right: theme.space_3}
        flow: Right spacing: theme.space_2
        align: Align{y: 0.5}
        draw_bg.color: theme.color_bg_container
        draw_bg.border_radius: theme.container_corner_radius

        icon := Label{
            width: 24 height: 24
            text: ""
        }
        content := View{
            width: Fill height: Fit
            flow: Down spacing: theme.space_1
            title := Label{
                text: "Title"
                draw_text.color: theme.color_label_inner
                draw_text.text_style: theme.font_bold{font_size: theme.font_size_4}
            }
            subtitle := Label{
                text: "Subtitle"
                draw_text.color: theme.color_label_inner_inactive
                draw_text.text_style.font_size: theme.font_size_p
            }
        }
        badge := RoundedView{
            width: Fit height: Fit
            padding: theme.mspace_h_1
            draw_bg.color: theme.color_bg_highlight_inline
            draw_bg.border_radius: 4.0
            badge_label := Label{
                text: "new"
                draw_text.color: theme.color_highlight
                draw_text.text_style.font_size: theme.font_size_code
                draw_text.text_style: theme.font_bold{}
            }
        }
    }

    startup() do #(App::script_component(vm)){
        ui: Root{
            main_window := Window{
                pass.clear_color: theme.color_bg_app
                window.inner_size: vec2(400, 600)
                body +: {
                    width: Fill height: Fill
                    flow: Down spacing: 0

                    // Header
                    SolidView{
                        width: Fill height: Fit
                        padding: theme.mspace_3
                        draw_bg.color: theme.color_app_caption_bar
                        Label{
                            text: "My Cards"
                            draw_text.color: theme.color_label_inner
                            draw_text.text_style: theme.font_bold{font_size: theme.font_size_2}
                        }
                    }

                    // Card list
                    ScrollYView{
                        width: Fill height: Fill
                        padding: theme.mspace_2
                        flow: Down spacing: theme.space_1
                        CardItem{title.text: "First Card" subtitle.text: "Description here"}
                        CardItem{title.text: "Second Card" subtitle.text: "Another card"}
                    }

                    // Footer
                    SolidView{
                        width: Fill height: Fit
                        padding: theme.mspace_2
                        draw_bg.color: theme.color_bg_container
                        Label{
                            text: "2 items"
                            draw_text.color: theme.color_label_inner_inactive
                            draw_text.text_style.font_size: theme.font_size_code
                        }
                    }
                }
            }
        }
    }
}

最佳实践

  1. 生产应用中的颜色始终使用 theme.* -- 当存在 theme.color_error 时,绝不要硬编码 #ff0000。 这样可以启用主题切换和无障碍支持。

  2. 使用主题字体以保持排版一致 -- 优先使用 theme.font_bold{font_size: theme.font_size_2}, 而不是手动指定字体系列。

  3. 使用主题间距以保持布局一致 -- theme.mspace_2 和 theme.space_2 可以营造和谐的节奏。避免使用 padding: Inset{top: 8, ...} 这样的神奇数字。

  4. 使用 {} 语法覆盖 -- 扩展主题值,而不是替换它们: theme.font_bold{font_size: 20} 保留粗体字体系列,但覆盖字号。 theme.mspace_2{left: theme.space_3} 保留上、右、下边距,但覆盖左边距。

  5. 使用合并运算符 +: 进行部分覆盖 -- 只想更改某个嵌套属性时: draw_text +: {text_style +: {font_size: theme.font_size_3}}

  6. 选择合适的文本颜色变量:

    • theme.color_label_inner 用于主要 UI 文本(按钮、标签)
    • theme.color_label_inner_inactive 用于次要/弱化文本
    • theme.color_text 用于常规内容文本
    • theme.color_text_placeholder 用于占位文本
  7. 使用适配状态的颜色变体 -- 在悬停/聚焦/按下时会变化的 widget 应使用匹配的 _hover、_focus、_down 后缀。

  8. 通过乘法调整透明度 -- theme.color_label_inner_inactive * 0.8 可以生成更柔和的变体,而无需新增变量。

  9. 在 widgets_mod 之前选择主题 -- 必须在调用 theme_mod() 和 widgets_mod() 之间设置主题,这样 widget 定义才能获取 正确的主题值。

  10. 对于简单应用,使用 crate::makepad_widgets::script_mod(vm),它会通过一次调用加载 所有内容,并使用默认的深色主题。

源文件

  • 深色主题:widgets/src/theme_desktop_dark.rs
  • 浅色主题:widgets/src/theme_desktop_light.rs
  • 主题骨架:widgets/src/theme_desktop_skeleton.rs
  • 主题加载器:widgets/src/lib.rs(theme_mod 和 widgets_mod 函数)
  • 用法示例:examples/todo/src/app.rs(浅色主题,完整使用主题变量)
  • 用法示例:examples/counter/src/app.rs(默认深色主题)

评论

支持 Markdown