diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..a05a706 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[build] +rustdocflags = ["--document-private-items"] diff --git a/.gitignore b/.gitignore index 48c3ca4..6d5b113 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,16 @@ -/dist/ -/target/ -/Cargo.lock +# Trunk dist directory. +/frontend/dist/ +# The CSS file is the output of Sass compilation (/frontend/style/main.scss). +/frontend/main.css +# Temporary folder you can use for IDE artifacts. +# Notably, this is used in the VSCode settings override +# for rust-analyzer.check.extraArgs (--target-dir tmp/rust-analyzer). +/tmp/ +# All Cargo output directories. +# Generally there should be only one, at the same level as this file. +target/ +debug/ +# These are backup files generated by rustfmt +**/*.rs.bk +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/.taurignore b/.taurignore index a0c14e7..3824f57 100644 --- a/.taurignore +++ b/.taurignore @@ -1,7 +1,12 @@ -## ignore these files/folders when watching for backend rebuild: -**/chipbox/src/** -**/chipbox/ui/** -**/chipbox/scss-input/** -**/chipbox/scss-output/** -rustfmt.toml -Trunk.toml +# List of items to ignore for backend rebuilds. +# See https://github.com/tauri-apps/tauri/issues/4617 + +# All frontend files. +/frontend/ +# Temporary folder you can use for IDE artifacts. +# Notably, this is used in the VSCode settings override +# for rust-analyzer.check.extraArgs (--target-dir tmp/rust-analyzer-check). +/tmp/ +# All Cargo output directories. +# Generally there should be only one, at the same level as this file. +target/ diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 3e58558..b3c9d51 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,9 +1,5 @@ { "recommendations": [ - "tauri-apps.tauri-vscode", - "rust-lang.rust-analyzer", - "sibiraj-s.vscode-scss-formatter", - "mrmlnc.vscode-scss", - "adrianwilczynski.format-selection-as-html" + "rust-lang.rust-analyzer" ] } diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 34c686e..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,201 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "Debug frontend", - "type": "msedge", - "request": "launch", - "port": 9229, - "runtimeExecutable": "${workspaceFolder}/target/debug/chipbox.exe", - "useWebView": true, - "url": "http://localhost:1420", - "webRoot": "${workspaceFolder}", - "preLaunchTask": "tauri-build-debug" - }, - { - "type": "lldb", - "request": "attach", - "name": "Attach to backend", - "program": "${workspaceFolder}/target~/debug/chipbox.exe" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'chipbox-backend'", - "cargo": { - "args": [ - "test", - "--no-run", - "--bin=chipbox-backend", - "--package=chipbox-backend" - ], - "filter": { - "name": "chipbox-backend", - "kind": "bin" - } - }, - "args": [], - "presentation": { - "group": "unit" - }, - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'chipbox-backend-lib'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=chipbox-backend-lib" - ], - "filter": { - "name": "chipbox-backend-lib", - "kind": "lib" - } - }, - "args": [], - "presentation": { - "group": "unit" - }, - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'chipbox-common'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=chipbox-common" - ], - "filter": { - "name": "chipbox-common", - "kind": "lib" - } - }, - "args": [], - "presentation": { - "group": "unit" - }, - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'chipbox-glue'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=chipbox-glue" - ], - "filter": { - "name": "chipbox-glue", - "kind": "lib" - } - }, - "args": [], - "presentation": { - "group": "unit" - }, - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'chipbox-ui-app'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=chipbox-ui-app" - ], - "filter": { - "name": "chipbox-ui-app", - "kind": "lib" - } - }, - "args": [], - "presentation": { - "group": "unit" - }, - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'chipbox-ui-panel'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=chipbox-ui-panel" - ], - "filter": { - "name": "chipbox-ui-panel", - "kind": "lib" - } - }, - "args": [], - "presentation": { - "group": "unit" - }, - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in library 'chipbox-ui-spinner'", - "cargo": { - "args": [ - "test", - "--no-run", - "--lib", - "--package=chipbox-ui-spinner" - ], - "filter": { - "name": "chipbox-ui-spinner", - "kind": "lib" - } - }, - "args": [], - "presentation": { - "group": "unit" - }, - "cwd": "${workspaceFolder}" - }, - { - "type": "lldb", - "request": "launch", - "name": "Debug unit tests in executable 'chipbox'", - "cargo": { - "args": [ - "test", - "--no-run", - "--bin=chipbox", - "--package=chipbox" - ], - "filter": { - "name": "chipbox", - "kind": "bin" - } - }, - "args": [], - "presentation": { - "group": "unit" - }, - "cwd": "${workspaceFolder}" - } - ], -} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..544b0ff --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,212 @@ +{ + "rust-analyzer.procMacro.ignored": { + "leptos_macro": [ + "server" + ], + "tauri_macros": [ + "generate_context" + ] + }, + "rust-analyzer.rustfmt.overrideCommand": [ + "leptosfmt", + "--stdin", + "--rustfmt" + ], + "postcssSorting.config": { + "order": [ + "custom-properties", + "dollar-variables", + "declarations", + "at-rules", + "rules" + ], + "properties-order": [ + "box-sizing", + "display", + "position", + "top", + "right", + "bottom", + "left", + "float", + "clear", + "align-content", + "align-items", + "align-self", + "flex", + "flex-basis", + "flex-direction", + "flex-flow", + "flex-grow", + "flex-shrink", + "flex-wrap", + "justify-content", + "grid", + "grid-area", + "grid-template", + "grid-template-areas", + "grid-template-rows", + "grid-template-columns", + "grid-column", + "grid-column-start", + "grid-column-end", + "grid-row", + "grid-row-start", + "grid-row-end", + "grid-auto-rows", + "grid-auto-columns", + "grid-auto-flow", + "grid-gap", + "grid-row-gap", + "grid-column-gap", + "order", + "columns", + "column-gap", + "column-fill", + "column-rule", + "column-rule-width", + "column-rule-style", + "column-rule-color", + "column-span", + "column-count", + "column-width", + "backface-visibility", + "perspective", + "perspective-origin", + "transform", + "transform-origin", + "transform-style", + "transition", + "transition-delay", + "transition-duration", + "transition-property", + "transition-timing-function", + "visibility", + "opacity", + "z-index", + "margin", + "margin-top", + "margin-right", + "margin-bottom", + "margin-left", + "outline", + "outline-offset", + "outline-width", + "outline-style", + "outline-color", + "border", + "border-top", + "border-right", + "border-bottom", + "border-left", + "border-width", + "border-top-width", + "border-right-width", + "border-bottom-width", + "border-left-width", + "border-style", + "border-top-style", + "border-right-style", + "border-bottom-style", + "border-left-style", + "border-radius", + "border-top-left-radius", + "border-top-right-radius", + "border-bottom-left-radius", + "border-bottom-right-radius", + "border-color", + "border-top-color", + "border-right-color", + "border-bottom-color", + "border-left-color", + "border-image", + "border-image-source", + "border-image-width", + "border-image-outset", + "border-image-repeat", + "border-image-slice", + "box-shadow", + "background", + "background-attachment", + "background-clip", + "background-color", + "background-image", + "background-repeat", + "background-position", + "background-size", + "cursor", + "padding", + "padding-top", + "padding-right", + "padding-bottom", + "padding-left", + "width", + "min-width", + "max-width", + "height", + "min-height", + "max-height", + "overflow", + "overflow-x", + "overflow-y", + "resize", + "list-style", + "list-style-type", + "list-style-position", + "list-style-image", + "caption-side", + "table-layout", + "border-collapse", + "border-spacing", + "empty-cells", + "animation", + "animation-name", + "animation-duration", + "animation-timing-function", + "animation-delay", + "animation-iteration-count", + "animation-direction", + "animation-fill-mode", + "animation-play-state", + "vertical-align", + "direction", + "tab-size", + "text-align", + "text-align-last", + "text-justify", + "text-indent", + "text-transform", + "text-decoration", + "text-decoration-color", + "text-decoration-line", + "text-decoration-style", + "text-rendering", + "text-shadow", + "text-overflow", + "line-height", + "word-spacing", + "letter-spacing", + "white-space", + "word-break", + "word-wrap", + "color", + "font", + "font-family", + "font-size", + "font-size-adjust", + "font-stretch", + "font-weight", + "font-smoothing", + "osx-font-smoothing", + "font-variant", + "font-style", + "content", + "quotes", + "counter-reset", + "counter-increment", + "page-break-before", + "page-break-after", + "page-break-inside" + ] + }, +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 9de635e..c006340 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -1,77 +1,17 @@ { - // See https://go.microsoft.com/fwlink/?LinkId=733558 - // for the documentation about the tasks.json format "version": "2.0.0", "tasks": [ { - "label": "tauri-build-debug", + "label": "sass-watch", "type": "shell", - "command": "cargo tauri build --debug", - "problemMatcher": [], - "group": { - "kind": "build", - "isDefault": true - }, - "presentation": { - "echo": true, - "reveal": "always", - "focus": false, - "panel": "shared", - "showReuseMessage": false, - "clear": true - }, - "runOptions": { - "instanceLimit": 1, - "reevaluateOnRerun": true - } + "command": "sass --watch frontend/style/stylesheets:frontend/style/ --no-source-map", + "problemMatcher": [] }, { - "label": "tauri-dev", + "label": "sass", "type": "shell", - "command": "cargo tauri dev", - "problemMatcher": [], - "group": { - "kind": "build", - "isDefault": true - }, - "presentation": { - "echo": true, - "reveal": "silent", - "focus": false, - "panel": "shared", - "showReuseMessage": true, - "clear": true - }, - "runOptions": { - "runOn": "folderOpen", - "instanceLimit": 1, - "reevaluateOnRerun": true - } - }, - { - "label": "scss-watch", - "type": "shell", - "command": "sass --watch scss-input/:scss-output/ --no-source-map", - "problemMatcher": [ - "$node-sass" - ], - "group": { - "kind": "build", - "isDefault": true - }, - "presentation": { - "echo": true, - "reveal": "silent", - "focus": false, - "panel": "shared", - "showReuseMessage": true, - "clear": true - }, - "runOptions": { - "runOn": "folderOpen", - "instanceLimit": 1, - "reevaluateOnRerun": true - } + "command": "sass frontend/style/stylesheets:frontend/style/ --no-source-map", + "problemMatcher": [] } ] } diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index b62854d..5df43a3 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -60,7 +60,7 @@ representative at an online or offline event. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at -contact.chipbox@gmail.com. +. All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the @@ -116,13 +116,13 @@ the community. This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at -https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. +. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). -[homepage]: https://www.contributor-covenant.org +[homepage]: For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. +. Translations are available at +. diff --git a/Cargo.toml b/Cargo.toml index 156897d..be932fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,131 +1,69 @@ -## ------------------------------------------------ -## workspace config [workspace] -members = [ - "backend", - "glue", - "common", - ## ui members - "ui/app", - "ui/panel", - "ui/spinner", -] +resolver = "2" +members = ["frontend", "backend", "glue", "common"] +package.edition = "2021" -[workspace.dependencies] -## project deps -chipbox-backend = { path = "backend" } -chipbox-backend-lib = { path = "backend/lib" } -chipbox-glue = { path = "glue" } -chipbox-common = { path = "common" } -# ui deps -chipbox-ui-app = { path = "ui/app" } -chipbox-ui-panel = { path = "ui/panel" } -chipbox-ui-spinner = { path = "ui/spinner" } -## utility -const_format = { version = "0.2", features = ["fmt"] } -home = { version = "0.5" } -gen_value = "0.7" -once_cell = "1.19" -chrono = { version = "0.4", features = ["serde"] } -rb = { version = "0.4" } -derive_more = { version = "0.99", features = ["nightly"] } -cowstr = { version = "1.3", features = ["serde"] } -## tracing -tracing = "0.1" -tracing-subscriber = { version = "0.3" } -tracing-web = "0.1" -tracing-appender = "0.2" -## error handling -color-eyre = "0.6" -## concurrency -futures = { version = "0.3" } -tokio = { version = "1.37", features = [ - "macros", - "fs", - "tracing", - "time", - "sync", -] } -## serde -serde-wasm-bindgen = { version = "0.6" } -serde = { version = "1.0", features = ["derive", "rc"] } -serde_json = "1.0" -## tauri -tauri = { version = "1.6", features = ["shell-open"] } -tauri-build = "1.5" # build script -tauri-sys = { git = "https://github.com/JonasKruckenberg/tauri-sys", features = [ - "event", -] } -tauri-plugin-window-state = "0.1" # save window state on app exit -wasm-bindgen-futures = { version = "0.4" } -## wasm frontend -yew = { version = "0.21", features = ["csr"] } -wasm-bindgen = { version = "0.2", features = ["serde-serialize"] } -wasm-timer = { version = "0.2" } -js-sys = { version = "0.3" } -web-sys = { version = "0.3", features = [ - "Element", - "DomTokenList", - "ResizeObserver", - "CustomEvent", -] } -gloo = { version = "0.11", features = ["futures"] } -## css -cssparser = { git = "https://github.com/servo/rust-cssparser/", features = [ - "serde", -] } -cssparser-color = { git = "https://github.com/servo/rust-cssparser/", features = [ - "serde", -] } -## audio -cpal = { git = "https://github.com/RustAudio/cpal", features = [ - "asio", - "jack", -] } - -## ------------------------------------------------ -## package config -[package] -name = "chipbox" -version = "0.0.0" -authors = ["chipnertkj "] -exclude = ["target/", ".github/"] -readme = "README.md" -edition = "2021" -license = "MPL-2.0" -repository = "https://github.com/chipnertkj/chipbox" -categories = ["multimedia::audio"] -keywords = ["daw", "music", "vst", "clap", "audio"] -description = "Open-source DAW with a node graph system." - -[badges] -maintenance = { status = "actively-developed" } - -# Low optimization. [profile.dev] -lto = false opt-level = 1 -incremental = true -debug = "full" +incremental = true # incremental causes so many issues with tauri... -## Keep debug info. [profile.release] -lto = "fat" opt-level = 3 incremental = false debug = "full" -[dependencies] -## project deps -chipbox-common = { workspace = true } -chipbox-glue = { workspace = true, features = ["frontend"] } -## ui deps -chipbox-ui-app = { workspace = true } -## tracing -tracing = { workspace = true } -tracing-subscriber = { workspace = true } -tracing-web = { workspace = true } -## wasm frontend -yew = { workspace = true } -## utility -const_format = { workspace = true } +[workspace.dependencies] +## workspace deps +chipbox-common = { path = "common" } +chipbox-glue = { path = "glue" } + +## common deps +serde = { version = "1.0.215", features = ["derive", "unstable"] } +tracing = { version = "0.1.40" } +thiserror = { version = "2.0.3" } +derive_more = { version = "1.0.0", features = [ + "from", + "as_ref", + "display", + "not", + "add", +] } +chrono = { version = "0.4.38", features = ["serde"] } +serde_json = { version = "1.0.133", features = ["preserve_order"] } +delegate = { version = "0.13.1" } +eyre = { version = "0.6.12" } +csscolorparser = { version = "0.7.0", features = ["serde", "lab"] } +itertools = { version = "0.13.0" } +slotmap = { version = "1.0.7", features = ["serde"] } +url = { version = "2.5.4", features = ["serde"] } +email_address = { version = "0.2.9", features = ["serde"] } +parking_lot = { version = "0.12.3" } + +## backend deps +color-eyre = { version = "0.6.3" } +tauri = { version = "2.1.1", features = [] } +tauri-build = { version = "2.0.3", features = [] } +tauri-plugin-shell = "2.0.2" +tokio = { version = "1.41.1", features = [ + "rt-multi-thread", + "macros", + "fs", + "io-util", +] } +tracing-subscriber = { version = "0.3.18", features = ["env-filter"] } +tracing-appender = "0.2.3" +home = { version = "0.5.9" } +fs4 = { version = "0.11.1", features = ["tokio"] } + +## frontend deps +leptos = { version = "0.7.0-rc2", features = ["csr", "nightly"] } +leptos-use = { version = "0.14.0-rc3", features = [ + "use_window", + "use_event_listener", +] } +wasm-bindgen = "=0.2.95" +wasm-bindgen-futures = "=0.4.45" +serde-wasm-bindgen = "0.6.5" +console_error_panic_hook = "0.1.7" +web-time = "1.1.0" +tracing-web = "0.1.3" diff --git a/Trunk.toml b/Trunk.toml index 8c38b7d..cfa283d 100644 --- a/Trunk.toml +++ b/Trunk.toml @@ -1,21 +1,14 @@ [build] -target = "./index.html" +target = "./frontend/index.html" [watch] -## Ignore these files/folders when watching for frontend rebuild. -ignore = [ - "backend/", - ".github/", - "scss-input/", - ".gitignore", - "LICENSE.txt", - "README.md", - "rustfmt.toml", - ".taurignore", - "Trunk.toml", -] +ignore = [] [serve] -address = "127.0.0.1" +addresses = ["127.0.0.1"] port = 1420 open = false +ws_protocol = "ws" + +[tools] +wasm_bindgen = "0.2.95" diff --git a/attribution/sass-boilerplate/LICENSE b/attribution/sass-boilerplate/LICENSE new file mode 100644 index 0000000..9e9795f --- /dev/null +++ b/attribution/sass-boilerplate/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Kitty Giraudel + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..b21bd68 --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ + +# Generated by Tauri +# will have schema files for capabilities auto-completion +/gen/schemas diff --git a/backend/Cargo.lock b/backend/Cargo.lock new file mode 100644 index 0000000..9490eb7 --- /dev/null +++ b/backend/Cargo.lock @@ -0,0 +1,4469 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "atk" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4af014b17dd80e8af9fa689b2d4a211ddba6eb583c1622f35d0cb543f6b17e4" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "251e0b7d90e33e0ba930891a505a9a35ece37b2dd37a14f3ffc306c13b980009" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide 0.7.4", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + +[[package]] +name = "brotli" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.6.0", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cargo_toml" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a969e13a7589e9e3e4207e153bae624ade2b5622fb4684a4923b23ec3d57719" +dependencies = [ + "serde", + "toml 0.8.2", +] + +[[package]] +name = "cc" +version = "1.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +dependencies = [ + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chipbox" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-shell", +] + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.52.6", +] + +[[package]] +name = "cocoa" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" +dependencies = [ + "bitflags 2.6.0", + "block", + "cocoa-foundation", + "core-foundation 0.10.0", + "core-graphics 0.24.0", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d" +dependencies = [ + "bitflags 2.6.0", + "block", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", + "libc", + "objc", +] + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa 0.4.8", + "matches", + "phf 0.8.0", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.77", +] + +[[package]] +name = "ctor" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +dependencies = [ + "quote", + "syn 2.0.77", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.77", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.77", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlopen2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "dpi" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "embed-resource" +version = "2.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4edcacde9351c33139a41e3c97eb2334351a81a2791bebb0b243df837128f602" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 0.8.2", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "erased-serde" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" +dependencies = [ + "serde", + "typeid", +] + +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "flate2" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.0", +] + +[[package]] +name = "fluent-uri" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5ba081bdef3b75ebcdbfc953699ed2d7417d6bd853347a42a37d76406a33646" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31ff856cb3386dae1703a920f803abafcc580e9b5f711ca62ed1620c25b51ff2" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90fbf5c033c65d93792192a49a8efb5bb1e640c419682a58bb96f5ae77f3d4a" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2ea8a4909d530f79921290389cbd7c34cb9d623bfe970eaae65ca5f9cd9cce" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee8f00f4ee46cad2939b8990f5c70c94ff882c3028f3cc5abf950fa4ab53043" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generator" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "windows 0.48.0", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.6.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93c4f5e0e20b60e10631a5f06da7fe3dda744b05ad0ea71fee2f47adf865890c" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771437bf1de2c1c0b496c11505bdf748e26066bbe942dfc8f614c9460f6d7722" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6063efb63db582968fb7df72e1ae68aa6360dcfb0a75143f34fc7d616bad75e" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "html5ever" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.11", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "itoa 1.0.11", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core 0.52.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", + "serde", +] + +[[package]] +name = "infer" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb33622da908807a06f9513c19b3c1ad50fab3e4137d82a78107d502075aa199" +dependencies = [ + "cfb", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b1fb8864823fad91877e6caea0baca82e49e8db50f8e5c9f9a453e27d3330fc" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "jsonptr" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c6e529149475ca0b2820835d3dce8fcc41c6b943ca608d32f35b449255e4627" +dependencies = [ + "fluent-uri", + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.6.0", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kuchikiki" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 1.9.3", + "matches", + "selectors", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markup5ever" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +dependencies = [ + "log", + "phf 0.10.1", + "phf_codegen 0.10.0", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "muda" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba8ac4080fb1e097c2c22acae467e46e4da72d941f02e82b67a87a2a89fa38b1" +dependencies = [ + "cocoa", + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc", + "once_cell", + "png", + "serde", + "thiserror", + "windows-sys 0.59.0", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.6.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate 2.0.2", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.6.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.6.0", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "open" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "os_pipe" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared 0.10.0", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros 0.11.2", + "phf_shared 0.11.2", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared 0.11.2", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator 0.11.2", + "phf_shared 0.11.2", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "plist" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" +dependencies = [ + "base64 0.22.1", + "indexmap 2.5.0", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.7.4", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "reqwest" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.77", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "selectors" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +dependencies = [ + "bitflags 1.3.2", + "cssparser", + "derive_more", + "fxhash", + "log", + "matches", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", + "thin-slice", +] + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] + +[[package]] +name = "serde" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2676ba99bd82f75cae5cbd2c8eda6fa0b8760f18978ea840e980dd5567b5c5b6" +dependencies = [ + "erased-serde", + "serde", + "typeid", +] + +[[package]] +name = "serde_derive" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "serde_json" +version = "1.0.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +dependencies = [ + "itoa 1.0.11", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "serde_spanned" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.11", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.5.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "servo_arc" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shared_child" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09fa9338aed9a1df411814a5b2252f7cd206c55ae9bf2fa763f8de84603aa60c" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "softbuffer" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d623bff5d06f60d738990980d782c8c866997d9194cfe79ecad00aa2f76826dd" +dependencies = [ + "bytemuck", + "cfg_aliases", + "core-graphics 0.23.2", + "foreign-types", + "js-sys", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall", + "wasm-bindgen", + "web-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "state" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8" +dependencies = [ + "loom", +] + +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared 0.10.0", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a97abbc7d6cfd0720da3e06fcb1cf2ac87cbfdb5bbbce103a1279a211c4d81" +dependencies = [ + "bitflags 2.6.0", + "cocoa", + "core-foundation 0.10.0", + "core-graphics 0.24.0", + "crossbeam-channel", + "dispatch", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "instant", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc", + "once_cell", + "parking_lot", + "raw-window-handle", + "scopeguard", + "tao-macros", + "unicode-segmentation", + "url", + "windows 0.58.0", + "windows-core 0.58.0", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.0.0-rc.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8345ccc676ef16e26b61fc0f5340b4e770678b1e1f53f08c69ebdac5e56b422" +dependencies = [ + "anyhow", + "bytes", + "cocoa", + "dirs", + "dunce", + "embed_plist", + "futures-util", + "getrandom 0.2.15", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc", + "percent-encoding", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "state", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror", + "tokio", + "tray-icon", + "url", + "urlpattern", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows 0.58.0", +] + +[[package]] +name = "tauri-build" +version = "2.0.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5ad5fcfaf02cf79aa6727f6c5df38567d8dce172b00b62690c6bc46c08b7ce" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "toml 0.8.2", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.0.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809ef6316726fc72593d296cf6f4e7461326e310c313d6a6c42b6e7f1e2671cf" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.77", + "tauri-utils", + "thiserror", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.0.0-rc.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1359e8861d210d25731f8b1bfbb4d111dd06406cf73c59659366ef450364d811" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.77", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.0.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7dded420c86183f592d0fe925ef9447f41e26fa79f0bdfef8d3f17bfbcdbfb7" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars", + "serde", + "serde_json", + "tauri-utils", + "toml 0.8.2", + "walkdir", +] + +[[package]] +name = "tauri-plugin-shell" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83800ddf78b820172efb5ed7310344e8e4f97fd30cd8237a3f20c12a79eb136" +dependencies = [ + "encoding_rs", + "log", + "open", + "os_pipe", + "regex", + "schemars", + "serde", + "serde_json", + "shared_child", + "tauri", + "tauri-plugin", + "thiserror", + "tokio", +] + +[[package]] +name = "tauri-runtime" +version = "2.0.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c72b844f387bfc3341c355f3e16b8cbf4161848fa4e348670effb222cd3ba5" +dependencies = [ + "dpi", + "gtk", + "http", + "jni", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror", + "url", + "windows 0.58.0", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.0.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73accf936a7cd01d1382de7850726fdf6c1f6ab3b01ccb7a0950cb852e332596" +dependencies = [ + "cocoa", + "gtk", + "http", + "jni", + "log", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows 0.58.0", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.0.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53d9fe87e985b273696ae22ce2b9f099a8f1b44bc8fb127467bda5fcb3e4371" +dependencies = [ + "brotli", + "cargo_metadata", + "ctor", + "dunce", + "glob", + "html5ever", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.2", + "proc-macro2", + "quote", + "regex", + "schemars", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror", + "toml 0.8.2", + "url", + "urlpattern", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb" +dependencies = [ + "embed-resource", + "toml 0.7.8", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "thin-slice" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa 1.0.11", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.19.15", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.5.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.5.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tray-icon" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "131a65b2cef2081bc14dbcd414c906edbfa3bb5323dd7e748cc298614681196b" +dependencies = [ + "core-graphics 0.24.0", + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", + "png", + "serde", + "thiserror", + "windows-sys 0.59.0", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlpattern" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9bd5ff03aea02fa45b13a7980151fe45009af1980ba69f651ec367121a31609" +dependencies = [ + "derive_more", + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.77", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "wasm-streams" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webview2-com" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61ff3d9d0ee4efcb461b14eb3acfda2702d10dc329f339303fc3e57215ae2c" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows 0.58.0", + "windows-core 0.58.0", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "webview2-com-sys" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3a3e2eeb58f82361c93f9777014668eb3d07e7d174ee4c819575a9208011886" +dependencies = [ + "thiserror", + "windows 0.58.0", + "windows-core 0.58.0", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8cdd6999298d969289d8078dae02ce798ad23452075985cccba8b6326711ecf" +dependencies = [ + "cocoa", + "objc", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-version" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998aa457c9ba8ff2fb9f13e9d2a930dabcea28f1d0ab94d687d8b3654844515" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wry" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b8049c8f239cdbfaaea4bacb9646f6b208938ceec0acd5b3e99cd05f70903f" +dependencies = [ + "base64 0.22.1", + "block", + "cocoa", + "core-graphics 0.24.0", + "crossbeam-channel", + "dpi", + "dunce", + "gdkx11", + "gtk", + "html5ever", + "http", + "javascriptcore-rs", + "jni", + "kuchikiki", + "libc", + "ndk", + "objc", + "objc_id", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows 0.58.0", + "windows-core 0.58.0", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 173c209..f9e392f 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,31 +1,30 @@ [package] -name = "chipbox-backend" -version = "0.0.0" +name = "chipbox" +version = "0.1.0" edition = "2021" -[features] -## (generated by create-tauri-app) -## this feature is used for production builds or when `devPath` points to the filesystem -custom-protocol = ["tauri/custom-protocol"] +# See https://v2.tauri.app/start/migrate/from-tauri-1/#preparing-for-mobile +# This is needed for mobile platforms compatibility. +[lib] +name = "chipbox_lib" +crate-type = ["lib", "cdylib", "staticlib"] [build-dependencies] -## (generated by create-tauri-app) -tauri-build = { workspace = true } +tauri-build.workspace = true [dependencies] -## project deps +chipbox-common = { workspace = true } chipbox-glue = { workspace = true, features = ["backend"] } -chipbox-backend-lib = { workspace = true } -## tracing -tracing = { workspace = true } -tracing-subscriber = { workspace = true } -tracing-appender = { workspace = true } -## concurrency -tokio = { workspace = true } -## tauri -tauri = { workspace = true } -tauri-plugin-window-state = { workspace = true } -## error handling -color-eyre = { workspace = true } -## utility -once_cell = { workspace = true } +tauri.workspace = true +tauri-plugin-shell.workspace = true +serde.workspace = true +serde_json.workspace = true +tracing.workspace = true +tracing-subscriber.workspace = true +tracing-appender.workspace = true +color-eyre.workspace = true +thiserror.workspace = true +home.workspace = true +tokio.workspace = true +fs4.workspace = true +delegate.workspace = true diff --git a/backend/build.rs b/backend/build.rs index d8c4eca..d860e1e 100644 --- a/backend/build.rs +++ b/backend/build.rs @@ -1,4 +1,3 @@ fn main() { - // Tauri pre-build setup. tauri_build::build() } diff --git a/backend/capabilities/default.json b/backend/capabilities/default.json new file mode 100644 index 0000000..86d2089 --- /dev/null +++ b/backend/capabilities/default.json @@ -0,0 +1,15 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Capability for the main window", + "windows": [ + "main" + ], + "permissions": [ + "core:app:default", + "core:window:allow-is-maximized", + "core:window:allow-toggle-maximize", + "core:window:allow-close", + "core:window:allow-minimize" + ] +} diff --git a/backend/lib/Cargo.toml b/backend/lib/Cargo.toml deleted file mode 100644 index b8fc022..0000000 --- a/backend/lib/Cargo.toml +++ /dev/null @@ -1,24 +0,0 @@ -[package] -name = "chipbox-backend-lib" -version = "0.0.0" -edition = "2021" - -[dev-dependencies] -tracing-subscriber = { workspace = true } - -[dependencies] -# workspace deps -chipbox-common = { workspace = true } -# deps -tracing = { workspace = true } -serde = { workspace = true } -serde_json = { workspace = true } -tokio = { workspace = true } -cpal = { workspace = true } -color-eyre = { workspace = true } -home = { workspace = true } -gen_value = { workspace = true } -once_cell = { workspace = true } -rb = { workspace = true } -tauri = { workspace = true } -cowstr.workspace = true diff --git a/backend/lib/src/app_data.rs b/backend/lib/src/app_data.rs deleted file mode 100644 index a62a049..0000000 --- a/backend/lib/src/app_data.rs +++ /dev/null @@ -1,27 +0,0 @@ -pub mod msg; - -mod app_state; -use self::app_state::AppState; - -/// All data required for managing the application. -/// -/// Includes its state and the handle to the Tauri application. -pub struct AppData { - pub state: AppState, - tauri_app: tauri::AppHandle, -} - -impl AppData { - /// Create new app data. - pub fn new(tauri_app: tauri::AppHandle) -> Self { - Self { - state: Default::default(), - tauri_app, - } - } - - /// Associated Tauri application handle. - pub fn tauri_app(&self) -> &tauri::AppHandle { - &self.tauri_app - } -} diff --git a/backend/lib/src/app_data/app_state.rs b/backend/lib/src/app_data/app_state.rs deleted file mode 100644 index d55cbfe..0000000 --- a/backend/lib/src/app_data/app_state.rs +++ /dev/null @@ -1,51 +0,0 @@ -pub use edit_state::EditState; -pub mod edit_state; - -use crate::common; -use common::app::{AwaitConfigReason, BackendAppState}; -use common::Settings; - -#[derive(Default)] -/// Backend application state. -pub enum AppState { - #[default] - /// Backend is in the process of reading user config. - ReadingSettings, - /// Settings read has been attempted, but no valid configuration was found. - AwaitConfig { reason: AwaitConfigReason }, - /// Backend is awaiting commands from the frontend. - Idle { settings: Settings }, - /// Backend is ready to edit a project. - Edit { - inner: Box, - settings: Settings, - }, -} - -/// Initialize app state from an optional configuration. -impl From> for AppState { - fn from(settings_opt: Option) -> Self { - match settings_opt { - Some(settings) => AppState::Idle { settings }, - None => AppState::AwaitConfig { - reason: AwaitConfigReason::NoConfig, - }, - } - } -} - -/// Convert app state to a minimal, serializable version. -impl From<&AppState> for BackendAppState { - fn from(app: &AppState) -> Self { - match app { - AppState::ReadingSettings => BackendAppState::ReadingSettings, - AppState::AwaitConfig { ref reason } => { - BackendAppState::AwaitConfig { - reason: reason.clone(), - } - } - AppState::Idle { .. } => BackendAppState::Idle, - AppState::Edit { .. } => BackendAppState::Editor, - } - } -} diff --git a/backend/lib/src/app_data/app_state/edit_state.rs b/backend/lib/src/app_data/app_state/edit_state.rs deleted file mode 100644 index 59dbbd9..0000000 --- a/backend/lib/src/app_data/app_state/edit_state.rs +++ /dev/null @@ -1,30 +0,0 @@ -pub mod audio_engine; - -use self::audio_engine::AudioEngine; -use crate::common; -use common::{Project, Settings}; - -pub struct EditState { - pub project: Project, - pub audio_engine: AudioEngine, -} - -impl EditState { - /// # Errors - /// Failure if encountered problems while creating the audio engine. - pub fn create_project( - settings: &Settings, - name: String, - ) -> Result { - let audio_engine = AudioEngine::from_settings(&settings.audio_engine)?; - let editor = Self { - audio_engine, - project: Project::new(name), - }; - Ok(editor) - } - - pub fn play_stream(&mut self) -> Result<(), cpal::PlayStreamError> { - self.audio_engine.play() - } -} diff --git a/backend/lib/src/app_data/app_state/edit_state/audio_engine.rs b/backend/lib/src/app_data/app_state/edit_state/audio_engine.rs deleted file mode 100644 index 1f2c1b1..0000000 --- a/backend/lib/src/app_data/app_state/edit_state/audio_engine.rs +++ /dev/null @@ -1,560 +0,0 @@ -pub use self::error::{ResetDeviceError, ResetStreamError, SettingsError}; - -use self::buffer::{Buffer, BufferConfig, Consumer, MonoFrame, StereoFrame}; -use self::host_id::HostId; -use self::stream_config::{SampleFormat, StreamConfig}; -use self::stream_handle::StreamHandle; -use chipbox_common as common; -use common::audio_engine::{SelectedDevice, Settings}; -use cpal::traits::{DeviceTrait as _, HostTrait, StreamTrait as _}; -use cpal::Sample; -use rb::RbConsumer; - -mod buffer; -mod device; -mod error; -mod host_id; -mod stream_config; - -pub mod stream_handle; - -/// Represents a configuration of the AudioEngine. -pub struct AudioEngineConfig { - pub host_id: HostId, - pub selected_device: SelectedDevice, - pub output_stream: StreamConfig, - pub output_buffer_config: BufferConfig, - pub playing: bool, -} - -pub struct AudioEngine { - host: cpal::Host, - output_device: cpal::Device, - output_stream_handle: StreamHandle, - config: AudioEngineConfig, - output_buffer: Buffer, -} - -#[derive(Debug)] -pub enum Error { - Settings(SettingsError), - HostUnavailable(cpal::HostUnavailable), -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::Settings(err) => Some(err), - Self::HostUnavailable(err) => Some(err), - } - } -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Settings(err) => err.fmt(f), - Self::HostUnavailable(err) => err.fmt(f), - } - } -} - -impl AudioEngine { - /// Creates an `AudioEngine` instance from the given audio engine `Settings`. - pub fn from_settings(settings: &Settings) -> Result { - // Read HostId and open Host. - let host_id = HostId::try_from(&settings.host) - .map_err(|err| Error::Settings(SettingsError::HostIdParse(err)))?; - let host = cpal::host_from_id(host_id.into()) - .map_err(Error::HostUnavailable)?; - - // Open output device. - let output_device = Self::output_device(&host, &settings.output_device) - .map_err(|err| { - Error::Settings(SettingsError::InvalidStreamConfig( - stream_config::Error::Device(err), - )) - })?; - - // Get output stream config. - let output_stream_config = - StreamConfig::from_settings(&settings.output_stream_config) - .map_err(|err| { - Error::Settings(SettingsError::StreamConfigParse(err)) - })?; - let supported_output_stream_config = - Self::supported_output_stream_config( - &output_device, - &output_stream_config, - ) - .map_err(|err| { - Error::Settings(SettingsError::InvalidStreamConfig(err)) - })?; - - // Calculate frame count in buffer. - let cpal::SampleRate(sample_rate) = - supported_output_stream_config.sample_rate(); - let buffer_duration = settings - .output_stream_config - .buffer_duration(); - // Convert duration to number of frames. - let buffer_length = (buffer_duration.as_secs_f64() / sample_rate as f64) - .ceil() as usize; - // Prepare buffer config. - let output_buffer_config = - match supported_output_stream_config.channels() { - MonoFrame::CHANNEL_COUNT => BufferConfig::Mono { - length: buffer_length, - }, - StereoFrame::CHANNEL_COUNT => BufferConfig::Stereo { - length: buffer_length, - }, - n => { - return Err(Error::Settings( - SettingsError::InvalidStreamConfig( - stream_config::Error::UnsupportedChannelCount(n), - ), - )) - } - }; - - // Prepare engine config. - let config = AudioEngineConfig { - host_id, - selected_device: settings.output_device.clone(), - output_buffer_config, - output_stream: output_stream_config, - playing: false, - }; - - // Construct buffer. - let mut output_buffer = - Buffer::from_config(&config.output_buffer_config); - let consumer = output_buffer.consumer(); - - // Create output stream. - let output_stream = Self::create_output_stream( - &output_device, - &supported_output_stream_config, - consumer, - ) - .map_err(|err| { - Error::Settings(SettingsError::InvalidStreamConfig(err)) - })?; - let output_stream_handle = Self::add_thread_local_stream(output_stream); - - // Construct. - tracing::info!("created audio engine from settings: `{:?}`", settings); - Ok(Self { - host, - output_device, - output_stream_handle, - output_buffer, - config, - }) - } - - /// Plays the output stream. - pub fn play(&mut self) -> Result<(), cpal::PlayStreamError> { - tracing::info!("starting audio engine"); - let result = self.with_output_stream(|stream| stream.play()); - if result.is_ok() { - // Update current config. - self.config.playing = true; - } - result - } - - /// Pauses the output stream. - pub fn pause(&mut self) -> Result<(), cpal::PauseStreamError> { - tracing::info!("pausing audio engine"); - let result = self.with_output_stream(|stream| stream.pause()); - if result.is_ok() { - // Update current config. - self.config.playing = false; - } - result - } - - /// Resets the output device and stream. - pub fn reset_output_device(&mut self) -> Result<(), ResetDeviceError> { - tracing::info!("resetting output device"); - // Reset device. - self.output_device = - Self::output_device(&self.host, &self.config.selected_device) - .map_err(ResetDeviceError::Device)?; - // Reset stream. - self.reset_output_stream() - .map_err(ResetDeviceError::Stream)?; - Ok(()) - } - - /// Resets the output stream. - pub fn reset_output_stream(&mut self) -> Result<(), ResetStreamError> { - tracing::info!("resetting output stream"); - let supported_output_stream_config = - Self::supported_output_stream_config( - &self.output_device, - &self.config.output_stream, - ) - .map_err(ResetStreamError::Config)?; - // This replaces the stream handle with a new one. - // The handle removes the old stream from the thread-local - // storage on drop. - self.output_stream_handle = Self::add_thread_local_stream( - Self::create_output_stream( - &self.output_device, - &supported_output_stream_config, - self.output_buffer.consumer(), - ) - .map_err(ResetStreamError::Config)?, - ); - // Play if previously configured to. - if self.config.playing { - self.play() - .map_err(ResetStreamError::Play)?; - } - Ok(()) - } - - // Helper function for adding a stream to the thread-local storage. - fn add_thread_local_stream(stream: cpal::Stream) -> StreamHandle { - stream_handle::with_streams_mut(|streams| { - streams.insert(stream) - // Unlikely (max index is usize^2-1). - // Panic on overflow. - .expect( - "unable to insert stream due to generational index overflow", - ) - }) - } - - // Helper function for accessing the output stream from thread-local storage. - fn with_output_stream(&self, f: F) -> T - where - F: FnOnce(&cpal::Stream) -> T, - { - stream_handle::with_streams(|streams| { - let stream = streams - .get(&self.output_stream_handle) - // Panic. Why are you accessing an invalid stream? - .expect("stream not found"); - f(stream) - }) - } - - fn supported_output_stream_config( - output_device: &cpal::Device, - expected_stream_config: &StreamConfig, - ) -> Result { - // Get a supported config. - let supported_config = match expected_stream_config { - // Get default output device config. - StreamConfig::Default => output_device - .default_output_config() - .map_err(|err| { - stream_config::Error::Device(device::Error::Other( - Box::new(err), - )) - })?, - // Read custom output stream config. - StreamConfig::Custom { - sample_format, - sample_rate, - channels, - } => { - let SampleFormat(sample_format) = sample_format; - // Retrieve all supported configs. - let supported_configs = output_device - .supported_output_configs() - .map_err(|x| { - stream_config::Error::Device( - device::Error::Disconnected(Box::new(x)), - ) - })?; - // Find a config that matches the requested parameters. - supported_configs - .into_iter() - .find(|x| { - x.channels() == *channels - && x.min_sample_rate() <= *sample_rate - && x.max_sample_rate() >= *sample_rate - && x.sample_format() == *sample_format - }) - .ok_or(stream_config::Error::NoMatchingConfig)? - .with_sample_rate(*sample_rate) - } - }; - // Check if the channel count is supported. - let channel_count = supported_config.channels(); - if !Buffer::SUPPORTED_CHANNEL_COUNTS.contains(&channel_count) { - Err(stream_config::Error::UnsupportedChannelCount(channel_count)) - } else { - Ok(supported_config) - } - } - - // Helper function for creating an output stream based on the given `StreamConfig`. - fn create_output_stream( - output_device: &cpal::Device, - supported_config: &cpal::SupportedStreamConfig, - consumer: Consumer, - ) -> Result { - // Check if the channel count is supported. - let channel_count = supported_config.channels(); - if !Buffer::SUPPORTED_CHANNEL_COUNTS.contains(&channel_count) { - Err(stream_config::Error::UnsupportedChannelCount(channel_count)) - } else { - // Build output stream. - let stream = Self::build_output_stream( - output_device, - supported_config, - consumer, - ) - .map_err(|x| stream_config::Error::Other(Box::new(x)))?; - // Ok! - tracing::info!( - "created output stream with config: {:?}", - supported_config - ); - Ok(stream) - } - } - - fn build_output_stream( - output_device: &cpal::Device, - supported_config: &cpal::SupportedStreamConfig, - mut consumer: Consumer, - ) -> Result { - let config = supported_config.config(); - let sample_format = supported_config.sample_format(); - let channel_count = supported_config.channels(); - match sample_format { - // Special case: f64 matches the memory layout of our buffer. - cpal::SampleFormat::F64 => output_device.build_output_stream( - &config, - move |data: &mut [f64], _info| { - Self::output_callback_f64( - data, - &mut consumer, - channel_count, - ) - }, - Self::output_error_callback, - None, - ), - // Rest of the cases has to convert samples manually. - cpal::SampleFormat::F32 => output_device.build_output_stream( - &config, - move |data: &mut [f32], _info| { - Self::output_callback::( - data, - &mut consumer, - channel_count, - ) - }, - Self::output_error_callback, - None, - ), - cpal::SampleFormat::I8 => output_device.build_output_stream( - &config, - move |data: &mut [i8], _info| { - Self::output_callback::( - data, - &mut consumer, - channel_count, - ) - }, - Self::output_error_callback, - None, - ), - cpal::SampleFormat::I16 => output_device.build_output_stream( - &config, - move |data: &mut [i16], _info| { - Self::output_callback::( - data, - &mut consumer, - channel_count, - ) - }, - Self::output_error_callback, - None, - ), - cpal::SampleFormat::I32 => output_device.build_output_stream( - &config, - move |data: &mut [i32], _info| { - Self::output_callback::( - data, - &mut consumer, - channel_count, - ) - }, - Self::output_error_callback, - None, - ), - cpal::SampleFormat::I64 => output_device.build_output_stream( - &config, - move |data: &mut [i64], _info| { - Self::output_callback::( - data, - &mut consumer, - channel_count, - ) - }, - Self::output_error_callback, - None, - ), - cpal::SampleFormat::U8 => output_device.build_output_stream( - &config, - move |data: &mut [u8], _info| { - Self::output_callback::( - data, - &mut consumer, - channel_count, - ) - }, - Self::output_error_callback, - None, - ), - cpal::SampleFormat::U16 => output_device.build_output_stream( - &config, - move |data: &mut [u16], _info| { - Self::output_callback::( - data, - &mut consumer, - channel_count, - ) - }, - Self::output_error_callback, - None, - ), - cpal::SampleFormat::U32 => output_device.build_output_stream( - &config, - move |data: &mut [u32], _info| { - Self::output_callback::( - data, - &mut consumer, - channel_count, - ) - }, - Self::output_error_callback, - None, - ), - cpal::SampleFormat::U64 => output_device.build_output_stream( - &config, - move |data: &mut [u64], _info| { - Self::output_callback::( - data, - &mut consumer, - channel_count, - ) - }, - Self::output_error_callback, - None, - ), - _ => todo!("unsupported sample format: {:?}", sample_format), - } - } - - fn output_callback_f64( - data: &mut [f64], - consumer: &mut Consumer, - channel_count: cpal::ChannelCount, - ) { - match consumer { - Consumer::Mono(consumer) - if channel_count == MonoFrame::CHANNEL_COUNT => - { - // reinterpret data as MonoFrame slice, as memory layout matches - let data = unsafe { - std::slice::from_raw_parts_mut( - data.as_ptr() as *mut MonoFrame, - data.len(), - ) - }; - let _result = consumer.read(data); - } - Consumer::Stereo(consumer) - if channel_count == StereoFrame::CHANNEL_COUNT => - { - // reinterpret data as StereoFrame slice, as memory layout matches - let data = unsafe { - std::slice::from_raw_parts_mut( - data.as_ptr() as *mut StereoFrame, - data.len(), - ) - }; - let _result = consumer.read(data); - } - _ => unreachable!(), - } - } - - fn output_callback( - data: &mut [T], - consumer: &mut Consumer, - channel_count: cpal::ChannelCount, - ) where - T: cpal::Sample + cpal::FromSample, - { - match consumer { - Consumer::Mono(consumer) - if channel_count == MonoFrame::CHANNEL_COUNT => - { - for sample in data { - let read_frame = Default::default(); - let _result = consumer.read(&mut [read_frame]); - *sample = read_frame.center.to_sample(); - } - } - Consumer::Stereo(consumer) - if channel_count == StereoFrame::CHANNEL_COUNT => - { - for frame in data.chunks_exact_mut(2) { - let read_frame = Default::default(); - let _result = consumer.read(&mut [read_frame]); - frame[0] = read_frame.left.to_sample(); - frame[1] = read_frame.right.to_sample(); - } - } - _ => unreachable!(), - } - } - - fn output_error_callback(err: cpal::StreamError) { - tracing::error!("output stream error: {}", err); - } - - // Helper function for opening an output device based on the given device selector. - fn output_device( - host: &cpal::Host, - device_selection: &SelectedDevice, - ) -> Result { - match &device_selection { - // Open default output device. - SelectedDevice::Default => host - .default_output_device() - .ok_or(device::Error::NoDefault), - // Open output device with matching name. - // In case of multiple matches, the first one is selected. - // TODO: Do any of the platforms allow for duplicate names? - SelectedDevice::Named(name) => host - .output_devices() - .map_err(|x| device::Error::Other(Box::new(x)))? - .try_find(|d| { - Ok(&d - .name() - .map_err(|x| device::Error::Other(Box::new(x)))? - == name) - })? - .ok_or(device::Error::NoMatch), - } - } -} - -impl TryFrom<&Settings> for AudioEngine { - type Error = Error; - fn try_from(value: &Settings) -> Result { - Self::from_settings(value) - } -} diff --git a/backend/lib/src/app_data/app_state/edit_state/audio_engine/buffer.rs b/backend/lib/src/app_data/app_state/edit_state/audio_engine/buffer.rs deleted file mode 100644 index 4a0e750..0000000 --- a/backend/lib/src/app_data/app_state/edit_state/audio_engine/buffer.rs +++ /dev/null @@ -1,57 +0,0 @@ -pub use self::mono::{MonoBuffer, MonoFrame}; -pub use self::stereo::{StereoBuffer, StereoFrame}; - -mod mono; -mod stereo; - -pub enum Buffer { - Mono(MonoBuffer), - Stereo(StereoBuffer), -} - -pub enum Producer { - Mono(rb::Producer), - Stereo(rb::Producer), -} - -pub enum Consumer { - Mono(rb::Consumer), - Stereo(rb::Consumer), -} - -pub enum BufferConfig { - Mono { length: usize }, - Stereo { length: usize }, -} - -impl Buffer { - pub const SUPPORTED_CHANNEL_COUNTS: [u16; 2] = - [MonoFrame::CHANNEL_COUNT, StereoFrame::CHANNEL_COUNT]; - - pub fn from_config(config: &BufferConfig) -> Self { - match config { - BufferConfig::Mono { length } => Self::Mono(MonoBuffer { - inner: rb::SpscRb::new(*length), - sample_index: 0, - }), - BufferConfig::Stereo { length } => Self::Stereo(StereoBuffer { - inner: rb::SpscRb::new(*length), - sample_index: 0, - }), - } - } - - pub fn producer(&mut self) -> Producer { - match self { - Self::Mono(buffer) => Producer::Mono(buffer.producer()), - Self::Stereo(buffer) => Producer::Stereo(buffer.producer()), - } - } - - pub fn consumer(&mut self) -> Consumer { - match self { - Self::Mono(buffer) => Consumer::Mono(buffer.consumer()), - Self::Stereo(buffer) => Consumer::Stereo(buffer.consumer()), - } - } -} diff --git a/backend/lib/src/app_data/app_state/edit_state/audio_engine/buffer/mono.rs b/backend/lib/src/app_data/app_state/edit_state/audio_engine/buffer/mono.rs deleted file mode 100644 index e4f563f..0000000 --- a/backend/lib/src/app_data/app_state/edit_state/audio_engine/buffer/mono.rs +++ /dev/null @@ -1,27 +0,0 @@ -use rb::RB; - -#[derive(Debug, Clone, Copy, PartialEq, Default)] -#[repr(C)] -pub struct MonoFrame { - pub center: f64, -} - -impl MonoFrame { - pub const CHANNEL_COUNT: u16 = 1; -} - -pub struct MonoBuffer { - pub inner: rb::SpscRb, - /// Relative to the start of the frame. - pub sample_index: usize, -} - -impl MonoBuffer { - pub fn producer(&mut self) -> rb::Producer { - self.inner.producer() - } - - pub fn consumer(&mut self) -> rb::Consumer { - self.inner.consumer() - } -} diff --git a/backend/lib/src/app_data/app_state/edit_state/audio_engine/buffer/stereo.rs b/backend/lib/src/app_data/app_state/edit_state/audio_engine/buffer/stereo.rs deleted file mode 100644 index 2321834..0000000 --- a/backend/lib/src/app_data/app_state/edit_state/audio_engine/buffer/stereo.rs +++ /dev/null @@ -1,28 +0,0 @@ -use rb::RB; - -#[derive(Debug, Clone, Copy, PartialEq, Default)] -#[repr(C)] -pub struct StereoFrame { - pub left: f64, - pub right: f64, -} - -impl StereoFrame { - pub const CHANNEL_COUNT: u16 = 2; -} - -pub struct StereoBuffer { - pub inner: rb::SpscRb, - /// Relative to the start of the frame. - pub sample_index: usize, -} - -impl StereoBuffer { - pub fn producer(&mut self) -> rb::Producer { - self.inner.producer() - } - - pub fn consumer(&mut self) -> rb::Consumer { - self.inner.consumer() - } -} diff --git a/backend/lib/src/app_data/app_state/edit_state/audio_engine/device.rs b/backend/lib/src/app_data/app_state/edit_state/audio_engine/device.rs deleted file mode 100644 index ad7f77c..0000000 --- a/backend/lib/src/app_data/app_state/edit_state/audio_engine/device.rs +++ /dev/null @@ -1,34 +0,0 @@ -#[derive(Debug)] -pub enum Error { - NoDefault, - NoMatch, - Disconnected(Box), - Other(Box), -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Error::NoDefault => write!( - f, - "unable to find a default device or no default device is set" - ), - Error::NoMatch => write!(f, "no matching device"), - Error::Disconnected(err) => { - write!(f, "device was disconnected: {err}") - } - Error::Other(err) => write!(f, "{err}"), - } - } -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Error::NoDefault => None, - Error::NoMatch => None, - Error::Disconnected(err) => Some(err.as_ref()), - Error::Other(err) => Some(err.as_ref()), - } - } -} diff --git a/backend/lib/src/app_data/app_state/edit_state/audio_engine/error.rs b/backend/lib/src/app_data/app_state/edit_state/audio_engine/error.rs deleted file mode 100644 index 81e4f29..0000000 --- a/backend/lib/src/app_data/app_state/edit_state/audio_engine/error.rs +++ /dev/null @@ -1,76 +0,0 @@ -use super::{device, host_id, stream_config}; - -#[derive(Debug)] -pub enum SettingsError { - StreamConfigParse(stream_config::ParseError), - HostIdParse(host_id::ParseError), - InvalidStreamConfig(stream_config::Error), -} - -impl std::error::Error for SettingsError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - SettingsError::StreamConfigParse(err) => Some(err), - SettingsError::HostIdParse(err) => Some(err), - SettingsError::InvalidStreamConfig(err) => Some(err), - } - } -} - -impl std::fmt::Display for SettingsError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - SettingsError::StreamConfigParse(err) => err.fmt(f), - SettingsError::HostIdParse(err) => err.fmt(f), - SettingsError::InvalidStreamConfig(err) => err.fmt(f), - } - } -} - -#[derive(Debug)] -pub enum ResetStreamError { - Config(stream_config::Error), - Play(cpal::PlayStreamError), -} - -impl std::error::Error for ResetStreamError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - ResetStreamError::Config(err) => Some(err), - ResetStreamError::Play(err) => Some(err), - } - } -} - -impl std::fmt::Display for ResetStreamError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ResetStreamError::Config(err) => err.fmt(f), - ResetStreamError::Play(err) => err.fmt(f), - } - } -} - -#[derive(Debug)] -pub enum ResetDeviceError { - Stream(ResetStreamError), - Device(device::Error), -} - -impl std::error::Error for ResetDeviceError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - ResetDeviceError::Stream(err) => Some(err), - ResetDeviceError::Device(err) => Some(err), - } - } -} - -impl std::fmt::Display for ResetDeviceError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ResetDeviceError::Stream(err) => err.fmt(f), - ResetDeviceError::Device(err) => err.fmt(f), - } - } -} diff --git a/backend/lib/src/app_data/app_state/edit_state/audio_engine/host_id.rs b/backend/lib/src/app_data/app_state/edit_state/audio_engine/host_id.rs deleted file mode 100644 index 0e77181..0000000 --- a/backend/lib/src/app_data/app_state/edit_state/audio_engine/host_id.rs +++ /dev/null @@ -1,130 +0,0 @@ -use chipbox_common as common; -use cowstr::CowStr; -use std::str::FromStr; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub struct HostId(cpal::HostId); - -impl From for cpal::HostId { - fn from(val: HostId) -> Self { - let HostId(val) = val; - val - } -} - -#[derive(Debug)] -pub enum ParseError { - Invalid { value: CowStr }, - WrongPlatform { value: CowStr }, -} - -impl std::fmt::Display for ParseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ParseError::Invalid { value } => { - write!(f, "`{value}` is not convertible to a valid host id") - } - ParseError::WrongPlatform { value } => { - write!( - f, - "host `{value}` is not supported on this platform ({platform})", - platform = std::env::consts::OS - ) - } - } - } -} - -impl std::error::Error for ParseError {} - -impl FromStr for HostId { - type Err = ParseError; - fn from_str(value: &str) -> Result { - const WASAPI: &str = "wasapi"; - const ASIO: &str = "asio"; - const JACK: &str = "jack"; - const ALSA: &str = "alsa"; - const CORE_AUDIO: &str = "coreaudio"; - const OBOE: &str = "oboe"; - const EMSCRIPTEN: &str = "emscripten"; - const WEBAUDIO: &str = "webaudio"; - const NULL: &str = "null"; - const SUPPORTED_BACKENDS: [&str; 9] = [ - WASAPI, ASIO, JACK, ALSA, CORE_AUDIO, OBOE, EMSCRIPTEN, WEBAUDIO, - NULL, - ]; - - match value.to_lowercase().as_str() { - #[cfg(target_os = "windows")] - WASAPI => Ok(Self(cpal::HostId::Wasapi)), - #[cfg(target_os = "windows")] - ASIO => Ok(Self(cpal::HostId::Asio)), - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd" - ))] - JACK => Ok(Self(cpal::HostId::Jack)), - #[cfg(any( - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd" - ))] - ALSA => Ok(Self(cpal::HostId::Alsa)), - #[cfg(target_os = "macos")] - CORE_AUDIO => Ok(Self(cpal::HostId::CoreAudio)), - #[cfg(target_os = "android")] - OBOE => Ok(Self(cpal::HostId::Oboe)), - #[cfg(target_os = "emscripten")] - EMSCRIPTEN => Ok(Self(cpal::HostId::Emscripten)), - #[cfg(all(target_arch = "wasm32", feature = "wasm-bindgen"))] - WEBAUDIO => Ok(Self(cpal::HostId::WebAudio)), - #[cfg(not(any( - windows, - target_os = "linux", - target_os = "dragonfly", - target_os = "freebsd", - target_os = "netbsd", - target_os = "macos", - target_os = "ios", - target_os = "emscripten", - target_os = "android", - all(target_arch = "wasm32", feature = "wasm-bindgen"), - )))] - NULL => Ok(Self(cpal::HostId::Null)), - _ => { - if SUPPORTED_BACKENDS.contains(&value) { - Err(ParseError::WrongPlatform { - value: value.into(), - }) - } else { - Err(ParseError::Invalid { - value: value.into(), - }) - } - } - } - } -} - -static DEFAULT_HOST_ID: once_cell::sync::Lazy = - once_cell::sync::Lazy::new(|| HostId(cpal::default_host().id())); - -impl TryFrom<&common::settings::audio_engine::SelectedHost> for HostId { - type Error = ParseError; - - fn try_from( - value: &common::settings::audio_engine::SelectedHost, - ) -> Result { - match value { - common::settings::audio_engine::SelectedHost::Default => { - Ok(*DEFAULT_HOST_ID) - } - common::settings::audio_engine::SelectedHost::Named(name) => { - Self::from_str(name) - } - } - } -} diff --git a/backend/lib/src/app_data/app_state/edit_state/audio_engine/stream_config.rs b/backend/lib/src/app_data/app_state/edit_state/audio_engine/stream_config.rs deleted file mode 100644 index 81a6bf5..0000000 --- a/backend/lib/src/app_data/app_state/edit_state/audio_engine/stream_config.rs +++ /dev/null @@ -1,109 +0,0 @@ -pub use sample_format::SampleFormat; - -use chipbox_common as common; -use std::str::FromStr as _; -mod sample_format; - -#[derive(Debug)] -pub enum Error { - Device(super::device::Error), - NoMatchingConfig, - UnsupportedChannelCount(u16), - Other(Box), -} - -impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Error::Device(err) => write!(f, "{err}"), - Error::NoMatchingConfig => { - write!( - f, - "unable to find a matching stream config for this device" - ) - } - Error::UnsupportedChannelCount(err) => { - write!(f, "unsupported channel count: {err}") - } - Error::Other(err) => write!(f, "{err}"), - } - } -} - -impl std::error::Error for Error { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Error::Device(err) => Some(err), - Error::NoMatchingConfig => None, - Error::UnsupportedChannelCount(_) => None, - Error::Other(err) => Some(err.as_ref()), - } - } -} - -#[derive(Debug, Default, Clone, PartialEq)] -pub enum StreamConfig { - #[default] - Default, - Custom { - sample_format: SampleFormat, - sample_rate: cpal::SampleRate, - channels: cpal::ChannelCount, - }, -} - -impl StreamConfig { - pub fn from_settings( - settings: &common::audio_engine::StreamConfig, - ) -> Result { - Self::try_from(settings) - } -} - -#[derive(Debug)] -pub enum ParseError { - SampleFormat(sample_format::ParseError), -} - -impl std::fmt::Display for ParseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - ParseError::SampleFormat(err) => { - write!(f, "unable to parse sample format: {err}") - } - } - } -} - -impl std::error::Error for ParseError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - ParseError::SampleFormat(err) => Some(err), - } - } -} - -impl TryFrom<&common::audio_engine::StreamConfig> for StreamConfig { - type Error = ParseError; - fn try_from( - value: &common::audio_engine::StreamConfig, - ) -> Result { - match value { - common::audio_engine::StreamConfig::Default => Ok(Self::Default), - common::audio_engine::StreamConfig::Custom { - sample_format, - sample_rate, - channels, - buffer_duration: _, - } => { - let sample_format = SampleFormat::from_str(sample_format) - .map_err(ParseError::SampleFormat)?; - Ok(Self::Custom { - sample_format, - sample_rate: cpal::SampleRate(*sample_rate), - channels: *channels, - }) - } - } - } -} diff --git a/backend/lib/src/app_data/app_state/edit_state/audio_engine/stream_config/sample_format.rs b/backend/lib/src/app_data/app_state/edit_state/audio_engine/stream_config/sample_format.rs deleted file mode 100644 index 21ab89c..0000000 --- a/backend/lib/src/app_data/app_state/edit_state/audio_engine/stream_config/sample_format.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::str::FromStr; - -#[derive(Debug, Clone, PartialEq)] -pub struct ParseError(String); - -impl std::fmt::Display for ParseError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "invalid sample format: {}", self.0) - } -} - -impl std::error::Error for ParseError {} - -#[derive(Debug, Clone, PartialEq)] -pub struct SampleFormat(pub cpal::SampleFormat); - -impl From for SampleFormat { - fn from(value: cpal::SampleFormat) -> Self { - Self(value) - } -} - -impl FromStr for SampleFormat { - type Err = ParseError; - - fn from_str(s: &str) -> Result { - const I8: &str = "i8"; - const I16: &str = "i16"; - const I32: &str = "i32"; - const I64: &str = "i64"; - const U8: &str = "u8"; - const U16: &str = "u16"; - const U32: &str = "u32"; - const U64: &str = "u64"; - const F32: &str = "f32"; - const F64: &str = "f64"; - - match s.to_lowercase().as_str() { - I8 => Ok(Self(cpal::SampleFormat::I8)), - I16 => Ok(Self(cpal::SampleFormat::I16)), - I32 => Ok(Self(cpal::SampleFormat::I32)), - I64 => Ok(Self(cpal::SampleFormat::I64)), - U8 => Ok(Self(cpal::SampleFormat::U8)), - U16 => Ok(Self(cpal::SampleFormat::U16)), - U32 => Ok(Self(cpal::SampleFormat::U32)), - U64 => Ok(Self(cpal::SampleFormat::U64)), - F32 => Ok(Self(cpal::SampleFormat::F32)), - F64 => Ok(Self(cpal::SampleFormat::F64)), - _ => Err(ParseError(s.into())), - } - } -} - -impl std::fmt::Display for SampleFormat { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self.0) - } -} diff --git a/backend/lib/src/app_data/app_state/edit_state/audio_engine/stream_handle.rs b/backend/lib/src/app_data/app_state/edit_state/audio_engine/stream_handle.rs deleted file mode 100644 index c499e03..0000000 --- a/backend/lib/src/app_data/app_state/edit_state/audio_engine/stream_handle.rs +++ /dev/null @@ -1,183 +0,0 @@ -use gen_value::vec::GenVec; -pub use thread_local::{ - clear_streams, init_streams, with_streams, with_streams_mut, -}; - -type StreamIdxProd = usize; -pub type StreamIdx = (StreamIdxProd, StreamIdxProd); -type StreamsGenVec = GenVec; - -impl From<&StreamHandle> for StreamIdx { - fn from(x: &StreamHandle) -> Self { - x.idx - } -} - -#[derive(Debug, PartialEq, Eq, Hash)] -pub struct StreamHandle { - idx: StreamIdx, -} - -#[derive(Default)] -pub struct Streams { - inner: StreamsGenVec, -} - -impl Streams { - pub fn from_gen_vec(streams: StreamsGenVec) -> Self { - Self { inner: streams } - } - - pub fn insert( - &mut self, - stream: cpal::Stream, - ) -> Result { - let handle = self - .inner - .insert(stream) - .map(|x| StreamHandle { idx: x }); - tracing::info!("adding stream to thread-local storage: `{:?}`", handle); - handle - } - - pub fn get( - &self, - idx: impl Into, - ) -> Result<&cpal::Stream, gen_value::Error> { - self.inner.get(idx.into()) - } - - fn remove( - &mut self, - idx: impl Into, - ) -> Result<(), gen_value::Error> { - self.inner.remove(idx.into()) - } -} - -impl From for Streams { - fn from(streams: StreamsGenVec) -> Self { - Self::from_gen_vec(streams) - } -} - -mod thread_local { - impl Drop for super::StreamHandle { - fn drop(&mut self) { - let _ = STREAMS.try_with(|streams| { - match &mut *streams.borrow_mut() { - Some(streams) => { - match streams.remove(self.idx) { - Ok(_) => { - tracing::info!("dropped stream. id: `{:?}`", self.idx); - } - Err(_) => { - tracing::warn!("stream already dropped. id: `{:?}`", self.idx); - } - } - } - None => { - tracing::warn!("stream handle dropped while STREAMS not initialized. id: `{:?}`", self.idx); - } - }; - }); - } - } - - use super::Streams; - use std::cell::RefCell; - use std::sync::Mutex; - - pub fn with_streams(f: F) -> T - where - F: FnOnce(&Streams) -> T, - { - verify_thread_id(); - STREAMS.with(|x| match &*x.borrow() { - Some(x) => f(x), - None => panic!( - "STREAMS thread-local accessed but not initialized on thread {:?}", - std::thread::current().id() - ), - }) - } - - pub fn with_streams_mut(f: F) -> T - where - F: FnOnce(&mut Streams) -> T, - { - verify_thread_id(); - STREAMS.with(|x| match &mut *x.borrow_mut() { - Some(x) => f(x), - None => panic!( - "STREAMS thread-local accessed but not initialized on thread {:?}", - std::thread::current().id() - ), - }) - } - - fn verify_thread_id() { - let current_thread_id = std::thread::current().id(); - let stream_thread_id = STREAM_THREAD_ID - .lock() - .unwrap(); - match &*stream_thread_id { - Some(stream_thread_id) => assert_eq!( - current_thread_id, - *stream_thread_id, - "STREAMS thread-local initialized on thread {stream_thread_id:?}, \ - but accessed from thread {current_thread_id:?}", - ), - None => panic!( - "STREAMS thread-local not initialized on thread {:?}", - std::thread::current().id() - ), - } - } - - pub fn init_streams() { - tracing::info!("initializing thread-local storage for streams"); - STREAMS.with_borrow_mut(|streams_opt| { - let mut stream_thread_id = STREAM_THREAD_ID - .lock() - .unwrap(); - match &*stream_thread_id - { - Some(thread_id) => { - panic!( - "STREAMS thread-local already initialized on thread {thread_id:?}", - ); - } - None => { - let current_thread_id = std::thread::current().id(); - *stream_thread_id = Some(current_thread_id); - *streams_opt = Some(Default::default()); - tracing::info!( - "initialized STREAMS thread-local on thread {current_thread_id:?}", - ); - } - } - }); - } - - pub fn clear_streams() { - tracing::info!("clearing thread-local storage for streams"); - let stream_count = STREAMS.with(|x| { - x.borrow() - .as_ref() - .map(|x| x.inner.len()) - .unwrap_or_default() - }); - STREAMS.with_borrow_mut(|streams_opt| { - *streams_opt = None; - }); - tracing::info!("removed streams: {stream_count}",); - } - - static STREAM_THREAD_ID: Mutex> = - Mutex::new(None); - - thread_local! { - static STREAMS: RefCell> = const { RefCell::new(None) }; - } -} diff --git a/backend/lib/src/app_data/msg.rs b/backend/lib/src/app_data/msg.rs deleted file mode 100644 index a1b5043..0000000 --- a/backend/lib/src/app_data/msg.rs +++ /dev/null @@ -1,27 +0,0 @@ -use self::request::handle_frontend_request; - -use super::AppData; -use crate::{common, ThreadMsg}; -use common::app::msg::FrontendMsg; -mod request; - -/// Handles a message from the parent thread. -/// Returns `true` if the app should quit. -pub fn handle_thread_msg(app_data: &mut AppData, msg: ThreadMsg) -> bool { - match msg { - // Handle frontend message. - ThreadMsg::Frontend(msg) => handle_frontend_msg(app_data, msg), - // Quit. - ThreadMsg::Exit => return true, - }; - false -} - -/// Handles messages from the frontend. -fn handle_frontend_msg(app_data: &mut AppData, msg: FrontendMsg) { - match msg { - FrontendMsg::Request(request) => { - handle_frontend_request(app_data, request) - } - } -} diff --git a/backend/lib/src/app_data/msg/request.rs b/backend/lib/src/app_data/msg/request.rs deleted file mode 100644 index 75c4297..0000000 --- a/backend/lib/src/app_data/msg/request.rs +++ /dev/null @@ -1,27 +0,0 @@ -use crate::app_data::AppData; -use crate::{common, AppThread}; -use common::app::msg::request::FrontendRequest; -use common::app::msg::BackendMsg; - -mod backend_response; - -/// Handles the request and sends a response to the frontend. -pub(super) fn handle_frontend_request( - app_data: &mut AppData, - request: FrontendRequest, -) { - // Handle request and prepare response. - let response = match request { - FrontendRequest::AppState => backend_response::app_state(app_data), - FrontendRequest::Settings => backend_response::settings(app_data), - FrontendRequest::UseDefaultSettings => { - backend_response::use_default_settings(app_data) - } - }; - - // Send response. - AppThread::send_message( - &app_data.tauri_app, - BackendMsg::Response(response), - ); -} diff --git a/backend/lib/src/app_data/msg/request/backend_response.rs b/backend/lib/src/app_data/msg/request/backend_response.rs deleted file mode 100644 index df94834..0000000 --- a/backend/lib/src/app_data/msg/request/backend_response.rs +++ /dev/null @@ -1,55 +0,0 @@ -use crate::app_data::app_state::AppState; -use crate::app_data::AppData; -use crate::common; -use common::app::msg::request::BackendResponse; - -/// Reply with current state. -pub(super) fn app_state(app_data: &mut AppData) -> BackendResponse { - // Get state and convert it to `BackendAppState`. - let app_state = (&app_data.state).into(); - - // Prepare response. - BackendResponse::BackendAppState(app_state) -} - -/// Reply with current settings. -pub(super) fn settings(app_data: &mut AppData) -> BackendResponse { - // Get settings. - let settings_opt = match app_data.state { - AppState::Idle { ref settings } => Some(settings.clone()), - _ => None, - }; - - // Prepare response. - BackendResponse::Settings(settings_opt) -} - -/// Set default settings and reply with a copy. -pub(super) fn use_default_settings(app_data: &mut AppData) -> BackendResponse { - // Apply and return settings. - match app_data.state { - // Fail gracefully if in the middle of reading settings. - AppState::ReadingSettings => { - tracing::error!("Frontend attempted to set settings to default while backend was still reading config."); - } - // Change state to idle if awaiting config. - AppState::AwaitConfig { .. } => { - app_data.state = AppState::Idle { - settings: Default::default(), - }; - } - // Modify if idle. - AppState::Idle { ref mut settings } => { - *settings = Default::default(); - } - // Modify if editing a project. - AppState::Edit { - ref mut settings, .. - } => { - *settings = Default::default(); - } - } - - // Prepare response. - BackendResponse::UseDefaultSettings(Default::default()) -} diff --git a/backend/lib/src/configured_state.rs b/backend/lib/src/configured_state.rs deleted file mode 100644 index ae8065c..0000000 --- a/backend/lib/src/configured_state.rs +++ /dev/null @@ -1,6 +0,0 @@ -use chipbox_common as common; - -pub trait ConfiguredState { - fn settings(&self) -> &common::Settings; - fn settings_mut(&mut self) -> &mut common::Settings; -} diff --git a/backend/lib/src/dir.rs b/backend/lib/src/dir.rs deleted file mode 100644 index 12d71a1..0000000 --- a/backend/lib/src/dir.rs +++ /dev/null @@ -1,78 +0,0 @@ -use std::path::{Path, PathBuf}; - -use crate::error::io::IoError; -use tokio::fs; - -/// Name of application data directory. -const DATA_DIR: &str = ".chipbox"; - -#[derive(Debug)] -/// Describes an error that occurred while reading the settings file. -pub enum DataDirError { - /// An I/O error occurred. - Io(IoError), - /// The `HOME` environment variable was not set. - HomeDir, -} - -/// Convenience conversion. -impl From for DataDirError { - fn from(err: IoError) -> Self { - DataDirError::Io(err) - } -} - -/// Error implementation. -impl std::error::Error for DataDirError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - DataDirError::Io(err) => Some(err), - DataDirError::HomeDir => None, - } - } -} - -/// Display implementation. -impl std::fmt::Display for DataDirError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - DataDirError::Io(err) => { - write!(f, "io error during settings read: {}", err) - } - DataDirError::HomeDir => { - write!(f, "`HOME` env variable not set") - } - } - } -} - -/// Constructs the path to a file in the application data directory. -/// -/// Asserts the existence of its parent directory with `fs::create_dir_all`. -/// # Errors -/// List of possible errors: -/// - An I/O error occurred. -/// - The `HOME` environment variable was not set. -pub async fn data_path() -> Result { - // Retrieve the path to the HOME directory. - let home_path = home::home_dir().ok_or(DataDirError::HomeDir)?; - - // Construct the path. - let path = home_path.join(DATA_DIR); - - // Ensure the directory exists. - fs::create_dir_all(&path) - .await - .map_err(|err| IoError { - err, - path: path.clone(), - })?; - - // Convert the path to a canonical path. - let canonical = path - .canonicalize() - .map_err(|err| IoError { err, path })?; - - // Return final data path. - Ok(canonical) -} diff --git a/backend/lib/src/error.rs b/backend/lib/src/error.rs deleted file mode 100644 index c6a1ad6..0000000 --- a/backend/lib/src/error.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod io; -pub mod serde; diff --git a/backend/lib/src/error/io.rs b/backend/lib/src/error/io.rs deleted file mode 100644 index e5dc8eb..0000000 --- a/backend/lib/src/error/io.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::path::PathBuf; - -#[derive(Debug)] -pub struct IoError { - pub err: std::io::Error, - pub path: PathBuf, -} - -impl std::error::Error for IoError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - Some(&self.err) - } -} - -impl std::fmt::Display for IoError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{} at path `{}`", self.err, self.path.display()) - } -} diff --git a/backend/lib/src/error/serde.rs b/backend/lib/src/error/serde.rs deleted file mode 100644 index 379b4ed..0000000 --- a/backend/lib/src/error/serde.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::path::PathBuf; - -#[derive(Debug)] -pub struct SerdeError { - pub err: serde_json::Error, - pub path: PathBuf, -} - -impl std::error::Error for SerdeError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - Some(&self.err) - } -} - -impl std::fmt::Display for SerdeError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.err) - } -} diff --git a/backend/lib/src/lib.rs b/backend/lib/src/lib.rs deleted file mode 100644 index ab9cc47..0000000 --- a/backend/lib/src/lib.rs +++ /dev/null @@ -1,208 +0,0 @@ -#![feature(try_find)] -#![feature(const_for)] -#![feature(const_trait_impl)] -#![feature(let_chains)] - -pub use chipbox_common as common; -pub mod dir; - -use app_data::{msg, AppData}; -use common::app::msg::cmd::BackendCmd; -use common::app::msg::{BackendMsg, FrontendMsg}; -use common::app::AwaitConfigReason; -use settings::SettingsExt as _; -use tauri::{async_runtime, Manager as _}; -mod app_data; -mod error; -mod project_selection; -mod settings; - -#[derive(Debug, PartialEq)] -/// Messages received by the app thread. -pub enum ThreadMsg { - /// Tauri forwarded a message from the frontend. - Frontend(FrontendMsg), - /// Main requested to exit. - /// Immediatelly closes the app thread. - Exit, -} - -/// App thread data. -/// -/// Includes all relevant application data as well as a receiver for -/// incoming messages from main. -pub struct AppThread { - data: AppData, - rx: async_runtime::Receiver, -} - -impl AppThread { - /// Create a new app thread, ready to run. - /// - /// Requires a channel RX from the parent thread. - pub fn new( - rx: async_runtime::Receiver, - tauri_app: tauri::AppHandle, - ) -> Self { - AppThread { - data: AppData::new(tauri_app), - rx, - } - } - - /// Run the app thread. - /// - /// Read settings and enter the message loop. - pub async fn run(mut self) { - tracing::trace!("App thread started."); - - // Read settings. - tracing::trace!("Reading settings."); - let result = Self::read_settings().await; - - // Handle the result. - match result { - // Read attempt succeeded. - Ok(settings_opt) => { - tracing::trace!("Settings ok."); - - // Prepare settings update message. - let msg = BackendCmd::UpdateSettings(match settings_opt { - Some(ref settings) => Ok(settings.clone()), - None => Err(AwaitConfigReason::NoConfig), - }); - - // Send message to client. - Self::send_message(self.data.tauri_app(), msg.into()); - - // Update state based on whether there was a valid config. - self.data.state = settings_opt.into(); - - // Enter message loop. - self.poll_messages().await - } - // Something went wrong while reading settings. - Err(err) => { - // Prepare error message. - let err_msg = format!("Settings read failed: {}", err); - tracing::error!(err_msg); - - // Send message to client. - let msg = BackendCmd::UpdateSettings(Err( - AwaitConfigReason::Error(err_msg.into()), - )); - Self::send_message(self.data.tauri_app(), msg.into()); - - // Wait for exit message. - self.poll_until_exit_message() - .await; - } - }; - - // Exit. - tracing::trace!("App thread finished."); - } - - // Wait for exit message. - async fn poll_until_exit_message(&mut self) { - // Await exit message. - let result = Self::poll_message_until(&mut self.rx, |msg| match msg { - ThreadMsg::Exit => Some(()), - _ => None, - }) - .await; - - // Handle the result. - match result { - // Success. - Some(_) => { - tracing::trace!("Received exit message."); - } - // Channel is already closed. - None => { - // Fail gracefully. - tracing::error!( - "Channel was closed before app thread received an exit message." - ); - } - } - } - - /// Polls messages from the channel in a loop. - /// The given closure is called to process the message. - /// If the closure returns `None`, the next message is polled. - /// If the closure returns `Some(T)`, the loop ends. - /// - /// # Returns - /// - `Ok(T)` if the loop ends, with `T` being the return value of the closure. - /// - `Err(())` if the channel is closed before the loop ends. - /// - /// # Notes - /// - If you neither send the expected message or close the channel, - /// the loop will never end. - /// - If the closure never returns `Some(T)`, the loop will keep polling - /// until the channel is closed. - async fn poll_message_until( - rx: &mut async_runtime::Receiver, - mut f: F, - ) -> Option - where - F: FnMut(ThreadMsg) -> Option, - { - tracing::trace!("Polling for messages."); - // Wait for messages, stop when the channel is closed. - while let Some(msg) = rx.recv().await { - // Call the closure. - let opt = f(msg); - // Return `Ok(T)` if the closure returned `Some(T)`. - // Otherwise, continue polling. - if let Some(result) = opt { - return Some(result); - } - } - // Channel is closed. - None - } - - /// Polls messages from the channel in a loop. - async fn poll_messages(&mut self) { - Self::poll_message_until(&mut self.rx, |msg| { - match msg::handle_thread_msg(&mut self.data, msg) { - true => Some(()), - false => None, - } - }) - .await; - // Channel was closed. - tracing::trace!("Channel closed."); - } - - /// Send an `AppMessage` to the client window. - fn send_message(tauri_app: &tauri::AppHandle, msg: BackendMsg) { - tracing::trace!("Sending message to frontend: {:?}", msg); - tauri_app - .emit_all(BackendMsg::event_name(), msg) - .unwrap_or_else(|err| { - tracing::error!("Failed to send message to frontend: {}", err); - }) - } - - /// Read settings from the config file. - /// Returns `Ok(None)` if the config file does not exist. - async fn read_settings( - ) -> Result, settings::SettingsError> { - match common::Settings::read().await { - // Settings found. - Ok(settings) => Ok(Some(settings)), - // We catch `std::io::ErrorKind::NotFound`. - // Not having a config file is a valid state. - Err(settings::SettingsError::Io(ref e)) - if e.err.kind() == std::io::ErrorKind::NotFound => - { - Ok(None) - } - // Something else went wrong. - Err(err) => Err(err), - } - } -} diff --git a/backend/lib/src/settings.rs b/backend/lib/src/settings.rs deleted file mode 100644 index 969a554..0000000 --- a/backend/lib/src/settings.rs +++ /dev/null @@ -1,136 +0,0 @@ -use crate::dir::{self, DataDirError}; -use crate::error::io::IoError; -use crate::error::serde::SerdeError; -use chipbox_common as common; -use common::Settings; -use std::path::PathBuf; -use tokio::fs; - -#[derive(Debug)] -/// Describes an error that occurred while reading the settings file. -pub enum SettingsError { - /// An I/O error occurred. - Io(IoError), - /// A serialization/deserialization error occurred. - Serde(SerdeError), - /// A data-dir operation failed. See `dir` module. - DataDir(DataDirError), -} - -/// Convenience conversion. -impl From for SettingsError { - fn from(err: IoError) -> Self { - SettingsError::Io(err) - } -} - -/// Convenience conversion. -impl From for SettingsError { - fn from(err: SerdeError) -> Self { - SettingsError::Serde(err) - } -} - -/// Convenience conversion. -impl From for SettingsError { - fn from(err: DataDirError) -> Self { - SettingsError::DataDir(err) - } -} - -/// Error implementation. -impl std::error::Error for SettingsError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - SettingsError::Io(err) => Some(err), - SettingsError::Serde(err) => Some(err), - SettingsError::DataDir(err) => Some(err), - } - } -} - -/// Display implementation. -impl std::fmt::Display for SettingsError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - SettingsError::Io(err) => { - write!(f, "io error during settings read: {}", err) - } - SettingsError::Serde(err) => { - write!(f, "serde error during settings read: {}", err) - } - SettingsError::DataDir(err) => { - write!(f, "data-dir error during settings read: {}", err) - } - } - } -} - -/// Extension trait for `Settings`. -/// -/// This trait provides methods for reading and writing a settings file. -pub trait SettingsExt { - /// Returns the path to the settings file. - /// Ensures the directory exists. - async fn file_path() -> Result; - - /// Reads the settings file. - async fn read() -> Result; - - /// Writes the settings file. - async fn write(&self) -> Result<(), SettingsError>; -} - -/// Extends `common::Settings` with methods for reading and writing a -/// settings file. -impl SettingsExt for Settings { - /// Constructs the path to the settings file. - async fn file_path() -> Result { - const FILENAME: &str = "settings.json"; - let path = dir::data_path() - .await? - .join(FILENAME); - Ok(path) - } - - /// Reads the settings file. - async fn read() -> Result { - // Retrieve the path to the settings file. - let path = Self::file_path().await?; - - // Read the settings file. - let data = fs::read_to_string(&path) - .await - .map_err(|err| { - let path = path.to_owned(); - IoError { err, path } - })?; - - // Parse the settings file. - let settings = serde_json::from_str(&data) - .map_err(|err| SerdeError { err, path })?; - - // Return settings. - Ok(settings) - } - - /// Writes the settings file. - async fn write(&self) -> Result<(), SettingsError> { - // Retrieve the path to the settings file. - let path = Self::file_path().await?; - - // Serialize the settings file. - let data = serde_json::to_string(&self).map_err(|err| { - let path = path.to_owned(); - SerdeError { err, path } - })?; - - // Write the settings file. - fs::write(&path, data) - .await - .map_err(|err| IoError { err, path })?; - - // All done. - Ok(()) - } -} diff --git a/backend/src/app_thread.rs b/backend/src/app_thread.rs deleted file mode 100644 index 836af4d..0000000 --- a/backend/src/app_thread.rs +++ /dev/null @@ -1,108 +0,0 @@ -use crate::backend_lib::{AppThread, ThreadMsg}; -use crate::glue::msg::BackendAppTx; -use tauri::async_runtime::{self, JoinHandle, Mutex}; -use tauri::Manager as _; - -/// Thread handle for the app thread. -#[derive(Default)] -pub(super) struct ManagedJoinHandle(Mutex>>); - -/// Join the app thread. -async fn join(managed_app_thread_handle: &mut ManagedJoinHandle) { - tracing::trace!("Waiting for app thread to finish."); - - // Lock app thread join handle mutex. - let ManagedJoinHandle(ref mut mutex) = managed_app_thread_handle; - let mut lock = mutex.lock().await; - - // Take ownership of the join handle. - match lock.take() { - // Thread is running as expected. - Some(join_handle) => { - // Join the app thread. - join_handle - .await - // Fail gracefully. - .unwrap_or_else(|err| { - // Join failed. - tracing::error!( - "Failed to join app thread due to panic: {err}" - ); - }); - } - // Thread was aleady closed (or ownership was lost). - None => { - // Fail gracefully. - // App thread was closed or closing it is out of scope. - tracing::error!( - "Attempted to join app thread while it was not running." - ); - } - } - - // All ok. - tracing::trace!("App thread joined."); -} - -/// Start the backend-lib app thread -/// and set up a message channel between them. -pub async fn start( - managed_app_thread_handle: &mut ManagedJoinHandle, - app: &tauri::AppHandle, -) { - // Create message channels. - let (tx, rx) = async_runtime::channel::(128); - - // Store the message TX in the app state. - app.manage(BackendAppTx(tx)); - - // Start the app thread. - let join_handle = - async_runtime::spawn(AppThread::new(rx, app.clone()).run()); - - // Store the join handle. - let ManagedJoinHandle(ref mut mutex) = managed_app_thread_handle; - *mutex.lock().await = Some(join_handle); -} - -/// Send an exit message to the backend-lib app thread -/// and wait for it to finish. -pub async fn close( - managed_app_thread_handle: &mut ManagedJoinHandle, - app: &tauri::AppHandle, -) { - tracing::trace!("Sending exit message to app thread."); - - // Get message sender. - match app.try_state::() { - // Message sender state is available. - Some(state) => { - // Get message sender. - let BackendAppTx(ref tx) = state.inner(); - - // Send exit message. - match tx.send(ThreadMsg::Exit).await { - // Message sent. - Ok(_) => { - // Join the app thread. - join(managed_app_thread_handle).await; - } - // The channel was already closed. - Err(_) => { - // Fail gracefully. - // Channel is closed, so we can assume that the app thread has finished. - tracing::error!("Sending exit message to app thread failed, channel was already closed."); - } - } - } - // Could not get message sender from app state. - None => { - // Fail gracefully. - // App state is not available, so we can assume that the app thread was never started. - tracing::error!("Sending exit message to app thread failed, app thread TX was not stored in app state."); - } - }; - - // All ok. - tracing::trace!("App closed."); -} diff --git a/backend/lib/src/project_selection.rs b/backend/src/file.rs similarity index 100% rename from backend/lib/src/project_selection.rs rename to backend/src/file.rs diff --git a/backend/src/lib.rs b/backend/src/lib.rs new file mode 100644 index 0000000..25cf401 --- /dev/null +++ b/backend/src/lib.rs @@ -0,0 +1,13 @@ +//! This library implements the backend part of `chipbox`. +//! +//! It boots up a [`tauri`] application and handles requests from the frontend. +//! It also manages most system resources required to run the application, +//! such as audio devices and file handles, used to render audio as part of the +//! functionality provided by the whole application. + +mod file; +mod paths; +mod tauri_run; +mod tracing_layers; + +pub use tauri_run::run; diff --git a/backend/src/main.rs b/backend/src/main.rs index 38a6fd7..6f5aed7 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,97 +1,16 @@ -//! Chipbox `tauri` application backend. - -use color_eyre::eyre; -use glue::handler::BuilderGlue as _; - -use {chipbox_backend_lib as backend_lib, chipbox_glue as glue}; - -mod app_thread; -mod tracing_setup; - -/// Construct and configure the `tauri_plugin_window_state` plugin. -fn window_plugin() -> tauri::plugin::TauriPlugin -where - R: tauri::Runtime, -{ - tauri_plugin_window_state::Builder::default().build() -} - -/// Construct and configure a `tauri::App`. -fn tauri_app() -> tauri::App { - // Create builder. - tauri::Builder::default() - // Add window plugin. - .plugin(window_plugin()) - // See `glue::handler::BuilderGlue`. - .glue_invoke_handler() - // Use project context. - .build(tauri::generate_context!()) - // Something went wrong while building the app. - // Unrecoverable error. - .expect("error while building `tauri::App`") -} - -/// Tauri app is exiting. -/// Called on `tauri::RunEvent::Exit` -fn exit( - managed_app_thread_handle: &mut app_thread::ManagedJoinHandle, - app: &tauri::AppHandle, -) { - tracing::trace!("Tauri app is exiting as requested."); - - // Close the backend lib app thread. - let rt = tauri::async_runtime::handle(); - rt.block_on(app_thread::close(managed_app_thread_handle, app)); -} - -/// Tauri app is ready. -/// Called on `tauri::RunEvent::Ready`. -fn ready( - managed_app_thread_handle: &mut app_thread::ManagedJoinHandle, - app: &tauri::AppHandle, -) { - // Start the backend lib app thread. - let rt = tauri::async_runtime::handle(); - rt.block_on(app_thread::start(managed_app_thread_handle, app)); - - // All ok. - tracing::trace!("Tauri app is ready."); -} - -/// Tauri app event handler. -fn run( - managed_app_thread_handle: &mut app_thread::ManagedJoinHandle, - app: &tauri::AppHandle, - event: tauri::RunEvent, -) { - match event { - tauri::RunEvent::Ready => ready(managed_app_thread_handle, app), - tauri::RunEvent::Exit => exit(managed_app_thread_handle, app), - _ => {} - } -} - -/// Application entry point. -fn main() -> eyre::Result<()> { - // Install color-eyre. - color_eyre::install()?; - - // Initialize tracing. - let rt = tauri::async_runtime::handle(); - let _guard = tracing_setup::init(rt)?; - - // Create managed handle for the app thread. - let mut managed_app_thread_handle = - app_thread::ManagedJoinHandle::default(); - - // Start tauri app. - tauri_app() - .run(move |app, event| run(&mut managed_app_thread_handle, app, event)); - - // Tauri calls `std::process::exit(0)` after `RunEvent::Exit`. - // Modify accordingly if changed in the future. - // It is expected behavior as of now. - eyre::bail!( - "Process should've terminated after `RunEvent::Exit` but did not" - ); +//! The binary for the backend application. +//! +//! Defines the entry point for platforms other than mobile. +//! See [`main`] for more information. + +// Prevents additional console window on Windows in release. +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +/// Inner entry point code resides in [chipbox_lib::run]. +/// This function is only relevant for platforms other than mobile. +/// +/// This is done for mobile platform compatibility, see +/// [here](https://v2.tauri.app/start/migrate/from-tauri-1/#preparing-for-mobile). +fn main() -> color_eyre::Result<()> { + chipbox_lib::run() } diff --git a/backend/src/paths.rs b/backend/src/paths.rs new file mode 100644 index 0000000..34e56e9 --- /dev/null +++ b/backend/src/paths.rs @@ -0,0 +1,3 @@ +pub(crate) mod data; +pub(crate) mod log; +pub(crate) mod settings; diff --git a/backend/src/paths/data.rs b/backend/src/paths/data.rs new file mode 100644 index 0000000..4f48c7f --- /dev/null +++ b/backend/src/paths/data.rs @@ -0,0 +1,34 @@ +use chipbox_common::error; +use error::fs::FromIo as _; +use std::path::PathBuf; +use tokio::fs; + +/// Error type for accessing the application data directory. +#[derive(Debug, thiserror::Error)] +pub(crate) enum DataDirError { + #[error("file error: {0}")] + Fs(#[from] error::fs::FsError), + #[error("unable to determine home directory")] + HomeDir, +} + +/// Convenience [`Result`](std::result::Result) alias for [`DataDirError`]. +pub(crate) type DataDirResult = std::result::Result; + +/// Constructs the path to a file in the application data directory. +/// +/// The directory is created if it does not exist. +/// The returned path is canonicalized. +pub(crate) async fn dir_path() -> DataDirResult { + // Retrieve the path to the HOME directory. + let home_path = home::home_dir().ok_or(DataDirError::HomeDir)?; + // Construct the path. + let dir_path = home_path.join(".chipbox"); + // Ensure the directory exists. + fs::create_dir_all(&dir_path) + .await + .map_fs_err(dir_path.clone())?; + // Convert the path to a canonical path. + let canonical = dir_path.canonicalize().map_fs_err(dir_path.clone())?; + Ok(canonical) +} diff --git a/backend/src/paths/log.rs b/backend/src/paths/log.rs new file mode 100644 index 0000000..1466195 --- /dev/null +++ b/backend/src/paths/log.rs @@ -0,0 +1,7 @@ +use crate::paths; +use std::path::PathBuf; + +pub(crate) async fn file_path() -> Result { + let dir_path = paths::data::dir_path().await?; + Ok(dir_path.join("chipbox-backend.log")) +} diff --git a/backend/src/paths/settings.rs b/backend/src/paths/settings.rs new file mode 100644 index 0000000..771f7c0 --- /dev/null +++ b/backend/src/paths/settings.rs @@ -0,0 +1,7 @@ +use crate::paths; +use std::path::PathBuf; + +pub(crate) async fn file_path() -> Result { + let dir_path = paths::data::dir_path().await?; + Ok(dir_path.join("chipbox-settings.json")) +} diff --git a/backend/src/tauri_run.rs b/backend/src/tauri_run.rs new file mode 100644 index 0000000..1de4d80 --- /dev/null +++ b/backend/src/tauri_run.rs @@ -0,0 +1,26 @@ +//! Shared library entry point for the application. +//! +//! See [`run`] and the main function in the binary for more information. + +use crate::tracing_layers; +use chipbox_glue::{handler::BuilderGlue as _, loaded_project::LoadedProject}; + +/// Shared library entry point for the application. +/// +/// See [here](https://v2.tauri.app/start/migrate/from-tauri-1/#preparing-for-mobile) +/// for more information. +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() -> color_eyre::Result<()> { + // Must be first, installs panic and error report hooks. + color_eyre::install()?; + let _tracing_guard = tracing_layers::init()?; + + // Use [`tauri`] as the application framework. + tauri::Builder::default() + .plugin(tauri_plugin_shell::init()) + .manage(LoadedProject::default()) + .glue_invoke_handler() + .run(tauri::generate_context!()) + .expect("error while running tauri application"); + Ok(()) +} diff --git a/backend/src/tracing_layers.rs b/backend/src/tracing_layers.rs new file mode 100644 index 0000000..c6f9a58 --- /dev/null +++ b/backend/src/tracing_layers.rs @@ -0,0 +1,48 @@ +//! Functionality for initializing tracing capabilities. + +use crate::paths; +use color_eyre::eyre::{Context, ContextCompat}; +use tauri::async_runtime; +use tracing::subscriber; +use tracing_appender::{ + non_blocking::{NonBlocking, WorkerGuard}, + rolling, +}; +use tracing_subscriber::{fmt, layer::SubscriberExt as _, Layer, Registry}; + +fn stdout_layer() -> impl Layer + 'static { + fmt::layer().with_writer(std::io::stdout) +} + +/// Initialize and set a global tracing subscriber with a file logger. +/// +/// Returns a worker guard. Dropping it will close the logger. +pub fn init() -> color_eyre::Result { + // Construct subscriber. + let (appender, guard) = appender().wrap_err("failed to create appender")?; + let file_log_layer = fmt::layer().with_writer(appender).with_ansi(false); + let subscriber = Registry::default() + .with(stdout_layer()) + .with(file_log_layer); + // Install subscriber. + subscriber::set_global_default(subscriber)?; + Ok(guard) +} + +/// Create a [`tracing_appender`] file writer. +/// +/// The file used by the writer is chosen by [`paths::log::file_path`]. +fn appender() -> color_eyre::Result<(NonBlocking, WorkerGuard)> { + // Get log file path. + let rt = async_runtime::handle(); + let file_path = rt.block_on(paths::log::file_path())?; + let parent_path = file_path + .parent() + .wrap_err("unable to get parent path of log file")?; + let file_name = file_path + .file_name() + .wrap_err("unable to get file name of log file")?; + // Create log file appender. + let appender = rolling::never(parent_path, file_name); + Ok(tracing_appender::non_blocking(appender)) +} diff --git a/backend/src/tracing_setup.rs b/backend/src/tracing_setup.rs deleted file mode 100644 index 96b5305..0000000 --- a/backend/src/tracing_setup.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::backend_lib; -use backend_lib::dir; -use color_eyre::eyre; -use tracing::subscriber; -use tracing_appender::non_blocking::WorkerGuard; -use tracing_appender::rolling; -use tracing_subscriber::fmt; -use tracing_subscriber::layer::SubscriberExt as _; - -/// Initialize tracing and the file logger. -/// -/// Returns a guard. Dropping it will close the file logger. -pub(super) fn init( - rt: tauri::async_runtime::RuntimeHandle, -) -> eyre::Result { - // Construct data path. - let data_path = rt.block_on(async move { dir::data_path().await })?; - - /// Log file name. - const LOG_FILE_NAME: &str = "chipbox-backend.log"; - // Create log file. - std::fs::File::create(data_path.join(LOG_FILE_NAME))?; - - // Create log file appender. - let appender = rolling::never(data_path, LOG_FILE_NAME); - let (non_blocking_appender, guard) = - tracing_appender::non_blocking(appender); - - // Set up std-out logging. - let stdout_layer = fmt::layer().with_writer(std::io::stdout); - - // Set up file logging - let file_log_layer = fmt::layer() - .with_writer(non_blocking_appender) - .with_ansi(false); - - // Construct subscriber. - let subscriber = tracing_subscriber::Registry::default() - .with(stdout_layer) - .with(file_log_layer); - - // Install subscriber. - subscriber::set_global_default(subscriber)?; - - Ok(guard) -} diff --git a/backend/tauri.conf.json b/backend/tauri.conf.json index ecbbb60..c85c2de 100644 --- a/backend/tauri.conf.json +++ b/backend/tauri.conf.json @@ -1,46 +1,31 @@ { - "build": { - "beforeDevCommand": "trunk serve", - "beforeBuildCommand": "trunk build", - "devPath": "http://localhost:1420", - "distDir": "../dist", - "withGlobalTauri": true - }, - "package": { "productName": "chipbox", - "version": "0.0.0" - }, - "tauri": { - "allowlist": { - "all": false, - "shell": { - "all": false, - "open": true - } + "version": "0.1.0", + "identifier": "com.chipnertkj.chipbox", + "build": { + "beforeDevCommand": "trunk serve", + "devUrl": "http://localhost:1420", + "beforeBuildCommand": "trunk build", + "frontendDist": "../dist" }, - "bundle": { - "active": false, - "targets": "all", - "identifier": "com.chipnertkj.chipbox", - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ] - }, - "security": { - "csp": "default-src 'self'; script-src 'self' 'unsafe-eval'; style-src 'unsafe-inline'; style-src-elem 'self' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com;" + "app": { + "withGlobalTauri": true, + "windows": [ + { + "decorations": false, + "fullscreen": false, + "width": 800, + "height": 600, + "resizable": true, + "title": "chipbox" + } + ], + "security": { + "csp": null + } }, - "windows": [ - { - "fullscreen": false, - "resizable": true, - "title": "chipbox", - "width": 800, - "height": 600 - } - ] - } + "bundle": { + "active": true, + "targets": "all" + } } diff --git a/bacon.toml b/bacon.toml new file mode 100644 index 0000000..2bc94cf --- /dev/null +++ b/bacon.toml @@ -0,0 +1,13 @@ +default_job = "clippy" + +# Run clippy on the default target +[jobs.clippy] +command = [ + "cargo", + "clippy", + "--color", + "always", + "--target-dir", + "tmp/bacon-check", +] +need_stdout = false diff --git a/common/Cargo.toml b/common/Cargo.toml index bb10c52..55fcba3 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -4,10 +4,15 @@ version = "0.1.0" edition = "2021" [dependencies] -chrono = { workspace = true } -serde = { workspace = true } -once_cell = { workspace = true } -cssparser = { workspace = true } -derive_more = { workspace = true } -cssparser-color = { workspace = true } -cowstr = { workspace = true } +chrono.workspace = true +derive_more.workspace = true +serde.workspace = true +serde_json.workspace = true +thiserror.workspace = true +delegate.workspace = true +tracing.workspace = true +csscolorparser.workspace = true +itertools.workspace = true +slotmap.workspace = true +url.workspace = true +email_address.workspace = true diff --git a/common/src/app.rs b/common/src/app.rs deleted file mode 100644 index b3580e2..0000000 --- a/common/src/app.rs +++ /dev/null @@ -1,45 +0,0 @@ -pub mod msg; - -use cowstr::CowStr; -use serde::{Deserialize, Serialize}; - -/// The reason why the user config is not ready. -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] -pub enum AwaitConfigReason { - #[default] - /// This is the first time the application has been started. - /// - /// The user has not yet configured the application. - NoConfig, - /// An error occurred while reading the user config. - Error(CowStr), -} - -impl std::error::Error for AwaitConfigReason {} - -impl std::fmt::Display for AwaitConfigReason { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - AwaitConfigReason::NoConfig => { - write!(f, "No user configuration found") - } - AwaitConfigReason::Error(err) => { - write!(f, "Error reading user configuration: {}", err) - } - } - } -} - -/// Minimal description of the current state of the backend. -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] -pub enum BackendAppState { - /// Backend is currently reading user config. - #[default] - ReadingSettings, - /// User config has been read, but no valid configuration was found. - AwaitConfig { reason: AwaitConfigReason }, - /// User config was read and is valid. - Idle, - /// Backend is ready to edit a project. - Editor, -} diff --git a/common/src/app/msg.rs b/common/src/app/msg.rs deleted file mode 100644 index 35f3b46..0000000 --- a/common/src/app/msg.rs +++ /dev/null @@ -1,48 +0,0 @@ -pub mod cmd; -pub mod request; - -use self::cmd::BackendCmd; -use self::request::{BackendResponse, FrontendRequest}; -use super::BackendAppState; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -/// Messages sent by the backend app thread. -pub enum BackendMsg { - /// Send a command from the backend. - Cmd(BackendCmd), - /// Response to a frontend request. - Response(BackendResponse), -} - -impl BackendMsg { - /// JS Event name used by the frontend. - pub const fn event_name() -> &'static str { - "chipbox-app-message" - } -} - -impl From for BackendMsg { - fn from(cmd: BackendCmd) -> Self { - Self::Cmd(cmd) - } -} - -impl From for BackendMsg { - fn from(response: BackendResponse) -> Self { - Self::Response(response) - } -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -/// Messages sent by the frontend app thread. -pub enum FrontendMsg { - /// Query information from the backend. - Request(FrontendRequest), -} - -impl From for FrontendMsg { - fn from(request: FrontendRequest) -> Self { - Self::Request(request) - } -} diff --git a/common/src/app/msg/cmd.rs b/common/src/app/msg/cmd.rs deleted file mode 100644 index d4e44f2..0000000 --- a/common/src/app/msg/cmd.rs +++ /dev/null @@ -1,12 +0,0 @@ -use crate::app::AwaitConfigReason; -use crate::Settings; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -pub enum BackendCmd { - /// Inform the frontend that the backend app - /// is currently reading user config. - ReadingSettings, - /// Update the config of the frontend app. - UpdateSettings(Result), -} diff --git a/common/src/app/msg/request.rs b/common/src/app/msg/request.rs deleted file mode 100644 index 353860a..0000000 --- a/common/src/app/msg/request.rs +++ /dev/null @@ -1,26 +0,0 @@ -use super::BackendAppState; -use crate::Settings; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -/// Messages sent by the backend app thread -/// in response to frontend queries. -pub enum BackendResponse { - /// Respond with current `BackendAppState`. - BackendAppState(BackendAppState), - /// Respond with current `Settings`. - Settings(Option), - /// Respond with default `Settings`. - UseDefaultSettings(Settings), -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)] -/// Messages sent by the frontend app thread requesting information from the backend. -pub enum FrontendRequest { - /// Query current `BackendAppState`. - AppState, - /// Query current `Settings`. - Settings, - /// Use default settings and query a copy from the backend. - UseDefaultSettings, -} diff --git a/common/src/css.rs b/common/src/css.rs new file mode 100644 index 0000000..b5c6729 --- /dev/null +++ b/common/src/css.rs @@ -0,0 +1,5 @@ +//! Functionality for handling CSS data. +//! +//! Used primarily for theming. + +pub mod color; diff --git a/common/src/css/color.rs b/common/src/css/color.rs new file mode 100644 index 0000000..f8a0df7 --- /dev/null +++ b/common/src/css/color.rs @@ -0,0 +1,63 @@ +//! CSS color value validation. + +pub use csscolorparser::{Color, ParseColorError}; +use serde::{Deserialize, Serialize}; + +/// A valid CSS color value, as defined in W3C's +/// [CSS Color Module Level 4](https://www.w3.org/TR/css-color-4/). +/// +/// This struct is used to ensure that a given string is a valid +/// CSS color value. It uses the [`csscolorparser`] crate to parse +/// the color string. The color value is stored as the string +/// that was fed during construction. +/// +/// This requires the value to be parsed both on construction of a +/// [`CssColor`] instance, as well as later when the value is +/// converted to a [`Color`] by the consumer of this API, which may +/// not be desirable. In that case, consider using a raw string +/// instead, and parse it manually using the [`csscolorparser`] crate. +/// +/// See the [`csscolorparser`] crate for more information on CSS +/// color parsing. +/// # Examples +/// A [`CssColor`] can be created from any string that is a valid +/// CSS color value. Instances can be converted into a [`Color`] +/// using the [`into_color`](CssColor::into_color) method. +/// ``` +/// # use chipbox_common::css::color::CssColor; +/// let crimson_named = CssColor::new("crimson").unwrap(); +/// let crimson_hex = CssColor::new("#dc143c").unwrap(); +/// assert_eq!(crimson_named.into_color(), crimson_hex.into_color()); +/// ``` +#[derive(PartialEq, Serialize, Deserialize, Debug, Clone, derive_more::AsRef)] +pub struct CssColor(String); + +impl CssColor { + /// Creates a new [`CssColor`] instance from a given string. + /// + /// This function asserts that the provided string is a valid + /// CSS color value. If the string is not a valid CSS color + /// value, a [`ParseColorError`] will be returned. + /// + /// # Returns + /// A `Result` containing the [`CssColor`] instance if the + /// string is a valid CSS color value, or a `ParseColorError` + /// if it isn't. + pub fn new(color: S) -> Result + where + S: Into + AsRef, + { + csscolorparser::parse(color.as_ref()).map(|_| Self(color.into())) + } + + /// Converts the [`CssColor`] into a [`Color`] instance. + /// + /// This function will panic if the string is not a valid + /// CSS color value. This in general should never happen. + /// The string is validated in the [`new`](CssColor::new) + /// function, and the default constructor is not usable, + /// due to the inner string field being private. + pub fn into_color(self) -> Color { + csscolorparser::parse(self.as_ref()).expect("valid css color") + } +} diff --git a/common/src/error.rs b/common/src/error.rs new file mode 100644 index 0000000..f7f7360 --- /dev/null +++ b/common/src/error.rs @@ -0,0 +1,3 @@ +//! Common error types used throughout the project. + +pub mod fs; diff --git a/common/src/error/fs.rs b/common/src/error/fs.rs new file mode 100644 index 0000000..169b3da --- /dev/null +++ b/common/src/error/fs.rs @@ -0,0 +1,45 @@ +//! Defines an error type for filesystem operations. + +use std::{io, path::PathBuf}; + +/// Error type for filesystem operations. +/// +/// Holds information about an error related to a particular file or directory. +#[derive(thiserror::Error, Debug)] +pub enum FsError { + /// Encountered an IO error. + #[error("{path:?}: io error: {io}")] + IO { + /// The error that occurred. + io: io::Error, + /// The file or directory that was being accessed when the error occurred. + path: PathBuf, + }, + /// Attempted to access the parent directory of a path that does not have one. + #[error("{path:?}: no parent directory")] + NoParentDirectory { + /// The file or directory that was being accessed when the error occurred. + path: PathBuf, + }, +} + +/// Convenience [`Result`] alias for [`FsError`]. +pub type FsResult = Result; + +/// Extension trait for [`std::io::Result`]. +/// +/// Used to convert IO errors into the [`FsError`] type. +pub trait FromIo { + /// Maps an IO error into the [`FsError`] type and includes + /// the path that was being accessed when the error occurred. + fn map_fs_err(self, path: impl Into) -> FsResult; +} + +impl FromIo for io::Result { + fn map_fs_err(self, path: impl Into) -> FsResult { + self.map_err(|e| FsError::IO { + io: e, + path: path.into(), + }) + } +} diff --git a/common/src/lib.rs b/common/src/lib.rs index b304f9a..ef17ae2 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,6 +1,14 @@ -pub mod app; +//! Common code shared between the frontend and backend. + +#![feature(never_type)] +#![warn( + missing_docs, + unreachable_pub, + missing_debug_implementations, + rust_2018_idioms +)] + +pub mod css; +pub mod error; pub mod project; pub mod settings; - -pub use project::Project; -pub use settings::{audio_engine, Settings, Theme}; diff --git a/common/src/project.rs b/common/src/project.rs index 867bbce..71e343d 100644 --- a/common/src/project.rs +++ b/common/src/project.rs @@ -1,25 +1,54 @@ -pub use channels::Channels; -pub use meta::ProjectMeta; +//! Project format used internally by `chipbox`. +//! +//! The format is designed to be backwards compatible, allowing the +//! introduction of breaking changes to it's internal structure without +//! affecting the users ability to deserialize and operate on +//! projects created with older versions of the software. +//! +//! # `latest` module re-export +//! To create an instance using the latest version of the project +//! format, use the [`latest`] module +//! re-export. +//! +//! This re-export will keep changing its underlying module +//! in the future as new versions of the project format are added. +//! This **will** introduce breaking changes to consumers of the +//! [`latest`] module API, and this is intentional. +//! Consumers must update their code to use the newer API when +//! those changes are made. +//! +//! New versions of the [`latest`] module will have their changes +//! since the last version documented in the module level documentation. +//! +//! If you wish to use a static version of the project format +//! instead, see the [section below](#working-with-older-versions). +//! This may be desirable if breaking changes in the API are not +//! something that is tolerable by your use case. +//! +//! # Working with older versions +//! To convert an older version of the project format to the +//! latest version, use the [`any`] module and its +//! [`AnyProject`](crate::project::any::AnyProject) type. +//! See the module level documentation for [`any`] +//! for more information on exact usage. +//! +//! The `project` module also exports separate modules for +//! each concrete version of the project format, like the +//! [`v1`] module, in case you need to work with older +//! versions of the project format without converting them +//! to the latest version. -mod channels; -mod meta; +// Changing this re-export to another module (like `v2`, `v3` etc) +// will change the behavior of [`any`] and most definitely introduce +// breaking changes. +// +// This is intentional - the latest version of the project +// is effectively defined by this re-export, and consumers +// will need to update their code on latest version changes. +// +// See the module level documentation for more information. +pub use v1 as latest; -use serde::{Deserialize, Serialize}; - -/// Serializable project data. -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct Project { - /// Project metadata. - pub meta: ProjectMeta, - /// Audio channels. - pub channels: Channels, -} - -impl Project { - pub fn new(name: String) -> Self { - Self { - meta: ProjectMeta::new(name), - channels: Channels::default(), - } - } -} +pub mod any; +pub mod meta; +pub mod v1; diff --git a/common/src/project/any.rs b/common/src/project/any.rs new file mode 100644 index 0000000..12c7623 --- /dev/null +++ b/common/src/project/any.rs @@ -0,0 +1,192 @@ +//! Convert older versions of the project format to the latest +//! supported version. +//! +//! The [`AnyProject`] type allows you to convert from older versions +//! of the project format. It implements [`From`] for every +//! version, and has a method [`into_latest`](AnyProject::into_latest) +//! which recursively converts a project to use the latest available +//! version, as defined by the [`latest`] module re-export. +//! +//! See the [`project`](crate::project) module documentation +//! for more information on working with the latest version +//! of the format. + +use super::{ + latest, + meta::{ProjectMeta, ProjectVersion}, + v1, +}; + +/// A wrapper enum around [`latest::Project`] and its older +/// versions. Can be used to contain a project which +/// uses any version of the format. +/// +/// This type has a marker variant, [`Latest`](AnyProject::Latest). +/// It is equivalent to one of the other, concrete variants, +/// except it also marks the inner value as using the latest +/// version of the format. +/// +/// The latest version of [`Project`](latest::Project) is +/// decided by the [`crate::project::latest`] module re-export. +/// See [`project`](crate::project) module documentation for more information. +/// +/// See [`Self::from_json_str`] and [`Self::into_latest`] for information on how +/// to load and convert projects from older versions of the format using this type. +#[derive(derive_more::From, Debug, Clone)] +pub enum AnyProject { + /// This is a marker variant for a project which uses + /// the latest version of the format. + /// + /// It is equivalent to one of the other, concrete + /// variants, except it also marks the inner value + /// as using the latest version of the format. + /// + /// The latest version of [`Project`](latest::Project) is + /// decided by the [`latest`] module re-export. + /// You can check which version is the latest using + /// [`latest::Project::project_meta`]. + #[from(skip)] // Handled by `Self::into_latest` instead! + Latest(latest::Project), + /// First version of the project format. + /// See the [`v1`] module for more information. + V1(v1::Project), +} + +impl AnyProject { + /// Get the version of the underlying project. + /// + /// This function will return the same value for two of the + /// variants, as the [`Latest`](AnyProject::Latest) variant + /// is a marker wrapper around one of the concrete variants. + /// + /// See [`AnyProject`] for more information. + pub fn version(&self) -> ProjectVersion { + match self { + Self::Latest(..) => ProjectVersion::latest(), + Self::V1(..) => v1::Project::project_meta().version(), + } + } + + /// Recursively convert [`self`] into the latest version of + /// the format, [`latest::Project`]. + /// + /// This function automatically applies the necessary migration + /// tasks at every step in the conversion. + pub fn into_latest(self) -> latest::Project { + match self { + // `self` is latest, return! + Self::Latest(project) => project, + // Convert self into the next version and recursively + // attempt to return it as `latest::Project`. + _ => self.upgrade().into_latest(), + } + } + + /// Convert [`self`](AnyProject) into the next version of the format. + /// + /// If the format used is already the latest version, this + /// function will instead wrap the underlying value in [`AnyProject::Latest`]. + /// + /// # Panics + /// This function will panic if [`self`](AnyProject) is already + /// [`Self::Latest`](AnyProject::Latest). + /// + /// This is to discourage the pointless operation. + /// Additionally, this could cause an infinite loop if used improperly. + fn upgrade(self) -> AnyProject { + match self { + // V1 is currently the latest. + Self::V1(v1) => Self::Latest(v1), + // Self::Latest is already the latest recognized version. + Self::Latest(..) => { + panic!( + "attempted to upgrade AnyProject::Latest - this could cause an infinite loop" + ) + } + } + } + + /// Deserialize a project of any supported format version from its + /// JSON representation. + /// + /// This function will never return the value as the + /// [`Latest`](AnyProject::Latest) variant, even if + /// the serialized project is using the latest version of + /// the format. To retrieve the inner project as the + /// latest version, use [`into_latest`](Self::into_latest) + /// on the returned value. + /// + /// See [`AnyProject`] for more information. + /// + /// # Version detection mechanism + /// Version detection is done by attempting to deserialize + /// the project as a [`ProjectMeta`] object, and then + /// checking for the output of [`ProjectMeta::version`]. + /// This means that any project compliant with this API + /// should additionally deserialize into a + /// [flattened](https://serde.rs/attr-flatten.html) + /// version of [`ProjectMeta`]. + /// + /// This can be done without wasting precious memory, while also being able to use + /// the [`Serialize`] derive macro like in the following example: + /// ``` + /// # use serde::{Serialize, Deserialize, Serializer}; + /// # use chipbox_common::project::meta::ProjectMeta; + /// // Prevent clippy warning from `()` field. + /// // We are intentionally using a zero-sized type! + /// #[allow(clippy::manual_non_exhaustive)] + /// #[derive(Serialize, Deserialize)] + /// pub struct Project { + /// #[serde(flatten)] + /// #[serde(serialize_with = "emit_project_meta")] + /// project_meta: (), + /// // other fields... + /// } + /// + /// fn emit_project_meta(_: &(), s: S) -> Result { + /// let meta: ProjectMeta = unimplemented!("define meta for this version"); + /// meta.serialize(s) + /// } + /// ``` + /// This lets you deserialize it as a [`ProjectMeta`] + /// object, without it being actually stored in the struct. + /// Thanks to this, [`from_json_str`](Self::from_json_str) is now able to + /// read the metadata and determine which version of the + /// project format to use. + pub fn from_json_str(s: &str) -> serde_json::Result> { + let meta: ProjectMeta = serde_json::from_str(s)?; + let any_opt = match meta.version() { + ProjectVersion::Unrecognized => None, + ProjectVersion::V1 => Some(Self::V1(serde_json::from_str(s)?)), + }; + Ok(any_opt) + } +} + +#[cfg(test)] +mod test { + use super::*; + use v1::song::meta::SongMeta; + + /// Ensure [`AnyProject::from_json_str`] works as expected. + /// + /// Attempts to deserialize a [`v1::Project`] to [`AnyProject`]. + #[test] + fn deserialize_v1_to_any() { + let project = v1::Project::new(SongMeta::new_now("abc", None::<&str>, [])); + let s = serde_json::to_string(&project).expect("serialize"); + AnyProject::from_json_str(&s).expect("deserialize"); + } + + /// Ensure a project of the latest version can be deserialized back + /// to the latest version of the format using [`AnyProject::into_latest`]. + #[test] + fn deserialize_latest_to_latest() { + let project = latest::Project::new(SongMeta::new_now("abc", None::<&str>, [])); + let s = serde_json::to_string(&project).expect("serialize"); + AnyProject::from_json_str(&s) + .expect("deserialize") + .expect("recognize latest version") + .into_latest(); + } +} diff --git a/common/src/project/channels.rs b/common/src/project/channels.rs deleted file mode 100644 index 6795716..0000000 --- a/common/src/project/channels.rs +++ /dev/null @@ -1,253 +0,0 @@ -pub mod synth; - -pub use self::synth::SynthChannel; - -#[derive(Debug, PartialEq, Eq)] -pub struct OrderFromChannelIdxError { - idx: ChannelIdx, -} - -impl std::error::Error for OrderFromChannelIdxError {} - -impl std::fmt::Display for OrderFromChannelIdxError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "channel index not found in order vector: {:?}", self.idx) - } -} - -#[derive( - Debug, - Clone, - Copy, - serde::Serialize, - serde::Deserialize, - PartialEq, - Eq, - Hash, -)] -/// Channel identifier. -/// Corresponds to the item index in the internal vector where the channel is stored. -/// -/// Does not represent the order in which channels should be displayed. -pub enum ChannelIdx { - Synth(usize), - // Add automation, audio, event channels... -} - -#[derive( - Debug, Clone, Default, serde::Serialize, serde::Deserialize, PartialEq, -)] -/// Audio channels of a project. -/// -/// The order in which channels are displayed is stored in the `order` vector. -pub struct Channels { - synth: Vec, - /// Order in which channels are displayed. - /// Implementation ensures that no duplicate channel ids are used. - order: Vec, -} - -impl Channels { - /// Get a slice of all the synth channels. - pub fn synth(&self) -> &[SynthChannel] { - &self.synth - } - - /// Get a mutable slice of all the synth channels. - pub fn synth_mut(&mut self) -> &mut [SynthChannel] { - &mut self.synth - } - - /// Get a slice showing the order in which channels should be displayed. - pub fn order(&self) -> &[ChannelIdx] { - &self.order - } - - /// Find the *order vector index* of the given `ChannelIdx`. - /// - /// Returns an error if the given item is not in the order vector. - fn find_order_idx_pos( - &self, - idx: ChannelIdx, - ) -> Result { - self.order - .iter() - .position(|i| *i == idx) - .ok_or(OrderFromChannelIdxError { idx }) - } - - /// Swap the order of channels `A` and `B`. - pub fn swap_idx_order( - &mut self, - idx_a: ChannelIdx, - idx_b: ChannelIdx, - ) -> Result<(), OrderFromChannelIdxError> { - if idx_a == idx_b { - Ok(()) - } else { - let idx_a = self.find_order_idx_pos(idx_a)?; - let idx_b = self.find_order_idx_pos(idx_b)?; - self.order.swap(idx_a, idx_b); - Ok(()) - } - } - - /// Move order of channel `A` so that it appears before channel `B`. - /// - /// Returns an error if the channels are the same. - pub fn move_idx_before( - &mut self, - idx_a: ChannelIdx, - idx_b: ChannelIdx, - ) -> Result<(), OrderFromChannelIdxError> { - if idx_a == idx_b { - Ok(()) - } else { - self.order - .remove(self.find_order_idx_pos(idx_a)?); - self.order - .insert(self.find_order_idx_pos(idx_b)?, idx_a); - Ok(()) - } - } - - /// Move order of channel `A` so that it appears after channel `B`. - /// - /// Returns an error if the channels are the same. - pub fn move_idx_after( - &mut self, - idx_a: ChannelIdx, - idx_b: ChannelIdx, - ) -> Result<(), OrderFromChannelIdxError> { - if idx_a == idx_b { - Ok(()) - } else { - self.order - .remove(self.find_order_idx_pos(idx_a)?); - self.order - .insert(self.find_order_idx_pos(idx_b)? + 1, idx_a); - Ok(()) - } - } - - /// Remove the given channel. - pub fn remove_channel( - &mut self, - idx: ChannelIdx, - ) -> Result<(), OrderFromChannelIdxError> { - // Remove from order vec. - self.order - .remove(self.find_order_idx_pos(idx)?); - // Remove from internal channels vec. - match idx { - ChannelIdx::Synth(idx) => self.synth.remove(idx), - }; - Ok(()) - } -} - -#[cfg(test)] -/// Assert the behavior of `Channels` impl. -mod tests { - use super::*; - - #[test] - /// Assert the behavior of `find_order_idx_pos`. - fn find_order_idx_pos() { - let channels = Channels { - order: vec![ - ChannelIdx::Synth(2), - ChannelIdx::Synth(1), - ChannelIdx::Synth(0), - ], - ..Default::default() - }; - assert_eq!(channels.find_order_idx_pos(ChannelIdx::Synth(0)), Ok(2)); - assert_eq!(channels.find_order_idx_pos(ChannelIdx::Synth(1)), Ok(1)); - assert_eq!(channels.find_order_idx_pos(ChannelIdx::Synth(2)), Ok(0)); - assert!(channels - .find_order_idx_pos(ChannelIdx::Synth(3)) - .is_err()); - } - - #[test] - /// Assert the behavior of `swap_idx_order`. - fn swap_idx_order() { - let mut channels = Channels { - order: vec![ - ChannelIdx::Synth(0), - ChannelIdx::Synth(1), - ChannelIdx::Synth(2), - ], - // Actual channels are not used in this test. - ..Default::default() - }; - // Swap channels 0 and 2. - channels - .swap_idx_order(ChannelIdx::Synth(0), ChannelIdx::Synth(2)) - .unwrap(); - // Channel 0 is now at order index 2. - assert_eq!( - channels.order, - vec![ - ChannelIdx::Synth(2), - ChannelIdx::Synth(1), - ChannelIdx::Synth(0), - ] - ); - } - - #[test] - /// Assert the behavior of `move_idx_before`. - fn move_idx_before() { - let mut channels = Channels { - order: vec![ - ChannelIdx::Synth(0), - ChannelIdx::Synth(1), - ChannelIdx::Synth(2), - ], - // Actual channels are not used in this test. - ..Default::default() - }; - // Move channel 0 before channel 2. - channels - .move_idx_before(ChannelIdx::Synth(0), ChannelIdx::Synth(2)) - .unwrap(); - // Channel 0 is now at order index 1. - assert_eq!( - channels.order, - vec![ - ChannelIdx::Synth(1), - ChannelIdx::Synth(0), - ChannelIdx::Synth(2), - ] - ); - } - - #[test] - /// Assert the behavior of `move_idx_after`. - fn move_idx_after() { - let mut channels = Channels { - order: vec![ - ChannelIdx::Synth(0), - ChannelIdx::Synth(1), - ChannelIdx::Synth(2), - ], - // Actual channels are not used in this test. - ..Default::default() - }; - // Move channel 0 after channel 2. - channels - .move_idx_after(ChannelIdx::Synth(0), ChannelIdx::Synth(2)) - .unwrap(); - // Channel 0 is now at order index 2. - assert_eq!( - channels.order, - vec![ - ChannelIdx::Synth(1), - ChannelIdx::Synth(2), - ChannelIdx::Synth(0), - ] - ); - } -} diff --git a/common/src/project/channels/synth.rs b/common/src/project/channels/synth.rs deleted file mode 100644 index d48c36b..0000000 --- a/common/src/project/channels/synth.rs +++ /dev/null @@ -1,64 +0,0 @@ -mod pattern; -mod tuning; - -use self::pattern::{ModLayerMeta, Pattern}; -use self::tuning::Tuning; - -#[derive( - Debug, - PartialEq, - PartialOrd, - Clone, - Copy, - Eq, - Ord, - serde::Serialize, - serde::Deserialize, -)] -pub struct PatternId(pub u16); - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)] -/// An audio channel that defines its own synthesizer. -/// -/// A `SynthChannel` is a set of `Patterns`. -/// More specifically, a synth channel is a sequence of IDs that refer to patterns, -/// which themselves are also stored by the channel. -/// Those ID items are called `Slot`s. -/// -/// `Pattern`s are used to provide the synthesizer with note events. -/// They are loosely unique (users may define patterns that hold identical data), -/// but different slots can refer to the same pattern. -/// -/// During rendering, the renderer travels through the slots in a linear fashion. -/// It then uses note data from the pattern that the slot at a given timestamp refers to. -/// Note data is then passed to the synthesizer node graph as note events. -/// -/// The graph outputs a computed sample per each note at a given timestamp. -/// Those samples are mixed together to produce the final audio output. -pub struct SynthChannel { - pub name: String, - pub patterns: Vec, - pub slots: Vec, - mod_layers_meta: Vec, - tuning: Tuning, -} - -impl SynthChannel { - pub fn new(name: String, tuning: Tuning) -> Self { - Self { - name, - patterns: Vec::new(), - slots: Vec::new(), - tuning, - mod_layers_meta: Vec::new(), - } - } - - pub fn mod_layers_meta(&self) -> &[ModLayerMeta] { - &self.mod_layers_meta - } - - pub fn mod_layers_meta_mut(&mut self) -> &mut [ModLayerMeta] { - &mut self.mod_layers_meta - } -} diff --git a/common/src/project/channels/synth/pattern.rs b/common/src/project/channels/synth/pattern.rs deleted file mode 100644 index 837a31a..0000000 --- a/common/src/project/channels/synth/pattern.rs +++ /dev/null @@ -1,23 +0,0 @@ -mod mod_layer; - -pub use self::mod_layer::{ModLayer, ModLayerMeta}; - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)] -pub struct Pattern { - length: u32, - notes: Vec, - mod_layers: Vec, -} - -impl Pattern { - pub fn set_pitch_count(&mut self, pitch_count: u32) { - self.notes - .retain(|x| x.pitch < pitch_count); - } -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)] -pub struct Note { - pub length: u32, - pub pitch: u32, -} diff --git a/common/src/project/channels/synth/pattern/mod_layer.rs b/common/src/project/channels/synth/pattern/mod_layer.rs deleted file mode 100644 index d2e31ec..0000000 --- a/common/src/project/channels/synth/pattern/mod_layer.rs +++ /dev/null @@ -1,19 +0,0 @@ -#[derive( - Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq, -)] -pub struct ModLayer { - points: Vec, -} - -#[derive( - Debug, Clone, serde::Serialize, serde::Deserialize, Default, PartialEq, -)] -pub struct ModPoint { - pub x: u32, - pub y: f64, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)] -pub struct ModLayerMeta { - pub name: String, -} diff --git a/common/src/project/channels/synth/tuning.rs b/common/src/project/channels/synth/tuning.rs deleted file mode 100644 index 9bb370e..0000000 --- a/common/src/project/channels/synth/tuning.rs +++ /dev/null @@ -1,13 +0,0 @@ -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)] -/// A tuning system. -/// Defines the available pitches and their corresponding frequencies. -pub struct Tuning { - pub pitches: Vec, -} - -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq)] -/// A named pitch value in the tuning system. -pub struct Pitch { - pub name: String, - pub frequency: f64, -} diff --git a/common/src/project/meta.rs b/common/src/project/meta.rs index 8a0a625..4359402 100644 --- a/common/src/project/meta.rs +++ b/common/src/project/meta.rs @@ -1,28 +1,42 @@ +//! Additional information about a [`project`](crate::project) that isn't directly modifiable +//! by the user. +//! +//! This information is primarily used in +//! [`AnyProject::from_json_str`](crate::project::any::AnyProject::from_json_str) +//! for deserializing a project file into the appropriate +//! version of the format. +//! It must remain stable between different versions of the software, +//! although future versions may add optional fields. +//! +//! See the [`project`](crate::project) module documentation +//! for more information. + use serde::{Deserialize, Serialize}; +pub use version::ProjectVersion; + +mod version; -/// Metadata about a project. -#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] +/// A stable representation of a project file's metadata. +/// Future versions may add optional fields. +/// +/// See the [`meta`](self) module documentation for more information. +#[derive(Serialize, Deserialize, PartialEq, Clone, Copy, Debug)] pub struct ProjectMeta { - /// Name of the project. - pub name: String, - /// Description of the project. - pub description: Option, - /// Name of the author of the project. - pub author: Option, - /// Date when the project was created. - pub creation_date: chrono::DateTime, - /// `None` if the project hasn't been modified since creation. - pub modification_date: Option>, + /// The version identifier used for determining + /// the version of the format to use for deserialization. + version: ProjectVersion, } impl ProjectMeta { - pub fn new(name: String) -> Self { - Self { - name, - description: None, - author: None, - creation_date: chrono::Utc::now(), - modification_date: None, + /// Get the version identifier describing a project. + pub fn version(&self) -> ProjectVersion { + self.version + } + + /// Get the metadata used for the [`v1`](crate::project::v1) project format. + pub(crate) const fn v1() -> &'static Self { + &Self { + version: ProjectVersion::V1, } } } diff --git a/common/src/project/meta/version.rs b/common/src/project/meta/version.rs new file mode 100644 index 0000000..83a7b5d --- /dev/null +++ b/common/src/project/meta/version.rs @@ -0,0 +1,48 @@ +//! This module lists all versions of the project format +//! that can be interpreted by the current version of +//! the software. + +use crate::project::latest; +use serde::{Deserialize, Serialize}; + +/// Version of the project format used by a project. +/// +/// This metadata value is used for determining the +/// version of the format that should be used for +/// the deserialization of a project file. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, derive_more::Display)] +pub enum ProjectVersion { + /// The first version of the project format. + #[display("v1")] + V1, + /// An unsupported version of the project format. + /// This is only used for signalling the issue to the user. + #[serde(untagged)] + Unrecognized, +} + +impl ProjectVersion { + /// The latest known version of the project format. + /// + /// Determined by the [`latest`] module re-export. + pub const fn latest() -> Self { + latest::Project::project_meta().version + } + + /// Check if this version is the latest version + /// of the project format that can be used. + pub fn is_latest(&self) -> bool { + self == &Self::latest() + } +} + +#[cfg(test)] +mod test { + use super::*; + + /// Ensures the latest version is actually the latest. + #[test] + fn latest_is_latest() { + assert!(ProjectVersion::latest().is_latest()); + } +} diff --git a/common/src/project/v1.rs b/common/src/project/v1.rs new file mode 100644 index 0000000..1760f0a --- /dev/null +++ b/common/src/project/v1.rs @@ -0,0 +1,60 @@ +//! Initial implementation of the project format. +//! +//! This module defines [`Project`], a structure +//! that contains information about a `chipbox` project. +//! +//! See the [`project`](crate::project) module documentation +//! for more information. + +use super::meta::ProjectMeta; +use serde::{Deserialize, Serialize, Serializer}; +use song::{meta::SongMeta, Song}; + +pub mod cmd; +pub mod song; + +/// Holds all project data, including song metadata, note, automation, node graph data, +/// as well as metadata for the assets used within the project. +/// +/// A [`Project`] is a container describing a [`Song`], the primary output of the program. +/// The wrapper is primarily used for versioning, but may contain things like workspace-specific configuration. +#[allow(clippy::manual_non_exhaustive)] +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Project { + /// Zero-sized field for metadata serialization. + /// See [`Self::emit_project_meta`]. + #[serde(flatten)] + #[serde(serialize_with = "Project::emit_project_meta")] + project_meta: (), + /// The song described by this project. + pub song: Song, +} + +impl Project { + /// Create an empty project with the given song metadata. + pub fn new(song_meta: SongMeta) -> Self { + Self { + project_meta: (), + song: Song::new(song_meta), + } + } + + /// Get the metadata for this project format version. + /// + /// See [`ProjectMeta`] for more information. + pub const fn project_meta() -> &'static ProjectMeta { + ProjectMeta::v1() + } + + /// Add a field to the serializer containing project + /// format metadata. + /// This is later used to choose which version of the + /// format to deserialize a project with. + /// + /// See [`ProjectMeta`] and + /// [`AnyProject::from_json_str`](crate::project::any::AnyProject::from_json_str) + /// for more information. + fn emit_project_meta(_: &(), s: S) -> Result { + Self::project_meta().serialize(s) + } +} diff --git a/common/src/project/v1/cmd.rs b/common/src/project/v1/cmd.rs new file mode 100644 index 0000000..42d5b10 --- /dev/null +++ b/common/src/project/v1/cmd.rs @@ -0,0 +1,32 @@ +//! An interface for performing operations on a [`Project`]. + +use super::Project; + +/// Defines operations that can be performed on a project +/// in order to modify its contents. +#[derive(Debug)] +pub enum Cmd { + /// Add a new instrument channel to the song. + AddInstrumentChannel, +} + +/// A manager for a [`Project`] that provides a limited set of methods for +/// modifying the project's contents. +#[derive(Debug)] +pub struct ProjectManager { + project: Project, +} + +impl ProjectManager { + /// Create a new [`ProjectManager`] for a [`Project`]. + pub fn new(project: Project) -> Self { + Self { project } + } + + /// Execute a command on the project. + pub fn execute(&mut self, cmd: Cmd) { + match cmd { + Cmd::AddInstrumentChannel => todo!(), + } + } +} diff --git a/common/src/project/v1/song.rs b/common/src/project/v1/song.rs new file mode 100644 index 0000000..dcd215d --- /dev/null +++ b/common/src/project/v1/song.rs @@ -0,0 +1,29 @@ +//! Primary form of output for the program. + +use instrument_channel::InstrumentChannel; +use meta::SongMeta; +use serde::{Deserialize, Serialize}; + +pub mod beat; +pub mod instrument_channel; +pub mod meta; + +/// It contains the data needed to render the song, including the notes, +/// automation, and instrument channels, as well as some additional metadata. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Song { + /// Additional context that describes the song but is not part of the audio rendering process. + pub meta: SongMeta, + /// The instrument channels that make up the song. + pub instrument_channels: Vec, +} + +impl Song { + /// Create an empty song with the given metadata. + pub fn new(meta: SongMeta) -> Self { + Self { + meta, + instrument_channels: vec![], + } + } +} diff --git a/common/src/project/v1/song/beat.rs b/common/src/project/v1/song/beat.rs new file mode 100644 index 0000000..2fd402d --- /dev/null +++ b/common/src/project/v1/song/beat.rs @@ -0,0 +1,49 @@ +//! Types for time measurement based on a [beat](https://en.wikipedia.org/wiki/Beat_(music)). + +use serde::{Deserialize, Serialize}; + +/// A number representing a count of [beats](https://en.wikipedia.org/wiki/Beat_(music)). +#[derive( + PartialEq, + Eq, + PartialOrd, + Ord, + Serialize, + Deserialize, + Debug, + Clone, + Copy, + derive_more::Not, + derive_more::Add, + derive_more::From, + derive_more::AsRef, + derive_more::AsMut, +)] +pub struct BeatCount(pub u32); + +/// A floating-point distance in [beats](https://en.wikipedia.org/wiki/Beat_(music)) relative to an arbitrary point. +#[derive( + PartialEq, + PartialOrd, + Serialize, + Deserialize, + Debug, + Clone, + Copy, + derive_more::Add, + derive_more::From, + derive_more::AsRef, + derive_more::AsMut, +)] +pub struct BeatDistance(pub f64); + +impl BeatDistance { + delegate::delegate! { + to self.0 { + /// Clamp the beat distance to a given range. + /// Uses [`f64::clamp`]. + #[into] + pub fn clamp(self, #[into] min: impl Into, #[into] max: impl Into) -> Self; + } + } +} diff --git a/common/src/project/v1/song/instrument_channel.rs b/common/src/project/v1/song/instrument_channel.rs new file mode 100644 index 0000000..f758a40 --- /dev/null +++ b/common/src/project/v1/song/instrument_channel.rs @@ -0,0 +1,62 @@ +//! A channel on the timeline for producing sound +//! using notes, patterns and instrument node graphs. + +use crate::css::color::CssColor; +use control_parameter::ControlParameters; +use frequency_scale::FrequencyScales; +use pattern::Patterns; +use preset::Presets; +use serde::{Deserialize, Serialize}; +use timeline::Timeline; + +mod control_parameter; +mod frequency_scale; +mod instrument; +mod pattern; +mod preset; +mod timeline; + +pub use control_parameter::{ControlParameter, ControlParameterId}; +pub use frequency_scale::{FrequencyLabelId, FrequencyScale, FrequencyScaleId}; +pub use instrument::Instrument; +pub use pattern::{ControlPoint, Coordinate, Note, Pattern, PatternId}; +pub use preset::{Preset, PresetId}; + +/// An audio channel that organizes instruments and notes +/// in order to produce sound. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct InstrumentChannel { + /// A short name describing the channel. + pub name: String, + /// The color used to visually distinguish the channel in the timeline. + pub color: CssColor, + + /// The instrument used for audio rendering. + pub instrument: Instrument, + + /// The timeline of an instrument channel. + /// Defines the order in which [`Pattern`]s are played. + /// Patterns may be reused across the timeline. + pub timeline: Timeline, + + /// [`Pattern`]s for use across the timeline of the channel. + /// Holds notes and parameters used for rendering. + /// Patterns may be reused across the timeline. + pub patterns: Patterns, + + /// [`Preset`]s for the instrument used by this channel. + /// Each [`Pattern`] has a preset assigned that is used to render its notes. + /// Presets can be reused across patterns. + pub presets: Presets, + + /// [`FrequencyScale`]s exposed to the patterns in this channel. + /// Every [`Pattern`] has a frequency scale assigned that is used to + /// determine the frequency of its notes. + /// Frequency scales can be reused across patterns. + pub frequency_scales: FrequencyScales, + + /// [`ControlParameter`]s exposed to the patterns and their notes in this channel. + /// Every note in a pattern can define [`ControlPoint`]s that affect the + /// value of the parameter over the duration of the note. + pub control_parameters: ControlParameters, +} diff --git a/common/src/project/v1/song/instrument_channel/control_parameter.rs b/common/src/project/v1/song/instrument_channel/control_parameter.rs new file mode 100644 index 0000000..26f8cff --- /dev/null +++ b/common/src/project/v1/song/instrument_channel/control_parameter.rs @@ -0,0 +1,23 @@ +use serde::{Deserialize, Serialize}; +use slotmap::SlotMap; + +/// A parameter that can be controlled by an instrument. +/// +/// The value of a control parameter is stored per-note by control points across +/// that note. +#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] +pub struct ControlParameter { + /// A short name describing the control parameter. + pub name: String, +} + +slotmap::new_key_type! { + /// A unique identifier for a [`ControlParameter`]. + pub struct ControlParameterId; +} + +/// A container for [`ControlParameter`] instances available in a song. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct ControlParameters { + patterns: SlotMap, +} diff --git a/common/src/project/v1/song/instrument_channel/frequency_scale.rs b/common/src/project/v1/song/instrument_channel/frequency_scale.rs new file mode 100644 index 0000000..29cff30 --- /dev/null +++ b/common/src/project/v1/song/instrument_channel/frequency_scale.rs @@ -0,0 +1,30 @@ +use crate::css::color::CssColor; +use frequency_label::FrequencyLabels; +use serde::{Deserialize, Serialize}; +use slotmap::SlotMap; + +mod frequency_label; + +pub use frequency_label::FrequencyLabelId; + +/// A scale that defines the possible frequencies in a pattern. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct FrequencyScale { + /// A short name representing the frequency scale. + pub name: String, + /// A color used to visually distinguish the frequency scale in the editor. + pub color: CssColor, + /// The available frequencies in the scale. + pub labels: FrequencyLabels, +} + +slotmap::new_key_type! { + /// A unique identifier for a [`FrequencyScale`]. + pub struct FrequencyScaleId; +} + +/// A container for [`FrequencyScale`] instances available in a song. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct FrequencyScales { + patterns: SlotMap, +} diff --git a/common/src/project/v1/song/instrument_channel/frequency_scale/frequency_label.rs b/common/src/project/v1/song/instrument_channel/frequency_scale/frequency_label.rs new file mode 100644 index 0000000..6c51250 --- /dev/null +++ b/common/src/project/v1/song/instrument_channel/frequency_scale/frequency_label.rs @@ -0,0 +1,21 @@ +use serde::{Deserialize, Serialize}; +use slotmap::SlotMap; + +/// A label that denotes a frequency playable by an instrument. +#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] +pub(super) struct FrequencyLabel { + pub name: String, + /// The frequency of the audio fed to the instrument in Hz. + pub frequency: f64, +} + +slotmap::new_key_type! { + /// A unique identifier for a [`Pattern`]. + pub struct FrequencyLabelId; +} + +/// A container for [`Pattern`] instances available in a song. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct FrequencyLabels { + patterns: SlotMap, +} diff --git a/common/src/project/v1/song/instrument_channel/instrument.rs b/common/src/project/v1/song/instrument_channel/instrument.rs new file mode 100644 index 0000000..e10324e --- /dev/null +++ b/common/src/project/v1/song/instrument_channel/instrument.rs @@ -0,0 +1,12 @@ +use crate::css::color::CssColor; +use serde::{Deserialize, Serialize}; + +/// An instrument in the song. +/// Reacts to notes and automation. +#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] +pub struct Instrument { + /// A short name representing the instrument. + pub name: String, + /// A color used to visually distinguish the instrument in the timeline. + pub color: CssColor, +} diff --git a/common/src/project/v1/song/instrument_channel/pattern.rs b/common/src/project/v1/song/instrument_channel/pattern.rs new file mode 100644 index 0000000..1aef01f --- /dev/null +++ b/common/src/project/v1/song/instrument_channel/pattern.rs @@ -0,0 +1,109 @@ +use super::{FrequencyScaleId, PresetId}; +use crate::project::latest::song::beat::BeatCount; +use serde::{Deserialize, Serialize}; +use slotmap::SlotMap; + +mod note; + +pub use note::{ControlPoint, Coordinate, Note}; + +/// A collection of notes organized into a pattern. +/// Used for audio rendering. +#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] +pub struct Pattern { + notes: Vec, + /// The number of beats in the pattern. + /// The speed at which the pattern is played is defined by the song's + /// beats-per-minute value. + width: BeatCount, + /// Identifier of the pattern's frequency scale. + /// The available scales are defined by the channel. + frequency_scale: FrequencyScaleId, + /// The identifier of the instrument preset used for playback. + /// The available presets are defined by the channel. + preset: PresetId, +} + +impl Pattern { + /// Creates an empty pattern with the given paraneters. + pub fn new(width: BeatCount, frequency_scale: FrequencyScaleId, preset: PresetId) -> Self { + Self { + notes: vec![], + width, + frequency_scale, + preset, + } + } + + /// Return a slice containing all notes in the pattern. + pub fn notes(&self) -> &[Note] { + &self.notes + } + + /// Resizes the pattern to the given width. + /// + /// Each note is validated with the new parameters using + /// [`Self::validate_note`]. + /// Invalid notes are discarded. + pub fn set_width(&mut self, width: BeatCount) { + // Set new width first. + self.width = width; + // Then validate each note. + self.notes = self + .notes + .drain(..) + // Validate notes using new parameters. + .filter_map(|note| Self::validate_note(note, width)) + .collect(); + } + + /// Add a [`Note`] to the pattern. + /// + /// The note is validated using [`Self::validate_note`]. + /// If it turns out to be invalid, the function returns [`None`]. + pub fn add_note(&mut self, note: Note) -> Option<()> { + Self::validate_note(note, self.width) + // Add note if valid. + .map(|note| self.notes.push(note)) + } + + /// Modifies a [`Note`] so that it fits within the bounds of a + /// pattern of given width. + /// - The start [`Coordinate`] of the note must be >= 0.0 + /// - The end [`Coordinate`] of the note must be <= `pattern_width` + /// + /// Returns [`None`] if a valid note cannot exist given the requirements. + fn validate_note(note: Note, pattern_width: BeatCount) -> Option { + note.clip(0.0, pattern_width.0) + } +} + +slotmap::new_key_type! { + /// A unique identifier for a [`Pattern`]. + pub struct PatternId; +} + +/// A container for [`Pattern`] instances available in a song. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Patterns { + patterns: SlotMap, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::project::v1::song::instrument_channel::FrequencyLabelId; + use note::Coordinate; + + #[test] + fn set_width() { + let label_id = FrequencyLabelId::default(); + let mut pattern = Pattern::new(3.into(), FrequencyScaleId::default(), PresetId::default()); + pattern.add_note(Note::new_flat(Coordinate::new(0.0, label_id), 1.0).unwrap()); + pattern.add_note(Note::new_flat(Coordinate::new(-1.0, label_id), 4.0).unwrap()); + pattern.add_note(Note::new_flat(Coordinate::new(2.0, label_id), 1.0).unwrap()); + assert_eq!(pattern.notes().len(), 3); + pattern.set_width(1.into()); + assert_eq!(pattern.notes().len(), 2); + } +} diff --git a/common/src/project/v1/song/instrument_channel/pattern/note.rs b/common/src/project/v1/song/instrument_channel/pattern/note.rs new file mode 100644 index 0000000..1766890 --- /dev/null +++ b/common/src/project/v1/song/instrument_channel/pattern/note.rs @@ -0,0 +1,121 @@ +use crate::project::v1::song::beat::BeatDistance; +use itertools::Itertools as _; +use serde::{Deserialize, Serialize}; + +mod control_point; +mod cooordinate; + +pub use control_point::ControlPoint; +pub use cooordinate::Coordinate; + +/// A single note in a pattern. +/// +/// A note is described by a sorted set of coordinates. +/// Each [`Coordinate`] is a position on the x axis of a pattern, as well as the +/// frequency label, based on the pattern's frequency scale. +/// +/// A note is ensured to have at least two coordinates. +/// Coordinates in the same position on the x axis are collapsed. +/// The frequency label of the left-most coordinate is used. +#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] +pub struct Note { + /// Coordinates that define a note. + coords: Vec, + /// Control points that enable per-note automation. + control_points: Vec, +} + +impl Note { + /// Creates a new [`Note`] from a single [`Coordinate`] and a width. + pub fn new_flat(start_coord: Coordinate, width: impl Into) -> Option { + let end_coord = Coordinate { + x: start_coord.x + width.into(), + ..start_coord + }; + Self::new([start_coord, end_coord]) + } + + /// Creates a new [`Note`] from an iterable collection of [`Coordinate`] + /// items. + /// + /// The coordinates are sorted and deduplicated before attempting to + /// construct the note. + /// + /// If the processed coordinate set has less than two unique coordinates, + /// the note is considered invalid. + pub fn new(coords: impl IntoIterator) -> Option { + // Sort and dedup coords. + let coords = coords + .into_iter() + // Sort across the x axis. + .sorted_by(|a, b| a.x.partial_cmp(&b.x).unwrap()) + // Remove points that are on the same x position. + .dedup_by(|a, b| a.x == b.x) + .collect::>(); + + // Ensure there are at least two coords. + if coords.len() >= 2 { + // Note is valid. + Some(Self { + coords, + control_points: vec![], + }) + } else { + // Note had less than two unique points. + // It doesn't have a length and is considered invalid. + None + } + } + + /// Get the coordinates that define a [`Note`]. + /// + /// The returned coordinates are ensured to be sorted by their x position. + /// It is also ensured that there are at least two of them and that they are + /// not on the same x position. + pub fn coords(&self) -> &[Coordinate] { + &self.coords + } + + /// Clip the [`Note`] to the given bounds on the x axis. + /// + /// The final note is constructed from the clipped coordinates + /// using [`Note::new`]. + /// + /// # Panics + /// Panics if any of the + pub fn clip(self, min_x: impl Into, max_x: impl Into) -> Option { + let min_x = min_x.into(); + let max_x = max_x.into(); + let coords = self.coords.into_iter().map(|mut coord| { + coord.x = coord.x.clamp(min_x, max_x); + coord + }); + Self::new(coords) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::project::v1::song::instrument_channel::frequency_scale::FrequencyLabelId; + + /// Ensure we can construct a unit note without issues. + /// The note should have two coordinates, one at x 0.0 and one at x 1.0. + #[test] + fn unit_note() { + let label_id = FrequencyLabelId::default(); + let base_coord = Coordinate::new(0.0, label_id); + let expected = vec![base_coord, Coordinate::new(1.0, label_id)]; + let note = Note::new_flat(base_coord, 1.0).unwrap(); + assert_eq!(note.coords, expected); + } + + /// Ensure we cannot construct a note with less than two unique coordinates. + #[test] + fn invalid_note() { + let label_id = FrequencyLabelId::default(); + let base_coord = Coordinate::new(0.0, label_id); + let note = Note::new_flat(base_coord, 0.0); + assert!(note.is_none()); + } +} diff --git a/common/src/project/v1/song/instrument_channel/pattern/note/control_point.rs b/common/src/project/v1/song/instrument_channel/pattern/note/control_point.rs new file mode 100644 index 0000000..4c3af37 --- /dev/null +++ b/common/src/project/v1/song/instrument_channel/pattern/note/control_point.rs @@ -0,0 +1,16 @@ +use crate::project::v1::song::{beat::BeatDistance, instrument_channel::ControlParameterId}; +use serde::{Deserialize, Serialize}; + +/// A control point that enables per-note automation. +/// Defines the value that a control parameter should +/// have on a note at a particular point across its length. +#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] +pub struct ControlPoint { + /// The horizontal position of a control point of a note, relative to the + /// coordinate closest to the beginning of the pattern. + pub x: BeatDistance, + /// The identifier of the parameter being controlled. + pub parameter: ControlParameterId, + /// The value that the parameter should be set to at this positiion. + pub value: f64, +} diff --git a/common/src/project/v1/song/instrument_channel/pattern/note/cooordinate.rs b/common/src/project/v1/song/instrument_channel/pattern/note/cooordinate.rs new file mode 100644 index 0000000..7a824c0 --- /dev/null +++ b/common/src/project/v1/song/instrument_channel/pattern/note/cooordinate.rs @@ -0,0 +1,28 @@ +use crate::project::v1::song::{ + beat::BeatDistance, instrument_channel::frequency_scale::FrequencyLabelId, +}; +use serde::{Deserialize, Serialize}; + +/// A positional coordinate that defines the placement of a note +/// across a pattern. +#[derive(PartialEq, Serialize, Deserialize, Debug, Clone, Copy)] +pub struct Coordinate { + /// The horizontal position of a coordinate of a note, relative to the + /// beginning of the pattern. + pub x: BeatDistance, + /// The vertical position of a coordinate of a note, + /// relative to the lowest possible frequency label in a pattern. + /// This is based on the pattern's frequency scale. + pub freq_label_id: FrequencyLabelId, +} + +impl Coordinate { + /// Creates a new coordinate with a particular horizontal and vertical + /// position. + pub fn new(x: impl Into, freq_label_id: impl Into) -> Self { + Self { + x: x.into(), + freq_label_id: freq_label_id.into(), + } + } +} diff --git a/common/src/project/v1/song/instrument_channel/preset.rs b/common/src/project/v1/song/instrument_channel/preset.rs new file mode 100644 index 0000000..ff2a0d9 --- /dev/null +++ b/common/src/project/v1/song/instrument_channel/preset.rs @@ -0,0 +1,22 @@ +//! On-the-fly instrument parameter customization. + +use serde::{Deserialize, Serialize}; +use slotmap::SlotMap; + +/// A collection of values fed to an instrument's parameters. +/// +/// Enables customizing the sound of an instrument without changing +/// the underlying node graph. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Preset {} + +slotmap::new_key_type! { + /// A unique identifier for a [`Preset`]. + pub struct PresetId; +} + +/// A container for [`Preset`] instances available in a song. +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Presets { + presets: SlotMap, +} diff --git a/common/src/project/v1/song/instrument_channel/timeline.rs b/common/src/project/v1/song/instrument_channel/timeline.rs new file mode 100644 index 0000000..b8e4514 --- /dev/null +++ b/common/src/project/v1/song/instrument_channel/timeline.rs @@ -0,0 +1,7 @@ +use super::PatternId; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Timeline { + slots: Vec, +} diff --git a/common/src/project/v1/song/meta.rs b/common/src/project/v1/song/meta.rs new file mode 100644 index 0000000..b44d010 --- /dev/null +++ b/common/src/project/v1/song/meta.rs @@ -0,0 +1,42 @@ +//! Song metadata for this version of the project format. + +use chrono::{DateTime, Utc}; +use serde::{Deserialize, Serialize}; + +mod author; + +pub use author::{Author, Contact}; + +/// Additional information about a song, including +/// the name, description, author, etc. +#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] +pub struct SongMeta { + /// The name of the song. + pub name: String, + /// A description of the song that may contain details such as artist notes + /// or cultural and historical context. + pub description: Option, + /// The authors contributing to the song. + pub authors: Vec, + /// The date and time the song was created. + pub datetime_created: Option>, +} + +impl SongMeta { + /// Creates a new `SongMeta` with a valid creation date. + /// + /// The date will be set to the current date and time as + /// of calling this function. + pub fn new_now( + name: impl Into, + description: Option>, + authors: impl Into>, + ) -> Self { + Self { + name: name.into(), + description: description.map(|d| d.into()), + authors: authors.into(), + datetime_created: Some(Utc::now()), + } + } +} diff --git a/common/src/project/v1/song/meta/author.rs b/common/src/project/v1/song/meta/author.rs new file mode 100644 index 0000000..b9e29f7 --- /dev/null +++ b/common/src/project/v1/song/meta/author.rs @@ -0,0 +1,25 @@ +use email_address::EmailAddress; +use serde::{Deserialize, Serialize}; +use url::Url; + +/// Contact information for a person. +#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] +pub struct Contact { + /// The username of the person. + pub username: String, + /// The full name of the person. + pub full_name: Option, + /// The email address used for contacting the person. + pub email: Option, + /// The URL of a website associated with the person. + pub url: Option, +} + +/// Information about an author and their role on a project. +#[derive(PartialEq, Serialize, Deserialize, Debug, Clone)] +pub struct Author { + /// Contact information for the author. + pub contact: Contact, + /// The role of the author in creating the project. + pub role: Option, +} diff --git a/common/src/settings.rs b/common/src/settings.rs index 241c139..059118c 100644 --- a/common/src/settings.rs +++ b/common/src/settings.rs @@ -1,15 +1,14 @@ -pub use theme::{Theme, ThemeSelector, UserThemes}; -pub mod audio_engine; +//! User configuration. -mod theme; use serde::{Deserialize, Serialize}; -use std::path::PathBuf; -/// User configuration for the entire application. -#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Default)] +/// User configuration. +#[derive(Serialize, Deserialize, Debug, Clone)] pub struct Settings { - pub user_themes: UserThemes, - pub selected_theme: ThemeSelector, - pub recent_projects: Vec, - pub audio_engine: audio_engine::Settings, + /// Unrecognized keys from the settings file. + /// + /// Kept so that fields removed in newer versions of the software + /// can still be serialized back into the same file. + #[serde(flatten)] + unrecognized: serde_json::Value, } diff --git a/common/src/settings/audio_engine.rs b/common/src/settings/audio_engine.rs deleted file mode 100644 index f11ef08..0000000 --- a/common/src/settings/audio_engine.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::time::Duration; - -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)] -pub struct Settings { - pub host: SelectedHost, - pub output_device: SelectedDevice, - pub output_stream_config: StreamConfig, -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)] -pub enum SelectedHost { - #[default] - Default, - Named(String), -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)] -pub enum SelectedDevice { - #[default] - Default, - Named(String), -} - -#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Clone, Default)] -pub enum StreamConfig { - #[default] - Default, - Custom { - sample_format: String, - sample_rate: u32, - buffer_duration: Duration, - channels: u16, - }, -} - -impl StreamConfig { - pub fn buffer_duration(&self) -> Duration { - match self { - StreamConfig::Default => Duration::from_millis(150), - StreamConfig::Custom { - buffer_duration, .. - } => *buffer_duration, - } - } -} diff --git a/common/src/settings/theme.rs b/common/src/settings/theme.rs deleted file mode 100644 index 869ea70..0000000 --- a/common/src/settings/theme.rs +++ /dev/null @@ -1,43 +0,0 @@ -mod color; -mod dimension; -mod font_families; -mod font_weight; -mod global; -mod themes; -mod user; - -pub use color::Color; -pub use dimension::Dimension; -pub use font_families::FontFamilies; -pub use themes::{get, ThemeSelector}; -pub use user::UserThemes; - -use self::font_weight::FontWeight; - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] -pub struct Theme { - pub name: String, - pub fonts: FontTheme, - pub text: TextTheme, -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] -pub struct FontTheme { - pub family_sans: FontFamilies, - pub family_mono: FontFamilies, -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] -pub struct TextProps { - pub color: Color, - pub font_size: Dimension, - pub font_weight: FontWeight, - pub line_height: Dimension, -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] -pub struct TextTheme { - pub primary: TextProps, - pub secondary: TextProps, - pub tertiary: TextProps, -} diff --git a/common/src/settings/theme/color.rs b/common/src/settings/theme/color.rs deleted file mode 100644 index 576e18d..0000000 --- a/common/src/settings/theme/color.rs +++ /dev/null @@ -1,237 +0,0 @@ -use super::global::Global; -use cssparser::ToCss as _; - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] -pub enum Color { - Color(cssparser_color::Color), - Global(Global), -} - -impl<'a> TryFrom<&'a str> for Color { - type Error = cssparser::ParseError<'a, ()>; - fn try_from(value: &'a str) -> Result { - let mut parser_input = cssparser::ParserInput::new(value); - let mut parser = cssparser::Parser::new(&mut parser_input); - // Try to parse as `Color`. - let color_result = cssparser_color::Color::parse(&mut parser); - if let Ok(color) = color_result { - // Is a `Color`. - if parser.is_exhausted() { - // Parser exhausted. - Ok(Color::Color(color)) - } else { - // Parser not exhausted. - Err(cssparser::ParseError { - kind: cssparser::ParseErrorKind::Basic( - cssparser::BasicParseErrorKind::UnexpectedToken( - parser.next()?.to_owned(), - ), - ), - location: parser.current_source_location(), - }) - } - } else { - // Parse as `Global`. - let global = Global::try_from(value)?; - if parser.is_exhausted() { - // Parser exhausted. - Ok(Color::Global(global)) - } else { - // Parser not exhausted. - Err(cssparser::ParseError { - kind: cssparser::ParseErrorKind::Basic( - cssparser::BasicParseErrorKind::UnexpectedToken( - parser.next()?.to_owned(), - ), - ), - location: parser.current_source_location(), - }) - } - } - } -} - -impl cssparser::ToCss for Color { - fn to_css(&self, dest: &mut W) -> std::fmt::Result - where - W: std::fmt::Write, - { - match self { - Color::Color(color) => color.to_css(dest), - Color::Global(global) => global.to_css(dest), - } - } -} - -impl std::fmt::Display for Color { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.to_css(f) - } -} - -#[cfg(test)] -mod tests { - use cssparser::ToCss; - - use crate::settings::theme::global::Global; - - use super::Color; - - #[test] - fn rgba() { - let input = "rgba(0, 0, 0, 0)"; - let color = Color::try_from(input).unwrap(); - assert_eq!(input, format!("{}", color)); - assert_eq!( - color, - Color::Color(cssparser_color::Color::Rgba( - cssparser_color::RgbaLegacy { - red: 0, - green: 0, - blue: 0, - alpha: 0., - } - )) - ); - } - - #[test] - fn rgb() { - let input = "rgb(0, 0, 0)"; - let color = Color::try_from(input).unwrap(); - let expected = Color::Color(cssparser_color::Color::Rgba( - cssparser_color::RgbaLegacy { - red: 0, - green: 0, - blue: 0, - alpha: 1., - }, - )); - assert_eq!(input, format!("{}", color)); - assert_eq!(color, expected); - } - - #[test] - fn hex_8() { - let input = "#00000000"; - let color = Color::try_from(input).unwrap(); - let expected = Color::Color(cssparser_color::Color::Rgba( - cssparser_color::RgbaLegacy { - red: 0, - green: 0, - blue: 0, - alpha: 0., - }, - )); - assert_eq!(expected.to_css_string(), format!("{}", color)); - assert_eq!(color, expected); - } - - #[test] - fn hex_6() { - let input = "#000000"; - let color = Color::try_from(input).unwrap(); - let expected = Color::Color(cssparser_color::Color::Rgba( - cssparser_color::RgbaLegacy { - red: 0, - green: 0, - blue: 0, - alpha: 1., - }, - )); - assert_eq!(expected.to_css_string(), format!("{}", color)); - assert_eq!(color, expected); - } - - #[test] - fn hex_3() { - let input = "#000"; - let color = Color::try_from(input).unwrap(); - let expected = Color::Color(cssparser_color::Color::Rgba( - cssparser_color::RgbaLegacy { - red: 0, - green: 0, - blue: 0, - alpha: 1., - }, - )); - assert_eq!(expected.to_css_string(), format!("{}", color)); - assert_eq!(color, expected); - } - - #[test] - fn hex_4() { - let input = "#0000"; - let color = Color::try_from(input).unwrap(); - let expected = Color::Color(cssparser_color::Color::Rgba( - cssparser_color::RgbaLegacy { - red: 0, - green: 0, - blue: 0, - alpha: 0., - }, - )); - assert_eq!(expected.to_css_string(), format!("{}", color)); - assert_eq!(color, expected); - } - - #[test] - fn hsl() { - let input = "hsl(0 0% 0%)"; - let color = Color::try_from(input).unwrap(); - let expected = - Color::Color(cssparser_color::Color::Hsl(cssparser_color::Hsl { - hue: Some(0.), - saturation: Some(0.), - lightness: Some(0.), - alpha: Some(1.), - })); - assert_eq!(expected.to_css_string(), format!("{}", color)); - assert_eq!(color, expected); - } - - #[test] - fn hsla() { - let input = "hsla(0, 0%, 0%, 0)"; - let color = Color::try_from(input).unwrap(); - let expected = - Color::Color(cssparser_color::Color::Hsl(cssparser_color::Hsl { - hue: Some(0.), - saturation: Some(0.), - lightness: Some(0.), - alpha: Some(0.), - })); - assert_eq!(expected.to_css_string(), format!("{}", color)); - assert_eq!(color, expected); - } - - #[test] - fn global() { - let input = "inherit"; - let color = Color::try_from(input).unwrap(); - let expected = Color::Global(Global::Inherit); - assert_eq!(expected.to_css_string(), format!("{}", color)); - assert_eq!(color, expected); - } - - #[test] - fn invalid() { - let input = "invalid"; - let color = Color::try_from(input); - assert!(color.is_err()); - } - - #[test] - fn empty() { - let input = ""; - let color = Color::try_from(input); - assert!(color.is_err()); - } - - #[test] - fn unexhausted() { - let input = "#000 invalid"; - let color = Color::try_from(input); - assert!(color.is_err()); - } -} diff --git a/common/src/settings/theme/dimension.rs b/common/src/settings/theme/dimension.rs deleted file mode 100644 index 02cbd81..0000000 --- a/common/src/settings/theme/dimension.rs +++ /dev/null @@ -1,124 +0,0 @@ -use cssparser::ToCss as _; - -/// Owned version of `cssparser::Token::Dimension`. -/// -/// https://docs.rs/cssparser/latest/cssparser/enum.Token.html#variant.Dimension -/// https://drafts.csswg.org/css-syntax/#dimension-token-diagram -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] -pub struct Dimension { - has_sign: bool, - value: f32, - int_value: Option, - unit: String, -} - -impl<'a> TryFrom<&'a str> for Dimension { - type Error = cssparser::BasicParseError<'a>; - fn try_from(value: &'a str) -> Result { - let mut parser_input = cssparser::ParserInput::new(value); - let mut parser = cssparser::Parser::new(&mut parser_input); - let token = parser.next()?; - if let cssparser::Token::Dimension { - has_sign, - value, - int_value, - unit, - } = token - { - let dimension = Self { - has_sign: *has_sign, - value: *value, - int_value: *int_value, - unit: unit.to_string(), - }; - if parser.is_exhausted() { - Ok(dimension) - } else { - let err = Self::Error { - kind: cssparser::BasicParseErrorKind::UnexpectedToken( - parser.next()?.to_owned(), - ), - location: parser.current_source_location(), - }; - Err(err) - } - } else { - let err = Self::Error { - kind: cssparser::BasicParseErrorKind::UnexpectedToken( - token.to_owned(), - ), - location: parser.current_source_location(), - }; - Err(err) - } - } -} - -impl cssparser::ToCss for Dimension { - fn to_css(&self, dest: &mut W) -> std::fmt::Result - where - W: std::fmt::Write, - { - cssparser::Token::Dimension { - has_sign: self.has_sign, - value: self.value, - int_value: self.int_value, - unit: self.unit.as_str().into(), - } - .to_css(dest) - } -} - -impl std::fmt::Display for Dimension { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.to_css(f) - } -} - -#[cfg(test)] -mod tests { - use super::Dimension; - - #[test] - fn negative_rem() { - let input = "-1rem"; - let dimension = Dimension::try_from(input).unwrap(); - assert!(dimension.has_sign); - assert_eq!(dimension.value, -1.0); - assert_eq!(dimension.int_value, Some(-1)); - assert_eq!(dimension.unit, "rem"); - assert_eq!(input, format!("{}", dimension)); - } - - #[test] - fn px_frac() { - let input = "30.5px"; - let dimension = Dimension::try_from(input).unwrap(); - assert!(!dimension.has_sign); - assert_eq!(dimension.value, 30.5); - assert_eq!(dimension.int_value, None); - assert_eq!(dimension.unit, "px"); - assert_eq!(input, format!("{}", dimension)); - } - - #[test] - fn invalid() { - let input = "invalid"; - let dimension = Dimension::try_from(input); - assert!(dimension.is_err()); - } - - #[test] - fn empty() { - let input = ""; - let dimension = Dimension::try_from(input); - assert!(dimension.is_err()); - } - - #[test] - fn unexhausted() { - let input = "30px invalid"; - let dimension = Dimension::try_from(input); - assert!(dimension.is_err()); - } -} diff --git a/common/src/settings/theme/font_families.rs b/common/src/settings/theme/font_families.rs deleted file mode 100644 index 137822c..0000000 --- a/common/src/settings/theme/font_families.rs +++ /dev/null @@ -1,77 +0,0 @@ -use cssparser::ToCss as _; -mod family; - -#[allow(unused_imports)] -pub use family::{ - FontFamily, Generic, CURSIVE_STR, EMOJI_STR, FANGSONG_STR, FANTASY_STR, - MATH_STR, MONOSPACE_STR, SANS_SERIF_STR, SERIF_STR, SYSTEM_UI_STR, - UI_MONOSPACE_STR, UI_ROUNDED_STR, UI_SANS_SERIF_STR, UI_SERIF_STR, -}; - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] -pub struct FontFamilies(Vec); - -impl<'a> TryFrom<&'a str> for FontFamilies { - type Error = cssparser::ParseError<'a, ()>; - - fn try_from(value: &'a str) -> Result { - let families = value - // Split by comma and trim. - .split(',') - .map(|s| s.trim()) - // Remove empty items. - .filter(|s| !s.is_empty()) - // Convert to `FontFamily`. - .map(|s| s.try_into()) - // Collect into `Vec`. - .collect::>()?; - Ok(FontFamilies(families)) - } -} - -impl cssparser::ToCss for FontFamilies { - fn to_css(&self, dest: &mut W) -> std::fmt::Result - where - W: std::fmt::Write, - { - let mut iter = self.0.iter(); - if let Some(first) = iter.next() { - dest.write_str(first.as_ref())?; - for item in iter { - dest.write_str(", ")?; - dest.write_str(item.as_ref())?; - } - } - Ok(()) - } -} - -impl std::fmt::Display for FontFamilies { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.to_css(f) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn families() { - let input = format!( - "{sans_serif}, {serif}, \"Lato\"", - sans_serif = family::SANS_SERIF_STR, - serif = family::SERIF_STR - ); - let font_families: FontFamilies = input - .as_str() - .try_into() - .unwrap(); - let expected = FontFamilies(vec![ - FontFamily::Generic(family::Generic::SansSerif), - FontFamily::Generic(family::Generic::Serif), - "\"Lato\"".try_into().unwrap(), - ]); - assert_eq!(font_families, expected); - } -} diff --git a/common/src/settings/theme/font_families/family.rs b/common/src/settings/theme/font_families/family.rs deleted file mode 100644 index 1802ffd..0000000 --- a/common/src/settings/theme/font_families/family.rs +++ /dev/null @@ -1,87 +0,0 @@ -use cssparser::ToCss as _; -mod custom; -mod generic; - -pub use crate::settings::theme::global::Global; -pub use custom::Custom; -#[allow(unused_imports)] -pub use generic::{ - Generic, CURSIVE_STR, EMOJI_STR, FANGSONG_STR, FANTASY_STR, MATH_STR, - MONOSPACE_STR, SANS_SERIF_STR, SERIF_STR, SYSTEM_UI_STR, UI_MONOSPACE_STR, - UI_ROUNDED_STR, UI_SANS_SERIF_STR, UI_SERIF_STR, -}; - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] -pub enum FontFamily { - Custom(Custom), - Generic(Generic), - Global(Global), -} - -impl<'a> TryFrom<&'a str> for FontFamily { - type Error = cssparser::ParseError<'a, ()>; - - fn try_from(value: &'a str) -> Result { - Custom::try_from(value) - .map(Self::Custom) - .or_else(|_| Generic::try_from(value).map(Self::Generic)) - .or_else(|_| Global::try_from(value).map(Self::Global)) - } -} - -impl cssparser::ToCss for FontFamily { - fn to_css(&self, dest: &mut W) -> std::fmt::Result - where - W: std::fmt::Write, - { - match self { - FontFamily::Global(global) => global.to_css(dest), - FontFamily::Generic(generic) => generic.to_css(dest), - FontFamily::Custom(custom) => custom.to_css(dest), - } - } -} - -impl std::fmt::Display for FontFamily { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.to_css(f) - } -} - -impl AsRef for FontFamily { - fn as_ref(&self) -> &str { - match self { - FontFamily::Global(global) => global.as_ref(), - FontFamily::Generic(generic) => generic.as_ref(), - FontFamily::Custom(custom) => custom.as_ref(), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn family_generic() { - let input = "sans-serif"; - let font_family: FontFamily = input.try_into().unwrap(); - assert_eq!(input, font_family.as_ref()); - assert_eq!(font_family, FontFamily::Generic(Generic::SansSerif)); - } - - #[test] - fn family_custom() { - let input = "\"Lato\""; - let font_family: FontFamily = input.try_into().unwrap(); - assert_eq!(input, font_family.as_ref()); - } - - #[test] - fn family_global() { - let input = "inherit"; - let font_family: FontFamily = input.try_into().unwrap(); - assert_eq!(input, font_family.as_ref()); - assert_eq!(font_family, FontFamily::Global(Global::Inherit)); - } -} diff --git a/common/src/settings/theme/font_families/family/custom.rs b/common/src/settings/theme/font_families/family/custom.rs deleted file mode 100644 index b298237..0000000 --- a/common/src/settings/theme/font_families/family/custom.rs +++ /dev/null @@ -1,98 +0,0 @@ -use cssparser::ToCss as _; - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] -pub struct Custom(String); - -impl<'a> TryFrom<&'a str> for Custom { - type Error = cssparser::ParseError<'a, ()>; - fn try_from(value: &'a str) -> Result { - // Prepare parser. - let mut parse_input = cssparser::ParserInput::new(value); - let mut parser = cssparser::Parser::new(&mut parse_input); - // Get token. - let token = parser.next()?; - if let cssparser::Token::QuotedString(s) = token { - // Try to parse token as `QuotedString`. - let string = format!("\"{}\"", s); - if parser.is_exhausted() { - // Is a `Custom`. - Ok(Custom(string)) - } else { - // Too many tokens. - Err(cssparser::ParseError { - kind: cssparser::ParseErrorKind::Basic( - cssparser::BasicParseErrorKind::UnexpectedToken( - parser.next()?.to_owned(), - ), - ), - location: parser.current_source_location(), - }) - } - } else { - // Not a `QuotedString`. - Err(cssparser::ParseError { - kind: cssparser::ParseErrorKind::Basic( - cssparser::BasicParseErrorKind::UnexpectedToken( - token.to_owned(), - ), - ), - location: parser.current_source_location(), - }) - } - } -} - -impl cssparser::ToCss for Custom { - fn to_css(&self, dest: &mut W) -> std::fmt::Result - where - W: std::fmt::Write, - { - dest.write_str(&self.0) - } -} - -impl std::fmt::Display for Custom { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.to_css(f) - } -} - -impl AsRef for Custom { - fn as_ref(&self) -> &str { - &self.0 - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn custom() { - let input = "\"Lato\""; - let custom: Custom = input.try_into().unwrap(); - assert_eq!(input, custom.as_ref()); - assert_eq!(input, Custom("\"Lato\"".into()).as_ref()); - } - - #[test] - fn invalid() { - let input = "invalid"; - let result: Result = input.try_into(); - assert!(result.is_err()); - } - - #[test] - fn empty() { - let input = ""; - let result: Result = input.try_into(); - assert!(result.is_err()); - } - - #[test] - fn unexhausted() { - let input = "\"Lato\" invalid"; - let result: Result = input.try_into(); - assert!(result.is_err()); - } -} diff --git a/common/src/settings/theme/font_families/family/generic.rs b/common/src/settings/theme/font_families/family/generic.rs deleted file mode 100644 index e217eae..0000000 --- a/common/src/settings/theme/font_families/family/generic.rs +++ /dev/null @@ -1,254 +0,0 @@ -use cssparser::ToCss as _; - -pub const SERIF_STR: &str = "serif"; -pub const SANS_SERIF_STR: &str = "sans-serif"; -pub const MONOSPACE_STR: &str = "monospace"; -pub const CURSIVE_STR: &str = "cursive"; -pub const FANTASY_STR: &str = "fantasy"; -pub const SYSTEM_UI_STR: &str = "system-ui"; -pub const UI_SERIF_STR: &str = "ui-serif"; -pub const UI_SANS_SERIF_STR: &str = "ui-sans-serif"; -pub const UI_MONOSPACE_STR: &str = "ui-monospace"; -pub const UI_ROUNDED_STR: &str = "ui-rounded"; -pub const EMOJI_STR: &str = "emoji"; -pub const MATH_STR: &str = "math"; -pub const FANGSONG_STR: &str = "fangsong"; - -/// https://developer.mozilla.org/en-US/docs/Web/CSS/font-family#syntax -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] -pub enum Generic { - Serif, - SansSerif, - Monospace, - Cursive, - Fantasy, - SystemUi, - UiSerif, - UiSansSerif, - UiMonospace, - UiRounded, - Emoji, - Math, - Fangsong, -} - -impl<'a> TryFrom<&'a str> for Generic { - type Error = cssparser::ParseError<'a, ()>; - - fn try_from(value: &'a str) -> Result { - let mut parse_input = cssparser::ParserInput::new(value); - let mut parser = cssparser::Parser::new(&mut parse_input); - let token = parser.next()?; - if let cssparser::Token::Ident(s) = token { - let first = match s.as_ref() { - SERIF_STR => Ok(Generic::Serif), - SANS_SERIF_STR => Ok(Generic::SansSerif), - MONOSPACE_STR => Ok(Generic::Monospace), - CURSIVE_STR => Ok(Generic::Cursive), - FANTASY_STR => Ok(Generic::Fantasy), - SYSTEM_UI_STR => Ok(Generic::SystemUi), - UI_SERIF_STR => Ok(Generic::UiSerif), - UI_SANS_SERIF_STR => Ok(Generic::UiSansSerif), - UI_MONOSPACE_STR => Ok(Generic::UiMonospace), - UI_ROUNDED_STR => Ok(Generic::UiRounded), - EMOJI_STR => Ok(Generic::Emoji), - MATH_STR => Ok(Generic::Math), - FANGSONG_STR => Ok(Generic::Fangsong), - _ => Err(cssparser::ParseError { - kind: cssparser::ParseErrorKind::Basic( - cssparser::BasicParseErrorKind::UnexpectedToken( - token.to_owned(), - ), - ), - location: parser.current_source_location(), - }), - }?; - if parser.is_exhausted() { - Ok(first) - } else { - Err(cssparser::ParseError { - kind: cssparser::ParseErrorKind::Basic( - cssparser::BasicParseErrorKind::UnexpectedToken( - parser.next()?.to_owned(), - ), - ), - location: parser.current_source_location(), - }) - } - } else { - return Err(cssparser::ParseError { - kind: cssparser::ParseErrorKind::Basic( - cssparser::BasicParseErrorKind::UnexpectedToken( - token.to_owned(), - ), - ), - location: parser.current_source_location(), - }); - } - } -} - -impl cssparser::ToCss for Generic { - fn to_css(&self, dest: &mut W) -> std::fmt::Result - where - W: std::fmt::Write, - { - dest.write_str(self.as_ref()) - } -} - -impl std::fmt::Display for Generic { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.to_css(f) - } -} - -impl AsRef for Generic { - fn as_ref(&self) -> &str { - match self { - Generic::Serif => SERIF_STR, - Generic::SansSerif => SANS_SERIF_STR, - Generic::Monospace => MONOSPACE_STR, - Generic::Cursive => CURSIVE_STR, - Generic::Fantasy => FANTASY_STR, - Generic::SystemUi => SYSTEM_UI_STR, - Generic::UiSerif => UI_SERIF_STR, - Generic::UiSansSerif => UI_SANS_SERIF_STR, - Generic::UiMonospace => UI_MONOSPACE_STR, - Generic::UiRounded => UI_ROUNDED_STR, - Generic::Emoji => EMOJI_STR, - Generic::Math => MATH_STR, - Generic::Fangsong => FANGSONG_STR, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn serif() { - let input = SERIF_STR; - let font_family: Generic = input.try_into().unwrap(); - assert_eq!(input, font_family.as_ref()); - assert_eq!(font_family, Generic::Serif); - } - - #[test] - fn sans_serif() { - let input = SANS_SERIF_STR; - let font_family: Generic = input.try_into().unwrap(); - assert_eq!(input, font_family.as_ref()); - assert_eq!(font_family, Generic::SansSerif); - } - - #[test] - fn monospace() { - let input = MONOSPACE_STR; - let font_family: Generic = input.try_into().unwrap(); - assert_eq!(input, font_family.as_ref()); - assert_eq!(font_family, Generic::Monospace); - } - - #[test] - fn cursive() { - let input = CURSIVE_STR; - let font_family: Generic = input.try_into().unwrap(); - assert_eq!(input, font_family.as_ref()); - assert_eq!(font_family, Generic::Cursive); - } - - #[test] - fn fantasy() { - let input = FANTASY_STR; - let font_family: Generic = input.try_into().unwrap(); - assert_eq!(input, font_family.as_ref()); - assert_eq!(font_family, Generic::Fantasy); - } - - #[test] - fn system_ui() { - let input = SYSTEM_UI_STR; - let font_family: Generic = input.try_into().unwrap(); - assert_eq!(input, font_family.as_ref()); - assert_eq!(font_family, Generic::SystemUi); - } - - #[test] - fn ui_serif() { - let input = UI_SERIF_STR; - let font_family: Generic = input.try_into().unwrap(); - assert_eq!(input, font_family.as_ref()); - assert_eq!(font_family, Generic::UiSerif); - } - - #[test] - fn ui_sans_serif() { - let input = UI_SANS_SERIF_STR; - let font_family: Generic = input.try_into().unwrap(); - assert_eq!(input, font_family.as_ref()); - assert_eq!(font_family, Generic::UiSansSerif); - } - - #[test] - fn ui_monospace() { - let input = UI_MONOSPACE_STR; - let font_family: Generic = input.try_into().unwrap(); - assert_eq!(input, font_family.as_ref()); - assert_eq!(font_family, Generic::UiMonospace); - } - - #[test] - fn ui_rounded() { - let input = UI_ROUNDED_STR; - let font_family: Generic = input.try_into().unwrap(); - assert_eq!(input, font_family.as_ref()); - assert_eq!(font_family, Generic::UiRounded); - } - - #[test] - fn emoji() { - let input = EMOJI_STR; - let font_family: Generic = input.try_into().unwrap(); - assert_eq!(input, font_family.as_ref()); - assert_eq!(font_family, Generic::Emoji); - } - - #[test] - fn math() { - let input = MATH_STR; - let font_family: Generic = input.try_into().unwrap(); - assert_eq!(input, font_family.as_ref()); - assert_eq!(font_family, Generic::Math); - } - - #[test] - fn fangsong() { - let input = FANGSONG_STR; - let font_family: Generic = input.try_into().unwrap(); - assert_eq!(input, font_family.as_ref()); - assert_eq!(font_family, Generic::Fangsong); - } - - #[test] - fn invalid() { - let input = "invalid"; - let font_family: Result = input.try_into(); - assert!(font_family.is_err()); - } - - #[test] - fn empty() { - let input = ""; - let font_family: Result = input.try_into(); - assert!(font_family.is_err()); - } - - #[test] - fn unexhausted() { - let input = format!("{SERIF_STR} invalid"); - let font_family: Result = input.as_str().try_into(); - assert!(font_family.is_err()); - } -} diff --git a/common/src/settings/theme/font_weight.rs b/common/src/settings/theme/font_weight.rs deleted file mode 100644 index cba0e1e..0000000 --- a/common/src/settings/theme/font_weight.rs +++ /dev/null @@ -1,109 +0,0 @@ -use cssparser::ToCss as _; - -use self::absolute_weight::AbsoluteWeight; -use self::relative_weight::RelativeWeight; -use super::global::Global; - -#[allow(unused_imports)] -pub use absolute_weight::{BOLD_STR, NORMAL_STR}; -#[allow(unused_imports)] -pub use relative_weight::{BOLDER_STR, LIGHTER_STR}; - -mod absolute_weight; -mod relative_weight; - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] -pub enum FontWeight { - Absolute(AbsoluteWeight), - Relative(RelativeWeight), - - Global(Global), -} - -impl<'a> TryFrom<&'a str> for FontWeight { - type Error = cssparser::ParseError<'a, ()>; - - fn try_from(value: &'a str) -> Result { - AbsoluteWeight::try_from(value) - .map(Self::Absolute) - .or_else(|_| RelativeWeight::try_from(value).map(Self::Relative)) - .or_else(|_| Global::try_from(value).map(Self::Global)) - } -} - -impl cssparser::ToCss for FontWeight { - fn to_css(&self, dest: &mut W) -> std::fmt::Result - where - W: std::fmt::Write, - { - match self { - Self::Absolute(weight) => weight.to_css(dest), - Self::Relative(weight) => weight.to_css(dest), - Self::Global(global) => global.to_css(dest), - } - } -} - -impl std::fmt::Display for FontWeight { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.to_css(f) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn absolute() { - let input = "bold"; - let font_weight: FontWeight = input.try_into().unwrap(); - assert_eq!( - input, - font_weight - .to_string() - .as_str() - ); - assert_eq!(font_weight, FontWeight::Absolute(AbsoluteWeight::Bold)); - } - - #[test] - fn relative() { - let input = "bolder"; - let font_weight: FontWeight = input.try_into().unwrap(); - assert_eq!( - input, - font_weight - .to_string() - .as_str() - ); - assert_eq!(font_weight, FontWeight::Relative(RelativeWeight::Bolder)); - } - - #[test] - fn global() { - let input = "inherit"; - let font_weight: FontWeight = input.try_into().unwrap(); - assert_eq!( - input, - font_weight - .to_string() - .as_str() - ); - assert_eq!(font_weight, FontWeight::Global(Global::Inherit)); - } - - #[test] - fn invalid() { - let input = "invalid"; - let font_weight: Result = input.try_into(); - assert!(font_weight.is_err()); - } - - #[test] - fn unexhausted() { - let input = format!("{bolder} invalid", bolder = BOLDER_STR); - let font_weight: Result = input.as_str().try_into(); - assert!(font_weight.is_err()); - } -} diff --git a/common/src/settings/theme/font_weight/absolute_weight.rs b/common/src/settings/theme/font_weight/absolute_weight.rs deleted file mode 100644 index 45dd30d..0000000 --- a/common/src/settings/theme/font_weight/absolute_weight.rs +++ /dev/null @@ -1,147 +0,0 @@ -use cssparser::ToCss as _; - -pub const NORMAL_STR: &str = "normal"; -pub const BOLD_STR: &str = "bold"; - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] -pub enum AbsoluteWeight { - Normal, - Bold, - Numeric(f32), -} - -impl<'a> TryFrom<&'a str> for AbsoluteWeight { - type Error = cssparser::ParseError<'a, ()>; - fn try_from(value: &'a str) -> Result { - // Prepare parser. - let mut parser_input = cssparser::ParserInput::new(value); - let mut parser = cssparser::Parser::new(&mut parser_input); - // Try to parse as `Ident`. - let token = parser.next()?; - if let cssparser::Token::Ident(ident) = token { - // Is an `Ident`. - let weight = match ident.as_ref() { - NORMAL_STR => Ok(AbsoluteWeight::Normal), - BOLD_STR => Ok(AbsoluteWeight::Bold), - // Invalid `Ident`. - _ => Err(cssparser::ParseError { - kind: cssparser::ParseErrorKind::Basic( - cssparser::BasicParseErrorKind::UnexpectedToken( - token.to_owned(), - ), - ), - location: parser.current_source_location(), - }), - }?; - if parser.is_exhausted() { - // Parser exhausted. - Ok(weight) - } else { - // Parser not exhausted. - Err(cssparser::ParseError { - kind: cssparser::ParseErrorKind::Basic( - cssparser::BasicParseErrorKind::UnexpectedToken( - parser.next()?.to_owned(), - ), - ), - location: parser.current_source_location(), - }) - } - } else { - // Parse as `Number`. - if let cssparser::Token::Number { value, .. } = token { - // Is a `Number`. - // https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight#number - let weight = - Ok(AbsoluteWeight::Numeric(value.clamp(0., 1000.))); - if parser.is_exhausted() { - // Parser exhausted. - weight - } else { - // Parser not exhausted. - Err(cssparser::ParseError { - kind: cssparser::ParseErrorKind::Basic( - cssparser::BasicParseErrorKind::UnexpectedToken( - parser.next()?.to_owned(), - ), - ), - location: parser.current_source_location(), - }) - } - } else { - // Not a `Number` or valid `Ident`. - Err(cssparser::ParseError { - kind: cssparser::ParseErrorKind::Basic( - cssparser::BasicParseErrorKind::UnexpectedToken( - token.to_owned(), - ), - ), - location: parser.current_source_location(), - }) - } - } - } -} - -impl cssparser::ToCss for AbsoluteWeight { - fn to_css(&self, dest: &mut W) -> std::fmt::Result - where - W: std::fmt::Write, - { - match self { - AbsoluteWeight::Normal => dest.write_str(NORMAL_STR), - AbsoluteWeight::Bold => dest.write_str(BOLD_STR), - AbsoluteWeight::Numeric(value) => value.to_css(dest), - } - } -} - -impl std::fmt::Display for AbsoluteWeight { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.to_css(f) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn normal() { - assert_eq!(AbsoluteWeight::Normal, NORMAL_STR.try_into().unwrap()); - } - - #[test] - fn bold() { - assert_eq!(AbsoluteWeight::Bold, BOLD_STR.try_into().unwrap()); - } - - #[test] - fn numeric() { - assert_eq!(AbsoluteWeight::Numeric(100.), "100".try_into().unwrap()); - } - - #[test] - fn invalid() { - assert!(AbsoluteWeight::try_from("invalid").is_err()); - } - - #[test] - fn empty() { - assert!(AbsoluteWeight::try_from("").is_err()); - } - - #[test] - fn clamp() { - assert_eq!(AbsoluteWeight::Numeric(1000.), "1500".try_into().unwrap()); - assert_eq!(AbsoluteWeight::Numeric(0.), "-1500".try_into().unwrap()); - } - - #[test] - fn unexhausted() { - assert!(AbsoluteWeight::try_from( - format!("{BOLD_STR} invalid").as_str() - ) - .is_err()); - } -} diff --git a/common/src/settings/theme/font_weight/relative_weight.rs b/common/src/settings/theme/font_weight/relative_weight.rs deleted file mode 100644 index 94839aa..0000000 --- a/common/src/settings/theme/font_weight/relative_weight.rs +++ /dev/null @@ -1,117 +0,0 @@ -use cssparser::ToCss as _; - -pub const LIGHTER_STR: &str = "lighter"; -pub const BOLDER_STR: &str = "bolder"; - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] -/// https://developer.mozilla.org/en-US/docs/Web/CSS/font-weight -pub enum RelativeWeight { - Lighter, - Bolder, -} - -impl<'a> TryFrom<&'a str> for RelativeWeight { - type Error = cssparser::ParseError<'a, ()>; - fn try_from(value: &'a str) -> Result { - let mut parser_input = cssparser::ParserInput::new(value); - let mut parser = cssparser::Parser::new(&mut parser_input); - let token = parser.next()?; - if let cssparser::Token::Ident(ident) = token { - let weight = match ident.as_ref() { - LIGHTER_STR => Ok(RelativeWeight::Lighter), - BOLDER_STR => Ok(RelativeWeight::Bolder), - _ => Err(cssparser::ParseError { - kind: cssparser::ParseErrorKind::Basic( - cssparser::BasicParseErrorKind::UnexpectedToken( - token.to_owned(), - ), - ), - location: parser.current_source_location(), - }), - }?; - if parser.is_exhausted() { - // Parser exhausted. - Ok(weight) - } else { - // Parser not exhausted. - Err(cssparser::ParseError { - kind: cssparser::ParseErrorKind::Basic( - cssparser::BasicParseErrorKind::UnexpectedToken( - parser.next()?.to_owned(), - ), - ), - location: parser.current_source_location(), - }) - } - } else { - Err(cssparser::ParseError { - kind: cssparser::ParseErrorKind::Basic( - cssparser::BasicParseErrorKind::UnexpectedToken( - token.to_owned(), - ), - ), - location: parser.current_source_location(), - }) - } - } -} - -impl cssparser::ToCss for RelativeWeight { - fn to_css(&self, dest: &mut W) -> std::fmt::Result - where - W: std::fmt::Write, - { - dest.write_str(self.as_ref()) - } -} - -impl std::fmt::Display for RelativeWeight { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.to_css(f) - } -} - -impl AsRef for RelativeWeight { - fn as_ref(&self) -> &str { - match self { - RelativeWeight::Lighter => LIGHTER_STR, - RelativeWeight::Bolder => BOLDER_STR, - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn lighter() { - let weight = RelativeWeight::try_from(LIGHTER_STR).unwrap(); - assert_eq!(weight, RelativeWeight::Lighter); - } - - #[test] - fn bolder() { - let weight = RelativeWeight::try_from(BOLDER_STR).unwrap(); - assert_eq!(weight, RelativeWeight::Bolder); - } - - #[test] - fn invalid() { - let weight = RelativeWeight::try_from("invalid"); - assert!(weight.is_err()); - } - - #[test] - fn empty() { - let weight = RelativeWeight::try_from(""); - assert!(weight.is_err()); - } - - #[test] - fn unexhausted() { - let input = format!("{BOLDER_STR} invalid"); - let weight = RelativeWeight::try_from(input.as_str()); - assert!(weight.is_err()); - } -} diff --git a/common/src/settings/theme/global.rs b/common/src/settings/theme/global.rs deleted file mode 100644 index 3fc640e..0000000 --- a/common/src/settings/theme/global.rs +++ /dev/null @@ -1,143 +0,0 @@ -use cssparser::ToCss as _; - -const INHERIT_STR: &str = "inherit"; -const INITIAL_STR: &str = "initial"; -const REVERT_STR: &str = "revert"; -const REVERT_LAYER_STR: &str = "revert-layer"; -const UNSET_STR: &str = "unset"; - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] -pub enum Global { - Inherit, - Initial, - Revert, - RevertLayer, - Unset, -} - -impl<'a> TryFrom<&'a str> for Global { - type Error = cssparser::ParseError<'a, ()>; - - fn try_from(value: &'a str) -> Result { - let mut parse_input = cssparser::ParserInput::new(value); - let mut parser = cssparser::Parser::new(&mut parse_input); - let token = parser.next()?; - if let cssparser::Token::Ident(s) = token { - let first = match s.as_ref() { - INHERIT_STR => Ok(Self::Inherit), - INITIAL_STR => Ok(Self::Initial), - REVERT_STR => Ok(Self::Revert), - REVERT_LAYER_STR => Ok(Self::RevertLayer), - UNSET_STR => Ok(Self::Unset), - _ => Err(cssparser::ParseError { - kind: cssparser::ParseErrorKind::Basic( - cssparser::BasicParseErrorKind::UnexpectedToken( - token.to_owned(), - ), - ), - location: parser.current_source_location(), - }), - }?; - if parser.is_exhausted() { - Ok(first) - } else { - Err(cssparser::ParseError { - kind: cssparser::ParseErrorKind::Basic( - cssparser::BasicParseErrorKind::UnexpectedToken( - parser.next()?.to_owned(), - ), - ), - location: parser.current_source_location(), - }) - } - } else { - Err(cssparser::ParseError { - kind: cssparser::ParseErrorKind::Basic( - cssparser::BasicParseErrorKind::UnexpectedToken( - token.to_owned(), - ), - ), - location: parser.current_source_location(), - }) - } - } -} - -impl cssparser::ToCss for Global { - fn to_css(&self, dest: &mut W) -> std::fmt::Result - where - W: std::fmt::Write, - { - dest.write_str(self.as_ref()) - } -} - -impl std::fmt::Display for Global { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.to_css(f) - } -} - -impl AsRef for Global { - fn as_ref(&self) -> &str { - match self { - Global::Inherit => INHERIT_STR, - Global::Initial => INITIAL_STR, - Global::Revert => REVERT_STR, - Global::RevertLayer => REVERT_LAYER_STR, - Global::Unset => UNSET_STR, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn inherit() { - let input = INHERIT_STR; - let global: Global = input.try_into().unwrap(); - assert_eq!(input, global.as_ref()); - assert_eq!(global, Global::Inherit); - } - - #[test] - fn initial() { - let input = INITIAL_STR; - let global: Global = input.try_into().unwrap(); - assert_eq!(input, global.as_ref()); - assert_eq!(global, Global::Initial); - } - - #[test] - fn revert() { - let input = REVERT_STR; - let global: Global = input.try_into().unwrap(); - assert_eq!(input, global.as_ref()); - assert_eq!(global, Global::Revert); - } - - #[test] - fn revert_layer() { - let input = REVERT_LAYER_STR; - let global: Global = input.try_into().unwrap(); - assert_eq!(input, global.as_ref()); - assert_eq!(global, Global::RevertLayer); - } - - #[test] - fn unset() { - let input = UNSET_STR; - let global: Global = input.try_into().unwrap(); - assert_eq!(input, global.as_ref()); - assert_eq!(global, Global::Unset); - } - - #[test] - fn invalid() { - let input = "invalid"; - let global: Result = input.try_into(); - assert!(global.is_err()); - } -} diff --git a/common/src/settings/theme/themes.rs b/common/src/settings/theme/themes.rs deleted file mode 100644 index ebf9f7f..0000000 --- a/common/src/settings/theme/themes.rs +++ /dev/null @@ -1,31 +0,0 @@ -mod chipbox_dark; - -use super::Theme; -use once_cell::sync::Lazy; - -#[derive( - serde::Serialize, serde::Deserialize, Debug, Default, Clone, PartialEq, -)] -pub enum DefaultThemeSelector { - #[default] - ChipboxDark, -} - -pub fn get(selector: &DefaultThemeSelector) -> &'static Theme { - static CHIPBOX_DARK: Lazy = Lazy::new(chipbox_dark::theme); - match selector { - DefaultThemeSelector::ChipboxDark => &CHIPBOX_DARK, - } -} - -#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, PartialEq)] -pub enum ThemeSelector { - Default(DefaultThemeSelector), - Custom(String), -} - -impl Default for ThemeSelector { - fn default() -> Self { - ThemeSelector::Default(DefaultThemeSelector::default()) - } -} diff --git a/common/src/settings/theme/themes/chipbox_dark.rs b/common/src/settings/theme/themes/chipbox_dark.rs deleted file mode 100644 index a1c3256..0000000 --- a/common/src/settings/theme/themes/chipbox_dark.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::settings::theme::*; - -pub fn theme() -> Theme { - Theme { - name: name(), - fonts: fonts(), - text: text(), - } -} - -fn name() -> String { - "chipbox-dark".into() -} - -fn fonts() -> FontTheme { - FontTheme { - family_sans: "\"Lato\", sans-serif" - .try_into() - .unwrap(), - family_mono: "\"IBM Plex Mono\", monospace" - .try_into() - .unwrap(), - } -} - -fn text() -> TextTheme { - TextTheme { - primary: text_primary(), - secondary: text_secondary(), - tertiary: text_tertiary(), - } -} - -fn text_primary() -> TextProps { - TextProps { - color: "hsl(0, 0%, 90%)" - .try_into() - .unwrap(), - font_size: "1rem".try_into().unwrap(), - font_weight: "400".try_into().unwrap(), - line_height: "1.15rem".try_into().unwrap(), - } -} - -fn text_secondary() -> TextProps { - TextProps { - color: "hsl(0, 0%, 60%)" - .try_into() - .unwrap(), - font_size: "1rem".try_into().unwrap(), - font_weight: "400".try_into().unwrap(), - line_height: "1.15rem".try_into().unwrap(), - } -} - -fn text_tertiary() -> TextProps { - TextProps { - color: "hsl(0, 0%, 40%)" - .try_into() - .unwrap(), - font_size: "1rem".try_into().unwrap(), - font_weight: "400".try_into().unwrap(), - line_height: "1.15rem".try_into().unwrap(), - } -} diff --git a/common/src/settings/theme/user.rs b/common/src/settings/theme/user.rs deleted file mode 100644 index 778471d..0000000 --- a/common/src/settings/theme/user.rs +++ /dev/null @@ -1,49 +0,0 @@ -use super::themes::ThemeSelector; -use super::Theme; -use serde::{Deserialize, Serialize}; -use std::collections::HashMap; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Default)] -pub struct UserThemes { - inner: HashMap, -} - -impl UserThemes { - pub fn get(&self, selector: &ThemeSelector) -> Option<&Theme> { - match selector { - ThemeSelector::Default(selector) => Some(super::get(selector)), - ThemeSelector::Custom(name) => self.inner.get(name), - } - } - - pub fn get_mut(&mut self, name: &str) -> Option<&mut Theme> { - self.inner.get_mut(name) - } - - pub fn insert(&mut self, name: String, theme: Theme) { - self.inner.insert(name, theme); - } - - pub fn remove(&mut self, name: &str) { - self.inner.remove(name); - } - - pub fn iter(&self) -> std::collections::hash_map::Iter { - self.inner.iter() - } - - pub fn iter_mut( - &mut self, - ) -> std::collections::hash_map::IterMut { - self.inner.iter_mut() - } -} - -impl IntoIterator for UserThemes { - type Item = (String, Theme); - type IntoIter = std::collections::hash_map::IntoIter; - - fn into_iter(self) -> Self::IntoIter { - self.inner.into_iter() - } -} diff --git a/frontend/Cargo.lock b/frontend/Cargo.lock new file mode 100644 index 0000000..d633ef9 --- /dev/null +++ b/frontend/Cargo.lock @@ -0,0 +1,5195 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" + +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "async-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "atk" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4af014b17dd80e8af9fa689b2d4a211ddba6eb583c1622f35d0cb543f6b17e4" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "251e0b7d90e33e0ba930891a505a9a35ece37b2dd37a14f3ffc306c13b980009" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "attribute-derive" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f1ee502851995027b06f99f5ffbeffa1406b38d0b318a1ebfa469332c6cbafd" +dependencies = [ + "attribute-derive-macro", + "derive-where", + "manyhow", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "attribute-derive-macro" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3601467f634cfe36c4780ca9c75dea9a5b34529c1f2810676a337e7e0997f954" +dependencies = [ + "collection_literals", + "interpolator", + "manyhow", + "proc-macro-utils 0.8.0", + "proc-macro2", + "quote", + "quote-use", + "syn 2.0.77", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide 0.7.4", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +dependencies = [ + "serde", +] + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" +dependencies = [ + "objc2", +] + +[[package]] +name = "brotli" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f7971dbd9326d58187408ab83117d8ac1bb9c17b085fdacd1cf2f598719b6b" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a45bd2e4095a8b518033b128020dd4a55aab1c0a381ba4404a472630f4bc362" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytemuck" +version = "1.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773d90827bc3feecfb67fab12e24de0749aad83c74b9504ecde46237b5cd24e2" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.6.0", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b96ec4966b5813e2c0507c1f86115c8c5abaadc3980879c3424042a02fd1ad3" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "cargo_toml" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a969e13a7589e9e3e4207e153bae624ade2b5622fb4684a4923b23ec3d57719" +dependencies = [ + "serde", + "toml 0.8.2", +] + +[[package]] +name = "cc" +version = "1.1.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +dependencies = [ + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chipbox" +version = "0.1.0" +dependencies = [ + "serde", + "serde_json", + "tauri", + "tauri-build", + "tauri-plugin-shell", +] + +[[package]] +name = "chipbox-ui" +version = "0.1.0" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "leptos", + "serde", + "serde-wasm-bindgen", + "wasm-bindgen", + "wasm-bindgen-futures", +] + +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "serde", + "windows-targets 0.52.6", +] + +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "cocoa" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f79398230a6e2c08f5c9760610eb6924b52aa9e7950a619602baba59dcbbdbb2" +dependencies = [ + "bitflags 2.6.0", + "block", + "cocoa-foundation", + "core-foundation 0.10.0", + "core-graphics 0.24.0", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14045fb83be07b5acf1c0884b2180461635b433455fa35d1cd6f17f1450679d" +dependencies = [ + "bitflags 2.6.0", + "block", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", + "libc", + "objc", +] + +[[package]] +name = "collection_literals" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186dce98367766de751c42c4f03970fc60fc012296e706ccbb9d5df9b6c1e271" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "config" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7328b20597b53c2454f0b1919720c25c7339051c02b72b7e05409e00b14132be" +dependencies = [ + "convert_case 0.6.0", + "lazy_static", + "nom", + "pathdiff", + "serde", + "toml 0.8.2", +] + +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if", + "wasm-bindgen", +] + +[[package]] +name = "const_format" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "convert_case" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b55271e5c8c478ad3f38ad24ef34923091e0548492a266d19b3c0b4d82574c63" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c07782be35f9e1140080c6b96f0d44b739e2278479f64e02fdab4e32dfd8b081" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "core-graphics-types 0.1.3", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", + "core-graphics-types 0.2.0", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" +dependencies = [ + "bitflags 1.3.2", + "core-foundation 0.9.4", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.6.0", + "core-foundation 0.10.0", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa 0.4.8", + "matches", + "phf 0.8.0", + "proc-macro2", + "quote", + "smallvec", + "syn 1.0.109", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.77", +] + +[[package]] +name = "ctor" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" +dependencies = [ + "quote", + "syn 2.0.77", +] + +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.77", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "dashmap" +version = "5.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856" +dependencies = [ + "cfg-if", + "hashbrown 0.14.5", + "lock_api", + "once_cell", + "parking_lot_core", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", + "serde", +] + +[[package]] +name = "derive-where" +version = "1.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62d671cc41a825ebabc75757b62d3d168c577f9149b2d49ece1dad1f72119d25" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "derive_more" +version = "0.99.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +dependencies = [ + "convert_case 0.4.0", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.77", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "5.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.48.0", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dlopen2" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1297103d2bbaea85724fcee6294c2d50b1081f9ad47d0f6f6f61eda65315a6" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b99bf03862d7f545ebc28ddd33a665b50865f4dfd84031a393823879bd4c54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "dpi" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f25c0e292a7ca6d6498557ff1df68f32c99850012b6ea401cf8daf771f22ff53" +dependencies = [ + "serde", +] + +[[package]] +name = "drain_filter_polyfill" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" + +[[package]] +name = "dtoa" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbb2bf8e87535c23f7a8a321e364ce21462d0ff10cb6407820e8e96dfff6653" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "embed-resource" +version = "2.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4edcacde9351c33139a41e3c97eb2334351a81a2791bebb0b243df837128f602" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 0.8.2", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "erased-serde" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24e2389d65ab4fab27dc2a5de7b191e1f6617d1f1c8855c0dc569c94a4cbb18d" +dependencies = [ + "serde", + "typeid", +] + +[[package]] +name = "fdeflate" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f9bfee30e4dedf0ab8b422f03af778d9612b63f502710fc500a334ebe2de645" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "flate2" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" +dependencies = [ + "crc32fast", + "miniz_oxide 0.8.0", +] + +[[package]] +name = "fluent-uri" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17c704e9dbe1ddd863da1e6ff3567795087b1eb201ce80d8fa81162e1516500d" +dependencies = [ + "bitflags 1.3.2", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a5c6c585bc94aaf2c7b51dd4c2ba22680844aba4c687be581871a6f518c5742" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-executor" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" + +[[package]] +name = "futures-macro" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f5ba081bdef3b75ebcdbfc953699ed2d7417d6bd853347a42a37d76406a33646" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31ff856cb3386dae1703a920f803abafcc580e9b5f711ca62ed1620c25b51ff2" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a90fbf5c033c65d93792192a49a8efb5bb1e640c419682a58bb96f5ae77f3d4a" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2ea8a4909d530f79921290389cbd7c34cb9d623bfe970eaae65ca5f9cd9cce" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fee8f00f4ee46cad2939b8990f5c70c94ff882c3028f3cc5abf950fa4ab53043" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generator" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "windows 0.48.0", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.6.0", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "gloo-net" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06f627b1a58ca3d42b45d6104bf1e1a03799df472df00988b6ba21accc10580" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "http", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-utils" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5555354113b18c547c1d3a98fbf7fb32a9ff4f6fa112ce823a21641a0ba3aa" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93c4f5e0e20b60e10631a5f06da7fe3dda744b05ad0ea71fee2f47adf865890c" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "771437bf1de2c1c0b496c11505bdf748e26066bbe942dfc8f614c9460f6d7722" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6063efb63db582968fb7df72e1ae68aa6360dcfb0a75143f34fc7d616bad75e" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "html-escape" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1ad449764d627e22bfd7cd5e8868264fc9236e07c752972b4080cd351cb476" +dependencies = [ + "utf8-width", +] + +[[package]] +name = "html5ever" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.11", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" + +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "itoa 1.0.11", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cde7055719c54e36e95e8719f95883f22072a48ede39db7fc17a4e1d5281e9b9" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core 0.52.0", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3804960be0bb5e4edb1e1ad67afd321a9ecfd875c3e65c099468fd2717d7cae" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", + "serde", +] + +[[package]] +name = "infer" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb33622da908807a06f9513c19b3c1ad50fab3e4137d82a78107d502075aa199" +dependencies = [ + "cfb", +] + +[[package]] +name = "instant" +version = "0.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "interpolator" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71dd52191aae121e8611f1e8dc3e324dd0dd1dee1e6dd91d10ee07a3cfb4d9d8" + +[[package]] +name = "inventory" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "is-docker" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "928bae27f42bc99b60d9ac7334e3a21d10ad8f1835a4e12ec3ec0464765ed1b3" +dependencies = [ + "once_cell", +] + +[[package]] +name = "is-wsl" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "173609498df190136aa7dea1a91db051746d339e18476eed5ca40521f02d7aa5" +dependencies = [ + "is-docker", + "once_cell", +] + +[[package]] +name = "itertools" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b1fb8864823fad91877e6caea0baca82e49e8db50f8e5c9f9a453e27d3330fc" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror", +] + +[[package]] +name = "jsonptr" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c6e529149475ca0b2820835d3dce8fcc41c6b943ca608d32f35b449255e4627" +dependencies = [ + "fluent-uri", + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.6.0", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "kuchikiki" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f29e4755b7b995046f510a7520c42b2fed58b77bd94d5a87a8eb43d2fd126da8" +dependencies = [ + "cssparser", + "html5ever", + "indexmap 1.9.3", + "matches", + "selectors", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "leptos" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15911b4e53bb6e1b033d717eadb39924418a4a288279128122e5a65c70ba3e6" +dependencies = [ + "cfg-if", + "leptos_config", + "leptos_dom", + "leptos_macro", + "leptos_reactive", + "leptos_server", + "server_fn", + "tracing", + "typed-builder", + "typed-builder-macro", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "leptos_config" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbc4d78fba18c1ccab48ffc9f3d35b39821f896b0a28bdd616a846b6241036c9" +dependencies = [ + "config", + "regex", + "serde", + "thiserror", + "typed-builder", +] + +[[package]] +name = "leptos_dom" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ccb04d4763603bb665fa35cb9642d0bd75313117d10efda9b79243c023e69df" +dependencies = [ + "async-recursion", + "cfg-if", + "drain_filter_polyfill", + "futures", + "getrandom 0.2.15", + "html-escape", + "indexmap 2.5.0", + "itertools", + "js-sys", + "leptos_reactive", + "once_cell", + "pad-adapter", + "paste", + "rustc-hash", + "serde", + "serde_json", + "server_fn", + "smallvec", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "leptos_hot_reload" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cc61e5cce26761562cd3332630b3fbaddb1c4f77744e41474c7212ad279c5d9" +dependencies = [ + "anyhow", + "camino", + "indexmap 2.5.0", + "parking_lot", + "proc-macro2", + "quote", + "rstml", + "serde", + "syn 2.0.77", + "walkdir", +] + +[[package]] +name = "leptos_macro" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90eaea005cabb879c091c84cfec604687ececfd540469e5a30a60c93489a2f23" +dependencies = [ + "attribute-derive", + "cfg-if", + "convert_case 0.6.0", + "html-escape", + "itertools", + "leptos_hot_reload", + "prettyplease", + "proc-macro-error", + "proc-macro2", + "quote", + "rstml", + "server_fn_macro", + "syn 2.0.77", + "tracing", + "uuid", +] + +[[package]] +name = "leptos_reactive" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef2f99f377472459b0d320b46e9a9516b0e68dee5ed8c9eeb7e8eb9fefec5d2" +dependencies = [ + "base64 0.22.1", + "cfg-if", + "futures", + "indexmap 2.5.0", + "js-sys", + "oco_ref", + "paste", + "pin-project", + "rustc-hash", + "self_cell", + "serde", + "serde-wasm-bindgen", + "serde_json", + "slotmap", + "thiserror", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "leptos_server" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f07be202a433baa8c50050de4f9c116efccffc57208bcda7bd1bb9b8e87dca9" +dependencies = [ + "inventory", + "lazy_static", + "leptos_macro", + "leptos_reactive", + "serde", + "server_fn", + "thiserror", + "tracing", +] + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.158" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libredox" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +dependencies = [ + "bitflags 2.6.0", + "libc", +] + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "manyhow" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91ea592d76c0b6471965708ccff7e6a5d277f676b90ab31f4d3f3fc77fade64" +dependencies = [ + "manyhow-macros", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "manyhow-macros" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c64621e2c08f2576e4194ea8be11daf24ac01249a4f53cd8befcbb7077120ead" +dependencies = [ + "proc-macro-utils 0.8.0", + "proc-macro2", + "quote", +] + +[[package]] +name = "markup5ever" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +dependencies = [ + "log", + "phf 0.10.1", + "phf_codegen 0.10.0", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata 0.1.10", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", + "simd-adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" +dependencies = [ + "adler2", +] + +[[package]] +name = "mio" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +dependencies = [ + "hermit-abi", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "muda" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba8ac4080fb1e097c2c22acae467e46e4da72d941f02e82b67a87a2a89fa38b1" +dependencies = [ + "cocoa", + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc", + "once_cell", + "png", + "serde", + "thiserror", + "windows-sys 0.59.0", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.6.0", + "jni-sys", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e613fc340b2220f734a8595782c551f1250e969d87d3be1ae0579e8d4065179" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1844ef2428cc3e1cb900be36181049ef3d3193c63e43026cfe202983b27a56" +dependencies = [ + "proc-macro-crate 2.0.2", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc-sys" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" + +[[package]] +name = "objc2" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" +dependencies = [ + "objc-sys", + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" +dependencies = [ + "bitflags 2.6.0", + "block2", + "libc", + "objc2", + "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-core-data" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-encode" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7891e71393cd1f227313c9379a26a584ff3d7e6e7159e988851f0934c993f0f8" + +[[package]] +name = "objc2-foundation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags 2.6.0", + "block2", + "dispatch", + "libc", + "objc2", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags 2.6.0", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "object" +version = "0.36.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" +dependencies = [ + "memchr", +] + +[[package]] +name = "oco_ref" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c51ebcefb2f0b9a5e0bea115532c8ae4215d1b01eff176d0f4ba4192895c2708" +dependencies = [ + "serde", + "thiserror", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "open" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a877bf6abd716642a53ef1b89fb498923a4afca5c754f9050b4d081c05c4b3" +dependencies = [ + "is-wsl", + "libc", + "pathdiff", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "os_pipe" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffd2b0a5634335b135d5728d84c5e0fd726954b87111f7506a61c502280d982" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pad-adapter" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56d80efc4b6721e8be2a10a5df21a30fa0b470f1539e53d8b4e6e75faf938b63" + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pathdiff" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8835116a5c179084a830efb3adc117ab007512b535bc1a21c991d3b32a6b44dd" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared 0.10.0", +] + +[[package]] +name = "phf" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" +dependencies = [ + "phf_macros 0.11.2", + "phf_shared 0.11.2", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_generator" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" +dependencies = [ + "phf_shared 0.11.2", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "phf_macros" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3444646e286606587e49f3bcf1679b8cef1dc2c5ecc29ddacaffc305180d464b" +dependencies = [ + "phf_generator 0.11.2", + "phf_shared 0.11.2", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "plist" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42cf17e9a1800f5f396bc67d193dc9411b59012a5876445ef450d449881e1016" +dependencies = [ + "base64 0.22.1", + "indexmap 2.5.0", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06e4b0d3d1312775e782c86c91a111aa1f910cbb65e1337f9975b5f9a554b5e1" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.7.4", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "prettyplease" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479cf940fbbb3426c32c5d5176f62ad57549a0bb84773423ba8be9d089f5faba" +dependencies = [ + "proc-macro2", + "syn 2.0.77", +] + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro-utils" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f59e109e2f795a5070e69578c4dc101068139f74616778025ae1011d4cd41a8" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", +] + +[[package]] +name = "proc-macro-utils" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaf08a13de400bc215877b5bdc088f241b12eb42f0a548d3390dc1c56bb7071" +dependencies = [ + "proc-macro2", + "quote", + "smallvec", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proc-macro2-diagnostics" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", + "version_check", + "yansi", +] + +[[package]] +name = "quick-xml" +version = "0.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d3a6e5838b60e0e8fa7a43f22ade549a37d61f8bdbe636d0d7816191de969c2" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "quote-use" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9619db1197b497a36178cfc736dc96b271fe918875fbf1344c436a7e93d0321e" +dependencies = [ + "quote", + "quote-use-macros", +] + +[[package]] +name = "quote-use-macros" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82ebfb7faafadc06a7ab141a6f67bcfb24cb8beb158c6fe933f2f035afa99f35" +dependencies = [ + "proc-macro-utils 0.10.0", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags 2.6.0", +] + +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata 0.4.7", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax 0.6.29", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax 0.8.4", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + +[[package]] +name = "reqwest" +version = "0.12.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "once_cell", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-util", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "windows-registry", +] + +[[package]] +name = "rstml" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe542870b8f59dd45ad11d382e5339c9a1047cde059be136a7016095bbdefa77" +dependencies = [ + "proc-macro2", + "proc-macro2-diagnostics", + "quote", + "syn 2.0.77", + "syn_derive", + "thiserror", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09c024468a378b7e36765cd36702b7a90cc3cba11654f6685c8f233408e89e92" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", +] + +[[package]] +name = "schemars_derive" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1eee588578aff73f856ab961cd2f79e36bc45d7ded33a7562adba4667aecc0e" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.77", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "selectors" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +dependencies = [ + "bitflags 1.3.2", + "cssparser", + "derive_more", + "fxhash", + "log", + "matches", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", + "thin-slice", +] + +[[package]] +name = "self_cell" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d369a96f978623eb3dc28807c4852d6cc617fed53da5d3c400feff1ef34a714a" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] + +[[package]] +name = "send_wrapper" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73" +dependencies = [ + "futures-core", +] + +[[package]] +name = "serde" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2676ba99bd82f75cae5cbd2c8eda6fa0b8760f18978ea840e980dd5567b5c5b6" +dependencies = [ + "erased-serde", + "serde", + "typeid", +] + +[[package]] +name = "serde-wasm-bindgen" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + +[[package]] +name = "serde_derive" +version = "1.0.209" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "serde_json" +version = "1.0.127" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +dependencies = [ + "itoa 1.0.11", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_qs" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0431a35568651e363364210c91983c1da5eb29404d9f0928b67d4ebcfa7d330c" +dependencies = [ + "percent-encoding", + "serde", + "thiserror", +] + +[[package]] +name = "serde_repr" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c64451ba24fc7a6a2d60fc75dd9c83c90903b19028d4eff35e88fc1e86564e9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "serde_spanned" +version = "0.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb5b1b31579f3811bf615c144393417496f152e12ac8b7663bf664f4a815306d" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.11", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cecfa94848272156ea67b2b1a53f20fc7bc638c4a46d2f8abde08f05f4b857" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.5.0", + "serde", + "serde_derive", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8fee4991ef4f274617a51ad4af30519438dacb2f56ac773b08a1922ff743350" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "server_fn" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "024b400db1aca5bd4188714f7bbbf7a2e1962b9a12a80b2a21e937e509086963" +dependencies = [ + "bytes", + "ciborium", + "const_format", + "dashmap", + "futures", + "gloo-net", + "http", + "js-sys", + "once_cell", + "send_wrapper", + "serde", + "serde_json", + "serde_qs", + "server_fn_macro_default", + "thiserror", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf0e6f71fc924df36e87f27dfbd447f0bedd092d365db3a5396878256d9f00c" +dependencies = [ + "const_format", + "convert_case 0.6.0", + "proc-macro2", + "quote", + "syn 2.0.77", + "xxhash-rust", +] + +[[package]] +name = "server_fn_macro_default" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "556e4fd51eb9ee3e7d9fb0febec6cef486dcbc8f7f427591dfcfebee1abe1ad4" +dependencies = [ + "server_fn_macro", + "syn 2.0.77", +] + +[[package]] +name = "servo_arc" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shared_child" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09fa9338aed9a1df411814a5b2252f7cd206c55ae9bf2fa763f8de84603aa60c" +dependencies = [ + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "slotmap" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +dependencies = [ + "serde", + "version_check", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "softbuffer" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d623bff5d06f60d738990980d782c8c866997d9194cfe79ecad00aa2f76826dd" +dependencies = [ + "bytemuck", + "cfg_aliases", + "core-graphics 0.23.2", + "foreign-types", + "js-sys", + "log", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall", + "wasm-bindgen", + "web-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "state" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8" +dependencies = [ + "loom", +] + +[[package]] +name = "string_cache" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared 0.10.0", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "swift-rs" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4057c98e2e852d51fdcfca832aac7b571f6b351ad159f9eda5db1655f8d0c4d7" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn_derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1329189c02ff984e9736652b1631330da25eaa6bc639089ed4915d25446cbe7b" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" +dependencies = [ + "futures-core", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3a97abbc7d6cfd0720da3e06fcb1cf2ac87cbfdb5bbbce103a1279a211c4d81" +dependencies = [ + "bitflags 2.6.0", + "cocoa", + "core-foundation 0.10.0", + "core-graphics 0.24.0", + "crossbeam-channel", + "dispatch", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "instant", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc", + "once_cell", + "parking_lot", + "raw-window-handle", + "scopeguard", + "tao-macros", + "unicode-segmentation", + "url", + "windows 0.58.0", + "windows-core 0.58.0", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4e16beb8b2ac17db28eab8bca40e62dbfbb34c0fcdc6d9826b11b7b5d047dfd" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.0.0-rc.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8345ccc676ef16e26b61fc0f5340b4e770678b1e1f53f08c69ebdac5e56b422" +dependencies = [ + "anyhow", + "bytes", + "cocoa", + "dirs", + "dunce", + "embed_plist", + "futures-util", + "getrandom 0.2.15", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc", + "percent-encoding", + "raw-window-handle", + "reqwest", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "state", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror", + "tokio", + "tray-icon", + "url", + "urlpattern", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows 0.58.0", +] + +[[package]] +name = "tauri-build" +version = "2.0.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5ad5fcfaf02cf79aa6727f6c5df38567d8dce172b00b62690c6bc46c08b7ce" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "toml 0.8.2", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.0.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809ef6316726fc72593d296cf6f4e7461326e310c313d6a6c42b6e7f1e2671cf" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.77", + "tauri-utils", + "thiserror", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.0.0-rc.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1359e8861d210d25731f8b1bfbb4d111dd06406cf73c59659366ef450364d811" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.77", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-plugin" +version = "2.0.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7dded420c86183f592d0fe925ef9447f41e26fa79f0bdfef8d3f17bfbcdbfb7" +dependencies = [ + "anyhow", + "glob", + "plist", + "schemars", + "serde", + "serde_json", + "tauri-utils", + "toml 0.8.2", + "walkdir", +] + +[[package]] +name = "tauri-plugin-shell" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83800ddf78b820172efb5ed7310344e8e4f97fd30cd8237a3f20c12a79eb136" +dependencies = [ + "encoding_rs", + "log", + "open", + "os_pipe", + "regex", + "schemars", + "serde", + "serde_json", + "shared_child", + "tauri", + "tauri-plugin", + "thiserror", + "tokio", +] + +[[package]] +name = "tauri-runtime" +version = "2.0.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75c72b844f387bfc3341c355f3e16b8cbf4161848fa4e348670effb222cd3ba5" +dependencies = [ + "dpi", + "gtk", + "http", + "jni", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror", + "url", + "windows 0.58.0", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.0.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73accf936a7cd01d1382de7850726fdf6c1f6ab3b01ccb7a0950cb852e332596" +dependencies = [ + "cocoa", + "gtk", + "http", + "jni", + "log", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows 0.58.0", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.0.0-rc.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53d9fe87e985b273696ae22ce2b9f099a8f1b44bc8fb127467bda5fcb3e4371" +dependencies = [ + "brotli", + "cargo_metadata", + "ctor", + "dunce", + "glob", + "html5ever", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.2", + "proc-macro2", + "quote", + "regex", + "schemars", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror", + "toml 0.8.2", + "url", + "urlpattern", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5993dc129e544393574288923d1ec447c857f3f644187f4fbf7d9a875fbfc4fb" +dependencies = [ + "embed-resource", + "toml 0.7.8", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "thin-slice" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" + +[[package]] +name = "thiserror" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa 1.0.11", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.19.15", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.5.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.5.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "tray-icon" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "131a65b2cef2081bc14dbcd414c906edbfa3bb5323dd7e748cc298614681196b" +dependencies = [ + "core-graphics 0.24.0", + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "once_cell", + "png", + "serde", + "thiserror", + "windows-sys 0.59.0", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typed-builder" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77739c880e00693faef3d65ea3aad725f196da38b22fdc7ea6ded6e1ce4d3add" +dependencies = [ + "typed-builder-macro", +] + +[[package]] +name = "typed-builder-macro" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f718dfaf347dcb5b983bfc87608144b0bad87970aebcbea5ce44d2a30c08e63" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "typeid" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e13db2e0ccd5e14a544e8a246ba2312cd25223f616442d7f2cb0e3db614236e" + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-xid" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229730647fbc343e3a80e463c1db7f78f3855d3f3739bee0dda773c9a037c90a" + +[[package]] +name = "url" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "urlpattern" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9bd5ff03aea02fa45b13a7980151fe45009af1980ba69f651ec367121a31609" +dependencies = [ + "derive_more", + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8-width" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86bd8d4e895da8537e5315b8254664e6b769c4ff3db18321b297a1e7004392e3" + +[[package]] +name = "uuid" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dfa00651efa65069b0b6b651f4aaa31ba9e3c3ce0137aaad053604ee7e0314" +dependencies = [ + "getrandom 0.2.15", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "version-compare" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852e951cb7832cb45cb1169900d19760cfa39b82bc0ea9c0e5a14ae88411c98b" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3b17ae1f6c8a2b28506cd96d412eebf83b4a0ff2cbefeeb952f2f9dfa44ba18" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.77", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "wasm-streams" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76b1bc1e54c581da1e9f179d0b38512ba358fb1af2d634a1affe42e37172361a" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62daa38afc514d1f8f12b8693d30d5993ff77ced33ce30cd04deebc267a6d57c" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webview2-com" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61ff3d9d0ee4efcb461b14eb3acfda2702d10dc329f339303fc3e57215ae2c" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows 0.58.0", + "windows-core 0.58.0", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d228f15bba3b9d56dde8bddbee66fa24545bd17b48d5128ccf4a8742b18e431" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "webview2-com-sys" +version = "0.33.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3a3e2eeb58f82361c93f9777014668eb3d07e7d174ee4c819575a9208011886" +dependencies = [ + "thiserror", + "windows 0.58.0", + "windows-core 0.58.0", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8cdd6999298d969289d8078dae02ce798ad23452075985cccba8b6326711ecf" +dependencies = [ + "cocoa", + "objc", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd04d41d93c4992d421894c18c8b43496aa748dd4c081bac0dc93eb0489272b6" +dependencies = [ + "windows-core 0.58.0", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-core" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ba6d44ec8c2591c134257ce647b7ea6b20335bf6379a27dac5f1641fcf59f99" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-implement" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bbd5b46c938e506ecbce286b6628a02171d56153ba733b6c741fc627ec9579b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "windows-interface" +version = "0.58.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053c4c462dc91d3b1504c6fe5a726dd15e216ba718e84a0e46a88fbe5ded3515" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] + +[[package]] +name = "windows-registry" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-result" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-strings" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" +dependencies = [ + "windows-result", + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-version" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6998aa457c9ba8ff2fb9f13e9d2a930dabcea28f1d0ab94d687d8b3654844515" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + +[[package]] +name = "wry" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b8049c8f239cdbfaaea4bacb9646f6b208938ceec0acd5b3e99cd05f70903f" +dependencies = [ + "base64 0.22.1", + "block", + "cocoa", + "core-graphics 0.24.0", + "crossbeam-channel", + "dpi", + "dunce", + "gdkx11", + "gtk", + "html5ever", + "http", + "javascriptcore-rs", + "jni", + "kuchikiki", + "libc", + "ndk", + "objc", + "objc_id", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows 0.58.0", + "windows-core 0.58.0", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "xxhash-rust" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a5cbf750400958819fb6178eaa83bee5cd9c29a26a40cc241df8c70fdd46984" + +[[package]] +name = "yansi" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.77", +] diff --git a/frontend/Cargo.toml b/frontend/Cargo.toml new file mode 100644 index 0000000..b0b0ad8 --- /dev/null +++ b/frontend/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "chipbox-frontend" +version = "0.1.0" +edition = "2021" + +[dependencies] +## member deps +chipbox-glue = { workspace = true, features = ["frontend"] } +chipbox-common.workspace = true +## frontend deps +leptos.workspace = true +leptos-use.workspace = true +wasm-bindgen.workspace = true +wasm-bindgen-futures.workspace = true +serde.workspace = true +serde-wasm-bindgen.workspace = true +console_error_panic_hook.workspace = true +tracing-subscriber.workspace = true +tracing-web.workspace = true +tracing.workspace = true +eyre.workspace = true diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..6216d54 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,15 @@ + + + + + chipbox + + + + + + + + + + diff --git a/frontend/src/app.rs b/frontend/src/app.rs new file mode 100644 index 0000000..a08b050 --- /dev/null +++ b/frontend/src/app.rs @@ -0,0 +1,51 @@ +mod editor; +mod home; +mod title_bar; +mod wizard; + +use chipbox_glue::cmd; +use editor::Editor; +use home::Home; +use leptos::prelude::*; +use leptos_router::{ + components::{Route, Router, Routes}, + path, +}; +use title_bar::TitleBar; +use wizard::Wizard; + +fn fetch() -> impl IntoView { + // TODO: add information on reporting issues. + view! { +
+

"Fetching current state from the backend..."

+
+ } +} + +fn error_retry(title: &'static str, msg: String) -> impl IntoView { + // TODO: add information on reporting issues. + view! { +
+

{title}

+ {msg} +

"Press the button to retry"

+ +
+ } +} + +fn content() -> impl IntoView {} + +#[component] +pub(crate) fn App() -> impl IntoView { + let project = signal(None); + + view! { + +
{move || content()}
+ } +} diff --git a/frontend/src/app/editor.rs b/frontend/src/app/editor.rs new file mode 100644 index 0000000..41022ed --- /dev/null +++ b/frontend/src/app/editor.rs @@ -0,0 +1,6 @@ +use leptos::prelude::*; + +#[component] +pub(crate) fn Editor() -> impl IntoView { + view! {
} +} diff --git a/frontend/src/app/editor/state.rs b/frontend/src/app/editor/state.rs new file mode 100644 index 0000000..afd6b44 --- /dev/null +++ b/frontend/src/app/editor/state.rs @@ -0,0 +1,24 @@ +use chipbox_common::project::latest::Project; + +pub struct State { + /// State of the project, should be an exact copy of the version + /// on the backend. + project: Project, + /// Local changes to the project, unverified by the backend. + project_preview: Project, +} + +impl From for State { + fn from(project: Project) -> Self { + Self { + project: project.clone(), + project_preview: project, + } + } +} + +impl State { + pub fn project_preview(&self) -> &Project { + &self.project_preview + } +} diff --git a/frontend/src/app/home.rs b/frontend/src/app/home.rs new file mode 100644 index 0000000..2dd67e6 --- /dev/null +++ b/frontend/src/app/home.rs @@ -0,0 +1,25 @@ +use chipbox_glue::cmd; +use leptos::{prelude::*, task}; + +#[component] +pub(crate) fn Home() -> impl IntoView { + view! { +
+ + +
+ } +} + +async fn open_project() { + todo!() +} + +async fn create_project() { + let cmd_args = todo!(); + let response = cmd::create_project(&cmd_args).await; +} diff --git a/frontend/src/app/title_bar.rs b/frontend/src/app/title_bar.rs new file mode 100644 index 0000000..c24823a --- /dev/null +++ b/frontend/src/app/title_bar.rs @@ -0,0 +1,67 @@ +use crate::tauri_api; +use button::{Button, State}; +use leptos::{prelude::*, task}; +use leptos_use::{use_event_listener, use_window}; + +mod button; + +/// Custom window title bar. +#[component] +pub(crate) fn TitleBar() -> impl IntoView { + let maximized = Maximized::register(); + + view! { +
+ // TODO: title bar context-dependent content +
+
+
+
+
+ } +} + +/// Memoized signal for updating the resize button based on window state. +/// Automatically registers a callback on window resize, +/// updating the signal value. +struct Maximized { + memo: Memo, + set: WriteSignal, +} + +impl Maximized { + /// Register an event listener that updates a signal based on window state. + /// + /// Returns the signal and listener handle. + fn register() -> Self { + // Signals for updating the resize button. + let (value, set) = signal(false); + // Update `maximized` signal on window resize. + let _stop = use_event_listener(use_window(), leptos::ev::resize, move |_| { + // Safety: safe as long as the API remains stable. + let window = unsafe { tauri_api::window::js_current_window() }; + task::spawn_local(async move { set(window.is_maximized().await) }) + }); + tracing::debug!("registered window resize listener"); + // Construct memo, return handle ownership. + let memo = Memo::new(move |_| value()); + Self { memo, set } + } +} diff --git a/frontend/src/app/title_bar/button.rs b/frontend/src/app/title_bar/button.rs new file mode 100644 index 0000000..c8f7e07 --- /dev/null +++ b/frontend/src/app/title_bar/button.rs @@ -0,0 +1,120 @@ +use crate::tauri_api; +use leptos::{prelude::*, task}; + +#[component] +/// A button that implements title bar functionality. +pub(crate) fn Button(state: State) -> impl IntoView { + let path_data = state.path_data(); + let stroke = state.stroke(); + let class = state.class(); + let on_click = move |_| state.on_click(); + + view! { + + } +} + +#[derive(PartialEq, Clone, Copy)] +/// Holds current state of a [`Button`]. +pub(crate) enum State { + /// Show more options + More, + /// Minimize the window. + Minimize, + /// Maximize the window. + /// + /// Takes a reference to a signal representing the maximized state of the window. + /// This signal will be updated whenever the button is pressed in this state. + Maximize(WriteSignal), + /// Restore the window. + /// + /// Takes a reference to a signal representing the maximized state of the window. + /// This signal will be updated whenever the button is pressed in this state. + Restore(WriteSignal), + /// Request to close the window. + Close, +} + +impl State { + /// SVG path data used for the buttons icon. + fn path_data(&self) -> &'static str { + match self { + State::More => "M3 13v-2h2v2zm8 0v-2h2v2zm8 0v-2h2v2z", + State::Minimize => "M4 11h16v2H4z", + State::Maximize(_) => "M4 4h16v16H4zm2 4v10h12V8z", + State::Restore(_) => "M4 8h4V4h12v12h-4v4H4zm12 0v6h2V6h-8v2zM6 12v6h8v-6z", + State::Close => { + "M13.46 12L19 17.54V19h-1.46L12 13.46L6.46 + 19H5v-1.46L10.54 12L5 6.46V5h1.46L12 + 10.54L17.54 5H19v1.46z" + } + } + } + + /// SVG stroke used for the buttons icon. + fn stroke(&self) -> Option<&'static str> { + match self { + State::More => Some("currentColor"), + _ => None, + } + } + + /// CSS class used for the button. + fn class(&self) -> &'static str { + match self { + State::Close => "title-bar-button-close", + _ => "title-bar-button", + } + } + + /// Generates a click handler function for the button. + /// The returned handler will be different depending on the state. + fn on_click(self) { + // Safety: safe as long as the API remains stable. + let window = unsafe { tauri_api::window::js_current_window() }; + match self { + State::More => todo!(), + State::Minimize => Self::on_minimize(window), + State::Maximize(set_maximized) | State::Restore(set_maximized) => { + Self::on_resize(window, set_maximized) + } + State::Close => Self::on_close(window), + } + } + + /// Called when the minimize button is pressed. + /// Minimizes the window. + fn on_minimize(window: tauri_api::window::JsWindow) { + task::spawn_local(async move { + tracing::debug!("minimizing window"); + // Safety: safe as long as the API remains stable. + unsafe { window.js_minimize() }.await; + }); + } + + /// Called when the resize button is pressed. + /// Toggles the maximized state of the window. + /// Updates a signal with the new maximized state. + fn on_resize(window: tauri_api::window::JsWindow, set_maximized: WriteSignal) { + task::spawn_local(async move { + tracing::debug!("toggling window maximized state"); + // Safety: safe as long as the API remains stable. + unsafe { window.js_toggle_maximize() }.await; + set_maximized(window.is_maximized().await); + }); + } + + /// Called when the close button is pressed. + /// Requests to close the window. + fn on_close(window: tauri_api::window::JsWindow) { + task::spawn_local(async move { + tracing::debug!("requesting window to close"); + // Safety: safe as long as the API remains stable. + unsafe { window.js_close() }.await; + }); + } +} diff --git a/frontend/src/app/wizard.rs b/frontend/src/app/wizard.rs new file mode 100644 index 0000000..da8a1e9 --- /dev/null +++ b/frontend/src/app/wizard.rs @@ -0,0 +1,6 @@ +use leptos::prelude::*; + +#[component] +pub(crate) fn Wizard() -> impl IntoView { + view! {
} +} diff --git a/frontend/src/main.rs b/frontend/src/main.rs new file mode 100644 index 0000000..60c97d0 --- /dev/null +++ b/frontend/src/main.rs @@ -0,0 +1,14 @@ +pub(crate) mod tauri_api; + +mod app; +mod tracing_layers; + +use app::App; + +fn main() { + console_error_panic_hook::set_once(); + tracing_layers::init(); + leptos::mount::mount_to_body(|| { + leptos::view! { } + }) +} diff --git a/frontend/src/tauri_api.rs b/frontend/src/tauri_api.rs new file mode 100644 index 0000000..8f9c26c --- /dev/null +++ b/frontend/src/tauri_api.rs @@ -0,0 +1 @@ +pub(crate) mod window; diff --git a/frontend/src/tauri_api/window.rs b/frontend/src/tauri_api/window.rs new file mode 100644 index 0000000..2744bd2 --- /dev/null +++ b/frontend/src/tauri_api/window.rs @@ -0,0 +1,63 @@ +use wasm_bindgen::prelude::*; + +#[allow(non_snake_case)] +#[wasm_bindgen] +unsafe extern "C" { + /// + /// # Safety + /// This function is safe to execute as long as the function signature matches + /// the [Tauri JavaScript API](https://v2.tauri.app/reference/javascript/api/). + #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "window"], js_name = "Window")] + pub(crate) type JsWindow; + + /// + /// # Safety + /// This function is safe to execute as long as the function signature matches + /// the [Tauri JavaScript API](https://v2.tauri.app/reference/javascript/api/). + #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "window"], js_name = "getCurrentWindow")] + pub(crate) unsafe fn js_current_window() -> JsWindow; + + /// + /// # Safety + /// This function is safe to execute as long as the function signature matches + /// the [Tauri JavaScript API](https://v2.tauri.app/reference/javascript/api/). + #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "window"], method, js_name = "close")] + pub(crate) async unsafe fn js_close(this: &JsWindow); + + /// + /// # Note + /// This function returns a [`JsValue`]. + /// Use the more rusty [`is_maximized`][Window::is_maximized]. + /// # Safety + /// This function is safe to execute as long as the function signature matches + /// the [Tauri JavaScript API](https://v2.tauri.app/reference/javascript/api/). + #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "window"], method, js_name = "isMaximized")] + async unsafe fn js_is_maximized(this: &JsWindow) -> JsValue; + + /// + /// # Safety + /// This function is safe to execute as long as the function signature matches + /// the [Tauri JavaScript API](https://v2.tauri.app/reference/javascript/api/). + #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "window"], method, js_name = "toggleMaximize")] + pub(crate) async unsafe fn js_toggle_maximize(this: &JsWindow); + + /// + /// # Safety + /// This function is safe to execute as long as the function signature matches + /// the [Tauri JavaScript API](https://v2.tauri.app/reference/javascript/api/). + #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "window"], method, js_name = "minimize")] + pub(crate) async unsafe fn js_minimize(this: &JsWindow); +} + +impl JsWindow { + /// Check if the window is currently maximized. + /// + /// The resulting value is deserialized from a [`JsValue`]. + /// This function will panic if the underlying API does not return a boolean. + /// See [`JsWindow::js_is_maximized`]. + pub(crate) async fn is_maximized(&self) -> bool { + // Safety: safe as long as the API remains stable. + let is_maximized = unsafe { self.js_is_maximized() }.await; + serde_wasm_bindgen::from_value(is_maximized).expect("is_maximized from js should give bool") + } +} diff --git a/frontend/src/tracing_layers.rs b/frontend/src/tracing_layers.rs new file mode 100644 index 0000000..ad82cfc --- /dev/null +++ b/frontend/src/tracing_layers.rs @@ -0,0 +1,48 @@ +//! Functionality for initializing tracing capabilities. + +use tracing::subscriber; +use tracing_subscriber::{ + fmt::{self, format::Pretty}, + layer::SubscriberExt as _, + EnvFilter, Layer, Registry, +}; +use tracing_web::{performance_layer, MakeWebConsoleWriter}; + +/// Initialize and set a global tracing subscriber. +pub(super) fn init() { + // Construct performance layer. + let perf_layer = performance_layer().with_details_from_fields(Pretty::default()); + // Construct env filter. + let env_filter = + EnvFilter::from_default_env().add_directive("debug".parse().expect("invalid directive")); + // Construct subscriber. + let subscriber = { + tracing_subscriber::registry() + .with(fmt_layer()) + .with(perf_layer) + .with(env_filter) + }; + // Install subscriber. + subscriber::set_global_default(subscriber).expect("unable to set global default subscriber"); +} + +/// Create a [`tracing_web`] console writer. +/// +/// This adds a writer that outputs the trace to the web console. +fn console_writer() -> MakeWebConsoleWriter { + MakeWebConsoleWriter::new().with_pretty_level() +} + +/// Construct a pretty-formatted tracing layer for console output. +fn fmt_layer() -> impl Layer + 'static { + fmt::layer() + // File, line, and target are always included. + .with_file(true) + .with_line_number(true) + .with_target(true) + // `std::time` is not available in browsers. + .without_time() + // Add console writer. + .with_writer(console_writer()) + .with_level(false) +} diff --git a/frontend/style.old/_app.scss b/frontend/style.old/_app.scss new file mode 100644 index 0000000..418044a --- /dev/null +++ b/frontend/style.old/_app.scss @@ -0,0 +1,2 @@ +@use "app/home"; +@use "app/container"; diff --git a/public/placeholder b/frontend/style.old/_design.scss similarity index 100% rename from public/placeholder rename to frontend/style.old/_design.scss diff --git a/frontend/style.old/_scaled-px.scss b/frontend/style.old/_scaled-px.scss new file mode 100644 index 0000000..4767eab --- /dev/null +++ b/frontend/style.old/_scaled-px.scss @@ -0,0 +1,3 @@ +@function scaled-px($px) { + @return calc($px / 16px) * 1rem; +} diff --git a/frontend/style.old/_title-bar.scss b/frontend/style.old/_title-bar.scss new file mode 100644 index 0000000..42e5002 --- /dev/null +++ b/frontend/style.old/_title-bar.scss @@ -0,0 +1,78 @@ +@use "scaled-px" as *; + +#title-bar { + box-sizing: border-box; + display: flex; + flex-direction: row; + border-bottom: #000 solid scaled-px(1px); + background-color: hsl(0, 0%, 7%); + width: 100vw; + height: scaled-px(38px); + -webkit-app-region: drag; + user-select: none; +} + +#title-bar-content { + flex: 1; + height: 100%; +} + +#title-bar-buttons { + display: flex; + height: 100%; +} + +.title-bar-separator { + background-color: #3d3d3d; + width: scaled-px(1px); + height: 100%; +} + +.title-bar-button { + display: flex; + align-items: center; + border: none; + background-color: #131313; + width: scaled-px(50px); + height: 100%; + -webkit-app-region: no-drag; + svg { + margin: 0 auto; + width: scaled-px(18px); + height: scaled-px(18px); + + path { + color: #7f7f7f; + stroke-width: 2; + } + } +} + +.title-bar-button:hover { + background-color: #303030; + svg > path { + color: #fff; + } +} + +.title-bar-button:hover:active { + background-color: #3e3e3e; + svg > path { + color: #ddd; + } +} + +.title-bar-button-close { + @extend .title-bar-button; +} + +.title-bar-button-close:hover { + background-color: #f00; + svg > path { + color: #fff; + } +} + +.title-bar-button-close:hover:active { + background-color: #a00; +} diff --git a/frontend/style.old/app/_container.scss b/frontend/style.old/app/_container.scss new file mode 100644 index 0000000..11c47be --- /dev/null +++ b/frontend/style.old/app/_container.scss @@ -0,0 +1,138 @@ +@use "../scaled-px" as *; + +$border-width: scaled-px(1px); +$header-height: scaled-px(37px); +$icon-size: scaled-px(18px); + +@mixin container-border($styles) { + border: #000 none $border-width; + border-style: $styles; +} + +@mixin container-inner-border($styles) { + border: #3d3d3d none $border-width; + border-style: $styles; +} + +@mixin containers-horizontal { + display: flex; + flex-direction: row; + .container { + border-right-style: solid; + } + .container:last-child { + border-right-style: none; + } +} + +@mixin containers-vertical { + display: flex; + flex-direction: column; + .container { + border-bottom-style: solid; + } + .container:last-child { + border-bottom-style: none; + } +} + +.containers { + @include container-border(solid); + .containers { + border-style: none; + } +} + +.containers.horizontal { + @include containers-horizontal; +} + +.containers.vertical { + @include containers-vertical; +} + +.container { + flex: 1 1; + background-color: #222; + padding: scaled-px(6px); + @include container-border(none); +} + +.container-inner { + display: flex; + flex-direction: column; + margin: auto; + height: 100%; +} + +.container-header { + position: relative; + flex-direction: row; + margin-right: scaled-px(48px); + height: $header-height; +} + +.container-header-clip { + position: absolute; + width: 100%; + overflow: clip; +} + +.container-header-overflow { + display: flex; + flex-direction: row; + height: $header-height; + overflow-x: scroll; +} + +.container-header-overflow::-webkit-scrollbar { + display: none; +} + +.container-tab { + display: flex; + align-items: center; + flex: 0 0 auto; + z-index: 1; + background-color: #131313; + padding: scaled-px(8px); + @include container-inner-border(solid solid none solid); + + .icon { + width: $icon-size; + height: $icon-size; + color: #808080; + } + .close-button { + display: flex; + align-items: center; + justify-content: center; + margin-left: scaled-px(8px); + + .close-icon { + width: $icon-size; + height: $icon-size; + color: #808080; + } + } + .close-button:hover { + .close-icon { + color: #fff; + } + } + p { + margin: scaled-px(2px) scaled-px(8px) 0; + color: #cccccc; + font-family: "Questrial", sans-serif; + user-select: none; + } +} + +.container-content { + box-sizing: border-box; + flex-grow: 1; + margin-top: -$border-width; + background-color: #131313; + width: 100%; + @include container-inner-border(solid); +} diff --git a/frontend/style.old/app/_home.scss b/frontend/style.old/app/_home.scss new file mode 100644 index 0000000..0b25c38 --- /dev/null +++ b/frontend/style.old/app/_home.scss @@ -0,0 +1,23 @@ +@use "container"; + +#home { + height: 100%; +} + +@media (max-aspect-ratio: 1/1) { + #home { + @include container.containers-vertical; + } + #release-notes-container { + flex: 0 1 40%; + } +} + +@media not (max-aspect-ratio: 1/1) { + #home { + @include container.containers-horizontal; + } + #release-notes-container { + flex: 0 1 30%; + } +} diff --git a/frontend/style.old/main.scss b/frontend/style.old/main.scss new file mode 100644 index 0000000..e8ffd05 --- /dev/null +++ b/frontend/style.old/main.scss @@ -0,0 +1,43 @@ +@use "title-bar"; +@use "app"; +@use "design"; + +// HTML spans the entire window. +// Overflow will be clipped. +html { + width: 100vw; + height: 100vh; + overflow: clip; +} + +// Body spans the entire window. +// It's a flex container containing the title bar and the main content. +// Overflow will be clipped. +body { + display: flex; + flex-direction: column; + margin: 0; + background-color: #000; + width: 100vw; + height: 100vh; + overflow: clip; +} + +// Main is the actual content of the window. +// This primarily excludes the title bar. +// The content may overflow if the window is too small. +main { + flex: 1 1 auto; + width: 100%; + height: auto; + overflow: visible; +} + +// A button does not have a default style. +// It is transparent, has no border or padding, and shows a pointer. +button { + border: 0; + background-color: transparent; + cursor: pointer; + padding: 0; +} diff --git a/frontend/style/main.css b/frontend/style/main.css new file mode 100644 index 0000000..de86f70 --- /dev/null +++ b/frontend/style/main.css @@ -0,0 +1,332 @@ +html { + line-height: 1.15; + -webkit-text-size-adjust: 100%; +} + +body { + margin: 0; +} + +main { + display: block; +} + +h1 { + margin: 0.67em 0; + font-size: 2em; +} + +hr { + box-sizing: content-box; + height: 0; + overflow: visible; +} + +pre { + font-family: monospace, monospace; + font-size: 1em; +} + +a { + background-color: transparent; +} + +abbr[title] { + border-bottom: none; + text-decoration: underline dotted; +} + +b, +strong { + font-weight: bolder; +} + +code, +kbd, +samp { + font-family: monospace, monospace; + font-size: 1em; +} + +small { + font-size: 80%; +} + +sub, +sup { + position: relative; + vertical-align: baseline; + line-height: 0; + font-size: 75%; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +img { + border-style: none; +} + +button, +input, +optgroup, +select, +textarea { + margin: 0; + line-height: 1.15; + font-family: inherit; + font-size: 100%; +} + +button, +input { + overflow: visible; +} + +button, +select { + text-transform: none; +} + +button, +[type=button], +[type=reset], +[type=submit] { + -webkit-appearance: button; + appearance: button; +} + +button::-moz-focus-inner, +[type=button]::-moz-focus-inner, +[type=reset]::-moz-focus-inner, +[type=submit]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +button:-moz-focusring, +[type=button]:-moz-focusring, +[type=reset]:-moz-focusring, +[type=submit]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +legend { + box-sizing: border-box; + display: table; + padding: 0; + max-width: 100%; + white-space: normal; + color: inherit; +} + +progress { + vertical-align: baseline; +} + +textarea { + overflow: auto; +} + +[type=checkbox], +[type=radio] { + box-sizing: border-box; + padding: 0; +} + +[type=number]::-webkit-inner-spin-button, +[type=number]::-webkit-outer-spin-button { + height: auto; +} + +[type=search] { + outline-offset: -2px; + appearance: textfield; + -webkit-appearance: textfield; +} + +[type=search]::-webkit-search-decoration { + -webkit-appearance: none; +} + +::-webkit-file-upload-button { + font: inherit; + -webkit-appearance: button; +} + +details { + display: block; +} + +summary { + display: list-item; +} + +template { + display: none; +} + +[hidden] { + display: none; +} + +html { + box-sizing: border-box; +} + +html, +body { + margin: 0; + width: 100vw; + height: 100vh; + overflow: hidden; +} + +body { + display: flex; + flex-direction: column; +} + +main { + overflow: auto; + flex: 1; +} + +*, +*::before, +*::after { + box-sizing: inherit; +} + +a { + text-decoration: none; + color: rgb(229, 0, 80); +} +a:hover, a:active, a:focus, a:focus-within { + text-decoration: underline; + color: rgb(220, 220, 220); +} + +.clearfix::after { + display: table; + clear: both; + content: ""; +} + +.container { + box-sizing: border-box; + margin-right: auto; + margin-left: auto; + padding-right: 20px; + padding-left: 20px; + width: 100%; + max-width: 1180px; +} + +.hide-text { + padding: 0; + overflow: hidden; + text-indent: 101%; + white-space: nowrap; +} + +.visually-hidden { + position: absolute; + margin: -1px; + border: 0; + padding: 0; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0 0 0 0); +} + +body { + color: rgb(220, 220, 220); + font: normal 125%/1.4 "Open Sans", "Helvetica Neue Light", "Helvetica Neue", "Helvetica", "Arial", sans-serif; +} + +#title-bar { + box-sizing: border-box; + display: flex; + flex-direction: row; + border-bottom: #000 solid 0.0625rem; + background-color: hsl(0, 0%, 7%); + width: 100vw; + height: 2.375rem; + -webkit-app-region: drag; + user-select: none; +} + +#title-bar-content { + flex: 1; + height: 100%; +} + +#title-bar-buttons { + display: flex; + height: 100%; +} + +.title-bar-separator { + background-color: #3d3d3d; + width: 0.0625rem; + height: 100%; +} + +.title-bar-button, .title-bar-button-close { + display: flex; + align-items: center; + border: none; + background-color: #131313; + width: 3.125rem; + height: 100%; + -webkit-app-region: no-drag; +} +.title-bar-button svg, .title-bar-button-close svg { + margin: 0 auto; + width: 1.125rem; + height: 1.125rem; +} +.title-bar-button svg path, .title-bar-button-close svg path { + color: #7f7f7f; + stroke-width: 2; +} + +.title-bar-button:hover, .title-bar-button-close:hover { + background-color: #303030; +} +.title-bar-button:hover svg > path, .title-bar-button-close:hover svg > path { + color: #fff; +} + +.title-bar-button:hover:active, .title-bar-button-close:hover:active { + background-color: #3e3e3e; +} +.title-bar-button:hover:active svg > path, .title-bar-button-close:hover:active svg > path { + color: #ddd; +} + +.title-bar-button-close:hover { + background-color: #f00; +} +.title-bar-button-close:hover svg > path { + color: #fff; +} + +.title-bar-button-close:hover:active { + background-color: #a00; +} + +body { + background-color: #000; +} diff --git a/frontend/style/stylesheets/README.md b/frontend/style/stylesheets/README.md new file mode 100644 index 0000000..736ed0e --- /dev/null +++ b/frontend/style/stylesheets/README.md @@ -0,0 +1,7 @@ +# Main file + +The main file (usually labelled `main.scss`) should be the only Sass file from the whole code base not to begin with an underscore. This file should not contain anything but `@use` and comments. + +_Note: when using [Eyeglass](https://github.com/sass-eyeglass/eyeglass) for distribution, it might be a fine idea to name this file `index.scss` rather than `main.scss` in order to stick to [Eyeglass modules specifications](https://github.com/sass-eyeglass/eyeglass#writing-an-eyeglass-module-with-sass-files). See [#21](https://github.com/KittyGiraudel/sass-boilerplate/issues/21) for reference._ + +Reference: [Sass Guidelines](https://sass-guidelin.es/) > [Architecture](https://sass-guidelin.es/#architecture) > [Main file](https://sass-guidelin.es/#main-file) diff --git a/frontend/style/stylesheets/abstracts/README.md b/frontend/style/stylesheets/abstracts/README.md new file mode 100644 index 0000000..d6a6aef --- /dev/null +++ b/frontend/style/stylesheets/abstracts/README.md @@ -0,0 +1,17 @@ +# Abstracts + +The `abstracts/` folder gathers all Sass tools and helpers used across the project. Every global variable, function, mixin and placeholder should be put in here. + +The rule of thumb for this folder is that it should not output a single line of CSS when compiled on its own. These are nothing but Sass helpers. + +## \_index.scss + +With the deprecation of the `@import` keyword, we now use the `@use` and `@forward` keywords. Unlike with `@import`, adding abstracts with `@use` at the top of the `main.scss` file does not make them globally accessible to eveything else coming afterwards. + +Because of that we need to import those abstracts using `@use` keyword each time we need them inside the individual files, similar to how we would import utilities in the JavaScript world for that matter. + +This is where we need the `@forward` keyword. + +We can create an `_index.scss` file. In this file, we can collect all the abstract partials with the `@forward` keyword and re-export all abstract modules when we need them as a singular entry. + +Reference: [Sass Guidelines](https://sass-guidelin.es/) > [Architecture](https://sass-guidelin.es/#architecture) > [Abstracts folder](https://sass-guidelin.es/#abstracts-folder) diff --git a/frontend/style/stylesheets/abstracts/_functions.scss b/frontend/style/stylesheets/abstracts/_functions.scss new file mode 100644 index 0000000..c8d52a3 --- /dev/null +++ b/frontend/style/stylesheets/abstracts/_functions.scss @@ -0,0 +1,37 @@ +//! This file contains all application-wide Sass functions. + +/// Native `url(..)` function wrapper +/// @param {String} $base - base URL for the asset +/// @param {String} $type - asset type folder (e.g. `fonts/`) +/// @param {String} $path - asset path +/// @return {Url} +/// +@function asset($base, $type, $path) { + @return url($base + $type + $path); +} + +/// Returns URL to an image based on its path +/// @param {String} $path - image path +/// @param {String} $base [$base-url] - base URL +/// @return {Url} +/// @require $base-url +@function image($path, $base: $base-url) { + @return asset($base, 'images/', $path); +} + +/// Returns URL to a font based on its path +/// @param {String} $path - font path +/// @param {String} $base [$base-url] - base URL +/// @return {Url} +/// @require $base-url +@function font($path, $base: $base-url) { + @return asset($base, 'fonts/', $path); +} + +/// Scale a pixel value according to root font size +/// @param {Number} $px - pixel value +/// @return {Number} +/// @require $px +@function scaled-px($px) { + @return calc($px / 16px) * 1rem; +} diff --git a/frontend/style/stylesheets/abstracts/_index.scss b/frontend/style/stylesheets/abstracts/_index.scss new file mode 100644 index 0000000..2b9ef6a --- /dev/null +++ b/frontend/style/stylesheets/abstracts/_index.scss @@ -0,0 +1,3 @@ +@forward '../abstracts/functions'; +@forward '../abstracts/mixins'; +@forward '../abstracts/variables'; diff --git a/frontend/style/stylesheets/abstracts/_mixins.scss b/frontend/style/stylesheets/abstracts/_mixins.scss new file mode 100644 index 0000000..7b56086 --- /dev/null +++ b/frontend/style/stylesheets/abstracts/_mixins.scss @@ -0,0 +1,34 @@ +//! This file contains all application-wide Sass mixins. + +/// Event wrapper +/// @author Harry Roberts +/// @param {Bool} $self [false] - Whether or not to include current selector +/// @link https://twitter.com/csswizardry/status/478938530342006784 Original tweet from Harry Roberts +@mixin on-event($self: false) { + @if $self { + &, + &:hover, + &:active, + &:focus, + &:focus-within { + @content; + } + } + @else { + &:hover, + &:active, + &:focus, + &:focus-within { + @content; + } + } +} + +/// Make a context based selector a little more friendly +/// @author Kitty Giraudel +/// @param {String} $context +@mixin when-inside($context) { + #{$context} & { + @content; + } +} diff --git a/frontend/style/stylesheets/abstracts/_variables.scss b/frontend/style/stylesheets/abstracts/_variables.scss new file mode 100644 index 0000000..b5f32c7 --- /dev/null +++ b/frontend/style/stylesheets/abstracts/_variables.scss @@ -0,0 +1,48 @@ +//! This file contains all application-wide Sass variables. + +/// Regular font family +/// @type List +$text-font-stack: 'Open Sans', 'Helvetica Neue Light', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif !default; + +/// Code (monospace) font family +/// @type List +$code-font-stack: 'Courier New', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Monaco', monospace !default; + +/// Copy text color +/// @type Color +$text-color: rgb(220, 220, 220) !default; + +/// Main brand color +/// @type Color +$brand-color: rgb(229, 0, 80) !default; + +/// Light grey +/// @type Color +$light-grey: rgb(237, 237, 237) !default; + +/// Medium grey +/// @type Color +$mid-grey: rgb(153, 153, 153) !default; + +/// Dark grey +/// @type Color +$dark-grey: rgb(68, 68, 68) !default; + +/// Container's maximum width +/// @type Length +$max-width: 1180px !default; + +/// Breakpoints map +/// @prop {String} keys - Keys are identifiers mapped to a given length +/// @prop {Map} values - Values are actual breakpoints expressed in pixels +$breakpoints: ( + 'small': 320px, + 'medium': 768px, + 'large': 1024px, + ) !default; + +/// Relative or absolute URL where all assets are served from +/// @type String +/// @example scss - When using a CDN +/// $base-url: 'https://cdn.example.com/assets/'; +$base-url: '/assets/' !default; diff --git a/frontend/style/stylesheets/base/README.md b/frontend/style/stylesheets/base/README.md new file mode 100644 index 0000000..9967c7f --- /dev/null +++ b/frontend/style/stylesheets/base/README.md @@ -0,0 +1,5 @@ +# Base + +The `base/` folder holds what we might call the boilerplate code for the project. In there, you might find some typographic rules, and probably a stylesheet (that I’m used to calling `_base.scss`), defining some standard styles for commonly used HTML elements. + +Reference: [Sass Guidelines](https://sass-guidelin.es/) > [Architecture](https://sass-guidelin.es/#architecture) > [Base folder](https://sass-guidelin.es/#base-folder) diff --git a/frontend/style/stylesheets/base/_base.scss b/frontend/style/stylesheets/base/_base.scss new file mode 100644 index 0000000..f42ac26 --- /dev/null +++ b/frontend/style/stylesheets/base/_base.scss @@ -0,0 +1,47 @@ +//! This file contains very basic styles. + +@use '../abstracts'; + +// Set up a decent box model on the root element +html { + box-sizing: border-box; +} + +html, +body { + margin: 0; + width: 100vw; + height: 100vh; + overflow: hidden; +} + +body { + display: flex; + flex-direction: column; +} + +main { + flex: 1; + overflow: auto; +} + +// Make all elements from the DOM inherit from the parent box-sizing +// Since `*` has a specificity of 0, it does not override the `html` value +// making all elements inheriting from the root box-sizing value +// See: https://css-tricks.com/inheriting-box-sizing-probably-slightly-better-best-practice/ +*, +*::before, +*::after { + box-sizing: inherit; +} + +// Basic styles for links +a { + text-decoration: none; + color: abstracts.$brand-color; + + @include abstracts.on-event { + text-decoration: underline; + color: abstracts.$text-color; + } +} diff --git a/frontend/style/stylesheets/base/_fonts.scss b/frontend/style/stylesheets/base/_fonts.scss new file mode 100644 index 0000000..eede6c2 --- /dev/null +++ b/frontend/style/stylesheets/base/_fonts.scss @@ -0,0 +1 @@ +//! This file contains all @font-face declarations, if any. diff --git a/frontend/style/stylesheets/base/_helpers.scss b/frontend/style/stylesheets/base/_helpers.scss new file mode 100644 index 0000000..9204490 --- /dev/null +++ b/frontend/style/stylesheets/base/_helpers.scss @@ -0,0 +1,48 @@ +@use '../abstracts'; + +//! This file contains CSS helper classes. + +// Clear inner floats +.clearfix::after { + display: table; + clear: both; + content: ""; +} + +// Main content containers +// 1. Make the container full-width with a maximum width +// 2. Center it in the viewport +// 3. Leave some space on the edges, especially valuable on small screens +.container { + box-sizing: border-box; + margin-right: auto; // 2 + margin-left: auto; // 2 + padding-right: 20px; // 3 + padding-left: 20px; // 3 + width: 100%; // 1 + max-width: abstracts.$max-width; // 1 +} + +// Hide text while making it readable for screen readers +// 1. Needed in WebKit-based browsers because of an implementation bug; +// See: https://code.google.com/p/chromium/issues/detail?id=457146 +.hide-text { + padding: 0; // 1 + overflow: hidden; + text-indent: 101%; + white-space: nowrap; +} + +// Hide element while making it readable for screen readers +// Shamelessly borrowed from HTML5Boilerplate: +// https://github.com/h5bp/html5-boilerplate/blob/master/src/css/main.css#L119-L133 +.visually-hidden { + position: absolute; + margin: -1px; + border: 0; + padding: 0; + width: 1px; + height: 1px; + overflow: hidden; + clip: rect(0 0 0 0); +} diff --git a/frontend/style/stylesheets/base/_typography.scss b/frontend/style/stylesheets/base/_typography.scss new file mode 100644 index 0000000..3fe4be2 --- /dev/null +++ b/frontend/style/stylesheets/base/_typography.scss @@ -0,0 +1,7 @@ +@use '../abstracts'; + +// Basic typography style for copy text +body { + color: abstracts.$text-color; + font: normal 125% / 1.4 abstracts.$text-font-stack; +} diff --git a/frontend/style/stylesheets/components/README.md b/frontend/style/stylesheets/components/README.md new file mode 100644 index 0000000..5745a1e --- /dev/null +++ b/frontend/style/stylesheets/components/README.md @@ -0,0 +1,5 @@ +# Components + +For small components, there is the `components/` folder. While `layout/` is macro (defining the global wireframe), `components/` is more focused on widgets. It contains all kind of specific modules like a slider, a loader, a widget, and basically anything along those lines. There are usually a lot of files in components/ since the whole site/application should be mostly composed of tiny modules. + +Reference: [Sass Guidelines](https://sass-guidelin.es/) > [Architecture](https://sass-guidelin.es/#architecture) > [Components folder](https://sass-guidelin.es/#components-folder) diff --git a/frontend/style/stylesheets/components/_title-bar.scss b/frontend/style/stylesheets/components/_title-bar.scss new file mode 100644 index 0000000..21bf0aa --- /dev/null +++ b/frontend/style/stylesheets/components/_title-bar.scss @@ -0,0 +1,78 @@ +@use "../abstracts/functions" as *; + +#title-bar { + box-sizing: border-box; + display: flex; + flex-direction: row; + border-bottom: #000 solid scaled-px(1px); + background-color: hsl(0, 0%, 7%); + width: 100vw; + height: scaled-px(38px); + -webkit-app-region: drag; + user-select: none; +} + +#title-bar-content { + flex: 1; + height: 100%; +} + +#title-bar-buttons { + display: flex; + height: 100%; +} + +.title-bar-separator { + background-color: #3d3d3d; + width: scaled-px(1px); + height: 100%; +} + +.title-bar-button { + display: flex; + align-items: center; + border: none; + background-color: #131313; + width: scaled-px(50px); + height: 100%; + -webkit-app-region: no-drag; + svg { + margin: 0 auto; + width: scaled-px(18px); + height: scaled-px(18px); + + path { + color: #7f7f7f; + stroke-width: 2; + } + } +} + +.title-bar-button:hover { + background-color: #303030; + svg > path { + color: #fff; + } +} + +.title-bar-button:hover:active { + background-color: #3e3e3e; + svg > path { + color: #ddd; + } +} + +.title-bar-button-close { + @extend .title-bar-button; +} + +.title-bar-button-close:hover { + background-color: #f00; + svg > path { + color: #fff; + } +} + +.title-bar-button-close:hover:active { + background-color: #a00; +} diff --git a/frontend/style/stylesheets/layout/README.md b/frontend/style/stylesheets/layout/README.md new file mode 100644 index 0000000..8f3d535 --- /dev/null +++ b/frontend/style/stylesheets/layout/README.md @@ -0,0 +1,5 @@ +# Layout + +The `layout/` folder contains everything that takes part in laying out the site or application. This folder could have stylesheets for the main parts of the site (header, footer, navigation, sidebar…), the grid system or even CSS styles for all the forms. + +Reference: [Sass Guidelines](https://sass-guidelin.es/) > [Architecture](https://sass-guidelin.es/#architecture) > [Layout folder](https://sass-guidelin.es/#layout-folder) diff --git a/frontend/style/stylesheets/main.scss b/frontend/style/stylesheets/main.scss new file mode 100644 index 0000000..88863da --- /dev/null +++ b/frontend/style/stylesheets/main.scss @@ -0,0 +1,18 @@ +@charset "UTF-8"; + +// 1. Vendors +@use 'vendors/normalize'; + +// 2. Base stuff +@use 'base/base'; +@use 'base/fonts'; +@use 'base/helpers'; +@use 'base/typography'; + +// 3. Layout-related sections + +// 4. Components +@use 'components/title-bar'; + +// 6. Themes +@use 'themes/default'; diff --git a/frontend/style/stylesheets/themes/README.md b/frontend/style/stylesheets/themes/README.md new file mode 100644 index 0000000..4a4a0b5 --- /dev/null +++ b/frontend/style/stylesheets/themes/README.md @@ -0,0 +1,7 @@ +# Theme + +On large sites and applications, it is not unusual to have different themes. There are certainly different ways of dealing with themes but I personally like having them all in a `themes/` folder. + +*Note — This is very project-specific and is likely to be non-existent on many projects.* + +Reference: [Sass Guidelines](https://sass-guidelin.es/) > [Architecture](https://sass-guidelin.es/#architecture) > [Themes folder](https://sass-guidelin.es/#themes-folder) diff --git a/frontend/style/stylesheets/themes/_default.scss b/frontend/style/stylesheets/themes/_default.scss new file mode 100644 index 0000000..ea49e59 --- /dev/null +++ b/frontend/style/stylesheets/themes/_default.scss @@ -0,0 +1,6 @@ +//! When having several themes, this file contains everything related to the +//! default one. + +body { + background-color: #000; +} diff --git a/frontend/style/stylesheets/vendors/README.md b/frontend/style/stylesheets/vendors/README.md new file mode 100644 index 0000000..50f4f75 --- /dev/null +++ b/frontend/style/stylesheets/vendors/README.md @@ -0,0 +1,7 @@ +# Vendors + +Most projects will have a `vendors/` folder containing all the CSS files from external libraries and frameworks – Normalize, Bootstrap, jQueryUI, FancyCarouselSliderjQueryPowered, and so on. Putting those aside in the same folder is a good way to say “Hey, this is not from me, not my code, not my responsibility”. + +If you have to override a section of any vendor, I recommend you have an 8th folder called `vendors-extensions/` in which you may have files named exactly after the vendors they overwrite. For instance, `vendors-extensions/_bootstrap.scss` is a file containing all CSS rules intended to re-declare some of Bootstrap’s default CSS. This is to avoid editing the vendor files themselves, which is generally not a good idea. + +Reference: [Sass Guidelines](https://sass-guidelin.es/) > [Architecture](https://sass-guidelin.es/#architecture) > [Vendors folder](https://sass-guidelin.es/#vendors-folder) diff --git a/frontend/style/stylesheets/vendors/_normalize.scss b/frontend/style/stylesheets/vendors/_normalize.scss new file mode 100644 index 0000000..2ee75fd --- /dev/null +++ b/frontend/style/stylesheets/vendors/_normalize.scss @@ -0,0 +1,248 @@ +//! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css + +// Document ========================================================================== + +// 1. Correct the line height in all browsers. +// 2. Prevent adjustments of font size after orientation changes in iOS. +html { + line-height: 1.15; // 1 + -webkit-text-size-adjust: 100%; // 2 +} + +// Sections ========================================================================== + +// Remove the margin in all browsers. +body { + margin: 0; +} + +// Render the `main` element consistently in IE. +main { + display: block; +} + +// Correct the font size and margin on `h1` elements within `section` and +// `article` contexts in Chrome, Firefox, and Safari. +h1 { + margin: 0.67em 0; + font-size: 2em; +} + +// Grouping content ========================================================================== + +// 1. Add the correct box sizing in Firefox. +// 2. Show the overflow in Edge and IE. +hr { + box-sizing: content-box; // 1 + height: 0; // 1 + overflow: visible; // 2 +} + +// 1. Correct the inheritance and scaling of font size in all browsers. +// 2. Correct the odd `em` font sizing in all browsers. +pre { + font-family: monospace, monospace; // 1 + font-size: 1em; // 2 +} + +// Text-level semantics ========================================================================== + +// Remove the gray background on active links in IE 10. +a { + background-color: transparent; +} + +// 1. Remove the bottom border in Chrome 57- +// 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. +abbr[title] { + border-bottom: none; // 1 + text-decoration: underline dotted; // 2 +} + +// Add the correct font weight in Chrome, Edge, and Safari. +b, +strong { + font-weight: bolder; +} + +// 1. Correct the inheritance and scaling of font size in all browsers. +// 2. Correct the odd `em` font sizing in all browsers. +code, +kbd, +samp { + font-family: monospace, monospace; // 1 + font-size: 1em; // 2 +} + +// Add the correct font size in all browsers. +small { + font-size: 80%; +} + +// Prevent `sub` and `sup` elements from affecting the line height in +// all browsers. +sub, +sup { + position: relative; + vertical-align: baseline; + line-height: 0; + font-size: 75%; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +// Embedded content ========================================================================== + +// Remove the border on images inside links in IE 10. +img { + border-style: none; +} + +// Forms ========================================================================== + +// 1. Change the font styles in all browsers. +// 2. Remove the margin in Firefox and Safari. +button, +input, +optgroup, +select, +textarea { + margin: 0; // 2 + line-height: 1.15; // 1 + font-family: inherit; // 1 + font-size: 100%; // 1 +} + +// Show the overflow in IE. +// 1. Show the overflow in Edge. +button, +input { + // 1 + overflow: visible; +} + +// Remove the inheritance of text transform in Edge, Firefox, and IE. +// 1. Remove the inheritance of text transform in Firefox. +button, +select { + // 1 + text-transform: none; +} + +// Correct the inability to style clickable types in iOS and Safari. +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; + appearance: button; +} + +// Remove the inner border and padding in Firefox. +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +// Restore the focus styles unset by the previous rule. +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +// Correct the padding in Firefox. +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +// 1. Correct the text wrapping in Edge and IE. +// 2. Correct the color inheritance from `fieldset` elements in IE. +// 3. Remove the padding so developers are not caught out when they zero out +// `fieldset` elements in all browsers. +legend { + box-sizing: border-box; // 1 + display: table; // 1 + padding: 0; // 3 + max-width: 100%; // 1 + white-space: normal; // 1 + color: inherit; // 2 +} + +// Add the correct vertical alignment in Chrome, Firefox, and Opera. +progress { + vertical-align: baseline; +} + +// Remove the default vertical scrollbar in IE 10+. +textarea { + overflow: auto; +} + +// 1. Add the correct box sizing in IE 10. +// 2. Remove the padding in IE 10. +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; // 1 + padding: 0; // 2 +} + +// Correct the cursor style of increment and decrement buttons in Chrome. +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +// 1. Correct the odd appearance in Chrome and Safari. +// 2. Correct the outline style in Safari. +[type="search"] { + outline-offset: -2px; // 2 + appearance: textfield; // 1 + -webkit-appearance: textfield; // 1 +} + +// Remove the inner padding in Chrome and Safari on macOS. +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +// 1. Correct the inability to style clickable types in iOS and Safari. +// 2. Change font properties to `inherit` in Safari. +::-webkit-file-upload-button { + font: inherit; // 2 + -webkit-appearance: button; // 1 +} + +// Interactive ========================================================================== + +// Add the correct display in Edge, IE 10+, and Firefox. +details { + display: block; +} + +// Add the correct display in all browsers. +summary { + display: list-item; +} + +// Misc ========================================================================== + +// Add the correct display in IE 10+. +template { + display: none; +} + +// Add the correct display in IE 10. +[hidden] { + display: none; +} diff --git a/glue/Cargo.toml b/glue/Cargo.toml index f235a58..6445e24 100644 --- a/glue/Cargo.toml +++ b/glue/Cargo.toml @@ -1,50 +1,42 @@ [package] name = "chipbox-glue" -version = "0.0.0" +version = "0.1.0" edition = "2021" [features] backend = [ - # workspace deps - "dep:chipbox-backend-lib", # deps "dep:tauri", "dep:tokio", + "dep:parking_lot", ] frontend = [ # deps - "dep:yew", + "dep:leptos", "dep:wasm-bindgen", "dep:wasm-bindgen-futures", - "dep:wasm-timer", + "dep:web-time", "dep:serde-wasm-bindgen", - "dep:js-sys", - "dep:web-sys", - "dep:tauri-sys", ] [dependencies] ## backend only deps -# workspace deps -chipbox-backend-lib = { workspace = true, optional = true } # deps tauri = { workspace = true, optional = true } tokio = { workspace = true, optional = true } +parking_lot = { workspace = true, optional = true } ## fronted only deps # deps -yew = { workspace = true, optional = true } +leptos = { workspace = true, optional = true } wasm-bindgen = { workspace = true, optional = true } wasm-bindgen-futures = { workspace = true, optional = true } -wasm-timer = { workspace = true, optional = true } +web-time = { workspace = true, optional = true } serde-wasm-bindgen = { workspace = true, optional = true } -js-sys = { workspace = true, optional = true } -web-sys = { workspace = true, optional = true } -tauri-sys = { workspace = true, optional = true } ## common deps -# workspace deps -chipbox-common = { workspace = true } # deps +chipbox-common = { workspace = true } tracing = { workspace = true } serde = { workspace = true } +thiserror = { workspace = true } diff --git a/glue/src/cmd.rs b/glue/src/cmd.rs new file mode 100644 index 0000000..acc4791 --- /dev/null +++ b/glue/src/cmd.rs @@ -0,0 +1,8 @@ +//! Commands callable from the frontend. +//! TODO: module comment too vague + +pub mod create_project; +pub mod loaded_project; + +#[cfg(feature = "frontend")] +pub use {create_project::frontend::create_project, loaded_project::frontend::loaded_project}; diff --git a/glue/src/cmd/create_project.rs b/glue/src/cmd/create_project.rs new file mode 100644 index 0000000..ccc287a --- /dev/null +++ b/glue/src/cmd/create_project.rs @@ -0,0 +1,52 @@ +//! Create a new project on the backend. + +#[cfg(feature = "frontend")] +pub use frontend::Args; + +#[cfg(feature = "backend")] +pub(crate) mod backend { + use crate::loaded_project::LoadedProject; + use chipbox_common::project::latest::{ + song::meta::{Author, SongMeta}, + Project, + }; + + /// Creates a project with the given song metadata and current timestamp. + /// The project is stored in the [`LoadedProject`] state in the [tauri application](tauri::App). + #[tauri::command(rename_all = "snake_case")] + pub(crate) async fn create_project( + state: tauri::State<'_, LoadedProject>, + name: String, + description: Option, + authors: Vec, + ) -> Result { + let project = Project::new(SongMeta::new_now(name, description, authors)); + let LoadedProject(mutex) = state.inner(); + *mutex.lock() = Some(project.clone()); + Ok(project) + } +} + +#[cfg(feature = "frontend")] +pub(crate) mod frontend { + use crate::tauri_api::core::{invoke, InvokeResult}; + use chipbox_common::project::latest::{song::meta::Author, Project}; + + /// Arguments for the [`create_project`] command. + #[derive(serde::Serialize, Debug)] + pub struct Args<'a> { + /// The name of the project. + pub name: &'a str, + /// A short description of the project. + pub description: Option<&'a str>, + /// The authors of the project. + pub authors: &'a [Author], + } + + /// Request the backend to create a new project with the given arguments. + /// Returns a copy of the project on success. + pub async fn create_project<'a>(args: &'a Args<'a>) -> InvokeResult<'a, Project, Args<'a>> { + // No need to unwrap `Result<_, !>` here, since serde seems to omit it. + invoke("create_project", args).await + } +} diff --git a/glue/src/cmd/loaded_project.rs b/glue/src/cmd/loaded_project.rs new file mode 100644 index 0000000..1406e07 --- /dev/null +++ b/glue/src/cmd/loaded_project.rs @@ -0,0 +1,31 @@ +//! Retrieve the currently loaded project. + +#[cfg(feature = "backend")] +pub(crate) mod backend { + use crate::loaded_project::LoadedProject; + use chipbox_common::project::latest::Project; + + /// Return a copy of the currently loaded project state. + #[tauri::command(rename_all = "snake_case")] + pub(crate) async fn loaded_project( + state: tauri::State<'_, LoadedProject>, + ) -> Result, !> { + let LoadedProject(mutex) = state.inner(); + let loaded_project = mutex.lock().clone(); + tracing::info!("frontend requested current project state: {loaded_project:?}"); + Ok(loaded_project) + } +} + +#[cfg(feature = "frontend")] +pub(crate) mod frontend { + use crate::tauri_api::core::{invoke, InvokeResult}; + use chipbox_common::project::latest::Project; + + /// Request the backend to retrieve a copy of the currently loaded project, if applicable. + /// Returns `None` if no project is loaded. + pub async fn loaded_project() -> InvokeResult<'static, Option, ()> { + // No need to unwrap `Result<_, !>` here, since serde seems to omit it. + invoke("loaded_project", &()).await + } +} diff --git a/glue/src/handler.rs b/glue/src/handler.rs index 630c73e..aa23e56 100644 --- a/glue/src/handler.rs +++ b/glue/src/handler.rs @@ -1,18 +1,23 @@ -//! The `handler` module contains the `add_to_builder` function. -//! It is responsible for generating an invoke handler and adding it to a `tauri::Builder`. - -use crate::msg::{__cmd__frontend_msg, frontend_msg}; +//! This module defines [`BuilderGlue`], an extension trait for [`tauri::Builder`]. +//! +//! The trait allows you to add commands defined in the [`cmd`](crate::cmd) module to a [`tauri`] application. +use crate::cmd::{create_project::backend::*, loaded_project::backend::*}; +/// Extension trait for [`tauri::Builder`]. +/// +/// This trait is responsible for generating a `tauri::ipc::Invoke` handler and adding it to a [`tauri::Builder`]. +/// The generated handler contains commands defined by the [`cmd`](crate::cmd) module. +/// They are required for backend-frontend interoperability. pub trait BuilderGlue { - /// Generates an invoke handler and adds it to a `tauri::Builder`. + /// Modifies `Self` to use a handler supplied by the implementation of this function. /// - /// The handler contains commands required for backend and frontend interop. + /// The generated handler contains commands defined by the [`cmd`](crate::cmd) module. fn glue_invoke_handler(self) -> Self; } -/// Implement `BuilderGlue` for `tauri::Builder`. +/// Any commands added to [`crate::cmd`] must be added here for `tauri` to recognize them! impl BuilderGlue for tauri::Builder { fn glue_invoke_handler(self) -> Self { - self.invoke_handler(tauri::generate_handler![frontend_msg]) + self.invoke_handler(tauri::generate_handler![create_project, loaded_project]) } } diff --git a/glue/src/invoke.rs b/glue/src/invoke.rs deleted file mode 100644 index c9a20ee..0000000 --- a/glue/src/invoke.rs +++ /dev/null @@ -1,140 +0,0 @@ -//! Defines a frontend interface for calling backend commands. - -mod infallible; - -use infallible::Infallible; -use wasm_bindgen::prelude::*; -use wasm_timer::Instant; - -#[wasm_bindgen] -extern "C" { - #[wasm_bindgen(catch)] - #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "tauri"])] - /// Run a `tauri` commmand on the backend. - /// The command has to be registered as a `tauri` command in the command handler. - async fn invoke(cmd: &str, args: JsValue) -> Result; -} - -/// Call a backend command on `chipbox-backend` and retrieve the returned value. -/// The command has to be registered as a `tauri` command. -/// -/// Returns the exact value retrieved from the backend command. -/// If the command returns a `Result`, use `invoke_query` instead. -/// Failure to do so may cause a type-mismatch, which will result in a panic. -/// -/// # Panics -/// - Panics if the function signature (note the generic parameters) does not -/// match that of the specified backend command. This also applies if the -/// command natively returns a `Result`. -/// `invoke` returns a JS Promise error if the command does not exist. -/// - Panics if the command name `cmd` does not match any command on the backend. -pub(crate) async fn invoke_query_infallible( - cmd: &str, - args: &Args, -) -> T -where - T: for<'de> serde::Deserialize<'de> + std::fmt::Debug, - Args: serde::Serialize + std::fmt::Debug, -{ - // Invoke query with private `Infallible` type. - invoke_query::(cmd, args) - .await - // If this happens, you're misusing the function. - .expect("infallible") -} - -/// Call a backend command on `chipbox-backend` and retrieve the result. -/// The command has to be registered as a `tauri` command. -/// -/// Returns a `Result`, as defined in the backend command's function signature. -/// If the command does not return a `Result`, use `invoke_query_infallible` instead. -/// -/// # TODO -/// - Explain why we can't use `invoke_query` for infallible queries. -/// -/// # Panics -/// - Panics if the function signature (note the generic parameters) does not -/// match that of the specified backend command. -/// - Panics if the command name `cmd` does not match any command on the backend. -pub(crate) async fn invoke_query( - cmd: &str, - args: &Args, -) -> Result -where - T: for<'de> serde::Deserialize<'de> + std::fmt::Debug, - E: for<'de> serde::Deserialize<'de> + std::fmt::Debug, - Args: serde::Serialize + std::fmt::Debug, -{ - // Format debug command name. - let cmd_pretty = format!("{cmd}({args:?})"); - - // Start perf timer. - tracing::trace!("{cmd_pretty}: Begin query."); - let instant_begin = Instant::now(); - - // Serialize query arguments. - let args = serde_wasm_bindgen::to_value(args) - // Invalid arguments struct. - // Verify that serialization is set up correctly. - .expect("{cmd_pretty}: Unable to serialize query arguments."); - // Invoke backend command. - let backend_result = invoke(cmd, args).await; - - // End perf timer. - let elapsed = Instant::now().duration_since(instant_begin); - tracing::trace!("{cmd_pretty}: elapsed: {elapsed:?}"); - tracing::trace!("{cmd_pretty}: Response: `{backend_result:?}`"); - - // Handle response. - match backend_result { - Ok(js_value) => { - // Deserialize `Ok(T)` response. - let value = serde_wasm_bindgen::from_value(js_value) - .unwrap_or_else(|err| { - // Type mismatch. - tracing::error!( - "{cmd_pretty}: Unable to deserialize `Ok(T)`: {err}" - ); - panic!( - "{cmd_pretty}: Invalid response - type mismatch. \ - Unable to deserialize `Ok(T)`. \ - Does the function signature match `{cmd}`?", - ); - }); - // Deserialization ok. - tracing::info!("{cmd_pretty} -> Ok({value:?})"); - Ok(value) - } - Err(js_value) => { - // Deserialize error response. - // `js_value` is cloned so that we can later print the error trace - // in case of a missing command handler. - let value = serde_wasm_bindgen::from_value(js_value.clone()) - .unwrap_or_else(|err| { - // Error cause may be either type mismatch or missing - // command handler. A missing command handler will - // cause `invoke` to return a JS Promise error. - tracing::error!( - "{cmd_pretty}: Unable to deserialize error response: {err}" - ); - // The fallback is needed due to command jank. - // See comment at scope root. - tracing::warn!( - "{cmd_pretty}: Unable to deserialize error value. Falling back to `Err(JsValue)`" - ); - tracing::warn!( - "{cmd_pretty}: Printing error trace from the captured `JsValue`..." - ); - tracing::error!("{cmd_pretty}: Root cause was `{js_value:?}`"); - panic!( - "{cmd_pretty}: Invalid response - type mismatch. \ - Does the function signature match `{cmd}`? \ - Is the command available from the command handler?" - ); - }); - // Deserialization ok. - tracing::error!("{cmd_pretty} -> Err({value:?})"); - Err(value) - } - } -} diff --git a/glue/src/invoke/infallible.rs b/glue/src/invoke/infallible.rs deleted file mode 100644 index a635901..0000000 --- a/glue/src/invoke/infallible.rs +++ /dev/null @@ -1,2 +0,0 @@ -#[derive(serde::Serialize, serde::Deserialize, Debug)] -pub enum Infallible {} diff --git a/glue/src/lib.rs b/glue/src/lib.rs index e110402..2345150 100644 --- a/glue/src/lib.rs +++ b/glue/src/lib.rs @@ -1,12 +1,19 @@ -#![feature(never_type)] - -pub use chipbox_common as common; +//! Glue code used for communication between the frontend and backend. -#[cfg(feature = "backend")] -pub use chipbox_backend_lib as backend_lib; +#![warn( + missing_docs, + unreachable_pub, + missing_debug_implementations, + rust_2018_idioms +)] +#![deny(unsafe_op_in_unsafe_fn)] +#![feature(never_type)] #[cfg(feature = "backend")] pub mod handler; +#[cfg(feature = "backend")] +pub mod loaded_project; #[cfg(feature = "frontend")] -mod invoke; -pub mod msg; +pub(crate) mod tauri_api; + +pub mod cmd; diff --git a/glue/src/loaded_project.rs b/glue/src/loaded_project.rs new file mode 100644 index 0000000..57f40bc --- /dev/null +++ b/glue/src/loaded_project.rs @@ -0,0 +1,13 @@ +//! Tauri application state for a project that may be loaded and unloaded. + +use chipbox_common::project::latest::Project; +use parking_lot::Mutex; + +/// A wrapper around a [`Project`] that is stored in the [tauri application's](tauri::App) state manager. +/// +/// The project is an [`Option`] because the application does not need to have a project loaded to run. +/// This may happen when the application is first started, for example. +/// +/// The option is wrapped in a [`Mutex`] to enable concurrent mutation. +#[derive(Default, Debug)] +pub struct LoadedProject(pub Mutex>); diff --git a/glue/src/msg.rs b/glue/src/msg.rs deleted file mode 100644 index bab8cdb..0000000 --- a/glue/src/msg.rs +++ /dev/null @@ -1,71 +0,0 @@ -use crate::common; -use common::app::msg::FrontendMsg; - -#[cfg(feature = "backend")] -use {crate::backend_lib::ThreadMsg, tauri::async_runtime::Sender}; - -/// Wrapper around a `async_runtime::Sender` for the backend lib app thread. -/// Used as a managed state in the tauri app. -#[cfg(feature = "backend")] -pub struct BackendAppTx(pub Sender); - -#[cfg(feature = "backend")] -impl BackendAppTx { - fn inner_tx_from_app(app: &tauri::AppHandle) -> &Sender - where - R: tauri::Runtime, - { - use tauri::Manager as _; - let BackendAppTx(tx) = app - .state::() - .inner(); - tx - } -} - -/// Send frontend message to backend app thread. -#[cfg(feature = "backend")] -#[tauri::command] -pub(crate) async fn frontend_msg( - app: tauri::AppHandle, - msg: FrontendMsg, -) -> bool -where - R: tauri::Runtime, -{ - use tokio::sync::mpsc::error::SendError; - - tracing::trace!("Forwarding frontend message to backend thread: {:?}", msg); - - // Send frontend message to backend thread. - let result = BackendAppTx::inner_tx_from_app(&app) - .send(ThreadMsg::Frontend(msg)) - .await; - - // Handle result. - match result { - Ok(()) => true, - Err(SendError(msg)) => { - // Fail gracefully. - tracing::error!( - "Failed to deliver frontend message to backend. Channel was closed. Original message: {msg:?}", - ); - false - } - } -} - -#[cfg(feature = "frontend")] -#[must_use] -pub async fn send(msg: FrontendMsg) -> bool { - #[derive(serde::Serialize, Debug)] - struct FrontendMsgCmdArgs { - msg: FrontendMsg, - } - - crate::invoke::invoke_query_infallible::( - "frontend_msg", - &FrontendMsgCmdArgs { msg }, - ) - .await -} diff --git a/glue/src/tauri_api.rs b/glue/src/tauri_api.rs new file mode 100644 index 0000000..7aee382 --- /dev/null +++ b/glue/src/tauri_api.rs @@ -0,0 +1,6 @@ +//! Relevant [`wasm_bindgen`] definitions and rusty wrappers from the [Tauri JavaScript API](https://v2.tauri.app/reference/javascript/api/). +//! +//! This may be replaced by a [`tauri`] provided crate in the future. +//! Definitions rely on the API remaining stable over time. + +pub(crate) mod core; diff --git a/glue/src/tauri_api/core.rs b/glue/src/tauri_api/core.rs new file mode 100644 index 0000000..876c397 --- /dev/null +++ b/glue/src/tauri_api/core.rs @@ -0,0 +1,80 @@ +//! Relevant definitions from the [`core`](https://v2.tauri.app/reference/javascript/api/namespacecore/) +//! namespace of the [Tauri JavaScript API](https://v2.tauri.app/reference/javascript/api/). + +use serde::{Deserialize, Serialize}; +use std::fmt::Debug; +use wasm_bindgen::prelude::*; +use web_time::Instant; + +#[wasm_bindgen] +unsafe extern "C" { + /// https://v2.tauri.app/reference/javascript/api/namespacecore/#invoke + /// + /// Run a `tauri` commmand on the backend. + /// The command has to be registered as a `tauri` command in the command handler. + /// + /// # Safety + /// This function is safe to execute as long as the function signature matches + /// the [Tauri JavaScript API](https://v2.tauri.app/reference/javascript/api/). + #[wasm_bindgen(catch)] + #[wasm_bindgen(js_namespace = ["window", "__TAURI__", "core"], js_name = "invoke")] + async unsafe fn js_invoke(cmd: &str, args: JsValue) -> Result; +} + +pub(crate) type InvokeResult<'a, T, Args> = std::result::Result>; + +/// An error encountered while invoking a command on the backend. +#[derive(thiserror::Error, Debug)] +pub enum InvokeError<'a, Args: Debug> { + /// Encountered a JS exception while executing the command. + #[error("backend returned a js exception: {0:?}")] + Exception(JsValue), + /// Unable to serialize arguments provided to the command. + #[error("unable to serialize arguments `{args:?}`: {e}")] + Serialize { + e: serde_wasm_bindgen::Error, + args: &'a Args, + }, + /// Unable to deserialize the response returned by the backend. + #[error("unable to deserialize response: {0}")] + Deserialize(serde_wasm_bindgen::Error), +} + +/// Invoke a command on the backend. +/// +/// The command is assumed to be registered as a [`tauri`] command in the command handler. +/// See the [`handler`](crate::handler) module for more information. +/// +/// May return an error if serialization or deserialization fails, or if a JavaScript exception occurs. +/// This can happen if, for example, the function attempts to call a command that is not registered as a `tauri` command. +pub(crate) async fn invoke<'a, Args, T>( + cmd: &'static str, + args: &'a Args, +) -> Result> +where + Args: Serialize + Debug, + T: for<'de> Deserialize<'de> + Debug, +{ + tracing::debug!("{cmd}({args:?})"); + // Serialize query arguments. + let js_args = + serde_wasm_bindgen::to_value(args).map_err(|e| InvokeError::Serialize { args, e })?; + + // Start perf timer. + let instant_begin = Instant::now(); + // Invoke backend command. + // Safety: safe as long as the API remains stable. + let js_response = unsafe { js_invoke(cmd, js_args) }.await; + // End perf timer. + let elapsed = Instant::now().duration_since(instant_begin); + tracing::debug!("{cmd}({args:?}): done in {elapsed:?}"); + + // Deserialize response. + tracing::trace!("{cmd}({args:?}): js returned {js_response:?}"); + let js_value = js_response.map_err(InvokeError::Exception)?; + let value = serde_wasm_bindgen::from_value(js_value).map_err(InvokeError::Deserialize)?; + tracing::trace!("{cmd}({args:?}): deserializes into {value:?}"); + // Return result. + tracing::info!("{cmd}({args:?}) -> {value:?}"); + Ok(value) +} diff --git a/index.html b/index.html deleted file mode 100644 index 2deafd2..0000000 --- a/index.html +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - chipbox - - - - - - diff --git a/rustfmt.toml b/rustfmt.toml index 7d834fd..497b17b 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,3 +1,4 @@ -chain_width = 30 -max_width = 80 -imports_granularity = "Module" +edition = "2021" +format_code_in_doc_comments = true +normalize_comments = true +normalize_doc_attributes = true diff --git a/scss-input/_buttons.scss b/scss-input/_buttons.scss deleted file mode 100644 index 3ac0ee2..0000000 --- a/scss-input/_buttons.scss +++ /dev/null @@ -1,4 +0,0 @@ -button { - background-color: #181818; - margin: 0.5rem; -} diff --git a/scss-input/_components.scss b/scss-input/_components.scss deleted file mode 100644 index 8effcf6..0000000 --- a/scss-input/_components.scss +++ /dev/null @@ -1,2 +0,0 @@ -@use "components/spinner"; -@use "components/panel"; diff --git a/scss-input/_defaults.scss b/scss-input/_defaults.scss deleted file mode 100644 index a031a27..0000000 --- a/scss-input/_defaults.scss +++ /dev/null @@ -1,10 +0,0 @@ -@use "text"; - -body { - background-color: hsl(0, 0%, 10%); - margin: 0; -} - -:root { - font-size: 16px; -} diff --git a/scss-input/_text.scss b/scss-input/_text.scss deleted file mode 100644 index f63786b..0000000 --- a/scss-input/_text.scss +++ /dev/null @@ -1,93 +0,0 @@ -h1, -h2, -h3, -h4, -h5, -h6 { - @extend .header; -} -h1 { - @extend .primary; -} -h2 { - @extend .secondary; -} -h3, -h4, -h5, -h6 { - @extend .tertiary; -} - -p, -li { - @extend .text; - margin: 0.5rem; -} - -code { - @extend .code; -} - -.code { - @extend .text; - @extend .mono; - margin: 1rem; -} - -.text { - @extend .sans; - font-size: 1rem; - font-weight: 400; - line-height: 1.15rem; - -webkit-user-select: none; - user-select: none; -} -.text.left { - text-align: left; -} -.text.right { - text-align: right; -} -.text.center { - text-align: center; -} -.text.primary { - color: hsl(0, 0%, 80%); -} -.text.secondary { - color: hsl(0, 0%, 70%); -} -.text.tertiary { - color: hsl(0, 0%, 50%); -} -@import "https://fonts.googleapis.com/css?family=Lato:300,400,600,700"; -.text.sans { - font-family: "Lato", sans-serif; -} -@import "https://fonts.googleapis.com/css?family=IBM Plex Mono:300,400,600,700"; -.text.mono { - font-family: "IBM Plex Mono", monospace; -} - -.header { - @extend .text; - margin: 0.5rem; -} -.header.primary { - font-size: 1.5rem; - font-weight: 500; - line-height: 1.5rem; -} -.header.secondary { - font-size: 1.5rem; - font-weight: 500; - line-height: 1.5rem; -} -// .header.tertiary {} -.header.title { - font-size: 2.5rem; - line-height: 2.5rem; - margin-top: 1rem; - margin-bottom: 1rem; -} diff --git a/scss-input/_utils.scss b/scss-input/_utils.scss deleted file mode 100644 index 2113e46..0000000 --- a/scss-input/_utils.scss +++ /dev/null @@ -1,15 +0,0 @@ -.drop-shadow { - filter: drop-shadow(0rem 0.45rem 0.25rem hsla(0, 0%, 0%, 0.5)); -} - -.drop-shadow.primary { - filter: drop-shadow(0.25rem 0.35rem 0.15rem hsla(0, 0%, 0%, 0.95)); -} - -.drop-shadow.secondary { - filter: drop-shadow(0rem 0.45rem 0.25rem hsla(0, 0%, 0%, 0.5)); -} - -.drop-shadow.tertiary { - filter: drop-shadow(0rem 0.25rem 0.25rem hsla(0, 0%, 0%, 0.25)); -} diff --git a/scss-input/components/_panel.scss b/scss-input/components/_panel.scss deleted file mode 100644 index 79427b0..0000000 --- a/scss-input/components/_panel.scss +++ /dev/null @@ -1,185 +0,0 @@ -@use "../text"; -@use "../utils"; - -#editor { - display: flex; - flex-wrap: wrap; - flex-direction: column; -} - -#main-panel { - flex: 6 1 20rem; -} -#right-panel { - flex: 1 3 20rem; -} -#main-row { - flex: 3 1 30rem; - display: flex; - flex-wrap: wrap; - flex-direction: row; -} -#bottom-panel { - flex: 1 2 20rem; -} - -.panel-root { - border-width: 0.0625rem; - border-color: hsla(0, 0%, 0%, 1); - background-color: hsl(0, 0%, 20%); - border-style: solid; - display: flex; -} - -.panel-container { - margin: 0.5rem; - flex: 1; - display: flex; - flex-direction: column; - overflow: clip; -} - -.panel-header { - position: relative; - display: flex; - flex-wrap: nowrap; - align-items: flex-end; - height: 2.25rem; - margin-right: 3rem; -} - -.panel-header-overflow-x { - position: relative; - display: flex; - flex-direction: row; - flex-wrap: nowrap; - align-items: flex-end; - overflow-x: scroll; -} - -.panel-header-overflow-clip { - width: 100%; - position: absolute; - overflow: clip; -} - -.panel-header-overflow-x::-webkit-scrollbar { - display: none; -} - -.panel-header-gradient { - opacity: 0; - transition: opacity 1s cubic-bezier(0.25, 0.75, 0.25, 0.75); - z-index: 3; - pointer-events: none; - display: block; - position: absolute; - max-width: 3rem; - min-width: 0; - width: 49.5%; - top: 0; - height: calc(100% - 0.125rem); -} - -.panel-header-gradient.active { - opacity: 1; - transition: opacity 1s cubic-bezier(0.25, 0.75, 0.25, 0.75); -} - -.panel-header-gradient.right { - @extend .panel-header-gradient; - right: 0; - background: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, hsl(0, 0%, 20%) 99%); -} - -.panel-header-gradient.left { - @extend .panel-header-gradient; - left: 0; - background: linear-gradient(to left, rgba(0, 0, 0, 0) 0%, hsl(0, 0%, 20%) 99%); -} - -.panel-tab { - margin: 0; - position: relative; - border-style: solid; - border-width: 0.125rem; - border-color: hsl(0, 0%, 35%); - border-radius: 0.5rem 0.5rem 0 0; - background-color: hsl(0, 0%, 14%); -} - -.panel-tab.inactive { - height: 2rem; - border-color: hsl(0, 0%, 12%); - background-color: hsl(0, 0%, 12%); - transition: - height 0.15s cubic-bezier(0.12, 0, 0.39, 0), - border-radius 0.1s cubic-bezier(0.12, 0, 0.39, 0); -} - -.panel-tab.active { - @extend .drop-shadow, .secondary; - z-index: 2; - border-bottom-width: 0; - height: 2.25rem; - transition: - height 0.15s cubic-bezier(1, 0.5, 0.16, 1), - border-radius 0.15s cubic-bezier(0.85, 0, 0.15, 1), - background-color 0.015s cubic-bezier(0.85, 0, 0.15, 1); -} - -.panel-tab.inactive:nth-child(n + 2) { - border-top-left-radius: 0; -} - -.panel-tab.inactive:has(+ .panel-tab) { - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.panel-tab.inactive:nth-child(1) { - border-top-left-radius: 0.5rem; -} - -.panel-tab.inactive:hover { - @extend .text, .secondary; - background-color: hsl(0, 0%, 15%); - border-color: hsl(0, 0%, 15%); - transition: all 0.0125s cubic-bezier(0.85, 0, 0.15, 1); -} - -.panel-tab.active > .panel-tab-title { - @extend .primary; -} - -.panel-tab.inactive > .panel-tab-title { - @extend .tertiary; -} - -.panel-tab.inactive:hover > .panel-tab-title { - @extend .secondary; -} - -.panel-tab-title { - @extend .text; - margin: 0 0.25rem; - margin-right: 2.5rem; - text-align: left; - text-wrap: nowrap; -} - -.panel-content { - z-index: 0; - position: relative; - display: inline-block; - margin: 0; - top: calc(-1 * 0.125rem); - flex: 1; - - border-style: solid; - border-width: 0.125rem; - border-color: hsl(0, 0%, 35%); - border-radius: 0 0.5rem 0.5rem; - - background-color: hsl(0, 0%, 14%); -} diff --git a/scss-input/components/_spinner.scss b/scss-input/components/_spinner.scss deleted file mode 100644 index 006705d..0000000 --- a/scss-input/components/_spinner.scss +++ /dev/null @@ -1,81 +0,0 @@ -/* - Adapted from: https://codepen.io/supah/details/BjYLdW - Original source license: The MIT License - - Modifications to the source are available under the Mozilla Public License 2.0. - See the LICENSE.txt file in the workspace root for full license text. - - Below is the original source license text: - - BEGIN LICENSE TEXT - - Copyright (c) 2023 by Fabio Ottaviani (https://codepen.io/supah/pen/BjYLdW) - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the Software - is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies - or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE - OR OTHER DEALINGS IN THE SOFTWARE. - - END LICENSE TEXT -*/ - -.spinner { - animation: rotate 2s linear infinite; - width: 5rem; - height: 5rem; - & .path { - stroke: hsla(0, 0%, 100%, 0.75); - stroke-linecap: round; - animation: dash 1.5s ease-in-out infinite; - } -} - -.spinner.primary { - width: 4rem; - height: 4rem; - margin: 1.25rem; -} - -.spinner.secondary { - width: 1.5rem; - height: 1.5rem; -} - -.spinner.tertiary { - width: 1rem; - height: 1rem; -} - -@keyframes rotate { - 100% { - transform: rotate(360deg); - } -} - -@keyframes dash { - 0% { - stroke-dasharray: 1, 150; - stroke-dashoffset: 0; - } - 50% { - stroke-dasharray: 90, 150; - stroke-dashoffset: -35; - } - 100% { - stroke-dasharray: 90, 150; - stroke-dashoffset: -124; - } -} diff --git a/scss-input/main.scss b/scss-input/main.scss deleted file mode 100644 index 6d66e60..0000000 --- a/scss-input/main.scss +++ /dev/null @@ -1,4 +0,0 @@ -@use "defaults"; -@use "utils"; -@use "components"; -@use "buttons"; diff --git a/scss-output/main.css b/scss-output/main.css deleted file mode 100644 index 39cfef7..0000000 --- a/scss-output/main.css +++ /dev/null @@ -1,446 +0,0 @@ -@import "https://fonts.googleapis.com/css?family=Lato:300,400,600,700"; -@import "https://fonts.googleapis.com/css?family=IBM Plex Mono:300,400,600,700"; -p, -li { - margin: 0.5rem; -} - -.code, code { - margin: 1rem; -} - -.text, .panel-tab.inactive:hover, .panel-tab-title, .header, h1, -h2, -h3, -h4, -h5, -h6, p, -li, .code, code { - font-size: 1rem; - font-weight: 400; - line-height: 1.15rem; - -webkit-user-select: none; - user-select: none; -} - -.text.left, .left.panel-tab.inactive:hover, .left.panel-tab-title, .left.header, h1.left, -h2.left, -h3.left, -h4.left, -h5.left, -h6.left, p.left, -li.left, .left.code, code.left { - text-align: left; -} - -.text.right, .right.panel-tab.inactive:hover, .right.panel-tab-title, .right.header, h1.right, -h2.right, -h3.right, -h4.right, -h5.right, -h6.right, p.right, -li.right, .right.code, code.right { - text-align: right; -} - -.text.center, .center.panel-tab.inactive:hover, .center.panel-tab-title, .center.header, h1.center, -h2.center, -h3.center, -h4.center, -h5.center, -h6.center, p.center, -li.center, .center.code, code.center { - text-align: center; -} - -.text.primary, .primary.panel-tab.inactive:hover, .primary.panel-tab-title, .panel-tab.active > .panel-tab-title, .primary.header, -h2.primary, -h3.primary, -h4.primary, -h5.primary, -h6.primary, p.primary, -li.primary, .primary.code, code.primary, h1.header, h1, h1.code { - color: hsl(0deg, 0%, 80%); -} - -.text.secondary, .secondary.panel-tab-title, .text.panel-tab.active, .panel-tab-title.panel-tab.active, .panel-tab.inactive:hover, .panel-tab.inactive:hover > .panel-tab-title, .secondary.header, .header.panel-tab.active, h1.secondary, h1.panel-tab.active, -h3.secondary, -h3.panel-tab.active, -h4.secondary, -h4.panel-tab.active, -h5.secondary, -h5.panel-tab.active, -h6.secondary, -h6.panel-tab.active, p.secondary, p.panel-tab.active, -li.secondary, -li.panel-tab.active, .secondary.code, .code.panel-tab.active, code.secondary, code.panel-tab.active, h2.header, -h2, h2.code { - color: hsl(0deg, 0%, 70%); -} - -.text.tertiary, .tertiary.panel-tab.inactive:hover, .tertiary.panel-tab-title, .panel-tab.inactive > .panel-tab-title, .tertiary.header, h1.tertiary, -h2.tertiary, p.tertiary, -li.tertiary, .tertiary.code, code.tertiary, h3.header, -h3, h3.code, -h4.header, -h4, -h4.code, -h5.header, -h5, -h5.code, -h6.header, -h6, -h6.code { - color: hsl(0deg, 0%, 50%); -} - -.text.sans, .text, .panel-tab.inactive:hover, .panel-tab-title, .header, h1, -h2, -h3, -h4, -h5, -h6, p, -li, .code, code { - font-family: "Lato", sans-serif; -} - -.text.mono, .mono.panel-tab.inactive:hover, .mono.panel-tab-title, .mono.header, h1.mono, -h2.mono, -h3.mono, -h4.mono, -h5.mono, -h6.mono, p.mono, -li.mono, .code, code { - font-family: "IBM Plex Mono", monospace; -} - -.header, h1, -h2, -h3, -h4, -h5, -h6 { - margin: 0.5rem; -} - -.header.primary, .panel-tab.active > .header.panel-tab-title, -h2.primary, -.panel-tab.active > h2.panel-tab-title, -h3.primary, -.panel-tab.active > h3.panel-tab-title, -h4.primary, -.panel-tab.active > h4.panel-tab-title, -h5.primary, -.panel-tab.active > h5.panel-tab-title, -h6.primary, -.panel-tab.active > h6.panel-tab-title, h1 { - font-size: 1.5rem; - font-weight: 500; - line-height: 1.5rem; -} - -.header.secondary, .header.panel-tab.active, .header.panel-tab.inactive:hover, .panel-tab.inactive:hover > .header.panel-tab-title, h1.secondary, h1.panel-tab.active, h1.panel-tab.inactive:hover, .panel-tab.inactive:hover > h1.panel-tab-title, -h3.secondary, -h3.panel-tab.active, -h3.panel-tab.inactive:hover, -.panel-tab.inactive:hover > h3.panel-tab-title, -h4.secondary, -h4.panel-tab.active, -h4.panel-tab.inactive:hover, -.panel-tab.inactive:hover > h4.panel-tab-title, -h5.secondary, -h5.panel-tab.active, -h5.panel-tab.inactive:hover, -.panel-tab.inactive:hover > h5.panel-tab-title, -h6.secondary, -h6.panel-tab.active, -h6.panel-tab.inactive:hover, -.panel-tab.inactive:hover > h6.panel-tab-title, -h2 { - font-size: 1.5rem; - font-weight: 500; - line-height: 1.5rem; -} - -.header.title, h1.title, -h2.title, -h3.title, -h4.title, -h5.title, -h6.title { - font-size: 2.5rem; - line-height: 2.5rem; - margin-top: 1rem; - margin-bottom: 1rem; -} - -body { - background-color: hsl(0deg, 0%, 10%); - margin: 0; -} - -:root { - font-size: 16px; -} - -.drop-shadow, .panel-tab.active { - filter: drop-shadow(0rem 0.45rem 0.25rem hsla(0deg, 0%, 0%, 0.5)); -} - -.drop-shadow.primary, .primary.panel-tab.active, .panel-tab.active > .drop-shadow.panel-tab-title, .panel-tab.active > .panel-tab.active.panel-tab-title { - filter: drop-shadow(0.25rem 0.35rem 0.15rem hsla(0deg, 0%, 0%, 0.95)); -} - -.drop-shadow.secondary, .panel-tab.active, .drop-shadow.panel-tab.inactive:hover, .panel-tab.active.inactive:hover, .panel-tab.inactive:hover > .drop-shadow.panel-tab-title, .panel-tab.inactive:hover > .panel-tab.active.panel-tab-title { - filter: drop-shadow(0rem 0.45rem 0.25rem hsla(0deg, 0%, 0%, 0.5)); -} - -.drop-shadow.tertiary, .tertiary.panel-tab.active, .panel-tab.inactive > .drop-shadow.panel-tab-title, .panel-tab.inactive > .panel-tab.active.panel-tab-title { - filter: drop-shadow(0rem 0.25rem 0.25rem hsla(0deg, 0%, 0%, 0.25)); -} - -/* - Adapted from: https://codepen.io/supah/details/BjYLdW - Original source license: The MIT License - - Modifications to the source are available under the Mozilla Public License 2.0. - See the LICENSE.txt file in the workspace root for full license text. - - Below is the original source license text: - - BEGIN LICENSE TEXT - - Copyright (c) 2023 by Fabio Ottaviani (https://codepen.io/supah/pen/BjYLdW) - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the Software - is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all copies - or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. - IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE - OR OTHER DEALINGS IN THE SOFTWARE. - - END LICENSE TEXT -*/ -.spinner { - animation: rotate 2s linear infinite; - width: 5rem; - height: 5rem; -} -.spinner .path { - stroke: hsla(0deg, 0%, 100%, 0.75); - stroke-linecap: round; - animation: dash 1.5s ease-in-out infinite; -} - -.spinner.primary { - width: 4rem; - height: 4rem; - margin: 1.25rem; -} - -.spinner.secondary { - width: 1.5rem; - height: 1.5rem; -} - -.spinner.tertiary { - width: 1rem; - height: 1rem; -} - -@keyframes rotate { - 100% { - transform: rotate(360deg); - } -} -@keyframes dash { - 0% { - stroke-dasharray: 1, 150; - stroke-dashoffset: 0; - } - 50% { - stroke-dasharray: 90, 150; - stroke-dashoffset: -35; - } - 100% { - stroke-dasharray: 90, 150; - stroke-dashoffset: -124; - } -} -#editor { - display: flex; - flex-wrap: wrap; - flex-direction: column; -} - -#main-panel { - flex: 6 1 20rem; -} - -#right-panel { - flex: 1 3 20rem; -} - -#main-row { - flex: 3 1 30rem; - display: flex; - flex-wrap: wrap; - flex-direction: row; -} - -#bottom-panel { - flex: 1 2 20rem; -} - -.panel-root { - border-width: 0.0625rem; - border-color: hsl(0deg, 0%, 0%); - background-color: hsl(0deg, 0%, 20%); - border-style: solid; - display: flex; -} - -.panel-container { - margin: 0.5rem; - flex: 1; - display: flex; - flex-direction: column; - overflow: clip; -} - -.panel-header { - position: relative; - display: flex; - flex-wrap: nowrap; - align-items: flex-end; - height: 2.25rem; - margin-right: 3rem; -} - -.panel-header-overflow-x { - position: relative; - display: flex; - flex-direction: row; - flex-wrap: nowrap; - align-items: flex-end; - overflow-x: scroll; -} - -.panel-header-overflow-clip { - width: 100%; - position: absolute; - overflow: clip; -} - -.panel-header-overflow-x::-webkit-scrollbar { - display: none; -} - -.panel-header-gradient, .panel-header-gradient.left, .panel-header-gradient.right { - opacity: 0; - transition: opacity 1s cubic-bezier(0.25, 0.75, 0.25, 0.75); - z-index: 3; - pointer-events: none; - display: block; - position: absolute; - max-width: 3rem; - min-width: 0; - width: 49.5%; - top: 0; - height: calc(100% - 0.125rem); -} - -.panel-header-gradient.active { - opacity: 1; - transition: opacity 1s cubic-bezier(0.25, 0.75, 0.25, 0.75); -} - -.panel-header-gradient.right { - right: 0; - background: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, hsl(0deg, 0%, 20%) 99%); -} - -.panel-header-gradient.left { - left: 0; - background: linear-gradient(to left, rgba(0, 0, 0, 0) 0%, hsl(0deg, 0%, 20%) 99%); -} - -.panel-tab { - margin: 0; - position: relative; - border-style: solid; - border-width: 0.125rem; - border-color: hsl(0deg, 0%, 35%); - border-radius: 0.5rem 0.5rem 0 0; - background-color: hsl(0deg, 0%, 14%); -} - -.panel-tab.inactive { - height: 2rem; - border-color: hsl(0deg, 0%, 12%); - background-color: hsl(0deg, 0%, 12%); - transition: height 0.15s cubic-bezier(0.12, 0, 0.39, 0), border-radius 0.1s cubic-bezier(0.12, 0, 0.39, 0); -} - -.panel-tab.active { - z-index: 2; - border-bottom-width: 0; - height: 2.25rem; - transition: height 0.15s cubic-bezier(1, 0.5, 0.16, 1), border-radius 0.15s cubic-bezier(0.85, 0, 0.15, 1), background-color 0.015s cubic-bezier(0.85, 0, 0.15, 1); -} - -.panel-tab.inactive:nth-child(n+2) { - border-top-left-radius: 0; -} - -.panel-tab.inactive:has(+ .panel-tab) { - border-top-left-radius: 0; - border-top-right-radius: 0; -} - -.panel-tab.inactive:nth-child(1) { - border-top-left-radius: 0.5rem; -} - -.panel-tab.inactive:hover { - background-color: hsl(0deg, 0%, 15%); - border-color: hsl(0deg, 0%, 15%); - transition: all 0.0125s cubic-bezier(0.85, 0, 0.15, 1); -} - -.panel-tab-title { - margin: 0 0.25rem; - margin-right: 2.5rem; - text-align: left; - text-wrap: nowrap; -} - -.panel-content { - z-index: 0; - position: relative; - display: inline-block; - margin: 0; - top: -0.125rem; - flex: 1; - border-style: solid; - border-width: 0.125rem; - border-color: hsl(0deg, 0%, 35%); - border-radius: 0 0.5rem 0.5rem; - background-color: hsl(0deg, 0%, 14%); -} - -button { - background-color: #181818; - margin: 0.5rem; -} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index 9e4053c..0000000 --- a/src/main.rs +++ /dev/null @@ -1,8 +0,0 @@ -mod tracing_setup; -mod ui_setup; - -/// Application entry point. -fn main() { - tracing_setup::init(); - ui_setup::init(); -} diff --git a/src/tracing_setup.rs b/src/tracing_setup.rs deleted file mode 100644 index 8d2a226..0000000 --- a/src/tracing_setup.rs +++ /dev/null @@ -1,23 +0,0 @@ -use tracing_subscriber::fmt::format::Pretty; -use tracing_subscriber::layer::SubscriberExt as _; -use tracing_subscriber::util::SubscriberInitExt as _; - -/// Initialize `tracing`, `tracing_subscriber` and `tracing_web`. -pub(super) fn init() { - let fmt_layer = move || { - tracing_subscriber::fmt::layer() - // WebViews do not support time. - .without_time() - .with_writer(tracing_web::MakeConsoleWriter) - }; - - let performance_layer = move || { - tracing_web::performance_layer() - .with_details_from_fields(Pretty::default()) - }; - - tracing_subscriber::registry() - .with(fmt_layer()) - .with(performance_layer()) - .init(); -} diff --git a/src/ui_setup.rs b/src/ui_setup.rs deleted file mode 100644 index bf03416..0000000 --- a/src/ui_setup.rs +++ /dev/null @@ -1,6 +0,0 @@ -use chipbox_ui_app as ui_app; -use ui_app::App; - -pub(super) fn init() { - let _ = yew::Renderer::::new().render(); -} diff --git a/ui/README.md b/ui/README.md deleted file mode 100644 index 00185db..0000000 --- a/ui/README.md +++ /dev/null @@ -1,4 +0,0 @@ -# Yew component crates directory -Yew UI components should be divided into separate crates for better compilation times. -## Naming convention -A components crate name should be in the following format: `chipbox-ui-{component}`. diff --git a/ui/app/Cargo.toml b/ui/app/Cargo.toml deleted file mode 100644 index 8d1fb7c..0000000 --- a/ui/app/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "chipbox-ui-app" -version = "0.1.0" -edition = "2021" - -[dependencies] -## project deps -chipbox-glue = { workspace = true } -chipbox-common = { workspace = true } -## ui deps -chipbox-ui-spinner = { workspace = true } -chipbox-ui-panel = { workspace = true } -## tracing -tracing = { workspace = true } -## concurrency -futures = { workspace = true } -tokio = { version = "*", features = [], default-features = false } -## serde -serde = { workspace = true } -## utils -const_format = { workspace = true } -cowstr = { workspace = true } -## wasm frontend -yew = { workspace = true } -web-sys = { workspace = true } -tauri-sys = { workspace = true } diff --git a/ui/app/src/app.rs b/ui/app/src/app.rs deleted file mode 100644 index 2270948..0000000 --- a/ui/app/src/app.rs +++ /dev/null @@ -1,51 +0,0 @@ -use self::backend_query::BackendQuery; -use self::home::Home; -use self::setup::Setup; -use self::state::AppState; -use yew::prelude::*; - -mod backend_query; -mod editor; -mod home; -mod setup; -mod state; - -#[function_component] -pub fn App() -> yew::Html { - // Initial state of the application. - // Rerender on state change. - let app_state = use_state_eq(AppState::default); - - // After each render, query the backend. - use_effect({ - let app_state = app_state.clone(); - move || query_backend_app_state(app_state) - }); - - match &*app_state { - AppState::BackendQuery(ref backend_query_state) => html! { - - }, - AppState::Setup(ref setup_state) => html! { - - }, - AppState::Home(ref home_state) => html! { - - }, - AppState::BackendClosed => html_backend_closed(), - } -} - -fn html_backend_closed() -> Html { - html! { -
-

{"Backend app thread channel closed"}

-

{"Unable to deliver query message to backend app thread - thread channel was closed."}

-

{"See backend logs for details."}

-
- } -} - -fn query_backend_app_state(app_state: UseStateHandle) { - unreachable!() -} diff --git a/ui/app/src/app/backend_query.rs b/ui/app/src/app/backend_query.rs deleted file mode 100644 index f111d70..0000000 --- a/ui/app/src/app/backend_query.rs +++ /dev/null @@ -1,54 +0,0 @@ -use chipbox_ui_spinner::Spinner; -use yew::prelude::*; - -#[derive(PartialEq, Default, Clone, Copy)] -pub(super) enum BackendQueryState { - #[default] - WaitForBackend, - ReadSettings, - QuerySettings, -} - -impl AsRef for BackendQueryState { - fn as_ref(&self) -> &str { - match self { - Self::WaitForBackend => "Waiting for backend...", - Self::ReadSettings => "Reading settings...", - Self::QuerySettings => "Querying settings...", - } - } -} - -#[derive(Properties, PartialEq)] -pub(super) struct Props { - pub(super) state: BackendQueryState, -} - -#[function_component] -pub(super) fn BackendQuery(props: &Props) -> yew::Html { - const ROOT_STYLE: &str = - "height: 100vh; display: flex; justify-content: center; \ - flex-direction: column; text-align: center;"; - const MAIN_STYLE: &str = const_format::formatc!("flex: 1; {}", ROOT_STYLE); - const FOOTER_SYLE: &str = - const_format::formatc!("flex: 0; margin-bottom: 1rem; {}", ROOT_STYLE); - const FOOTER_TEXT: &str = - const_format::formatc!("chipbox {}", env!("CARGO_PKG_VERSION")); - let message_text = props.state.as_ref(); - - html! { -
-
- -

- {message_text} -

-
-
-

- {FOOTER_TEXT} -

-
-
- } -} diff --git a/ui/app/src/app/editor.rs b/ui/app/src/app/editor.rs deleted file mode 100644 index e0464e4..0000000 --- a/ui/app/src/app/editor.rs +++ /dev/null @@ -1,28 +0,0 @@ -use bottom_panel::BottomPanel; -use main_panel::MainPanel; -use right_panel::RightPanel; -use yew::prelude::*; - -mod bottom_panel; -mod main_panel; -mod right_panel; - -#[derive(Properties, PartialEq)] -pub(super) struct Props {} - -#[function_component] -pub(super) fn Editor(props: &Props) -> yew::Html { - // Main styling. - const MAIN_STYLE: &str = "width: 100vw; height: 100vh;"; - const MAIN_CLASS: &str = ""; - - html! { -
-
- - -
- -
- } -} diff --git a/ui/app/src/app/editor/bottom_panel.rs b/ui/app/src/app/editor/bottom_panel.rs deleted file mode 100644 index c8bea28..0000000 --- a/ui/app/src/app/editor/bottom_panel.rs +++ /dev/null @@ -1,54 +0,0 @@ -mod timeline; - -use self::timeline::Timeline; -use chipbox_ui_panel::Panel; -use yew::prelude::*; - -#[derive(Properties, PartialEq)] -pub(super) struct Props { - #[prop_or_default] - pub(super) style: AttrValue, - #[prop_or_default] - pub(super) class: AttrValue, -} - -#[derive(PartialEq)] -pub enum Tab { - Timeline, - Mixer, -} - -impl chipbox_ui_panel::Tab for Tab { - const TABS: &'static [Self] = &[Self::Timeline, Self::Mixer]; -} - -impl yew::ToHtml for Tab { - fn to_html(&self) -> yew::virtual_dom::VNode { - match self { - Self::Timeline => html! { }, - Self::Mixer => html! { "Mixer" }, - } - } -} - -impl std::fmt::Display for Tab { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Timeline => write!(f, "Timeline"), - Self::Mixer => write!(f, "Mixer"), - } - } -} - -#[function_component] -pub(super) fn BottomPanel(props: &Props) -> yew::Html { - // Retrieve props. - let Props { style, class } = props; - - let style = format!("{}", style); - let class = format!("{}", class); - - html! { - id="bottom-panel" style={style} class={class} /> - } -} diff --git a/ui/app/src/app/editor/bottom_panel/timeline.rs b/ui/app/src/app/editor/bottom_panel/timeline.rs deleted file mode 100644 index 7427421..0000000 --- a/ui/app/src/app/editor/bottom_panel/timeline.rs +++ /dev/null @@ -1,8 +0,0 @@ -use yew::prelude::*; - -#[function_component] -pub fn Timeline() -> Html { - html! { -
- } -} diff --git a/ui/app/src/app/editor/main_panel.rs b/ui/app/src/app/editor/main_panel.rs deleted file mode 100644 index 2ce6ffd..0000000 --- a/ui/app/src/app/editor/main_panel.rs +++ /dev/null @@ -1,54 +0,0 @@ -use chipbox_ui_panel::Panel; -use yew::prelude::*; - -#[derive(Properties, PartialEq)] -pub(super) struct Props { - #[prop_or_default] - pub(super) style: AttrValue, - #[prop_or_default] - pub(super) class: AttrValue, -} - -#[derive(PartialEq)] -pub enum Tab { - Pattern, - NodeTree, - Audio, -} - -impl chipbox_ui_panel::Tab for Tab { - const TABS: &'static [Self] = &[Self::Pattern, Self::NodeTree, Self::Audio]; -} - -impl yew::ToHtml for Tab { - fn to_html(&self) -> yew::virtual_dom::VNode { - match self { - Self::Pattern => html! { "Pattern" }, - Self::NodeTree => html! { "Node Tree" }, - Self::Audio => html! { "Audio" }, - } - } -} - -impl std::fmt::Display for Tab { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Pattern => write!(f, "Pattern"), - Self::NodeTree => write!(f, "Node Tree"), - Self::Audio => write!(f, "Audio"), - } - } -} - -#[function_component] -pub(super) fn MainPanel(props: &Props) -> yew::Html { - // Retrieve props. - let Props { style, class } = props; - - let style = format!("{}", style); - let class = format!("{}", class); - - html! { - id="main-panel" style={style} class={class} /> - } -} diff --git a/ui/app/src/app/editor/right_panel.rs b/ui/app/src/app/editor/right_panel.rs deleted file mode 100644 index 1531f45..0000000 --- a/ui/app/src/app/editor/right_panel.rs +++ /dev/null @@ -1,51 +0,0 @@ -use chipbox_ui_panel::Panel; -use yew::prelude::*; - -#[derive(Properties, PartialEq)] -pub(super) struct Props { - #[prop_or_default] - pub(super) style: AttrValue, - #[prop_or_default] - pub(super) class: AttrValue, -} - -#[derive(PartialEq)] -pub enum Tab { - Slot, - Project, -} - -impl chipbox_ui_panel::Tab for Tab { - const TABS: &'static [Self] = &[Self::Slot, Self::Project]; -} - -impl yew::ToHtml for Tab { - fn to_html(&self) -> yew::virtual_dom::VNode { - match self { - Self::Slot => html! { "Slot" }, - Self::Project => html! { "Project" }, - } - } -} - -impl std::fmt::Display for Tab { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Slot => write!(f, "Slot"), - Self::Project => write!(f, "Project"), - } - } -} - -#[function_component] -pub(super) fn RightPanel(props: &Props) -> yew::Html { - // Retrieve props. - let Props { style, class } = props; - - let style = format!("{}", style); - let class = format!("{}", class); - - html! { - id="right-panel" style={style} class={class}/> - } -} diff --git a/ui/app/src/app/home.rs b/ui/app/src/app/home.rs deleted file mode 100644 index 524f859..0000000 --- a/ui/app/src/app/home.rs +++ /dev/null @@ -1,63 +0,0 @@ -use super::backend_query::{BackendQuery, BackendQueryState}; -use const_format::formatc; -use yew::prelude::*; - -#[derive(PartialEq, Clone)] -pub(super) enum HomeState { - Welcome, -} - -#[derive(Properties, PartialEq)] -pub(super) struct Props { - pub(super) state: HomeState, -} - -#[function_component] -pub(super) fn Home(props: &Props) -> yew::Html { - match &props.state { - HomeState::Welcome => html_welcome(), - } -} - -fn html_querying_settings() -> yew::Html { - html! { - - } -} - -fn html_welcome() -> Html { - // On click new project. - let on_click_new = move |_| {}; - - html! { -
-

- {"chipbox"} - - {formatc!("v{}", env!("CARGO_PKG_VERSION"))} - -

- - -
-
-

{"Recent projects"}

-
-

{"todo"}

-
-
-
- } -} diff --git a/ui/app/src/app/setup.rs b/ui/app/src/app/setup.rs deleted file mode 100644 index 2db5b28..0000000 --- a/ui/app/src/app/setup.rs +++ /dev/null @@ -1,35 +0,0 @@ -use cowstr::CowStr; -use yew::prelude::*; - -#[derive(PartialEq, Clone)] -pub(super) enum SetupState { - First, - Error(CowStr), -} - -#[derive(Properties, PartialEq)] -pub(super) struct Props { - pub(super) state: SetupState, -} - -#[function_component] -pub(super) fn Setup(props: &Props) -> yew::Html { - match props.state { - SetupState::First => html_first(), - SetupState::Error(ref _err) => todo!(), - } -} - -fn html_first() -> yew::Html { - let on_click = move |_: MouseEvent| {}; - - html! { - <> -

{"First time setup"}

- - - } -} diff --git a/ui/app/src/app/state.rs b/ui/app/src/app/state.rs deleted file mode 100644 index adb92a7..0000000 --- a/ui/app/src/app/state.rs +++ /dev/null @@ -1,44 +0,0 @@ -use super::backend_query::BackendQueryState; -use super::home::HomeState; -use super::setup::SetupState; -use crate::common; -use common::app::{AwaitConfigReason, BackendAppState}; - -impl From for AppState { - fn from(value: BackendAppState) -> Self { - match value { - BackendAppState::ReadingSettings => { - Self::BackendQuery(BackendQueryState::ReadSettings) - } - BackendAppState::AwaitConfig { reason } => match reason { - AwaitConfigReason::NoConfig => Self::Setup(SetupState::First), - AwaitConfigReason::Error(err) => { - Self::Setup(SetupState::Error(err)) - } - }, - BackendAppState::Idle => Self::Home(HomeState::Welcome), - BackendAppState::Editor => todo!(), - } - } -} - -#[derive(PartialEq)] -/// Frontend application state. -pub enum AppState { - /// Backend app thread channel was closed. - /// Communication with the backend is no longer possible. - BackendClosed, - /// Frontend is waiting for backend app thread response. - BackendQuery(BackendQueryState), - /// Frontend is in setup mode. - /// This is where the user can modify application configuration. - Setup(SetupState), - /// User must select a project to continue to the editor. - Home(HomeState), -} - -impl Default for AppState { - fn default() -> Self { - Self::BackendQuery(Default::default()) - } -} diff --git a/ui/app/src/lib.rs b/ui/app/src/lib.rs deleted file mode 100644 index 9cdb7ca..0000000 --- a/ui/app/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -// Required by `const_format::formatc!`. -#![feature(const_mut_refs)] -// Required by `chipbox_ui_panel::Panel`. -#![feature(generic_const_exprs)] -// Disables warn from `generic_const_exprs`. -#![allow(incomplete_features)] - -pub use app::App; -pub mod app; -pub use {chipbox_common as common, chipbox_glue as glue}; diff --git a/ui/panel/Cargo.toml b/ui/panel/Cargo.toml deleted file mode 100644 index f178e11..0000000 --- a/ui/panel/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -[package] -name = "chipbox-ui-panel" -version = "0.1.0" -edition = "2021" - -[dependencies] -## tracing -tracing = { workspace = true } -## wasm frontend -yew = { workspace = true } -web-sys = { workspace = true } -js-sys = { workspace = true } -wasm-bindgen = { workspace = true } -gloo = { workspace = true } diff --git a/ui/panel/src/lib.rs b/ui/panel/src/lib.rs deleted file mode 100644 index 9c1c9df..0000000 --- a/ui/panel/src/lib.rs +++ /dev/null @@ -1,10 +0,0 @@ -// Required by `Panel`. -#![feature(generic_const_exprs)] -// Disables warn from `generic_const_exprs`. -#![allow(incomplete_features)] - -mod panel; -mod tab; - -pub use panel::Panel; -pub use tab::Tab; diff --git a/ui/panel/src/panel.rs b/ui/panel/src/panel.rs deleted file mode 100644 index f594594..0000000 --- a/ui/panel/src/panel.rs +++ /dev/null @@ -1,239 +0,0 @@ -use crate::Tab; -use std::cell::Cell; -use std::rc::Rc; -use wasm_bindgen::closure::Closure; -use web_sys::wasm_bindgen::JsCast as _; -use yew::prelude::*; - -#[derive(Properties, PartialEq)] -pub struct Props { - #[prop_or_default] - pub style: AttrValue, - #[prop_or_default] - pub class: AttrValue, - pub id: &'static str, -} - -pub struct Panel -where - T: Tab, - [(); T::TABS.len()]:, -{ - tab_idx: Rc>, - msg_cb: Callback<()>, - _msg_cl: Closure, - tab_refs: Rc<[NodeRef; T::TABS.len()]>, - left_gradient_ref: NodeRef, - right_gradient_ref: NodeRef, - overflow_x_ref: NodeRef, - overflow_x_observer: web_sys::ResizeObserver, -} - -impl Component for Panel -where - T: Tab, - [(); T::TABS.len()]:, -{ - type Message = (); - type Properties = Props; - - fn create(ctx: &Context) -> Self { - let tab_idx = Rc::new(Cell::new(0)); - let tab_refs = Rc::new(std::array::from_fn(|_| NodeRef::default())); - let left_gradient_ref = NodeRef::default(); - let right_gradient_ref = NodeRef::default(); - let overflow_x_ref = NodeRef::default(); - let msg_cb = ctx - .link() - .callback(|_: ()| ()); - - let msg_cl = { - let msg_cb = msg_cb.clone(); - wasm_bindgen::closure::Closure::new(move || msg_cb.emit(())) - }; - let msg_fn = msg_cl - .as_ref() - .unchecked_ref(); - - let overflow_x_observer = web_sys::ResizeObserver::new(msg_fn) - .expect("failed to create observer"); - - Self { - tab_idx, - msg_cb, - _msg_cl: msg_cl, - tab_refs, - left_gradient_ref, - right_gradient_ref, - overflow_x_ref, - overflow_x_observer, - } - } - - fn update(&mut self, _ctx: &Context, _msg: Self::Message) -> bool { - true - } - - fn rendered(&mut self, _ctx: &Context, _first_render: bool) { - self.tab_refs - .iter() - .enumerate() - .for_each(|(idx, node_ref)| self.apply_tab_style(idx, node_ref)); - let overflow_x = self - .overflow_x_ref - .cast::() - .expect("should be element"); - self.overflow_x_observer - .observe(&overflow_x); - Self::apply_gradient_style( - &self.left_gradient_ref, - &self.right_gradient_ref, - overflow_x, - ) - } - - fn view(&self, ctx: &Context) -> Html { - let props = &ctx.props(); - let style = format!("{}", props.style); - let class = format!("panel-root {}", props.class); - let id = props.id; - - let onscroll = { - let left_gradient_ref = self.left_gradient_ref.clone(); - let right_gradient_ref = self - .right_gradient_ref - .clone(); - Callback::from(move |event: Event| { - let target: web_sys::Element = event - .target() - .expect("should have target") - .dyn_into() - .expect("should be element"); - Self::apply_gradient_style( - &left_gradient_ref, - &right_gradient_ref, - target, - ); - }) - }; - - html! { -
-
-
-
-
- { - T::TABS - .iter() - .enumerate() - .map(|(idx, tab)| { - self.html_tab(idx, tab) - }).collect::() - } -
-
-
-
-
-
- {T::TABS[self.tab_idx.get()].to_html()} -
-
-
- } - } -} - -impl Panel -where - T: Tab, - [(); T::TABS.len()]:, -{ - fn apply_tab_style(&self, idx: usize, node_ref: &NodeRef) { - let is_active = self.tab_idx.get() == idx; - let class_list = node_ref - .cast::() - .expect("tab ref should be element") - .class_list(); - const ACTIVE_CLASS: &str = "active"; - const INACTIVE_CLASS: &str = "inactive"; - let (add, remove) = if is_active { - (ACTIVE_CLASS, INACTIVE_CLASS) - } else { - (INACTIVE_CLASS, ACTIVE_CLASS) - }; - class_list - .add_1(add) - .expect("should be able to add class"); - class_list - .remove_1(remove) - .expect("should be able to remove class"); - } - - fn html_tab(&self, idx: usize, tab: &T) -> Html { - let msg_cb = self.msg_cb.clone(); - let tab_idx = self.tab_idx.clone(); - let is_active = self.tab_idx.get() == idx; - html! { - - } - } - - fn apply_gradient_style( - left_gradient_ref: &NodeRef, - right_gradient_ref: &NodeRef, - overflow_x: web_sys::Element, - ) { - let left_gradient = left_gradient_ref - .cast::() - .expect("should be element"); - let right_gradient = right_gradient_ref - .cast::() - .expect("should be element"); - - let scroll_left = overflow_x.scroll_left(); - let client_width = overflow_x.client_width(); - let scroll_width = overflow_x.scroll_width(); - - const ACTIVE_CLASS: &str = "active"; - - if scroll_left == 0 { - left_gradient - .class_list() - .remove_1(ACTIVE_CLASS) - .expect("failed to remove class"); - } else { - left_gradient - .class_list() - .add_1(ACTIVE_CLASS) - .expect("failed to add class"); - } - - if scroll_left + client_width >= scroll_width { - right_gradient - .class_list() - .remove_1(ACTIVE_CLASS) - .expect("failed to remove class"); - } else { - right_gradient - .class_list() - .add_1(ACTIVE_CLASS) - .expect("failed to add class"); - } - } -} diff --git a/ui/panel/src/tab.rs b/ui/panel/src/tab.rs deleted file mode 100644 index 3dce9fc..0000000 --- a/ui/panel/src/tab.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub trait Tab -where - T: PartialEq + yew::ToHtml + std::fmt::Display + 'static, - Self: PartialEq + yew::ToHtml + std::fmt::Display + 'static, -{ - const TABS: &'static [T]; -} diff --git a/ui/spinner/Cargo.toml b/ui/spinner/Cargo.toml deleted file mode 100644 index a3fc6f2..0000000 --- a/ui/spinner/Cargo.toml +++ /dev/null @@ -1,10 +0,0 @@ -[package] -name = "chipbox-ui-spinner" -version = "0.1.0" -edition = "2021" - -[dependencies] -## tracing -tracing = { workspace = true } -## wasm frontend -yew = { workspace = true } diff --git a/ui/spinner/src/lib.rs b/ui/spinner/src/lib.rs deleted file mode 100644 index 201e54e..0000000 --- a/ui/spinner/src/lib.rs +++ /dev/null @@ -1,23 +0,0 @@ -use yew::prelude::*; - -#[derive(Properties, PartialEq)] -pub struct Props { - #[prop_or_default] - pub container_class: AttrValue, - pub svg_class: AttrValue, -} - -#[function_component] -pub fn Spinner(props: &Props) -> yew::Html { - let Props { - container_class, - svg_class, - } = props; - html! { - - - - - - } -}