Merge remote-tracking branch 'new_repo/main'

This commit is contained in:
Skyler Lehmkuhl 2024-12-03 13:36:36 -05:00
commit d0fcd4c0b8
52 changed files with 15420 additions and 0 deletions

7
README.md Normal file
View File

@ -0,0 +1,7 @@
# Lightningbeam 2
This README needs content. This is Lightningbeam rewritten with Tauri.
To test:
`pnpm tauri dev`

16
package.json Normal file
View File

@ -0,0 +1,16 @@
{
"name": "lightningbeam",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"tauri": "tauri"
},
"devDependencies": {
"@tauri-apps/cli": "^2"
},
"dependencies": {
"@tauri-apps/plugin-dialog": "~2",
"@tauri-apps/plugin-fs": "~2"
}
}

151
pnpm-lock.yaml Normal file
View File

@ -0,0 +1,151 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
'@tauri-apps/plugin-dialog':
specifier: ~2
version: 2.0.1
'@tauri-apps/plugin-fs':
specifier: ~2
version: 2.0.2
devDependencies:
'@tauri-apps/cli':
specifier: ^2
version: 2.0.4
packages:
'@tauri-apps/api@2.1.1':
resolution: {integrity: sha512-fzUfFFKo4lknXGJq8qrCidkUcKcH2UHhfaaCNt4GzgzGaW2iS26uFOg4tS3H4P8D6ZEeUxtiD5z0nwFF0UN30A==}
'@tauri-apps/cli-darwin-arm64@2.0.4':
resolution: {integrity: sha512-siH7rOHobb16rPbc11k64p1mxIpiRCkWmzs2qmL5IX21Gx9K5onI3Tk67Oqpf2uNupbYzItrOttaDT4NHFC7tw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@tauri-apps/cli-darwin-x64@2.0.4':
resolution: {integrity: sha512-zIccfbCoZMfmUpnk6PFCV0keFyfVj1A9XV3Oiiitj/dkTZ9CQvzjhX3XC0XcK4rsTWegfr2PjSrK06aiPAROAw==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@tauri-apps/cli-linux-arm-gnueabihf@2.0.4':
resolution: {integrity: sha512-fgQqJzefOGWCBNg4yrVA82Rg4s1XQr5K0dc2rCxBhJfa696e8dQ1LDrnWq/AiO5r+uHfVaoQTIUvxxpFicYRSA==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
'@tauri-apps/cli-linux-arm64-gnu@2.0.4':
resolution: {integrity: sha512-u8wbt5tPA9pI6j+d7jGrfOz9UVCiTp+IYzKNiIqlrDsAjqAUFaNXYHKqOUboeFWEmI4zoCWj6LgpS2OJTQ5FKg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@tauri-apps/cli-linux-arm64-musl@2.0.4':
resolution: {integrity: sha512-hntF1V8e3V1hlrESm93PsghDhf3lA5pbvFrRfYxU1c+fVD/jRXGVw8BH3O1lW8MWwhEg1YdhKk01oAgsuHLuig==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@tauri-apps/cli-linux-x64-gnu@2.0.4':
resolution: {integrity: sha512-Iq1GGJb+oT1T0ZV8izrgf0cBtlzPCJaWcNueRbf1ZXquMf+FSTyQv+/Lo8rq5T6buOIJOH7cAOTuEWWqiCZteg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@tauri-apps/cli-linux-x64-musl@2.0.4':
resolution: {integrity: sha512-9NTk6Pf0bSwXqCBdAA+PDYts9HeHebZzIo8mbRzRyUbER6QngG5HZb9Ka36Z1QWtJjdRy6uxSb4zb/9NuTeHfA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@tauri-apps/cli-win32-arm64-msvc@2.0.4':
resolution: {integrity: sha512-OF2e9oxiBFR8A8wVMOhUx9QGN/I1ZkquWC7gVQBnA56nx9PabJlDT08QBy5UD8USqZFVznnfNr2ehlheQahb3g==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@tauri-apps/cli-win32-ia32-msvc@2.0.4':
resolution: {integrity: sha512-T+hCKB3rFP6q0saHHtR02hm6wr1ZPJ0Mkii3oRTxjPG6BBXoVzHNCYzvdgEGJPTA2sFuAQtJH764NRtNlDMifw==}
engines: {node: '>= 10'}
cpu: [ia32]
os: [win32]
'@tauri-apps/cli-win32-x64-msvc@2.0.4':
resolution: {integrity: sha512-GVaiI3KWRFLomjJmApHqihhYlkJ+7FqhumhVfBO6Z2tWzZjQyVQgTdNp0kYEuW2WoAYEj0dKY6qd4YM33xYcUA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
'@tauri-apps/cli@2.0.4':
resolution: {integrity: sha512-Hl9eFXz+O366+6su9PfaSzu2EJdFe1p8K8ghkWmi40dz8VmSE7vsMTaOStD0I71ckSOkh2ICDX7FQTBgjlpjWw==}
engines: {node: '>= 10'}
hasBin: true
'@tauri-apps/plugin-dialog@2.0.1':
resolution: {integrity: sha512-fnUrNr6EfvTqdls/ufusU7h6UbNFzLKvHk/zTuOiBq01R3dTODqwctZlzakdbfSp/7pNwTKvgKTAgl/NAP/Z0Q==}
'@tauri-apps/plugin-fs@2.0.2':
resolution: {integrity: sha512-4YZaX2j7ta81M5/DL8aN10kTnpUkEpkPo1FTYPT8Dd0ImHe3azM8i8MrtjrDGoyBYLPO3zFv7df/mSCYF8oA0Q==}
snapshots:
'@tauri-apps/api@2.1.1': {}
'@tauri-apps/cli-darwin-arm64@2.0.4':
optional: true
'@tauri-apps/cli-darwin-x64@2.0.4':
optional: true
'@tauri-apps/cli-linux-arm-gnueabihf@2.0.4':
optional: true
'@tauri-apps/cli-linux-arm64-gnu@2.0.4':
optional: true
'@tauri-apps/cli-linux-arm64-musl@2.0.4':
optional: true
'@tauri-apps/cli-linux-x64-gnu@2.0.4':
optional: true
'@tauri-apps/cli-linux-x64-musl@2.0.4':
optional: true
'@tauri-apps/cli-win32-arm64-msvc@2.0.4':
optional: true
'@tauri-apps/cli-win32-ia32-msvc@2.0.4':
optional: true
'@tauri-apps/cli-win32-x64-msvc@2.0.4':
optional: true
'@tauri-apps/cli@2.0.4':
optionalDependencies:
'@tauri-apps/cli-darwin-arm64': 2.0.4
'@tauri-apps/cli-darwin-x64': 2.0.4
'@tauri-apps/cli-linux-arm-gnueabihf': 2.0.4
'@tauri-apps/cli-linux-arm64-gnu': 2.0.4
'@tauri-apps/cli-linux-arm64-musl': 2.0.4
'@tauri-apps/cli-linux-x64-gnu': 2.0.4
'@tauri-apps/cli-linux-x64-musl': 2.0.4
'@tauri-apps/cli-win32-arm64-msvc': 2.0.4
'@tauri-apps/cli-win32-ia32-msvc': 2.0.4
'@tauri-apps/cli-win32-x64-msvc': 2.0.4
'@tauri-apps/plugin-dialog@2.0.1':
dependencies:
'@tauri-apps/api': 2.1.1
'@tauri-apps/plugin-fs@2.0.2':
dependencies:
'@tauri-apps/api': 2.1.1

5091
src-tauri/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

27
src-tauri/Cargo.toml Normal file
View File

@ -0,0 +1,27 @@
[package]
name = "lightningbeam"
version = "0.1.0"
description = "A Tauri App"
authors = ["you"]
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
# The `_lib` suffix may seem redundant but it is necessary
# to make the lib name unique and wouldn't conflict with the bin name.
# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519
name = "lightningbeam_lib"
crate-type = ["staticlib", "cdylib", "rlib"]
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = [] }
tauri-plugin-shell = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
tauri-plugin-fs = "2"
tauri-plugin-dialog = "2"

3
src-tauri/build.rs Normal file
View File

@ -0,0 +1,3 @@
fn main() {
tauri_build::build()
}

View File

@ -0,0 +1,39 @@
{
"$schema": "../gen/schemas/desktop-schema.json",
"identifier": "default",
"description": "Capability for the main window",
"windows": [
"main"
],
"permissions": [
"core:default",
"core:window:allow-close",
"shell:allow-open",
"fs:default",
{
"identifier": "fs:allow-exists",
"allow": [
{
"path": "$DOCUMENT/*"
}
]
},
{
"identifier": "fs:allow-app-write-recursive",
"allow": [
{
"path": "$DOCUMENT/*"
}
]
},
{
"identifier": "fs:allow-app-read-recursive",
"allow": [
{
"path": "$DOCUMENT/*"
}
]
},
"dialog:default"
]
}

BIN
src-tauri/icons/128x128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
src-tauri/icons/32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 974 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 903 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

BIN
src-tauri/icons/icon.icns Normal file

Binary file not shown.

BIN
src-tauri/icons/icon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

BIN
src-tauri/icons/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

16
src-tauri/src/lib.rs Normal file
View File

@ -0,0 +1,16 @@
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_shell::init())
.invoke_handler(tauri::generate_handler![greet])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

6
src-tauri/src/main.rs Normal file
View File

@ -0,0 +1,6 @@
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
fn main() {
lightningbeam_lib::run()
}

34
src-tauri/tauri.conf.json Normal file
View File

@ -0,0 +1,34 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "lightningbeam",
"version": "0.1.0",
"identifier": "org.lightningbeam.app",
"build": {
"frontendDist": "../src"
},
"app": {
"withGlobalTauri": true,
"windows": [
{
"title": "lightningbeam",
"width": 1500,
"height": 1024
}
],
"security": {
"csp": null
}
},
"bundle": {
"active": true,
"targets": "all",
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico"
]
}
}

142
src/assets/draw.svg Normal file
View File

@ -0,0 +1,142 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
viewBox="0 0 24 24"
id="svg30571"
version="1.1"
inkscape:version="0.91+devel r"
sodipodi:docname="gimp-tool-pencil-24.svg"
inkscape:export-filename="/home/klaus/Bilder/icons/Symbolic/hicolor/24x24/apps/gimp-channel.png"
inkscape:export-xdpi="98.181816"
inkscape:export-ydpi="98.181816">
<defs
id="defs30573">
<linearGradient
id="linearGradient19282-4"
osb:paint="solid"
gradientTransform="matrix(0.34682586,0,0,0.30620888,-482.61525,330.965)">
<stop
style="stop-color:#bebebe;stop-opacity:1;"
offset="0"
id="stop19284-0" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient19282-4"
id="linearGradient10396"
x1="-148.71875"
y1="-36.782017"
x2="-144"
y2="-36.782017"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.4999998,0,0,1.4999998,239.50009,1088.3622)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient19282-4"
id="linearGradient10390"
x1="82.000198"
y1="187"
x2="94.000198"
y2="187"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.4999998,0,0,1.4999998,-122.00028,762.86224)" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="16"
inkscape:cx="11.379883"
inkscape:cy="17.128924"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:snap-page="true"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-text-baseline="true"
showborder="false"
inkscape:window-width="1440"
inkscape:window-height="752"
inkscape:window-x="149"
inkscape:window-y="27"
inkscape:window-maximized="0"
inkscape:snap-global="false">
<inkscape:grid
type="xygrid"
id="grid4369" />
</sodipodi:namedview>
<metadata
id="metadata30576">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<dc:creator>
<cc:Agent>
<dc:title>Klaus Staedtler</dc:title>
</cc:Agent>
</dc:creator>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1028.3622)">
<g
id="g9722">
<path
sodipodi:nodetypes="ccccc"
inkscape:connector-curvature="0"
id="path69005"
d="m 19.5,1029.8622 -3,3 c 1.993094,0.039 3.919679,1.6611 4.000124,3.5 l 3,-3 c -0.0831,-1.898 -1.946084,-3.5763 -4.000124,-3.5 z"
style="fill:url(#linearGradient10396);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.49999976" />
<rect
ry="0"
rx="0"
y="1028.3622"
x="1.3463471e-06"
height="23.999996"
width="23.999994"
id="rect63426"
style="fill:none;stroke:none;stroke-width:1.49999976" />
<path
inkscape:connector-curvature="0"
sodipodi:nodetypes="cccccc"
id="path2273-6-2"
d="m 4,1046.3622 11,-11.9977 c 2.054034,-0.076 3.916963,1.5997 3.999997,3.4977 L 8,1049.3622 l -7,3 z"
style="fill:url(#linearGradient10390);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.49999976" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

114
src/assets/ellipse.svg Normal file
View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="24"
height="24"
viewBox="0 0 24 24"
id="svg30571"
version="1.1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="ellipse.svg"
inkscape:export-filename="/home/klaus/Bilder/icons/Symbolic/hicolor/24x24/apps/gimp-channel.png"
inkscape:export-xdpi="98.181816"
inkscape:export-ydpi="98.181816"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs30573" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="32.000001"
inkscape:cx="14.640625"
inkscape:cy="10.25"
inkscape:document-units="px"
inkscape:current-layer="g4376"
showgrid="true"
units="px"
inkscape:snap-page="true"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-text-baseline="true"
showborder="false"
inkscape:window-width="2256"
inkscape:window-height="1432"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:snap-global="false"
inkscape:showpageshadow="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050">
<inkscape:grid
type="xygrid"
id="grid4362"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px"
visible="true" />
</sodipodi:namedview>
<metadata
id="metadata30576">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<dc:creator>
<cc:Agent>
<dc:title>Klaus Staedtler</dc:title>
</cc:Agent>
</dc:creator>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1028.3622)">
<g
id="g4376">
<rect
ry="0"
rx="0.70450491"
y="1028.3622"
x="-2.4536132e-06"
height="24"
width="24"
id="rect18652"
style="display:inline;fill:none;stroke:none;stroke-width:1.5" />
<ellipse
style="fill:#bfbfbf;fill-opacity:0.5;stroke:#bfbfbf;stroke-width:2.20663;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="path1"
cx="12.005858"
cy="1040.6517"
rx="10.780354"
ry="7.8231997" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

108
src/assets/infopanel.svg Normal file
View File

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
viewBox="0 0 16 16"
id="svg30571"
version="1.1"
inkscape:version="0.92pre1 unknown"
sodipodi:docname="gimp-information.svg">
<defs
id="defs30573" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:zoom="15.524752"
inkscape:cx="4.9851892"
inkscape:cy="17.36011"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
units="px"
inkscape:snap-page="true"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-text-baseline="true"
showborder="false"
inkscape:window-width="1920"
inkscape:window-height="1016"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:snap-global="false"
showguides="false" />
<metadata
id="metadata30576">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>
image/svg+xml
</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:creator>
<cc:Agent>
<dc:title>Klaus Staedtler </dc:title>
</cc:Agent>
</dc:creator>
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1036.3622)">
<g
id="g3940">
<g
inkscape:label="gimp-question"
id="gimp-question"
style="display:inline"
transform="translate(-141.0002,517.3622)">
<path
style="fill:#bebebe;fill-opacity:1;stroke:none"
d="m -92,302 c -4.418278,0 -8,3.58172 -8,8 0,4.41828 3.581722,8 8,8 4.418278,0 8,-3.58172 8,-8 0,-4.41828 -3.581722,-8 -8,-8 z m 0,2 c 3.313708,0 6,2.68629 6,6 0,3.31371 -2.686292,6 -6,6 -3.313708,0 -6,-2.68629 -6,-6 0,-3.31371 2.686292,-6 6,-6 z"
transform="translate(241.0002,217)"
id="path27976"
inkscape:connector-curvature="0" />
</g>
<text
transform="scale(1.3005016,0.76893407)"
sodipodi:linespacing="125%"
id="text3956"
y="1364.6973"
x="3.7694485"
style="font-style:normal;font-weight:normal;font-size:17.11714172px;line-height:125%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:0.98779672px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
xml:space="preserve"><tspan
y="1364.6973"
x="3.7694485"
id="tspan3958"
sodipodi:role="line"
style="stroke-width:0.98779672px">i</tspan></text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="32" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 256"><path fill="#F7DF1E" d="M0 0h256v256H0V0Z"></path><path d="m67.312 213.932l19.59-11.856c3.78 6.701 7.218 12.371 15.465 12.371c7.905 0 12.89-3.092 12.89-15.12v-81.798h24.057v82.138c0 24.917-14.606 36.259-35.916 36.259c-19.245 0-30.416-9.967-36.087-21.996m85.07-2.576l19.588-11.341c5.157 8.421 11.859 14.607 23.715 14.607c9.969 0 16.325-4.984 16.325-11.858c0-8.248-6.53-11.17-17.528-15.98l-6.013-2.58c-17.357-7.387-28.87-16.667-28.87-36.257c0-18.044 13.747-31.792 35.228-31.792c15.294 0 26.292 5.328 34.196 19.247l-18.732 12.03c-4.125-7.389-8.591-10.31-15.465-10.31c-7.046 0-11.514 4.468-11.514 10.31c0 7.217 4.468 10.14 14.778 14.608l6.014 2.577c20.45 8.765 31.963 17.7 31.963 37.804c0 21.654-17.012 33.51-39.867 33.51c-22.339 0-36.774-10.654-43.819-24.574"></path></svg>

After

Width:  |  Height:  |  Size: 995 B

119
src/assets/paint_bucket.svg Normal file
View File

@ -0,0 +1,119 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24"
height="24"
viewBox="0 0 24 24"
id="svg30571"
version="1.1"
inkscape:version="0.91+devel r"
sodipodi:docname="gimp-tool-bucket-fill-24.svg"
inkscape:export-filename="/home/klaus/Bilder/icons/Symbolic/hicolor/24x24/apps/gimp-channel.png"
inkscape:export-xdpi="98.181816"
inkscape:export-ydpi="98.181816">
<defs
id="defs30573" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="11.313709"
inkscape:cx="9.684375"
inkscape:cy="7.5578562"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:snap-page="true"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-text-baseline="true"
showborder="false"
inkscape:window-width="1920"
inkscape:window-height="1016"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:snap-global="false">
<inkscape:grid
type="xygrid"
id="grid4362" />
</sodipodi:namedview>
<metadata
id="metadata30576">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<dc:creator>
<cc:Agent>
<dc:title>Klaus Staedtler</dc:title>
</cc:Agent>
</dc:creator>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1028.3622)">
<g
id="g4376">
<rect
ry="0"
rx="0.70450491"
y="1028.3622"
x="-2.4536132e-06"
height="24"
width="24"
id="rect18652"
style="display:inline;fill:none;stroke:none;stroke-width:1.5" />
<path
style="display:inline;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1.50918233"
d="M 12.5,1033.3622 8.7948418,1036.1158 5,1038.917 l 1.9208458,2.8485 1.5928966,2.3737 4.1696406,6.3144 c 0.575152,0.8632 1.609538,1.1695 2.295645,0.6646 l 7.449134,-5.4597 c 0.686063,-0.5049 0.762582,-1.6055 0.1874,-2.4688 L 18.5,1037.0179 v 2.1838 c 0,1.6781 -1.344837,2.6798 -3.000803,2.6798 -1.655967,0 -2.997967,-1.3605 -2.999197,-3.0385 z"
id="rect18654"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccsssc" />
<rect
ry="1.5"
rx="1.5019009"
y="1028.3622"
x="14"
height="12"
width="3"
id="rect18680"
style="fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:1.50095022" />
<path
sodipodi:nodetypes="cscsassc"
inkscape:connector-curvature="0"
id="rect4226"
d="m 2.6286757,1039.0239 c 0.7338369,-2.01 8.9980703,-4.6771 6.0614933,-2.6248 L 5,1038.9781 c 0,0 -0.054529,5.7713 0,8.884 0.018075,1.0319 -0.8292466,1.8295 -1.6378861,1.7929 C 2.6123391,1049.621 2.0709578,1049.1957 2,1047.8621 c -0.1653874,-3.1086 -0.2990049,-6.2971 0.6286797,-8.8382 z"
style="opacity:0.5;fill:#bebebe;fill-opacity:1;stroke:none;stroke-width:0.69631809;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
id="svg7384"
height="16"
width="16"
version="1.1"
sodipodi:docname="gimp-prefs-systemc.svg"
viewBox="0 0 16 16"
inkscape:version="0.92pre1 unknown">
<defs
id="defs10" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1016"
id="namedview8"
showgrid="true"
inkscape:zoom="43.1875"
inkscape:cx="5.725592"
inkscape:cy="8"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg7384">
<inkscape:grid
type="xygrid"
id="grid4233" />
</sodipodi:namedview>
<metadata
id="metadata90">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title>Gnome Symbolic Icon Theme</dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<title
id="title9167">Gnome Symbolic Icon Theme</title>
<g
id="g4953"
transform="matrix(1.0088031,0,0,1.0014609,-445.94089,-462.66315)">
<path
id="path3908"
style="color:#000000;text-indent:0;text-transform:none;fill:#bebebe"
d="m 445.55,462.09 c -0.39933,0 -0.78638,0.0916 -1.1433,0.21572 l 1.8849,1.8797 c 0.38735,0.38627 0.38735,1.0004 0,1.3867 l -0.71069,0.70874 c -0.38735,0.38628 -1.0031,0.38628 -1.3905,0 l -1.8849,-1.8797 c -0.12444,0.35591 -0.2163,0.74191 -0.2163,1.1402 0,1.9061 1.5494,3.4513 3.4608,3.4513 0.39933,0 0.78638,-0.0916 1.1433,-0.2157 l 1.1742,1.171 a 2.4722,2.4654 0 0 1 0.0618,0 l 2.0703,-2.0646 -1.2051,-1.2018 c 0.12444,-0.35592 0.2163,-0.74191 0.2163,-1.1402 0,-1.9061 -1.5494,-3.4513 -3.4608,-3.4513 z m 6.5507,7.8886 -2.0703,2.0646 a 2.4722,2.4654 0 0 1 0.0309,0.0924 l 1.1433,1.1402 c -0.12444,0.35596 -0.2163,0.74196 -0.2163,1.1402 0,1.9061 1.5494,3.4513 3.4608,3.4513 0.43346,0 0.8536,-0.10141 1.236,-0.24653 l -2.0085,-2.003 c -0.38735,-0.38629 -0.38735,-1.0312 0,-1.4175 l 0.67979,-0.67792 c 0.19367,-0.19315 0.45794,-0.30816 0.71069,-0.30816 0.25276,0 0.51702,0.11501 0.7107,0.30816 l 1.9467,1.9413 c 0.10485,-0.32958 0.1854,-0.68351 0.1854,-1.0477 0,-1.9061 -1.5494,-3.4513 -3.4608,-3.4513 -0.39933,0 -0.78639,0.0916 -1.1433,0.2157 l -1.2051,-1.2018 z"
inkscape:connector-curvature="0" />
<path
id="path3910"
style="color:#000000;text-indent:0;text-transform:none;fill:#bebebe"
d="m 455.86,462 -1.5425,1.4375 c -0.45151,0.42079 -0.5292,1.1488 -0.2663,1.7065 l -5.8882,5.9958 a 1.4917,1.4876 0 0 0 -0.0311,2.5e-4 1.4917,1.4876 0 0 0 -0.84016,-0.1484 1.4917,1.4876 0 0 0 -0.86663,0.44059 l -3.9462,3.9973 a 1.49411,1.49001 0 1 0 2.1294,2.0907 l 3.9462,-3.9973 a 1.4917,1.4876 0 0 0 0.29713,-1.7377 l 5.8885,-5.9648 c 0.55782,0.24837 1.2732,0.14697 1.7068,-0.2922 l 1.41,-1.57 -2,-1.97 z"
inkscape:connector-curvature="0" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

114
src/assets/rectangle.svg Normal file
View File

@ -0,0 +1,114 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="24"
height="24"
viewBox="0 0 24 24"
id="svg30571"
version="1.1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="rectangle.svg"
inkscape:export-filename="/home/klaus/Bilder/icons/Symbolic/hicolor/24x24/apps/gimp-channel.png"
inkscape:export-xdpi="98.181816"
inkscape:export-ydpi="98.181816"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs30573" />
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="32.000001"
inkscape:cx="14.640625"
inkscape:cy="10.25"
inkscape:document-units="px"
inkscape:current-layer="g4376"
showgrid="true"
units="px"
inkscape:snap-page="true"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-text-baseline="true"
showborder="false"
inkscape:window-width="2256"
inkscape:window-height="1432"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:snap-global="false"
inkscape:showpageshadow="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050">
<inkscape:grid
type="xygrid"
id="grid4362"
originx="0"
originy="0"
spacingy="1"
spacingx="1"
units="px"
visible="true" />
</sodipodi:namedview>
<metadata
id="metadata30576">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<dc:creator>
<cc:Agent>
<dc:title>Klaus Staedtler</dc:title>
</cc:Agent>
</dc:creator>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(0,-1028.3622)">
<g
id="g4376">
<rect
ry="0"
rx="0.70450491"
y="1028.3622"
x="-2.4536132e-06"
height="24"
width="24"
id="rect18652"
style="display:inline;fill:none;stroke:none;stroke-width:1.5" />
<rect
style="fill:#bfbfbf;fill-opacity:0.5;stroke:#bfbfbf;stroke-width:2.45423;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect1"
width="21.338375"
height="14.60754"
x="1.3635877"
y="1033.5981" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.3 KiB

139
src/assets/select.svg Normal file
View File

@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 23.999999 24"
id="svg7384"
height="24"
width="24"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="gimp-cursor.svg">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1920"
inkscape:window-height="1016"
id="namedview1507"
showgrid="true"
inkscape:zoom="14.75"
inkscape:cx="-4.8474576"
inkscape:cy="8.8813559"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
inkscape:current-layer="svg7384">
<inkscape:grid
type="xygrid"
id="grid4149" />
</sodipodi:namedview>
<metadata
id="metadata90">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<dc:contributor>
<cc:Agent>
<dc:title>Barbara Muraus, Jakub Steiner, Klaus Staedtler</dc:title>
</cc:Agent>
</dc:contributor>
<dc:description>Images originally created as the &quot;Art Libre&quot; icon set. Extended and adopted for GIMP</dc:description>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs7386">
<linearGradient
osb:paint="solid"
id="linearGradient8074">
<stop
id="stop8072"
offset="0"
style="stop-color:#be00be;stop-opacity:1;" />
</linearGradient>
<linearGradient
osb:paint="solid"
id="linearGradient7561">
<stop
id="stop7558"
offset="0"
style="stop-color:#a5a5a5;stop-opacity:1;" />
</linearGradient>
<linearGradient
osb:paint="solid"
id="linearGradient7548">
<stop
id="stop7546"
offset="0"
style="stop-color:#ebebeb;stop-opacity:1;" />
</linearGradient>
<linearGradient
osb:paint="solid"
id="linearGradient7542">
<stop
id="stop7538"
offset="0"
style="stop-color:#c9c9c9;stop-opacity:1;" />
</linearGradient>
<linearGradient
gradientTransform="matrix(0,-735328.32,170712.69,0,2464326300,577972450)"
osb:paint="solid"
id="linearGradient19282">
<stop
id="stop19284"
offset="0"
style="stop-color:#b4b4b4;stop-opacity:1;" />
</linearGradient>
<linearGradient
gradientTransform="matrix(0.34682586,0,0,0.30620888,-33.35187,162.03851)"
osb:paint="solid"
id="linearGradient19282-4">
<stop
id="stop19284-0"
offset="0"
style="stop-color:#bebebe;stop-opacity:1;" />
</linearGradient>
<linearGradient
gradientTransform="matrix(1,0,0,1.0064514,523.05932,150.423)"
gradientUnits="userSpaceOnUse"
y2="607.03845"
x2="133.0002"
y1="607.03845"
x1="125.0002"
id="linearGradient7314"
xlink:href="#linearGradient19282-4" />
</defs>
<g
transform="matrix(1.5,0,0,1.5,-97.872258,20.46468)"
style="display:inline"
id="stock">
<g
id="gimp-cursor"
style="display:inline"
transform="translate(-55.752028,-612.64312)">
<path
style="fill:url(#linearGradient7314);fill-opacity:1;stroke:none"
d="m 125.0002,601.85133 0,9.14867 0.82307,-0.0129 1.64616,-3.1742 3.06144,5.1871 2.46933,0 -3.88462,-6 3.88462,0 0,-0.89033 -8,-5.10967 z"
id="path8245"
inkscape:connector-curvature="0"
sodipodi:nodetypes="ccccccccccc" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

59
src/assets/stage.svg Normal file
View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="48"
height="48"
viewBox="0 0 48 48"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="stage.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:document-units="px"
inkscape:zoom="24.708333"
inkscape:cx="24.020236"
inkscape:cy="24"
inkscape:window-width="2256"
inkscape:window-height="1432"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1" />
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:#0ab000;fill-opacity:1;stroke:#3b3b3b;stroke-linejoin:round"
id="rect1"
width="20.971228"
height="20.316715"
x="2.6980395"
y="3.9082527" />
<circle
style="fill:#b60000;fill-opacity:1;stroke:#000000;stroke-linejoin:round;stroke-opacity:1"
id="path1"
cx="22.783701"
cy="25.66276"
r="11.847676" />
<path
style="fill:none;stroke:#004dfc;stroke-width:4.7;stroke-linejoin:round;stroke-opacity:1;stroke-dasharray:none"
d="M 19.446559,46.060078 C 36.809147,40.328545 40.163661,35.008185 34.301881,28.512991 28.440102,22.017798 24.943338,18.995634 33.163696,19.961367 41.384052,20.9271 45.130651,19.787402 45.649455,15.370861 46.168258,10.954319 39.070556,5.4037776 31.870759,5.2488045"
id="path2" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

6
src/assets/tauri.svg Normal file
View File

@ -0,0 +1,6 @@
<svg width="206" height="231" viewBox="0 0 206 231" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M143.143 84C143.143 96.1503 133.293 106 121.143 106C108.992 106 99.1426 96.1503 99.1426 84C99.1426 71.8497 108.992 62 121.143 62C133.293 62 143.143 71.8497 143.143 84Z" fill="#FFC131"/>
<ellipse cx="84.1426" cy="147" rx="22" ry="22" transform="rotate(180 84.1426 147)" fill="#24C8DB"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M166.738 154.548C157.86 160.286 148.023 164.269 137.757 166.341C139.858 160.282 141 153.774 141 147C141 144.543 140.85 142.121 140.558 139.743C144.975 138.204 149.215 136.139 153.183 133.575C162.73 127.404 170.292 118.608 174.961 108.244C179.63 97.8797 181.207 86.3876 179.502 75.1487C177.798 63.9098 172.884 53.4021 165.352 44.8883C157.82 36.3744 147.99 30.2165 137.042 27.1546C126.095 24.0926 114.496 24.2568 103.64 27.6274C92.7839 30.998 83.1319 37.4317 75.8437 46.1553C74.9102 47.2727 74.0206 48.4216 73.176 49.5993C61.9292 50.8488 51.0363 54.0318 40.9629 58.9556C44.2417 48.4586 49.5653 38.6591 56.679 30.1442C67.0505 17.7298 80.7861 8.57426 96.2354 3.77762C111.685 -1.01901 128.19 -1.25267 143.769 3.10474C159.348 7.46215 173.337 16.2252 184.056 28.3411C194.775 40.457 201.767 55.4101 204.193 71.404C206.619 87.3978 204.374 103.752 197.73 118.501C191.086 133.25 180.324 145.767 166.738 154.548ZM41.9631 74.275L62.5557 76.8042C63.0459 72.813 63.9401 68.9018 65.2138 65.1274C57.0465 67.0016 49.2088 70.087 41.9631 74.275Z" fill="#FFC131"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M38.4045 76.4519C47.3493 70.6709 57.2677 66.6712 67.6171 64.6132C65.2774 70.9669 64 77.8343 64 85.0001C64 87.1434 64.1143 89.26 64.3371 91.3442C60.0093 92.8732 55.8533 94.9092 51.9599 97.4256C42.4128 103.596 34.8505 112.392 30.1816 122.756C25.5126 133.12 23.9357 144.612 25.6403 155.851C27.3449 167.09 32.2584 177.598 39.7906 186.112C47.3227 194.626 57.153 200.784 68.1003 203.846C79.0476 206.907 90.6462 206.743 101.502 203.373C112.359 200.002 122.011 193.568 129.299 184.845C130.237 183.722 131.131 182.567 131.979 181.383C143.235 180.114 154.132 176.91 164.205 171.962C160.929 182.49 155.596 192.319 148.464 200.856C138.092 213.27 124.357 222.426 108.907 227.222C93.458 232.019 76.9524 232.253 61.3736 227.895C45.7948 223.538 31.8055 214.775 21.0867 202.659C10.3679 190.543 3.37557 175.59 0.949823 159.596C-1.47592 143.602 0.768139 127.248 7.41237 112.499C14.0566 97.7497 24.8183 85.2327 38.4045 76.4519ZM163.062 156.711L163.062 156.711C162.954 156.773 162.846 156.835 162.738 156.897C162.846 156.835 162.954 156.773 163.062 156.711Z" fill="#24C8DB"/>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

143
src/assets/timeline.svg Normal file
View File

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 15.999999 16.00003"
id="svg7384"
height="16.000031"
width="16"
version="1.1"
inkscape:version="0.91 r13725"
sodipodi:docname="gimp-video.svg">
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="640"
inkscape:window-height="480"
id="namedview1509"
showgrid="false"
inkscape:zoom="14.749972"
inkscape:cx="-387.86346"
inkscape:cy="427.40701"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="0"
inkscape:current-layer="svg7384" />
<metadata
id="metadata90">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<dc:contributor>
<cc:Agent>
<dc:title>Barbara Muraus, Jakub Steiner, Klaus Staedtler</dc:title>
</cc:Agent>
</dc:contributor>
<dc:description>Images originally created as the &quot;Art Libre&quot; icon set. Extended and adopted for GIMP</dc:description>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs7386">
<linearGradient
osb:paint="solid"
id="linearGradient8074">
<stop
id="stop8072"
offset="0"
style="stop-color:#be00be;stop-opacity:1;" />
</linearGradient>
<linearGradient
osb:paint="solid"
id="linearGradient7561">
<stop
id="stop7558"
offset="0"
style="stop-color:#a5a5a5;stop-opacity:1;" />
</linearGradient>
<linearGradient
osb:paint="solid"
id="linearGradient7548">
<stop
id="stop7546"
offset="0"
style="stop-color:#ebebeb;stop-opacity:1;" />
</linearGradient>
<linearGradient
osb:paint="solid"
id="linearGradient7542">
<stop
id="stop7538"
offset="0"
style="stop-color:#c9c9c9;stop-opacity:1;" />
</linearGradient>
<linearGradient
gradientTransform="matrix(0,-735328.32,170712.69,0,2464326300,577972450)"
osb:paint="solid"
id="linearGradient19282">
<stop
id="stop19284"
offset="0"
style="stop-color:#b4b4b4;stop-opacity:1;" />
</linearGradient>
<linearGradient
gradientTransform="matrix(0.34682586,0,0,0.30620888,26.67918,122.03851)"
osb:paint="solid"
id="linearGradient19282-4">
<stop
id="stop19284-0"
offset="0"
style="stop-color:#bebebe;stop-opacity:1;" />
</linearGradient>
<linearGradient
gradientTransform="translate(576.89471,203.31841)"
gradientUnits="userSpaceOnUse"
y2="-14"
x2="-144"
y1="-14"
x1="-157"
id="linearGradient7027"
xlink:href="#linearGradient19282-4" />
</defs>
<g
transform="translate(-119.08356,-36.287169)"
style="display:inline"
id="stock">
<g
id="gimp-video"
style="display:inline"
transform="translate(-61.947694,-602.7128)">
<g
style="display:inline"
id="emblem-videos"
transform="translate(-380.96875,257)">
<g
id="g1873">
<path
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient7027);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.50793636;marker:none;enable-background:new"
d="m -156,-22 c 0,0 0,0 0,2 l 0,12 c 0,2 -1,2 -1,2 l 1,0 c 0,0 1,0 1,-1 l 1,0 c 0,1 -1,1 -1,1 l 1,0 c 0,0 1,0 1,-1 l 6,0 c 0,1 -0.67515,1 -1,1 l 1,0 c 0,0 1,0 1,-1 l 1,0 c 0,1 -1,1 -1,1 l 1,0 c 0,0 1,0 1,-2 l 0,-12 c 0,-2 0,-2 0,-2 l -3,0 c 0,0 0,0 0,2 l 0,2 -6,0 0,-2 c 0,-2 0,-2 0,-2 z m 1,1 1,0 0,1 -1,0 z m 9,0 1,0 0,1 -1,0 z m -9,2 1,0 0,1 -1,0 z m 9,0 1,0 0,1 -1,0 z m -9,2 1,0 0,1 -1,0 z m 2,0 6,0 0,4 -6,0 z m 7,0 1,0 0,1 -1,0 z m -9,2 1,0 0,1 -1,0 z m 9,0 1,0 0,1 -1,0 z m -9,2 1,0 0,1 -1,0 z m 9,0 1,0 0,1 -1,0 z m -7,1 6,0 0,4 -6,0 z m -2,1 1,0 0,1 -1,0 z m 9,0 1,0 0,1 -1,0 z m -9,2 1,0 0,1 -1,0 z m 9,0 1,0 0,1 -1,0 z"
transform="translate(719.96895,404)"
id="rect5523"
inkscape:connector-curvature="0" />
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 4.9 KiB

128
src/assets/toolbar.svg Normal file
View File

@ -0,0 +1,128 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
width="48"
height="48"
viewBox="0 0 48 48"
version="1.1"
id="svg1"
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
sodipodi:docname="buttons.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg">
<sodipodi:namedview
id="namedview1"
pagecolor="#505050"
bordercolor="#ffffff"
borderopacity="1"
inkscape:showpageshadow="0"
inkscape:pageopacity="0"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:document-units="px"
inkscape:zoom="24.708333"
inkscape:cx="24"
inkscape:cy="24"
inkscape:window-width="2256"
inkscape:window-height="1432"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="layer1" />
<defs
id="defs1">
<linearGradient
id="linearGradient2"
inkscape:collect="always">
<stop
style="stop-color:#808080;stop-opacity:1;"
offset="0"
id="stop2" />
<stop
style="stop-color:#808080;stop-opacity:0.4511821;"
offset="1"
id="stop3" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2"
id="linearGradient3"
x1="3.7859402"
y1="12.61396"
x2="22.232294"
y2="12.61396"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-25.623077,0.3951571)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2"
id="linearGradient4"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-25.718328,22.541895)"
x1="3.7859402"
y1="12.61396"
x2="22.232294"
y2="12.61396" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2"
id="linearGradient5"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-48.08964,0.36092948)"
x1="3.7859402"
y1="12.61396"
x2="22.232294"
y2="12.61396" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2"
id="linearGradient6"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(-48.184891,22.507667)"
x1="3.7859402"
y1="12.61396"
x2="22.232294"
y2="12.61396" />
</defs>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1">
<rect
style="fill:url(#linearGradient3);stroke:#3b3b3b;stroke-width:1;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect1"
width="17.446354"
height="17.295057"
x="-21.337137"
y="4.3615885"
transform="rotate(-90)" />
<rect
style="fill:url(#linearGradient4);stroke:#3b3b3b;stroke-width:1;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect1-5"
width="17.446354"
height="17.295057"
x="-21.432388"
y="26.508327"
transform="rotate(-90)" />
<rect
style="fill:url(#linearGradient5);stroke:#3b3b3b;stroke-width:1;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect1-7"
width="17.446354"
height="17.295057"
x="-43.803699"
y="4.3273611"
transform="rotate(-90)" />
<rect
style="fill:url(#linearGradient6);stroke:#3b3b3b;stroke-width:1;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
id="rect1-5-0"
width="17.446354"
height="17.295057"
x="-43.898952"
y="26.474098"
transform="rotate(-90)" />
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.9 KiB

200
src/assets/transform.svg Normal file
View File

@ -0,0 +1,200 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:osb="http://www.openswatchbook.org/uri/2009/osb"
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="24.000006"
height="24"
viewBox="0 0 24.000006 24"
id="svg30571"
version="1.1"
inkscape:version="0.92.2 2405546, 2018-03-11"
sodipodi:docname="gimp-tool-cage-symbolic.svg"
inkscape:export-filename="/home/klaus/Bilder/icons/Symbolic/hicolor/24x24/apps/gimp-channel.png"
inkscape:export-xdpi="98.181816"
inkscape:export-ydpi="98.181816">
<defs
id="defs30573">
<linearGradient
id="linearGradient19282-4"
osb:paint="solid"
gradientTransform="matrix(0.34682586,0,0,0.30620888,-482.61525,330.965)">
<stop
style="stop-color:#bebebe;stop-opacity:1;"
offset="0"
id="stop19284-0" />
</linearGradient>
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient19282-4"
id="linearGradient7345-8"
x1="-178.03125"
y1="-39.984375"
x2="-174"
y2="-39.984375"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.4883721,0,0,1.4883721,264.97678,1093.8973)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient19282-4"
id="linearGradient7363-5"
x1="-178.03125"
y1="-39.984375"
x2="-174"
y2="-39.984375"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.4883721,0,0,1.4883721,264.97674,1108.8738)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient19282-4"
id="linearGradient7357-3"
x1="-178.03125"
y1="-39.984375"
x2="-174"
y2="-39.984375"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.488372,0,0,1.4883721,282.97674,1105.8739)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient19282-4"
id="linearGradient7351-7"
x1="-178.03125"
y1="-39.984375"
x2="-174"
y2="-39.984375"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.4883721,0,0,1.4883721,281.97674,1090.8738)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient19282-4"
id="linearGradient7333-2"
x1="82.250198"
y1="225.35899"
x2="95.881874"
y2="225.35899"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.4970767,0,0,1.4999996,-121.2401,702.86229)" />
<linearGradient
inkscape:collect="always"
xlink:href="#linearGradient19282-4"
id="linearGradient7339-4"
x1="-178.03125"
y1="-39.984375"
x2="-174"
y2="-39.984375"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.4883721,0,0,1.4883721,273.97674,1101.8738)" />
</defs>
<sodipodi:namedview
id="base"
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="16"
inkscape:cx="-0.71288204"
inkscape:cy="12.378924"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="true"
units="px"
inkscape:snap-page="true"
inkscape:snap-bbox="true"
inkscape:bbox-paths="true"
inkscape:bbox-nodes="true"
inkscape:snap-bbox-edge-midpoints="true"
inkscape:snap-bbox-midpoints="true"
inkscape:object-paths="true"
inkscape:snap-intersection-paths="true"
inkscape:object-nodes="true"
inkscape:snap-smooth-nodes="true"
inkscape:snap-midpoints="true"
inkscape:snap-object-midpoints="true"
inkscape:snap-center="true"
inkscape:snap-text-baseline="true"
showborder="false"
inkscape:window-width="1366"
inkscape:window-height="704"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="0"
inkscape:snap-global="false"
showguides="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0">
<inkscape:grid
type="xygrid"
id="grid4369"
originx="21.437509"
originy="1.8125" />
</sodipodi:namedview>
<metadata
id="metadata30576">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<dc:creator>
<cc:Agent>
<dc:title>Klaus Staedtler</dc:title>
</cc:Agent>
</dc:creator>
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(21.437509,-1030.1747)">
<g
style="display:inline"
transform="translate(-21.437503,1.8125)"
id="gimp-tool-cage-24">
<path
inkscape:connector-curvature="0"
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient7345-8);fill-opacity:1;stroke:none;stroke-width:1.48837185;marker:none;enable-background:new"
d="m 6.0000274,1034.3856 a 3,3 0 0 1 -3,3 3,3 0 0 1 -2.999999981863,-3 3,3 0 0 1 2.999999981863,-3 3,3 0 0 1 3,3 z"
id="path3908-1" />
<path
inkscape:connector-curvature="0"
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient7363-5);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.48837173;marker:none;enable-background:new"
d="m 5.999994,1049.3622 a 3,3 0 0 1 -3,3 3,3 0 0 1 -2.9999999604645,-3 3,3 0 0 1 2.9999999604645,-3 3,3 0 0 1 3,3 z"
id="path3910-5" />
<path
inkscape:connector-curvature="0"
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient7357-3);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.48837173;marker:none;enable-background:new"
d="m 24,1046.3622 a 3,3 0 0 1 -3,3 3,3 0 0 1 -3,-3 3,3 0 0 1 3,-3 3,3 0 0 1 3,3 z"
id="path3912-2" />
<path
inkscape:connector-curvature="0"
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient7351-7);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:1.48837173;marker:none;enable-background:new"
d="m 23,1031.3622 a 3,3 0 0 1 -3,3 3,3 0 0 1 -3,-3 3,3 0 0 1 3,-3 3,3 0 0 1 3,3 z"
id="path3914-1" />
<path
style="color:#000000;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:medium;line-height:normal;font-family:sans-serif;font-variant-ligatures:normal;font-variant-position:normal;font-variant-caps:normal;font-variant-numeric:normal;font-variant-alternates:normal;font-feature-settings:normal;text-indent:0;text-align:start;text-decoration:none;text-decoration-line:none;text-decoration-style:solid;text-decoration-color:#000000;letter-spacing:normal;word-spacing:normal;text-transform:none;writing-mode:lr-tb;direction:ltr;text-orientation:mixed;dominant-baseline:auto;baseline-shift:baseline;text-anchor:start;white-space:normal;shape-padding:0;clip-rule:nonzero;display:inline;overflow:visible;visibility:visible;opacity:1;isolation:auto;mix-blend-mode:normal;color-interpolation:sRGB;color-interpolation-filters:linearRGB;solid-color:#000000;solid-opacity:1;vector-effect:none;fill:url(#linearGradient7333-2);fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:2.24780607;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;color-rendering:auto;image-rendering:auto;shape-rendering:auto;text-rendering:auto;enable-background:new"
d="m 21.039062,1030.0371 -19.1621089,3.3828 v 0.9414 17.2989 l 10.2812499,-7.9981 10.087891,4.4844 z m -2.076171,2.6484 0.792968,11.8946 -7.914062,-3.5176 -7.7187501,6.002 v -11.7598 z"
id="rect3916-8"
inkscape:connector-curvature="0" />
<path
inkscape:connector-curvature="0"
style="color:#000000;display:inline;overflow:visible;visibility:visible;fill:url(#linearGradient7339-4);fill-opacity:1;stroke:none;stroke-width:1.48837173;marker:none;enable-background:new"
d="m 15,1042.3622 a 3,3 0 0 1 -3,3 3,3 0 0 1 -3,-3 3,3 0 0 1 3,-3 3,3 0 0 1 3,3 z"
id="path38587-3" />
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.7 KiB

2003
src/bezier.js Normal file

File diff suppressed because it is too large Load Diff

577
src/coloris.css Normal file
View File

@ -0,0 +1,577 @@
.clr-picker {
display: none;
flex-wrap: wrap;
position: absolute;
width: 200px;
z-index: 1000;
border-radius: 10px;
background-color: #fff;
justify-content: flex-end;
direction: ltr;
box-shadow: 0 0 5px rgba(0,0,0,.05), 0 5px 20px rgba(0,0,0,.1);
-moz-user-select: none;
-webkit-user-select: none;
user-select: none;
}
.clr-picker.clr-open,
.clr-picker[data-inline="true"] {
display: flex;
}
.clr-picker[data-inline="true"] {
position: relative;
}
.clr-gradient {
position: relative;
width: 100%;
height: 100px;
margin-bottom: 15px;
border-radius: 3px 3px 0 0;
background-image: linear-gradient(rgba(0,0,0,0), #000), linear-gradient(90deg, #fff, currentColor);
cursor: pointer;
}
.clr-marker {
position: absolute;
width: 12px;
height: 12px;
margin: -6px 0 0 -6px;
border: 1px solid #fff;
border-radius: 50%;
background-color: currentColor;
cursor: pointer;
}
.clr-picker input[type="range"]::-webkit-slider-runnable-track {
width: 100%;
height: 16px;
}
.clr-picker input[type="range"]::-webkit-slider-thumb {
width: 16px;
height: 16px;
-webkit-appearance: none;
}
.clr-picker input[type="range"]::-moz-range-track {
width: 100%;
height: 16px;
border: 0;
}
.clr-picker input[type="range"]::-moz-range-thumb {
width: 16px;
height: 16px;
border: 0;
}
.clr-hue {
background-image: linear-gradient(to right, #f00 0%, #ff0 16.66%, #0f0 33.33%, #0ff 50%, #00f 66.66%, #f0f 83.33%, #f00 100%);
}
.clr-hue,
.clr-alpha {
position: relative;
width: calc(100% - 40px);
height: 8px;
margin: 5px 20px;
border-radius: 4px;
}
.clr-alpha span {
display: block;
height: 100%;
width: 100%;
border-radius: inherit;
background-image: linear-gradient(90deg, rgba(0,0,0,0), currentColor);
}
.clr-hue input[type="range"],
.clr-alpha input[type="range"] {
position: absolute;
width: calc(100% + 32px);
height: 16px;
left: -16px;
top: -4px;
margin: 0;
background-color: transparent;
opacity: 0;
cursor: pointer;
appearance: none;
-webkit-appearance: none;
}
.clr-hue div,
.clr-alpha div {
position: absolute;
width: 16px;
height: 16px;
left: 0;
top: 50%;
margin-left: -8px;
transform: translateY(-50%);
border: 2px solid #fff;
border-radius: 50%;
background-color: currentColor;
box-shadow: 0 0 1px #888;
pointer-events: none;
}
.clr-alpha div:before {
content: '';
position: absolute;
height: 100%;
width: 100%;
left: 0;
top: 0;
border-radius: 50%;
background-color: currentColor;
}
.clr-format {
display: none;
order: 1;
width: calc(100% - 40px);
margin: 0 20px 20px;
}
.clr-segmented {
display: flex;
position: relative;
width: 100%;
margin: 0;
padding: 0;
border: 1px solid #ddd;
border-radius: 15px;
box-sizing: border-box;
color: #999;
font-size: 12px;
}
.clr-segmented input,
.clr-segmented legend {
position: absolute;
width: 100%;
height: 100%;
margin: 0;
padding: 0;
border: 0;
left: 0;
top: 0;
opacity: 0;
pointer-events: none;
}
.clr-segmented label {
flex-grow: 1;
margin: 0;
padding: 4px 0;
font-size: inherit;
font-weight: normal;
line-height: initial;
text-align: center;
cursor: pointer;
}
.clr-segmented label:first-of-type {
border-radius: 10px 0 0 10px;
}
.clr-segmented label:last-of-type {
border-radius: 0 10px 10px 0;
}
.clr-segmented input:checked + label {
color: #fff;
background-color: #666;
}
.clr-swatches {
order: 2;
width: calc(100% - 32px);
margin: 0 16px;
}
.clr-swatches div {
display: flex;
flex-wrap: wrap;
padding-bottom: 12px;
justify-content: center;
}
.clr-swatches button {
position: relative;
width: 20px;
height: 20px;
margin: 0 4px 6px 4px;
padding: 0;
border: 0;
border-radius: 50%;
color: inherit;
text-indent: -1000px;
white-space: nowrap;
overflow: hidden;
cursor: pointer;
}
.clr-swatches button:after {
content: '';
display: block;
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
border-radius: inherit;
background-color: currentColor;
box-shadow: inset 0 0 0 1px rgba(0,0,0,.1);
}
input.clr-color {
order: 1;
width: calc(100% - 80px);
height: 32px;
margin: 15px 20px 20px auto;
padding: 0 10px;
border: 1px solid #ddd;
border-radius: 16px;
color: #444;
background-color: #fff;
font-family: sans-serif;
font-size: 14px;
text-align: center;
box-shadow: none;
}
input.clr-color:focus {
outline: none;
border: 1px solid #1e90ff;
}
.clr-close,
.clr-clear {
display: none;
order: 2;
height: 24px;
margin: 0 20px 20px;
padding: 0 20px;
border: 0;
border-radius: 12px;
color: #fff;
background-color: #666;
font-family: inherit;
font-size: 12px;
font-weight: 400;
cursor: pointer;
}
.clr-close {
display: block;
margin: 0 20px 20px auto;
}
.clr-preview {
position: relative;
width: 32px;
height: 32px;
margin: 15px 0 20px 20px;
border-radius: 50%;
overflow: hidden;
}
.clr-preview:before,
.clr-preview:after {
content: '';
position: absolute;
height: 100%;
width: 100%;
left: 0;
top: 0;
border: 1px solid #fff;
border-radius: 50%;
}
.clr-preview:after {
border: 0;
background-color: currentColor;
box-shadow: inset 0 0 0 1px rgba(0,0,0,.1);
}
.clr-preview button {
position: absolute;
width: 100%;
height: 100%;
z-index: 1;
margin: 0;
padding: 0;
border: 0;
border-radius: 50%;
outline-offset: -2px;
background-color: transparent;
text-indent: -9999px;
cursor: pointer;
overflow: hidden;
}
.clr-marker,
.clr-hue div,
.clr-alpha div,
.clr-color {
box-sizing: border-box;
}
.clr-field {
display: inline-block;
position: relative;
color: transparent;
}
.clr-field input {
margin: 0;
direction: ltr;
}
.clr-field.clr-rtl input {
text-align: right;
}
.clr-field button {
position: absolute;
width: 30px;
height: 100%;
right: 0;
top: 50%;
transform: translateY(-50%);
margin: 0;
padding: 0;
border: 0;
color: inherit;
text-indent: -1000px;
white-space: nowrap;
overflow: hidden;
pointer-events: none;
}
.clr-field.clr-rtl button {
right: auto;
left: 0;
}
.clr-field button:after {
content: '';
display: block;
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
border-radius: inherit;
background-color: currentColor;
box-shadow: inset 0 0 1px rgba(0,0,0,.5);
}
.clr-alpha,
.clr-alpha div,
.clr-swatches button,
.clr-preview:before,
.clr-field button {
background-image: repeating-linear-gradient(45deg, #aaa 25%, transparent 25%, transparent 75%, #aaa 75%, #aaa), repeating-linear-gradient(45deg, #aaa 25%, #fff 25%, #fff 75%, #aaa 75%, #aaa);
background-position: 0 0, 4px 4px;
background-size: 8px 8px;
}
.clr-marker:focus {
outline: none;
}
.clr-keyboard-nav .clr-marker:focus,
.clr-keyboard-nav .clr-hue input:focus + div,
.clr-keyboard-nav .clr-alpha input:focus + div,
.clr-keyboard-nav .clr-segmented input:focus + label {
outline: none;
box-shadow: 0 0 0 2px #1e90ff, 0 0 2px 2px #fff;
}
.clr-picker[data-alpha="false"] .clr-alpha {
display: none;
}
.clr-picker[data-minimal="true"] {
padding-top: 16px;
}
.clr-picker[data-minimal="true"] .clr-gradient,
.clr-picker[data-minimal="true"] .clr-hue,
.clr-picker[data-minimal="true"] .clr-alpha,
.clr-picker[data-minimal="true"] .clr-color,
.clr-picker[data-minimal="true"] .clr-preview {
display: none;
}
/** Dark theme **/
.clr-dark {
background-color: #444;
}
.clr-dark .clr-segmented {
border-color: #777;
}
.clr-dark .clr-swatches button:after {
box-shadow: inset 0 0 0 1px rgba(255,255,255,.3);
}
.clr-dark input.clr-color {
color: #fff;
border-color: #777;
background-color: #555;
}
.clr-dark input.clr-color:focus {
border-color: #1e90ff;
}
.clr-dark .clr-preview:after {
box-shadow: inset 0 0 0 1px rgba(255,255,255,.5);
}
.clr-dark .clr-alpha,
.clr-dark .clr-alpha div,
.clr-dark .clr-swatches button,
.clr-dark .clr-preview:before {
background-image: repeating-linear-gradient(45deg, #666 25%, transparent 25%, transparent 75%, #888 75%, #888), repeating-linear-gradient(45deg, #888 25%, #444 25%, #444 75%, #888 75%, #888);
}
/** Polaroid theme **/
.clr-picker.clr-polaroid {
border-radius: 6px;
box-shadow: 0 0 5px rgba(0,0,0,.1), 0 5px 30px rgba(0,0,0,.2);
}
.clr-picker.clr-polaroid:before {
content: '';
display: block;
position: absolute;
width: 16px;
height: 10px;
left: 20px;
top: -10px;
border: solid transparent;
border-width: 0 8px 10px 8px;
border-bottom-color: currentColor;
box-sizing: border-box;
color: #fff;
filter: drop-shadow(0 -4px 3px rgba(0,0,0,.1));
pointer-events: none;
}
.clr-picker.clr-polaroid.clr-dark:before {
color: #444;
}
.clr-picker.clr-polaroid.clr-left:before {
left: auto;
right: 20px;
}
.clr-picker.clr-polaroid.clr-top:before {
top: auto;
bottom: -10px;
transform: rotateZ(180deg);
}
.clr-polaroid .clr-gradient {
width: calc(100% - 20px);
height: 120px;
margin: 10px;
border-radius: 3px;
}
.clr-polaroid .clr-hue,
.clr-polaroid .clr-alpha {
width: calc(100% - 30px);
height: 10px;
margin: 6px 15px;
border-radius: 5px;
}
.clr-polaroid .clr-hue div,
.clr-polaroid .clr-alpha div {
box-shadow: 0 0 5px rgba(0,0,0,.2);
}
.clr-polaroid .clr-format {
width: calc(100% - 20px);
margin: 0 10px 15px;
}
.clr-polaroid .clr-swatches {
width: calc(100% - 12px);
margin: 0 6px;
}
.clr-polaroid .clr-swatches div {
padding-bottom: 10px;
}
.clr-polaroid .clr-swatches button {
width: 22px;
height: 22px;
}
.clr-polaroid input.clr-color {
width: calc(100% - 60px);
margin: 10px 10px 15px auto;
}
.clr-polaroid .clr-clear {
margin: 0 10px 15px 10px;
}
.clr-polaroid .clr-close {
margin: 0 10px 15px auto;
}
.clr-polaroid .clr-preview {
margin: 10px 0 15px 10px;
}
/** Large theme **/
.clr-picker.clr-large {
width: 275px;
}
.clr-large .clr-gradient {
height: 150px;
}
.clr-large .clr-swatches button {
width: 22px;
height: 22px;
}
/** Pill (horizontal) theme **/
.clr-picker.clr-pill {
width: 380px;
padding-left: 180px;
box-sizing: border-box;
}
.clr-pill .clr-gradient {
position: absolute;
width: 180px;
height: 100%;
left: 0;
top: 0;
margin-bottom: 0;
border-radius: 3px 0 0 3px;
}
.clr-pill .clr-hue {
margin-top: 20px;
}

1263
src/coloris.js Normal file

File diff suppressed because it is too large Load Diff

782
src/d3-interpolate-path.js vendored Normal file
View File

@ -0,0 +1,782 @@
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.d3 = global.d3 || {}));
}(this, (function (exports) { 'use strict';
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) {
symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
}
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function _typeof(obj) {
"@babel/helpers - typeof";
if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
_typeof = function (obj) {
return typeof obj;
};
} else {
_typeof = function (obj) {
return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};
}
return _typeof(obj);
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function _extends() {
_extends = Object.assign || function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
function _unsupportedIterableToArray(o, minLen) {
if (!o) return;
if (typeof o === "string") return _arrayLikeToArray(o, minLen);
var n = Object.prototype.toString.call(o).slice(8, -1);
if (n === "Object" && o.constructor) n = o.constructor.name;
if (n === "Map" || n === "Set") return Array.from(o);
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
}
function _arrayLikeToArray(arr, len) {
if (len == null || len > arr.length) len = arr.length;
for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
return arr2;
}
function _createForOfIteratorHelper(o, allowArrayLike) {
var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
if (!it) {
if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
if (it) o = it;
var i = 0;
var F = function () {};
return {
s: F,
n: function () {
if (i >= o.length) return {
done: true
};
return {
done: false,
value: o[i++]
};
},
e: function (e) {
throw e;
},
f: F
};
}
throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
}
var normalCompletion = true,
didErr = false,
err;
return {
s: function () {
it = it.call(o);
},
n: function () {
var step = it.next();
normalCompletion = step.done;
return step;
},
e: function (e) {
didErr = true;
err = e;
},
f: function () {
try {
if (!normalCompletion && it.return != null) it.return();
} finally {
if (didErr) throw err;
}
}
};
}
/**
* de Casteljau's algorithm for drawing and splitting bezier curves.
* Inspired by https://pomax.github.io/bezierinfo/
*
* @param {Number[][]} points Array of [x,y] points: [start, control1, control2, ..., end]
* The original segment to split.
* @param {Number} t Where to split the curve (value between [0, 1])
* @return {Object} An object { left, right } where left is the segment from 0..t and
* right is the segment from t..1.
*/
function decasteljau(points, t) {
var left = [];
var right = [];
function decasteljauRecurse(points, t) {
if (points.length === 1) {
left.push(points[0]);
right.push(points[0]);
} else {
var newPoints = Array(points.length - 1);
for (var i = 0; i < newPoints.length; i++) {
if (i === 0) {
left.push(points[0]);
}
if (i === newPoints.length - 1) {
right.push(points[i + 1]);
}
newPoints[i] = [(1 - t) * points[i][0] + t * points[i + 1][0], (1 - t) * points[i][1] + t * points[i + 1][1]];
}
decasteljauRecurse(newPoints, t);
}
}
if (points.length) {
decasteljauRecurse(points, t);
}
return {
left: left,
right: right.reverse()
};
}
/**
* Convert segments represented as points back into a command object
*
* @param {Number[][]} points Array of [x,y] points: [start, control1, control2, ..., end]
* Represents a segment
* @return {Object} A command object representing the segment.
*/
function pointsToCommand(points) {
var command = {};
if (points.length === 4) {
command.x2 = points[2][0];
command.y2 = points[2][1];
}
if (points.length >= 3) {
command.x1 = points[1][0];
command.y1 = points[1][1];
}
command.x = points[points.length - 1][0];
command.y = points[points.length - 1][1];
if (points.length === 4) {
// start, control1, control2, end
command.type = 'C';
} else if (points.length === 3) {
// start, control, end
command.type = 'Q';
} else {
// start, end
command.type = 'L';
}
return command;
}
/**
* Runs de Casteljau's algorithm enough times to produce the desired number of segments.
*
* @param {Number[][]} points Array of [x,y] points for de Casteljau (the initial segment to split)
* @param {Number} segmentCount Number of segments to split the original into
* @return {Number[][][]} Array of segments
*/
function splitCurveAsPoints(points, segmentCount) {
segmentCount = segmentCount || 2;
var segments = [];
var remainingCurve = points;
var tIncrement = 1 / segmentCount; // x-----x-----x-----x
// t= 0.33 0.66 1
// x-----o-----------x
// r= 0.33
// x-----o-----x
// r= 0.5 (0.33 / (1 - 0.33)) === tIncrement / (1 - (tIncrement * (i - 1))
// x-----x-----x-----x----x
// t= 0.25 0.5 0.75 1
// x-----o----------------x
// r= 0.25
// x-----o----------x
// r= 0.33 (0.25 / (1 - 0.25))
// x-----o----x
// r= 0.5 (0.25 / (1 - 0.5))
for (var i = 0; i < segmentCount - 1; i++) {
var tRelative = tIncrement / (1 - tIncrement * i);
var split = decasteljau(remainingCurve, tRelative);
segments.push(split.left);
remainingCurve = split.right;
} // last segment is just to the end from the last point
segments.push(remainingCurve);
return segments;
}
/**
* Convert command objects to arrays of points, run de Casteljau's algorithm on it
* to split into to the desired number of segments.
*
* @param {Object} commandStart The start command object
* @param {Object} commandEnd The end command object
* @param {Number} segmentCount The number of segments to create
* @return {Object[]} An array of commands representing the segments in sequence
*/
function splitCurve(commandStart, commandEnd, segmentCount) {
var points = [[commandStart.x, commandStart.y]];
if (commandEnd.x1 != null) {
points.push([commandEnd.x1, commandEnd.y1]);
}
if (commandEnd.x2 != null) {
points.push([commandEnd.x2, commandEnd.y2]);
}
points.push([commandEnd.x, commandEnd.y]);
return splitCurveAsPoints(points, segmentCount).map(pointsToCommand);
}
var commandTokenRegex = /[MLCSTQAHVZmlcstqahv]|-?[\d.e+-]+/g;
/**
* List of params for each command type in a path `d` attribute
*/
var typeMap = {
M: ['x', 'y'],
L: ['x', 'y'],
H: ['x'],
V: ['y'],
C: ['x1', 'y1', 'x2', 'y2', 'x', 'y'],
S: ['x2', 'y2', 'x', 'y'],
Q: ['x1', 'y1', 'x', 'y'],
T: ['x', 'y'],
A: ['rx', 'ry', 'xAxisRotation', 'largeArcFlag', 'sweepFlag', 'x', 'y'],
Z: []
}; // Add lower case entries too matching uppercase (e.g. 'm' == 'M')
Object.keys(typeMap).forEach(function (key) {
typeMap[key.toLowerCase()] = typeMap[key];
});
function arrayOfLength(length, value) {
var array = Array(length);
for (var i = 0; i < length; i++) {
array[i] = value;
}
return array;
}
/**
* Converts a command object to a string to be used in a `d` attribute
* @param {Object} command A command object
* @return {String} The string for the `d` attribute
*/
function commandToString(command) {
return "".concat(command.type).concat(typeMap[command.type].map(function (p) {
return command[p];
}).join(','));
}
/**
* Converts command A to have the same type as command B.
*
* e.g., L0,5 -> C0,5,0,5,0,5
*
* Uses these rules:
* x1 <- x
* x2 <- x
* y1 <- y
* y2 <- y
* rx <- 0
* ry <- 0
* xAxisRotation <- read from B
* largeArcFlag <- read from B
* sweepflag <- read from B
*
* @param {Object} aCommand Command object from path `d` attribute
* @param {Object} bCommand Command object from path `d` attribute to match against
* @return {Object} aCommand converted to type of bCommand
*/
function convertToSameType(aCommand, bCommand) {
var conversionMap = {
x1: 'x',
y1: 'y',
x2: 'x',
y2: 'y'
};
var readFromBKeys = ['xAxisRotation', 'largeArcFlag', 'sweepFlag']; // convert (but ignore M types)
if (aCommand.type !== bCommand.type && bCommand.type.toUpperCase() !== 'M') {
var aConverted = {};
Object.keys(bCommand).forEach(function (bKey) {
var bValue = bCommand[bKey]; // first read from the A command
var aValue = aCommand[bKey]; // if it is one of these values, read from B no matter what
if (aValue === undefined) {
if (readFromBKeys.includes(bKey)) {
aValue = bValue;
} else {
// if it wasn't in the A command, see if an equivalent was
if (aValue === undefined && conversionMap[bKey]) {
aValue = aCommand[conversionMap[bKey]];
} // if it doesn't have a converted value, use 0
if (aValue === undefined) {
aValue = 0;
}
}
}
aConverted[bKey] = aValue;
}); // update the type to match B
aConverted.type = bCommand.type;
aCommand = aConverted;
}
return aCommand;
}
/**
* Interpolate between command objects commandStart and commandEnd segmentCount times.
* If the types are L, Q, or C then the curves are split as per de Casteljau's algorithm.
* Otherwise we just copy commandStart segmentCount - 1 times, finally ending with commandEnd.
*
* @param {Object} commandStart Command object at the beginning of the segment
* @param {Object} commandEnd Command object at the end of the segment
* @param {Number} segmentCount The number of segments to split this into. If only 1
* Then [commandEnd] is returned.
* @return {Object[]} Array of ~segmentCount command objects between commandStart and
* commandEnd. (Can be segmentCount+1 objects if commandStart is type M).
*/
function splitSegment(commandStart, commandEnd, segmentCount) {
var segments = []; // line, quadratic bezier, or cubic bezier
if (commandEnd.type === 'L' || commandEnd.type === 'Q' || commandEnd.type === 'C') {
segments = segments.concat(splitCurve(commandStart, commandEnd, segmentCount)); // general case - just copy the same point
} else {
var copyCommand = _extends({}, commandStart); // convert M to L
if (copyCommand.type === 'M') {
copyCommand.type = 'L';
}
segments = segments.concat(arrayOfLength(segmentCount - 1).map(function () {
return copyCommand;
}));
segments.push(commandEnd);
}
return segments;
}
/**
* Extends an array of commandsToExtend to the length of the referenceCommands by
* splitting segments until the number of commands match. Ensures all the actual
* points of commandsToExtend are in the extended array.
*
* @param {Object[]} commandsToExtend The command object array to extend
* @param {Object[]} referenceCommands The command object array to match in length
* @param {Function} excludeSegment a function that takes a start command object and
* end command object and returns true if the segment should be excluded from splitting.
* @return {Object[]} The extended commandsToExtend array
*/
function extend(commandsToExtend, referenceCommands, excludeSegment) {
// compute insertion points:
// number of segments in the path to extend
var numSegmentsToExtend = commandsToExtend.length - 1; // number of segments in the reference path.
var numReferenceSegments = referenceCommands.length - 1; // this value is always between [0, 1].
var segmentRatio = numSegmentsToExtend / numReferenceSegments; // create a map, mapping segments in referenceCommands to how many points
// should be added in that segment (should always be >= 1 since we need each
// point itself).
// 0 = segment 0-1, 1 = segment 1-2, n-1 = last vertex
var countPointsPerSegment = arrayOfLength(numReferenceSegments).reduce(function (accum, d, i) {
var insertIndex = Math.floor(segmentRatio * i); // handle excluding segments
if (excludeSegment && insertIndex < commandsToExtend.length - 1 && excludeSegment(commandsToExtend[insertIndex], commandsToExtend[insertIndex + 1])) {
// set the insertIndex to the segment that this point should be added to:
// round the insertIndex essentially so we split half and half on
// neighbouring segments. hence the segmentRatio * i < 0.5
var addToPriorSegment = segmentRatio * i % 1 < 0.5; // only skip segment if we already have 1 point in it (can't entirely remove a segment)
if (accum[insertIndex]) {
// TODO - Note this is a naive algorithm that should work for most d3-area use cases
// but if two adjacent segments are supposed to be skipped, this will not perform as
// expected. Could be updated to search for nearest segment to place the point in, but
// will only do that if necessary.
// add to the prior segment
if (addToPriorSegment) {
if (insertIndex > 0) {
insertIndex -= 1; // not possible to add to previous so adding to next
} else if (insertIndex < commandsToExtend.length - 1) {
insertIndex += 1;
} // add to next segment
} else if (insertIndex < commandsToExtend.length - 1) {
insertIndex += 1; // not possible to add to next so adding to previous
} else if (insertIndex > 0) {
insertIndex -= 1;
}
}
}
accum[insertIndex] = (accum[insertIndex] || 0) + 1;
return accum;
}, []); // extend each segment to have the correct number of points for a smooth interpolation
var extended = countPointsPerSegment.reduce(function (extended, segmentCount, i) {
// if last command, just add `segmentCount` number of times
if (i === commandsToExtend.length - 1) {
var lastCommandCopies = arrayOfLength(segmentCount, _extends({}, commandsToExtend[commandsToExtend.length - 1])); // convert M to L
if (lastCommandCopies[0].type === 'M') {
lastCommandCopies.forEach(function (d) {
d.type = 'L';
});
}
return extended.concat(lastCommandCopies);
} // otherwise, split the segment segmentCount times.
return extended.concat(splitSegment(commandsToExtend[i], commandsToExtend[i + 1], segmentCount));
}, []); // add in the very first point since splitSegment only adds in the ones after it
extended.unshift(commandsToExtend[0]);
return extended;
}
/**
* Takes a path `d` string and converts it into an array of command
* objects. Drops the `Z` character.
*
* @param {String|null} d A path `d` string
*/
function pathCommandsFromString(d) {
// split into valid tokens
var tokens = (d || '').match(commandTokenRegex) || [];
var commands = [];
var commandArgs;
var command; // iterate over each token, checking if we are at a new command
// by presence in the typeMap
for (var i = 0; i < tokens.length; ++i) {
commandArgs = typeMap[tokens[i]]; // new command found:
if (commandArgs) {
command = {
type: tokens[i]
}; // add each of the expected args for this command:
for (var a = 0; a < commandArgs.length; ++a) {
command[commandArgs[a]] = +tokens[i + a + 1];
} // need to increment our token index appropriately since
// we consumed token args
i += commandArgs.length;
commands.push(command);
}
}
return commands;
}
/**
* Interpolate from A to B by extending A and B during interpolation to have
* the same number of points. This allows for a smooth transition when they
* have a different number of points.
*
* Ignores the `Z` command in paths unless both A and B end with it.
*
* This function works directly with arrays of command objects instead of with
* path `d` strings (see interpolatePath for working with `d` strings).
*
* @param {Object[]} aCommandsInput Array of path commands
* @param {Object[]} bCommandsInput Array of path commands
* @param {(Function|Object)} interpolateOptions
* @param {Function} interpolateOptions.excludeSegment a function that takes a start command object and
* end command object and returns true if the segment should be excluded from splitting.
* @param {Boolean} interpolateOptions.snapEndsToInput a boolean indicating whether end of input should
* be sourced from input argument or computed.
* @returns {Function} Interpolation function that maps t ([0, 1]) to an array of path commands.
*/
function interpolatePathCommands(aCommandsInput, bCommandsInput, interpolateOptions) {
// make a copy so we don't mess with the input arrays
var aCommands = aCommandsInput == null ? [] : aCommandsInput.slice();
var bCommands = bCommandsInput == null ? [] : bCommandsInput.slice();
var _ref = _typeof(interpolateOptions) === 'object' ? interpolateOptions : {
excludeSegment: interpolateOptions,
snapEndsToInput: true
},
excludeSegment = _ref.excludeSegment,
snapEndsToInput = _ref.snapEndsToInput; // both input sets are empty, so we don't interpolate
if (!aCommands.length && !bCommands.length) {
return function nullInterpolator() {
return [];
};
} // do we add Z during interpolation? yes if both have it. (we'd expect both to have it or not)
var addZ = (aCommands.length === 0 || aCommands[aCommands.length - 1].type === 'Z') && (bCommands.length === 0 || bCommands[bCommands.length - 1].type === 'Z'); // we temporarily remove Z
if (aCommands.length > 0 && aCommands[aCommands.length - 1].type === 'Z') {
aCommands.pop();
}
if (bCommands.length > 0 && bCommands[bCommands.length - 1].type === 'Z') {
bCommands.pop();
} // if A is empty, treat it as if it used to contain just the first point
// of B. This makes it so the line extends out of from that first point.
if (!aCommands.length) {
aCommands.push(bCommands[0]); // otherwise if B is empty, treat it as if it contains the first point
// of A. This makes it so the line retracts into the first point.
} else if (!bCommands.length) {
bCommands.push(aCommands[0]);
} // extend to match equal size
var numPointsToExtend = Math.abs(bCommands.length - aCommands.length);
if (numPointsToExtend !== 0) {
// B has more points than A, so add points to A before interpolating
if (bCommands.length > aCommands.length) {
aCommands = extend(aCommands, bCommands, excludeSegment); // else if A has more points than B, add more points to B
} else if (bCommands.length < aCommands.length) {
bCommands = extend(bCommands, aCommands, excludeSegment);
}
} // commands have same length now.
// convert commands in A to the same type as those in B
aCommands = aCommands.map(function (aCommand, i) {
return convertToSameType(aCommand, bCommands[i]);
}); // create mutable interpolated command objects
var interpolatedCommands = aCommands.map(function (aCommand) {
return _objectSpread2({}, aCommand);
});
if (addZ) {
interpolatedCommands.push({
type: 'Z'
});
aCommands.push({
type: 'Z'
}); // required for when returning at t == 0
}
return function pathCommandInterpolator(t) {
// at 1 return the final value without the extensions used during interpolation
if (t === 1 && snapEndsToInput) {
return bCommandsInput == null ? [] : bCommandsInput;
} // work with aCommands directly since interpolatedCommands are mutated
if (t === 0) {
return aCommands;
} // interpolate the commands using the mutable interpolated command objs
for (var i = 0; i < interpolatedCommands.length; ++i) {
// if (interpolatedCommands[i].type === 'Z') continue;
var aCommand = aCommands[i];
var bCommand = bCommands[i];
var interpolatedCommand = interpolatedCommands[i];
var _iterator = _createForOfIteratorHelper(typeMap[interpolatedCommand.type]),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var arg = _step.value;
interpolatedCommand[arg] = (1 - t) * aCommand[arg] + t * bCommand[arg]; // do not use floats for flags (#27), round to integer
if (arg === 'largeArcFlag' || arg === 'sweepFlag') {
interpolatedCommand[arg] = Math.round(interpolatedCommand[arg]);
}
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
}
return interpolatedCommands;
};
}
/** @typedef InterpolateOptions */
/**
* Interpolate from A to B by extending A and B during interpolation to have
* the same number of points. This allows for a smooth transition when they
* have a different number of points.
*
* Ignores the `Z` character in paths unless both A and B end with it.
*
* @param {String} a The `d` attribute for a path
* @param {String} b The `d` attribute for a path
* @param {((command1, command2) => boolean|{
* excludeSegment?: (command1, command2) => boolean;
* snapEndsToInput?: boolean
* })} interpolateOptions The excludeSegment function or an options object
* - interpolateOptions.excludeSegment a function that takes a start command object and
* end command object and returns true if the segment should be excluded from splitting.
* - interpolateOptions.snapEndsToInput a boolean indicating whether end of input should
* be sourced from input argument or computed.
* @returns {Function} Interpolation function that maps t ([0, 1]) to a path `d` string.
*/
function interpolatePath(a, b, interpolateOptions) {
var aCommands = pathCommandsFromString(a);
var bCommands = pathCommandsFromString(b);
var _ref2 = _typeof(interpolateOptions) === 'object' ? interpolateOptions : {
excludeSegment: interpolateOptions,
snapEndsToInput: true
},
excludeSegment = _ref2.excludeSegment,
snapEndsToInput = _ref2.snapEndsToInput;
if (!aCommands.length && !bCommands.length) {
return function nullInterpolator() {
return '';
};
}
var commandInterpolator = interpolatePathCommands(aCommands, bCommands, {
excludeSegment: excludeSegment,
snapEndsToInput: snapEndsToInput
});
return function pathStringInterpolator(t) {
// at 1 return the final value without the extensions used during interpolation
if (t === 1 && snapEndsToInput) {
return b == null ? '' : b;
}
var interpolatedCommands = commandInterpolator(t); // convert to a string (fastest concat: https://jsperf.com/join-concat/150)
var interpolatedString = '';
var _iterator2 = _createForOfIteratorHelper(interpolatedCommands),
_step2;
try {
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
var interpolatedCommand = _step2.value;
interpolatedString += commandToString(interpolatedCommand);
}
} catch (err) {
_iterator2.e(err);
} finally {
_iterator2.f();
}
return interpolatedString;
};
}
exports.interpolatePath = interpolatePath;
exports.interpolatePathCommands = interpolatePathCommands;
exports.pathCommandsFromString = pathCommandsFromString;
Object.defineProperty(exports, '__esModule', { value: true });
})));

606
src/fit-curve.js Normal file
View File

@ -0,0 +1,606 @@
/**
* @preserve JavaScript implementation of
* Algorithm for Automatically Fitting Digitized Curves
* by Philip J. Schneider
* "Graphics Gems", Academic Press, 1990
*
* The MIT License (MIT)
*
* https://github.com/soswow/fit-curves
*/
/**
* Fit one or more Bezier curves to a set of points.
*
* @param {Array<Array<Number>>} points - Array of digitized points, e.g. [[5,5],[5,50],[110,140],[210,160],[320,110]]
* @param {Number} maxError - Tolerance, squared error between points and fitted curve
* @returns {Array<Array<Array<Number>>>} Array of Bezier curves, where each element is [first-point, control-point-1, control-point-2, second-point] and points are [x, y]
*/
export function fitCurve(points, maxError, progressCallback) {
if (!Array.isArray(points)) {
throw new TypeError("First argument should be an array");
}
points.forEach(function (point) {
if (!Array.isArray(point) || point.some(function (item) {
return typeof item !== 'number';
}) || point.length !== points[0].length) {
throw Error("Each point should be an array of numbers. Each point should have the same amount of numbers.");
}
});
// Remove duplicate points
points = points.filter(function (point, i) {
return i === 0 || !point.every(function (val, j) {
return val === points[i - 1][j];
});
});
if (points.length < 2) {
return [];
}
var len = points.length;
var leftTangent = createTangent(points[1], points[0]);
var rightTangent = createTangent(points[len - 2], points[len - 1]);
return fitCubic(points, leftTangent, rightTangent, maxError, progressCallback);
}
/**
* Fit a Bezier curve to a (sub)set of digitized points.
* Your code should not call this function directly. Use {@link fitCurve} instead.
*
* @param {Array<Array<Number>>} points - Array of digitized points, e.g. [[5,5],[5,50],[110,140],[210,160],[320,110]]
* @param {Array<Number>} leftTangent - Unit tangent vector at start point
* @param {Array<Number>} rightTangent - Unit tangent vector at end point
* @param {Number} error - Tolerance, squared error between points and fitted curve
* @returns {Array<Array<Array<Number>>>} Array of Bezier curves, where each element is [first-point, control-point-1, control-point-2, second-point] and points are [x, y]
*/
function fitCubic(points, leftTangent, rightTangent, error, progressCallback) {
var MaxIterations = 20; //Max times to try iterating (to find an acceptable curve)
var bezCurve, //Control points of fitted Bezier curve
u, //Parameter values for point
uPrime, //Improved parameter values
maxError, prevErr, //Maximum fitting error
splitPoint, prevSplit, //Point to split point set at if we need more than one curve
centerVector, toCenterTangent, fromCenterTangent, //Unit tangent vector(s) at splitPoint
beziers, //Array of fitted Bezier curves if we need more than one curve
dist, i;
//console.log('fitCubic, ', points.length);
//Use heuristic if region only has two points in it
if (points.length === 2) {
dist = maths.vectorLen(maths.subtract(points[0], points[1])) / 3.0;
bezCurve = [points[0], maths.addArrays(points[0], maths.mulItems(leftTangent, dist)), maths.addArrays(points[1], maths.mulItems(rightTangent, dist)), points[1]];
return [bezCurve];
}
//Parameterize points, and attempt to fit curve
u = chordLengthParameterize(points);
var _generateAndReport = generateAndReport(points, u, u, leftTangent, rightTangent, progressCallback);
bezCurve = _generateAndReport[0];
maxError = _generateAndReport[1];
splitPoint = _generateAndReport[2];
if (maxError === 0 || maxError < error) {
return [bezCurve];
}
//If error not too large, try some reparameterization and iteration
if (maxError < error * error) {
uPrime = u;
prevErr = maxError;
prevSplit = splitPoint;
for (i = 0; i < MaxIterations; i++) {
uPrime = reparameterize(bezCurve, points, uPrime);
var _generateAndReport2 = generateAndReport(points, u, uPrime, leftTangent, rightTangent, progressCallback);
bezCurve = _generateAndReport2[0];
maxError = _generateAndReport2[1];
splitPoint = _generateAndReport2[2];
if (maxError < error) {
return [bezCurve];
}
//If the development of the fitted curve grinds to a halt,
//we abort this attempt (and try a shorter curve):
else if (splitPoint === prevSplit) {
var errChange = maxError / prevErr;
if (errChange > .9999 && errChange < 1.0001) {
break;
}
}
prevErr = maxError;
prevSplit = splitPoint;
}
}
//Fitting failed -- split at max error point and fit recursively
beziers = [];
//To create a smooth transition from one curve segment to the next, we
//calculate the line between the points directly before and after the
//center, and use that as the tangent both to and from the center point.
centerVector = maths.subtract(points[splitPoint - 1], points[splitPoint + 1]);
//However, this won't work if they're the same point, because the line we
//want to use as a tangent would be 0. Instead, we calculate the line from
//that "double-point" to the center point, and use its tangent.
if (centerVector.every(function (val) {
return val === 0;
})) {
//[x,y] -> [-y,x]: http://stackoverflow.com/a/4780141/1869660
centerVector = maths.subtract(points[splitPoint - 1], points[splitPoint]);
var _ref = [-centerVector[1], centerVector[0]];
centerVector[0] = _ref[0];
centerVector[1] = _ref[1];
}
toCenterTangent = maths.normalize(centerVector);
//To and from need to point in opposite directions:
fromCenterTangent = maths.mulItems(toCenterTangent, -1);
/*
Note: An alternative to this "divide and conquer" recursion could be to always
let new curve segments start by trying to go all the way to the end,
instead of only to the end of the current subdivided polyline.
That might let many segments fit a few points more, reducing the number of total segments.
However, a few tests have shown that the segment reduction is insignificant
(240 pts, 100 err: 25 curves vs 27 curves. 140 pts, 100 err: 17 curves on both),
and the results take twice as many steps and milliseconds to finish,
without looking any better than what we already have.
*/
beziers = beziers.concat(fitCubic(points.slice(0, splitPoint + 1), leftTangent, toCenterTangent, error, progressCallback));
beziers = beziers.concat(fitCubic(points.slice(splitPoint), fromCenterTangent, rightTangent, error, progressCallback));
return beziers;
};
function generateAndReport(points, paramsOrig, paramsPrime, leftTangent, rightTangent, progressCallback) {
var bezCurve, maxError, splitPoint;
bezCurve = generateBezier(points, paramsPrime, leftTangent, rightTangent, progressCallback);
//Find max deviation of points to fitted curve.
//Here we always use the original parameters (from chordLengthParameterize()),
//because we need to compare the current curve to the actual source polyline,
//and not the currently iterated parameters which reparameterize() & generateBezier() use,
//as those have probably drifted far away and may no longer be in ascending order.
var _computeMaxError = computeMaxError(points, bezCurve, paramsOrig);
maxError = _computeMaxError[0];
splitPoint = _computeMaxError[1];
if (progressCallback) {
progressCallback({
bez: bezCurve,
points: points,
params: paramsOrig,
maxErr: maxError,
maxPoint: splitPoint
});
}
return [bezCurve, maxError, splitPoint];
}
/**
* Use least-squares method to find Bezier control points for region.
*
* @param {Array<Array<Number>>} points - Array of digitized points
* @param {Array<Number>} parameters - Parameter values for region
* @param {Array<Number>} leftTangent - Unit tangent vector at start point
* @param {Array<Number>} rightTangent - Unit tangent vector at end point
* @returns {Array<Array<Number>>} Approximated Bezier curve: [first-point, control-point-1, control-point-2, second-point] where points are [x, y]
*/
function generateBezier(points, parameters, leftTangent, rightTangent) {
var bezCurve,
//Bezier curve ctl pts
A,
a,
//Precomputed rhs for eqn
C,
X,
//Matrices C & X
det_C0_C1,
det_C0_X,
det_X_C1,
//Determinants of matrices
alpha_l,
alpha_r,
//Alpha values, left and right
epsilon,
segLength,
i,
len,
tmp,
u,
ux,
firstPoint = points[0],
lastPoint = points[points.length - 1];
bezCurve = [firstPoint, null, null, lastPoint];
//console.log('gb', parameters.length);
//Compute the A's
A = maths.zeros_Xx2x2(parameters.length);
for (i = 0, len = parameters.length; i < len; i++) {
u = parameters[i];
ux = 1 - u;
a = A[i];
a[0] = maths.mulItems(leftTangent, 3 * u * (ux * ux));
a[1] = maths.mulItems(rightTangent, 3 * ux * (u * u));
}
//Create the C and X matrices
C = [[0, 0], [0, 0]];
X = [0, 0];
for (i = 0, len = points.length; i < len; i++) {
u = parameters[i];
a = A[i];
C[0][0] += maths.dot(a[0], a[0]);
C[0][1] += maths.dot(a[0], a[1]);
C[1][0] += maths.dot(a[0], a[1]);
C[1][1] += maths.dot(a[1], a[1]);
tmp = maths.subtract(points[i], bezier.q([firstPoint, firstPoint, lastPoint, lastPoint], u));
X[0] += maths.dot(a[0], tmp);
X[1] += maths.dot(a[1], tmp);
}
//Compute the determinants of C and X
det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
det_C0_X = C[0][0] * X[1] - C[1][0] * X[0];
det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
//Finally, derive alpha values
alpha_l = det_C0_C1 === 0 ? 0 : det_X_C1 / det_C0_C1;
alpha_r = det_C0_C1 === 0 ? 0 : det_C0_X / det_C0_C1;
//If alpha negative, use the Wu/Barsky heuristic (see text).
//If alpha is 0, you get coincident control points that lead to
//divide by zero in any subsequent NewtonRaphsonRootFind() call.
segLength = maths.vectorLen(maths.subtract(firstPoint, lastPoint));
epsilon = 1.0e-6 * segLength;
if (alpha_l < epsilon || alpha_r < epsilon) {
//Fall back on standard (probably inaccurate) formula, and subdivide further if needed.
bezCurve[1] = maths.addArrays(firstPoint, maths.mulItems(leftTangent, segLength / 3.0));
bezCurve[2] = maths.addArrays(lastPoint, maths.mulItems(rightTangent, segLength / 3.0));
} else {
//First and last control points of the Bezier curve are
//positioned exactly at the first and last data points
//Control points 1 and 2 are positioned an alpha distance out
//on the tangent vectors, left and right, respectively
bezCurve[1] = maths.addArrays(firstPoint, maths.mulItems(leftTangent, alpha_l));
bezCurve[2] = maths.addArrays(lastPoint, maths.mulItems(rightTangent, alpha_r));
}
return bezCurve;
};
/**
* Given set of points and their parameterization, try to find a better parameterization.
*
* @param {Array<Array<Number>>} bezier - Current fitted curve
* @param {Array<Array<Number>>} points - Array of digitized points
* @param {Array<Number>} parameters - Current parameter values
* @returns {Array<Number>} New parameter values
*/
function reparameterize(bezier, points, parameters) {
/*
var j, len, point, results, u;
results = [];
for (j = 0, len = points.length; j < len; j++) {
point = points[j], u = parameters[j];
results.push(newtonRaphsonRootFind(bezier, point, u));
}
return results;
//*/
return parameters.map(function (p, i) {
return newtonRaphsonRootFind(bezier, points[i], p);
});
};
/**
* Use Newton-Raphson iteration to find better root.
*
* @param {Array<Array<Number>>} bez - Current fitted curve
* @param {Array<Number>} point - Digitized point
* @param {Number} u - Parameter value for "P"
* @returns {Number} New u
*/
function newtonRaphsonRootFind(bez, point, u) {
/*
Newton's root finding algorithm calculates f(x)=0 by reiterating
x_n+1 = x_n - f(x_n)/f'(x_n)
We are trying to find curve parameter u for some point p that minimizes
the distance from that point to the curve. Distance point to curve is d=q(u)-p.
At minimum distance the point is perpendicular to the curve.
We are solving
f = q(u)-p * q'(u) = 0
with
f' = q'(u) * q'(u) + q(u)-p * q''(u)
gives
u_n+1 = u_n - |q(u_n)-p * q'(u_n)| / |q'(u_n)**2 + q(u_n)-p * q''(u_n)|
*/
var d = maths.subtract(bezier.q(bez, u), point),
qprime = bezier.qprime(bez, u),
numerator = maths.mulMatrix(d, qprime),
denominator = maths.sum(maths.squareItems(qprime)) + 2 * maths.mulMatrix(d, bezier.qprimeprime(bez, u));
if (denominator === 0) {
return u;
} else {
return u - numerator / denominator;
}
};
/**
* Assign parameter values to digitized points using relative distances between points.
*
* @param {Array<Array<Number>>} points - Array of digitized points
* @returns {Array<Number>} Parameter values
*/
function chordLengthParameterize(points) {
var u = [],
currU,
prevU,
prevP;
points.forEach(function (p, i) {
currU = i ? prevU + maths.vectorLen(maths.subtract(p, prevP)) : 0;
u.push(currU);
prevU = currU;
prevP = p;
});
u = u.map(function (x) {
return x / prevU;
});
return u;
};
/**
* Find the maximum squared distance of digitized points to fitted curve.
*
* @param {Array<Array<Number>>} points - Array of digitized points
* @param {Array<Array<Number>>} bez - Fitted curve
* @param {Array<Number>} parameters - Parameterization of points
* @returns {Array<Number>} Maximum error (squared) and point of max error
*/
function computeMaxError(points, bez, parameters) {
var dist, //Current error
maxDist, //Maximum error
splitPoint, //Point of maximum error
v, //Vector from point to curve
i, count, point, t;
maxDist = 0;
splitPoint = Math.floor(points.length / 2);
var t_distMap = mapTtoRelativeDistances(bez, 10);
for (i = 0, count = points.length; i < count; i++) {
point = points[i];
//Find 't' for a point on the bez curve that's as close to 'point' as possible:
t = find_t(bez, parameters[i], t_distMap, 10);
v = maths.subtract(bezier.q(bez, t), point);
dist = v[0] * v[0] + v[1] * v[1];
if (dist > maxDist) {
maxDist = dist;
splitPoint = i;
}
}
return [maxDist, splitPoint];
};
//Sample 't's and map them to relative distances along the curve:
var mapTtoRelativeDistances = function mapTtoRelativeDistances(bez, B_parts) {
var B_t_curr;
var B_t_dist = [0];
var B_t_prev = bez[0];
var sumLen = 0;
for (var i = 1; i <= B_parts; i++) {
B_t_curr = bezier.q(bez, i / B_parts);
sumLen += maths.vectorLen(maths.subtract(B_t_curr, B_t_prev));
B_t_dist.push(sumLen);
B_t_prev = B_t_curr;
}
//Normalize B_length to the same interval as the parameter distances; 0 to 1:
B_t_dist = B_t_dist.map(function (x) {
return x / sumLen;
});
return B_t_dist;
};
function find_t(bez, param, t_distMap, B_parts) {
if (param < 0) {
return 0;
}
if (param > 1) {
return 1;
}
/*
'param' is a value between 0 and 1 telling us the relative position
of a point on the source polyline (linearly from the start (0) to the end (1)).
To see if a given curve - 'bez' - is a close approximation of the polyline,
we compare such a poly-point to the point on the curve that's the same
relative distance along the curve's length.
But finding that curve-point takes a little work:
There is a function "B(t)" to find points along a curve from the parametric parameter 't'
(also relative from 0 to 1: http://stackoverflow.com/a/32841764/1869660
http://pomax.github.io/bezierinfo/#explanation),
but 't' isn't linear by length (http://gamedev.stackexchange.com/questions/105230).
So, we sample some points along the curve using a handful of values for 't'.
Then, we calculate the length between those samples via plain euclidean distance;
B(t) concentrates the points around sharp turns, so this should give us a good-enough outline of the curve.
Thus, for a given relative distance ('param'), we can now find an upper and lower value
for the corresponding 't' by searching through those sampled distances.
Finally, we just use linear interpolation to find a better value for the exact 't'.
More info:
http://gamedev.stackexchange.com/questions/105230/points-evenly-spaced-along-a-bezier-curve
http://stackoverflow.com/questions/29438398/cheap-way-of-calculating-cubic-bezier-length
http://steve.hollasch.net/cgindex/curves/cbezarclen.html
https://github.com/retuxx/tinyspline
*/
var lenMax, lenMin, tMax, tMin, t;
//Find the two t-s that the current param distance lies between,
//and then interpolate a somewhat accurate value for the exact t:
for (var i = 1; i <= B_parts; i++) {
if (param <= t_distMap[i]) {
tMin = (i - 1) / B_parts;
tMax = i / B_parts;
lenMin = t_distMap[i - 1];
lenMax = t_distMap[i];
t = (param - lenMin) / (lenMax - lenMin) * (tMax - tMin) + tMin;
break;
}
}
return t;
}
/**
* Creates a vector of length 1 which shows the direction from B to A
*/
function createTangent(pointA, pointB) {
return maths.normalize(maths.subtract(pointA, pointB));
}
/*
Simplified versions of what we need from math.js
Optimized for our input, which is only numbers and 1x2 arrays (i.e. [x, y] coordinates).
*/
var maths = function () {
function maths() {
_classCallCheck(this, maths);
}
maths.zeros_Xx2x2 = function zeros_Xx2x2(x) {
var zs = [];
while (x--) {
zs.push([0, 0]);
}
return zs;
};
maths.mulItems = function mulItems(items, multiplier) {
return items.map(function (x) {
return x * multiplier;
});
};
maths.mulMatrix = function mulMatrix(m1, m2) {
//https://en.wikipedia.org/wiki/Matrix_multiplication#Matrix_product_.28two_matrices.29
//Simplified to only handle 1-dimensional matrices (i.e. arrays) of equal length:
return m1.reduce(function (sum, x1, i) {
return sum + x1 * m2[i];
}, 0);
};
maths.subtract = function subtract(arr1, arr2) {
return arr1.map(function (x1, i) {
return x1 - arr2[i];
});
};
maths.addArrays = function addArrays(arr1, arr2) {
return arr1.map(function (x1, i) {
return x1 + arr2[i];
});
};
maths.addItems = function addItems(items, addition) {
return items.map(function (x) {
return x + addition;
});
};
maths.sum = function sum(items) {
return items.reduce(function (sum, x) {
return sum + x;
});
};
maths.dot = function dot(m1, m2) {
return maths.mulMatrix(m1, m2);
};
maths.vectorLen = function vectorLen(v) {
return Math.hypot.apply(Math, v);
};
maths.divItems = function divItems(items, divisor) {
return items.map(function (x) {
return x / divisor;
});
};
maths.squareItems = function squareItems(items) {
return items.map(function (x) {
return x * x;
});
};
maths.normalize = function normalize(v) {
return this.divItems(v, this.vectorLen(v));
};
return maths;
}();
var bezier = function () {
function bezier() {
_classCallCheck(this, bezier);
}
bezier.q = function q(ctrlPoly, t) {
var tx = 1.0 - t;
var pA = maths.mulItems(ctrlPoly[0], tx * tx * tx),
pB = maths.mulItems(ctrlPoly[1], 3 * tx * tx * t),
pC = maths.mulItems(ctrlPoly[2], 3 * tx * t * t),
pD = maths.mulItems(ctrlPoly[3], t * t * t);
return maths.addArrays(maths.addArrays(pA, pB), maths.addArrays(pC, pD));
};
bezier.qprime = function qprime(ctrlPoly, t) {
var tx = 1.0 - t;
var pA = maths.mulItems(maths.subtract(ctrlPoly[1], ctrlPoly[0]), 3 * tx * tx),
pB = maths.mulItems(maths.subtract(ctrlPoly[2], ctrlPoly[1]), 6 * tx * t),
pC = maths.mulItems(maths.subtract(ctrlPoly[3], ctrlPoly[2]), 3 * t * t);
return maths.addArrays(maths.addArrays(pA, pB), pC);
};
bezier.qprimeprime = function qprimeprime(ctrlPoly, t) {
return maths.addArrays(maths.mulItems(maths.addArrays(maths.subtract(ctrlPoly[2], maths.mulItems(ctrlPoly[1], 2)), ctrlPoly[0]), 6 * (1.0 - t)), maths.mulItems(maths.addArrays(maths.subtract(ctrlPoly[3], maths.mulItems(ctrlPoly[2], 2)), ctrlPoly[1]), 6 * t));
};
return bezier;
}();

48
src/index.html Normal file
View File

@ -0,0 +1,48 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="stylesheet" href="styles.css" />
<link rel="stylesheet" href="coloris.css"/>
<script src="coloris.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Tauri App</title>
<script type="module" src="/simplify.js"></script>
<script type="module" src="/d3-interpolate-path.js"></script>
<script type="module" src="/main.js" defer></script>
</head>
<body>
<!-- <main class="container"> -->
<!-- <h1>Welcome to Tauri</h1>
<div class="row">
<a href="https://tauri.app" target="_blank">
<img src="/assets/tauri.svg" class="logo tauri" alt="Tauri logo" />
</a>
<a
href="https://developer.mozilla.org/en-US/docs/Web/JavaScript"
target="_blank"
>
<img
src="/assets/javascript.svg"
class="logo vanilla"
alt="JavaScript logo"
/>
</a>
</div>
<p>Click on the Tauri logo to learn more about the framework</p> -->
<!-- <form class="row" id="greet-form">
<input id="greet-input" placeholder="Enter a name..." />
<button type="submit">Greet</button>
</form>
<p id="greet-msg"></p> -->
<div class="pane" id="root">
<!-- <img src="/assets/tauri.svg" class="logo tauri" alt="Tauri logo" /> -->
</div>
<!-- </main> -->
</body>
</html>

2426
src/main.js Normal file

File diff suppressed because it is too large Load Diff

97
src/newfile.js Normal file
View File

@ -0,0 +1,97 @@
let overlay;
let newFileDialog;
function createNewFileDialog(callback) {
overlay = document.createElement('div');
overlay.id = 'overlay';
document.body.appendChild(overlay);
newFileDialog = document.createElement('div');
newFileDialog.id = 'newFileDialog';
newFileDialog.classList.add('hidden');
document.body.appendChild(newFileDialog);
// Create dialog content dynamically
const title = document.createElement('h3');
title.textContent = 'Create New File';
newFileDialog.appendChild(title);
// Create Width input
const widthLabel = document.createElement('label');
widthLabel.setAttribute('for', 'width');
widthLabel.classList.add('dialog-label');
widthLabel.textContent = 'Width:';
newFileDialog.appendChild(widthLabel);
const widthInput = document.createElement('input');
widthInput.type = 'number';
widthInput.id = 'width';
widthInput.classList.add('dialog-input');
widthInput.value = '1500'; // Default value
newFileDialog.appendChild(widthInput);
// Create Height input
const heightLabel = document.createElement('label');
heightLabel.setAttribute('for', 'height');
heightLabel.classList.add('dialog-label');
heightLabel.textContent = 'Height:';
newFileDialog.appendChild(heightLabel);
const heightInput = document.createElement('input');
heightInput.type = 'number';
heightInput.id = 'height';
heightInput.classList.add('dialog-input');
heightInput.value = '1000'; // Default value
newFileDialog.appendChild(heightInput);
// Create FPS input
const fpsLabel = document.createElement('label');
fpsLabel.setAttribute('for', 'fps');
fpsLabel.classList.add('dialog-label');
fpsLabel.textContent = 'Frames per Second:';
newFileDialog.appendChild(fpsLabel);
const fpsInput = document.createElement('input');
fpsInput.type = 'number';
fpsInput.id = 'fps';
fpsInput.classList.add('dialog-input');
fpsInput.value = '12'; // Default value
newFileDialog.appendChild(fpsInput);
// Create Create button
const createButton = document.createElement('button');
createButton.textContent = 'Create';
createButton.classList.add('dialog-button');
createButton.onclick = createNewFile;
newFileDialog.appendChild(createButton);
// Create the new file (simulation)
function createNewFile() {
const width = document.getElementById('width').value;
const height = document.getElementById('height').value;
const fps = document.getElementById('fps').value;
console.log(`New file created with width: ${width} and height: ${height}`);
callback(width, height, fps)
// Add any further logic to handle the new file creation here
closeDialog(); // Close the dialog after file creation
}
// Close the dialog if the overlay is clicked
overlay.onclick = closeDialog;
}
// Show the dialog
function showNewFileDialog() {
overlay.style.display = 'block';
newFileDialog.style.display = 'block';
}
// Close the dialog
function closeDialog() {
overlay.style.display = 'none';
newFileDialog.style.display = 'none';
}
export { createNewFileDialog, showNewFileDialog, closeDialog };

176
src/quadtree.js Normal file
View File

@ -0,0 +1,176 @@
class Quadtree {
constructor(boundary, capacity) {
// Boundary is the bounding box of the area this quadtree node covers
// Capacity is the maximum number of curves a node can hold before subdividing
this.boundary = boundary; // {x: {min: <value>, max: <value>}, y: {min: <value>, max: <value>}}
this.capacity = capacity;
this.curveIndexes = [];
this.curves = [];
this.divided = false;
this.nw = null; // Northwest quadrant
this.ne = null; // Northeast quadrant
this.sw = null; // Southwest quadrant
this.se = null; // Southeast quadrant
}
// Check if a bounding box intersects with the boundary of this quadtree node
intersects(bbox) {
return !(bbox.x.max < this.boundary.x.min || bbox.x.min > this.boundary.x.max ||
bbox.y.max < this.boundary.y.min || bbox.y.min > this.boundary.y.max);
}
// Subdivide this quadtree node into 4 quadrants
subdivide() {
const xMid = (this.boundary.x.min + this.boundary.x.max) / 2;
const yMid = (this.boundary.y.min + this.boundary.y.max) / 2;
const nwBoundary = { x: { min: this.boundary.x.min, max: xMid }, y: { min: this.boundary.y.min, max: yMid }};
const neBoundary = { x: { min: xMid, max: this.boundary.x.max }, y: { min: this.boundary.y.min, max: yMid }};
const swBoundary = { x: { min: this.boundary.x.min, max: xMid }, y: { min: yMid, max: this.boundary.y.max }};
const seBoundary = { x: { min: xMid, max: this.boundary.x.max }, y: { min: yMid, max: this.boundary.y.max }};
this.nw = new Quadtree(nwBoundary, this.capacity);
this.ne = new Quadtree(neBoundary, this.capacity);
this.sw = new Quadtree(swBoundary, this.capacity);
this.se = new Quadtree(seBoundary, this.capacity);
this.divided = true;
}
insert (curve, curveIdx) {
const bbox = curve.bbox()
if (!this.intersects(curve.bbox())) {
let newNode = new Quadtree(this.boundary, this.capacity)
newNode.curveIndexes = this.curveIndexes;
newNode.curves = this.curves;
newNode.divided = this.divided;
newNode.nw = this.nw;
newNode.ne = this.ne;
newNode.sw = this.sw;
newNode.se = this.se;
this.curveIndexes = [];
this.curves = [];
this.subdivide()
if (bbox.x.max < this.boundary.x.max) {
if (bbox.y.max < this.boundary.y.max) {
this.boundary.x.min -= this.boundary.x.max - this.boundary.x.min
this.boundary.y.min -= this.boundary.y.max - this.boundary.y.min
this.nw = newNode
} else {
this.boundary.x.min -= this.boundary.x.max - this.boundary.x.min
this.boundary.y.max += this.boundary.y.max - this.boundary.y.min
this.sw = newNode
}
} else {
if (bbox.y.max < this.boundary.y.max) {
this.boundary.x.max += this.boundary.x.max - this.boundary.x.min
this.boundary.y.min -= this.boundary.y.max - this.boundary.y.min
this.ne = newNode
} else {
this.boundary.x.max += this.boundary.x.max - this.boundary.x.min
this.boundary.y.max += this.boundary.y.max - this.boundary.y.min
this.se = newNode
}
}
return this.insert(curve, curveIdx)
} else {
return this._insert(curve, curveIdx)
}
}
// Insert a curve into the quadtree, subdividing if necessary
_insert(curve, curveIdx) {
// If the curve's bounding box doesn't intersect this node's boundary, do nothing
if (!this.intersects(curve.bbox())) {
return false;
}
// If the node has space, insert the curve here
if (this.curves.length < this.capacity) {
this.curves.push(curve);
this.curveIndexes.push(curveIdx)
return true;
}
// Otherwise, subdivide and insert the curve into the appropriate quadrant
if (!this.divided) {
this.subdivide();
}
return (
this.nw._insert(curve, curveIdx) ||
this.ne._insert(curve, curveIdx) ||
this.sw._insert(curve, curveIdx) ||
this.se._insert(curve, curveIdx)
);
}
// Query all curves that intersect with a given bounding box
query(range, found = []) {
// If the range doesn't intersect with this node's boundary, return
if (!this.intersects(range)) {
return found;
}
// Check the curves in this node
for (let i = 0; i < this.curves.length; i++) {
if (this.bboxIntersect(this.curves[i].bbox(), range)) {
found.push(this.curveIndexes[i]); // Return the curve index instead of the curve
}
}
// If the node is subdivided, check the child quadrants
if (this.divided) {
this.nw.query(range, found);
this.ne.query(range, found);
this.sw.query(range, found);
this.se.query(range, found);
}
return found;
}
// Helper method to check if two bounding boxes intersect
bboxIntersect(bbox1, bbox2) {
return !(bbox1.x.max < bbox2.x.min || bbox1.x.min > bbox2.x.max ||
bbox1.y.max < bbox2.y.min || bbox1.y.min > bbox2.y.max);
}
clear() {
this.curveIndexes = [];
this.curves = [];
this.divided = false;
this.nw = null; // Northwest quadrant
this.ne = null; // Northeast quadrant
this.sw = null; // Southwest quadrant
this.se = null; // Southeast quadrant
}
draw(ctx) {
// Debug visualization
ctx.save()
ctx.strokeStyle = "red"
ctx.lineWidth = 1
ctx.beginPath()
ctx.rect(
this.boundary.x.min,
this.boundary.y.min,
this.boundary.x.max-this.boundary.x.min,
this.boundary.y.max-this.boundary.y.min
)
ctx.stroke()
if (this.divided) {
this.nw.draw(ctx)
this.ne.draw(ctx)
this.sw.draw(ctx)
this.se.draw(ctx)
}
ctx.restore()
}
}
export { Quadtree };

123
src/simplify.js Normal file
View File

@ -0,0 +1,123 @@
/*
(c) 2017, Vladimir Agafonkin
Simplify.js, a high-performance JS polyline simplification library
mourner.github.io/simplify-js
*/
(function () { 'use strict';
// to suit your point format, run search/replace for '.x' and '.y';
// for 3D version, see 3d branch (configurability would draw significant performance overhead)
// square distance between 2 points
function getSqDist(p1, p2) {
var dx = p1.x - p2.x,
dy = p1.y - p2.y;
return dx * dx + dy * dy;
}
// square distance from a point to a segment
function getSqSegDist(p, p1, p2) {
var x = p1.x,
y = p1.y,
dx = p2.x - x,
dy = p2.y - y;
if (dx !== 0 || dy !== 0) {
var t = ((p.x - x) * dx + (p.y - y) * dy) / (dx * dx + dy * dy);
if (t > 1) {
x = p2.x;
y = p2.y;
} else if (t > 0) {
x += dx * t;
y += dy * t;
}
}
dx = p.x - x;
dy = p.y - y;
return dx * dx + dy * dy;
}
// rest of the code doesn't care about point format
// basic distance-based simplification
function simplifyRadialDist(points, sqTolerance) {
var prevPoint = points[0],
newPoints = [prevPoint],
point;
for (var i = 1, len = points.length; i < len; i++) {
point = points[i];
if (getSqDist(point, prevPoint) > sqTolerance) {
newPoints.push(point);
prevPoint = point;
}
}
if (prevPoint !== point) newPoints.push(point);
return newPoints;
}
function simplifyDPStep(points, first, last, sqTolerance, simplified) {
var maxSqDist = sqTolerance,
index;
for (var i = first + 1; i < last; i++) {
var sqDist = getSqSegDist(points[i], points[first], points[last]);
if (sqDist > maxSqDist) {
index = i;
maxSqDist = sqDist;
}
}
if (maxSqDist > sqTolerance) {
if (index - first > 1) simplifyDPStep(points, first, index, sqTolerance, simplified);
simplified.push(points[index]);
if (last - index > 1) simplifyDPStep(points, index, last, sqTolerance, simplified);
}
}
// simplification using Ramer-Douglas-Peucker algorithm
function simplifyDouglasPeucker(points, sqTolerance) {
var last = points.length - 1;
var simplified = [points[0]];
simplifyDPStep(points, 0, last, sqTolerance, simplified);
simplified.push(points[last]);
return simplified;
}
// both algorithms combined for awesome performance
function simplify(points, tolerance, highestQuality) {
if (points.length <= 2) return points;
var sqTolerance = tolerance !== undefined ? tolerance * tolerance : 1;
points = highestQuality ? points : simplifyRadialDist(points, sqTolerance);
points = simplifyDouglasPeucker(points, sqTolerance);
return points;
}
// export as AMD module / Node module / browser or worker variable
if (typeof define === 'function' && define.amd) define(function() { return simplify; });
else if (typeof module !== 'undefined') {
module.exports = simplify;
module.exports.default = simplify;
} else if (typeof self !== 'undefined') self.simplify = simplify;
else window.simplify = simplify;
})();

415
src/styles.css Normal file
View File

@ -0,0 +1,415 @@
body {
width: 100%;
height: 100%;
overflow: hidden;
}
.logo.vanilla:hover {
filter: drop-shadow(0 0 2em #ffe21c);
}
:root {
--lineheight: 24px;
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: var(--lineheight);
font-weight: 400;
color: #0f0f0f;
background-color: #f6f6f6;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
height: 100%;
}
.container {
margin: 0;
padding-top: 10vh;
display: flex;
flex-direction: column;
justify-content: center;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: 0.75s;
}
.logo.tauri:hover {
filter: drop-shadow(0 0 2em #24c8db);
}
.row {
display: flex;
justify-content: center;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
h1 {
text-align: center;
}
input,
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
color: #0f0f0f;
background-color: #ffffff;
transition: border-color 0.25s;
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2);
}
div {
/* this should be on everything by default, really */
box-sizing: border-box;
}
button {
cursor: pointer;
}
button:hover {
border-color: #396cd8;
}
button:active {
border-color: #396cd8;
background-color: #e8e8e8;
}
input,
button {
outline: none;
}
#greet-input {
margin-right: 5px;
}
@media (prefers-color-scheme: dark) {
:root {
color: #f6f6f6;
background-color: #2f2f2f;
}
a:hover {
color: #24c8db;
}
input,
button {
color: #ffffff;
background-color: #0f0f0f98;
}
button:active {
background-color: #0f0f0f69;
}
}
.header {
height: 60px;
min-width: 100%;
background-color: #3f3f3f;
text-align: left;
z-index: 1;
}
.icon {
width: var(--lineheight);
height: var(--lineheight);
}
.panecontainer {
width: 100%;
height: 100%;
}
.horizontal-grid, .vertical-grid {
display: flex;
gap: 5px;
background-color: #0f0f0f;
width: 100%;
height: 100%;
contain: strict;
}
.horizontal-grid {
flex-direction: row;
}
.vertical-grid {
flex-direction: column;
}
/* I don't fully understand this selector but it works for now */
.horizontal-grid:hover:not(:has(*:hover)) {
background: #666;
cursor: ew-resize;
}
.vertical-grid:hover:not(:has(*:hover)) {
background: #666;
cursor: ns-resize
}
.scroll {
overflow: scroll;
width: 100%;
height: 100%;
background-color: #555;
}
.stage {
width: 1500px;
height: 1000px;
overflow: scroll;
}
.toolbar {
display: flex;
flex-direction: row;
gap: 10px;
padding: 5px;
flex-wrap: wrap;
align-content: flex-start;
justify-content: space-around;
}
.toolbtn {
width: calc( 3 * var(--lineheight) );
height: calc( 3 * var(--lineheight) );
background-color: #2f2f2f;
}
.horiz_break {
width: 100%;
height: 5px;
background-color: #2f2f2f;
}
.clr-field {
width: 100%;
}
.clr-field button {
width: 50% !important;
/* height: 100% !important; */
/* margin: 100px; */
border-radius: 5px;
}
.clr-field input {
width: 50%;
}
.infopanel {
width: 100%;
height: 100%;
background-color: #3f3f3f;
display: flex;
box-sizing: border-box;
gap: calc( var(--lineheight) / 2 );
padding: calc( var(--lineheight) / 2 );
flex-direction: column;
flex-wrap: wrap;
align-content: flex-start;
}
.infopanel-field {
width: 300px;
height: var(--lineheight);
display: flex;
flex-direction: row;
}
.infopanel-label {
flex: 1 1 50%;
}
.infopanel-input {
flex: 1 1 50%;
width: 50%;
}
.layers-container {
overflow-y: scroll;
}
.layers {
background-color: #222222;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
min-height: 100%;
}
.frames-container {
background-color: #222222;
display: flex;
flex-direction: column;
flex-wrap: nowrap;
/* overflow-x: scroll; */
/* overflow-y:inherit; */
min-height: 100%;
}
.layer-header {
width: 100%;
height: calc( 2 * var(--lineheight));
background-color: #3f3f3f;
border-top: 1px solid #4f4f4f;
border-bottom: 1px solid #222222;
flex-shrink: 0;
}
.layer-track {
min-width: 100%;
height: calc( 2 * var(--lineheight));
background: repeating-linear-gradient(to right, transparent, transparent 24px, #3f3f3f 24px, #3f3f3f 25px),
repeating-linear-gradient(to right, #222222, #222222 100px, #151515 100px, #151515 125px);
display: flex;
flex-direction: row;
border-top: 1px solid #222222;
border-bottom: 1px solid #3f3f3f;
flex-shrink: 0;
}
.frame {
width: 25px;
height: 100%;
background-color: #4f4f4f;
flex-grow: 0;
flex-shrink: 0;
border-right: 1px solid #3f3f3f;
border-left: 1px solid #555555;
}
.frame:hover {
background-color: #555555;
}
.frame.active {
background-color: #666666;
}
.frame.keyframe {
position: relative;
}
.frame.keyframe::before {
content: ''; /* Creates a pseudo-element */
position: absolute;
bottom: 0; /* Position the circle at the bottom of the div */
left: 50%; /* Center the circle horizontally */
transform: translateX(-50%); /* Adjust for perfect centering */
width: 50%; /* Set the width of the circle to half of the div's width */
height: 0; /* Initially set to 0 */
padding-bottom: 50%; /* Set padding-bottom to 50% of the div's width to create a circle */
border-radius: 50%; /* Make the shape a circle */
background-color: #222; /* Set the color of the circle (black in this case) */
margin-bottom: 5px;
}
.frame.motion {
background-color: #7a00b3;
border: none;
}
.frame.motion:hover, .frame.motion.active {
background-color: #530379;
border: none;
}
/* :nth-child(1 of .frame.motion) {
background-color: blue;
}
:nth-last-child(1 of .frame.motion) {
background-color: red;
} */
.frame-highlight {
background-color: red;
width: 25px;
height: calc( 2 * var(--lineheight) - 2px);
position: relative;
}
.hidden {
display: none;
}
#overlay {
display: none; /* Hidden by default */
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 999; /* Under the dialog */
}
/* Scoped styles for the dialog */
#newFileDialog {
display: none; /* Hidden by default */
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: #444;
border: 1px solid #333;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
padding: 20px;
width: 300px;
z-index: 1000; /* Make sure it's in front of other elements */
}
#newFileDialog .dialog-label {
display: block;
margin: 10px 0 5px;
}
#newFileDialog .dialog-input {
width: 100%;
padding: 8px;
margin: 5px 0;
border: 1px solid #333;
}
#newFileDialog .dialog-button {
width: 100%;
padding: 10px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
}
#newFileDialog .dialog-button:hover {
background-color: #0056b3;
}
#popupMenu {
background-color: #222;
box-shadow: 0 4px 8px rgba(0,0,0,0.5);
padding: 20px;
border-radius: 5px;
position: absolute;
}
#popupMenu ul {
padding: 0px;
margin: 0px;
}
#popupMenu li {
color: #ccc;
list-style-type: none;
display: flex;
align-items: center; /* Vertically center the image and text */
padding: 5px 0; /* Add padding for better spacing */
}
#popupMenu li:hover {
background-color: #444;
cursor:pointer;
}
#popupMenu li:not(:last-child) {
border-bottom: 1px solid #444; /* Horizontal line for all li elements except the last */
}
#popupMenu li img {
margin-right: 10px; /* Space between the icon and text */
width: 20px; /* Adjust the width of the icon */
height: 20px; /* Adjust the height of the icon */
}

94
src/utils.js Normal file
View File

@ -0,0 +1,94 @@
function titleCase(str) {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
}
function getMousePositionFraction(event, element) {
const rect = element.getBoundingClientRect(); // Get the element's position and size
if (element.classList.contains('horizontal-grid')) {
// If the element has the "horizontal-grid" class, calculate the horizontal position (X)
const xPos = event.clientX - rect.left; // Mouse X position relative to the element
const fraction = xPos / rect.width; // Fraction of the width
return Math.min(Math.max(fraction, 0), 1); // Ensure the fraction is between 0 and 1
} else if (element.classList.contains('vertical-grid')) {
// If the element has the "vertical-grid" class, calculate the vertical position (Y)
const yPos = event.clientY - rect.top; // Mouse Y position relative to the element
const fraction = yPos / rect.height; // Fraction of the height
return Math.min(Math.max(fraction, 0), 1); // Ensure the fraction is between 0 and 1
}
return 0; // If neither class is present, return 0 (or handle as needed)
}
function getKeyframesSurrounding(frames, index) {
let lastKeyframeBefore = undefined;
let firstKeyframeAfter = undefined;
// Find the last keyframe before the given index
for (let i = index - 1; i >= 0; i--) {
if (frames[i].frameType === "keyframe") {
lastKeyframeBefore = i;
break;
}
}
// Find the first keyframe after the given index
for (let i = index + 1; i < frames.length; i++) {
if (frames[i].frameType === "keyframe") {
firstKeyframeAfter = i;
break;
}
}
return { lastKeyframeBefore, firstKeyframeAfter };
}
function invertPixels(ctx, width, height) {
// Create an off-screen canvas for the pattern
const patternCanvas = document.createElement('canvas');
const patternContext = patternCanvas.getContext('2d');
// Define the size of the repeating pattern (2x2 pixels)
const patternSize = 2;
patternCanvas.width = patternSize;
patternCanvas.height = patternSize;
// Create the alternating pattern (regular and inverted pixels)
function createInvertedPattern() {
const patternData = patternContext.createImageData(patternSize, patternSize);
const data = patternData.data;
// Fill the pattern with alternating colors (inverted every other pixel)
for (let i = 0; i < patternSize; i++) {
for (let j = 0; j < patternSize; j++) {
const index = (i * patternSize + j) * 4;
// Determine if we should invert the color
if ((i + j) % 2 === 0) {
data[index] = 255; // Red
data[index + 1] = 0; // Green
data[index + 2] = 0; // Blue
data[index + 3] = 255; // Alpha
} else {
data[index] = 0; // Red (inverted)
data[index + 1] = 255; // Green (inverted)
data[index + 2] = 255; // Blue (inverted)
data[index + 3] = 255; // Alpha
}
}
}
// Set the pattern on the off-screen canvas
patternContext.putImageData(patternData, 0, 0);
return patternCanvas;
}
// Create the pattern using the function
const pattern = ctx.createPattern(createInvertedPattern(), 'repeat');
// Draw a rectangle with the pattern
ctx.globalCompositeOperation = "difference"
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, width, height);
ctx.globalCompositeOperation = "source-over"
}
export { titleCase, getMousePositionFraction, getKeyframesSurrounding, invertPixels };

74
src/vector.js Normal file
View File

@ -0,0 +1,74 @@
class Vector {
constructor(x, y, z) {
if (arguments.length === 1) {
z = x.z;
y = x.y;
x = x.x;
}
this.x = x;
this.y = y;
if (z !== undefined) {
this.z = z;
}
}
dist(other, y, z = 0) {
if (y !== undefined) other = { x: other, y, z };
let sum = 0;
sum += (this.x - other.x) ** 2;
sum += (this.y - other.y) ** 2;
let z1 = this.z ? this.z : 0;
let z2 = other.z ? other.z : 0;
sum += (z1 - z2) ** 2;
return sum ** 0.5;
}
normalize(f) {
let mag = this.dist(0, 0, 0);
return new Vector((f * this.x) / mag, (f * this.y) / mag, (f * this.z) / mag);
}
getAngle() {
return -Math.atan2(this.y, this.x);
}
reflect(other) {
let p = new Vector(other.x - this.x, other.y - this.y);
if (other.z !== undefined) {
p.z = other.z;
if (this.z !== undefined) {
p.z -= this.z;
}
}
return this.subtract(p);
}
add(other) {
let p = new Vector(this.x + other.x, this.y + other.y);
if (this.z !== undefined) {
p.z = this.z;
if (other.z !== undefined) {
p.z += other.z;
}
}
return p;
}
subtract(other) {
let p = new Vector(this.x - other.x, this.y - other.y);
if (this.z !== undefined) {
p.z = this.z;
if (other.z !== undefined) {
p.z -= other.z;
}
}
return p;
}
scale(f = 1) {
if (f === 0) {
return new Vector(0, 0, this.z === undefined ? undefined : 0);
}
let p = new Vector(this.x * f, this.y * f);
if (this.z !== undefined) {
p.z = this.z * f;
}
return p;
}
}
export { Vector };