import { WindowWidget } from './window'; import { BaseWidget, Widget, WidgetHandler, HandlingSystem } from './widget' import { ColorFiller, LinearGradient, RadialGradient } from './style'; import { Vec, XY } from './common'; import * as Protocol from './protocol'; import { EventEmitter } from 'events'; export class WindowSystem extends EventEmitter { public windows: Map = new Map(); public widgets: Map = new Map(); public handlers: WidgetHandler[] = []; public windowHandlers: WidgetHandler[] = []; public widgetSystems: Map = new Map(); prep(a: Vec | BaseWidget | ColorFiller | LinearGradient | RadialGradient | any): any { if (a instanceof Vec) { return { '$t': 'v', '$v': a.xy() }; } else if (a instanceof BaseWidget) { return { '$t': 'w', '$v': a.id }; } else if (a instanceof ColorFiller) { return { '$t': 'c', '$v': a.col }; } else if (a instanceof LinearGradient) { return { '$t': 'l', '$v': { 'o': a.origin, 't': a.toward, 's': a.start, 'p': a.stop, 'a': a.from, 'b': a.to, } }; } else if (a instanceof RadialGradient) { return { '$t': 'r', '$v': { 'o': a.origin, 'r': a.radius, 's': a.start, 'p': a.stop, 'a': a.from, 'b': a.to, } }; } else { return { '$t': '', '$v': this.rawPrep(a) }; } } rawPrep(a: any): any { if (Array.isArray(a)) return a.map((x) => this.prep(x)); else if (typeof a === 'object' && a != null) { let res: any = {}; Object.keys(a).forEach((k) => { res[k] = this.prep(a[k]); }); return res; } else return a; } unprep(a: any): any { if (!a.$t) { return this.rawUnprep(a.$v); } else if (a.$t === 'v') { return new Vec(a.$v); } else if (a.$t === 'w') { return this.widgets.get(a.$v); } else if (a.$t === 'c') { return new ColorFiller(a.$v); } else if (a.$t === 'l') { return new LinearGradient(a.$v.o, a.$v.t, a.$v.a, a.$v.b, a.$v.s, a.$v.p); } else if (a.$t === 'r') { return new RadialGradient(a.$v.o, a.$v.r, a.$v.a, a.$v.b, a.$v.s, a.$v.p); } } rawUnprep(a: any): any { if (Array.isArray(a)) return a.map((x) => this.unprep(a)); else if (typeof a === 'object') { let res:any = {}; Object.keys(a).forEach((k) => { res[k] = this.unprep(a[k]); }); return res; } else return a; } add(w: WindowWidget | BaseWidget | HandlingSystem) { if (w instanceof HandlingSystem) { this.widgetSystems.set(w.name, w); return; } this.handlers.forEach((handler) => { w.register(handler); }); w.onAny((evt, at, origin, ...args) => { this.emit('widget event', evt, at, origin, ...args); }); if (w instanceof WindowWidget) { this.windowHandlers.forEach((handler) => { w.register(handler); }); this.windows.set(w.id, w); } w.system = this; this.widgets.set(w.id, w); w.children.forEach((c) => { this.add(c); }); } apply(pcall: Protocol.ProtocolCall): Protocol.ProtocolResponse { let respond = Protocol.makeResponse.bind(pcall); switch (pcall.type) { case 'perform': { let call = pcall.call; switch (call.kind) { case 'create-widget': { let w = this.widgets.get(call.parentId); if (w != null && (w instanceof WindowWidget || w instanceof Widget)) { let res = Widget.make(w, { data: call.data || null, id: call.id, name: call.name || undefined }); this.add(res); return respond({ type: 0, id: 'SUCCESS', reason: 'Widget created successfully.' }, res.serialize()); } else return respond({ type: 2, id: 'PARENT-NOT-FOUND', reason: 'Parent not found.' }); }; case 'create-window': { let res = WindowWidget.make({ data: call.data || null, id: call.id, name: call.name || undefined }); this.add(res); return respond({ type: 0, id: 'SUCCESS', reason: 'Window created successfully.' }, res.serialize()); }; case 'destroy': { if (this.widgets.has(call.id)) { this.widgets.get(call.id)!.destroy(call.reason); return respond({ type: 0, id: 'SUCCESS', reason: 'BaseWidget destroyed successfully.' }); } return respond({ type: 1, id: 'WIDGET-NOT-FOUND', reason: 'BaseWidget not found.' }); }; case 'annihilate': { let c: Protocol.ProtocolAnnihilatePerformer = call; this.windows.forEach((w) => { w.destroy(c.reason); }); this.widgets.forEach((w) => { w.destroy(c.reason); }); return respond({ type: 0, id: 'SUCCESS', reason: 'All windows and widgets destroyed successfully.' }); }; case 'emit': { if (this.widgets.has(call.id)) { this.widgets.get(call.id)!.emitOutFromHere(call.event, ...call.args); return respond({ type: 0, id: 'SUCCESS', reason: 'Bidirectional event emitted successfully.' }); } return respond({ type: 1, id: 'WIDGET-NOT-FOUND', reason: 'BaseWidget not found.' }); }; case 'emit-up': { if (this.widgets.has(call.id)) { this.widgets.get(call.id)!.emitUpFromHere(call.event, ...call.args); return respond({ type: 0, id: 'SUCCESS', reason: 'Upward event emitted successfully.' }); } return respond({ type: 1, id: 'WIDGET-NOT-FOUND', reason: 'BaseWidget not found.' }); }; case 'emit-down': { if (this.widgets.has(call.id)) { this.widgets.get(call.id)!.emitDownFromHere(call.event, ...call.args); return respond({ type: 0, id: 'SUCCESS', reason: 'Downward event emitted successfully.' }); } return respond({ type: 1, id: 'WIDGET-NOT-FOUND', reason: 'BaseWidget not found.' }); }; case "set": { let widget = this.widgets.get(call.id); if (widget !== undefined) { let keys = call.keys; keys.unshift('data'); let res: any = widget; keys.slice(0, -1).forEach((k, i) => { if (!(k in res)) { return respond({ type: 3, id: 'KEY-NOT-FOUND', reason: `Key #${i - 1} (${k}) not found in this widget's data!` }); } res = res[k]; }); res[keys.slice(-1)[0]] = call.value; return respond({ type: 0, id: 'SUCCESS', reason: 'Widget value set successfully.' }); }; return respond({ type: 1, id: 'WIDGET-NOT-FOUND', reason: 'BaseWidget not found.' }); }; default: { return respond({ type: -1, id: 'INVALID-CALL', reason: `Invalid call: no such performer kind '${(call as any).kind}'!` }); }; }; }; case "query": { let call = pcall.call; switch (call.kind) { case "data": { let widget = this.widgets.get(call.id); if (widget !== undefined) { let keys = call.keys; let res: any = widget.data; keys.forEach((k, i) => { if (!(k in res)) { return respond({ type: 3, id: 'KEY-NOT-FOUND', reason: `Key #${i} (${k}) not found in this widget's data!` }); } res = res[k]; }); return respond({ type: 0, id: 'SUCCESS', reason: 'Widget value retrieved successfully.' }, res); }; return respond({ type: 1, id: 'WIDGET-NOT-FOUND', reason: 'BaseWidget not found.' }); }; case "name": { let widget = this.widgets.get(call.id); if (widget !== undefined) { return respond({ type: 0, id: 'SUCCESS', reason: 'Widget name retrieved successfully.' }, widget.name()); }; return respond({ type: 1, id: 'WIDGET-NOT-FOUND', reason: 'BaseWidget not found.' }); }; default: { return respond({ type: -1, id: 'INVALID-CALL', reason: `Invalid call: no such query kind '${(call as any).kind}'!` }); }; } }; default: { return respond({ type: -2, id: 'INVALID', reason: `Invalid protocol call: '${(pcall as any).type}' is not one of 'performer', 'query`! }); }; } } registerAllWidgets(handler: WidgetHandler) { this.widgets.forEach((w) => { w.register(handler); }); this.handlers.push(handler); } registerAllWindows(handler: WidgetHandler) { this.windows.forEach((w) => { w.register(handler); }); this.windowHandlers.push(handler); } touching(pos: XY) { return Array.from(this.widgets.values()).filter((w) => this.isTouching(pos, w)); } isTouching(pos: XY, widget: BaseWidget) { let gb = widget.getGlobalBounds(); return ( pos.x >= gb.left && pos.x <= gb.right && pos.y >= gb.top && pos.y <= gb.bottom ); } emitAt(pos: XY, evt: string | symbol, ...args: any[]) { let deepest: BaseWidget | null = null; let depth = 0; this.touching(pos).forEach((w) => { let d = w.depth(); if (!deepest || d > depth) return (deepest = w, depth = d); }); if (deepest) deepest!.emitOutFromHere(evt, ...args); } }