add controlPanel component
This commit is contained in:
parent
409ebdf7fa
commit
4e609c171a
185
lib/_buttons.js
Normal file
185
lib/_buttons.js
Normal file
|
@ -0,0 +1,185 @@
|
|||
const STYLE_DEFAULT = {
|
||||
background: '#eee',
|
||||
color: '#111',
|
||||
border_color: '',
|
||||
border_width: 0,
|
||||
border_radius: 5,
|
||||
|
||||
text_font: 'sans-serif',
|
||||
text_size: 12,
|
||||
}
|
||||
|
||||
const STYLE_HOVER = {
|
||||
background: '#ccc',
|
||||
color: '#111',
|
||||
}
|
||||
|
||||
const STYLE_PRESSED = {
|
||||
background: '#aaa',
|
||||
color: '#000',
|
||||
}
|
||||
|
||||
const STYLE_DISABLED = {
|
||||
background: '#777',
|
||||
color: '#333',
|
||||
}
|
||||
|
||||
export default class Button {
|
||||
|
||||
#was_pressed = false;
|
||||
#was_hovering = false;
|
||||
|
||||
#props = { };
|
||||
#bounds = { minx: null, miny: null, maxx: null, maxy: null, centerx: null, centery: null };
|
||||
#cstyles = { default: STYLE_DEFAULT, hover: STYLE_HOVER, pressed: STYLE_PRESSED, disabled: STYLE_DISABLED };
|
||||
|
||||
constructor(properties) {
|
||||
this.#props = Object.assign({
|
||||
content: '',
|
||||
x: null, y: null,
|
||||
w: null, h: null,
|
||||
width: null,
|
||||
height: null,
|
||||
|
||||
style_default: STYLE_DEFAULT,
|
||||
style_hover: STYLE_HOVER,
|
||||
style_pressed: STYLE_PRESSED,
|
||||
style_disabled: STYLE_DISABLED,
|
||||
|
||||
on_mouse_enter: null,
|
||||
on_mouse_exit: null,
|
||||
on_press: null,
|
||||
on_release: null,
|
||||
|
||||
align_x: -1,
|
||||
align_y: -1,
|
||||
|
||||
enabled: true,
|
||||
}, properties);
|
||||
|
||||
if ( this.#props.x === null || this.#props.y === null || this.#props.width === null || this.#props.height === null )
|
||||
throw( '"x", "y", "width", and "height" must all be defined in the button properties!' );
|
||||
|
||||
Object.seal(this.#props);
|
||||
this.#calculateStyles();
|
||||
this.#calculateBounds();
|
||||
}
|
||||
|
||||
#calculateBounds() {
|
||||
const offset_x = (this.#props.align_x-1)*this.#props.width/2,
|
||||
offset_y = (this.#props.align_y-1)*this.#props.height/2;
|
||||
|
||||
this.#bounds.minx = this.#props.x + offset_x,
|
||||
this.#bounds.miny = this.#props.y + offset_y,
|
||||
this.#bounds.maxx = this.#props.x + this.#props.width + offset_x,
|
||||
this.#bounds.maxy = this.#props.y + this.#props.height + offset_y,
|
||||
this.#bounds.centerx = this.#props.x + this.#props.width/2 + offset_x,
|
||||
this.#bounds.centery = this.#props.y + this.#props.height/2 + offset_y;
|
||||
}
|
||||
|
||||
#calculateStyles() {
|
||||
this.#cstyles.default = Object.assign({}, STYLE_DEFAULT, this.#props.style_default);
|
||||
this.#cstyles.hover = Object.assign({}, STYLE_DEFAULT, this.#props.style_default, this.#props.style_hover);
|
||||
this.#cstyles.pressed = Object.assign({}, STYLE_DEFAULT, this.#props.style_default, this.#props.style_pressed);
|
||||
this.#cstyles.disabled = Object.assign({}, STYLE_DEFAULT, this.#props.style_default, this.#props.style_disabled);
|
||||
}
|
||||
|
||||
update(properties) {
|
||||
try {
|
||||
Object.assign(this.#props, properties);
|
||||
} catch(e) {console.warn( `Encountered an unrecognized property! Original error: ${e.message}` )}
|
||||
|
||||
if ( 'x' in properties || 'y' in properties || 'width' in properties || 'height' in properties || 'align_x' in properties || 'align_y' in properties )
|
||||
this.#calculateBounds();
|
||||
|
||||
if ( 'style_default' in properties || 'style_hover' in properties || 'style_pressed' in properties || 'style_disabled' in properties )
|
||||
this.#calculateStyles();
|
||||
}
|
||||
|
||||
/** Shorthand for .update({ x: <value>, y: <value>, ... }) */
|
||||
place( x, y, width=null, height=null ) {
|
||||
this.#props.x = x,
|
||||
this.#props.y = y;
|
||||
if ( width !== null ) this.#props.width = width;
|
||||
if ( height !== null ) this.#props.height = height;
|
||||
this.#calculateBounds();
|
||||
}
|
||||
|
||||
/** Shorthand for .update({ style_<stylename>: <value> }) */
|
||||
style( stylename, style ) {
|
||||
if (!( 'style_'+stylename in this.#props ))
|
||||
throw( `Style name must be either "default", "hover", "pressed", or "disabled". Received "${stylename}" instead.` );
|
||||
|
||||
this.#props['style_'+stylename] = style;
|
||||
this.#calculateStyles();
|
||||
}
|
||||
|
||||
/** Shorthand for .update({ content: <value> }) */
|
||||
text( content ) { this.#props.content = content }
|
||||
|
||||
/** Shorthand for .update({ enabled: true }) */
|
||||
enable() { this.#props.enabled = true }
|
||||
|
||||
/** Shorthand for .update({ enabled: false }) */
|
||||
disable() { this.#props.enabled = false }
|
||||
|
||||
/**
|
||||
* Returns whether the specified point (by default the mouse's position) is hovering over the button.
|
||||
* @param {number} x (optional) x override.
|
||||
* @param {number} y (optional) y override.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isHovering(x=mouseX, y=mouseY) {
|
||||
return x > this.#bounds.minx && x < this.#bounds.maxx && y > this.#bounds.miny && y < this.#bounds.maxy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the button is currently being pressed.
|
||||
* @returns {boolean}
|
||||
*/
|
||||
isPressed() {
|
||||
return this.isHovering() && mouseIsPressed;
|
||||
}
|
||||
|
||||
#getCurrentStyle( hovering, pressed ) {
|
||||
if ( !this.#props.enabled ) return this.#cstyles.disabled;
|
||||
if ( pressed ) return this.#cstyles.pressed;
|
||||
if ( hovering ) return this.#cstyles.hover;
|
||||
return this.#cstyles.default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the button on the specified canvas, or the global canvas by default.
|
||||
* @param context (optional) The p5 canvas to draw to.
|
||||
*/
|
||||
draw( context=globalThis ) {
|
||||
const is_hovering = this.isHovering();
|
||||
const is_pressed = mouseIsPressed && (is_hovering || this.#was_pressed);
|
||||
const style = this.#getCurrentStyle( is_hovering, is_pressed );
|
||||
if ( style.background ) context.fill( style.background );
|
||||
else context.noFill();
|
||||
|
||||
|
||||
if ( style.border_color && style.border_radius ) {
|
||||
context.stroke( style.border_color );
|
||||
context.strokeWeight( style.border_width );
|
||||
} else context.noStroke();
|
||||
|
||||
context.rect( this.#bounds.minx, this.#bounds.miny, this.#props.width, this.#props.height, style.border_radius );
|
||||
|
||||
noStroke();
|
||||
context.fill( style.color );
|
||||
context.textAlign( CENTER, CENTER );
|
||||
context.textSize( style.text_size );
|
||||
context.textFont( style.text_font );
|
||||
context.text( this.#props.content, this.#bounds.centerx, this.#bounds.centery );
|
||||
|
||||
if ( !this.#was_pressed && is_pressed && this.#props.on_press ) this.#props.on_press();
|
||||
if ( this.#was_pressed && !is_pressed && this.#props.on_release ) this.#props.on_release();
|
||||
if ( !this.#was_hovering && is_hovering && this.#props.on_mouse_enter ) this.#props.on_mouse_enter();
|
||||
if ( this.#was_hovering && !is_hovering && this.#props.on_mouse_exit ) this.#props.on_mouse_exit();
|
||||
|
||||
this.#was_pressed = is_pressed;
|
||||
this.#was_hovering = is_hovering;
|
||||
}
|
||||
}
|
BIN
public/assets/down.png
Normal file
BIN
public/assets/down.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
BIN
public/assets/empty.png
Normal file
BIN
public/assets/empty.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
BIN
public/assets/left.png
Normal file
BIN
public/assets/left.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
BIN
public/assets/right.png
Normal file
BIN
public/assets/right.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.8 KiB |
BIN
public/assets/up.png
Normal file
BIN
public/assets/up.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.7 KiB |
21
src/components/Arrow.js
Normal file
21
src/components/Arrow.js
Normal file
|
@ -0,0 +1,21 @@
|
|||
export class Arrow {
|
||||
static images = {};
|
||||
|
||||
static preload() {
|
||||
Arrow.images.up = loadImage('/assets/up.png');
|
||||
Arrow.images.down = loadImage('/assets/down.png');
|
||||
Arrow.images.left = loadImage('/assets/left.png');
|
||||
Arrow.images.right = loadImage('/assets/right.png');
|
||||
Arrow.images.empty = loadImage('/assets/empty.png');
|
||||
}
|
||||
|
||||
constructor(direction) {
|
||||
this.direction = direction;
|
||||
this.image = Arrow.images[direction];
|
||||
}
|
||||
|
||||
draw(x, y) {
|
||||
image(this.image, x, y, 40, 40);
|
||||
}
|
||||
}
|
||||
|
74
src/components/ControlPanel.js
Normal file
74
src/components/ControlPanel.js
Normal file
|
@ -0,0 +1,74 @@
|
|||
import { Arrow } from "./Arrow";
|
||||
import { colors } from "../utils/theme";
|
||||
|
||||
export class ControlPanel {
|
||||
constructor({ name, x, y, numBoxes }) {
|
||||
this.name = name;
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.numBoxes = numBoxes;
|
||||
this.boxWidth = 48;
|
||||
this.boxHeight = 48;
|
||||
this.boxSpacing = 8;
|
||||
this.contents = Array(numBoxes).fill(null);
|
||||
this.empty = new Arrow('empty');
|
||||
this.fontSize = 20;
|
||||
this.gap = this.fontSize;
|
||||
}
|
||||
|
||||
updateBox(index, content) {
|
||||
if (index >= 0 && index < this.numBoxes) {
|
||||
this.contents[index] = content;
|
||||
}
|
||||
}
|
||||
|
||||
draw() {
|
||||
rectMode(CORNER);
|
||||
|
||||
// Label
|
||||
fill(colors.tertiary);
|
||||
noStroke();
|
||||
rect(this.x, this.y, this.getPanelWidth(), this.getTextBoxHeight(), 5);
|
||||
fill(colors.secondary);
|
||||
textAlign(CENTER, CENTER);
|
||||
textSize(this.fontSize);
|
||||
text(this.name, this.x + this.getPanelWidth() / 2, this.y + this.getTextBoxHeight() /2);
|
||||
|
||||
// White panel
|
||||
fill(255);
|
||||
noStroke();
|
||||
rect(this.x, this.y + 24 + this.gap, this.getPanelWidth(), this.boxHeight + this.boxSpacing, 6);
|
||||
|
||||
// Boxes
|
||||
for (let i = 0; i < this.numBoxes; i++) {
|
||||
const bx = this.x + 12 + i * (this.boxWidth + this.boxSpacing);
|
||||
const by = this.y + 24 + this.gap + (this.boxSpacing * 1) / 2;
|
||||
|
||||
// Draw box
|
||||
// fill(255);
|
||||
// stroke(0);
|
||||
// strokeWeight(1.5);
|
||||
// rect(bx, by, this.boxWidth, this.boxHeight, 4);
|
||||
|
||||
// Draw content if it exists
|
||||
if (this.contents[i]) {
|
||||
if (this.contents[i] instanceof Arrow) {
|
||||
this.contents[i].draw(bx + this.boxWidth/2 - 20, by + this.boxHeight/2 - 20);
|
||||
}
|
||||
} else {
|
||||
this.empty.draw(bx + this.boxWidth / 2 - 20, by + this.boxHeight / 2 - 20);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
getPanelWidth() {
|
||||
return this.numBoxes * (this.boxWidth + this.boxSpacing) + 12;
|
||||
}
|
||||
|
||||
getTextBoxHeight(){
|
||||
return this.fontSize + 16;
|
||||
}
|
||||
}
|
10
src/main.js
10
src/main.js
|
@ -1,4 +1,5 @@
|
|||
import { colors } from './theme.js';
|
||||
import { colors } from './utils/theme.js';
|
||||
import { Arrow } from './components/Arrow.js';
|
||||
import StartScene from './scenes/startScene.js';
|
||||
import GameScene from './scenes/gameScene.js';
|
||||
|
||||
|
@ -13,7 +14,7 @@ function setup(){
|
|||
mgr = new SceneManager();
|
||||
mgr.addScene(StartScene);
|
||||
mgr.addScene(GameScene);
|
||||
mgr.showScene(StartScene);
|
||||
mgr.showScene(GameScene);
|
||||
};
|
||||
|
||||
function draw(){
|
||||
|
@ -31,8 +32,13 @@ function mousePressed(){
|
|||
mgr.handleEvent('mousePressed');
|
||||
};
|
||||
|
||||
function preload(){
|
||||
Arrow.preload();
|
||||
}
|
||||
|
||||
|
||||
window.setup = setup;
|
||||
window.draw = draw;
|
||||
window.windowResized = windowResized;
|
||||
window.mousePressed = mousePressed;
|
||||
window.preload = preload;
|
||||
|
|
|
@ -1,13 +1,39 @@
|
|||
import { colors } from '../theme.js';
|
||||
import { draw as drawWorld, groundHeight } from '../world.js';
|
||||
import { colors } from '../utils/theme.js';
|
||||
import { Cat } from '../cat.js';
|
||||
import { buttonS } from '../utils/theme.js';
|
||||
import { MyButton } from '../utils/components.js';
|
||||
import { Arrow } from '../components/Arrow.js';
|
||||
import { ControlPanel } from '../components/controlPanel.js';
|
||||
|
||||
|
||||
export default function GameScene() {
|
||||
let cat;
|
||||
let runButton;
|
||||
let blocks;
|
||||
|
||||
this.name = "GameScene";
|
||||
|
||||
this.setup = () => {
|
||||
cat = new Cat(width / 6, height - 167.5, 150);
|
||||
|
||||
runButton = new MyButton({
|
||||
x: width / 16 * 15,
|
||||
y: height / 16,
|
||||
text: "run >>",
|
||||
mode: "CENTER",
|
||||
style: buttonS,
|
||||
onPress: () => {
|
||||
console.log("Run button pressed");
|
||||
}
|
||||
});
|
||||
|
||||
blocks = new ControlPanel({
|
||||
name: 'blocks',
|
||||
x: width / 32,
|
||||
y: height / 32,
|
||||
numBoxes: 4
|
||||
});
|
||||
|
||||
};
|
||||
|
||||
this.draw = () => {
|
||||
|
@ -27,12 +53,15 @@ export default function GameScene() {
|
|||
|
||||
// Sprite
|
||||
cat.draw();
|
||||
runButton.draw();
|
||||
blocks.draw();
|
||||
|
||||
};
|
||||
|
||||
this.onResize = () => {
|
||||
if (cat) {
|
||||
cat.setPosition(width / 2, height - 177.5);
|
||||
}
|
||||
cat.setPosition(width / 2, height - 177.5);
|
||||
runButton.setPosition((width / 16) * 15, height / 16);
|
||||
blocks.x = width / 16 - 40;
|
||||
blocks.y = height / 16 + 8;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { colors } from '../theme.js';
|
||||
import { colors } from '../utils/theme.js';
|
||||
import { Cat } from '../cat.js';
|
||||
import { drawRect } from '../utils/draw.js';
|
||||
import { buttonL } from '../utils/theme.js';
|
||||
import GameScene from './gameScene.js';
|
||||
|
||||
export default function StartScene() {
|
||||
|
@ -13,7 +13,7 @@ export default function StartScene() {
|
|||
};
|
||||
let startButton;
|
||||
|
||||
// this.name = "StartScene";
|
||||
this.name = "StartScene";
|
||||
|
||||
this.setup = () => {
|
||||
cat = new Cat(width / 2, height - 200, 200);
|
||||
|
@ -25,24 +25,24 @@ export default function StartScene() {
|
|||
startButton.locate(width/2, height/2 + 50);
|
||||
|
||||
// Size
|
||||
startButton.width = 300;
|
||||
startButton.height = 75;
|
||||
startButton.width = buttonL.width;
|
||||
startButton.height = buttonL.height;
|
||||
|
||||
// Visual styling
|
||||
startButton.color = colors.tertiary;
|
||||
startButton.stroke = colors.secondary;
|
||||
startButton.strokeWeight = 3;
|
||||
startButton.color = buttonL.color;
|
||||
startButton.stroke = buttonL.stroke;
|
||||
startButton.strokeWeight = buttonL.strokeWeight;
|
||||
|
||||
// Text properties
|
||||
startButton.text = 'start';
|
||||
startButton.textFont = "Pixelify Sans";
|
||||
startButton.textSize = 32;
|
||||
startButton.textColor = colors.secondary;
|
||||
startButton.textFont = buttonL.textFont;
|
||||
startButton.textSize = buttonL.textSize;
|
||||
startButton.textColor = buttonL.textColor;
|
||||
|
||||
startButton.onPress = () => {
|
||||
console.log("press");
|
||||
this.sceneManager.showScene(GameScene);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
this.draw = () => {
|
||||
|
@ -89,19 +89,9 @@ export default function StartScene() {
|
|||
this.onResize = () => {
|
||||
if (cat) {
|
||||
cat.setPosition(width / 2, height - 200);
|
||||
startButton.locate(width/2, height/2 + 50);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.mousePressed = function (){
|
||||
const x = mouseX;
|
||||
const y = mouseY;
|
||||
|
||||
return;
|
||||
|
||||
this.sceneManager.showScene(GameScene);
|
||||
|
||||
};
|
||||
|
||||
this.exit = function () {
|
||||
if (cat) {
|
||||
|
|
10
src/theme.js
10
src/theme.js
|
@ -1,10 +0,0 @@
|
|||
const colors = {
|
||||
primary: "#a6d1ff",
|
||||
secondary: "#ff4f64",
|
||||
tertiary: "#fff29d",
|
||||
accent: "#a4e4b6"
|
||||
};
|
||||
|
||||
export {
|
||||
colors
|
||||
}
|
40
src/utils/components.js
Normal file
40
src/utils/components.js
Normal file
|
@ -0,0 +1,40 @@
|
|||
export class MyButton {
|
||||
constructor({ x, y, text, mode = "CORNER", style = {}, onPress = null }) {
|
||||
this.button = new Clickable();
|
||||
|
||||
// Apply layout
|
||||
this.button.mode = mode;
|
||||
this.button.locate(x, y);
|
||||
|
||||
// Apply text
|
||||
this.button.text = text;
|
||||
|
||||
// Apply styles
|
||||
this.button.width = style.width || 150;
|
||||
this.button.height = style.height || 50;
|
||||
this.button.color = style.color || "#ffffff";
|
||||
this.button.stroke = style.stroke || "#000000";
|
||||
this.button.strokeWeight = style.strokeWeight || 2;
|
||||
this.button.textFont = style.textFont || "sans-serif";
|
||||
this.button.textSize = style.textSize || 16;
|
||||
this.button.textColor = style.textColor || "#000000";
|
||||
|
||||
// Event
|
||||
if (onPress) {
|
||||
this.button.onPress = onPress;
|
||||
}
|
||||
}
|
||||
|
||||
draw() {
|
||||
this.button.draw();
|
||||
}
|
||||
|
||||
setText(newText) {
|
||||
this.button.text = newText;
|
||||
}
|
||||
|
||||
setPosition(x, y) {
|
||||
this.button.locate(x, y);
|
||||
}
|
||||
};
|
||||
|
32
src/utils/theme.js
Normal file
32
src/utils/theme.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
const colors = {
|
||||
primary: "#a6d1ff",
|
||||
secondary: "#ff4f64",
|
||||
tertiary: "#fff29d",
|
||||
accent: "#a4e4b6",
|
||||
};
|
||||
|
||||
const button = {
|
||||
color: colors.tertiary,
|
||||
stroke: colors.secondary,
|
||||
strokeWeight: 3,
|
||||
textFont: "Pixelify Sans",
|
||||
textColor: colors.secondary
|
||||
}
|
||||
|
||||
const buttonL = {
|
||||
...button,
|
||||
width: 300,
|
||||
height: 75,
|
||||
textSize: 32,
|
||||
};
|
||||
|
||||
|
||||
const buttonS = {
|
||||
...button,
|
||||
width: 100,
|
||||
height: 40,
|
||||
textSize: 20,
|
||||
strokeWeight: 3,
|
||||
};
|
||||
|
||||
export { colors, buttonL, buttonS };
|
|
@ -1,4 +1,4 @@
|
|||
import { colors } from './theme.js';
|
||||
import { colors } from './utils/theme.js';
|
||||
|
||||
export const groundHeight = 100;
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user