@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.
Files changed (224) hide show
  1. package/README.md +30 -15
  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/presets/round/index.d.ts +1 -1
  7. package/dist/lib/brushes/presets/round/index.js +12 -7
  8. package/dist/lib/brushes/presets/square/index.d.ts +1 -1
  9. package/dist/lib/brushes/presets/square/index.js +7 -3
  10. package/dist/lib/brushes/shapeImage/index.d.ts +7 -3
  11. package/dist/lib/brushes/shapeImage/index.js +15 -9
  12. package/dist/lib/brushes/shapes/index.d.ts +18 -0
  13. package/dist/lib/brushes/shapes/index.js +86 -0
  14. package/dist/lib/brushes/types/index.d.ts +8 -0
  15. package/dist/lib/classes/BrushSettings/index.d.ts +29 -2
  16. package/dist/lib/classes/BrushSettings/index.js +51 -14
  17. package/dist/lib/classes/KeyboardShortcuts/index.d.ts +31 -2
  18. package/dist/lib/classes/KeyboardShortcuts/index.js +66 -4
  19. package/dist/lib/classes/Layer/index.d.ts +10 -0
  20. package/dist/lib/classes/Layer/index.js +15 -1
  21. package/dist/lib/classes/LayerAutosave/index.d.ts +9 -15
  22. package/dist/lib/classes/LayerAutosave/index.js +30 -43
  23. package/dist/lib/classes/LayerStack/index.d.ts +15 -0
  24. package/dist/lib/classes/LayerStack/index.js +26 -2
  25. package/dist/lib/classes/PaintCanvas/index.d.ts +72 -3
  26. package/dist/lib/classes/PaintCanvas/index.js +106 -12
  27. package/dist/lib/classes/PngEncoder/index.d.ts +41 -0
  28. package/dist/lib/classes/PngEncoder/index.js +104 -0
  29. package/dist/lib/classes/ProfileGraphs/index.d.ts +87 -0
  30. package/dist/lib/classes/ProfileGraphs/index.js +148 -0
  31. package/dist/lib/classes/SettingsAutosave/index.d.ts +3 -2
  32. package/dist/lib/classes/SettingsAutosave/index.js +6 -3
  33. package/dist/lib/classes/UiSettings/index.d.ts +68 -0
  34. package/dist/lib/classes/UiSettings/index.js +82 -0
  35. package/dist/lib/messages/schema.d.ts +720 -0
  36. package/dist/lib/presets/schema.d.ts +360 -0
  37. package/dist/lib/presets/schema.js +8 -1
  38. package/dist/lib/profiles/index.d.ts +61 -0
  39. package/dist/lib/profiles/index.js +131 -0
  40. package/dist/lib/profiles/schema.d.ts +73 -0
  41. package/dist/lib/profiles/schema.js +53 -0
  42. package/dist/lib/rendering/sample/index.d.ts +11 -0
  43. package/dist/lib/rendering/sample/index.js +36 -5
  44. package/dist/lib/settings/index.d.ts +2 -1
  45. package/dist/lib/settings/index.js +7 -2
  46. package/dist/lib/settings/schema.d.ts +67 -0
  47. package/dist/lib/settings/schema.js +20 -0
  48. package/dist/lib/validation/validators.generated.js +13191 -3655
  49. package/dist/locales/en-GB.js +39 -2
  50. package/dist/locales/en-US.js +39 -2
  51. package/dist/locales/fr.js +39 -2
  52. package/dist/locales/ja.js +39 -2
  53. package/dist/locales/ko.js +39 -2
  54. package/dist/locales/zh-Hans.js +39 -2
  55. package/dist/locales/zh-Hant.js +39 -2
  56. package/dist/package.js +1 -1
  57. package/dist/testing/recordingCanvas.js +2 -1
  58. package/dist/ui/ask/index.d.ts +48 -0
  59. package/dist/ui/ask/index.js +67 -0
  60. package/dist/ui/base/index.d.ts +10 -3
  61. package/dist/ui/base/index.js +19 -4
  62. package/dist/ui/dismiss/index.d.ts +21 -0
  63. package/dist/ui/dismiss/index.js +27 -0
  64. package/dist/ui/drag/index.d.ts +51 -0
  65. package/dist/ui/drag/index.js +81 -0
  66. package/dist/ui/elements/PaintCanvas/index.d.ts +17 -12
  67. package/dist/ui/elements/PaintCanvas/index.js +36 -17
  68. package/dist/ui/elements/PaintCanvas/style.js +1 -1
  69. package/dist/ui/elements/PcAppSettings/index.d.ts +6 -6
  70. package/dist/ui/elements/PcAppSettings/index.js +21 -65
  71. package/dist/ui/elements/PcBrushConfig/index.d.ts +34 -16
  72. package/dist/ui/elements/PcBrushConfig/index.js +263 -153
  73. package/dist/ui/elements/PcBrushConfig/style.js +1 -1
  74. package/dist/ui/elements/PcBrushSize/index.d.ts +53 -0
  75. package/dist/ui/elements/PcBrushSize/index.js +66 -0
  76. package/dist/ui/elements/PcBrushTest/index.d.ts +26 -0
  77. package/dist/ui/elements/PcBrushTest/index.js +79 -6
  78. package/dist/ui/elements/PcBrushTest/style.js +1 -1
  79. package/dist/ui/elements/PcButton/index.d.ts +12 -3
  80. package/dist/ui/elements/PcButton/index.js +18 -10
  81. package/dist/ui/elements/PcCanvas/index.d.ts +27 -2
  82. package/dist/ui/elements/PcCanvas/index.js +63 -13
  83. package/dist/ui/elements/PcCanvas/style.js +1 -1
  84. package/dist/ui/elements/PcCanvasMap/style.js +1 -1
  85. package/dist/ui/elements/PcClipboardTools/index.d.ts +49 -0
  86. package/dist/ui/elements/PcClipboardTools/index.js +125 -0
  87. package/dist/ui/elements/PcContextMenu/index.d.ts +2 -2
  88. package/dist/ui/elements/PcDialog/index.d.ts +46 -10
  89. package/dist/ui/elements/PcDialog/index.js +102 -51
  90. package/dist/ui/elements/PcDialogWindow/index.d.ts +141 -0
  91. package/dist/ui/elements/PcDialogWindow/index.js +385 -0
  92. package/dist/ui/elements/PcInputSettings/index.d.ts +13 -0
  93. package/dist/ui/elements/PcInputSettings/index.js +16 -1
  94. package/dist/ui/elements/PcMenu/index.d.ts +48 -3
  95. package/dist/ui/elements/PcMenu/index.js +111 -19
  96. package/dist/ui/elements/PcNumberSlider/index.d.ts +137 -0
  97. package/dist/ui/elements/PcNumberSlider/index.js +247 -0
  98. package/dist/ui/elements/PcPane/index.d.ts +3 -1
  99. package/dist/ui/elements/PcPane/index.js +44 -7
  100. package/dist/ui/elements/PcPanel/index.d.ts +2 -2
  101. package/dist/ui/elements/PcPanel/index.js +8 -8
  102. package/dist/ui/elements/PcPanelBrushes/index.d.ts +5 -17
  103. package/dist/ui/elements/PcPanelBrushes/index.js +13 -48
  104. package/dist/ui/elements/PcPanelBrushes/style.js +1 -1
  105. package/dist/ui/elements/PcPanelColours/index.js +2 -2
  106. package/dist/ui/elements/PcPanelColours/style.js +1 -1
  107. package/dist/ui/elements/PcPanelLayers/index.js +1 -1
  108. package/dist/ui/elements/PcPanelLayers/style.js +1 -1
  109. package/dist/ui/elements/PcPopover/index.d.ts +39 -1
  110. package/dist/ui/elements/PcPopover/index.js +72 -4
  111. package/dist/ui/elements/PcProfileEditor/index.d.ts +103 -0
  112. package/dist/ui/elements/PcProfileEditor/index.js +259 -0
  113. package/dist/ui/elements/PcProfileGraph/index.d.ts +84 -0
  114. package/dist/ui/elements/PcProfileGraph/index.js +317 -0
  115. package/dist/ui/elements/PcShortcutSettings/index.js +9 -13
  116. package/dist/ui/elements/PcSliderRange/index.d.ts +147 -0
  117. package/dist/ui/elements/PcSliderRange/index.js +307 -0
  118. package/dist/ui/elements/PcStats/index.d.ts +19 -4
  119. package/dist/ui/elements/PcStats/index.js +29 -13
  120. package/dist/ui/elements/PcThemePicker/index.d.ts +17 -15
  121. package/dist/ui/elements/PcThemePicker/index.js +22 -21
  122. package/dist/ui/elements/PcToolSettings/index.d.ts +48 -9
  123. package/dist/ui/elements/PcToolSettings/index.js +382 -318
  124. package/dist/ui/elements/PcToolSettings/style.js +1 -1
  125. package/dist/ui/elements/PcToolbar/index.d.ts +15 -0
  126. package/dist/ui/elements/PcToolbar/index.js +55 -14
  127. package/dist/ui/elements/PcToolbar/style.js +1 -1
  128. package/dist/ui/icons/images/icon-brush@2x.js +3 -0
  129. package/dist/ui/icons/images/icon-circle-mask@2x.js +3 -0
  130. package/dist/ui/icons/images/icon-clipboard@2x.js +3 -0
  131. package/dist/ui/icons/images/icon-cog@2x.js +3 -0
  132. package/dist/ui/icons/images/icon-cursor@2x.js +3 -0
  133. package/dist/ui/icons/images/icon-diagonal-tlbr-size@2x.js +3 -0
  134. package/dist/ui/icons/images/icon-diagonal-trbl-size@2x.js +3 -0
  135. package/dist/ui/icons/images/icon-eraser@2x.js +3 -0
  136. package/dist/ui/icons/images/icon-eyedropper@2x.js +3 -0
  137. package/dist/ui/icons/images/icon-flip-horizontal@2x.js +3 -0
  138. package/dist/ui/icons/images/icon-flip-vertical@2x.js +3 -0
  139. package/dist/ui/icons/images/icon-focus-pen@2x.js +3 -0
  140. package/dist/ui/icons/images/icon-freehand-mask@2x.js +3 -0
  141. package/dist/ui/icons/images/icon-hidden@2x.js +3 -0
  142. package/dist/ui/icons/images/icon-horizontal-size@2x.js +3 -0
  143. package/dist/ui/icons/images/icon-layers@2x.js +3 -0
  144. package/dist/ui/icons/images/icon-linked@2x.js +3 -0
  145. package/dist/ui/icons/images/icon-locked@2x.js +3 -0
  146. package/dist/ui/icons/images/icon-moon@2x.js +3 -0
  147. package/dist/ui/icons/images/icon-pan@2x.js +3 -0
  148. package/dist/ui/icons/images/icon-polygon-mask@2x.js +3 -0
  149. package/dist/ui/icons/images/icon-pressure@2x.js +3 -0
  150. package/dist/ui/icons/images/icon-profile-graph@2x.js +3 -0
  151. package/dist/ui/icons/images/icon-randomise@2x.js +3 -0
  152. package/dist/ui/icons/images/icon-rectangle-mask@2x.js +3 -0
  153. package/dist/ui/icons/images/icon-redo@2x.js +3 -0
  154. package/dist/ui/icons/images/icon-rotate-90-left@2x.js +3 -0
  155. package/dist/ui/icons/images/icon-rotate-90-right@2x.js +3 -0
  156. package/dist/ui/icons/images/icon-rotate@2x.js +3 -0
  157. package/dist/ui/icons/images/icon-smoothing-bezier-cubic@2x.js +3 -0
  158. package/dist/ui/icons/images/icon-smoothing-bezier-quad@2x.js +3 -0
  159. package/dist/ui/icons/images/icon-smoothing-linear@2x.js +3 -0
  160. package/dist/ui/icons/images/icon-star@2x.js +3 -0
  161. package/dist/ui/icons/images/icon-sun@2x.js +3 -0
  162. package/dist/ui/icons/images/icon-tilt@2x.js +3 -0
  163. package/dist/ui/icons/images/icon-undo@2x.js +3 -0
  164. package/dist/ui/icons/images/icon-unlinked@2x.js +3 -0
  165. package/dist/ui/icons/images/icon-unlocked@2x.js +3 -0
  166. package/dist/ui/icons/images/icon-vertical-size@2x.js +3 -0
  167. package/dist/ui/icons/images/icon-visible@2x.js +3 -0
  168. package/dist/ui/icons/images/icon-zoom-in@2x.js +3 -0
  169. package/dist/ui/icons/images/icon-zoom-out@2x.js +3 -0
  170. package/dist/ui/icons/images/icon-zoom@2x.js +3 -0
  171. package/dist/ui/icons/index.d.ts +1 -1
  172. package/dist/ui/icons/index.js +86 -72
  173. package/dist/ui/index.d.ts +13 -3
  174. package/dist/ui/index.js +15 -4
  175. package/dist/ui/layout/index.d.ts +9 -1
  176. package/dist/ui/layout/index.js +11 -2
  177. package/dist/ui/styles/index.d.ts +21 -1
  178. package/dist/ui/styles/index.js +73 -14
  179. package/dist/ui/tokens/index.d.ts +5 -4
  180. package/dist/ui/tokens/index.js +12 -7
  181. package/locales/en-GB.json +39 -2
  182. package/locales/en-US.json +39 -2
  183. package/locales/fr.json +39 -2
  184. package/locales/ja.json +39 -2
  185. package/locales/ko.json +39 -2
  186. package/locales/zh-Hans.json +39 -2
  187. package/locales/zh-Hant.json +39 -2
  188. package/package.json +27 -18
  189. package/dist/ui/icons/images/brush.js +0 -3
  190. package/dist/ui/icons/images/circle-mask.js +0 -3
  191. package/dist/ui/icons/images/clipboard.js +0 -3
  192. package/dist/ui/icons/images/cursor.js +0 -3
  193. package/dist/ui/icons/images/eraser.js +0 -3
  194. package/dist/ui/icons/images/eyedropper.js +0 -3
  195. package/dist/ui/icons/images/flip-horizontal.js +0 -3
  196. package/dist/ui/icons/images/flip-vertical.js +0 -3
  197. package/dist/ui/icons/images/focus-pen.js +0 -3
  198. package/dist/ui/icons/images/freehand-mask.js +0 -3
  199. package/dist/ui/icons/images/hidden.js +0 -3
  200. package/dist/ui/icons/images/layers.js +0 -3
  201. package/dist/ui/icons/images/linked.js +0 -3
  202. package/dist/ui/icons/images/locked.js +0 -3
  203. package/dist/ui/icons/images/moon.js +0 -3
  204. package/dist/ui/icons/images/pan.js +0 -3
  205. package/dist/ui/icons/images/polygon-mask.js +0 -3
  206. package/dist/ui/icons/images/pressure.js +0 -3
  207. package/dist/ui/icons/images/randomise.js +0 -3
  208. package/dist/ui/icons/images/rectangle-mask.js +0 -3
  209. package/dist/ui/icons/images/redo.js +0 -3
  210. package/dist/ui/icons/images/rotate-90-left.js +0 -3
  211. package/dist/ui/icons/images/rotate-90-right.js +0 -3
  212. package/dist/ui/icons/images/smoothing-bezier-cubic.js +0 -3
  213. package/dist/ui/icons/images/smoothing-bezier-quad.js +0 -3
  214. package/dist/ui/icons/images/smoothing-linear.js +0 -3
  215. package/dist/ui/icons/images/star.js +0 -3
  216. package/dist/ui/icons/images/sun.js +0 -3
  217. package/dist/ui/icons/images/tilt.js +0 -3
  218. package/dist/ui/icons/images/undo.js +0 -3
  219. package/dist/ui/icons/images/unlinked.js +0 -3
  220. package/dist/ui/icons/images/unlocked.js +0 -3
  221. package/dist/ui/icons/images/visible.js +0 -3
  222. package/dist/ui/icons/images/zoom-in.js +0 -3
  223. package/dist/ui/icons/images/zoom-out.js +0 -3
  224. package/dist/ui/icons/images/zoom.js +0 -3
@@ -7,6 +7,9 @@ import { Panel } from "../../../lib/classes/PaintCanvas/index.js";
7
7
  import "../../../lib/classes/BrushSettings/index.js";
8
8
  import { MAX_ZOOM, MIN_ZOOM } from "../../../lib/classes/Viewport/index.js";
9
9
  import { iconLabel } from "../../icons/index.js";
10
+ import "../PcBrushSize/index.js";
11
+ import "../PcNumberSlider/index.js";
12
+ import "../PcPopover/index.js";
10
13
  import { html, nothing, svg, unsafeCSS } from "lit";
11
14
  import { live } from "lit/directives/live.js";
12
15
  import { styleMap } from "lit/directives/style-map.js";
@@ -51,6 +54,10 @@ var withSource = (preset, params, source, on) => Object.fromEntries(params.map((
51
54
  * roundAngle(12.3456); // 12.35
52
55
  */
53
56
  var roundAngle = (degrees) => Math.round(degrees * 100) / 100;
57
+ /** Largest width or height the move tool's fields go to (pixels or percent) */
58
+ var MAX_MOVE_SIZE = 1e4;
59
+ /** Shown in a move tool field while there's nothing to move */
60
+ var NO_VALUE = "–";
54
61
  /** Steps of the zoom slider, across the whole zoom range */
55
62
  var ZOOM_SLIDER_STEPS = 1e3;
56
63
  /** How many times bigger the largest zoom is than the smallest */
@@ -81,19 +88,21 @@ var zoomPercent = (zoom) => {
81
88
  const percent = zoom * 100;
82
89
  return `${percent < 10 ? Math.round(percent * 10) / 10 : Math.round(percent)}%`;
83
90
  };
84
- /** Read a number input, or undefined if it isn't a number */
85
- var numberValue = (event) => {
86
- const value = event.target.valueAsNumber;
87
- return Number.isFinite(value) ? value : void 0;
88
- };
89
91
  /**
90
- * The bar of settings for the current tool, shown at the top of the canvas when the tool has any:
92
+ * The bar of settings for the current tool, shown at the top of the canvas when the tool has any. The tool's settings
93
+ * are at the start (part `start`); the end, on the right (part `end`), is for your own items. The `start` and `end`
94
+ * slots add your own items to each. (Cut, copy and paste are `<pc-clipboard-tools>`.)
91
95
  *
92
- * - Brush and eraser: the preset (its name and shape; shows or hides the preset menu), width, height, constrain proportions, pressure, tilt, opacity and "Configure" (fires
93
- * `pc-configure-brush` with the tool, "brush" or "eraser").
96
+ * - Brush and eraser: the preset (its name and shape; shows or hides the preset menu), the size (`pc-brush-size`), the
97
+ * size settings (a cog button that shows or hides a popover under it with pressure, tilt and focus pen mode above the
98
+ * width, constrain proportions and height), opacity and "Configure" (fires `pc-configure-brush` with the tool,
99
+ * "brush" or "eraser"). The size, width, height and opacity are `pc-number-slider`s: pressed and dragged right or up
100
+ * to raise them, left or down to lower them, or tapped to type a value. Focus pen mode: only pens draw, and touches
101
+ * are ignored while a pen is down or just lifted.
94
102
  * - Lassos: select all, and invert selection and deselect (only while there is a mask).
95
103
  * - Move, scale and rotate: width and height (in pixels or percent), constrain proportions, flip horizontal and vertical,
96
- * angle (to 2 decimal places), rotate 90° left and right, and reset (undoes the whole transform).
104
+ * angle (to 2 decimal places), rotate 90° left and right, and reset (undoes the whole transform). The width, height
105
+ * and angle are `pc-number-slider`s.
97
106
  * - Colour sample: point sample (the pixel under the pointer) or area sample (the average of a dragged rectangle).
98
107
  * - Zoom: zoom in and out, a slider across the zoom range (logarithmic) with the zoom as a percentage, and reset (100%),
99
108
  * all around the centre of the view.
@@ -101,13 +110,16 @@ var numberValue = (event) => {
101
110
  * Changes to the brush or eraser apply to the current settings only, not the preset. Styling is structural only.
102
111
  *
103
112
  * @example
104
- * html`<pc-tool-settings .app=${app}></pc-tool-settings>`
113
+ * html`<pc-tool-settings .app=${app}>
114
+ * <button slot="end" @click=${share}>Share</button>
115
+ * </pc-tool-settings>`
105
116
  */
106
117
  var PcToolSettings = class extends PcElement {
107
118
  static properties = {
108
119
  ...PcElement.properties,
109
120
  app: { attribute: false },
110
- percent: { state: true }
121
+ percent: { state: true },
122
+ sizeOpen: { state: true }
111
123
  };
112
124
  static styles = [
113
125
  PcElement.styles,
@@ -115,6 +127,7 @@ var PcToolSettings = class extends PcElement {
115
127
  unsafeCSS(PILLS_CSS),
116
128
  unsafeCSS(style_default)
117
129
  ];
130
+ placeSizeSettings = () => this.sizePopover?.placeBelow();
118
131
  changes = new ChangeController(this, () => {
119
132
  const app = this.app;
120
133
  return app ? [
@@ -130,6 +143,47 @@ var PcToolSettings = class extends PcElement {
130
143
  constructor() {
131
144
  super();
132
145
  this.percent = false;
146
+ this.sizeOpen = false;
147
+ }
148
+ disconnectedCallback() {
149
+ super.disconnectedCallback();
150
+ this.listenWhileSizeOpen(false);
151
+ }
152
+ /** The size settings' popover, while a brush or eraser's settings are shown */
153
+ get sizePopover() {
154
+ return this.renderRoot.querySelector("pc-popover.size-popover");
155
+ }
156
+ /** Follow the window's scrolling and resizing (or stop) */
157
+ listenWhileSizeOpen(open) {
158
+ const method = open ? "addEventListener" : "removeEventListener";
159
+ window[method]("scroll", this.placeSizeSettings, true);
160
+ window[method]("resize", this.placeSizeSettings);
161
+ }
162
+ /**
163
+ * Hide the size settings (e.g. on Escape or a press outside them), returning focus to their button if it was in them.
164
+ */
165
+ closeSizeSettings() {
166
+ const focusWasInside = this.sizePopover.matches(":focus-within");
167
+ this.sizeOpen = false;
168
+ if (focusWasInside) this.renderRoot.querySelector(".size-settings")?.focus();
169
+ }
170
+ willUpdate(changed) {
171
+ super.willUpdate(changed);
172
+ const tool = this.app?.tool;
173
+ if (tool !== Tool.Brush && tool !== Tool.Eraser) this.sizeOpen = false;
174
+ }
175
+ updated(changed) {
176
+ super.updated(changed);
177
+ if (!changed.has("sizeOpen")) return;
178
+ this.listenWhileSizeOpen(this.sizeOpen);
179
+ if (this.sizeOpen) this.showSizeSettings();
180
+ }
181
+ /** Put the opened size settings under their button, once they can be measured */
182
+ async showSizeSettings() {
183
+ const popover = this.sizePopover;
184
+ popover.anchor = this.renderRoot.querySelector(".size-settings");
185
+ await popover.updateComplete;
186
+ popover.placeBelow();
133
187
  }
134
188
  /** Fire an event that crosses shadow DOM boundaries */
135
189
  emit(type, detail) {
@@ -150,21 +204,45 @@ var PcToolSettings = class extends PcElement {
150
204
  });
151
205
  }
152
206
  /**
153
- * Render the focus pen mode toggle, on the right of every tool's settings: while on, only pens draw, and touches are
154
- * ignored while a pen is down or just lifted.
207
+ * Render the focus pen mode toggle, for the brush and eraser: while on, only pens draw, and touches are ignored while a
208
+ * pen is down or just lifted.
155
209
  */
156
210
  renderFocusPen(app) {
157
- return html`<label
158
- class="icon-toggle focus-pen"
211
+ const on = app.input.data.focusPen;
212
+ return html`<button
213
+ type="button"
214
+ class="focus-pen icon-button"
159
215
  title=${this.t("paintCanvas.toolSettings.focusPenHint")}
216
+ aria-label=${this.t("paintCanvas.toolSettings.focusPen")}
217
+ aria-pressed=${String(on)}
218
+ @click=${() => app.input.update({ focusPen: !on })}
160
219
  >
161
- <input
162
- type="checkbox"
163
- .checked=${live(app.input.data.focusPen)}
164
- @change=${(e) => app.input.update({ focusPen: e.target.checked })}
165
- />
166
- ${iconLabel(this.iconUrl("focusPen"), this.t("paintCanvas.toolSettings.focusPen"))}
167
- </label>`;
220
+ <img src=${this.iconUrl("focusPen")} alt="" />
221
+ </button>`;
222
+ }
223
+ /**
224
+ * Render a setting as a named `pc-number-slider` (dragged or typed), from 1 to 256 pixels unless given another range.
225
+ */
226
+ renderNumberField(name, label, value, change, { min = 1, max = 256, unit = "", step = 1, dragStep = step, text = "", disabled = false } = {}) {
227
+ return html`<div class="field">
228
+ <span aria-hidden="true">${label}</span>
229
+ <pc-number-slider
230
+ class=${name}
231
+ min=${min}
232
+ max=${max}
233
+ step=${step}
234
+ drag-step=${dragStep}
235
+ unit=${unit}
236
+ label=${label}
237
+ .text=${text}
238
+ ?disabled=${disabled}
239
+ .value=${live(value)}
240
+ @pc-change=${(e) => {
241
+ change(e.detail.value);
242
+ this.requestUpdate();
243
+ }}
244
+ ></pc-number-slider>
245
+ </div>`;
168
246
  }
169
247
  /** Render the brush or eraser settings */
170
248
  renderBrush(app, settings, kind) {
@@ -173,143 +251,136 @@ var PcToolSettings = class extends PcElement {
173
251
  const tilt = TILT_PARAMS.some((param) => preset[param]?.useTilt);
174
252
  const label = this.t(kind === "eraser" ? "paintCanvas.tool.eraser" : "paintCanvas.tool.brush");
175
253
  const panel = kind === "eraser" ? Panel.Eraser : Panel.Brush;
176
- return html`<div
177
- role="toolbar"
178
- aria-label=${this.t("paintCanvas.toolSettings.settings", { tool: label })}
179
- >
180
- <button
181
- type="button"
182
- class="preset"
183
- title=${this.t("paintCanvas.toolSettings.presets", { tool: label })}
184
- aria-expanded=${app.panel === panel ? "true" : "false"}
185
- @click=${() => app.togglePanel(panel)}
186
- >
187
- <span
188
- class=${preset.image ? "shape-preview image" : "shape-preview"}
189
- style=${styleMap({ maskImage: typeof preset.image === "string" ? `url("${preset.image}")` : void 0 })}
190
- ></span>
191
- ${preset.name ?? preset.id}
192
- </button>
193
- <label>
194
- ${this.t("paintCanvas.common.width")}
195
- <input
196
- type="number"
197
- class="width"
198
- min=${1}
199
- max=${256}
200
- .value=${String(settings.width)}
201
- @change=${(e) => {
202
- const value = numberValue(e);
203
- if (value !== void 0) settings.setSize(value, settings.height);
204
- }}
205
- />
206
- </label>
207
- <label
208
- class="icon-toggle"
209
- title=${this.t("paintCanvas.toolSettings.constrainProportions")}
254
+ const sizeSettings = this.t("paintCanvas.toolSettings.sizeSettings");
255
+ return {
256
+ label: this.t("paintCanvas.toolSettings.settings", { tool: label }),
257
+ start: html`<button
258
+ type="button"
259
+ class="preset"
260
+ data-panel=${panel}
261
+ title=${this.t("paintCanvas.toolSettings.presets", { tool: label })}
262
+ aria-expanded=${app.panel === panel ? "true" : "false"}
263
+ @click=${() => app.togglePanel(panel)}
264
+ >
265
+ <span
266
+ class=${preset.image ? "shape-preview image" : "shape-preview"}
267
+ style=${styleMap({ maskImage: typeof preset.image === "string" ? `url("${preset.image}")` : void 0 })}
268
+ ></span>
269
+ ${preset.name ?? preset.id}
270
+ </button>
271
+ <div class="field">
272
+ <span aria-hidden="true">${this.t("paintCanvas.common.size")}</span>
273
+ <pc-brush-size
274
+ class="size"
275
+ .settings=${settings}
276
+ label=${this.t("paintCanvas.brushMenu.size", { tool: label })}
277
+ ></pc-brush-size>
278
+ </div>
279
+ <button
280
+ type="button"
281
+ class="size-settings"
282
+ title=${sizeSettings}
283
+ aria-label=${sizeSettings}
284
+ aria-haspopup="dialog"
285
+ aria-expanded=${this.sizeOpen ? "true" : "false"}
286
+ @click=${() => this.sizeOpen = !this.sizeOpen}
287
+ >
288
+ <img src=${this.iconUrl("cog")} alt="" />
289
+ </button>
290
+
291
+ ${this.renderNumberField("opacity", this.t("paintCanvas.common.opacity"), Math.round((preset.alpha ?? 1) * 100), (value) => settings.update({ alpha: value / 100 }), {
292
+ min: 0,
293
+ max: 100,
294
+ unit: "%"
295
+ })}
296
+ <button
297
+ type="button"
298
+ class="configure pill-s"
299
+ @click=${() => this.emit("pc-configure-brush", kind)}
300
+ >
301
+ ${this.t("paintCanvas.toolSettings.configure")}
302
+ </button>`,
303
+ after: html`<pc-popover
304
+ class="size-popover"
305
+ arrow
306
+ label=${sizeSettings}
307
+ ?open=${this.sizeOpen}
308
+ @pc-close=${this.closeSizeSettings}
210
309
  >
211
- <input
212
- type="checkbox"
213
- class="constrain"
214
- .checked=${preset.constrainProportions ?? false}
215
- @change=${(e) => settings.update({ constrainProportions: e.target.checked })}
216
- />
217
- ${iconLabel(this.iconUrl(preset.constrainProportions ? "linked" : "unlinked"), this.t("paintCanvas.toolSettings.constrainProportions"))}
218
- </label>
219
- <label>
220
- ${this.t("paintCanvas.common.height")}
221
- <input
222
- type="number"
223
- class="height"
224
- min=${1}
225
- max=${256}
226
- .value=${String(settings.height)}
227
- @change=${(e) => {
228
- const value = numberValue(e);
229
- if (value !== void 0) settings.setHeight(value);
230
- }}
231
- />
232
- </label>
233
- <label
234
- class="icon-toggle"
235
- title=${this.t("paintCanvas.toolSettings.pressure")}
236
- >
237
- <input
238
- type="checkbox"
239
- class="pressure"
240
- .checked=${pressure}
241
- @change=${(e) => settings.update(withSource(preset, PRESSURE_PARAMS, "usePressure", e.target.checked))}
242
- />
243
- ${iconLabel(this.iconUrl("pressure"), this.t("paintCanvas.toolSettings.pressure"))}
244
- </label>
245
- <label
246
- class="icon-toggle"
247
- title=${this.t("paintCanvas.toolSettings.tilt")}
248
- >
249
- <input
250
- type="checkbox"
251
- class="tilt"
252
- .checked=${tilt}
253
- @change=${(e) => settings.update(withSource(preset, TILT_PARAMS, "useTilt", e.target.checked))}
254
- />
255
- ${iconLabel(this.iconUrl("tilt"), this.t("paintCanvas.toolSettings.tilt"))}
256
- </label>
257
- <label>
258
- ${this.t("paintCanvas.common.opacity")}
259
- <input
260
- type="number"
261
- class="opacity"
262
- min="0"
263
- max="100"
264
- .value=${String(Math.round((preset.alpha ?? 1) * 100))}
265
- @change=${(e) => {
266
- const value = numberValue(e);
267
- if (value !== void 0) settings.update({ alpha: Math.max(0, Math.min(100, value)) / 100 });
268
- }}
269
- />
270
- <span class="unit">%</span>
271
- </label>
272
- <button
273
- type="button"
274
- class="configure"
275
- @click=${() => this.emit("pc-configure-brush", kind)}
276
- >
277
- ${this.t("paintCanvas.toolSettings.configure")}
278
- </button>
279
- ${this.renderFocusPen(app)}
280
- </div>`;
310
+ <div class="size-toggles">
311
+ <label
312
+ class="icon-toggle"
313
+ title=${this.t("paintCanvas.toolSettings.pressure")}
314
+ >
315
+ <input
316
+ type="checkbox"
317
+ class="pressure"
318
+ .checked=${pressure}
319
+ @change=${(e) => settings.update(withSource(preset, PRESSURE_PARAMS, "usePressure", e.target.checked))}
320
+ />
321
+ ${iconLabel(this.iconUrl("pressure"), this.t("paintCanvas.toolSettings.pressure"))}
322
+ </label>
323
+ <label
324
+ class="icon-toggle"
325
+ title=${this.t("paintCanvas.toolSettings.tilt")}
326
+ >
327
+ <input
328
+ type="checkbox"
329
+ class="tilt"
330
+ .checked=${tilt}
331
+ @change=${(e) => settings.update(withSource(preset, TILT_PARAMS, "useTilt", e.target.checked))}
332
+ />
333
+ ${iconLabel(this.iconUrl("tilt"), this.t("paintCanvas.toolSettings.tilt"))}
334
+ </label>
335
+ ${this.renderFocusPen(app)}
336
+ </div>
337
+ <div class="size-fields">
338
+ ${this.renderNumberField("width", this.t("paintCanvas.common.width"), settings.width, (value) => settings.setSize(value, settings.height))}
339
+ <label
340
+ class="icon-toggle"
341
+ title=${this.t("paintCanvas.toolSettings.constrainProportions")}
342
+ >
343
+ <input
344
+ type="checkbox"
345
+ class="constrain"
346
+ .checked=${preset.constrainProportions ?? false}
347
+ @change=${(e) => settings.update({ constrainProportions: e.target.checked })}
348
+ />
349
+ ${iconLabel(this.iconUrl(preset.constrainProportions ? "linked" : "unlinked"), this.t("paintCanvas.toolSettings.constrainProportions"))}
350
+ </label>
351
+ ${this.renderNumberField("height", this.t("paintCanvas.common.height"), settings.height, (value) => settings.setHeight(value))}
352
+ </div>
353
+ </pc-popover>`
354
+ };
281
355
  }
282
356
  /** Render the mask settings */
283
357
  renderMask(app) {
284
- return html`<div
285
- role="toolbar"
286
- aria-label=${this.t("paintCanvas.toolSettings.maskSettings")}
287
- >
288
- <button
289
- type="button"
290
- class="pill select-all"
291
- @click=${() => app.selection.selectAll()}
292
- >
293
- ${this.t("paintCanvas.toolSettings.selectAll")}
294
- </button>
295
- <button
296
- type="button"
297
- class="pill invert"
298
- ?disabled=${!app.selection.active}
299
- @click=${() => app.selection.invert()}
300
- >
301
- ${this.t("paintCanvas.toolSettings.invertSelection")}
302
- </button>
303
- <button
304
- type="button"
305
- class="pill deselect"
306
- ?disabled=${!app.selection.active}
307
- @click=${() => app.selection.clear()}
308
- >
309
- ${this.t("paintCanvas.toolSettings.deselect")}
310
- </button>
311
- ${this.renderFocusPen(app)}
312
- </div>`;
358
+ return {
359
+ label: this.t("paintCanvas.toolSettings.maskSettings"),
360
+ start: html`<button
361
+ type="button"
362
+ class="pill-s select-all"
363
+ @click=${() => app.selection.selectAll()}
364
+ >
365
+ ${this.t("paintCanvas.toolSettings.selectAll")}
366
+ </button>
367
+ <button
368
+ type="button"
369
+ class="pill-s invert"
370
+ ?disabled=${!app.selection.active}
371
+ @click=${() => app.selection.invert()}
372
+ >
373
+ ${this.t("paintCanvas.toolSettings.invertSelection")}
374
+ </button>
375
+ <button
376
+ type="button"
377
+ class="pill-s deselect"
378
+ ?disabled=${!app.selection.active}
379
+ @click=${() => app.selection.clear()}
380
+ >
381
+ ${this.t("paintCanvas.toolSettings.deselect")}
382
+ </button>`
383
+ };
313
384
  }
314
385
  /**
315
386
  * Resize the move tool's box to a width or height (in the current unit), keeping flips, and the other side in
@@ -319,7 +390,6 @@ var PcToolSettings = class extends PcElement {
319
390
  const { box, source } = app.transform;
320
391
  if (!box || !source) return;
321
392
  const pixels = this.percent ? source[side] * value / 100 : value;
322
- if (pixels <= 0) return;
323
393
  const other = side === "width" ? "height" : "width";
324
394
  const ratio = Math.abs(box[other] / box[side]);
325
395
  app.transform.setBox({
@@ -328,118 +398,101 @@ var PcToolSettings = class extends PcElement {
328
398
  ...app.constrainProportions ? { [other]: Math.sign(box[other]) * pixels * ratio } : {}
329
399
  });
330
400
  }
331
- /** A box side in the current unit, for an input */
401
+ /** A box side in the current unit (pixels or percent) */
332
402
  sideValue(box, source, side) {
333
403
  const pixels = Math.abs(box[side]);
334
- return String(Math.round(this.percent ? pixels / source[side] * 100 : pixels));
404
+ return Math.round(this.percent ? pixels / source[side] * 100 : pixels);
335
405
  }
336
406
  /** Render the move, scale and rotate settings */
337
407
  renderMove(app) {
338
408
  const { box, source } = app.transform;
339
409
  const disabled = !box || !source;
340
- const side = (name, label) => html`<label>
341
- ${label}
342
- <input
343
- type="number"
344
- class=${name}
345
- min="1"
346
- ?disabled=${disabled}
347
- .value=${box && source ? this.sideValue(box, source, name) : ""}
348
- @change=${(e) => {
349
- const value = numberValue(e);
350
- if (value !== void 0) this.resizeBox(app, name, value);
351
- }}
352
- />
353
- </label>`;
410
+ const side = (name, label) => this.renderNumberField(name, label, box && source ? this.sideValue(box, source, name) : 0, (value) => this.resizeBox(app, name, value), {
411
+ max: MAX_MOVE_SIZE,
412
+ unit: this.percent ? "%" : "",
413
+ text: disabled ? NO_VALUE : "",
414
+ disabled
415
+ });
354
416
  const rotate = (by) => this.changeBox(app, (current) => ({ rotation: by(current.rotation) % 360 }));
355
- return html`<div
356
- role="toolbar"
357
- aria-label=${this.t("paintCanvas.toolSettings.settings", { tool: this.t("paintCanvas.tool.move") })}
358
- >
359
- ${side("width", this.t("paintCanvas.common.width"))}
360
- <label
361
- class="icon-toggle"
362
- title=${this.t("paintCanvas.toolSettings.constrainProportions")}
363
- >
364
- <input
365
- type="checkbox"
366
- class="constrain"
367
- .checked=${live(app.constrainProportions)}
368
- @change=${(e) => app.setConstrainProportions(e.target.checked)}
369
- />
370
- ${iconLabel(this.iconUrl(app.constrainProportions ? "linked" : "unlinked"), this.t("paintCanvas.toolSettings.constrainProportions"))}
371
- </label>
372
- ${side("height", this.t("paintCanvas.common.height"))}
373
- <label>
374
- <input
375
- type="checkbox"
376
- class="percent"
377
- .checked=${this.percent}
378
- @change=${(e) => this.percent = e.target.checked}
379
- />
380
- ${this.t("paintCanvas.toolSettings.percent")}
381
- </label>
382
- <button
383
- type="button"
384
- class="icon-button flip-horizontal"
385
- title=${this.t("paintCanvas.toolSettings.flipHorizontal")}
386
- ?disabled=${disabled}
387
- @click=${() => this.changeBox(app, (current) => ({ width: -current.width }))}
388
- >
389
- ${iconLabel(this.iconUrl("flipHorizontal"), this.t("paintCanvas.toolSettings.flipHorizontal"))}
390
- </button>
391
- <button
392
- type="button"
393
- class="icon-button flip-vertical"
394
- title=${this.t("paintCanvas.toolSettings.flipVertical")}
395
- ?disabled=${disabled}
396
- @click=${() => this.changeBox(app, (current) => ({ height: -current.height }))}
397
- >
398
- ${iconLabel(this.iconUrl("flipVertical"), this.t("paintCanvas.toolSettings.flipVertical"))}
399
- </button>
400
- <label>
401
- ${this.t("paintCanvas.toolSettings.angle")}
402
- <input
403
- type="number"
404
- class="angle"
405
- step="0.01"
417
+ return {
418
+ label: this.t("paintCanvas.toolSettings.settings", { tool: this.t("paintCanvas.tool.move") }),
419
+ start: html`${side("width", this.t("paintCanvas.common.width"))}
420
+ <label
421
+ class="icon-toggle"
422
+ title=${this.t("paintCanvas.toolSettings.constrainProportions")}
423
+ >
424
+ <input
425
+ type="checkbox"
426
+ class="constrain"
427
+ .checked=${live(app.constrainProportions)}
428
+ @change=${(e) => app.setConstrainProportions(e.target.checked)}
429
+ />
430
+ ${iconLabel(this.iconUrl(app.constrainProportions ? "linked" : "unlinked"), this.t("paintCanvas.toolSettings.constrainProportions"))}
431
+ </label>
432
+ ${side("height", this.t("paintCanvas.common.height"))}
433
+ <label>
434
+ <input
435
+ type="checkbox"
436
+ class="percent"
437
+ .checked=${this.percent}
438
+ @change=${(e) => this.percent = e.target.checked}
439
+ />
440
+ ${this.t("paintCanvas.toolSettings.percent")}
441
+ </label>
442
+ <button
443
+ type="button"
444
+ class="icon-button flip-horizontal"
445
+ title=${this.t("paintCanvas.toolSettings.flipHorizontal")}
406
446
  ?disabled=${disabled}
407
- .value=${box ? String(roundAngle(box.rotation)) : ""}
408
- @change=${(e) => {
409
- const value = numberValue(e);
410
- if (value !== void 0) rotate(() => roundAngle(value));
411
- }}
412
- />
413
- <span class="unit">°</span>
414
- </label>
415
- <button
416
- type="button"
417
- class="icon-button rotate-left"
418
- title=${this.t("paintCanvas.toolSettings.rotateLeft")}
419
- ?disabled=${disabled}
420
- @click=${() => rotate((rotation) => rotation - 90)}
421
- >
422
- ${iconLabel(this.iconUrl("rotate90Left"), this.t("paintCanvas.toolSettings.rotateLeft"))}
423
- </button>
424
- <button
425
- type="button"
426
- class="icon-button rotate-right"
427
- title=${this.t("paintCanvas.toolSettings.rotateRight")}
428
- ?disabled=${disabled}
429
- @click=${() => rotate((rotation) => rotation + 90)}
430
- >
431
- ${iconLabel(this.iconUrl("rotate90Right"), this.t("paintCanvas.toolSettings.rotateRight"))}
432
- </button>
433
- <button
434
- type="button"
435
- class="pill reset"
436
- ?disabled=${disabled}
437
- @click=${() => app.cancelTransform()}
438
- >
439
- ${this.t("paintCanvas.toolSettings.reset")}
440
- </button>
441
- ${this.renderFocusPen(app)}
442
- </div>`;
447
+ @click=${() => this.changeBox(app, (current) => ({ width: -current.width }))}
448
+ >
449
+ ${iconLabel(this.iconUrl("flipHorizontal"), this.t("paintCanvas.toolSettings.flipHorizontal"))}
450
+ </button>
451
+ <button
452
+ type="button"
453
+ class="icon-button flip-vertical"
454
+ title=${this.t("paintCanvas.toolSettings.flipVertical")}
455
+ ?disabled=${disabled}
456
+ @click=${() => this.changeBox(app, (current) => ({ height: -current.height }))}
457
+ >
458
+ ${iconLabel(this.iconUrl("flipVertical"), this.t("paintCanvas.toolSettings.flipVertical"))}
459
+ </button>
460
+ ${this.renderNumberField("angle", this.t("paintCanvas.toolSettings.angle"), box ? roundAngle(box.rotation) : 0, (value) => rotate(() => roundAngle(value)), {
461
+ min: -360,
462
+ max: 360,
463
+ step: .01,
464
+ dragStep: 1,
465
+ unit: "°",
466
+ text: disabled ? NO_VALUE : "",
467
+ disabled
468
+ })}
469
+ <button
470
+ type="button"
471
+ class="icon-button rotate-left"
472
+ title=${this.t("paintCanvas.toolSettings.rotateLeft")}
473
+ ?disabled=${disabled}
474
+ @click=${() => rotate((rotation) => rotation - 90)}
475
+ >
476
+ ${iconLabel(this.iconUrl("rotate90Left"), this.t("paintCanvas.toolSettings.rotateLeft"))}
477
+ </button>
478
+ <button
479
+ type="button"
480
+ class="icon-button rotate-right"
481
+ title=${this.t("paintCanvas.toolSettings.rotateRight")}
482
+ ?disabled=${disabled}
483
+ @click=${() => rotate((rotation) => rotation + 90)}
484
+ >
485
+ ${iconLabel(this.iconUrl("rotate90Right"), this.t("paintCanvas.toolSettings.rotateRight"))}
486
+ </button>
487
+ <button
488
+ type="button"
489
+ class="pill-s reset"
490
+ ?disabled=${disabled}
491
+ @click=${() => app.cancelTransform()}
492
+ >
493
+ ${this.t("paintCanvas.toolSettings.reset")}
494
+ </button>`
495
+ };
443
496
  }
444
497
  /** Render the zoom settings */
445
498
  renderZoom(app) {
@@ -448,52 +501,49 @@ var PcToolSettings = class extends PcElement {
448
501
  x: view.width / 2,
449
502
  y: view.height / 2
450
503
  };
451
- return html`<div
452
- role="toolbar"
453
- aria-label=${this.t("paintCanvas.toolSettings.settings", { tool: this.t("paintCanvas.tool.zoom") })}
454
- >
455
- <button
456
- type="button"
457
- class="zoom-in"
458
- title=${this.t("paintCanvas.toolSettings.zoomIn")}
459
- aria-label=${this.t("paintCanvas.toolSettings.zoomIn")}
460
- ?disabled=${view.zoom >= MAX_ZOOM}
461
- @click=${() => view.zoomIn(centre)}
462
- >
463
- <img src=${this.iconUrl("zoomIn")} alt="" />
464
- </button>
465
- <button
466
- type="button"
467
- class="zoom-out"
468
- title=${this.t("paintCanvas.toolSettings.zoomOut")}
469
- aria-label=${this.t("paintCanvas.toolSettings.zoomOut")}
470
- ?disabled=${view.zoom <= MIN_ZOOM}
471
- @click=${() => view.zoomOut(centre)}
472
- >
473
- <img src=${this.iconUrl("zoomOut")} alt="" />
474
- </button>
475
- <label>
476
- ${this.t("paintCanvas.tool.zoom")}
477
- <input
478
- type="range"
479
- class="zoom"
480
- min="0"
481
- max=${ZOOM_SLIDER_STEPS}
482
- step="1"
483
- .value=${String(zoomToSlider(view.zoom))}
484
- @input=${(e) => view.zoomAt(centre, sliderToZoom(e.target.valueAsNumber))}
485
- />
486
- <output>${zoomPercent(view.zoom)}</output>
487
- </label>
488
- <button
489
- type="button"
490
- class="pill reset"
491
- @click=${() => view.zoomAt(centre, 1)}
492
- >
493
- ${this.t("paintCanvas.toolSettings.reset")}
494
- </button>
495
- ${this.renderFocusPen(app)}
496
- </div>`;
504
+ return {
505
+ label: this.t("paintCanvas.toolSettings.settings", { tool: this.t("paintCanvas.tool.zoom") }),
506
+ start: html`<button
507
+ type="button"
508
+ class="zoom-in"
509
+ title=${this.t("paintCanvas.toolSettings.zoomIn")}
510
+ aria-label=${this.t("paintCanvas.toolSettings.zoomIn")}
511
+ ?disabled=${view.zoom >= MAX_ZOOM}
512
+ @click=${() => view.zoomIn(centre)}
513
+ >
514
+ <img src=${this.iconUrl("zoomIn")} alt="" />
515
+ </button>
516
+ <button
517
+ type="button"
518
+ class="zoom-out"
519
+ title=${this.t("paintCanvas.toolSettings.zoomOut")}
520
+ aria-label=${this.t("paintCanvas.toolSettings.zoomOut")}
521
+ ?disabled=${view.zoom <= MIN_ZOOM}
522
+ @click=${() => view.zoomOut(centre)}
523
+ >
524
+ <img src=${this.iconUrl("zoomOut")} alt="" />
525
+ </button>
526
+ <label>
527
+ ${this.t("paintCanvas.tool.zoom")}
528
+ <input
529
+ type="range"
530
+ class="zoom"
531
+ min="0"
532
+ max=${ZOOM_SLIDER_STEPS}
533
+ step="1"
534
+ .value=${String(zoomToSlider(view.zoom))}
535
+ @input=${(e) => view.zoomAt(centre, sliderToZoom(e.target.valueAsNumber))}
536
+ />
537
+ <output>${zoomPercent(view.zoom)}</output>
538
+ </label>
539
+ <button
540
+ type="button"
541
+ class="pill-s reset"
542
+ @click=${() => view.zoomAt(centre, 1)}
543
+ >
544
+ ${this.t("paintCanvas.toolSettings.reset")}
545
+ </button>`
546
+ };
497
547
  }
498
548
  /** Render the colour sample tool's settings: point or area sampling */
499
549
  renderColourSample(app) {
@@ -506,11 +556,9 @@ var PcToolSettings = class extends PcElement {
506
556
  this.t("paintCanvas.toolSettings.areaSample"),
507
557
  svg`<defs><linearGradient id="area-gradient"><stop offset="0" stop-color="currentColor" stop-opacity="0" /><stop offset="1" stop-color="currentColor" /></linearGradient></defs><rect x="2" y="4" width="16" height="12" fill="url(#area-gradient)" />`
508
558
  ]];
509
- return html`<div
510
- role="toolbar"
511
- aria-label=${this.t("paintCanvas.toolSettings.settings", { tool: this.t("paintCanvas.tool.colourSample") })}
512
- >
513
- ${modes.map(([mode, label, icon]) => html`<button
559
+ return {
560
+ label: this.t("paintCanvas.toolSettings.settings", { tool: this.t("paintCanvas.tool.colourSample") }),
561
+ start: html`${modes.map(([mode, label, icon]) => html`<button
514
562
  type="button"
515
563
  class="sample-${mode}"
516
564
  title=${label}
@@ -529,22 +577,38 @@ var PcToolSettings = class extends PcElement {
529
577
  >
530
578
  ${icon}
531
579
  </svg>
532
- </button>`)}
533
- ${this.renderFocusPen(app)}
534
- </div>`;
580
+ </button>`)}`
581
+ };
535
582
  }
536
- render() {
537
- const app = this.app;
538
- if (!app) return nothing;
583
+ /** The current tool's settings, if it has any */
584
+ toolView(app) {
539
585
  switch (app.tool) {
540
586
  case Tool.Brush: return this.renderBrush(app, app.brush, "brush");
541
587
  case Tool.Eraser: return this.renderBrush(app, app.eraser, "eraser");
542
588
  case Tool.Move: return this.renderMove(app);
543
589
  case Tool.Zoom: return this.renderZoom(app);
544
590
  case Tool.ColourSample: return this.renderColourSample(app);
545
- default: return MASK_TOOLS.includes(app.tool) ? this.renderMask(app) : nothing;
591
+ default: return MASK_TOOLS.includes(app.tool) ? this.renderMask(app) : void 0;
546
592
  }
547
593
  }
594
+ render() {
595
+ const app = this.app;
596
+ const view = app && this.toolView(app);
597
+ if (!view) return nothing;
598
+ return html`<div
599
+ role="toolbar"
600
+ aria-label=${view.label}
601
+ @scroll=${this.placeSizeSettings}
602
+ >
603
+ <div class="start" part="start">
604
+ ${view.start}<slot name="start"></slot>
605
+ </div>
606
+ <div class="end" part="end">
607
+ <slot name="end"></slot>
608
+ </div>
609
+ </div>
610
+ ${view.after ?? nothing}`;
611
+ }
548
612
  };
549
613
  defineElement("pc-tool-settings", PcToolSettings);
550
- export { PcToolSettings, ZOOM_SLIDER_STEPS, hasToolSettings, roundAngle, sliderToZoom, withSource, zoomPercent, zoomToSlider };
614
+ export { MAX_MOVE_SIZE, PcToolSettings, ZOOM_SLIDER_STEPS, hasToolSettings, roundAngle, sliderToZoom, withSource, zoomPercent, zoomToSlider };