Skip to content

Commit 79066b3

Browse files
committed
Layer support
1 parent 946cddd commit 79066b3

File tree

1 file changed

+111
-40
lines changed

1 file changed

+111
-40
lines changed

src/pg/inputPixelEditor/inputPixelEditor.ts

Lines changed: 111 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,49 @@ import diffGrid from './utils/diffGrid';
1717

1818
type Pixel = { x: number, y: number };
1919

20+
enum HistoryType {
21+
Group,
22+
Pixel,
23+
ColorUpdate,
24+
ColorAdd,
25+
ColorRemove,
26+
LayerAdd,
27+
LayerRemove,
28+
LayerName,
29+
LayerLock,
30+
LayerUnlock,
31+
LayerExport,
32+
LayerVisible,
33+
LayerOpacity
34+
}
35+
36+
type HistoryGroupType = {
37+
name: string
38+
}
39+
40+
type HistoryPixelType = {
41+
pixels: [number, number, number][],
42+
layer: number
43+
}
44+
45+
type HistoryColorUpdateType = {
46+
color: [number, number, number, number],
47+
index: number
48+
}
49+
50+
type History = {
51+
type: HistoryType,
52+
data: HistoryGroupType | HistoryPixelType | HistoryColorUpdateType
53+
}
54+
55+
type Layer = {
56+
name: string,
57+
visible: boolean,
58+
locked: boolean,
59+
opacity: number,
60+
export: boolean
61+
}
62+
2063
@Component({
2164
selector: 'pg-input-pixel-editor',
2265
style,
@@ -27,7 +70,6 @@ export default class PgInputPixelEditor extends HTMLElement {
2770
@Prop(normalizeInt) height: number = 10;
2871
@Prop(normalizeInt) size: number = 10;
2972
@Prop(normalizeInt) gridSize: number = 1;
30-
@Prop() value: string = '';
3173
@Prop() placeholder: string = '';
3274

3375
@Part() $canvas: HTMLCanvasElement;
@@ -41,12 +83,14 @@ export default class PgInputPixelEditor extends HTMLElement {
4183
#startY: number = -1;
4284
#x: number = -1;
4385
#y: number = -1;
86+
#layer: number = 0;
87+
#layers: Layer[] = [];
4488
#isCtrl: boolean = false;
4589
#isShift: boolean = false;
4690
#isAlt: boolean = false;
47-
#data: number[][] = [];
48-
#undoHistory: [number, number, number][][] = [];
49-
#redoHistory: [number, number, number][][] = [];
91+
#data: number[][][] = [];
92+
#undoHistory: History[] = [];
93+
#redoHistory: History[] = [];
5094
#context: CanvasRenderingContext2D;
5195
#colors: string[] = ['transparent', '#000'];
5296
#baseLayer: HTMLCanvasElement;
@@ -114,15 +158,23 @@ export default class PgInputPixelEditor extends HTMLElement {
114158
const actualHeight = this.height * totalSize - this.gridSize;
115159
this.$canvas.width = actualWidth;
116160
this.$canvas.height = actualHeight;
117-
this.#data = fillGrid(this.width, this.height);
161+
this.#layer = 0;
162+
this.#layers = [{
163+
name: 'Layer 1',
164+
export: true,
165+
locked: false,
166+
visible: true,
167+
opacity: 1
168+
}];
169+
this.#data = [fillGrid(this.width, this.height)];
118170
[this.#baseLayer, this.#baseLayerContext] = createLayer(actualWidth, actualHeight);
119171
[this.#editLayer, this.#editLayerContext] = createLayer(actualWidth, actualHeight);
120172
[this.#noEditLayer, this.#noEditLayerContext] = createLayer(actualWidth, actualHeight);
121173
[this.#previewLayer, this.#previewLayerContext] = createLayer(actualWidth, actualHeight);
122174
}
123175

124176
#handleChange() {
125-
const path = bitmaskToPath(this.#data, { scale: 1 });
177+
const path = bitmaskToPath(this.#data[this.#layer], { scale: 1 });
126178
console.log('change:', path);
127179
this.dispatchEvent(new CustomEvent('change', {
128180
detail: { value: path }
@@ -138,7 +190,7 @@ export default class PgInputPixelEditor extends HTMLElement {
138190
this.#delayTimerId = window.setTimeout(this.#handleChange.bind(this), 1000);
139191
};
140192

141-
#setPixel (x: number, y: number, color: number) {
193+
#setPixel(x: number, y: number, color: number) {
142194
if (x > this.width) {
143195
throw new Error(`Invalid x; ${x} > ${this.width}`);
144196
}
@@ -179,9 +231,9 @@ export default class PgInputPixelEditor extends HTMLElement {
179231
x * totalSize, y * totalSize, this.size + 2, this.size + 2,
180232
x * totalSize, y * totalSize, this.size + 2, this.size + 2
181233
);
182-
console.log('draw pixel(x, y, color, data):', x, y, color, this.#data[y][x]);
234+
console.log('draw pixel(x, y, color, data):', x, y, color, this.#data[this.#layer][y][x]);
183235
// Verify this is the only place setting pixel data!
184-
this.#data[y][x] = color;
236+
this.#data[this.#layer][y][x] = color;
185237
this.#delayedChange();
186238
}
187239

@@ -289,7 +341,7 @@ export default class PgInputPixelEditor extends HTMLElement {
289341
if (newX >= this.width) { newX = this.width - 1; }
290342
if (newY >= this.height) { newY = this.height - 1; }
291343
this.#isPressed = true;
292-
this.#startColor = this.#data[newY][newX];
344+
this.#startColor = this.#data[this.#layer][newY][newX];
293345
this.#startX = newX;
294346
this.#startY = newY;
295347
this.#x = newX;
@@ -299,7 +351,7 @@ export default class PgInputPixelEditor extends HTMLElement {
299351
switch (this.#inputMode) {
300352
case InputMode.Pixel:
301353
this.#setPixel(newX, newY, color);
302-
this.#data[newY][newX] = color;
354+
this.#data[this.#layer][newY][newX] = color;
303355
break;
304356
}
305357
console.log(this.#inputMode, newX, newY);
@@ -320,7 +372,7 @@ export default class PgInputPixelEditor extends HTMLElement {
320372
switch (this.#inputMode) {
321373
case InputMode.Pixel:
322374
this.#setPixel(newX, newY, 0);
323-
this.#data[newY][newX] = 0;
375+
this.#data[this.#layer][newY][newX] = 0;
324376
break;
325377
}
326378
} else {
@@ -405,7 +457,7 @@ export default class PgInputPixelEditor extends HTMLElement {
405457
case InputMode.Pixel:
406458
for (var point of points) {
407459
this.#setPixel(point[0], point[1], color);
408-
data[point[1]][point[0]] = color;
460+
data[this.#layer][point[1]][point[0]] = color;
409461
}
410462
break;
411463
case InputMode.Line:
@@ -460,99 +512,118 @@ export default class PgInputPixelEditor extends HTMLElement {
460512
// ToDo: Code this
461513
}
462514
clear() {
463-
this.#data = fillGrid(this.width, this.height);
515+
const gridEmpty = fillGrid(this.width, this.height);
516+
const diff = diffGrid(this.#data[this.#layer], gridEmpty);
517+
this.#undoHistory.push({
518+
type: HistoryType.Group,
519+
data: {
520+
name: 'Clear'
521+
}
522+
});
523+
this.#undoHistory.push({
524+
type: HistoryType.Pixel,
525+
data: {
526+
pixels: [],
527+
layer: this.#layer
528+
}
529+
});
530+
this.#data = [fillGrid(this.width, this.height)];
464531
this.#updateGrid();
465532
}
466533
clearHistory() {
467534
this.#undoHistory = [];
468535
this.#redoHistory = [];
469536
}
470537
applyTemplate(template: number[][]) {
471-
this.#data = template;
538+
this.#data = [template];
472539
}
473540
flipHorizontal() {
474-
const cloned = cloneGrid(this.#data);
541+
const cloned = cloneGrid(this.#data[this.#layer]);
475542
const w = cloned[0].length - 1;
476-
iterateGrid(this.#data, (x, y) => {
477-
cloned[y][x] = this.#data[y][w - x];
543+
iterateGrid(this.#data[this.#layer], (x, y) => {
544+
cloned[y][x] = this.#data[this.#layer][y][w - x];
478545
});
479-
this.#data = cloned;
546+
this.#data[this.#layer] = cloned;
480547
}
481548
flipVertical() {
482-
const cloned = cloneGrid(this.#data);
549+
const cloned = cloneGrid(this.#data[this.#layer]);
483550
const h = cloned.length - 1;
484-
iterateGrid(this.#data, (x, y) => {
485-
cloned[y][x] = this.#data[h - y][x];
551+
iterateGrid(this.#data[this.#layer], (x, y) => {
552+
cloned[this.#layer][y][x] = this.#data[h - y][x];
486553
});
487-
this.#data = cloned;
554+
this.#data[this.#layer] = cloned;
488555
}
489556
move(translateX: number, translateY: number) {
490557
const cloned = fillGrid(this.width, this.height);
491558
for (let iy = 0; iy < this.height; iy++) {
492559
cloned[iy].fill(0);
493560
}
494-
iterateGrid(this.#data, (x, y) => {
561+
iterateGrid(this.#data[this.#layer], (x, y) => {
495562
if (y - translateY < 0
496563
|| x - translateX < 0
497564
|| y - translateY >= this.height
498565
|| x - translateX >= this.width) {
499566
return;
500567
}
501-
cloned[y][x] = this.#data[y - translateY][x - translateX];
568+
cloned[y][x] = this.#data[this.#layer][y - translateY][x - translateX];
502569
});
503-
this.#data = cloned;
570+
this.#data[this.#layer] = cloned;
504571
}
505572
invert() {
506573
// Only works with 2 colors
507574
if (this.#colors.length > 2) {
508575
return;
509576
}
510-
const cloned = cloneGrid(this.#data);
577+
const cloned = cloneGrid(this.#data[this.#layer]);
511578
iterateGrid(cloned, (x, y) => {
512579
cloned[y][x] = cloned[y][x] === 0 ? 1 : 0;
513580
});
514-
this.#data = cloned;
581+
this.#data[this.#layer] = cloned;
515582
}
516583
undo() {
517584
// ToDo: Rewrite to use new history api
518585
const revert = this.#undoHistory.pop();
519586
if (!revert) { return; }
520-
this.#redoHistory.push(revert);
521-
revert?.forEach((item) => {
522-
const [x, y] = item;
523-
this.#data[y][x] = item[2];
524-
// redraw canvas
525-
});
587+
switch(revert.type) {
588+
case HistoryType.Pixel:
589+
this.#redoHistory.push(revert);
590+
(revert.data as HistoryPixelType).pixels.forEach((item) => {
591+
const [x, y] = item;
592+
this.#data[this.#layer][y][x] = item[2];
593+
// redraw canvas
594+
});
595+
break;
596+
}
526597
}
527598
redo() {
528599
// ToDo: Rewrite to use new history api
529-
const revert = this.#redoHistory.pop();
600+
/*const revert = this.#redoHistory.pop();
530601
if (!revert) { return; }
531602
this.#undoHistory.push(revert);
532603
revert?.forEach((item) => {
533604
const [x, y] = item;
534605
this.#data[y][x] = item[2];
535606
// redraw canvas
536-
});
607+
});*/
537608
}
538609
rotate(counterClockwise: boolean = false) {
539-
const cloned = cloneGrid(this.#data);
610+
const cloned = cloneGrid(this.#data[this.#layer]);
540611
if (counterClockwise) {
541612
const newData = this.#data[0].map((val, index) => this.#data.map(row => row[row.length - 1 - index]));
542613
for (let iy = 0; iy < this.height; iy++) {
543614
for (let ix = 0; ix < this.width; ix++) {
544-
cloned[iy][ix] = newData[iy][ix];
615+
cloned[iy][ix] = newData[this.#layer][iy][ix];
545616
}
546617
}
547618
} else {
548619
const newData = this.#data[0].map((val, index) => this.#data.map(row => row[index]).reverse());
549620
for (let iy = 0; iy < this.height; iy++) {
550621
for (let ix = 0; ix < this.width; ix++) {
551-
cloned[iy][ix] = newData[iy][ix];
622+
cloned[iy][ix] = newData[this.#layer][iy][ix];
552623
}
553624
}
554625
}
555-
this.#data = cloned;
626+
this.#data[this.#layer] = cloned;
556627
}
557628
hasUndo() {
558629
return this.#undoHistory.length !== 0;

0 commit comments

Comments
 (0)