- 桌面应用
- 跨平台
【免费下载链接】nodegui
A library for building cross-platform native desktop applications with Node.js and CSS 🚀. React NodeGui : https://react.nodegui.org and Vue NodeGui: https://vue.nodegui.org
导读
本文深入解析 NodeGui(基于 Qt 与 Node.js 的跨平台原生桌面应用框架)中FillRule枚举的完整语义与用法。FillRule对应 Qt 中Qt::FillRule枚举,用于决定 QPainterPath 这类自相交或嵌套路径的哪些区域应被填充,是进行复杂图形绘制的关键开关。读完本文,你将掌握两个枚举值的数学判定原理、在 QPainterPath 上的读写方法,以及通过 QPainter.drawPath 绘制复杂形状的完整调用链。
FillRule 枚举的定义与位置
在 NodeGui 中,FillRule定义于 TypeScript 侧的 QtEnums 模块,源文件为 src/lib/QtEnums/FillRule/index.ts:
export enum FillRule { OddEvenFill = 0, WindingFill = 1, }它由 src/lib/QtEnums/index.ts 统一导出:
export { FillRule } from './FillRule';FillRule是对 Qt 原生枚举Qt::FillRule的一一映射封装,包含两个成员:
| 枚举成员 | 数值 | Qt 对应枚举 | 填充判定规则 |
|---|---|---|---|
FillRule.OddEvenFill | 0 | Qt::OddEvenFill | 奇偶规则(Odd-Even Rule) |
FillRule.WindingFill | 1 | Qt::WindingFill | 非零环绕规则(Non-Zero Winding Rule) |
在自动生成的 API 文档 website/docs/api/generated/enums/fillrule.md 中,同样只记录了这两个枚举成员,且数值分别为0与1,与 TypeScript 源码完全一致。由于 TypeScript 枚举默认按声明顺序从 0 自增,这两个数值也可视为隐式约定的稳定标识——它们会被直接透传到原生层。
两种填充规则的数学原理
FillRule解决的核心问题是:当一条路径存在自相交或包含嵌套子路径时,哪些区域属于"内部"(应被填充)。两种规则基于不同的判定算法:
OddEvenFill(奇偶规则,数值 0)
从待判定点向任意方向引一条射线,统计它与路径线段(子路径边界)的交点个数:
- 交点个数为奇数→ 该点在路径内部,被填充;
- 交点个数为偶数→ 该点在路径外部,不填充。
这条规则的显著特征是不关心路径的绘制方向(顺时针或逆时针)。对于五角星这类自相交图形,采用 OddEvenFill 时,中央的五边形区域因射线会穿越偶数条线段而保持镂空,绘制结果是一个带空心中心的五角星轮廓。重叠区域遵循"异或"式的交替逻辑。
WindingFill(非零环绕规则,数值 1)
同样从待判定点引射线,但会统计路径与射线的方向(环绕方向):
- 每穿过一条线段,根据线段相对射线的走向将环绕数(winding number)加 1 或减 1;
- 若最终环绕数非零→ 该点位于路径内部,被填充;
- 若最终环绕数为零→ 该点位于路径外部,不填充。
这条规则的显著特征是依赖子路径的绘制方向。仍以五角星为例:若外轮廓与内部凹入部分按同一方向绘制,WindingFill 会把整颗星(包括中央五边形)全部填充为实心;若内圈反向绘制,则中央区域会被挖空。实践中,WindingFill常用于绘制环形、回字形等需要依靠方向区分内外边界的形状。
一句话对比:OddEvenFill 只看"穿越次数"的奇偶;WindingFill 还额外统计"穿越方向"的净环绕数。
在 QPainterPath 中的读写接口
FillRule在 NodeGui 中的主要消费方是QPainterPath,封装文件为 src/lib/QtWidgets/QPainterPath.ts,其中提供了两个相关方法:
fillRule(): FillRule { return this.native.fillRule(); } setFillRule(fillRule: FillRule): void { return this.native.setFillRule(fillRule); }path.fillRule():返回当前路径的填充规则(FillRule.OddEvenFill或FillRule.WindingFill)。新建的QPainterPath默认采用Qt::OddEvenFill;path.setFillRule(fillRule):设置路径的填充规则,在向路径添加子路径之前调用效果最佳。
原生调用链验证
从源码结构看,这两个方法最终通过 N-API 落到 Qt 原生对象上,完整的调用链是:
- TypeScript 侧调用
this.native.fillRule()/this.native.setFillRule(fillRule)(见 QPainterPath.ts); - 原生封装类
QPainterPathWrap在 qpainterpath_wrap.cpp 与 qpainterpath_wrap.cpp 中实现桥接:
Napi::Value QPainterPathWrap::fillRule(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); int v = static_cast<int>(this->instance->fillRule()); return Napi::Number::From(env, v); } Napi::Value QPainterPathWrap::setFillRule(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); int v = info[0].As<Napi::Number>().Int32Value(); this->instance->setFillRule(static_cast<Qt::FillRule>(v)); return env.Null(); }可以看到,枚举值在边界处以int形式传递,并在原生侧强转回Qt::FillRule,这正是OddEvenFill = 0、WindingFill = 1这两个数值约定被定义在 TypeScript 枚举中的根本原因。对应的原生方法声明位于 qpainterpath_wrap.h。
绘制时的完整消费链:QPainter.drawPath
设置好填充规则的QPainterPath最终要交给QPainter绘制。NodeGui 的QPainter在 src/lib/QtWidgets/QPainter.ts 中暴露了drawPath:
drawPath(path: QPainterPath): void { return this.native.drawPath(path.native); }因此一条完整的绘制链路是:
- 创建
QPainterPath实例; - 用
moveTo、lineTo、cubicTo、quadTo、closeSubpath等构建子路径; - 调用
setFillRule(FillRule.WindingFill | FillRule.OddEvenFill)选择填充规则; - 在 QPainter(例如窗口或控件的
paintEvent中获取)上调用drawPath(path)完成绘制。
值得说明的是,QPainter.ts 中还留有drawPolygon(..., Qt::FillRule fillRule = Qt::OddEvenFill)的 TODO 注释,表明 Qt 的drawPolygon本身也接受填充规则参数,但当前 NodeGui 封装尚未实现该方法——因此现阶段在 NodeGui 中操作填充规则,统一走QPainterPath.setFillRule这一途径。
实战示例:用两种规则绘制五角星
结合QPainterPath已有的公开 API(moveTo、lineTo、closeSubpath、setFillRule)与QPainter.drawPath,可以编写一个展示两种填充规则差异的最小示例。五角星属于典型的自相交路径:外轮廓的五个顶点与内凹的五个顶点交替连线后,中央会形成一个闭合小五边形。
import { QPainterPath } from '@nodegui/nodegui'; import { FillRule } from '@nodegui/nodegui'; function createStarPath(cx: number, cy: number, outerRadius: number, innerRadius: number): QPainterPath { const path = new QPainterPath(); path.moveTo(cx, cy - outerRadius); for (let i = 1; i < 5; i++) { const outerAngle = -Math.PI / 2 + (i * 2 * Math.PI) / 5; const innerAngle = -Math.PI / 2 + ((i - 0.5) * 2 * Math.PI) / 5; path.lineTo(cx + outerRadius * Math.cos(outerAngle), cy + outerRadius * Math.sin(outerAngle)); path.lineTo(cx + innerRadius * Math.cos(innerAngle), cy + innerRadius * Math.sin(innerAngle)); } path.closeSubpath(); return path; } // 奇偶规则:中央小五边形镂空 const hollowStar = createStarPath(60, 60, 50, 20); hollowStar.setFillRule(FillRule.OddEvenFill); // 非零环绕规则:整颗星实心填充(各线段按同一方向绘制) const solidStar = createStarPath(180, 60, 50, 20); solidStar.setFillRule(FillRule.WindingFill);将这两个 path 分别经painter.drawPath(hollowStar)与painter.drawPath(solidStar)绘制到画布,即可直观看到同一几何路径在两种规则下的填充差异:左侧星中央镂空,右侧星整体实心。
选择建议与注意事项
- 默认值:新建的
QPainterPath默认使用OddEvenFill。若你的形状不含自相交或方向性嵌套,两种规则的结果一致,无需显式设置; - 何时用 OddEvenFill:绘制五角星、爆炸符号等自相交图形时,希望重叠区域交替镂空;或子路径方向不可控(如从外部数据导入的路径)时,用它可获得与方向无关的稳定结果;
- 何时用 WindingFill:绘制环形、回字形等需要靠方向区分内外的形状,或希望重叠区域取并集而非镂空时;注意它依赖子路径的绘制方向(顺时针/逆时针),构建路径时需保持一致;
- 设置时机:
setFillRule应在路径构建完成(或构建相关子路径之前)调用;对同一QPainterPath反复切换规则是允许的,绘制时会按当前规则生效。
小结
FillRule虽只是两个数值的简单枚举,却是理解 NodeGui 复杂图形绘制的一把钥匙:OddEvenFill = 0与WindingFill = 1分别对应 Qt 的奇偶规则与非零环绕规则,二者通过QPainterPath.setFillRule/fillRule读写,最终由QPainter.drawPath消费。相关实现可分别在 QPainterPath.ts、qpainterpath_wrap.cpp 与 QPainter.ts 中继续查阅,QPainterPath的完整 API 清单见 website/docs/api/generated/classes/qpainterpath.md。
- 桌面应用
- 跨平台
【免费下载链接】nodegui
A library for building cross-platform native desktop applications with Node.js and CSS 🚀. React NodeGui : https://react.nodegui.org and Vue NodeGui: https://vue.nodegui.org
相关推荐
NodeGui BrushStyle 枚举完全指南:QBrush 填充样式与数值对照
NodeGui BrushStyle 枚举完全指南:QBrush 填充样式与数值对照 本指南以 NodeGui 官方 API 文档中的 BrushStyle 枚
桌面应用跨平台NodeGui 中的 ApplicationAttribute 枚举详解:从 Qt 应用级属性到 Node.js 桌面应用实践
NodeGui 中的 ApplicationAttribute 枚举详解:从 Qt 应用级属性到 Node.js 桌面应用实践 导读 ApplicationAt
桌面应用跨平台NodeGui 中 SequenceMatch 枚举详解:QKeySequence 按键序列匹配的原理与实战
NodeGui 中 SequenceMatch 枚举详解:QKeySequence 按键序列匹配的原理与实战 SequenceMatch 是 Node.js 跨
桌面应用跨平台
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考