Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
let darkMode = window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;

let color = 'blue';
if (darkMode) {
color = 'yellow';
}
const t1 = new Polygon();
t1.addPoint(-30, 0);
t1.addPoint(30, 30);
t1.addPoint(0, 30);
t1.debug = true;
t1.setPosition(getWidth() / 2, getHeight() / 2);
t1.setAnchor({ horizontal: 0, vertical: 0 });
t1.setColor(color);
add(t1);
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
title: Polygon - Negative Coordiantes
layout: example
code: negativecoordinates.js
---

Polygons can contain points which are negative relative to their position. This affects bounding box calculations.
4 changes: 4 additions & 0 deletions site/examples/groups/anchor/anchor.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,7 @@ g3.setPosition((3 * getWidth()) / 4, getHeight() / 2);
g3.setAnchor({ vertical: 1, horizontal: 1 });
g3.debug = true;
add(g3);

console.log(g1.getBounds());
console.log(g2.getBounds());
console.log(g3.getBounds());
49 changes: 34 additions & 15 deletions src/graphics/group.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,36 +47,54 @@ class Group extends Thing {
this._hiddenContext = this._hiddenCanvas.getContext('2d');
this._lastRecordedBounds = {};
this.bounds = null;
/**
* The left-most x coordinate of elements in this group, which is considered its x value.
* @private
* @type {number}
*/
this._minX = 0;
/**
* The top-most y coordinate of elements in this group, which is considered its y value.
* @private
* @type {number}
*/
this._minY = 0;
}

/**
* X position of the group, indicated by its left bound.
* @type {number}
*/
get x() {
return this.getBounds().left;
if (this._boundsInvalidated) {
this._updateBounds();
}
return this._minX;
}

set x(x) {
if (!this.bounds) {
return;
}
this.setPosition(x, this.bounds.top);
this.setPosition(x, this._minY);
}

/**
* X position of the group, indicated by its top bound.
* @type {number}
*/
get y() {
return this.getBounds().top;
if (this._boundsInvalidated) {
this._updateBounds();
}
return this._minY;
}

set y(y) {
if (!this.bounds) {
return;
}
this.setPosition(this.bounds.left, y);
this.setPosition(this._minX, y);
}

/**
Expand Down Expand Up @@ -157,9 +175,8 @@ class Group extends Thing {
* @param {number} y
*/
setPosition(x, y) {
const bounds = this.getBounds();
const dx = x - bounds.left;
const dy = y - bounds.top;
const dx = x - this.x;
const dy = y - this.y;
this.move(dx, dy);
}

Expand All @@ -184,14 +201,14 @@ class Group extends Thing {
// in the top left corner.
// this means that only the bounding box surrounding the top
// left corner needs to be drawn to the destination canvas
this._hiddenContext.translate(-bounds.left, -bounds.top);
this._hiddenContext.translate(-this.x, -this.y);
this.elements
.filter(element => element.alive)
.sort((a, b) => a.layer - b.layer)
.forEach(element => {
element.draw(this._hiddenContext);
});
this._hiddenContext.translate(bounds.left, bounds.top);
this._hiddenContext.translate(this.x, this.y);
context.drawImage(this._hiddenCanvas, 0, 0, width, height);
context.closePath();
});
Expand Down Expand Up @@ -271,14 +288,16 @@ class Group extends Thing {
maxX = Math.max(maxX, right);
maxY = Math.max(maxY, bottom);
});
this.bounds = {
left: minX,
right: maxX,
top: minY,
bottom: maxY,
};
const width = maxX - minX;
const height = maxY - minY;
this.bounds = {
left: minX - this.anchor.horizontal * width,
right: maxX - this.anchor.horizontal * width,
top: minY - this.anchor.vertical * height,
bottom: maxY - this.anchor.vertical * height,
};
this._minX = minX;
this._minY = minY;
this._hiddenCanvas.width = this.devicePixelRatio * width;
this._hiddenCanvas.height = this.devicePixelRatio * height;
this._hiddenCanvas.style.width = `${width}px`;
Expand Down
1 change: 1 addition & 0 deletions src/graphics/thing.js
Original file line number Diff line number Diff line change
Expand Up @@ -571,6 +571,7 @@ class Thing {
context.strokeStyle = 'red';
context.fill();
const bounds = this.getBounds();
// move back to the origin
context.translate(-drawX, -drawY);
context.strokeRect(
bounds.left,
Expand Down
20 changes: 19 additions & 1 deletion test/group.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,9 +149,18 @@ describe('Groups', () => {
right: 5,
});
});
it('Considers anchoring', () => {
const g = new Group();
g.add(new Rectangle(10, 10));
expect(g.getBounds().left).toEqual(0);
g.setAnchor({ horizontal: 1.0, vertical: 0 });
expect(g.getBounds().left).toEqual(-10);
g.setAnchor({ horizontal: 0.5, vertical: 0.5 });
expect(g.getBounds()).toEqual({ top: -5, left: -5, right: 5, bottom: 5 });
});
});
describe('Positioning', () => {
it('A groups x is its left bound', () => {
it("A Group's x is its left bound", () => {
const g = new Group();
g.add(new Rectangle(20, 20));
expect(g.x).toEqual(g.getBounds().left);
Expand All @@ -160,6 +169,15 @@ describe('Groups', () => {
expect(g.x).toEqual(g.getBounds().left);
expect(g.x).toEqual(10);
});
it("A Group's anchoring doesn't affect its position", () => {
const g = new Group();
g.add(new Rectangle(20, 20));
expect(g.x).toEqual(0);
g.setPosition(10, 10);
expect(g.x).toEqual(10);
g.setAnchor({ horizontal: 1.0, vertical: 0 });
expect(g.x).toEqual(10);
});
});
describe('containsPoint', () => {
it('Should be true if any of its children contain the point', () => {
Expand Down
31 changes: 31 additions & 0 deletions test/polygon.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ describe('Polygon', () => {
expect(new Polygon().type).toEqual('Polygon');
});
});
describe("A Polygon's position (x, y)", () => {
it('Is unaffected by adding points', () => {
const p = new Polygon();
p.addPoint(10, 10);
p.addPoint(20, 10);
p.addPoint(10, 20);
expect(p.x).toEqual(0);
});
});
describe('addPoint', () => {
it("Invalidates the superclass's bounds", () => {
const p = new Polygon();
Expand All @@ -27,6 +36,7 @@ describe('Polygon', () => {
p.addPoint(30, 0);
p.addPoint(200, 0);
expect(p.getWidth()).toBe(200);
expect(p.width).toBe(200);
});
});
describe('getHeight()', () => {
Expand All @@ -37,6 +47,7 @@ describe('Polygon', () => {
p.addPoint(0, 120);
p.addPoint(0, 90);
expect(p.getHeight()).toBe(100);
expect(p.height).toBe(100);
});
});
describe('move()', () => {
Expand Down Expand Up @@ -109,5 +120,25 @@ describe('Polygon', () => {
right: 0,
});
});
it('Its bounds are affected by anchoring', () => {
const p = new Polygon();
p.addPoint(-10, 0);
p.addPoint(10, 0);
p.addPoint(10, 20);
p.addPoint(-10, 20);
expect(p.getBounds()).toEqual({
top: 0,
left: -10,
right: 10,
bottom: 20,
});
p.setAnchor({ vertical: 1, horizontal: 1 });
expect(p.getBounds()).toEqual({
top: -20,
left: -30,
bottom: 0,
right: -10,
});
});
});
});