MoreRSS

site iconSkywind | 林伟修改

有多个 10K+ 开源项目。程序员,1992年(小学)开始写代码,至今乐此不彼。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Skywind | 林伟的 RSS 预览

在 Vim 里实现可定制表单对话框

2026-05-02 20:25:00

Vim 的功能非常强大,但这种强大是有代价的——你得把它全部记住

记忆的负担

就拿替换命令来说。”查找替换”这么一个最常见的编辑操作,在 Vim 里有一堆变体:

  • 替换所有匹配项,而不只是每行第一个?加 g
  • 每次替换前要确认?加 c
  • 不区分大小写?加 i。或者在模式里写 \c
  • 要不要用正则?默认就是正则。但用哪种风格?\v 是 very-magic,\V 是纯文本,还有默认的 magic 模式,各有各的转义规则。
  • 只匹配整个单词?用 \<\> 把模式包起来。容易忘,也容易打错。
  • 替换整个文件?前面加 %。只替换选中区域?用 '<,'>。指定行范围?输入行号。

于是你写出这样的命令:

:%s/\v(foo|bar)/baz/gci

新手要花很久才能记住这些标志位。有经验的用户也会忘掉不常用的那几个。

而且替换只是一个内置命令。真正麻烦的是插件——每个插件都有自己的命令、自己的参数和语法。天天用的那些还好,肌肉记忆自然形成;但那些偶尔才用一次的?每次都得重新翻文档。

这就是 Vim 界面的根本矛盾:命令行天生为速度优化,而不是为可发现性优化。如果你已经记住了命令,它快得飞起;如果你忘了——哪怕只是忘了一个标志位——就只能干瞪眼。

如果能把一个命令的所有可选项一次性展示在用户面前呢?不是藏在 :help 里,而是就在屏幕上,一目了然:

这是一个用 vim-quickui 搭建的查找替换对话框。所有选项清清楚楚:正则模式、大小写敏感、全词匹配、是否确认、替换范围。不管是第一次用,还是隔了几个月再回来用,完全没有记忆负担——你看到有什么选项,勾选你需要的,直接开始。

那么问题来了,在 Vim 里怎么才能做出这样的对话框?

原生工具帮不了你太多。Vim 给你的只有 input() 做单行输入和 inputlist() 做简单列表选择——没有文本框,没有复选框,没有单选按钮,没有办法把多个控件放在一个窗口里。如果要做一个多字段的表单,只能一个接一个地调用阻塞式的 input(),而且填错了前面的内容没法回头修改。

这种方式,不能扩展。

更好的方式:vim-quickui Dialog

vim-quickui 是一个为 Vim 和 NeoVim 设计的 TUI 组件库,提供菜单、列表框、文本框等控件——全部用纯 VimScript 实现,不依赖任何外部工具。

在 1.5.0 版本中,它新增了一套数据驱动的对话框系统:你把控件声明为一组字典,quickui 将它们渲染到一个弹出窗口中;用户完成操作后,所有值以一个字典返回给你。

不需要 +python,不需要 Lua,不需要外部依赖,纯 VimScript 搞定。

对新手来说,这降低了上手 Vim 的门槛——不用先把每个命令和标志位都背下来,才敢去用一个功能。对老手来说,这减少了为偶尔使用的命令反复查文档的次数,让你保持在心流状态里。

安装

vim-plug

Plug 'skywind3000/vim-quickui'

或者用 Vim 内置包管理:

cd ~/.vim/pack/vendor/start && git clone https://github.com/skywind3000/vim-quickui

可选设置 Unicode 边框:

let g:quickui_border_style = 2

完事了,没有构建步骤,没有依赖。

你的第一个对话框

来做一个简单的设置对话框,把下面的代码放到一个函数里:

function! MySettings()
    let items = [
        \ {'type': 'label', 'text': 'Settings:'},
        \ {'type': 'input', 'name': 'name', 'prompt': 'Name:',
        \  'value': 'test'},
        \ {'type': 'radio', 'name': 'choice', 'prompt': 'Pick:',
        \  'items': ['A', 'B', 'C']},
        \ {'type': 'check', 'name': 'flag',
        \  'text': 'Enable Feature'},
        \ {'type': 'button', 'name': 'confirm',
        \  'items': [' &OK ', ' &Cancel ']},
        \ ]
    let result = quickui#dialog#open(items, {'title': 'Settings'})
    echo result
endfunc

执行 :call MySettings() 效果如下:

dialog screenshot

一个真正的对话框,在 Vim 里,带多个控件。

逐行解释一下:

  • label —— 顶部的静态文本,不可聚焦
  • input —— 带提示标签和默认值的文本输入框
  • radio —— 单选按钮组,只能选一个
  • check —— 复选框,可以切换开关
  • button —— 底部的按钮行

你可以用 TabShift-Tab 在控件间切换焦点,在输入框里直接打字,按 Space 切换复选框或选择单选项,按 Enter 或点击按钮确认。

所有的值都在 result 字典里返回。

用户是怎么退出的?

对话框关闭后,你需要知道两件事:用户是确认了还是取消了?如果确认了,是按了按钮还是在输入框里按了回车?

返回值有两个关键字段:

  • button_index —— 按了哪个按钮(0 起始),取消时为 -1
  • button —— 按钮控件的 name,如果是在非按钮控件上按回车或取消则为 ''

这是你在每个对话框里都会用到的判断模式:

let r = quickui#dialog#open(items, opts)

if r.button_index == -1
    " 用户按了 ESC、Ctrl-C 或关闭按钮
    echo 'Cancelled'
elseif r.button == ''
    " 用户在输入框/单选/复选上按了回车
    echo 'Confirmed (Enter): name=' . r.name
else
    " 用户点击了某个按钮
    echo 'Button pressed: ' . r.button . ' #' . r.button_index
endif

几个要点:

  • button_index 从 0 开始。第一个按钮返回 0,第二个返回 1,依此类推。
  • button 区分回车和按钮点击。当 button_index0 时,检查 r.button:如果为 '',说明是在非按钮控件上按了回车;如果非空,说明是点击了第一个按钮。
  • 取消时仍然返回值。即使按了 ESC,r.name 等字段仍然包含用户在取消前输入的内容。下次重新打开对话框时可以恢复状态。

大多数情况下,你只需要这样判断:

let r = quickui#dialog#open(items, opts)

if r.button_index >= 0 && r.button != ''
    " 用户点击了某个按钮——处理返回值
    echo 'Name: ' . r.name
endif

或者如果你有 OK 和 Cancel 两个按钮:

" ' &OK ' 是按钮 0,' &Cancel ' 是按钮 1
if r.button_index == 0 && r.button != ''
    echo 'Accepted: ' . r.name
endif

一个实战例子

来做一个更贴近真实插件的东西:一个”新建项目”的表单,包含所有控件类型:

function! NewProject()
    let items = [
        \ {'type': 'label', 'text': 'Create New Project:'},
        \ {'type': 'input', 'name': 'project_name', 'prompt': 'Project:'},
        \ {'type': 'input', 'name': 'email', 'prompt': 'Email:'},
        \ {'type': 'dropdown', 'name': 'language', 'prompt': 'Language:',
        \  'items': ['Python', 'JavaScript', 'Go', 'Rust', 'C++'],
        \  'value': 0},
        \ {'type': 'dropdown', 'name': 'build', 'prompt': 'Build:',
        \  'items': ['Make', 'CMake', 'Cargo', 'npm', 'pip'],
        \  'value': 0},
        \ {'type': 'radio', 'name': 'license', 'prompt': 'License:',
        \  'items': ['&MIT', '&Apache', '&GPL', '&Proprietary'],
        \  'value': 0},
        \ {'type': 'check', 'name': 'git_init',
        \  'text': 'Initialize git repo', 'value': 1},
        \ {'type': 'check', 'name': 'ci',
        \  'text': 'Add CI config'},
        \ {'type': 'button', 'name': 'confirm',
        \  'items': [' &Create ', '  Cancel  ']},
        \ ]

    let opts = {'title': 'New Project', 'w': 50, 'focus': 'project_name'}
    let result = quickui#dialog#open(items, opts)

    " 检查用户是否点击了 "Create" 按钮(按钮 0)
    if result.button_index == 0 && result.button != ''
        let languages = ['Python', 'JavaScript', 'Go', 'Rust', 'C++']
        let builds = ['Make', 'CMake', 'Cargo', 'npm', 'pip']

        echo 'Project:  ' . result.project_name
        echo 'Email:    ' . result.email
        echo 'Language: ' . languages[result.language]
        echo 'Build:    ' . builds[result.build]
        echo 'License:  ' . result.license
        echo 'Git:      ' . (result.git_init ? 'yes' : 'no')
        echo 'CI:       ' . (result.ci ? 'yes' : 'no')
    else
        echo 'Cancelled'
    endif
endfunc

效果截图:

这个例子展示了几个要点:

下拉列表控件(dropdown)显示为一个折叠的选择框。按 EnterSpace 弹出选项列表供选择。返回值是 0 起始的索引,需要自己映射回文本。

分隔线(separator)在复选框和按钮之间画一条水平线,替代了控件间的默认空行,保持布局整洁。

opts.focus 把初始焦点设置到 project_name 输入框,用户打开对话框就能立刻开始输入。

提示文本自动对齐。注意 Project:Email:Language:Build:License: 这些标签都是左对齐的,它们后面的控件起始位置在同一列。QuickUI 会自动计算最长的提示文本,补齐其余的。

热键标记。按钮、单选和复选文本中的 & 标记了热键字符。比如 &CreateC 成为热键——在对话框的任何地方(不在输入框中时)按 C 就能触发该按钮。单选组的 &MIT&Apache 等同理。

使用技巧

分享几个我在开发对话框过程中总结的经验:

先设好 opts.w 如果不设宽度,QuickUI 会自动计算。对于简单对话框没问题,但对于有多个字段的表单,显式设置一个宽度(比如 50)能让布局更一致。

'value' 设默认值。 每个控件都支持 value 字段。输入框接收字符串,单选/下拉/复选框接收数字。预填好默认值能让用户少打几个字。

复选框不需要提示标签。 和输入框、单选不同,复选框的文本本身就是标签,不加 prompt 看着更自然。如果想让它和其他有提示的控件对齐,也可以加 'prompt' 字段。

给按钮行命名。 如果只有一行按钮,默认名字 'button' 就够了。但如果有两行按钮(比如 “Apply/Reset” 和 “OK/Cancel”),要给它们不同的 name,这样才能区分用户点的是哪一行。

更多进阶特性

本文只覆盖了基础用法。对话框系统还有更多能力:

  • 输入历史 —— 输入框可以通过 history 字段在多次调用间共享历史记录,按 Ctrl+Up / Ctrl+Down 浏览
  • 垂直单选 —— 选项文本较长时,单选组会自动切换为垂直布局
  • 表单验证 —— 通过 opts.validator 设置一个回调函数,在对话框关闭前验证字段值
  • 鼠标支持 —— 点击任何控件即可聚焦、切换或激活
  • 自定义颜色和边框 —— 配合你的 Vim 配色方案

完整参考可查看 vim-quickui 仓库中的 Dialog Guide

不只是对话框

vim-quickui 不仅仅是对话框。它还提供:

  • 顶部菜单栏 —— 屏幕顶部的下拉菜单,Borland/Turbo C++ 风格
  • 右键菜单 —— 光标附近弹出的上下文菜单
  • 列表框 —— 带搜索功能的可滚动列表
  • 文本框 —— 在弹出窗口中显示文本
  • 预览窗口 —— 在光标附近预览文件内容
  • 输入框 —— 简单的单行输入(比完整对话框轻量)
  • 终端 —— 在弹出窗口中运行 shell 命令

全部是纯 VimScript 实现,全部同时支持 Vim 和 NeoVim。

详细文档参见:完整文档


如果你觉得 vim-quickui 有用,欢迎去 GitHub star 一下,也帮助更多人发现这个项目。

有问题或想法?欢迎提 issue 或者在下面评论。

Loading

The post 在 Vim 里实现可定制表单对话框 appeared first on Skywind Inside.

单头文件 C++ 游戏开发库(GameLib.h)

2026-04-03 22:48:39

最近小孩在学 C++(信奥),学了堆语法以后不知道干嘛,学了一年也只会对着黑窗口打印内容,正反馈太弱了,不像其他语言,学个几天就能做出漂亮的东西来,恰巧他对开发游戏感兴趣,我扫了一眼现在 C++ 的游戏开发框架,都太复杂了,SDL 概念琐碎,SFML 使用麻烦,所以我写了个针对初学者的游戏库,只有一个头文件 GameLib.h 零依赖,拷贝到代码目录 include 就能用,十行代码就能出个小 demo:

本着简化一切的思想,使用起来比 PyGame 还要简单:

#include "GameLib.h"

int main() {
    GameLib game;
    game.Open(640, 480, "My Game", true);

    int x = 320, y = 240;

    while (!game.IsClosed()) {
        if (game.IsKeyDown(KEY_LEFT))  x -= 3;
        if (game.IsKeyDown(KEY_RIGHT)) x += 3;
        if (game.IsKeyDown(KEY_UP))    y -= 3;
        if (game.IsKeyDown(KEY_DOWN))  y += 3;

        game.Clear(COLOR_BLACK);
        game.FillCircle(x, y, 15, COLOR_CYAN);
        game.DrawText(10, 10, "Up/Down/Left/Right to move!", COLOR_WHITE);
        game.Update();
        game.WaitFrame(60);
    }
    return 0;
}

就这么几行代码,没有 SDL 里反锁的像素格式,消息机制,各种乱七八糟 SDL_ 开头的对象,也没有初始化就需要 500 行代码的 DirectX 那么麻烦,所有复杂的东西藏起来,只留下做游戏的乐趣。

编译:

g++ main.cpp -o game.exe

不需要加任何编译参数,很多初学者连命令行编译都不懂(比如我家小孩),只会直接在 DevC++ 里点编译+运行,让他们像用其他库一样添加一些类似 -ld3d9x 之类编译参数,可能直接就劝退一大群人,因此这个库完全使用默认编译参数,所有依赖都是动态库自己手工加载。

运行就能用方向键控制小球移动:

几行代码迅速看到反馈。

为什么做这个库呢?

市面上的游戏库(SDL、SFML、raylib)都很好,但对于刚接触 C++ 的初学者来说:

  • SDL 要配置头文件路径、链接十几个 dll,使用复杂
  • SFML 要用 CMake
  • raylib 需要熟练掌握线性代数,三维图形基础知识,熟练 C/C++,对初学者太不友好

GameLib 的目标是零门槛:把 GameLib.h 拷到项目文件夹,写一个 .cpp 文件,点编译,游戏就跑起来了。

它专门为 Dev C++(很多学校编程课在用的 IDE)设计,兼容其自带的 GCC 4.9.2 编译器。当然,任何支持 C++11 的 Windows 编译器都可以用。

(点击下面展开更多)

特性一览

零配置:

  • 单个头文件 GameLib.h,拷贝即用
  • 不依赖 SDL / SFML / DirectX / OpenGL
  • 编译参数都不需要加(全动态加载),可选择性添加 -mwindows 参数
  • 兼容 Dev C++ 自带的 GCC 4.9.2

开箱即用的绘图:

  • 画点、线、矩形、圆、三角形(描边和填充)
  • 内嵌 8×8 像素点阵字体,支持所有可打印 ASCII 字符
  • DrawPrintf 像 printf 一样在屏幕上格式化输出
  • 所有图形算法自行实现(Bresenham 直线、中点圆、扫描线填充)

精灵系统:

  • 加载 PNG、JPG、BMP、GIF 等格式
  • 支持 8-bit 调色板、24-bit、32-bit 图片(自动转换为 32 位 ARGB)
  • 24 位图片自动补全 alpha 通道(设为不透明)
  • 翻转、Color Key 透明、Alpha 混合、区域裁剪绘制
  • 用整数 ID 管理,不需要理解指针和对象生命周期

键盘和鼠标:

  • IsKeyDown — 按住检测
  • IsKeyPressed — 单次按下检测(按下瞬间触发一次)
  • 鼠标位置和三键状态
  • 预定义所有常用按键常量:KEY_AKEY_Z、方向键、F1F12

声音:

  • PlayWAV — 播放音效(WAV 格式,异步)
  • PlayMusic / StopMusic — 播放背景音乐(MP3/MIDI,基于 MCI)
  • 音效和音乐独立通道,互不干扰

游戏工具:

  • Random(min, max) — 随机数
  • RectOverlap / CircleOverlap — 碰撞检测
  • Distance — 两点距离
  • DrawGrid / FillCell — 网格绘制(适合俄罗斯方块、棋盘类游戏)
  • GetDeltaTime / GetFPS — 帧时间和帧率

Tilemap 系统:

  • CreateTilemap — 用 tileset 精灵创建瓦片地图
  • SetTile / GetTile — 设置和读取瓦片
  • DrawTilemap — 绘制地图,支持不透明、Color Key、Alpha 三种模式
  • 只绘制屏幕可见范围内的瓦片,大地图也不卡
  • 配合相机偏移轻松实现横版卷轴和视差滚动

快速上手

第一步:下载

GameLib.h 放到你的项目文件夹里。

第二步: 写代码

创建一个 main.cpp:

#include "GameLib.h"

int main() {
    GameLib game;
    game.Open(800, 600, "Hello GameLib", true);

    while (!game.IsClosed()) {
        game.Clear(COLOR_DARK_BLUE);
        game.DrawTextScale(200, 250, "Hello, World!", COLOR_GOLD, 3);
        game.DrawText(280, 320, "Press ESC to exit", COLOR_GRAY);

        if (game.IsKeyPressed(KEY_ESCAPE)) break;

        game.Update();
        game.WaitFrame(60);
    }
    return 0;
}

第三步:编译运行

Dev C++:新建项目 > 添加 main.cpp > 编译运行。

或者命令行:

g++ -o game.exe main.cpp -mwindows

就行了。

随机星空

再来个有点视觉效果的例子,随机星空:

#include "GameLib.h"

int main() {
    GameLib game;
    game.Open(800, 600, "Starfield", true);

    // 生成 200 颗星星
    int sx[200], sy[200], speed[200];
    uint32_t colors[] = {COLOR_WHITE, COLOR_LIGHT_GRAY, COLOR_YELLOW, COLOR_CYAN};
    for (int i = 0; i < 200; i++) {
        sx[i] = GameLib::Random(0, 799);
        sy[i] = GameLib::Random(0, 599);
        speed[i] = GameLib::Random(1, 5);
    }

    while (!game.IsClosed()) {
        game.Clear(COLOR_BLACK);

        for (int i = 0; i < 200; i++) {
            sx[i] -= speed[i];
            if (sx[i] < 0) {
                sx[i] = 800;
                sy[i] = GameLib::Random(0, 599);
            }
            game.SetPixel(sx[i], sy[i], colors[speed[i] % 4]);
        }

        game.DrawText(250, 290, "Press ESC to exit", COLOR_GRAY);
        if (game.IsKeyPressed(KEY_ESCAPE)) break;

        game.Update();
        game.WaitFrame(60);
    }
    return 0;
}

就这么简单,运行效果(点击播放 GIF 动画):

几行代码就能有看得见摸得着的东西,正反馈远远强过黑窗口打印文字。

这个 GameLib.h 适合开发哪些类型的游戏呢?

  • 太空射击 (Space Shooter)
  • 横版卷轴 (Side-Scrolling Platformer)
  • 俄罗斯方块 (Tetris)
  • 贪吃蛇 (Snake)
  • 打砖块 (Breakout)
  • 走迷宫 (Maze)
  • 接水果 (Fruit Catcher)
  • 弹幕游戏 (Bullet Hell)
  • 画板程序 (Paint)
  • 任何你能想到的 2D 小游戏

更多例子请访问项目主页:

https://github.com/skywind3000/GameLib

里面有 15 个例子程序,包括类似《打砖块》 ,《太空射击》之类游戏的代码:

一步一步教你从:窗口控制,键盘鼠标交互,图形绘制,精灵,音乐,卷轴等覆盖各个 GameLib.h 的功能点。

欢迎学会了 C++ 语法想做点什么的同学尝试。

Loading

The post 单头文件 C++ 游戏开发库(GameLib.h) appeared first on Skywind Inside.

使用 rclone bisync 两步搭建个人云盘

2026-01-30 00:27:00

如何最快搭建个人云盘?Nextcloud 太大太臃肿,后面一堆 apache/nginx/php/mysql,命令行同步也不好用,私货还太多。

著名的 syncthing 要求有 P2P 中转服务;还要求同步时另一台电脑也开着机,不开机的话,你要在服务器上做一个同步点持续运行,那么做了这个服务和 C/S 架构的 rclone 有啥区别呢?还要多依赖一个 P2P 中转服务(虽然可以找到别人提供的),其他的开源产品也好不到哪里,相比之下 rclone 是最轻量级的解决方案了。

恰巧 rclone 最近几年一直在优化 bisync 功能,就是双向同步,网盘的核心功能,bisync 在 2022 年引入 rclone 一直处于 experimental/beta 阶段,其实几年前就基本可以用了,官方文档在 2025 年下半年正式移除 experimental/beta 字样,然后 bisync 功能正式转正。

所以我们用 rclone 的 bisync 功能来分别搭建客户端和服务端:

1️⃣ 服务端搭建

使用 supervisor 编辑 /etc/supervisor/conf.d/rclone-sftp.conf 文件:

[program:rclone-sftp]
command=/home/data/app/rclone/rclone serve sftp /home/data/sync --addr :2022 --user MYNAME --pass MYPASS --cache-dir /var/cache/rclone --vfs-cache-mode full
user=nobody
autorestart=true

然后重启:

sudo supervisorctl reload

一个 rclone 提供的 sftp 服务就启动了,就是这么简单,它速度快性能好,传输有加密。

最后我们在 sftp 的文件夹里建立一个叫做 bisync 的文件夹:

sudo mkdir /home/data/sync/bisync
sudo chown -R nobody:nogroup /home/data/sync

处理好权限,让 nobody 这个用户能够读写即可,就这么几步操作,比搭建个 Nextcloud 之类的服务方便太多了。

2️⃣ 客户端

我用 WSL,编辑配置 /root/.config/rclone/rclone.conf 内容如下:

[sftp1]
type = sftp
host = 192.168.1.12
user = MYNAME
port = 2022
pass = [运行 rclone obscure "MYPASS" 得到的加密字符串]
shell_type = unix
md5sum_command = md5sum
sha1sum_command = sha1sum

不想手工填写也可以用 rclone config 交互式的初始化并连接 sftp 服务,一旦服务配置好了,我么就能找个文件夹然后在定时任务里 bisync 了。

不过首先要初始化一下:

mkdir /mnt/e/Local/Cloud/rclone
rclone bisync /mnt/e/Local/Cloud/rclone sftp1:/bisync --resync

这个 --resync 参数用于首次同步初始化,后面就定时任务了。

编辑同步脚本 /home/data/shim/rclone-bisync-sftp1 内容如下:

#!/bin/bash

# configure
RCLONE_BIN="/home/data/app/rclone/rclone"
RCLONE_REMOTE=sftp1:/bisync
RCLONE_LOCAL=/mnt/e/Local/Cloud/rclone
LOGFILE="/var/log/rclone-bisync.log"

# start
$RCLONE_BIN bisync "$RCLONE_LOCAL" "$RCLONE_REMOTE" \
        --create-empty-src-dirs \
        --compare size,modtime \
        --resilient \
        --recover \
        --max-lock 2m \
        --verbose \
        --log-file="$LOGFILE"

定时启动:

编辑 /etc/cron.d/rclone_bisync 内容如下:

*/5 * * * * root /home/data/shim/rclone-bisync-sftp1

即可,调试完正常可以把启动脚本里的 --verbose 去掉,没错误就不写日志了。你可以在多个客户端上进行类似的配置,不管 Windows 还是 Linux,要点就是先 config 上远端 sftp,然后初始化 --resync,然后就是定时调用 bisync 子命令就行。

然后你就得到了一个干净纯粹的,开源的,多端同步网盘了。

后记

在 bisync 之前,rclone 实现网盘主要靠 mount,这个功能类似 samba,每次读取写入都要走网络,局域网还行,跨机房就卡的不行了,它后来提供了 cache 机制有一定缓解,但一致性又有问题,经常不同步,这边改了不能像 samba 那样通知那边。

所以 rclone 做网盘体验最好的目前就是 bisync 功能了,bisync 的 bisync 可以用很多源,作为服务端,如果你不想搭建一大套 webserver / fileserver 来做源的话,最好就是用 rclone 自己可以 serve 的服务,包括:http,sftp,webdav,nfs 等等。

我试过 webdav 等,发现如果想要加密,前面还得再套一层 https 代理,或者又搞一大堆证书什么的,异常麻烦,所以体验最好,速度最快的是 sftp 服务。

Loading

The post 使用 rclone bisync 两步搭建个人云盘 appeared first on Skywind Inside.

C++ 最好用的 TUI 界面库 Turbo Vision 2.0

2026-01-03 22:06:35

说起文本模式界面库,也许有人听过 Turbo Vision 2.0,这货是当年 Borland C++ 3.1 (不是 Turbo C 2.0)背后的商用界面库 TV2 的开源版,经过多年迭代,如今是一款支持 unicode 和现代 C++ 跨平台的 TUI 库了:

项目地址:https://github.com/magiblot/tvision

主要特性:

  • 跨平台:Windows,Linux,macOS,DOS,FreeBSD
  • 支持鼠标,支持 unicode,正常显示中文;
  • 多窗口:窗口可以自由拖拽移动,扩大缩小,全屏,互相覆盖(overlap)
  • 控件多:主菜单,对话框,checkbox, radiobox,dropdown list,文件选择器,进度条,阴影
  • 文本编辑:语法高亮,自动 indent,鼠标选中,shift+方向键选中,CTRL+C/CTRL+V
  • 支持真彩:原来老的 Borland 版本的 TV2 只支持 16 色。

Linux 下大部分 TUI 程序都是平铺窗口,无法重叠,更无法自由拖动,而 Turbo Vision 2.0 完全像用图形界面程序一样,鼠标操作这些窗口自由移动,扩大缩小,全屏化:

而且 Linux 下面大部分程序对 ALT+ ,CTRL+ ,SHIFT+CTRL+ 等组合键支持非常有限,而 TV2 对不同平台的键盘鼠标做了很好的兼容,让你在远程终端里也能自由的使用各种组合键和功能键。

用过 Borland C++ 3.1 的人一定会以为 BC31 重生了,某种意义上来讲是的,不要觉得技术古老,Linux 下面 TUI 发展几十年,没一个打得过 Turbo Vision 2 的:

这是 TV2 做的终端模拟器 tvterm,各位天天用 tmux 分屏模拟,但 tmux 发展了那么长时间都不支持子窗口互相重叠覆盖,鼠标拖动,TV2 可以让你像用桌面软件一样灵活的操作各个子窗口。

Vim/NeoVim 直到 2019 年才支持 popup/floatwin 可以实现上面类似的效果,而 Turbo Vision 2 在三十年前就做到了。

过去 TV2 只支持 ANSI 编码,无法显示中文,如今中文,日文都能正常显示:

包括 emoji:

你在 Linux 下大部分 TUI 程序想调整下设置都只能编写配置文件,配置 vim 也只能写 vimrc,但 TV2 里有丰富的 TUI 设置对话框:

大部分设置,对话框里点点鼠标就能搞定,毫无学习门槛,无需看很多帮助才知道怎么写配置:

当然 TV2 也有内置帮助系统,它有一套类似 .chm 等帮助系统,内部包含索引,跳转,可以在 TUI 内随时查看你的帮助文件,比如下面的快捷键帮助,基本上和 Windows 程序一脉相承:

最后是编程接口,linux 下一堆 tui 接口,比如 ncurses 这样的都是比较初级的原始的,没有任何高级控件,同时接口也是 C 的,而 TV2.0 基本上所有高级控件你都可以直接使用,写起代码来类似传统的 Qt 程序:

class HelloApp : public TApplication {
public:
    HelloApp();
    void showHelloDialog();
};

HelloApp::HelloApp() : 
    TProgInit(&TApplication::initStatusLine,
              &TApplication::initMenuBar,
              &TApplication::initDeskTop) {
    // 启动时显示对话框
    showHelloDialog();
}

void HelloApp::showHelloDialog() {
    // 创建对话框 (x, y, width, height)
    TDialog *dialog = new TDialog(TRect(0, 0, 40, 9), "Hello World");

    if (dialog) {
        // 居中对话框
        dialog->options |= ofCentered;

        // 添加 "Hello World" 静态文本 (居中显示)
        TStaticText *text = new TStaticText(
            TRect(2, 2, 38, 5), 
            "\003Hello World"  // \003 表示居中对齐
        );
        dialog->insert(text);

        // 添加 OK 按钮 (居中)
        TButton *button = new TButton(
            TRect(14, 5, 26, 7),
            "~O~K",
            cmOK,
            bfDefault
        );
        dialog->insert(button);

        // 显示模态对话框
        deskTop->execView(dialog);

        // 清理
        TObject::destroy(dialog);
    }
}

int main() {
    HelloApp app;
    app.run();
    app.shutDown();
    return 0;
}

基本就是类似 Qt 的编程方式:事件驱动+控件组合+OOP,不是 ncurses 那种 select/read 标准输入,自己解码半天终端控制码,在琐碎的控制各处显示,自己控制状态切换和重绘,写的你想吐。

Linux 下面基于 ncurses 二次封装的库也很多,但大多是个人项目,浅层封装,Turbo Vision 过去作为支持 Borland C++ 3.1 这种商用级 IDE 的界面库,不是这些玩票项目可以比得了的。

编程语言 TMBASIC 也是用 Turbo Vision 2 做的界面:

补充点历史,Turbo Vision 最初是 Borland 公司用 Pascal 实现的,用于 Turbo Pascal,后来在做 Borland C++ 3.x 的时候用 C++ 重新实现了一个版本,用于支持 BC31 的界面,在 BC31 里包含头文件和静态库,你可以直接用它构建 TUI 程序,接着 Borland 在 1997 年将 C++ 版本的完全开源了,就是我们上面说的这套的源头;

而 Pascal 版本的 Turbo Vision 一直没开源,Free Pascal 后面又根据 C++ 开源的版本重新实现了一套叫做 Free Vision 的界面库给 Free Pascal 做 IDE 用:

我很喜欢它的设置界面,根本不用读文档写配置文件,直接对话框里全部帮你归好类了:

c
是不是很眼熟?Free Pascal 的 IDE,背后使用的 Free Vision,和上面的 Turbo Vision 同出一源。

PS:Turbo Vision 还出过几本书:

想看的话到开头项目主页里有链接。

Loading

The post C++ 最好用的 TUI 界面库 Turbo Vision 2.0 appeared first on Skywind Inside.

如何清理你的系统盘(C: 盘)?

2025-12-27 00:32:00

就一年没清理 C 盘,今天清理出 140GB 的东西来,而且还没影响使用,所有程序都在往你的 C 盘拉屎。不要网友问我怎么清理下来的:

1)右键 C 盘属性,清理;
2)自己到 TEMP 目录手动清空,注意有三个 Temp:AppData 下面两个,Windows 目录下有一个;
3)SpaceSniffer 找占用大的目录,清空不合理的,这个需要一点经验,拿不准可问下 AI 某目录能不能清空;
4)清浏览器缓存;
5)最后 CCleaner 跑一遍。

上面一系列任务做完基本就清理出 100GB 的内容了,然后再使用 Dism++ 删除系统还原点后,又多出 40GB 来:

最开始剩余空间只有 130GB,现在有 272GB 剩余,可以做一次年度全盘备份了。

Loading

The post 如何清理你的系统盘(C: 盘)? appeared first on Skywind Inside.

Rust 适合开发游戏吗?

2025-10-30 13:48:00

Rust 并不适合开发游戏,它更擅长有明确定义,边界清晰的项目,这样你类型体操一遍做完问题不大,比如重构个已经十年没变过的 C 模块,但游戏领域并没有明确的定义,策划案不停修改,你的代码不但要求快速迭代还要求不停重构,那么类型体操在这过程中就变成锁死你的紧箍咒。

参考文章:3 年写了 10 万行代码开发者吐槽:当初用 Rust 是被忽悠了

这里解释一下,为什么当项目没有明确定义时,rust 为啥会显得笨拙?因为此时不但需求会随时间变来变去,更要命的是你没有一份十年没变过的 C 代码做参考,对项目整体实现缺乏全局的认识,只有自底向上的方法不断尝试和修正自己,不段反思和改进中上层代码,才能像盲人摸象那样逐步认清楚整个世界因该是啥样。

而此时类型体操会在这时勒得你喘不过气来,一个之前需要横着用的变量现在需要竖着用了,你思来想去发现你完全没办像其他语言那样改成法竖着用了,于是你只有引入更大范围的重写才能解决问题,你觉得这样很难受,去 rust 社区寻求帮助,但发现他们并不会帮助你真的解决语言问题,只会一个劲的指责你 “你觉得痛苦正是因为你对 rust 不熟悉导致的” 或者 “rust 逼迫你更大范围重写正是逼你尽早写出更好的代码”,他们这么说在定义清晰的项目里的确没问题,越早重构越好,但在定义不清晰的项目上里存在大量中间设计,你今天改成这样,八成不是最终形态,隔几天可能还要改,而此时 rust 却逼迫你每次都提前费精力进行更大范围的重写,即使这时完全没必要的,过两天就不需要的,他也不许你像其他语言那样用快速实现的方式应对新的中间状态,等需求稳定了,技术方案也收敛的情况下再进行迭代和完善,然后你就抓狂了!

网友 FENG DONG:项目没有明确定义时候,需要设计 cost function,然后让代码沿着 gradient 演进。但 Rust 不是一种可微的语言。

网友 Aaltonen:游戏开发是 “巨型可变状态球” (giant balls of mutable state),需求像 gradient descent 一样逐步演进,但 Rust 的借用检查器(borrow checker)和所有权模型强制你 “一次性想好”,导致小改动引发大重构

那么 Rust 适合开发游戏引擎么?我只能说 depends,主要到了现在也没啥 rust 开发游戏引擎的成功例子啊,一天到晚到处搞营销的 rust 游戏引擎 bevy 基本上类似个玩具,到现在都没啥靠谱的商业游戏案例,顶多几个独立小游戏之类,而且它要求用 rust 来写业务逻辑,这个上面已经论述过,它并不适合开发游戏业务逻辑;

其次游戏引擎有很多部件,有需要高度优化各种汇编技巧的性能部分(类似 ffmpeg),有需要同各种系统图形 api 打交道的抽象硬件层部分(不断同 direct3d,opengl,vulkan 还有各种系统 api 打交道),还有需要大量整合第三方(基本是 c++ 库,比如 imgui, box2d, sdl, bgfx, physx)的胶水层,这些也并不适合 rust。除非你觉得 ffmpeg 这样的项目适合用 rust 重写,或者可以用 rust 把大量久经考验的第三方库全部发明一遍。

最后,成熟的游戏引擎里都是有各种 hack 或者 dirty trick 的,rust 做这些并不是特别方便。

配图:“But it’s SAFE !!”

“But it's SAFE !!”

Loading

The post Rust 适合开发游戏吗? appeared first on Skywind Inside.