cleaned up UIElement code

This commit is contained in:
Joe Weaver 2020-11-18 10:57:55 -05:00
parent 6149b983a5
commit fff1ac4907
8 changed files with 195 additions and 109 deletions

View File

@ -15,13 +15,14 @@ export default abstract class CanvasNode extends GameNode implements Region {
constructor(){ constructor(){
super(); super();
this.position.setOnChange(this.positionChanged);
this._size = new Vec2(0, 0); this._size = new Vec2(0, 0);
this._size.setOnChange(this.sizeChanged); this._size.setOnChange(() => this.sizeChanged());
this._scale = new Vec2(1, 1); this._scale = new Vec2(1, 1);
this._scale.setOnChange(this.scaleChanged); this._scale.setOnChange(() => this.scaleChanged());
this._boundary = new AABB(); this._boundary = new AABB();
this.updateBoundary(); this.updateBoundary();
this.size.set(101, 101);
} }
get size(): Vec2 { get size(): Vec2 {
@ -30,7 +31,8 @@ export default abstract class CanvasNode extends GameNode implements Region {
set size(size: Vec2){ set size(size: Vec2){
this._size = size; this._size = size;
this._size.setOnChange(this.sizeChanged); // Enter as a lambda to bind "this"
this._size.setOnChange(() => this.sizeChanged());
this.sizeChanged(); this.sizeChanged();
} }
@ -40,23 +42,21 @@ export default abstract class CanvasNode extends GameNode implements Region {
set scale(scale: Vec2){ set scale(scale: Vec2){
this._scale = scale; this._scale = scale;
this._scale.setOnChange(this.scaleChanged); // Enter as a lambda to bind "this"
this._scale.setOnChange(() => this.scaleChanged());
this.scaleChanged(); this.scaleChanged();
} }
protected positionChanged(): void {
protected positionChanged = (): void => { super.positionChanged();
if(this.hasPhysics){
this.collisionShape.center = this.position;
}
this.updateBoundary(); this.updateBoundary();
} }
protected sizeChanged = (): void => { protected sizeChanged(): void {
this.updateBoundary(); this.updateBoundary();
} }
protected scaleChanged = (): void => { protected scaleChanged(): void {
this.updateBoundary(); this.updateBoundary();
} }

View File

@ -56,7 +56,7 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
constructor(){ constructor(){
this.input = InputReceiver.getInstance(); this.input = InputReceiver.getInstance();
this._position = new Vec2(0, 0); this._position = new Vec2(0, 0);
this._position.setOnChange(this.positionChanged); this._position.setOnChange(() => this.positionChanged());
this.receiver = new Receiver(); this.receiver = new Receiver();
this.emitter = new Emitter(); this.emitter = new Emitter();
} }
@ -68,7 +68,7 @@ export default abstract class GameNode implements Positioned, Unique, Updateable
set position(pos: Vec2) { set position(pos: Vec2) {
this._position = pos; this._position = pos;
this._position.setOnChange(this.positionChanged); this._position.setOnChange(() => this.positionChanged());
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 * 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){ if(this.hasPhysics){
this.collisionShape.center = this.position; this.collisionShape.center = this.position;
} }

View File

@ -5,18 +5,13 @@ import Vec2 from "../DataTypes/Vec2";
/** /**
* The representation of a UIElement - the parent class of things like buttons * 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 // Style attributes
protected textColor: Color;
protected backgroundColor: Color; protected backgroundColor: Color;
protected borderColor: Color; protected borderColor: Color;
protected text: string;
protected font: string;
protected fontSize: number;
protected hAlign: string;
protected vAlign: string;
protected borderRadius: number; protected borderRadius: number;
protected borderWidth: number; protected borderWidth: number;
protected padding: Vec2;
// EventAttributes // EventAttributes
onClick: Function; onClick: Function;
@ -35,16 +30,11 @@ export default class UIElement extends CanvasNode {
super(); super();
this.position = position; this.position = position;
this.textColor = new Color(0, 0, 0, 1);
this.backgroundColor = new Color(0, 0, 0, 0); this.backgroundColor = new Color(0, 0, 0, 0);
this.borderColor = 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.borderRadius = 5;
this.borderWidth = 1; this.borderWidth = 1;
this.padding = Vec2.ZERO;
this.onClick = null; this.onClick = null;
this.onClickEventId = null; this.onClickEventId = null;
@ -60,16 +50,12 @@ export default class UIElement extends CanvasNode {
this.isEntered = false; this.isEntered = false;
} }
setText(text: string): void {
this.text = text;
}
setBackgroundColor(color: Color): void { setBackgroundColor(color: Color): void {
this.backgroundColor = color; this.backgroundColor = color;
} }
setTextColor(color: Color): void { setPadding(padding: Vec2): void {
this.textColor = color; this.padding.copy(padding);
} }
update(deltaT: number): void { 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 * 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 { protected calculateBorderColor(): string {
return this.borderColor.toStringRGBA(); 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);
}
} }

View File

@ -1,13 +1,12 @@
import UIElement from "../UIElement"; import Label from "./Label";
import Color from "../../Utils/Color"; import Color from "../../Utils/Color";
import Vec2 from "../../DataTypes/Vec2"; import Vec2 from "../../DataTypes/Vec2";
export default class Button extends UIElement{ export default class Button extends Label {
constructor(position: Vec2, text: string){ constructor(position: Vec2, text: string){
super(position); super(position, text);
this.text = text;
this.backgroundColor = new Color(150, 75, 203); this.backgroundColor = new Color(150, 75, 203);
this.borderColor = new Color(41, 46, 30); this.borderColor = new Color(41, 46, 30);
this.textColor = new Color(255, 255, 255); this.textColor = new Color(255, 255, 255);

View File

@ -1,9 +1,130 @@
import Vec2 from "../../DataTypes/Vec2"; import Vec2 from "../../DataTypes/Vec2";
import Color from "../../Utils/Color";
import UIElement from "../UIElement"; import UIElement from "../UIElement";
export default class Label extends 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){ constructor(position: Vec2, text: string){
super(position); super(position);
this.text = text; 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);
} }
} }

View File

@ -13,6 +13,7 @@ export default class Viewport {
private view: AABB; private view: AABB;
private boundary: AABB; private boundary: AABB;
private following: GameNode; private following: GameNode;
private focus: Vec2;
/** /**
* A queue of previous positions of what this viewport is following. Used for smoothing viewport movement * 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.smoothingFactor = 10;
this.scrollZoomEnabled = false; this.scrollZoomEnabled = false;
this.canvasSize = Vec2.ZERO; this.canvasSize = Vec2.ZERO;
this.focus = Vec2.ZERO;
} }
enableZoom(): void { enableZoom(): void {
@ -130,6 +132,14 @@ export default class Viewport {
if(smoothingFactor < 1) smoothingFactor = 1; if(smoothingFactor < 1) smoothingFactor = 1;
this.smoothingFactor = smoothingFactor; 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 * Returns true if the CanvasNode is inside of the viewport
@ -223,6 +233,7 @@ export default class Viewport {
this.view.center.copy(pos); this.view.center.copy(pos);
} else { } else {
this.lastPositions.enqueue(this.focus);
if(this.lastPositions.getSize() > this.smoothingFactor){ if(this.lastPositions.getSize() > this.smoothingFactor){
this.lastPositions.dequeue(); this.lastPositions.dequeue();
} }

View File

@ -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 = <Button>this.add.uiElement(UIElementType.BUTTON, "Main", {position: new Vec2(size.x, size.y), text: "Play Game"});
this.playBtn.setBackgroundColor(Color.GREEN);
this.playBtn.setPadding(new Vec2(50, 10));
this.playBtn.onClick = () => {
console.log("Play");
}
}
updateScene(): void {
Debug.log("mp", InputReceiver.getInstance().getMousePosition().toString());
}
}

View File

@ -1,5 +1,6 @@
import GameLoop from "./Loop/GameLoop"; import GameLoop from "./Loop/GameLoop";
import {} from "./index"; import {} from "./index";
import MainMenu from "./_DemoClasses/Mario/MainMenu";
import Level1 from "./_DemoClasses/Mario/Level1"; import Level1 from "./_DemoClasses/Mario/Level1";
function main(){ function main(){
@ -26,7 +27,7 @@ function main(){
} }
let sm = game.getSceneManager(); let sm = game.getSceneManager();
sm.addScene(Level1, sceneOptions); sm.addScene(MainMenu, {});
} }
CanvasRenderingContext2D.prototype.roundedRect = function(x: number, y: number, w: number, h: number, r: number): void { CanvasRenderingContext2D.prototype.roundedRect = function(x: number, y: number, w: number, h: number, r: number): void {