Skip to content

Commit 3873811

Browse files
committed
Fix various bugs.
1 parent 63a6c86 commit 3873811

File tree

4 files changed

+202
-4
lines changed

4 files changed

+202
-4
lines changed

src/pg/inputPixelEditor/__examples__/basic/basic.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<div class="example">
2-
<pg-input-pixel-editor part="input" width="10" height="10" size="10"></pg-input-pixel-editor>
2+
<pg-input-pixel-editor part="input" width="10" height="10" size="10" style="box-shadow:0 0 1rem rgba(0, 0, 0, 0.25)"></pg-input-pixel-editor>
33
<div>
44
<code>onchange: <span part="value1"></span></code>
55
</div>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
:host {
2+
display: inline-flex;
3+
}
4+
5+
canvas {
6+
touch-action: none;
7+
}
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
<canvas part="canvas"></canvas>
1+
<canvas part="canvas" tabindex="0" draggable="false"></canvas>

src/pg/inputPixelEditor/inputPixelEditor.ts

Lines changed: 193 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ import iterateGrid from './utils/interateGrid';
1414
import bitmaskToPath from './utils/bitmapToMask';
1515
import createLayer from './utils/createLayer';
1616

17+
type Pixel = { x: number, y: number };
18+
1719
@Component({
1820
selector: 'pg-input-pixel-editor',
1921
style,
@@ -58,10 +60,42 @@ export default class PgInputPixelEditor extends HTMLElement {
5860
if (context === null) { return; }
5961
this.#context = context;
6062
// Wire Up Events
63+
this.$canvas.addEventListener(
64+
'contextMenu',
65+
this.handleContextMenu.bind(this)
66+
);
67+
this.$canvas.addEventListener(
68+
'doubleClick',
69+
this.handleDoubleClick.bind(this)
70+
);
6171
this.$canvas.addEventListener(
6272
'pointerdown',
6373
this.handlePointerDown.bind(this)
6474
);
75+
this.$canvas.addEventListener(
76+
'pointerup',
77+
this.handlePointerUp.bind(this)
78+
);
79+
this.$canvas.addEventListener(
80+
'pointermove',
81+
this.handlePointerMove.bind(this)
82+
);
83+
this.$canvas.addEventListener(
84+
'pointerenter',
85+
this.handlePointerEnter.bind(this)
86+
);
87+
this.$canvas.addEventListener(
88+
'pointerleave',
89+
this.handlePointerLeave.bind(this)
90+
);
91+
this.$canvas.addEventListener(
92+
'keydown',
93+
this.handleKeyDown.bind(this)
94+
);
95+
this.$canvas.addEventListener(
96+
'keyup',
97+
this.handleKeyUp.bind(this)
98+
);
6599
}
66100

67101
render(changes) {
@@ -76,6 +110,7 @@ export default class PgInputPixelEditor extends HTMLElement {
76110
const actualHeight = this.height * totalSize - this.gridSize;
77111
this.$canvas.width = actualWidth;
78112
this.$canvas.height = actualHeight;
113+
this.#data = fillGrid(this.width, this.height);
79114
[this.#baseLayer, this.#baseLayerContext] = createLayer(actualWidth, actualHeight);
80115
[this.#editLayer, this.#editLayerContext] = createLayer(actualWidth, actualHeight);
81116
[this.#noEditLayer, this.#noEditLayerContext] = createLayer(actualWidth, actualHeight);
@@ -96,7 +131,7 @@ export default class PgInputPixelEditor extends HTMLElement {
96131
#delayTimerId: number = 0;
97132
#delayedChange() {
98133
clearInterval(this.#delayTimerId);
99-
this.#delayTimerId = window.setTimeout(this.#handleChange, 1000);
134+
this.#delayTimerId = window.setTimeout(this.#handleChange.bind(this), 1000);
100135
};
101136

102137
#setPixel (x: number, y: number, color: number) {
@@ -144,7 +179,72 @@ export default class PgInputPixelEditor extends HTMLElement {
144179
// Verify this is the only place setting pixel data!
145180
this.#data[y][x] = color;
146181
this.#delayedChange();
147-
};
182+
}
183+
184+
#setPreview(pixels: Pixel[], previousX: number, previousY: number) {
185+
const totalSize = this.size + this.gridSize;
186+
const actualWidth = this.width * totalSize - this.gridSize;
187+
const actualHeight = this.height * totalSize - this.gridSize;
188+
const { minX, maxX, minY, maxY } = pixels.reduce((previous, current) => {
189+
return {
190+
minX: Math.min(previous.minX, current.x, previousX),
191+
maxX: Math.max(previous.maxX, current.x, previousX),
192+
minY: Math.min(previous.minY, current.y, previousY),
193+
maxY: Math.max(previous.maxY, current.y, previousY)
194+
};
195+
}, { minX: this.width, maxX: 0, minY: this.height, maxY: 0 });
196+
// base layer to main canvas
197+
this.#context.drawImage(
198+
this.#baseLayer,
199+
minX * totalSize, minY * totalSize, maxX * totalSize, maxY * totalSize,
200+
minX * totalSize, minY * totalSize, maxX * totalSize, maxY * totalSize
201+
);
202+
// edit to main canvas
203+
this.#context.drawImage(
204+
this.#editLayer,
205+
minX * totalSize, minY * totalSize, maxX * totalSize, maxY * totalSize,
206+
minX * totalSize, minY * totalSize, maxX * totalSize, maxY * totalSize
207+
);
208+
// preview layer
209+
this.#previewLayerContext.clearRect(0, 0, actualWidth, actualHeight);
210+
pixels.forEach(({ x, y }) => {
211+
this.#previewLayerContext.fillStyle = WHITE;
212+
this.#previewLayerContext.beginPath();
213+
this.#previewLayerContext.arc(x * totalSize + 5, y * totalSize + 5, 3, 0, 2 * Math.PI);
214+
this.#previewLayerContext.closePath();
215+
this.#previewLayerContext.fill();
216+
this.#previewLayerContext.fillStyle = '#1B79C8';
217+
this.#previewLayerContext.beginPath();
218+
this.#previewLayerContext.arc(x * totalSize + 5, y * totalSize + 5, 2, 0, 2 * Math.PI);
219+
this.#previewLayerContext.closePath();
220+
this.#previewLayerContext.fill();
221+
});
222+
// preview layer to main canvas
223+
this.#context.drawImage(
224+
this.#previewLayer,
225+
minX * totalSize, minY * totalSize, maxX * totalSize, maxY * totalSize,
226+
minX * totalSize, minY * totalSize, maxX * totalSize, maxY * totalSize
227+
);
228+
console.log('render preview', minX, minY, maxX, maxY);
229+
}
230+
231+
handleKeyDown(event: KeyboardEvent) {
232+
if (event.key === ' ') {
233+
console.log('space!')
234+
}
235+
}
236+
237+
handleKeyUp(event: KeyboardEvent) {
238+
239+
}
240+
241+
handleContextMenu(event: MouseEvent) {
242+
event?.preventDefault();
243+
}
244+
245+
handleDoubleClick(event: MouseEvent) {
246+
event?.preventDefault();
247+
}
148248

149249
handlePointerDown(event: MouseEvent) {
150250
if (event.buttons !== 1 && event.buttons !== 32) {
@@ -227,6 +327,97 @@ export default class PgInputPixelEditor extends HTMLElement {
227327
this.#isPressed = false;
228328
}
229329

330+
handlePointerMove(event: PointerEvent) {
331+
const canvas = this.$canvas;
332+
if (this.#isPressed) {
333+
const data = this.#data;
334+
const rect = canvas.getBoundingClientRect();
335+
const totalSize = this.size + this.gridSize;
336+
const points: [number, number][] = [];
337+
const startX = this.#startX;
338+
const startY = this.#startY;
339+
const x = this.#x;
340+
const y = this.#y;
341+
// If supported get all the inbetween points
342+
// really noticable for pen support + pencil tool
343+
if (typeof event.getCoalescedEvents === 'function') {
344+
const events = event.getCoalescedEvents();
345+
for (const evt of events) {
346+
let tX = Math.floor((evt.clientX - rect.left) / totalSize);
347+
let tY = Math.floor((evt.clientY - rect.top) / totalSize);
348+
if (tX >= this.width || tY >= this.height || (tX === x && tY === y)) {
349+
continue;
350+
}
351+
points.push([tX, tY]);
352+
}
353+
} else {
354+
let newX = Math.floor((event.clientX - rect.left) / totalSize);
355+
let newY = Math.floor((event.clientY - rect.top) / totalSize);
356+
if (newX === x && newY === y) { return; }
357+
if (newX >= this.width) { newX = this.width - 1; }
358+
if (newY >= this.height) { newY = this.height - 1; }
359+
points.push([newX, newY]);
360+
}
361+
// Is Eraser
362+
const color = event.buttons === 32 ? 0 : 1;
363+
// Shape tools only care about the last point
364+
if (points.length === 0) { return; }
365+
const [lastX, lastY] = points.at(-1) as [number, number];
366+
// This is not ideal, but might be good enough,
367+
// really it should be finding the point furthest absolute
368+
// point from startX/startY.
369+
this.#x = lastX;
370+
this.#y = lastY;
371+
switch (this.#inputMode) {
372+
case InputMode.Pixel:
373+
for (var point of points) {
374+
this.#setPixel(point[0], point[1], color);
375+
data[point[1]][point[0]] = color;
376+
}
377+
break;
378+
case InputMode.Line:
379+
console.log(x, y)
380+
this.#setPreview(getLinePixels(startX, startY, lastX, lastY), x, y);
381+
break;
382+
case InputMode.Rectangle:
383+
this.#setPreview(getRectanglePixels(startX, startY, lastX, lastY), x, y);
384+
break;
385+
case InputMode.RectangleOutline:
386+
this.#setPreview(getRectangleOutlinePixels(startX, startY, lastX, lastY), x, y);
387+
break;
388+
case InputMode.Ellipse:
389+
this.#setPreview(getEllipseOutlinePixels(startX, startY, lastX, lastY), x, y);
390+
break;
391+
case InputMode.EllipseOutline:
392+
this.#setPreview(getEllipseOutlinePixels(startX, startY, lastX, lastY), x, y);
393+
break;
394+
}
395+
}
396+
}
397+
398+
handlePointerEnter(event: MouseEvent) {
399+
console.log('hmmmm')
400+
if (!this.#isPressed && !this.#isEditing) {
401+
this.#isEditing = true;
402+
// base layer to main canvas
403+
this.#context.drawImage(this.#baseLayer, 0, 0);
404+
// editing layer to main canvas
405+
this.#context.drawImage(this.#isEditing ? this.#editLayer : this.#noEditLayer, 0, 0);
406+
}
407+
console.log('enter');
408+
}
409+
410+
handlePointerLeave(event: MouseEvent) {
411+
if (!this.#isPressed) {
412+
this.#isEditing = false;
413+
// base layer to main canvas
414+
this.#context.drawImage(this.#baseLayer, 0, 0);
415+
// editing layer to main canvas
416+
this.#context.drawImage(this.#isEditing ? this.#editLayer : this.#noEditLayer, 0, 0);
417+
}
418+
console.log('leave');
419+
}
420+
230421
#updateGrid() {
231422
// base layer to main canvas
232423
this.#context.drawImage(this.#baseLayer, 0, 0);

0 commit comments

Comments
 (0)