diff --git a/src/Input/InputReceiver.ts b/src/Input/InputReceiver.ts index 268fd09..37ff36c 100644 --- a/src/Input/InputReceiver.ts +++ b/src/Input/InputReceiver.ts @@ -21,7 +21,8 @@ export default class InputReceiver{ this.receiver = new Receiver(); this.keyJustPressed = new Map(); this.keyPressed = new Map(); - this.mousePressPosition = null; + this.mousePosition = new Vec2(0, 0); + this.mousePressPosition = new Vec2(0, 0); this.eventQueue = EventQueue.getInstance(); this.eventQueue.subscribe(this.receiver, ["mouse_down", "mouse_up", "mouse_move", "key_down", "key_up", "canvas_blur"]); diff --git a/src/Nodes/UIElement.ts b/src/Nodes/UIElement.ts index 3246823..51c9534 100644 --- a/src/Nodes/UIElement.ts +++ b/src/Nodes/UIElement.ts @@ -9,12 +9,22 @@ export default class UIElement extends CanvasNode{ borderColor: Color; text: string; font: string; + fontSize: number; + hAlign: string; + vAlign: string; // EventAttributes onClick: Function; onClickEventId: string; - onHover: Function; - onHoverEventId: string; + onRelease: Function; + onReleaseEventId: string; + onEnter: Function; + onEnterEventId: string; + onLeave: Function; + onLeaveEventId: string; + + protected isClicked: boolean; + protected isEntered: boolean; constructor(){ super(); @@ -22,13 +32,23 @@ export default class UIElement extends CanvasNode{ this.backgroundColor = new Color(0, 0, 0, 0); this.borderColor = new Color(0, 0, 0, 0); this.text = ""; - this.font = "30px Arial"; + this.font = "Arial"; + this.fontSize = 30; + this.hAlign = "center"; + this.vAlign = "center"; this.onClick = null; this.onClickEventId = null; + this.onRelease = null; + this.onReleaseEventId = null; - this.onHover = null; - this.onHoverEventId = null; + this.onEnter = null; + this.onEnterEventId = null; + this.onLeave = null; + this.onLeaveEventId = null; + + this.isClicked = false; + this.isEntered = false; } setPosition(vecOrX: Vec2 | number, y: number = null): void { @@ -61,32 +81,96 @@ export default class UIElement extends CanvasNode{ update(deltaT: number): void { if(this.input.isMouseJustPressed()){ - let mousePos = this.input.getMousePressPosition(); - if(mousePos.x >= this.position.x && mousePos.x <= this.position.x + this.size.x){ - // Inside x bounds - if(mousePos.y >= this.position.y && mousePos.y <= this.position.y + this.size.y){ - // Inside element - if(this.onHover !== null){ - this.onHover(); - } + let clickPos = this.input.getMousePressPosition(); + if(this.contains(clickPos.x, clickPos.y)){ + this.isClicked = true; - if(this.onClick !== null){ - this.onClick(); - } - if(this.onClickEventId !== null){ - let data = {}; - this.emit(this.onClickEventId, data); - } + if(this.onClick !== null){ + this.onClick(); + } + if(this.onClickEventId !== null){ + let data = {}; + this.emit(this.onClickEventId, data); } } } + + if(!this.input.isMousePressed()){ + if(this.isClicked){ + this.isClicked = false; + } + } + + let mousePos = this.input.getMousePosition(); + if(mousePos && this.contains(mousePos.x, mousePos.y)){ + this.isEntered = true; + + if(this.onEnter !== null){ + this.onEnter(); + } + if(this.onEnterEventId !== null){ + let data = {}; + this.emit(this.onEnterEventId, data); + } + + } else if(this.isEntered) { + this.isEntered = false; + + if(this.onLeave !== null){ + this.onLeave(); + } + if(this.onLeaveEventId !== null){ + let data = {}; + this.emit(this.onLeaveEventId, data); + } + } else if(this.isClicked) { + // If mouse is dragged off of element while down, it is not clicked anymore + this.isClicked = false; + } } - render(ctx: CanvasRenderingContext2D, viewportOrigin: Vec2, viewportSize: Vec2){ - ctx.fillStyle = this.backgroundColor.toStringRGBA(); + 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; + } + + protected calculateBackgroundColor(): string { + return this.backgroundColor.toStringRGBA(); + } + + protected calculateTextColor(): string { + return this.textColor.toStringRGBA(); + } + + render(ctx: CanvasRenderingContext2D, viewportOrigin: Vec2, viewportSize: Vec2): void { + ctx.font = this.fontSize + "px " + this.font; + let offset = this.calculateOffset(ctx); + + ctx.fillStyle = this.calculateBackgroundColor(); ctx.fillRect(this.position.x - viewportOrigin.x, this.position.y - viewportOrigin.y, this.size.x, this.size.y); - ctx.fillStyle = this.textColor.toStringRGBA(); - ctx.font = this.font; - ctx.fillText(this.text, this.position.x - viewportOrigin.x, this.position.y - viewportOrigin.y + 30); + + ctx.fillStyle = this.calculateTextColor(); + ctx.fillText(this.text, this.position.x + offset.x - viewportOrigin.x, this.position.y + offset.y - viewportOrigin.y); } } \ No newline at end of file diff --git a/src/Nodes/UIElements/Button.ts b/src/Nodes/UIElements/Button.ts new file mode 100644 index 0000000..8cfa985 --- /dev/null +++ b/src/Nodes/UIElements/Button.ts @@ -0,0 +1,23 @@ +import UIElement from "../UIElement"; +import Color from "../../Utils/Color"; +import Vec2 from "../../DataTypes/Vec2"; + +export default class Button extends UIElement{ + + constructor(){ + super(); + this.backgroundColor = new Color(150, 75, 203); + this.borderColor = new Color(41, 46, 30); + this.textColor = new Color(255, 255, 255); + } + + protected calculateBackgroundColor(): string { + if(this.isEntered && !this.isClicked){ + return this.backgroundColor.lighten().toStringRGBA(); + } else if(this.isClicked){ + return this.backgroundColor.darken().toStringRGBA(); + } else { + return this.backgroundColor.toStringRGBA(); + } + } +} \ No newline at end of file diff --git a/src/Nodes/UIElements/Label.ts b/src/Nodes/UIElements/Label.ts new file mode 100644 index 0000000..fbadd7e --- /dev/null +++ b/src/Nodes/UIElements/Label.ts @@ -0,0 +1,8 @@ +import UIElement from "../UIElement"; + +export default class Label extends UIElement{ + constructor(text: string){ + super(); + this.text = text; + } +} \ No newline at end of file diff --git a/src/Utils/Color.ts b/src/Utils/Color.ts index ec054d9..70f5ba6 100644 --- a/src/Utils/Color.ts +++ b/src/Utils/Color.ts @@ -12,19 +12,27 @@ export default class Color{ this.b = b; this.a = a; } + + lighten(): Color { + return new Color(MathUtils.clamp(this.r + 40, 0, 255), MathUtils.clamp(this.g + 40, 0, 255), MathUtils.clamp(this.b + 40, 0, 255), this.a); + } + + darken(): Color { + return new Color(MathUtils.clamp(this.r - 40, 0, 255), MathUtils.clamp(this.g - 40, 0, 255), MathUtils.clamp(this.b - 40, 0, 255), this.a); + } - toString(): string{ + toString(): string { return "#" + MathUtils.toHex(this.r, 2) + MathUtils.toHex(this.g, 2) + MathUtils.toHex(this.b, 2); } - toStringRGB(){ + toStringRGB(): string { return "rgb(" + this.r.toString() + ", " + this.g.toString() + ", " + this.b.toString() + ")"; } - toStringRGBA(){ + toStringRGBA(): string { if(this.a === null){ - throw "No alpha value assigned to color"; + return this.toStringRGB(); } - return "rgb(" + this.r.toString() + ", " + this.g.toString() + ", " + this.b.toString() + ", " + this.a.toString() +")" + return "rgba(" + this.r.toString() + ", " + this.g.toString() + ", " + this.b.toString() + ", " + this.a.toString() +")" } } \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 32d1345..18f2d9c 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,6 +4,7 @@ import Player from "./Nodes/Player"; import UIElement from "./Nodes/UIElement"; import ColoredCircle from "./Nodes/ColoredCircle"; import Color from "./Utils/Color"; +import Button from "./Nodes/UIElements/Button"; function main(){ // Create the game object @@ -15,31 +16,27 @@ function main(){ // Initialize GameObjects let player = new Player(); - let recordButton = new UIElement(); + let recordButton = new Button(); recordButton.setSize(100, 50); recordButton.setText("Record"); - recordButton.setBackgroundColor(new Color(200, 100, 0, 0.3)); recordButton.setPosition(400, 30); recordButton.onClickEventId = "record_button_press"; - let stopButton = new UIElement(); + let stopButton = new Button(); stopButton.setSize(100, 50); stopButton.setText("Stop"); - stopButton.setBackgroundColor(new Color(200, 0, 0, 0.3)); stopButton.setPosition(550, 30); stopButton.onClickEventId = "stop_button_press"; - let playButton = new UIElement(); + let playButton = new Button(); playButton.setSize(100, 50); playButton.setText("Play"); - playButton.setBackgroundColor(new Color(0, 200, 0, 0.3)); playButton.setPosition(700, 30); playButton.onClickEventId = "play_button_press"; - let cycleFramerateButton = new UIElement(); + let cycleFramerateButton = new Button(); cycleFramerateButton.setSize(150, 50); cycleFramerateButton.setText("Cycle FPS"); - cycleFramerateButton.setBackgroundColor(new Color(200, 0, 200, 0.3)); cycleFramerateButton.setPosition(5, 400); let i = 0; let fps = [15, 30, 60]; @@ -48,10 +45,9 @@ function main(){ i = (i + 1) % 3; } - let pauseButton = new UIElement(); + let pauseButton = new Button(); pauseButton.setSize(100, 50); pauseButton.setText("Pause"); - pauseButton.setBackgroundColor(new Color(200, 0, 200, 1)); pauseButton.setPosition(700, 400); pauseButton.onClick = () => { game.getGameState().addScene(pauseMenu); @@ -62,10 +58,9 @@ function main(){ modalBackground.setBackgroundColor(new Color(0, 0, 0, 0.4)); modalBackground.setPosition(200, 100); - let resumeButton = new UIElement(); + let resumeButton = new Button(); resumeButton.setSize(100, 50); resumeButton.setText("Resume"); - resumeButton.setBackgroundColor(new Color(200, 0, 200, 1)); resumeButton.setPosition(400, 200); resumeButton.onClick = () => { game.getGameState().removeScene();