Compare commits

..

41 Commits

Author SHA1 Message Date
b0be0ba8b4 HomeContext new ui system 2023-02-05 02:56:18 +01:00
a4e4e103bd something something ui 2023-02-05 02:56:04 +01:00
3e7d993662 printer 2023-02-04 23:24:57 +01:00
dabc42ea25 print help in rs command 2023-02-04 20:44:02 +01:00
af8241505f added getHight and getWidth to Rect 2023-02-04 20:43:39 +01:00
d56b871554 improved canvas render to context 2023-02-04 20:41:10 +01:00
8934d40c0b use new event delegate in HomeContext 2023-01-30 16:44:52 +01:00
8f7739c26a implemented UIEvents delegate in WindowContext 2023-01-30 16:44:17 +01:00
a1ce5957d1 use simple list renderer in HomeContext 2023-01-30 16:06:46 +01:00
9e128eaad2 added simple list renderer 2023-01-30 16:06:27 +01:00
881aad743d added renderToContext to Canvas 2023-01-30 16:06:13 +01:00
df86bae738 removed unused import 2023-01-30 03:15:13 +01:00
c731dcef36 i fogor again 2023-01-30 03:14:54 +01:00
e14138c7a0 i forgor 2023-01-30 03:04:53 +01:00
d684360547 renamed MainTerm to HomeContext to avoid confusion 2023-01-30 03:04:20 +01:00
a9eb569a02 moved log to own package 2023-01-30 02:59:55 +01:00
77d330a71d removed unused allContexts 2023-01-30 02:49:43 +01:00
391c6b19fd refactored Logging 2023-01-30 02:47:54 +01:00
632173d122 README feature update 2023-01-28 03:58:26 +01:00
7aa1306077 documentation 2023-01-28 03:56:32 +01:00
15b7112348 changed startup 2023-01-28 03:56:21 +01:00
2cbcc86dce added main term (may rename later) 2023-01-28 03:56:07 +01:00
f71505ce28 removed unused file 2023-01-28 03:55:46 +01:00
cb1f892e6d added UIApp and renamed CLIBase 2023-01-28 03:55:32 +01:00
b54a25eec6 added stacktrace to debug flags 2023-01-28 02:33:38 +01:00
a87f50eec7 added disk cli program 2023-01-27 14:34:25 +01:00
95a9ab63d0 added side fromString 2023-01-27 14:34:06 +01:00
f6c0feda4b catch setDiskLabel error 2023-01-27 13:50:51 +01:00
916831743e removed get side in redstone 2023-01-27 13:39:20 +01:00
72e71c8e36 added Restone cli 2022-12-19 23:28:46 +01:00
8180599c04 Net cli update on new icmp 2022-12-19 22:57:41 +01:00
0b965d32b6 added more icmp stuff 2022-12-19 22:33:17 +01:00
f988c79e2f improved Routing 2022-12-19 21:25:39 +01:00
45a8851e2a private routing method 2022-12-19 21:11:01 +01:00
a6ed7818da another big refactor 2022-12-19 21:06:23 +01:00
3cb1811dcb improved terminal stuff 2022-12-19 17:35:52 +01:00
c6e5a836ea Net improvments 2022-12-19 17:35:14 +01:00
a7dbdff535 i don't even know anymore 2022-12-17 15:08:07 +01:00
2b8aa06117 added terminal app with example hello world 2022-12-15 14:21:26 +01:00
1863462b44 fixed missing char event in windowContext 2022-12-15 14:20:46 +01:00
209e40d0d5 added entrypoint 2022-12-15 14:19:57 +01:00
72 changed files with 1598 additions and 352 deletions

View File

@@ -14,7 +14,7 @@ all: clean build
build: $(MIN_PATH) build: $(MIN_PATH)
debug: HAXE_FLAGS += -D webconsole --debug debug: HAXE_FLAGS += -D webconsole -D error_stack --debug
debug: build debug: build
$(HAXE_PATH): $(shell find src -name '*.hx') $(HAXE_PATH): $(shell find src -name '*.hx')

View File

@@ -7,6 +7,7 @@ General purpose "operation system" for [ComputerCraft](https://tweaked.cc/) buil
- Hardware abstraction - Hardware abstraction
- Virtual screens to switch between multiple GUI apps - Virtual screens to switch between multiple GUI apps
- Reactive UI framework - Reactive UI framework
- Solid base to easily applications
# Building # Building

View File

@@ -1,5 +1,5 @@
-p src -p src
--main Startup --main kernel.Entrypoint
--library cctweaked:git:https://git.kapelle.org/niklas/cctweaked-haxelib.git --library cctweaked:git:https://git.kapelle.org/niklas/cctweaked-haxelib.git
--library tink_core --library tink_core

View File

@@ -1,78 +1,10 @@
import kernel.http.HTTPRequest.Http; import lib.HomeContext;
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;
class Startup { class Startup {
public static function main() { public static function main() {
Init.initKernel(); var main = new HomeContext();
httpTest();
}
private static function httpTest() { main.run();
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);
}
});
}
});
} }
} }

137
src/bin/Disk.hx Normal file
View 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
View 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
View 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
View 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
View 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
View 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()}');
}
}
}

View File

@@ -4,7 +4,7 @@ import kernel.turtle.Turtle;
import haxe.MainLoop; import haxe.MainLoop;
import kernel.net.Routing; import kernel.net.Routing;
import cc.OS; import cc.OS;
import util.Debug; import lib.Debug;
import kernel.ui.WindowManager; import kernel.ui.WindowManager;
import kernel.peripherals.Peripherals.Peripheral; import kernel.peripherals.Peripherals.Peripheral;
import kernel.net.Net; import kernel.net.Net;
@@ -17,7 +17,6 @@ class Init {
WindowManager.instance = new WindowManager(); WindowManager.instance = new WindowManager();
MainTerm.instance = new MainTerm(); MainTerm.instance = new MainTerm();
Log.init();
if (Turtle.isTurtle()){ if (Turtle.isTurtle()){
Turtle.instance = new Turtle(); Turtle.instance = new Turtle();

View File

@@ -1,10 +1,11 @@
package kernel; package kernel;
import util.Pos; import kernel.log.Log;
import lib.Pos;
import cc.HTTP.HTTPResponse; import cc.HTTP.HTTPResponse;
import lua.TableTools; import lua.TableTools;
import lua.Coroutine; import lua.Coroutine;
import util.Vec.Vec2; import lib.Vec.Vec2;
import haxe.Exception; import haxe.Exception;
using tink.CoreApi; using tink.CoreApi;

View File

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

View File

@@ -1,12 +1,12 @@
package kernel; package kernel;
import util.Pos; import lib.Pos;
using tink.CoreApi;
import kernel.ui.TermWriteable; import kernel.ui.TermWriteable;
import cc.Term; import cc.Term;
import util.Vec.Vec2; import lib.Vec.Vec2;
import util.Color; import lib.Color;
using tink.CoreApi;
/** /**
Represents the main computer screen. Represents the main computer screen.

View File

@@ -1,9 +1,9 @@
package kernel; package kernel;
using tink.CoreApi;
import cc.OS; import cc.OS;
using tink.CoreApi;
/** /**
Wrapper class for using timer. Wrapper class for using timer.
**/ **/

48
src/kernel/log/Log.hx Normal file
View 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
}
}

View File

@@ -0,0 +1,9 @@
package kernel.log;
enum LogLevel {
Info;
Warn;
Error;
Debug;
Silly;
}

View File

@@ -0,0 +1,7 @@
package kernel.log;
typedef LogLine = {
level: LogLevel,
message: String,
time: Int,
}

View File

@@ -1,5 +1,6 @@
package kernel.net; package kernel.net;
import kernel.log.Log;
using tink.CoreApi; using tink.CoreApi;
/** /**

View File

@@ -1,15 +1,15 @@
package kernel.net; package kernel.net;
using tink.CoreApi; import haxe.ds.ReadOnlyArray;
import kernel.net.Package.NetworkID; import kernel.net.Package.NetworkID;
import kernel.peripherals.Peripherals.Peripheral; import kernel.peripherals.Peripherals.Peripheral;
import kernel.Log; import kernel.log.Log;
import kernel.Timer; import kernel.Timer;
import cc.OS; import cc.OS;
using tink.CoreApi;
using Lambda; using Lambda;
using util.Extender.LambdaExtender; using lib.Extender.LambdaExtender;
/** /**
Class responsible for everything network related. Class responsible for everything network related.
@@ -25,7 +25,7 @@ class Net {
public static inline final DEFAULT_TTL:Int = 10; public static inline final DEFAULT_TTL:Int = 10;
public final networkID:NetworkID = OS.getComputerID(); 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 final protoHandlers:Map<String, Callback<Package>> = new Map();
private var interfaces:Array<INetworkInterface>; private var interfaces:Array<INetworkInterface>;
@@ -37,6 +37,26 @@ class Net {
for (interf in interfaces){ for (interf in interfaces){
setupInterf(interf); 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) { private function setupInterf(interf: INetworkInterface) {
@@ -57,7 +77,7 @@ class Net {
case Response: case Response:
// Got a response to a send message. Invoke the callback // Got a response to a send message. Invoke the callback
if (responseBus.exists(pack.msgID)) { if (responseBus.exists(pack.msgID)) {
responseBus[pack.msgID].invoke(pack); responseBus[pack.msgID].invoke(Outcome.Success(pack));
} }
case RouteDiscover(_) | RouteDiscoverResponse(_) | RouteDiscoverUpdate(_): case RouteDiscover(_) | RouteDiscoverResponse(_) | RouteDiscoverUpdate(_):
// Delegate to Routing // Delegate to Routing
@@ -91,6 +111,13 @@ class Net {
private function forwardPackage(pack: Package) { private function forwardPackage(pack: Package) {
if (pack.ttl == 0){ 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 // Drop package
return; return;
} }
@@ -164,8 +191,16 @@ class Net {
var timeout:Timer = null; var timeout:Timer = null;
responseBus[pack.msgID] = ((reponse:Package) -> { responseBus[pack.msgID] = ((reponse:Outcome<Package,Error>) -> {
resolve(reponse);
switch reponse {
case Success(pack):
resolve(pack);
case Failure(err):
reject(err);
}
// Always remove the timeout
if (timeout != null) { if (timeout != null) {
timeout.cancle(); timeout.cancle();
} }
@@ -198,4 +233,31 @@ class Net {
public function removeProto(proto:String) { public function removeProto(proto:String) {
protoHandlers.remove(proto); 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;
}
} }

View File

@@ -1,15 +1,15 @@
package kernel.net; package kernel.net;
import kernel.log.Log;
import kernel.peripherals.Peripherals.Peripheral; import kernel.peripherals.Peripherals.Peripheral;
import kernel.net.INetworkInterface; import kernel.net.INetworkInterface;
import kernel.net.Package.NetworkID;
using tink.CoreApi; using tink.CoreApi;
import kernel.net.Package.NetworkID;
@:structInit typedef Route = { @:structInit typedef Route = {
interf:INetworkInterface, interf:INetworkInterface,
rep: NetworkID, rep:NetworkID,
cost:Int cost:Int
} }
@@ -22,13 +22,13 @@ class Routing {
**/ **/
public static var instance: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>; public final onNewNeigbor:Signal<Int>;
private final onNewNeigborTrigger:SignalTrigger<NetworkID> = Signal.trigger(); private final onNewNeigborTrigger:SignalTrigger<NetworkID> = Signal.trigger();
private final routingTable:Map<NetworkID, Route> = new Map(); private final routingTable:Map<NetworkID, Route> = new Map();
private var routeUpdateInQueue: Bool = false; private var routeUpdateInQueue:Bool = false;
@:allow(kernel.Init) @:allow(kernel.Init)
private function new() { private function new() {
@@ -37,61 +37,59 @@ class Routing {
@:allow(kernel.Init) @:allow(kernel.Init)
private function init() { private function init() {
routingTable.set(Net.instance.networkID,{interf: Loopback.instance,cost:0,rep:Net.instance.networkID}); routingTable.set(Net.instance.networkID, {interf: Loopback.instance, cost: 0, rep: Net.instance.networkID});
for (modem in Peripheral.instance.getModems()) { brodcastRoutingTable();
modem.send(Net.BRODCAST_PORT, Net.instance.networkID, newRoutDiscoverPackage());
}
} }
/**
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() { private function prepareRouteUpdate() {
if (this.routeUpdateInQueue){ if (this.routeUpdateInQueue) {
return; return;
} }
this.routeUpdateInQueue = true; this.routeUpdateInQueue = true;
new Timer(UPDATE_WAIT_TIME,()->{ new Timer(UPDATE_WAIT_TIME, () -> {
brodcastUpdatedRoutes(); brodcastRoutingTable();
this.routeUpdateInQueue = false; this.routeUpdateInQueue = false;
}); });
} }
private function brodcastUpdatedRoutes() { /**
var pack: Package = { Brodcast the current routing table to all peers.
type: RouteDiscoverUpdate(genRouteList()), **/
toID: Net.BRODCAST_PORT, private function brodcastRoutingTable() {
msgID: null, var pack = newRoutDiscoverPackage();
fromID: Net.instance.networkID,
data: null,
ttl: 0, // Prevent forwarding
};
for (modem in Peripheral.instance.getModems()) { 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. Handle incomming packages that involve route discovery.
**/ **/
public function handleRoutePackage(pack:Package, interf:INetworkInterface):Void { @:allow(kernel.net.Net)
addPossibleRoute(pack.fromID,interf,0,pack.fromID); 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): case RouteDiscoverResponse(routes):
for (route in routes) { for (route in routes) {
addPossibleRoute(route.id,interf,route.cost,pack.fromID); addPossibleRoute(route.id, interf, route.cost, pack.fromID);
} }
false; false;
case RouteDiscover(routes): case RouteDiscover(routes):
for (route in routes) { for (route in routes) {
addPossibleRoute(route.id,interf,route.cost,pack.fromID); addPossibleRoute(route.id, interf, route.cost, pack.fromID);
} }
true; true;
case RouteDiscoverUpdate(routes): case RouteDiscoverUpdate(routes):
for (route in routes) { for (route in routes) {
addPossibleRoute(route.id,interf,route.cost,pack.fromID); addPossibleRoute(route.id, interf, route.cost, pack.fromID);
} }
false; false;
default: default:
@@ -99,7 +97,7 @@ class Routing {
false; false;
}; };
if (!shouldRespond){ if (!shouldRespond) {
return; return;
} }
@@ -113,12 +111,15 @@ class Routing {
ttl: 0, // Prevent forwarding 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}> = []; Generate a list of routes to send to a peer based on the current routing table.
for (k => v in this.routingTable){ **/
private function genRouteList():Array<{id:NetworkID, cost:Int}> {
var routes:Array<{id:NetworkID, cost:Int}> = [];
for (k => v in this.routingTable) {
routes.push({ routes.push({
id: k, id: k,
cost: v.cost cost: v.cost
@@ -144,36 +145,40 @@ class Routing {
Called when a route to a client has been disoverd. Called when a route to a client has been disoverd.
Its possible to be called multiple times with the same id but different addr. 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) { private function addPossibleRoute(toID:Int, interf:INetworkInterface, cost:Int, rep:NetworkID) {
if (toID == Net.instance.networkID){ if (toID == Net.instance.networkID) {
return; return;
} }
var fullCost = cost+interf.getBaseRoutingCost(); var fullCost = cost + interf.getBaseRoutingCost();
if (this.routingTable.exists(toID)){ if (this.routingTable.exists(toID)) {
if (this.routingTable[toID].cost > fullCost){ if (this.routingTable[toID].cost > fullCost) {
this.routingTable[toID] = {interf:interf,cost:cost + interf.getBaseRoutingCost(),rep: rep}; this.routingTable[toID] = {interf: interf, cost: cost + interf.getBaseRoutingCost(), rep: rep};
this.prepareRouteUpdate(); this.prepareRouteUpdate();
} }
}else{ } else {
this.routingTable[toID] = {interf:interf,cost:cost + interf.getBaseRoutingCost(),rep: rep}; this.routingTable[toID] = {interf: interf, cost: cost + interf.getBaseRoutingCost(), rep: rep};
this.onNewNeigborTrigger.trigger(toID); this.onNewNeigborTrigger.trigger(toID);
this.prepareRouteUpdate(); 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) { 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; return null;
}else{ } else {
return {interf: route.interf,rep: route.rep}; return {interf: route.interf, rep: route.rep};
} }
} }
public function getRouteTable():Map<NetworkID, Route> {
return this.routingTable;
}
} }

View File

@@ -1,6 +1,5 @@
package kernel.peripherals; package kernel.peripherals;
import kernel.net.Package;
using tink.CoreApi; using tink.CoreApi;
class Drive implements IPeripheral { class Drive implements IPeripheral {
@@ -52,8 +51,13 @@ class Drive implements IPeripheral {
this.native.setDiskLabel(); this.native.setDiskLabel();
} }
public inline function setDiskLabel(label: String) { public inline function setDiskLabel(label: String): Null<Error> {
this.native.setDiskLabel(label); try {
this.native.setDiskLabel(label);
return null;
} catch (e: Dynamic) {
return new Error("Invalid label");
}
} }
public inline function hasData():Bool { public inline function hasData():Bool {

View File

@@ -1,10 +1,10 @@
package kernel.peripherals; package kernel.peripherals;
using tink.CoreApi;
import kernel.net.Package; import kernel.net.Package;
import kernel.net.INetworkInterface; import kernel.net.INetworkInterface;
using tink.CoreApi;
class Modem implements INetworkInterface implements IPeripheral { class Modem implements INetworkInterface implements IPeripheral {
public final addr:String; public final addr:String;
public var onMessage(default, null):Signal<Package>; public var onMessage(default, null):Signal<Package>;

View File

@@ -21,6 +21,7 @@ class Peripheral {
private var modes: Array<Modem> = []; private var modes: Array<Modem> = [];
private var drives:Array<Drive> = []; private var drives:Array<Drive> = [];
private var redstone:Array<Redstone> = []; private var redstone:Array<Redstone> = [];
private var printers:Array<Printer> = [];
@:allow(kernel.Init) @:allow(kernel.Init)
private function new() { private function new() {
@@ -85,7 +86,24 @@ class Peripheral {
this.drives = allDrives.map(s -> return new Drive((cc.Peripheral.wrap(s) : Dynamic), s)); this.drives = allDrives.map(s -> return new Drive((cc.Peripheral.wrap(s) : Dynamic), s));
} }
/**
Get redstone peripheral.
**/
public function getRedstone(side: String): Redstone { public function getRedstone(side: String): Redstone {
return this.redstone.find(item -> item.getAddr() == side); 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));
}
} }

View 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();
}
}

View File

@@ -1,7 +1,7 @@
package kernel.peripherals; package kernel.peripherals;
import haxe.ds.ReadOnlyArray; import haxe.ds.ReadOnlyArray;
import util.Color; import lib.Color;
using tink.CoreApi; using tink.CoreApi;

View File

@@ -1,11 +1,10 @@
package kernel.peripherals; package kernel.peripherals;
import lib.Pos;
import util.Pos;
import cc.Term.TerminalSize; import cc.Term.TerminalSize;
import kernel.ui.TermWriteable; import kernel.ui.TermWriteable;
import util.Vec.Vec2; import lib.Vec.Vec2;
import util.Color; import lib.Color;
using tink.CoreApi; using tink.CoreApi;

View File

@@ -7,4 +7,24 @@ enum abstract Side(String) to String {
var Right = "right"; var Right = "right";
var Front = "front"; var Front = "front";
var Back = "back"; 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;
}
}
} }

View File

@@ -1,5 +1,6 @@
package kernel.turtle; package kernel.turtle;
import kernel.log.Log;
import kernel.turtle.Types; import kernel.turtle.Types;
using tink.CoreApi; using tink.CoreApi;

View File

@@ -1,6 +1,6 @@
package kernel.ui; package kernel.ui;
import util.Color; import lib.Color;
@:structInit class Pixel { @:structInit class Pixel {
public var char:String; public var char:String;

View File

@@ -1,9 +1,8 @@
package kernel.ui; package kernel.ui;
import lib.Pos;
import util.Pos; import lib.Vec.Vec2;
import util.Vec.Vec2; import lib.Color;
import util.Color;
import kernel.ui.TermWriteable; import kernel.ui.TermWriteable;
using tink.CoreApi; using tink.CoreApi;
@@ -89,6 +88,7 @@ class TermBuffer implements TermWriteable {
target.setCursorPos(cursorPos.x, cursorPos.y); target.setCursorPos(cursorPos.x, cursorPos.y);
target.setTextColor(currentTextColor); target.setTextColor(currentTextColor);
target.setBackgroundColor(currentBgColor); target.setBackgroundColor(currentBgColor);
target.setCursorBlink(cursorBlink);
} }
private function safeWriteScreenBuffer(pos:Pos, char:String) { private function safeWriteScreenBuffer(pos:Pos, char:String) {

View File

@@ -1,10 +1,10 @@
package kernel.ui; package kernel.ui;
import util.Pos; import lib.Pos;
using tink.CoreApi; import lib.Color;
import lib.Vec.Vec2;
import util.Color; using tink.CoreApi;
import util.Vec.Vec2;
/** /**
Interface describing a terminal. E.g. the main computer screen or a external screen. Interface describing a terminal. E.g. the main computer screen or a external screen.

View File

@@ -1,12 +1,12 @@
package kernel.ui; package kernel.ui;
import util.Pos; import lib.Pos;
using tink.CoreApi; import lib.Vec.Vec2;
import lib.Color;
import util.Vec.Vec2;
import util.Color;
import kernel.ui.TermWriteable; import kernel.ui.TermWriteable;
using tink.CoreApi;
/** /**
A term writer that can switch beetween its internal buffer and another termwriter. A term writer that can switch beetween its internal buffer and another termwriter.
The other target is most of the time a real screen The other target is most of the time a real screen

View File

@@ -1,19 +1,22 @@
package kernel.ui; package kernel.ui;
import util.Pos; import lib.ui.rendere.UIEventDelegate;
using tink.CoreApi; import lib.Pos;
import lib.Color;
import util.Color;
import kernel.ButtonType; import kernel.ButtonType;
import util.Vec.Vec2; import lib.Vec.Vec2;
import kernel.ui.TermWriteable; import kernel.ui.TermWriteable;
using tink.CoreApi;
/** /**
The main object you interact with when writing anything to the screen. The main object you interact with when writing anything to the screen.
**/ **/
class WindowContext implements TermWriteable { class WindowContext implements TermWriteable {
private final writer:VirtualTermWriter; 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 onClick(default, null):Signal<{button:ButtonType, pos:Pos}>;
public var onKey(default, null):Signal<{keyCode:Int, isHeld:Bool}>; public var onKey(default, null):Signal<{keyCode:Int, isHeld:Bool}>;
public var onKeyUp(default, null):Signal<Int>; 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 onMouseScroll(default, null):Signal<{dir:Int, pos:Pos}>;
public var onMouseUp(default, null):Signal<{button:ButtonType, pos:Pos}>; public var onMouseUp(default, null):Signal<{button:ButtonType, pos:Pos}>;
public var onPaste(default, null):Signal<String>; 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 onClickTrigger:SignalTrigger<{button:ButtonType, pos:Pos}>;
@:allow(kernel.ui.WindowManager) private final onKeyTrigger:SignalTrigger<{keyCode:Int, isHeld:Bool}>; @: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 onMouseScrollTrigger:SignalTrigger<{dir:Int, pos:Pos}>;
@:allow(kernel.ui.WindowManager) private final onMouseUpTrigger:SignalTrigger<{button:ButtonType, 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 onPasteTrigger:SignalTrigger<String>;
@:allow(kernel.ui.WindowManager) private final onCharTrigger:SignalTrigger<String>;
@:allow(kernel.ui.WindowManager) @:allow(kernel.ui.WindowManager)
private function new(writer:VirtualTermWriter) { private function new(writer:VirtualTermWriter) {
@@ -42,6 +47,7 @@ class WindowContext implements TermWriteable {
this.onMouseScrollTrigger = Signal.trigger(); this.onMouseScrollTrigger = Signal.trigger();
this.onMouseUpTrigger = Signal.trigger(); this.onMouseUpTrigger = Signal.trigger();
this.onPasteTrigger = Signal.trigger(); this.onPasteTrigger = Signal.trigger();
this.onCharTrigger = Signal.trigger();
this.onClick = onClickTrigger.asSignal(); this.onClick = onClickTrigger.asSignal();
this.onKey = onKeyTrigger.asSignal(); this.onKey = onKeyTrigger.asSignal();
@@ -50,6 +56,7 @@ class WindowContext implements TermWriteable {
this.onMouseScroll = onMouseScrollTrigger.asSignal(); this.onMouseScroll = onMouseScrollTrigger.asSignal();
this.onMouseUp = onMouseUpTrigger.asSignal(); this.onMouseUp = onMouseUpTrigger.asSignal();
this.onPaste = onPasteTrigger.asSignal(); this.onPaste = onPasteTrigger.asSignal();
this.onChar = onCharTrigger.asSignal();
} }
public var onResize(default, null):Signal<Vec2<Int>>; public var onResize(default, null):Signal<Vec2<Int>>;
@@ -133,4 +140,12 @@ class WindowContext implements TermWriteable {
public function reset() { public function reset() {
writer.reset(); writer.reset();
} }
/**
Delegate events to an UIEventDelegate.
Set to null to stop delegating events.
**/
public function delegateEvents(delegate: Null<UIEventDelegate>){
this.eventDelegate = delegate;
}
} }

View File

@@ -14,63 +14,106 @@ class WindowManager {
**/ **/
public static var instance:WindowManager; public static var instance:WindowManager;
private var currentMainContext:WindowContext; private var currentMainContext:WindowContext;
private final allContexts:Array<WindowContext> = new Array();
private final outputMap:Map<String, WindowContext> = new Map(); private final outputMap:Map<String, WindowContext> = new Map();
@:allow(kernel.Init) @:allow(kernel.Init)
private function new() { private function new() {
KernelEvents.instance.onKey.handle(params -> { KernelEvents.instance.onKey.handle(params -> {
if (currentMainContext != null) { 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 -> { KernelEvents.instance.onKeyUp.handle(keyCode -> {
if (currentMainContext != null) { 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 -> { KernelEvents.instance.onMouseClick.handle(params -> {
if (currentMainContext != null) { 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 -> { KernelEvents.instance.onMouseDrag.handle(params -> {
if (currentMainContext != null) { 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 -> { KernelEvents.instance.onMouseScroll.handle(params -> {
if (currentMainContext != null) { 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 -> { KernelEvents.instance.onMouseUp.handle(params -> {
if (currentMainContext != null) { 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 -> { KernelEvents.instance.onPaste.handle(text -> {
if (currentMainContext != null) { 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 -> { KernelEvents.instance.onMonitorTouch.handle(params -> {
// TODO // 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 { public function createNewContext():WindowContext {
var newContext = new WindowContext(new VirtualTermWriter()); var newContext = new WindowContext(new VirtualTermWriter());
allContexts.push(newContext);
return newContext; return newContext;
} }
@@ -80,6 +123,9 @@ class WindowManager {
return arr; return arr;
} }
/**
Move context to output. If output is "main", context will be moved to main screen.
**/
public function focusContextToOutput(context:WindowContext, output:String) { public function focusContextToOutput(context:WindowContext, output:String) {
var target:TermWriteable; var target:TermWriteable;
if (output == "main") { if (output == "main") {

View File

@@ -1,4 +1,4 @@
package util; package lib;
/** /**
Macros with static information. Macros with static information.

View File

@@ -1,4 +1,4 @@
package util; package lib;
import kernel.peripherals.Redstone.BundleMask; import kernel.peripherals.Redstone.BundleMask;

View File

@@ -1,9 +1,9 @@
package util; package lib;
import kernel.log.Log;
import lua.NativeStringTools; import lua.NativeStringTools;
import lib.ui.Canvas; import lib.ui.Canvas;
import cc.ComputerCraft; import cc.ComputerCraft;
import kernel.Log;
#if webconsole #if webconsole
import cc.HTTP; import cc.HTTP;
import kernel.net.Net; import kernel.net.Net;

View File

@@ -1,4 +1,4 @@
package util; package lib;
import haxe.Exception; import haxe.Exception;

109
src/lib/HomeContext.hx Normal file
View 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);
}
}

View File

@@ -1,4 +1,4 @@
package util; package lib;
class MathI { class MathI {
public static function max(a:Int, b:Int):Int { public static function max(a:Int, b:Int):Int {

24
src/lib/ObjMerge.hx Normal file
View 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);
}
}

View File

@@ -1,6 +1,6 @@
package util; package lib;
import util.Vec.Vec2; import lib.Vec.Vec2;
/** /**
Reporesents a Point in a 2D `Int` System. Reporesents a Point in a 2D `Int` System.

View File

@@ -1,6 +1,6 @@
package util; package lib;
import util.Vec.Vec3; import lib.Vec.Vec3;
/** /**
Reporesents a Point in a 3D `Int` System. Reporesents a Point in a 3D `Int` System.

46
src/lib/Rect.hx Normal file
View 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;
}
}

View File

@@ -1,6 +1,6 @@
package lib; package lib;
import util.Color; import lib.Color;
import kernel.ui.TermWriteable; import kernel.ui.TermWriteable;
/** /**

12
src/lib/Vec.hx Normal file
View 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
View 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
View 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();
}
}

View 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);
}
}

View 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;
}

View File

@@ -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>) { public function new(value: Array<T>) {
super(value); super(value);
} }

View File

@@ -1,8 +1,8 @@
package util; package lib.observable;
using tink.CoreApi; using tink.CoreApi;
class Observable<T> { class ObservableValue<T> implements Observable<T> {
private var value:T; private var value:T;
private var callbacks:CallbackList<T> = new CallbackList(); private var callbacks:CallbackList<T> = new CallbackList();

View File

@@ -1,8 +1,9 @@
package lib.turtle; package lib.turtle;
import util.Pos; import lib.Pos;
import util.Pos3; import lib.Pos3;
import kernel.turtle.Turtle; import kernel.turtle.Turtle;
using tink.CoreApi; using tink.CoreApi;
class TurtleExecuter { class TurtleExecuter {

View File

@@ -10,7 +10,6 @@ import lua.NativeStringTools;
using tink.CoreApi; using tink.CoreApi;
/** /**
Save a set of turtle instructions to a string and execute them. Save a set of turtle instructions to a string and execute them.
**/ **/

View File

@@ -1,7 +1,8 @@
package lib.ui; package lib.ui;
import util.Pos; import kernel.ui.WindowContext;
import util.Rect; import lib.Pos;
import lib.Rect;
import kernel.ui.Pixel; import kernel.ui.Pixel;
abstract Canvas(Array<Array<Pixel>>) to Array<Array<Pixel>> from Array<Array<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 { public function getBounds(): Rect {
return new Rect({x:0,y:0},{ return new Rect({x:0,y:0},{
x: maxWidth(), x: maxWidth() - 1,
y: height() 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 { class CanvasKeyValueIterator {

8
src/lib/ui/Dimensions.hx Normal file
View 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
View 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>;
}

View File

@@ -1,8 +1,8 @@
package lib.ui; package lib.ui;
import util.Pos; import lib.Pos;
import util.Vec.Vec2;
import kernel.ButtonType; import kernel.ButtonType;
using tink.CoreApi; using tink.CoreApi;
typedef UIEvents = { typedef UIEvents = {
@@ -13,4 +13,5 @@ typedef UIEvents = {
public var ?onMouseScroll:Callback<{dir:Int, pos:Pos}>; public var ?onMouseScroll:Callback<{dir:Int, pos:Pos}>;
public var ?onMouseUp:Callback<{button:ButtonType, pos:Pos}>; public var ?onMouseUp:Callback<{button:ButtonType, pos:Pos}>;
public var ?onPaste:Callback<String>; public var ?onPaste:Callback<String>;
public var ?onChar:Callback<String>;
} }

View 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});
}
}

View 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;
}
}

View 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;
}
}

View File

@@ -0,0 +1,7 @@
package lib.ui.elements;
import lib.ui.rendere.UIEventDelegate;
interface UIElement extends UIEventDelegate {
public function render():Canvas;
}

View 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) {}
}

View File

@@ -1,9 +1,9 @@
package lib.ui.reactive; package lib.ui.reactive;
import util.Pos; import lib.Pos;
import util.Rect; import util.Rect;
import util.ObservableArray; import util.ObservableArray;
import util.Vec.Vec2; import lib.Vec.Vec2;
class ListElement extends UIElement { class ListElement extends UIElement {

View File

@@ -1,40 +1,77 @@
package lib.ui.reactive; package lib.ui.reactive;
import util.Pos; import lib.ui.Dimensions;
import util.ObjMerge;
import lib.Pos;
import util.Observable; import util.Observable;
import lib.Color;
import lib.Vec.Vec2;
using tink.CoreApi; using tink.CoreApi;
import util.Color; typedef TextElementArgs = {
import util.Vec.Vec2; public var ?text:Observable<String>;
import util.MathI; public var ?simpleText: String;
public var ?bg:Color;
public var ?fg:Color;
public var ?padding: Dimensions;
public var ?margin: Dimensions;
}
class TextElement extends UIElement { class TextElement extends UIElement {
private final text:Observable<String>; private var args:TextElementArgs;
private final bg:Color;
private final fg:Color;
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); super(events);
this.args = args;
this.text = text; if (args.text != null) {
this.bg = background; args.text.subscribe(value -> {
this.fg = textColor; this.changedTrigger.trigger(null);
});
}else if (args.simpleText == null) {
throw new Error("text or simpleText must be set");
}
}
this.text.subscribe(value -> { private function getText() {
this.changedTrigger.trigger(null); if (args.text != null) {
}); return args.text.get();
} else {
return args.simpleText;
}
} }
public function render(bounds:Vec2<Int>,offset: Pos):Canvas { public function render(bounds:Vec2<Int>,offset: Pos):Canvas {
var rtn = new Canvas(); var rtn = new Canvas();
for (i in 0...MathI.min(Math.floor(text.get().length / bounds.x) + 1, bounds.y)) { var fullText = getText();
var line = (text.get().substr(i * bounds.x, bounds.x));
for (char in 0...line.length) { var writePoint: Pos = {x: 0,y: 0};
rtn.set({x: char, y: i}, {textColor: fg, char: line.charAt(char), bg: bg});
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);
} }
} }

View 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});

View File

@@ -1,8 +1,9 @@
package lib.ui.reactive; package lib.ui.reactive;
import util.Pos; import util.ObjMerge;
import util.Rect; import lib.Pos;
import util.Vec.Vec2; import lib.Rect;
import lib.Vec.Vec2;
using tink.CoreApi; using tink.CoreApi;
@@ -33,4 +34,14 @@ abstract class UIElement {
return null; 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;
}
} }

View 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);
}
}

View File

@@ -0,0 +1,5 @@
package lib.ui.rendere;
interface UIEventDelegate {
public function getEventHandlers(): UIEvents;
}

View File

@@ -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);
}
}

View File

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