some ui stuff

This commit is contained in:
Djeeberjr 2023-07-08 14:01:38 +02:00
parent 4f881117cf
commit c390519393
10 changed files with 26 additions and 435 deletions

View File

@ -40,6 +40,7 @@ class StatelessVirtualTermWriter implements VirtualTermWriter {
}
if (enabled) {
target.reset();
renderFunc();
renderRequested = false;
}else{
@ -84,6 +85,8 @@ class StatelessVirtualTermWriter implements VirtualTermWriter {
});
target = newTarget;
target.reset();
}
public function isEnabled():Bool {

View File

@ -46,6 +46,7 @@ class HomeContext {
WindowManager.instance.focusContextToOutput(ctx, "main");
renderer = new RootElement();
renderer.setTitle("Home");
ctx.delegateEvents(renderer);
stateless.setRenderFunc(this.render);
@ -146,7 +147,6 @@ class HomeContext {
}
private function render() {
ctx.clear();
ctx.setCursorBlink(false);
var workspaceIDs:Array<Int> = [for (k=>v in workspaces) k];
@ -163,11 +163,11 @@ class HomeContext {
));
}
children.push(new TextElement('Output :${selectedOutput}', {onClick: this.cycleOutput}));
children.push(new TextElement('Output: ${selectedOutput}', {onClick: this.cycleOutput}));
children.push(new TextElement('Exit', {onClick: KernelEvents.instance.shutdown}));
renderer.setChildren(children);
renderer.render().renderToContext(ctx);
renderer.render(ctx.getSize()).renderToContext(ctx);
}
}

View File

@ -3,6 +3,7 @@ package lib.ui.elements;
class RootElement implements UIElement {
private var children:Array<UIElement>;
private final eventManager:UIEventManager = new UIEventManager();
private var title:String = "";
public function new(?children:Array<UIElement>) {
if (children == null) {
@ -20,14 +21,21 @@ class RootElement implements UIElement {
this.children = children;
}
public function render():Canvas {
public function render(bounds: Pos):Canvas {
var canvas = new Canvas();
var offset = new Pos({x: 0, y: 0});
if (hasTitle()) {
var title = new TextElement(this.title);
var halfWidth = Math.floor(bounds.x / 2) - Math.floor(this.title.length / 2);
canvas.combine(title.render(bounds), {x: halfWidth, y: offset.y});
offset = new Pos({x: 0, y: 1});
}
this.eventManager.clearMap();
for (child in children) {
var childCanvas = child.render();
var childCanvas = child.render(bounds);
var bounds = childCanvas.getBounds();
bounds.offset(offset);
@ -39,4 +47,12 @@ class RootElement implements UIElement {
return canvas;
}
public function setTitle(title: String) {
this.title = title;
}
private inline function hasTitle(): Bool {
return title != "";
}
}

View File

@ -24,7 +24,7 @@ class TextElement implements UIElement {
return uiEvents;
}
public function render():Canvas {
public function render(bounds: Pos):Canvas {
var canvas = new Canvas();
for (i in 0...this.text.length) {
var c = this.text.charAt(i);

View File

@ -3,5 +3,5 @@ package lib.ui.elements;
import lib.ui.rendere.UIEventDelegate;
interface UIElement extends UIEventDelegate {
public function render():Canvas;
public function render(bounds: Pos):Canvas;
}

View File

@ -1,74 +0,0 @@
package lib.ui.reactive;
import lib.Pos;
import util.Rect;
import util.ObservableArray;
import lib.Vec.Vec2;
class ListElement extends UIElement {
private final content:ObservableArray<UIElement>;
private var elementMap: Map<Rect,UIElement> = new Map(); // Position in the map is relative.
private var offset:Pos;
public function new(content: ObservableArray<UIElement>) {
var events: UIEvents = {
onClick: (p)->{
var element = UIElement.getElementInMap((p.pos:Pos) - offset,elementMap);
if (element != null)
if (element.eventListner.onClick != null)
element.eventListner.onClick.invoke(p);
},
onMouseUp: (p)->{
var element = UIElement.getElementInMap((p.pos:Pos) - offset,elementMap);
if (element != null)
if (element.eventListner.onMouseUp != null)
element.eventListner.onMouseUp.invoke(p);
},
onMouseScroll: (p)->{
var element = UIElement.getElementInMap((p.pos:Pos) - offset,elementMap);
if (element != null)
if (element.eventListner.onMouseScroll != null)
element.eventListner.onMouseScroll.invoke(p);
},
};
super(events);
this.content = content;
this.content.subscribe(value -> {
this.changedTrigger.trigger(null);
// TODO: subscribe to elements and forward onChange event
});
}
public function render(bounds:Vec2<Int>,offset: Pos):Canvas {
var canvas: Canvas = new Canvas();
var writePoint:Pos = {x: 0, y: 0};
this.offset = offset;
for(element in this.content.get()){
if (bounds.y - writePoint.y <= 0) {
// No more space to render children
break;
}
var childRender = element.render({
x: bounds.x,
y: bounds.y - writePoint.y
}, offset + writePoint);
canvas.combine(childRender, writePoint);
elementMap.set(new Rect(writePoint,
{
x: childRender.maxWidth() + writePoint.x,
y: writePoint.y + (childRender.height() - 1),
}
),element);
writePoint = {x: 0, y: writePoint.y + childRender.height()};
}
return canvas;
}
}

View File

@ -1,170 +0,0 @@
package lib.ui.reactive;
import util.Pos;
import util.Rect;
import util.Color;
import util.Vec.Vec2;
import kernel.ui.WindowContext;
using tink.CoreApi;
class ReactiveUI {
private final context:WindowContext;
private final children:Array<UIElement>;
private var elementMap: Map<Rect,UIElement> = new Map();
public function new(context:WindowContext, children:Array<UIElement>) {
this.context = context;
this.children = children;
for (child in children) {
child.changed.handle(_ -> {
context.reset();
render();
});
}
setupEvents();
}
private function setupEvents() {
context.onClick.handle(params ->{
for (k => v in elementMap){
if (k.isInside(params.pos)){
if (v.eventListner.onClick != null){
v.eventListner.onClick.invoke(params);
}
}
}
});
// context.onKey.handle(params ->{
// for (k => v in elementMap){
// if (k.isInside(params.pos)){
// if (v.eventListner.onKey != null){
// v.eventListner.onKey.invoke(params);
// }
// }
// }
// });
// context.onKeyUp.handle(params ->{
// for (k => v in elementMap){
// if (k.isInside(params.pos)){
// if (v.eventListner.onKeyUp != null){
// v.eventListner.onKeyUp.invoke(params);
// }
// }
// }
// });
// context.onMouseDrag.handle(params ->{
// for (k => v in elementMap){
// if (k.isInside(params.pos)){
// if (v.eventListner.onMouseDrag != null){
// v.eventListner.onMouseDrag.invoke(params);
// }
// }
// }
// });
context.onMouseScroll.handle(params ->{
for (k => v in elementMap){
if (k.isInside(params.pos)){
if (v.eventListner.onMouseScroll != null){
v.eventListner.onMouseScroll.invoke(params);
}
}
}
});
context.onMouseUp.handle(params ->{
for (k => v in elementMap){
if (k.isInside(params.pos)){
if (v.eventListner.onMouseUp != null){
v.eventListner.onMouseUp.invoke(params);
}
}
}
});
// context.onPaste.handle(params ->{
// for (k => v in elementMap){
// if (k.isInside(params.pos)){
// if (v.eventListner.onPaste != null){
// v.eventListner.onPaste.invoke(params);
// }
// }
// }
// });
}
public function render() {
var size = context.getSize();
var result = renderChildren(children, size);
this.elementMap = result.map;
writeToContext(result.canvas);
}
private function writeToContext(screen:Canvas) {
var currentBg:Color = Black;
var currentFg:Color = White;
var currentLine = 0;
context.setBackgroundColor(currentBg);
context.setTextColor(currentFg);
context.setCursorPos(0, 0);
for (key => pixel in screen) {
if (key.y != currentLine) {
currentLine = key.y;
context.setCursorPos(key.x, key.y);
}
if (pixel == null) {
context.write(' ');
} else {
if (pixel.bg != currentBg) {
context.setBackgroundColor(pixel.bg);
currentBg = pixel.bg;
}
if (pixel.textColor != currentFg) {
context.setTextColor(pixel.textColor);
currentFg = pixel.textColor;
}
context.write(pixel.char);
}
}
}
public static function renderChildren(children:Array<UIElement>, bounds:Vec2<Int>):{canvas: Canvas, map: Map<Rect,UIElement>} {
var canvas:Canvas = new Canvas();
var elementMap: Map<Rect,UIElement> = new Map();
var writePoint:Pos = {x: 0, y: 0};
for (child in children) {
if (bounds.y - writePoint.y <= 0) {
// No more space to render children
break;
}
var childRender = child.render({
x: bounds.x,
y: bounds.y - writePoint.y
},writePoint);
canvas.combine(childRender, writePoint);
elementMap.set(new Rect(writePoint,{x: childRender.maxWidth() + writePoint.x, y: writePoint.y + (childRender.height() - 1)}),child);
writePoint = {x: 0, y: writePoint.y + childRender.height()};
}
return {canvas: canvas,map: elementMap};
}
}

View File

@ -1,77 +0,0 @@
package lib.ui.reactive;
import lib.ui.Dimensions;
import util.ObjMerge;
import lib.Pos;
import util.Observable;
import lib.Color;
import lib.Vec.Vec2;
using tink.CoreApi;
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 var args:TextElementArgs;
public function new(args: TextElementArgs,events: UIEvents = null) {
super(events);
this.args = args;
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");
}
}
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();
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 UIElement.applyPaddignAndMargin(rtn, this.args.padding, this.args.margin);
}
}

View File

@ -1,60 +0,0 @@
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,47 +0,0 @@
package lib.ui.reactive;
import util.ObjMerge;
import lib.Pos;
import lib.Rect;
import lib.Vec.Vec2;
using tink.CoreApi;
abstract class UIElement {
/**
Render the element inside the bounds. `offset` is the offset to the parents position
and can be used to calculate the absolute position of element.
Just save `offset` and pass it to the children.
**/
abstract public function render(bounds:Vec2<Int>, offset: Pos):Canvas;
public var changed(default, null):Signal<Noise>;
private final changedTrigger:SignalTrigger<Noise> = Signal.trigger();
public final eventListner:UIEvents = {};
public function new(events: UIEvents = null) {
changed = changedTrigger.asSignal();
if (events != null){
this.eventListner = events;
}
}
public static function getElementInMap(pos: Pos,elementMap: Map<Rect,UIElement>):Null<UIElement>{
for (k => v in elementMap){
if (k.isInside(pos)){
return v;
}
}
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;
}
}