graphico 0.0.2 → 0.0.4

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.
package/dist/canvas.js CHANGED
@@ -13,13 +13,9 @@ var Canvas = /** @class */ (function () {
13
13
  if (options === void 0) { options = {}; }
14
14
  var _this = this;
15
15
  /**
16
- * Whether or not the control is focused
16
+ * Can be used to render 2D graphics onto the canvas
17
17
  */
18
- this.focused = false;
19
- /**
20
- * Frame counter, increments every frame
21
- */
22
- this.frame = 0;
18
+ this.graphics = [];
23
19
  /**
24
20
  * Contains a list of current keys pressed
25
21
  */
@@ -36,41 +32,55 @@ var Canvas = /** @class */ (function () {
36
32
  * Contains the mouse Y-coordinate, in pixels
37
33
  */
38
34
  this.mouseY = 0;
35
+ /**
36
+ * Represents the animation ID handle to cancel the animation
37
+ */
38
+ this.animation = 0;
39
+ /**
40
+ * The last frame's high resolution timestamp
41
+ */
42
+ this.lastFrame = 0;
43
+ /**
44
+ * Determine whether the client is focused or not
45
+ */
46
+ this.focused = false;
39
47
  this.config = Canvas.setDefaults(options, Canvas.defaults);
40
- var canvas = document.createElement('canvas');
41
- var graphics = canvas.getContext('2d');
42
- if (graphics) {
43
- this.graphics = graphics;
44
- }
45
- else {
46
- throw new Error('Could not initialize canvas graphics.');
47
- }
48
- // Set static properties
49
- this.config.parent.appendChild(canvas);
50
- canvas.tabIndex = 1; // For element focus
51
- canvas.style.outline = 'none';
52
- canvas.style.imageRendering = 'pixelated';
53
- // Set custom properties
54
- canvas.width = this.config.width;
55
- canvas.height = this.config.height;
56
- canvas.style.width = "".concat(this.config.width * this.config.scale, "px");
57
- canvas.style.height = "".concat(this.config.height * this.config.scale, "px");
58
- canvas.style.background = this.config.background.toString();
59
- canvas.style.border = "".concat(this.config.scale, "px solid ").concat(this.config.border);
60
- canvas.style.cursor = this.config.showMouse ? 'default' : 'none';
61
- // Initialize main sequence
62
- if (this.config.framesPerSecond > 0) {
63
- setInterval(function () {
64
- if (!_this.focused) {
65
- return;
66
- }
67
- _this.frame++;
68
- _this.log(_this.frame);
69
- _this.config.loop(_this.frame, _this.keys, _this.mouseButtons, _this.mouseX, _this.mouseY);
70
- }, 1000 / this.config.framesPerSecond);
48
+ this.width = this.config.width;
49
+ this.height = this.config.height;
50
+ // Create the container <div> element and set properties
51
+ var container = document.createElement('div');
52
+ container.tabIndex = 1; // For element focus
53
+ container.style.outline = 'none';
54
+ container.style.width = "".concat(this.config.width * this.config.scale, "px");
55
+ container.style.height = "".concat(this.config.height * this.config.scale, "px");
56
+ container.style.border = "".concat(this.config.scale, "px solid ").concat(this.config.border);
57
+ container.style.background = this.config.background;
58
+ container.style.cursor = this.config.showMouse ? 'default' : 'none';
59
+ container.style.position = 'relative';
60
+ this.config.parent.appendChild(container);
61
+ // Create canvas layers
62
+ for (var i = 0; i < this.config.numLayers; i++) {
63
+ var canvas = document.createElement('canvas');
64
+ var graphics = canvas.getContext('2d', { 'willReadFrequently': this.config.acceleration === 'software' });
65
+ if (graphics) {
66
+ this.graphics.push(graphics);
67
+ }
68
+ else {
69
+ throw new Error('Could not initialize canvas graphics.');
70
+ }
71
+ // Set properties for canvas
72
+ canvas.style.imageRendering = 'pixelated';
73
+ canvas.style.boxSizing = 'border-box';
74
+ canvas.width = this.config.width;
75
+ canvas.height = this.config.height;
76
+ canvas.style.width = '100%';
77
+ canvas.style.height = '100%';
78
+ canvas.style.position = 'absolute';
79
+ graphics.imageSmoothingEnabled = false;
80
+ container.appendChild(canvas);
71
81
  }
72
82
  // Event listeners
73
- canvas.addEventListener('mousemove', function (e) {
83
+ container.addEventListener('mousemove', function (e) {
74
84
  if (!_this.focused) {
75
85
  return;
76
86
  }
@@ -79,7 +89,7 @@ var Canvas = /** @class */ (function () {
79
89
  _this.log(e.type, _this.mouseX, _this.mouseY);
80
90
  _this.config.mousemove(_this.mouseX, _this.mouseY);
81
91
  });
82
- canvas.addEventListener('keydown', function (e) {
92
+ container.addEventListener('keydown', function (e) {
83
93
  if (!_this.focused) {
84
94
  return;
85
95
  }
@@ -91,7 +101,7 @@ var Canvas = /** @class */ (function () {
91
101
  _this.config.keydown(key);
92
102
  }
93
103
  });
94
- canvas.addEventListener('keyup', function (e) {
104
+ container.addEventListener('keyup', function (e) {
95
105
  if (!_this.focused) {
96
106
  return;
97
107
  }
@@ -103,7 +113,7 @@ var Canvas = /** @class */ (function () {
103
113
  _this.config.keyup(key);
104
114
  }
105
115
  });
106
- canvas.addEventListener('mousedown', function (e) {
116
+ container.addEventListener('mousedown', function (e) {
107
117
  if (!_this.focused) {
108
118
  return;
109
119
  }
@@ -114,7 +124,7 @@ var Canvas = /** @class */ (function () {
114
124
  _this.config.mousedown(button);
115
125
  }
116
126
  });
117
- canvas.addEventListener('mouseup', function (e) {
127
+ container.addEventListener('mouseup', function (e) {
118
128
  if (!_this.focused) {
119
129
  return;
120
130
  }
@@ -126,20 +136,52 @@ var Canvas = /** @class */ (function () {
126
136
  _this.config.mouseup(button);
127
137
  }
128
138
  });
129
- canvas.addEventListener('focusin', function (e) {
139
+ container.addEventListener('focusin', function (e) {
130
140
  _this.focused = true;
131
- canvas.style.borderColor = _this.config.border;
141
+ container.style.borderColor = _this.config.border;
132
142
  _this.log(e.type, _this.focused);
143
+ _this.animation = requestAnimationFrame(function (time) { return _this.startAnimate(time); });
133
144
  });
134
- canvas.addEventListener('focusout', function (e) {
145
+ container.addEventListener('focusout', function (e) {
135
146
  _this.focused = false;
136
- canvas.style.borderColor = _this.config.borderBlur;
147
+ container.style.borderColor = _this.config.borderBlur;
137
148
  _this.log(e.type, _this.focused);
149
+ cancelAnimationFrame(_this.animation);
150
+ });
151
+ window.addEventListener('blur', function (e) {
152
+ _this.log(e.type);
153
+ cancelAnimationFrame(_this.animation);
138
154
  });
139
- canvas.addEventListener('contextmenu', function (e) { return e.preventDefault(); });
155
+ container.addEventListener('contextmenu', function (e) { return e.preventDefault(); });
140
156
  // Focus on the canvas
141
- canvas.focus();
157
+ container.focus();
142
158
  }
159
+ /**
160
+ * Start the animation.
161
+ */
162
+ Canvas.prototype.startAnimate = function (time) {
163
+ var _this = this;
164
+ this.log('startAnimate', time);
165
+ this.lastFrame = time;
166
+ this.animation = requestAnimationFrame(function (time) { return _this.animate(time); });
167
+ };
168
+ /**
169
+ * Run the main animation loop.
170
+ */
171
+ Canvas.prototype.animate = function (time) {
172
+ var _this = this;
173
+ var _a;
174
+ var currentFrame = time;
175
+ var dt = currentFrame - this.lastFrame;
176
+ this.lastFrame = currentFrame;
177
+ this.log('animate', dt, currentFrame);
178
+ var drawables = (_a = this.config.loop(dt)) !== null && _a !== void 0 ? _a : [];
179
+ for (var _i = 0, drawables_1 = drawables; _i < drawables_1.length; _i++) {
180
+ var drawable = drawables_1[_i];
181
+ this.draw(drawable);
182
+ }
183
+ this.animation = requestAnimationFrame(function (time) { return _this.animate(time); });
184
+ };
143
185
  /**
144
186
  * Determine whether a key is currently pressed.
145
187
  * @param key The key to check
@@ -156,18 +198,98 @@ var Canvas = /** @class */ (function () {
156
198
  Canvas.prototype.isMouseButtonDown = function (button) {
157
199
  return this.mouseButtons.includes(button);
158
200
  };
201
+ /**
202
+ * Get the current cursor position.
203
+ * @returns Cursor position as `[x, y]`
204
+ */
205
+ Canvas.prototype.getMousePosition = function () {
206
+ return [this.mouseX, this.mouseY];
207
+ };
208
+ /**
209
+ * Get the color of the selected pixel.
210
+ * @param x The pixel's x-coordinate
211
+ * @param y The pixel's y-coordinate
212
+ * @param layer The zero-indexed layer to get data from
213
+ * @returns `[red, green, blue, alpha]`
214
+ */
215
+ Canvas.prototype.getPixel = function (x, y, layer) {
216
+ if (layer === void 0) { layer = 0; }
217
+ var data = this.graphics[layer].getImageData(x, y, 1, 1).data;
218
+ this.log(this.graphics[layer].getImageData(x, y, 2, 2));
219
+ ;
220
+ return [data[0], data[1], data[2], data[3]];
221
+ };
222
+ /**
223
+ * Set the color of the selected pixel.
224
+ * @param x The pixel's x-coordinate
225
+ * @param y The pixel's y-coordinate
226
+ * @param color `[red, green, blue, alpha]`
227
+ * @param layer The zero-indexed layer to set data to
228
+ */
229
+ Canvas.prototype.setPixel = function (x, y, color, layer) {
230
+ if (layer === void 0) { layer = 0; }
231
+ var data = this.graphics[layer].getImageData(x, y, 1, 1);
232
+ data.data[0] = color[0];
233
+ data.data[1] = color[1];
234
+ data.data[2] = color[2];
235
+ data.data[3] = color[3];
236
+ this.log(data);
237
+ this.graphics[layer].putImageData(data, x, y);
238
+ };
239
+ /**
240
+ * Take a screenshot of the canvas contents and save to a .png file.
241
+ * @param name The file name of the screenshot
242
+ */
243
+ Canvas.prototype.screenshot = function (name) {
244
+ if (name === void 0) { name = 'screenshot'; }
245
+ // Create an offscreen canvas
246
+ var screen = new Canvas({
247
+ parent: document.createElement('div'),
248
+ height: this.config.height,
249
+ width: this.config.width,
250
+ scale: this.config.scale,
251
+ });
252
+ // Draw the background and each layer
253
+ screen.graphics[0].fillStyle = this.config.background;
254
+ screen.graphics[0].fillRect(0, 0, screen.width, screen.height);
255
+ for (var _i = 0, _a = this.graphics; _i < _a.length; _i++) {
256
+ var graphic = _a[_i];
257
+ screen.graphics[0].drawImage(graphic.canvas, 0, 0);
258
+ }
259
+ // Generate a data URL and set it as the download parameter for <a>
260
+ var dataURL = screen.graphics[0].canvas.toDataURL();
261
+ var downloadLink = document.createElement('a');
262
+ downloadLink.setAttribute('href', dataURL);
263
+ downloadLink.setAttribute('download', name);
264
+ downloadLink.click();
265
+ // Remove all keys and mouse buttons down because we lose focus
266
+ this.keys.splice(0);
267
+ this.mouseButtons.splice(0);
268
+ };
159
269
  /**
160
270
  * Draw an object onto the canvas.
161
271
  * @param drawable Any drawable object
272
+ * @param layer The zero-indexed layer to draw to
162
273
  */
163
- Canvas.prototype.draw = function (drawable) {
164
- drawable.draw(this.graphics);
274
+ Canvas.prototype.draw = function (drawable, layer) {
275
+ if (layer === void 0) { layer = 0; }
276
+ drawable.draw(this.graphics[layer]);
165
277
  };
166
278
  /**
167
279
  * Completely clears the canvas.
280
+ * @param layer The zero-indexed layer to clear, if unset, will clear all layers
168
281
  */
169
- Canvas.prototype.clear = function () {
170
- this.graphics.clearRect(0, 0, this.config.width, this.config.height);
282
+ Canvas.prototype.clear = function (layer) {
283
+ if (layer === void 0) { layer = -1; }
284
+ if (layer < 0) {
285
+ for (var _i = 0, _a = this.graphics; _i < _a.length; _i++) {
286
+ var graphic = _a[_i];
287
+ graphic.clearRect(0, 0, this.config.width, this.config.height);
288
+ }
289
+ }
290
+ else {
291
+ this.graphics[layer].clearRect(0, 0, this.config.width, this.config.height);
292
+ }
171
293
  };
172
294
  /**
173
295
  * Log a message to the debug console.
@@ -206,7 +328,8 @@ var Canvas = /** @class */ (function () {
206
328
  border: 'transparent',
207
329
  borderBlur: 'transparent',
208
330
  showMouse: true,
209
- framesPerSecond: 60,
331
+ numLayers: 1,
332
+ acceleration: 'hardware',
210
333
  keydown: function () { return; },
211
334
  keyup: function () { return; },
212
335
  mousemove: function () { return; },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphico",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "Canvas 2D rendering toolkit for games and visual projects",
5
5
  "homepage": "https://npm.nicfv.com/",
6
6
  "bin": "",
package/types/canvas.d.ts CHANGED
@@ -14,14 +14,6 @@ export declare class Canvas {
14
14
  * Can be used to render 2D graphics onto the canvas
15
15
  */
16
16
  private readonly graphics;
17
- /**
18
- * Whether or not the control is focused
19
- */
20
- private focused;
21
- /**
22
- * Frame counter, increments every frame
23
- */
24
- private frame;
25
17
  /**
26
18
  * Contains a list of current keys pressed
27
19
  */
@@ -30,6 +22,14 @@ export declare class Canvas {
30
22
  * Contains a list of current mouse buttons pressed
31
23
  */
32
24
  private readonly mouseButtons;
25
+ /**
26
+ * The width of the canvas, in pixels
27
+ */
28
+ readonly width: number;
29
+ /**
30
+ * The height of the canvas, in pixels
31
+ */
32
+ readonly height: number;
33
33
  /**
34
34
  * Contains the mouse X-coordinate, in pixels
35
35
  */
@@ -38,11 +38,31 @@ export declare class Canvas {
38
38
  * Contains the mouse Y-coordinate, in pixels
39
39
  */
40
40
  private mouseY;
41
+ /**
42
+ * Represents the animation ID handle to cancel the animation
43
+ */
44
+ private animation;
45
+ /**
46
+ * The last frame's high resolution timestamp
47
+ */
48
+ private lastFrame;
49
+ /**
50
+ * Determine whether the client is focused or not
51
+ */
52
+ private focused;
41
53
  /**
42
54
  * Create a new canvas with the provided options
43
55
  * @param options Configuration options
44
56
  */
45
57
  constructor(options?: Partial<Options>);
58
+ /**
59
+ * Start the animation.
60
+ */
61
+ private startAnimate;
62
+ /**
63
+ * Run the main animation loop.
64
+ */
65
+ private animate;
46
66
  /**
47
67
  * Determine whether a key is currently pressed.
48
68
  * @param key The key to check
@@ -55,15 +75,43 @@ export declare class Canvas {
55
75
  * @returns True if `button` is down
56
76
  */
57
77
  isMouseButtonDown(button: number): boolean;
78
+ /**
79
+ * Get the current cursor position.
80
+ * @returns Cursor position as `[x, y]`
81
+ */
82
+ getMousePosition(): [number, number];
83
+ /**
84
+ * Get the color of the selected pixel.
85
+ * @param x The pixel's x-coordinate
86
+ * @param y The pixel's y-coordinate
87
+ * @param layer The zero-indexed layer to get data from
88
+ * @returns `[red, green, blue, alpha]`
89
+ */
90
+ getPixel(x: number, y: number, layer?: number): [number, number, number, number];
91
+ /**
92
+ * Set the color of the selected pixel.
93
+ * @param x The pixel's x-coordinate
94
+ * @param y The pixel's y-coordinate
95
+ * @param color `[red, green, blue, alpha]`
96
+ * @param layer The zero-indexed layer to set data to
97
+ */
98
+ setPixel(x: number, y: number, color: [number, number, number, number], layer?: number): void;
99
+ /**
100
+ * Take a screenshot of the canvas contents and save to a .png file.
101
+ * @param name The file name of the screenshot
102
+ */
103
+ screenshot(name?: string): void;
58
104
  /**
59
105
  * Draw an object onto the canvas.
60
106
  * @param drawable Any drawable object
107
+ * @param layer The zero-indexed layer to draw to
61
108
  */
62
- draw(drawable: Drawable): void;
109
+ draw(drawable: Drawable, layer?: number): void;
63
110
  /**
64
111
  * Completely clears the canvas.
112
+ * @param layer The zero-indexed layer to clear, if unset, will clear all layers
65
113
  */
66
- clear(): void;
114
+ clear(layer?: number): void;
67
115
  /**
68
116
  * Log a message to the debug console.
69
117
  */
@@ -114,9 +162,15 @@ export interface Options {
114
162
  */
115
163
  readonly showMouse: boolean;
116
164
  /**
117
- * The number of frames to render every second
165
+ * The number of layers in this canvas
118
166
  */
119
- readonly framesPerSecond: number;
167
+ readonly numLayers: number;
168
+ /**
169
+ * Uses hardware (GPU) or software (CPU) acceleration
170
+ * - For pixel manipulation, software acceleration is recommended
171
+ * - Otherwise, hardware acceleration is recommended (default)
172
+ */
173
+ readonly acceleration: 'hardware' | 'software';
120
174
  /**
121
175
  * Event listener for when a key is pressed
122
176
  * @param key The key that was pressed
@@ -144,16 +198,11 @@ export interface Options {
144
198
  */
145
199
  readonly mouseup: (button: number) => void;
146
200
  /**
147
- * Event listener for a the main loop, only called when:
148
- * - `framesPerSecond` > 0
149
- * - The canvas is focused
150
- * @param frame The frame sequence number
151
- * @param keys A list of keys that are currently pressed
152
- * @param mouseButtons A list of buttons that are currently pressed
153
- * @param x Cursor X-coordinate
154
- * @param y Cursor Y-coordinate
201
+ * Event listener for a the main animation loop
202
+ * @param dt The number of milliseconds in between frames
203
+ * @returns An array of `Drawable` to render on layer 0, or void
155
204
  */
156
- readonly loop: (frame: number, keys: string[], mouseButtons: number[], x: number, y: number) => void;
205
+ readonly loop: (dt: number) => Drawable[] | (void | never);
157
206
  }
158
207
  /**
159
208
  * Represents an object that can be drawn on the canvas.