Original commit: 18971b05f2
This commit is contained in:
Wojciech Daniło 2020-05-30 22:34:30 +02:00 committed by GitHub
parent 2389718495
commit 8dbab93116
39 changed files with 3059 additions and 1393 deletions

View File

@ -10,27 +10,27 @@
"integrity": "sha512-GLyWIFBbGvpKPGo55JyRZAo4lVbnBiD52cKlw/0Vt+wnmKvWJkpZvsjVoaIolyBXDeAQKSicRtqFNPem9w0WYA=="
},
"@babel/code-frame": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.8.3.tgz",
"integrity": "sha512-a9gxpmdXtZEInkCSHUJDLHZVBgb1QS0jhss4cPP93EW7s+uC5bikET2twEF3KV+7rDblJcmNvTR7VJejqd2C2g==",
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.1.tgz",
"integrity": "sha512-IGhtTmpjGbYzcEDOw7DcQtbQSXcG9ftmAXtWTu9V936vDye4xjjekktFAtgZsWpzTj/X01jocB46mTywm/4SZw==",
"dev": true,
"requires": {
"@babel/highlight": "^7.8.3"
"@babel/highlight": "^7.10.1"
}
},
"@babel/helper-validator-identifier": {
"version": "7.9.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz",
"integrity": "sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==",
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.1.tgz",
"integrity": "sha512-5vW/JXLALhczRCWP0PnFDMCJAchlBvM7f4uk/jXritBnIa6E1KmqmtrS3yn1LAnxFBypQ3eneLuXjsnfQsgILw==",
"dev": true
},
"@babel/highlight": {
"version": "7.9.0",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.9.0.tgz",
"integrity": "sha512-lJZPilxX7Op3Nv/2cvFdnlepPXDxi29wxteT57Q965oc5R9v86ztx0jfxVrTcBk8C2kcPkkDa2Z4T3ZsPPVWsQ==",
"version": "7.10.1",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.1.tgz",
"integrity": "sha512-8rMof+gVP8mxYZApLF/JgNDAkdKa+aJt3ZYxF8z6+j/hpeXL7iMsKCPHa2jNMHu/qqBwzQF4OHNoYi8dMA/rYg==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.9.0",
"@babel/helper-validator-identifier": "^7.10.1",
"chalk": "^2.0.0",
"js-tokens": "^4.0.0"
}
@ -1251,9 +1251,9 @@
}
},
"@octokit/types": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-4.0.2.tgz",
"integrity": "sha512-+4X6qfhT/fk/5FD66395NrFLxCzD6FsGlpPwfwvnukdyfYbhiZB/FJltiT1XM5Q63rGGBSf9FPaNV3WpNHm54A==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-4.1.1.tgz",
"integrity": "sha512-gOuIVmMCfHzSv3QBwLZjGDEDtYC73A5+tYcccEoq+Jd/h9rG/Mfc+h0+GEU+mpGhx86n7eBw6J/0BZ0zAmLkKg==",
"dev": true,
"requires": {
"@types/node": ">= 8"
@ -1289,9 +1289,9 @@
"dev": true
},
"@types/fs-extra": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-8.1.1.tgz",
"integrity": "sha512-TcUlBem321DFQzBNuz8p0CLLKp0VvF/XH9E4KHNmgwyp4E3AfgI5cjiIVZWlbfThBop2qxFIh4+LeY6hVWWZ2w==",
"version": "9.0.1",
"resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-9.0.1.tgz",
"integrity": "sha512-B42Sxuaz09MhC3DDeW5kubRcQ5by4iuVQ0cRRWM2lggLzAa/KVom0Aft/208NgMvNQQZ86s5rVcqDdn/SH0/mg==",
"requires": {
"@types/node": "*"
}
@ -1307,6 +1307,11 @@
"@types/node": "*"
}
},
"@types/json-schema": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.4.tgz",
"integrity": "sha512-8+KAKzEvSUdeo+kmqnKrqgeE+LcA0tjYWFY7RPProVYwnqDjukzO+3b6dLD56rYX5TdWejnEOLJYOIeh4CXKuA=="
},
"@types/minimatch": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz",
@ -1727,25 +1732,25 @@
"integrity": "sha512-NSjtqZ3x2kYiDp3Qezsgukx/AUzKPr3Xgf9by4cYt05ILWGAptepeeu0Uv+7MO+41o6ujhLixTou8979JGg2Kg=="
},
"app-builder-lib": {
"version": "22.6.1",
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-22.6.1.tgz",
"integrity": "sha512-ENL7r+H7IBfDb4faeLASgndsXrAT7AV7m7yJjcpbFDXYma6an7ZWGFIvR0HJrsfiC5TIB8kdLJ/aMSImrrSi/Q==",
"version": "22.7.0",
"resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-22.7.0.tgz",
"integrity": "sha512-blRKwV8h0ztualXS50ciCTo39tbuDGNS+ldcy8+KLvKXuT6OpYnSJ7M6MSfPT+xWatshMHJV1rJx3Tl+k/Sn/g==",
"requires": {
"7zip-bin": "~5.0.3",
"@develar/schema-utils": "~2.6.5",
"async-exit-hook": "^2.0.1",
"bluebird-lst": "^1.0.9",
"builder-util": "22.6.1",
"builder-util-runtime": "8.7.0",
"builder-util": "22.7.0",
"builder-util-runtime": "8.7.1",
"chromium-pickle-js": "^0.2.0",
"debug": "^4.1.1",
"ejs": "^3.1.2",
"electron-publish": "22.6.1",
"debug": "^4.2.0",
"ejs": "^3.1.3",
"electron-publish": "22.7.0",
"fs-extra": "^9.0.0",
"hosted-git-info": "^3.0.4",
"is-ci": "^2.0.0",
"isbinaryfile": "^4.0.6",
"js-yaml": "^3.13.1",
"js-yaml": "^3.14.0",
"lazy-val": "^1.0.4",
"minimatch": "^3.0.4",
"normalize-package-data": "^2.5.0",
@ -1756,11 +1761,11 @@
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
"requires": {
"ms": "^2.1.1"
"ms": "2.1.2"
}
},
"fs-extra": {
@ -2512,21 +2517,21 @@
"dev": true
},
"builder-util": {
"version": "22.6.1",
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-22.6.1.tgz",
"integrity": "sha512-A9cF+bSHqRTSKIUHEyE92Tl0Uh12N7yZRH9bccIL3gRUwtp6ulF28LsjNIWTSQ1clZo2M895cT5PCrKzjPQFVg==",
"version": "22.7.0",
"resolved": "https://registry.npmjs.org/builder-util/-/builder-util-22.7.0.tgz",
"integrity": "sha512-UV3MKL0mwjMq2y9JlBf28Cegpj0CrIXcjGkO0TXn+QZ6Yy9rY6lHOuUvpQ19ct2Qh1o+QSwH3Q1nKUf5viJBBg==",
"requires": {
"7zip-bin": "~5.0.3",
"@types/debug": "^4.1.5",
"@types/fs-extra": "^8.1.0",
"@types/fs-extra": "^9.0.1",
"app-builder-bin": "3.5.9",
"bluebird-lst": "^1.0.9",
"builder-util-runtime": "8.7.0",
"builder-util-runtime": "8.7.1",
"chalk": "^4.0.0",
"debug": "^4.1.1",
"debug": "^4.2.0",
"fs-extra": "^9.0.0",
"is-ci": "^2.0.0",
"js-yaml": "^3.13.1",
"js-yaml": "^3.14.0",
"source-map-support": "^0.5.19",
"stat-mode": "^1.0.0",
"temp-file": "^3.3.7"
@ -2564,11 +2569,11 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
"requires": {
"ms": "^2.1.1"
"ms": "2.1.2"
}
},
"fs-extra": {
@ -2626,20 +2631,20 @@
}
},
"builder-util-runtime": {
"version": "8.7.0",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.7.0.tgz",
"integrity": "sha512-G1AqqVM2vYTrSFR982c1NNzwXKrGLQjVjaZaWQdn4O6Z3YKjdMDofw88aD9jpyK9ZXkrCxR0tI3Qe9wNbyTlXg==",
"version": "8.7.1",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.7.1.tgz",
"integrity": "sha512-uEBH1nAnTvzjcsrh2XI3qOzJ39h0+9kuIuwj+kCc3a07TZNGShfJcai8fFzL3mNgGjEFxoq+XMssR11r+FOFSg==",
"requires": {
"debug": "^4.1.1",
"debug": "^4.2.0",
"sax": "^1.2.4"
},
"dependencies": {
"debug": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz",
"integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==",
"requires": {
"ms": "^2.1.1"
"ms": "2.1.2"
}
}
}
@ -3176,6 +3181,17 @@
"webpack-sources": "^1.0.1"
},
"dependencies": {
"ajv": {
"version": "6.12.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz",
"integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==",
"requires": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
"json-schema-traverse": "^0.4.1",
"uri-js": "^4.2.2"
}
},
"cacache": {
"version": "13.0.1",
"resolved": "https://registry.npmjs.org/cacache/-/cacache-13.0.1.tgz",
@ -3226,11 +3242,12 @@
}
},
"schema-utils": {
"version": "2.6.6",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.6.6.tgz",
"integrity": "sha512-wHutF/WPSbIi9x6ctjGGk2Hvl0VOz5l3EKEuKbjPlB30mKZUzb9A5k9yEXRX3pwyqVLPvpfZZEllaFq/M718hA==",
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.0.tgz",
"integrity": "sha512-0ilKFI6QQF5nxDZLFn2dMjvc4hjg/Wkg7rHd3jK6/A4a1Hl9VFdQWvgB1UMGoU94pad1P/8N7fMcEnLnSiju8A==",
"requires": {
"ajv": "^6.12.0",
"@types/json-schema": "^7.0.4",
"ajv": "^6.12.2",
"ajv-keywords": "^3.4.1"
}
},
@ -4184,15 +4201,15 @@
}
},
"dmg-builder": {
"version": "22.6.1",
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-22.6.1.tgz",
"integrity": "sha512-jUTN0acP15puzevtQASj7QEPgUGpedWSuSnOwR/++JbeYRTwU2oro09h/KZnaeMcxgxjdmT3tYLJeY1XUfPbRg==",
"version": "22.7.0",
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-22.7.0.tgz",
"integrity": "sha512-5Ea2YEz6zSNbyGzZD+O9/MzmaXb6oa15cSKWo4JQ1xP4rorOpte7IOj2jcwYjtc+Los2gu1lvT314OC1OZIWgg==",
"requires": {
"app-builder-lib": "22.6.1",
"builder-util": "22.6.1",
"app-builder-lib": "22.7.0",
"builder-util": "22.7.0",
"fs-extra": "^9.0.0",
"iconv-lite": "^0.5.1",
"js-yaml": "^3.13.1",
"js-yaml": "^3.14.0",
"sanitize-filename": "^1.6.3"
},
"dependencies": {
@ -4349,17 +4366,17 @@
}
},
"electron-builder": {
"version": "22.6.1",
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-22.6.1.tgz",
"integrity": "sha512-3/VNg9GfXKHM53TilFtfF1+bsAR8THK1XHgeqCpsiequa02J9jTPc/DhpCUKQPkrs6/EIGxP7uboop7XYoew0Q==",
"version": "22.7.0",
"resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-22.7.0.tgz",
"integrity": "sha512-t6E3oMutpST64YWbZCg7HodEwJOsnjUF1vnDIHm2MW6CFZPX8tlCK6efqaV66LU0E0Nkp/JH6TE5bCqQ1+VdPQ==",
"requires": {
"@types/yargs": "^15.0.5",
"app-builder-lib": "22.6.1",
"app-builder-lib": "22.7.0",
"bluebird-lst": "^1.0.9",
"builder-util": "22.6.1",
"builder-util-runtime": "8.7.0",
"builder-util": "22.7.0",
"builder-util-runtime": "8.7.1",
"chalk": "^4.0.0",
"dmg-builder": "22.6.1",
"dmg-builder": "22.7.0",
"fs-extra": "^9.0.0",
"is-ci": "^2.0.0",
"lazy-val": "^1.0.4",
@ -4446,14 +4463,14 @@
"integrity": "sha512-R1oD5gMBPS7PVU8gJwH6CtT0e6VSoD0+SzSnYpNm+dBkcijgA+K7VAMHDfnRq/lkKPZArpzplTW6jfiMYosdzw=="
},
"electron-publish": {
"version": "22.6.1",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-22.6.1.tgz",
"integrity": "sha512-/MkS47ospdSfAFW5Jp52OzYou14HhGJpZ51uAc3GJ5rCfACeqpimC/n1ajRLE3hcXxTWfd3t9MCuClq5jrUO5w==",
"version": "22.7.0",
"resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-22.7.0.tgz",
"integrity": "sha512-hmU69xlb6vvAV3QfpHYDlkdZMFdBAgDbptoxbLFrnTq5bOkcL8AaDbvxeoZ4+lvqgs29NwqGpkHo2oN+p/hCfg==",
"requires": {
"@types/fs-extra": "^8.1.0",
"@types/fs-extra": "^9.0.1",
"bluebird-lst": "^1.0.9",
"builder-util": "22.6.1",
"builder-util-runtime": "8.7.0",
"builder-util": "22.7.0",
"builder-util-runtime": "8.7.1",
"chalk": "^4.0.0",
"fs-extra": "^9.0.0",
"lazy-val": "^1.0.4",
@ -4517,9 +4534,9 @@
}
},
"mime": {
"version": "2.4.5",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.5.tgz",
"integrity": "sha512-3hQhEUF027BuxZjQA3s7rIv/7VCQPa27hN9u9g87sEkWaKwQPuXOkVKtOeiyUrnWqTDiOs8Ed2rwg733mB0R5w=="
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.6.tgz",
"integrity": "sha512-RZKhC3EmpBchfTGBVb8fb+RL2cWyw/32lshnsETttkBAyAUXSGHxbEJWWRXc751DrIxG1q04b8QwMbAwkRPpUA=="
},
"supports-color": {
"version": "7.1.0",
@ -6495,9 +6512,9 @@
}
},
"global-agent": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.1.8.tgz",
"integrity": "sha512-VpBe/rhY6Rw2VDOTszAMNambg+4Qv8j0yiTNDYEXXXxkUNGWLHp8A3ztK4YDBbFNcWF4rgsec6/5gPyryya/+A==",
"version": "2.1.9",
"resolved": "https://registry.npmjs.org/global-agent/-/global-agent-2.1.9.tgz",
"integrity": "sha512-hylHMbK50BzJbUmQ0LPYK1+AY862Xkvje4DAaonQDfMewGIe7/+XHfG90FORsEsrfw7pskhwV4cFAYgVAPNrdw==",
"optional": true,
"requires": {
"boolean": "^3.0.0",
@ -8255,9 +8272,9 @@
}
},
"min-indent": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.0.tgz",
"integrity": "sha1-z8RcN+nsDY8KDsPdTvf3w6vjklY=",
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
"integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
"dev": true
},
"minimalistic-assert": {
@ -8560,9 +8577,9 @@
"dev": true
},
"node-abi": {
"version": "2.17.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.17.0.tgz",
"integrity": "sha512-dFRAA0ACk/aBo0TIXQMEWMLUTyWYYT8OBYIzLmEUrQTElGRjxDCvyBZIsDL0QA7QCaj9PrawhOmTEdsuLY4uOQ==",
"version": "2.18.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.18.0.tgz",
"integrity": "sha512-yi05ZoiuNNEbyT/xXfSySZE+yVnQW6fxPZuFbLyS1s6b5Kw3HzV2PHOM4XR+nsjzkHxByK+2Wg+yCQbe35l8dw==",
"requires": {
"semver": "^5.4.1"
},
@ -11635,9 +11652,9 @@
}
},
"uglify-js": {
"version": "3.9.3",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.3.tgz",
"integrity": "sha512-r5ImcL6QyzQGVimQoov3aL2ZScywrOgBXGndbWrdehKoSvGe/RmiE5Jpw/v+GvxODt6l2tpBXwA7n+qZVlHBMA==",
"version": "3.9.4",
"resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.9.4.tgz",
"integrity": "sha512-8RZBJq5smLOa7KslsNsVcSH+KOXf1uDU8yqLeNuVKwmT0T3FA0ZoXlinQfRad7SDcbZZRZE4ov+2v71EnxNyCA==",
"dev": true,
"optional": true,
"requires": {

View File

@ -8,14 +8,15 @@ use crate::animation;
// ================
// === Position ===
// ================
// =============
// === Value ===
// =============
/// The type of the position of the simulation. In particular, the Position could be `f32`
/// (1-dimensional simulation), or `Vector<f32>` (3-dimensional simulation).
pub trait Position
= 'static + Copy + Default + Debug + Magnitude + Normalize
/// The type of the value of the simulation. In particular, the Value could be `f32`
/// (1-dimensional simulation), or `Vector3<f32>` (3-dimensional simulation).
pub trait Value
= 'static + Copy + Default + Debug + Normalize
+ Magnitude <Output=f32>
+ Neg< Output=Self>
+ Sub<Self , Output=Self>
+ Add<Self , Output=Self>
@ -93,17 +94,17 @@ impl Thresholds {
/// A fixed step physics simulator used to simulate `PhysicsState`.
#[derive(Clone,Copy,Debug,Default)]
pub struct SimulationData<T> {
position : T,
target_position : T,
velocity : T,
mass : Mass,
spring : Spring,
drag : Drag,
thresholds : Thresholds,
active : bool,
value : T,
target_value : T,
velocity : T,
mass : Mass,
spring : Spring,
drag : Drag,
thresholds : Thresholds,
active : bool,
}
impl<T:Position> SimulationData<T> {
impl<T:Value> SimulationData<T> {
/// Constructor.
pub fn new() -> Self {
default()
@ -113,30 +114,30 @@ impl<T:Position> SimulationData<T> {
fn step(&mut self, delta_seconds:f32) {
if self.active {
let velocity = self.velocity.magnitude();
let distance = (self.position - self.target_position).magnitude();
let distance = (self.value - self.target_value).magnitude();
let snap_velocity = velocity < self.thresholds.speed;
let snap_distance = distance < self.thresholds.distance;
let should_snap = snap_velocity && snap_distance;
if should_snap {
self.position = self.target_position;
self.value = self.target_value;
self.velocity = default();
self.active = false;
} else {
let force = self.spring_force() + self.drag_force();
let acceleration = force / self.mass.value;
self.velocity = self.velocity + acceleration * delta_seconds;
self.position = self.position + self.velocity * delta_seconds;
self.value = self.value + self.velocity * delta_seconds;
}
}
}
/// Compute spring force.
fn spring_force(&self) -> T {
let position_delta = self.target_position - self.position;
let distance = position_delta.magnitude();
let value_delta = self.target_value - self.value;
let distance = value_delta.magnitude();
if distance > 0.0 {
let coefficient = distance * self.spring.value;
position_delta.normalize() * coefficient
value_delta.normalize() * coefficient
} else {
default()
}
@ -152,44 +153,44 @@ impl<T:Position> SimulationData<T> {
// === Getters ===
#[allow(missing_docs)]
impl<T:Position> SimulationData<T> {
pub fn position (&self) -> T { self.position }
pub fn target_position (&self) -> T { self.target_position }
pub fn velocity (&self) -> T { self.velocity }
pub fn mass (&self) -> Mass { self.mass }
pub fn spring (&self) -> Spring { self.spring }
pub fn drag (&self) -> Drag { self.drag }
pub fn thresholds (&self) -> Thresholds { self.thresholds }
pub fn active (&self) -> bool { self.active }
impl<T:Value> SimulationData<T> {
pub fn value (&self) -> T { self.value }
pub fn target_value (&self) -> T { self.target_value }
pub fn velocity (&self) -> T { self.velocity }
pub fn mass (&self) -> Mass { self.mass }
pub fn spring (&self) -> Spring { self.spring }
pub fn drag (&self) -> Drag { self.drag }
pub fn thresholds (&self) -> Thresholds { self.thresholds }
pub fn active (&self) -> bool { self.active }
}
// === Setters ===
#[allow(missing_docs)]
impl<T:Position> SimulationData<T> {
impl<T:Value> SimulationData<T> {
pub fn set_velocity (&mut self, velocity:T) { self.velocity = velocity; }
pub fn set_mass (&mut self, mass:Mass) { self.mass = mass; }
pub fn set_spring (&mut self, spring:Spring) { self.spring = spring; }
pub fn set_drag (&mut self, drag:Drag) { self.drag = drag; }
pub fn set_thresholds (&mut self, thresholds:Thresholds) { self.thresholds = thresholds; }
pub fn set_position(&mut self, position:T) {
pub fn set_value(&mut self, value:T) {
self.active = true;
self.position = position;
self.value = value;
}
pub fn set_target_position(&mut self, target_position:T) {
pub fn set_target_value(&mut self, target_value:T) {
self.active = true;
self.target_position = target_position;
self.target_value = target_value;
}
pub fn update_position<F:FnOnce(T)->T>(&mut self, f:F) {
self.set_position(f(self.position()));
pub fn update_value<F:FnOnce(T)->T>(&mut self, f:F) {
self.set_value(f(self.value()));
}
pub fn update_target_position<F:FnOnce(T)->T>(&mut self, f:F) {
self.set_target_position(f(self.target_position()));
pub fn update_target_value<F:FnOnce(T)->T>(&mut self, f:F) {
self.set_target_value(f(self.target_value()));
}
pub fn update_velocity<F:FnOnce(T)->T>(&mut self, f:F) {
@ -211,6 +212,12 @@ impl<T:Position> SimulationData<T> {
pub fn update_thresholds<F:FnOnce(Thresholds)->Thresholds>(&mut self, f:F) {
self.set_thresholds(f(self.thresholds()));
}
pub fn skip(&mut self) {
self.active = false;
self.value = self.target_value;
self.velocity = default();
}
}
@ -226,7 +233,7 @@ pub struct Simulation<T:Copy> {
data : Rc<Cell<SimulationData<T>>>
}
impl<T:Position> Simulation<T> {
impl<T:Value> Simulation<T> {
/// Constructor.
pub fn new() -> Self {
default()
@ -244,17 +251,17 @@ impl<T:Position> Simulation<T> {
// === Getters ===
#[allow(missing_docs)]
impl<T:Position> Simulation<T> {
impl<T:Value> Simulation<T> {
pub fn active(&self) -> bool {
self.data.get().active()
}
pub fn position(&self) -> T {
self.data.get().position()
pub fn value(&self) -> T {
self.data.get().value()
}
pub fn target_position(&self) -> T {
self.data.get().target_position()
pub fn target_value(&self) -> T {
self.data.get().target_value()
}
}
@ -262,7 +269,7 @@ impl<T:Position> Simulation<T> {
// === Setters ===
#[allow(missing_docs)]
impl<T:Position> Simulation<T> {
impl<T:Value> Simulation<T> {
pub fn set_mass(&self, mass:Mass) {
self.data.update(|mut sim| {sim.set_mass(mass); sim});
}
@ -279,16 +286,20 @@ impl<T:Position> Simulation<T> {
self.data.update(|mut sim| {sim.set_velocity(velocity); sim});
}
pub fn set_position(&self, position:T) {
self.data.update(|mut sim| {sim.set_position(position); sim});
pub fn set_value(&self, value:T) {
self.data.update(|mut sim| {sim.set_value(value); sim});
}
pub fn set_target_position(&self, target_position:T) {
self.data.update(|mut sim| {sim.set_target_position(target_position); sim});
pub fn set_target_value(&self, target_value:T) {
self.data.update(|mut sim| {sim.set_target_value(target_value); sim});
}
pub fn update_target_position<F:FnOnce(T)->T>(&self, f:F) {
self.data.update(|mut sim| {sim.update_target_position(f); sim});
pub fn update_target_value<F:FnOnce(T)->T>(&self, f:F) {
self.data.update(|mut sim| {sim.update_target_value(f); sim});
}
pub fn skip(&self) {
self.data.update(|mut sim| {sim.skip(); sim});
}
}
@ -310,7 +321,7 @@ pub type DynSimulator<T> = Simulator<T,Box<dyn Fn(T)>>;
#[derive(CloneRef,Derivative,Shrinkwrap)]
#[derivative(Clone(bound=""))]
#[derivative(Debug(bound=""))]
pub struct Simulator<T:Position,Cb> {
pub struct Simulator<T:Value,Cb> {
#[shrinkwrap(main_field)]
simulation : Simulation<T>,
animation_loop : Rc<CloneCell<Option<FixedFrameRateAnimationStep<T,Cb>>>>,
@ -319,7 +330,7 @@ pub struct Simulator<T:Position,Cb> {
callback : Rc<Cb>,
}
impl<T:Position,Cb> Simulator<T,Cb>
impl<T:Value,Cb> Simulator<T,Cb>
where Cb : Callback<T> {
/// Constructor.
pub fn new(callback:Cb) -> Self {
@ -334,7 +345,7 @@ where Cb : Callback<T> {
// === Setters ===
#[allow(missing_docs)]
impl<T:Position,Cb> Simulator<T,Cb>
impl<T:Value,Cb> Simulator<T,Cb>
where Cb : Callback<T> {
pub fn set_callback(&mut self, callback:Cb) {
let callback = Rc::new(callback);
@ -343,26 +354,32 @@ where Cb : Callback<T> {
self.start();
}
pub fn set_position(&self, position:T) {
self.simulation.set_position(position);
pub fn set_value(&self, value:T) {
self.simulation.set_value(value);
self.start();
}
pub fn set_target_position(&self, target_position:T) {
self.simulation.set_target_position(target_position);
pub fn set_target_value(&self, target_value:T) {
self.simulation.set_target_value(target_value);
self.start();
}
pub fn update_target_position<F:FnOnce(T)->T>(&self, f:F) {
self.simulation.update_target_position(f);
pub fn update_target_value<F:FnOnce(T)->T>(&self, f:F) {
self.simulation.update_target_value(f);
self.start();
}
pub fn skip(&self) {
self.simulation.skip();
self.stop();
(self.callback)(self.simulation.value());
}
}
// === Private API ===
impl<T:Position,Cb> Simulator<T,Cb>
impl<T:Value,Cb> Simulator<T,Cb>
where Cb : Callback<T> {
fn init(self) -> Self {
self.start();
@ -388,14 +405,14 @@ where Cb : Callback<T> {
/// Alias for `FixedFrameRateLoop` with specified step callback.
pub type FixedFrameRateAnimationStep<T,Cb> = animation::FixedFrameRateLoop<Step<T,Cb>>;
pub type Step<T,Cb> = impl Fn(animation::TimeInfo);
fn step<T:Position,Cb>(simulator:&Simulator<T,Cb>) -> Step<T,Cb>
fn step<T:Value,Cb>(simulator:&Simulator<T,Cb>) -> Step<T,Cb>
where Cb : Callback<T> {
let this = simulator.clone_ref();
move |time:animation::TimeInfo| {
let delta_seconds = time.frame / 1000.0;
if this.simulation.active() {
this.simulation.step(delta_seconds);
(this.callback)(this.simulation.position());
(this.callback)(this.simulation.value());
} else {
this.stop();
}

View File

@ -27,7 +27,7 @@ use nalgebra::Vector4;
/// just want it, for example to match the behavior of color mixing in web browsers, which is
/// broken for many years already:
/// https://stackoverflow.com/questions/60179850/webgl-2-0-canvas-blending-with-html-in-linear-color-space
#[derive(Clone,Copy,Debug,PartialEq)]
#[derive(Clone,Copy,Debug,Default,PartialEq)]
pub struct Color<D> {
/// The underlying color representation. It is either `Alpha` or a color space instance.
pub data : D
@ -225,3 +225,11 @@ impl<C> From<C> for Alpha<C> {
Self {alpha,color}
}
}
impl<C:Default> Default for Alpha<C> {
fn default() -> Self {
let alpha = 1.0;
let color = default();
Self {alpha,color}
}
}

View File

@ -18,7 +18,7 @@ macro_rules! define_color_space {
pub type $a_name = Color<Alpha<$data_name>>;
$(#[$($meta)*])*
#[derive(Clone,Copy,Debug,PartialEq)]
#[derive(Clone,Copy,Debug,Default,PartialEq)]
#[allow(missing_docs)]
pub struct $data_name {
$(pub $comp : f32),*
@ -26,14 +26,15 @@ macro_rules! define_color_space {
impl $data_name {
/// Constructor.
pub fn new($($comp:f32),*) -> Self {
pub fn new($($comp:impl Into<f32>),*) -> Self {
$(let $comp = $comp.into();)*
Self {$($comp),*}
}
}
impl $name {
/// Constructor.
pub fn new($($comp:f32),*) -> Self {
pub fn new($($comp:impl Into<f32>),*) -> Self {
let data = $data_name::new($($comp),*);
Self {data}
}
@ -41,7 +42,8 @@ macro_rules! define_color_space {
impl $a_name {
/// Constructor.
pub fn new($($comp:f32),*,alpha:f32) -> Self {
pub fn new($($comp:impl Into<f32>),*,alpha:impl Into<f32>) -> Self {
let alpha = alpha.into();
let color = $data_name::new($($comp),*);
let data = Alpha {alpha,color};
Self {data}

View File

@ -23,7 +23,7 @@ use events::ZoomEvent;
#[derive(Debug)]
pub struct Navigator {
_events : NavigatorEvents,
simulator : physics::inertia::DynSimulator<Point3>,
simulator : physics::inertia::DynSimulator<V3>,
resize_callback : callback::Handle
}
@ -39,12 +39,12 @@ impl Navigator {
Self {simulator,_events,resize_callback}
}
fn create_simulator(camera:&Camera2d) -> physics::inertia::DynSimulator<Point3> {
fn create_simulator(camera:&Camera2d) -> physics::inertia::DynSimulator<V3> {
let camera_ref = camera.clone_ref();
let update = Box::new(move |p:Point3| camera_ref.set_position(p.into()));
let update = Box::new(move |p:V3| camera_ref.set_position(p.into()));
let simulator = physics::inertia::DynSimulator::new(update);
simulator.set_position(camera.position().into());
simulator.set_target_position(camera.position().into());
simulator.set_value(camera.position().into());
simulator.set_target_value(camera.position().into());
simulator
}
@ -54,7 +54,7 @@ impl Navigator {
, min_zoom : f32
, max_zoom : f32
, zoom_speed : f32
) -> (physics::inertia::DynSimulator<Point3>,callback::Handle,NavigatorEvents) {
) -> (physics::inertia::DynSimulator<V3>,callback::Handle,NavigatorEvents) {
let simulator = Self::create_simulator(&camera);
let panning_callback = enclose!((dom,camera,mut simulator) move |pan: PanEvent| {
let fovy_slope = camera.half_fovy_slope();
@ -64,15 +64,15 @@ impl Navigator {
let dx = pan.movement.x * movement_scale_for_distance;
let dy = pan.movement.y * movement_scale_for_distance;
let diff = Point3::new(dx,dy,0.0);
simulator.update_target_position(|p| p + diff);
let diff = V3::new(dx,dy,0.0);
simulator.update_target_value(|p| p + diff);
});
let resize_callback = camera.add_screen_update_callback(
enclose!((mut simulator,camera) move |_:&Vector2<f32>| {
let position = camera.position().into();
simulator.set_position(position);
simulator.set_target_position(position);
simulator.set_value(position);
simulator.set_target_value(position);
simulator.set_velocity(default());
})
);
@ -87,8 +87,8 @@ impl Navigator {
let x = -normalized.x * camera.screen().aspect();
let y = -normalized.y;
let z = half_height / camera.half_fovy_slope();
let direction = Point3::new(x,y,z).normalize();
let mut position = simulator.target_position();
let direction = V3::new(x,y,z).normalize();
let mut position = simulator.target_value();
let min_zoom = camera.clipping().near + min_zoom;
let zoom_amount = zoom.amount * position.z;
let direction = direction * zoom_amount;
@ -100,7 +100,7 @@ impl Navigator {
else if too_close { min_zoom_limit / direction.z }
else { 1.0 };
position += direction * zoom_factor;
simulator.set_target_position(position);
simulator.set_target_value(position);
});
(simulator,resize_callback, NavigatorEvents::new(&dom, panning_callback, zoom_callback, zoom_speed))
}

View File

@ -719,6 +719,13 @@ pub trait ObjectOps : Object {
self.display_object().rc.set_position(t);
}
fn set_position_xy(&self, t:Vector2<f32>) {
self.mod_position(|p| {
p.x = t.x;
p.y = t.y;
})
}
fn set_scale(&self, t:Vector3<f32>) {
self.display_object().rc.set_scale(t);
}

View File

@ -186,7 +186,7 @@ define_sdf_shapes! {
PlaneAngle (angle:Angle<Radians>) {
float pi_2 = 2.0 * PI;
float angle_norm = value(angle) / pi_2;
angle_norm = abs(mod(angle_norm,2.0)-1.0);
angle_norm = 1.0 - abs(mod(angle_norm,2.0)-1.0);
float angle_rad = angle_norm * pi_2;
float off = angle_norm - 0.5; // Fixes artifacts with 0 and 360 degrees.
float distance = abs(position).x*cos(angle_rad/2.0) - position.y*sin(angle_rad/2.0) - off;
@ -263,8 +263,9 @@ define_sdf_shapes! {
// === Triangle ===
Triangle (width:f32, height:f32) {
vec2 norm = normalize(vec2(height,width/2.0));
float dist = max(abs(position).x*norm.x + position.y*norm.y - height*norm.y, -position.y);
vec2 norm = normalize(vec2(height,width/2.0));
float pos_y = -position.y - height/2.0;
float dist = max(abs(position).x*norm.x + position.y*norm.y - height/2.0*norm.y, pos_y);
return bound_sdf(dist,bounding_box(width,height/2.0));
}
}

View File

@ -18,13 +18,23 @@ pub use crate::math::topology::unit::Degrees;
// =====================
/// Provides a `px` method to every unit that can be converted to a pixel distance.
#[allow(missing_docs)]
pub trait PixelDistance {
type Output;
/// Distance in pixels.
fn px(&self) -> Var<Distance<Pixels>>;
fn px(&self) -> Self::Output;
}
impl<T: unit::PixelDistance> PixelDistance for T {
fn px(&self) -> Var<Distance<Pixels>> {
impl PixelDistance for i32 {
type Output = Var<Distance<Pixels>>;
fn px(&self) -> Self::Output {
unit::PixelDistance::px(self).into()
}
}
impl PixelDistance for f32 {
type Output = Var<Distance<Pixels>>;
fn px(&self) -> Self::Output {
unit::PixelDistance::px(self).into()
}
}

View File

@ -3,6 +3,7 @@
use crate::prelude::*;
use crate::data::color;
use crate::display::shape::primitive::def::unit::PixelDistance;
use crate::math::algebra::Acos;
use crate::math::algebra::Asin;
use crate::math::algebra::Cos;
@ -21,6 +22,7 @@ use nalgebra::Scalar;
use std::ops::*;
// ======================
// === VarInitializer ===
// ======================
@ -55,6 +57,15 @@ impl<T,U,V> VarInitializerMarker<Var<Unit<T,Anything,V>>> for Unit<T,U,V> where
impl<T,S1,S2> VarInitializerMarker<Var<Vector2<T>>> for (S1,S2)
where T:Scalar, S1:VarInitializerMarkerNested<Var<T>>, S2:VarInitializerMarkerNested<Var<T>> {}
impl<T:Scalar> VarInitializerMarker<Var<Vector2<T>>> for Var<V2<T>> {}
impl<T:Scalar> VarInitializerMarker<Var<Vector3<T>>> for Var<V3<T>> {}
impl<T:Scalar> VarInitializerMarker<Var<Vector4<T>>> for Var<V4<T>> {}
impl<T:Scalar> VarInitializerMarker<Var<Vector2<T>>> for &Var<V2<T>> {}
impl<T:Scalar> VarInitializerMarker<Var<Vector3<T>>> for &Var<V3<T>> {}
impl<T:Scalar> VarInitializerMarker<Var<Vector4<T>>> for &Var<V4<T>> {}
// === Nested ===
@ -192,6 +203,81 @@ impl<T:Scalar> Dim3 for Var<Vector3<T>> {
impl<T:Scalar> HasComponents for Var<V2<T>> {
type Component = Var<T>;
}
impl<T:Scalar> HasComponents for Var<V3<T>> {
type Component = Var<T>;
}
impl<T:Scalar> Dim1 for Var<V2<T>> {
fn x(&self) -> Var<T> {
match self {
Self::Static (t) => Var::Static(t.x),
Self::Dynamic (t) => Var::Dynamic(format!("{}.x",t).into())
}
}
}
impl<T:Scalar> Dim2 for Var<V2<T>> {
fn y(&self) -> Var<T> {
match self {
Self::Static (t) => Var::Static(t.y),
Self::Dynamic (t) => Var::Dynamic(format!("{}.y",t).into())
}
}
}
impl<T:Scalar> Dim1 for Var<V3<T>> {
fn x(&self) -> Var<T> {
match self {
Self::Static (t) => Var::Static(t.x),
Self::Dynamic (t) => Var::Dynamic(format!("{}.x",t).into())
}
}
}
impl<T:Scalar> Dim2 for Var<V3<T>> {
fn y(&self) -> Var<T> {
match self {
Self::Static (t) => Var::Static(t.y),
Self::Dynamic (t) => Var::Dynamic(format!("{}.y",t).into())
}
}
}
impl<T:Scalar> Dim3 for Var<V3<T>> {
fn z(&self) -> Var<T> {
match self {
Self::Static (t) => Var::Static(t.z),
Self::Dynamic (t) => Var::Dynamic(format!("{}.z",t).into())
}
}
}
impl PixelDistance for Var<V2<f32>> {
type Output = Var<V2<Distance<Pixels>>>;
fn px(&self) -> Self::Output {
match self {
Self::Static (t) => Var::Static(V2(Distance::new(t.x),Distance::new(t.y))),
Self::Dynamic (t) => Var::Dynamic(t.clone())
}
}
}
impl PixelDistance for Var<V3<f32>> {
type Output = Var<V3<Distance<Pixels>>>;
fn px(&self) -> Self::Output {
match self {
Self::Static (t) => Var::Static(V3(Distance::new(t.x),Distance::new(t.y),Distance::new(t.z))),
Self::Dynamic (t) => Var::Dynamic(t.clone())
}
}
}
// =================
// === Operators ===
// =================

View File

@ -45,17 +45,17 @@ impl TextFieldMouseFrp {
let set_cursor_action = Self::set_cursor_lambda(text_field_ptr.clone());
let select_action = Self::select_lambda(text_field_ptr);
frp::new_network! { text_field
def is_inside = mouse.position.map(is_inside);
def click_in = mouse.press.gate(&is_inside);
def click_in_bool = click_in.constant(true);
def mouse_up_bool = mouse.release.constant(false);
def selecting = click_in_bool.merge(&mouse_up_bool);
def multicursor = keyboard.keyboard.key_mask.map(is_multicursor_mode);
def block_selection = keyboard.keyboard.key_mask.map(is_block_selection);
def click_in_pos = mouse.position.sample(&click_in);
def select_pos = mouse.position.gate(&selecting);
def set_cursor_action = click_in_pos.map2(&multicursor,set_cursor_action);
def select_action = select_pos.map2(&block_selection,select_action);
is_inside <- mouse.position.map(is_inside);
click_in <- mouse.press.gate(&is_inside);
click_in_bool <- click_in.constant(true);
mouse_up_bool <- mouse.release.constant(false);
selecting <- any (click_in_bool,mouse_up_bool);
multicursor <- keyboard.keyboard.key_mask.map(is_multicursor_mode);
block_selection <- keyboard.keyboard.key_mask.map(is_block_selection);
click_in_pos <- mouse.position.sample(&click_in);
select_pos <- mouse.position.gate(&selecting);
set_cursor_action <- click_in_pos.map2(&multicursor,set_cursor_action);
select_action <- select_pos.map2(&block_selection,select_action);
}
let network = text_field;
Self {mouse,network,click_in,selecting,multicursor,set_cursor_action,select_action}

View File

@ -208,12 +208,14 @@ impl<T> From<T> for Value
}
}
impl Semigroup for Value {
impl PartialSemigroup<&Value> for Value {
fn concat_mut(&mut self, other:&Self) {
*self = other.clone()
}
}
fn concat_mut_take(&mut self, other:Self) {
impl PartialSemigroup<Value> for Value {
fn concat_mut(&mut self, other:Self) {
*self = other
}
}

View File

@ -39,7 +39,7 @@ impl Theme {
}
}
impl Semigroup for Theme {
impl PartialSemigroup<&Theme> for Theme {
fn concat_mut(&mut self, other:&Self) {
self.tree.concat_mut(&other.tree);
}

View File

@ -6,6 +6,7 @@
use crate::prelude::*;
use crate::animation::physics::inertia;
use crate::animation::physics::inertia::DynSimulator;
use crate::display::object::traits::*;
use crate::display::scene::MouseTarget;
@ -42,7 +43,7 @@ impl ShapeViewEvents {
mouse_over <- source_();
mouse_out <- source_();
is_mouse_over <- [mouse_over,mouse_out].toggle();
is_mouse_over <- any (mouse_over,mouse_out).toggle();
out_on_drop <- on_drop.gate(&is_mouse_over);
eval_ out_on_drop (mouse_out.emit(()));
}
@ -210,3 +211,13 @@ pub fn animation2(network:&frp::Network) -> (DynSimulator<f32>, frp::Stream<f32>
let source = DynSimulator::<f32>::new(Box::new(f!((t) target.emit(t))));
(source,target.into())
}
/// Define a new animation FRP network.
pub fn animator<T:inertia::Value>(network:&frp::Network) -> (DynSimulator<T>, frp::Stream<T>) {
frp::extend! { network
def target = source::<T>();
}
let source = DynSimulator::<T>::new(Box::new(f!((t) target.emit(t))));
(source,target.into())
}

View File

@ -36,7 +36,12 @@ pub mod types {
pub use super::algebra::Matrix4x2;
pub use super::algebra::Matrix4x3;
pub use super::algebra::V2;
pub use super::algebra::V3;
pub use super::algebra::V4;
pub use super::algebra::Zero;
pub use super::algebra::zero;
pub use super::algebra::HasComponents;
pub use super::algebra::Dim1;
pub use super::algebra::Dim2;
@ -44,6 +49,4 @@ pub mod types {
pub use super::algebra::Abs;
pub use super::algebra::Magnitude;
pub use super::algebra::Normalize;
pub use super::algebra::Point3;
}

View File

@ -18,6 +18,13 @@ pub use nalgebra::Matrix4x2;
pub use nalgebra::Matrix4x3;
use nalgebra;
use nalgebra::Scalar;
use nalgebra::Matrix;
use nalgebra::ComplexField;
use nalgebra::Dim;
use nalgebra::storage::Storage;
use std::ops::AddAssign;
@ -31,6 +38,11 @@ pub trait Zero {
fn zero() -> Self;
}
/// Smart constructor for the `Zero` trait.
pub fn zero<T:Zero>() -> T {
<T as Zero>::zero()
}
// === Impls ===
@ -46,7 +58,7 @@ macro_rules! gen_zero {
macro_rules! gen_zero_nalgebra {
([$($ty:ident),*]) => {$(
impl<T:nalgebra::Scalar+num_traits::Zero> Zero for $ty<T> {
impl<T:Scalar+num_traits::Zero> Zero for $ty<T> {
fn zero() -> Self {
nalgebra::zero()
}
@ -155,18 +167,28 @@ impl Pow<f32> for f32 {
/// Types which have magnitude value.
#[allow(missing_docs)]
pub trait Magnitude {
fn magnitude(&self) -> f32;
type Output;
fn magnitude(&self) -> Self::Output;
}
// === Impls ===
impl Magnitude for f32 {
fn magnitude(&self) -> f32 {
type Output = f32;
fn magnitude(&self) -> Self::Output {
self.abs()
}
}
impl<N:ComplexField, R:Dim, C:Dim, S:Storage<N,R,C>> Magnitude for Matrix<N,R,C,S> {
type Output = N::RealField;
fn magnitude(&self) -> Self::Output {
self.norm()
}
}
// ==============
// === Signum ===
@ -275,6 +297,7 @@ impl Normalize for f32 {
}
// ===================
// === Square Root ===
// ===================
@ -394,61 +417,194 @@ impl Acos for f32 {
// =============
// === Point ===
// =============
/// A coordinate in space.
#[derive(Clone,Copy,Debug,Neg,Sub,Add,Div,AddAssign,From,Shrinkwrap)]
#[shrinkwrap(mutable)]
pub struct Point3 {
/// Underlying representation
pub matrix : Vector3<f32>
macro_rules! define_vector {
($name:ident {$($field:ident),*}) => {
/// A coordinate in space.
#[derive(Clone,Copy,Debug,Default)]
#[repr(C)]
pub struct $name<T=f32> {
$(
/// Vector component.
pub $field : T
),*
}
/// Smart constructor.
#[allow(non_snake_case)]
pub fn $name<T>($($field:T),*) -> $name<T> {
$name {$($field),*}
}
impl<T> $name<T> {
/// Constructor.
pub fn new($($field:T),*) -> Self {
Self {$($field),*}
}
/// Converts the struct to slice.
///
/// # Safety
/// The code is safe as the struct is implemented as `repr(C)`.
#[allow(unsafe_code)]
#[allow(trivial_casts)]
pub fn as_slice(&self) -> &[T] {
// Safe, because $name is defined as `#[repr(C)]`.
let ptr = self as *const $name<T> as *const T;
unsafe {
std::slice::from_raw_parts(ptr, std::mem::size_of::<T>())
}
}
}
impl Magnitude for $name {
type Output = f32;
fn magnitude(&self) -> Self::Output {
$(let $field = self.$field * self.$field;)*
let sum = 0.0 $(+$field)*;
sum.sqrt()
}
}
impl Normalize for $name {
fn normalize(&self) -> Self {
let magnitude = self.magnitude();
$(let $field = self.$field / magnitude;)*
Self {$($field),*}
}
}
impl AddAssign<f32> for $name {
fn add_assign(&mut self, rhs:f32) {
$(self.$field += rhs;)*
}
}
impl<T,S> AddAssign<$name<S>> for $name<T>
where T:AddAssign<S> {
fn add_assign(&mut self, rhs:$name<S>) {
$(self.$field += rhs.$field;)*
}
}
impl<T,S> Add<$name<S>> for $name<T>
where T:Add<S> {
type Output = $name<<T as Add<S>>::Output>;
fn add(self,rhs:$name<S>) -> Self::Output {
$(let $field = self.$field.add(rhs.$field);)*
$name {$($field),*}
}
}
impl<T,S> Sub<$name<S>> for $name<T>
where T:Sub<S> {
type Output = $name<<T as Sub<S>>::Output>;
fn sub(self,rhs:$name<S>) -> Self::Output {
$(let $field = self.$field.sub(rhs.$field);)*
$name {$($field),*}
}
}
impl<T> Neg for $name<T>
where T:Neg {
type Output = $name<<T as Neg>::Output>;
fn neg(self) -> Self::Output {
$(let $field = self.$field.neg();)*
$name {$($field),*}
}
}
impl Mul<f32> for $name {
type Output = $name;
fn mul(self, rhs:f32) -> Self::Output {
$(let $field = self.$field.mul(rhs);)*
Self {$($field),*}
}
}
impl Div<f32> for $name {
type Output = $name;
fn div(self, rhs:f32) -> Self::Output {
$(let $field = self.$field.div(rhs);)*
Self {$($field),*}
}
}
impl Mul<$name> for f32 {
type Output = $name;
fn mul(self, rhs:$name) -> Self::Output {
$(let $field = self.mul(rhs.$field);)*
$name {$($field),*}
}
}
};
}
impl Point3 {
/// Constructor.
pub fn new(x:f32, y:f32, z:f32) -> Self {
let matrix = Vector3::new(x,y,z);
Self {matrix}
define_vector! {V2 {x,y}}
define_vector! {V3 {x,y,z}}
define_vector! {V4 {x,y,z,w}}
impl<T:Scalar> From<Vector2<T>> for V2<T> {
fn from(t:Vector2<T>) -> Self {
V2(t.x,t.y)
}
}
impl Default for Point3 {
fn default() -> Self {
let matrix = nalgebra::zero();
Self {matrix}
impl<T:Scalar> Into<Vector2<T>> for V2<T> {
fn into(self) -> Vector2<T> {
Vector2::new(self.x,self.y)
}
}
impl Magnitude for Point3 {
fn magnitude(&self) -> f32 {
self.matrix.magnitude()
impl<T:Scalar> Into<Vector2<T>> for &V2<T> {
fn into(self) -> Vector2<T> {
Vector2::new(self.x,self.y)
}
}
impl Normalize for Point3 {
fn normalize(&self) -> Self {
Self {matrix:self.matrix.normalize()}
impl<T:Scalar> From<Vector3<T>> for V3<T> {
fn from(t:Vector3<T>) -> Self {
V3(t.x,t.y,t.z)
}
}
impl Mul<f32> for Point3 {
type Output = Point3;
fn mul(self, rhs:f32) -> Self::Output {
let matrix = self.matrix * rhs;
Self {matrix}
impl<T:Scalar> Into<Vector3<T>> for V3<T> {
fn into(self) -> Vector3<T> {
Vector3::new(self.x,self.y,self.z)
}
}
impl Into<Vector3<f32>> for Point3 {
fn into(self) -> Vector3<f32> {
self.matrix
impl<T:Scalar> Into<Vector3<T>> for &V3<T> {
fn into(self) -> Vector3<T> {
Vector3::new(self.x,self.y,self.z)
}
}
impl<T:Scalar> From<Vector4<T>> for V4<T> {
fn from(t:Vector4<T>) -> Self {
V4(t.x,t.y,t.z,t.w)
}
}
impl<T:Scalar> Into<Vector4<T>> for V4<T> {
fn into(self) -> Vector4<T> {
Vector4::new(self.x,self.y,self.z,self.w)
}
}
impl<T:Scalar> Into<Vector4<T>> for &V4<T> {
fn into(self) -> Vector4<T> {
Vector4::new(self.x,self.y,self.z,self.w)
}
}
// ============================
// === Algebraic Structures ===
// ============================

View File

@ -183,23 +183,34 @@ pub type Distance<Type=Anything,Repr=f32> = Unit<quantity::Distance,Type,Repr>;
pub struct Pixels;
/// Provides a `px` method to every unit that can be converted to a pixel distance.
#[allow(missing_docs)]
pub trait PixelDistance {
type Output;
/// Distance in pixels.
fn px(&self) -> Distance<Pixels>;
fn px(&self) -> Self::Output;
}
impl PixelDistance for f32 {
fn px(&self) -> Distance<Pixels> {
type Output = Distance<Pixels>;
fn px(&self) -> Self::Output {
Distance::new(*self)
}
}
impl PixelDistance for i32 {
fn px(&self) -> Distance<Pixels> {
type Output = Distance<Pixels>;
fn px(&self) -> Self::Output {
Distance::new(*self as f32)
}
}
impl PixelDistance for V2<f32> {
type Output = V2<Distance<Pixels>>;
fn px(&self) -> Self::Output {
V2(Distance::new(self.x),Distance::new(self.y))
}
}
impls! { From + &From <Distance<Pixels>> for Glsl { |t| { t.value.into() } }}
impls! { From<PhantomData<Distance<Pixels>>> for glsl::PrimType {

View File

@ -180,6 +180,110 @@ impl Storable for f32 {
fn slice_to_items_mut (buffer: &mut [Self]) -> &mut [Self::Cell] { buffer }
}
impl<T:Storable<Cell=T>> Storable for V2<T>
where Self:GpuDefault + GpuKnownSize + PhantomInto<glsl::PrimType>, T:ItemBounds {
type Cell = T;
type Rows = U2;
type Cols = U1;
#[allow(unsafe_code)]
fn slice_from_items(buffer: &[Self::Cell]) -> &[Self] {
// This code casts slice to vector. It is safe because vector was defined as `#[repr(C)]`.
let len = buffer.len() / Self::item_count();
unsafe { std::slice::from_raw_parts(buffer.as_ptr().cast(), len) }
}
#[allow(unsafe_code)]
fn slice_from_items_mut(buffer: &mut [Self::Cell]) -> &mut [Self] {
// This code casts slice to vector. It is safe because vector was defined as `#[repr(C)]`.
let len = buffer.len() / Self::item_count();
unsafe { std::slice::from_raw_parts_mut(buffer.as_mut_ptr().cast(), len) }
}
#[allow(unsafe_code)]
fn slice_to_items(buffer: &[Self]) -> &[Self::Cell] {
// This code casts slice to vector. It is safe because vector was defined as `#[repr(C)]`.
let len = buffer.len() * Self::item_count();
unsafe { std::slice::from_raw_parts(buffer.as_ptr().cast(), len) }
}
#[allow(unsafe_code)]
fn slice_to_items_mut(buffer: &mut [Self]) -> &mut [Self::Cell] {
// This code casts slice to vector. It is safe because vector was defined as `#[repr(C)]`.
let len = buffer.len() * Self::item_count();
unsafe { std::slice::from_raw_parts_mut(buffer.as_mut_ptr().cast(), len) }
}
}
impl<T:Storable<Cell=T>> Storable for V3<T>
where Self:GpuDefault + GpuKnownSize + PhantomInto<glsl::PrimType>, T:ItemBounds {
type Cell = T;
type Rows = U2;
type Cols = U1;
#[allow(unsafe_code)]
fn slice_from_items(buffer: &[Self::Cell]) -> &[Self] {
// This code casts slice to vector. It is safe because vector was defined as `#[repr(C)]`.
let len = buffer.len() / Self::item_count();
unsafe { std::slice::from_raw_parts(buffer.as_ptr().cast(), len) }
}
#[allow(unsafe_code)]
fn slice_from_items_mut(buffer: &mut [Self::Cell]) -> &mut [Self] {
// This code casts slice to vector. It is safe because vector was defined as `#[repr(C)]`.
let len = buffer.len() / Self::item_count();
unsafe { std::slice::from_raw_parts_mut(buffer.as_mut_ptr().cast(), len) }
}
#[allow(unsafe_code)]
fn slice_to_items(buffer: &[Self]) -> &[Self::Cell] {
// This code casts slice to vector. It is safe because vector was defined as `#[repr(C)]`.
let len = buffer.len() * Self::item_count();
unsafe { std::slice::from_raw_parts(buffer.as_ptr().cast(), len) }
}
#[allow(unsafe_code)]
fn slice_to_items_mut(buffer: &mut [Self]) -> &mut [Self::Cell] {
// This code casts slice to vector. It is safe because vector was defined as `#[repr(C)]`.
let len = buffer.len() * Self::item_count();
unsafe { std::slice::from_raw_parts_mut(buffer.as_mut_ptr().cast(), len) }
}
}
impl<T:Storable<Cell=T>> Storable for V4<T>
where Self:GpuDefault + GpuKnownSize + PhantomInto<glsl::PrimType>, T:ItemBounds {
type Cell = T;
type Rows = U2;
type Cols = U1;
#[allow(unsafe_code)]
fn slice_from_items(buffer: &[Self::Cell]) -> &[Self] {
// This code casts slice to vector. It is safe because vector was defined as `#[repr(C)]`.
let len = buffer.len() / Self::item_count();
unsafe { std::slice::from_raw_parts(buffer.as_ptr().cast(), len) }
}
#[allow(unsafe_code)]
fn slice_from_items_mut(buffer: &mut [Self::Cell]) -> &mut [Self] {
// This code casts slice to vector. It is safe because vector was defined as `#[repr(C)]`.
let len = buffer.len() / Self::item_count();
unsafe { std::slice::from_raw_parts_mut(buffer.as_mut_ptr().cast(), len) }
}
#[allow(unsafe_code)]
fn slice_to_items(buffer: &[Self]) -> &[Self::Cell] {
// This code casts slice to vector. It is safe because vector was defined as `#[repr(C)]`.
let len = buffer.len() * Self::item_count();
unsafe { std::slice::from_raw_parts(buffer.as_ptr().cast(), len) }
}
#[allow(unsafe_code)]
fn slice_to_items_mut(buffer: &mut [Self]) -> &mut [Self::Cell] {
// This code casts slice to vector. It is safe because vector was defined as `#[repr(C)]`.
let len = buffer.len() * Self::item_count();
unsafe { std::slice::from_raw_parts_mut(buffer.as_mut_ptr().cast(), len) }
}
}
impl<T:Storable<Cell=T>,R,C> Storable for MatrixMN<T,R,C>
where T:ItemBounds, Self:MatrixCtx<T,R,C>,
@ -292,6 +396,36 @@ impl JsBufferView for [u8] {
}
}
#[allow(unsafe_code)]
impl<T> JsBufferView for [V2<T>]
where T : Storable<Cell=T> + ItemBounds,
V2<T> : Storable,
[Cell<V2<T>>] : JsBufferView {
unsafe fn js_buffer_view(&self) -> js_sys::Object {
<V2<T> as Storable>::slice_to_items(self).js_buffer_view()
}
}
#[allow(unsafe_code)]
impl<T> JsBufferView for [V3<T>]
where T : Storable<Cell=T> + ItemBounds,
V3<T> : Storable,
[Cell<V3<T>>] : JsBufferView {
unsafe fn js_buffer_view(&self) -> js_sys::Object {
<V3<T> as Storable>::slice_to_items(self).js_buffer_view()
}
}
#[allow(unsafe_code)]
impl<T> JsBufferView for [V4<T>]
where T : Storable<Cell=T> + ItemBounds,
V4<T> : Storable,
[Cell<V4<T>>] : JsBufferView {
unsafe fn js_buffer_view(&self) -> js_sys::Object {
<V4<T> as Storable>::slice_to_items(self).js_buffer_view()
}
}
#[allow(unsafe_code)]
impl<T: Storable<Cell=T>,R,C> JsBufferView for [MatrixMN<T,R,C>]
where Self : MatrixCtx<T,R,C>,

View File

@ -1,6 +1,7 @@
//! Defines abstraction for data types that have a default value when used as GPU values.
use nalgebra::*;
use crate::math::types::*;
@ -39,6 +40,10 @@ define_gpu_defaults! {
f32 = 0.0,
bool = false,
V2<f32> = V2::new(0.0,0.0),
V3<f32> = V3::new(0.0,0.0,0.0),
V4<f32> = V4::new(0.0,0.0,0.0,0.0),
Vector2<f32> = Vector2::new(0.0,0.0),
Vector3<f32> = Vector3::new(0.0,0.0,0.0),
Vector4<f32> = Vector4::new(0.0,0.0,0.0,1.0),

View File

@ -68,6 +68,7 @@ macro_rules! with_all_prim_types {
[Matrix2x3 f32] [Matrix2x4 f32]
[Matrix3x2 f32] [Matrix3x4 f32]
[Matrix4x2 f32] [Matrix4x3 f32]
[V2 f32] [V3 f32] [V4 f32]
]
}
}

View File

@ -1,6 +1,7 @@
//! This module implements type-level utils for checking the size of values for a given type.
use nalgebra::*;
use crate::math::types::*;
use crate::system::gpu::data::buffer::item::MatrixCtx;
@ -37,6 +38,21 @@ impl GpuKnownSize for i32 { type GpuByteSize = U4; }
impl GpuKnownSize for u32 { type GpuByteSize = U4; }
impl GpuKnownSize for f32 { type GpuByteSize = U4; }
impl<T> GpuKnownSize for V2<T>
where T:GpuKnownSize, U2:DimMul<GpuByteSize<T>>, Mul<U2,GpuByteSize<T>>:DimName {
type GpuByteSize = Mul<U2,GpuByteSize<T>>;
}
impl<T> GpuKnownSize for V3<T>
where T:GpuKnownSize, U3:DimMul<GpuByteSize<T>>, Mul<U3,GpuByteSize<T>>:DimName {
type GpuByteSize = Mul<U3,GpuByteSize<T>>;
}
impl<T> GpuKnownSize for V4<T>
where T:GpuKnownSize, U4:DimMul<GpuByteSize<T>>, Mul<U4,GpuByteSize<T>>:DimName {
type GpuByteSize = Mul<U4,GpuByteSize<T>>;
}
type Mul<A,B> = <A as DimMul<B>>::Output;
impl<T:GpuKnownSize,R:DimName,C:DimName> GpuKnownSize for MatrixMN<T,R,C>
where Self:MatrixCtx<T,R,C>,

View File

@ -6,6 +6,7 @@ use web_sys::WebGlUniformLocation;
use crate::system::gpu::Context;
use crate::system::gpu::data::prim::*;
use crate::math::types::*;
@ -43,6 +44,9 @@ impl UniformUpload for f32 {
}
}
// === Vector ===
impl UniformUpload for Vector2<f32> {
fn upload_uniform(&self, context:&Context, location:&WebGlUniformLocation) {
context.uniform2fv_with_f32_array(Some(location),self.data.as_slice());
@ -118,6 +122,87 @@ impl UniformUpload for Vector4<bool> {
}
}
// === V ===
impl UniformUpload for V2<f32> {
fn upload_uniform(&self, context:&Context, location:&WebGlUniformLocation) {
context.uniform2fv_with_f32_array(Some(location),self.as_slice());
}
}
impl UniformUpload for V3<f32> {
fn upload_uniform(&self, context:&Context, location:&WebGlUniformLocation) {
context.uniform3fv_with_f32_array(Some(location),self.as_slice());
}
}
impl UniformUpload for V4<f32> {
fn upload_uniform(&self, context:&Context, location:&WebGlUniformLocation) {
context.uniform4fv_with_f32_array(Some(location),self.as_slice());
}
}
impl UniformUpload for V2<i32> {
fn upload_uniform(&self, context:&Context, location:&WebGlUniformLocation) {
context.uniform2iv_with_i32_array(Some(location),self.as_slice());
}
}
impl UniformUpload for V3<i32> {
fn upload_uniform(&self, context:&Context, location:&WebGlUniformLocation) {
context.uniform3iv_with_i32_array(Some(location),self.as_slice());
}
}
impl UniformUpload for V4<i32> {
fn upload_uniform(&self, context:&Context, location:&WebGlUniformLocation) {
context.uniform4iv_with_i32_array(Some(location),self.as_slice());
}
}
impl UniformUpload for V2<u32> {
fn upload_uniform(&self, context:&Context, location:&WebGlUniformLocation) {
context.uniform2uiv_with_u32_array(Some(location),self.as_slice());
}
}
impl UniformUpload for V3<u32> {
fn upload_uniform(&self, context:&Context, location:&WebGlUniformLocation) {
context.uniform3uiv_with_u32_array(Some(location),self.as_slice());
}
}
impl UniformUpload for V4<u32> {
fn upload_uniform(&self, context:&Context, location:&WebGlUniformLocation) {
context.uniform4uiv_with_u32_array(Some(location),self.as_slice());
}
}
impl UniformUpload for V2<bool> {
fn upload_uniform(&self, context:&Context, location:&WebGlUniformLocation) {
let v:Vec<i32> = self.as_slice().iter().cloned().map(|t| if t {1} else {0}).collect();
context.uniform2iv_with_i32_array(Some(location),&v);
}
}
impl UniformUpload for V3<bool> {
fn upload_uniform(&self, context:&Context, location:&WebGlUniformLocation) {
let v:Vec<i32> = self.as_slice().iter().cloned().map(|t| if t {1} else {0}).collect();
context.uniform3iv_with_i32_array(Some(location),&v);
}
}
impl UniformUpload for V4<bool> {
fn upload_uniform(&self, context:&Context, location:&WebGlUniformLocation) {
let v:Vec<i32> = self.as_slice().iter().cloned().map(|t| if t {1} else {0}).collect();
context.uniform4iv_with_i32_array(Some(location),&v);
}
}
// === Matrix ===
impl UniformUpload for Matrix2<f32> {
fn upload_uniform(&self, context:&Context, location:&WebGlUniformLocation) {
context.uniform_matrix2fv_with_f32_array(Some(location),false,self.data.as_slice());

View File

@ -105,6 +105,51 @@ where [ T1:Into<Glsl>, T2:Into<Glsl>, T3:Into<Glsl>, T4:Into<Glsl> ] { |t| {
}}}
// === From Vectors to Glsl ===
impls! {[T:Into<Glsl>] From <V2<T>> for Glsl { |t| {
let x = t.x.into();
let y = t.y.into();
iformat!("vec2({x},{y})").into()
}}}
impls! {[T:Into<Glsl>] From <V3<T>> for Glsl { |t| {
let x = t.x.into();
let y = t.y.into();
let z = t.z.into();
iformat!("vec2({x},{y},{z})").into()
}}}
impls! {[T:Into<Glsl>] From <V4<T>> for Glsl { |t| {
let x = t.x.into();
let y = t.y.into();
let z = t.z.into();
let w = t.w.into();
iformat!("vec2({x},{y},{z},{w})").into()
}}}
impls! {[T:RefInto<Glsl>] From <&V2<T>> for Glsl { |t| {
let x = t.x.glsl();
let y = t.y.glsl();
iformat!("vec2({x},{y})").into()
}}}
impls! {[T:RefInto<Glsl>] From <&V3<T>> for Glsl { |t| {
let x = t.x.glsl();
let y = t.y.glsl();
let z = t.z.glsl();
iformat!("vec2({x},{y},{z})").into()
}}}
impls! {[T:RefInto<Glsl>] From <&V4<T>> for Glsl { |t| {
let x = t.x.glsl();
let y = t.y.glsl();
let z = t.z.glsl();
let w = t.w.glsl();
iformat!("vec2({x},{y},{z},{w})").into()
}}}
// === From Prim Types to Glsl ===
impls! { From + &From <bool> for Glsl { |t| t.to_string().into() } }
@ -857,6 +902,10 @@ define_glsl_prim_type_conversions! {
u32 => UInt,
f32 => Float,
V2<f32> => Vec2,
V3<f32> => Vec3,
V4<f32> => Vec4,
Vector2<f32> => Vec2,
Vector3<f32> => Vec3,
Vector4<f32> => Vec4,

View File

@ -74,13 +74,13 @@ impl<Parameter:frp::Data> FencedAction<Parameter> {
/// Wrap the `action` in `FencedAction`.
fn fence(network:&frp::Network, action:impl Fn(&Parameter) + 'static) -> Self {
frp::extend! { network
def trigger = source::<Parameter>();
def triggered = trigger.constant(());
def switch = gather();
switch.attach(&triggered);
def performed = trigger.map(move |param| action(param));
switch.attach(&performed);
def is_running = switch.toggle();
trigger <- source::<Parameter>();
triggered <- trigger.constant(());
switch <- any(...);
switch <+ triggered;
performed <- trigger.map(move |param| action(param));
switch <+ performed;
is_running <- switch.toggle();
}
Self {trigger,is_running}
}
@ -158,7 +158,7 @@ impl GraphEditorIntegratedWithController {
// Changes in Graph Editor
let is_handling_notification = handle_notification.is_running;
def is_hold = is_handling_notification.zip_with(&invalidate.is_running, |l,r| *l || *r);
def is_hold = is_handling_notification.all_with(&invalidate.is_running, |l,r| *l || *r);
def _action = editor_outs.node_removed .map2(&is_hold,node_removed);
def _action = editor_outs.connection_added .map2(&is_hold,connection_created);
def _action = editor_outs.connection_removed.map2(&is_hold,connection_removed);

View File

@ -246,13 +246,23 @@ where K:Eq+Hash {
// === Impls ===
impl<K,V,S> Semigroup for HashMapTree<K,V,S>
impl<K,V,S> PartialSemigroup<HashMapTree<K,V,S>> for HashMapTree<K,V,S>
where K : Eq + Hash + Clone,
V : Semigroup,
S : BuildHasher + Clone {
fn concat_mut(&mut self, other:Self) {
self.value.concat_mut(&other.value);
PartialSemigroup::concat_mut(&mut self.branches, other.branches);
}
}
impl<K,V,S> PartialSemigroup<&HashMapTree<K,V,S>> for HashMapTree<K,V,S>
where K : Eq + Hash + Clone,
V : Semigroup,
S : BuildHasher + Clone {
fn concat_mut(&mut self, other:&Self) {
self.value.concat_mut(&other.value);
self.branches.concat_mut(&other.branches);
PartialSemigroup::concat_mut(&mut self.branches, &other.branches);
}
}

View File

@ -9,7 +9,7 @@ use ensogl::prelude::*;
use ensogl::display::navigation::navigator::Navigator;
use ensogl::system::web;
use ensogl::application::Application;
use graph_editor::{GraphEditor, EdgeTarget};
use graph_editor::GraphEditor;
use wasm_bindgen::prelude::*;
use ensogl::display::object::ObjectOps;
use ensogl_core_msdf_sys::run_once_initialized;
@ -33,13 +33,13 @@ pub fn run_example_shapes() {
}
fn fence<T,Out>(network:&frp::Network, trigger:T) -> (frp::Stream,frp::Stream<bool>)
fn _fence<T,Out>(network:&frp::Network, trigger:T) -> (frp::Stream,frp::Stream<bool>)
where T:frp::HasOutput<Output=Out>, T:Into<frp::Stream<Out>>, Out:frp::Data {
let trigger = trigger.into();
frp::extend! { network
def trigger_ = trigger.constant(());
def runner = source::<()>();
def switch = gather();
def switch = any_mut();
switch.attach(&trigger_);
def triggered = trigger.map(f_!(runner.emit(())));
switch.attach(&triggered);
@ -49,16 +49,6 @@ where T:frp::HasOutput<Output=Out>, T:Into<frp::Stream<Out>>, Out:frp::Data {
(runner,condition)
}
//fn fenced_gate<F,T,X>(network:&frp::Network, target:&frp::Stream<X>, f:F) -> frp::Stream<X>
//where F:'static+Fn()->T, X:frp::Data {
// let target = target.clone_ref();
// frp::extend! { network
// let (trigger,runner,condition) = fence(&network);
// def _eval = runner.map(move |_| {f();});
// def out = target.gate(&condition);
// }
// out
//}
fn init(app:&Application) {
@ -67,7 +57,6 @@ fn init(app:&Application) {
dark.insert("graph_editor.node.background.color", color::Lcha::new(0.2,0.013,0.18,1.0));
dark.insert("graph_editor.node.selection.color", color::Lcha::new(0.72,0.5,0.22,1.0));
dark.insert("graph_editor.node.selection.size", 7.0);
// dark.insert("graph_editor.node.selection.color", color::Lcha::new(0.7,0.59,0.18,1.0));
dark.insert("animation.duration", 0.5);
dark.insert("graph.node.shadow.color", 5.0);
dark.insert("graph.node.shadow.size", 5.0);
@ -78,39 +67,7 @@ fn init(app:&Application) {
let _bg = app.display.scene().style_sheet.var("application.background.color");
// println!("{:?}",bg.value());
// println!("{:?}",app.display.scene().style_sheet.debug_sheet_nodes_count());
// let t1 : color::Hsla = color::Hsla::new(0.0,0.0,0.03,1.0);
// let t2 : color::Lcha = t1.into();
// let t4 : color::Rgba = color::Rgba::from(t2);
// println!("{:?}", t2);
// println!("{:?}", color::Rgba::from(t1));
// println!("{:?}", t4);
// println!("{:?}", color::Hsla::from(color::LinearRgba::new(0.2,0.3,0.4,1.0)));
//
// let x = color::Hsla::from(color::Rgba::new(0.031,0.031,0.031,1.0));
// let y = color::Rgba::from(x);
// println!("{:?}", y);
// let xyz = color::Xyz::from(color::Rgb::new(0.2,0.4,0.6));
// let lab = color::Lab::from(color::Rgb::new(0.2,0.4,0.6));
// let lch = color::Lch::from(color::Rgb::new(0.2,0.4,0.6));
// let lch = color::Lch::from(color::Rgb::new(1.0,0.0,0.0));
// println!("{:?}", xyz);
// println!("{:?}", lab);
// println!("{:?}", lch);
// println!("-----------");
// println!("{:?}", color::Rgb::from(xyz));
// println!("{:?}", color::Rgb::from(lab));
// println!("{:?}", color::Rgb::from(lch));
// println!("{:?}", color::Lab::from(color::Xyz::new(0.1,0.2,0.3)));
// println!("{:?}", palette::Xyz::from(palette::Srgb::new(0.2,0.4,0.6)));
// println!("{:?}", palette::Lab::from(palette::LinSrgb::new(0.2,0.4,0.6)));
// println!("{:?}", palette::Lab::from(palette::Xyz::new(0.1,0.2,0.3)));
// color::test();
let world = &app.display;
let scene = world.scene();
@ -124,27 +81,27 @@ fn init(app:&Application) {
let node1_id = graph_editor.add_node();
let node2_id = graph_editor.add_node();
//
graph_editor.frp.set_node_position.emit((node1_id,Position::new(100.0 , 250.0)));
graph_editor.frp.set_node_position.emit((node2_id,Position::new(200.0 , 50.0)));
//
graph_editor.frp.set_node_expression.emit((node1_id,expression_mock()));
graph_editor.frp.set_node_expression.emit((node2_id,expression_mock2()));
frp::new_network! { network
def trigger = source::<()>();
let (runner,condition) = fence(&network,&trigger);
def _eval = runner.map(f_!( {
graph_editor.frp.connect_nodes.emit((EdgeTarget::new(node1_id,default()),EdgeTarget::new(node2_id,vec![1,0,2])));
}));
def _debug = graph_editor.frp.outputs.edge_added.map2(&condition, |id,cond| {
let owner = if *cond { "GUI" } else { "ME" };
println!("Edge {:?} added by {}!",id,owner)
});
// frp::new_network! { network
// def trigger = source::<()>();
// let (runner,condition) = fence(&network,&trigger);
// def _eval = runner.map(f_!( {
// graph_editor.frp.connect_nodes.emit((EdgeTarget::new(node1_id,default()),EdgeTarget::new(node2_id,vec![1,0,2])));
// }));
// def _debug = graph_editor.frp.outputs.edge_added.map2(&condition, |id,cond| {
// let owner = if *cond { "GUI" } else { "ME" };
// println!("Edge {:?} added by {}!",id,owner)
// });
//
// }
}
trigger.emit(());
// trigger.emit(());
let mut was_rendered = false;
@ -152,7 +109,7 @@ fn init(app:&Application) {
world.on_frame(move |_| {
let _keep_alive = &navigator;
let _keep_alive = &graph_editor;
let _keep_alive = &network;
// let _keep_alive = &network;
// Temporary code removing the web-loader instance.
// To be changed in the future.
@ -181,8 +138,6 @@ use graph_editor::component::node::port::Expression;
pub fn expression_mock() -> Expression {
let pattern_crumb = vec![Seq{right:false },Or,Or,Build];
let _val = ast::crumbs::SegmentMatchCrumb::Body{val:pattern_crumb};
let code = "open \"data.csv\"".into();
let output_span_tree = default();
let input_span_tree = span_tree::builder::TreeBuilder::new(15)

View File

@ -153,9 +153,9 @@ impl Default for Keyboard {
change_set <- on_pressed . map(KeyMaskChange::on_pressed);
change_unset <- on_released . map(KeyMaskChange::on_released);
change_clear <- on_defocus . map(|_| KeyMaskChange::on_defocus());
change_set_unset <- [change_set, change_unset];
change <- [change_set_unset, change_clear];
prev_key_mask <- gather::<KeyMask>();
change_set_unset <- any (change_set,change_unset);
change <- any (change_set_unset,change_clear);
prev_key_mask <- any_mut::<KeyMask>();
key_mask <- change.map2(&prev_key_mask,KeyMaskChange::updated_mask);
prev_key_mask <+ key_mask;
previous_key_mask <- key_mask.previous();

View File

@ -71,18 +71,18 @@ pub struct Mouse {
impl Default for Mouse {
fn default() -> Self {
frp::new_network! { mouse
def release = source_();
def press = source_();
def wheel = source_();
def leave = source_();
def position = source();
def down_const = press.constant(true);
def up_const = release.constant(false);
def down = down_const.merge(&up_const);
def up = down.map(|t| !t);
def prev_position = position.previous();
def translation = position.map2(&prev_position,|t,s| t - s);
def distance = translation.map(|t:&Position| t.length());
release <- source_();
press <- source_();
wheel <- source_();
leave <- source_();
position <- source();
down_const <- press.constant(true);
up_const <- release.constant(false);
down <- any (down_const,up_const);
up <- down.map(|t| !t);
prev_position <- position.previous();
translation <- position.map2(&prev_position,|t,s| t - s);
distance <- translation.map(|t:&Position| t.length());
};
let network = mouse;
Self {network,release,press,leave,wheel,down,up,position,prev_position,translation,distance}

View File

@ -71,21 +71,21 @@
///
/// - Stream merge.
/// ```compile_fail
/// all_nodes <- [selected_nodes,non_selected_nodes];
/// all_nodes <- any (selected_nodes,non_selected_nodes);
/// ```
/// Desugars to:
/// ```compile_fail
/// def all_nodes = merge2(&selected_nodes,non_selected_nodes);
/// def all_nodes = any2 (&selected_nodes,&non_selected_nodes);
/// ```
///
///
/// - Stream merge dropping input values.
/// ```compile_fail
/// all_nodes <-_ [selected_nodes,non_selected_nodes];
/// all_nodes <- any_ (selected_nodes,non_selected_nodes);
/// ```
/// Desugars to:
/// ```compile_fail
/// def all_nodes = merge2_(&selected_nodes,non_selected_nodes);
/// def all_nodes = any2_ (&selected_nodes,&non_selected_nodes);
/// ```
///
///
@ -218,15 +218,37 @@ macro_rules! extend_line2 {
([$($lines:tt)*] $net:ident def $name:ident $(:$ty:ty)? = $tgt1:ident . $tgt2:ident . $tgt3:ident . $tgt4:ident . $base:ident$(::<$param:ty>)?($($arg:tt)*) $($ts:tt)*) => { $crate::extend_line2! { [$($lines)* let $name $(:$ty)? = $net.$base$(::<$param>)?(concat!(stringify!($net),".",stringify!($name)),&$tgt1.$tgt2.$tgt3.$tgt4,$($arg)*) ;] $net def $name = $name $($ts)* } };
([$($lines:tt)*] $net:ident def $name:ident $(:$ty:ty)? = $tgt1:ident . $tgt2:ident . $tgt3:ident . $tgt4:ident . $tgt5:ident . $base:ident$(::<$param:ty>)?($($arg:tt)*) $($ts:tt)*) => { $crate::extend_line2! { [$($lines)* let $name $(:$ty)? = $net.$base$(::<$param>)?(concat!(stringify!($net),".",stringify!($name)),&$tgt1.$tgt2.$tgt3.$tgt4.$tgt5,$($arg)*) ;] $net def $name = $name $($ts)* } };
([] $net:ident $name:ident <- [ $($arg1:ident).+ ] ) => { let $name = $($arg1).+.clone_ref(); };
([] $net:ident $name:ident <- [ $($arg1:ident).+ , $($arg2:ident).+ ] $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = merge2(&$($arg1).+,&$($arg2).+) $($ts)* } };
([] $net:ident $name:ident <- [ $($arg1:ident).+ , $($arg2:ident).+ , $($arg3:ident).+ ] $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = merge3(&$($arg1).+,&$($arg2).+,&$($arg3).+) $($ts)* } };
([] $net:ident $name:ident <- [ $($arg1:ident).+ , $($arg2:ident).+ , $($arg3:ident).+ , $($arg4:ident).+ ] $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = merge4(&$($arg1).+,&$($arg2).+,&$($arg3).+,&$($arg4).+) $($ts)* } };
([] $net:ident $name:ident <- any (...) $($ts:tt)* ) => {$crate::extend_line2! { [] $net $name <- any_mut() $($ts)* } };
([] $net:ident $name:ident <- any ( $($arg1:ident).+ ) ) => { let $name = $($arg1).+.clone_ref(); };
([] $net:ident $name:ident <- any ( $($arg1:ident).+ , $($arg2:ident).+ ) $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = any2(&$($arg1).+,&$($arg2).+) $($ts)* } };
([] $net:ident $name:ident <- any ( $($arg1:ident).+ , $($arg2:ident).+ , $($arg3:ident).+ ) $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = any3(&$($arg1).+,&$($arg2).+,&$($arg3).+) $($ts)* } };
([] $net:ident $name:ident <- any ( $($arg1:ident).+ , $($arg2:ident).+ , $($arg3:ident).+ , $($arg4:ident).+ ) $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = any4(&$($arg1).+,&$($arg2).+,&$($arg3).+,&$($arg4).+) $($ts)* } };
([] $net:ident $name:ident <- any_ (...) $($ts:tt)* ) => {$crate::extend_line2! { [] $net $name <- any_mut_() $($ts)* } };
([] $net:ident $name:ident <- any_ ( $($arg1:ident).+ ) ) => { let $name = $($arg1).+.constant(()); };
([] $net:ident $name:ident <- any_ ( $($arg1:ident).+ , $($arg2:ident).+ ) $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = any2_(&$($arg1).+,&$($arg2).+) $($ts)* } };
([] $net:ident $name:ident <- any_ ( $($arg1:ident).+ , $($arg2:ident).+ , $($arg3:ident).+ ) $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = any3_(&$($arg1).+,&$($arg2).+,&$($arg3).+) $($ts)* } };
([] $net:ident $name:ident <- any_ ( $($arg1:ident).+ , $($arg2:ident).+ , $($arg3:ident).+ , $($arg4:ident).+ ) $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = any4_(&$($arg1).+,&$($arg2).+,&$($arg3).+,&$($arg4).+) $($ts)* } };
([] $net:ident $name:ident <- all (...) $($ts:tt)* ) => {$crate::extend_line2! { [] $net $name <- all_mut() $($ts)* } };
([] $net:ident $name:ident <- all ( $($arg1:ident).+ ) ) => { let $name = $($arg1).+.clone_ref(); };
([] $net:ident $name:ident <- all ( $($arg1:ident).+ , $($arg2:ident).+ ) $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = all2(&$($arg1).+,&$($arg2).+) $($ts)* } };
([] $net:ident $name:ident <- all ( $($arg1:ident).+ , $($arg2:ident).+ , $($arg3:ident).+ ) $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = all3(&$($arg1).+,&$($arg2).+,&$($arg3).+) $($ts)* } };
([] $net:ident $name:ident <- all ( $($arg1:ident).+ , $($arg2:ident).+ , $($arg3:ident).+ , $($arg4:ident).+ ) $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = all4(&$($arg1).+,&$($arg2).+,&$($arg3).+,&$($arg4).+) $($ts)* } };
([] $net:ident $name:ident <- all [...] $($ts:tt)* ) => {$crate::extend_line2! { [] $net $name <- all_mut() $($ts)* } };
([] $net:ident $name:ident <- all [ $($arg1:ident).+ ] ) => { let $name = $($arg1).+.clone_ref(); };
([] $net:ident $name:ident <- all [ $($arg1:ident).+ , $($arg2:ident).+ ] $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = all_vec2(&$($arg1).+,&$($arg2).+) $($ts)* } };
([] $net:ident $name:ident <- all [ $($arg1:ident).+ , $($arg2:ident).+ , $($arg3:ident).+ ] $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = all_vec3(&$($arg1).+,&$($arg2).+,&$($arg3).+) $($ts)* } };
([] $net:ident $name:ident <- all [ $($arg1:ident).+ , $($arg2:ident).+ , $($arg3:ident).+ , $($arg4:ident).+ ] $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = all_vec4(&$($arg1).+,&$($arg2).+,&$($arg3).+,&$($arg4).+) $($ts)* } };
([] $net:ident $name:ident <- all_ (...) $($ts:tt)* ) => {$crate::extend_line2! { [] $net $name <- all_mut_() $($ts)* } };
([] $net:ident $name:ident <- all_ ( $($arg1:ident).+ ) ) => { let $name = $($arg1).+.constant(()); };
([] $net:ident $name:ident <- all_ ( $($arg1:ident).+ , $($arg2:ident).+ ) $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = all2_(&$($arg1).+,&$($arg2).+) $($ts)* } };
([] $net:ident $name:ident <- all_ ( $($arg1:ident).+ , $($arg2:ident).+ , $($arg3:ident).+ ) $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = all3_(&$($arg1).+,&$($arg2).+,&$($arg3).+) $($ts)* } };
([] $net:ident $name:ident <- all_ ( $($arg1:ident).+ , $($arg2:ident).+ , $($arg3:ident).+ , $($arg4:ident).+ ) $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = all4_(&$($arg1).+,&$($arg2).+,&$($arg3).+,&$($arg4).+) $($ts)* } };
([] $net:ident $name:ident <-_ [ $($arg1:ident).+ ] ) => { let $name = $($arg1).+.constant(()); };
([] $net:ident $name:ident <-_ [ $($arg1:ident).+ , $($arg2:ident).+ ] $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = merge2_(&$($arg1).+,&$($arg2).+) $($ts)* } };
([] $net:ident $name:ident <-_ [ $($arg1:ident).+ , $($arg2:ident).+ , $($arg3:ident).+ ] $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = merge3_(&$($arg1).+,&$($arg2).+,&$($arg3).+) $($ts)* } };
([] $net:ident $name:ident <-_ [ $($arg1:ident).+ , $($arg2:ident).+ , $($arg3:ident).+ , $($arg4:ident).+ ] $($ts:tt)* ) => {$crate::extend_line2! { [] $net def $name = merge4_(&$($arg1).+,&$($arg2).+,&$($arg3).+,&$($arg4).+) $($ts)* } };
([] $net:ident $name:ident <= $($toks:tt)*) => {$crate::extend_line2! { [] $net def $name = $($toks)* . iter()} };
([] $net:ident $name:ident <- $($toks:tt)*) => {$crate::extend_line2! { [] $net def $name = $($toks)* } };

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,11 @@
//! Root module for graph component definitions.
pub mod connection;
pub mod connection2;
pub mod edge;
pub mod edge2;
pub mod cursor;
pub mod node;
pub mod visualization;
pub use cursor::Cursor;
pub use edge::Edge;
pub use node::Node;

View File

@ -1,220 +0,0 @@
//! Definition of the Connection component.
#![allow(missing_docs)]
// WARNING! UNDER HEAVY DEVELOPMENT. EXPECT DRASTIC CHANGES.
use crate::prelude::*;
use enso_frp;
use enso_frp as frp;
use ensogl::data::color;
use ensogl::display::Attribute;
use ensogl::display::Buffer;
use ensogl::display::Sprite;
use ensogl::display::scene::Scene;
use ensogl::display::shape::*;
use ensogl::display::traits::*;
use ensogl::display;
use ensogl::gui::component;
// ==================
// === Connection ===
// ==================
/// Canvas node shape definition.
pub mod shape {
use super::*;
ensogl::define_shape_system! {
(radius:f32, start_angle:f32, angle:f32) {
let radius = 1.px() * radius;
let width = LINE_WIDTH.px();
let width2 = width / 2.0;
let ring = Circle(&radius + &width2) - Circle(radius-width2);
let right : Var<f32> = (std::f32::consts::PI/2.0).into();
let rot = right - &angle/2.0;
let mask = Plane().cut_angle_fast(angle).rotate(rot);
let shape = ring * mask;
let shape = shape.fill(color::Rgba::from(color::Lcha::new(0.6,0.5,0.76,1.0)));
shape.into()
}
}
}
/// Canvas node shape definition.
pub mod line {
use super::*;
ensogl::define_shape_system! {
() {
let width = LINE_WIDTH.px();
let height : Var<Distance<Pixels>> = "input_size.y".into();
let shape = Rect((width,height));
let shape = shape.fill(color::Rgba::from(color::Lcha::new(0.6,0.5,0.76,1.0)));
shape.into()
}
}
}
/// Canvas node shape definition.
pub mod helper {
use super::*;
ensogl::define_shape_system! {
() {
let shape = Circle(2.px());
let shape = shape.fill(color::Rgba::new(1.0,0.0,0.0,1.0));
shape.into()
}
}
}
const LINE_WIDTH : f32 = 4.0;
const PADDING : f32 = 5.0;
// ==================
// === Connection ===
// ==================
/// Connection definition.
#[derive(AsRef,Clone,CloneRef,Debug,Deref)]
pub struct Connection {
data : Rc<ConnectionData>,
}
impl AsRef<Connection> for Connection {
fn as_ref(&self) -> &Self {
self
}
}
#[derive(Clone,CloneRef,Debug)]
pub struct InputEvents {
pub network : frp::Network,
pub source_width : frp::Source<f32>,
pub target_position : frp::Source<frp::Position>,
}
impl InputEvents {
pub fn new() -> Self {
frp::new_network! { network
def source_width = source();
def target_position = source();
}
Self {network,source_width,target_position}
}
}
impl Default for InputEvents {
fn default() -> Self {
Self::new()
}
}
/// Internal data of `Connection`
#[derive(Debug)]
#[allow(missing_docs)]
pub struct ConnectionData {
pub object : display::object::Instance,
pub logger : Logger,
pub events : InputEvents,
pub corner : component::ShapeView<shape::Shape>,
pub side_line : component::ShapeView<line::Shape>,
pub main_line : component::ShapeView<line::Shape>,
pub source_width : Rc<Cell<f32>>,
pub target_position : Rc<Cell<frp::Position>>,
}
impl Connection {
/// Constructor.
pub fn new(scene:&Scene) -> Self {
let logger = Logger::new("edge");
let object = display::object::Instance::new(&logger);
let corner = component::ShapeView::<shape::Shape>::new(&logger.sub("corner"),scene);
let side_line = component::ShapeView::<line::Shape>::new(&logger.sub("side_line"),scene);
let main_line = component::ShapeView::<line::Shape>::new(&logger.sub("main_line"),scene);
object.add_child(&corner);
object.add_child(&side_line);
object.add_child(&main_line);
side_line.mod_rotation(|r| r.z = std::f32::consts::PI/2.0);
let input = InputEvents::new();
let network = &input.network;
let source_width : Rc<Cell<f32>> = default();
let target_position = Rc::new(Cell::new(frp::Position::default()));
source_width.set(100.0);
frp::extend! { network
eval input.target_position ((t) target_position.set(*t));
eval input.source_width ((t) source_width.set(*t));
on_change <-_ [input.source_width,input.target_position];
eval_ on_change ([source_width,target_position,object,side_line,main_line,corner] {
let target = target_position.get();
let target = Vector2::new(target.x - object.position().x, target.y - object.position().y);
let radius = 14.0;
let width = source_width.get() / 2.0;
let side_circle_x = width - radius;
let side = target.x.signum();
let target = Vector2::new(target.x.abs(),target.y);
let corner_grow = ((target.x - width) * 0.6).max(0.0);
let corner_radius = 20.0 + corner_grow;
let corner_radius = corner_radius.min(target.y.abs());
let corner_x = target.x - corner_radius;
let x = (corner_x - side_circle_x).clamp(-corner_radius,radius);
let y = (radius*radius + corner_radius*corner_radius - x*x).sqrt();
let angle1 = f32::atan2(y,x);
let angle2 = f32::atan2(radius,corner_radius);
let corner_angle = std::f32::consts::PI - angle1 - angle2;
let angle_overlap = if corner_x > width { 0.0 } else { 0.1 };
corner.shape.angle.set((corner_angle + angle_overlap) * side);
let corner_y = - y;
let corner_side = (corner_radius + PADDING) * 2.0;
corner.shape.sprite.size().set(Vector2::new(corner_side,corner_side));
corner.shape.radius.set(corner_radius);
corner.mod_position(|t| t.x = corner_x * side);
corner.mod_position(|t| t.y = corner_y);
let line_overlap = 2.0;
side_line.shape.sprite.size().set(Vector2::new(10.0,corner_x - width + line_overlap));
side_line.mod_position(|p| p.x = side*(width + corner_x)/2.0);
main_line.shape.sprite.size().set(Vector2::new(10.0,corner_y - target.y + line_overlap));
main_line.mod_position(|p| {
p.x = side * target.x;
p.y = (target.y + corner_y) / 2.0;
});
});
}
let events = input;
let data = Rc::new(ConnectionData {object,logger,events,corner,side_line,main_line
,source_width,target_position});
Self {data}
}
}
impl display::Object for Connection {
fn display_object(&self) -> &display::object::Instance {
&self.object
}
}

View File

@ -16,32 +16,101 @@ use ensogl::display::{Sprite, Attribute};
use ensogl::display;
use ensogl::gui::component::animation;
use ensogl::gui::component::animation2;
use ensogl::gui::component::animator;
use ensogl::gui::component;
use ensogl::system::web;
#[derive(Debug,Clone)]
pub enum Mode {
Normal,
Cursor,
Highlight {
host : display::object::Instance,
position : Vector2<f32>,
size : Vector2<f32>,
}
pub struct StyleParam<T> {
pub value : T,
pub animate : bool,
}
impl Mode {
pub fn highlight<H>(host:H, position:Vector2<f32>, size:Vector2<f32>) -> Self
where H:display::Object {
let host = host.display_object().clone_ref();
Self::Highlight {host,position,size}
}
}
impl Default for Mode {
impl<T:Default> Default for StyleParam<T> {
fn default() -> Self {
Self::Normal
let value = default();
let animate = true;
Self {value,animate}
}
}
impl<T> StyleParam<T> {
pub fn new(value:T) -> Self {
let animate = true;
Self {value,animate}
}
pub fn new_no_animation(value:T) -> Self {
let animate = false;
Self {value,animate}
}
}
#[derive(Debug,Clone,Default)]
pub struct Style {
host : Option<display::object::Instance>,
size : Option<StyleParam<Vector2<f32>>>,
offset : Option<StyleParam<Vector2<f32>>>,
color : Option<StyleParam<color::Lcha>>,
radius : Option<f32>,
press : Option<f32>,
}
impl Style {
pub fn highlight<H>
(host:H, size:Vector2<f32>, color:Option<color::Lcha>) -> Self
where H:display::Object {
let host = Some(host.display_object().clone_ref());
let size = Some(StyleParam::new(size));
let color = color.map(StyleParam::new);
Self {host,size,color,..default()}
}
pub fn color(color:color::Lcha) -> Self {
let color = Some(StyleParam::new(color));
Self {color,..default()}
}
pub fn color_no_animation(color:color::Lcha) -> Self {
let color = Some(StyleParam::new_no_animation(color));
Self {color,..default()}
}
pub fn selection(size:Vector2<f32>) -> Self {
let offset = Some(StyleParam::new_no_animation(-size / 2.0));
let size = Some(StyleParam::new_no_animation(size.abs() + Vector2::new(16.0,16.0)));
Self {size,offset,..default()}
}
pub fn pressed() -> Self {
let press = Some(1.0);
Self {press,..default()}
}
pub fn press(mut self) -> Self {
self.press = Some(1.0);
self
}
}
impl PartialSemigroup<&Style> for Style {
#[allow(clippy::clone_on_copy)]
fn concat_mut(&mut self, other:&Self) {
if self.host . is_none() { self.host = other.host . clone() }
if self.size . is_none() { self.size = other.size . clone() }
if self.offset . is_none() { self.offset = other.offset . clone() }
if self.color . is_none() { self.color = other.color . clone() }
if self.radius . is_none() { self.radius = other.radius . clone() }
if self.press . is_none() { self.press = other.press . clone() }
}
}
impl PartialSemigroup<Style> for Style {
fn concat_mut(&mut self, other:Self) {
self.concat_mut(&other)
}
}
@ -56,57 +125,52 @@ pub mod shape {
use super::*;
ensogl::define_shape_system! {
(position:Vector2<f32>, width:f32, height:f32, selection_size:Vector2<f32>, press:f32, radius:f32) {
( position : Vector2<f32>
, dim : V2
, offset : V2
, selection_size : Vector2<f32>
, press : f32
, radius : f32
, color : Vector4<f32>
) {
let press_diff = 2.px() * &press;
let radius = 1.px() * radius - &press_diff;
let selection_width = 1.px() * &selection_size.x() * &press;
let selection_height = 1.px() * &selection_size.y() * &press;
let width = (1.px() * &width - &press_diff * 2.0) + selection_width.abs();
let height = (1.px() * &height - &press_diff * 2.0) + selection_height.abs();
let offset : Var<V2<Distance<Pixels>>> = offset.px();
let selection_width = 1.px() * &selection_size.x(); // * &press;
let selection_height = 1.px() * &selection_size.y(); // * &press;
let width = (1.px() * &dim.x() - &press_diff * 2.0) + selection_width.abs();
let height = (1.px() * &dim.y() - &press_diff * 2.0) + selection_height.abs();
let cursor = Rect((width,height))
.corners_radius(radius)
.translate((-&selection_width/2.0, -&selection_height/2.0))
.translate(offset)
.translate(("input_position.x","input_position.y"))
.fill(color::Rgba::new(1.0,1.0,1.0,0.2));
.fill("srgba(input_color)");
cursor.into()
}
}
}
///// Shape view for Cursor.
//#[derive(Clone,CloneRef,Debug)]
//#[allow(missing_docs)]
//pub struct CursorView {}
//
//impl component::ShapeViewDefinition for CursorView {
// type Shape = shape::Shape;
//}
// ==============
// ===================
// === InputEvents ===
// ==============
// ===================
/// Cursor events.
#[derive(Clone,CloneRef,Debug)]
#[allow(missing_docs)]
pub struct InputEvents {
pub network : frp::Network,
pub set_mode : frp::Source<Mode>,
pub press : frp::Source,
pub release : frp::Source,
pub network : frp::Network,
pub set_style : frp::Source<Style>,
}
impl Default for InputEvents {
fn default() -> Self {
frp::new_network! { cursor_events
def set_mode = source();
def press = source();
def release = source();
def set_style = source();
}
let network = cursor_events;
Self {network,set_mode,press,release}
Self {network,set_style}
}
}
@ -178,12 +242,6 @@ impl Cursor {
scene.views.main.remove(&shape_system.shape_system.symbol);
scene.views.cursor.add(&shape_system.shape_system.symbol);
// let scene_view = scene.views.new();
// scene.views.main.remove(&shape_system.shape_system.symbol);
// scene_view.add(&shape_system.shape_system.symbol);
let network = &input.network;
@ -197,95 +255,129 @@ impl Cursor {
view_data.radius.set(value)
});
let view_data = view.shape.clone_ref();
let width = animation(network,move |value| {
view_data.width.set(value)
});
let view_data = view.shape.clone_ref();
let height = animation(network,move |value| {
view_data.height.set(value)
});
let (size,current_size) = animator::<V2>(network);
let (offset,current_offset) = animator::<V2>(network);
let (anim_use_fixed_pos_setter,anim_use_fixed_pos) = animation2(network);
let (anim_pos_x_setter,anim_pos_x) = animation2(network);
let (anim_pos_y_setter,anim_pos_y) = animation2(network);
let (anim_color_lab_l_setter,anim_color_lab_l) = animation2(network);
let (anim_color_lab_a_setter,anim_color_lab_a) = animation2(network);
let (anim_color_lab_b_setter,anim_color_lab_b) = animation2(network);
let (anim_color_alpha_setter,anim_color_alpha) = animation2(network);
anim_color_lab_l_setter.set_target_value(1.0);
anim_color_alpha_setter.set_target_value(0.2);
radius.set_target_value(8.0);
size.set_target_value(V2::new(16.0,16.0));
let mouse = &scene.mouse.frp;
frp::extend! { network
def anim_position = anim_pos_x.zip_with(&anim_pos_y,|x,y| frp::Position::new(*x,*y));
eval current_size ((v) view.shape.dim.set(*v));
eval current_offset ((v) view.shape.offset.set(*v));
def _t_press = input.press.map(enclose!((press) move |_| {
press.set_target_position(1.0);
}));
def anim_position = anim_pos_x.all_with(&anim_pos_y,|x,y| frp::Position::new(*x,*y));
def _t_release = input.release.map(enclose!((press) move |_| {
press.set_target_position(0.0);
}));
anim_color <- all_with4(&anim_color_lab_l,&anim_color_lab_a,&anim_color_lab_b,&anim_color_alpha,
|l,a,b,alpha| color::Rgba::from(color::Laba::new(*l,*a,*b,*alpha))
);
def fixed_position = input.set_mode.map(enclose!((anim_pos_x_setter,anim_pos_y_setter) move |m| {
match m {
Mode::Highlight {host,..} => {
let p = host.global_position();
anim_pos_x_setter.set_target_position(p.x);
anim_pos_y_setter.set_target_position(p.y);
anim_use_fixed_pos_setter.set_target_position(1.0);
Some(p)
}
_ => {
anim_use_fixed_pos_setter.set_target_position(0.0);
None
def _ev = input.set_style.map(enclose!((size,anim_pos_x_setter,anim_pos_y_setter) move |style| {
match &style.press {
None => press.set_target_value(0.0),
Some(t) => press.set_target_value(*t),
}
match &style.host {
None => anim_use_fixed_pos_setter.set_target_value(0.0),
Some(host) => {
let position = host.global_position();
anim_pos_x_setter.set_target_value(position.x);
anim_pos_y_setter.set_target_value(position.y);
anim_use_fixed_pos_setter.set_target_value(1.0);
}
}
match &style.color {
None => {
anim_color_lab_l_setter.set_target_value(1.0);
anim_color_lab_a_setter.set_target_value(0.0);
anim_color_lab_b_setter.set_target_value(0.0);
anim_color_alpha_setter.set_target_value(0.2);
}
Some(new_color) => {
let lab = color::Laba::from(new_color.value);
anim_color_lab_l_setter.set_target_value(lab.lightness);
anim_color_lab_a_setter.set_target_value(lab.a);
anim_color_lab_b_setter.set_target_value(lab.b);
anim_color_alpha_setter.set_target_value(lab.alpha);
if !new_color.animate {
anim_color_lab_l_setter.skip();
anim_color_lab_a_setter.skip();
anim_color_lab_b_setter.skip();
anim_color_alpha_setter.skip();
}
}
}
match &style.size {
None => {
size.set_target_value(V2::new(16.0,16.0));
}
Some(new_size) => {
size.set_target_value(V2::new(new_size.value.x,new_size.value.y));
if !new_size.animate { size.skip() }
}
}
match &style.offset {
None => {
offset.set_target_value(V2::new(0.0,0.0));
}
Some(new_offset) => {
offset.set_target_value(V2::new(new_offset.value.x,new_offset.value.y));
if !new_offset.animate { offset.skip() }
}
}
match &style.radius {
None => radius.set_target_value(8.0),
Some(r) => radius.set_target_value(*r),
}
}));
def fixed_position = input.set_style.map(|style| style.host.as_ref().map(|t| t.global_position()));
def uses_mouse_position = fixed_position.map(|p| p.is_none());
def mouse_position = mouse.position.gate(&uses_mouse_position);
def position = mouse.position.zip_with3(&anim_position,&anim_use_fixed_pos, |p,ap,au| {
def position = mouse.position.all_with3(&anim_position,&anim_use_fixed_pos, |p,ap,au| {
let x = ap.x * au + p.x * (1.0 - au);
let y = ap.y * au + p.y * (1.0 - au);
frp::Position::new(x,y)
});
def _position = position.map(f!((p) {
view.shape.position.set(Vector2::new(p.x,p.y));
}));
eval anim_color ((t) view.shape.color.set(Vector4::new(t.red,t.green,t.blue,t.alpha)));
eval position ((p) view.shape.position.set(Vector2::new(p.x,p.y)));
def _position = mouse_position.map(f!([anim_pos_x_setter,anim_pos_y_setter](p) {
anim_pos_x_setter.set_target_position(p.x);
anim_pos_y_setter.set_target_position(p.y);
}));
def _t_mode = input.set_mode.map(enclose!((radius,width,height) move |m| {
match m {
Mode::Normal => {
radius.set_target_position(8.0);
width.set_target_position(16.0);
height.set_target_position(16.0);
}
Mode::Highlight {size,..} => {
radius.set_target_position(4.0);
width.set_target_position(size.x);
height.set_target_position(size.y);
}
_ => panic!()
};
anim_pos_x_setter.set_target_value(p.x);
anim_pos_y_setter.set_target_value(p.y);
}));
}
radius.set_target_position(8.0);
width.set_target_position(16.0);
height.set_target_position(16.0);
input.set_mode.emit(Mode::Normal);
input.set_style.emit(Style::default());
let frp = Events {input,position};
@ -293,17 +385,6 @@ impl Cursor {
let data = Rc::new(data);
Cursor {data}
}
// /// Position setter.
// pub fn set_position(&self, pos:Vector2<f32>) {
// self.view.shape.position.set(pos);
// }
/// Selection size setter.
pub fn set_selection_size(&self, pos:Vector2<f32>) {
self.view.shape.selection_size.set(pos);
}
}

View File

@ -0,0 +1,883 @@
//! Definition of the Edge component.
use crate::prelude::*;
use enso_frp;
use enso_frp as frp;
use ensogl::data::color;
use ensogl::display::Attribute;
use ensogl::display::Buffer;
use ensogl::display::Sprite;
use ensogl::display::scene::Scene;
use ensogl::display::shape::*;
use ensogl::display::traits::*;
use ensogl::display;
use ensogl::gui::component;
use super::node;
fn min(a:f32,b:f32) -> f32 {
f32::min(a,b)
}
fn max(a:f32,b:f32) -> f32 {
f32::max(a,b)
}
// =================
// === Constants ===
// =================
const LINE_SHAPE_WIDTH : f32 = LINE_WIDTH + 2.0 * PADDING;
const LINE_SIDE_OVERLAP : f32 = 1.0;
const LINE_SIDES_OVERLAP : f32 = 2.0 * LINE_SIDE_OVERLAP;
const LINE_WIDTH : f32 = 4.0;
const MOUSE_OFFSET : f32 = 2.0;
const NODE_HALF_HEIGHT : f32 = NODE_HEIGHT / 2.0;
const NODE_HEIGHT : f32 = node::NODE_HEIGHT;
const NODE_PADDING : f32 = node::SHADOW_SIZE;
const PADDING : f32 = 4.0;
const RIGHT_ANGLE : f32 = std::f32::consts::PI / 2.0;
const INFINITE : f32 = 99999.0;
// =========================
// === Shape Definitions ===
// =========================
macro_rules! define_corner_start {($($color:tt)*) => {
/// Shape definition.
pub mod corner {
use super::*;
ensogl::define_shape_system! {
(radius:f32, angle:f32, start_angle:f32, pos:Vector2<f32>, dim:Vector2<f32>) {
let radius = 1.px() * radius;
let width = LINE_WIDTH.px();
let width2 = width / 2.0;
let ring = Circle(&radius + &width2) - Circle(radius-width2);
let right : Var<f32> = (RIGHT_ANGLE).into();
let rot = right - &angle/2.0 + start_angle;
let mask = Plane().cut_angle_fast(angle).rotate(rot);
let shape = ring * mask;
let shadow_size = 10.px();
let n_radius = &shadow_size + 1.px() * dim.y();
let n_shape = Rect((&shadow_size*2.0 + 2.px() * dim.x(),&n_radius*2.0)).corners_radius(n_radius);
let n_shape = n_shape.fill(color::Rgba::new(1.0,0.0,0.0,1.0));
let tx = - 1.px() * pos.x();
let ty = - 1.px() * pos.y();
let n_shape = n_shape.translate((tx,ty));
let shape = shape - n_shape;
let shape = shape.fill(color::Rgba::from($($color)*));
shape.into()
}
}
}
}}
macro_rules! define_corner_end {($($color:tt)*) => {
/// Shape definition.
pub mod corner {
use super::*;
ensogl::define_shape_system! {
(radius:f32, angle:f32, start_angle:f32, pos:Vector2<f32>, dim:Vector2<f32>) {
let radius = 1.px() * radius;
let width = LINE_WIDTH.px();
let width2 = width / 2.0;
let ring = Circle(&radius + &width2) - Circle(radius-width2);
let right : Var<f32> = (RIGHT_ANGLE).into();
let rot = right - &angle/2.0 + start_angle;
let mask = Plane().cut_angle_fast(angle).rotate(rot);
let shape = ring * mask;
let shadow_size = 10.px() + 1.px();
let n_radius = &shadow_size + 1.px() * dim.y();
let n_shape = Rect((&shadow_size*2.0 + 2.px() * dim.x(),&n_radius*2.0)).corners_radius(n_radius);
let n_shape = n_shape.fill(color::Rgba::new(1.0,0.0,0.0,1.0));
let tx = - 1.px() * pos.x();
let ty = - 1.px() * pos.y();
let n_shape = n_shape.translate((tx,ty));
let shape = shape * n_shape;
let shape = shape.fill(color::Rgba::from($($color)*));
shape.into()
}
}
}
}}
macro_rules! define_line {($($color:tt)*) => {
/// Shape definition.
pub mod line {
use super::*;
ensogl::define_shape_system! {
() {
let width = LINE_WIDTH.px();
let height : Var<Distance<Pixels>> = "input_size.y".into();
let shape = Rect((width,height));
let shape = shape.fill(color::Rgba::from($($color)*));
shape.into()
}
}
}
}}
macro_rules! define_arrow {($($color:tt)*) => {
/// Shape definition.
pub mod arrow {
use super::*;
ensogl::define_shape_system! {
() {
let width : Var<Distance<Pixels>> = "input_size.x".into();
let height : Var<Distance<Pixels>> = "input_size.y".into();
let width = width - (2.0 * PADDING).px();
let height = height - (2.0 * PADDING).px();
let triangle = Triangle(width,height);
let offset = (LINE_WIDTH/2.0).px();
let triangle_l = triangle.translate_x(-&offset);
let triangle_r = triangle.translate_x(&offset);
let shape = triangle_l + triangle_r;
let shape = shape.fill(color::Rgba::from($($color)*));
shape.into()
}
}
}
}}
// ========================
// === Shape Operations ===
// ========================
trait LayoutLine {
fn layout_v(&self,start:Vector2<f32>,len:f32);
fn layout_h(&self,start:Vector2<f32>,len:f32);
fn layout_v_no_overlap(&self,start:Vector2<f32>,len:f32);
fn layout_h_no_overlap(&self,start:Vector2<f32>,len:f32);
}
impl LayoutLine for component::ShapeView<front::line::Shape> {
fn layout_v(&self, start:Vector2<f32>, len:f32) {
let pos = Vector2::new(start.x, start.y + len/2.0);
let size = Vector2::new(LINE_SHAPE_WIDTH, len.abs()+LINE_SIDES_OVERLAP);
self.shape.sprite.size().set(size);
self.set_position_xy(pos);
}
fn layout_h(&self, start:Vector2<f32>, len:f32) {
let pos = Vector2::new(start.x + len/2.0, start.y);
let size = Vector2::new(LINE_SHAPE_WIDTH, len.abs()+LINE_SIDES_OVERLAP);
self.shape.sprite.size().set(size);
self.set_position_xy(pos);
}
fn layout_v_no_overlap(&self, start:Vector2<f32>, len:f32) {
let pos = Vector2::new(start.x, start.y + len/2.0);
let size = Vector2::new(LINE_SHAPE_WIDTH, len.abs());
self.shape.sprite.size().set(size);
self.set_position_xy(pos);
}
fn layout_h_no_overlap(&self, start:Vector2<f32>, len:f32) {
let pos = Vector2::new(start.x + len/2.0, start.y);
let size = Vector2::new(LINE_SHAPE_WIDTH, len.abs());
self.shape.sprite.size().set(size);
self.set_position_xy(pos);
}
}
impl LayoutLine for component::ShapeView<back::line::Shape> {
fn layout_v(&self, start:Vector2<f32>, len:f32) {
let pos = Vector2::new(start.x, start.y + len/2.0);
let size = Vector2::new(LINE_SHAPE_WIDTH, len.abs()+LINE_SIDES_OVERLAP);
self.shape.sprite.size().set(size);
self.set_position_xy(pos);
}
fn layout_h(&self, start:Vector2<f32>, len:f32) {
let pos = Vector2::new(start.x + len/2.0, start.y);
let size = Vector2::new(LINE_SHAPE_WIDTH, len.abs()+LINE_SIDES_OVERLAP);
self.shape.sprite.size().set(size);
self.set_position_xy(pos);
}
fn layout_v_no_overlap(&self, start:Vector2<f32>, len:f32) {
let pos = Vector2::new(start.x, start.y + len/2.0);
let size = Vector2::new(LINE_SHAPE_WIDTH, len.abs());
self.shape.sprite.size().set(size);
self.set_position_xy(pos);
}
fn layout_h_no_overlap(&self, start:Vector2<f32>, len:f32) {
let pos = Vector2::new(start.x + len/2.0, start.y);
let size = Vector2::new(LINE_SHAPE_WIDTH, len.abs());
self.shape.sprite.size().set(size);
self.set_position_xy(pos);
}
}
// ===========================
// === Front / Back Shapes ===
// ===========================
/// Shape definitions which will be rendered in the front layer (on top of nodes).
pub mod front {
use super::*;
define_corner_start!(color::Lcha::new(0.6,0.5,0.76,1.0));
define_line!(color::Lcha::new(0.6,0.5,0.76,1.0));
define_arrow!(color::Lcha::new(0.6,0.5,0.76,1.0));
}
/// Shape definitions which will be rendered in the bottom layer (below nodes).
pub mod back {
use super::*;
define_corner_end!(color::Lcha::new(0.6,0.5,0.76,1.0));
define_line!(color::Lcha::new(0.6,0.5,0.76,1.0));
define_arrow!(color::Lcha::new(0.6,0.5,0.76,1.0));
}
// ===========================
// === Front / Back Layers ===
// ===========================
macro_rules! define_components {
($name:ident {
$($field:ident : $field_type:ty),* $(,)?
}) => {
#[derive(Debug,Clone,CloneRef)]
#[allow(missing_docs)]
pub struct $name {
pub logger : Logger,
pub display_object : display::object::Instance,
$(pub $field : component::ShapeView<$field_type>),*
}
impl $name {
/// Constructor.
pub fn new(logger:Logger, scene:&Scene) -> Self {
let display_object = display::object::Instance::new(&logger);
$(let $field = component::ShapeView::new(&logger.sub(stringify!($field)),scene);)*
$(display_object.add_child(&$field);)*
Self {logger,display_object,$($field),*}
}
}
impl display::Object for $name {
fn display_object(&self) -> &display::object::Instance {
&self.display_object
}
}
}
}
define_components!{
Front {
corner : front::corner::Shape,
corner2 : front::corner::Shape,
corner3 : front::corner::Shape,
side_line : front::line::Shape,
side_line2 : front::line::Shape,
main_line : front::line::Shape,
port_line : front::line::Shape,
arrow : front::arrow::Shape,
}
}
define_components!{
Back {
corner : back::corner::Shape,
corner2 : back::corner::Shape,
corner3 : back::corner::Shape,
side_line : back::line::Shape,
side_line2 : back::line::Shape,
main_line : back::line::Shape,
arrow : back::arrow::Shape,
}
}
// TODO: Implement proper sorting and remove.
/// Hack function used to register the elements for the sorting purposes. To be removed.
pub fn sort_hack_1(scene:&Scene) {
let logger = Logger::new("hack_sort");
component::ShapeView::<back::corner::Shape>::new(&logger,scene);
component::ShapeView::<back::line::Shape>::new(&logger,scene);
component::ShapeView::<back::arrow::Shape>::new(&logger,scene);
}
// TODO: Implement proper sorting and remove.
/// Hack function used to register the elements for the sorting purposes. To be removed.
pub fn sort_hack_2(scene:&Scene) {
let logger = Logger::new("hack_sort");
component::ShapeView::<front::corner::Shape>::new(&logger,scene);
component::ShapeView::<front::line::Shape>::new(&logger,scene);
component::ShapeView::<front::arrow::Shape>::new(&logger,scene);
}
// ===========
// === FRP ===
// ===========
/// FRP endpoints of the edge.
#[derive(Clone,CloneRef,Debug)]
#[allow(missing_docs)]
pub struct Frp {
pub source_width : frp::Source<f32>,
pub target_position : frp::Source<frp::Position>,
pub target_attached : frp::Source<bool>,
}
impl Frp {
/// Constructor.
pub fn new(network:&frp::Network) -> Self {
frp::extend! { network
def source_width = source();
def target_position = source();
def target_attached = source();
}
Self {source_width,target_position,target_attached}
}
}
// ==================
// === Math Utils ===
// ==================
/// For the given radius of the first circle (`r1`), radius of the second circle (`r2`), and the
/// x-axis position of the second circle (`x`), computes the y-axis position of the second circle in
/// such a way, that the borders of the circle cross at the right angle. It also computes the angle
/// of the intersection. Please note, that the center of the first circle is in the origin.
///
/// ```compile_fail
/// r1
/// ◄───► (1) x^2 + y^2 = r1^2 + r2^2
/// _____ (1) => y = sqrt((r1^2 + r2^2)/x^2)
/// .' `.
/// / _.-"""B-._ ▲
/// | .'0┼ | `. │ angle1 = A-XY-0
/// \/ │ / \ │ r2 angle2 = 0-XY-B
/// |`._ │__.' | │ alpha = B-XY-X_AXIS
/// | A└───┼─ | ▼
/// | (x,y) | tg(angle1) = y / x
/// \ / tg(angle2) = r1 / r2
/// `._ _.' alpha = PI - angle1 - angle2
/// `-....-'
///```
fn circle_intersection(x:f32, r1:f32, r2:f32) -> (f32,f32) {
let x_norm = x.clamp(-r2,r1);
let y = (r1*r1 + r2*r2 - x_norm*x_norm).sqrt();
let angle1 = f32::atan2(y,x_norm);
let angle2 = f32::atan2(r1,r2);
let angle = std::f32::consts::PI - angle1 - angle2;
(y,angle)
}
// ============
// === Edge ===
// ============
/// Edge definition.
#[derive(AsRef,Clone,CloneRef,Debug,Deref)]
pub struct Edge {
#[deref]
model : Rc<EdgeModel>,
network : frp::Network,
}
impl AsRef<Edge> for Edge {
fn as_ref(&self) -> &Self {
self
}
}
impl display::Object for EdgeModelData {
fn display_object(&self) -> &display::object::Instance {
&self.display_object
}
}
impl Edge {
/// Constructor.
pub fn new(scene:&Scene) -> Self {
let network = frp::Network::new();
let data = Rc::new(EdgeModelData::new(scene,&network));
let model = Rc::new(EdgeModel {data});
Self {model,network} . init()
}
fn init(self) -> Self {
let network = &self.network;
let input = &self.frp;
let target_position = &self.target_position;
let target_attached = &self.target_attached;
let source_width = &self.source_width;
let model = &self.model;
frp::extend! { network
eval input.target_position ((t) target_position.set(*t));
eval input.target_attached ((t) target_attached.set(*t));
eval input.source_width ((t) source_width.set(*t));
on_change <- any_ (input.source_width, input.target_position, input.target_attached);
eval_ on_change (model.redraw());
}
self
}
}
impl display::Object for Edge {
fn display_object(&self) -> &display::object::Instance {
&self.display_object
}
}
// =================
// === EdgeModel ===
// =================
/// Edge definition.
#[derive(AsRef,Clone,CloneRef,Debug,Deref)]
pub struct EdgeModel {
data : Rc<EdgeModelData>,
}
/// Internal data of `Edge`
#[derive(Debug)]
#[allow(missing_docs)]
pub struct EdgeModelData {
pub display_object : display::object::Instance,
pub logger : Logger,
pub frp : Frp,
pub front : Front,
pub back : Back,
pub source_width : Rc<Cell<f32>>,
pub target_position : Rc<Cell<frp::Position>>,
pub target_attached : Rc<Cell<bool>>,
}
impl EdgeModelData {
/// Constructor.
pub fn new(scene:&Scene, network:&frp::Network) -> Self {
let logger = Logger::new("edge");
let display_object = display::object::Instance::new(&logger);
let front = Front::new(logger.sub("front"),scene);
let back = Back::new(logger.sub("back"),scene);
display_object.add_child(&front);
display_object.add_child(&back);
front . side_line . mod_rotation(|r| r.z = RIGHT_ANGLE);
back . side_line . mod_rotation(|r| r.z = RIGHT_ANGLE);
front . side_line2 . mod_rotation(|r| r.z = RIGHT_ANGLE);
back . side_line2 . mod_rotation(|r| r.z = RIGHT_ANGLE);
let frp = Frp::new(&network);
let source_width = Rc::new(Cell::new(100.0));
let target_position = Rc::new(Cell::new(frp::Position::default()));
let target_attached = Rc::new(Cell::new(false));
Self {display_object,logger,frp,front,back,source_width,target_position,target_attached}
}
/// Redraws the connection.
#[allow(clippy::cognitive_complexity)]
pub fn redraw(&self) {
// === Variables ===
let fg = &self.front;
let bg = &self.back;
let target_attached = self.target_attached.get();
let node_half_width = self.source_width.get() / 2.0;
let node_circle = Vector2::new(node_half_width-NODE_HALF_HEIGHT,0.0);
let node_radius = NODE_HALF_HEIGHT;
// === Target ===
//
// Target is the end position of the connection in local node space (the origin is placed in
// the center of the node). We handle lines drawing in special way when target is below the
// node (for example, not to draw the port line above source node).
//
// ╭──────────────╮
// │ ┼ (0,0) │
// ╰──────────────╯────╮
// │
// ▢ target
let world_space_target = self.target_position.get();
let target_x = world_space_target.x - self.position().x;
let target_y = world_space_target.y - self.position().y;
let side = target_x.signum();
let target_x = target_x.abs();
let target = Vector2::new(target_x,target_y);
let target_is_below_node_x = target.x < node_half_width;
let target_is_below_node_y = target.y < (-NODE_HALF_HEIGHT);
let target_is_below_node = target_is_below_node_x && target_is_below_node_y;
let port_line_len_max = NODE_HALF_HEIGHT + NODE_PADDING;
let side_right_angle = side * RIGHT_ANGLE;
// === Upward Discovery ===
//
// Discovers when the connection should go upwards. The `upward_corner_radius` defines the
// preferred radius for every corner in this scenario.
//
// ╭─────╮ ╭─╮
// ╰─────╯────╯ │
// ▢
let upward_corner_radius = 20.0;
let min_len_for_non_curved_line = upward_corner_radius + port_line_len_max;
let upward_distance = target.y + min_len_for_non_curved_line;
// === Flat side ===
//
// Maximum side distance before connection is curved up.
//
// ╭─────╮◄──► ╭─────╮◄──►╭─╮
// ╰─────╯───╮ ╰─────╯────╯ │
// ▢ ▢
let flat_side_size = 40.0;
let is_flat_side = target.x < node_half_width + flat_side_size;
let downward_flat = if target_is_below_node_x {target_is_below_node_y} else {target.y<0.0};
let downward_far = -target.y > min_len_for_non_curved_line || target_is_below_node;
let is_down = if is_flat_side {downward_flat} else {downward_far};
// === Port Line Length ===
//
// ╭──╮
// ╰──╯───╮
// ╵
// ╭──┼──╮ ▲ Port line covers the area above target node and the area of target node
// │ ▢ │ ▼ shadow. It can be shorter if the target position is below the node or the
// ╰─────╯ connection is being dragged, in order not to overlap with the source node.
let port_line_start = Vector2::new(side * target.x, target.y + MOUSE_OFFSET);
let space_attached = -port_line_start.y - NODE_HALF_HEIGHT - LINE_SIDE_OVERLAP;
let space = space_attached - NODE_PADDING;
let len_below_free = max(0.0,min(port_line_len_max,space));
let len_below_attached = max(0.0,min(port_line_len_max,space_attached));
let len_below = if target_attached {len_below_attached} else {len_below_free};
let far_side_len = if target_is_below_node {len_below} else {port_line_len_max};
let flat_side_len = min(far_side_len,-port_line_start.y);
let mut port_line_len = if is_flat_side && is_down {flat_side_len} else {far_side_len};
let port_line_end = Vector2::new(target.x,port_line_start.y + port_line_len);
// === Corner1 ===
//
// The first corner on the line. It is always placed at the right angle to the tangent of
// the node border. In case the edge is in the drag mode, the curve is divided into two
// parts. The first part is placed under the source node shadow, while the second part is
// placed on the top layer.
//
// ╭─────╮ ╭─────╮ 2╭──╮3
// ╰─────╯──╮1 ╰─────╯──╯1 ▢
// ▢
let mut corner1_target = port_line_end;
if !is_down {
corner1_target.x = if is_flat_side {
let radius_grow = max(0.0,target.x - node_half_width + upward_corner_radius);
node_half_width + upward_corner_radius + radius_grow
} else {
let radius1 = node_half_width + (target.x - node_half_width)/2.0;
let radius2 = node_half_width + 2.0*upward_corner_radius;
min(radius1,radius2)
};
corner1_target.y = min(upward_corner_radius,upward_distance/2.0);
}
let corner1_grow = ((corner1_target.x - node_half_width) * 0.6).max(0.0);
let corner1_radius = 20.0 + corner1_grow;
let corner1_radius = corner1_radius.min(corner1_target.y.abs());
let corner1_x = corner1_target.x - corner1_radius;
let corner1_x_loc = corner1_x - node_circle.x;
let (y,angle) = circle_intersection(corner1_x_loc,node_radius,corner1_radius);
let corner1_y = if is_down {-y} else {y};
let corner1 = Vector2::new(corner1_x*side, corner1_y);
let angle_overlap = if corner1_x > node_half_width { 0.0 } else { 0.1 };
let corner1_side = (corner1_radius + PADDING) * 2.0;
let corner1_size = Vector2::new(corner1_side,corner1_side);
let corner1_start_angle = if is_down {0.0} else {side_right_angle};
let corner1_angle = (angle + angle_overlap) * side;
let corner1_angle = if is_down {corner1_angle} else {side_right_angle};
bg.corner.shape.sprite.size().set(corner1_size);
bg.corner.shape.start_angle.set(corner1_start_angle);
bg.corner.shape.angle.set(corner1_angle);
bg.corner.shape.radius.set(corner1_radius);
bg.corner.shape.pos.set(corner1);
bg.corner.set_position_xy(corner1);
if !target_attached {
bg.corner.shape.dim.set(Vector2::new(node_half_width,NODE_HALF_HEIGHT));
fg.corner.shape.sprite.size().set(corner1_size);
fg.corner.shape.start_angle.set(corner1_start_angle);
fg.corner.shape.angle.set(corner1_angle);
fg.corner.shape.radius.set(corner1_radius);
fg.corner.shape.pos.set(corner1);
fg.corner.shape.dim.set(Vector2::new(node_half_width,NODE_HALF_HEIGHT));
fg.corner.set_position_xy(corner1);
} else {
fg.corner.shape.sprite.size().set(zero());
bg.corner.shape.dim.set(Vector2::new(INFINITE,INFINITE));
}
// === Side Line ===
//
// Side line is the first horizontal line. In case the edge is in drag mode, the line is
// divided into two segments. The first is placed below the shadow of the source node, while
// the second is placed on the top layer. The side line placement is the same in case of
// upwards connections - it is then placed between node and corenr 1.
//
// ╭─────╮ ╭─────╮ 2╭──╮3
// ╰─────╯╴──╮ ╰─────╯╴─╯1 ▢
// ▢
let side_line_shift = LINE_SIDES_OVERLAP;
let side_line_len = max(0.0,corner1_x - node_half_width + side_line_shift);
let bg_line_x = node_half_width - side_line_shift;
let bg_line_start = Vector2::new(side*bg_line_x,0.0);
if target_attached {
let bg_line_len = side*side_line_len;
fg.side_line.shape.sprite.size().set(zero());
bg.side_line.layout_h(bg_line_start,bg_line_len);
} else {
let bg_max_len = NODE_PADDING + side_line_shift;
let bg_line_len = min(side_line_len,bg_max_len);
let bg_end_x = bg_line_x + bg_line_len;
let fg_line_start = Vector2::new(side*(bg_end_x+LINE_SIDE_OVERLAP),0.0);
let fg_line_len = side*(side_line_len - bg_line_len);
let bg_line_len_overlap = side * min(side_line_len,bg_max_len+LINE_SIDES_OVERLAP);
bg.side_line.layout_h(bg_line_start,bg_line_len_overlap);
fg.side_line.layout_h_no_overlap(fg_line_start,fg_line_len);
}
// === Main Line (downwards) ===
//
// Main line is the long vertical line. In case it is placed below the node and the edge is
// in drag mode, it is divided into two segments. The upper segment is drawn behind node
// shadow, while the second is drawn on the top layer. In case of edge in drag mode drawn
// next to node, only the top layer segment is used.
//
// Please note that only applies to edges going down. Refer to docs of main line of edges
// going up to learn more.
//
// Double edge: Single edge:
// ╭─────╮ ╭─────╮
// ╰──┬──╯ ╰─────╯────╮
// ╷ │
// ▢ ▢
if is_down {
let main_line_end_y = corner1.y;
let main_line_len = main_line_end_y - port_line_start.y;
if !target_attached && target_is_below_node {
let back_line_start_y = max(-NODE_HALF_HEIGHT - NODE_PADDING, port_line_start.y);
let back_line_start = Vector2::new(port_line_start.x, back_line_start_y);
let back_line_len = main_line_end_y - back_line_start_y;
let front_line_len = main_line_len - back_line_len;
bg.main_line.layout_v(back_line_start, back_line_len);
fg.main_line.layout_v(port_line_start, front_line_len);
} else if target_attached {
let main_line_start_y = port_line_start.y + port_line_len;
let main_line_start = Vector2::new(port_line_start.x, main_line_start_y);
fg.main_line.shape.sprite.size().set(zero());
bg.main_line.layout_v(main_line_start, main_line_len - port_line_len);
} else {
bg.main_line.shape.sprite.size().set(zero());
fg.main_line.layout_v(port_line_start, main_line_len);
}
}
if !is_down {
// === Corner2 & Corner3 Radius ===
//
// ╭─────╮ 2╭──╮3
// ╰─────╯──╯1 ▢
let corner2_radius = corner1_radius;
let corner3_radius = upward_corner_radius;
let corner2_x = corner1_target.x + corner1_radius;
let corner3_x = port_line_end.x - corner3_radius;
let corner2_bbox_x = corner2_x - corner2_radius;
let corner3_bbox_x = corner3_x + corner3_radius;
let corner_2_3_dist = corner3_bbox_x - corner2_bbox_x;
let corner_2_3_side = corner_2_3_dist.signum();
let corner_2_3_dist = corner_2_3_dist.abs();
let corner_2_3_width = corner2_radius + corner3_radius;
let corner_2_3_do_scale = corner_2_3_dist < corner_2_3_width;
let corner_2_3_scale = corner_2_3_dist / corner_2_3_width;
let corner_2_3_scale = if corner_2_3_do_scale {corner_2_3_scale} else {1.0};
let side_combined = side * corner_2_3_side;
let corner2_radius = corner2_radius * corner_2_3_scale;
let corner3_radius = corner3_radius * corner_2_3_scale;
let is_right_side = (side_combined - 1.0) < std::f32::EPSILON;
// === Corner2 & Corner3 Placement ===
//
// ╭─────╮ 2╭──╮3
// ╰─────╯──╯1 ▢
let corner3_side = (corner3_radius + PADDING) * 2.0;
let corner3_size = Vector2::new(corner3_side,corner3_side);
let corner3_x = port_line_end.x - corner_2_3_side * corner3_radius;
let corner3_y = port_line_end.y;
let corner2_y = corner3_y + corner3_radius - corner2_radius;
let corner2_y = max(corner2_y, corner1.y);
let corner3_y = max(corner3_y,corner2_y - corner3_radius + corner2_radius);
let corner3 = Vector2::new(corner3_x*side,corner3_y);
let corner3_angle = if is_right_side {0.0} else {-RIGHT_ANGLE};
if target_attached {
fg.corner3.shape.sprite.size().set(zero());
bg.corner3.shape.sprite.size().set(corner3_size);
bg.corner3.shape.start_angle.set(corner3_angle);
bg.corner3.shape.angle.set(RIGHT_ANGLE);
bg.corner3.shape.radius.set(corner3_radius);
bg.corner3.shape.pos.set(corner3);
bg.corner3.shape.dim.set(Vector2::new(INFINITE,INFINITE));
bg.corner3.set_position_xy(corner3);
} else {
bg.corner3.shape.sprite.size().set(zero());
fg.corner3.shape.sprite.size().set(corner3_size);
fg.corner3.shape.start_angle.set(corner3_angle);
fg.corner3.shape.angle.set(RIGHT_ANGLE);
fg.corner3.shape.radius.set(corner3_radius);
fg.corner3.shape.pos.set(corner3);
fg.corner3.shape.dim.set(zero());
fg.corner3.set_position_xy(corner3);
}
let corner2_x = corner1_target.x + corner_2_3_side * corner2_radius;
let corner2 = Vector2::new(corner2_x*side,corner2_y);
let corner2_angle = if is_right_side {-RIGHT_ANGLE} else {0.0};
if target_attached {
fg.corner2.shape.sprite.size().set(zero());
bg.corner2.shape.sprite.size().set(corner1_size);
bg.corner2.shape.start_angle.set(corner2_angle);
bg.corner2.shape.angle.set(RIGHT_ANGLE);
bg.corner2.shape.radius.set(corner2_radius);
bg.corner2.shape.pos.set(corner2);
bg.corner2.shape.dim.set(Vector2::new(INFINITE,INFINITE));
bg.corner2.set_position_xy(corner2);
} else {
bg.corner2.shape.sprite.size().set(zero());
fg.corner2.shape.sprite.size().set(corner1_size);
fg.corner2.shape.start_angle.set(corner2_angle);
fg.corner2.shape.angle.set(RIGHT_ANGLE);
fg.corner2.shape.radius.set(corner2_radius);
fg.corner2.shape.pos.set(corner2);
fg.corner2.shape.dim.set(zero());
fg.corner2.set_position_xy(corner2);
}
// === Main Line (upwards) ===
//
// Main line is the first vertical line of the edge placed between the corner 1 and the
// corner 2. In case the line is long enough, it has an arrow pointing up to show its
// direction.
//
// ╭─────╮ 2╭──╮3
// ╰─────╯──╯1 ▢
let main_line_len = corner2_y - corner1.y;
let main_line_start = Vector2::new(side*corner1_target.x,corner1.y);
if target_attached {
fg.main_line.shape.sprite.size().set(zero());
bg.main_line.layout_v(main_line_start, main_line_len);
} else {
bg.main_line.shape.sprite.size().set(zero());
fg.main_line.layout_v(main_line_start, main_line_len);
}
if main_line_len > 2.0 {
let arrow_y = (corner1.y - corner1_radius + corner2_y + corner2_radius)/2.0;
let arrow_pos = Vector2::new(main_line_start.x, arrow_y);
let arrow_size = Vector2::new(20.0,20.0);
if target_attached {
fg.arrow.shape.sprite.size().set(zero());
bg.arrow.shape.sprite.size().set(arrow_size);
bg.arrow.set_position_xy(arrow_pos);
} else {
bg.arrow.shape.sprite.size().set(zero());
fg.arrow.shape.sprite.size().set(arrow_size);
fg.arrow.set_position_xy(arrow_pos);
}
} else {
bg.arrow.shape.sprite.size().set(zero());
fg.arrow.shape.sprite.size().set(zero());
}
// === Side Line 2 ===
//
// Side line 2 is the horizontal line connecting corner 2 and corner 3.
//
// ╭─────╮ 2╭──╮3
// ╰─────╯──╯1 ▢
let side_line2_len = side*(corner3_x - corner2_x);
let side_line2_start = Vector2::new(side*corner2_x,corner2_y + corner2_radius);
if target_attached {
fg.side_line2.shape.sprite.size().set(zero());
bg.side_line2.layout_h(side_line2_start,side_line2_len);
} else {
bg.side_line2.shape.sprite.size().set(zero());
fg.side_line2.layout_h(side_line2_start,side_line2_len);
}
port_line_len = corner3_y - port_line_start.y;
} else {
fg.arrow.shape.sprite.size().set(zero());
bg.arrow.shape.sprite.size().set(zero());
fg.corner3.shape.sprite.size().set(zero());
bg.corner3.shape.sprite.size().set(zero());
fg.corner2.shape.sprite.size().set(zero());
bg.corner2.shape.sprite.size().set(zero());
fg.side_line2.shape.sprite.size().set(zero());
bg.side_line2.shape.sprite.size().set(zero());
}
// === Port Line ===
fg.port_line.layout_v(port_line_start, port_line_len);
}
}

View File

@ -23,76 +23,11 @@ use ensogl::gui::component::animation;
use ensogl::gui::component::animation2;
use ensogl::gui::component;
use super::connection::Connection;
use super::edge;
use crate::component::visualization;
// =============
// === Icons ===
// =============
/// Icons definitions.
pub mod icons {
use super::*;
/// History icon.
pub fn history() -> AnyShape {
let radius_diff = 0.5.px();
let corners_radius = 2.0.px();
let width_diff = &corners_radius * 3.0;
let offset = 2.px();
let width = 32.px();
let height = 16.px();
let persp_diff1 = 6.px();
let width2 = &width - &width_diff;
let width3 = &width2 - &width_diff;
let corners_radius2 = &corners_radius - &radius_diff;
let corners_radius3 = &corners_radius2 - &radius_diff;
let persp_diff2 = &persp_diff1 * 2.0;
let rect1 = Rect((&width ,&height)).corners_radius(&corners_radius);
let rect2 = Rect((&width2,&height)).corners_radius(&corners_radius2).translate_y(&persp_diff1);
let rect3 = Rect((&width3,&height)).corners_radius(&corners_radius3).translate_y(&persp_diff2);
let rect3 = rect3 - rect2.translate_y(&offset);
let rect2 = rect2 - rect1.translate_y(&offset);
let rect1 = rect1.fill(color::Rgba::new(0.26, 0.69, 0.99, 1.00));
let rect2 = rect2.fill(color::Rgba::new(0.26, 0.69, 0.99, 0.6));
let rect3 = rect3.fill(color::Rgba::new(0.26, 0.69, 0.99, 0.4));
let icon = (rect3 + rect2 + rect1).translate_y(-persp_diff2/2.0);
icon.into()
}
}
/// Ring angle shape definition.
pub fn ring_angle<R,W,A>(inner_radius:R, width:W, angle:A) -> AnyShape
where R : Into<Var<Distance<Pixels>>>,
W : Into<Var<Distance<Pixels>>>,
A : Into<Var<Angle<Radians>>> {
let inner_radius = inner_radius.into();
let width = width.into();
let angle = angle.into();
let angle2 = &angle / 2.0;
let radius = &width / 2.0;
let inner = Circle(&inner_radius);
let outer = Circle(&inner_radius + &width);
let section = Plane().cut_angle(&angle);
let corner1 = Circle(&radius).translate_y(inner_radius + radius);
let corner2 = corner1.rotate(&angle2);
let corner1 = corner1.rotate(-&angle2);
let ring = &outer - &inner;
let pie = &ring * &section;
let out = &pie + &corner1 + &corner2;
let out = out.fill(color::Rgba::new(0.9,0.9,0.9,1.0));
out.into()
}
const NODE_SHAPE_PADDING : f32 = 40.0;
@ -108,7 +43,7 @@ pub mod shape {
(style:Style, selection:f32) {
let bg_color = style.get("graph_editor.node.background.color").color().unwrap_or_else(|| color::Rgba::new(1.0,0.0,0.0,1.0).into());
let selection_color = style.get("graph_editor.node.selection.color").color().unwrap_or_else(|| color::Rgba::new(1.0,0.0,0.0,1.0).into());
let selection_size = style.get("graph_editor.node.selection.size").number().unwrap_or(8.0);
let _selection_size = style.get("graph_editor.node.selection.size").number().unwrap_or(8.0);
let border_size_f = 16.0;
@ -123,7 +58,7 @@ pub mod shape {
// === Shadow ===
let shadow_size = 14.px();
let shadow_size = SHADOW_SIZE.px();
let shadow_width = &width + &shadow_size * 2.0;
let shadow_height = &height + &shadow_size * 2.0;
let shadow_radius = &shadow_height / 2.0;
@ -131,17 +66,25 @@ pub mod shape {
let shadow_color = color::LinearGradient::new()
.add(0.0,color::Rgba::new(0.0,0.0,0.0,0.0).into_linear())
.add(1.0,color::Rgba::new(0.0,0.0,0.0,0.20).into_linear());
let shadow_color = color::SdfSampler::new(shadow_color).max_distance(border_size_f).slope(color::Slope::Exponent(4.0));
let shadow_color = color::SdfSampler::new(shadow_color).max_distance(border_size_f).slope(color::Slope::Exponent(2.0));
let shadow = shadow.fill(shadow_color);
// === Selection ===
let selection_size = selection_size.px();
let select_width = &width - 2.px() + &selection_size * 2.0 * &selection;
let select_height = &height - 2.px() + &selection_size * 2.0 * &selection;
let selection_offset = 4.px();
let selection_size = 7.px();
let select_width = &width - 2.px() + &selection_offset * 2.0 * &selection;
let select_height = &height - 2.px() + &selection_offset * 2.0 * &selection;
let select_radius = &select_height / 2.0;
let select = Rect((select_width,select_height)).corners_radius(select_radius);
let select = Rect((&select_width,&select_height)).corners_radius(&select_radius);
let select2_width = &width - 2.px() + &selection_size * 2.0 * &selection;
let select2_height = &height - 2.px() + &selection_size * 2.0 * &selection;
let select2_radius = &select2_height / 2.0;
let select2 = Rect((&select2_width,&select2_height)).corners_radius(&select2_radius);
let select = select2 - select;
let select = select.fill(color::Rgba::from(selection_color));
let out = select + shadow + shape;
@ -271,12 +214,13 @@ impl Deref for Frp {
// === Node ===
// ============
// FIXME: Remove all Weak nodes - no needed anymore
/// Node definition.
#[derive(AsRef,Clone,CloneRef,Debug,Deref)]
/// Internal data of `Node`
#[derive(Clone,CloneRef,Debug)]
#[allow(missing_docs)]
pub struct Node {
data : Rc<NodeModelWithNetwork>,
pub model : Rc<NodeModel>,
pub frp_network : frp::Network,
}
impl AsRef<Node> for Node {
@ -285,42 +229,8 @@ impl AsRef<Node> for Node {
}
}
/// Weak version of `Node`.
#[derive(Clone,CloneRef,Debug)]
pub struct WeakNode {
data : Weak<NodeModelWithNetwork>
}
impl WeakElement for WeakNode {
type Strong = Node;
fn new(view: &Self::Strong) -> Self {
view.downgrade()
}
fn view(&self) -> Option<Self::Strong> {
self.upgrade()
}
}
impl WeakKey for WeakNode {
type Key = display::object::Id;
fn with_key<F, R>(view: &Self::Strong, f: F) -> R where F: FnOnce(&Self::Key) -> R {
f(&view.id())
}
}
/// Internal data of `Node`
#[derive(Clone,CloneRef,Debug)]
#[allow(missing_docs)]
pub struct NodeModelWithNetwork {
pub model : Rc<NodeModel>,
pub frp_network : frp::Network,
}
impl Deref for NodeModelWithNetwork {
impl Deref for Node {
type Target = NodeModel;
fn deref(&self) -> &Self::Target {
&self.model
@ -342,16 +252,9 @@ pub struct NodeModel {
pub visualization_container : visualization::Container,
}
//pub const NODE_WIDTH : f32 = 284.0;
pub const NODE_HEIGHT : f32 = 28.0;
pub const TEXT_OFF : f32 = 12.0;
impl Node {
pub fn new(scene:&Scene) -> Self {
let data = Rc::new(NodeModelWithNetwork::new(scene));
Self {data}
}
}
pub const TEXT_OFF : f32 = 10.0;
pub const SHADOW_SIZE : f32 = 10.0;
impl NodeModel {
@ -359,11 +262,13 @@ impl NodeModel {
pub fn new(scene:&Scene, network:&frp::Network) -> Self {
let logger = Logger::new("node");
let _connection = Connection::new(scene); // FIXME hack for sorting
edge::sort_hack_1(scene);
let output_area = component::ShapeView::<output_area::Shape>::new(&logger.sub("output_area"),scene);
let main_area = component::ShapeView::<shape::Shape>::new(&logger.sub("main_area"),scene);
let drag_area = component::ShapeView::<drag_area::Shape>::new(&logger.sub("drag_area"),scene);
edge::sort_hack_2(scene);
port::sort_hack(scene); // FIXME hack for sorting
let display_object = display::object::Instance::new(&logger);
@ -435,7 +340,7 @@ impl NodeModel {
impl NodeModelWithNetwork {
impl Node {
pub fn new(scene:&Scene) -> Self {
let frp_network = frp::Network::new();
let model = Rc::new(NodeModel::new(scene,&frp_network));
@ -448,15 +353,15 @@ impl NodeModelWithNetwork {
let (output_area_size_setter, output_area_size) = animation2(&frp_network);
frp::extend! { frp_network
eval_ inputs.select (selection.set_target_position(1.0));
eval_ inputs.deselect (selection.set_target_position(0.0));
eval_ inputs.select (selection.set_target_value(1.0));
eval_ inputs.deselect (selection.set_target_value(0.0));
eval inputs.set_expression ((expr) model.set_expression(expr));
eval output_area_size ((size) model.output_area.shape.grow.set(*size));
eval_ model.output_area.events.mouse_over (output_area_size_setter.set_target_position(1.0));
eval_ model.output_area.events.mouse_out (output_area_size_setter.set_target_position(0.0));
eval_ model.output_area.events.mouse_over (output_area_size_setter.set_target_value(1.0));
eval_ model.output_area.events.mouse_out (output_area_size_setter.set_target_value(0.0));
eval inputs.set_visualization ((content)
model.visualization_container.frp.set_visualization.emit(content)
@ -468,29 +373,75 @@ impl NodeModelWithNetwork {
}
impl StrongRef for Node {
type WeakRef = WeakNode;
fn downgrade(&self) -> WeakNode {
WeakNode {data:Rc::downgrade(&self.data)}
}
}
impl WeakRef for WeakNode {
type StrongRef = Node;
fn upgrade(&self) -> Option<Node> {
self.data.upgrade().map(|data| Node{data})
}
}
impl display::Object for Node {
fn display_object(&self) -> &display::object::Instance {
&self.display_object
}
}
impl display::WeakObject for WeakNode {
fn try_display_object(&self) -> Option<display::object::Instance> {
self.upgrade().map(|ref t| t.display_object().clone_ref())
}
}
//// =============
//// === Icons ===
//// =============
//
///// Icons definitions.
//pub mod icons {
// use super::*;
//
// /// History icon.
// pub fn history() -> AnyShape {
// let radius_diff = 0.5.px();
// let corners_radius = 2.0.px();
// let width_diff = &corners_radius * 3.0;
// let offset = 2.px();
// let width = 32.px();
// let height = 16.px();
// let persp_diff1 = 6.px();
//
// let width2 = &width - &width_diff;
// let width3 = &width2 - &width_diff;
// let corners_radius2 = &corners_radius - &radius_diff;
// let corners_radius3 = &corners_radius2 - &radius_diff;
// let persp_diff2 = &persp_diff1 * 2.0;
//
// let rect1 = Rect((&width ,&height)).corners_radius(&corners_radius);
// let rect2 = Rect((&width2,&height)).corners_radius(&corners_radius2).translate_y(&persp_diff1);
// let rect3 = Rect((&width3,&height)).corners_radius(&corners_radius3).translate_y(&persp_diff2);
//
// let rect3 = rect3 - rect2.translate_y(&offset);
// let rect2 = rect2 - rect1.translate_y(&offset);
//
// let rect1 = rect1.fill(color::Rgba::new(0.26, 0.69, 0.99, 1.00));
// let rect2 = rect2.fill(color::Rgba::new(0.26, 0.69, 0.99, 0.6));
// let rect3 = rect3.fill(color::Rgba::new(0.26, 0.69, 0.99, 0.4));
//
// let icon = (rect3 + rect2 + rect1).translate_y(-persp_diff2/2.0);
// icon.into()
// }
//}
//
///// Ring angle shape definition.
//pub fn ring_angle<R,W,A>(inner_radius:R, width:W, angle:A) -> AnyShape
// where R : Into<Var<Distance<Pixels>>>,
// W : Into<Var<Distance<Pixels>>>,
// A : Into<Var<Angle<Radians>>> {
// let inner_radius = inner_radius.into();
// let width = width.into();
// let angle = angle.into();
//
// let angle2 = &angle / 2.0;
// let radius = &width / 2.0;
// let inner = Circle(&inner_radius);
// let outer = Circle(&inner_radius + &width);
// let section = Plane().cut_angle(&angle);
// let corner1 = Circle(&radius).translate_y(inner_radius + radius);
// let corner2 = corner1.rotate(&angle2);
// let corner1 = corner1.rotate(-&angle2);
// let ring = &outer - &inner;
// let pie = &ring * &section;
// let out = &pie + &corner1 + &corner2;
// let out = out.fill(color::Rgba::new(0.9,0.9,0.9,1.0));
// out.into()
//}
//

View File

@ -109,11 +109,13 @@ pub fn sort_hack(scene:&Scene) {
#[derive(Debug,Clone,CloneRef)]
pub struct Events {
pub network : frp::Network,
pub cursor_mode : frp::Stream<cursor::Mode>,
pub press : frp::Stream<span_tree::Crumbs>,
press_source : frp::Source<span_tree::Crumbs>,
cursor_mode_source : frp::Merge<cursor::Mode>,
pub network : frp::Network,
pub cursor_style : frp::Stream<cursor::Style>,
pub press : frp::Stream<span_tree::Crumbs>,
pub hover : frp::Stream<Option<span_tree::Crumbs>>,
press_source : frp::Source<span_tree::Crumbs>,
hover_source : frp::Source<Option<span_tree::Crumbs>>,
cursor_style_source : frp::Any<cursor::Style>,
}
@ -172,8 +174,9 @@ pub struct Manager {
impl Manager {
pub fn new(logger:&Logger, scene:&Scene) -> Self {
frp::new_network! { network
def cursor_mode_source = gather::<cursor::Mode>();
def press_source = source::<span_tree::Crumbs>();
cursor_style_source <- any_mut::<cursor::Style>();
press_source <- source::<span_tree::Crumbs>();
hover_source <- source::<Option<span_tree::Crumbs>>();
}
let logger = logger.sub("port_manager");
@ -184,9 +187,11 @@ impl Manager {
let label = component::ShapeView::<label::Shape>::new(&logger,&scene);
let ports = default();
let width = default();
let cursor_mode = (&cursor_mode_source).into();
let cursor_style = (&cursor_style_source).into();
let press = (&press_source).into();
let frp = Events {network,cursor_mode,press,cursor_mode_source,press_source};
let hover = (&hover_source).into();
let frp = Events
{network,cursor_style,press,hover,cursor_style_source,press_source,hover_source};
label.mod_position(|t| t.y -= 4.0);
@ -222,7 +227,7 @@ impl Manager {
let port = component::ShapeView::<shape::Shape>::new(&logger,&self.scene);
let unit = 7.224_609_4;
let width = unit * span.size.value as f32;
let width2 = width + 4.0;
let width2 = width + 8.0;
let node_height = 28.0;
let height = 18.0;
port.shape.sprite.size().set(Vector2::new(width2,node_height));
@ -237,16 +242,21 @@ impl Manager {
def _foo = port.events.mouse_over . map(f_!(hover.set(1.0);));
def _foo = port.events.mouse_out . map(f_!(hover.set(0.0);));
def out = port.events.mouse_out.constant(cursor::Mode::Normal);
def over = port.events.mouse_over.constant(cursor::Mode::highlight(&port,Vector2::new(x,0.0),Vector2::new(width2,height)));
def out = port.events.mouse_out.constant(cursor::Style::default());
def over = port.events.mouse_over.constant(cursor::Style::highlight(&port,Vector2::new(width2,height),Some(color::Lcha::new(0.6,0.5,0.76,1.0))));
// FIXME: the following lines leak memory in the current FRP
// implementation because self.frp does not belong to this network and
// we are attaching node there. Nothing bad should happen though.
self.frp.cursor_mode_source.attach(&over);
self.frp.cursor_mode_source.attach(&out);
self.frp.cursor_style_source.attach(&over);
self.frp.cursor_style_source.attach(&out);
let crumbs_down = crumbs.clone();
let crumbs_over = crumbs.clone();
let press_source = &self.frp.press_source;
def _press = port.events.mouse_down.map(f_!(press_source.emit(&crumbs)));
let hover_source = &self.frp.hover_source;
eval_ port.events.mouse_down (press_source.emit(&crumbs_down));
eval_ port.events.mouse_over (hover_source.emit(&Some(crumbs_over.clone())));
eval_ port.events.mouse_out (hover_source.emit(&None));
}
ports.push(port);
port_networks.push(port_network);

View File

@ -25,7 +25,6 @@
#![recursion_limit="1024"]
#[warn(missing_docs)]
pub mod component;
@ -34,28 +33,28 @@ pub mod prelude {
pub use ensogl::prelude::*;
}
use ensogl::application;
use ensogl::prelude::*;
use ensogl::traits::*;
use ensogl::application::shortcut;
use crate::component::cursor::Cursor;
use crate::component::cursor;
use crate::component::node;
use crate::component::node::Node as NodeView;
use crate::component::connection::Connection as EdgeView;
use crate::component::visualization::MockDataGenerator3D;
use crate::component::visualization::Visualization;
use crate::component::visualization;
use enso_frp as frp;
use enso_frp::io::keyboard;
use enso_frp::Position;
use ensogl::application::shortcut;
use ensogl::application;
use ensogl::data::color;
use ensogl::display::object::Id;
use ensogl::display::Scene;
use ensogl::display::world::*;
use ensogl::display;
use ensogl::prelude::*;
use ensogl::system::web::StyleSetter;
use ensogl::system::web;
use ensogl::traits::*;
use nalgebra::Vector2;
use ensogl::display::Scene;
use crate::component::visualization::Visualization;
use crate::component::visualization;
use crate::component::visualization::MockDataGenerator3D;
@ -111,6 +110,10 @@ where T:Eq+Hash, S:std::hash::BuildHasher {
}
impl<T,S> SharedHashSet<T,S> {
pub fn is_empty(&self) -> bool {
self.raw.borrow().is_empty()
}
pub fn clear(&self) {
self.raw.borrow_mut().clear()
}
@ -337,59 +340,74 @@ impl Commands {
#[derive(Debug,Clone,CloneRef,Shrinkwrap)]
pub struct FrpInputs {
#[shrinkwrap(main_field)]
commands : Commands,
pub connect_detached_edges_to_node : frp::Source<EdgeTarget>,
pub connect_edge_source : frp::Source<(EdgeId,EdgeTarget)>,
pub connect_edge_target : frp::Source<(EdgeId,EdgeTarget)>,
pub connect_nodes : frp::Source<(EdgeTarget,EdgeTarget)>,
pub deselect_all_nodes : frp::Source,
pub press_node_input : frp::Source<EdgeTarget>,
pub remove_all_node_edges : frp::Source<NodeId>,
pub remove_all_node_input_edges : frp::Source<NodeId>,
pub remove_all_node_output_edges : frp::Source<NodeId>,
pub remove_edge : frp::Source<EdgeId>,
pub select_node : frp::Source<NodeId>,
pub remove_node : frp::Source<NodeId>,
pub set_node_expression : frp::Source<(NodeId,node::Expression)>,
pub set_node_position : frp::Source<(NodeId,Position)>,
pub set_visualization_data : frp::Source<NodeId>,
pub translate_selected_nodes : frp::Source<Position>,
pub cycle_visualization : frp::Source<NodeId>,
pub set_visualization : frp::Source<(NodeId,Option<Visualization>)>,
pub register_visualization_class : frp::Source<Option<Rc<visualization::Handle>>>,
commands : Commands,
pub set_detached_edge_targets : frp::Source<EdgeTarget>,
pub set_edge_source : frp::Source<(EdgeId,EdgeTarget)>,
pub set_edge_target : frp::Source<(EdgeId,EdgeTarget)>,
pub unset_edge_source : frp::Source<EdgeId>,
pub unset_edge_target : frp::Source<EdgeId>,
pub connect_nodes : frp::Source<(EdgeTarget,EdgeTarget)>,
pub deselect_all_nodes : frp::Source,
pub press_node_input : frp::Source<EdgeTarget>,
pub remove_all_node_edges : frp::Source<NodeId>,
pub remove_all_node_input_edges : frp::Source<NodeId>,
pub remove_all_node_output_edges : frp::Source<NodeId>,
pub remove_edge : frp::Source<EdgeId>,
pub select_node : frp::Source<NodeId>,
pub remove_node : frp::Source<NodeId>,
pub set_node_expression : frp::Source<(NodeId,node::Expression)>,
pub set_node_position : frp::Source<(NodeId,Position)>,
pub set_visualization_data : frp::Source<NodeId>,
pub translate_selected_nodes : frp::Source<Position>,
pub cycle_visualization : frp::Source<NodeId>,
pub set_visualization : frp::Source<(NodeId,Option<Visualization>)>,
pub register_visualization_class : frp::Source<Option<Rc<visualization::Handle>>>,
hover_node_input : frp::Source<Option<EdgeTarget>>,
some_edge_targets_detached : frp::Source,
all_edge_targets_attached : frp::Source,
}
impl FrpInputs {
pub fn new(network:&frp::Network) -> Self {
frp::extend! { network
def connect_detached_edges_to_node = source();
def connect_edge_source = source();
def connect_edge_target = source();
def connect_nodes = source();
def deselect_all_nodes = source();
def press_node_input = source();
def remove_all_node_edges = source();
def remove_all_node_input_edges = source();
def remove_all_node_output_edges = source();
def remove_edge = source();
def select_node = source();
def remove_node = source();
def set_node_expression = source();
def set_node_position = source();
def set_visualization_data = source();
def translate_selected_nodes = source();
def cycle_visualization = source();
def set_visualization = source();
def register_visualization_class = source();
def set_detached_edge_targets = source();
def set_edge_source = source();
def set_edge_target = source();
def unset_edge_source = source();
def unset_edge_target = source();
def connect_nodes = source();
def deselect_all_nodes = source();
def press_node_input = source();
def remove_all_node_edges = source();
def remove_all_node_input_edges = source();
def remove_all_node_output_edges = source();
def remove_edge = source();
def select_node = source();
def remove_node = source();
def set_node_expression = source();
def set_node_position = source();
def set_visualization_data = source();
def translate_selected_nodes = source();
def cycle_visualization = source();
def set_visualization = source();
def register_visualization_class = source();
def hover_node_input = source();
def some_edge_targets_detached = source();
def all_edge_targets_attached = source();
}
let commands = Commands::new(&network);
Self {commands,remove_edge,press_node_input,remove_all_node_edges
,remove_all_node_input_edges,remove_all_node_output_edges,set_visualization_data
,connect_detached_edges_to_node,connect_edge_source,connect_edge_target
,set_detached_edge_targets,set_edge_source,set_edge_target
,unset_edge_source,unset_edge_target
,set_node_position,select_node,remove_node,translate_selected_nodes,set_node_expression
,connect_nodes,deselect_all_nodes,cycle_visualization,set_visualization
,register_visualization_class
}
,register_visualization_class,some_edge_targets_detached,all_edge_targets_attached
,hover_node_input
}
}
}
@ -430,7 +448,7 @@ macro_rules! generate_frp_outputs {
#[derive(Debug,Clone,CloneRef)]
pub struct UnsealedFrpOutputs {
network : frp::Network,
$($field : frp::Merge<$field_ty>),*
$($field : frp::Any<$field_ty>),*
}
#[derive(Debug,Clone,CloneRef)]
@ -443,7 +461,7 @@ macro_rules! generate_frp_outputs {
impl UnsealedFrpOutputs {
pub fn new() -> Self {
frp::new_network! { network
$(def $field = gather();)*
$(def $field = any_mut();)*
}
Self {network, $($field),*}
}
@ -472,6 +490,11 @@ generate_frp_outputs! {
edge_removed : EdgeId,
edge_source_set : (EdgeId,EdgeTarget),
edge_target_set : (EdgeId,EdgeTarget),
edge_source_unset : EdgeId,
edge_target_unset : EdgeId,
some_edge_targets_detached : (),
all_edge_targets_attached : (),
connection_added : EdgeId,
connection_removed : EdgeId,
@ -486,7 +509,7 @@ generate_frp_outputs! {
#[derive(Clone,CloneRef,Debug)]
pub struct Node {
pub view : NodeView,
pub view : component::Node,
pub in_edges : SharedHashSet<EdgeId>,
pub out_edges : SharedHashSet<EdgeId>,
}
@ -495,7 +518,7 @@ pub struct Node {
pub struct NodeId(pub Id);
impl Node {
pub fn new(view:NodeView) -> Self {
pub fn new(view:component::Node) -> Self {
let in_edges = default();
let out_edges = default();
Self {view,in_edges,out_edges}
@ -520,7 +543,7 @@ impl display::Object for Node {
#[derive(Clone,CloneRef,Debug)]
pub struct Edge {
pub view : EdgeView,
pub view : component::Edge,
source : Rc<RefCell<Option<EdgeTarget>>>,
target : Rc<RefCell<Option<EdgeTarget>>>,
}
@ -529,7 +552,7 @@ pub struct Edge {
pub struct EdgeId(pub Id);
impl Edge {
pub fn new(view:EdgeView) -> Self {
pub fn new(view:component::Edge) -> Self {
let source = default();
let target = default();
Self {view,source,target}
@ -579,6 +602,7 @@ impl display::Object for Edge {
}
// ==================
// === EdgeTarget ===
// ==================
@ -590,8 +614,9 @@ pub struct EdgeTarget {
}
impl EdgeTarget {
pub fn new(node_id:NodeId, port:span_tree::Crumbs) -> Self {
let port = Rc::new(port);
pub fn new(node_id:impl Into<NodeId>, port:span_tree::Crumbs) -> Self {
let node_id = node_id.into();
let port = Rc::new(port);
Self {node_id,port}
}
}
@ -675,17 +700,17 @@ pub struct TouchNetwork<T:frp::Data> {
impl<T:frp::Data> TouchNetwork<T> {
pub fn new(network:&frp::Network, mouse:&frp::io::Mouse) -> Self {
frp::extend! { network
def down = source::<T> ();
def down_bool = down.map(|_| true);
def up_bool = mouse.release.map(|_| false);
def is_down = down_bool.merge(&up_bool);
def was_down = is_down.previous();
def mouse_up = mouse.release.gate(&was_down);
def pos_on_down = mouse.position.sample(&down);
def pos_on_up = mouse.position.sample(&mouse_up);
def should_select = pos_on_up.map3(&pos_on_down,&mouse.distance,Self::check);
def up = down.sample(&mouse_up);
def selected = up.gate(&should_select);
down <- source::<T> ();
down_bool <- down.map(|_| true);
up_bool <- mouse.release.map(|_| false);
is_down <- any (down_bool,up_bool);
was_down <- is_down.previous();
mouse_up <- mouse.release.gate(&was_down);
pos_on_down <- mouse.position.sample(&down);
pos_on_up <- mouse.position.sample(&mouse_up);
should_select <- pos_on_up.map3(&pos_on_down,&mouse.distance,Self::check);
up <- down.sample(&mouse_up);
selected <- up.gate(&should_select);
}
Self {down,up,is_down,selected}
}
@ -704,8 +729,8 @@ pub struct TouchState {
impl TouchState {
pub fn new(network:&frp::Network, mouse:&frp::io::Mouse) -> Self {
let nodes = TouchNetwork::<NodeId>::new(&network,mouse);
let background = TouchNetwork::<()>::new(&network,mouse);
let nodes = TouchNetwork::<NodeId>::new(&network,mouse);
let background = TouchNetwork::<()>::new(&network,mouse);
Self {nodes,background}
}
}
@ -745,56 +770,37 @@ impl Deref for GraphEditorModelWithNetwork {
}
impl GraphEditorModelWithNetwork {
pub fn new<S:Into<Scene>>(scene:S, cursor:Cursor) -> Self {
pub fn new<S:Into<Scene>>(scene:S, cursor:component::Cursor) -> Self {
let network = frp::Network::new();
let model = GraphEditorModel::new(scene,cursor,&network);
Self {model,network}
}
fn new_node(&self, outputs:&UnsealedFrpOutputs) -> NodeId {
let view = NodeView::new(&self.scene);
fn new_node
( &self
, cursor_style : &frp::Source<cursor::Style>
, output_press : &frp::Source<NodeId>
) -> NodeId {
let view = component::Node::new(&self.scene);
let node = Node::new(view);
let node_id = node.id();
self.add_child(&node);
let cursor = &self.cursor;
let touch = &self.touch_state;
let model = &self.model;
let touch = &self.touch_state;
let model = &self.model;
frp::new_bridge_network! { [self.network, node.view.main_area.events.network]
def _node_on_down_tagged = node.view.drag_area.events.mouse_down.map(f_!(
touch.nodes.down.emit(node_id)
));
def _cursor_mode = node.view.ports.frp.cursor_mode.map(f!((mode)
cursor.frp.set_mode.emit(mode)
));
def new_edge = node.view.frp.output_ports.mouse_down.map(f_!([model] {
if let Some(node) = model.nodes.get_cloned_ref(&node_id) {
let view = EdgeView::new(&model.scene);
model.add_child(&view);
let edge = Edge::new(view);
let edge_id = edge.id();
model.edges.insert(edge);
model.edges.detached_target.insert(edge_id);
node.out_edges.insert(edge_id);
edge_id
} else { default() }
}));
outputs.edge_added <+ new_edge;
def new_edge_source = new_edge.map(move |id| (*id,EdgeTarget::new(node_id,default())));
outputs.edge_source_set <+ new_edge_source;
def _eval = new_edge.map2(&cursor.frp.position,f!([model](id,position){
if let Some(edge) = model.edges.get_cloned_ref(id) {
edge.view.events.target_position.emit(position)
}
}));
def _press_node_input = node.view.ports.frp.press.map(f!((crumbs)
eval_ node.view.drag_area.events.mouse_down(touch.nodes.down.emit(node_id));
eval node.view.ports.frp.cursor_style ((style) cursor_style.emit(style));
eval_ node.view.frp.output_ports.mouse_down (output_press.emit(node_id));
eval node.view.ports.frp.press ((crumbs)
model.frp.press_node_input.emit(EdgeTarget::new(node_id,crumbs.clone()))
));
);
eval node.view.ports.frp.hover ([model](crumbs) {
let target = crumbs.as_ref().map(|c| EdgeTarget::new(node_id,c.clone()));
model.frp.hover_node_input.emit(target);
});
}
self.nodes.insert(node_id,node);
@ -819,7 +825,7 @@ pub struct GraphEditorModel {
pub logger : Logger,
pub display_object : display::object::Instance,
pub scene : Scene,
pub cursor : Cursor,
pub cursor : component::Cursor,
pub nodes : Nodes,
pub edges : Edges,
touch_state : TouchState,
@ -829,7 +835,7 @@ pub struct GraphEditorModel {
// === Public ===
impl GraphEditorModel {
pub fn new<S:Into<Scene>>(scene:S, cursor:Cursor, network:&frp::Network) -> Self {
pub fn new<S:Into<Scene>>(scene:S, cursor:component::Cursor, network:&frp::Network) -> Self {
let scene = scene.into();
let logger = Logger::new("GraphEditor");
let display_object = display::object::Instance::new(logger.clone());
@ -841,10 +847,18 @@ impl GraphEditorModel {
}
fn new_edge(&self) -> EdgeId {
let edge = Edge::new(EdgeView::new(&self.scene));
let edge = Edge::new(component::Edge::new(&self.scene));
let edge_id = edge.id();
self.add_child(&edge);
self.edges.insert(edge.clone_ref());
edge.id()
let first_detached = self.edges.detached_target.is_empty();
self.edges.detached_target.insert(edge_id);
if first_detached {
self.frp.some_edge_targets_detached.emit(());
}
edge_id
}
pub fn all_nodes(&self) -> Vec<NodeId> {
@ -951,11 +965,12 @@ impl GraphEditorModel {
// === Connect ===
impl GraphEditorModel {
fn connect_edge_source(&self, edge_id:EdgeId, target:&EdgeTarget) {
fn set_edge_source(&self, edge_id:EdgeId, target:impl Into<EdgeTarget>) {
let target = target.into();
if let Some(edge) = self.edges.get_cloned_ref(&edge_id) {
if let Some(node) = self.nodes.get_cloned_ref(&target.node_id) {
node.out_edges.insert(edge_id);
edge.set_source(target.clone());
edge.set_source(target);
// FIXME: both lines require edge to refresh. Let's make it more efficient.
self.refresh_edge_position(edge_id);
self.refresh_edge_source_width(edge_id);
@ -963,16 +978,33 @@ impl GraphEditorModel {
}
}
fn connect_edge_target(&self, edge_id:EdgeId, target:&EdgeTarget) {
fn set_edge_target(&self, edge_id:EdgeId, target:impl Into<EdgeTarget>) {
let target = target.into();
if let Some(edge) = self.edges.get_cloned_ref(&edge_id) {
if let Some(node) = self.nodes.get_cloned_ref(&target.node_id) {
node.in_edges.insert(edge_id);
edge.set_target(target.clone());
edge.set_target(target);
self.edges.detached_target.remove(&edge_id);
let all_attached = self.edges.detached_target.is_empty();
if all_attached {
self.frp.all_edge_targets_attached.emit(());
}
edge.view.frp.target_attached.emit(true);
self.refresh_edge_position(edge_id);
};
}
}
fn take_edges_with_detached_targets(&self) -> HashSet<EdgeId> {
let edges = self.edges.detached_target.mem_take();
if !edges.is_empty() {
self.frp.all_edge_targets_attached.emit(());
}
edges
}
fn overlapping_edges(&self, target:&EdgeTarget) -> Vec<EdgeId> {
let mut overlapping = vec![];
if let Some(node) = self.nodes.get_cloned_ref(&target.node_id) {
@ -1030,7 +1062,7 @@ impl GraphEditorModel {
if let Some(edge) = self.edges.get_cloned_ref(&edge_id) {
if let Some(edge_source) = edge.source() {
if let Some(node) = self.nodes.get_cloned_ref(&edge_source.node_id) {
edge.view.events.source_width.emit(node.view.width());
edge.view.frp.source_width.emit(node.view.width());
}
}
};
@ -1056,7 +1088,7 @@ impl GraphEditorModel {
let offset = node.view.ports.get_port_offset(&edge_target.port).unwrap_or_else(|| Vector2::new(0.0,0.0));
let node_position = node.view.position();
let pos = frp::Position::new(node_position.x + offset.x, node_position.y + offset.y);
edge.view.events.target_position.emit(pos);
edge.view.frp.target_position.emit(pos);
}
}
};
@ -1140,7 +1172,7 @@ fn enable_disable_toggle
let disable = disable.clone_ref();
let toggle = toggle.clone_ref();
frp::extend! { network
out <- gather();
out <- any(...);
on_toggle <- toggle.map2(&out,|_,t| !t);
on_enable <- enable.constant(true);
on_disable <- disable.constant(false);
@ -1186,7 +1218,7 @@ impl Default for SelectionMode {
#[allow(unused_parens)]
fn new_graph_editor(world:&World) -> GraphEditor {
let scene = world.scene();
let cursor = Cursor::new(world.scene());
let cursor = component::Cursor::new(world.scene());
web::body().set_style_or_panic("cursor","none");
world.add_child(&cursor);
@ -1207,7 +1239,10 @@ fn new_graph_editor(world:&World) -> GraphEditor {
// === Selection Target Redirection ===
frp::extend! { network
def mouse_down_target = mouse.press.map(f_!(model.scene.mouse.target.get()));
mouse_down_target <- mouse.press.map(f_!(model.scene.mouse.target.get()));
mouse_up_target <- mouse.release.map(f_!(model.scene.mouse.target.get()));
background_up <- mouse_up_target.map(|t| if t==&display::scene::Target::Background {Some(())} else {None}).unwrap();
eval mouse_down_target([touch,model](target) {
match target {
display::scene::Target::Background => touch.background.down.emit(()),
@ -1224,24 +1259,45 @@ fn new_graph_editor(world:&World) -> GraphEditor {
// === Cursor Selection ===
frp::extend! { network
def mouse_on_down_position = mouse.position.sample(&mouse.press);
def selection_zero = source::<Position>();
def selection_size_down = mouse.position.map2(&mouse_on_down_position,|m,n|{m-n});
def selection_size_if_down = selection_size_down.gate(&touch.background.is_down);
def selection_size_on_down = selection_zero.sample(&mouse.press);
def selection_size = selection_size_if_down.merge(&selection_size_on_down);
mouse_on_down_position <- mouse.position.sample(&mouse.press);
selection_size_down <- mouse.position.map2(&mouse_on_down_position,|m,n|{m-n});
selection_size <- selection_size_down.gate(&touch.background.is_down);
on_press_style <- mouse.press . constant(cursor::Style::pressed());
on_release_style <- mouse.release . constant(cursor::Style::default());
cursor_selection_start <- selection_size.map(|p| cursor::Style::selection(Vector2::new(p.x,p.y)));
cursor_selection_end <- mouse.release . constant(cursor::Style::default());
cursor_selection <- any (cursor_selection_start, cursor_selection_end);
cursor_press <- any (on_press_style, on_release_style);
eval selection_size ((p) cursor.set_selection_size(Vector2::new(p.x,p.y)));
eval_ mouse.press (cursor.frp.press.emit(()));
eval_ mouse.release (cursor.frp.release.emit(()));
}
// === Cursor Color ===
frp::extend! { network
let style = cursor::Style::color_no_animation(color::Lcha::new(0.6,0.5,0.76,1.0)).press();
cursor_style_on_edge_drag <- outputs.some_edge_targets_detached.constant(style);
cursor_style_on_edge_drag_stop <- outputs.all_edge_targets_attached.constant(default());
cursor_style_edge_drag <- any (cursor_style_on_edge_drag,cursor_style_on_edge_drag_stop);
}
// === Node Select ===
frp::extend! { network
def deselect_all_nodes = gather_();
deselect_all_nodes <- any_(...);
let multi_select_flag = enable_disable_toggle
( network
@ -1271,7 +1327,8 @@ fn new_graph_editor(world:&World) -> GraphEditor {
, &inputs.toggle_node_inverse_select
);
selection_mode <- zip_with4
selection_mode <- all_with4
(&multi_select_flag,&merge_select_flag,&subtract_select_flag,&inverse_select_flag,
|multi,merge,subtract,inverse| {
if *multi { SelectionMode::Multi }
@ -1284,6 +1341,7 @@ fn new_graph_editor(world:&World) -> GraphEditor {
let node_pressed = touch.nodes.selected.clone_ref();
node_was_selected <- node_pressed.map(f!((id) model.nodes.selected.contains(id)));
should_select <- node_pressed.map3(&selection_mode,&node_was_selected,
@ -1311,11 +1369,52 @@ fn new_graph_editor(world:&World) -> GraphEditor {
}
// === Node Connect ===
// === Add Node ===
frp::extend! { network
outputs.edge_source_set <+ inputs.connect_edge_source;
outputs.edge_target_set <+ inputs.connect_edge_target;
node_cursor_style <- source::<cursor::Style>();
let node_output_touch = TouchNetwork::<NodeId>::new(&network,&mouse);
trace node_output_touch.selected;
on_connect_drag_mode <- node_output_touch.down.constant(true);
on_connect_follow_mode <- node_output_touch.selected.constant(false);
connect_drag_mode <- any (on_connect_drag_mode,on_connect_follow_mode);
new_edge <- node_output_touch.down.map(f_!(model.new_edge()));
outputs.edge_added <+ new_edge;
new_edge_source <- new_edge.map2(&node_output_touch.down, move |id,node_id| (*id,EdgeTarget::new(node_id,default())));
outputs.edge_source_set <+ new_edge_source;
let add_node_at_cursor = inputs.add_node_at_cursor.clone_ref();
add_node <- any (inputs.add_node, add_node_at_cursor);
new_node <- add_node.map(f_!([model,node_cursor_style] model.new_node(&node_cursor_style,&node_output_touch.down)));
outputs.node_added <+ new_node;
node_with_position <- add_node_at_cursor.map3(&new_node,&mouse.position,|_,id,pos| (*id,*pos));
outputs.node_position_set <+ node_with_position;
cursor_style <- all
[ cursor_selection
, cursor_press
, cursor_style_edge_drag
, node_cursor_style
].fold();
eval cursor_style ((style) cursor.frp.set_style.emit(style));
}
// === Node Connect ===
frp::extend! { network
outputs.edge_source_set <+ inputs.set_edge_source;
outputs.edge_target_set <+ inputs.set_edge_target;
let endpoints = inputs.connect_nodes.clone_ref();
edge <- endpoints . map(f_!(model.new_edge()));
@ -1325,35 +1424,32 @@ fn new_graph_editor(world:&World) -> GraphEditor {
outputs.edge_source_set <+ new_edge_source;
outputs.edge_target_set <+ new_edge_target;
new_node_input <- [inputs.press_node_input, inputs.connect_detached_edges_to_node];
detached_targets <= new_node_input.map(f_!(model.edges.detached_target.mem_take()));
new_edge_target <- detached_targets.map2(&new_node_input, |id,t| (*id,t.clone()));
port_mouse_up <- inputs.hover_node_input.sample(&mouse.release).unwrap();
attach_all_edges <- any (port_mouse_up, inputs.press_node_input, inputs.set_detached_edge_targets);
detached_edge <= attach_all_edges.map(f_!(model.take_edges_with_detached_targets()));
new_edge_target <- detached_edge.map2(&attach_all_edges, |id,t| (*id,t.clone()));
outputs.edge_target_set <+ new_edge_target;
overlapping_edges <= outputs.edge_target_set._1().map(f!((t) model.overlapping_edges(t)));
outputs.edge_removed <+ overlapping_edges;
drop_on_bg_up <- background_up.gate(&connect_drag_mode);
drop_edges <- any (drop_on_bg_up,touch.background.down);
edge_to_drop <= drop_edges.map(f_!(model.take_edges_with_detached_targets()));
eval edge_to_drop ((id) model.remove_edge(id));
}
// === Add Node ===
frp::extend! { network
let add_node_at_cursor = inputs.add_node_at_cursor.clone_ref();
add_node <- [inputs.add_node, add_node_at_cursor];
new_node <- add_node.map(f_!([model,outputs] model.new_node(&outputs)));
outputs.node_added <+ new_node;
node_with_position <- add_node_at_cursor.map3(&new_node,&mouse.position,|_,id,pos| (*id,*pos));
outputs.node_position_set <+ node_with_position;
}
// === Remove Node ===
frp::extend! { network
all_nodes <= inputs.remove_all_nodes . map(f_!(model.all_nodes()));
selected_nodes <= inputs.remove_selected_nodes . map(f_!(model.selected_nodes()));
nodes_to_remove <- [all_nodes, selected_nodes];
nodes_to_remove <- any (all_nodes, selected_nodes);
eval nodes_to_remove ((node_id) inputs.remove_all_node_edges.emit(node_id));
outputs.node_removed <+ nodes_to_remove;
@ -1363,16 +1459,16 @@ fn new_graph_editor(world:&World) -> GraphEditor {
// === Remove Edge ===
frp::extend! { network
rm_input_edges <- [inputs.remove_all_node_edges, inputs.remove_all_node_input_edges];
rm_output_edges <- [inputs.remove_all_node_edges, inputs.remove_all_node_output_edges];
rm_input_edges <- any (inputs.remove_all_node_edges, inputs.remove_all_node_input_edges);
rm_output_edges <- any (inputs.remove_all_node_edges, inputs.remove_all_node_output_edges);
input_edges_to_rm <= rm_input_edges . map(f!((node_id) model.node_in_edges(node_id)));
output_edges_to_rm <= rm_output_edges . map(f!((node_id) model.node_out_edges(node_id)));
edges_to_rm <- [inputs.remove_edge, input_edges_to_rm, output_edges_to_rm];
edges_to_rm <- any (inputs.remove_edge, input_edges_to_rm, output_edges_to_rm);
outputs.edge_removed <+ edges_to_rm;
}
// === Set NodeView Expression ===
// === Set Node Expression ===
frp::extend! { network
outputs.node_expression_set <+ inputs.set_node_expression;
@ -1382,10 +1478,10 @@ fn new_graph_editor(world:&World) -> GraphEditor {
node_drag <- mouse.translation.gate(&touch.nodes.is_down);
was_selected <- touch.nodes.down.map(f!((id) model.nodes.selected.contains(id)));
tx_sel_nodes <- [node_drag, inputs.translate_selected_nodes];
tx_sel_nodes <- any (node_drag, inputs.translate_selected_nodes);
non_selected_drag <- tx_sel_nodes.map2(&touch.nodes.down,|_,id|*id).gate_not(&was_selected);
selected_drag <= tx_sel_nodes.map(f_!(model.nodes.selected.keys())).gate(&was_selected);
nodes_to_drag <- [non_selected_drag,selected_drag];
nodes_to_drag <- any (non_selected_drag, selected_drag);
nodes_new_pos <- nodes_to_drag.map2(&tx_sel_nodes,f!((id,tx) model.node_pos_mod(id,tx)));
outputs.node_position_set <+ nodes_new_pos;
@ -1398,13 +1494,15 @@ fn new_graph_editor(world:&World) -> GraphEditor {
// === Move Edges ===
def _move_connections = cursor.frp.position.map(f!([edges](position) {
cursor_pos_on_detach <- cursor.frp.position.sample(&inputs.some_edge_targets_detached);
edge_refresh_cursor_pos <- any (cursor_pos_on_detach,cursor.frp.position);
eval edge_refresh_cursor_pos ([edges](position) {
edges.detached_target.for_each(|id| {
if let Some(edge) = edges.get_cloned_ref(id) {
edge.view.events.target_position.emit(position)
edge.view.frp.target_position.emit(position)
}
})
}));
});
// === Vis Cycling ===
def _cycle_vis = inputs.debug_cycle_visualization_for_selected_node.map(f!([inputs,nodes](_) {
@ -1472,8 +1570,11 @@ fn new_graph_editor(world:&World) -> GraphEditor {
// === OUTPUTS REBIND ===
eval outputs.edge_source_set (((id,tgt)) model.connect_edge_source(*id,tgt));
eval outputs.edge_target_set (((id,tgt)) model.connect_edge_target(*id,tgt));
outputs.some_edge_targets_detached <+ inputs.some_edge_targets_detached;
outputs.all_edge_targets_attached <+ inputs.all_edge_targets_attached;
eval outputs.edge_source_set (((id,tgt)) model.set_edge_source(*id,tgt));
eval outputs.edge_target_set (((id,tgt)) model.set_edge_target(*id,tgt));
eval outputs.node_selected ((id) model.select_node(id));
eval outputs.node_deselected ((id) model.deselect_node(id));
eval outputs.edge_removed ((id) model.remove_edge(id));
@ -1482,9 +1583,9 @@ fn new_graph_editor(world:&World) -> GraphEditor {
// === Connection discovery ===
// === Edge discovery ===
edge_endpoint_set <- [outputs.edge_source_set,outputs.edge_target_set]._0();
edge_endpoint_set <- any (outputs.edge_source_set, outputs.edge_target_set)._0();
both_endpoints_set <- edge_endpoint_set.map(f!((id) model.is_connection(id)));
new_connection <- edge_endpoint_set.gate(&both_endpoints_set);
outputs.connection_added <+ new_connection;

View File

@ -2,7 +2,6 @@
//! various general-purpose instances.
use super::semigroup::Semigroup;
use super::semigroup::SemigroupIm;
@ -24,23 +23,16 @@ pub trait Monoid : Default + Semigroup {
}
}
}
}
/// Immutable Monoid definition.
pub trait MonoidIm : Default + SemigroupIm {
/// Repeat a value n times. Given that this works on a Monoid it will not fail if you request 0
/// or fewer repetitions.
fn times(&self, n:usize) -> Self {
std::iter::repeat(self).take(n).fold(Default::default(),|l,r| l.concat(r))
std::iter::repeat(self).take(n).fold(Default::default(),|l,r| l.concat_ref(r))
}
}
// === Default Impls ===
impl<T> Monoid for T where T : Default + Semigroup {}
impl<T> MonoidIm for T where T : Default + SemigroupIm {}
impl<T> Monoid for T where T : Default + Semigroup {}
@ -62,3 +54,54 @@ mod tests {
assert_eq!(vec_1_2.times(3) , vec_1_2_times_3);
}
}
// TODO: Think what to do with this. It would not be needed if tuples implement Iter. Alternatively
// we could immplement own tuple type.
//trait Foldable {
// type Item : Monoid;
// fn fold(self) -> Self::Item;
//}
//
//
//
//macro_rules! replace {
// ($a:tt,$b:tt) => {$b};
//}
//
//
//macro_rules! define_foldable_for_tuple {
// (0$(,$num:tt)*) => {
// impl<T:Monoid> Foldable for (T,$(replace!{$num,T}),*) {
// type Item = T;
// fn fold(self) -> Self::Item {
// self.0$(.concat(self.$num))*
// }
// }
//
// impl<T:Monoid> Foldable for &(T,$(replace!{$num,T}),*) {
// type Item = T;
// fn fold(self) -> Self::Item {
// self.0.clone()$(.concat(&self.$num))*
// }
// }
// };
//}
//
//define_foldable_for_tuple![0];
//define_foldable_for_tuple![0,1];
//define_foldable_for_tuple![0,1,2];
//define_foldable_for_tuple![0,1,2,3];
//define_foldable_for_tuple![0,1,2,3,4];
//define_foldable_for_tuple![0,1,2,3,4,5];
//define_foldable_for_tuple![0,1,2,3,4,5,6];
//define_foldable_for_tuple![0,1,2,3,4,5,6,7];
//define_foldable_for_tuple![0,1,2,3,4,5,6,7,8];
//define_foldable_for_tuple![0,1,2,3,4,5,6,7,8,9];
//define_foldable_for_tuple![0,1,2,3,4,5,6,7,8,9,10];
//define_foldable_for_tuple![0,1,2,3,4,5,6,7,8,9,10,11];
//define_foldable_for_tuple![0,1,2,3,4,5,6,7,8,9,10,11,12];
//define_foldable_for_tuple![0,1,2,3,4,5,6,7,8,9,10,11,12,13];
//define_foldable_for_tuple![0,1,2,3,4,5,6,7,8,9,10,11,12,13,14];
//define_foldable_for_tuple![0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15];

View File

@ -17,58 +17,34 @@ use std::iter::Extend;
/// Mutable Semigroup definition. Impls should satisfy the associativity law:
/// `x.concat(y.concat(z)) = x.concat(y).concat(z)`, in symbolic form:
/// `x <> (y <> z) = (x <> y) <> z`
pub trait Semigroup : Clone {
pub trait PartialSemigroup<T> : Clone {
/// An associative operation.
fn concat_mut(&mut self, other:&Self);
fn concat_mut(&mut self, other:T);
/// An associative operation which consumes the argument.
fn concat_mut_take(&mut self, other:Self) {
self.concat_mut(&other)
/// An associative operation.
fn concat_ref(&self, other:T) -> Self where Self:Clone {
self.clone().concat(other)
}
/// Repeat a value n times. Given that this works on a Semigroup it is allowed to fail if you
/// request 0 or fewer repetitions, and the default definition will do so.
fn partial_times(&mut self, n:usize) {
/// An associative operation.
fn concat(mut self, other:T) -> Self {
self.concat_mut(other);
self
}
}
impl<T> Semigroup for T where T : PartialSemigroup<T> + for<'t> PartialSemigroup<&'t T> {}
pub trait Semigroup : PartialSemigroup<Self> + for<'t> PartialSemigroup<&'t Self> {
fn partial_times_mut(&mut self, n:usize) {
let val = self.clone();
for _ in 0..n-1 {
self.concat_mut(&val)
}
}
}
/// Immutable Semigroup definition. Impls should satisfy the associativity law:
/// `x.concat(y.concat(z)) = x.concat(y).concat(z)`, in symbolic form:
/// `x <> (y <> z) = (x <> y) <> z`
pub trait SemigroupIm : Clone {
/// An associative operation.
fn concat(&self, other:&Self) -> Self;
/// An associative operation which consumes the argument.
fn concat_take(&self, other:Self) -> Self {
self.concat(&other)
}
/// Repeat a value n times. Given that this works on a Semigroup it is allowed to fail if you
/// request 0 or fewer repetitions, and the default definition will do so.
fn partial_times(&self, n:usize) -> Self where Self:Clone {
std::iter::repeat(self).take(n-1).fold(self.clone(),|l,r| l.concat(r))
}
}
// === Default Impls ===
impl<T:Semigroup> SemigroupIm for T {
default fn concat(&self, other:&Self) -> Self {
let mut this = self.clone();
this.concat_mut(other);
this
}
default fn concat_take(&self, other:Self) -> Self {
let mut this = self.clone();
this.concat_mut_take(other);
this
fn partial_times(mut self, n:usize) -> Self {
self.partial_times_mut(n);
self
}
}
@ -80,7 +56,7 @@ impl<T:Semigroup> SemigroupIm for T {
// === Option ===
impl<T:Semigroup> Semigroup for Option<T> {
impl<T:Semigroup> PartialSemigroup<&Option<T>> for Option<T> {
fn concat_mut(&mut self, other:&Self) {
if let Some(r) = other {
match self {
@ -89,12 +65,14 @@ impl<T:Semigroup> Semigroup for Option<T> {
}
}
}
}
fn concat_mut_take(&mut self, other:Self) {
impl<T:Semigroup> PartialSemigroup<Option<T>> for Option<T> {
fn concat_mut(&mut self, other:Self) {
if let Some(r) = other {
match self {
None => *self = Some(r),
Some(l) => l.concat_mut_take(r)
Some(l) => l.concat_mut(r)
}
}
}
@ -103,7 +81,7 @@ impl<T:Semigroup> Semigroup for Option<T> {
// === HashMap ===
impl<K,V,S> Semigroup for HashMap<K,V,S>
impl<K,V,S> PartialSemigroup<&HashMap<K,V,S>> for HashMap<K,V,S>
where K : Eq + Hash + Clone,
V : Semigroup,
S : Clone + BuildHasher {
@ -115,8 +93,13 @@ where K : Eq + Hash + Clone,
.or_insert_with(|| new_val.clone());
}
}
}
fn concat_mut_take(&mut self, other:Self) {
impl<K,V,S> PartialSemigroup<HashMap<K,V,S>> for HashMap<K,V,S>
where K : Eq + Hash + Clone,
V : Semigroup,
S : Clone + BuildHasher {
fn concat_mut(&mut self, other:Self) {
for (key,new_val) in other {
self.entry(key)
.and_modify(|val| val.concat_mut(&new_val))
@ -128,12 +111,14 @@ where K : Eq + Hash + Clone,
// === Vec ===
impl<T:Clone> Semigroup for Vec<T> {
impl<T:Clone> PartialSemigroup<&Vec<T>> for Vec<T> {
fn concat_mut(&mut self, other:&Self) {
self.extend(other.iter().cloned())
}
}
fn concat_mut_take(&mut self, other:Self) {
impl<T:Clone> PartialSemigroup<Vec<T>> for Vec<T> {
fn concat_mut(&mut self, other:Self) {
self.extend(other.into_iter())
}
}