窗口与页面
当前项目把窗口主循环和页面声明分开:
main.cpp:创建 GLFW 窗口、OpenGL 上下文、主动帧率节流、事件等待。app/app.h:定义应用入口函数。app/dsl_app.h:把 DSL Runtime 接到应用生命周期。app/*.cpp:每个 cpp 是一个独立 demo / 页面程序。CMakeLists.txt:为每个app/*.cpp生成一个可执行程序。
页面开发通常只需要写 app/*.cpp。
DSL App 入口
使用 DSL 的 app 文件包含:
#include "app/dsl_app.h"
然后实现:
namespace app {
const DslAppConfig& dslAppConfig();
void compose(core::dsl::Ui& ui, const core::dsl::Screen& screen);
} // namespace app
DslAppConfig
当前字段:
struct DslAppConfig {
const char* title = "App";
const char* pageId = "app";
core::Color clearColor = {0.16f, 0.18f, 0.20f, 1.0f};
int windowWidth = 800;
int windowHeight = 600;
bool showFrameCountInTitle = false;
double fps = 0.0;
const char* iconPath = "assets/icon.png";
};
title:窗口标题。pageId:Runtime compose 页面时使用的页面 id,必须稳定。clearColor:Runtime cache 重建/full redraw 时使用的背景色。windowWidth/windowHeight:初始窗口大小。showFrameCountInTitle:是否在窗口标题里显示实际 render/swap FPS。fps:动画时的主动帧率上限。0.0表示不额外限制,正数表示最多多少 FPS,例如90.0。iconPath:窗口图标 PNG 路径,默认使用assets/icon.png。Windows exe 图标由 CMake 挂载assets/icon.ico。
示例:
const DslAppConfig& dslAppConfig() {
static const DslAppConfig config = {
"Calculator",
"calculator",
{0.07f, 0.075f, 0.085f, 1.0f},
430,
760,
false,
90.0
};
return config;
}
calculator 当前把 fps 写成 90.0。gallery 默认 fps = 90.0;Settings 中打开 Unlock 90 FPS limit 后会把 fps 同步为 0.0,回到显示器刷新率。默认关闭 Glass/blur。
compose
compose(core::dsl::Ui& ui, const core::dsl::Screen& screen) 是页面声明入口。
screen.width 和 screen.height 是逻辑尺寸,已经由 app/dsl_app.h 根据 DPI scale 换算。页面布局应使用这个逻辑尺寸,不要直接取 framebuffer 像素尺寸。
推荐根节点写法:
void compose(core::dsl::Ui& ui, const core::dsl::Screen& screen) {
ui.row("root")
.size(screen.width, screen.height)
.content([&] {
// sidebar
// content
})
.build();
}
页面状态可以放在 app/*.cpp 的匿名 namespace 中:
namespace {
int selectedPage = 0;
bool optionMotion = true;
} // namespace
点击事件修改状态后,app/dsl_app.h 会在 Runtime 需要重新 compose 时重新声明页面,并标记 full redraw。
主循环行为
main.cpp 当前负责:
- 初始化 GLFW。
- 创建 OpenGL 3.3 core profile 窗口。
- 加载 glad。
- 调用
app::initialize(window)。 - 每轮计算
deltaSeconds。 - 读取 framebuffer 尺寸、DPI scale、pointer scale。
- 调用
app::update(...)。 - 只有 Runtime 请求渲染时才调用
app::render(...)和glfwSwapBuffers(...)。 - 没有动画时用
glfwWaitEvents()休眠。 - 有动画时按窗口所在显示器 refresh rate 主动节流。
- 如果
DslAppConfig::fps为正数,则动画帧率上限为min(refreshRate, fps)。
当前主动节流使用 glfwSwapInterval(0),避免和 SwapBuffers 的 vsync 阻塞叠加。
Windows 下会调用 timeBeginPeriod(1),并在 CMake 中链接 winmm,用于改善高刷新率等待精度。
dsl_app.h 行为
app/dsl_app.h 内部持有一个静态 core::dsl::Runtime。
它不会每轮都 compose。当前只在这些情况 compose:
- 首次运行。
- 逻辑尺寸变化。
- 点击回调修改业务状态,Runtime 返回
needsCompose()。
点击导致重新 compose 后,会立即用 deltaSeconds = 0 调一次 Runtime update,让新 DSL 树和 Runtime 缓存实例同步,然后标记 full redraw。
Demo 程序
当前 app/ 下主要有:
demo.cpp:简单 DSL 示例。gallery.cpp:完整 Gallery,包含 Controls / Text / Animation / Settings / Bing / About。calculator.cpp:计算器 demo,示例中直接通过DslAppConfig::fps = 90.0限制动画帧率。
CMakeLists.txt 会扫描 app/*.cpp:
file(GLOB APP_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/app/*.cpp")
foreach(app_source ${APP_SOURCES})
get_filename_component(app_name "${app_source}" NAME_WE)
add_executable(${app_name} main.cpp "${app_source}" ${CORE_SOURCES})
configure_opengl_app(${app_name})
endforeach()
所以新增 app/example.cpp 后,会生成 example.exe。
脏区与整屏重绘
当前 DSL Runtime 支持 framebuffer cache + scissor 的脏区渲染。
常见局部重绘:
- hover / pressed 颜色变化。
- 按钮按下缩放。
- transition 中的位置、透明度、颜色、旋转等变化。
- 文本颜色或透明度变化。
常见 full redraw:
- 首帧。
- 窗口/framebuffer 尺寸变化。
- 页面状态变化后重新 compose。
- dirty rect 触碰到 blur 元素 1.2 倍保护范围。
- Runtime cache 创建或重建。
页面和组件不用自己实现脏区,但要做到:
- id 稳定。
- 结构尽量稳定。
- 动画写成 DSL 属性目标值。
- hover / pressed 用
.states()。 - 点击只改业务状态,不直接碰 primitive。
当前限制
- 还没有独立 Router。
- 已有矩形 clip 和滚动条/offset 控制;圆角 clip、复杂 overflow 规则还没下沉。
- 已有基础键盘 focus / text input / 选择 / 剪贴板;IME 组合态和撤销栈还没下沉。
- 已有基础 z-index;它只影响同级绘制和 topmost hit-test,不参与布局。
- blur 依赖 framebuffer cache,触碰其 1.2 倍保护范围的变化会保守升级 full redraw。
- 当前不是 QML 那样的 scene graph/batch renderer。