333 lines
12 KiB
TypeScript
333 lines
12 KiB
TypeScript
/**
|
|
* Copyright (c) 2016 Aleksi 'Allexit' Talarmo
|
|
*
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
namespace Std {
|
|
|
|
export class IO {
|
|
|
|
listenerCounter: number;
|
|
mouseListenerCounter: number;
|
|
listeners: {[id: number]: TerminalInput.KeyListener};
|
|
mouseListeners: {[id: number]: TerminalInput.MouseInputListener};
|
|
|
|
output: Output;
|
|
input: Input;
|
|
audioOutput: AudioOutput;
|
|
|
|
mousePos: {x: number, y: number} = {x: -1, y: -1};
|
|
|
|
constructor(terminal: JSTerminal.Terminal) {
|
|
this.listeners = {};
|
|
this.mouseListeners = {};
|
|
$(document).keypress(evt => {
|
|
Object.getOwnPropertyNames(this.listeners).forEach(id => {
|
|
this.listeners[parseInt(id)].handleKeypress(evt);
|
|
});
|
|
});
|
|
$(document).keydown(evt => {
|
|
Object.getOwnPropertyNames(this.listeners).forEach(id => {
|
|
this.listeners[parseInt(id)].handleKeydown(evt);
|
|
});
|
|
});
|
|
$(document).keyup(evt => {
|
|
Object.getOwnPropertyNames(this.listeners).forEach(id => {
|
|
this.listeners[parseInt(id)].handleKeyup(evt);
|
|
});
|
|
});
|
|
|
|
let termElem = $(terminal.selector);
|
|
$(terminal.selector).mousemove(evt => {
|
|
let term = translatePageToTerminal(termElem, terminal, evt.pageX, evt.pageY);
|
|
let x = term[0];
|
|
let y = term[1];
|
|
let changed = x != this.mousePos.x || y != this.mousePos.y;
|
|
if (changed) {
|
|
let mousePos = {x: x, y: y};
|
|
Object.getOwnPropertyNames(this.mouseListeners).forEach(id => {
|
|
this.mouseListeners[parseInt(id)].mousemove(this.mousePos, mousePos);
|
|
});
|
|
this.mousePos = mousePos;
|
|
}
|
|
});
|
|
|
|
$(terminal.selector).mouseleave(evt => {
|
|
if (this.mousePos.x != -1) {
|
|
Object.getOwnPropertyNames(this.mouseListeners).forEach(id => {
|
|
this.mouseListeners[parseInt(id)].mousemove(this.mousePos, {x: -1, y: -1});
|
|
});
|
|
this.mousePos = {x: -1, y: -1};
|
|
}
|
|
});
|
|
|
|
$(terminal.selector).mousedown(evt => {
|
|
let term = translatePageToTerminal(termElem, terminal, evt.pageX, evt.pageY);
|
|
Object.getOwnPropertyNames(this.mouseListeners).forEach(id => {
|
|
this.mouseListeners[parseInt(id)].mousedown(evt.button, term[0], term[1]);
|
|
});
|
|
});
|
|
|
|
$(terminal.selector).mouseup(evt => {
|
|
let term = translatePageToTerminal(termElem, terminal, evt.pageX, evt.pageY);
|
|
Object.getOwnPropertyNames(this.mouseListeners).forEach(id => {
|
|
this.mouseListeners[parseInt(id)].mouseup(evt.button, term[0], term[1]);
|
|
});
|
|
});
|
|
|
|
$(terminal.selector).click(evt => {
|
|
let term = translatePageToTerminal(termElem, terminal, evt.pageX, evt.pageY);
|
|
Object.getOwnPropertyNames(this.mouseListeners).forEach(id => {
|
|
this.mouseListeners[parseInt(id)].click(evt.button, term[0], term[1]);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Add a listener which then receives all key-events.
|
|
* Returns the ID of the listener.
|
|
*/
|
|
addListener(listener: TerminalInput.KeyListener): number {
|
|
let id = this.listenerCounter++;
|
|
this.listeners[id] = listener;
|
|
listener.onListen(id);
|
|
return id;
|
|
}
|
|
|
|
/**
|
|
* Remove listener with the specified ID.
|
|
*/
|
|
removeListener(id: number) {
|
|
delete this.listeners[id];
|
|
}
|
|
|
|
/**
|
|
* Add a mouse listener which then receives all mouse-events.
|
|
* Returns the ID of the mouse listener.
|
|
*/
|
|
addMouseListener(listener: TerminalInput.MouseInputListener): number {
|
|
let id = this.mouseListenerCounter++;
|
|
this.mouseListeners[id] = listener;
|
|
listener.onMouseListen(id);
|
|
return id;
|
|
}
|
|
|
|
/**
|
|
* Remove mouse listener with the specified ID.
|
|
*/
|
|
removeMouseListener(id: number) {
|
|
delete this.mouseListeners[id];
|
|
}
|
|
|
|
/**
|
|
* Set standard output channel.
|
|
* Used when calling print, println, clear and refresh.
|
|
*/
|
|
setOutput(output: Output) {
|
|
this.output = output;
|
|
}
|
|
|
|
/**
|
|
* Set standard input channel
|
|
* Used when calling readline.
|
|
*/
|
|
setInput(input: Input) {
|
|
this.input = input;
|
|
}
|
|
|
|
/**
|
|
* Set standard audio output channel
|
|
* Used when calling playSound, stopSound, and loadSound
|
|
*/
|
|
setAudioOutput(audioOutput: AudioOutput) {
|
|
this.audioOutput = audioOutput;
|
|
}
|
|
|
|
/**
|
|
* Calls print for the assigned IO interface.
|
|
* Standard IO prints out a line to the cursor's last location
|
|
*/
|
|
print(text: string, style?: string) {
|
|
if (!this.output) { return; }
|
|
this.output.print(text, style);
|
|
}
|
|
|
|
/**
|
|
* Calls println for the assigned IO interface.
|
|
* Standard IO prints out a line to a new line relatively to the cursor's last location.
|
|
*/
|
|
println(text: string, style?: string) {
|
|
if (!this.output) { return; }
|
|
this.output.println(text, style);
|
|
}
|
|
|
|
/**
|
|
* Calls refresh for the assigned IO interface.
|
|
* Standard IO refreshes the IO buffer so it can be drawn and then draws this.
|
|
* This last step can be avouded though with the boolean parameter.
|
|
*/
|
|
refresh(alsoRefreshTerminal: boolean = true) {
|
|
if (!this.output) { return; }
|
|
this.output.refresh(alsoRefreshTerminal);
|
|
}
|
|
|
|
/**
|
|
* Calls clear for the assigned IO interface.
|
|
* Standard IO clears the (bash and terminal) screen.
|
|
*/
|
|
clear(style?: string) {
|
|
if (!this.output) { return; }
|
|
this.output.clear(style);
|
|
}
|
|
|
|
/**
|
|
* Calls readline for the assigned IO inteface
|
|
* Sandard IO starts taking input and calls the callback with a string it readed while printing it out.
|
|
* Note: Some IO interfaces might not implement this method. Only use it
|
|
* When using stdio
|
|
*/
|
|
readline(readline: Readline) {
|
|
if (!this.input) { return; }
|
|
this.input.readline(readline);
|
|
}
|
|
|
|
/**
|
|
* Calls play for the assigned AudioOutput interface.
|
|
* Jukebox (Standerd IO) starts playing a sound in the given channel. If no channel is given, channel 0.
|
|
* Note: Some IO interfaces might not implement this method.
|
|
*/
|
|
playSound(channel?: number, sound?: Sound) {
|
|
if (!this.audioOutput) { return; }
|
|
this.audioOutput.play(channel, sound);
|
|
}
|
|
|
|
|
|
/**
|
|
* Calls pause for the assigned AudioOutput interface.
|
|
* Jukebox (Standerd IO) pauses the sound at the specified channel. Sound can be resumed with stdio.play(channel).
|
|
* Note: Some IO interfaces might not implement this method.
|
|
*/
|
|
pauseSound(channel?: number) {
|
|
if (!this.audioOutput) { return; }
|
|
this.audioOutput.pause(channel);
|
|
}
|
|
|
|
/**
|
|
* Calls stop for the assigned AudioOutput interface.
|
|
* Jukebox (Standerd IO) stops the current sound at given channel. If no channel is given, channel 0.
|
|
* Note: Some IO interfaces might not implement this method.
|
|
*/
|
|
stopSound(channel?: number) {
|
|
if (!this.audioOutput) { return; }
|
|
this.audioOutput.stop(channel);
|
|
}
|
|
|
|
/**
|
|
* Calls stop for the assigned AudioOutput interface.
|
|
* Jukebox (Standerd IO) loads the given sound and returns a refrence string, which points to a loaded sound.
|
|
* Note: Some IO interfaces might not implement this method. If AudioOutput-interface not specified (or Jukebox launched), it will return an empty string.
|
|
*/
|
|
loadSound(path: string, onready?: (ref: string) => void): string {
|
|
if (!this.audioOutput) { return ""; }
|
|
return this.audioOutput.loadSound(path, onready);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Anything with the Std.Input-interface will be able to handle readline-calls from Std.IO.
|
|
*/
|
|
export interface Input {
|
|
readline(readline: Readline);
|
|
}
|
|
|
|
/**
|
|
* Anything with the Std.Output-interface will be able to produce output through Std.IO.
|
|
*/
|
|
export interface Output {
|
|
print(text: string, style?: string);
|
|
println(text: string, style?: string);
|
|
refresh(alsoRefreshTerminal?: boolean);
|
|
clear(style?: string);
|
|
}
|
|
|
|
/**
|
|
* Anything with the Std.AudioOutput-interface will be able to produce audio output through Std.IO.
|
|
* The AudioOutput is implemented in the default terminal, but can be overriden.
|
|
*/
|
|
export interface AudioOutput {
|
|
play(channel?: number, sound?: Sound);
|
|
pause(channel?: number);
|
|
stop(channel?: number);
|
|
loadSound(path: string, onready?: (ref: string) => void): string;
|
|
}
|
|
|
|
/**
|
|
* An interface used to define properties of a sound played.
|
|
*
|
|
* soundRefrence is the string returned by AudioOutput to play the corresponding sound.
|
|
* volume defines the volume of the sound played.
|
|
* loops defines weather the sound should loop once played.
|
|
* onLoadedCallback is the callback triggered once the soundfile is loaded. This is also called in the AudioOutput-interface.
|
|
* onEndCallback is the callback triggered when the sound has stopped playing.
|
|
* onStartCallback is the callback triggered when the sound has started playing.
|
|
*/
|
|
export interface Sound {
|
|
soundRefrence: string;
|
|
volume?: number;
|
|
loops?: boolean;
|
|
onEndCallback?: () => void;
|
|
onStartCallback?: () => void;
|
|
}
|
|
|
|
/**
|
|
* Utility interface to define what options can be defined when readme is called.
|
|
*/
|
|
export interface Readline {
|
|
callback: (response: string) => void;
|
|
onChangeCallback?: (current: string) => void;
|
|
style?: string;
|
|
prefix?: string;
|
|
printAfterDone?: boolean;
|
|
hidden?: boolean;
|
|
default?: string;
|
|
}
|
|
|
|
function translatePageToTerminal(termElem: JQuery, terminal: JSTerminal.Terminal, x: number, y: number) {
|
|
let cellW = termElem.outerWidth()/terminal.width; // Portional width of a single cell;
|
|
let cellH = termElem.outerHeight()/terminal.height; // Portional height of a single cell;
|
|
let termX = Math.floor((x - termElem.offset().left)/cellW);
|
|
let termY = Math.floor((y - termElem.offset().top)/cellH);
|
|
return [termX, termY]
|
|
}
|
|
}
|
|
|
|
namespace TerminalInput {
|
|
|
|
/**
|
|
* Listens to general key-input events (handleKeypress, handleKeydown, handleKeyup)
|
|
*/
|
|
export interface KeyListener {
|
|
onListen(id: number);
|
|
handleKeypress(keypress: JQueryKeyEventObject);
|
|
handleKeydown(keydown: JQueryKeyEventObject);
|
|
handleKeyup(keyup: JQueryKeyEventObject);
|
|
}
|
|
|
|
/**
|
|
* Listens to general mouse events (mousemove, mousedown, mouseup and clicking).
|
|
* Note: When mouse is moved outside of the terminal, mousemove will trigger with new x and y being -1.
|
|
*/
|
|
export interface MouseInputListener {
|
|
onMouseListen(id: number);
|
|
mousemove(oldPos: {x: number, y: number}, newPos: {x: number, y: number});
|
|
mousedown(mousebutton: number, x: number, y: number);
|
|
mouseup(mousebutton: number, x: number, y: number);
|
|
click(mousebutton: number, x: number, y: number);
|
|
}
|
|
}
|