跳转至

ICFTheme - 主题接口

ICFTheme 是 UI 主题系统的顶层抽象,提供对颜色方案、动画规格、圆角规范和字体样式的统一访问入口。选择把这几个分散的设计维度聚合成一个接口,是因为在实际使用中它们总是作为一个整体被切换——我们很少需要"只换颜色但不换动画"的场景。

接口结构

ICFTheme 本身是一个抽象接口,包含四个子组件的访问器:

#include "ui/core/theme.h"

// 获取主题的各个子组件
const ICFTheme& theme = getTheme();  // 通过 ThemeFactory 获取

auto& colors = theme.color_scheme();  // ICFColorScheme&
auto& motion = theme.motion_spec();   // IMotionSpec&
auto& radius = theme.radius_scale();  // IRadiusScale&
auto& fonts = theme.font_type();      // IFontType&

所有访问器都返回引用,这是因为子组件的生命周期由 ICFTheme 的实现类管理,调用方不需要关心所有权问题。

获取主题实例

ICFTheme 的构造函数是 protected 的,不允许直接构造。这是有意为之——强制使用 ThemeFactory 来创建主题实例,保证所有主题对象都经过统一的创建流程:

#include "ui/core/theme_factory.h"

// 通过工厂创建主题
ThemeFactory* factory = getFactory();  // 获取具体工厂实现
auto theme = factory->fromName("default");  // std::unique_ptr<ICFTheme>

// 从 JSON 创建主题
QByteArray json = loadThemeJson();
auto custom_theme = factory->fromJson(json);

// 序列化主题
QByteArray serialized = factory->toJson(theme.get());

使用工厂模式的好处是可以在运行时动态切换主题实现,比如从文件加载的 JSON 主题和硬编码的默认主题可以共存。

子组件使用

拿到主题对象后,通过它访问各个子组件:

// 获取颜色
QColor primary = theme.color_scheme().queryColor("md.primary");
QColor surface = theme.color_scheme().queryColor("md.surface");

// 获取动画参数
int duration = theme.motion_spec().queryDuration("md.motion.shortEnter");
int easing = theme.motion_spec().queryEasing("md.motion.standard");

// 获取圆角值
float corner = theme.radius_scale().queryRadiusScale("cornerSmall");

// 获取字体
QFont body_font = theme.font_type().queryTargetFont("bodyLarge");

注意这里每个子组件都有自己的查询语法——这是为兼容 Material Design 3 的 token 命名设计的。具体的 token 名称由各个子组件的实现决定,ICFTheme 不做统一规定。

设计决策

把四个子组件聚合到 ICFTheme 里,而不是让使用者分别管理它们,主要是为了简化主题切换逻辑。如果没有这个聚合层,每次切换主题就需要同步替换四个不同的对象,很容易出现不一致的状态——比如颜色方案已经是 dark 模式了,但字体还是 light 模式的。

另一个值得注意的设计点是,子组件用 unique_ptr 持有而不是直接嵌入。这样做允许实现类在运行时选择具体的子组件实现,而不需要在编译期固定类型。对于支持插件式主题扩展的场景,这个灵活性很重要。

线程安全

ICFTheme 接口本身不提供线程安全保证。如果需要在多线程环境下访问同一个主题对象,调用方需要自己加锁。实际使用中,我们通常为每个线程维护独立的主题访问入口,或者通过 TLS 缓存主题查询结果,避免频繁的跨线程访问。

相关文档