{"id":13511903,"url":"https://github.com/qiniu/qpaint","last_synced_at":"2025-07-19T22:05:10.079Z","repository":{"id":50467935,"uuid":"197244134","full_name":"qiniu/qpaint","owner":"qiniu","description":"极客时间专栏《许式伟的架构课》相关的源代码：QPaint (画图程序)","archived":false,"fork":false,"pushed_at":"2020-04-04T19:34:21.000Z","size":352,"stargazers_count":179,"open_issues_count":0,"forks_count":86,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-05-05T20:17:44.375Z","etag":null,"topics":["architecture","go","golang","javascript","mvc-pattern"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/qiniu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-07-16T18:05:04.000Z","updated_at":"2025-01-19T07:50:10.000Z","dependencies_parsed_at":"2022-08-12T21:21:41.590Z","dependency_job_id":null,"html_url":"https://github.com/qiniu/qpaint","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/qiniu/qpaint","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qiniu%2Fqpaint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qiniu%2Fqpaint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qiniu%2Fqpaint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qiniu%2Fqpaint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/qiniu","download_url":"https://codeload.github.com/qiniu/qpaint/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/qiniu%2Fqpaint/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":266026308,"owners_count":23866033,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["architecture","go","golang","javascript","mvc-pattern"],"created_at":"2024-08-01T03:01:17.900Z","updated_at":"2025-07-19T22:05:09.806Z","avatar_url":"https://github.com/qiniu.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# QPaint (by Qiniu.com)\n\n[![Build Status](https://travis-ci.org/qiniu/qpaint.svg?branch=master)](https://travis-ci.org/qiniu/qpaint) [![GoDoc](https://godoc.org/github.com/qiniu/qpaint?status.svg)](https://godoc.org/github.com/qiniu/qpaint)\n\n[![Qiniu Logo](http://open.qiniudn.com/logo.png)](http://www.qiniu.com/)\n\n## 辅助界面元素\n\n### 接口规格 (第 31 讲)\n\n| 类型 | 属性 | 方法 | 事件 |\n| ------------- | ---------- | ------ | ------------- |\n| BaseLineWidthPicker | id: string\u003cbr\u003evalue: number | blur() | onchange(event: Event) |\n| BaseColorPicker | id: string\u003cbr\u003evalue: Color\u003cbr\u003epalette: string | blur() | onchange(event: Event) |\n| ColorPicker | id: string\u003cbr\u003epalette: string\u003cbr\u003evalue: Color | blur() | onchange(event: Event) |\n\n## QPaint DOM (第 30 讲)\n\n### 网络协议\n\n在 29 讲的基础上，我们增加了以下能力：\n\n| 功能 | 请求包 | 返回包 |\n| ------------- | ---------- | ------------- |\n| 同步变更 | POST /drawings/`\u003cDrawingID\u003e`/sync\u003cbr\u003eContent-Type: `application/json`\u003cbr\u003e\u003cbr\u003e{\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\"shapes\": [`\u003cShapeID1\u003e`, `\u003cShapeID2\u003e`, `\u003cShapeID3\u003e`, ...],\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\"changes\": [\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;{\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\"id\": `\u003cShapeID\u003e`,\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;`\u003cShape\u003e`\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;},\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;...\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;]\u003cbr\u003e} | 200 OK |\n\n## QPaint DOM (第 29 讲)\n\n### 网络协议\n\n| 功能 | 请求包 | 返回包 |\n| ------------- | ---------- | ------------- |\n| 创建新drawing | POST /drawings | 200 OK\u003cbr\u003eContent-Type: `application/json`\u003cbr\u003e\u003cbr\u003e{\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\"id\": `\u003cDrawingID\u003e`\u003cbr\u003e} |\n| 获得drawing | GET /drawings/`\u003cDrawingID\u003e` | 200 OK\u003cbr\u003eContent-Type: `application/json`\u003cbr\u003e\u003cbr\u003e{\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\"shapes\": [\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;{\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\"id\": `\u003cShapeID\u003e`\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;`\u003cShape\u003e`\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;},\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;...\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;]\u003cbr\u003e} |\n| 删除drawing | DELETE /drawings/`\u003cDrawingID\u003e` | 200 OK |\n| 创建新shape | POST /drawings/`\u003cDrawingID\u003e`/shapes\u003cbr\u003eContent-Type: `application/json`\u003cbr\u003e\u003cbr\u003e{\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\"id\": `\u003cShapeID\u003e`,\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;`\u003cShape\u003e`\u003cbr\u003e} | 200 OK |\n| 取得shape | GET /drawings/`\u003cDrawingID\u003e`/shapes/`\u003cShapeID\u003e` | 200 OK\u003cbr\u003eContent-Type: `application/json`\u003cbr\u003e\u003cbr\u003e{\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;`\u003cShape\u003e`\u003cbr\u003e} |\n| 修改shape | POST /drawings/`\u003cDrawingID\u003e`/shapes/`\u003cShapeID\u003e`\u003cbr\u003eContent-Type: `application/json`\u003cbr\u003e\u003cbr\u003e{\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;`\u003cShape\u003e`\u003cbr\u003e} | 200 OK |\n| 修改shape的顺序 | POST /drawings/`\u003cDrawingID\u003e`/shapes/`\u003cShapeID\u003e`\u003cbr\u003eContent-Type: `application/json`\u003cbr\u003e\u003cbr\u003e{\u003cbr\u003e\u0026nbsp;\u0026nbsp;\u0026nbsp;\u0026nbsp;\"zorder\": `\u003cZorderOperation\u003e`\u003cbr\u003e} | 200 OK |\n| 删除shape | DELETE /drawings/`\u003cDrawingID\u003e`/shapes/`\u003cShapeID\u003e` | 200 OK |\n\n其中 `\u003cShape\u003e` 是这样的：\n\n```\n\"path\": {\n    \"points\": [\n        {\"x\": \u003cX\u003e, \"y\": \u003cY\u003e},\n        ...\n    ],\n    \"close\": \u003cBoolean\u003e,\n    \"style\": \u003cShapeStyle\u003e\n}\n```\n或:\n```\n\"line\": {\n    \"pt1\": {\"x\": \u003cX\u003e, \"y\": \u003cY\u003e},\n    \"pt2\": {\"x\": \u003cX\u003e, \"y\": \u003cY\u003e},\n    \"style\": \u003cShapeStyle\u003e\n}\n```\n或：\n```\n\"rect\": {\n    \"x\": \u003cX\u003e,\n    \"y\": \u003cY\u003e,\n    \"width\": \u003cWidth\u003e,\n    \"height\": \u003cHeight\u003e,\n    \"style\": \u003cShapeStyle\u003e\n}\n```\n或：\n```\n\"ellipse\": {\n    \"x\": \u003cX\u003e,\n    \"y\": \u003cY\u003e,\n    \"radiusX\": \u003cRadiusX\u003e,\n    \"radiusY\": \u003cRadiusY\u003e,\n    \"style\": \u003cShapeStyle\u003e\n}\n```\n\n其中 `\u003cShapeStyle\u003e` 是这样的：\n\n```\n{\n    \"lineWidth\": \u003cWidth\u003e,  // 线宽\n    \"lineColor\": \u003cColor\u003e,  // 线型颜色\n    \"fillColor\": \u003cColor\u003e   // 填充色\n}\n```\n\n其中 `\u003cZorderOperation\u003e` 可能的值为：\n\n* \"top\": 到最顶\n* \"bottom\": 到最底\n* \"front\": 往前一层\n* \"back\": 往后一层\n\n## QPaint Web (第 27 讲)\n\n### Session-based Model\n\n* [dom.js](https://github.com/qiniu/qpaint/blob/v27/paintweb/www/dom.js)\n\n```TypeScript\ninterface Shape {\n    onpaint(ctx: CanvasRenderingContext2D): void\n    bound(): Rect\n    hitTest(pt: Point): {hitCode: number, hitShape: Shape}\n    setProp(key: string, val: any): void\n    move(dx, dy: number): void\n}\n```\n\n| 类型  | View | Controllers |\n| ------------- | ---------- | ------------- |\n| QPaintDoc | onpaint(ctx) | addShape(shape)\u003cbr\u003edeleteShape(shape)\u003cbr\u003ehitTest(pt) |\n| QLine\u003cbr\u003eQRect\u003cbr\u003eQEllipse\u003cbr\u003eQPath | onpaint(ctx) | new QLine(pt1, pt2, style)\u003cbr\u003enew QRect(rect, style)\u003cbr\u003enew QEllipse(x, y, radiusX, radiusY, style)\u003cbr\u003enew QPath(points, close, style)\u003cbr\u003ebound()\u003cbr\u003ehitTest(pt)\u003cbr\u003esetProp(key, val)\u003cbr\u003emove(dx, dy) |\n| QShapeStyle | new QShapeStyle(\u003cbr\u003e\u0026nbsp;\u0026nbsp;lineWidth, lineColor, fillColor\u003cbr\u003e) | setProp(key, val)\u003cbr\u003eclone() |\n\n### ViewModel\n\n* [view.js](https://github.com/qiniu/qpaint/blob/v27/paintweb/www/view.js)\n* [index.htm](https://github.com/qiniu/qpaint/blob/v27/paintweb/www/index.htm)\n\n```TypeScript\ninterface Controller {\n  stop(): void\n  onpaint(ctx: CanvasRenderingContext2D): void\n}\n```\n\n| 类型  | Model | View | Controllers |\n| --------- | ------- | ---------- | ------------- |\n| 数据 | doc: QPaintDoc | style: QShapeStyle\u003cbr\u003edrawing: DOMElement | controllers: map[string]Controller |\n| 方法 | - | invalidateRect(rect) | get currentKey()\u003cbr\u003eget selection()\u003cbr\u003eset selection(shape)\u003cbr\u003egetMousePos(event)\u003cbr\u003eregisterController(name, ctrl)\u003cbr\u003einvokeController(name)\u003cbr\u003estopController()\u003cbr\u003efireControllerReset() |\n| 事件 | - | onpaint(ctx) | onmousedown(event)\u003cbr\u003eonmousemove(event)\u003cbr\u003eonmouseup(event)\u003cbr\u003eondblclick(event)\u003cbr\u003eonkeydown(event)\u003cbr\u003eonSelectionChanged(old)\u003cbr\u003eonControllerReset() |\n\n### Controllers\n\n* Menu, PropSelectors, MousePosTracker: [accel/menu.js](https://github.com/qiniu/qpaint/blob/v27/paintweb/www/accel/menu.js)\n* ShapeSelector: [accel/select.js](https://github.com/qiniu/qpaint/blob/v27/paintweb/www/accel/select.js)\n* Create Path: [creator/path.js](https://github.com/qiniu/qpaint/blob/v27/paintweb/www/creator/path.js)\n* Create FreePath: [creator/freepath.js](https://github.com/qiniu/qpaint/blob/v27/paintweb/www/creator/freepath.js)\n* Create Line, Rect, Ellipse, Circle: [creator/rect.js](https://github.com/qiniu/qpaint/blob/v27/paintweb/www/creator/rect.js)\n\n| 类型 | Event | Model | View |\n| --- | --- | --- | --- |\n| Menu | onControllerReset() | - | controllers: map[string]Controller\u003cbr\u003eget currentKey()\u003cbr\u003einvokeController(name) |\n| PropSelectors | onSelectionChanged(old) | shape.style\u003cbr\u003eshape.setProp(key, val)\u003cbr\u003estyle.clone() | style: QShapeStyle\u003cbr\u003eget selection()\u003cbr\u003einvalidateRect(rect) |\n| MousePosTracker | onmousemove | - | getMousePos(event) |\n| QShapeSelector | onmousedown(event)\u003cbr\u003eonmousemove(event)\u003cbr\u003eonmouseup(event)\u003cbr\u003eonkeydown(event)\u003cbr\u003eonpaint(ctx) | doc.deleteShape(shape)\u003cbr\u003edoc.hitTest(pt)\u003cbr\u003eshape.move(dx, dy)\u003cbr\u003eshape.bound() | get selection()\u003cbr\u003eset selection(shape)\u003cbr\u003egetMousePos(event)\u003cbr\u003einvalidateRect(rect)\u003cbr\u003eregisterController(name, ctrl) |\n| QPathCreator | onmousedown(event)\u003cbr\u003eonmousemove(event)\u003cbr\u003eonmouseup(event)\u003cbr\u003eondblclick(event)\u003cbr\u003eonkeydown(event)\u003cbr\u003eonpaint(ctx) | new QPath(points, close, style)\u003cbr\u003edoc.addShape(shape) | getMousePos(event)\u003cbr\u003einvalidateRect(rect)\u003cbr\u003eregisterController(name, ctrl)\u003cbr\u003efireControllerReset() |\n| QFreePathCreator | onmousedown(event)\u003cbr\u003eonmousemove(event)\u003cbr\u003eonmouseup(event)\u003cbr\u003eonkeydown(event)\u003cbr\u003eonpaint(ctx) | new QPath(points, close, style)\u003cbr\u003edoc.addShape(shape) | getMousePos(event)\u003cbr\u003einvalidateRect(rect)\u003cbr\u003eregisterController(name, ctrl)\u003cbr\u003efireControllerReset() |\n| QRectCreator | onmousedown(event)\u003cbr\u003eonmousemove(event)\u003cbr\u003eonmouseup(event)\u003cbr\u003eonkeydown(event)\u003cbr\u003eonpaint(ctx) | new QLine(pt1, pt2, style)\u003cbr\u003enew QRect(rect, style)\u003cbr\u003enew QEllipse(x, y, radiusX, radiusY, style)\u003cbr\u003edoc.addShape(shape) | getMousePos(event)\u003cbr\u003einvalidateRect(rect)\u003cbr\u003eregisterController(name, ctrl)\u003cbr\u003efireControllerReset() |\n\n### Change Notes\n\n* https://github.com/qiniu/qpaint/compare/v26...v27\n\n#### Session-based Model\n\n| 类型  | View | Controllers | 修改说明 |\n| ------- | ---------- | ------- | ------ |\n| QPaintDoc | - | deleteShape(shape)\u003cbr\u003ehitTest(pt) | - 增加 hitTest (确定鼠标点中哪个图形)、deleteShape (删除某个图形)，都用于 QShapeSelector |\n| QLine\u003cbr\u003eQRect\u003cbr\u003eQEllipse\u003cbr\u003eQPath | - | new QLine(pt1, pt2, style)\u003cbr\u003enew QRect(rect, style)\u003cbr\u003enew QEllipse(x, y, radiusX, radiusY, style)\u003cbr\u003enew QPath(points, close, style)\u003cbr\u003ebound()\u003cbr\u003ehitTest(pt)\u003cbr\u003esetProp(key, val)\u003cbr\u003emove(dx, dy) | - 构造函数 style 参数由 QLineStyle 改为 QShapeStyle\u003cbr\u003e- bound (求图形的外接矩形)、hitTest 用于选择图形\u003cbr\u003e- setProp (修改图形样式的某个属性)\u003cbr\u003e- move (移动图形) |\n| QShapeStyle | new QShapeStyle(\u003cbr\u003e\u0026nbsp;\u0026nbsp;lineWidth, lineColor, fillColor\u003cbr\u003e) | setProp(key, val)\u003cbr\u003eclone() | - QLineStyle 改名为 QShapeStyle\u003cbr\u003e- 属性 width、color 改名为 lineWidth、lineColor\u003cbr\u003e- 增加属性 fillColor (图形的填充色)\u003cbr\u003e- 增加 setProp、clone (克隆图形样式) |\n\n#### ViewModel\n\n| 类型  | Model | View | Controllers | 修改说明 |\n| --------- | ------- | ----- | ----- | ------ |\n| 数据 | - | style: QShapeStyle | - | - 属性 properties 改名为 style\n| 方法 | - | - | get selection()\u003cbr\u003eset selection(shape)\u003cbr\u003efireControllerReset() | - 删除了 get lineStyle()，和 properties 统一为 style\u003cbr\u003e- 增加了 selection 读写\u003cbr\u003e- fireControllerReset，用于让创建图形的 Controller 完成或放弃图形创建时发出 onControllerReset 事件\n| 事件 | - | - | onSelectionChanged(old)\u003cbr\u003eonControllerReset() | - onSelectionChanged 在被选择的图形改变时发出\u003cbr\u003e- onControllerReset (见 fireControllerReset 的说明) |\n\n#### Controllers\n\n| 类型 | Event | Model | View | 修改说明 |\n| --- | --- | --- | --- | --- |\n| Menu | onControllerReset() | - | - | - 引入了 v2 版本的切换 Controller 的范式，更接近现代的交互范式\n| PropSelectors | onSelectionChanged(old) | shape.style\u003cbr\u003eshape.setProp(key, val)\u003cbr\u003estyle.clone() | style: QShapeStyle\u003cbr\u003eget selection()\u003cbr\u003einvalidateRect(rect) | - 这个 Controller 要比上一版本的复杂很多：之前只是修改 view 的 properties (现在是 style) 属性，以便于创建图形时引用。现在是改变它时还会作用于 selection (被选中的图形)，改变它的样式；而且，在 selection 改变时，会自动更新界面以反映被选图形的样式 |\n| MousePosTracker | - | - | - | - 无变化 |\n| QShapeSelector | onmousedown(event)\u003cbr\u003eonmousemove(event)\u003cbr\u003eonmouseup(event)\u003cbr\u003eonkeydown(event)\u003cbr\u003eonpaint(ctx) | doc.deleteShape(shape)\u003cbr\u003edoc.hitTest(pt)\u003cbr\u003eshape.move(dx, dy)\u003cbr\u003eshape.bound() | get selection()\u003cbr\u003eset selection(shape)\u003cbr\u003egetMousePos(event)\u003cbr\u003einvalidateRect(rect)\u003cbr\u003eregisterController(name, ctrl) | - 完全新增的 Controller |\n| QPathCreator | - | new QPath(points, close, style) | fireControllerReset() | - QPath 的 style 参数从 QLineStyle 变为 QShapeStyle\u003cbr\u003e- 完成或放弃图形创建时发出 onControllerReset 事件 |\n| QFreePathCreator | - | new QPath(points, close, style) | fireControllerReset() | - 同上 |\n| QRectCreator | - | new QLine(pt1, pt2, style)\u003cbr\u003enew QRect(rect, style)\u003cbr\u003enew QEllipse(x, y, radiusX, radiusY, style) | fireControllerReset() | - 同上 |\n\n## QPaint Web (第 26 讲)\n\n### Session-based Model\n\n* [dom.js](https://github.com/qiniu/qpaint/blob/v26/paintweb/www/dom.js)\n\n```TypeScript\ninterface Shape {\n    onpaint(ctx: CanvasRenderingContext2D): void\n}\n```\n\n| 类型  | View | Controllers |\n| ------------- | ---------- | ------------- |\n| QPaintDoc | onpaint(ctx) | addShape(shape) |\n| QLine\u003cbr\u003eQRect\u003cbr\u003eQEllipse\u003cbr\u003eQPath | onpaint(ctx) | new QLine(pt1, pt2, style)\u003cbr\u003enew QRect(rect, style)\u003cbr\u003enew QEllipse(x, y, radiusX, radiusY, style)\u003cbr\u003enew QPath(points, close, style) |\n| QLineStyle | - | new QLineStyle(width, color) |\n\n### ViewModel\n\n* [view.js](https://github.com/qiniu/qpaint/blob/v26/paintweb/www/view.js)\n* [index.htm](https://github.com/qiniu/qpaint/blob/v26/paintweb/www/index.htm)\n\n```TypeScript\ninterface Controller {\n  stop(): void\n  onpaint(ctx: CanvasRenderingContext2D): void\n}\n```\n\n| 类型  | Model | View | Controllers |\n| --------- | ------- | ---------- | ------------- |\n| 数据 | doc: QPaintDoc | properties: {\u003cbr\u003e\u0026nbsp;\u0026nbsp;lineWidth: number\u003cbr\u003e\u0026nbsp;\u0026nbsp;lineColor: string\u003cbr\u003e}\u003cbr\u003edrawing: DOMElement | controllers: map[string]Controller |\n| 方法 | - | invalidateRect(rect) | get currentKey()\u003cbr\u003eget lineStyle()\u003cbr\u003egetMousePos(event)\u003cbr\u003eregisterController(name, ctrl)\u003cbr\u003einvokeController(name)\u003cbr\u003estopController() |\n| 事件 | - | onpaint(ctx) | onmousedown(event)\u003cbr\u003eonmousemove(event)\u003cbr\u003eonmouseup(event)\u003cbr\u003eondblclick(event)\u003cbr\u003eonkeydown(event)\u003cbr\u003e |\n\n### Controllers\n\n* Menu, PropSelectors, MousePosTracker: [accel/menu.js](https://github.com/qiniu/qpaint/blob/v26/paintweb/www/accel/menu.js)\n* Create Path: [creator/path.js](https://github.com/qiniu/qpaint/blob/v26/paintweb/www/creator/path.js)\n* Create FreePath: [creator/freepath.js](https://github.com/qiniu/qpaint/blob/v26/paintweb/www/creator/freepath.js)\n* Create Line, Rect, Ellipse, Circle: [creator/rect.js](https://github.com/qiniu/qpaint/blob/v26/paintweb/www/creator/rect.js)\n\n| 类型 | Event | Model | View |\n| --- | --- | --- | --- |\n| Menu | - | - | controllers: map[string]Controller\u003cbr\u003eget currentKey()\u003cbr\u003einvokeController(name) |\n| PropSelectors | - | - | properties: {\u003cbr\u003e\u0026nbsp;\u0026nbsp;lineWidth: number\u003cbr\u003e\u0026nbsp;\u0026nbsp;lineColor: string\u003cbr\u003e} |\n| MousePosTracker | onmousemove | - | getMousePos(event) |\n| QPathCreator | onmousedown(event)\u003cbr\u003eonmousemove(event)\u003cbr\u003eonmouseup(event)\u003cbr\u003eondblclick(event)\u003cbr\u003eonkeydown(event)\u003cbr\u003eonpaint(ctx) | new QPath(points, close, style)\u003cbr\u003edoc.addShape(shape) | getMousePos(event)\u003cbr\u003einvalidateRect(rect)\u003cbr\u003eregisterController(name, ctrl) |\n| QFreePathCreator | onmousedown(event)\u003cbr\u003eonmousemove(event)\u003cbr\u003eonmouseup(event)\u003cbr\u003eonkeydown(event)\u003cbr\u003eonpaint(ctx) | new QPath(points, close, style)\u003cbr\u003edoc.addShape(shape) | getMousePos(event)\u003cbr\u003einvalidateRect(rect)\u003cbr\u003eregisterController(name, ctrl) |\n| QRectCreator | onmousedown(event)\u003cbr\u003eonmousemove(event)\u003cbr\u003eonmouseup(event)\u003cbr\u003eonkeydown(event)\u003cbr\u003eonpaint(ctx) | new QLine(pt1, pt2, style)\u003cbr\u003enew QRect(rect, style)\u003cbr\u003enew QEllipse(x, y, radiusX, radiusY, style)\u003cbr\u003edoc.addShape(shape) | getMousePos(event)\u003cbr\u003einvalidateRect(rect)\u003cbr\u003eregisterController(name, ctrl) |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqiniu%2Fqpaint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fqiniu%2Fqpaint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fqiniu%2Fqpaint/lists"}