@lvl99dotcom/paintcanvas 0.5.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 (120) hide show
  1. package/README.md +2 -2
  2. package/dist/lib/brushes/configRanges/index.d.ts +17 -0
  3. package/dist/lib/brushes/configRanges/index.js +31 -1
  4. package/dist/lib/brushes/params/index.d.ts +2 -1
  5. package/dist/lib/brushes/params/index.js +7 -4
  6. package/dist/lib/brushes/types/index.d.ts +8 -0
  7. package/dist/lib/classes/Layer/index.d.ts +10 -0
  8. package/dist/lib/classes/Layer/index.js +15 -1
  9. package/dist/lib/classes/LayerAutosave/index.d.ts +9 -15
  10. package/dist/lib/classes/LayerAutosave/index.js +30 -43
  11. package/dist/lib/classes/LayerStack/index.d.ts +15 -0
  12. package/dist/lib/classes/LayerStack/index.js +26 -2
  13. package/dist/lib/classes/PaintCanvas/index.d.ts +36 -5
  14. package/dist/lib/classes/PaintCanvas/index.js +45 -14
  15. package/dist/lib/classes/PngEncoder/index.d.ts +41 -0
  16. package/dist/lib/classes/PngEncoder/index.js +104 -0
  17. package/dist/lib/classes/ProfileGraphs/index.d.ts +87 -0
  18. package/dist/lib/classes/ProfileGraphs/index.js +148 -0
  19. package/dist/lib/classes/SettingsAutosave/index.d.ts +1 -1
  20. package/dist/lib/classes/SettingsAutosave/index.js +2 -1
  21. package/dist/lib/classes/UiSettings/index.d.ts +24 -2
  22. package/dist/lib/classes/UiSettings/index.js +32 -7
  23. package/dist/lib/messages/schema.d.ts +720 -0
  24. package/dist/lib/presets/schema.d.ts +360 -0
  25. package/dist/lib/presets/schema.js +8 -1
  26. package/dist/lib/profiles/index.d.ts +61 -0
  27. package/dist/lib/profiles/index.js +131 -0
  28. package/dist/lib/profiles/schema.d.ts +73 -0
  29. package/dist/lib/profiles/schema.js +53 -0
  30. package/dist/lib/rendering/sample/index.d.ts +11 -0
  31. package/dist/lib/rendering/sample/index.js +36 -5
  32. package/dist/lib/settings/index.d.ts +2 -1
  33. package/dist/lib/settings/index.js +4 -1
  34. package/dist/lib/settings/schema.d.ts +53 -1
  35. package/dist/lib/settings/schema.js +9 -2
  36. package/dist/lib/storage/index.d.ts +26 -0
  37. package/dist/lib/storage/index.js +38 -1
  38. package/dist/lib/validation/validators.generated.js +13061 -3675
  39. package/dist/lib/version/index.d.ts +25 -0
  40. package/dist/lib/version/index.js +35 -0
  41. package/dist/locales/en-GB.js +35 -0
  42. package/dist/locales/en-US.js +35 -0
  43. package/dist/locales/fr.js +35 -0
  44. package/dist/locales/ja.js +35 -0
  45. package/dist/locales/ko.js +35 -0
  46. package/dist/locales/zh-Hans.js +35 -0
  47. package/dist/locales/zh-Hant.js +35 -0
  48. package/dist/package.js +1 -1
  49. package/dist/ui/ask/index.d.ts +48 -0
  50. package/dist/ui/ask/index.js +67 -0
  51. package/dist/ui/base/index.d.ts +8 -1
  52. package/dist/ui/base/index.js +19 -4
  53. package/dist/ui/elements/PaintCanvas/index.d.ts +13 -3
  54. package/dist/ui/elements/PaintCanvas/index.js +25 -6
  55. package/dist/ui/elements/PaintCanvas/style.js +1 -1
  56. package/dist/ui/elements/PcAppSettings/index.d.ts +15 -7
  57. package/dist/ui/elements/PcAppSettings/index.js +53 -62
  58. package/dist/ui/elements/PcBrushConfig/index.d.ts +26 -14
  59. package/dist/ui/elements/PcBrushConfig/index.js +214 -145
  60. package/dist/ui/elements/PcBrushConfig/style.js +1 -1
  61. package/dist/ui/elements/PcBrushTest/index.d.ts +1 -0
  62. package/dist/ui/elements/PcBrushTest/index.js +15 -9
  63. package/dist/ui/elements/PcBrushTest/style.js +1 -1
  64. package/dist/ui/elements/PcButton/index.d.ts +12 -3
  65. package/dist/ui/elements/PcButton/index.js +18 -10
  66. package/dist/ui/elements/PcButtonMenu/index.d.ts +88 -0
  67. package/dist/ui/elements/PcButtonMenu/index.js +125 -0
  68. package/dist/ui/elements/PcCanvas/index.d.ts +15 -1
  69. package/dist/ui/elements/PcCanvas/index.js +42 -19
  70. package/dist/ui/elements/PcCanvas/style.js +1 -1
  71. package/dist/ui/elements/PcClipboardTools/index.d.ts +11 -7
  72. package/dist/ui/elements/PcClipboardTools/index.js +30 -29
  73. package/dist/ui/elements/PcDialog/index.d.ts +46 -10
  74. package/dist/ui/elements/PcDialog/index.js +102 -51
  75. package/dist/ui/elements/PcDialogWindow/index.d.ts +141 -0
  76. package/dist/ui/elements/PcDialogWindow/index.js +385 -0
  77. package/dist/ui/elements/PcInputSettings/index.d.ts +2 -1
  78. package/dist/ui/elements/PcInputSettings/index.js +9 -15
  79. package/dist/ui/elements/PcLayerItem/index.d.ts +21 -6
  80. package/dist/ui/elements/PcLayerItem/index.js +53 -26
  81. package/dist/ui/elements/PcLayerItem/style.js +1 -1
  82. package/dist/ui/elements/PcPanelBrushes/index.d.ts +3 -2
  83. package/dist/ui/elements/PcPanelBrushes/index.js +7 -5
  84. package/dist/ui/elements/PcPanelBrushes/style.js +1 -1
  85. package/dist/ui/elements/PcPanelColours/index.js +2 -2
  86. package/dist/ui/elements/PcPanelColours/style.js +1 -1
  87. package/dist/ui/elements/PcPanelLayers/index.js +1 -1
  88. package/dist/ui/elements/PcPanelLayers/style.js +1 -1
  89. package/dist/ui/elements/PcProfileEditor/index.d.ts +103 -0
  90. package/dist/ui/elements/PcProfileEditor/index.js +259 -0
  91. package/dist/ui/elements/PcProfileGraph/index.d.ts +84 -0
  92. package/dist/ui/elements/PcProfileGraph/index.js +317 -0
  93. package/dist/ui/elements/PcShortcutSettings/index.js +9 -13
  94. package/dist/ui/elements/PcSliderRange/index.d.ts +147 -0
  95. package/dist/ui/elements/PcSliderRange/index.js +308 -0
  96. package/dist/ui/elements/PcStats/index.d.ts +19 -4
  97. package/dist/ui/elements/PcStats/index.js +29 -13
  98. package/dist/ui/elements/PcToggleCheckbox/index.d.ts +13 -2
  99. package/dist/ui/elements/PcToggleCheckbox/index.js +27 -8
  100. package/dist/ui/elements/PcToolSettings/index.d.ts +1 -0
  101. package/dist/ui/elements/PcToolSettings/index.js +14 -14
  102. package/dist/ui/elements/PcVersion/index.d.ts +12 -0
  103. package/dist/ui/elements/PcVersion/index.js +43 -0
  104. package/dist/ui/icons/images/icon-profile-graph@2x.js +3 -0
  105. package/dist/ui/icons/index.d.ts +1 -1
  106. package/dist/ui/icons/index.js +2 -0
  107. package/dist/ui/index.d.ts +9 -2
  108. package/dist/ui/index.js +9 -2
  109. package/dist/ui/layout/index.d.ts +2 -2
  110. package/dist/ui/layout/index.js +2 -2
  111. package/dist/ui/styles/index.d.ts +21 -1
  112. package/dist/ui/styles/index.js +73 -14
  113. package/locales/en-GB.json +35 -0
  114. package/locales/en-US.json +35 -0
  115. package/locales/fr.json +35 -0
  116. package/locales/ja.json +35 -0
  117. package/locales/ko.json +35 -0
  118. package/locales/zh-Hans.json +35 -0
  119. package/locales/zh-Hant.json +35 -0
  120. package/package.json +25 -23
@@ -1,14 +1,17 @@
1
1
  import { PcElement } from "../../base";
2
+ import { type PillSize } from "../../styles";
2
3
  import "../PcIcon";
3
4
  /** How a button looks */
4
5
  export type ButtonVariant = "accent" | "cancel" | "dismiss" | "pill" | "icon";
5
6
  /**
6
- * A button: affirmative (`accent`, the default), cancelling (`cancel`), a quiet text button (`dismiss`), a small pill
7
- * (`pill`), or a boxed icon button (`icon`, which needs a `label` for screen readers). Its text goes in its content.
8
- * Clicks reach listeners on the element; a disabled button sends none.
7
+ * A button: affirmative (`accent`, the default), cancelling (`cancel`), a quiet text button (`dismiss`), a pill (`pill`,
8
+ * the same as `accent`), or a boxed icon button (`icon`, which needs a `label` for screen readers). Its text goes in its
9
+ * content. All but icon buttons are pills, of the `size` given (`pill` by default; see `PILL_SIZE_STYLES`). Clicks
10
+ * reach listeners on the element; a disabled button sends none.
9
11
  *
10
12
  * @example
11
13
  * html`<pc-button variant="cancel" @click=${close}>Cancel</pc-button>`
14
+ * html`<pc-button size="pill-l">Start</pc-button>`
12
15
  * html`<pc-button variant="icon" icon="undo" label="Undo"></pc-button>`
13
16
  */
14
17
  export declare class PcButton extends PcElement {
@@ -17,6 +20,10 @@ export declare class PcButton extends PcElement {
17
20
  type: StringConstructor;
18
21
  reflect: boolean;
19
22
  };
23
+ size: {
24
+ type: StringConstructor;
25
+ reflect: boolean;
26
+ };
20
27
  icon: {
21
28
  type: StringConstructor;
22
29
  };
@@ -43,6 +50,8 @@ export declare class PcButton extends PcElement {
43
50
  };
44
51
  static styles: import("lit").CSSResultGroup[];
45
52
  variant: ButtonVariant;
53
+ /** How big the pill is (not for icon buttons) */
54
+ size: PillSize;
46
55
  /** Name of an icon to show before the text (or alone, for an icon button) */
47
56
  icon?: string;
48
57
  /** The button's name for screen readers, when its content doesn't say it (e.g. an icon button) */
@@ -1,14 +1,19 @@
1
1
  import { PcElement, defineElement } from "../../base/index.js";
2
+ import { PILL_SIZES, pillSizeDeclarations } from "../../styles/index.js";
2
3
  import "../PcIcon/index.js";
3
- import { css, html, nothing } from "lit";
4
+ import { css, html, nothing, unsafeCSS } from "lit";
4
5
  // src/ui/elements/PcButton/index.ts
6
+ /** Each pill size, for the buttons that are pills (all but icon buttons) */
7
+ var SIZE_STYLES = unsafeCSS(PILL_SIZES.map((size) => `:host([size="${size}"]) button { ${pillSizeDeclarations(size)} }`).join("\n"));
5
8
  /**
6
- * A button: affirmative (`accent`, the default), cancelling (`cancel`), a quiet text button (`dismiss`), a small pill
7
- * (`pill`), or a boxed icon button (`icon`, which needs a `label` for screen readers). Its text goes in its content.
8
- * Clicks reach listeners on the element; a disabled button sends none.
9
+ * A button: affirmative (`accent`, the default), cancelling (`cancel`), a quiet text button (`dismiss`), a pill (`pill`,
10
+ * the same as `accent`), or a boxed icon button (`icon`, which needs a `label` for screen readers). Its text goes in its
11
+ * content. All but icon buttons are pills, of the `size` given (`pill` by default; see `PILL_SIZE_STYLES`). Clicks
12
+ * reach listeners on the element; a disabled button sends none.
9
13
  *
10
14
  * @example
11
15
  * html`<pc-button variant="cancel" @click=${close}>Cancel</pc-button>`
16
+ * html`<pc-button size="pill-l">Start</pc-button>`
12
17
  * html`<pc-button variant="icon" icon="undo" label="Undo"></pc-button>`
13
18
  */
14
19
  var PcButton = class extends PcElement {
@@ -18,6 +23,10 @@ var PcButton = class extends PcElement {
18
23
  type: String,
19
24
  reflect: true
20
25
  },
26
+ size: {
27
+ type: String,
28
+ reflect: true
29
+ },
21
30
  icon: { type: String },
22
31
  label: { type: String },
23
32
  disabled: {
@@ -38,9 +47,8 @@ var PcButton = class extends PcElement {
38
47
  flex: 1;
39
48
  align-items: center;
40
49
  justify-content: center;
50
+ box-sizing: border-box;
41
51
  gap: var(--pc-space-1);
42
- min-height: var(--pc-control-size);
43
- padding: var(--pc-space-1) var(--pc-space-3);
44
52
  border: 0;
45
53
  /* A joined button group gives each button its corners */
46
54
  border-radius: var(--pc-group-radius, var(--pc-radius-pill));
@@ -48,10 +56,11 @@ var PcButton = class extends PcElement {
48
56
  color: var(--pc-accent-text);
49
57
  font: inherit;
50
58
  font-family: var(--pc-font);
51
- font-size: var(--pc-font-size);
59
+ ${unsafeCSS(pillSizeDeclarations("pill"))}
52
60
  font-weight: bold;
53
61
  cursor: pointer;
54
62
  }
63
+ ${SIZE_STYLES}
55
64
  button:hover:not(:disabled) {
56
65
  filter: brightness(1.15);
57
66
  }
@@ -76,12 +85,10 @@ var PcButton = class extends PcElement {
76
85
  filter: none;
77
86
  text-decoration: underline;
78
87
  }
79
- :host([variant="pill"]) button {
80
- min-height: 30px;
81
- }
82
88
  :host([variant="icon"]) button {
83
89
  min-height: 0;
84
90
  padding: 2px;
91
+ line-height: normal;
85
92
  border: 1px solid var(--pc-border);
86
93
  border-radius: var(--pc-group-radius, var(--pc-radius-small));
87
94
  background: var(--pc-surface);
@@ -101,6 +108,7 @@ var PcButton = class extends PcElement {
101
108
  constructor() {
102
109
  super();
103
110
  this.variant = "accent";
111
+ this.size = "pill";
104
112
  this.disabled = false;
105
113
  }
106
114
  /** Move keyboard focus to the button */
@@ -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 };
@@ -15,6 +15,8 @@ export declare const HANDLE_GRAB_DISTANCE = 8;
15
15
  export declare const ANTS_INTERVAL = 100;
16
16
  /** In focus pen mode, how long after a pen lifts touches are still ignored (milliseconds) */
17
17
  export declare const PEN_LIFT_GRACE_MS = 500;
18
+ /** The most device pixels per CSS pixel the canvases on screen use (see `PcCanvas.pixelRatio`) */
19
+ export declare const MAX_PIXEL_RATIO = 2;
18
20
  /**
19
21
  * Space covered by the toolbar and chat on a screen of the given width.
20
22
  *
@@ -92,7 +94,10 @@ export declare class PcCanvas extends PcElement {
92
94
  insets?: Insets;
93
95
  /** The insets in effect (see `insets`) */
94
96
  private get activeInsets();
95
- /** Device pixels per CSS pixel (replaceable for tests) */
97
+ /**
98
+ * Device pixels per CSS pixel for the canvases on screen, at most `MAX_PIXEL_RATIO`: phones with 3 have 2.25 times
99
+ * the pixels to draw and put on screen for little visible difference (replaceable for tests).
100
+ */
96
101
  pixelRatio: () => number;
97
102
  private changes;
98
103
  /** Whether anything but the stroke in progress changed since the last update (so everything is redrawn) */
@@ -116,6 +121,8 @@ export declare class PcCanvas extends PcElement {
116
121
  private antsOffset;
117
122
  /** Viewport that has been fitted once (so resizing doesn't refit) */
118
123
  private fittedViewport?;
124
+ /** The screen canvas whose 2D context has been made (see `prepareScene`) */
125
+ private preparedCanvas?;
119
126
  private frame;
120
127
  /** Animation frame drawing only the brush cursor, if requested */
121
128
  private overlayFrame;
@@ -163,6 +170,13 @@ export declare class PcCanvas extends PcElement {
163
170
  connectedCallback(): void;
164
171
  disconnectedCallback(): void;
165
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;
166
180
  /**
167
181
  * Note what changed: the stroke in progress (and the area it changed), or anything else.
168
182
  */
@@ -21,6 +21,7 @@ import "../../../lib/rendering/sample/index.js";
21
21
  import { gestureAction } from "../../../lib/utils/gestures/index.js";
22
22
  import { snapToPixelGrid } from "../../../lib/brushes/params/index.js";
23
23
  import { html, unsafeCSS } from "lit";
24
+ import { keyed } from "lit/directives/keyed.js";
24
25
  // src/ui/elements/PcCanvas/index.ts
25
26
  /** Toolbar width in CSS pixels (kept clear when fitting the canvas) */
26
27
  var TOOLBAR_WIDTH = 50;
@@ -39,6 +40,8 @@ var HANDLE_GRAB_DISTANCE = 8;
39
40
  var ANTS_INTERVAL = 100;
40
41
  /** In focus pen mode, how long after a pen lifts touches are still ignored (milliseconds) */
41
42
  var PEN_LIFT_GRACE_MS = 500;
43
+ /** The most device pixels per CSS pixel the canvases on screen use (see `PcCanvas.pixelRatio`) */
44
+ var MAX_PIXEL_RATIO = 2;
42
45
  /**
43
46
  * Space covered by the toolbar and chat on a screen of the given width.
44
47
  *
@@ -131,8 +134,11 @@ var PcCanvas = class extends PcElement {
131
134
  const app = this.app;
132
135
  return this.insets ?? interfaceInsets(app.viewport.width, app.interfaceVisible);
133
136
  }
134
- /** Device pixels per CSS pixel (replaceable for tests) */
135
- pixelRatio = () => window.devicePixelRatio || 1;
137
+ /**
138
+ * Device pixels per CSS pixel for the canvases on screen, at most `MAX_PIXEL_RATIO`: phones with 3 have 2.25 times
139
+ * the pixels to draw and put on screen for little visible difference (replaceable for tests).
140
+ */
141
+ pixelRatio = () => Math.min(window.devicePixelRatio || 1, 2);
136
142
  changes = new ChangeController(this, () => [
137
143
  this.app,
138
144
  this.app?.brush,
@@ -167,6 +173,8 @@ var PcCanvas = class extends PcElement {
167
173
  antsOffset = 0;
168
174
  /** Viewport that has been fitted once (so resizing doesn't refit) */
169
175
  fittedViewport;
176
+ /** The screen canvas whose 2D context has been made (see `prepareScene`) */
177
+ preparedCanvas;
170
178
  frame = 0;
171
179
  /** Animation frame drawing only the brush cursor, if requested */
172
180
  overlayFrame = 0;
@@ -242,6 +250,7 @@ var PcCanvas = class extends PcElement {
242
250
  this.frame = 0;
243
251
  }
244
252
  firstUpdated() {
253
+ this.prepareScene();
245
254
  if (typeof ResizeObserver !== "undefined") {
246
255
  this.resizeObserver = new ResizeObserver(() => this.syncSize());
247
256
  this.resizeObserver.observe(this);
@@ -249,6 +258,18 @@ var PcCanvas = class extends PcElement {
249
258
  this.syncSize();
250
259
  }
251
260
  /**
261
+ * Make a new screen canvas's 2D context where the app's canvases draw (see `PaintCanvas.rendering`). Drawn from layers
262
+ * kept in regular memory (`cpu`), keeping the screen canvas there too means each frame copies only the area that
263
+ * changed, instead of sending whole layers to the GPU. The first context decides that; later `getContext("2d")` calls
264
+ * get this one, so the screen canvas is made again when the app's rendering differs (see `render`).
265
+ */
266
+ prepareScene() {
267
+ const canvas = this.canvas;
268
+ if (!canvas || canvas === this.preparedCanvas) return;
269
+ this.preparedCanvas = canvas;
270
+ canvas.getContext("2d", { willReadFrequently: this.app?.rendering !== "gpu" });
271
+ }
272
+ /**
252
273
  * Note what changed: the stroke in progress (and the area it changed), or anything else.
253
274
  */
254
275
  sceneChanged(target) {
@@ -260,6 +281,7 @@ var PcCanvas = class extends PcElement {
260
281
  } else if (!(this.committing && target === app?.layers)) this.fullChange = true;
261
282
  }
262
283
  updated(changed) {
284
+ this.prepareScene();
263
285
  const resized = this.syncSize();
264
286
  const onlyStroke = changed.size === 0 && !this.fullChange && !resized;
265
287
  this.requestDraw(onlyStroke ? this.strokeArea : void 0);
@@ -884,6 +906,7 @@ var PcCanvas = class extends PcElement {
884
906
  if (!ctx) return;
885
907
  ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
886
908
  const sampling = this.sampling;
909
+ this.sampler.hidden = !sampling;
887
910
  if (!sampling) return;
888
911
  const ratio = this.pixelRatio();
889
912
  ctx.save();
@@ -1024,26 +1047,26 @@ var PcCanvas = class extends PcElement {
1024
1047
  });
1025
1048
  }
1026
1049
  render() {
1027
- return html`<canvas
1028
- class="scene"
1029
- aria-label=${this.t("paintCanvas.canvas.label")}
1030
- @pointerdown=${this.onPointerDown}
1031
- @pointermove=${this.onPointerMove}
1032
- @pointerup=${this.onPointerUp}
1033
- @pointercancel=${this.onPointerCancel}
1034
- @touchstart=${this.onTouchStart}
1035
- @pointerleave=${this.onPointerLeave}
1036
- @wheel=${this.onWheel}
1037
- @gesturestart=${this.onGestureStart}
1038
- @gesturechange=${this.onGestureChange}
1039
- @gestureend=${(e) => e.preventDefault()}
1040
- @dblclick=${() => this.app?.applyLasso(this.app.lasso.close())}
1041
- @contextmenu=${(e) => e.preventDefault()}
1042
- ></canvas>
1050
+ return html`${keyed(this.app?.rendering ?? "cpu", html`<canvas
1051
+ class="scene"
1052
+ aria-label=${this.t("paintCanvas.canvas.label")}
1053
+ @pointerdown=${this.onPointerDown}
1054
+ @pointermove=${this.onPointerMove}
1055
+ @pointerup=${this.onPointerUp}
1056
+ @pointercancel=${this.onPointerCancel}
1057
+ @touchstart=${this.onTouchStart}
1058
+ @pointerleave=${this.onPointerLeave}
1059
+ @wheel=${this.onWheel}
1060
+ @gesturestart=${this.onGestureStart}
1061
+ @gesturechange=${this.onGestureChange}
1062
+ @gestureend=${(e) => e.preventDefault()}
1063
+ @dblclick=${() => this.app?.applyLasso(this.app.lasso.close())}
1064
+ @contextmenu=${(e) => e.preventDefault()}
1065
+ ></canvas>`)}
1043
1066
  <canvas class="outlines" aria-hidden="true"></canvas>
1044
1067
  <canvas class="overlay" aria-hidden="true"></canvas>
1045
1068
  <canvas class="sampler" aria-hidden="true"></canvas>`;
1046
1069
  }
1047
1070
  };
1048
1071
  defineElement("pc-canvas", PcCanvas);
1049
- export { ANTS_INTERVAL, CHAT_WIDTH_RATIO, HANDLE_GRAB_DISTANCE, LASSO_TOOLS, PEN_LIFT_GRACE_MS, PcCanvas, SAMPLER_HEIGHT, SAMPLER_WIDTH, TOOLBAR_WIDTH, TOUCH_SAMPLER_GAP, cursorFor, interfaceInsets, samplerPosition };
1072
+ export { ANTS_INTERVAL, CHAT_WIDTH_RATIO, HANDLE_GRAB_DISTANCE, LASSO_TOOLS, MAX_PIXEL_RATIO, PEN_LIFT_GRACE_MS, PcCanvas, SAMPLER_HEIGHT, SAMPLER_WIDTH, TOOLBAR_WIDTH, TOUCH_SAMPLER_GAP, cursorFor, interfaceInsets, samplerPosition };
@@ -1,3 +1,3 @@
1
1
  // src/ui/elements/PcCanvas/style.css?inline
2
- var style_default = ":host {\n display: block;\n position: absolute;\n inset: 0;\n touch-action: none;\n}\ncanvas {\n display: block;\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n}\ncanvas.outlines,\ncanvas.sampler {\n pointer-events: none;\n}\ncanvas.overlay {\n pointer-events: none;\n /* Inverts what is underneath so the brush outline is visible on any colour */\n mix-blend-mode: difference;\n}\n";
2
+ var style_default = ":host {\n display: block;\n position: absolute;\n inset: 0;\n touch-action: none;\n}\ncanvas {\n display: block;\n position: absolute;\n inset: 0;\n width: 100%;\n height: 100%;\n}\ncanvas[hidden] {\n display: none;\n}\ncanvas.outlines,\ncanvas.sampler {\n pointer-events: none;\n}\ncanvas.overlay {\n pointer-events: none;\n /* Inverts what is underneath so the brush outline is visible on any colour */\n mix-blend-mode: difference;\n}\n";
3
3
  export { style_default as default };
@@ -1,13 +1,17 @@
1
1
  import { nothing } from "lit";
2
2
  import { PcElement } from "../../base";
3
3
  import PaintCanvas, { Tool } from "../../../lib/classes/PaintCanvas";
4
- import "../PcContextMenu";
4
+ import "../PcButtonMenu";
5
5
  /** The tools that cut, copy and paste are shown for: the brush, eraser, lassos and Move tool */
6
6
  export declare const CLIPBOARD_TOOLS: readonly Tool[];
7
+ /** Window width (CSS pixels) below which the clipboard tools are one ⋮ menu */
8
+ export declare const CLIPBOARD_MENU_BELOW = 900;
7
9
  /**
8
- * Cut, copy and paste buttons for the canvas's masked image data, shown for the brush, eraser, lassos and Move tool
9
- * (see `CLIPBOARD_TOOLS`). Cut and copy work while there's a mask and no transform is pending; paste while there's
10
- * copied image data. While `narrow`, they're one ⋮ menu (`pc-context-menu`) instead.
10
+ * Cut, copy, copy merged and paste buttons for the canvas's masked image data, shown for the brush, eraser, lassos and
11
+ * Move tool (see `CLIPBOARD_TOOLS`). "Copy merged" copies the shown layers mixed as shown (see `PaintCanvas.copyMerged`).
12
+ * Cut and both copies work while there's a mask and no transform is pending; paste while there's copied image data.
13
+ * They're one ⋮ menu instead while the window is narrower than `CLIPBOARD_MENU_BELOW` (900px), or while `narrow` (see
14
+ * `pc-button-menu`).
11
15
  *
12
16
  * `<paint-canvas>` puts it at the end of its top pane, after the tool settings.
13
17
  *
@@ -36,13 +40,13 @@ export declare class PcClipboardTools extends PcElement {
36
40
  };
37
41
  static styles: import("lit").CSSResultGroup[];
38
42
  app?: PaintCanvas;
39
- /** Whether the buttons are one ⋮ menu (e.g. while the layout is narrow) */
43
+ /** Whether the buttons are one ⋮ menu whatever the window's width (e.g. while the layout is narrow) */
40
44
  narrow: boolean;
41
45
  private changes;
42
46
  constructor();
43
- /** Cut, copy and paste, each with whether it can be done now */
47
+ /** Cut, copy, copy merged and paste, each with whether it can be done now */
44
48
  private actions;
45
49
  /** Hide for tools without clipboard actions (e.g. zoom) */
46
50
  protected willUpdate(): void;
47
- render(): import("lit").TemplateResult<1> | typeof nothing | import("lit").TemplateResult<1>[];
51
+ render(): import("lit").TemplateResult<1> | typeof nothing;
48
52
  }
@@ -1,10 +1,9 @@
1
1
  import { Tool } from "../../../lib/classes/PaintCanvas/tool.js";
2
2
  import { PcElement, defineElement } from "../../base/index.js";
3
3
  import ChangeController from "../../ChangeController/index.js";
4
- import { PILLS_CSS } from "../../styles/index.js";
5
4
  import "../../../lib/classes/PaintCanvas/index.js";
6
- import "../PcContextMenu/index.js";
7
- import { css, html, nothing, unsafeCSS } from "lit";
5
+ import "../PcButtonMenu/index.js";
6
+ import { css, html, nothing } from "lit";
8
7
  // src/ui/elements/PcClipboardTools/index.ts
9
8
  /** The tools that cut, copy and paste are shown for: the brush, eraser, lassos and Move tool */
10
9
  var CLIPBOARD_TOOLS = [
@@ -16,10 +15,14 @@ var CLIPBOARD_TOOLS = [
16
15
  Tool.FreeformLasso,
17
16
  Tool.Move
18
17
  ];
18
+ /** Window width (CSS pixels) below which the clipboard tools are one ⋮ menu */
19
+ var CLIPBOARD_MENU_BELOW = 900;
19
20
  /**
20
- * Cut, copy and paste buttons for the canvas's masked image data, shown for the brush, eraser, lassos and Move tool
21
- * (see `CLIPBOARD_TOOLS`). Cut and copy work while there's a mask and no transform is pending; paste while there's
22
- * copied image data. While `narrow`, they're one ⋮ menu (`pc-context-menu`) instead.
21
+ * Cut, copy, copy merged and paste buttons for the canvas's masked image data, shown for the brush, eraser, lassos and
22
+ * Move tool (see `CLIPBOARD_TOOLS`). "Copy merged" copies the shown layers mixed as shown (see `PaintCanvas.copyMerged`).
23
+ * Cut and both copies work while there's a mask and no transform is pending; paste while there's copied image data.
24
+ * They're one ⋮ menu instead while the window is narrower than `CLIPBOARD_MENU_BELOW` (900px), or while `narrow` (see
25
+ * `pc-button-menu`).
23
26
  *
24
27
  * `<paint-canvas>` puts it at the end of its top pane, after the tool settings.
25
28
  *
@@ -35,15 +38,11 @@ var PcClipboardTools = class extends PcElement {
35
38
  reflect: true
36
39
  }
37
40
  };
38
- static styles = [
39
- PcElement.styles,
40
- unsafeCSS(PILLS_CSS),
41
- css`
41
+ static styles = [PcElement.styles, css`
42
42
  :host {
43
43
  display: flex;
44
44
  flex: none;
45
45
  align-items: center;
46
- gap: 8px;
47
46
  padding: 4px 8px;
48
47
  background: var(--pc-bar);
49
48
  border-bottom: 1px solid var(--pc-surface-active);
@@ -51,8 +50,7 @@ var PcClipboardTools = class extends PcElement {
51
50
  :host([hidden]) {
52
51
  display: none;
53
52
  }
54
- `
55
- ];
53
+ `];
56
54
  changes = new ChangeController(this, () => this.app ? [
57
55
  this.app,
58
56
  this.app.selection,
@@ -62,7 +60,7 @@ var PcClipboardTools = class extends PcElement {
62
60
  super();
63
61
  this.narrow = false;
64
62
  }
65
- /** Cut, copy and paste, each with whether it can be done now */
63
+ /** Cut, copy, copy merged and paste, each with whether it can be done now */
66
64
  actions(app) {
67
65
  const noCopy = !app.selection.active || app.transform.pending;
68
66
  return [
@@ -78,6 +76,12 @@ var PcClipboardTools = class extends PcElement {
78
76
  disabled: noCopy,
79
77
  run: () => app.copy()
80
78
  },
79
+ {
80
+ name: "copy-merged",
81
+ label: this.t("paintCanvas.common.copyMerged"),
82
+ disabled: noCopy,
83
+ run: () => app.copyMerged()
84
+ },
81
85
  {
82
86
  name: "paste",
83
87
  label: this.t("paintCanvas.common.paste"),
@@ -94,25 +98,22 @@ var PcClipboardTools = class extends PcElement {
94
98
  const app = this.app;
95
99
  if (!app || this.hidden) return nothing;
96
100
  const actions = this.actions(app);
97
- if (this.narrow) return html`<pc-context-menu
98
- class="more"
99
- label=${this.t("paintCanvas.toolSettings.moreActions")}
100
- .items=${actions.map(({ name, label, disabled }) => ({
101
+ return html`<pc-button-menu
102
+ size="pill-s"
103
+ label=${this.t("paintCanvas.toolSettings.moreActions")}
104
+ menu-below=${900}
105
+ ?menu=${this.narrow}
106
+ .items=${actions.map(({ name, label, disabled }) => ({
101
107
  value: name,
102
108
  label,
103
109
  disabled
104
110
  }))}
105
- @pc-select=${(event) => actions.find((action) => action.name === event.detail.value)?.run()}
106
- ></pc-context-menu>`;
107
- return actions.map((action) => html`<button
108
- type="button"
109
- class="pill ${action.name}"
110
- ?disabled=${action.disabled}
111
- @click=${action.run}
112
- >
113
- ${action.label}
114
- </button>`);
111
+ @pc-select=${(event) => {
112
+ event.stopPropagation();
113
+ actions.find((action) => action.name === event.detail.value)?.run();
114
+ }}
115
+ ></pc-button-menu>`;
115
116
  }
116
117
  };
117
118
  defineElement("pc-clipboard-tools", PcClipboardTools);
118
- export { CLIPBOARD_TOOLS, PcClipboardTools };
119
+ export { CLIPBOARD_MENU_BELOW, CLIPBOARD_TOOLS, PcClipboardTools };