@lvl99dotcom/paintcanvas 0.4.0 → 0.6.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/README.md +30 -15
- package/dist/lib/brushes/configRanges/index.d.ts +17 -0
- package/dist/lib/brushes/configRanges/index.js +31 -1
- package/dist/lib/brushes/params/index.d.ts +2 -1
- package/dist/lib/brushes/params/index.js +7 -4
- package/dist/lib/brushes/presets/round/index.d.ts +1 -1
- package/dist/lib/brushes/presets/round/index.js +12 -7
- package/dist/lib/brushes/presets/square/index.d.ts +1 -1
- package/dist/lib/brushes/presets/square/index.js +7 -3
- package/dist/lib/brushes/shapeImage/index.d.ts +7 -3
- package/dist/lib/brushes/shapeImage/index.js +15 -9
- package/dist/lib/brushes/shapes/index.d.ts +18 -0
- package/dist/lib/brushes/shapes/index.js +86 -0
- package/dist/lib/brushes/types/index.d.ts +8 -0
- package/dist/lib/classes/BrushSettings/index.d.ts +29 -2
- package/dist/lib/classes/BrushSettings/index.js +51 -14
- package/dist/lib/classes/KeyboardShortcuts/index.d.ts +31 -2
- package/dist/lib/classes/KeyboardShortcuts/index.js +66 -4
- package/dist/lib/classes/Layer/index.d.ts +10 -0
- package/dist/lib/classes/Layer/index.js +15 -1
- package/dist/lib/classes/LayerAutosave/index.d.ts +9 -15
- package/dist/lib/classes/LayerAutosave/index.js +30 -43
- package/dist/lib/classes/LayerStack/index.d.ts +15 -0
- package/dist/lib/classes/LayerStack/index.js +26 -2
- package/dist/lib/classes/PaintCanvas/index.d.ts +72 -3
- package/dist/lib/classes/PaintCanvas/index.js +106 -12
- package/dist/lib/classes/PngEncoder/index.d.ts +41 -0
- package/dist/lib/classes/PngEncoder/index.js +104 -0
- package/dist/lib/classes/ProfileGraphs/index.d.ts +87 -0
- package/dist/lib/classes/ProfileGraphs/index.js +148 -0
- package/dist/lib/classes/SettingsAutosave/index.d.ts +3 -2
- package/dist/lib/classes/SettingsAutosave/index.js +6 -3
- package/dist/lib/classes/UiSettings/index.d.ts +68 -0
- package/dist/lib/classes/UiSettings/index.js +82 -0
- package/dist/lib/messages/schema.d.ts +720 -0
- package/dist/lib/presets/schema.d.ts +360 -0
- package/dist/lib/presets/schema.js +8 -1
- package/dist/lib/profiles/index.d.ts +61 -0
- package/dist/lib/profiles/index.js +131 -0
- package/dist/lib/profiles/schema.d.ts +73 -0
- package/dist/lib/profiles/schema.js +53 -0
- package/dist/lib/rendering/sample/index.d.ts +11 -0
- package/dist/lib/rendering/sample/index.js +36 -5
- package/dist/lib/settings/index.d.ts +2 -1
- package/dist/lib/settings/index.js +7 -2
- package/dist/lib/settings/schema.d.ts +67 -0
- package/dist/lib/settings/schema.js +20 -0
- package/dist/lib/validation/validators.generated.js +13191 -3655
- package/dist/locales/en-GB.js +39 -2
- package/dist/locales/en-US.js +39 -2
- package/dist/locales/fr.js +39 -2
- package/dist/locales/ja.js +39 -2
- package/dist/locales/ko.js +39 -2
- package/dist/locales/zh-Hans.js +39 -2
- package/dist/locales/zh-Hant.js +39 -2
- package/dist/package.js +1 -1
- package/dist/testing/recordingCanvas.js +2 -1
- package/dist/ui/ask/index.d.ts +48 -0
- package/dist/ui/ask/index.js +67 -0
- package/dist/ui/base/index.d.ts +10 -3
- package/dist/ui/base/index.js +19 -4
- package/dist/ui/dismiss/index.d.ts +21 -0
- package/dist/ui/dismiss/index.js +27 -0
- package/dist/ui/drag/index.d.ts +51 -0
- package/dist/ui/drag/index.js +81 -0
- package/dist/ui/elements/PaintCanvas/index.d.ts +17 -12
- package/dist/ui/elements/PaintCanvas/index.js +36 -17
- package/dist/ui/elements/PaintCanvas/style.js +1 -1
- package/dist/ui/elements/PcAppSettings/index.d.ts +6 -6
- package/dist/ui/elements/PcAppSettings/index.js +21 -65
- package/dist/ui/elements/PcBrushConfig/index.d.ts +34 -16
- package/dist/ui/elements/PcBrushConfig/index.js +263 -153
- package/dist/ui/elements/PcBrushConfig/style.js +1 -1
- package/dist/ui/elements/PcBrushSize/index.d.ts +53 -0
- package/dist/ui/elements/PcBrushSize/index.js +66 -0
- package/dist/ui/elements/PcBrushTest/index.d.ts +26 -0
- package/dist/ui/elements/PcBrushTest/index.js +79 -6
- package/dist/ui/elements/PcBrushTest/style.js +1 -1
- package/dist/ui/elements/PcButton/index.d.ts +12 -3
- package/dist/ui/elements/PcButton/index.js +18 -10
- package/dist/ui/elements/PcCanvas/index.d.ts +27 -2
- package/dist/ui/elements/PcCanvas/index.js +63 -13
- package/dist/ui/elements/PcCanvas/style.js +1 -1
- package/dist/ui/elements/PcCanvasMap/style.js +1 -1
- package/dist/ui/elements/PcClipboardTools/index.d.ts +49 -0
- package/dist/ui/elements/PcClipboardTools/index.js +125 -0
- package/dist/ui/elements/PcContextMenu/index.d.ts +2 -2
- package/dist/ui/elements/PcDialog/index.d.ts +46 -10
- package/dist/ui/elements/PcDialog/index.js +102 -51
- package/dist/ui/elements/PcDialogWindow/index.d.ts +141 -0
- package/dist/ui/elements/PcDialogWindow/index.js +385 -0
- package/dist/ui/elements/PcInputSettings/index.d.ts +13 -0
- package/dist/ui/elements/PcInputSettings/index.js +16 -1
- package/dist/ui/elements/PcMenu/index.d.ts +48 -3
- package/dist/ui/elements/PcMenu/index.js +111 -19
- package/dist/ui/elements/PcNumberSlider/index.d.ts +137 -0
- package/dist/ui/elements/PcNumberSlider/index.js +247 -0
- package/dist/ui/elements/PcPane/index.d.ts +3 -1
- package/dist/ui/elements/PcPane/index.js +44 -7
- package/dist/ui/elements/PcPanel/index.d.ts +2 -2
- package/dist/ui/elements/PcPanel/index.js +8 -8
- package/dist/ui/elements/PcPanelBrushes/index.d.ts +5 -17
- package/dist/ui/elements/PcPanelBrushes/index.js +13 -48
- package/dist/ui/elements/PcPanelBrushes/style.js +1 -1
- package/dist/ui/elements/PcPanelColours/index.js +2 -2
- package/dist/ui/elements/PcPanelColours/style.js +1 -1
- package/dist/ui/elements/PcPanelLayers/index.js +1 -1
- package/dist/ui/elements/PcPanelLayers/style.js +1 -1
- package/dist/ui/elements/PcPopover/index.d.ts +39 -1
- package/dist/ui/elements/PcPopover/index.js +72 -4
- package/dist/ui/elements/PcProfileEditor/index.d.ts +103 -0
- package/dist/ui/elements/PcProfileEditor/index.js +259 -0
- package/dist/ui/elements/PcProfileGraph/index.d.ts +84 -0
- package/dist/ui/elements/PcProfileGraph/index.js +317 -0
- package/dist/ui/elements/PcShortcutSettings/index.js +9 -13
- package/dist/ui/elements/PcSliderRange/index.d.ts +147 -0
- package/dist/ui/elements/PcSliderRange/index.js +307 -0
- package/dist/ui/elements/PcStats/index.d.ts +19 -4
- package/dist/ui/elements/PcStats/index.js +29 -13
- package/dist/ui/elements/PcThemePicker/index.d.ts +17 -15
- package/dist/ui/elements/PcThemePicker/index.js +22 -21
- package/dist/ui/elements/PcToolSettings/index.d.ts +48 -9
- package/dist/ui/elements/PcToolSettings/index.js +382 -318
- package/dist/ui/elements/PcToolSettings/style.js +1 -1
- package/dist/ui/elements/PcToolbar/index.d.ts +15 -0
- package/dist/ui/elements/PcToolbar/index.js +55 -14
- package/dist/ui/elements/PcToolbar/style.js +1 -1
- package/dist/ui/icons/images/icon-brush@2x.js +3 -0
- package/dist/ui/icons/images/icon-circle-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-clipboard@2x.js +3 -0
- package/dist/ui/icons/images/icon-cog@2x.js +3 -0
- package/dist/ui/icons/images/icon-cursor@2x.js +3 -0
- package/dist/ui/icons/images/icon-diagonal-tlbr-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-diagonal-trbl-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-eraser@2x.js +3 -0
- package/dist/ui/icons/images/icon-eyedropper@2x.js +3 -0
- package/dist/ui/icons/images/icon-flip-horizontal@2x.js +3 -0
- package/dist/ui/icons/images/icon-flip-vertical@2x.js +3 -0
- package/dist/ui/icons/images/icon-focus-pen@2x.js +3 -0
- package/dist/ui/icons/images/icon-freehand-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-hidden@2x.js +3 -0
- package/dist/ui/icons/images/icon-horizontal-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-layers@2x.js +3 -0
- package/dist/ui/icons/images/icon-linked@2x.js +3 -0
- package/dist/ui/icons/images/icon-locked@2x.js +3 -0
- package/dist/ui/icons/images/icon-moon@2x.js +3 -0
- package/dist/ui/icons/images/icon-pan@2x.js +3 -0
- package/dist/ui/icons/images/icon-polygon-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-pressure@2x.js +3 -0
- package/dist/ui/icons/images/icon-profile-graph@2x.js +3 -0
- package/dist/ui/icons/images/icon-randomise@2x.js +3 -0
- package/dist/ui/icons/images/icon-rectangle-mask@2x.js +3 -0
- package/dist/ui/icons/images/icon-redo@2x.js +3 -0
- package/dist/ui/icons/images/icon-rotate-90-left@2x.js +3 -0
- package/dist/ui/icons/images/icon-rotate-90-right@2x.js +3 -0
- package/dist/ui/icons/images/icon-rotate@2x.js +3 -0
- package/dist/ui/icons/images/icon-smoothing-bezier-cubic@2x.js +3 -0
- package/dist/ui/icons/images/icon-smoothing-bezier-quad@2x.js +3 -0
- package/dist/ui/icons/images/icon-smoothing-linear@2x.js +3 -0
- package/dist/ui/icons/images/icon-star@2x.js +3 -0
- package/dist/ui/icons/images/icon-sun@2x.js +3 -0
- package/dist/ui/icons/images/icon-tilt@2x.js +3 -0
- package/dist/ui/icons/images/icon-undo@2x.js +3 -0
- package/dist/ui/icons/images/icon-unlinked@2x.js +3 -0
- package/dist/ui/icons/images/icon-unlocked@2x.js +3 -0
- package/dist/ui/icons/images/icon-vertical-size@2x.js +3 -0
- package/dist/ui/icons/images/icon-visible@2x.js +3 -0
- package/dist/ui/icons/images/icon-zoom-in@2x.js +3 -0
- package/dist/ui/icons/images/icon-zoom-out@2x.js +3 -0
- package/dist/ui/icons/images/icon-zoom@2x.js +3 -0
- package/dist/ui/icons/index.d.ts +1 -1
- package/dist/ui/icons/index.js +86 -72
- package/dist/ui/index.d.ts +13 -3
- package/dist/ui/index.js +15 -4
- package/dist/ui/layout/index.d.ts +9 -1
- package/dist/ui/layout/index.js +11 -2
- package/dist/ui/styles/index.d.ts +21 -1
- package/dist/ui/styles/index.js +73 -14
- package/dist/ui/tokens/index.d.ts +5 -4
- package/dist/ui/tokens/index.js +12 -7
- package/locales/en-GB.json +39 -2
- package/locales/en-US.json +39 -2
- package/locales/fr.json +39 -2
- package/locales/ja.json +39 -2
- package/locales/ko.json +39 -2
- package/locales/zh-Hans.json +39 -2
- package/locales/zh-Hant.json +39 -2
- package/package.json +27 -18
- package/dist/ui/icons/images/brush.js +0 -3
- package/dist/ui/icons/images/circle-mask.js +0 -3
- package/dist/ui/icons/images/clipboard.js +0 -3
- package/dist/ui/icons/images/cursor.js +0 -3
- package/dist/ui/icons/images/eraser.js +0 -3
- package/dist/ui/icons/images/eyedropper.js +0 -3
- package/dist/ui/icons/images/flip-horizontal.js +0 -3
- package/dist/ui/icons/images/flip-vertical.js +0 -3
- package/dist/ui/icons/images/focus-pen.js +0 -3
- package/dist/ui/icons/images/freehand-mask.js +0 -3
- package/dist/ui/icons/images/hidden.js +0 -3
- package/dist/ui/icons/images/layers.js +0 -3
- package/dist/ui/icons/images/linked.js +0 -3
- package/dist/ui/icons/images/locked.js +0 -3
- package/dist/ui/icons/images/moon.js +0 -3
- package/dist/ui/icons/images/pan.js +0 -3
- package/dist/ui/icons/images/polygon-mask.js +0 -3
- package/dist/ui/icons/images/pressure.js +0 -3
- package/dist/ui/icons/images/randomise.js +0 -3
- package/dist/ui/icons/images/rectangle-mask.js +0 -3
- package/dist/ui/icons/images/redo.js +0 -3
- package/dist/ui/icons/images/rotate-90-left.js +0 -3
- package/dist/ui/icons/images/rotate-90-right.js +0 -3
- package/dist/ui/icons/images/smoothing-bezier-cubic.js +0 -3
- package/dist/ui/icons/images/smoothing-bezier-quad.js +0 -3
- package/dist/ui/icons/images/smoothing-linear.js +0 -3
- package/dist/ui/icons/images/star.js +0 -3
- package/dist/ui/icons/images/sun.js +0 -3
- package/dist/ui/icons/images/tilt.js +0 -3
- package/dist/ui/icons/images/undo.js +0 -3
- package/dist/ui/icons/images/unlinked.js +0 -3
- package/dist/ui/icons/images/unlocked.js +0 -3
- package/dist/ui/icons/images/visible.js +0 -3
- package/dist/ui/icons/images/zoom-in.js +0 -3
- package/dist/ui/icons/images/zoom-out.js +0 -3
- package/dist/ui/icons/images/zoom.js +0 -3
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Open-source painting engine and renderer for the browser, with optional UI.
|
|
4
4
|
|
|
5
|
+
[Read the docs](https://lvl99.github.io/paintcanvas/), [try PaintCanvas in your browser](https://lvl99.github.io/paintcanvas/app/) or [see the UI elements](https://lvl99.github.io/paintcanvas/app/elements.html).
|
|
6
|
+
|
|
5
7
|
- **Engine** (`@lvl99dotcom/paintcanvas/lib/*`): layers, brushes and erasers, custom brush presets, selection, transforms, clipboard, undo/redo, viewport, pen/touch/mouse input and rendering. No UI, and nothing is stored unless you ask (e.g. keeping the drawing in the browser).
|
|
6
8
|
- **Messages**: the engine sends and receives small drawing messages (patches of pixels, strokes, undo and redo), so you can share a drawing over any transport you choose.
|
|
7
9
|
- **UI** (`@lvl99dotcom/paintcanvas/ui`), optional: Lit web components, from buttons and sliders up to a full `<paint-canvas>` layout with slots for your own parts.
|
|
@@ -69,7 +71,7 @@ Importing `@lvl99dotcom/paintcanvas/ui` registers every element. To load less, i
|
|
|
69
71
|
|
|
70
72
|
## Documentation
|
|
71
73
|
|
|
72
|
-
|
|
74
|
+
The documentation is published at https://lvl99.github.io/paintcanvas/. Its guides and API pages are in [docs/](docs/), a [Docusaurus](https://docusaurus.io) site (`pnpm docs`). The guides:
|
|
73
75
|
|
|
74
76
|
- [The layout and its slots](docs/docs/layout.md)
|
|
75
77
|
- [Building your own UI on the engine](docs/docs/engine.md)
|
|
@@ -81,13 +83,15 @@ Guides and API pages are in [docs/](docs/), a [Docusaurus](https://docusaurus.io
|
|
|
81
83
|
|
|
82
84
|
## Demo
|
|
83
85
|
|
|
86
|
+
The demo is published at https://lvl99.github.io/paintcanvas/app/. To run it locally:
|
|
87
|
+
|
|
84
88
|
```sh
|
|
85
89
|
pnpm dev
|
|
86
90
|
```
|
|
87
91
|
|
|
88
92
|
Starts a development server and opens the demo in your browser. It uses the source directly, so saving a change to `src/`, `demo/` or `locales/` reloads the page with the change straight away. (The whole page reloads because custom elements can't be redefined in a running page, so the drawing starts again.)
|
|
89
93
|
|
|
90
|
-
- http://localhost:5190/: a full-page PaintCanvas with a layers panel, the colour palette (from the colours at the top of the toolbar), a menu with input settings, keyboard shortcuts, settings import and export, a dark
|
|
94
|
+
- http://localhost:5190/: a full-page PaintCanvas with a layers panel, the colour palette (from the colours at the top of the toolbar), a menu with input settings, keyboard shortcuts, settings import and export, a theme picker (light, dark or system) and a "New drawing" button. The drawing and the settings, including the theme and language, are kept in the browser, so they survive reloads. Below 600px wide, the toolbar and the layers rail sit over the canvas.
|
|
91
95
|
- http://localhost:5190/elements.html: the base elements.
|
|
92
96
|
|
|
93
97
|
To try it with a pen or touch on another device (e.g. a tablet) on the same network, run `pnpm dev --host` and open the network address it prints. This makes the server reachable by anyone on that network.
|
|
@@ -115,18 +119,29 @@ Source layout:
|
|
|
115
119
|
|
|
116
120
|
Tests sit in a `tests` folder beside the code. Unit tests (`*.test.ts`) run in jsdom; pixel tests (`*.browser.test.ts`) run in headless Chromium.
|
|
117
121
|
|
|
118
|
-
| Command | Does
|
|
119
|
-
| ----------------------- |
|
|
120
|
-
| `pnpm typecheck` | Checks types in the source, tests, scripts and demo
|
|
121
|
-
| `pnpm lint` | Checks the code with ESLint
|
|
122
|
-
| `pnpm format` | Formats files with Prettier (`pnpm format:check` only checks)
|
|
123
|
-
| `pnpm test` | Runs the unit tests
|
|
124
|
-
| `pnpm test:browser` | Runs the pixel tests in a real browser
|
|
125
|
-
| `pnpm test:all` | Runs both
|
|
126
|
-
| `pnpm coverage` | Runs both, with a coverage report in `coverage/`
|
|
127
|
-
| `pnpm build` | Builds the package into `dist/`: ES modules matching `src/`, with types
|
|
128
|
-
| `pnpm build:validators` | Regenerates the validators after changing the preset, message or document schemas
|
|
129
|
-
| `pnpm
|
|
130
|
-
| `pnpm
|
|
122
|
+
| Command | Does |
|
|
123
|
+
| ----------------------- | ----------------------------------------------------------------------------------- |
|
|
124
|
+
| `pnpm typecheck` | Checks types in the source, tests, scripts and demo |
|
|
125
|
+
| `pnpm lint` | Checks the code with ESLint |
|
|
126
|
+
| `pnpm format` | Formats files with Prettier (`pnpm format:check` only checks) |
|
|
127
|
+
| `pnpm test` | Runs the unit tests |
|
|
128
|
+
| `pnpm test:browser` | Runs the pixel tests in a real browser |
|
|
129
|
+
| `pnpm test:all` | Runs both |
|
|
130
|
+
| `pnpm coverage` | Runs both, with a coverage report in `coverage/` |
|
|
131
|
+
| `pnpm build` | Builds the package into `dist/`: ES modules matching `src/`, with types |
|
|
132
|
+
| `pnpm build:validators` | Regenerates the validators after changing the preset, message or document schemas |
|
|
133
|
+
| `pnpm icons:build` | Optimises the icons' SVGs and makes their PNGs (`pnpm build` and `pnpm dev` run it) |
|
|
134
|
+
| `pnpm dev` | Starts the demo, reloading it when files change (`pnpm demo` does the same) |
|
|
135
|
+
| `pnpm docs` | Starts the documentation site (`pnpm docs:build` builds it into `docs/build/`) |
|
|
136
|
+
| `pnpm demo:build` | Builds the demo pages as a static site into `.pages/app/` |
|
|
137
|
+
| `pnpm pages:build` | Builds the GitHub Pages site (docs and demo) into `.pages/` |
|
|
138
|
+
| `pnpm pages:preview` | Serves the built site at http://localhost:5191/paintcanvas/ |
|
|
139
|
+
| `pnpm pages:deploy` | Builds the site from `main` and publishes it to the `gh-pages` branch |
|
|
140
|
+
|
|
141
|
+
Icons live in `src/ui/icons/images`, each as `icon-<name>.svg` (the 64 × 64 source artwork) and `icon-<name>@2x.png` (128 × 128, which the UI uses). Edit or add the SVG, then run `pnpm icons:build`: it optimises the SVGs with svgo and makes the PNGs from them. A test fails if a PNG doesn't match its SVG. Register a new icon in `src/ui/icons/index.ts`.
|
|
131
142
|
|
|
132
143
|
Each folder in `src/lib` and `src/ui` is published as its own import path (e.g. `src/lib/classes/PaintCanvas` as `@lvl99dotcom/paintcanvas/lib/classes/PaintCanvas`). `sideEffects` in `package.json` lists the UI modules that register elements when loaded (`src/ui/index.ts` and each `src/ui/elements/*/index.ts`), so bundlers keep them; add any other module that registers elements there.
|
|
144
|
+
|
|
145
|
+
### GitHub Pages
|
|
146
|
+
|
|
147
|
+
The docs and demo are published on GitHub Pages from the `gh-pages` branch, which holds only the built site. `pnpm pages:deploy` updates it: on a clean `main` that matches `origin/main`, it builds the site and commits and pushes it to `gh-pages`. Run it after each release, or whenever `main` has changes to the docs or demo. GitHub Pages is set to serve the root of the `gh-pages` branch (Settings → Pages).
|
|
@@ -23,6 +23,13 @@ export declare const PARAM_LIMITS: Record<VaryingParam, {
|
|
|
23
23
|
min: number;
|
|
24
24
|
max: number;
|
|
25
25
|
}>;
|
|
26
|
+
/**
|
|
27
|
+
* The config key of a setting's variation.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* configKey("width"); // "widthConfig"
|
|
31
|
+
*/
|
|
32
|
+
export declare const configKey: (param: VaryingParam) => `${VaryingParam}Config`;
|
|
26
33
|
/**
|
|
27
34
|
* Read a setting as the configuration window shows it. When it varies, its range comes from the first source turned
|
|
28
35
|
* on: scale settings (width, height, opacity) go from `value × min factor` to the value; rotation goes from the angle
|
|
@@ -50,3 +57,13 @@ export declare const writeRange: (config: BrushConfig, param: VaryingParam, rang
|
|
|
50
57
|
* when it is the first, a starting range (half to full size, the angle itself, or no scatter).
|
|
51
58
|
*/
|
|
52
59
|
export declare const toggleSource: (config: BrushConfig, param: VaryingParam, source: VariationSource, on: boolean) => Partial<BrushConfig>;
|
|
60
|
+
/**
|
|
61
|
+
* Make a setting vary with one source only (or none, with `undefined`), as config changes. Switching from another
|
|
62
|
+
* source keeps the setting's range; the first source takes a starting range (see `toggleSource`). The setting's profile
|
|
63
|
+
* graph is kept.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* selectSource({ id: "a", widthConfig: { usePressure: true, minPressure: 0.5 } }, "width", "tilt");
|
|
67
|
+
* // { widthConfig: { usePressure: false, useTilt: true, minTilt: 0.5, maxTilt: 1, useRandomise: false, … } }
|
|
68
|
+
*/
|
|
69
|
+
export declare const selectSource: (config: BrushConfig, param: VaryingParam, source: VariationSource | undefined) => Partial<BrushConfig>;
|
|
@@ -65,6 +65,12 @@ var SOURCE_DEFAULTS = {
|
|
|
65
65
|
tilt: [0, 1],
|
|
66
66
|
randomise: [1, 1]
|
|
67
67
|
};
|
|
68
|
+
/**
|
|
69
|
+
* The config key of a setting's variation.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* configKey("width"); // "widthConfig"
|
|
73
|
+
*/
|
|
68
74
|
var configKey = (param) => `${param}Config`;
|
|
69
75
|
var clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
70
76
|
/** A setting's value as stored (alpha as 0–1) */
|
|
@@ -203,4 +209,28 @@ var toggleSource = (config, param, source, on) => {
|
|
|
203
209
|
});
|
|
204
210
|
return { [key]: variation };
|
|
205
211
|
};
|
|
206
|
-
|
|
212
|
+
/**
|
|
213
|
+
* Make a setting vary with one source only (or none, with `undefined`), as config changes. Switching from another
|
|
214
|
+
* source keeps the setting's range; the first source takes a starting range (see `toggleSource`). The setting's profile
|
|
215
|
+
* graph is kept.
|
|
216
|
+
*
|
|
217
|
+
* @example
|
|
218
|
+
* selectSource({ id: "a", widthConfig: { usePressure: true, minPressure: 0.5 } }, "width", "tilt");
|
|
219
|
+
* // { widthConfig: { usePressure: false, useTilt: true, minTilt: 0.5, maxTilt: 1, useRandomise: false, … } }
|
|
220
|
+
*/
|
|
221
|
+
var selectSource = (config, param, source) => {
|
|
222
|
+
const key = configKey(param);
|
|
223
|
+
const previous = VARIATION_SOURCES.find((each) => readRange(config, param).sources[each]);
|
|
224
|
+
if (source && !previous) return toggleSource(config, param, source, true);
|
|
225
|
+
const variation = { ...config[key] };
|
|
226
|
+
for (const each of VARIATION_SOURCES) variation[SOURCE_KEYS[each][0]] = each === source;
|
|
227
|
+
if (source && previous && source !== previous) {
|
|
228
|
+
const [, fromMin, fromMax] = SOURCE_KEYS[previous];
|
|
229
|
+
const [, toMin, toMax] = SOURCE_KEYS[source];
|
|
230
|
+
const [minDefault, maxDefault] = SOURCE_DEFAULTS[previous];
|
|
231
|
+
variation[toMin] = variation[fromMin] ?? minDefault;
|
|
232
|
+
variation[toMax] = variation[fromMax] ?? maxDefault;
|
|
233
|
+
}
|
|
234
|
+
return { [key]: variation };
|
|
235
|
+
};
|
|
236
|
+
export { PARAM_LIMITS, VARIATION_SOURCES, configKey, readRange, selectSource, toggleSource, writeRange };
|
|
@@ -49,7 +49,8 @@ export declare const tiltAmount: (tiltX?: number, tiltY?: number) => number;
|
|
|
49
49
|
export declare const tiltDirection: (tiltX?: number, tiltY?: number) => number;
|
|
50
50
|
/**
|
|
51
51
|
* Combine a parameter's randomise, pressure and tilt factors into one factor (they multiply). Returns 1 when nothing is enabled.
|
|
52
|
-
* Pressure and tilt only apply to pens; for other devices they count as full (`maxPressure`/`maxTilt`).
|
|
52
|
+
* Pressure and tilt only apply to pens; for other devices they count as full (`maxPressure`/`maxTilt`). Each input goes
|
|
53
|
+
* through the parameter's profile graph (see `applyProfile`) before it sets the factor between its min and max.
|
|
53
54
|
*
|
|
54
55
|
* @example
|
|
55
56
|
* variationFactor({ useRandomise: true, minRandomise: 0.5, maxRandomise: 1 }, { isPen: false }, () => 0); // 0.5
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { applyProfile } from "../../profiles/index.js";
|
|
1
2
|
// src/lib/brushes/params/index.ts
|
|
2
3
|
/**
|
|
3
4
|
* Linear interpolation between two values.
|
|
@@ -27,22 +28,24 @@ var tiltAmount = (tiltX = 0, tiltY = 0) => clamp01(Math.hypot(tiltX, tiltY) / 90
|
|
|
27
28
|
var tiltDirection = (tiltX = 0, tiltY = 0) => Math.atan2(tiltY, tiltX) * 180 / Math.PI;
|
|
28
29
|
/**
|
|
29
30
|
* Combine a parameter's randomise, pressure and tilt factors into one factor (they multiply). Returns 1 when nothing is enabled.
|
|
30
|
-
* Pressure and tilt only apply to pens; for other devices they count as full (`maxPressure`/`maxTilt`).
|
|
31
|
+
* Pressure and tilt only apply to pens; for other devices they count as full (`maxPressure`/`maxTilt`). Each input goes
|
|
32
|
+
* through the parameter's profile graph (see `applyProfile`) before it sets the factor between its min and max.
|
|
31
33
|
*
|
|
32
34
|
* @example
|
|
33
35
|
* variationFactor({ useRandomise: true, minRandomise: 0.5, maxRandomise: 1 }, { isPen: false }, () => 0); // 0.5
|
|
34
36
|
*/
|
|
35
37
|
var variationFactor = (config, input, random = Math.random) => {
|
|
36
38
|
if (!config) return 1;
|
|
39
|
+
const { profile } = config;
|
|
37
40
|
let factor = 1;
|
|
38
|
-
if (config.useRandomise) factor *= lerp(config.minRandomise ?? 1, config.maxRandomise ?? 1, random());
|
|
41
|
+
if (config.useRandomise) factor *= lerp(config.minRandomise ?? 1, config.maxRandomise ?? 1, applyProfile(profile, random()));
|
|
39
42
|
if (config.usePressure) {
|
|
40
43
|
const pressure = input.isPen ? clamp01(input.pressure ?? 1) : 1;
|
|
41
|
-
factor *= lerp(config.minPressure ?? 0, config.maxPressure ?? 1, pressure);
|
|
44
|
+
factor *= lerp(config.minPressure ?? 0, config.maxPressure ?? 1, applyProfile(profile, pressure));
|
|
42
45
|
}
|
|
43
46
|
if (config.useTilt) {
|
|
44
47
|
const tilt = input.isPen ? tiltAmount(input.tiltX, input.tiltY) : 1;
|
|
45
|
-
factor *= lerp(config.minTilt ?? 0, config.maxTilt ?? 1, tilt);
|
|
48
|
+
factor *= lerp(config.minTilt ?? 0, config.maxTilt ?? 1, applyProfile(profile, tilt));
|
|
46
49
|
}
|
|
47
50
|
return factor;
|
|
48
51
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type BrushConfig, type BrushesConfig } from "../../types";
|
|
2
2
|
/** Hard round brushes */
|
|
3
3
|
export declare const round: BrushesConfig;
|
|
4
4
|
/** Soft-edged round brushes, for shading. Stamps are about a quarter of the size apart: closer, the soft edges add up and look hard. */
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import { BUILT_IN_AUTHOR } from "../../authors/index.js";
|
|
2
|
+
import { SmoothingType } from "../../types/index.js";
|
|
2
3
|
import { sizedPresets } from "../sized/index.js";
|
|
3
4
|
import round_default$1 from "./round.js";
|
|
4
5
|
import soft_round_default from "./soft-round.js";
|
|
5
6
|
// src/lib/brushes/presets/round/index.ts
|
|
6
7
|
/** Settings shared by the round presets */
|
|
7
8
|
var DEFAULT_CONFIG = {
|
|
8
|
-
version: "1.
|
|
9
|
+
version: "1.1.0",
|
|
9
10
|
author: BUILT_IN_AUTHOR,
|
|
10
|
-
constrainProportions: true
|
|
11
|
+
constrainProportions: true,
|
|
12
|
+
smoothingType: SmoothingType.BezierCubic,
|
|
13
|
+
sampleRate: 5
|
|
11
14
|
};
|
|
12
15
|
/** Hard round brushes */
|
|
13
16
|
var round = sizedPresets("round", "Round", [
|
|
@@ -15,13 +18,16 @@ var round = sizedPresets("round", "Round", [
|
|
|
15
18
|
5,
|
|
16
19
|
10,
|
|
17
20
|
20,
|
|
18
|
-
|
|
21
|
+
30,
|
|
22
|
+
40,
|
|
23
|
+
50,
|
|
24
|
+
100,
|
|
25
|
+
150
|
|
19
26
|
], {
|
|
20
27
|
...DEFAULT_CONFIG,
|
|
21
28
|
image: round_default$1,
|
|
22
|
-
sampleRate: 20,
|
|
23
29
|
spacing: 1
|
|
24
|
-
});
|
|
30
|
+
}, (size) => size === 1 ? { snapToPixelGrid: true } : {});
|
|
25
31
|
/** Soft-edged round brushes, for shading. Stamps are about a quarter of the size apart: closer, the soft edges add up and look hard. */
|
|
26
32
|
var softRound = sizedPresets("softRound", "Soft round", [
|
|
27
33
|
5,
|
|
@@ -34,8 +40,7 @@ var softRound = sizedPresets("softRound", "Soft round", [
|
|
|
34
40
|
150
|
|
35
41
|
], {
|
|
36
42
|
...DEFAULT_CONFIG,
|
|
37
|
-
image: soft_round_default
|
|
38
|
-
sampleRate: 5
|
|
43
|
+
image: soft_round_default
|
|
39
44
|
}, (size) => ({ spacing: Math.max(1, Math.round(size / 4)) }));
|
|
40
45
|
var round_default = {
|
|
41
46
|
...round,
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BUILT_IN_AUTHOR } from "../../authors/index.js";
|
|
2
|
+
import { SmoothingType } from "../../types/index.js";
|
|
2
3
|
import { sizedPresets } from "../sized/index.js";
|
|
3
4
|
import square_default$1 from "./square.js";
|
|
4
5
|
/** Hard square brushes, snapped to the pixel grid */
|
|
@@ -9,13 +10,16 @@ var square = sizedPresets("square", "Square", [
|
|
|
9
10
|
20,
|
|
10
11
|
30,
|
|
11
12
|
40,
|
|
12
|
-
50
|
|
13
|
+
50,
|
|
14
|
+
100,
|
|
15
|
+
150
|
|
13
16
|
], {
|
|
14
|
-
version: "1.
|
|
17
|
+
version: "1.1.0",
|
|
15
18
|
author: BUILT_IN_AUTHOR,
|
|
16
19
|
constrainProportions: true,
|
|
17
20
|
image: square_default$1,
|
|
18
|
-
|
|
21
|
+
smoothingType: SmoothingType.BezierCubic,
|
|
22
|
+
sampleRate: 5,
|
|
19
23
|
snapToPixelGrid: true
|
|
20
24
|
}, (size) => ({ spacing: Math.max(1, Math.round(size / 10)) }));
|
|
21
25
|
var square_default = { ...square };
|
|
@@ -34,7 +34,11 @@ export interface ShapeImageDependencies {
|
|
|
34
34
|
*/
|
|
35
35
|
export declare const readShapeImage: (blob: Blob, { decode, createCanvas, }?: ShapeImageDependencies) => Promise<ShapeImage>;
|
|
36
36
|
/**
|
|
37
|
-
* Read a brush shape from a PNG image on the clipboard.
|
|
38
|
-
* used.
|
|
37
|
+
* Read a brush shape from a PNG image on the clipboard. Without one (or when the browser won't let it be read), the
|
|
38
|
+
* `fallback` PNG is used if there is one (e.g. image data copied on the canvas, `canvas.copiedPng()`). Throws a
|
|
39
|
+
* `ShapeImageError` if there is none or it can't be used.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* const shape = await readClipboardShape(navigator.clipboard, {}, () => canvas.copiedPng());
|
|
39
43
|
*/
|
|
40
|
-
export declare const readClipboardShape: (clipboard?: Pick<Clipboard, "read">, deps?: ShapeImageDependencies) => Promise<ShapeImage>;
|
|
44
|
+
export declare const readClipboardShape: (clipboard?: Pick<Clipboard, "read">, deps?: ShapeImageDependencies, fallback?: () => Promise<Blob | undefined>) => Promise<ShapeImage>;
|
|
@@ -44,18 +44,24 @@ var readShapeImage = async (blob, { decode = (file) => createImageBitmap(file),
|
|
|
44
44
|
};
|
|
45
45
|
};
|
|
46
46
|
/**
|
|
47
|
-
* Read a brush shape from a PNG image on the clipboard.
|
|
48
|
-
* used.
|
|
47
|
+
* Read a brush shape from a PNG image on the clipboard. Without one (or when the browser won't let it be read), the
|
|
48
|
+
* `fallback` PNG is used if there is one (e.g. image data copied on the canvas, `canvas.copiedPng()`). Throws a
|
|
49
|
+
* `ShapeImageError` if there is none or it can't be used.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* const shape = await readClipboardShape(navigator.clipboard, {}, () => canvas.copiedPng());
|
|
49
53
|
*/
|
|
50
|
-
var readClipboardShape = async (clipboard = navigator.clipboard, deps = {}) => {
|
|
51
|
-
|
|
54
|
+
var readClipboardShape = async (clipboard = navigator.clipboard, deps = {}, fallback = async () => void 0) => {
|
|
55
|
+
const png = await clipboardPng(clipboard) ?? await fallback();
|
|
56
|
+
if (!png) throw new ShapeImageError(SHAPE_ERRORS.noImage);
|
|
57
|
+
return readShapeImage(png, deps);
|
|
58
|
+
};
|
|
59
|
+
/** The PNG image on the clipboard, if there is one and it can be read */
|
|
60
|
+
var clipboardPng = async (clipboard) => {
|
|
52
61
|
try {
|
|
53
|
-
|
|
62
|
+
return await (await clipboard.read()).find((entry) => entry.types.includes("image/png"))?.getType("image/png");
|
|
54
63
|
} catch {
|
|
55
|
-
|
|
64
|
+
return;
|
|
56
65
|
}
|
|
57
|
-
const item = items.find((entry) => entry.types.includes("image/png"));
|
|
58
|
-
if (!item) throw new ShapeImageError(SHAPE_ERRORS.noImage);
|
|
59
|
-
return readShapeImage(await item.getType("image/png"), deps);
|
|
60
66
|
};
|
|
61
67
|
export { MAX_SHAPE_FILE_BYTES, MAX_SHAPE_SIZE, SHAPE_ERRORS, ShapeImageError, readClipboardShape, readShapeImage };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** A brush shape that comes with PaintCanvas (all square) */
|
|
2
|
+
export interface BuiltInShape {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
/** PNG data URL */
|
|
6
|
+
image: string;
|
|
7
|
+
}
|
|
8
|
+
/** The brush shapes that come with PaintCanvas, the ones its presets use */
|
|
9
|
+
export declare const BUILT_IN_SHAPES: readonly BuiltInShape[];
|
|
10
|
+
/**
|
|
11
|
+
* Which built-in shape a brush uses: its id, "round" for a brush without an image (drawn round), or undefined for one
|
|
12
|
+
* of the user's own (uploaded or pasted).
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* builtInShapeId(undefined); // "round"
|
|
16
|
+
* builtInShapeId("data:image/png;base64,…"); // undefined, unless it's a built-in shape's image
|
|
17
|
+
*/
|
|
18
|
+
export declare const builtInShapeId: (image: unknown) => string | undefined;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import round_default from "../presets/round/round.js";
|
|
2
|
+
import soft_round_default from "../presets/round/soft-round.js";
|
|
3
|
+
import square_default from "../presets/square/square.js";
|
|
4
|
+
import pencil_2b_default from "../presets/drawing/pencil-2b.js";
|
|
5
|
+
import pencil_hb_default from "../presets/drawing/pencil-hb.js";
|
|
6
|
+
import felt_pen_default from "../presets/drawing/felt-pen.js";
|
|
7
|
+
import brush_pen_default from "../presets/drawing/brush-pen.js";
|
|
8
|
+
import textured_paint_default from "../presets/painting/textured-paint.js";
|
|
9
|
+
import watery_paint_default from "../presets/painting/watery-paint.js";
|
|
10
|
+
import splatter_default from "../presets/painting/splatter.js";
|
|
11
|
+
import hair_default from "../presets/painting/hair.js";
|
|
12
|
+
import grass_default from "../presets/painting/grass.js";
|
|
13
|
+
// src/lib/brushes/shapes/index.ts
|
|
14
|
+
/** The brush shapes that come with PaintCanvas, the ones its presets use */
|
|
15
|
+
var BUILT_IN_SHAPES = [
|
|
16
|
+
{
|
|
17
|
+
id: "round",
|
|
18
|
+
name: "Round",
|
|
19
|
+
image: round_default
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: "softRound",
|
|
23
|
+
name: "Soft round",
|
|
24
|
+
image: soft_round_default
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "square",
|
|
28
|
+
name: "Square",
|
|
29
|
+
image: square_default
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
id: "pencil2b",
|
|
33
|
+
name: "Pencil 2B",
|
|
34
|
+
image: pencil_2b_default
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
id: "pencilHb",
|
|
38
|
+
name: "Pencil HB",
|
|
39
|
+
image: pencil_hb_default
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: "feltPen",
|
|
43
|
+
name: "Felt pen",
|
|
44
|
+
image: felt_pen_default
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
id: "brushPen",
|
|
48
|
+
name: "Brush pen",
|
|
49
|
+
image: brush_pen_default
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
id: "texturedPaint",
|
|
53
|
+
name: "Textured paint",
|
|
54
|
+
image: textured_paint_default
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
id: "wateryPaint",
|
|
58
|
+
name: "Watery paint",
|
|
59
|
+
image: watery_paint_default
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
id: "splatter",
|
|
63
|
+
name: "Splatter",
|
|
64
|
+
image: splatter_default
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
id: "hair",
|
|
68
|
+
name: "Hair",
|
|
69
|
+
image: hair_default
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
id: "grass",
|
|
73
|
+
name: "Grass",
|
|
74
|
+
image: grass_default
|
|
75
|
+
}
|
|
76
|
+
];
|
|
77
|
+
/**
|
|
78
|
+
* Which built-in shape a brush uses: its id, "round" for a brush without an image (drawn round), or undefined for one
|
|
79
|
+
* of the user's own (uploaded or pasted).
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* builtInShapeId(undefined); // "round"
|
|
83
|
+
* builtInShapeId("data:image/png;base64,…"); // undefined, unless it's a built-in shape's image
|
|
84
|
+
*/
|
|
85
|
+
var builtInShapeId = (image) => image === void 0 ? "round" : BUILT_IN_SHAPES.find((shape) => shape.image === image)?.id;
|
|
86
|
+
export { BUILT_IN_SHAPES, builtInShapeId };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ProfilePoint } from "../../profiles";
|
|
1
2
|
export interface AssetAuthorDetails {
|
|
2
3
|
/** Author's name */
|
|
3
4
|
name: string;
|
|
@@ -94,6 +95,13 @@ export interface BrushParamConfig {
|
|
|
94
95
|
* @default 1
|
|
95
96
|
*/
|
|
96
97
|
maxTilt?: number;
|
|
98
|
+
/**
|
|
99
|
+
* The profile graph the input (pressure, tilt or the random number) goes through before setting the factor between
|
|
100
|
+
* its min and max, e.g. so a light touch goes most of the way (see `applyProfile`). A straight line by default.
|
|
101
|
+
*/
|
|
102
|
+
profile?: ProfilePoint[];
|
|
103
|
+
/** The ID of the profile graph `profile` came from (a preset's or one of the user's), to show its name */
|
|
104
|
+
profileId?: string;
|
|
97
105
|
}
|
|
98
106
|
export interface BrushConfig {
|
|
99
107
|
/** Unique ID */
|
|
@@ -26,6 +26,19 @@ export declare const clamp: (value: number, min: number, max: number) => number;
|
|
|
26
26
|
* stepOpacity(1, 1); // 10
|
|
27
27
|
*/
|
|
28
28
|
export declare const stepOpacity: (percent: number, direction: 1 | -1) => number;
|
|
29
|
+
/**
|
|
30
|
+
* A brush size with the given width and proportions (width ÷ height), in whole pixels. The width is kept within the
|
|
31
|
+
* range where the smaller side is at least `MIN_BRUSH_SIZE` and the larger side at most `MAX_BRUSH_SIZE`, so the
|
|
32
|
+
* proportions are kept at either end.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* proportionalSize(0, 3); // { width: 3, height: 1 }
|
|
36
|
+
* proportionalSize(1000, 0.5); // { width: 128, height: 256 }
|
|
37
|
+
*/
|
|
38
|
+
export declare const proportionalSize: (width: number, aspect: number) => {
|
|
39
|
+
width: number;
|
|
40
|
+
height: number;
|
|
41
|
+
};
|
|
29
42
|
/**
|
|
30
43
|
* The settings of a brush or eraser: the selected preset, an editable copy of its settings (changed from the tool
|
|
31
44
|
* settings bar or the configuration window, without changing the preset) and the size. The brush and eraser each have
|
|
@@ -49,6 +62,11 @@ export default class BrushSettings extends EventTarget {
|
|
|
49
62
|
presetId: string;
|
|
50
63
|
/** The current settings: a copy of the selected preset, with any changes */
|
|
51
64
|
private current;
|
|
65
|
+
/**
|
|
66
|
+
* The brush's proportions (width ÷ height), kept apart from the rounded size so resizing to either end and back
|
|
67
|
+
* doesn't change them. Set by the preset and by setting the size while proportions aren't constrained.
|
|
68
|
+
*/
|
|
69
|
+
private aspect;
|
|
52
70
|
constructor(presets: BrushesConfig);
|
|
53
71
|
/** The current settings: the selected preset's, with any changes made since selecting it */
|
|
54
72
|
get preset(): BrushConfig;
|
|
@@ -79,13 +97,18 @@ export default class BrushSettings extends EventTarget {
|
|
|
79
97
|
*/
|
|
80
98
|
setHeight(height: number): void;
|
|
81
99
|
/**
|
|
82
|
-
* Set the size, clamped to 1–256 pixels. If the settings constrain proportions, height follows width
|
|
100
|
+
* Set the size, clamped to 1–256 pixels. If the settings constrain proportions, height follows width, and the width
|
|
101
|
+
* stops where either side would leave that range.
|
|
83
102
|
*/
|
|
84
103
|
setSize(width: number, height?: number): void;
|
|
85
104
|
/**
|
|
86
|
-
* Change the size by a number of pixels (positive = bigger)
|
|
105
|
+
* Change the size by a number of pixels (positive = bigger), keeping the proportions: the larger side changes by
|
|
106
|
+
* `delta`. Stops when the smaller side reaches 1 pixel or the larger 256. Used when dragging up/right (bigger) or
|
|
107
|
+
* down/left (smaller).
|
|
87
108
|
*/
|
|
88
109
|
resize(delta: number): void;
|
|
110
|
+
/** The larger side (what `resize` changes), which `grow` and `shrink` step from */
|
|
111
|
+
get largerSide(): number;
|
|
89
112
|
/**
|
|
90
113
|
* Increase the size by one step (at least 1 pixel).
|
|
91
114
|
*/
|
|
@@ -108,6 +131,10 @@ export default class BrushSettings extends EventTarget {
|
|
|
108
131
|
* Use a preset's (or configuration's) default size.
|
|
109
132
|
*/
|
|
110
133
|
private applyPresetSize;
|
|
134
|
+
/**
|
|
135
|
+
* Set the width, with the height following the stored proportions (see `proportionalSize`).
|
|
136
|
+
*/
|
|
137
|
+
private setProportionalSize;
|
|
111
138
|
/**
|
|
112
139
|
* Dispatch a `change` event.
|
|
113
140
|
*/
|
|
@@ -30,6 +30,22 @@ var stepOpacity = (percent, direction) => {
|
|
|
30
30
|
return clamp((direction > 0 ? Math.floor(steps + 1e-9) + 1 : Math.ceil(steps - 1e-9) - 1) * 10, 1, 100);
|
|
31
31
|
};
|
|
32
32
|
/**
|
|
33
|
+
* A brush size with the given width and proportions (width ÷ height), in whole pixels. The width is kept within the
|
|
34
|
+
* range where the smaller side is at least `MIN_BRUSH_SIZE` and the larger side at most `MAX_BRUSH_SIZE`, so the
|
|
35
|
+
* proportions are kept at either end.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* proportionalSize(0, 3); // { width: 3, height: 1 }
|
|
39
|
+
* proportionalSize(1000, 0.5); // { width: 128, height: 256 }
|
|
40
|
+
*/
|
|
41
|
+
var proportionalSize = (width, aspect) => {
|
|
42
|
+
const fitted = Math.round(clamp(width, Math.max(1, 1 * aspect), Math.min(256, 256 * aspect)));
|
|
43
|
+
return {
|
|
44
|
+
width: fitted,
|
|
45
|
+
height: Math.round(clamp(fitted / aspect, 1, 256))
|
|
46
|
+
};
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
33
49
|
* The settings of a brush or eraser: the selected preset, an editable copy of its settings (changed from the tool
|
|
34
50
|
* settings bar or the configuration window, without changing the preset) and the size. The brush and eraser each have
|
|
35
51
|
* their own instance.
|
|
@@ -52,6 +68,11 @@ var BrushSettings = class extends EventTarget {
|
|
|
52
68
|
presetId;
|
|
53
69
|
/** The current settings: a copy of the selected preset, with any changes */
|
|
54
70
|
current;
|
|
71
|
+
/**
|
|
72
|
+
* The brush's proportions (width ÷ height), kept apart from the rounded size so resizing to either end and back
|
|
73
|
+
* doesn't change them. Set by the preset and by setting the size while proportions aren't constrained.
|
|
74
|
+
*/
|
|
75
|
+
aspect = 1;
|
|
55
76
|
constructor(presets) {
|
|
56
77
|
super();
|
|
57
78
|
const first = Object.values(presets)[0];
|
|
@@ -118,39 +139,47 @@ var BrushSettings = class extends EventTarget {
|
|
|
118
139
|
* Set the height, clamped to 1–256 pixels. If the settings constrain proportions, width follows height.
|
|
119
140
|
*/
|
|
120
141
|
setHeight(height) {
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
return;
|
|
124
|
-
}
|
|
125
|
-
const ratio = this.width / this.height;
|
|
126
|
-
this.setSize(clamp(height, 1, 256) * ratio);
|
|
142
|
+
if (this.current.constrainProportions) this.setProportionalSize(height * this.aspect);
|
|
143
|
+
else this.setSize(this.width, height);
|
|
127
144
|
}
|
|
128
145
|
/**
|
|
129
|
-
* Set the size, clamped to 1–256 pixels. If the settings constrain proportions, height follows width
|
|
146
|
+
* Set the size, clamped to 1–256 pixels. If the settings constrain proportions, height follows width, and the width
|
|
147
|
+
* stops where either side would leave that range.
|
|
130
148
|
*/
|
|
131
149
|
setSize(width, height = width) {
|
|
132
|
-
|
|
150
|
+
if (this.current.constrainProportions) {
|
|
151
|
+
this.setProportionalSize(width);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
133
154
|
this.width = Math.round(clamp(width, 1, 256));
|
|
134
|
-
this.height = Math.round(clamp(
|
|
155
|
+
this.height = Math.round(clamp(height, 1, 256));
|
|
156
|
+
this.aspect = this.width / this.height;
|
|
135
157
|
this.notify();
|
|
136
158
|
}
|
|
137
159
|
/**
|
|
138
|
-
* Change the size by a number of pixels (positive = bigger)
|
|
160
|
+
* Change the size by a number of pixels (positive = bigger), keeping the proportions: the larger side changes by
|
|
161
|
+
* `delta`. Stops when the smaller side reaches 1 pixel or the larger 256. Used when dragging up/right (bigger) or
|
|
162
|
+
* down/left (smaller).
|
|
139
163
|
*/
|
|
140
164
|
resize(delta) {
|
|
141
|
-
|
|
165
|
+
const larger = Math.max(this.width, this.height) + delta;
|
|
166
|
+
this.setProportionalSize(this.aspect >= 1 ? larger : larger * this.aspect);
|
|
167
|
+
}
|
|
168
|
+
/** The larger side (what `resize` changes), which `grow` and `shrink` step from */
|
|
169
|
+
get largerSide() {
|
|
170
|
+
return Math.max(this.width, this.height);
|
|
142
171
|
}
|
|
143
172
|
/**
|
|
144
173
|
* Increase the size by one step (at least 1 pixel).
|
|
145
174
|
*/
|
|
146
175
|
grow() {
|
|
147
|
-
this.resize(Math.max(1, Math.round(this.
|
|
176
|
+
this.resize(Math.max(1, Math.round(this.largerSide * BRUSH_SIZE_STEP)));
|
|
148
177
|
}
|
|
149
178
|
/**
|
|
150
179
|
* Decrease the size by one step (at least 1 pixel).
|
|
151
180
|
*/
|
|
152
181
|
shrink() {
|
|
153
|
-
this.resize(-Math.max(1, Math.round(this.
|
|
182
|
+
this.resize(-Math.max(1, Math.round(this.largerSide * BRUSH_SIZE_STEP)));
|
|
154
183
|
}
|
|
155
184
|
/**
|
|
156
185
|
* Increase the opacity by one step (see `stepOpacity`).
|
|
@@ -175,6 +204,14 @@ var BrushSettings = class extends EventTarget {
|
|
|
175
204
|
applyPresetSize(preset) {
|
|
176
205
|
this.width = preset.width ?? 1;
|
|
177
206
|
this.height = preset.height ?? this.width;
|
|
207
|
+
this.aspect = this.width / this.height;
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Set the width, with the height following the stored proportions (see `proportionalSize`).
|
|
211
|
+
*/
|
|
212
|
+
setProportionalSize(width) {
|
|
213
|
+
Object.assign(this, proportionalSize(width, this.aspect));
|
|
214
|
+
this.notify();
|
|
178
215
|
}
|
|
179
216
|
/**
|
|
180
217
|
* Dispatch a `change` event.
|
|
@@ -183,4 +220,4 @@ var BrushSettings = class extends EventTarget {
|
|
|
183
220
|
this.dispatchEvent(new Event("change"));
|
|
184
221
|
}
|
|
185
222
|
};
|
|
186
|
-
export { BRUSH_OPACITY_STEP, BRUSH_SIZE_STEP, MAX_BRUSH_SIZE, MIN_BRUSH_OPACITY, MIN_BRUSH_SIZE, clamp, BrushSettings as default, stepOpacity };
|
|
223
|
+
export { BRUSH_OPACITY_STEP, BRUSH_SIZE_STEP, MAX_BRUSH_SIZE, MIN_BRUSH_OPACITY, MIN_BRUSH_SIZE, clamp, BrushSettings as default, proportionalSize, stepOpacity };
|