用 Go 写跨平台原生 GUI 的 SDK。底层用 CGo 绑定 C++ FLTK,上层提供类 iOS UIKit 的 API:addSubview 加块回调。
产物是单个原生可执行文件,不带浏览器引擎,无运行时依赖。
- Use FLTK like the Cocoa Framework for iOS/Mac.
- FLTK Resource Doc
┌────────────────────────────────────────────────────────────┐
│ 编写层(两种写法,共用同一套语义,互不分叉) │
│ │
│ 纯 Go(已交付) TypeScript(开发中) │
│ 直接调 uikit bun 打包 → 字节码 → 内嵌 │
└──────────┬──────────────────────────────┬──────────────────┘
│ │
│ ┌────────▼────────┐
│ │ js_host │ 解释器宿主
│ │ js_uikit │ 绑定面
│ └────────┬────────┘
│ │ 绑定面由 uikit_catalog
│ │ 单一事实源生成,不手抄
┌──────────▼──────────────────────────────▼──────────────────┐
│ uikit 控件语义层:窗口、控件、布局、事件 │
├─────────────────────────────────────────────────────────────┤
│ fltk_bridge C ABI 与 cgo,回调按 id 表分发 │
├─────────────────────────────────────────────────────────────┤
│ FLTK 1.4.5 静态链接。macOS 走 Quartz,Windows 走 GDI+, │
│ Linux 走 Xlib │
└─────────────────────────────────────────────────────────────┘
图中的 js_host、js_uikit 是待实现的运行时层,目前尚无对应包。
分层规矩
- 渲染、布局、动画、逐帧计算全部在原生侧。脚本只在用户操作发生时被叫醒,不参与每一帧。
*_bridge包只做 cgo 与 C 类型转换,里面不出现任何脚本值。- 两种写法共用同一套 uikit 语义,不允许长出第二套控件行为。
实测基线(macOS arm64,一个标签加一个按钮的最小应用)
| 项 | fltk2go | 同机同口径的 Electron |
|---|---|---|
| 分发体积 | 8.4 MB | 288 MB |
| 启动到窗口可显示 | 112~142 ms | 295~304 ms |
| 物理内存 | 26 MB | 78~110 MB |
| 进程数 | 1 | 5 |
| 目录 | 职责 |
|---|---|
uikit/ |
控件语义层,按控件分子包 |
fltk_bridge/ |
C ABI 与 cgo 绑定 |
foundation/ |
Rect 等基础值类型 |
libs/ |
各平台预编译静态库与产物清单 |
fltk_build/ |
构建 FLTK 静态库 |
libs_build/ |
构建 FLTK 之外的原生库 |
examples/ |
示例,每个都能独立运行 |
docs/ |
运行、自动化、API 文档 |
go get github.com/0xdevelop/fltk2go@latestpackage main
import (
"runtime"
"strconv"
"github.com/0xdevelop/fltk2go"
"github.com/0xdevelop/fltk2go/foundation"
"github.com/0xdevelop/fltk2go/uikit/button"
"github.com/0xdevelop/fltk2go/uikit/label"
"github.com/0xdevelop/fltk2go/uikit/window"
)
func main() {
// 必需:绑定主线程。GUI 调用被调度到别的 OS 线程会崩。
runtime.LockOSThread()
win := window.NewUIWindow(600, 400, "Counter")
root := win.RootView()
title := label.NewUILabel(&foundation.Rect{X: 20, Y: 20, Width: 560, Height: 40}, "Clicked 0 count")
btn := button.NewUIButton(&foundation.Rect{X: 20, Y: 80, Width: 160, Height: 44}, "点我 +1")
count := 0
btn.OnTouchUpInside(func() {
count++
title.SetText("Clicked " + strconv.Itoa(count) + " count")
})
root.AddSubview(title)
root.AddSubview(btn)
win.Show()
fltk2go.Run()
}控件从 uikit/<控件> 子包引入,也可以从 uikit 门面包统一引入。
cd examples
go run . # 聚合入口:左侧选择示例,右侧预览
go run ./counter/main.go # 单个示例,可在 IDE 里断点调试./build_fltk.sh # FLTK 静态库
./build_libs.sh quickjs-ng # FLTK 之外的原生库产物落在 libs/<库名>/<系统>/<架构>/,每份带一个清单文件,记录上游版本与构建信息。
目标是让前端开发者用前端思维在 fltk2go 上快速做界面和效果:整个 TS 项目拷进来,经桥接层驱动 FLTK C++ 出原生效果,逐帧计算仍在原生侧。
依赖管理与打包只支持 bun,不支持 npm、yarn、pnpm,也不需要 Node.js 运行时。
边界先讲清楚:能用的是任意结构的 TS 项目和纯逻辑的包;用不了的是依赖浏览器 API 的代码(document、DOM、React/Vue 的渲染部分),因为二进制里没有浏览器。
当前已合入的实验能力:
ts_view→uikit_declaration→uikit_generator:构建期把窗口、label/button 声明生成 Go UIKit 调用,事件指向 Go handler 方法;已有 bun 端到端测试,尚无面向应用的 CLI。uikit_catalog:描述 12 种控件,校验其与现有 UIKit API 的一致性,并提供未来运行时绑定用的 TypeScript 类型声明;目前还未接入ts_view生成链。quickjs_bridge:QuickJS-ng 求值、字节码、宿主回调与句柄对象模型。当前仅附带 macOS amd64/arm64 universal 静态库,默认在 macOS 启用;Linux/Windows 默认不编译该实验包,需要先执行./build_libs.sh quickjs-ng构建本机依赖,再用-tags fltk2go_quickjs启用。
完整 TS 应用打包与运行宿主仍在开发中;目前应使用上面的 Go 入口运行应用。Linux/Windows 的 QuickJS 链路尚未完成运行验证。
给 agent 和测试用的语义化控件通道,不靠屏幕坐标点击。只在设置 FLTK2GO_AUTOMATION_DEBUG=1 时启动;用 -tags release 构建会编进一个禁用桩,发布版无法暴露该服务。
FLTK2GO_AUTOMATION_DEBUG=1 go run .
curl -s http://127.0.0.1:8765/debug/automation/snapshot给控件加稳定 ID:
startButton.View().
SetAutomationID("app.start").
SetAutomationName("Start mapping")接口清单、MCP 用法、agent 工作流见 docs/AUTOMATION_AGENT_GUIDE.md。
- 运行说明 — 环境、运行、测试、常见问题
- Agent 自动化指南
- Playwright 接入
- API 文档
fltk2go 以 BSD-3 发布,见 LICENSE。
底层 FLTK 采用 LGPL 加静态链接豁免:静态链接不构成衍生作品,不要求公开应用源码。若修改 FLTK 本身,改动须按 LGPL 提供。本仓库对 FLTK 的唯一改动是 fltk_build/fltk-1.4.patch,已随仓库公开。使用本库的程序需声明用到了 FLTK。