Compare commits
41 Commits
621dfb71ca
...
b0be0ba8b4
| Author | SHA1 | Date | |
|---|---|---|---|
| b0be0ba8b4 | |||
| a4e4e103bd | |||
| 3e7d993662 | |||
| dabc42ea25 | |||
| af8241505f | |||
| d56b871554 | |||
| 8934d40c0b | |||
| 8f7739c26a | |||
| a1ce5957d1 | |||
| 9e128eaad2 | |||
| 881aad743d | |||
| df86bae738 | |||
| c731dcef36 | |||
| e14138c7a0 | |||
| d684360547 | |||
| a9eb569a02 | |||
| 77d330a71d | |||
| 391c6b19fd | |||
| 632173d122 | |||
| 7aa1306077 | |||
| 15b7112348 | |||
| 2cbcc86dce | |||
| f71505ce28 | |||
| cb1f892e6d | |||
| b54a25eec6 | |||
| a87f50eec7 | |||
| 95a9ab63d0 | |||
| f6c0feda4b | |||
| 916831743e | |||
| 72e71c8e36 | |||
| 8180599c04 | |||
| 0b965d32b6 | |||
| f988c79e2f | |||
| 45a8851e2a | |||
| a6ed7818da | |||
| 3cb1811dcb | |||
| c6e5a836ea | |||
| a7dbdff535 | |||
| 2b8aa06117 | |||
| 1863462b44 | |||
| 209e40d0d5 |
2
Makefile
2
Makefile
@@ -14,7 +14,7 @@ all: clean build
|
||||
|
||||
build: $(MIN_PATH)
|
||||
|
||||
debug: HAXE_FLAGS += -D webconsole --debug
|
||||
debug: HAXE_FLAGS += -D webconsole -D error_stack --debug
|
||||
debug: build
|
||||
|
||||
$(HAXE_PATH): $(shell find src -name '*.hx')
|
||||
|
||||
@@ -7,6 +7,7 @@ General purpose "operation system" for [ComputerCraft](https://tweaked.cc/) buil
|
||||
- Hardware abstraction
|
||||
- Virtual screens to switch between multiple GUI apps
|
||||
- Reactive UI framework
|
||||
- Solid base to easily applications
|
||||
|
||||
# Building
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
-p src
|
||||
--main Startup
|
||||
--main kernel.Entrypoint
|
||||
|
||||
--library cctweaked:git:https://git.kapelle.org/niklas/cctweaked-haxelib.git
|
||||
--library tink_core
|
||||
|
||||
@@ -1,78 +1,10 @@
|
||||
import kernel.http.HTTPRequest.Http;
|
||||
import util.Observable;
|
||||
import lib.ui.reactive.TextElement;
|
||||
import lib.ui.reactive.ReactiveUI;
|
||||
import kernel.ui.WindowManager;
|
||||
import kernel.net.Net;
|
||||
import kernel.KernelEvents;
|
||||
import kernel.Log;
|
||||
import kernel.Init;
|
||||
|
||||
using util.Extender.LambdaExtender;
|
||||
import lib.HomeContext;
|
||||
|
||||
class Startup {
|
||||
public static function main() {
|
||||
Init.initKernel();
|
||||
httpTest();
|
||||
}
|
||||
var main = new HomeContext();
|
||||
|
||||
private static function httpTest() {
|
||||
Log.moveToOutput("main");
|
||||
// var url = "https://mock.codes/400";
|
||||
// var url = "https://jsonplaceholder.typicode.com/todos/1";
|
||||
var url = "https://domainnotexsist.net/aaaa";
|
||||
Http.request(url).handle((outcome)->{
|
||||
switch outcome{
|
||||
case Success(data):
|
||||
Log.debug(data.body);
|
||||
case Failure(failure):
|
||||
Log.error(failure.reason);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static function uiTest() {
|
||||
var context = WindowManager.instance.createNewContext();
|
||||
|
||||
var text1 = new Observable("My text");
|
||||
var text2 = new Observable("Another one");
|
||||
|
||||
var ui = new ReactiveUI(context,[
|
||||
new TextElement(text1,Orange,White,{
|
||||
onClick: (p) ->{
|
||||
text1.set("Click");
|
||||
}
|
||||
}),
|
||||
new TextElement(text2,Black,White,{
|
||||
onClick: (p)->{
|
||||
text2.set("Click");
|
||||
}
|
||||
})
|
||||
]);
|
||||
|
||||
ui.render();
|
||||
WindowManager.instance.focusContextToOutput(context,"main");
|
||||
}
|
||||
|
||||
private static function sendTest() {
|
||||
Net.instance.registerProto("ping",(pack)->{
|
||||
Log.debug("Message: " + pack.data);
|
||||
pack.respond("Hello from: "+Net.instance.networkID);
|
||||
});
|
||||
|
||||
KernelEvents.instance.onChar.handle((char)->{
|
||||
if (char == "s"){
|
||||
Log.debug("Sending to 4");
|
||||
Net.instance.sendAndAwait(4,"ping","Hello world").handle((result)->{
|
||||
switch (result){
|
||||
case Success(pack):
|
||||
Log.debug("Response: "+pack.data);
|
||||
case Failure(err):
|
||||
Log.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
main.run();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
137
src/bin/Disk.hx
Normal file
137
src/bin/Disk.hx
Normal file
@@ -0,0 +1,137 @@
|
||||
package bin;
|
||||
|
||||
import kernel.peripherals.Peripherals.Peripheral;
|
||||
import lib.cli.TermHandle;
|
||||
import lib.cli.CLIApp;
|
||||
|
||||
using tink.CoreApi;
|
||||
using Lambda;
|
||||
|
||||
class Disk extends CLIApp {
|
||||
|
||||
private var handle:TermHandle;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function invoke(handle:TermHandle):Future<Bool> {
|
||||
this.handle = handle;
|
||||
var subcommand = handle.args[0];
|
||||
var driveAddr:Null<String> = handle.args[1];
|
||||
|
||||
switch (subcommand) {
|
||||
case "ls":
|
||||
Peripheral.instance.getDrives().foreach(drive -> {
|
||||
var addr = drive.getAddr();
|
||||
var label = drive.getDiskLabel();
|
||||
var id = drive.getDiskID();
|
||||
|
||||
if (drive.isDiskPresent()){
|
||||
if (drive.hasAudio()){
|
||||
handle.writeLn('${addr} => ${label} [AUDIO]');
|
||||
}else{
|
||||
handle.writeLn('${addr} => ${label} (${id})');
|
||||
}
|
||||
}else {
|
||||
handle.writeLn('${addr} => [NO DISK]');
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
case "play":
|
||||
var drive = Peripheral.instance.getDrive(driveAddr);
|
||||
|
||||
if (drive == null){
|
||||
handle.writeLn("Drive not found: " + driveAddr);
|
||||
return Future.sync(false);
|
||||
}
|
||||
|
||||
if (!drive.isDiskPresent()){
|
||||
handle.writeLn("No disk in drive: " + driveAddr);
|
||||
return Future.sync(false);
|
||||
}
|
||||
|
||||
if (!drive.hasAudio()){
|
||||
handle.writeLn("Disk in drive " + driveAddr + " does not have audio");
|
||||
return Future.sync(false);
|
||||
}
|
||||
|
||||
drive.playAudio();
|
||||
case "stop":
|
||||
var drive = Peripheral.instance.getDrive(driveAddr);
|
||||
|
||||
if (drive == null){
|
||||
handle.writeLn("Drive not found: " + driveAddr);
|
||||
return Future.sync(false);
|
||||
}
|
||||
|
||||
if (!drive.isDiskPresent()){
|
||||
handle.writeLn("No disk in drive: " + driveAddr);
|
||||
return Future.sync(false);
|
||||
}
|
||||
|
||||
if (!drive.hasAudio()){
|
||||
handle.writeLn("Disk in drive: " + driveAddr + " does not have audio");
|
||||
return Future.sync(false);
|
||||
}
|
||||
|
||||
drive.stopAudio();
|
||||
|
||||
case "eject":
|
||||
var drive = Peripheral.instance.getDrive(driveAddr);
|
||||
|
||||
if (drive == null){
|
||||
handle.writeLn("Drive not found: " + driveAddr);
|
||||
return Future.sync(false);
|
||||
}
|
||||
|
||||
if (!drive.isDiskPresent()){
|
||||
handle.writeLn("No disk in drive: " + driveAddr);
|
||||
return Future.sync(false);
|
||||
}
|
||||
|
||||
drive.ejectDisk();
|
||||
case "lable":
|
||||
var drive = Peripheral.instance.getDrive(driveAddr);
|
||||
var label:String = handle.args[2];
|
||||
|
||||
if (drive == null){
|
||||
handle.writeLn("Drive not found: " + driveAddr);
|
||||
return Future.sync(false);
|
||||
}
|
||||
|
||||
if (!drive.isDiskPresent()){
|
||||
handle.writeLn("No disk in drive: " + driveAddr);
|
||||
return Future.sync(false);
|
||||
}
|
||||
|
||||
if (label == null || label == ""){
|
||||
handle.writeLn(drive.getDiskLabel());
|
||||
}else{
|
||||
var err = drive.setDiskLabel(label);
|
||||
if (err != null){
|
||||
handle.writeLn("Failed to set lable");
|
||||
return Future.sync(false);
|
||||
}
|
||||
}
|
||||
case "help":
|
||||
case null:
|
||||
printHelp();
|
||||
default:
|
||||
handle.writeLn("Unknown subcommand: " + subcommand);
|
||||
printHelp();
|
||||
return Future.sync(false);
|
||||
}
|
||||
|
||||
return Future.sync(true);
|
||||
}
|
||||
|
||||
private function printHelp() {
|
||||
handle.writeLn("Usage: disk <subcommand> [args]");
|
||||
handle.writeLn("Subcommands:");
|
||||
handle.writeLn(" ls");
|
||||
handle.writeLn(" play <drive>");
|
||||
handle.writeLn(" stop <drive>");
|
||||
handle.writeLn(" eject <drive>");
|
||||
handle.writeLn(" label <drive> [label]");
|
||||
}
|
||||
}
|
||||
21
src/bin/HelloWorld.hx
Normal file
21
src/bin/HelloWorld.hx
Normal file
@@ -0,0 +1,21 @@
|
||||
package bin;
|
||||
|
||||
import lib.cli.TermHandle;
|
||||
import lib.cli.CLIApp;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
class HelloWorld extends CLIApp {
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function invoke(handle: TermHandle):Future<Bool> {
|
||||
var world:String = "world";
|
||||
if (handle.args.length > 0) {
|
||||
world = handle.args[0];
|
||||
}
|
||||
|
||||
handle.write('Hello, $world!');
|
||||
return Future.sync(true);
|
||||
}
|
||||
}
|
||||
106
src/bin/Net.hx
Normal file
106
src/bin/Net.hx
Normal file
@@ -0,0 +1,106 @@
|
||||
package bin;
|
||||
|
||||
import kernel.peripherals.Peripherals.Peripheral;
|
||||
import kernel.net.Routing;
|
||||
import haxe.ds.ReadOnlyArray;
|
||||
import lib.cli.TermHandle;
|
||||
import lib.cli.CLIApp;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
class Net extends CLIApp {
|
||||
private var handle:TermHandle;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function invoke(handle:TermHandle):Future<Bool> {
|
||||
this.handle = handle;
|
||||
|
||||
var subcommand = handle.args[0];
|
||||
var subcommand_args = handle.args.slice(1);
|
||||
|
||||
switch (subcommand) {
|
||||
case "route":
|
||||
return Future.sync(route(subcommand_args));
|
||||
case "iface":
|
||||
return Future.sync(iface(subcommand_args));
|
||||
case "help":
|
||||
printHelp();
|
||||
return Future.sync(true);
|
||||
case "ping":
|
||||
return ping(subcommand_args);
|
||||
case "proto":
|
||||
return Future.sync(protos());
|
||||
default:
|
||||
handle.writeLn("Unknown subcommand: " + subcommand);
|
||||
printHelp();
|
||||
return Future.sync(false);
|
||||
}
|
||||
}
|
||||
|
||||
private function printHelp() {
|
||||
handle.writeLn("net route");
|
||||
handle.writeLn("net iface");
|
||||
handle.writeLn("net help");
|
||||
handle.writeLn("net proto");
|
||||
}
|
||||
|
||||
private function route(args:ReadOnlyArray<String>):Bool {
|
||||
var routes = Routing.instance.getRouteTable();
|
||||
|
||||
for(k => v in routes) {
|
||||
handle.writeLn('${k} => ${v.interf.name()}(${v.cost})');
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function iface(args:ReadOnlyArray<String>):Bool {
|
||||
var modems = Peripheral.instance.getModems();
|
||||
|
||||
for (modem in modems) {
|
||||
handle.writeLn(modem.name());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function ping(args:ReadOnlyArray<String>): Future<Bool> {
|
||||
return new Future<Bool>(trigger -> {
|
||||
if (args.length != 1) {
|
||||
handle.writeLn("Usage: net ping id");
|
||||
trigger(false);
|
||||
return null;
|
||||
}
|
||||
|
||||
var toID:Null<Int> = Std.parseInt(args[0]);
|
||||
|
||||
if (toID == null) {
|
||||
handle.writeLn("Invalid ID");
|
||||
trigger(false);
|
||||
return null;
|
||||
}
|
||||
|
||||
kernel.net.Net.instance.ping(toID).handle(result -> {
|
||||
switch (result){
|
||||
case Success(_):
|
||||
handle.writeLn("Ping succeeded");
|
||||
trigger(true);
|
||||
case Failure(failure):
|
||||
handle.writeLn("Ping failed: " + failure);
|
||||
trigger(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function protos():Bool {
|
||||
var protos = kernel.net.Net.instance.getActiveProtocols();
|
||||
|
||||
for (proto in protos) {
|
||||
handle.writeLn(proto);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
44
src/bin/Redstone.hx
Normal file
44
src/bin/Redstone.hx
Normal file
@@ -0,0 +1,44 @@
|
||||
package bin;
|
||||
|
||||
import kernel.peripherals.Peripherals.Peripheral;
|
||||
import kernel.peripherals.Side;
|
||||
import lib.cli.TermHandle;
|
||||
import lib.cli.CLIApp;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
class Redstone extends CLIApp {
|
||||
public function new() {}
|
||||
|
||||
public function invoke(handle:TermHandle):Future<Bool> {
|
||||
var subcommand = handle.args[0];
|
||||
|
||||
if (subcommand == null) {
|
||||
handle.writeLn("Usage: redstone <on|off|get> <side>");
|
||||
return Future.sync(false);
|
||||
}
|
||||
|
||||
var side:Null<Side> = handle.args[1];
|
||||
if (side == null) {
|
||||
handle.writeLn("Invalid side");
|
||||
return Future.sync(false);
|
||||
}
|
||||
|
||||
switch (subcommand) {
|
||||
case "on":
|
||||
Peripheral.instance.getRedstone(side).setOutput(true);
|
||||
case "off":
|
||||
Peripheral.instance.getRedstone(side).setOutput(false);
|
||||
case "get":
|
||||
var value = Peripheral.instance.getRedstone(side).getAnalogInput();
|
||||
handle.writeLn("Analog input: " + value);
|
||||
case "help":
|
||||
handle.writeLn("Usage: redstone <on|off|get> <side>");
|
||||
default:
|
||||
handle.writeLn("Invalid subcommand");
|
||||
return Future.sync(false);
|
||||
}
|
||||
|
||||
return Future.sync(true);
|
||||
}
|
||||
}
|
||||
166
src/bin/Terminal.hx
Normal file
166
src/bin/Terminal.hx
Normal file
@@ -0,0 +1,166 @@
|
||||
package bin;
|
||||
|
||||
import lib.ui.UIApp;
|
||||
import lib.cli.TermHandle;
|
||||
import lib.cli.CLIApp;
|
||||
import lib.Color;
|
||||
import kernel.ui.WindowContext;
|
||||
import kernel.ui.WindowManager;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
class Terminal extends UIApp {
|
||||
private var context:WindowContext;
|
||||
private var input:String = "";
|
||||
private var backlog:Array<String> = [];
|
||||
private var exitTrigger: Bool -> Void;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function invoke(context: WindowContext): Future<Bool> {
|
||||
this.context = context;
|
||||
|
||||
this.context.onChar.handle(char -> {
|
||||
this.input += char;
|
||||
this.redrawInput();
|
||||
});
|
||||
|
||||
this.context.onKey.handle(e -> {
|
||||
if (e.keyCode == 259) {
|
||||
this.input = this.input.substr(0, this.input.length - 1);
|
||||
this.redrawInput();
|
||||
} else if (e.keyCode == 257) {
|
||||
this.backlog.push("> " + this.input);
|
||||
var command = this.input;
|
||||
this.input = "";
|
||||
this.redrawBacklog();
|
||||
this.redrawInput();
|
||||
this.invokeCommand(command);
|
||||
}
|
||||
});
|
||||
|
||||
WindowManager.instance.focusContextToOutput(context, "main");
|
||||
this.redrawInput();
|
||||
|
||||
return new Future<Bool>(cb -> {
|
||||
this.exitTrigger = cb;
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
private function redrawBacklog() {
|
||||
var size = this.context.getSize();
|
||||
var linesAvailable = size.y - 1;
|
||||
|
||||
var start:Int = this.backlog.length - linesAvailable;
|
||||
|
||||
for (i in 0...linesAvailable) {
|
||||
var line = this.backlog[start + i];
|
||||
|
||||
this.context.setCursorPos(0, i);
|
||||
this.context.clearLine();
|
||||
|
||||
if (line != null) {
|
||||
this.context.write(line);
|
||||
}
|
||||
}
|
||||
this.moveCursorToInput();
|
||||
}
|
||||
|
||||
private function redrawInput() {
|
||||
var size = this.context.getSize();
|
||||
|
||||
this.context.setCursorPos(0, size.y - 1);
|
||||
this.context.clearLine();
|
||||
|
||||
this.context.setTextColor(Color.Blue);
|
||||
this.context.write("> ");
|
||||
|
||||
this.context.setTextColor(Color.White);
|
||||
this.context.write(this.input);
|
||||
|
||||
this.context.setCursorBlink(true);
|
||||
}
|
||||
|
||||
private function invokeCommand(command:String):Void {
|
||||
var args = this.parseArgs(command);
|
||||
|
||||
if (args.length == 0) {
|
||||
return;
|
||||
}
|
||||
var commandName = args[0];
|
||||
|
||||
// Handle built-in commands
|
||||
switch (commandName) {
|
||||
case "clear":
|
||||
this.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
var commandArgs:Array<String> = args.slice(1);
|
||||
var hadInput = false;
|
||||
var handle = new TermHandle(commandArgs, {
|
||||
onWrite: (s:String) -> {
|
||||
if (!hadInput) {
|
||||
this.backlog.push("");
|
||||
hadInput = true;
|
||||
}
|
||||
this.backlog[this.backlog.length - 1] += s;
|
||||
this.redrawBacklog();
|
||||
},
|
||||
onNewLine: () -> {
|
||||
this.backlog.push("");
|
||||
this.redrawBacklog();
|
||||
}
|
||||
});
|
||||
|
||||
var prog:CLIApp = getProgByName(commandName);
|
||||
|
||||
if (prog == null) {
|
||||
this.backlog.push("Command not found: " + commandName);
|
||||
this.redrawBacklog();
|
||||
return;
|
||||
}
|
||||
|
||||
this.context.setCursorBlink(false);
|
||||
|
||||
prog.invoke(handle).handle((exitCode) -> {
|
||||
// Cleanup extra newline
|
||||
if (this.backlog[this.backlog.length - 1] == "") {
|
||||
this.backlog.pop();
|
||||
}
|
||||
|
||||
this.redrawInput();
|
||||
});
|
||||
}
|
||||
|
||||
private function parseArgs(command:String):Array<String> {
|
||||
// TODO: tim and quote handling
|
||||
return command.split(" ");
|
||||
}
|
||||
|
||||
private function clear() {
|
||||
this.backlog = [];
|
||||
this.redrawBacklog();
|
||||
}
|
||||
|
||||
private function getProgByName(name:String):CLIApp {
|
||||
switch (name) {
|
||||
case "hello":
|
||||
return new HelloWorld();
|
||||
case "net":
|
||||
return new Net();
|
||||
case "rs":
|
||||
return new Redstone();
|
||||
case "disk":
|
||||
return new Disk();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private function moveCursorToInput() {
|
||||
var size = this.context.getSize();
|
||||
this.context.setCursorPos(this.input.length + 2, size.y - 1);
|
||||
}
|
||||
}
|
||||
14
src/kernel/Entrypoint.hx
Normal file
14
src/kernel/Entrypoint.hx
Normal file
@@ -0,0 +1,14 @@
|
||||
package kernel;
|
||||
|
||||
import kernel.log.Log;
|
||||
|
||||
class Entrypoint {
|
||||
public static function main() {
|
||||
Init.initKernel();
|
||||
try {
|
||||
Startup.main();
|
||||
}catch(e){
|
||||
Log.error('Error in startup: ${e.toString()}');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import kernel.turtle.Turtle;
|
||||
import haxe.MainLoop;
|
||||
import kernel.net.Routing;
|
||||
import cc.OS;
|
||||
import util.Debug;
|
||||
import lib.Debug;
|
||||
import kernel.ui.WindowManager;
|
||||
import kernel.peripherals.Peripherals.Peripheral;
|
||||
import kernel.net.Net;
|
||||
@@ -17,7 +17,6 @@ class Init {
|
||||
|
||||
WindowManager.instance = new WindowManager();
|
||||
MainTerm.instance = new MainTerm();
|
||||
Log.init();
|
||||
|
||||
if (Turtle.isTurtle()){
|
||||
Turtle.instance = new Turtle();
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
package kernel;
|
||||
|
||||
import util.Pos;
|
||||
import kernel.log.Log;
|
||||
import lib.Pos;
|
||||
import cc.HTTP.HTTPResponse;
|
||||
import lua.TableTools;
|
||||
import lua.Coroutine;
|
||||
import util.Vec.Vec2;
|
||||
import lib.Vec.Vec2;
|
||||
import haxe.Exception;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
package kernel;
|
||||
|
||||
import kernel.ui.WindowContext;
|
||||
import kernel.ui.WindowManager;
|
||||
import kernel.ui.TermWriteable;
|
||||
import lib.TermIO;
|
||||
#if webconsole
|
||||
import kernel.net.Net;
|
||||
import util.Debug;
|
||||
#end
|
||||
|
||||
/**
|
||||
Log messages to specified output.
|
||||
**/
|
||||
class Log {
|
||||
private static var context:WindowContext;
|
||||
private static var writer:TermIO;
|
||||
|
||||
/**
|
||||
Depends on: WindowManager
|
||||
**/
|
||||
@:allow(kernel.Init)
|
||||
private static function init() {
|
||||
Log.context = WindowManager.instance.createNewContext();
|
||||
Log.writer = new TermIO(Log.context);
|
||||
}
|
||||
|
||||
private static function setMainoutout(newOutput:TermWriteable) {
|
||||
writer = new TermIO(newOutput);
|
||||
}
|
||||
|
||||
public static function info(msg:Dynamic, ?pos:haxe.PosInfos) {
|
||||
writer.writeLn(logLine("INFO",pos,msg));
|
||||
#if webconsole
|
||||
Debug.printWeb(logLine("INFO",pos,msg));
|
||||
#end
|
||||
}
|
||||
|
||||
public static function warn(msg:Dynamic, ?pos:haxe.PosInfos) {
|
||||
writer.writeLn(logLine("WARN",pos,msg), Yellow);
|
||||
#if webconsole
|
||||
Debug.printWeb(logLine("WARN",pos,msg));
|
||||
#end
|
||||
}
|
||||
|
||||
public static function error(msg:Dynamic, ?pos:haxe.PosInfos) {
|
||||
writer.writeLn(logLine("ERRO",pos,msg), Red);
|
||||
#if webconsole
|
||||
Debug.printWeb(logLine("ERRO",pos,msg));
|
||||
#end
|
||||
}
|
||||
|
||||
public static function debug(msg:Dynamic, ?pos:haxe.PosInfos) {
|
||||
#if debug
|
||||
writer.writeLn(logLine("DEBG",pos,msg), Gray);
|
||||
#if webconsole
|
||||
Debug.printWeb(logLine("DEBG",pos,msg));
|
||||
#end
|
||||
#end
|
||||
}
|
||||
|
||||
public static function silly(msg:Dynamic, ?pos:haxe.PosInfos) {
|
||||
writer.writeLn(logLine("SILY",pos,msg), LightGray);
|
||||
#if webconsole
|
||||
Debug.printWeb(logLine("SILY",pos,msg));
|
||||
#end
|
||||
}
|
||||
|
||||
public static function moveToOutput(addr:String) {
|
||||
WindowManager.instance.focusContextToOutput(context, addr);
|
||||
}
|
||||
|
||||
private static function logLine(tag: String,pos: haxe.PosInfos,msg: Dynamic): String {
|
||||
#if debug
|
||||
return '[$tag][${pos.className}:${pos.lineNumber}]: ${Std.string(msg)}';
|
||||
#else
|
||||
return '[$tag]: ${Std.string(msg)}';
|
||||
#end
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
package kernel;
|
||||
|
||||
import util.Pos;
|
||||
using tink.CoreApi;
|
||||
|
||||
import lib.Pos;
|
||||
import kernel.ui.TermWriteable;
|
||||
import cc.Term;
|
||||
import util.Vec.Vec2;
|
||||
import util.Color;
|
||||
import lib.Vec.Vec2;
|
||||
import lib.Color;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
/**
|
||||
Represents the main computer screen.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package kernel;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
import cc.OS;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
/**
|
||||
Wrapper class for using timer.
|
||||
**/
|
||||
|
||||
48
src/kernel/log/Log.hx
Normal file
48
src/kernel/log/Log.hx
Normal file
@@ -0,0 +1,48 @@
|
||||
package kernel.log;
|
||||
|
||||
#if webconsole
|
||||
import lib.Debug;
|
||||
#end
|
||||
|
||||
/**
|
||||
Log messages to specified output.
|
||||
**/
|
||||
class Log {
|
||||
private static inline final MAX_LINES:Int = 100;
|
||||
|
||||
private static final logLines:Array<LogLine> = [];
|
||||
|
||||
public static function info(msg:Dynamic, ?pos:haxe.PosInfos) {
|
||||
log({level: Info, message: Std.string(msg),time: 0});
|
||||
}
|
||||
|
||||
public static function warn(msg:Dynamic, ?pos:haxe.PosInfos) {
|
||||
log({level: Warn, message: Std.string(msg),time: 0});
|
||||
}
|
||||
|
||||
public static function error(msg:Dynamic, ?pos:haxe.PosInfos) {
|
||||
log({level: Error, message: Std.string(msg),time: 0});
|
||||
}
|
||||
|
||||
public static function debug(msg:Dynamic, ?pos:haxe.PosInfos) {
|
||||
#if debug
|
||||
log({level: Debug, message: Std.string(msg),time: 0});
|
||||
#end
|
||||
}
|
||||
|
||||
public static function silly(msg:Dynamic, ?pos:haxe.PosInfos) {
|
||||
log({level: Silly, message: Std.string(msg),time: 0});
|
||||
}
|
||||
|
||||
private static function log(line: LogLine, ?pos:haxe.PosInfos) {
|
||||
logLines.push(line);
|
||||
|
||||
if (logLines.length > MAX_LINES) {
|
||||
logLines.shift();
|
||||
}
|
||||
|
||||
#if webconsole
|
||||
Debug.printWeb('[${Std.string(line.level)}] ${line.message}');
|
||||
#end
|
||||
}
|
||||
}
|
||||
9
src/kernel/log/LogLevel.hx
Normal file
9
src/kernel/log/LogLevel.hx
Normal file
@@ -0,0 +1,9 @@
|
||||
package kernel.log;
|
||||
|
||||
enum LogLevel {
|
||||
Info;
|
||||
Warn;
|
||||
Error;
|
||||
Debug;
|
||||
Silly;
|
||||
}
|
||||
7
src/kernel/log/LogLine.hx
Normal file
7
src/kernel/log/LogLine.hx
Normal file
@@ -0,0 +1,7 @@
|
||||
package kernel.log;
|
||||
|
||||
typedef LogLine = {
|
||||
level: LogLevel,
|
||||
message: String,
|
||||
time: Int,
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package kernel.net;
|
||||
|
||||
import kernel.log.Log;
|
||||
using tink.CoreApi;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package kernel.net;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
import haxe.ds.ReadOnlyArray;
|
||||
import kernel.net.Package.NetworkID;
|
||||
import kernel.peripherals.Peripherals.Peripheral;
|
||||
import kernel.Log;
|
||||
import kernel.log.Log;
|
||||
import kernel.Timer;
|
||||
import cc.OS;
|
||||
|
||||
using tink.CoreApi;
|
||||
using Lambda;
|
||||
using util.Extender.LambdaExtender;
|
||||
using lib.Extender.LambdaExtender;
|
||||
|
||||
/**
|
||||
Class responsible for everything network related.
|
||||
@@ -25,7 +25,7 @@ class Net {
|
||||
public static inline final DEFAULT_TTL:Int = 10;
|
||||
|
||||
public final networkID:NetworkID = OS.getComputerID();
|
||||
private final responseBus:Map<Int, Callback<Package>> = new Map();
|
||||
private final responseBus:Map<Int, Callback<Outcome<Package,Error>>> = new Map();
|
||||
private final protoHandlers:Map<String, Callback<Package>> = new Map();
|
||||
private var interfaces:Array<INetworkInterface>;
|
||||
|
||||
@@ -37,6 +37,26 @@ class Net {
|
||||
for (interf in interfaces){
|
||||
setupInterf(interf);
|
||||
}
|
||||
|
||||
setupPingHandle();
|
||||
}
|
||||
|
||||
private function setupPingHandle() {
|
||||
this.registerProto("icmp", pack -> {
|
||||
switch pack.data.type {
|
||||
case "ping":
|
||||
this.respondTo(pack, "pong");
|
||||
case "died":
|
||||
// If we get a "died" message from a node when one of our packages ttl hits 0
|
||||
// the `data.msgId` prop is the message id
|
||||
var msgID:Int = pack.data.msgID;
|
||||
if (responseBus.exists(msgID)) {
|
||||
responseBus[msgID].invoke(Outcome.Failure(new Error("TTL reached 0")));
|
||||
}
|
||||
default:
|
||||
Log.silly('Unknown icmp message: ${pack.data}');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private function setupInterf(interf: INetworkInterface) {
|
||||
@@ -57,7 +77,7 @@ class Net {
|
||||
case Response:
|
||||
// Got a response to a send message. Invoke the callback
|
||||
if (responseBus.exists(pack.msgID)) {
|
||||
responseBus[pack.msgID].invoke(pack);
|
||||
responseBus[pack.msgID].invoke(Outcome.Success(pack));
|
||||
}
|
||||
case RouteDiscover(_) | RouteDiscoverResponse(_) | RouteDiscoverUpdate(_):
|
||||
// Delegate to Routing
|
||||
@@ -91,6 +111,13 @@ class Net {
|
||||
|
||||
private function forwardPackage(pack: Package) {
|
||||
if (pack.ttl == 0){
|
||||
|
||||
if (pack.type.match(Data(_))) {
|
||||
// If the package is a data package and the ttl hits 0
|
||||
// we send a "died" message to the sender
|
||||
sendAndForget(pack.fromID, "icmp", {type:"died", msgID: pack.msgID});
|
||||
}
|
||||
|
||||
// Drop package
|
||||
return;
|
||||
}
|
||||
@@ -164,8 +191,16 @@ class Net {
|
||||
|
||||
var timeout:Timer = null;
|
||||
|
||||
responseBus[pack.msgID] = ((reponse:Package) -> {
|
||||
resolve(reponse);
|
||||
responseBus[pack.msgID] = ((reponse:Outcome<Package,Error>) -> {
|
||||
|
||||
switch reponse {
|
||||
case Success(pack):
|
||||
resolve(pack);
|
||||
case Failure(err):
|
||||
reject(err);
|
||||
}
|
||||
|
||||
// Always remove the timeout
|
||||
if (timeout != null) {
|
||||
timeout.cancle();
|
||||
}
|
||||
@@ -198,4 +233,31 @@ class Net {
|
||||
public function removeProto(proto:String) {
|
||||
protoHandlers.remove(proto);
|
||||
}
|
||||
|
||||
/**
|
||||
Sends a ping package to the given id. Returns true if there was a response.
|
||||
**/
|
||||
public function ping(toID: NetworkID): Promise<Noise> {
|
||||
return new Promise<Noise>((resolve,reject)->{
|
||||
this.sendAndAwait(toID,"icmp",{type:"ping"}).handle(pack -> {
|
||||
switch pack {
|
||||
case Success(_):
|
||||
resolve(Noise);
|
||||
case Failure(err):
|
||||
reject(err);
|
||||
}
|
||||
});
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public function getActiveProtocols(): ReadOnlyArray<String> {
|
||||
var arr = new Array<String>();
|
||||
|
||||
for (proto in protoHandlers.keys()) {
|
||||
arr.push(proto);
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package kernel.net;
|
||||
|
||||
import kernel.log.Log;
|
||||
import kernel.peripherals.Peripherals.Peripheral;
|
||||
import kernel.net.INetworkInterface;
|
||||
import kernel.net.Package.NetworkID;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
import kernel.net.Package.NetworkID;
|
||||
|
||||
@:structInit typedef Route = {
|
||||
interf:INetworkInterface,
|
||||
rep: NetworkID,
|
||||
rep:NetworkID,
|
||||
cost:Int
|
||||
}
|
||||
|
||||
@@ -22,13 +22,13 @@ class Routing {
|
||||
**/
|
||||
public static var instance:Routing;
|
||||
|
||||
public static inline final UPDATE_WAIT_TIME:Float = 3;
|
||||
public static inline final UPDATE_WAIT_TIME:Float = 1;
|
||||
|
||||
public final onNewNeigbor:Signal<Int>;
|
||||
|
||||
private final onNewNeigborTrigger:SignalTrigger<NetworkID> = Signal.trigger();
|
||||
private final routingTable:Map<NetworkID, Route> = new Map();
|
||||
private var routeUpdateInQueue: Bool = false;
|
||||
private var routeUpdateInQueue:Bool = false;
|
||||
|
||||
@:allow(kernel.Init)
|
||||
private function new() {
|
||||
@@ -37,61 +37,59 @@ class Routing {
|
||||
|
||||
@:allow(kernel.Init)
|
||||
private function init() {
|
||||
routingTable.set(Net.instance.networkID,{interf: Loopback.instance,cost:0,rep:Net.instance.networkID});
|
||||
for (modem in Peripheral.instance.getModems()) {
|
||||
modem.send(Net.BRODCAST_PORT, Net.instance.networkID, newRoutDiscoverPackage());
|
||||
}
|
||||
routingTable.set(Net.instance.networkID, {interf: Loopback.instance, cost: 0, rep: Net.instance.networkID});
|
||||
brodcastRoutingTable();
|
||||
}
|
||||
|
||||
/**
|
||||
Prepares an brodcast of the current routing table. If a brodcast is already in queue it will be ignored.
|
||||
After UPDATE_WAIT_TIME seconds the routing table will be brodcasted. This is done to prevent spamming the network.
|
||||
**/
|
||||
private function prepareRouteUpdate() {
|
||||
if (this.routeUpdateInQueue){
|
||||
if (this.routeUpdateInQueue) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.routeUpdateInQueue = true;
|
||||
|
||||
new Timer(UPDATE_WAIT_TIME,()->{
|
||||
brodcastUpdatedRoutes();
|
||||
new Timer(UPDATE_WAIT_TIME, () -> {
|
||||
brodcastRoutingTable();
|
||||
this.routeUpdateInQueue = false;
|
||||
});
|
||||
}
|
||||
|
||||
private function brodcastUpdatedRoutes() {
|
||||
var pack: Package = {
|
||||
type: RouteDiscoverUpdate(genRouteList()),
|
||||
toID: Net.BRODCAST_PORT,
|
||||
msgID: null,
|
||||
fromID: Net.instance.networkID,
|
||||
data: null,
|
||||
ttl: 0, // Prevent forwarding
|
||||
};
|
||||
/**
|
||||
Brodcast the current routing table to all peers.
|
||||
**/
|
||||
private function brodcastRoutingTable() {
|
||||
var pack = newRoutDiscoverPackage();
|
||||
|
||||
for (modem in Peripheral.instance.getModems()) {
|
||||
|
||||
modem.send(Net.BRODCAST_PORT, Net.instance.networkID,pack);
|
||||
modem.send(Net.BRODCAST_PORT, Net.instance.networkID, pack);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Handle incomming packages that involve route discovery.
|
||||
**/
|
||||
public function handleRoutePackage(pack:Package, interf:INetworkInterface):Void {
|
||||
addPossibleRoute(pack.fromID,interf,0,pack.fromID);
|
||||
@:allow(kernel.net.Net)
|
||||
private function handleRoutePackage(pack:Package, interf:INetworkInterface):Void {
|
||||
addPossibleRoute(pack.fromID, interf, 0, pack.fromID);
|
||||
|
||||
var shouldRespond: Bool = switch pack.type {
|
||||
var shouldRespond:Bool = switch pack.type {
|
||||
case RouteDiscoverResponse(routes):
|
||||
for (route in routes) {
|
||||
addPossibleRoute(route.id,interf,route.cost,pack.fromID);
|
||||
addPossibleRoute(route.id, interf, route.cost, pack.fromID);
|
||||
}
|
||||
false;
|
||||
case RouteDiscover(routes):
|
||||
for (route in routes) {
|
||||
addPossibleRoute(route.id,interf,route.cost,pack.fromID);
|
||||
addPossibleRoute(route.id, interf, route.cost, pack.fromID);
|
||||
}
|
||||
true;
|
||||
case RouteDiscoverUpdate(routes):
|
||||
for (route in routes) {
|
||||
addPossibleRoute(route.id,interf,route.cost,pack.fromID);
|
||||
addPossibleRoute(route.id, interf, route.cost, pack.fromID);
|
||||
}
|
||||
false;
|
||||
default:
|
||||
@@ -99,7 +97,7 @@ class Routing {
|
||||
false;
|
||||
};
|
||||
|
||||
if (!shouldRespond){
|
||||
if (!shouldRespond) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -113,12 +111,15 @@ class Routing {
|
||||
ttl: 0, // Prevent forwarding
|
||||
}
|
||||
|
||||
interf.send(response.toID,Net.instance.networkID,response);
|
||||
interf.send(response.toID, Net.instance.networkID, response);
|
||||
}
|
||||
|
||||
private function genRouteList(): Array<{id:NetworkID,cost:Int}> {
|
||||
var routes: Array<{id:NetworkID,cost:Int}> = [];
|
||||
for (k => v in this.routingTable){
|
||||
/**
|
||||
Generate a list of routes to send to a peer based on the current routing table.
|
||||
**/
|
||||
private function genRouteList():Array<{id:NetworkID, cost:Int}> {
|
||||
var routes:Array<{id:NetworkID, cost:Int}> = [];
|
||||
for (k => v in this.routingTable) {
|
||||
routes.push({
|
||||
id: k,
|
||||
cost: v.cost
|
||||
@@ -144,36 +145,40 @@ class Routing {
|
||||
Called when a route to a client has been disoverd.
|
||||
Its possible to be called multiple times with the same id but different addr.
|
||||
**/
|
||||
private function addPossibleRoute(toID:Int, interf:INetworkInterface, cost:Int, rep: NetworkID) {
|
||||
if (toID == Net.instance.networkID){
|
||||
private function addPossibleRoute(toID:Int, interf:INetworkInterface, cost:Int, rep:NetworkID) {
|
||||
if (toID == Net.instance.networkID) {
|
||||
return;
|
||||
}
|
||||
|
||||
var fullCost = cost+interf.getBaseRoutingCost();
|
||||
var fullCost = cost + interf.getBaseRoutingCost();
|
||||
|
||||
if (this.routingTable.exists(toID)){
|
||||
if (this.routingTable[toID].cost > fullCost){
|
||||
this.routingTable[toID] = {interf:interf,cost:cost + interf.getBaseRoutingCost(),rep: rep};
|
||||
if (this.routingTable.exists(toID)) {
|
||||
if (this.routingTable[toID].cost > fullCost) {
|
||||
this.routingTable[toID] = {interf: interf, cost: cost + interf.getBaseRoutingCost(), rep: rep};
|
||||
this.prepareRouteUpdate();
|
||||
}
|
||||
}else{
|
||||
this.routingTable[toID] = {interf:interf,cost:cost + interf.getBaseRoutingCost(),rep: rep};
|
||||
} else {
|
||||
this.routingTable[toID] = {interf: interf, cost: cost + interf.getBaseRoutingCost(), rep: rep};
|
||||
this.onNewNeigborTrigger.trigger(toID);
|
||||
this.prepareRouteUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public function getRouteToID(networkID:NetworkID):{interf: INetworkInterface, rep: NetworkID} {
|
||||
public function getRouteToID(networkID:NetworkID):{interf:INetworkInterface, rep:NetworkID} {
|
||||
if (networkID == Net.instance.networkID) {
|
||||
return {interf: Loopback.instance,rep: Net.instance.networkID};
|
||||
return {interf: Loopback.instance, rep: Net.instance.networkID};
|
||||
}
|
||||
|
||||
var route = this.routingTable[networkID];
|
||||
var route:Null<Route> = this.routingTable[networkID];
|
||||
|
||||
if (route == null){
|
||||
if (route == null) {
|
||||
return null;
|
||||
}else{
|
||||
return {interf: route.interf,rep: route.rep};
|
||||
} else {
|
||||
return {interf: route.interf, rep: route.rep};
|
||||
}
|
||||
}
|
||||
|
||||
public function getRouteTable():Map<NetworkID, Route> {
|
||||
return this.routingTable;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package kernel.peripherals;
|
||||
|
||||
import kernel.net.Package;
|
||||
using tink.CoreApi;
|
||||
|
||||
class Drive implements IPeripheral {
|
||||
@@ -52,8 +51,13 @@ class Drive implements IPeripheral {
|
||||
this.native.setDiskLabel();
|
||||
}
|
||||
|
||||
public inline function setDiskLabel(label: String) {
|
||||
this.native.setDiskLabel(label);
|
||||
public inline function setDiskLabel(label: String): Null<Error> {
|
||||
try {
|
||||
this.native.setDiskLabel(label);
|
||||
return null;
|
||||
} catch (e: Dynamic) {
|
||||
return new Error("Invalid label");
|
||||
}
|
||||
}
|
||||
|
||||
public inline function hasData():Bool {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package kernel.peripherals;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
import kernel.net.Package;
|
||||
import kernel.net.INetworkInterface;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
class Modem implements INetworkInterface implements IPeripheral {
|
||||
public final addr:String;
|
||||
public var onMessage(default, null):Signal<Package>;
|
||||
|
||||
@@ -21,6 +21,7 @@ class Peripheral {
|
||||
private var modes: Array<Modem> = [];
|
||||
private var drives:Array<Drive> = [];
|
||||
private var redstone:Array<Redstone> = [];
|
||||
private var printers:Array<Printer> = [];
|
||||
|
||||
@:allow(kernel.Init)
|
||||
private function new() {
|
||||
@@ -85,7 +86,24 @@ class Peripheral {
|
||||
this.drives = allDrives.map(s -> return new Drive((cc.Peripheral.wrap(s) : Dynamic), s));
|
||||
}
|
||||
|
||||
/**
|
||||
Get redstone peripheral.
|
||||
**/
|
||||
public function getRedstone(side: String): Redstone {
|
||||
return this.redstone.find(item -> item.getAddr() == side);
|
||||
}
|
||||
|
||||
/**
|
||||
Get all connected printers.
|
||||
**/
|
||||
|
||||
public function getPrinters():ReadOnlyArray<Printer> {
|
||||
return this.printers;
|
||||
}
|
||||
|
||||
private function findPrinters() {
|
||||
var allPrinters = cc.Peripheral.getNames().toArray().filter(s -> cc.Peripheral.getType(s) == "printer");
|
||||
this.printers = allPrinters.map(s -> return new Printer((cc.Peripheral.wrap(s) : Dynamic), s));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
56
src/kernel/peripherals/Printer.hx
Normal file
56
src/kernel/peripherals/Printer.hx
Normal file
@@ -0,0 +1,56 @@
|
||||
package kernel.peripherals;
|
||||
|
||||
import lib.Rect;
|
||||
import lib.Pos;
|
||||
|
||||
class Printer implements IPeripheral {
|
||||
|
||||
private final native:cc.periphs.Printer.Printer;
|
||||
private final addr:String;
|
||||
|
||||
public function new(native: cc.periphs.Printer.Printer, addr: String) {
|
||||
this.native = native;
|
||||
this.addr = addr;
|
||||
}
|
||||
|
||||
public function getAddr():String {
|
||||
return addr;
|
||||
}
|
||||
|
||||
public function write(text: String){
|
||||
|
||||
}
|
||||
|
||||
public function getCurserPos(): Pos {
|
||||
return new Pos({x: 0, y: 0});
|
||||
}
|
||||
|
||||
public function setCurserPos(pos: Pos){
|
||||
this.native.setCursorPos(pos.x, pos.y);
|
||||
}
|
||||
|
||||
public function getPageSize(): Rect {
|
||||
var pos = this.native.getPageSize();
|
||||
return new Rect({x: 0, y: 0}, {x: pos.x, y: pos.y});
|
||||
}
|
||||
|
||||
public function newPage(): Bool{
|
||||
return this.native.newPage();
|
||||
}
|
||||
|
||||
public function endPage(): Bool{
|
||||
return this.native.endPage();
|
||||
}
|
||||
|
||||
public function setPageTitle(title: String){
|
||||
this.native.setPageTitle(title);
|
||||
}
|
||||
|
||||
public function getInkLevel(): Float{
|
||||
return this.native.getInkLevel();
|
||||
}
|
||||
|
||||
public function getPaperLevel():Int {
|
||||
return this.native.getPaperLevel();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package kernel.peripherals;
|
||||
|
||||
import haxe.ds.ReadOnlyArray;
|
||||
import util.Color;
|
||||
import lib.Color;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
package kernel.peripherals;
|
||||
|
||||
|
||||
import util.Pos;
|
||||
import lib.Pos;
|
||||
import cc.Term.TerminalSize;
|
||||
import kernel.ui.TermWriteable;
|
||||
import util.Vec.Vec2;
|
||||
import util.Color;
|
||||
import lib.Vec.Vec2;
|
||||
import lib.Color;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
|
||||
@@ -7,4 +7,24 @@ enum abstract Side(String) to String {
|
||||
var Right = "right";
|
||||
var Front = "front";
|
||||
var Back = "back";
|
||||
|
||||
@:from
|
||||
static public function fromString(s: String) {
|
||||
switch (s) {
|
||||
case "top":
|
||||
return Top;
|
||||
case "bottom":
|
||||
return Bottom;
|
||||
case "left":
|
||||
return Left;
|
||||
case "right":
|
||||
return Right;
|
||||
case "front":
|
||||
return Front;
|
||||
case "back":
|
||||
return Back;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package kernel.turtle;
|
||||
|
||||
import kernel.log.Log;
|
||||
import kernel.turtle.Types;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package kernel.ui;
|
||||
|
||||
import util.Color;
|
||||
import lib.Color;
|
||||
|
||||
@:structInit class Pixel {
|
||||
public var char:String;
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package kernel.ui;
|
||||
|
||||
|
||||
import util.Pos;
|
||||
import util.Vec.Vec2;
|
||||
import util.Color;
|
||||
import lib.Pos;
|
||||
import lib.Vec.Vec2;
|
||||
import lib.Color;
|
||||
import kernel.ui.TermWriteable;
|
||||
|
||||
using tink.CoreApi;
|
||||
@@ -89,6 +88,7 @@ class TermBuffer implements TermWriteable {
|
||||
target.setCursorPos(cursorPos.x, cursorPos.y);
|
||||
target.setTextColor(currentTextColor);
|
||||
target.setBackgroundColor(currentBgColor);
|
||||
target.setCursorBlink(cursorBlink);
|
||||
}
|
||||
|
||||
private function safeWriteScreenBuffer(pos:Pos, char:String) {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package kernel.ui;
|
||||
|
||||
import util.Pos;
|
||||
using tink.CoreApi;
|
||||
import lib.Pos;
|
||||
import lib.Color;
|
||||
import lib.Vec.Vec2;
|
||||
|
||||
import util.Color;
|
||||
import util.Vec.Vec2;
|
||||
using tink.CoreApi;
|
||||
|
||||
/**
|
||||
Interface describing a terminal. E.g. the main computer screen or a external screen.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
package kernel.ui;
|
||||
|
||||
import util.Pos;
|
||||
using tink.CoreApi;
|
||||
|
||||
import util.Vec.Vec2;
|
||||
import util.Color;
|
||||
import lib.Pos;
|
||||
import lib.Vec.Vec2;
|
||||
import lib.Color;
|
||||
import kernel.ui.TermWriteable;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
/**
|
||||
A term writer that can switch beetween its internal buffer and another termwriter.
|
||||
The other target is most of the time a real screen
|
||||
|
||||
@@ -1,19 +1,22 @@
|
||||
package kernel.ui;
|
||||
|
||||
import util.Pos;
|
||||
using tink.CoreApi;
|
||||
|
||||
import util.Color;
|
||||
import lib.ui.rendere.UIEventDelegate;
|
||||
import lib.Pos;
|
||||
import lib.Color;
|
||||
import kernel.ButtonType;
|
||||
import util.Vec.Vec2;
|
||||
import lib.Vec.Vec2;
|
||||
import kernel.ui.TermWriteable;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
/**
|
||||
The main object you interact with when writing anything to the screen.
|
||||
**/
|
||||
class WindowContext implements TermWriteable {
|
||||
private final writer:VirtualTermWriter;
|
||||
|
||||
@:allow(kernel.ui.WindowManager) private var eventDelegate: Null<UIEventDelegate>;
|
||||
|
||||
public var onClick(default, null):Signal<{button:ButtonType, pos:Pos}>;
|
||||
public var onKey(default, null):Signal<{keyCode:Int, isHeld:Bool}>;
|
||||
public var onKeyUp(default, null):Signal<Int>;
|
||||
@@ -21,6 +24,7 @@ class WindowContext implements TermWriteable {
|
||||
public var onMouseScroll(default, null):Signal<{dir:Int, pos:Pos}>;
|
||||
public var onMouseUp(default, null):Signal<{button:ButtonType, pos:Pos}>;
|
||||
public var onPaste(default, null):Signal<String>;
|
||||
public var onChar(default, null):Signal<String>;
|
||||
|
||||
@:allow(kernel.ui.WindowManager) private final onClickTrigger:SignalTrigger<{button:ButtonType, pos:Pos}>;
|
||||
@:allow(kernel.ui.WindowManager) private final onKeyTrigger:SignalTrigger<{keyCode:Int, isHeld:Bool}>;
|
||||
@@ -29,6 +33,7 @@ class WindowContext implements TermWriteable {
|
||||
@:allow(kernel.ui.WindowManager) private final onMouseScrollTrigger:SignalTrigger<{dir:Int, pos:Pos}>;
|
||||
@:allow(kernel.ui.WindowManager) private final onMouseUpTrigger:SignalTrigger<{button:ButtonType, pos:Pos}>;
|
||||
@:allow(kernel.ui.WindowManager) private final onPasteTrigger:SignalTrigger<String>;
|
||||
@:allow(kernel.ui.WindowManager) private final onCharTrigger:SignalTrigger<String>;
|
||||
|
||||
@:allow(kernel.ui.WindowManager)
|
||||
private function new(writer:VirtualTermWriter) {
|
||||
@@ -42,6 +47,7 @@ class WindowContext implements TermWriteable {
|
||||
this.onMouseScrollTrigger = Signal.trigger();
|
||||
this.onMouseUpTrigger = Signal.trigger();
|
||||
this.onPasteTrigger = Signal.trigger();
|
||||
this.onCharTrigger = Signal.trigger();
|
||||
|
||||
this.onClick = onClickTrigger.asSignal();
|
||||
this.onKey = onKeyTrigger.asSignal();
|
||||
@@ -50,6 +56,7 @@ class WindowContext implements TermWriteable {
|
||||
this.onMouseScroll = onMouseScrollTrigger.asSignal();
|
||||
this.onMouseUp = onMouseUpTrigger.asSignal();
|
||||
this.onPaste = onPasteTrigger.asSignal();
|
||||
this.onChar = onCharTrigger.asSignal();
|
||||
}
|
||||
|
||||
public var onResize(default, null):Signal<Vec2<Int>>;
|
||||
@@ -133,4 +140,12 @@ class WindowContext implements TermWriteable {
|
||||
public function reset() {
|
||||
writer.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
Delegate events to an UIEventDelegate.
|
||||
Set to null to stop delegating events.
|
||||
**/
|
||||
public function delegateEvents(delegate: Null<UIEventDelegate>){
|
||||
this.eventDelegate = delegate;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,63 +14,106 @@ class WindowManager {
|
||||
**/
|
||||
public static var instance:WindowManager;
|
||||
private var currentMainContext:WindowContext;
|
||||
private final allContexts:Array<WindowContext> = new Array();
|
||||
private final outputMap:Map<String, WindowContext> = new Map();
|
||||
|
||||
@:allow(kernel.Init)
|
||||
private function new() {
|
||||
KernelEvents.instance.onKey.handle(params -> {
|
||||
if (currentMainContext != null) {
|
||||
currentMainContext.onKeyTrigger.trigger(params);
|
||||
if (currentMainContext.eventDelegate != null){
|
||||
var foo = currentMainContext.eventDelegate.getEventHandlers();
|
||||
foo.onKey != null ? foo.onKey.invoke(params) : null;
|
||||
}else{
|
||||
currentMainContext.onKeyTrigger.trigger(params);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KernelEvents.instance.onKeyUp.handle(keyCode -> {
|
||||
if (currentMainContext != null) {
|
||||
currentMainContext.onKeyUpTrigger.trigger(keyCode);
|
||||
if (currentMainContext.eventDelegate != null){
|
||||
var foo = currentMainContext.eventDelegate.getEventHandlers();
|
||||
foo.onKeyUp != null ? foo.onKeyUp.invoke(keyCode) : null;
|
||||
}else{
|
||||
currentMainContext.onKeyUpTrigger.trigger(keyCode);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KernelEvents.instance.onMouseClick.handle(params -> {
|
||||
if (currentMainContext != null) {
|
||||
currentMainContext.onClickTrigger.trigger(params);
|
||||
if (currentMainContext.eventDelegate != null){
|
||||
var foo = currentMainContext.eventDelegate.getEventHandlers();
|
||||
foo.onClick != null ? foo.onClick.invoke(params) : null;
|
||||
}else{
|
||||
currentMainContext.onClickTrigger.trigger(params);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KernelEvents.instance.onMouseDrag.handle(params -> {
|
||||
if (currentMainContext != null) {
|
||||
currentMainContext.onMouseDragTrigger.trigger(params);
|
||||
if (currentMainContext.eventDelegate != null){
|
||||
var foo = currentMainContext.eventDelegate.getEventHandlers();
|
||||
foo.onMouseDrag != null ? foo.onMouseDrag.invoke(params) : null;
|
||||
}else{
|
||||
currentMainContext.onMouseDragTrigger.trigger(params);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KernelEvents.instance.onMouseScroll.handle(params -> {
|
||||
if (currentMainContext != null) {
|
||||
currentMainContext.onMouseScrollTrigger.trigger(params);
|
||||
if (currentMainContext.eventDelegate != null){
|
||||
var foo = currentMainContext.eventDelegate.getEventHandlers();
|
||||
foo.onMouseScroll != null ? foo.onMouseScroll.invoke(params) : null;
|
||||
}else{
|
||||
currentMainContext.onMouseScrollTrigger.trigger(params);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KernelEvents.instance.onMouseUp.handle(params -> {
|
||||
if (currentMainContext != null) {
|
||||
currentMainContext.onMouseUpTrigger.trigger(params);
|
||||
if (currentMainContext.eventDelegate != null){
|
||||
var foo = currentMainContext.eventDelegate.getEventHandlers();
|
||||
foo.onMouseUp != null ? foo.onMouseUp.invoke(params) : null;
|
||||
}else{
|
||||
currentMainContext.onMouseUpTrigger.trigger(params);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KernelEvents.instance.onPaste.handle(text -> {
|
||||
if (currentMainContext != null) {
|
||||
currentMainContext.onPasteTrigger.trigger(text);
|
||||
if (currentMainContext.eventDelegate != null){
|
||||
var foo = currentMainContext.eventDelegate.getEventHandlers();
|
||||
foo.onPaste != null ? foo.onPaste.invoke(text) : null;
|
||||
}else{
|
||||
currentMainContext.onPasteTrigger.trigger(text);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KernelEvents.instance.onMonitorTouch.handle(params -> {
|
||||
// TODO
|
||||
});
|
||||
|
||||
KernelEvents.instance.onChar.handle(char -> {
|
||||
if (currentMainContext != null) {
|
||||
if (currentMainContext.eventDelegate != null){
|
||||
var foo = currentMainContext.eventDelegate.getEventHandlers();
|
||||
foo.onChar != null ? foo.onChar.invoke(char) : null;
|
||||
}else{
|
||||
currentMainContext.onCharTrigger.trigger(char);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public function createNewContext():WindowContext {
|
||||
var newContext = new WindowContext(new VirtualTermWriter());
|
||||
|
||||
allContexts.push(newContext);
|
||||
|
||||
return newContext;
|
||||
}
|
||||
|
||||
@@ -80,6 +123,9 @@ class WindowManager {
|
||||
return arr;
|
||||
}
|
||||
|
||||
/**
|
||||
Move context to output. If output is "main", context will be moved to main screen.
|
||||
**/
|
||||
public function focusContextToOutput(context:WindowContext, output:String) {
|
||||
var target:TermWriteable;
|
||||
if (output == "main") {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package util;
|
||||
package lib;
|
||||
|
||||
/**
|
||||
Macros with static information.
|
||||
@@ -1,4 +1,4 @@
|
||||
package util;
|
||||
package lib;
|
||||
|
||||
import kernel.peripherals.Redstone.BundleMask;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package util;
|
||||
package lib;
|
||||
|
||||
import kernel.log.Log;
|
||||
import lua.NativeStringTools;
|
||||
import lib.ui.Canvas;
|
||||
import cc.ComputerCraft;
|
||||
import kernel.Log;
|
||||
#if webconsole
|
||||
import cc.HTTP;
|
||||
import kernel.net.Net;
|
||||
@@ -1,4 +1,4 @@
|
||||
package util;
|
||||
package lib;
|
||||
|
||||
import haxe.Exception;
|
||||
|
||||
109
src/lib/HomeContext.hx
Normal file
109
src/lib/HomeContext.hx
Normal file
@@ -0,0 +1,109 @@
|
||||
package lib;
|
||||
|
||||
import lib.ui.elements.UIElement;
|
||||
import lib.ui.elements.TextElement;
|
||||
import lib.ui.elements.RootElement;
|
||||
import kernel.KernelEvents;
|
||||
import bin.Terminal;
|
||||
import kernel.ui.WindowManager;
|
||||
import kernel.ui.WindowContext;
|
||||
|
||||
/**
|
||||
The WindowContext that spawns all other contexts. This is the main terminal.
|
||||
Listens to global hotkey "POS1"/"HOME" to switch back to this context.
|
||||
**/
|
||||
class HomeContext {
|
||||
private static inline final MAX_CONTEXT:Int = 10;
|
||||
|
||||
private var ctx:WindowContext = null;
|
||||
private final workspaces:Map<Int, WindowContext> = [];
|
||||
private var currentWorkspace:Int = -1;
|
||||
|
||||
private var renderer:RootElement;
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function run() {
|
||||
// Create main terminal context
|
||||
ctx = WindowManager.instance.createNewContext();
|
||||
WindowManager.instance.focusContextToOutput(ctx, "main");
|
||||
|
||||
renderer = new RootElement();
|
||||
|
||||
ctx.delegateEvents(renderer);
|
||||
render();
|
||||
|
||||
// Register global key bindings to react to main terminal
|
||||
KernelEvents.instance.onKey.handle(e -> {
|
||||
// Is HOME pressed?
|
||||
// TODO: remove magic number
|
||||
if (e.keyCode == 268) {
|
||||
focusMainTerm();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
Adds a context to the list of contexts to manage. Returns the context ID. Returns -1 if the context could not be added.
|
||||
**/
|
||||
private function addContextNextWorkspace(context:WindowContext):Int {
|
||||
for (i in 0...MAX_CONTEXT) {
|
||||
if (!workspaces.exists(i)) {
|
||||
workspaces[i] = context;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
private function addContextToWorkspace(context:WindowContext, id:Int) {
|
||||
if (!workspaces.exists(id)) {
|
||||
workspaces[id] = context;
|
||||
}
|
||||
}
|
||||
|
||||
private function focusContext(id:Int) {
|
||||
if (workspaces.exists(id)) {
|
||||
WindowManager.instance.focusContextToOutput(workspaces[id], "main");
|
||||
currentWorkspace = id;
|
||||
}
|
||||
}
|
||||
|
||||
private function focusMainTerm() {
|
||||
render();
|
||||
WindowManager.instance.focusContextToOutput(ctx, "main");
|
||||
currentWorkspace = -1;
|
||||
}
|
||||
|
||||
private function handleSelectContext(contextId:Int) {
|
||||
if (workspaces[contextId] != null) {
|
||||
focusContext(contextId);
|
||||
} else {
|
||||
var newContext = WindowManager.instance.createNewContext();
|
||||
|
||||
// Create new terminal
|
||||
var term = new Terminal();
|
||||
term.invoke(newContext);
|
||||
|
||||
addContextToWorkspace(newContext, contextId);
|
||||
focusContext(contextId);
|
||||
}
|
||||
}
|
||||
|
||||
private function render() {
|
||||
ctx.clear();
|
||||
|
||||
var list = [
|
||||
for (i in 0...MAX_CONTEXT) workspaces.exists(i) ? 'Switch to context ${i}' : 'Create new Terminal on context ${i}'
|
||||
];
|
||||
|
||||
var children:Array<UIElement> = [
|
||||
for (i => line in list) new TextElement(line, {onClick: this.handleSelectContext.bind(i)})
|
||||
];
|
||||
|
||||
renderer.setChildren(children);
|
||||
|
||||
renderer.render().renderToContext(ctx);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package util;
|
||||
package lib;
|
||||
|
||||
class MathI {
|
||||
public static function max(a:Int, b:Int):Int {
|
||||
24
src/lib/ObjMerge.hx
Normal file
24
src/lib/ObjMerge.hx
Normal file
@@ -0,0 +1,24 @@
|
||||
package lib;
|
||||
|
||||
class ObjMerge {
|
||||
public static function merge<T>(obj1:T, obj2:T): T {
|
||||
if (obj1 == null) {
|
||||
return obj2;
|
||||
}
|
||||
|
||||
if (obj2 == null) {
|
||||
return obj1;
|
||||
}
|
||||
|
||||
var rtn:T = Reflect.copy(obj1);
|
||||
var fields = Reflect.fields(obj2);
|
||||
|
||||
for (field in fields) {
|
||||
if (Reflect.getProperty(obj1, field) == null) {
|
||||
Reflect.setProperty(rtn, field, Reflect.getProperty(obj2, field));
|
||||
}
|
||||
}
|
||||
|
||||
return (rtn:T);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package util;
|
||||
package lib;
|
||||
|
||||
import util.Vec.Vec2;
|
||||
import lib.Vec.Vec2;
|
||||
|
||||
/**
|
||||
Reporesents a Point in a 2D `Int` System.
|
||||
@@ -1,6 +1,6 @@
|
||||
package util;
|
||||
package lib;
|
||||
|
||||
import util.Vec.Vec3;
|
||||
import lib.Vec.Vec3;
|
||||
|
||||
/**
|
||||
Reporesents a Point in a 3D `Int` System.
|
||||
46
src/lib/Rect.hx
Normal file
46
src/lib/Rect.hx
Normal file
@@ -0,0 +1,46 @@
|
||||
package lib;
|
||||
|
||||
class Rect {
|
||||
|
||||
private final tl:Pos;
|
||||
private final br:Pos;
|
||||
|
||||
public function new(p1: Pos,p2:Pos) {
|
||||
this.tl = {
|
||||
x: MathI.min(p1.x,p2.x),
|
||||
y: MathI.min(p1.y,p2.y)
|
||||
};
|
||||
|
||||
this.br = {
|
||||
x: MathI.max(p1.x,p2.x),
|
||||
y: MathI.max(p1.y,p2.y)
|
||||
};
|
||||
}
|
||||
|
||||
public function getSize(): Int {
|
||||
return getWidth() * getHight();
|
||||
}
|
||||
|
||||
public function isInside(p: Pos): Bool {
|
||||
return (p.x >= tl.x && p.x <= br.x) && (p.y >= tl.y && p.y <= br.y);
|
||||
}
|
||||
|
||||
public function isOutside(p: Pos): Bool {
|
||||
return !this.isInside(p);
|
||||
}
|
||||
|
||||
public function getHight(): Int {
|
||||
return br.y - tl.y;
|
||||
}
|
||||
|
||||
public function getWidth(): Int {
|
||||
return br.x - tl.x;
|
||||
}
|
||||
|
||||
public function offset(pos: Pos) {
|
||||
tl.x += pos.x;
|
||||
tl.y += pos.y;
|
||||
br.x += pos.x;
|
||||
br.y += pos.y;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package lib;
|
||||
|
||||
import util.Color;
|
||||
import lib.Color;
|
||||
import kernel.ui.TermWriteable;
|
||||
|
||||
/**
|
||||
|
||||
12
src/lib/Vec.hx
Normal file
12
src/lib/Vec.hx
Normal file
@@ -0,0 +1,12 @@
|
||||
package lib;
|
||||
|
||||
@:structInit class Vec2<T> {
|
||||
public var x:T;
|
||||
public var y:T;
|
||||
}
|
||||
|
||||
@:structInit class Vec3<T> {
|
||||
public var x:T;
|
||||
public var y:T;
|
||||
public var z:T;
|
||||
}
|
||||
7
src/lib/cli/CLIApp.hx
Normal file
7
src/lib/cli/CLIApp.hx
Normal file
@@ -0,0 +1,7 @@
|
||||
package lib.cli;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
abstract class CLIApp {
|
||||
public abstract function invoke(handle: TermHandle): Future<Bool>;
|
||||
}
|
||||
29
src/lib/cli/TermHandle.hx
Normal file
29
src/lib/cli/TermHandle.hx
Normal file
@@ -0,0 +1,29 @@
|
||||
package lib.cli;
|
||||
|
||||
import haxe.ds.ReadOnlyArray;
|
||||
|
||||
typedef TermHandleEvents = {
|
||||
onWrite: String->Void,
|
||||
onNewLine: Void->Void,
|
||||
}
|
||||
|
||||
class TermHandle {
|
||||
public final args:ReadOnlyArray<String>;
|
||||
private final events:TermHandleEvents;
|
||||
|
||||
@:allow(bin.Terminal)
|
||||
private function new(args: Array<String>, events: TermHandleEvents) {
|
||||
this.args = args;
|
||||
this.events = events;
|
||||
}
|
||||
|
||||
public function write(s:String) {
|
||||
this.events.onWrite(s);
|
||||
}
|
||||
|
||||
public function writeLn(s:String = "") {
|
||||
if (s != "")
|
||||
this.events.onWrite(s);
|
||||
this.events.onNewLine();
|
||||
}
|
||||
}
|
||||
27
src/lib/observable/DummyObservable.hx
Normal file
27
src/lib/observable/DummyObservable.hx
Normal file
@@ -0,0 +1,27 @@
|
||||
package lib.observable;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
class DummyObservable<T> implements Observable<T> {
|
||||
private var value:T;
|
||||
|
||||
private function new(value:T) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public function set(value:T) {
|
||||
throw new haxe.exceptions.NotImplementedException();
|
||||
}
|
||||
|
||||
public function get():T {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
public function subscribe(callback:Callback<T>):CallbackLink {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function dummy<T>(value: T): Observable<T> {
|
||||
return new DummyObservable<T>(value);
|
||||
}
|
||||
}
|
||||
9
src/lib/observable/Observable.hx
Normal file
9
src/lib/observable/Observable.hx
Normal file
@@ -0,0 +1,9 @@
|
||||
package lib.observable;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
interface Observable<T> {
|
||||
public function set(value:T): Void;
|
||||
public function get():T;
|
||||
public function subscribe(callback:Callback<T>):CallbackLink;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package util;
|
||||
package lib.observable;
|
||||
|
||||
class ObservableArray<T> extends Observable<Array<T>> {
|
||||
class ObservableArray<T> extends ObservableValue<Array<T>> {
|
||||
public function new(value: Array<T>) {
|
||||
super(value);
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package util;
|
||||
package lib.observable;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
class Observable<T> {
|
||||
class ObservableValue<T> implements Observable<T> {
|
||||
private var value:T;
|
||||
private var callbacks:CallbackList<T> = new CallbackList();
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package lib.turtle;
|
||||
|
||||
import util.Pos;
|
||||
import util.Pos3;
|
||||
import lib.Pos;
|
||||
import lib.Pos3;
|
||||
import kernel.turtle.Turtle;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
class TurtleExecuter {
|
||||
|
||||
@@ -10,7 +10,6 @@ import lua.NativeStringTools;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
|
||||
/**
|
||||
Save a set of turtle instructions to a string and execute them.
|
||||
**/
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package lib.ui;
|
||||
|
||||
import util.Pos;
|
||||
import util.Rect;
|
||||
import kernel.ui.WindowContext;
|
||||
import lib.Pos;
|
||||
import lib.Rect;
|
||||
import kernel.ui.Pixel;
|
||||
|
||||
abstract Canvas(Array<Array<Pixel>>) to Array<Array<Pixel>> from Array<Array<Pixel>>{
|
||||
@@ -63,10 +64,65 @@ abstract Canvas(Array<Array<Pixel>>) to Array<Array<Pixel>> from Array<Array<Pix
|
||||
|
||||
public function getBounds(): Rect {
|
||||
return new Rect({x:0,y:0},{
|
||||
x: maxWidth(),
|
||||
y: height()
|
||||
x: maxWidth() - 1,
|
||||
y: height() - 1
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
Renders the canvas directly to the context
|
||||
**/
|
||||
public function renderToContext(ctx: WindowContext){
|
||||
var lastBgColor: Color = null;
|
||||
var lastTextColor: Color = null;
|
||||
|
||||
for (lineIndex => line in this) {
|
||||
if (line == null || line.length == 0) {
|
||||
// Line is empty
|
||||
continue;
|
||||
}
|
||||
|
||||
ctx.setCursorPos(0, lineIndex);
|
||||
|
||||
var pritableLine = "";
|
||||
|
||||
for (pixelIndex => pixel in line) {
|
||||
if (pixel == null) {
|
||||
// If the pixel is null we need to skip it with setCurosrPos.
|
||||
// Otherwise we will print an empty space with bg color
|
||||
ctx.write(pritableLine);
|
||||
pritableLine = "";
|
||||
ctx.setCursorPos(pixelIndex + 1, lineIndex);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (pixel.bg != lastBgColor) {
|
||||
// The background color has changed, we need to print the current line and set the new background color
|
||||
ctx.write(pritableLine);
|
||||
pritableLine = "";
|
||||
|
||||
// Set the new background color
|
||||
ctx.setBackgroundColor(pixel.bg);
|
||||
lastBgColor = pixel.bg;
|
||||
}
|
||||
|
||||
// Same as above but for the text color
|
||||
if (pixel.textColor != lastTextColor) {
|
||||
// The text color has changed, we need to print the current line and set the new text color
|
||||
ctx.write(pritableLine);
|
||||
pritableLine = "";
|
||||
|
||||
// Set the new text color
|
||||
ctx.setTextColor(pixel.textColor);
|
||||
lastTextColor = pixel.textColor;
|
||||
}
|
||||
|
||||
pritableLine += pixel.char;
|
||||
}
|
||||
|
||||
ctx.write(pritableLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class CanvasKeyValueIterator {
|
||||
|
||||
8
src/lib/ui/Dimensions.hx
Normal file
8
src/lib/ui/Dimensions.hx
Normal file
@@ -0,0 +1,8 @@
|
||||
package lib.ui;
|
||||
|
||||
typedef Dimensions = {
|
||||
public var ?top:Int;
|
||||
public var ?bottom:Int;
|
||||
public var ?left:Int;
|
||||
public var ?right:Int;
|
||||
}
|
||||
9
src/lib/ui/UIApp.hx
Normal file
9
src/lib/ui/UIApp.hx
Normal file
@@ -0,0 +1,9 @@
|
||||
package lib.ui;
|
||||
|
||||
import kernel.ui.WindowContext;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
abstract class UIApp {
|
||||
public abstract function invoke(context: WindowContext): Future<Bool>;
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package lib.ui;
|
||||
|
||||
import util.Pos;
|
||||
import util.Vec.Vec2;
|
||||
import lib.Pos;
|
||||
import kernel.ButtonType;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
typedef UIEvents = {
|
||||
@@ -13,4 +13,5 @@ typedef UIEvents = {
|
||||
public var ?onMouseScroll:Callback<{dir:Int, pos:Pos}>;
|
||||
public var ?onMouseUp:Callback<{button:ButtonType, pos:Pos}>;
|
||||
public var ?onPaste:Callback<String>;
|
||||
public var ?onChar:Callback<String>;
|
||||
}
|
||||
|
||||
26
src/lib/ui/elements/EventMap.hx
Normal file
26
src/lib/ui/elements/EventMap.hx
Normal file
@@ -0,0 +1,26 @@
|
||||
package lib.ui.elements;
|
||||
|
||||
import lib.ui.rendere.UIEventDelegate;
|
||||
|
||||
abstract EventMap(List<{bound:Rect, delegate:UIEventDelegate}>) {
|
||||
inline public function new(?i:List<{bound:Rect, delegate:UIEventDelegate}>) {
|
||||
if (i == null) {
|
||||
this = new List<{bound:Rect, delegate:UIEventDelegate}>();
|
||||
} else {
|
||||
this = i;
|
||||
}
|
||||
}
|
||||
|
||||
public function findResponseableDelegate(pos:Pos):UIEventDelegate {
|
||||
for (i in this) {
|
||||
if (i.bound.isInside(pos)) {
|
||||
return i.delegate;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function addElement(element:UIEventDelegate, bound:Rect) {
|
||||
this.add({bound: bound, delegate: element});
|
||||
}
|
||||
}
|
||||
42
src/lib/ui/elements/RootElement.hx
Normal file
42
src/lib/ui/elements/RootElement.hx
Normal file
@@ -0,0 +1,42 @@
|
||||
package lib.ui.elements;
|
||||
|
||||
class RootElement implements UIElement {
|
||||
private var children:Array<UIElement>;
|
||||
private final eventManager:UIEventManager = new UIEventManager();
|
||||
|
||||
public function new(?children:Array<UIElement>) {
|
||||
if (children == null) {
|
||||
children = [];
|
||||
} else {
|
||||
this.children = children;
|
||||
}
|
||||
}
|
||||
|
||||
public function getEventHandlers():UIEvents {
|
||||
return eventManager.getEventHandlers();
|
||||
}
|
||||
|
||||
public function setChildren(children:Array<UIElement>) {
|
||||
this.children = children;
|
||||
}
|
||||
|
||||
public function render():Canvas {
|
||||
var canvas = new Canvas();
|
||||
var offset = new Pos({x: 0, y: 0});
|
||||
|
||||
this.eventManager.clearMap();
|
||||
|
||||
for (child in children) {
|
||||
var childCanvas = child.render();
|
||||
var bounds = childCanvas.getBounds();
|
||||
bounds.offset(offset);
|
||||
|
||||
this.eventManager.addMapElement(child, bounds);
|
||||
canvas.combine(childCanvas, offset);
|
||||
|
||||
offset = new Pos({x: 0, y: offset.y + bounds.getHight() + 1});
|
||||
}
|
||||
|
||||
return canvas;
|
||||
}
|
||||
}
|
||||
36
src/lib/ui/elements/TextElement.hx
Normal file
36
src/lib/ui/elements/TextElement.hx
Normal file
@@ -0,0 +1,36 @@
|
||||
package lib.ui.elements;
|
||||
|
||||
import lib.ui.elements.UIElement;
|
||||
|
||||
class TextElement implements UIElement {
|
||||
public var text:String;
|
||||
|
||||
private final uiEvents:UIEvents;
|
||||
|
||||
public function new(text:String, ?uiEvents:UIEvents) {
|
||||
this.text = text;
|
||||
this.uiEvents = uiEvents;
|
||||
}
|
||||
|
||||
public function set(text:String) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public function get():String {
|
||||
return this.text;
|
||||
}
|
||||
|
||||
public function getEventHandlers():UIEvents {
|
||||
return uiEvents;
|
||||
}
|
||||
|
||||
public function render():Canvas {
|
||||
var canvas = new Canvas();
|
||||
for (i in 0...this.text.length) {
|
||||
var c = this.text.charAt(i);
|
||||
canvas.set({x: i, y: 0}, {bg: Black, textColor: White, char: c});
|
||||
}
|
||||
|
||||
return canvas;
|
||||
}
|
||||
}
|
||||
7
src/lib/ui/elements/UIElement.hx
Normal file
7
src/lib/ui/elements/UIElement.hx
Normal file
@@ -0,0 +1,7 @@
|
||||
package lib.ui.elements;
|
||||
|
||||
import lib.ui.rendere.UIEventDelegate;
|
||||
|
||||
interface UIElement extends UIEventDelegate {
|
||||
public function render():Canvas;
|
||||
}
|
||||
60
src/lib/ui/elements/UIEventManager.hx
Normal file
60
src/lib/ui/elements/UIEventManager.hx
Normal file
@@ -0,0 +1,60 @@
|
||||
package lib.ui.elements;
|
||||
|
||||
import kernel.log.Log;
|
||||
import kernel.ButtonType;
|
||||
import lib.ui.rendere.UIEventDelegate;
|
||||
|
||||
class UIEventManager implements UIEventDelegate {
|
||||
private var map:EventMap = new EventMap();
|
||||
|
||||
public function new() {}
|
||||
|
||||
public function clearMap() {
|
||||
this.map = new EventMap();
|
||||
}
|
||||
|
||||
public function addMapElement(element:UIEventDelegate, bound:Rect) {
|
||||
this.map.addElement(element, bound);
|
||||
}
|
||||
|
||||
public function getEventHandlers():UIEvents {
|
||||
return {
|
||||
onClick: handleClickEvent,
|
||||
onKey: handleKeyEvent,
|
||||
onKeyUp: handleKeyUpEvent,
|
||||
onMouseDrag: handleMouseDragEvent,
|
||||
onMouseScroll: handleMouseScrollEvent,
|
||||
onMouseUp: handleMouseUpEvent,
|
||||
onPaste: handlePasteEvent,
|
||||
onChar: handleCharEvent,
|
||||
};
|
||||
}
|
||||
|
||||
private function handleClickEvent(params:{button:ButtonType, pos:Pos}) {
|
||||
var element = this.map.findResponseableDelegate(params.pos);
|
||||
if (element == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var handlers = element.getEventHandlers();
|
||||
if (handlers == null || handlers.onClick == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
handlers.onClick.invoke(params);
|
||||
}
|
||||
|
||||
private function handleCharEvent(char:String) {}
|
||||
|
||||
private function handlePasteEvent(text:String) {}
|
||||
|
||||
private function handleMouseUpEvent(params:{button:ButtonType, pos:Pos}) {}
|
||||
|
||||
private function handleMouseScrollEvent(params:{dir:Int, pos:Pos}) {}
|
||||
|
||||
private function handleMouseDragEvent(params:{button:ButtonType, pos:Pos}) {}
|
||||
|
||||
private function handleKeyEvent(params:{keyCode:Int, isHeld:Bool}) {}
|
||||
|
||||
private function handleKeyUpEvent(key:Int) {}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
package lib.ui.reactive;
|
||||
|
||||
import util.Pos;
|
||||
import lib.Pos;
|
||||
import util.Rect;
|
||||
import util.ObservableArray;
|
||||
import util.Vec.Vec2;
|
||||
import lib.Vec.Vec2;
|
||||
|
||||
class ListElement extends UIElement {
|
||||
|
||||
|
||||
@@ -1,40 +1,77 @@
|
||||
package lib.ui.reactive;
|
||||
|
||||
import util.Pos;
|
||||
import lib.ui.Dimensions;
|
||||
import util.ObjMerge;
|
||||
import lib.Pos;
|
||||
import util.Observable;
|
||||
import lib.Color;
|
||||
import lib.Vec.Vec2;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
import util.Color;
|
||||
import util.Vec.Vec2;
|
||||
import util.MathI;
|
||||
typedef TextElementArgs = {
|
||||
public var ?text:Observable<String>;
|
||||
public var ?simpleText: String;
|
||||
public var ?bg:Color;
|
||||
public var ?fg:Color;
|
||||
public var ?padding: Dimensions;
|
||||
public var ?margin: Dimensions;
|
||||
}
|
||||
|
||||
class TextElement extends UIElement {
|
||||
private final text:Observable<String>;
|
||||
private final bg:Color;
|
||||
private final fg:Color;
|
||||
private var args:TextElementArgs;
|
||||
|
||||
public function new(text:Observable<String>, ?background:Color = Black, ?textColor:Color = White, events: UIEvents = null) {
|
||||
public function new(args: TextElementArgs,events: UIEvents = null) {
|
||||
super(events);
|
||||
this.args = args;
|
||||
|
||||
this.text = text;
|
||||
this.bg = background;
|
||||
this.fg = textColor;
|
||||
if (args.text != null) {
|
||||
args.text.subscribe(value -> {
|
||||
this.changedTrigger.trigger(null);
|
||||
});
|
||||
}else if (args.simpleText == null) {
|
||||
throw new Error("text or simpleText must be set");
|
||||
}
|
||||
}
|
||||
|
||||
this.text.subscribe(value -> {
|
||||
this.changedTrigger.trigger(null);
|
||||
});
|
||||
private function getText() {
|
||||
if (args.text != null) {
|
||||
return args.text.get();
|
||||
} else {
|
||||
return args.simpleText;
|
||||
}
|
||||
}
|
||||
|
||||
public function render(bounds:Vec2<Int>,offset: Pos):Canvas {
|
||||
var rtn = new Canvas();
|
||||
|
||||
for (i in 0...MathI.min(Math.floor(text.get().length / bounds.x) + 1, bounds.y)) {
|
||||
var line = (text.get().substr(i * bounds.x, bounds.x));
|
||||
for (char in 0...line.length) {
|
||||
rtn.set({x: char, y: i}, {textColor: fg, char: line.charAt(char), bg: bg});
|
||||
var fullText = getText();
|
||||
|
||||
var writePoint: Pos = {x: 0,y: 0};
|
||||
|
||||
for (pos in 0...fullText.length) {
|
||||
var char = fullText.charAt(pos);
|
||||
|
||||
if (char == "\n"){
|
||||
writePoint = {x: 0, y: writePoint.y + 1};
|
||||
continue;
|
||||
}
|
||||
|
||||
if (writePoint.y >= bounds.y) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (writePoint.x >= bounds.x) {
|
||||
writePoint = {x: 0, y: writePoint.y + 1};
|
||||
}
|
||||
|
||||
rtn.set(writePoint, {char: char,textColor: this.args.fg, bg: this.args.bg});
|
||||
|
||||
writePoint = {x: writePoint.x + 1, y: writePoint.y};
|
||||
}
|
||||
|
||||
return rtn;
|
||||
|
||||
|
||||
return UIElement.applyPaddignAndMargin(rtn, this.args.padding, this.args.margin);
|
||||
}
|
||||
}
|
||||
|
||||
60
src/lib/ui/reactive/TurtleController.hx
Normal file
60
src/lib/ui/reactive/TurtleController.hx
Normal file
@@ -0,0 +1,60 @@
|
||||
package lib.ui.reactive;
|
||||
|
||||
import lib.Debug;
|
||||
import lib.Vec.Vec2;
|
||||
import lib.Pos;
|
||||
|
||||
class TurtleController extends UIElement {
|
||||
|
||||
public function new() {
|
||||
super();
|
||||
}
|
||||
|
||||
private function addButton(label:String): Canvas {
|
||||
var txt = new TextElement({simpleText: '$label', fg: Red,bg: Orange});
|
||||
return txt.render({x:3,y:3},{x:0,y:0});
|
||||
}
|
||||
|
||||
public function render(bounds:Vec2<Int>, offset:Pos):Canvas {
|
||||
var canvas: Canvas = new Canvas();
|
||||
|
||||
canvas.combine(addButton("F"),{x:1,y:1});
|
||||
canvas.combine(addButton("F"),{x:5,y:2});
|
||||
|
||||
return canvas;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// var innerText = switch (sqr) {
|
||||
// case 0: "D";
|
||||
// case 1: "F";
|
||||
// case 2: "U";
|
||||
// case 3: "L";
|
||||
// case 4: "B";
|
||||
// case 5: "R";
|
||||
// case 6: "D";
|
||||
// case 7: "D";
|
||||
// case 8: "D";
|
||||
// default: "?";
|
||||
// };
|
||||
|
||||
// canvas.set({x: offsetX + 0, y: offsetY + 0}, {char: borderChar,bg: bgColor,textColor: textColor});
|
||||
// canvas.set({x: offsetX + 1, y: offsetY + 0}, {char: borderChar,bg: bgColor,textColor: textColor});
|
||||
// canvas.set({x: offsetX + 2, y: offsetY + 0}, {char: borderChar,bg: bgColor,textColor: textColor});
|
||||
// canvas.set({x: offsetX + 3, y: offsetY + 0}, {char: " ",bg: spaceColor,textColor: textColor});
|
||||
|
||||
// canvas.set({x: offsetX + 0, y: offsetY + 1}, {char: borderChar,bg: bgColor,textColor: textColor});
|
||||
// canvas.set({x: offsetX + 1, y: offsetY + 1}, {char: innerText,bg: bgColor,textColor: textColor});
|
||||
// canvas.set({x: offsetX + 2, y: offsetY + 1}, {char: borderChar,bg: bgColor,textColor: textColor});
|
||||
// canvas.set({x: offsetX + 3, y: offsetY + 1}, {char: " ",bg: spaceColor,textColor: textColor});
|
||||
|
||||
// canvas.set({x: offsetX + 0, y: offsetY + 2}, {char: borderChar,bg: bgColor,textColor: textColor});
|
||||
// canvas.set({x: offsetX + 1, y: offsetY + 2}, {char: borderChar,bg: bgColor,textColor: textColor});
|
||||
// canvas.set({x: offsetX + 2, y: offsetY + 2}, {char: borderChar,bg: bgColor,textColor: textColor});
|
||||
// canvas.set({x: offsetX + 3, y: offsetY + 2}, {char: " ",bg: spaceColor,textColor: textColor});
|
||||
|
||||
// canvas.set({x: offsetX + 0, y: offsetY + 3}, {char: " ",bg: spaceColor,textColor: textColor});
|
||||
// canvas.set({x: offsetX + 1, y: offsetY + 3}, {char: " ",bg: spaceColor,textColor: textColor});
|
||||
// canvas.set({x: offsetX + 2, y: offsetY + 3}, {char: " ",bg: spaceColor,textColor: textColor});
|
||||
// canvas.set({x: offsetX + 3, y: offsetY + 3}, {char: " ",bg: spaceColor,textColor: textColor});
|
||||
@@ -1,8 +1,9 @@
|
||||
package lib.ui.reactive;
|
||||
|
||||
import util.Pos;
|
||||
import util.Rect;
|
||||
import util.Vec.Vec2;
|
||||
import util.ObjMerge;
|
||||
import lib.Pos;
|
||||
import lib.Rect;
|
||||
import lib.Vec.Vec2;
|
||||
|
||||
using tink.CoreApi;
|
||||
|
||||
@@ -33,4 +34,14 @@ abstract class UIElement {
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
public static function applyPaddignAndMargin(canvas:Canvas,padding: Dimensions, margin: Dimensions):Canvas{
|
||||
var passing = ObjMerge.merge(padding, {top: 0, left: 0, bottom: 0, right: 0});
|
||||
var margin = ObjMerge.merge(margin, {top: 0, left: 0, bottom: 0, right: 0});
|
||||
var rtn = new Canvas();
|
||||
|
||||
rtn.combine(canvas,{x:margin.left + padding.left,y: margin.top + padding.top});
|
||||
|
||||
return rtn;
|
||||
}
|
||||
}
|
||||
|
||||
38
src/lib/ui/rendere/List.hx
Normal file
38
src/lib/ui/rendere/List.hx
Normal file
@@ -0,0 +1,38 @@
|
||||
package lib.ui.rendere;
|
||||
|
||||
class List implements UIEventDelegate{
|
||||
private final onElementClick: Null<Int->Void>;
|
||||
|
||||
public function new(?onElementClick: Int->Void) {
|
||||
this.onElementClick = onElementClick;
|
||||
}
|
||||
|
||||
public function render(list:Array<String>): Canvas {
|
||||
var canvas = new Canvas();
|
||||
for (line in 0...list.length) {
|
||||
for (char in 0...list[line].length) {
|
||||
var c = list[line].charAt(char);
|
||||
canvas.set({x: char, y: line}, {
|
||||
char: c,
|
||||
textColor: Color.White,
|
||||
bg: Color.Black,
|
||||
});
|
||||
}
|
||||
}
|
||||
return canvas;
|
||||
}
|
||||
|
||||
public function getEventHandlers(): UIEvents{
|
||||
return {
|
||||
onClick: handleClick
|
||||
};
|
||||
}
|
||||
|
||||
private function handleClick(e: {button:kernel.ButtonType, pos:Pos}): Void{
|
||||
if (this.onElementClick == null){
|
||||
return;
|
||||
}
|
||||
|
||||
this.onElementClick(e.pos.y);
|
||||
}
|
||||
}
|
||||
5
src/lib/ui/rendere/UIEventDelegate.hx
Normal file
5
src/lib/ui/rendere/UIEventDelegate.hx
Normal file
@@ -0,0 +1,5 @@
|
||||
package lib.ui.rendere;
|
||||
|
||||
interface UIEventDelegate {
|
||||
public function getEventHandlers(): UIEvents;
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
package util;
|
||||
|
||||
import util.Vec.Vec2;
|
||||
|
||||
class Rect {
|
||||
|
||||
private final tr:Pos;
|
||||
private final bl:Pos;
|
||||
|
||||
public function new(p1: Pos,p2:Pos) {
|
||||
this.tr = {
|
||||
x: MathI.max(p1.x,p2.x),
|
||||
y: MathI.max(p1.y,p2.y)
|
||||
};
|
||||
|
||||
this.bl = {
|
||||
x: MathI.min(p1.x,p2.x),
|
||||
y: MathI.min(p1.y,p2.y)
|
||||
};
|
||||
}
|
||||
|
||||
public function getSize(): Int {
|
||||
return (tr.x - bl.x) * (tr.y - bl.y);
|
||||
}
|
||||
|
||||
public function isInside(p: Pos): Bool {
|
||||
return (p.x >= bl.x && p.x <= tr.x) && (p.y >= bl.y && p.y <= tr.y);
|
||||
}
|
||||
|
||||
public function isOutside(p: Pos): Bool {
|
||||
return !this.isInside(p);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
package util;
|
||||
|
||||
@:structInit class Vec2<T> {
|
||||
public final x:T;
|
||||
public final y:T;
|
||||
}
|
||||
|
||||
@:structInit class Vec3<T> {
|
||||
public final x:T;
|
||||
public final y:T;
|
||||
public final z:T;
|
||||
}
|
||||
Reference in New Issue
Block a user