diff --git a/.agents/skills/borrow-fighters-repo-atlas/references/repo-map.md b/.agents/skills/borrow-fighters-repo-atlas/references/repo-map.md index c7e0918..e53a140 100644 --- a/.agents/skills/borrow-fighters-repo-atlas/references/repo-map.md +++ b/.agents/skills/borrow-fighters-repo-atlas/references/repo-map.md @@ -33,6 +33,7 @@ | `docs/15-character-combat-matrix.md` | Character identity, archetypes and tuning matrix | | `docs/16-sprite-combat-viewer-roadmap.md` | Sprite viewer/tooling roadmap for artists and combat alignment | | `docs/17-visual-scale-and-stage-metrics.md` | Runtime visual scale, fighter size and arena metrics | +| `docs/18-sprite-studio.md` | External Tauri + React sprite manifest editor | | `docs/adr/` | Architecture Decision Records | | `docs/adr/0007-sprite-frame-combat-runtime.md` | Decision for using `frames[].combat` in runtime hit/hurt/projectile origin resolution | | `docs/templates/` | Reusable proposal/checklist templates | @@ -63,6 +64,7 @@ | `src/math/` | Small geometry helpers | | `tests/` | Integration tests for combat, flags and sprites | | `tools/art/` | Local asset extraction utilities | +| `tools/sprite-studio/` | Isolated Tauri + React app for editing sprite manifests | ## Assets diff --git a/README.md b/README.md index d4684cf..4dfc420 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ A ideia continua sendo evoluir com decisões explícitas, escopo controlado e co - [`docs/11-sprite-pipeline.md`](docs/11-sprite-pipeline.md): formato candidato para atlas, animações, pivots e metadata de sprites. - [`docs/16-sprite-combat-viewer-roadmap.md`](docs/16-sprite-combat-viewer-roadmap.md): roadmap do viewer para artistas conferirem atlas, pivot, grade e boxes. - [`docs/17-visual-scale-and-stage-metrics.md`](docs/17-visual-scale-and-stage-metrics.md): escala visual alvo de personagens, arena e workflow de calibracao. +- [`docs/18-sprite-studio.md`](docs/18-sprite-studio.md): ferramenta Tauri + React para editar manifestos e atlas fora do loop do jogo. - [`docs/templates/mood-proposal.md`](docs/templates/mood-proposal.md): molde para proposta de moodboard. - [`docs/templates/character-concept.md`](docs/templates/character-concept.md): molde para personagem e mecânica. - [`docs/templates/adr-template.md`](docs/templates/adr-template.md): molde para novas decisões. @@ -50,6 +51,7 @@ A ideia continua sendo evoluir com decisões explícitas, escopo controlado e co - [`docs/15-character-combat-matrix.md`](docs/15-character-combat-matrix.md): matriz de identidade mecânica e tuning inicial de Rust, Duke, Go e C. - [`docs/16-sprite-combat-viewer-roadmap.md`](docs/16-sprite-combat-viewer-roadmap.md): ferramenta isolada para inspecionar sprites e preparar hitbox/hurtbox data-driven. - [`docs/17-visual-scale-and-stage-metrics.md`](docs/17-visual-scale-and-stage-metrics.md): padrao tecnico de tamanho em tela, escala de sprite e largura de arena. +- [`docs/18-sprite-studio.md`](docs/18-sprite-studio.md): app desktop externo para artistas editarem `*.sprite.json` com UI propria. - [`docs/09-ai-collaboration.md`](docs/09-ai-collaboration.md): como Codex, Claude e skills devem navegar o projeto. - [`AGENTS.md`](AGENTS.md): instruções persistentes para Codex. - [`CLAUDE.md`](CLAUDE.md): instruções persistentes para Claude Code. @@ -65,6 +67,7 @@ A ideia continua sendo evoluir com decisões explícitas, escopo controlado e co - [`docs/adr/0005-data-driven-audio-events.md`](docs/adr/0005-data-driven-audio-events.md): eventos de áudio data-driven com manifesto JSON. - [`docs/adr/0006-runtime-sprite-scale-and-scene-state.md`](docs/adr/0006-runtime-sprite-scale-and-scene-state.md): escala visual por manifesto e maquina de estados de cenas. - [`docs/adr/0007-sprite-frame-combat-runtime.md`](docs/adr/0007-sprite-frame-combat-runtime.md): metadata de hitbox/hurtbox por frame no runtime. +- [`docs/adr/0008-external-sprite-studio-tooling.md`](docs/adr/0008-external-sprite-studio-tooling.md): Sprite Studio externo em Tauri + React, isolado do codigo do jogo. ### GitHub @@ -174,6 +177,17 @@ cargo run -- --tool sprite-viewer --manifest assets/placeholder/c-fighter.sprite No Sprite Combat Viewer, use o mouse para inspecionar coordenadas locais do frame, arrastar personagem/dummy e ajustar alças de `frames[].combat`. `N` gera um rascunho de metadata a partir do overlay runtime do golpe selecionado, `Tab` / `Shift+Tab` alterna clip, `Enter` sincroniza clip com golpe, `C` / `Shift+C` alterna personagem de combate, `[` / `]` alterna golpe, `.` / `,` avança ou volta frame, `Espaço` pausa, mouse wheel controla zoom, `0` reseta zoom, `=` / `-` ajusta `scale`, `Setas` ou `Shift+Setas` move o `pivot`, `Ctrl+Setas` ajusta largura/altura do corpo físico, `Ctrl+Shift+Setas` ajusta altura abaixada, `Ctrl+S` salva manifestos de tuning, `O` mostra/esconde dummy, `M` mostra/esconde boxes de combate, `T` mostra/esconde trajetória prevista do projectile, `F5` recarrega manifesto/atlas, `F12` salva screenshot em `target/sprite-viewer-capture.png`, `F9`/`F10` gravam um MP4 local, `G` alterna grade, `P` alterna pivot, `B` alterna bounds, `R` reseta posição e `Esc` volta ao menu quando aberto por `Training`. O padrão de escala fica em [`docs/17-visual-scale-and-stage-metrics.md`](docs/17-visual-scale-and-stage-metrics.md), e o roadmap completo fica em [`docs/16-sprite-combat-viewer-roadmap.md`](docs/16-sprite-combat-viewer-roadmap.md). +O novo Sprite Studio externo vive em `tools/sprite-studio` e deve substituir o viewer Raylib em uma limpeza propria: + +```bash +cd tools/sprite-studio +pnpm install +pnpm build +pnpm tauri dev +``` + +Ele usa Tauri 1.8 + React, edita `*.sprite.json` por UI propria, possui file picker nativo, menu desktop, timeline, paineis colapsaveis, tutorial visual, autosave/backup, snap, guias de escala, presets de combat boxes, validacao do runtime e export de review. Detalhes e pre-requisitos ficam em [`docs/18-sprite-studio.md`](docs/18-sprite-studio.md). + Configurações disponíveis em `Versus Setup` e `Options`: | Preferência | Padrão | Efeito | diff --git a/docs/08-code-architecture.md b/docs/08-code-architecture.md index abe2a2e..efefd96 100644 --- a/docs/08-code-architecture.md +++ b/docs/08-code-architecture.md @@ -81,20 +81,23 @@ borrow-fighters/ │ ├── mod.rs # Tipos geométricos pequenos do jogo │ ├── rect.rs # Retângulos de colisão/hitbox │ └── vec2.rs # Vetores 2D se Raylib Vector2 não bastar -└── tests/ - ├── cli.rs # Contrato de argumentos de inicialização - ├── characters.rs # Contrato do registro de personagens - ├── combat_lab.rs # Estado testável do Combat Lab - ├── attack_frame_data.rs # Timing de golpes em frames - ├── move_data.rs # Contrato da tabela MoveSpec - ├── character_identity_tuning.rs # Intenção mecânica de Rust/Duke/Go/C por dados - ├── combat_rules.rs # Regras puras de combate e IA - ├── traditional_moves.rs # High/low/throw e ataques aéreos tradicionais - ├── cpu_traditional_moves.rs # Cobertura da CPU para golpes tradicionais - ├── feature_flags.rs # Contrato de flags runtime - ├── audio_manifest.rs # Contrato do manifesto e roteamento de áudio - ├── sprite_manifest.rs # Validação do formato JSON de sprites - └── sprite_selection.rs # Clip escolhido a partir do estado do lutador +├── tests/ +│ ├── cli.rs # Contrato de argumentos de inicialização +│ ├── characters.rs # Contrato do registro de personagens +│ ├── combat_lab.rs # Estado testável do Combat Lab +│ ├── attack_frame_data.rs # Timing de golpes em frames +│ ├── move_data.rs # Contrato da tabela MoveSpec +│ ├── character_identity_tuning.rs # Intenção mecânica de Rust/Duke/Go/C por dados +│ ├── combat_rules.rs # Regras puras de combate e IA +│ ├── traditional_moves.rs # High/low/throw e ataques aéreos tradicionais +│ ├── cpu_traditional_moves.rs # Cobertura da CPU para golpes tradicionais +│ ├── feature_flags.rs # Contrato de flags runtime +│ ├── audio_manifest.rs # Contrato do manifesto e roteamento de áudio +│ ├── sprite_manifest.rs # Validação do formato JSON de sprites +│ └── sprite_selection.rs # Clip escolhido a partir do estado do lutador +└── tools/ + ├── art/ # Utilitarios locais de extracao/ajuste de assets + └── sprite-studio/ # App Tauri 1.8 + React isolado para editar manifestos de sprite ``` O diretório `scenes/` ainda deve permanecer simples, sem framework de telas. `ui/` já abriga o overlay de debug do Combat Lab, mas ainda não deve virar um sistema genérico antes de haver HUD e menus suficientes para justificar isso. `characters/` já possui o registro mínimo de personagens, mas ainda deve permanecer simples e orientado a dados. Novos módulos só devem entrar quando reduzirem responsabilidade real dos arquivos atuais. @@ -127,6 +130,20 @@ Deve expor os módulos internos para testes e exemplos. Regras puras de jogo dev `src/engine/sprites/combat.rs` tambem fica em `engine` porque depende do formato de sprite, clip, pivot e escala visual. Ele nao depende de Raylib; apenas projeta metadata local do atlas para `Rect`/`Vec2` em coordenadas de mundo para que `game::World` possa usar com fallback. +### `tools/sprite-studio` + +O Sprite Studio e uma ferramenta externa em Tauri 1.8 + React. Ele nao faz parte do pacote Rust do jogo, nao importa structs do runtime e nao deve virar dependencia do `src/`. + +Responsabilidades: + +- abrir atlas e `*.sprite.json`; +- editar dados de manifesto; +- rodar validacao do runtime por comando externo (`cargo test`) sem linkar codigo do jogo; +- salvar artefatos consumidos pelo jogo; +- dar UI melhor para artistas e devs ajustarem pivot, escala, hitbox, hurtbox e origem de projectile. + +O contrato entre jogo e ferramenta e apenas o arquivo em disco. A decisao esta registrada em [`docs/adr/0008-external-sprite-studio-tooling.md`](adr/0008-external-sprite-studio-tooling.md). + ### `combat/*` Deve ser o núcleo mais estável do protótipo. Prioridade: diff --git a/docs/11-sprite-pipeline.md b/docs/11-sprite-pipeline.md index f22d860..acec3c8 100644 --- a/docs/11-sprite-pipeline.md +++ b/docs/11-sprite-pipeline.md @@ -133,11 +133,34 @@ Assets relacionados ao slice atual: - `assets/placeholder/arena-java-street.png` - `assets/placeholder/arena-terminal-compiler-lab.png` -As ferramentas locais ficam em `tools/art/` e devem ser tratadas como utilitarios de prototipo, nao como pipeline final. +As ferramentas locais ficam em `tools/art/` e `tools/sprite-studio/`. Scripts em `tools/art/` devem ser tratados como utilitarios de prototipo. O `tools/sprite-studio/` e o app desktop externo para editar manifestos e reduzir a dependencia do viewer Raylib embutido no jogo. + +## Sprite Studio + +O Sprite Studio vive em [`tools/sprite-studio/`](../tools/sprite-studio) e esta documentado em [`docs/18-sprite-studio.md`](18-sprite-studio.md). + +Ele usa Tauri 1.8 + React e nao compartilha codigo com o jogo. O contrato entre ferramenta e runtime e somente o artefato salvo em disco: + +- o app edita `*.sprite.json`; +- o app edita `assets/tuning/character-body-metrics.json`; +- o jogo carrega `*.sprite.json`; +- o jogo carrega `assets/tuning/character-body-metrics.json`; +- testes do jogo validam se o manifesto continua aceito. + +Comando: + +```bash +cd tools/sprite-studio +pnpm install +pnpm build +pnpm tauri dev +``` + +O Studio oferece file picker nativo, menu desktop, paineis colapsaveis, timeline horizontal, tutorial visual (`F1`), edicao de pivot/scale/boxes/origem, snap, guia de escala visual, presets iniciais de boxes, autosave em `target/sprite-studio-autosave/`, backup em `target/sprite-studio-backups/`, validacao do runtime e export de PNG/JSON para review. ## Sprite Combat Viewer -O primeiro viewer isolado de sprites vive em: +O viewer Raylib embutido no jogo continua disponivel temporariamente ate a limpeza dedicada que removera a ferramenta antiga. Ele vive em: - `src/scenes/sprite_viewer.rs`: estado testavel, carregamento de manifesto, clip/frame atual, playback e drag. - `src/engine/render/sprite_viewer.rs`: grid, pivot, bounds e desenho do atlas via Raylib. diff --git a/docs/12-technical-combat-guide.md b/docs/12-technical-combat-guide.md index 875a610..e1b3b7a 100644 --- a/docs/12-technical-combat-guide.md +++ b/docs/12-technical-combat-guide.md @@ -29,6 +29,7 @@ Sempre que um código novo alterar combate, personagens, input de combate, Comba | Combat Lab render | Orquestra Raylib da cena isolada, sprites, grid e projéteis | [`src/engine/render/combat_lab.rs`](../src/engine/render/combat_lab.rs) | Teste manual via Combat Lab | | Combat debug UI | Boxes, pivot, dummy, overlay e texto de timing do laboratório | [`src/ui/combat_debug.rs`](../src/ui/combat_debug.rs) | Teste manual via Combat Lab | | Sprite Combat Viewer | Ferramenta isolada para carregar atlas em runtime, ver grid, pivot, bounds e preparar boxes data-driven | [`src/scenes/sprite_viewer.rs`](../src/scenes/sprite_viewer.rs), [`src/scenes/sprite_viewer/combat_edit.rs`](../src/scenes/sprite_viewer/combat_edit.rs), [`src/engine/render/sprite_viewer.rs`](../src/engine/render/sprite_viewer.rs) | [`tests/sprite_viewer.rs`](../tests/sprite_viewer.rs), teste manual via `--tool sprite-viewer` | +| Sprite Studio | App externo Tauri 1.8 + React para editar manifestos sem depender de Raylib | [`tools/sprite-studio`](../tools/sprite-studio) | `pnpm build`; `pnpm tauri build --debug`; desktop requer pre-requisitos Tauri | | Input | Teclado/gamepad para luta, menu, Sprite Viewer e Combat Lab | [`src/engine/input.rs`](../src/engine/input.rs), [`src/engine/gamepad.rs`](../src/engine/gamepad.rs) | [`tests/cli.rs`](../tests/cli.rs), [`tests/feature_flags.rs`](../tests/feature_flags.rs) | | Sprite runtime | Manifest JSON, clip selection, projeção de `frames[].combat` e desenho por pivot | [`src/engine/sprites/`](../src/engine/sprites), [`src/engine/sprites/combat.rs`](../src/engine/sprites/combat.rs) | [`tests/sprite_manifest.rs`](../tests/sprite_manifest.rs), [`tests/sprite_selection.rs`](../tests/sprite_selection.rs) | @@ -74,7 +75,7 @@ Hitboxes: Essa técnica foi escolhida porque é legível, testável sem Raylib e suficiente para o Prototype 0.1. Quando o frame visual declara `frames[].combat`, o runtime projeta esses dados para coordenadas de mundo em [`src/engine/sprites/combat.rs`](../src/engine/sprites/combat.rs). A resolução da luta usa `frames[].combat.hitboxes[]` e `frames[].combat.hurtboxes[]` quando essas listas existem; se estiverem ausentes ou vazias, volta para `MoveSpec.hitbox` e `Fighter::hurtboxes()`. A decisão está registrada em [`docs/adr/0007-sprite-frame-combat-runtime.md`](adr/0007-sprite-frame-combat-runtime.md). -Rust, Duke, Go e C ja possuem `combat.projectile_origin` no primeiro frame do clip `special`. Esse ponto e projetado por [`src/engine/sprites/combat.rs`](../src/engine/sprites/combat.rs) e usado por [`src/game/world.rs`](../src/game/world.rs) ao criar o projectile, para evitar que o poder nasca desalinhado da mao. Rust `Borrow Jab`, heavy punch e kick ja possuem hitboxes de frame; os valores ainda reproduzem o alcance do `MoveSpec` para migrar com baixo risco. Hitboxes/hurtboxes restantes ainda devem ser calibradas no Sprite Combat Viewer antes de virar dado autoritativo. +Rust, Duke, Go e C ja possuem `combat.projectile_origin` no primeiro frame do clip `special`. Esse ponto e projetado por [`src/engine/sprites/combat.rs`](../src/engine/sprites/combat.rs) e usado por [`src/game/world.rs`](../src/game/world.rs) ao criar o projectile, para evitar que o poder nasca desalinhado da mao. Rust `Borrow Jab`, heavy punch e kick ja possuem hitboxes de frame; os valores ainda reproduzem o alcance do `MoveSpec` para migrar com baixo risco. Hitboxes/hurtboxes restantes devem ser calibradas no Sprite Studio, com o Sprite Combat Viewer Raylib apenas como ferramenta temporaria ate a limpeza dedicada. ### Escala Visual e Pivot @@ -97,9 +98,10 @@ O padrao de tamanho em tela fica em [`docs/17-visual-scale-and-stage-metrics.md` - personagens nao-humanos, como Go, devem mirar a mesma faixa principal quando a diferenca de tamanho nao for parte do gameplay; - a arena atual tem `896 px` jogaveis, cerca de `11,8` larguras de corpo padrao. -Use o Sprite Combat Viewer para calibrar: +Use o Sprite Studio para edicao visual confortavel e validacao de runtime. O Sprite Combat Viewer embutido continua disponivel apenas ate a limpeza dedicada: ```bash +cd tools/sprite-studio && pnpm tauri dev cargo run -- --tool sprite-viewer --manifest assets/placeholder/go-fighter.sprite.json --clip idle --character go --move light_punch cargo run -- --tool sprite-viewer --manifest assets/placeholder/c-fighter.sprite.json --clip idle --character c --move light_punch ``` @@ -353,6 +355,8 @@ Poses atuais: ### Sprite Combat Viewer +O Sprite Combat Viewer Raylib e ferramenta temporaria. A direcao aprovada em [`docs/adr/0008-external-sprite-studio-tooling.md`](adr/0008-external-sprite-studio-tooling.md) e mover a edicao rica para [`tools/sprite-studio`](../tools/sprite-studio). A paridade operacional ja existe; o viewer embutido deve ser removido em uma mudanca propria. + Abrir a ferramenta isolada de sprites: ```bash diff --git a/docs/16-sprite-combat-viewer-roadmap.md b/docs/16-sprite-combat-viewer-roadmap.md index f428083..6d9394d 100644 --- a/docs/16-sprite-combat-viewer-roadmap.md +++ b/docs/16-sprite-combat-viewer-roadmap.md @@ -2,7 +2,9 @@ ## Status -Em implementacao inicial na branch `tooling/sprite-combat-viewer`. +Implementado como ferramenta Raylib temporaria. O sucessor e o Sprite Studio externo em Tauri + React, documentado em [`docs/18-sprite-studio.md`](18-sprite-studio.md) e decidido em [`docs/adr/0008-external-sprite-studio-tooling.md`](adr/0008-external-sprite-studio-tooling.md). + +O Sprite Studio ja possui paridade operacional para abrir, editar, validar e exportar review de manifestos. O viewer Raylib deve ser removido em uma mudanca propria. Issue de rastreio: [#15](https://github.com/osdeving/borrow-fighters/issues/15). @@ -10,7 +12,7 @@ Issue de rastreio: [#15](https://github.com/osdeving/borrow-fighters/issues/15). Criar uma ferramenta leve para artistas e devs verificarem sprites, pivots, alinhamento visual, escala e, nas proximas fases, hitbox, hurtbox, origem de projectile e dummy de contato sem entrar no fluxo normal de luta. -O viewer nao deve ser tratado como editor final. Ele e uma ferramenta plugavel de producao para reduzir tentativa e erro ao criar atlas e ajustar manifestos. +O viewer nao deve ser tratado como editor final. Ele foi uma ferramenta plugavel para reduzir tentativa e erro ao criar atlas e ajustar manifestos. A direcao atual e mover a experiencia de edicao rica para o Sprite Studio externo. ## Por Que Nao Basta o Combat Lab @@ -27,16 +29,14 @@ O Sprite Combat Viewer fica um nivel antes: ele inspeciona o asset e seus dados. ## Pesquisa de GUI -Para o primeiro corte, Raylib puro e suficiente: grade, linhas, mouse drag, texto e atalhos de teclado cobrem a inspecao basica. - -Se a ferramenta passar a precisar de painel lateral com checkboxes, sliders, listas e inputs editaveis, a opcao candidata e `raygui`, biblioteca immediate-mode criada para o ecossistema raylib. O crate `raylib` 6.0 tambem expoe uma feature `raygui`, entao o caminho tecnico existe sem trocar stack. +Raylib puro foi suficiente para grade, linhas, mouse drag, texto e atalhos de teclado. A partir do momento em que a ferramenta passou a precisar de painel lateral, checkboxes, sliders, listas, inputs editaveis e fluxo de artista, a decisao mudou para Tauri + React. Decisao atual: -- usar Raylib puro no corte inicial; -- evitar `egui`/`imgui` enquanto a ferramenta couber em primitives; -- avaliar `raygui` na Fase 2 ou 3 se os controles por tecla ficarem insuficientes; -- nao salvar arquivos automaticamente antes de termos consenso sobre schema de boxes. +- manter Raylib viewer apenas ate a branch de remocao dedicada; +- evoluir UI rica no Sprite Studio; +- nao introduzir `raygui`, `egui` ou `imgui` dentro do jogo neste momento; +- remover o viewer Raylib quando o Sprite Studio conseguir salvar manifestos aceitos pelo jogo. Referencias: diff --git a/docs/18-sprite-studio.md b/docs/18-sprite-studio.md new file mode 100644 index 0000000..cd8d4c6 --- /dev/null +++ b/docs/18-sprite-studio.md @@ -0,0 +1,164 @@ +# 18 — Sprite Studio + +## Status + +Corte funcional implementado e validado em `tools/sprite-studio`. + +O Sprite Studio e a ferramenta desktop externa para revisar e editar artefatos de sprite do Borrow Fighters. Ele substituira o Sprite Combat Viewer embutido no jogo quando atingir paridade operacional. + +O corte atual cobre a paridade operacional necessaria para abrir, ajustar, validar e revisar manifestos de sprite fora do game loop. A remocao do viewer Raylib deve acontecer em um passo separado, para reduzir risco e manter o historico limpo. + +## Decisao + +A decisao arquitetural fica em [`docs/adr/0008-external-sprite-studio-tooling.md`](adr/0008-external-sprite-studio-tooling.md). + +Resumo: + +- Tauri 1.8 + React + TypeScript; +- pnpm como package manager; +- backend Rust apenas para ler/salvar arquivos locais; +- sem dependencia de codigo do jogo principal; +- comunicacao por artefatos: `*.sprite.json`, PNGs e manifestos de tuning. + +O Studio usa Tauri 1.8.x nesta fase porque o ambiente WSL/Ubuntu 20.04 do projeto possui GLib/GIO 2.64.6. A cadeia Linux do Tauri 2 puxa WebKitGTK/libsoup3 e exige GLib/GIO 2.70 via `glib-sys`/`gio-sys`, causando falha de `pkg-config`. Tauri 1 usa WebKitGTK/libsoup2 e compila com `libwebkit2gtk-4.0-dev`. + +## Como Rodar + +```bash +cd tools/sprite-studio +pnpm install +pnpm build +pnpm tauri dev +``` + +Build desktop debug: + +```bash +cd tools/sprite-studio +pnpm tauri build --debug +``` + +No Linux, o Tauri precisa de bibliotecas nativas de WebKitGTK, DBus, SVG e integracao desktop. Em Ubuntu 20.04, o conjunto esperado e: + +```bash +sudo apt-get install -y \ + libdbus-1-dev \ + libwebkit2gtk-4.0-dev \ + librsvg2-dev \ + libxdo-dev \ + libayatana-appindicator3-dev \ + libssl-dev +``` + +Em Ubuntu mais novo, a documentacao oficial do Tauri pode indicar `libwebkit2gtk-4.1-dev` em vez de `libwebkit2gtk-4.0-dev`. + +## Corte Atual + +O primeiro corte ja permite: + +- abrir manifestos por caminho relativo ao repo ou file picker nativo; +- usar presets de Rust, Duke, Go e C; +- carregar o atlas PNG referenciado pelo manifesto; +- navegar por clips e frames com lista lateral e timeline horizontal; +- usar barra de menu desktop (`File`, `Edit`, `View`, `Help`) e toolbar de acoes frequentes; +- esconder/mostrar browser lateral, inspector e timeline para abrir espaco no canvas; +- visualizar grid, `trimmed_bounds`, `source_crop`, guia de escala alvo, pivot, hitboxes, hurtboxes e origem de projétil; +- editar `scale`, `pivot`, `duration_ms`, `projectile_origin`, `hurtboxes` e `hitboxes`; +- adicionar caixas novas clicando no canvas em modo `Hurtbox` ou `Hitbox`; +- mover pivot/origem de projétil clicando ou arrastando no canvas; +- selecionar, mover e redimensionar hitboxes/hurtboxes com alças no canvas, cursor contextual e snap opcional de 1/2/4/8/16 px; +- controlar zoom pelo slider, por `+`/`-` ou mouse wheel sobre o canvas; +- desfazer/refazer edições com `Ctrl+Z`, `Ctrl+Shift+Z` ou `Ctrl+Y`; +- consultar `Help -> Sprite Studio Guide` ou `F1` para ver tutorial com diagramas sobre vertical slice, target range, pivot, hitbox, hurtbox e origem de projectile; +- validar manifesto antes de salvar, bloqueando erros que o runtime rejeitaria; +- rodar validacao do runtime por botao, usando `cargo test --test sprite_manifest --test sprite_frames --test sprite_selection --test characters`; +- aplicar presets por frame para hurtbox em pe, hurtbox abaixado, strike medio, strike baixo, origem de projectile e limpeza de metadata de combate; +- editar e salvar `assets/tuning/character-body-metrics.json` por personagem; +- salvar automaticamente rascunhos em `target/sprite-studio-autosave/`; +- criar backup do manifesto anterior em `target/sprite-studio-backups/` antes de salvar no arquivo real; +- exportar PNG de review com sprite, pivot, bounds e boxes; +- exportar JSON de review com frame atual, escala, metricas, validacoes, resultado do runtime e notas; +- salvar o manifesto JSON de volta no arquivo original. + +## Fluxo Recomendado + +1. Abrir o manifesto pelo preset ou por `Open Manifest`. +2. Escolher clip/frame pela lista lateral ou pela timeline. +3. Ligar `Scale guide` e ajustar `scale`/pivot ate o frame caber no range visual alvo. +4. Usar `Combat Presets` como rascunho inicial de hurtbox/hitbox/origem. +5. Refinar no canvas com snap ligado. +6. Rodar `Run Runtime Validation`. +7. Exportar PNG e JSON de review para anexar no PR ou na issue de arte. + +## Comandos De UI + +| Acao | Comando | +|---|---| +| Abrir ajuda | `F1` ou `Help -> Sprite Studio Guide` | +| Salvar manifesto | `Ctrl+S` ou `File -> Save JSON` | +| Desfazer | `Ctrl+Z` | +| Refazer | `Ctrl+Shift+Z` ou `Ctrl+Y` | +| Frame anterior/proximo | `Seta esquerda` / `Seta direita` | +| Zoom in/out | `+` / `-`, slider de zoom ou mouse wheel sobre o canvas | +| Criar hurtbox/hitbox | Selecionar modo `Hurtbox`/`Hitbox` e clicar no canvas | +| Mover pivot/origem/box | Arrastar no canvas | +| Redimensionar box | Arrastar as alcas da box selecionada | +| Esconder paineis | `View -> Hide Browser Panel`, `View -> Hide Inspector`, `View -> Hide Timeline` | + +## Autosave E Backup + +O autosave nao altera o manifesto real. Ele grava o estado sujo atual em: + +```text +target/sprite-studio-autosave/ +``` + +Ao salvar manualmente o manifesto real, o backend cria primeiro uma copia do arquivo anterior em: + +```text +target/sprite-studio-backups/ +``` + +Essas pastas ficam dentro de `target/` e nao devem ser versionadas. + +## Regra De Isolamento + +O Sprite Studio replica tipos de manifesto em TypeScript e comandos de arquivo em Rust dentro de `tools/sprite-studio`. Ele nao importa structs, crates, modulos ou testes do jogo. + +Se o schema `borrow-fighters.sprite.v1` mudar, atualize: + +- [`docs/11-sprite-pipeline.md`](11-sprite-pipeline.md); +- os testes do jogo que validam manifestos; +- os tipos locais do Sprite Studio; +- pelo menos um manifesto real usado como exemplo. + +## Criterio Para Remover O Viewer Do Jogo + +O Sprite Combat Viewer dentro do jogo deve continuar disponivel enquanto o Sprite Studio nao fizer tudo que o fluxo atual precisa. + +Remover o viewer Raylib somente quando o Sprite Studio conseguir: + +- abrir e salvar os manifestos atuais; +- editar scale, pivot, origem de projectile, hitbox e hurtbox por frame; +- editar as metricas fisicas `width`, `standing_height` e `crouch_height`; +- comparar frames contra escala alvo de personagem; +- suportar fluxo de review de artista sem depender do game loop; +- gerar JSON que `cargo test --all-targets` aceita. + +Quando isso acontecer, remover: + +- `src/scenes/sprite_viewer.rs`; +- `src/scenes/sprite_viewer/`; +- `src/engine/render/sprite_viewer.rs`; +- rotas `--tool sprite-viewer`; +- entrada de menu `Training -> Sprite Viewer`; +- testes dedicados ao viewer Raylib que deixarem de fazer sentido. + +Estado atual: a paridade de features e o smoke test desktop foram validados. O proximo passo e remover o viewer Raylib em uma mudanca propria. + +## Proximos Incrementos + +- Remover o Sprite Combat Viewer Raylib do jogo, menu e CLI. +- Adicionar comparacao lado a lado com um personagem de referencia real, se a guia de escala nao for suficiente. +- Criar presets mais especificos por personagem/golpe quando a identidade de combate estabilizar. +- Permitir exportar um pacote de review com PNG + JSON juntos. diff --git a/docs/adr/0008-external-sprite-studio-tooling.md b/docs/adr/0008-external-sprite-studio-tooling.md new file mode 100644 index 0000000..a3278dc --- /dev/null +++ b/docs/adr/0008-external-sprite-studio-tooling.md @@ -0,0 +1,131 @@ +# ADR 0008 — Sprite Studio externo em Tauri + React + +## Status + +Aceito. + +## Contexto + +O Sprite Combat Viewer nasceu dentro do jogo em Rust + Raylib para validar rapidamente atlas, pivot, escala, origem de projétil, hitbox e hurtbox. Esse corte foi útil para provar o formato `*.sprite.json`, mas a ferramenta começou a exigir UI de editor: painéis, timeline, seleção de frames, handles de resize, formulários, zoom, grid, undo/redo e salvamento de artefatos. + +Raylib continua adequada para o jogo, Combat Lab e debug visual rápido, mas não é uma boa base para uma ferramenta de produção voltada a artistas. + +## Decisão + +Criar um aplicativo separado chamado **Sprite Studio** em `tools/sprite-studio`, usando: + +- Tauri 1.8 para empacotar uma aplicação desktop leve com backend Rust; +- React + TypeScript para UI rica, painéis, formulários e canvas/SVG; +- pnpm como gerenciador de pacotes JavaScript; +- contratos de JSON duplicados dentro do Sprite Studio, sem dependência de código do jogo principal. + +Tauri 1.8 foi escolhido para o corte inicial por compatibilidade com o ambiente WSL/Ubuntu 20.04 do projeto. Esse ambiente fornece GLib/GIO 2.64.6 e `libwebkit2gtk-4.0-dev`; a cadeia Linux do Tauri 2 usa WebKitGTK/libsoup3 e exige GLib/GIO 2.70, quebrando em `pkg-config` via `glib-sys`/`gio-sys`. + +Revisitar Tauri 2 quando o baseline de desenvolvimento migrar para uma distro com GLib/GIO >= 2.70 ou quando o projeto decidir empacotar o Studio em um ambiente controlado. + +O jogo e o Sprite Studio se comunicam apenas por artefatos de disco: + +- `*.sprite.json`; +- atlas PNG; +- imagens de projétil; +- manifestos de tuning quando aplicável. + +Não haverá crate compartilhado, import de structs do jogo, workspace comum nem dependência de runtime entre os dois projetos nesta fase. + +## Consequências + +### Positivas + +- A UI de editor deixa de disputar espaço com o game loop. +- Artistas podem usar uma ferramenta dedicada para inspecionar atlas, grid, pivot, hitbox, hurtbox e origem de projétil. +- O jogo permanece focado em runtime e combate. +- O formato de artefatos continua simples e auditável. +- O isolamento reduz acoplamento entre protótipo jogável e tooling. + +### Negativas + +- Alguns contratos de manifesto serão duplicados. +- Mudanças no schema exigem atualização explícita no jogo e no Sprite Studio. +- O repositório passa a ter uma segunda stack: Rust/Raylib no jogo e Tauri/React no tooling. +- Validações precisam garantir que o Sprite Studio não gere JSON que o jogo rejeita. + +## Regra De Isolamento + +O Sprite Studio pode ler e escrever arquivos do repositório, mas não pode depender de módulos Rust, structs, crates internos ou lógica compilada do jogo. Se o schema evoluir, a sincronização deve acontecer por documentação, testes e exemplos de manifesto. + +## Migração Do Viewer Antigo + +O Sprite Combat Viewer embutido no jogo fica temporariamente disponível enquanto o Sprite Studio ainda não tiver paridade operacional. + +Critério mínimo para remover o viewer do jogo: + +- abrir um `*.sprite.json` existente; +- carregar o atlas referenciado; +- navegar por clips e frames; +- visualizar grid, frame bounds, pivot, escala, hitboxes, hurtboxes e origem de projétil; +- editar e salvar `scale`, `pivot`, `frames[].combat.hitboxes`, `frames[].combat.hurtboxes` e `frames[].combat.projectile_origin`; +- validar que o jogo carrega o manifesto salvo; +- documentar o fluxo para artistas. + +Quando esses critérios forem atendidos, remover o código de Sprite Viewer de `src/scenes`, `src/engine/render` e rotas de menu/CLI relacionadas, mantendo o Combat Lab no jogo. + +## Alternativas Consideradas + +### Manter tudo em Raylib + +Prós: + +- uma stack só; +- acesso direto ao rendering do jogo. + +Contras: + +- UI complexa demais para Raylib; +- painéis e formulários viram código manual; +- ferramenta de artista fica presa ao loop do jogo. + +### egui/eframe + +Prós: + +- Rust puro; +- boa UI imediata para ferramentas. + +Contras: + +- menos familiar para colaboradores de frontend; +- layout e edição visual rica tendem a exigir mais código customizado. + +### Electron + +Prós: + +- ecossistema maduro; +- React muito bem suportado. + +Contras: + +- runtime mais pesado; +- menos alinhado ao desejo de manter Rust no backend local. + +### C# + Windows Forms + +Prós: + +- rápido para UI desktop simples no Windows. + +Contras: + +- amarra a ferramenta a Windows; +- cria stack distante do restante do projeto; +- pior para colaboração cross-platform. + +## Critério De Revisão + +Revisar esta decisão se: + +- Tauri dificultar o acesso a arquivos locais no fluxo real de artistas; +- o baseline Linux do projeto deixar de precisar de Ubuntu 20.04/GLib 2.64; +- o Sprite Studio exigir rendering idêntico ao jogo em nível de shader/runtime; +- a duplicação de schema virar fonte frequente de bugs; +- o projeto decidir migrar para uma engine com editor visual próprio. diff --git a/tools/sprite-studio/.gitignore b/tools/sprite-studio/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/tools/sprite-studio/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/tools/sprite-studio/README.md b/tools/sprite-studio/README.md new file mode 100644 index 0000000..19be0da --- /dev/null +++ b/tools/sprite-studio/README.md @@ -0,0 +1,70 @@ +# Borrow Fighters Sprite Studio + +Desktop tool for inspecting and editing Borrow Fighters `*.sprite.json` files outside the Raylib game loop. + +This app is intentionally isolated from the main game code. It can read and write repository artifacts, but it must not import Rust modules, structs, crates or tests from the game. + +This cut uses Tauri 1.8 because the current WSL/Ubuntu 20.04 baseline ships GLib/GIO 2.64.6. Tauri 2 currently pulls the Linux WebKitGTK/libsoup3 path that requires GLib/GIO 2.70. + +## Commands + +```bash +pnpm install +pnpm build +pnpm tauri dev +pnpm tauri build --debug +``` + +## Linux Prerequisites + +Tauri requires native desktop libraries. On Ubuntu 20.04: + +```bash +sudo apt-get install -y \ + libdbus-1-dev \ + libwebkit2gtk-4.0-dev \ + librsvg2-dev \ + libxdo-dev \ + libayatana-appindicator3-dev \ + libssl-dev +``` + +Newer Ubuntu releases may use `libwebkit2gtk-4.1-dev`; check the official Tauri prerequisite docs for the local OS. + +## Current Scope + +- Open a sprite manifest by repository-relative path or native file picker. +- Use Rust, Duke, Go and C presets. +- Render the selected atlas frame. +- Navigate clips and frames through the side list and horizontal timeline. +- Use desktop-style File/Edit/View/Help menus and a toolbar. +- Collapse the browser panel, inspector and timeline when the canvas needs space. +- Inspect grid, frame bounds, visual scale target, pivot, hitboxes, hurtboxes and projectile origin. +- Edit `scale`, `pivot`, `duration_ms`, `projectile_origin`, `hitboxes` and `hurtboxes`. +- Drag pivot, projectile origin and boxes directly on the canvas. +- Resize boxes through visual handles, hover cursors and optional snap. +- Zoom with the slider, `+`/`-` or mouse wheel over the canvas. +- Seed initial combat boxes for standing/crouch hurtbox, mid/low strike and projectile origin. +- Undo/redo local edits. +- Open the built-in guide with `F1` or Help. +- Autosave dirty work to `target/sprite-studio-autosave/`. +- Backup the previous manifest to `target/sprite-studio-backups/` before manual saves. +- Edit `assets/tuning/character-body-metrics.json`. +- Run runtime validation through the repository `cargo test` contract. +- Export a PNG review image. +- Export a JSON review package with notes, selected frame data and validation output. +- Save the manifest JSON back to disk. + +## Artifact Contract + +The Sprite Studio produces files that the game consumes: + +- `assets/placeholder/*.sprite.json` +- PNG atlas files referenced by `image` + +Run the game test suite after saving production changes: + +```bash +cd ../.. +cargo test --all-targets +``` diff --git a/tools/sprite-studio/index.html b/tools/sprite-studio/index.html new file mode 100644 index 0000000..73491b7 --- /dev/null +++ b/tools/sprite-studio/index.html @@ -0,0 +1,13 @@ + + + + + + Borrow Fighters Sprite Studio + + + +
+ + + diff --git a/tools/sprite-studio/package.json b/tools/sprite-studio/package.json new file mode 100644 index 0000000..152f6fd --- /dev/null +++ b/tools/sprite-studio/package.json @@ -0,0 +1,25 @@ +{ + "name": "borrow-fighters-sprite-studio", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview", + "tauri": "tauri" + }, + "dependencies": { + "@tauri-apps/api": "1.6.0", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@tauri-apps/cli": "1.6.3", + "@types/react": "^19.1.8", + "@types/react-dom": "^19.1.6", + "@vitejs/plugin-react": "^4.6.0", + "typescript": "~5.8.3", + "vite": "^7.0.4" + } +} diff --git a/tools/sprite-studio/pnpm-lock.yaml b/tools/sprite-studio/pnpm-lock.yaml new file mode 100644 index 0000000..2c0b735 --- /dev/null +++ b/tools/sprite-studio/pnpm-lock.yaml @@ -0,0 +1,1250 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@tauri-apps/api': + specifier: 1.6.0 + version: 1.6.0 + react: + specifier: ^19.1.0 + version: 19.2.7 + react-dom: + specifier: ^19.1.0 + version: 19.2.7(react@19.2.7) + devDependencies: + '@tauri-apps/cli': + specifier: 1.6.3 + version: 1.6.3 + '@types/react': + specifier: ^19.1.8 + version: 19.2.17 + '@types/react-dom': + specifier: ^19.1.6 + version: 19.2.3(@types/react@19.2.17) + '@vitejs/plugin-react': + specifier: ^4.6.0 + version: 4.7.0(vite@7.3.5) + typescript: + specifier: ~5.8.3 + version: 5.8.3 + vite: + specifier: ^7.0.4 + version: 7.3.5 + +packages: + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.29.7': + resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.29.7': + resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.62.2': + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.62.2': + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.62.2': + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.62.2': + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.2': + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.2': + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.62.2': + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-loong64-musl@4.62.2': + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.62.2': + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.62.2': + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openbsd-x64@4.62.2': + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.2': + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.2': + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.62.2': + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} + cpu: [x64] + os: [win32] + + '@tauri-apps/api@1.6.0': + resolution: {integrity: sha512-rqI++FWClU5I2UBp4HXFvl+sBWkdigBkxnpJDQUWttNyG7IZP4FwQGhTNL5EOw0vI8i6eSAJ5frLqO7n7jbJdg==} + engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} + + '@tauri-apps/cli-darwin-arm64@1.6.3': + resolution: {integrity: sha512-fQN6IYSL8bG4NvkdKE4sAGF4dF/QqqQq4hOAU+t8ksOzHJr0hUlJYfncFeJYutr/MMkdF7hYKadSb0j5EE9r0A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tauri-apps/cli-darwin-x64@1.6.3': + resolution: {integrity: sha512-1yTXZzLajKAYINJOJhZfmMhCzweHSgKQ3bEgJSn6t+1vFkOgY8Yx4oFgWcybrrWI5J1ZLZAl47+LPOY81dLcyA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tauri-apps/cli-linux-arm-gnueabihf@1.6.3': + resolution: {integrity: sha512-CjTEr9r9xgjcvos09AQw8QMRPuH152B1jvlZt4PfAsyJNPFigzuwed5/SF7XAd8bFikA7zArP4UT12RdBxrx7w==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tauri-apps/cli-linux-arm64-gnu@1.6.3': + resolution: {integrity: sha512-G9EUUS4M8M/Jz1UKZqvJmQQCKOzgTb8/0jZKvfBuGfh5AjFBu8LHvlFpwkKVm1l4951Xg4ulUp6P9Q7WRJ9XSA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tauri-apps/cli-linux-arm64-musl@1.6.3': + resolution: {integrity: sha512-MuBTHJyNpZRbPVG8IZBN8+Zs7aKqwD22tkWVBcL1yOGL4zNNTJlkfL+zs5qxRnHlUsn6YAlbW/5HKocfpxVwBw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tauri-apps/cli-linux-x64-gnu@1.6.3': + resolution: {integrity: sha512-Uvi7M+NK3tAjCZEY1WGel+dFlzJmqcvu3KND+nqa22762NFmOuBIZ4KJR/IQHfpEYqKFNUhJfCGnpUDfiC3Oxg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tauri-apps/cli-linux-x64-musl@1.6.3': + resolution: {integrity: sha512-rc6B342C0ra8VezB/OJom9j/N+9oW4VRA4qMxS2f4bHY2B/z3J9NPOe6GOILeg4v/CV62ojkLsC3/K/CeF3fqQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tauri-apps/cli-win32-arm64-msvc@1.6.3': + resolution: {integrity: sha512-cSH2qOBYuYC4UVIFtrc1YsGfc5tfYrotoHrpTvRjUGu0VywvmyNk82+ZsHEnWZ2UHmu3l3lXIGRqSWveLln0xg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tauri-apps/cli-win32-ia32-msvc@1.6.3': + resolution: {integrity: sha512-T8V6SJQqE4PSWmYBl0ChQVmS6AR2hXFHURH2DwAhgSGSQ6uBXgwlYFcfIeQpBQA727K2Eq8X2hGfvmoySyHMRw==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + + '@tauri-apps/cli-win32-x64-msvc@1.6.3': + resolution: {integrity: sha512-HUkWZ+lYHI/Gjkh2QjHD/OBDpqLVmvjZGpLK9losur1Eg974Jip6k+vsoTUxQBCBDfj30eDBct9E1FvXOspWeg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tauri-apps/cli@1.6.3': + resolution: {integrity: sha512-q46umd6QLRKDd4Gg6WyZBGa2fWvk0pbeUA5vFomm4uOs1/17LIciHv2iQ4UD+2Yv5H7AO8YiE1t50V0POiEGEw==} + engines: {node: '>= 10'} + hasBin: true + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.17': + resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + baseline-browser-mapping@2.10.38: + resolution: {integrity: sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==} + engines: {node: '>=6.0.0'} + hasBin: true + + browserslist@4.28.4: + resolution: {integrity: sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + caniuse-lite@1.0.30001799: + resolution: {integrity: sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + electron-to-chromium@1.5.377: + resolution: {integrity: sha512-cH1jZgJHoezfTnKfKwnScpHywTFVnJUNITDPREFdhNjiuD502+QFpG0Qk7G8jhsV/f+CEAFlIrzP1fT+IMb92g==} + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.15: + resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.48: + resolution: {integrity: sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==} + engines: {node: '>=18'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + react-dom@19.2.7: + resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} + peerDependencies: + react: ^19.2.7 + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react@19.2.7: + resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} + engines: {node: '>=0.10.0'} + + rollup@4.62.2: + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.5: + resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==} + engines: {node: '>=10'} + hasBin: true + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + typescript@5.8.3: + resolution: {integrity: sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==} + engines: {node: '>=14.17'} + hasBin: true + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + vite@7.3.5: + resolution: {integrity: sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + +snapshots: + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.4 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.62.2': + optional: true + + '@rollup/rollup-android-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-x64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.62.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.62.2': + optional: true + + '@tauri-apps/api@1.6.0': {} + + '@tauri-apps/cli-darwin-arm64@1.6.3': + optional: true + + '@tauri-apps/cli-darwin-x64@1.6.3': + optional: true + + '@tauri-apps/cli-linux-arm-gnueabihf@1.6.3': + optional: true + + '@tauri-apps/cli-linux-arm64-gnu@1.6.3': + optional: true + + '@tauri-apps/cli-linux-arm64-musl@1.6.3': + optional: true + + '@tauri-apps/cli-linux-x64-gnu@1.6.3': + optional: true + + '@tauri-apps/cli-linux-x64-musl@1.6.3': + optional: true + + '@tauri-apps/cli-win32-arm64-msvc@1.6.3': + optional: true + + '@tauri-apps/cli-win32-ia32-msvc@1.6.3': + optional: true + + '@tauri-apps/cli-win32-x64-msvc@1.6.3': + optional: true + + '@tauri-apps/cli@1.6.3': + dependencies: + semver: 7.8.5 + optionalDependencies: + '@tauri-apps/cli-darwin-arm64': 1.6.3 + '@tauri-apps/cli-darwin-x64': 1.6.3 + '@tauri-apps/cli-linux-arm-gnueabihf': 1.6.3 + '@tauri-apps/cli-linux-arm64-gnu': 1.6.3 + '@tauri-apps/cli-linux-arm64-musl': 1.6.3 + '@tauri-apps/cli-linux-x64-gnu': 1.6.3 + '@tauri-apps/cli-linux-x64-musl': 1.6.3 + '@tauri-apps/cli-win32-arm64-msvc': 1.6.3 + '@tauri-apps/cli-win32-ia32-msvc': 1.6.3 + '@tauri-apps/cli-win32-x64-msvc': 1.6.3 + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.7 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.7 + + '@types/estree@1.0.9': {} + + '@types/react-dom@19.2.3(@types/react@19.2.17)': + dependencies: + '@types/react': 19.2.17 + + '@types/react@19.2.17': + dependencies: + csstype: 3.2.3 + + '@vitejs/plugin-react@4.7.0(vite@7.3.5)': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 7.3.5 + transitivePeerDependencies: + - supports-color + + baseline-browser-mapping@2.10.38: {} + + browserslist@4.28.4: + dependencies: + baseline-browser-mapping: 2.10.38 + caniuse-lite: 1.0.30001799 + electron-to-chromium: 1.5.377 + node-releases: 2.0.48 + update-browserslist-db: 1.2.3(browserslist@4.28.4) + + caniuse-lite@1.0.30001799: {} + + convert-source-map@2.0.0: {} + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + electron-to-chromium@1.5.377: {} + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escalade@3.2.0: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + js-tokens@4.0.0: {} + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + ms@2.1.3: {} + + nanoid@3.3.15: {} + + node-releases@2.0.48: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + postcss@8.5.15: + dependencies: + nanoid: 3.3.15 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + react-dom@19.2.7(react@19.2.7): + dependencies: + react: 19.2.7 + scheduler: 0.27.0 + + react-refresh@0.17.0: {} + + react@19.2.7: {} + + rollup@4.62.2: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.62.2 + '@rollup/rollup-android-arm64': 4.62.2 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-darwin-x64': 4.62.2 + '@rollup/rollup-freebsd-arm64': 4.62.2 + '@rollup/rollup-freebsd-x64': 4.62.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 + '@rollup/rollup-linux-arm-musleabihf': 4.62.2 + '@rollup/rollup-linux-arm64-gnu': 4.62.2 + '@rollup/rollup-linux-arm64-musl': 4.62.2 + '@rollup/rollup-linux-loong64-gnu': 4.62.2 + '@rollup/rollup-linux-loong64-musl': 4.62.2 + '@rollup/rollup-linux-ppc64-gnu': 4.62.2 + '@rollup/rollup-linux-ppc64-musl': 4.62.2 + '@rollup/rollup-linux-riscv64-gnu': 4.62.2 + '@rollup/rollup-linux-riscv64-musl': 4.62.2 + '@rollup/rollup-linux-s390x-gnu': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 + '@rollup/rollup-linux-x64-musl': 4.62.2 + '@rollup/rollup-openbsd-x64': 4.62.2 + '@rollup/rollup-openharmony-arm64': 4.62.2 + '@rollup/rollup-win32-arm64-msvc': 4.62.2 + '@rollup/rollup-win32-ia32-msvc': 4.62.2 + '@rollup/rollup-win32-x64-gnu': 4.62.2 + '@rollup/rollup-win32-x64-msvc': 4.62.2 + fsevents: 2.3.3 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.8.5: {} + + source-map-js@1.2.1: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + typescript@5.8.3: {} + + update-browserslist-db@1.2.3(browserslist@4.28.4): + dependencies: + browserslist: 4.28.4 + escalade: 3.2.0 + picocolors: 1.1.1 + + vite@7.3.5: + dependencies: + esbuild: 0.27.7 + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + postcss: 8.5.15 + rollup: 4.62.2 + tinyglobby: 0.2.17 + optionalDependencies: + fsevents: 2.3.3 + + yallist@3.1.1: {} diff --git a/tools/sprite-studio/src-tauri/.gitignore b/tools/sprite-studio/src-tauri/.gitignore new file mode 100644 index 0000000..b21bd68 --- /dev/null +++ b/tools/sprite-studio/src-tauri/.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/tools/sprite-studio/src-tauri/Cargo.lock b/tools/sprite-studio/src-tauri/Cargo.lock new file mode 100644 index 0000000..d21620a --- /dev/null +++ b/tools/sprite-studio/src-tauri/Cargo.lock @@ -0,0 +1,4019 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +dependencies = [ + "alloc-no-stdlib", +] + +[[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.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "atk" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd" +dependencies = [ + "atk-sys", + "bitflags 1.3.2", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.2.2", +] + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[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.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" + +[[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 = "borrow-fighters-sprite-studio" +version = "0.1.0" +dependencies = [ + "base64 0.22.1", + "serde", + "serde_json", + "tauri", + "tauri-build", +] + +[[package]] +name = "brotli" +version = "7.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc97b8f16f944bba54f0433f07e30be199b6dc2bd25937444bbad560bcea29bd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "4.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a334ef7c9e23abf0ce748e8cd309037da93e606ad52eb372e4ce327a0dcfbdfd" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bstr" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ae3f5d315924270530207e2a68396c3cc547f6dca3fbdca317cfb1a51edb593" + +[[package]] +name = "cairo-rs" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "glib", + "libc", + "thiserror", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" +dependencies = [ + "glib-sys", + "libc", + "system-deps 6.2.2", +] + +[[package]] +name = "cargo_toml" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "599aa35200ffff8f04c1925aa1acc92fa2e08874379ef42e210a80e527e60838" +dependencies = [ + "serde", + "toml 0.7.8", +] + +[[package]] +name = "cc" +version = "1.2.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e228eec9be7c17ccb640b59b36a5cd805ea2a564a4c5e162c2f659fea30d3b96" +dependencies = [ + "find-msvc-tools", + "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.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3431df59f28accaf4cb4eed4a9acc66bea3f3c3753aa6cdc2f024174ef232af7" +dependencies = [ + "smallvec", +] + +[[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.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "cocoa" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" +dependencies = [ + "bitflags 1.3.2", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" +dependencies = [ + "bitflags 1.3.2", + "block", + "core-foundation", + "core-graphics-types", + "libc", + "objc", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[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-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "core-graphics-types", + "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", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +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.118", +] + +[[package]] +name = "ctor" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a2785755761f3ddc1492979ce1e48d2c00d09311c39e4466429188f3dd6501" +dependencies = [ + "quote", + "syn 2.0.118", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.118", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.118", +] + +[[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-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "displaydoc" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ac70aa55017e108007fbaf5aa0f54b021c98f92ff8af59d42eda9da96e3dd4f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[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.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "embed-resource" +version = "2.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d506610004cfc74a6f5ee7e8c632b355de5eca1f03ee5e5e0ec11b77d4eb3d61" +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.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +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 = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[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.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +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.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-macro" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "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.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a" +dependencies = [ + "bitflags 1.3.2", + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140b2f5378256527150350a8346dbdb08fadc13453a7a2d73aecd5fab3c402a7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.2.2", +] + +[[package]] +name = "gdk-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e7a08c1e8f06f4177fb7e51a777b8c1689f743a7bc11ea91d44d2226073a88" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps 6.2.2", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca49a59ad8cfdf36ef7330fe7bdfbe1d34323220cc16a0de2679ee773aee2c2" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps 6.2.2", +] + +[[package]] +name = "gdkx11-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b7f8c7a84b407aa9b143877e267e848ff34106578b64d1e0a24bf550716178" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps 6.2.2", + "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.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.1+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi", +] + +[[package]] +name = "gio" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b" +dependencies = [ + "bitflags 1.3.2", + "futures-channel", + "futures-core", + "futures-io", + "gio-sys", + "glib", + "libc", + "once_cell", + "thiserror", +] + +[[package]] +name = "gio-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32157a475271e2c4a023382e9cab31c4584ee30a97da41d3c4e9fdd605abcf8d" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.2.2", + "winapi", +] + +[[package]] +name = "glib" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" +dependencies = [ + "bitflags 1.3.2", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "once_cell", + "smallvec", + "thiserror", +] + +[[package]] +name = "glib-macros" +version = "0.15.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10c6ae9f6fa26f4fb2ac16b528d138d971ead56141de489f8111e259b9df3c4a" +dependencies = [ + "anyhow", + "heck 0.4.1", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "glib-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" +dependencies = [ + "libc", + "system-deps 6.2.2", +] + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "globset" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52dfc19153a48bde0cbd630453615c8151bce3a5adfac7a0aebfbf0a1e1f57e3" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "gobject-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" +dependencies = [ + "glib-sys", + "libc", + "system-deps 6.2.2", +] + +[[package]] +name = "gtk" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0" +dependencies = [ + "atk", + "bitflags 1.3.2", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "once_cell", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5bc2f0587cba247f60246a0ca11fe25fb733eabc3de12d1965fc07efab87c84" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps 6.2.2", +] + +[[package]] +name = "gtk3-macros" +version = "0.15.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "684c0456c086e8e7e9af73ec5b84e35938df394712054550e81558d21c44ab0d" +dependencies = [ + "anyhow", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[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 = "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 = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.18", +] + +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core", +] + +[[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.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc50b891e4acf8fe0e71ef88ec43ad82ee07b3810ad09de10f1d01f072ed4b98" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "ignore" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b915661dd01db3f05050265b2477bcc6527b3792388e2749b41623cc592be67d" +dependencies = [ + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", +] + +[[package]] +name = "image" +version = "0.24.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5690139d2f55868e080017335e4b94cb7414274c74f1669c84fb5feba2c9f69d" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-traits", +] + +[[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.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f551f8c3a39f68f986517db0d1759de85881894fdc7db798bd2a9df9cb04b7fc" +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 = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "javascriptcore-rs" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf053e7843f2812ff03ef5afe34bb9c06ffee120385caad4f6b9967fcd37d41c" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "905fbb87419c5cde6e3269537e4ea7d46431f3008c5d057e915ef3f115e7793c" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 5.0.0", +] + +[[package]] +name = "jni" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" +dependencies = [ + "cesu8", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.118", +] + +[[package]] +name = "js-sys" +version = "0.3.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03d04c30968dffe80775bd4d7fb676131cd04a1fb46d2686dbffbaec2d9dfd31" +dependencies = [ + "cfg-if", + "futures-util", + "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 = "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 = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libredox" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f02ab6bace2054fb888a3c16f990117b579d14a3088e472d63c6011fa185c9d3" +dependencies = [ + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[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.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "ndk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" +dependencies = [ + "bitflags 1.3.2", + "jni-sys 0.3.1", + "ndk-sys", + "num_enum", + "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.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" +dependencies = [ + "jni-sys 0.3.1", +] + +[[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.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[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.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[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-foundation" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1add1b659e36c9607c7aab864a76c7a4c2760cd0cd2e120f3fb8b952c7e22bf9" +dependencies = [ + "block", + "objc", + "objc_id", +] + +[[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 = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "pango" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f" +dependencies = [ + "bitflags 1.3.2", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2a00081cde4661982ed91d80ef437c20eacaf6aa1a5962c0279ae194662c3aa" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.2.2", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" +dependencies = [ + "phf_macros 0.11.3", + "phf_shared 0.11.3", +] + +[[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.6", +] + +[[package]] +name = "phf_generator" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" +dependencies = [ + "phf_shared 0.11.3", + "rand 0.8.6", +] + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher 0.3.11", +] + +[[package]] +name = "phf_shared" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" +dependencies = [ + "siphasher 1.0.3", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plist" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "092791278e026273c1b65bbdcfbba3a300f2994c896bd01ab01da613c29c46f1" +dependencies = [ + "base64 0.22.1", + "indexmap 2.14.0", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[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.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +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-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.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.39.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[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.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +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.17", +] + +[[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.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.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.17", + "libredox", + "thiserror", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "regex" +version = "1.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "rfd" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0149778bd99b6959285b0933288206090c50e2327f47a9c463bfdbf45c8823ea" +dependencies = [ + "block", + "dispatch", + "glib-sys", + "gobject-sys", + "gtk-sys", + "js-sys", + "lazy_static", + "log", + "objc", + "objc-foundation", + "objc_id", + "raw-window-handle", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows 0.37.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags 2.13.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[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.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[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.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "serde_json" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8014e44b4736ed0538adeecded0fce2a272f22dc9578a7eb6b2d9993c74cfb9" +dependencies = [ + "indexmap 2.14.0", + "itoa 1.0.18", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_with" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a5c54c7310e7b8b9577c286d7e399ddd876c3e12b3ed917a8aabc4b96e9e8c" +dependencies = [ + "base64 0.22.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84d57bc0c8b9a17920c178daa6bb924850d54a9c97ab45194bb8c17ad66bb660" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[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.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +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 = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + +[[package]] +name = "siphasher" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "soup2" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b4d76501d8ba387cf0fefbe055c3e0a59891d09f0f995ae4e4b16f6b60f3c0" +dependencies = [ + "bitflags 1.3.2", + "gio", + "glib", + "libc", + "once_cell", + "soup2-sys", +] + +[[package]] +name = "soup2-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "009ef427103fcb17f802871647a7fa6c60cbb654b4c4e4c0ac60a31c5f6dc9cf" +dependencies = [ + "bitflags 1.3.2", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps 5.0.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "state" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" +dependencies = [ + "loom", +] + +[[package]] +name = "string_cache" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared 0.11.3", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0" +dependencies = [ + "phf_generator 0.11.3", + "phf_shared 0.11.3", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[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.118" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9ae57f904213ebb649ce6895b8a66c66f0203b9319718f69a5612a065b1422" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "system-deps" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18db855554db7bd0e73e06cf7ba3df39f97812cb11d3f75e71c39bf45171797e" +dependencies = [ + "cfg-expr 0.9.1", + "heck 0.3.3", + "pkg-config", + "toml 0.5.11", + "version-compare 0.0.11", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr 0.15.8", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare 0.2.1", +] + +[[package]] +name = "tao" +version = "0.16.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bf915e6c7112402f7b88a064cfbd264f851052df07fdc3a2abd3038b0cc434a" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "cc", + "cocoa", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dispatch", + "gdk", + "gdk-pixbuf", + "gdk-sys", + "gdkwayland-sys", + "gdkx11-sys", + "gio", + "glib", + "glib-sys", + "gtk", + "image", + "instant", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc", + "once_cell", + "parking_lot", + "png", + "raw-window-handle", + "scopeguard", + "serde", + "tao-macros", + "unicode-segmentation", + "uuid", + "windows 0.39.0", + "windows-implement 0.39.0", + "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.118", +] + +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae1f57c291a6ab8e1d2e6b8ad0a35ff769c9925deb8a89de85425ff08762d0c" +dependencies = [ + "anyhow", + "cocoa", + "dirs-next", + "dunce", + "embed_plist", + "encoding_rs", + "flate2", + "futures-util", + "getrandom 0.2.17", + "glib", + "glob", + "gtk", + "heck 0.5.0", + "http", + "ignore", + "log", + "objc", + "once_cell", + "percent-encoding", + "plist", + "rand 0.8.6", + "raw-window-handle", + "rfd", + "semver", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "state", + "tar", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "tempfile", + "thiserror", + "tokio", + "url", + "uuid", + "webkit2gtk", + "webview2-com", + "windows 0.39.0", +] + +[[package]] +name = "tauri-build" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2db08694eec06f53625cfc6fff3a363e084e5e9a238166d2989996413c346453" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs-next", + "heck 0.5.0", + "json-patch", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53438d78c4a037ffe5eafa19e447eea599bedfb10844cb08ec53c2471ac3ac3f" +dependencies = [ + "base64 0.21.7", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "tauri-utils", + "thiserror", + "time", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "1.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233988ac08c1ed3fe794cd65528d48d8f7ed4ab3895ca64cdaa6ad4d00c45c0b" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 1.0.109", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-runtime" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8066855882f00172935e3fa7d945126580c34dcbabab43f5d4f0c2398a67d47b" +dependencies = [ + "gtk", + "http", + "http-range", + "rand 0.8.6", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror", + "url", + "uuid", + "webview2-com", + "windows 0.39.0", +] + +[[package]] +name = "tauri-runtime-wry" +version = "0.14.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce361fec1e186705371f1c64ae9dd2a3a6768bc530d0a2d5e75a634bb416ad4d" +dependencies = [ + "cocoa", + "gtk", + "percent-encoding", + "rand 0.8.6", + "raw-window-handle", + "tauri-runtime", + "tauri-utils", + "uuid", + "webkit2gtk", + "webview2-com", + "windows 0.39.0", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "1.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c357952645e679de02cd35007190fcbce869b93ffc61b029f33fe02648453774" +dependencies = [ + "brotli", + "ctor", + "dunce", + "glob", + "heck 0.5.0", + "html5ever", + "infer", + "json-patch", + "kuchikiki", + "log", + "memchr", + "phf 0.11.3", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "serde_with", + "thiserror", + "url", + "walkdir", + "windows-version", +] + +[[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 = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix", + "windows-sys 0.61.2", +] + +[[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.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "time" +version = "0.3.51" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c17d80feb7334b40c484e45ed1a5273dfd8bfda537c3be2e74a06a6686f327" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcef1a61bdb119096e153208ec5cbec23944ce8bca13be5c7f60c634f7403935" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" +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.52.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc7f01b389ac15039e4dc9531aa973a135d7a4135281b12d7c1bc79fd57fffe" +dependencies = [ + "bytes", + "pin-project-lite", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[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.14.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.14.0", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +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.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.23.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "144d6b123cef80b301b8f72a9e2ca4370ddec21950d0a103dd22c437006d2db7" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "version-compare" +version = "0.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[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.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +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 = "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.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasm-bindgen" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ddb3f79143bced6de84270411622a2699cee572fc0875aeaf1e7867cf9fca1a" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503b14d284f2c8dac03b819967e155ea753f573586193b2b2c95990cb5d69280" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e21a184b13fb19e157296e2c46056aec9092264fab83e4ba59e68c61b323c3d" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fecefd9c35bd935a20fc3fc344b5f29138961e4f47fb03297d88f2587afb5ebd" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.118", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23939e44bb9a5d7576fa2b563dc2e136628f1224e88a8deed09e04858b77871f" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "web-sys" +version = "0.3.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6430a72df5eb332242960fe84b3002a241163998241eb596d4f739b9757061d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webkit2gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370" +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", + "soup2", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d76ca6ecc47aeba01ec61e480139dda143796abcae6f83bcddf50d6b5b1dcf3" +dependencies = [ + "atk-sys", + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pango-sys", + "pkg-config", + "soup2-sys", + "system-deps 6.2.2", +] + +[[package]] +name = "webview2-com" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a769c9f1a64a8734bde70caafac2b96cada12cd4aefa49196b3a386b8b4178" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows 0.39.0", + "windows-implement 0.39.0", +] + +[[package]] +name = "webview2-com-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaebe196c01691db62e9e4ca52c5ef1e4fd837dcae27dae3ada599b5a8fd05ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "webview2-com-sys" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac48ef20ddf657755fdcda8dfed2a7b4fc7e4581acce6fe9b88c3d64f29dee7" +dependencies = [ + "regex", + "serde", + "serde_json", + "thiserror", + "windows 0.39.0", + "windows-bindgen", + "windows-metadata", +] + +[[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.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[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 = "windows" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57b543186b344cc61c85b5aab0d2e3adf4e0f99bc076eff9aa5927bcc0b8a647" +dependencies = [ + "windows_aarch64_msvc 0.37.0", + "windows_i686_gnu 0.37.0", + "windows_i686_msvc 0.37.0", + "windows_x86_64_gnu 0.37.0", + "windows_x86_64_msvc 0.37.0", +] + +[[package]] +name = "windows" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" +dependencies = [ + "windows-implement 0.39.0", + "windows_aarch64_msvc 0.39.0", + "windows_i686_gnu 0.39.0", + "windows_i686_msvc 0.39.0", + "windows_x86_64_gnu 0.39.0", + "windows_x86_64_msvc 0.39.0", +] + +[[package]] +name = "windows" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-bindgen" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68003dbd0e38abc0fb85b939240f4bce37c43a5981d3df37ccbaaa981b47cb41" +dependencies = [ + "windows-metadata", + "windows-tokens", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement 0.60.2", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba01f98f509cb5dc05f4e5fc95e535f78260f15fea8fe1a8abdd08f774f1cee7" +dependencies = [ + "syn 1.0.109", + "windows-tokens", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-metadata" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm", + "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", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-tokens" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597" + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link", +] + +[[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_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2623277cb2d1c216ba3b578c0f3cf9cdebeddb6e66b1b218bb33596ea7769c3a" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_i686_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3925fd0b0b804730d44d4b6278c50f9699703ec49bcd628020f46f4ba07d9e1" + +[[package]] +name = "windows_i686_gnu" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" + +[[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_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce907ac74fe331b524c1298683efbf598bb031bc84d5e274db2083696d07c57c" + +[[package]] +name = "windows_i686_msvc" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2babfba0828f2e6b32457d5341427dcbb577ceef556273229959ac23a10af33d" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" + +[[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_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.37.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4dd6dc7df2d84cf7b33822ed5b86318fb1781948e9663bacd047fc9dd52259d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[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 = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wry" +version = "0.24.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a2a144c3ab5e83e04724bc8e67cea552ffae413185fda459fafdae173fd985d" +dependencies = [ + "base64 0.13.1", + "block", + "cocoa", + "core-graphics", + "crossbeam-channel", + "dunce", + "gdk", + "gio", + "glib", + "gtk", + "html5ever", + "http", + "kuchikiki", + "libc", + "log", + "objc", + "objc_id", + "once_cell", + "serde", + "serde_json", + "sha2", + "soup2", + "tao", + "thiserror", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows 0.39.0", + "windows-implement 0.39.0", +] + +[[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 = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce1022995ff5ff5d841ad7d994facc23098cd40152f2c1d11cd607c6f530653f" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ae7f38b72ec2a254e2b87ef277cf2cd4fb97cbebf944faa6f33354da0867930" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", + "synstructure", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/tools/sprite-studio/src-tauri/Cargo.toml b/tools/sprite-studio/src-tauri/Cargo.toml new file mode 100644 index 0000000..0e4c3de --- /dev/null +++ b/tools/sprite-studio/src-tauri/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "borrow-fighters-sprite-studio" +version = "0.1.0" +description = "Borrow Fighters sprite atlas and combat metadata tool" +authors = ["Borrow Fighters contributors"] +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[lib] +# The `_lib` suffix may seem redundant but it is necessary +# to make the lib name unique and wouldn't conflict with the bin name. +# This seems to be only an issue on Windows, see https://github.com/rust-lang/cargo/issues/8519 +name = "borrow_fighters_sprite_studio_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[features] +custom-protocol = ["tauri/custom-protocol"] + +[build-dependencies] +tauri-build = { version = "=1.5.6", features = [] } + +[dependencies] +tauri = { version = "=1.8.3", features = [ + "custom-protocol", + "dialog-open", + "dialog-save", +] } +serde = { version = "1", features = ["derive"] } +serde_json = "1" +base64 = "0.22" diff --git a/tools/sprite-studio/src-tauri/build.rs b/tools/sprite-studio/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/tools/sprite-studio/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/tools/sprite-studio/src-tauri/icons/128x128.png b/tools/sprite-studio/src-tauri/icons/128x128.png new file mode 100644 index 0000000..6be5e50 Binary files /dev/null and b/tools/sprite-studio/src-tauri/icons/128x128.png differ diff --git a/tools/sprite-studio/src-tauri/icons/128x128@2x.png b/tools/sprite-studio/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..e81bece Binary files /dev/null and b/tools/sprite-studio/src-tauri/icons/128x128@2x.png differ diff --git a/tools/sprite-studio/src-tauri/icons/32x32.png b/tools/sprite-studio/src-tauri/icons/32x32.png new file mode 100644 index 0000000..a437dd5 Binary files /dev/null and b/tools/sprite-studio/src-tauri/icons/32x32.png differ diff --git a/tools/sprite-studio/src-tauri/icons/Square107x107Logo.png b/tools/sprite-studio/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000..0ca4f27 Binary files /dev/null and b/tools/sprite-studio/src-tauri/icons/Square107x107Logo.png differ diff --git a/tools/sprite-studio/src-tauri/icons/Square142x142Logo.png b/tools/sprite-studio/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000..b81f820 Binary files /dev/null and b/tools/sprite-studio/src-tauri/icons/Square142x142Logo.png differ diff --git a/tools/sprite-studio/src-tauri/icons/Square150x150Logo.png b/tools/sprite-studio/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000..624c7bf Binary files /dev/null and b/tools/sprite-studio/src-tauri/icons/Square150x150Logo.png differ diff --git a/tools/sprite-studio/src-tauri/icons/Square284x284Logo.png b/tools/sprite-studio/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000..c021d2b Binary files /dev/null and b/tools/sprite-studio/src-tauri/icons/Square284x284Logo.png differ diff --git a/tools/sprite-studio/src-tauri/icons/Square30x30Logo.png b/tools/sprite-studio/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000..6219700 Binary files /dev/null and b/tools/sprite-studio/src-tauri/icons/Square30x30Logo.png differ diff --git a/tools/sprite-studio/src-tauri/icons/Square310x310Logo.png b/tools/sprite-studio/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000..f9bc048 Binary files /dev/null and b/tools/sprite-studio/src-tauri/icons/Square310x310Logo.png differ diff --git a/tools/sprite-studio/src-tauri/icons/Square44x44Logo.png b/tools/sprite-studio/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000..d5fbfb2 Binary files /dev/null and b/tools/sprite-studio/src-tauri/icons/Square44x44Logo.png differ diff --git a/tools/sprite-studio/src-tauri/icons/Square71x71Logo.png b/tools/sprite-studio/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000..63440d7 Binary files /dev/null and b/tools/sprite-studio/src-tauri/icons/Square71x71Logo.png differ diff --git a/tools/sprite-studio/src-tauri/icons/Square89x89Logo.png b/tools/sprite-studio/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000..f3f705a Binary files /dev/null and b/tools/sprite-studio/src-tauri/icons/Square89x89Logo.png differ diff --git a/tools/sprite-studio/src-tauri/icons/StoreLogo.png b/tools/sprite-studio/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000..4556388 Binary files /dev/null and b/tools/sprite-studio/src-tauri/icons/StoreLogo.png differ diff --git a/tools/sprite-studio/src-tauri/icons/icon.icns b/tools/sprite-studio/src-tauri/icons/icon.icns new file mode 100644 index 0000000..12a5bce Binary files /dev/null and b/tools/sprite-studio/src-tauri/icons/icon.icns differ diff --git a/tools/sprite-studio/src-tauri/icons/icon.ico b/tools/sprite-studio/src-tauri/icons/icon.ico new file mode 100644 index 0000000..b3636e4 Binary files /dev/null and b/tools/sprite-studio/src-tauri/icons/icon.ico differ diff --git a/tools/sprite-studio/src-tauri/icons/icon.png b/tools/sprite-studio/src-tauri/icons/icon.png new file mode 100644 index 0000000..e1cd261 Binary files /dev/null and b/tools/sprite-studio/src-tauri/icons/icon.png differ diff --git a/tools/sprite-studio/src-tauri/src/lib.rs b/tools/sprite-studio/src-tauri/src/lib.rs new file mode 100644 index 0000000..c183792 --- /dev/null +++ b/tools/sprite-studio/src-tauri/src/lib.rs @@ -0,0 +1,354 @@ +//! Provides local file commands for the Borrow Fighters Sprite Studio. +//! +//! System: Sprite Studio tooling. This backend is intentionally isolated from +//! the game crate and only reads or writes artifact files on disk. + +use std::{ + env, fs, io, + path::{Path, PathBuf}, + process::Command, + time::{SystemTime, UNIX_EPOCH}, +}; + +use base64::{engine::general_purpose, Engine as _}; +use serde::Serialize; +use serde_json::Value; + +#[derive(Debug, Serialize)] +struct SpriteDocument { + repo_root: String, + manifest_path: String, + atlas_path: String, + manifest_json: String, + atlas_data_url: String, +} + +#[derive(Debug, Serialize)] +struct SaveResult { + path: String, + bytes_written: usize, + backup_path: Option, +} + +#[derive(Debug, Serialize)] +struct TextDocument { + path: String, + contents: String, +} + +#[derive(Debug, Serialize)] +struct RuntimeValidationResult { + success: bool, + command: String, + stdout: String, + stderr: String, +} + +#[derive(Debug, Serialize)] +struct StudioError { + message: String, +} + +impl From for StudioError { + fn from(error: io::Error) -> Self { + Self { + message: error.to_string(), + } + } +} + +impl From for StudioError { + fn from(error: serde_json::Error) -> Self { + Self { + message: error.to_string(), + } + } +} + +fn repo_root() -> Result { + let mut current = env::current_dir()?; + loop { + if current.join("Cargo.toml").is_file() + && current.join("assets").is_dir() + && current.join("docs").is_dir() + { + return Ok(current); + } + if !current.pop() { + return Err(StudioError { + message: "Could not find Borrow Fighters repository root".to_string(), + }); + } + } +} + +fn normalize_repo_path(path: &str) -> Result { + let root = repo_root()?; + let raw = Path::new(path); + let full = if raw.is_absolute() { + raw.to_path_buf() + } else { + root.join(raw) + }; + let normalized = full.canonicalize().map_err(|error| StudioError { + message: format!("Could not resolve path `{path}`: {error}"), + })?; + if !normalized.starts_with(&root) { + return Err(StudioError { + message: format!("Path `{path}` is outside the repository"), + }); + } + Ok(normalized) +} + +fn normalize_repo_write_path(path: &str) -> Result { + let root = repo_root()?; + let raw = Path::new(path); + let full = if raw.is_absolute() { + raw.to_path_buf() + } else { + root.join(raw) + }; + let parent = full + .parent() + .ok_or_else(|| StudioError { + message: format!("Path `{path}` has no parent directory"), + })? + .canonicalize() + .map_err(|error| StudioError { + message: format!("Could not resolve parent for `{path}`: {error}"), + })?; + let file_name = full.file_name().ok_or_else(|| StudioError { + message: format!("Path `{path}` has no file name"), + })?; + let normalized = parent.join(file_name); + if !normalized.starts_with(&root) { + return Err(StudioError { + message: format!("Path `{path}` is outside the repository"), + }); + } + Ok(normalized) +} + +fn display_repo_path(path: &Path) -> Result { + let root = repo_root()?; + let relative = path.strip_prefix(&root).unwrap_or(path); + Ok(relative.to_string_lossy().replace('\\', "/")) +} + +fn image_data_url(path: &Path) -> Result { + let bytes = fs::read(path)?; + let mime = match path.extension().and_then(|extension| extension.to_str()) { + Some("jpg") | Some("jpeg") => "image/jpeg", + Some("webp") => "image/webp", + _ => "image/png", + }; + Ok(format!( + "data:{mime};base64,{}", + general_purpose::STANDARD.encode(bytes) + )) +} + +fn safe_artifact_name(path: &str) -> String { + path.chars() + .map(|character| { + if character.is_ascii_alphanumeric() || character == '-' || character == '_' { + character + } else { + '_' + } + }) + .collect() +} + +fn unix_timestamp() -> Result { + Ok(SystemTime::now() + .duration_since(UNIX_EPOCH) + .map_err(|error| StudioError { + message: format!("System clock is before Unix epoch: {error}"), + })? + .as_secs()) +} + +fn tool_output_dir(name: &str) -> Result { + let dir = repo_root()?.join("target").join(name); + fs::create_dir_all(&dir)?; + Ok(dir) +} + +fn backup_existing_file(path: &Path) -> Result, StudioError> { + if !path.is_file() { + return Ok(None); + } + let display = display_repo_path(path)?; + let backup_name = format!( + "{}.{}.bak.json", + safe_artifact_name(&display), + unix_timestamp()? + ); + let backup_path = tool_output_dir("sprite-studio-backups")?.join(backup_name); + fs::copy(path, &backup_path)?; + Ok(Some(backup_path)) +} + +#[tauri::command] +fn load_sprite_manifest(path: String) -> Result { + let manifest_path = normalize_repo_path(&path)?; + let manifest_json = fs::read_to_string(&manifest_path)?; + let manifest: Value = serde_json::from_str(&manifest_json)?; + let image_name = manifest + .get("image") + .and_then(Value::as_str) + .ok_or_else(|| StudioError { + message: "Manifest is missing string field `image`".to_string(), + })?; + let atlas_path = manifest_path + .parent() + .ok_or_else(|| StudioError { + message: "Manifest path has no parent directory".to_string(), + })? + .join(image_name) + .canonicalize() + .map_err(|error| StudioError { + message: format!("Could not resolve atlas `{image_name}`: {error}"), + })?; + let root = repo_root()?; + if !atlas_path.starts_with(&root) { + return Err(StudioError { + message: format!("Atlas `{image_name}` is outside the repository"), + }); + } + + Ok(SpriteDocument { + repo_root: root.to_string_lossy().to_string(), + manifest_path: display_repo_path(&manifest_path)?, + atlas_path: display_repo_path(&atlas_path)?, + manifest_json, + atlas_data_url: image_data_url(&atlas_path)?, + }) +} + +#[tauri::command] +fn save_sprite_manifest(path: String, manifest_json: String) -> Result { + let manifest_path = normalize_repo_write_path(&path)?; + let parsed: Value = serde_json::from_str(&manifest_json)?; + let pretty = format!("{}\n", serde_json::to_string_pretty(&parsed)?); + let backup_path = backup_existing_file(&manifest_path)?; + fs::write(&manifest_path, pretty.as_bytes())?; + + Ok(SaveResult { + path: display_repo_path(&manifest_path)?, + bytes_written: pretty.len(), + backup_path: backup_path.as_deref().map(display_repo_path).transpose()?, + }) +} + +#[tauri::command] +fn autosave_sprite_manifest( + path: String, + manifest_json: String, +) -> Result { + let parsed: Value = serde_json::from_str(&manifest_json)?; + let pretty = format!("{}\n", serde_json::to_string_pretty(&parsed)?); + let target_name = format!("{}.autosave.json", safe_artifact_name(&path)); + let autosave_path = tool_output_dir("sprite-studio-autosave")?.join(target_name); + fs::write(&autosave_path, pretty.as_bytes())?; + + Ok(SaveResult { + path: display_repo_path(&autosave_path)?, + bytes_written: pretty.len(), + backup_path: None, + }) +} + +#[tauri::command] +fn load_repo_text_file(path: String) -> Result { + let file_path = normalize_repo_path(&path)?; + Ok(TextDocument { + path: display_repo_path(&file_path)?, + contents: fs::read_to_string(&file_path)?, + }) +} + +#[tauri::command] +fn save_repo_text_file(path: String, contents: String) -> Result { + let file_path = normalize_repo_write_path(&path)?; + fs::write(&file_path, contents.as_bytes())?; + Ok(SaveResult { + path: display_repo_path(&file_path)?, + bytes_written: contents.len(), + backup_path: None, + }) +} + +#[tauri::command] +fn studio_repo_root() -> Result { + Ok(repo_root()?.to_string_lossy().to_string()) +} + +#[tauri::command] +fn validate_runtime_assets() -> Result { + let root = repo_root()?; + let args = [ + "test", + "--test", + "sprite_manifest", + "--test", + "sprite_frames", + "--test", + "sprite_selection", + "--test", + "characters", + ]; + let output = Command::new("cargo") + .args(args) + .current_dir(&root) + .output() + .map_err(|error| StudioError { + message: format!("Could not run cargo validation: {error}"), + })?; + + Ok(RuntimeValidationResult { + success: output.status.success(), + command: format!("cargo {}", args.join(" ")), + stdout: String::from_utf8_lossy(&output.stdout).to_string(), + stderr: String::from_utf8_lossy(&output.stderr).to_string(), + }) +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .invoke_handler(tauri::generate_handler![ + load_sprite_manifest, + load_repo_text_file, + save_repo_text_file, + save_sprite_manifest, + autosave_sprite_manifest, + studio_repo_root, + validate_runtime_assets + ]) + .run(tauri::generate_context!()) + .expect("error while running Borrow Fighters Sprite Studio"); +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn write_path_allows_new_files_inside_repo() { + let path = normalize_repo_write_path("target/sprite-studio-review-test.json") + .expect("new file under repo target should be writable"); + + assert!(path.ends_with("target/sprite-studio-review-test.json")); + } + + #[test] + fn write_path_rejects_files_outside_repo() { + let error = normalize_repo_write_path("/tmp/sprite-studio-review-test.json") + .expect_err("absolute path outside repo should be rejected"); + + assert!(error.message.contains("outside the repository")); + } +} diff --git a/tools/sprite-studio/src-tauri/src/main.rs b/tools/sprite-studio/src-tauri/src/main.rs new file mode 100644 index 0000000..26a5d2c --- /dev/null +++ b/tools/sprite-studio/src-tauri/src/main.rs @@ -0,0 +1,6 @@ +// Prevents additional console window on Windows in release, DO NOT REMOVE!! +#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")] + +fn main() { + borrow_fighters_sprite_studio_lib::run() +} diff --git a/tools/sprite-studio/src-tauri/tauri.conf.json b/tools/sprite-studio/src-tauri/tauri.conf.json new file mode 100644 index 0000000..cf20fbe --- /dev/null +++ b/tools/sprite-studio/src-tauri/tauri.conf.json @@ -0,0 +1,46 @@ +{ + "$schema": "../node_modules/@tauri-apps/cli/schema.json", + "build": { + "beforeDevCommand": "pnpm dev", + "beforeBuildCommand": "pnpm build", + "devPath": "http://localhost:1420", + "distDir": "../dist" + }, + "package": { + "productName": "Borrow Fighters Sprite Studio", + "version": "0.1.0" + }, + "tauri": { + "allowlist": { + "all": false, + "dialog": { + "open": true, + "save": true + } + }, + "windows": [ + { + "title": "Borrow Fighters Sprite Studio", + "width": 1280, + "height": 820, + "minWidth": 1080, + "minHeight": 720 + } + ], + "security": { + "csp": null + }, + "bundle": { + "active": true, + "targets": "all", + "identifier": "dev.borrowfighters.spritestudio", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ] + } + } +} diff --git a/tools/sprite-studio/src/App.css b/tools/sprite-studio/src/App.css new file mode 100644 index 0000000..805ac62 --- /dev/null +++ b/tools/sprite-studio/src/App.css @@ -0,0 +1,1403 @@ +:root { + color: #e8edf2; + background: #12161b; + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", + sans-serif; + font-size: 14px; + font-synthesis: none; + line-height: 1.35; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +* { + box-sizing: border-box; +} + +html, +body, +#root { + height: 100%; + margin: 0; +} + +button, +input, +select, +textarea { + font: inherit; +} + +button { + border: 1px solid #3a4653; + border-radius: 6px; + background: #202832; + color: #e8edf2; + cursor: pointer; + min-height: 32px; + padding: 6px 10px; +} + +button:hover:not(:disabled) { + border-color: #6aa7d8; + background: #253344; +} + +button:disabled { + color: #6d7885; + cursor: not-allowed; +} + +input, +select, +textarea { + border: 1px solid #33404c; + border-radius: 6px; + background: #0e1216; + color: #e8edf2; + min-height: 32px; + padding: 6px 8px; + width: 100%; +} + +textarea { + font-family: "JetBrains Mono", "Cascadia Code", Consolas, monospace; + font-size: 12px; + line-height: 1.45; + resize: none; +} + +label { + color: #aeb9c5; + display: grid; + gap: 5px; +} + +h1, +h2, +p, +dl, +dd { + margin: 0; +} + +h1 { + font-size: 22px; + font-weight: 700; + letter-spacing: 0; +} + +h2 { + color: #f4f7fa; + font-size: 13px; + font-weight: 700; + letter-spacing: 0; + text-transform: uppercase; +} + +.studio-shell { + display: grid; + grid-template-rows: auto minmax(0, 1fr); + height: 100%; + min-width: 1080px; +} + +.app-chrome { + background: #151a20; + border-bottom: 1px solid #28323c; + display: grid; + box-shadow: 0 1px 0 rgba(255, 255, 255, 0.04); +} + +.menu-bar { + align-items: center; + background: #0f141a; + border-bottom: 1px solid #232d37; + display: flex; + gap: 3px; + min-height: 32px; + padding: 0 10px 0 12px; +} + +.menu-logo { + border: 1px solid #3a4653; + border-radius: 5px; + display: grid; + flex: 0 0 auto; + height: 20px; + margin-right: 8px; + place-items: center; + position: relative; + width: 24px; +} + +.logo-mark-cut, +.logo-mark-core, +.logo-mark-spark { + position: absolute; +} + +.logo-mark-cut { + background: #0f141a; + border: 1px solid #67d4ff; + border-radius: 2px; + height: 12px; + left: 4px; + transform: skewX(-12deg); + width: 9px; +} + +.logo-mark-core { + background: linear-gradient(135deg, #ffb24a, #f46a62); + border-radius: 2px; + height: 10px; + right: 5px; + transform: skewX(-12deg); + width: 8px; +} + +.logo-mark-spark { + background: #66ff99; + border-radius: 999px; + height: 4px; + right: 3px; + top: 3px; + width: 4px; +} + +.menu-group { + position: relative; +} + +.menu-trigger { + background: transparent; + border: 1px solid transparent; + border-radius: 4px; + color: #d4dce4; + cursor: pointer; + min-height: 26px; + padding: 5px 10px; +} + +.menu-group.open .menu-trigger, +.menu-trigger:hover { + background: #202832; + border-color: #34414e; +} + +.menu-dropdown { + background: #1b222b; + border: 1px solid #34414e; + border-radius: 6px; + box-shadow: 0 12px 28px rgba(0, 0, 0, 0.42); + display: grid; + gap: 2px; + left: 0; + min-width: 230px; + padding: 6px; + position: absolute; + top: 30px; + z-index: 30; +} + +.menu-dropdown button { + align-items: center; + background: transparent; + border-color: transparent; + display: flex; + justify-content: space-between; + min-height: 28px; + text-align: left; +} + +.menu-dropdown button:hover:not(:disabled) { + background: #253344; + border-color: transparent; +} + +kbd { + color: #8190a0; + font-family: "JetBrains Mono", "Cascadia Code", Consolas, monospace; + font-size: 11px; + margin-left: 16px; +} + +.toolbar { + align-items: center; + background: linear-gradient(#1c242d, #151b22); + display: flex; + gap: 8px; + justify-content: space-between; + min-height: 46px; + padding: 7px 12px; +} + +.toolbar-group { + align-items: center; + display: flex; + gap: 5px; +} + +.toolbar-spacer { + flex: 1; +} + +.toolbar-divider { + background: #33404c; + height: 26px; + width: 1px; +} + +.toolbar-path { + background: #10151b; + border: 1px solid #2b3642; + border-radius: 6px; + color: #90a0af; + font-size: 12px; + max-width: min(420px, 34vw); + min-width: 140px; + overflow: hidden; + padding: 6px 10px; + text-overflow: ellipsis; + white-space: nowrap; +} + +.toolbar-button { + align-items: center; + background: #242d38; + border-color: #3b4856; + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.06), + 0 1px 0 rgba(0, 0, 0, 0.24); + display: inline-flex; + gap: 7px; + min-height: 30px; + padding: 5px 10px; +} + +.toolbar-button:hover:not(:disabled) { + background: #2b3847; + border-color: #6aa7d8; +} + +.toolbar-button.primary-action { + background: #173348; + border-color: #65c7ff; +} + +.toolbar-button.toggle.active { + background: #263548; + border-color: #65c7ff; + color: #ffffff; +} + +.toolbar-icon { + display: inline-block; + flex: 0 0 auto; + height: 16px; + position: relative; + width: 16px; +} + +.open-icon::before, +.open-icon::after, +.save-icon::before, +.save-icon::after, +.browser-icon::before, +.inspector-icon::before, +.timeline-icon::before, +.timeline-icon::after { + content: ""; + position: absolute; +} + +.open-icon::before { + border: 2px solid #ffd875; + border-radius: 2px; + height: 8px; + left: 2px; + top: 5px; + width: 12px; +} + +.open-icon::after { + background: #ffd875; + border-radius: 2px 2px 0 0; + height: 4px; + left: 3px; + top: 2px; + width: 7px; +} + +.save-icon::before { + border: 2px solid #8dc7ff; + border-radius: 2px; + inset: 2px; +} + +.save-icon::after { + background: #8dc7ff; + border-radius: 1px; + bottom: 4px; + height: 3px; + left: 5px; + width: 6px; +} + +.undo-icon::before, +.redo-icon::before { + color: #aeb9c5; + font-size: 17px; + left: 0; + line-height: 16px; + position: absolute; + top: -1px; +} + +.undo-icon::before { + content: "↶"; +} + +.redo-icon::before { + content: "↷"; +} + +.browser-icon::before, +.inspector-icon::before { + border: 2px solid #aeb9c5; + border-radius: 2px; + inset: 2px; +} + +.browser-icon::after, +.inspector-icon::after { + background: #aeb9c5; + content: ""; + position: absolute; + top: 4px; +} + +.browser-icon::after { + bottom: 4px; + left: 5px; + width: 2px; +} + +.inspector-icon::after { + bottom: 4px; + right: 5px; + width: 2px; +} + +.timeline-icon::before { + background: repeating-linear-gradient( + 90deg, + #aeb9c5 0 7px, + transparent 7px 9px + ); + height: 5px; + left: 1px; + top: 5px; + width: 14px; +} + +.timeline-icon::after { + background: #65c7ff; + height: 12px; + left: 7px; + top: 2px; + width: 2px; +} + +.help-icon::before { + color: #aeb9c5; + content: "?"; + font-weight: 700; + left: 4px; + position: absolute; + top: -1px; +} + +.workspace { + display: grid; + gap: 0; + grid-template-columns: 290px minmax(420px, 1fr) 340px; + min-height: 0; +} + +.workspace.left-collapsed { + grid-template-columns: minmax(420px, 1fr) 340px; +} + +.workspace.right-collapsed { + grid-template-columns: 290px minmax(420px, 1fr); +} + +.workspace.left-collapsed.right-collapsed { + grid-template-columns: minmax(420px, 1fr); +} + +.sidebar, +.inspector { + background: #151a20; + display: grid; + gap: 12px; + overflow: auto; + padding: 12px; +} + +.sidebar { + border-right: 1px solid #28323c; +} + +.inspector { + border-left: 1px solid #28323c; +} + +.stage-column { + background: + linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px), + linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px), #0f1318; + background-size: 32px 32px; + display: grid; + grid-template-rows: auto minmax(0, 1fr) auto auto; + min-width: 0; +} + +.panel { + background: #1b222b; + border: 1px solid #2b3642; + border-radius: 8px; + display: grid; + gap: 12px; + padding: 12px; +} + +.preset-grid, +.property-grid, +.quad-input { + display: grid; + gap: 8px; +} + +.preset-grid { + grid-template-columns: 1fr 1fr; +} + +.property-grid, +.quad-input { + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.metadata { + display: grid; + gap: 4px; +} + +.metadata dt { + color: #8190a0; + font-size: 12px; +} + +.metadata dd { + color: #d4dce4; + overflow-wrap: anywhere; +} + +.clip-list, +.frame-list { + display: grid; + gap: 6px; + max-height: 260px; + overflow: auto; +} + +.clip-list button, +.frame-list button { + align-items: center; + display: flex; + justify-content: space-between; + text-align: left; +} + +.clip-list small { + color: #90a0af; +} + +.selected { + border-color: #65c7ff; + background: #173348; + color: #ffffff; +} + +.section-meta { + border-bottom: 1px solid #34414e; + color: #90a0af; + font-size: 12px; + padding-bottom: 7px; +} + +.toolstrip { + align-items: center; + background: rgba(18, 22, 27, 0.92); + border-bottom: 1px solid #28323c; + display: flex; + flex-wrap: wrap; + gap: 12px; + justify-content: flex-start; + min-height: 54px; + padding: 10px 14px; +} + +.segmented { + display: flex; + flex: 0 0 auto; + gap: 6px; + flex-wrap: wrap; +} + +.view-toggles { + align-items: center; + display: flex; + flex: 1 1 360px; + flex-wrap: wrap; + gap: 12px; +} + +.view-toggles label { + align-items: center; + display: flex; + gap: 6px; +} + +.view-toggles input { + min-height: auto; + width: auto; +} + +.view-toggles select { + min-height: 28px; + width: 72px; +} + +.snap-step { + grid-template-columns: auto 72px; +} + +.inline-field { + align-items: center; + display: grid; + flex: 0 1 320px; + gap: 8px; + grid-template-columns: auto 180px 52px; + margin-left: auto; +} + +.stage-scroll { + align-items: center; + display: flex; + justify-content: center; + min-height: 0; + overflow: auto; + padding: 28px; +} + +.frame-stage { + background: + linear-gradient(45deg, #1a2027 25%, transparent 25%), + linear-gradient(-45deg, #1a2027 25%, transparent 25%), + linear-gradient(45deg, transparent 75%, #1a2027 75%), + linear-gradient(-45deg, transparent 75%, #1a2027 75%), #10151b; + background-position: + 0 0, + 0 8px, + 8px -8px, + -8px 0; + background-size: 16px 16px; + border: 1px solid #44515e; + box-shadow: 0 18px 48px rgba(0, 0, 0, 0.4); + position: relative; + touch-action: none; +} + +.frame-stage canvas, +.frame-overlay { + display: block; + inset: 0; + position: absolute; +} + +.frame-overlay { + pointer-events: none; +} + +.grid-fill { + fill: url("#grid"); + opacity: 0.35; +} + +#grid path { + fill: none; + stroke: #7a8794; + stroke-width: 0.35; +} + +.overlay-rect rect { + fill-opacity: 0.18; + stroke-width: 2; +} + +.overlay-rect.selected-box rect { + stroke-width: 3; +} + +.overlay-rect text { + font-size: 10px; + paint-order: stroke; + stroke: #10151b; + stroke-width: 3px; +} + +.overlay-rect.hurt rect { + fill: #55d17c; + stroke: #66ff99; +} + +.overlay-rect.hurt text { + fill: #bfffd1; +} + +.overlay-rect.hit rect { + fill: #f46a62; + stroke: #ff8b83; +} + +.overlay-rect.hit text { + fill: #ffd0cc; +} + +.overlay-rect.trimmed rect { + fill: #51b9ff; + stroke: #6cc7ff; + stroke-dasharray: 6 4; +} + +.overlay-rect.source rect { + fill: #f9c74f; + stroke: #ffd875; + stroke-dasharray: 3 3; +} + +.pivot-line { + stroke: #f9c74f; + stroke-width: 1.2; +} + +.pivot-point { + fill: #f9c74f; + stroke: #171d24; + stroke-width: 1.5; +} + +.selected-point { + filter: drop-shadow(0 0 5px rgba(249, 199, 79, 0.95)); +} + +.projectile-origin line, +.projectile-origin circle { + fill: #a78bfa; + stroke: #d7c7ff; + stroke-width: 2; +} + +.resize-handle { + fill: #ffffff; + stroke: #111820; + stroke-width: 1.2; +} + +.timeline-panel { + background: linear-gradient(#171d24, #12171d); + border-top: 1px solid #28323c; + display: grid; + gap: 6px; + max-height: 174px; + min-height: 136px; + overflow: hidden; + padding: 9px 12px 11px; +} + +.timeline-header { + align-items: center; + display: flex; + justify-content: space-between; + gap: 12px; +} + +.timeline-summary { + display: grid; + gap: 2px; +} + +.timeline-header strong { + color: #f4f7fa; +} + +.timeline-header span { + color: #90a0af; + font-size: 12px; +} + +.timeline-transport { + align-items: center; + background: #10151b; + border: 1px solid #34414e; + border-radius: 6px; + display: flex; + gap: 4px; + padding: 3px; +} + +.transport-button { + border-color: transparent; + display: grid; + min-height: 26px; + min-width: 30px; + padding: 3px 6px; + place-items: center; +} + +.timeline-current-frame { + color: #f4f7fa; + font-family: "JetBrains Mono", "Cascadia Code", Consolas, monospace; + font-size: 12px; + min-width: 28px; + text-align: center; +} + +.timeline-editor { + background: + linear-gradient(rgba(255, 255, 255, 0.035) 1px, transparent 1px), + #0e1318; + background-size: 100% 24px; + border: 1px solid #2b3642; + border-radius: 6px; + overflow-x: auto; + overflow-y: hidden; +} + +.timeline-strip { + padding: 0 0 6px; + position: relative; +} + +.timeline-ruler, +.timeline-track { + display: grid; +} + +.timeline-ruler { + color: #8190a0; + font-size: 11px; + height: 24px; + padding: 0; +} + +.timeline-ruler span { + align-items: center; + border-left: 1px solid #2f3b46; + display: flex; + gap: 5px; + padding-left: 7px; +} + +.timeline-ruler i { + background: #4d5c69; + display: inline-block; + height: 8px; + width: 1px; +} + +.timeline-track { + border-top: 1px solid #26323d; + padding: 6px; +} + +.timeline-frame { + align-content: space-between; + background: + linear-gradient(90deg, rgba(101, 199, 255, 0.12), transparent 72%), + #1c2530; + border-color: #34414e; + border-left-width: 0; + border-radius: 0; + display: grid; + gap: 4px; + min-height: 68px; + min-width: 0; + padding: 7px 8px 8px; + text-align: left; +} + +.timeline-frame:first-child { + border-left-width: 1px; + border-radius: 5px 0 0 5px; +} + +.timeline-frame:last-child { + border-radius: 0 5px 5px 0; +} + +.timeline-frame.active { + background: + linear-gradient(90deg, rgba(255, 216, 117, 0.18), transparent 76%), + #173348; + border-color: #65c7ff; + box-shadow: + inset 0 -3px 0 #65c7ff, + inset 0 1px 0 rgba(255, 255, 255, 0.1); +} + +.timeline-frame-index { + color: #ffd875; + font-family: "JetBrains Mono", "Cascadia Code", Consolas, monospace; + font-size: 10px; + letter-spacing: 0; +} + +.timeline-frame-name { + color: #f4f7fa; + font-size: 12px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.timeline-frame-duration { + color: #90a0af; + font-size: 11px; +} + +.timeline-markers { + align-items: center; + display: flex; + gap: 4px; + min-height: 8px; +} + +.timeline-playhead { + background: #ffd875; + bottom: 5px; + box-shadow: 0 0 0 1px rgba(16, 21, 27, 0.8); + position: absolute; + top: 0; + transform: translateX(-1px); + width: 2px; + z-index: 4; +} + +.timeline-playhead::before { + border-left: 6px solid transparent; + border-right: 6px solid transparent; + border-top: 7px solid #ffd875; + content: ""; + left: -5px; + position: absolute; + top: 0; +} + +.visually-hidden { + clip: rect(0 0 0 0); + clip-path: inset(50%); + height: 1px; + overflow: hidden; + position: absolute; + white-space: nowrap; + width: 1px; +} + +.marker { + border-radius: 999px; + display: inline-block; + height: 7px; + width: 18px; +} + +.marker.hurt { + background: #66ff99; +} + +.marker.hit { + background: #ff8b83; +} + +.marker.projectile { + background: #d7c7ff; +} + +.scale-guide rect { + fill: none; + pointer-events: none; + stroke-width: 1.4; +} + +.scale-guide-min { + stroke: rgba(102, 255, 153, 0.72); +} + +.scale-guide-max { + stroke: rgba(255, 216, 117, 0.78); + stroke-dasharray: 5 4; +} + +.scale-guide text { + fill: #ffd875; + font-size: 9px; + paint-order: stroke; + stroke: #10151b; + stroke-width: 3px; +} + +.statusbar { + align-items: center; + background: #171d24; + border-top: 1px solid #28323c; + color: #afbac5; + display: flex; + gap: 18px; + justify-content: space-between; + min-height: 38px; + padding: 8px 14px; + overflow: hidden; +} + +.statusbar span { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.box-card { + border: 1px solid #31404d; + border-radius: 6px; + display: grid; + gap: 8px; + padding: 10px; +} + +.selected-card { + border-color: #65c7ff; + box-shadow: 0 0 0 1px rgba(101, 199, 255, 0.3); +} + +.box-card-title { + align-items: center; + display: flex; + gap: 8px; + justify-content: space-between; +} + +.row-actions { + display: flex; + gap: 8px; +} + +.box-card-title button:first-child { + flex: 1; + text-align: left; +} + +.issue-list { + display: grid; + gap: 6px; + max-height: 180px; + overflow: auto; +} + +.issue { + border: 1px solid #34424f; + border-radius: 6px; + display: grid; + gap: 4px; + padding: 8px; +} + +.issue strong { + font-size: 11px; + letter-spacing: 0; + text-transform: uppercase; +} + +.issue.error { + background: rgba(244, 106, 98, 0.12); + border-color: rgba(255, 139, 131, 0.5); +} + +.issue.warning { + background: rgba(249, 199, 79, 0.1); + border-color: rgba(249, 199, 79, 0.45); +} + +.ok { + color: #66ff99; +} + +.warning { + color: #ffd875; +} + +.runtime-validation { + border: 1px solid #34424f; + border-radius: 6px; + display: grid; + gap: 6px; + padding: 8px; +} + +.runtime-validation code { + color: #aeb9c5; + font-family: "JetBrains Mono", "Cascadia Code", Consolas, monospace; + font-size: 11px; + overflow-wrap: anywhere; +} + +.runtime-validation pre { + background: #0e1216; + border: 1px solid #28323c; + border-radius: 6px; + color: #d4dce4; + font-family: "JetBrains Mono", "Cascadia Code", Consolas, monospace; + font-size: 11px; + margin: 0; + max-height: 160px; + overflow: auto; + padding: 8px; + white-space: pre-wrap; +} + +.summary-list { + display: grid; + gap: 8px; + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.summary-list div { + background: #10151b; + border: 1px solid #34414e; + border-radius: 6px; + display: grid; + gap: 4px; + padding: 8px; +} + +.summary-list dt { + color: #90a0af; + font-size: 11px; + overflow-wrap: anywhere; +} + +.summary-list dd { + color: #f4f7fa; + font-weight: 700; +} + +.summary-note { + color: #c6d0da; + font-size: 12px; +} + +.ok-panel { + background: rgba(85, 209, 124, 0.1); + border-color: rgba(102, 255, 153, 0.42); +} + +.error-panel { + background: rgba(244, 106, 98, 0.12); + border-color: rgba(255, 139, 131, 0.5); +} + +.review-notes { + min-height: 92px; +} + +.json-panel textarea { + height: 260px; +} + +.help-backdrop { + align-items: center; + background: rgba(5, 8, 12, 0.72); + display: flex; + inset: 0; + justify-content: center; + padding: 28px; + position: fixed; + z-index: 80; +} + +.help-window { + background: #151a20; + border: 1px solid #3a4653; + border-radius: 8px; + box-shadow: 0 22px 70px rgba(0, 0, 0, 0.56); + display: grid; + grid-template-rows: auto minmax(0, 1fr); + max-height: min(880px, calc(100vh - 56px)); + max-width: 1120px; + overflow: hidden; + width: min(1120px, calc(100vw - 56px)); +} + +.help-header { + align-items: center; + background: #1b222b; + border-bottom: 1px solid #2b3642; + display: flex; + justify-content: space-between; + gap: 18px; + padding: 16px 18px; +} + +.help-header h2 { + font-size: 18px; + text-transform: none; +} + +.help-header p { + color: #90a0af; + margin-top: 4px; +} + +.help-content { + display: grid; + gap: 16px; + overflow: auto; + padding: 18px; +} + +.help-content article { + background: #1b222b; + border: 1px solid #2b3642; + border-radius: 8px; + display: grid; + gap: 12px; + padding: 16px; +} + +.help-content h3 { + color: #f4f7fa; + font-size: 16px; + margin: 0; +} + +.help-content p, +.help-content li { + color: #c6d0da; +} + +.help-content p { + line-height: 1.55; +} + +.help-content ul { + margin: 0; + padding-left: 20px; +} + +.help-content ol { + margin: 0; + padding-left: 18px; +} + +.help-content code { + color: #ffd875; + font-family: "JetBrains Mono", "Cascadia Code", Consolas, monospace; +} + +.help-callout { + background: rgba(101, 199, 255, 0.1); + border: 1px solid rgba(101, 199, 255, 0.36); + border-radius: 6px; + color: #d4dce4; + line-height: 1.5; + padding: 10px 12px; +} + +.help-callout strong { + color: #f4f7fa; +} + +.workflow-grid, +.concept-grid { + display: grid; + gap: 10px; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.workflow-card, +.concept-card { + background: #10151b; + border: 1px solid #34414e; + border-radius: 6px; + display: grid; + gap: 8px; + padding: 11px; +} + +.workflow-card strong, +.concept-card strong { + color: #f4f7fa; +} + +.workflow-card li, +.concept-card span { + color: #c6d0da; + line-height: 1.45; +} + +.help-diagram { + background: #0f1318; + border: 1px solid #34414e; + border-radius: 6px; + height: auto; + max-height: 280px; + width: 100%; +} + +.diagram-bg { + fill: #10151b; +} + +.diagram-card { + fill: #1b222b; + stroke: #5a6978; + stroke-width: 3; +} + +.diagram-card.accent { + fill: rgba(101, 199, 255, 0.15); + stroke: #65c7ff; +} + +.diagram-source { + fill: rgba(249, 199, 79, 0.14); + stroke: #ffd875; + stroke-dasharray: 10 8; + stroke-width: 4; +} + +.diagram-target { + fill: rgba(249, 199, 79, 0.08); + stroke: #c9ad64; + stroke-dasharray: 8 7; + stroke-width: 3; +} + +.diagram-hurt { + fill: rgba(85, 209, 124, 0.24); + stroke: #66ff99; + stroke-width: 4; +} + +.diagram-hit { + fill: rgba(244, 106, 98, 0.28); + stroke: #ff8b83; + stroke-width: 4; +} + +.diagram-body { + fill: rgba(101, 199, 255, 0.2); + stroke: #65c7ff; + stroke-width: 4; +} + +.diagram-body.muted { + opacity: 0.65; +} + +.diagram-push { + fill: rgba(255, 216, 117, 0.12); + stroke: #ffd875; + stroke-dasharray: 9 6; + stroke-width: 3; +} + +.diagram-pivot-line, +.diagram-floor { + stroke: #f9c74f; + stroke-width: 4; +} + +.diagram-pivot { + fill: #f9c74f; + stroke: #10151b; + stroke-width: 3; +} + +.diagram-pivot.warning { + fill: #ff8b83; +} + +.diagram-projectile { + fill: #d7c7ff; + stroke: #a78bfa; + stroke-width: 4; +} + +.diagram-arrow { + fill: none; + stroke: #d7c7ff; + stroke-linecap: round; + stroke-width: 5; +} + +.diagram-hit.ghost { + fill: rgba(244, 106, 98, 0.12); + stroke-dasharray: 9 7; +} + +.help-diagram text { + fill: #f4f7fa; + font-size: 18px; + paint-order: stroke; + stroke: #10151b; + stroke-width: 5px; +} + +.shortcut-grid { + display: grid; + gap: 8px; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +.shortcut { + background: #10151b; + border: 1px solid #34414e; + border-radius: 6px; + display: grid; + gap: 4px; + padding: 10px; +} + +.shortcut span { + color: #90a0af; + font-size: 12px; +} + +.shortcut strong { + color: #e8edf2; +} + +@media (max-width: 1180px) { + .workspace { + grid-template-columns: 260px minmax(400px, 1fr) 300px; + } + + .workflow-grid, + .concept-grid { + grid-template-columns: 1fr; + } +} diff --git a/tools/sprite-studio/src/App.tsx b/tools/sprite-studio/src/App.tsx new file mode 100644 index 0000000..a25de31 --- /dev/null +++ b/tools/sprite-studio/src/App.tsx @@ -0,0 +1,2071 @@ +import { useEffect, useMemo, useRef, useState } from "react"; +import { open, save as saveDialog } from "@tauri-apps/api/dialog"; +import { invoke } from "@tauri-apps/api/tauri"; +import { + DEFAULT_MANIFEST_PATH, + BODY_METRICS_PATH, + EDIT_MODES, + PRESET_MANIFESTS, + cloneManifest, + clipFrameNames, + clampRect, + defaultBox, + formatCoordinate, + frameByName, + inferCharacterId, + toNumber, +} from "./manifest"; +import { + clampPoint, + handlePoints, + hitTestFrame, + localPointFromEvent, + moveRect, + resizeRect, + targetKey, +} from "./geometry"; +import { validateManifest, visualScaleSummary } from "./validation"; +import { HelpCenter } from "./HelpCenter"; +import { MenuBar } from "./MenuBar"; +import { Timeline } from "./Timeline"; +import type { + BoxKind, + BodyMetricsCatalog, + CharacterBodyMetrics, + DragIntent, + DragState, + EditMode, + RuntimeValidationResult, + SaveResult, + SelectionTarget, + SpriteDocument, + SpriteFrame, + SpriteManifest, + SpritePoint, + SpriteRect, + TextDocument, + ValidationIssue, +} from "./types"; +import "./App.css"; + +const HISTORY_LIMIT = 60; +type CombatPreset = + | "standing_hurt" + | "crouch_hurt" + | "mid_strike" + | "low_strike" + | "projectile_origin" + | "clear_frame"; + +function App() { + const canvasRef = useRef(null); + const [manifestPath, setManifestPath] = useState(DEFAULT_MANIFEST_PATH); + const [document, setDocument] = useState(null); + const [manifest, setManifest] = useState(null); + const [selectedClip, setSelectedClip] = useState(null); + const [selectedFrame, setSelectedFrame] = useState(null); + const [editMode, setEditMode] = useState("inspect"); + const [zoom, setZoom] = useState(2); + const [dirty, setDirty] = useState(false); + const [status, setStatus] = useState("Ready"); + const [mouseLocal, setMouseLocal] = useState(null); + const [selection, setSelection] = useState(null); + const [dragState, setDragState] = useState(null); + const [undoStack, setUndoStack] = useState([]); + const [redoStack, setRedoStack] = useState([]); + const [showGrid, setShowGrid] = useState(true); + const [showBounds, setShowBounds] = useState(true); + const [showScaleGuide, setShowScaleGuide] = useState(true); + const [snapEnabled, setSnapEnabled] = useState(true); + const [snapStep, setSnapStep] = useState(4); + const [hoverIntent, setHoverIntent] = useState(null); + const [reviewNotes, setReviewNotes] = useState(""); + const [runtimeValidation, setRuntimeValidation] = + useState(null); + const [runtimeValidationRunning, setRuntimeValidationRunning] = + useState(false); + const [leftPanelOpen, setLeftPanelOpen] = useState(true); + const [rightPanelOpen, setRightPanelOpen] = useState(true); + const [timelineOpen, setTimelineOpen] = useState(true); + const [helpOpen, setHelpOpen] = useState(false); + const [autosaveEnabled, setAutosaveEnabled] = useState(true); + const [lastAutosavePath, setLastAutosavePath] = useState(null); + const [lastBackupPath, setLastBackupPath] = useState(null); + const [bodyCatalog, setBodyCatalog] = useState( + null, + ); + const [bodyMetricsPath, setBodyMetricsPath] = useState(BODY_METRICS_PATH); + const [selectedCharacterId, setSelectedCharacterId] = useState("rust"); + const [bodyDirty, setBodyDirty] = useState(false); + + const frameNames = useMemo( + () => clipFrameNames(manifest, selectedClip), + [manifest, selectedClip], + ); + const timelineFrames = useMemo( + () => + frameNames + .map((frameName) => frameByName(manifest, frameName)) + .filter((frame): frame is SpriteFrame => !!frame) + .map((frame) => ({ ...frame, selected: frame.name === selectedFrame })), + [frameNames, manifest, selectedFrame], + ); + const activeFrame = useMemo( + () => frameByName(manifest, selectedFrame), + [manifest, selectedFrame], + ); + const manifestJson = useMemo( + () => (manifest ? JSON.stringify(manifest, null, 2) : ""), + [manifest], + ); + const validationIssues = useMemo( + () => validateManifest(manifest), + [manifest], + ); + const hasValidationErrors = validationIssues.some( + (issue) => issue.severity === "error", + ); + const scaleSummary = useMemo( + () => visualScaleSummary(activeFrame, manifest?.scale ?? 1), + [activeFrame, manifest?.scale], + ); + + useEffect(() => { + void loadManifest(DEFAULT_MANIFEST_PATH); + }, []); + + useEffect(() => { + drawFrame(document?.atlas_data_url, activeFrame, zoom, canvasRef.current); + }, [document?.atlas_data_url, activeFrame, zoom]); + + useEffect(() => { + function onKeyDown(event: KeyboardEvent) { + const target = event.target as HTMLElement | null; + const isTextInput = + target?.tagName === "INPUT" || target?.tagName === "TEXTAREA"; + if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "s") { + event.preventDefault(); + void saveManifest(); + return; + } + if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "z") { + event.preventDefault(); + if (event.shiftKey) { + redo(); + } else { + undo(); + } + return; + } + if ((event.ctrlKey || event.metaKey) && event.key.toLowerCase() === "y") { + event.preventDefault(); + redo(); + return; + } + if (!isTextInput && event.key === "ArrowLeft") { + event.preventDefault(); + moveFrame(-1); + } + if (!isTextInput && event.key === "ArrowRight") { + event.preventDefault(); + moveFrame(1); + } + if (!isTextInput && event.key === "F1") { + event.preventDefault(); + setHelpOpen(true); + } + if (!isTextInput && (event.key === "=" || event.key === "+")) { + event.preventDefault(); + changeZoom(0.25); + } + if (!isTextInput && event.key === "-") { + event.preventDefault(); + changeZoom(-0.25); + } + } + window.addEventListener("keydown", onKeyDown); + return () => window.removeEventListener("keydown", onKeyDown); + }); + + useEffect(() => { + if (!autosaveEnabled || !dirty || !document || !manifest) { + return; + } + const timeout = window.setTimeout(() => { + void autosaveManifest(); + }, 2500); + return () => window.clearTimeout(timeout); + }, [autosaveEnabled, dirty, document?.manifest_path, manifestJson]); + + async function loadManifest(path: string) { + try { + setStatus(`Loading ${path}`); + const loaded = await invoke("load_sprite_manifest", { + path, + }); + const parsed = JSON.parse(loaded.manifest_json) as SpriteManifest; + const firstClip = parsed.clips[0]?.name ?? null; + const firstFrame = + parsed.clips[0]?.frames[0] ?? parsed.frames[0]?.name ?? null; + + setDocument(loaded); + setManifest(parsed); + setManifestPath(loaded.manifest_path); + setSelectedCharacterId(inferCharacterId(loaded.manifest_path)); + setSelectedClip(firstClip); + setSelectedFrame(firstFrame); + setSelection(null); + setUndoStack([]); + setRedoStack([]); + setDirty(false); + setStatus(`Loaded ${loaded.manifest_path}`); + void loadBodyMetrics(); + } catch (error) { + setStatus(errorMessage(error)); + } + } + + async function chooseManifestFile() { + try { + const selected = await open({ + defaultPath: `${document?.repo_root ?? ""}/assets/placeholder`, + filters: [{ name: "Borrow Fighters sprite manifest", extensions: ["json"] }], + multiple: false, + title: "Open sprite manifest", + }); + const path = dialogPath(selected); + if (!path) { + setStatus("Open cancelled."); + return; + } + await loadManifest(path); + } catch (error) { + setStatus(errorMessage(error)); + } + } + + async function loadBodyMetrics(path = BODY_METRICS_PATH) { + try { + const loaded = await invoke("load_repo_text_file", { path }); + const parsed = JSON.parse(loaded.contents) as BodyMetricsCatalog; + setBodyCatalog(parsed); + setBodyMetricsPath(loaded.path); + setBodyDirty(false); + } catch (error) { + setStatus(`Body metrics not loaded: ${errorMessage(error)}`); + } + } + + async function saveBodyMetrics() { + if (!bodyCatalog) { + return; + } + const invalid = bodyCatalog.characters.some( + (entry) => + entry.body.width <= 0 || + entry.body.standing_height <= 0 || + entry.body.crouch_height <= 0, + ); + if (invalid) { + setStatus("Fix body metrics before saving."); + return; + } + try { + const contents = `${JSON.stringify(bodyCatalog, null, 2)}\n`; + const result = await invoke("save_repo_text_file", { + path: bodyMetricsPath, + contents, + }); + setBodyDirty(false); + setStatus(`Saved ${result.path} (${result.bytes_written} bytes)`); + } catch (error) { + setStatus(errorMessage(error)); + } + } + + async function runRuntimeValidation() { + try { + setRuntimeValidationRunning(true); + setStatus("Running runtime sprite validation..."); + const result = await invoke( + "validate_runtime_assets", + ); + setRuntimeValidation(result); + setStatus( + result.success + ? `Runtime validation passed: ${result.command}` + : `Runtime validation failed: ${result.command}`, + ); + } catch (error) { + setStatus(errorMessage(error)); + } finally { + setRuntimeValidationRunning(false); + } + } + + async function exportReviewJson() { + if (!document || !manifest || !activeFrame) { + return; + } + const fileName = `${activeFrame.name}-sprite-studio-review.json`; + try { + const selected = await saveDialog({ + defaultPath: `${document.repo_root}/${fileName}`, + filters: [{ name: "Sprite Studio review", extensions: ["json"] }], + title: "Export Sprite Studio review JSON", + }); + if (!selected) { + setStatus("Review export cancelled."); + return; + } + const payload = { + schema: "borrow-fighters.sprite-studio-review.v1", + created_by: "Borrow Fighters Sprite Studio", + manifest_path: document.manifest_path, + atlas_path: document.atlas_path, + clip: selectedClip, + frame: activeFrame.name, + frame_data: activeFrame, + manifest_scale: manifest.scale ?? 1, + visual_scale: scaleSummary, + body_metrics_character: selectedCharacterId, + body_metrics: activeBodyMetrics, + validation_issues: validationIssues, + runtime_validation: runtimeValidation, + notes: reviewNotes, + }; + const result = await invoke("save_repo_text_file", { + path: selected, + contents: `${JSON.stringify(payload, null, 2)}\n`, + }); + setStatus(`Exported ${result.path}`); + } catch (error) { + setStatus(errorMessage(error)); + } + } + + async function autosaveManifest() { + if (!document || !manifest) { + return; + } + try { + const result = await invoke("autosave_sprite_manifest", { + path: document.manifest_path, + manifestJson, + }); + setLastAutosavePath(result.path); + setStatus(`Autosaved ${result.path}`); + } catch (error) { + setStatus(`Autosave failed: ${errorMessage(error)}`); + } + } + + async function saveManifest() { + if (!document || !manifest) { + return; + } + if (hasValidationErrors) { + setStatus("Fix validation errors before saving."); + return; + } + try { + const result = await invoke("save_sprite_manifest", { + path: document.manifest_path, + manifestJson, + }); + setDirty(false); + setLastBackupPath(result.backup_path ?? null); + setStatus( + result.backup_path + ? `Saved ${result.path}; backup ${result.backup_path}` + : `Saved ${result.path} (${result.bytes_written} bytes)`, + ); + } catch (error) { + setStatus(errorMessage(error)); + } + } + + function pushHistory(snapshot = manifest) { + if (!snapshot) { + return; + } + setUndoStack((stack) => [ + ...stack.slice(Math.max(0, stack.length - HISTORY_LIMIT + 1)), + cloneManifest(snapshot), + ]); + setRedoStack([]); + } + + function updateManifest( + mutator: (next: SpriteManifest) => void, + options: { history?: boolean } = {}, + ) { + if (!manifest) { + return; + } + if (options.history !== false) { + pushHistory(manifest); + } + const next = cloneManifest(manifest); + mutator(next); + setManifest(next); + setDirty(true); + } + + function updateActiveFrame( + mutator: (frame: SpriteFrame) => void, + options: { history?: boolean } = {}, + ) { + if (!activeFrame) { + return; + } + updateManifest((next) => { + const nextFrame = next.frames.find( + (frame) => frame.name === activeFrame.name, + ); + if (nextFrame) { + mutator(nextFrame); + } + }, options); + } + + function undo() { + if (!manifest || undoStack.length === 0) { + return; + } + const previous = undoStack[undoStack.length - 1]; + setUndoStack((stack) => stack.slice(0, -1)); + setRedoStack((stack) => [...stack, cloneManifest(manifest)]); + setManifest(cloneManifest(previous)); + setDirty(true); + setStatus("Undo"); + } + + function redo() { + if (!manifest || redoStack.length === 0) { + return; + } + const next = redoStack[redoStack.length - 1]; + setRedoStack((stack) => stack.slice(0, -1)); + setUndoStack((stack) => [...stack, cloneManifest(manifest)]); + setManifest(cloneManifest(next)); + setDirty(true); + setStatus("Redo"); + } + + function handleClipSelection(clipName: string) { + const names = clipFrameNames(manifest, clipName); + setSelectedClip(clipName); + setSelectedFrame(names[0] ?? null); + setSelection(null); + } + + function moveFrame(delta: number) { + if (!frameNames.length || !selectedFrame) { + return; + } + const index = frameNames.indexOf(selectedFrame); + const nextIndex = (index + delta + frameNames.length) % frameNames.length; + setSelectedFrame(frameNames[nextIndex]); + setSelection(null); + } + + function changeZoom(delta: number) { + setZoom((current) => Math.min(5, Math.max(0.5, current + delta))); + } + + function handleStageWheel(event: React.WheelEvent) { + event.preventDefault(); + changeZoom(event.deltaY < 0 ? 0.25 : -0.25); + } + + function snapPoint(point: SpritePoint, frame: SpriteFrame): SpritePoint { + if (!snapEnabled || snapStep <= 1) { + return clampPoint(point, frame); + } + return clampPoint( + { + x: Math.round(point.x / snapStep) * snapStep, + y: Math.round(point.y / snapStep) * snapStep, + }, + frame, + ); + } + + function seedCombatPreset(preset: CombatPreset) { + if (!activeFrame) { + return; + } + const scale = manifest?.scale ?? 1; + const metrics = activeBodyMetrics ?? { + width: 76, + standing_height: 168, + crouch_height: 96, + }; + updateActiveFrame((frame) => { + frame.combat ??= {}; + const width = metrics.width / scale; + const standing = metrics.standing_height / scale; + const crouch = metrics.crouch_height / scale; + const centerX = frame.pivot.x; + const floorY = frame.pivot.y; + + if (preset === "clear_frame") { + frame.combat = {}; + return; + } + if (preset === "standing_hurt") { + frame.combat.hurtboxes = [ + clampRect( + { + x: centerX - width / 2, + y: floorY - standing, + w: width, + h: standing, + label: "body", + }, + frame, + ), + ]; + } + if (preset === "crouch_hurt") { + frame.combat.hurtboxes = [ + clampRect( + { + x: centerX - width * 0.58, + y: floorY - crouch, + w: width * 1.16, + h: crouch, + label: "crouch_body", + }, + frame, + ), + ]; + } + if (preset === "mid_strike") { + frame.combat.hitboxes = [ + clampRect( + { + x: centerX + width * 0.32, + y: floorY - standing * 0.68, + w: 54 / scale, + h: 34 / scale, + label: "mid_strike", + }, + frame, + ), + ]; + } + if (preset === "low_strike") { + frame.combat.hitboxes = [ + clampRect( + { + x: centerX + width * 0.26, + y: floorY - standing * 0.36, + w: 68 / scale, + h: 30 / scale, + label: "low_strike", + }, + frame, + ), + ]; + } + if (preset === "projectile_origin") { + frame.combat.projectile_origin = clampPoint( + { + x: centerX + width * 0.34, + y: floorY - standing * 0.63, + }, + frame, + ); + } + }); + } + + function beginStageEdit(event: React.PointerEvent) { + if (!activeFrame) { + return; + } + const rawPoint = localPointFromEvent(event, zoom); + const point = snapPoint(rawPoint, activeFrame); + setMouseLocal(rawPoint); + + const hit = hitTestFrame(activeFrame, rawPoint); + if (hit) { + beginDrag(event, hit, point); + return; + } + + if (editMode === "pivot") { + const intent: DragIntent = { action: "move", target: { type: "pivot" } }; + const nextPivot = clampPoint(point, activeFrame); + pushHistory(); + updateActiveFrame((frame) => { + frame.pivot = nextPivot; + }, { history: false }); + setSelection(intent.target); + setDragState({ + ...intent, + startMouse: point, + startPivot: nextPivot, + }); + event.currentTarget.setPointerCapture(event.pointerId); + return; + } + + if (editMode === "projectile") { + const intent: DragIntent = { + action: "move", + target: { type: "projectile" }, + }; + const nextProjectile = clampPoint(point, activeFrame); + pushHistory(); + updateActiveFrame((frame) => { + frame.combat ??= {}; + frame.combat.projectile_origin = nextProjectile; + }, { history: false }); + setSelection(intent.target); + setDragState({ + ...intent, + startMouse: point, + startProjectile: nextProjectile, + }); + event.currentTarget.setPointerCapture(event.pointerId); + return; + } + + if (editMode === "hurtbox" || editMode === "hitbox") { + const kind = editMode === "hurtbox" ? "hurtboxes" : "hitboxes"; + let addedIndex = 0; + updateActiveFrame((frame) => { + frame.combat ??= {}; + frame.combat[kind] ??= []; + const boxes = frame.combat[kind] ?? []; + boxes.push(defaultBox(point.x, point.y, frame, editMode)); + addedIndex = boxes.length - 1; + }); + setSelection({ type: "box", kind, index: addedIndex }); + return; + } + + setSelection(null); + } + + function beginDrag( + event: React.PointerEvent, + intent: DragIntent, + point: SpritePoint, + ) { + if (!activeFrame) { + return; + } + pushHistory(); + setSelection(intent.target); + const base = { + ...intent, + startMouse: point, + startPivot: + intent.target.type === "pivot" ? { ...activeFrame.pivot } : undefined, + startProjectile: + intent.target.type === "projectile" && + activeFrame.combat?.projectile_origin + ? { ...activeFrame.combat.projectile_origin } + : undefined, + startRect: + intent.target.type === "box" + ? { + ...((activeFrame.combat?.[intent.target.kind] ?? [])[ + intent.target.index + ] as SpriteRect), + } + : undefined, + }; + setDragState(base); + event.currentTarget.setPointerCapture(event.pointerId); + } + + function continueStageEdit(event: React.PointerEvent) { + if (!activeFrame) { + return; + } + const rawPoint = localPointFromEvent(event, zoom); + const point = snapPoint(rawPoint, activeFrame); + setMouseLocal(rawPoint); + if (!dragState) { + setHoverIntent(hitTestFrame(activeFrame, rawPoint)); + return; + } + setHoverIntent(null); + const delta = { + x: point.x - dragState.startMouse.x, + y: point.y - dragState.startMouse.y, + }; + + updateActiveFrame((frame) => { + if (dragState.target.type === "pivot" && dragState.startPivot) { + frame.pivot = clampPoint( + { + x: dragState.startPivot.x + delta.x, + y: dragState.startPivot.y + delta.y, + }, + frame, + ); + } + if ( + dragState.target.type === "projectile" && + dragState.startProjectile + ) { + frame.combat ??= {}; + frame.combat.projectile_origin = clampPoint( + { + x: dragState.startProjectile.x + delta.x, + y: dragState.startProjectile.y + delta.y, + }, + frame, + ); + } + if (dragState.target.type === "box" && dragState.startRect) { + const boxes = frame.combat?.[dragState.target.kind]; + if (!boxes?.[dragState.target.index]) { + return; + } + boxes[dragState.target.index] = + dragState.action === "resize" + ? resizeRect(dragState.startRect, dragState.handle, delta, frame) + : moveRect(dragState.startRect, delta, frame); + } + }, { history: false }); + } + + function endStageEdit(event: React.PointerEvent) { + if (event.currentTarget.hasPointerCapture(event.pointerId)) { + event.currentTarget.releasePointerCapture(event.pointerId); + } + setDragState(null); + } + + function leaveStage() { + setHoverIntent(null); + setMouseLocal(null); + } + + function updateBox( + kind: BoxKind, + index: number, + patch: Partial, + ) { + updateActiveFrame((frame) => { + const boxes = frame.combat?.[kind]; + if (!boxes?.[index]) { + return; + } + boxes[index] = { ...boxes[index], ...patch }; + }); + } + + function updateBodyMetrics(patch: Partial) { + if (!bodyCatalog) { + return; + } + const next = JSON.parse(JSON.stringify(bodyCatalog)) as BodyMetricsCatalog; + let entry = next.characters.find((item) => item.id === selectedCharacterId); + if (!entry) { + entry = { + id: selectedCharacterId, + body: { width: 76, standing_height: 168, crouch_height: 96 }, + }; + next.characters.push(entry); + } + entry.body = { ...entry.body, ...patch }; + setBodyCatalog(next); + setBodyDirty(true); + } + + function removeBox(kind: BoxKind, index: number) { + updateActiveFrame((frame) => { + frame.combat?.[kind]?.splice(index, 1); + }); + if (selection?.type === "box" && selection.kind === kind && selection.index === index) { + setSelection(null); + } + } + + async function exportReviewImage() { + if (!document?.atlas_data_url || !activeFrame) { + return; + } + try { + const dataUrl = await makeReviewImage( + document.atlas_data_url, + activeFrame, + zoom, + showBounds, + ); + const link = window.document.createElement("a"); + link.href = dataUrl; + link.download = `${activeFrame.name}-sprite-studio-review.png`; + link.click(); + setStatus(`Exported ${link.download}`); + } catch (error) { + setStatus(errorMessage(error)); + } + } + + const canvasWidth = activeFrame ? activeFrame.frame.w * zoom : 0; + const canvasHeight = activeFrame ? activeFrame.frame.h * zoom : 0; + const frameIndex = selectedFrame ? frameNames.indexOf(selectedFrame) : -1; + const selectedKey = targetKey(selection); + const activeBodyMetrics = + bodyCatalog?.characters.find((entry) => entry.id === selectedCharacterId) + ?.body ?? null; + const stageCursor = cursorForIntent(dragState ?? hoverIntent, editMode); + const workspaceClassName = [ + "workspace", + leftPanelOpen ? "" : "left-collapsed", + rightPanelOpen ? "" : "right-collapsed", + ] + .filter(Boolean) + .join(" "); + + return ( +
+
+ 0} + canSave={dirty && !hasValidationErrors} + canUndo={undoStack.length > 0} + leftPanelOpen={leftPanelOpen} + rightPanelOpen={rightPanelOpen} + timelineOpen={timelineOpen} + onExportPng={() => void exportReviewImage()} + onExportReview={() => void exportReviewJson()} + onHelp={() => setHelpOpen(true)} + onOpen={() => void chooseManifestFile()} + onRedo={redo} + onReload={() => void loadManifest(manifestPath)} + onRunValidation={() => void runRuntimeValidation()} + onSave={() => void saveManifest()} + onToggleAutosave={() => setAutosaveEnabled((enabled) => !enabled)} + onToggleLeftPanel={() => setLeftPanelOpen((open) => !open)} + onToggleRightPanel={() => setRightPanelOpen((open) => !open)} + onToggleTimeline={() => setTimelineOpen((open) => !open)} + onUndo={undo} + /> +
+
+ + +
+
+
+ + +
+
+
+ {manifestPath} +
+
+ + + + +
+
+
+ +
+ {leftPanelOpen && ( + + )} + +
+
+
+ {EDIT_MODES.map((mode) => ( + + ))} +
+
+ + + + + +
+ +
+ +
+ {activeFrame && ( +
+ + + + + + + + {showGrid && ( + + )} + {showBounds && renderRect(activeFrame.trimmed_bounds, "trimmed")} + {showBounds && renderRect(activeFrame.source_crop, "source")} + {showScaleGuide && + renderScaleGuide(activeFrame, manifest?.scale ?? 1)} + {activeFrame.combat?.hurtboxes?.map((box, index) => + renderEditableRect( + box, + "hurt", + { type: "box", kind: "hurtboxes", index }, + selectedKey, + ), + )} + {activeFrame.combat?.hitboxes?.map((box, index) => + renderEditableRect( + box, + "hit", + { type: "box", kind: "hitboxes", index }, + selectedKey, + ), + )} + + + + {activeFrame.combat?.projectile_origin && ( + + + + + + )} + +
+ )} +
+ + {timelineOpen && ( + moveFrame(1)} + onPrevious={() => moveFrame(-1)} + onSelect={(frameName) => { + setSelectedFrame(frameName); + setSelection(null); + }} + /> + )} + +
+ {status} + {dirty ? "Unsaved changes" : "Clean"} + {autosaveEnabled ? "Autosave on" : "Autosave off"} + autosave {lastAutosavePath ?? "-"} + backup {lastBackupPath ?? "-"} + + selected {selectedKey || "-"} | local{" "} + {formatCoordinate(mouseLocal?.x ?? null)},{" "} + {formatCoordinate(mouseLocal?.y ?? null)} + +
+
+ + {rightPanelOpen && ( +