UNPKG

@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
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