pixelweaver 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.development +1 -0
- package/.github/workflows/ci.yml +22 -0
- package/.github/workflows/publish.yml +18 -0
- package/.prettierignore +5 -0
- package/.prettierrc +16 -0
- package/.python-version +1 -0
- package/.rlsbl/bases/.github/workflows/ci.yml +21 -0
- package/.rlsbl/bases/.github/workflows/publish.yml +18 -0
- package/.rlsbl/bases/.rlsbl/changes/unreleased.jsonl +0 -0
- package/.rlsbl/bases/.rlsbl/hooks/post-release.sh +8 -0
- package/.rlsbl/bases/.rlsbl/hooks/pre-checks.sh +5 -0
- package/.rlsbl/bases/.rlsbl/hooks/pre-release.sh +8 -0
- package/.rlsbl/bases/.rlsbl/lint/go.toml +17 -0
- package/.rlsbl/bases/.rlsbl/lint/npm.toml +19 -0
- package/.rlsbl/bases/.rlsbl/lint/python.toml +25 -0
- package/.rlsbl/bases/CHANGELOG.md +5 -0
- package/.rlsbl/bases/LICENSE +21 -0
- package/.rlsbl/changes/.validated +1 -0
- package/.rlsbl/changes/0.1.0.jsonl +85 -0
- package/.rlsbl/changes/0.1.0.md +13 -0
- package/.rlsbl/changes/unreleased.jsonl +0 -0
- package/.rlsbl/config.json +6 -0
- package/.rlsbl/hashes.json +14 -0
- package/.rlsbl/hooks/post-release.sh +8 -0
- package/.rlsbl/hooks/pre-checks.sh +5 -0
- package/.rlsbl/hooks/pre-release.sh +8 -0
- package/.rlsbl/lint/go.toml +17 -0
- package/.rlsbl/lint/npm.toml +19 -0
- package/.rlsbl/lint/python.toml +25 -0
- package/.rlsbl/releases/unreleased.toml +0 -0
- package/.rlsbl/releases/v0.1.0.toml +3 -0
- package/.rlsbl/version +1 -0
- package/.selfdoc/hashes/hashes.json +146 -0
- package/.strictcli/schema.json +227 -0
- package/CHANGELOG.md +17 -0
- package/CLAUDE.md +100 -0
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/assets/icon.png +0 -0
- package/docs/_README.md +117 -0
- package/docs/cli-config.md +35 -0
- package/docs/cli-dev.md +21 -0
- package/docs/cli-diagnose.md +12 -0
- package/docs/cli-index.md +30 -0
- package/docs/cli-list.md +18 -0
- package/docs/cli-mcp.md +18 -0
- package/docs/cli-new.md +26 -0
- package/docs/cli-open.md +21 -0
- package/docs/cli-serve.md +21 -0
- package/docs/cli-stop.md +12 -0
- package/docs/gen-index.md +36 -0
- package/docs/index.md +13 -0
- package/docs/server-src-pixelweaver-__main__.md +12 -0
- package/docs/server-src-pixelweaver-autosave.md +12 -0
- package/docs/server-src-pixelweaver-bridge.md +12 -0
- package/docs/server-src-pixelweaver-cli.md +12 -0
- package/docs/server-src-pixelweaver-config.md +12 -0
- package/docs/server-src-pixelweaver-connections.md +12 -0
- package/docs/server-src-pixelweaver-main.md +12 -0
- package/docs/server-src-pixelweaver-mcp_bridge.md +12 -0
- package/docs/server-src-pixelweaver-mcp_drawing_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_export_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_frame_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_history_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_layer_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_lock.md +12 -0
- package/docs/server-src-pixelweaver-mcp_project_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_read_tools.md +12 -0
- package/docs/server-src-pixelweaver-mcp_registry.md +12 -0
- package/docs/server-src-pixelweaver-mcp_resources.md +12 -0
- package/docs/server-src-pixelweaver-mcp_server.md +12 -0
- package/docs/server-src-pixelweaver-protocol.md +12 -0
- package/docs/server-src-pixelweaver-state.md +12 -0
- package/docs/server-src-pixelweaver-storage.md +12 -0
- package/docs/server-src-pixelweaver-websocket.md +12 -0
- package/docs/server-src-pixelweaver.md +12 -0
- package/e2e/app-launch.test.ts +35 -0
- package/e2e/menus.test.ts +26 -0
- package/e2e/tools.test.ts +27 -0
- package/e2e/undo-redo.test.ts +11 -0
- package/eslint.config.js +62 -0
- package/index.html +13 -0
- package/package.json +48 -0
- package/playwright.config.ts +19 -0
- package/plugins/builtin/.gitkeep +0 -0
- package/plugins/builtin/advanced-fill-tool.ts +146 -0
- package/plugins/builtin/circle-tool.ts +186 -0
- package/plugins/builtin/diamond-tool.ts +182 -0
- package/plugins/builtin/dither-tool.ts +186 -0
- package/plugins/builtin/drawing-primitives-plugin.ts +362 -0
- package/plugins/builtin/drawing-utils.test.ts +495 -0
- package/plugins/builtin/drawing-utils.ts +431 -0
- package/plugins/builtin/effects/blur.ts +97 -0
- package/plugins/builtin/effects/color-effects.ts +278 -0
- package/plugins/builtin/effects/flip.ts +83 -0
- package/plugins/builtin/effects/glow.ts +118 -0
- package/plugins/builtin/effects/outline.ts +110 -0
- package/plugins/builtin/effects/rotate.ts +357 -0
- package/plugins/builtin/effects/scale.ts +258 -0
- package/plugins/builtin/effects/shadow.ts +111 -0
- package/plugins/builtin/effects/sharpen.ts +102 -0
- package/plugins/builtin/effects.test.ts +715 -0
- package/plugins/builtin/eraser-tool.ts +23 -0
- package/plugins/builtin/eyedropper-tool.ts +83 -0
- package/plugins/builtin/fill-tool.ts +93 -0
- package/plugins/builtin/gradient-tool.ts +204 -0
- package/plugins/builtin/gradient.test.ts +142 -0
- package/plugins/builtin/importers/aseprite-importer-plugin.ts +174 -0
- package/plugins/builtin/importers/aseprite-parser.ts +497 -0
- package/plugins/builtin/importers/piskel-importer-plugin.ts +222 -0
- package/plugins/builtin/importers/sky-spec-plugin.ts +409 -0
- package/plugins/builtin/line-tool.ts +267 -0
- package/plugins/builtin/make-stroke-tool.ts +271 -0
- package/plugins/builtin/noise-dither.test.ts +151 -0
- package/plugins/builtin/noise-tool.ts +131 -0
- package/plugins/builtin/pattern-stamp-tool.ts +162 -0
- package/plugins/builtin/pencil-tool.ts +25 -0
- package/plugins/builtin/rect-tool.ts +179 -0
- package/plugins/builtin/selection-tool.ts +388 -0
- package/plugins/builtin/selection.test.ts +195 -0
- package/plugins/builtin/tool-plugins.test.ts +529 -0
- package/public/favicon.svg +7 -0
- package/public/sw.js +91 -0
- package/pyproject.toml +49 -0
- package/scripts/eslint-wave-a-list.sh +24 -0
- package/scripts/eslint-wave-a-status.sh +25 -0
- package/scripts/fix-index-signature-access.py +127 -0
- package/scripts/fix-unchecked-index.py +128 -0
- package/scripts/fix-unchecked-vars.py +127 -0
- package/scripts/fix-wave-a-bangs.py +36 -0
- package/scripts/fix-wave-a-templates.py +108 -0
- package/scripts/fix-wave-b-void-expr.py +167 -0
- package/scripts/generate-command-params.py +540 -0
- package/scripts/migrate-single-frame-to-multi.py +171 -0
- package/scripts/smoke-test.sh +77 -0
- package/selfdoc.json +10 -0
- package/server/README.md +0 -0
- package/server/src/pixelweaver/__init__.py +1 -0
- package/server/src/pixelweaver/__main__.py +4 -0
- package/server/src/pixelweaver/autosave.py +114 -0
- package/server/src/pixelweaver/bridge.py +127 -0
- package/server/src/pixelweaver/cli.py +199 -0
- package/server/src/pixelweaver/config.py +24 -0
- package/server/src/pixelweaver/connections.py +54 -0
- package/server/src/pixelweaver/main.py +271 -0
- package/server/src/pixelweaver/mcp_bridge.py +189 -0
- package/server/src/pixelweaver/mcp_drawing_tools.py +178 -0
- package/server/src/pixelweaver/mcp_export_tools.py +291 -0
- package/server/src/pixelweaver/mcp_frame_tools.py +423 -0
- package/server/src/pixelweaver/mcp_history_tools.py +106 -0
- package/server/src/pixelweaver/mcp_layer_tools.py +64 -0
- package/server/src/pixelweaver/mcp_lock.py +37 -0
- package/server/src/pixelweaver/mcp_project_tools.py +302 -0
- package/server/src/pixelweaver/mcp_read_tools.py +163 -0
- package/server/src/pixelweaver/mcp_registry.py +247 -0
- package/server/src/pixelweaver/mcp_resources.py +312 -0
- package/server/src/pixelweaver/mcp_server.py +234 -0
- package/server/src/pixelweaver/protocol.py +219 -0
- package/server/src/pixelweaver/state.py +267 -0
- package/server/src/pixelweaver/storage.py +293 -0
- package/server/src/pixelweaver/websocket.py +282 -0
- package/server/tests/__init__.py +0 -0
- package/server/tests/conftest.py +17 -0
- package/server/tests/test_api.py +96 -0
- package/server/tests/test_bridge.py +161 -0
- package/server/tests/test_health.py +9 -0
- package/server/tests/test_integration.py +86 -0
- package/server/tests/test_mcp_bridge.py +293 -0
- package/server/tests/test_mcp_lock.py +34 -0
- package/server/tests/test_mcp_registry.py +679 -0
- package/server/tests/test_mcp_resources.py +648 -0
- package/server/tests/test_protocol.py +125 -0
- package/server/tests/test_state.py +87 -0
- package/server/tests/test_storage.py +306 -0
- package/server/tests/test_websocket.py +275 -0
- package/src/App.svelte +107 -0
- package/src/app.css +215 -0
- package/src/lib/animation/AnimationPreviewPanel.svelte +667 -0
- package/src/lib/animation/animation-commands.test.ts +228 -0
- package/src/lib/animation/animation-commands.ts +540 -0
- package/src/lib/animation/animation-preview-panel-plugin.ts +25 -0
- package/src/lib/animation/animation-preview.svelte.ts +151 -0
- package/src/lib/animation/clipboard.ts +134 -0
- package/src/lib/animation/frame-model.svelte.ts +437 -0
- package/src/lib/animation/frame-model.test.ts +314 -0
- package/src/lib/animation/frame-selection.svelte.ts +77 -0
- package/src/lib/animation/frame-tags.svelte.ts +238 -0
- package/src/lib/animation/frame-tags.test.ts +136 -0
- package/src/lib/animation/import.test.ts +141 -0
- package/src/lib/animation/import.ts +112 -0
- package/src/lib/animation/spritesheet-export.test.ts +239 -0
- package/src/lib/animation/spritesheet-export.ts +314 -0
- package/src/lib/canvas/CanvasViewport.svelte +216 -0
- package/src/lib/canvas/canvas-init-plugin.ts +178 -0
- package/src/lib/canvas/canvas-renderer.ts +408 -0
- package/src/lib/canvas/canvas-state.svelte.ts +232 -0
- package/src/lib/canvas/canvas-state.test.ts +139 -0
- package/src/lib/canvas/input-handler.ts +221 -0
- package/src/lib/canvas/input-plugin.ts +150 -0
- package/src/lib/canvas/onion-skin.ts +94 -0
- package/src/lib/canvas/pixel-buffer.test.ts +249 -0
- package/src/lib/canvas/pixel-buffer.ts +151 -0
- package/src/lib/canvas/render-state.svelte.ts +18 -0
- package/src/lib/canvas/shape-preview-state.svelte.ts +36 -0
- package/src/lib/canvas/tile-mode.test.ts +53 -0
- package/src/lib/canvas/tile-mode.ts +92 -0
- package/src/lib/canvas/viewport-utils.ts +24 -0
- package/src/lib/canvas/zoom-utils.ts +31 -0
- package/src/lib/color/.gitkeep +0 -0
- package/src/lib/color/color-commands.ts +87 -0
- package/src/lib/color/color-state.svelte.ts +98 -0
- package/src/lib/color/color-state.test.ts +91 -0
- package/src/lib/color/color-utils.test.ts +220 -0
- package/src/lib/color/color-utils.ts +243 -0
- package/src/lib/color/palette-state.svelte.ts +127 -0
- package/src/lib/color/palette-state.test.ts +154 -0
- package/src/lib/color/palette.ts +79 -0
- package/src/lib/core/bootstrap.ts +66 -0
- package/src/lib/core/command-params.generated.ts +1549 -0
- package/src/lib/core/command-params.ts +20 -0
- package/src/lib/core/command-runner.ts +79 -0
- package/src/lib/core/commands.ts +134 -0
- package/src/lib/core/dispatcher.test.ts +548 -0
- package/src/lib/core/dispatcher.ts +361 -0
- package/src/lib/core/index.test.ts +7 -0
- package/src/lib/core/notification-state.svelte.ts +119 -0
- package/src/lib/core/plugin-api.ts +210 -0
- package/src/lib/core/plugin-discovery.test.ts +53 -0
- package/src/lib/core/plugin-discovery.ts +65 -0
- package/src/lib/core/plugin-loader.test.ts +159 -0
- package/src/lib/core/plugin-loader.ts +240 -0
- package/src/lib/core/plugin-types.ts +286 -0
- package/src/lib/core/registries.svelte.ts +74 -0
- package/src/lib/core/tool-options-state.svelte.ts +61 -0
- package/src/lib/dock/DockLayout.svelte +375 -0
- package/src/lib/dock/TabAddMenu.svelte +90 -0
- package/src/lib/dock/dock-persistence.ts +46 -0
- package/src/lib/dock/dock-plugin.ts +49 -0
- package/src/lib/dock/dock-presets.ts +156 -0
- package/src/lib/dock/dock-state.svelte.ts +77 -0
- package/src/lib/dock/dock-types.ts +2 -0
- package/src/lib/dock/dockview-theme.css +226 -0
- package/src/lib/dock/dockview-theme.ts +17 -0
- package/src/lib/dock/header-action-renderer.ts +77 -0
- package/src/lib/edit/clipboard-state.ts +34 -0
- package/src/lib/export/download.ts +201 -0
- package/src/lib/export/png-metadata.ts +181 -0
- package/src/lib/history/ActionLogPanel.svelte +418 -0
- package/src/lib/history/action-log-panel-plugin.ts +24 -0
- package/src/lib/history/action-log.svelte.ts +172 -0
- package/src/lib/history/action-log.test.ts +168 -0
- package/src/lib/history/history-commands.ts +403 -0
- package/src/lib/history/macros.svelte.ts +320 -0
- package/src/lib/history/macros.test.ts +224 -0
- package/src/lib/history/replay-engine.test.ts +241 -0
- package/src/lib/history/replay-engine.ts +149 -0
- package/src/lib/history/spec-format.test.ts +250 -0
- package/src/lib/history/spec-format.ts +210 -0
- package/src/lib/iso/SeamCheckerPanel.svelte +251 -0
- package/src/lib/iso/iso-math.test.ts +77 -0
- package/src/lib/iso/iso-math.ts +77 -0
- package/src/lib/iso/seam-checker-panel-plugin.ts +25 -0
- package/src/lib/iso/seam-checker.test.ts +126 -0
- package/src/lib/iso/seam-checker.ts +93 -0
- package/src/lib/iso/tessellation.ts +67 -0
- package/src/lib/layers/compositor.test.ts +193 -0
- package/src/lib/layers/compositor.ts +175 -0
- package/src/lib/layers/layer-commands.test.ts +263 -0
- package/src/lib/layers/layer-commands.ts +429 -0
- package/src/lib/layers/layer-tree.svelte.ts +516 -0
- package/src/lib/layers/layer-tree.test.ts +383 -0
- package/src/lib/layers/layer-types.ts +56 -0
- package/src/lib/leveleditor/LevelEditorViewport.svelte +1808 -0
- package/src/lib/leveleditor/MapPropertiesPanel.svelte +266 -0
- package/src/lib/leveleditor/TilePickerPanel.svelte +324 -0
- package/src/lib/leveleditor/depth-sort.test.ts +70 -0
- package/src/lib/leveleditor/depth-sort.ts +39 -0
- package/src/lib/leveleditor/level-editor-commands.ts +353 -0
- package/src/lib/leveleditor/level-editor-viewport-plugin.ts +25 -0
- package/src/lib/leveleditor/map-properties-panel-plugin.ts +25 -0
- package/src/lib/leveleditor/map-state.svelte.ts +372 -0
- package/src/lib/leveleditor/map-state.test.ts +243 -0
- package/src/lib/leveleditor/tile-picker-panel-plugin.ts +25 -0
- package/src/lib/leveleditor/tile-source-registry.svelte.ts +91 -0
- package/src/lib/leveleditor/tiled-export.test.ts +144 -0
- package/src/lib/leveleditor/tiled-export.ts +374 -0
- package/src/lib/pywebview.d.ts +15 -0
- package/src/lib/recovery/.gitkeep +0 -0
- package/src/lib/recovery/idb-store.ts +118 -0
- package/src/lib/recovery/recovery-manager.test.ts +321 -0
- package/src/lib/recovery/recovery-manager.ts +115 -0
- package/src/lib/recovery/recovery-plugin.ts +55 -0
- package/src/lib/recovery/recovery-state.svelte.ts +21 -0
- package/src/lib/recovery/sw-plugin.ts +18 -0
- package/src/lib/recovery/sw-register.ts +17 -0
- package/src/lib/save/directory-format.ts +42 -0
- package/src/lib/save/project-snapshot.ts +139 -0
- package/src/lib/save/recent-projects.ts +56 -0
- package/src/lib/save/save-state.svelte.ts +35 -0
- package/src/lib/save/storage.ts +167 -0
- package/src/lib/save/zip-format.ts +45 -0
- package/src/lib/shortcuts/.gitkeep +0 -0
- package/src/lib/shortcuts/ShortcutEditorPanel.svelte +690 -0
- package/src/lib/shortcuts/default-bindings.ts +61 -0
- package/src/lib/shortcuts/shortcut-editor-panel-plugin.ts +25 -0
- package/src/lib/shortcuts/shortcut-init.ts +380 -0
- package/src/lib/shortcuts/shortcut-manager.test.ts +466 -0
- package/src/lib/shortcuts/shortcut-manager.ts +281 -0
- package/src/lib/shortcuts/shortcut-state.svelte.ts +78 -0
- package/src/lib/shortcuts/shortcuts-plugin.ts +17 -0
- package/src/lib/sync/patch-applicator.ts +300 -0
- package/src/lib/sync/patch-types.ts +65 -0
- package/src/lib/sync/project-manager.ts +108 -0
- package/src/lib/sync/sync-init.ts +152 -0
- package/src/lib/sync/sync-plugin.ts +19 -0
- package/src/lib/sync/sync-state.svelte.ts +56 -0
- package/src/lib/sync/ws-client.test.ts +604 -0
- package/src/lib/sync/ws-client.ts +574 -0
- package/src/lib/tools/.gitkeep +0 -0
- package/src/lib/ui/.gitkeep +0 -0
- package/src/lib/ui/AboutDialog.svelte +113 -0
- package/src/lib/ui/ColorPicker.svelte +761 -0
- package/src/lib/ui/ContextMenu.svelte +216 -0
- package/src/lib/ui/ExportDialog.svelte +747 -0
- package/src/lib/ui/FrameStrip.svelte +854 -0
- package/src/lib/ui/LayerPanel.svelte +810 -0
- package/src/lib/ui/MenuBar.svelte +590 -0
- package/src/lib/ui/NewProjectDialog.svelte +803 -0
- package/src/lib/ui/PluginManagerPanel.svelte +475 -0
- package/src/lib/ui/PromptDialog.svelte +252 -0
- package/src/lib/ui/RecoveryDialog.svelte +295 -0
- package/src/lib/ui/ResizeDialog.svelte +416 -0
- package/src/lib/ui/StatusBar.svelte +145 -0
- package/src/lib/ui/ToolbarPanel.svelte +488 -0
- package/src/lib/ui/animation-menu-commands.ts +194 -0
- package/src/lib/ui/command-palette/CommandPalette.svelte +232 -0
- package/src/lib/ui/command-palette/command-palette-plugin.ts +30 -0
- package/src/lib/ui/command-palette/command-palette-state.svelte.ts +190 -0
- package/src/lib/ui/command-palette/command-palette.test.ts +129 -0
- package/src/lib/ui/dialog-state.svelte.ts +70 -0
- package/src/lib/ui/edit-commands.ts +271 -0
- package/src/lib/ui/file-commands.ts +275 -0
- package/src/lib/ui/file-open.ts +99 -0
- package/src/lib/ui/help-commands.ts +93 -0
- package/src/lib/ui/image-commands.ts +181 -0
- package/src/lib/ui/layer-menu-commands.ts +420 -0
- package/src/lib/ui/menu-builder.ts +224 -0
- package/src/lib/ui/notifications/NotificationBanner.svelte +137 -0
- package/src/lib/ui/notifications/notification-plugin.ts +29 -0
- package/src/lib/ui/notifications/notification-state.svelte.ts +9 -0
- package/src/lib/ui/plugin-manager-panel-plugin.ts +26 -0
- package/src/lib/ui/plugin-state.svelte.ts +62 -0
- package/src/lib/ui/select-commands.ts +75 -0
- package/src/lib/ui/theme-plugin.ts +18 -0
- package/src/lib/ui/theme.svelte.ts +45 -0
- package/src/lib/ui/theme.test.ts +51 -0
- package/src/lib/ui/toolbar-config.ts +90 -0
- package/src/lib/ui/toolbar-plugin.ts +39 -0
- package/src/lib/ui/view-commands.ts +629 -0
- package/src/lib/variants/BisectionExportDialog.svelte +500 -0
- package/src/lib/variants/VariantPanel.svelte +822 -0
- package/src/lib/variants/bisection-export.test.ts +113 -0
- package/src/lib/variants/bisection-export.ts +148 -0
- package/src/lib/variants/palette-extraction.test.ts +111 -0
- package/src/lib/variants/palette-extraction.ts +84 -0
- package/src/lib/variants/palette-interpolation.test.ts +113 -0
- package/src/lib/variants/palette-interpolation.ts +87 -0
- package/src/lib/variants/palette-swap.test.ts +101 -0
- package/src/lib/variants/palette-swap.ts +114 -0
- package/src/lib/variants/variant-commands.ts +594 -0
- package/src/lib/variants/variant-panel-plugin.ts +27 -0
- package/src/lib/variants/variant-randomizer.ts +101 -0
- package/src/lib/variants/variant-state.svelte.ts +166 -0
- package/src/lib/variants/variant-state.test.ts +138 -0
- package/src/main.ts +14 -0
- package/src/vite-env.d.ts +3 -0
- package/svelte.config.js +2 -0
- package/todo/.done/audit-design-decisions.md +812 -0
- package/todo/.done/audit-implementation-plan.md +1235 -0
- package/todo/.done/happy-path-polish.md +177 -0
- package/todo/.done/pixelweaver-full-build.md +937 -0
- package/todo/.done/server-multi-frame-design.md +405 -0
- package/todo/.done/typed-dispatcher-design.md +435 -0
- package/todo/.done/unified-toolbar-and-action-system.md +323 -0
- package/todo/.obsolete/comprehensive-audit-obsolete-items.md +33 -0
- package/todo/.obsolete/tauri-desktop-bundle.md +424 -0
- package/todo/comprehensive-audit.md +1085 -0
- package/tsconfig.app.json +26 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +20 -0
- package/uv.lock +1167 -0
- package/vite.config.ts +32 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: server.src.pixelweaver.state
|
|
3
|
+
description: "In-memory authoritative state for PixelWeaver projects."
|
|
4
|
+
generated: true
|
|
5
|
+
nav_group: "API Reference"
|
|
6
|
+
nav_order: 22
|
|
7
|
+
---
|
|
8
|
+
<!-- generated by selfdoc gen, do not edit -->
|
|
9
|
+
|
|
10
|
+
# server.src.pixelweaver.state
|
|
11
|
+
|
|
12
|
+
:-: ref path="server.src.pixelweaver.state"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: server.src.pixelweaver.storage
|
|
3
|
+
description: "Project file I/O for PixelWeaver."
|
|
4
|
+
generated: true
|
|
5
|
+
nav_group: "API Reference"
|
|
6
|
+
nav_order: 23
|
|
7
|
+
---
|
|
8
|
+
<!-- generated by selfdoc gen, do not edit -->
|
|
9
|
+
|
|
10
|
+
# server.src.pixelweaver.storage
|
|
11
|
+
|
|
12
|
+
:-: ref path="server.src.pixelweaver.storage"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: server.src.pixelweaver.websocket
|
|
3
|
+
description: "WebSocket message handler for PixelWeaver."
|
|
4
|
+
generated: true
|
|
5
|
+
nav_group: "API Reference"
|
|
6
|
+
nav_order: 24
|
|
7
|
+
---
|
|
8
|
+
<!-- generated by selfdoc gen, do not edit -->
|
|
9
|
+
|
|
10
|
+
# server.src.pixelweaver.websocket
|
|
11
|
+
|
|
12
|
+
:-: ref path="server.src.pixelweaver.websocket"
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: server.src.pixelweaver
|
|
3
|
+
description: "PixelWeaver collaboration server."
|
|
4
|
+
generated: true
|
|
5
|
+
nav_group: "API Reference"
|
|
6
|
+
nav_order: 1
|
|
7
|
+
---
|
|
8
|
+
<!-- generated by selfdoc gen, do not edit -->
|
|
9
|
+
|
|
10
|
+
# server.src.pixelweaver
|
|
11
|
+
|
|
12
|
+
:-: ref path="server.src.pixelweaver"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
test.describe('App Launch', () => {
|
|
4
|
+
test('should load the application', async ({ page }) => {
|
|
5
|
+
await page.goto('/');
|
|
6
|
+
// Check that the app shell rendered
|
|
7
|
+
await expect(page.locator('canvas').first()).toBeVisible();
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
test('should show the menu bar', async ({ page }) => {
|
|
11
|
+
await page.goto('/');
|
|
12
|
+
await expect(page.getByText('PixelWeaver')).toBeVisible();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
test('should show the tool bar', async ({ page }) => {
|
|
16
|
+
await page.goto('/');
|
|
17
|
+
// The toolbar should have tool buttons
|
|
18
|
+
await expect(page.locator('.toolbar-panel')).toBeVisible();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('should show the status bar', async ({ page }) => {
|
|
22
|
+
await page.goto('/');
|
|
23
|
+
// Status bar shows canvas dimensions
|
|
24
|
+
await expect(page.getByText('32 x 32')).toBeVisible();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('should show the layer panel', async ({ page }) => {
|
|
28
|
+
await page.goto('/');
|
|
29
|
+
await expect(page.getByText('Layers', { exact: true })).toBeVisible();
|
|
30
|
+
// Click the Layers tab to reveal its content
|
|
31
|
+
await page.getByText('Layers', { exact: true }).click();
|
|
32
|
+
// Default layer should exist
|
|
33
|
+
await expect(page.getByText('Layer 1')).toBeVisible();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
test.describe('Menu Bar', () => {
|
|
4
|
+
test('should open File menu on click', async ({ page }) => {
|
|
5
|
+
await page.goto('/');
|
|
6
|
+
await page.getByText('File').click();
|
|
7
|
+
// Dropdown should appear with menu items
|
|
8
|
+
await expect(page.getByText('New Project')).toBeVisible();
|
|
9
|
+
await expect(page.getByText('Export')).toBeVisible();
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('should open Edit menu', async ({ page }) => {
|
|
13
|
+
await page.goto('/');
|
|
14
|
+
await page.getByRole('menuitem', { name: 'Edit' }).click();
|
|
15
|
+
await expect(page.getByText('Undo')).toBeVisible();
|
|
16
|
+
await expect(page.getByText('Redo')).toBeVisible();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('should close menu on Escape', async ({ page }) => {
|
|
20
|
+
await page.goto('/');
|
|
21
|
+
await page.getByText('File').click();
|
|
22
|
+
await expect(page.getByText('New Project')).toBeVisible();
|
|
23
|
+
await page.keyboard.press('Escape');
|
|
24
|
+
await expect(page.getByText('New Project')).not.toBeVisible();
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
test.describe('Tool Activation', () => {
|
|
4
|
+
test('should activate pencil tool with keyboard shortcut B', async ({ page }) => {
|
|
5
|
+
await page.goto('/');
|
|
6
|
+
await page.keyboard.press('b');
|
|
7
|
+
// The status bar should show the active tool
|
|
8
|
+
// Check for 'pencil' text in the status bar area
|
|
9
|
+
await expect(page.locator('.statusbar')).toContainText('pencil');
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('should activate eraser tool with keyboard shortcut E', async ({ page }) => {
|
|
13
|
+
await page.goto('/');
|
|
14
|
+
await page.keyboard.press('e');
|
|
15
|
+
await expect(page.locator('.statusbar')).toContainText('eraser');
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test('should switch tools by clicking toolbar buttons', async ({ page }) => {
|
|
19
|
+
await page.goto('/');
|
|
20
|
+
// Find and click a tool button (they show tool name as text)
|
|
21
|
+
const fillButton = page.locator('.toolbar button', { hasText: /fill/i });
|
|
22
|
+
if (await fillButton.count() > 0) {
|
|
23
|
+
await fillButton.click();
|
|
24
|
+
await expect(page.locator('.statusbar')).toContainText('fill');
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { test, expect } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
test.describe('Undo/Redo', () => {
|
|
4
|
+
test('should support Ctrl+Z for undo', async ({ page }) => {
|
|
5
|
+
await page.goto('/');
|
|
6
|
+
// This is a basic test -- just verify no crash on undo with empty stack
|
|
7
|
+
await page.keyboard.press('Control+z');
|
|
8
|
+
// App should still be responsive
|
|
9
|
+
await expect(page.locator('canvas').first()).toBeVisible();
|
|
10
|
+
});
|
|
11
|
+
});
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import js from '@eslint/js';
|
|
2
|
+
import ts from 'typescript-eslint';
|
|
3
|
+
import svelte from 'eslint-plugin-svelte';
|
|
4
|
+
import svelteParser from 'svelte-eslint-parser';
|
|
5
|
+
import globals from 'globals';
|
|
6
|
+
|
|
7
|
+
export default [
|
|
8
|
+
js.configs.recommended,
|
|
9
|
+
...ts.configs.strictTypeChecked,
|
|
10
|
+
...svelte.configs['flat/recommended'],
|
|
11
|
+
{
|
|
12
|
+
languageOptions: {
|
|
13
|
+
parserOptions: {
|
|
14
|
+
projectService: true,
|
|
15
|
+
tsconfigRootDir: import.meta.dirname,
|
|
16
|
+
extraFileExtensions: ['.svelte'],
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
files: ['**/*.svelte', '**/*.svelte.ts'],
|
|
22
|
+
languageOptions: {
|
|
23
|
+
parser: svelteParser,
|
|
24
|
+
parserOptions: {
|
|
25
|
+
parser: ts.parser,
|
|
26
|
+
projectService: true,
|
|
27
|
+
tsconfigRootDir: import.meta.dirname,
|
|
28
|
+
extraFileExtensions: ['.svelte'],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
languageOptions: {
|
|
34
|
+
globals: {
|
|
35
|
+
...globals.browser,
|
|
36
|
+
...globals.node,
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
// Honor the leading-underscore convention for intentionally unused symbols.
|
|
42
|
+
// Matches the prevailing _e / _ctx / _params pattern used in callback
|
|
43
|
+
// signatures where arguments are required but unused.
|
|
44
|
+
rules: {
|
|
45
|
+
'@typescript-eslint/no-unused-vars': [
|
|
46
|
+
'error',
|
|
47
|
+
{
|
|
48
|
+
args: 'all',
|
|
49
|
+
argsIgnorePattern: '^_',
|
|
50
|
+
caughtErrors: 'all',
|
|
51
|
+
caughtErrorsIgnorePattern: '^_',
|
|
52
|
+
destructuredArrayIgnorePattern: '^_',
|
|
53
|
+
varsIgnorePattern: '^_',
|
|
54
|
+
ignoreRestSiblings: true,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
ignores: ['dist/', 'node_modules/', 'src-tauri/', 'server/', 'scripts/', '*.config.js', '*.config.ts'],
|
|
61
|
+
},
|
|
62
|
+
];
|
package/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
7
|
+
<title>PixelWeaver</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="app"></div>
|
|
11
|
+
<script type="module" src="/src/main.ts"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/package.json
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pixelweaver",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vite",
|
|
7
|
+
"build": "vite build",
|
|
8
|
+
"preview": "vite preview",
|
|
9
|
+
"check": "svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json",
|
|
10
|
+
"test": "vitest run",
|
|
11
|
+
"test:watch": "vitest",
|
|
12
|
+
"lint": "eslint .",
|
|
13
|
+
"format": "prettier --write .",
|
|
14
|
+
"test:e2e": "playwright test"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@eslint/js": "^9.24.0",
|
|
18
|
+
"@fontsource-variable/inter": "^5.2.8",
|
|
19
|
+
"@iconify-json/lucide": "^1.2.101",
|
|
20
|
+
"@playwright/test": "^1.59.1",
|
|
21
|
+
"@sveltejs/vite-plugin-svelte": "^7.0.0",
|
|
22
|
+
"@tsconfig/svelte": "^5.0.8",
|
|
23
|
+
"@types/culori": "^4.0.1",
|
|
24
|
+
"@types/node": "^24.12.2",
|
|
25
|
+
"eslint": "^9.24.0",
|
|
26
|
+
"eslint-plugin-svelte": "^3.5.1",
|
|
27
|
+
"globals": "^16.0.0",
|
|
28
|
+
"jsdom": "^29.0.2",
|
|
29
|
+
"prettier": "^3.5.3",
|
|
30
|
+
"prettier-plugin-svelte": "^3.5.1",
|
|
31
|
+
"svelte": "^5.55.1",
|
|
32
|
+
"svelte-check": "^4.4.6",
|
|
33
|
+
"svelte-eslint-parser": "^1.2.0",
|
|
34
|
+
"typescript": "~6.0.2",
|
|
35
|
+
"typescript-eslint": "^8.32.1",
|
|
36
|
+
"unplugin-icons": "^23.0.1",
|
|
37
|
+
"vite": "^8.0.4",
|
|
38
|
+
"vitest": "^3.1.1"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"culori": "^4.0.2",
|
|
42
|
+
"dockview-core": "^5.2.0",
|
|
43
|
+
"fflate": "^0.8.2"
|
|
44
|
+
},
|
|
45
|
+
"keywords": [
|
|
46
|
+
"rlsbl"
|
|
47
|
+
]
|
|
48
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { defineConfig } from '@playwright/test';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
testDir: './e2e',
|
|
5
|
+
fullyParallel: true,
|
|
6
|
+
forbidOnly: !!process.env.CI,
|
|
7
|
+
retries: process.env.CI ? 2 : 0,
|
|
8
|
+
workers: process.env.CI ? 1 : undefined,
|
|
9
|
+
reporter: 'html',
|
|
10
|
+
use: {
|
|
11
|
+
baseURL: 'http://localhost:5173',
|
|
12
|
+
trace: 'on-first-retry',
|
|
13
|
+
},
|
|
14
|
+
webServer: {
|
|
15
|
+
command: 'npm run dev',
|
|
16
|
+
url: 'http://localhost:5173',
|
|
17
|
+
reuseExistingServer: !process.env.CI,
|
|
18
|
+
},
|
|
19
|
+
});
|
|
File without changes
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced Fill Tool Plugin -- flood fill with color tolerance.
|
|
3
|
+
*
|
|
4
|
+
* Registers:
|
|
5
|
+
* - Command: `flood_fill_tolerance` (tier: 'frame')
|
|
6
|
+
* - Tool: `fill-tolerance`
|
|
7
|
+
*
|
|
8
|
+
* Extends the basic flood fill by allowing a tolerance threshold.
|
|
9
|
+
* Pixels whose color distance (OKLab) from the target pixel is within
|
|
10
|
+
* the tolerance are included in the fill region.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { PluginModule } from '../../src/lib/core/plugin-loader.js';
|
|
14
|
+
import type { PixelBuffer } from '../../src/lib/canvas/pixel-buffer.js';
|
|
15
|
+
import type { Point } from './drawing-utils.js';
|
|
16
|
+
import { snapshotPixels, applyPixels, hexToRgba, makeSnapshotUndo } from './drawing-utils.js';
|
|
17
|
+
import { rgbToHex, colorDistance } from '../../src/lib/color/color-utils.js';
|
|
18
|
+
import AdvFillIcon from '~icons/lucide/wand';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Flood fill with color tolerance.
|
|
22
|
+
* Returns all connected pixels whose color distance from the starting pixel
|
|
23
|
+
* is <= tolerance (0-1 range, where 0 = exact match, 1 = any color).
|
|
24
|
+
*/
|
|
25
|
+
export function floodFillTolerance(
|
|
26
|
+
buffer: PixelBuffer,
|
|
27
|
+
startX: number,
|
|
28
|
+
startY: number,
|
|
29
|
+
tolerance: number,
|
|
30
|
+
): Point[] {
|
|
31
|
+
if (!buffer.inBounds(startX, startY)) return [];
|
|
32
|
+
|
|
33
|
+
const [tr, tg, tb, ta] = buffer.getPixel(startX, startY);
|
|
34
|
+
const targetHex = rgbToHex(tr, tg, tb);
|
|
35
|
+
const targetIsTransparent = ta === 0;
|
|
36
|
+
const visited = new Set<number>();
|
|
37
|
+
const result: Point[] = [];
|
|
38
|
+
const queue: Point[] = [{ x: startX, y: startY }];
|
|
39
|
+
const w = buffer.width;
|
|
40
|
+
|
|
41
|
+
visited.add(startY * w + startX);
|
|
42
|
+
|
|
43
|
+
while (queue.length > 0) {
|
|
44
|
+
const next = queue.shift();
|
|
45
|
+
if (!next) break;
|
|
46
|
+
const { x, y } = next;
|
|
47
|
+
result.push({ x, y });
|
|
48
|
+
|
|
49
|
+
const neighbors: Point[] = [
|
|
50
|
+
{ x: x - 1, y },
|
|
51
|
+
{ x: x + 1, y },
|
|
52
|
+
{ x, y: y - 1 },
|
|
53
|
+
{ x, y: y + 1 },
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
for (const n of neighbors) {
|
|
57
|
+
if (!buffer.inBounds(n.x, n.y)) continue;
|
|
58
|
+
const key = n.y * w + n.x;
|
|
59
|
+
if (visited.has(key)) continue;
|
|
60
|
+
visited.add(key);
|
|
61
|
+
|
|
62
|
+
const [r, g, b, a] = buffer.getPixel(n.x, n.y);
|
|
63
|
+
|
|
64
|
+
// Handle transparency matching: transparent pixels only match other transparent pixels
|
|
65
|
+
if (targetIsTransparent) {
|
|
66
|
+
if (a === 0) queue.push(n);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Non-transparent target: check if neighbor is non-transparent and within tolerance
|
|
71
|
+
if (a === 0) continue;
|
|
72
|
+
|
|
73
|
+
const neighborHex = rgbToHex(r, g, b);
|
|
74
|
+
const dist = colorDistance(targetHex, neighborHex);
|
|
75
|
+
if (dist <= tolerance) {
|
|
76
|
+
queue.push(n);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export const advancedFillToolPlugin: PluginModule = {
|
|
85
|
+
name: 'builtin/advanced-fill',
|
|
86
|
+
version: '1.0.0',
|
|
87
|
+
dependencies: [],
|
|
88
|
+
register(api) {
|
|
89
|
+
api.addCommand('flood_fill_tolerance', {
|
|
90
|
+
tier: 'frame',
|
|
91
|
+
|
|
92
|
+
execute(params, ctx) {
|
|
93
|
+
const buffer = ctx.getActiveBuffer?.();
|
|
94
|
+
if (!buffer) return;
|
|
95
|
+
|
|
96
|
+
const { r, g, b, a } = hexToRgba(params["color"]);
|
|
97
|
+
const tolerance = params["tolerance"];
|
|
98
|
+
const region = floodFillTolerance(
|
|
99
|
+
buffer,
|
|
100
|
+
params["x"],
|
|
101
|
+
params["y"],
|
|
102
|
+
tolerance,
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
const snapshot = snapshotPixels(buffer, region);
|
|
106
|
+
const fillPixels = region.map((p) => ({ ...p, r, g, b, a }));
|
|
107
|
+
applyPixels(buffer, fillPixels);
|
|
108
|
+
return snapshot;
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
undo: makeSnapshotUndo(),
|
|
112
|
+
|
|
113
|
+
describe(params) {
|
|
114
|
+
return `Flood fill at (${String(params["x"])}, ${String(params["y"])}) with ${String(params["color"])} tolerance=${String(params["tolerance"])}`;
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
api.addToolbarItem('toolbar:drawing-tools:fill-tolerance', {
|
|
119
|
+
toolbarId: 'drawing-tools',
|
|
120
|
+
kind: 'tool',
|
|
121
|
+
targetId: 'fill-tolerance',
|
|
122
|
+
group: 'advanced',
|
|
123
|
+
order: 10,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
api.addTool('fill-tolerance', {
|
|
127
|
+
icon: AdvFillIcon,
|
|
128
|
+
cursor: 'crosshair',
|
|
129
|
+
|
|
130
|
+
onPointerDown(_e, ctx) {
|
|
131
|
+
ctx.api.dispatch({
|
|
132
|
+
type: 'flood_fill_tolerance',
|
|
133
|
+
plugin: 'builtin/advanced-fill',
|
|
134
|
+
version: '1.0.0',
|
|
135
|
+
params: {
|
|
136
|
+
x: ctx.canvasX,
|
|
137
|
+
y: ctx.canvasY,
|
|
138
|
+
color: ctx.color,
|
|
139
|
+
tolerance: 0.1,
|
|
140
|
+
layerId: '',
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
},
|
|
146
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Circle/Ellipse Tool Plugin -- draws ellipses and circles.
|
|
3
|
+
*
|
|
4
|
+
* Registers:
|
|
5
|
+
* - Command: `draw_ellipse` (tier: 'frame')
|
|
6
|
+
* - Tool: `ellipse`
|
|
7
|
+
*
|
|
8
|
+
* Click-drag defines the bounding box. Shift constrains to a perfect circle
|
|
9
|
+
* (handled at the viewport level; the command just receives cx/cy/rx/ry).
|
|
10
|
+
* Supports outline and filled modes via the `filled` param.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { PluginModule } from '../../src/lib/core/plugin-loader.js';
|
|
14
|
+
import {
|
|
15
|
+
ellipseOutline,
|
|
16
|
+
ellipseFilled,
|
|
17
|
+
snapshotPixels,
|
|
18
|
+
applyPixels,
|
|
19
|
+
hexToRgba,
|
|
20
|
+
makeSnapshotUndo,
|
|
21
|
+
} from './drawing-utils.js';
|
|
22
|
+
import { getToolOptionValue } from '../../src/lib/core/tool-options-state.svelte.js';
|
|
23
|
+
import { setShapePreview, clearShapePreview } from '../../src/lib/canvas/shape-preview-state.svelte.js';
|
|
24
|
+
import CircleIcon from '~icons/lucide/circle';
|
|
25
|
+
import PaintBucketIcon from '~icons/lucide/paint-bucket';
|
|
26
|
+
|
|
27
|
+
export const circleToolPlugin: PluginModule = {
|
|
28
|
+
name: 'builtin/circle',
|
|
29
|
+
version: '1.0.0',
|
|
30
|
+
dependencies: [],
|
|
31
|
+
register(api) {
|
|
32
|
+
api.addCommand('draw_ellipse', {
|
|
33
|
+
tier: 'frame',
|
|
34
|
+
icon: CircleIcon,
|
|
35
|
+
|
|
36
|
+
execute(params, ctx) {
|
|
37
|
+
const buffer = ctx.getActiveBuffer?.();
|
|
38
|
+
if (!buffer) return;
|
|
39
|
+
|
|
40
|
+
const cx = params["cx"];
|
|
41
|
+
const cy = params["cy"];
|
|
42
|
+
const rx = params["rx"];
|
|
43
|
+
const ry = params["ry"];
|
|
44
|
+
const filled = params["filled"];
|
|
45
|
+
const { r, g, b, a } = hexToRgba(params["color"]);
|
|
46
|
+
|
|
47
|
+
const pointsFn = filled ? ellipseFilled : ellipseOutline;
|
|
48
|
+
const points = pointsFn(cx, cy, rx, ry);
|
|
49
|
+
|
|
50
|
+
const snapshot = snapshotPixels(buffer, points);
|
|
51
|
+
const pixels = points.map((p) => ({ ...p, r, g, b, a }));
|
|
52
|
+
applyPixels(buffer, pixels);
|
|
53
|
+
return snapshot;
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
undo: makeSnapshotUndo(),
|
|
57
|
+
|
|
58
|
+
describe(params) {
|
|
59
|
+
const filled = params["filled"] ? 'filled ' : '';
|
|
60
|
+
return `Drew ${filled}ellipse at (${String(params["cx"])},${String(params["cy"])}) rx=${String(params["rx"])} ry=${String(params["ry"])}`;
|
|
61
|
+
},
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
api.addToolbarItem('toolbar:drawing-tools:ellipse', {
|
|
65
|
+
toolbarId: 'drawing-tools',
|
|
66
|
+
kind: 'tool',
|
|
67
|
+
targetId: 'ellipse',
|
|
68
|
+
group: 'shapes',
|
|
69
|
+
order: 20,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// -- Stroke state --
|
|
73
|
+
let drawing = false;
|
|
74
|
+
let startX = 0;
|
|
75
|
+
let startY = 0;
|
|
76
|
+
let color = '';
|
|
77
|
+
|
|
78
|
+
api.addTool('ellipse', {
|
|
79
|
+
icon: CircleIcon,
|
|
80
|
+
cursor: 'crosshair',
|
|
81
|
+
options: [
|
|
82
|
+
{ id: 'filled', label: 'Filled', icon: PaintBucketIcon, type: 'toggle', defaultValue: false },
|
|
83
|
+
],
|
|
84
|
+
|
|
85
|
+
onPointerDown(_e, ctx) {
|
|
86
|
+
drawing = true;
|
|
87
|
+
startX = ctx.canvasX;
|
|
88
|
+
startY = ctx.canvasY;
|
|
89
|
+
color = ctx.color;
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
onPointerMove(e, ctx) {
|
|
93
|
+
if (!drawing) return;
|
|
94
|
+
|
|
95
|
+
// Shift constrains to perfect circle (equal radii)
|
|
96
|
+
let endX = ctx.canvasX;
|
|
97
|
+
let endY = ctx.canvasY;
|
|
98
|
+
if (e.shiftKey) {
|
|
99
|
+
const maxDim = Math.max(Math.abs(endX - startX), Math.abs(endY - startY));
|
|
100
|
+
endX = startX + maxDim * (Math.sign(endX - startX) || 1);
|
|
101
|
+
endY = startY + maxDim * (Math.sign(endY - startY) || 1);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Ctrl draws from center: start becomes center, mirror endpoint
|
|
105
|
+
let previewStartX = startX;
|
|
106
|
+
let previewStartY = startY;
|
|
107
|
+
let previewEndX = endX;
|
|
108
|
+
let previewEndY = endY;
|
|
109
|
+
if (e.ctrlKey) {
|
|
110
|
+
const dx = endX - startX;
|
|
111
|
+
const dy = endY - startY;
|
|
112
|
+
previewStartX = startX - dx;
|
|
113
|
+
previewStartY = startY - dy;
|
|
114
|
+
previewEndX = startX + dx;
|
|
115
|
+
previewEndY = startY + dy;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
setShapePreview({
|
|
119
|
+
type: 'ellipse',
|
|
120
|
+
startX: previewStartX,
|
|
121
|
+
startY: previewStartY,
|
|
122
|
+
endX: previewEndX,
|
|
123
|
+
endY: previewEndY,
|
|
124
|
+
color,
|
|
125
|
+
filled: getToolOptionValue('ellipse', 'filled') as boolean,
|
|
126
|
+
});
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
onPointerUp(e, ctx) {
|
|
130
|
+
if (!drawing) return;
|
|
131
|
+
drawing = false;
|
|
132
|
+
clearShapePreview();
|
|
133
|
+
|
|
134
|
+
// Shift constrains to perfect circle (equal radii)
|
|
135
|
+
let endX = ctx.canvasX;
|
|
136
|
+
let endY = ctx.canvasY;
|
|
137
|
+
if (e.shiftKey) {
|
|
138
|
+
const maxDim = Math.max(Math.abs(endX - startX), Math.abs(endY - startY));
|
|
139
|
+
endX = startX + maxDim * (Math.sign(endX - startX) || 1);
|
|
140
|
+
endY = startY + maxDim * (Math.sign(endY - startY) || 1);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Ctrl draws from center: start becomes center, mirror endpoint
|
|
144
|
+
let finalStartX = startX;
|
|
145
|
+
let finalStartY = startY;
|
|
146
|
+
let finalEndX = endX;
|
|
147
|
+
let finalEndY = endY;
|
|
148
|
+
if (e.ctrlKey) {
|
|
149
|
+
const dx = endX - startX;
|
|
150
|
+
const dy = endY - startY;
|
|
151
|
+
finalStartX = startX - dx;
|
|
152
|
+
finalStartY = startY - dy;
|
|
153
|
+
finalEndX = startX + dx;
|
|
154
|
+
finalEndY = startY + dy;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Bounding box from drag
|
|
158
|
+
const x0 = Math.min(finalStartX, finalEndX);
|
|
159
|
+
const y0 = Math.min(finalStartY, finalEndY);
|
|
160
|
+
const x1 = Math.max(finalStartX, finalEndX);
|
|
161
|
+
const y1 = Math.max(finalStartY, finalEndY);
|
|
162
|
+
|
|
163
|
+
// Derive center and radii from bounding box
|
|
164
|
+
const cx = Math.round((x0 + x1) / 2);
|
|
165
|
+
const cy = Math.round((y0 + y1) / 2);
|
|
166
|
+
const rx = Math.floor((x1 - x0) / 2);
|
|
167
|
+
const ry = Math.floor((y1 - y0) / 2);
|
|
168
|
+
|
|
169
|
+
ctx.api.dispatch({
|
|
170
|
+
type: 'draw_ellipse',
|
|
171
|
+
plugin: 'builtin/circle',
|
|
172
|
+
version: '1.0.0',
|
|
173
|
+
params: {
|
|
174
|
+
cx,
|
|
175
|
+
cy,
|
|
176
|
+
rx,
|
|
177
|
+
ry,
|
|
178
|
+
filled: getToolOptionValue('ellipse', 'filled'),
|
|
179
|
+
color,
|
|
180
|
+
layerId: '',
|
|
181
|
+
},
|
|
182
|
+
});
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
},
|
|
186
|
+
};
|