@lvl99dotcom/paintcanvas 0.6.0 → 0.7.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.
Files changed (59) hide show
  1. package/README.md +2 -2
  2. package/dist/lib/classes/PaintCanvas/index.d.ts +13 -5
  3. package/dist/lib/classes/PaintCanvas/index.js +10 -6
  4. package/dist/lib/classes/UiSettings/index.d.ts +24 -2
  5. package/dist/lib/classes/UiSettings/index.js +32 -7
  6. package/dist/lib/settings/schema.d.ts +4 -1
  7. package/dist/lib/settings/schema.js +3 -2
  8. package/dist/lib/storage/index.d.ts +26 -0
  9. package/dist/lib/storage/index.js +38 -1
  10. package/dist/lib/validation/validators.generated.js +20 -2
  11. package/dist/lib/version/index.d.ts +25 -0
  12. package/dist/lib/version/index.js +35 -0
  13. package/dist/locales/en-GB.js +5 -0
  14. package/dist/locales/en-US.js +5 -0
  15. package/dist/locales/fr.js +5 -0
  16. package/dist/locales/ja.js +5 -0
  17. package/dist/locales/ko.js +5 -0
  18. package/dist/locales/zh-Hans.js +5 -0
  19. package/dist/locales/zh-Hant.js +5 -0
  20. package/dist/package.js +1 -1
  21. package/dist/ui/elements/PaintCanvas/index.d.ts +13 -3
  22. package/dist/ui/elements/PaintCanvas/index.js +22 -5
  23. package/dist/ui/elements/PcAppSettings/index.d.ts +10 -2
  24. package/dist/ui/elements/PcAppSettings/index.js +42 -7
  25. package/dist/ui/elements/PcBrushConfig/index.d.ts +1 -0
  26. package/dist/ui/elements/PcBrushConfig/index.js +8 -9
  27. package/dist/ui/elements/PcBrushTest/index.d.ts +1 -0
  28. package/dist/ui/elements/PcBrushTest/index.js +8 -7
  29. package/dist/ui/elements/PcBrushTest/style.js +1 -1
  30. package/dist/ui/elements/PcButtonMenu/index.d.ts +88 -0
  31. package/dist/ui/elements/PcButtonMenu/index.js +125 -0
  32. package/dist/ui/elements/PcCanvas/index.d.ts +9 -0
  33. package/dist/ui/elements/PcCanvas/index.js +33 -17
  34. package/dist/ui/elements/PcClipboardTools/index.d.ts +7 -4
  35. package/dist/ui/elements/PcClipboardTools/index.js +20 -26
  36. package/dist/ui/elements/PcInputSettings/index.d.ts +2 -1
  37. package/dist/ui/elements/PcInputSettings/index.js +9 -15
  38. package/dist/ui/elements/PcLayerItem/index.d.ts +21 -6
  39. package/dist/ui/elements/PcLayerItem/index.js +53 -26
  40. package/dist/ui/elements/PcLayerItem/style.js +1 -1
  41. package/dist/ui/elements/PcSliderRange/index.js +3 -2
  42. package/dist/ui/elements/PcToggleCheckbox/index.d.ts +13 -2
  43. package/dist/ui/elements/PcToggleCheckbox/index.js +27 -8
  44. package/dist/ui/elements/PcToolSettings/index.d.ts +1 -0
  45. package/dist/ui/elements/PcToolSettings/index.js +8 -8
  46. package/dist/ui/elements/PcVersion/index.d.ts +12 -0
  47. package/dist/ui/elements/PcVersion/index.js +43 -0
  48. package/dist/ui/index.d.ts +3 -1
  49. package/dist/ui/index.js +4 -2
  50. package/dist/ui/layout/index.d.ts +2 -2
  51. package/dist/ui/layout/index.js +2 -2
  52. package/locales/en-GB.json +5 -0
  53. package/locales/en-US.json +5 -0
  54. package/locales/fr.json +5 -0
  55. package/locales/ja.json +5 -0
  56. package/locales/ko.json +5 -0
  57. package/locales/zh-Hans.json +5 -0
  58. package/locales/zh-Hant.json +5 -0
  59. package/package.json +2 -2
@@ -23,7 +23,8 @@ import "../PcDialog";
23
23
  * - `left`: `<pc-toolbar>` (a pane) by default; toolbars in the sides are minimised (their tools one button that opens a
24
24
  * menu of them) while the layout is narrow
25
25
  * - `top`: a horizontal `<pc-pane>` with `<pc-tool-settings>` (for tools that have settings) and, at its end,
26
- * `<pc-clipboard-tools>` (cut, copy and paste; a ⋮ menu while the layout is narrow) by default
26
+ * `<pc-clipboard-tools>` (cut, copy and paste; a ⋮ menu while the layout is narrow or the window is narrower than
27
+ * 900px) by default
27
28
  * - `right`: a `<pc-pane>` with `<pc-panel-layers>` by default
28
29
  * - `center`: `<pc-canvas>` by default
29
30
  * - `overlay`: drawn over the canvas; `<pc-canvas-map>` by default
@@ -36,7 +37,7 @@ import "../PcDialog";
36
37
  * does by default), the toolbar has no layers menu button.
37
38
  *
38
39
  * Each side has a mode, chosen by the layout's width from `left-modes` and `right-modes` (default `"docked,
39
- * rail@600"`), which the layout gives the side's `<pc-pane>`s (see `PcPane`): `docked` beside the canvas; `rail`, a
40
+ * floating@600"`), which the layout gives the side's `<pc-pane>`s (see `PcPane`): `docked` beside the canvas; `rail`, a
40
41
  * column of buttons (one per panel) that open each panel over the canvas; `floating`, the pane over the canvas as a
41
42
  * box that collapses to a button; or `overlay`, a rail over the canvas without a background (the canvas fills the
42
43
  * width behind it). Give side content in a `<pc-pane>` to have it follow the side's mode.
@@ -50,7 +51,9 @@ import "../PcDialog";
50
51
  * that offers to draw here instead. Fires `pc-toast` when the drawing can't be loaded or saved.
51
52
  *
52
53
  * With a `settings-store` name, the user's app settings (input settings, keyboard shortcuts, colour palette, colours,
53
- * theme and language) are kept in the browser under that name (see `SettingsAutosave`). Fires `pc-toast` when they can't be.
54
+ * theme, language and rendering) are kept in the browser under that name (see `SettingsAutosave`). Fires `pc-toast` when
55
+ * they can't be. The rendering chosen there is also kept in `localStorage` (see `saveRenderingChoice`), so the canvas the
56
+ * layout makes draws where the user last chose (see `PaintCanvasOptions.rendering`).
54
57
  *
55
58
  * @example
56
59
  * html`<paint-canvas style="height: 600px"></paint-canvas>`
@@ -154,6 +157,8 @@ export declare class PaintCanvasElement extends PcElement {
154
157
  private watchedAutosave?;
155
158
  /** Keeps the app settings under the `settings-store` name, if any */
156
159
  private ownSettingsAutosave?;
160
+ /** Stops keeping the rendering chosen in the canvas's UI settings under the `settings-store` name, if kept */
161
+ private stopKeepingRendering?;
157
162
  /** Whether drawing was blocked when shortcuts were last watched */
158
163
  private blockedShortcuts;
159
164
  /** Whether each side's slot has a layers menu (the toolbar's isn't needed then) */
@@ -204,6 +209,11 @@ export declare class PaintCanvasElement extends PcElement {
204
209
  * saves any change waiting to be saved).
205
210
  */
206
211
  private useSettingsStore;
212
+ /**
213
+ * Keep the rendering chosen in the canvas's UI settings (e.g. when they're restored, imported or changed) under a
214
+ * name, for the next time the layout makes a canvas. Returns what stops it.
215
+ */
216
+ private keepRendering;
207
217
  /** Show errors from the canvas's current autosave */
208
218
  private watchAutosaveErrors;
209
219
  /** Handle keyboard shortcuts for the current canvas, if asked to (not while blocked) */
@@ -5,7 +5,7 @@ import ChangeController from "../../ChangeController/index.js";
5
5
  import PaintCanvas, { Panel } from "../../../lib/classes/PaintCanvas/index.js";
6
6
  import KeyboardShortcuts, { SHORTCUT_EVENT } from "../../../lib/classes/KeyboardShortcuts/index.js";
7
7
  import { isTouchDevice } from "../../../lib/classes/InputSettings/index.js";
8
- import IndexedDbLayerStore, { IndexedDbSettingsStore } from "../../../lib/storage/index.js";
8
+ import IndexedDbLayerStore, { IndexedDbSettingsStore, loadRenderingChoice, saveRenderingChoice } from "../../../lib/storage/index.js";
9
9
  import SettingsAutosave, { SETTINGS_AUTOSAVE_ERROR_EVENT } from "../../../lib/classes/SettingsAutosave/index.js";
10
10
  import { AUTOSAVE_ERROR_EVENT, AutosaveState } from "../../../lib/classes/LayerAutosave/index.js";
11
11
  import { isOwnAuthor } from "../../../lib/brushes/authors/index.js";
@@ -45,7 +45,8 @@ var TAKE_OVER_CHOICE = "take-over";
45
45
  * - `left`: `<pc-toolbar>` (a pane) by default; toolbars in the sides are minimised (their tools one button that opens a
46
46
  * menu of them) while the layout is narrow
47
47
  * - `top`: a horizontal `<pc-pane>` with `<pc-tool-settings>` (for tools that have settings) and, at its end,
48
- * `<pc-clipboard-tools>` (cut, copy and paste; a ⋮ menu while the layout is narrow) by default
48
+ * `<pc-clipboard-tools>` (cut, copy and paste; a ⋮ menu while the layout is narrow or the window is narrower than
49
+ * 900px) by default
49
50
  * - `right`: a `<pc-pane>` with `<pc-panel-layers>` by default
50
51
  * - `center`: `<pc-canvas>` by default
51
52
  * - `overlay`: drawn over the canvas; `<pc-canvas-map>` by default
@@ -58,7 +59,7 @@ var TAKE_OVER_CHOICE = "take-over";
58
59
  * does by default), the toolbar has no layers menu button.
59
60
  *
60
61
  * Each side has a mode, chosen by the layout's width from `left-modes` and `right-modes` (default `"docked,
61
- * rail@600"`), which the layout gives the side's `<pc-pane>`s (see `PcPane`): `docked` beside the canvas; `rail`, a
62
+ * floating@600"`), which the layout gives the side's `<pc-pane>`s (see `PcPane`): `docked` beside the canvas; `rail`, a
62
63
  * column of buttons (one per panel) that open each panel over the canvas; `floating`, the pane over the canvas as a
63
64
  * box that collapses to a button; or `overlay`, a rail over the canvas without a background (the canvas fills the
64
65
  * width behind it). Give side content in a `<pc-pane>` to have it follow the side's mode.
@@ -72,7 +73,9 @@ var TAKE_OVER_CHOICE = "take-over";
72
73
  * that offers to draw here instead. Fires `pc-toast` when the drawing can't be loaded or saved.
73
74
  *
74
75
  * With a `settings-store` name, the user's app settings (input settings, keyboard shortcuts, colour palette, colours,
75
- * theme and language) are kept in the browser under that name (see `SettingsAutosave`). Fires `pc-toast` when they can't be.
76
+ * theme, language and rendering) are kept in the browser under that name (see `SettingsAutosave`). Fires `pc-toast` when
77
+ * they can't be. The rendering chosen there is also kept in `localStorage` (see `saveRenderingChoice`), so the canvas the
78
+ * layout makes draws where the user last chose (see `PaintCanvasOptions.rendering`).
76
79
  *
77
80
  * @example
78
81
  * html`<paint-canvas style="height: 600px"></paint-canvas>`
@@ -127,6 +130,8 @@ var PaintCanvasElement = class extends PcElement {
127
130
  watchedAutosave;
128
131
  /** Keeps the app settings under the `settings-store` name, if any */
129
132
  ownSettingsAutosave;
133
+ /** Stops keeping the rendering chosen in the canvas's UI settings under the `settings-store` name, if kept */
134
+ stopKeepingRendering;
130
135
  /** Whether drawing was blocked when shortcuts were last watched */
131
136
  blockedShortcuts = false;
132
137
  /** Whether each side's slot has a layers menu (the toolbar's isn't needed then) */
@@ -194,7 +199,7 @@ var PaintCanvasElement = class extends PcElement {
194
199
  return sideModeFor(parseSideModes(side === "left" ? this.leftModes : this.rightModes), this.width);
195
200
  }
196
201
  connectedCallback() {
197
- this.app ??= new PaintCanvas();
202
+ this.app ??= new PaintCanvas({ rendering: this.settingsStore ? loadRenderingChoice(this.settingsStore) : void 0 });
198
203
  super.connectedCallback();
199
204
  this.resizeObserver?.observe(this);
200
205
  this.addEventListener("pointerdown", this.onPointerDown, true);
@@ -284,10 +289,22 @@ var PaintCanvasElement = class extends PcElement {
284
289
  */
285
290
  useSettingsStore() {
286
291
  this.ownSettingsAutosave?.stop();
292
+ this.stopKeepingRendering?.();
287
293
  const name = this.settingsStore || void 0;
294
+ this.stopKeepingRendering = name === void 0 ? void 0 : this.keepRendering(name);
288
295
  this.ownSettingsAutosave = name === void 0 ? void 0 : new SettingsAutosave(this.app, new IndexedDbSettingsStore(name));
289
296
  this.ownSettingsAutosave?.addEventListener(SETTINGS_AUTOSAVE_ERROR_EVENT, this.onSettingsAutosaveError);
290
297
  }
298
+ /**
299
+ * Keep the rendering chosen in the canvas's UI settings (e.g. when they're restored, imported or changed) under a
300
+ * name, for the next time the layout makes a canvas. Returns what stops it.
301
+ */
302
+ keepRendering(name) {
303
+ const ui = this.app.ui;
304
+ const keep = () => saveRenderingChoice(name, ui.data.rendering);
305
+ ui.addEventListener("change", keep);
306
+ return () => ui.removeEventListener("change", keep);
307
+ }
291
308
  /** Show errors from the canvas's current autosave */
292
309
  watchAutosaveErrors() {
293
310
  const autosave = this.app?.autosave;
@@ -1,13 +1,15 @@
1
1
  import { nothing } from "lit";
2
2
  import { PcElement } from "../../base";
3
3
  import "../PcShortcutSettings";
4
+ import "../PcToggleCheckbox";
4
5
  import "../PcDialogWindow";
5
6
  import type PaintCanvas from "../../../lib/classes/PaintCanvas";
6
7
  import { type SaveMethod } from "../../../lib/rendering/screenshot";
7
8
  /**
8
- * Buttons for the user's app settings: "Keyboard shortcuts", which opens a window to change them (see
9
+ * The user's app settings: a "Draw with the GPU" switch (the user's `ui.data.rendering`, used from the next start, so it
10
+ * says when the page needs reloading), and buttons: "Keyboard shortcuts", which opens a window to change them (see
9
11
  * `pc-shortcut-settings`), and "Import settings" and "Export settings", which read and write a settings JSON file (input
10
- * settings, keyboard shortcuts, colour palette, colours, profile graphs, theme and language). The window is a shaded
12
+ * settings, keyboard shortcuts, colour palette, colours, profile graphs, theme, language and rendering). The window is a shaded
11
13
  * `<pc-dialog-window>` over the whole page; it closes with its ⨉ button, Escape or a press outside it, and focus goes
12
14
  * back to the button. Fires `pc-toast` after importing, or when importing or exporting fails.
13
15
  *
@@ -40,6 +42,7 @@ export declare class PcAppSettings extends PcElement {
40
42
  shortcutsOpen: boolean;
41
43
  /** Saves exported files (replaceable for tests) */
42
44
  saveFile: (blob: Blob, name: string) => Promise<SaveMethod | undefined>;
45
+ private changes;
43
46
  constructor();
44
47
  /** Close the keyboard shortcuts window */
45
48
  closeShortcuts(): void;
@@ -55,5 +58,10 @@ export declare class PcAppSettings extends PcElement {
55
58
  private toastError;
56
59
  /** The keyboard shortcuts window, with its contents only while open */
57
60
  private renderShortcuts;
61
+ /**
62
+ * The switch that draws with the GPU (or the CPU) from the next start, saying when the page needs reloading to use
63
+ * the choice (see `PaintCanvas.rendering`).
64
+ */
65
+ private renderRendering;
58
66
  render(): import("lit").TemplateResult<1> | typeof nothing;
59
67
  }
@@ -1,7 +1,9 @@
1
1
  import { SETTINGS_FILE_FORMAT } from "../../../lib/settings/schema.js";
2
2
  import { ICON_CONTROLS_CSS, PILLS_CSS, WINDOWS_CSS } from "../../styles/index.js";
3
3
  import { PcElement, defineElement, emit } from "../../base/index.js";
4
+ import ChangeController from "../../ChangeController/index.js";
4
5
  import "../PcShortcutSettings/index.js";
6
+ import "../PcToggleCheckbox/index.js";
5
7
  import "../PcDialogWindow/index.js";
6
8
  import { SETTINGS_FILE_ERRORS, SettingsFileError, applySettings, collectSettings, exportSettingsFile, parseSettingsFile, settingsFileName } from "../../../lib/settings/index.js";
7
9
  import { JSON_FILE_TYPE, saveFile } from "../../../lib/rendering/screenshot/index.js";
@@ -13,9 +15,10 @@ var SETTINGS_FILE_ERROR_KEYS = {
13
15
  [SETTINGS_FILE_ERRORS.read]: "paintCanvas.settings.fileReadFailed"
14
16
  };
15
17
  /**
16
- * Buttons for the user's app settings: "Keyboard shortcuts", which opens a window to change them (see
18
+ * The user's app settings: a "Draw with the GPU" switch (the user's `ui.data.rendering`, used from the next start, so it
19
+ * says when the page needs reloading), and buttons: "Keyboard shortcuts", which opens a window to change them (see
17
20
  * `pc-shortcut-settings`), and "Import settings" and "Export settings", which read and write a settings JSON file (input
18
- * settings, keyboard shortcuts, colour palette, colours, profile graphs, theme and language). The window is a shaded
21
+ * settings, keyboard shortcuts, colour palette, colours, profile graphs, theme, language and rendering). The window is a shaded
19
22
  * `<pc-dialog-window>` over the whole page; it closes with its ⨉ button, Escape or a press outside it, and focus goes
20
23
  * back to the button. Fires `pc-toast` after importing, or when importing or exporting fails.
21
24
  *
@@ -44,11 +47,23 @@ var PcAppSettings = class extends PcElement {
44
47
  font-family: var(--pc-font);
45
48
  font-size: var(--pc-font-size);
46
49
  }
50
+ /* One full-width button per row */
47
51
  .actions {
48
52
  display: flex;
49
- flex-wrap: wrap;
53
+ flex-direction: column;
50
54
  gap: var(--pc-space-2);
51
55
  }
56
+ .actions button {
57
+ width: 100%;
58
+ }
59
+ .hint {
60
+ display: block;
61
+ color: var(--pc-text-muted);
62
+ font-size: 0.85em;
63
+ }
64
+ .pending {
65
+ color: var(--pc-text);
66
+ }
52
67
  pc-dialog-window {
53
68
  --pc-dialog-width: 480px;
54
69
  }
@@ -60,6 +75,7 @@ var PcAppSettings = class extends PcElement {
60
75
  ];
61
76
  /** Saves exported files (replaceable for tests) */
62
77
  saveFile = (blob, name) => saveFile(blob, name, void 0, JSON_FILE_TYPE);
78
+ changes = new ChangeController(this, () => [this.app?.ui]);
63
79
  constructor() {
64
80
  super();
65
81
  this.shortcutsOpen = false;
@@ -127,14 +143,33 @@ var PcAppSettings = class extends PcElement {
127
143
  .settings=${app.shortcuts}
128
144
  ></pc-shortcut-settings>` : nothing}
129
145
  </pc-dialog-window>`;
146
+ }
147
+ /**
148
+ * The switch that draws with the GPU (or the CPU) from the next start, saying when the page needs reloading to use
149
+ * the choice (see `PaintCanvas.rendering`).
150
+ */
151
+ renderRendering(app) {
152
+ const chosen = app.ui.data.rendering;
153
+ const pending = chosen !== app.rendering;
154
+ return html`<pc-toggle-checkbox
155
+ class="gpu"
156
+ .checked=${chosen === "gpu"}
157
+ @change=${(e) => app.ui.update({ rendering: e.detail.checked ? "gpu" : "cpu" })}
158
+ >
159
+ ${this.t("paintCanvas.settings.gpu")}
160
+ <span class="hint ${pending ? "pending" : ""}" aria-live="polite">
161
+ ${this.t(pending ? "paintCanvas.settings.reloadToApply" : "paintCanvas.settings.gpuHint")}
162
+ </span>
163
+ </pc-toggle-checkbox>`;
130
164
  }
131
165
  render() {
132
166
  const app = this.app;
133
167
  if (!app) return nothing;
134
- return html`<div class="actions">
168
+ return html`${this.renderRendering(app)}
169
+ <div class="actions">
135
170
  <button
136
171
  type="button"
137
- class="pill-s shortcuts"
172
+ class="pill shortcuts"
138
173
  aria-haspopup="dialog"
139
174
  @click=${() => this.shortcutsOpen = true}
140
175
  >
@@ -142,14 +177,14 @@ var PcAppSettings = class extends PcElement {
142
177
  </button>
143
178
  <button
144
179
  type="button"
145
- class="pill-s import"
180
+ class="pill import"
146
181
  @click=${() => this.renderRoot.querySelector(".import-file")?.click()}
147
182
  >
148
183
  ${this.t("paintCanvas.settings.import")}
149
184
  </button>
150
185
  <button
151
186
  type="button"
152
- class="pill-s export"
187
+ class="pill export"
153
188
  @click=${() => void this.exportFile()}
154
189
  >
155
190
  ${this.t("paintCanvas.settings.export")}
@@ -2,6 +2,7 @@ import { nothing } from "lit";
2
2
  import { type ConfirmFn, type PromptFn } from "../../ask";
3
3
  import { PcElement } from "../../base";
4
4
  import "../PcBrushTest";
5
+ import "../PcToggleCheckbox";
5
6
  import "../PcDialogWindow";
6
7
  import "../PcSliderRange";
7
8
  import "../PcProfileEditor";
@@ -4,6 +4,7 @@ import { iconLabel } from "../../icons/index.js";
4
4
  import { askConfirm, askText, deletePresetQuestion } from "../../ask/index.js";
5
5
  import { PcElement, defineElement } from "../../base/index.js";
6
6
  import "../PcBrushTest/index.js";
7
+ import "../PcToggleCheckbox/index.js";
7
8
  import "../PcDialogWindow/index.js";
8
9
  import "../PcSliderRange/index.js";
9
10
  import "../PcProfileEditor/index.js";
@@ -500,16 +501,14 @@ var PcBrushConfig = class extends PcElement {
500
501
  >
501
502
  ${this.single("spacing", this.t("paintCanvas.brushConfig.spacing"), draft.spacing ?? 5, spacingLimits, (spacing) => this.change({ spacing }))}
502
503
  </div>
503
- <label>
504
- <input
505
- type="checkbox"
506
- class="snap"
507
- .checked=${draft.snapToPixelGrid ?? false}
508
- ?disabled=${this.busy}
509
- @change=${(e) => this.change({ snapToPixelGrid: e.target.checked })}
510
- />
504
+ <pc-toggle-checkbox
505
+ class="snap"
506
+ .checked=${draft.snapToPixelGrid ?? false}
507
+ ?disabled=${this.busy}
508
+ @change=${(e) => this.change({ snapToPixelGrid: e.detail.checked })}
509
+ >
511
510
  ${this.t("paintCanvas.brushConfig.snapToPixelGrid")}
512
- </label>
511
+ </pc-toggle-checkbox>
513
512
  </div>
514
513
  ${this.editingId ? html`<button
515
514
  slot="footer-start"
@@ -1,4 +1,5 @@
1
1
  import { PcElement } from "../../base";
2
+ import "../PcToggleCheckbox";
2
3
  import type { BrushConfig } from "../../../lib/brushes/types";
3
4
  import type BrushTipCache from "../../../lib/brushes/BrushTipCache";
4
5
  import { type CanvasFactory } from "../../../lib/classes/Layer";
@@ -1,6 +1,7 @@
1
1
  import style_default from "./style.js";
2
2
  import { PILLS_CSS } from "../../styles/index.js";
3
3
  import { PcElement, defineElement } from "../../base/index.js";
4
+ import "../PcToggleCheckbox/index.js";
4
5
  import StrokeEngine from "../../../lib/brushes/StrokeEngine/index.js";
5
6
  import Layer, { createOffscreenCanvas } from "../../../lib/classes/Layer/index.js";
6
7
  import { html, unsafeCSS } from "lit";
@@ -193,14 +194,14 @@ var PcBrushTest = class extends PcElement {
193
194
  <button type="button" class="pill clear" @click=${() => this.clear()}>
194
195
  ${this.t("paintCanvas.common.clear")}
195
196
  </button>
196
- <label class="show-points">
197
- <input
198
- type="checkbox"
199
- .checked=${this.showPoints}
200
- @change=${(e) => this.setShowPoints(e.target.checked)}
201
- />
197
+ <pc-toggle-checkbox
198
+ class="show-points"
199
+ size="s"
200
+ .checked=${this.showPoints}
201
+ @change=${(e) => this.setShowPoints(e.detail.checked)}
202
+ >
202
203
  ${this.t("paintCanvas.brushConfig.showStrokePoints")}
203
- </label>
204
+ </pc-toggle-checkbox>
204
205
  </div>
205
206
  `;
206
207
  }
@@ -1,3 +1,3 @@
1
1
  // src/ui/elements/PcBrushTest/style.css?inline
2
- var style_default = ":host {\n display: block;\n}\ncanvas {\n display: block;\n width: 100%;\n background: #fff;\n border: 1px solid var(--pc-border);\n touch-action: none;\n}\n/* \"Clear\", then \"Show stroke points\" */\n.actions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 12px;\n margin-top: 6px;\n}\n.show-points {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n cursor: pointer;\n}\n/* \"Clear\": a red pill */\nbutton.pill.clear {\n background: var(--pc-red);\n color: #ffffff;\n}\n";
2
+ var style_default = ":host {\n display: block;\n}\ncanvas {\n display: block;\n width: 100%;\n background: #fff;\n border: 1px solid var(--pc-border);\n touch-action: none;\n}\n/* \"Clear\", then \"Show stroke points\" */\n.actions {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: 12px;\n margin-top: 6px;\n}\n.show-points {\n display: inline-flex;\n}\n/* \"Clear\": a red pill */\nbutton.pill.clear {\n background: var(--pc-red);\n color: #ffffff;\n}\n";
3
3
  export { style_default as default };
@@ -0,0 +1,88 @@
1
+ import { PcElement } from "../../base";
2
+ import { type PillSize } from "../../styles";
3
+ import type { MenuItem } from "../PcMenu";
4
+ import "../PcContextMenu";
5
+ /** An action in a button menu: what `pc-select` gives, its label and whether it can be done now */
6
+ export type ButtonMenuItem = Pick<MenuItem, "value" | "label" | "disabled">;
7
+ /**
8
+ * Whether the window is narrower than a width (CSS pixels). False without a width or `matchMedia`.
9
+ *
10
+ * @example
11
+ * isNarrowerThan(900); // true in a 600px-wide window
12
+ */
13
+ export declare const isNarrowerThan: (width: number | undefined, matchMedia?: ((query: string) => {
14
+ matches: boolean;
15
+ }) | undefined) => boolean;
16
+ /**
17
+ * A row of pill buttons for actions, which can become one ⋮ menu (`pc-context-menu`): while `menu` is set, or while the
18
+ * window is narrower than `menu-below` (CSS pixels; only when set). Fires `pc-select` (detail: `{ value }`) when an
19
+ * action is chosen either way.
20
+ *
21
+ * @example
22
+ * html`<pc-button-menu label="More actions" menu-below="900" size="pill-s"
23
+ * .items=${[{ value: "cut", label: "Cut" }, { value: "paste", label: "Paste", disabled: true }]}
24
+ * @pc-select=${(e) => run(e.detail.value)}></pc-button-menu>`
25
+ */
26
+ export declare class PcButtonMenu extends PcElement {
27
+ static properties: {
28
+ items: {
29
+ attribute: boolean;
30
+ };
31
+ label: {
32
+ type: StringConstructor;
33
+ };
34
+ size: {
35
+ type: StringConstructor;
36
+ };
37
+ menu: {
38
+ type: BooleanConstructor;
39
+ reflect: boolean;
40
+ };
41
+ menuBelow: {
42
+ type: NumberConstructor;
43
+ attribute: string;
44
+ };
45
+ narrowWindow: {
46
+ state: boolean;
47
+ };
48
+ translations: {
49
+ attribute: boolean;
50
+ };
51
+ icons: {
52
+ attribute: boolean;
53
+ };
54
+ theme: {
55
+ type: StringConstructor;
56
+ reflect: boolean;
57
+ };
58
+ };
59
+ static styles: import("lit").CSSResultGroup[];
60
+ /** The actions, in order */
61
+ items: ButtonMenuItem[];
62
+ /** What the ⋮ button is called (e.g. "More actions") */
63
+ label: string;
64
+ /** The buttons' pill size (`pill` by default) */
65
+ size: PillSize;
66
+ /** Whether the actions are always one ⋮ menu */
67
+ menu: boolean;
68
+ /** Window width (CSS pixels) below which the actions are one ⋮ menu; none by default */
69
+ menuBelow?: number;
70
+ /** Whether the window is narrower than `menuBelow` */
71
+ narrowWindow: boolean;
72
+ /** Follows the window's width while `menuBelow` is set */
73
+ private query?;
74
+ private readonly onQueryChange;
75
+ constructor();
76
+ /** Whether the actions show as one ⋮ menu now */
77
+ get showsMenu(): boolean;
78
+ connectedCallback(): void;
79
+ disconnectedCallback(): void;
80
+ protected willUpdate(changed: Map<PropertyKey, unknown>): void;
81
+ /** Follow whether the window is narrower than `menuBelow` (when set and the browser can tell) */
82
+ private watchWidth;
83
+ /** Stop following the window's width */
84
+ private stopWatchingWidth;
85
+ /** Tell listeners an action was chosen */
86
+ private select;
87
+ render(): import("lit").TemplateResult<1> | import("lit").TemplateResult<1>[];
88
+ }
@@ -0,0 +1,125 @@
1
+ import { PcElement, defineElement, emit } from "../../base/index.js";
2
+ import { PILLS_CSS } from "../../styles/index.js";
3
+ import "../PcContextMenu/index.js";
4
+ import { css, html, unsafeCSS } from "lit";
5
+ // src/ui/elements/PcButtonMenu/index.ts
6
+ /**
7
+ * Whether the window is narrower than a width (CSS pixels). False without a width or `matchMedia`.
8
+ *
9
+ * @example
10
+ * isNarrowerThan(900); // true in a 600px-wide window
11
+ */
12
+ var isNarrowerThan = (width, matchMedia = typeof window.matchMedia === "function" ? (query) => window.matchMedia(query) : void 0) => width !== void 0 && width > 0 && matchMedia !== void 0 && matchMedia(`(width < ${width}px)`).matches;
13
+ /**
14
+ * A row of pill buttons for actions, which can become one ⋮ menu (`pc-context-menu`): while `menu` is set, or while the
15
+ * window is narrower than `menu-below` (CSS pixels; only when set). Fires `pc-select` (detail: `{ value }`) when an
16
+ * action is chosen either way.
17
+ *
18
+ * @example
19
+ * html`<pc-button-menu label="More actions" menu-below="900" size="pill-s"
20
+ * .items=${[{ value: "cut", label: "Cut" }, { value: "paste", label: "Paste", disabled: true }]}
21
+ * @pc-select=${(e) => run(e.detail.value)}></pc-button-menu>`
22
+ */
23
+ var PcButtonMenu = class extends PcElement {
24
+ static properties = {
25
+ ...PcElement.properties,
26
+ items: { attribute: false },
27
+ label: { type: String },
28
+ size: { type: String },
29
+ menu: {
30
+ type: Boolean,
31
+ reflect: true
32
+ },
33
+ menuBelow: {
34
+ type: Number,
35
+ attribute: "menu-below"
36
+ },
37
+ narrowWindow: { state: true }
38
+ };
39
+ static styles = [
40
+ PcElement.styles,
41
+ unsafeCSS(PILLS_CSS),
42
+ css`
43
+ :host {
44
+ display: flex;
45
+ align-items: center;
46
+ gap: var(--pc-space-2);
47
+ }
48
+ :host([hidden]) {
49
+ display: none;
50
+ }
51
+ `
52
+ ];
53
+ /** Follows the window's width while `menuBelow` is set */
54
+ query;
55
+ onQueryChange = (event) => {
56
+ this.narrowWindow = event.matches;
57
+ };
58
+ constructor() {
59
+ super();
60
+ this.items = [];
61
+ this.label = "";
62
+ this.size = "pill";
63
+ this.menu = false;
64
+ this.narrowWindow = false;
65
+ }
66
+ /** Whether the actions show as one ⋮ menu now */
67
+ get showsMenu() {
68
+ return this.menu || this.narrowWindow;
69
+ }
70
+ connectedCallback() {
71
+ super.connectedCallback();
72
+ this.watchWidth();
73
+ }
74
+ disconnectedCallback() {
75
+ super.disconnectedCallback();
76
+ this.stopWatchingWidth();
77
+ }
78
+ willUpdate(changed) {
79
+ super.willUpdate(changed);
80
+ if (changed.has("menuBelow") && this.isConnected) this.watchWidth();
81
+ }
82
+ /** Follow whether the window is narrower than `menuBelow` (when set and the browser can tell) */
83
+ watchWidth() {
84
+ this.stopWatchingWidth();
85
+ this.narrowWindow = isNarrowerThan(this.menuBelow);
86
+ if (!this.menuBelow || typeof window.matchMedia !== "function") return;
87
+ this.query = window.matchMedia(`(width < ${this.menuBelow}px)`);
88
+ this.query.addEventListener?.("change", this.onQueryChange);
89
+ }
90
+ /** Stop following the window's width */
91
+ stopWatchingWidth() {
92
+ this.query?.removeEventListener?.("change", this.onQueryChange);
93
+ this.query = void 0;
94
+ }
95
+ /** Tell listeners an action was chosen */
96
+ select(value) {
97
+ emit(this, "pc-select", { value });
98
+ }
99
+ render() {
100
+ if (this.showsMenu) return html`<pc-context-menu
101
+ class="more"
102
+ label=${this.label}
103
+ .items=${this.items.map(({ value, label, disabled }) => ({
104
+ value,
105
+ label,
106
+ disabled
107
+ }))}
108
+ @pc-select=${(event) => {
109
+ event.stopPropagation();
110
+ this.select(event.detail.value);
111
+ }}
112
+ ></pc-context-menu>`;
113
+ return this.items.map(({ value, label, disabled }) => html`<button
114
+ type="button"
115
+ class="${this.size} ${value}"
116
+ data-value=${value}
117
+ ?disabled=${disabled}
118
+ @click=${() => this.select(value)}
119
+ >
120
+ ${label}
121
+ </button>`);
122
+ }
123
+ };
124
+ defineElement("pc-button-menu", PcButtonMenu);
125
+ export { PcButtonMenu, isNarrowerThan };
@@ -121,6 +121,8 @@ export declare class PcCanvas extends PcElement {
121
121
  private antsOffset;
122
122
  /** Viewport that has been fitted once (so resizing doesn't refit) */
123
123
  private fittedViewport?;
124
+ /** The screen canvas whose 2D context has been made (see `prepareScene`) */
125
+ private preparedCanvas?;
124
126
  private frame;
125
127
  /** Animation frame drawing only the brush cursor, if requested */
126
128
  private overlayFrame;
@@ -168,6 +170,13 @@ export declare class PcCanvas extends PcElement {
168
170
  connectedCallback(): void;
169
171
  disconnectedCallback(): void;
170
172
  firstUpdated(): void;
173
+ /**
174
+ * Make a new screen canvas's 2D context where the app's canvases draw (see `PaintCanvas.rendering`). Drawn from layers
175
+ * kept in regular memory (`cpu`), keeping the screen canvas there too means each frame copies only the area that
176
+ * changed, instead of sending whole layers to the GPU. The first context decides that; later `getContext("2d")` calls
177
+ * get this one, so the screen canvas is made again when the app's rendering differs (see `render`).
178
+ */
179
+ private prepareScene;
171
180
  /**
172
181
  * Note what changed: the stroke in progress (and the area it changed), or anything else.
173
182
  */