@needle-tools/engine
Version:
Needle Engine is a web-based runtime for 3D apps. It runs on your machine for development with great integrations into editors like Unity or Blender - and can be deployed onto any device! It is flexible, extensible and networking and XR are built-in.
316 lines • 11.8 kB
JavaScript
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
import { showBalloonMessage } from "../../engine/debug/index.js";
import { Gizmos } from "../../engine/engine_gizmos.js";
import { PointerType } from "../../engine/engine_input.js";
import { serializable } from "../../engine/engine_serialization_decorator.js";
import { getParam } from "../../engine/engine_utils.js";
import { RGBAColor } from "../../engine/js-extensions/index.js";
import { Animator } from "../Animator.js";
import { Behaviour, GameObject } from "../Component.js";
import { EventList } from "../EventList.js";
import { Image } from "./Image.js";
import { GraphicRaycaster, Raycaster } from "./Raycaster.js";
const debug = getParam("debugbutton");
/// <summary>
///Transition mode for a Selectable.
/// </summary>
export var Transition;
(function (Transition) {
/// <summary>
/// No Transition.
/// </summary>
Transition[Transition["None"] = 0] = "None";
/// <summary>
/// Use an color tint transition.
/// </summary>
Transition[Transition["ColorTint"] = 1] = "ColorTint";
/// <summary>
/// Use a sprite swap transition.
/// </summary>
Transition[Transition["SpriteSwap"] = 2] = "SpriteSwap";
/// <summary>
/// Use an animation transition.
/// </summary>
Transition[Transition["Animation"] = 3] = "Animation";
})(Transition || (Transition = {}));
class ButtonColors {
colorMultiplier;
disabledColor;
fadeDuration;
highlightedColor;
normalColor;
pressedColor;
selectedColor;
}
__decorate([
serializable()
], ButtonColors.prototype, "colorMultiplier", void 0);
__decorate([
serializable(RGBAColor)
], ButtonColors.prototype, "disabledColor", void 0);
__decorate([
serializable()
], ButtonColors.prototype, "fadeDuration", void 0);
__decorate([
serializable(RGBAColor)
], ButtonColors.prototype, "highlightedColor", void 0);
__decorate([
serializable(RGBAColor)
], ButtonColors.prototype, "normalColor", void 0);
__decorate([
serializable(RGBAColor)
], ButtonColors.prototype, "pressedColor", void 0);
__decorate([
serializable(RGBAColor)
], ButtonColors.prototype, "selectedColor", void 0);
class AnimationTriggers {
disabledTrigger;
highlightedTrigger;
normalTrigger;
pressedTrigger;
selectedTrigger;
}
/**
* @category User Interface
* @group Components
*/
export class Button extends Behaviour {
/**
* Invokes the onClick event
*/
click() {
this.onClick?.invoke();
}
onClick = new EventList();
_isHovered = 0;
onPointerEnter(evt) {
const canSetCursor = evt.event.pointerType === "mouse" && evt.button === 0;
if (canSetCursor)
this._isHovered += 1;
if (debug)
console.warn("Button Enter", canSetCursor, this._isHovered, this.animationTriggers?.highlightedTrigger, this.animator);
if (!this.interactable)
return;
if (this.transition == Transition.Animation && this.animationTriggers && this.animator) {
this.animator.setTrigger(this.animationTriggers.highlightedTrigger);
}
else if (this.transition === Transition.ColorTint && this.colors) {
this._image?.setState("hovered");
}
if (canSetCursor)
this.context.input.setCursor("pointer");
}
onPointerExit() {
this._isHovered -= 1;
if (this._isHovered < 0)
this._isHovered = 0;
if (debug)
console.log("Button Exit", this._isHovered, this.animationTriggers?.highlightedTrigger, this.animator);
if (!this.interactable)
return;
if (this._isHovered > 0)
return;
this._isHovered = 0;
if (this.transition == Transition.Animation && this.animationTriggers && this.animator) {
this.animator.setTrigger(this.animationTriggers.normalTrigger);
}
else if (this.transition === Transition.ColorTint && this.colors) {
this._image?.setState("normal");
}
this.context.input.unsetCursor("pointer");
}
onPointerDown(_) {
if (debug)
console.log("Button Down", this.animationTriggers?.highlightedTrigger, this.animator);
if (!this.interactable)
return;
if (this.transition == Transition.Animation && this.animationTriggers && this.animator) {
this.animator.setTrigger(this.animationTriggers.pressedTrigger);
}
else if (this.transition === Transition.ColorTint && this.colors) {
this._image?.setState("pressed");
}
}
onPointerUp(_) {
if (debug)
console.warn("Button Up", this.animationTriggers?.highlightedTrigger, this.animator, this._isHovered);
if (!this.interactable)
return;
if (this.transition == Transition.Animation && this.animationTriggers && this.animator) {
this.animator.setTrigger(this._isHovered ? this.animationTriggers.highlightedTrigger : this.animationTriggers.normalTrigger);
}
else if (this.transition === Transition.ColorTint && this.colors) {
this._image?.setState(this._isHovered ? "hovered" : "normal");
}
}
onPointerClick(args) {
if (!this.interactable)
return;
if (args.button !== 0 && args.event.pointerType === PointerType.Mouse)
return;
// Button clicks should only run with left mouse button while using mouse
if (debug) {
console.warn("Button Click", this.onClick);
showBalloonMessage("CLICKED button " + this.name + " at " + this.context.time.frameCount);
}
// TODO: we can not *always* use the event right now because the hotspot sample is relying on onPointerClick on a parent object
// and it's not using the button
if (this.onClick && this.onClick.listenerCount > 0) {
this.onClick.invoke();
args.use();
// debug clicks for WebXR
if (debug) {
const pos = this.gameObject.worldPosition;
pos.add(this.gameObject.worldUp.multiplyScalar(1 + Math.random() * .5));
Gizmos.DrawLabel(pos, "CLICK:" + Date.now(), .1, 1 + Math.random() * .5);
}
}
}
colors;
transition;
animationTriggers;
animator;
// @serializable(Image)
// image? : Image;
set interactable(value) {
this._interactable = value;
if (this._image) {
this._image.setInteractable(value);
if (value)
this._image.setState("normal");
else
this._image.setState("disabled");
}
}
get interactable() { return this._interactable; }
_interactable = true;
set_interactable(value) {
this.interactable = value;
}
awake() {
super.awake();
if (debug)
console.log(this);
this._isInit = false;
this.init();
}
start() {
this._image?.setInteractable(this.interactable);
if (!this.gameObject.getComponentInParent(Raycaster)) {
this.gameObject.addComponent(GraphicRaycaster);
}
}
onEnable() {
super.onEnable();
}
onDestroy() {
if (this._isHovered)
this.context.input.unsetCursor("pointer");
}
_requestedAnimatorTrigger;
*setAnimatorTriggerAtEndOfFrame(requestedTriggerId) {
this._requestedAnimatorTrigger = requestedTriggerId;
yield;
yield;
if (this._requestedAnimatorTrigger == requestedTriggerId) {
this.animator?.setTrigger(requestedTriggerId);
}
}
_isInit = false;
_image;
init() {
if (this._isInit)
return;
this._isInit = true;
this._image = GameObject.getComponent(this.gameObject, Image);
if (this._image) {
this.stateSetup(this._image);
if (this.interactable)
this._image.setState("normal");
else
this._image.setState("disabled");
}
}
stateSetup(image) {
image.setInteractable(this.interactable);
// @marwie : If this piece of code could be moved to the SimpleStateBehavior instanciation location,
// Its setup could be eased :
// @see https://github.com/felixmariotto/three-mesh-ui/blob/7.1.x/examples/ex__keyboard.js#L407
const normal = this.getFinalColor(image.color, this.colors?.normalColor);
const normalState = {
state: "normal",
attributes: {
backgroundColor: normal,
backgroundOpacity: normal.alpha,
},
};
image.setupState(normalState);
const hover = this.getFinalColor(image.color, this.colors?.highlightedColor);
const hoverState = {
state: "hovered",
attributes: {
backgroundColor: hover,
backgroundOpacity: hover.alpha,
},
};
image.setupState(hoverState);
const pressed = this.getFinalColor(image.color, this.colors?.pressedColor);
const pressedState = {
state: "pressed",
attributes: {
backgroundColor: pressed,
backgroundOpacity: pressed.alpha,
}
};
image.setupState(pressedState);
const selected = this.getFinalColor(image.color, this.colors?.selectedColor);
const selectedState = {
state: "selected",
attributes: {
backgroundColor: selected,
backgroundOpacity: selected.alpha,
}
};
image.setupState(selectedState);
const disabled = this.getFinalColor(image.color, this.colors?.disabledColor);
const disabledState = {
state: "disabled",
attributes: {
backgroundColor: disabled,
// @marwie, this disabled alpha property doesn't seem to have the opacity requested in unity
backgroundOpacity: disabled.alpha
}
};
image.setupState(disabledState);
}
getFinalColor(col, col2) {
if (col2) {
return col.clone().multiply(col2).convertLinearToSRGB();
}
return col.clone().convertLinearToSRGB();
}
}
__decorate([
serializable(EventList)
], Button.prototype, "onClick", void 0);
__decorate([
serializable(ButtonColors)
], Button.prototype, "colors", void 0);
__decorate([
serializable()
], Button.prototype, "transition", void 0);
__decorate([
serializable(AnimationTriggers)
], Button.prototype, "animationTriggers", void 0);
__decorate([
serializable(Animator)
], Button.prototype, "animator", void 0);
__decorate([
serializable()
], Button.prototype, "interactable", null);
//# sourceMappingURL=Button.js.map