diff --git a/src/Nodes/CanvasNode.ts b/src/Nodes/CanvasNode.ts index f4dfe5b..cb8190a 100644 --- a/src/Nodes/CanvasNode.ts +++ b/src/Nodes/CanvasNode.ts @@ -15,13 +15,14 @@ export default abstract class CanvasNode extends GameNode implements Region { constructor(){ super(); - this.position.setOnChange(this.positionChanged); this._size = new Vec2(0, 0); - this._size.setOnChange(this.sizeChanged); + this._size.setOnChange(() => this.sizeChanged()); this._scale = new Vec2(1, 1); - this._scale.setOnChange(this.scaleChanged); + this._scale.setOnChange(() => this.scaleChanged()); this._boundary = new AABB(); this.updateBoundary(); + + this.size.set(101, 101); } get size(): Vec2 { @@ -30,7 +31,8 @@ export default abstract class CanvasNode extends GameNode implements Region { set size(size: Vec2){ this._size = size; - this._size.setOnChange(this.sizeChanged); + // Enter as a lambda to bind "this" + this._size.setOnChange(() => this.sizeChanged()); this.sizeChanged(); } @@ -40,23 +42,21 @@ export default abstract class CanvasNode extends GameNode implements Region { set scale(scale: Vec2){ this._scale = scale; - this._scale.setOnChange(this.scaleChanged); + // Enter as a lambda to bind "this" + this._scale.setOnChange(() => this.scaleChanged()); this.scaleChanged(); } - - protected positionChanged = (): void => { - if(this.hasPhysics){ - this.collisionShape.center = this.position; - } + protected positionChanged(): void { + super.positionChanged(); this.updateBoundary(); } - protected sizeChanged = (): void => { + protected sizeChanged(): void { this.updateBoundary(); } - protected scaleChanged = (): void => { + protected scaleChanged(): void { this.updateBoundary(); } diff --git a/src/Nodes/GameNode.ts b/src/Nodes/GameNode.ts index d3fc584..2c87d58 100644 --- a/src/Nodes/GameNode.ts +++ b/src/Nodes/GameNode.ts @@ -56,7 +56,7 @@ export default abstract class GameNode implements Positioned, Unique, Updateable constructor(){ this.input = InputReceiver.getInstance(); this._position = new Vec2(0, 0); - this._position.setOnChange(this.positionChanged); + this._position.setOnChange(() => this.positionChanged()); this.receiver = new Receiver(); this.emitter = new Emitter(); } @@ -68,7 +68,7 @@ export default abstract class GameNode implements Positioned, Unique, Updateable set position(pos: Vec2) { this._position = pos; - this._position.setOnChange(this.positionChanged); + this._position.setOnChange(() => this.positionChanged()); this.positionChanged(); } @@ -220,8 +220,7 @@ export default abstract class GameNode implements Positioned, Unique, Updateable /** * Called if the position vector is modified or replaced */ - // TODO - For some reason this isn't recognized in the child class - protected positionChanged = (): void => { + protected positionChanged(): void { if(this.hasPhysics){ this.collisionShape.center = this.position; } diff --git a/src/Nodes/UIElement.ts b/src/Nodes/UIElement.ts index 51f4dc8..476064c 100644 --- a/src/Nodes/UIElement.ts +++ b/src/Nodes/UIElement.ts @@ -5,18 +5,13 @@ import Vec2 from "../DataTypes/Vec2"; /** * The representation of a UIElement - the parent class of things like buttons */ -export default class UIElement extends CanvasNode { +export default abstract class UIElement extends CanvasNode { // Style attributes - protected textColor: Color; protected backgroundColor: Color; protected borderColor: Color; - protected text: string; - protected font: string; - protected fontSize: number; - protected hAlign: string; - protected vAlign: string; protected borderRadius: number; protected borderWidth: number; + protected padding: Vec2; // EventAttributes onClick: Function; @@ -35,16 +30,11 @@ export default class UIElement extends CanvasNode { super(); this.position = position; - this.textColor = new Color(0, 0, 0, 1); this.backgroundColor = new Color(0, 0, 0, 0); this.borderColor = new Color(0, 0, 0, 0); - this.text = ""; - this.font = "Arial"; - this.fontSize = 30; - this.hAlign = "center"; - this.vAlign = "center"; this.borderRadius = 5; this.borderWidth = 1; + this.padding = Vec2.ZERO; this.onClick = null; this.onClickEventId = null; @@ -60,16 +50,12 @@ export default class UIElement extends CanvasNode { this.isEntered = false; } - setText(text: string): void { - this.text = text; - } - setBackgroundColor(color: Color): void { this.backgroundColor = color; } - setTextColor(color: Color): void { - this.textColor = color; + setPadding(padding: Vec2): void { + this.padding.copy(padding); } update(deltaT: number): void { @@ -126,36 +112,6 @@ export default class UIElement extends CanvasNode { } } - /** - * Calculate the offset of the text - this is useful for rendering text with different alignments - * - */ - protected calculateOffset(ctx: CanvasRenderingContext2D): Vec2 { - let textWidth = ctx.measureText(this.text).width; - - let offset = new Vec2(0, 0); - - let hDiff = this.size.x - textWidth; - if(this.hAlign === "center"){ - offset.x = hDiff/2; - } else if (this.hAlign === "right"){ - offset.x = hDiff; - } - - if(this.vAlign === "top"){ - ctx.textBaseline = "top"; - offset.y = 0; - } else if (this.vAlign === "bottom"){ - ctx.textBaseline = "bottom"; - offset.y = this.size.y; - } else { - ctx.textBaseline = "middle"; - offset.y = this.size.y/2; - } - - return offset; - } - /** * Overridable method for calculating background color - useful for elements that want to be colored on different after certain events */ @@ -169,42 +125,4 @@ export default class UIElement extends CanvasNode { protected calculateBorderColor(): string { return this.borderColor.toStringRGBA(); } - - /** - * Overridable method for calculating text color - useful for elements that want to be colored on different after certain events - */ - protected calculateTextColor(): string { - return this.textColor.toStringRGBA(); - } - - render(ctx: CanvasRenderingContext2D): void { - // Grab the global alpha so we can adjust it for this render - let previousAlpha = ctx.globalAlpha; - ctx.globalAlpha = this.getLayer().getAlpha(); - - let origin = this.scene.getViewTranslation(this); - - ctx.font = this.fontSize + "px " + this.font; - let offset = this.calculateOffset(ctx); - - // Stroke and fill a rounded rect and give it text - ctx.fillStyle = this.calculateBackgroundColor(); - ctx.fillRoundedRect(this.position.x - origin.x - this.size.x/2, this.position.y - origin.y - this.size.y/2, - this.size.x, this.size.y, this.borderRadius); - - ctx.strokeStyle = this.calculateBorderColor(); - ctx.lineWidth = this.borderWidth; - ctx.strokeRoundedRect(this.position.x - origin.x - this.size.x/2, this.position.y - origin.y - this.size.y/2, - this.size.x, this.size.y, this.borderRadius); - - ctx.fillStyle = this.calculateTextColor(); - ctx.fillText(this.text, this.position.x + offset.x - origin.x - this.size.x/2, this.position.y + offset.y - origin.y - this.size.y/2); - - ctx.globalAlpha = previousAlpha; - - ctx.lineWidth = 4; - ctx.strokeStyle = "#00FF00" - let b = this.boundary; - ctx.strokeRect(b.x - b.hw - origin.x, b.y - b.hh - origin.y, b.hw*2, b.hh*2); - } } \ No newline at end of file diff --git a/src/Nodes/UIElements/Button.ts b/src/Nodes/UIElements/Button.ts index 30887af..ac85831 100644 --- a/src/Nodes/UIElements/Button.ts +++ b/src/Nodes/UIElements/Button.ts @@ -1,13 +1,12 @@ -import UIElement from "../UIElement"; +import Label from "./Label"; import Color from "../../Utils/Color"; import Vec2 from "../../DataTypes/Vec2"; -export default class Button extends UIElement{ +export default class Button extends Label { constructor(position: Vec2, text: string){ - super(position); - this.text = text; - + super(position, text); + this.backgroundColor = new Color(150, 75, 203); this.borderColor = new Color(41, 46, 30); this.textColor = new Color(255, 255, 255); diff --git a/src/Nodes/UIElements/Label.ts b/src/Nodes/UIElements/Label.ts index 2b9376c..b578de9 100644 --- a/src/Nodes/UIElements/Label.ts +++ b/src/Nodes/UIElements/Label.ts @@ -1,9 +1,130 @@ import Vec2 from "../../DataTypes/Vec2"; +import Color from "../../Utils/Color"; import UIElement from "../UIElement"; export default class Label extends UIElement{ + protected textColor: Color; + protected text: string; + protected font: string; + protected fontSize: number; + protected hAlign: string; + protected vAlign: string; + + /** A flag for if the width of the text has been measured on the canvas for auto width assignment */ + protected sizeAssigned: boolean; + constructor(position: Vec2, text: string){ super(position); this.text = text; + this.textColor = new Color(0, 0, 0, 1); + this.font = "Arial"; + this.fontSize = 30; + this.hAlign = "center"; + this.vAlign = "center"; + + this.sizeAssigned = false; + } + + setText(text: string): void { + this.text = text; + } + + setTextColor(color: Color): void { + this.textColor = color; + } + + /** + * Overridable method for calculating text color - useful for elements that want to be colored on different after certain events + */ + protected calculateTextColor(): string { + return this.textColor.toStringRGBA(); + } + + protected calculateTextWidth(ctx: CanvasRenderingContext2D): number { + ctx.font = this.fontSize + "px " + this.font; + return ctx.measureText(this.text).width; + } + + /** + * Calculate the offset of the text - this is useful for rendering text with different alignments + * + */ + protected calculateTextOffset(ctx: CanvasRenderingContext2D): Vec2 { + let textWidth = this.calculateTextWidth(ctx); + + let offset = new Vec2(0, 0); + + let hDiff = this.size.x - textWidth; + if(this.hAlign === "center"){ + offset.x = hDiff/2; + } else if (this.hAlign === "right"){ + offset.x = hDiff; + } + + if(this.vAlign === "top"){ + ctx.textBaseline = "top"; + offset.y = 0; + } else if (this.vAlign === "bottom"){ + ctx.textBaseline = "bottom"; + offset.y = this.size.y; + } else { + ctx.textBaseline = "middle"; + offset.y = this.size.y/2; + } + + return offset; + } + + protected sizeChanged(): void { + super.sizeChanged(); + this.sizeAssigned = true; + } + + protected autoSize(ctx: CanvasRenderingContext2D){ + let width = this.calculateTextWidth(ctx); + let height = this.fontSize; + this.size.set(width + this.padding.x*2, height + this.padding.y*2); + this.sizeAssigned = true; + } + + /** On the next render, size this element to it's current text using its current font size */ + sizeToText(): void { + this.sizeAssigned = false; + } + + render(ctx: CanvasRenderingContext2D): void { + // If the size is unassigned (by the user or automatically) assign it + if(!this.sizeAssigned){ + this.autoSize(ctx); + } + + // Grab the global alpha so we can adjust it for this render + let previousAlpha = ctx.globalAlpha; + ctx.globalAlpha = this.getLayer().getAlpha(); + + let origin = this.scene.getViewTranslation(this); + + ctx.font = this.fontSize + "px " + this.font; + let offset = this.calculateTextOffset(ctx); + + // Stroke and fill a rounded rect and give it text + ctx.fillStyle = this.calculateBackgroundColor(); + ctx.fillRoundedRect(this.position.x - origin.x - this.size.x/2, this.position.y - origin.y - this.size.y/2, + this.size.x, this.size.y, this.borderRadius); + + ctx.strokeStyle = this.calculateBorderColor(); + ctx.lineWidth = this.borderWidth; + ctx.strokeRoundedRect(this.position.x - origin.x - this.size.x/2, this.position.y - origin.y - this.size.y/2, + this.size.x, this.size.y, this.borderRadius); + + ctx.fillStyle = this.calculateTextColor(); + ctx.fillText(this.text, this.position.x + offset.x - origin.x - this.size.x/2, this.position.y + offset.y - origin.y - this.size.y/2); + + ctx.globalAlpha = previousAlpha; + + ctx.lineWidth = 4; + ctx.strokeStyle = "#00FF00" + let b = this.boundary; + ctx.strokeRect(b.x - b.hw - origin.x, b.y - b.hh - origin.y, b.hw*2, b.hh*2); } } \ No newline at end of file diff --git a/src/SceneGraph/Viewport.ts b/src/SceneGraph/Viewport.ts index df8c15b..a113d69 100644 --- a/src/SceneGraph/Viewport.ts +++ b/src/SceneGraph/Viewport.ts @@ -13,6 +13,7 @@ export default class Viewport { private view: AABB; private boundary: AABB; private following: GameNode; + private focus: Vec2; /** * A queue of previous positions of what this viewport is following. Used for smoothing viewport movement @@ -35,6 +36,7 @@ export default class Viewport { this.smoothingFactor = 10; this.scrollZoomEnabled = false; this.canvasSize = Vec2.ZERO; + this.focus = Vec2.ZERO; } enableZoom(): void { @@ -130,6 +132,14 @@ export default class Viewport { if(smoothingFactor < 1) smoothingFactor = 1; this.smoothingFactor = smoothingFactor; } + + /** + * Tells the viewport to focus on a point. Overidden by "following". + * @param focus + */ + setFocus(focus: Vec2): void { + this.focus.copy(focus); + } /** * Returns true if the CanvasNode is inside of the viewport @@ -223,6 +233,7 @@ export default class Viewport { this.view.center.copy(pos); } else { + this.lastPositions.enqueue(this.focus); if(this.lastPositions.getSize() > this.smoothingFactor){ this.lastPositions.dequeue(); } diff --git a/src/_DemoClasses/Mario/MainMenu.ts b/src/_DemoClasses/Mario/MainMenu.ts new file mode 100644 index 0000000..7e8932b --- /dev/null +++ b/src/_DemoClasses/Mario/MainMenu.ts @@ -0,0 +1,37 @@ +import Vec2 from "../../DataTypes/Vec2"; +import Debug from "../../Debug/Debug"; +import InputHandler from "../../Input/InputHandler"; +import InputReceiver from "../../Input/InputReceiver"; +import { GraphicType } from "../../Nodes/Graphics/GraphicTypes"; +import Button from "../../Nodes/UIElements/Button"; +import { UIElementType } from "../../Nodes/UIElements/UIElementTypes"; +import Scene from "../../Scene/Scene"; +import Color from "../../Utils/Color"; + +export default class MainMenu extends Scene { + + playBtn: Button; + + loadScene(): void { + + } + + startScene(): void { + this.addUILayer("Main"); + + let size = this.viewport.getHalfSize(); + this.viewport.setFocus(size); + + this.playBtn =