8bitworkshop/src/common/script/lib/scriptui.ts

194 lines
4.8 KiB
TypeScript

import * as io from "./io";
// sequence counter
var $$seq : number = 0;
// if an event is specified, it goes here
export const EVENT_KEY = "$$event";
// an object that can become interactive, identified by ID
export interface Interactive {
$$interact: InteractionRecord;
}
export interface InteractEvent {
interactid : number;
type: string;
x?: number;
y?: number;
button?: boolean;
}
export type InteractCallback = (event: InteractEvent) => void;
// InteractionRecord maps a target object to an interaction ID
// the $$callback is used once per script eval, then gets nulled
// whether or not it's invoked
// event comes from $$data.$$event
export class InteractionRecord implements io.Loadable {
readonly interacttarget: Interactive;
interactid : number;
lastevent : {} = null;
constructor(
interacttarget: Interactive,
private $$callback: InteractCallback
) {
this.interacttarget = interacttarget || (<any>this as Interactive);
this.interactid = ++$$seq;
}
$$setstate(newstate: {interactid: number}) {
this.interactid = newstate.interactid;
this.interacttarget.$$interact = this;
let event : InteractEvent = io.data.get(EVENT_KEY);
if (event && event.interactid == this.interactid) {
if (this.$$callback) {
this.$$callback(event);
}
this.lastevent = event;
io.data.set(EVENT_KEY, null);
}
this.$$callback = null;
}
$$getstate() {
//TODO: this isn't always cleared before we serialize (e.g. if exception or move element)
//and we do it in checkResult() too
this.$$callback = null;
return {interactid: this.interactid};
}
}
export function isInteractive(obj: object): obj is Interactive {
return !!((obj as Interactive).$$interact);
}
export function interact(object: any, callback) : InteractionRecord {
// TODO: limit to Bitmap, etc
if (typeof object === 'object') {
return new InteractionRecord(object, callback);
}
throw new Error(`This object is not capable of interaction.`);
}
///
export interface ScriptUIType {
uitype : string;
}
export class ScriptUISliderType implements ScriptUIType {
readonly uitype = 'slider';
value: number;
constructor(
readonly min: number,
readonly max: number,
readonly step: number
) {
this.value = min;
}
}
export class ScriptUISlider extends ScriptUISliderType implements io.Loadable {
initial(value: number) {
this.value = value;
return this;
}
$$getstate() {
return { value: this.value };
}
}
export function slider(min: number, max: number, step?: number) {
return new ScriptUISlider(min, max, step || 1);
}
///
export class ScriptUISelectType<T> implements ScriptUIType {
readonly uitype = 'select';
value: T;
index: number;
constructor(
readonly options: T[]
) {
this.index = 0;
this.value = this.options[this.index];
}
}
export class ScriptUISelect<T> extends ScriptUISelectType<T> implements io.Loadable {
initial(index: number) {
this.index = index;
this.value = this.options[index];
return this;
}
$$getstate() {
return { value: this.value, index: this.index };
}
}
export function select(options: any[]) {
return new ScriptUISelect(options);
}
///
export class ScriptUIButtonType extends InteractionRecord implements ScriptUIType, Interactive {
readonly uitype = 'button';
$$interact: InteractionRecord;
enabled?: boolean;
constructor(
readonly label: string,
callback: InteractCallback
) {
super(null, callback);
this.$$interact = this;
}
}
export class ScriptUIButton extends ScriptUIButtonType {
}
export function button(name: string, callback: InteractCallback) {
return new ScriptUIButton(name, callback);
}
export class ScriptUIToggle extends ScriptUIButton implements io.Loadable {
// share with InteractionRecord
$$getstate() {
let state = super.$$getstate() as any;
state.enabled = this.enabled;
return state;
}
$$setstate(newstate: any) {
this.enabled = newstate.enabled;
super.$$setstate(newstate);
}
}
export function toggle(name: string) {
return new ScriptUIToggle(name, function(e) {
this.enabled = !this.enabled;
});
}
///
export class ScriptUIShortcut extends InteractionRecord implements ScriptUIType, Interactive {
readonly uitype = 'shortcut';
$$interact: InteractionRecord;
constructor(
readonly key: string,
callback: InteractCallback
) {
super(null, callback);
this.$$interact = this;
}
}
export function key(key: string, callback: InteractCallback) {
return new ScriptUIShortcut(key, callback);
}