Update Files
This commit is contained in:
517
leenkx/Sources/zui/Ext.hx
Normal file
517
leenkx/Sources/zui/Ext.hx
Normal file
@ -0,0 +1,517 @@
|
||||
package zui;
|
||||
|
||||
import zui.Zui;
|
||||
import kha.input.KeyCode;
|
||||
|
||||
@:access(zui.Zui)
|
||||
class Ext {
|
||||
|
||||
public static function floatInput(ui: Zui, handle: Handle, label = "", align: Align = Left, precision = 1000.0): Float {
|
||||
handle.text = Std.string(Math.round(handle.value * precision) / precision);
|
||||
var text = ui.textInput(handle, label, align);
|
||||
handle.value = Std.parseFloat(text);
|
||||
return handle.value;
|
||||
}
|
||||
|
||||
static function initPath(handle: Handle, systemId: String) {
|
||||
handle.text = systemId == "Windows" ? "C:\\Users" : "/";
|
||||
// %HOMEDRIVE% + %HomePath%
|
||||
// ~
|
||||
}
|
||||
|
||||
public static var dataPath = "";
|
||||
static var lastPath = "";
|
||||
public static function fileBrowser(ui: Zui, handle: Handle, foldersOnly = false): String {
|
||||
var sep = "/";
|
||||
|
||||
#if kha_krom
|
||||
|
||||
var cmd = "ls ";
|
||||
var systemId = kha.System.systemId;
|
||||
if (systemId == "Windows") {
|
||||
cmd = "dir /b ";
|
||||
if (foldersOnly) cmd += "/ad ";
|
||||
sep = "\\";
|
||||
handle.text = StringTools.replace(handle.text, "\\\\", "\\");
|
||||
handle.text = StringTools.replace(handle.text, "\r", "");
|
||||
}
|
||||
if (handle.text == "") initPath(handle, systemId);
|
||||
|
||||
var save = Krom.getFilesLocation() + sep + dataPath + "dir.txt";
|
||||
if (handle.text != lastPath) Krom.sysCommand(cmd + '"' + handle.text + '"' + " > " + '"' + save + '"');
|
||||
lastPath = handle.text;
|
||||
var str = haxe.io.Bytes.ofData(Krom.loadBlob(save)).toString();
|
||||
var files = str.split("\n");
|
||||
|
||||
#elseif kha_kore
|
||||
|
||||
if (handle.text == "") initPath(handle, kha.System.systemId);
|
||||
var files = sys.FileSystem.isDirectory(handle.text) ? sys.FileSystem.readDirectory(handle.text) : [];
|
||||
|
||||
#elseif kha_webgl
|
||||
|
||||
var files: Array<String> = [];
|
||||
|
||||
var userAgent = untyped navigator.userAgent.toLowerCase();
|
||||
if (userAgent.indexOf(" electron/") > -1) {
|
||||
if (handle.text == "") {
|
||||
var pp = untyped window.process.platform;
|
||||
var systemId = pp == "win32" ? "Windows" : (pp == "darwin" ? "OSX" : "Linux");
|
||||
initPath(handle, systemId);
|
||||
}
|
||||
try {
|
||||
files = untyped require("fs").readdirSync(handle.text);
|
||||
}
|
||||
catch (e: Dynamic) {
|
||||
// Non-directory item selected
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
var files: Array<String> = [];
|
||||
|
||||
#end
|
||||
|
||||
// Up directory
|
||||
var i1 = handle.text.indexOf("/");
|
||||
var i2 = handle.text.indexOf("\\");
|
||||
var nested =
|
||||
(i1 > -1 && handle.text.length - 1 > i1) ||
|
||||
(i2 > -1 && handle.text.length - 1 > i2);
|
||||
handle.changed = false;
|
||||
if (nested && ui.button("..", Align.Left)) {
|
||||
handle.changed = ui.changed = true;
|
||||
handle.text = handle.text.substring(0, handle.text.lastIndexOf(sep));
|
||||
// Drive root
|
||||
if (handle.text.length == 2 && handle.text.charAt(1) == ":") handle.text += sep;
|
||||
}
|
||||
|
||||
// Directory contents
|
||||
for (f in files) {
|
||||
if (f == "" || f.charAt(0) == ".") continue; // Skip hidden
|
||||
if (ui.button(f, Align.Left)) {
|
||||
handle.changed = ui.changed = true;
|
||||
if (handle.text.charAt(handle.text.length - 1) != sep) handle.text += sep;
|
||||
handle.text += f;
|
||||
}
|
||||
}
|
||||
|
||||
return handle.text;
|
||||
}
|
||||
|
||||
public static function inlineRadio(ui: Zui, handle: Handle, texts: Array<String>, align: Align = Left): Int {
|
||||
if (!ui.isVisible(ui.ELEMENT_H())) {
|
||||
ui.endElement();
|
||||
return handle.position;
|
||||
}
|
||||
var step = ui._w / texts.length;
|
||||
var hovered = -1;
|
||||
if (ui.getHover()) {
|
||||
var ix = Std.int(ui.inputX - ui._x - ui._windowX);
|
||||
for (i in 0...texts.length) {
|
||||
if (ix < i * step + step) {
|
||||
hovered = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ui.getReleased()) {
|
||||
handle.position = hovered;
|
||||
handle.changed = ui.changed = true;
|
||||
}
|
||||
else handle.changed = false;
|
||||
|
||||
for (i in 0...texts.length) {
|
||||
if (handle.position == i) {
|
||||
ui.g.color = ui.t.ACCENT_HOVER_COL;
|
||||
if (!ui.enabled) ui.fadeColor();
|
||||
ui.g.fillRect(ui._x + step * i, ui._y + ui.buttonOffsetY, step, ui.BUTTON_H());
|
||||
}
|
||||
else if (hovered == i) {
|
||||
ui.g.color = ui.t.ACCENT_COL;
|
||||
if (!ui.enabled) ui.fadeColor();
|
||||
ui.g.drawRect(ui._x + step * i, ui._y + ui.buttonOffsetY, step, ui.BUTTON_H());
|
||||
}
|
||||
ui.g.color = ui.t.TEXT_COL; // Text
|
||||
ui._x += step * i;
|
||||
var _w = ui._w;
|
||||
ui._w = Std.int(step);
|
||||
ui.drawString(ui.g, texts[i], null, 0, align);
|
||||
ui._x -= step * i;
|
||||
ui._w = _w;
|
||||
}
|
||||
ui.endElement();
|
||||
return handle.position;
|
||||
}
|
||||
|
||||
static var wheelSelectedHandle: Handle = null;
|
||||
static var gradientSelectedHandle: Handle = null;
|
||||
public static function colorWheel(ui: Zui, handle: Handle, alpha = false, w: Null<Float> = null, h: Null<Float> = null, colorPreview = true, picker: Void->Void = null): kha.Color {
|
||||
if (w == null) w = ui._w;
|
||||
rgbToHsv(handle.color.R, handle.color.G, handle.color.B, ar);
|
||||
var chue = ar[0];
|
||||
var csat = ar[1];
|
||||
var cval = ar[2];
|
||||
var calpha = handle.color.A;
|
||||
// Wheel
|
||||
var px = ui._x;
|
||||
var py = ui._y;
|
||||
var scroll = ui.currentWindow != null ? ui.currentWindow.scrollEnabled : false;
|
||||
if (!scroll) {
|
||||
w -= ui.SCROLL_W();
|
||||
px += ui.SCROLL_W() / 2;
|
||||
}
|
||||
var _x = ui._x;
|
||||
var _y = ui._y;
|
||||
var _w = ui._w;
|
||||
ui._w = Std.int(28 * ui.SCALE());
|
||||
if (picker != null && ui.button("P")) {
|
||||
picker();
|
||||
ui.changed = false;
|
||||
handle.changed = false;
|
||||
return handle.color;
|
||||
}
|
||||
ui._x = _x;
|
||||
ui._y = _y;
|
||||
ui._w = _w;
|
||||
ui.image(ui.ops.color_wheel, kha.Color.fromFloats(cval, cval, cval));
|
||||
// Picker
|
||||
var ph = ui._y - py;
|
||||
var ox = px + w / 2;
|
||||
var oy = py + ph / 2;
|
||||
var cw = w * 0.7;
|
||||
var cwh = cw / 2;
|
||||
var cx = ox;
|
||||
var cy = oy + csat * cwh; // Sat is distance from center
|
||||
var gradTx = px + 0.897 * w ;
|
||||
var gradTy = oy - cwh;
|
||||
var gradW = 0.0777 * w;
|
||||
var gradH = cw;
|
||||
// Rotate around origin by hue
|
||||
var theta = chue * (Math.PI * 2.0);
|
||||
var cx2 = Math.cos(theta) * (cx - ox) - Math.sin(theta) * (cy - oy) + ox;
|
||||
var cy2 = Math.sin(theta) * (cx - ox) + Math.cos(theta) * (cy - oy) + oy;
|
||||
cx = cx2;
|
||||
cy = cy2;
|
||||
|
||||
ui._x = px - (scroll ? 0 : ui.SCROLL_W() / 2);
|
||||
ui._y = py;
|
||||
ui.image(ui.ops.black_white_gradient);
|
||||
|
||||
ui.g.color = 0xff000000;
|
||||
ui.g.fillRect(cx - 3 * ui.SCALE(), cy - 3 * ui.SCALE(), 6 * ui.SCALE(), 6 * ui.SCALE());
|
||||
ui.g.color = 0xffffffff;
|
||||
ui.g.fillRect(cx - 2 * ui.SCALE(), cy - 2 * ui.SCALE(), 4 * ui.SCALE(), 4 * ui.SCALE());
|
||||
|
||||
ui.g.color = 0xff000000;
|
||||
ui.g.fillRect(gradTx + gradW / 2 - 3 * ui.SCALE(), gradTy + (1 - cval) * gradH - 3 * ui.SCALE(), 6 * ui.SCALE(), 6 * ui.SCALE());
|
||||
ui.g.color = 0xffffffff;
|
||||
ui.g.fillRect(gradTx + gradW / 2 - 2 * ui.SCALE(), gradTy + (1 - cval) * gradH - 2 * ui.SCALE(), 4 * ui.SCALE(), 4 * ui.SCALE());
|
||||
|
||||
if (alpha) {
|
||||
var alphaHandle = handle.nest(1, {value: Math.round(calpha * 100) / 100});
|
||||
calpha = ui.slider(alphaHandle, "Alpha", 0.0, 1.0, true);
|
||||
if (alphaHandle.changed) handle.changed = ui.changed = true;
|
||||
}
|
||||
// Mouse picking for color wheel
|
||||
var gx = ox + ui._windowX;
|
||||
var gy = oy + ui._windowY;
|
||||
if (ui.inputStarted && ui.getInputInRect(gx - cwh, gy - cwh, cw, cw)) wheelSelectedHandle = handle;
|
||||
if (ui.inputReleased && wheelSelectedHandle != null) { wheelSelectedHandle = null; handle.changed = ui.changed = true; }
|
||||
if (ui.inputDown && wheelSelectedHandle == handle) {
|
||||
csat = Math.min(dist(gx, gy, ui.inputX, ui.inputY), cwh) / cwh;
|
||||
var angle = Math.atan2(ui.inputX - gx, ui.inputY - gy);
|
||||
if (angle < 0) angle = Math.PI + (Math.PI - Math.abs(angle));
|
||||
angle = Math.PI * 2 - angle;
|
||||
chue = angle / (Math.PI * 2);
|
||||
handle.changed = ui.changed = true;
|
||||
}
|
||||
// Mouse picking for cval
|
||||
if (ui.inputStarted && ui.getInputInRect(gradTx + ui._windowX, gradTy + ui._windowY, gradW, gradH)) gradientSelectedHandle = handle;
|
||||
if (ui.inputReleased && gradientSelectedHandle != null) { gradientSelectedHandle = null; handle.changed = ui.changed = true; }
|
||||
if (ui.inputDown && gradientSelectedHandle == handle) {
|
||||
cval = Math.max(0.01, Math.min(1, 1 - (ui.inputY - gradTy - ui._windowY) / gradH));
|
||||
handle.changed = ui.changed = true;
|
||||
}
|
||||
// Save as rgb
|
||||
hsvToRgb(chue, csat, cval, ar);
|
||||
handle.color = kha.Color.fromFloats(ar[0], ar[1], ar[2], calpha);
|
||||
|
||||
if (colorPreview) ui.text("", Right, handle.color);
|
||||
|
||||
var pos = Ext.inlineRadio(ui, Id.handle(), ["RGB", "HSV", "Hex"]);
|
||||
var h0 = handle.nest(0).nest(0);
|
||||
var h1 = handle.nest(0).nest(1);
|
||||
var h2 = handle.nest(0).nest(2);
|
||||
if (pos == 0) {
|
||||
h0.value = handle.color.R;
|
||||
|
||||
handle.color.R = ui.slider(h0, "R", 0, 1, true);
|
||||
h1.value = handle.color.G;
|
||||
|
||||
handle.color.G = ui.slider(h1, "G", 0, 1, true);
|
||||
h2.value = handle.color.B;
|
||||
handle.color.B = ui.slider(h2, "B", 0, 1, true);
|
||||
}
|
||||
else if (pos == 1) {
|
||||
rgbToHsv(handle.color.R, handle.color.G, handle.color.B, ar);
|
||||
h0.value = ar[0];
|
||||
h1.value = ar[1];
|
||||
h2.value = ar[2];
|
||||
var chue = ui.slider(h0, "H", 0, 1, true);
|
||||
var csat = ui.slider(h1, "S", 0, 1, true);
|
||||
var cval = ui.slider(h2, "V", 0, 1, true);
|
||||
hsvToRgb(chue, csat, cval, ar);
|
||||
handle.color = kha.Color.fromFloats(ar[0], ar[1], ar[2]);
|
||||
}
|
||||
else if (pos == 2) {
|
||||
#if js
|
||||
handle.text = untyped (handle.color >>> 0).toString(16);
|
||||
var hexCode = ui.textInput(handle, "#");
|
||||
|
||||
if (hexCode.length >= 1 && hexCode.charAt(0) == "#") // Allow # at the beginning
|
||||
hexCode = hexCode.substr(1);
|
||||
if (hexCode.length == 3) // 3 digit CSS style values like fa0 --> ffaa00
|
||||
hexCode = hexCode.charAt(0) + hexCode.charAt(0) + hexCode.charAt(1) + hexCode.charAt(1) + hexCode.charAt(2) + hexCode.charAt(2);
|
||||
if (hexCode.length == 4) // 4 digit CSS style values
|
||||
hexCode = hexCode.charAt(0) + hexCode.charAt(0) + hexCode.charAt(1) + hexCode.charAt(1) + hexCode.charAt(2) + hexCode.charAt(2) + hexCode.charAt(3) + hexCode.charAt(3);
|
||||
if (hexCode.length == 6) // Make the alpha channel optional
|
||||
hexCode = "ff" + hexCode;
|
||||
|
||||
handle.color = untyped parseInt(hexCode, 16);
|
||||
#end
|
||||
}
|
||||
if (h0.changed || h1.changed || h2.changed) handle.changed = ui.changed = true;
|
||||
|
||||
if (ui.getInputInRect(ui._windowX + px, ui._windowY + py, w, h == null ? (ui._y - py) : h) && ui.inputReleased) // Do not close if user clicks
|
||||
ui.changed = true;
|
||||
|
||||
return handle.color;
|
||||
}
|
||||
|
||||
static function rightAlignNumber(number: Int, length: Int): String {
|
||||
var s = number + "";
|
||||
while (s.length < length)
|
||||
s = " " + s;
|
||||
return s;
|
||||
}
|
||||
|
||||
public static var textAreaLineNumbers = false;
|
||||
public static var textAreaScrollPastEnd = false;
|
||||
public static var textAreaColoring: TTextColoring = null;
|
||||
|
||||
public static function textArea(ui: Zui, handle: Handle, align = Align.Left, editable = true, label = "", wordWrap = false): String {
|
||||
handle.text = StringTools.replace(handle.text, "\t", " ");
|
||||
var selected = ui.textSelectedHandle == handle; // Text being edited
|
||||
var lines = handle.text.split("\n");
|
||||
var showLabel = (lines.length == 1 && lines[0] == "");
|
||||
var keyPressed = selected && ui.isKeyPressed;
|
||||
ui.highlightOnSelect = false;
|
||||
ui.tabSwitchEnabled = false;
|
||||
|
||||
if (wordWrap && handle.text != "") {
|
||||
var cursorSet = false;
|
||||
var cursorPos = ui.cursorX;
|
||||
for (i in 0...handle.position) cursorPos += lines[i].length + 1; // + \n
|
||||
var words = lines.join(" ").split(" ");
|
||||
lines = [];
|
||||
var line = "";
|
||||
for (w in words) {
|
||||
var linew = ui.ops.font.width(ui.fontSize, line + " " + w);
|
||||
var wordw = ui.ops.font.width(ui.fontSize, " " + w);
|
||||
if (linew > ui._w - 10 && linew > wordw) {
|
||||
lines.push(line);
|
||||
line = "";
|
||||
}
|
||||
line = line == "" ? w : line + " " + w;
|
||||
|
||||
var linesLen = lines.length;
|
||||
for (l in lines) linesLen += l.length;
|
||||
if (selected && !cursorSet && cursorPos <= linesLen + line.length) {
|
||||
cursorSet = true;
|
||||
handle.position = lines.length;
|
||||
ui.cursorX = ui.highlightAnchor = cursorPos - linesLen;
|
||||
}
|
||||
}
|
||||
lines.push(line);
|
||||
if (selected) {
|
||||
ui.textSelected = handle.text = lines[handle.position];
|
||||
}
|
||||
}
|
||||
var cursorStartX = ui.cursorX;
|
||||
|
||||
if (textAreaLineNumbers) {
|
||||
var _y = ui._y;
|
||||
var _TEXT_COL = ui.t.TEXT_COL;
|
||||
ui.t.TEXT_COL = ui.t.ACCENT_COL;
|
||||
var maxLength = Math.ceil(Math.log(lines.length + 0.5) / Math.log(10)); // Express log_10 with natural log
|
||||
for (i in 0...lines.length) {
|
||||
ui.text(rightAlignNumber(i + 1, maxLength));
|
||||
ui._y -= ui.ELEMENT_OFFSET();
|
||||
}
|
||||
ui.t.TEXT_COL = _TEXT_COL;
|
||||
ui._y = _y;
|
||||
ui._x += (lines.length + "").length * 16 + 4;
|
||||
}
|
||||
|
||||
ui.g.color = ui.t.SEPARATOR_COL; // Background
|
||||
ui.drawRect(ui.g, true, ui._x + ui.buttonOffsetY, ui._y + ui.buttonOffsetY, ui._w - ui.buttonOffsetY * 2, lines.length * ui.ELEMENT_H() - ui.buttonOffsetY * 2);
|
||||
|
||||
var _textColoring = ui.textColoring;
|
||||
ui.textColoring = textAreaColoring;
|
||||
|
||||
for (i in 0...lines.length) { // Draw lines
|
||||
if ((!selected && ui.getHover()) || (selected && i == handle.position)) {
|
||||
handle.position = i; // Set active line
|
||||
handle.text = lines[i];
|
||||
ui.submitTextHandle = null;
|
||||
ui.textInput(handle, showLabel ? label : "", align, editable);
|
||||
if (keyPressed && ui.key != KeyCode.Return && ui.key != KeyCode.Escape) { // Edit text
|
||||
lines[i] = ui.textSelected;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (showLabel) {
|
||||
var TEXT_COL = ui.t.TEXT_COL;
|
||||
ui.t.TEXT_COL = ui.t.LABEL_COL;
|
||||
ui.text(label, Right);
|
||||
ui.t.TEXT_COL = TEXT_COL;
|
||||
}
|
||||
else {
|
||||
ui.text(lines[i], align);
|
||||
}
|
||||
}
|
||||
ui._y -= ui.ELEMENT_OFFSET();
|
||||
}
|
||||
ui._y += ui.ELEMENT_OFFSET();
|
||||
ui.textColoring = _textColoring;
|
||||
|
||||
if (textAreaScrollPastEnd) {
|
||||
ui._y += ui._h - ui.windowHeaderH - ui.ELEMENT_H() - ui.ELEMENT_OFFSET();
|
||||
}
|
||||
|
||||
if (keyPressed) {
|
||||
// Move cursor vertically
|
||||
if (ui.key == KeyCode.Down && handle.position < lines.length - 1) {
|
||||
handle.position++;
|
||||
scrollAlign(ui, handle);
|
||||
}
|
||||
if (ui.key == KeyCode.Up && handle.position > 0) {
|
||||
handle.position--;
|
||||
scrollAlign(ui, handle);
|
||||
}
|
||||
// New line
|
||||
if (editable && ui.key == KeyCode.Return && !wordWrap) {
|
||||
handle.position++;
|
||||
lines.insert(handle.position, lines[handle.position - 1].substr(ui.cursorX));
|
||||
lines[handle.position - 1] = lines[handle.position - 1].substr(0, ui.cursorX);
|
||||
ui.startTextEdit(handle);
|
||||
ui.cursorX = ui.highlightAnchor = 0;
|
||||
scrollAlign(ui, handle);
|
||||
}
|
||||
// Delete line
|
||||
if (editable && ui.key == KeyCode.Backspace && cursorStartX == 0 && handle.position > 0) {
|
||||
handle.position--;
|
||||
ui.cursorX = ui.highlightAnchor = lines[handle.position].length;
|
||||
lines[handle.position] += lines[handle.position + 1];
|
||||
lines.splice(handle.position + 1, 1);
|
||||
scrollAlign(ui, handle);
|
||||
}
|
||||
ui.textSelected = lines[handle.position];
|
||||
}
|
||||
|
||||
ui.highlightOnSelect = true;
|
||||
ui.tabSwitchEnabled = true;
|
||||
handle.text = lines.join("\n");
|
||||
return handle.text;
|
||||
}
|
||||
|
||||
static function scrollAlign(ui: Zui, handle: Handle) {
|
||||
// Scroll down
|
||||
if ((handle.position + 1) * ui.ELEMENT_H() + ui.currentWindow.scrollOffset > ui._h - ui.windowHeaderH) {
|
||||
ui.currentWindow.scrollOffset -= ui.ELEMENT_H();
|
||||
}
|
||||
// Scroll up
|
||||
else if ((handle.position + 1) * ui.ELEMENT_H() + ui.currentWindow.scrollOffset < ui.windowHeaderH) {
|
||||
ui.currentWindow.scrollOffset += ui.ELEMENT_H();
|
||||
}
|
||||
}
|
||||
|
||||
static var _ELEMENT_OFFSET = 0;
|
||||
static var _BUTTON_COL = 0;
|
||||
public static function beginMenu(ui: Zui) {
|
||||
_ELEMENT_OFFSET = ui.t.ELEMENT_OFFSET;
|
||||
_BUTTON_COL = ui.t.BUTTON_COL;
|
||||
ui.t.ELEMENT_OFFSET = 0;
|
||||
ui.t.BUTTON_COL = ui.t.SEPARATOR_COL;
|
||||
ui.g.color = ui.t.SEPARATOR_COL;
|
||||
ui.g.fillRect(0, 0, ui._windowW, MENUBAR_H(ui));
|
||||
}
|
||||
|
||||
public static function endMenu(ui: Zui) {
|
||||
ui.t.ELEMENT_OFFSET = _ELEMENT_OFFSET;
|
||||
ui.t.BUTTON_COL = _BUTTON_COL;
|
||||
}
|
||||
|
||||
public static function menuButton(ui: Zui, text: String): Bool {
|
||||
ui._w = Std.int(ui.ops.font.width(ui.fontSize, text) + 25 * ui.SCALE());
|
||||
return ui.button(text);
|
||||
}
|
||||
|
||||
public static inline function MENUBAR_H(ui: Zui): Float {
|
||||
return ui.BUTTON_H() * 1.1 + 2 + ui.buttonOffsetY;
|
||||
}
|
||||
|
||||
static inline function dist(x1: Float, y1: Float, x2: Float, y2: Float): Float {
|
||||
var vx = x1 - x2;
|
||||
var vy = y1 - y2;
|
||||
return Math.sqrt(vx * vx + vy * vy);
|
||||
}
|
||||
static inline function fract(f: Float): Float {
|
||||
return f - Std.int(f);
|
||||
}
|
||||
static inline function mix(x: Float, y: Float, a: Float): Float {
|
||||
return x * (1.0 - a) + y * a;
|
||||
}
|
||||
static inline function clamp(x: Float, minVal: Float, maxVal: Float): Float {
|
||||
return Math.min(Math.max(x, minVal), maxVal);
|
||||
}
|
||||
static inline function step(edge: Float, x: Float): Float {
|
||||
return x < edge ? 0.0 : 1.0;
|
||||
}
|
||||
|
||||
static inline var kx = 1.0;
|
||||
static inline var ky = 2.0 / 3.0;
|
||||
static inline var kz = 1.0 / 3.0;
|
||||
static inline var kw = 3.0;
|
||||
static var ar = [0.0, 0.0, 0.0];
|
||||
static function hsvToRgb(cR: Float, cG: Float, cB: Float, out: Array<Float>) {
|
||||
var px = Math.abs(fract(cR + kx) * 6.0 - kw);
|
||||
var py = Math.abs(fract(cR + ky) * 6.0 - kw);
|
||||
var pz = Math.abs(fract(cR + kz) * 6.0 - kw);
|
||||
out[0] = cB * mix(kx, clamp(px - kx, 0.0, 1.0), cG);
|
||||
out[1] = cB * mix(kx, clamp(py - kx, 0.0, 1.0), cG);
|
||||
out[2] = cB * mix(kx, clamp(pz - kx, 0.0, 1.0), cG);
|
||||
}
|
||||
|
||||
static inline var Kx = 0.0;
|
||||
static inline var Ky = -1.0 / 3.0;
|
||||
static inline var Kz = 2.0 / 3.0;
|
||||
static inline var Kw = -1.0;
|
||||
static inline var e = 1.0e-10;
|
||||
static function rgbToHsv(cR: Float, cG: Float, cB: Float, out: Array<Float>) {
|
||||
var px = mix(cB, cG, step(cB, cG));
|
||||
var py = mix(cG, cB, step(cB, cG));
|
||||
var pz = mix(Kw, Kx, step(cB, cG));
|
||||
var pw = mix(Kz, Ky, step(cB, cG));
|
||||
var qx = mix(px, cR, step(px, cR));
|
||||
var qy = mix(py, py, step(px, cR));
|
||||
var qz = mix(pw, pz, step(px, cR));
|
||||
var qw = mix(cR, px, step(px, cR));
|
||||
var d = qx - Math.min(qw, qy);
|
||||
out[0] = Math.abs(qz + (qw - qy) / (6.0 * d + e));
|
||||
out[1] = d / (qx + e);
|
||||
out[2] = qx;
|
||||
}
|
||||
}
|
363
leenkx/Sources/zui/GraphicsExtension.hx
Normal file
363
leenkx/Sources/zui/GraphicsExtension.hx
Normal file
@ -0,0 +1,363 @@
|
||||
/**
|
||||
Copied from deprecated
|
||||
https://github.com/Kode/Kha/blob/15bfbdc2b3c363fe70ac3d178d9d3439f717861a/Sources/kha/graphics2/GraphicsExtension.hx
|
||||
**/
|
||||
|
||||
package zui;
|
||||
|
||||
import kha.math.Vector2;
|
||||
import kha.math.FastVector2;
|
||||
import kha.graphics2.Graphics;
|
||||
import kha.graphics2.VerTextAlignment;
|
||||
import kha.graphics2.HorTextAlignment;
|
||||
|
||||
/**
|
||||
* Static extension functions for Graphics2.
|
||||
* Usage: "using zui.GraphicsExtension;"
|
||||
*/
|
||||
class GraphicsExtension {
|
||||
/**
|
||||
* Draws a arc.
|
||||
* @param ccw (optional) Specifies whether the drawing should be counterclockwise.
|
||||
* @param segments (optional) The amount of lines that should be used to draw the arc.
|
||||
*/
|
||||
public static function drawArc(g2: Graphics, cx: Float, cy: Float, radius: Float, sAngle: Float, eAngle: Float, strength: Float = 1, ccw: Bool = false,
|
||||
segments: Int = 0): Void {
|
||||
#if kha_html5
|
||||
if (kha.SystemImpl.gl == null) {
|
||||
var g: kha.js.CanvasGraphics = cast g2;
|
||||
radius -= strength / 2; // reduce radius to fit the line thickness within image width/height
|
||||
g.drawArc(cx, cy, radius, sAngle, eAngle, strength, ccw);
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
sAngle = sAngle % (Math.PI * 2);
|
||||
eAngle = eAngle % (Math.PI * 2);
|
||||
|
||||
if (ccw) {
|
||||
if (eAngle > sAngle)
|
||||
eAngle -= Math.PI * 2;
|
||||
}
|
||||
else if (eAngle < sAngle)
|
||||
eAngle += Math.PI * 2;
|
||||
|
||||
radius += strength / 2;
|
||||
if (segments <= 0)
|
||||
segments = Math.floor(10 * Math.sqrt(radius));
|
||||
|
||||
var theta = (eAngle - sAngle) / segments;
|
||||
var c = Math.cos(theta);
|
||||
var s = Math.sin(theta);
|
||||
|
||||
var x = Math.cos(sAngle) * radius;
|
||||
var y = Math.sin(sAngle) * radius;
|
||||
|
||||
for (n in 0...segments) {
|
||||
var px = x + cx;
|
||||
var py = y + cy;
|
||||
|
||||
var t = x;
|
||||
x = c * x - s * y;
|
||||
y = c * y + s * t;
|
||||
|
||||
drawInnerLine(g2, x + cx, y + cy, px, py, strength);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a filled arc.
|
||||
* @param ccw (optional) Specifies whether the drawing should be counterclockwise.
|
||||
* @param segments (optional) The amount of lines that should be used to draw the arc.
|
||||
*/
|
||||
public static function fillArc(g2: Graphics, cx: Float, cy: Float, radius: Float, sAngle: Float, eAngle: Float, ccw: Bool = false,
|
||||
segments: Int = 0): Void {
|
||||
#if kha_html5
|
||||
if (kha.SystemImpl.gl == null) {
|
||||
var g: kha.js.CanvasGraphics = cast g2;
|
||||
g.fillArc(cx, cy, radius, sAngle, eAngle, ccw);
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
sAngle = sAngle % (Math.PI * 2);
|
||||
eAngle = eAngle % (Math.PI * 2);
|
||||
|
||||
if (ccw) {
|
||||
if (eAngle > sAngle)
|
||||
eAngle -= Math.PI * 2;
|
||||
}
|
||||
else if (eAngle < sAngle)
|
||||
eAngle += Math.PI * 2;
|
||||
|
||||
if (segments <= 0)
|
||||
segments = Math.floor(10 * Math.sqrt(radius));
|
||||
|
||||
var theta = (eAngle - sAngle) / segments;
|
||||
var c = Math.cos(theta);
|
||||
var s = Math.sin(theta);
|
||||
|
||||
var x = Math.cos(sAngle) * radius;
|
||||
var y = Math.sin(sAngle) * radius;
|
||||
var sx = x + cx;
|
||||
var sy = y + cy;
|
||||
|
||||
for (n in 0...segments) {
|
||||
var px = x + cx;
|
||||
var py = y + cy;
|
||||
|
||||
var t = x;
|
||||
x = c * x - s * y;
|
||||
y = c * y + s * t;
|
||||
|
||||
g2.fillTriangle(px, py, x + cx, y + cy, sx, sy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a circle.
|
||||
* @param segments (optional) The amount of lines that should be used to draw the circle.
|
||||
*/
|
||||
public static function drawCircle(g2: Graphics, cx: Float, cy: Float, radius: Float, strength: Float = 1, segments: Int = 0): Void {
|
||||
#if kha_html5
|
||||
if (kha.SystemImpl.gl == null) {
|
||||
var g: kha.js.CanvasGraphics = cast g2;
|
||||
radius -= strength / 2; // reduce radius to fit the line thickness within image width/height
|
||||
g.drawCircle(cx, cy, radius, strength);
|
||||
return;
|
||||
}
|
||||
#end
|
||||
radius += strength / 2;
|
||||
|
||||
if (segments <= 0)
|
||||
segments = Math.floor(10 * Math.sqrt(radius));
|
||||
|
||||
var theta = 2 * Math.PI / segments;
|
||||
var c = Math.cos(theta);
|
||||
var s = Math.sin(theta);
|
||||
|
||||
var x = radius;
|
||||
var y = 0.0;
|
||||
|
||||
for (n in 0...segments) {
|
||||
var px = x + cx;
|
||||
var py = y + cy;
|
||||
|
||||
var t = x;
|
||||
x = c * x - s * y;
|
||||
y = c * y + s * t;
|
||||
drawInnerLine(g2, x + cx, y + cy, px, py, strength);
|
||||
}
|
||||
}
|
||||
|
||||
static function drawInnerLine(g2: Graphics, x1: Float, y1: Float, x2: Float, y2: Float, strength: Float): Void {
|
||||
var side = y2 > y1 ? 1 : 0;
|
||||
if (y2 == y1)
|
||||
side = x2 - x1 > 0 ? 1 : 0;
|
||||
|
||||
var vec = new FastVector2();
|
||||
if (y2 == y1)
|
||||
vec.setFrom(new FastVector2(0, -1));
|
||||
else
|
||||
vec.setFrom(new FastVector2(1, -(x2 - x1) / (y2 - y1)));
|
||||
vec.length = strength;
|
||||
var p1 = new FastVector2(x1 + side * vec.x, y1 + side * vec.y);
|
||||
var p2 = new FastVector2(x2 + side * vec.x, y2 + side * vec.y);
|
||||
var p3 = p1.sub(vec);
|
||||
var p4 = p2.sub(vec);
|
||||
g2.fillTriangle(p1.x, p1.y, p2.x, p2.y, p3.x, p3.y);
|
||||
g2.fillTriangle(p3.x, p3.y, p2.x, p2.y, p4.x, p4.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a filled circle.
|
||||
* @param segments (optional) The amount of lines that should be used to draw the circle.
|
||||
*/
|
||||
public static function fillCircle(g2: Graphics, cx: Float, cy: Float, radius: Float, segments: Int = 0): Void {
|
||||
#if kha_html5
|
||||
if (kha.SystemImpl.gl == null) {
|
||||
var g: kha.js.CanvasGraphics = cast g2;
|
||||
g.fillCircle(cx, cy, radius);
|
||||
return;
|
||||
}
|
||||
#end
|
||||
|
||||
if (segments <= 0) {
|
||||
segments = Math.floor(10 * Math.sqrt(radius));
|
||||
}
|
||||
|
||||
var theta = 2 * Math.PI / segments;
|
||||
var c = Math.cos(theta);
|
||||
var s = Math.sin(theta);
|
||||
|
||||
var x = radius;
|
||||
var y = 0.0;
|
||||
|
||||
for (n in 0...segments) {
|
||||
var px = x + cx;
|
||||
var py = y + cy;
|
||||
|
||||
var t = x;
|
||||
x = c * x - s * y;
|
||||
y = c * y + s * t;
|
||||
|
||||
g2.fillTriangle(px, py, x + cx, y + cy, cx, cy);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a convex polygon.
|
||||
*/
|
||||
public static function drawPolygon(g2: Graphics, x: Float, y: Float, vertices: Array<Vector2>, strength: Float = 1) {
|
||||
var iterator = vertices.iterator();
|
||||
var v0 = iterator.next();
|
||||
var v1 = v0;
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
var v2 = iterator.next();
|
||||
g2.drawLine(v1.x + x, v1.y + y, v2.x + x, v2.y + y, strength);
|
||||
v1 = v2;
|
||||
}
|
||||
g2.drawLine(v1.x + x, v1.y + y, v0.x + x, v0.y + y, strength);
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a filled convex polygon.
|
||||
*/
|
||||
public static function fillPolygon(g2: Graphics, x: Float, y: Float, vertices: Array<Vector2>) {
|
||||
var iterator = vertices.iterator();
|
||||
|
||||
if (!iterator.hasNext())
|
||||
return;
|
||||
var v0 = iterator.next();
|
||||
|
||||
if (!iterator.hasNext())
|
||||
return;
|
||||
var v1 = iterator.next();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
var v2 = iterator.next();
|
||||
g2.fillTriangle(v0.x + x, v0.y + y, v1.x + x, v1.y + y, v2.x + x, v2.y + y);
|
||||
v1 = v2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a cubic bezier using 4 pairs of points. If the x and y arrays have a length bigger then 4, the additional
|
||||
* points will be ignored. With a length smaller of 4 a error will occur, there is no check for this.
|
||||
* You can construct the curves visually in Inkscape with a path using default nodes.
|
||||
* Provide x and y in the following order: startPoint, controlPoint1, controlPoint2, endPoint
|
||||
* Reference: http://devmag.org.za/2011/04/05/bzier-curves-a-tutorial/
|
||||
*/
|
||||
public static function drawCubicBezier(g2: Graphics, x: Array<Float>, y: Array<Float>, segments: Int = 20, strength: Float = 1.0): Void {
|
||||
var t: Float;
|
||||
|
||||
var q0 = calculateCubicBezierPoint(0, x, y);
|
||||
var q1: Array<Float>;
|
||||
|
||||
for (i in 1...(segments + 1)) {
|
||||
t = i / segments;
|
||||
q1 = calculateCubicBezierPoint(t, x, y);
|
||||
g2.drawLine(q0[0], q0[1], q1[0], q1[1], strength);
|
||||
q0 = q1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws multiple cubic beziers joined by the end point. The minimum size is 4 pairs of points (a single curve).
|
||||
*/
|
||||
public static function drawCubicBezierPath(g2: Graphics, x: Array<Float>, y: Array<Float>, segments: Int = 20, strength: Float = 1.0): Void {
|
||||
var i = 0;
|
||||
var t: Float;
|
||||
var q0: Array<Float> = null;
|
||||
var q1: Array<Float> = null;
|
||||
|
||||
while (i < x.length - 3) {
|
||||
if (i == 0)
|
||||
q0 = calculateCubicBezierPoint(0, [x[i], x[i + 1], x[i + 2], x[i + 3]], [y[i], y[i + 1], y[i + 2], y[i + 3]]);
|
||||
|
||||
for (j in 1...(segments + 1)) {
|
||||
t = j / segments;
|
||||
q1 = calculateCubicBezierPoint(t, [x[i], x[i + 1], x[i + 2], x[i + 3]], [y[i], y[i + 1], y[i + 2], y[i + 3]]);
|
||||
g2.drawLine(q0[0], q0[1], q1[0], q1[1], strength);
|
||||
q0 = q1;
|
||||
}
|
||||
|
||||
i += 3;
|
||||
}
|
||||
}
|
||||
|
||||
static function calculateCubicBezierPoint(t: Float, x: Array<Float>, y: Array<Float>): Array<Float> {
|
||||
var u: Float = 1 - t;
|
||||
var tt: Float = t * t;
|
||||
var uu: Float = u * u;
|
||||
var uuu: Float = uu * u;
|
||||
var ttt: Float = tt * t;
|
||||
|
||||
// first term
|
||||
var p: Array<Float> = [uuu * x[0], uuu * y[0]];
|
||||
|
||||
// second term
|
||||
p[0] += 3 * uu * t * x[1];
|
||||
p[1] += 3 * uu * t * y[1];
|
||||
|
||||
// third term
|
||||
p[0] += 3 * u * tt * x[2];
|
||||
p[1] += 3 * u * tt * y[2];
|
||||
|
||||
// fourth term
|
||||
p[0] += ttt * x[3];
|
||||
p[1] += ttt * y[3];
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
static public function drawAlignedString(g2: Graphics, text: String, x: Float, y: Float, horAlign: HorTextAlignment, verAlign: VerTextAlignment): Void {
|
||||
var xoffset = 0.0;
|
||||
if (horAlign == TextCenter || horAlign == TextRight) {
|
||||
var width = g2.font.width(g2.fontSize, text);
|
||||
if (horAlign == TextCenter) {
|
||||
xoffset = -width * 0.5;
|
||||
}
|
||||
else {
|
||||
xoffset = -width;
|
||||
}
|
||||
}
|
||||
var yoffset = 0.0;
|
||||
if (verAlign == TextMiddle || verAlign == TextBottom) {
|
||||
var height = g2.font.height(g2.fontSize);
|
||||
if (verAlign == TextMiddle) {
|
||||
yoffset = -height * 0.5;
|
||||
}
|
||||
else {
|
||||
yoffset = -height;
|
||||
}
|
||||
}
|
||||
g2.drawString(text, x + xoffset, y + yoffset);
|
||||
}
|
||||
|
||||
static public function drawAlignedCharacters(g2: Graphics, text: Array<Int>, start: Int, length: Int, x: Float, y: Float, horAlign: HorTextAlignment,
|
||||
verAlign: VerTextAlignment): Void {
|
||||
var xoffset = 0.0;
|
||||
if (horAlign == TextCenter || horAlign == TextRight) {
|
||||
var width = g2.font.widthOfCharacters(g2.fontSize, text, start, length);
|
||||
if (horAlign == TextCenter) {
|
||||
xoffset = -width * 0.5;
|
||||
}
|
||||
else {
|
||||
xoffset = -width;
|
||||
}
|
||||
}
|
||||
var yoffset = 0.0;
|
||||
if (verAlign == TextMiddle || verAlign == TextBottom) {
|
||||
var height = g2.font.height(g2.fontSize);
|
||||
if (verAlign == TextMiddle) {
|
||||
yoffset = -height * 0.5;
|
||||
}
|
||||
else {
|
||||
yoffset = -height;
|
||||
}
|
||||
}
|
||||
g2.drawCharacters(text, start, length, x + xoffset, y + yoffset);
|
||||
}
|
||||
}
|
19
leenkx/Sources/zui/Id.hx
Normal file
19
leenkx/Sources/zui/Id.hx
Normal file
@ -0,0 +1,19 @@
|
||||
package zui;
|
||||
|
||||
import haxe.macro.Context;
|
||||
import haxe.macro.Expr;
|
||||
import haxe.macro.ExprTools;
|
||||
|
||||
class Id {
|
||||
|
||||
static var i = 0;
|
||||
|
||||
macro public static function pos(): Expr {
|
||||
return macro $v{i++};
|
||||
}
|
||||
|
||||
macro public static function handle(ops: Expr = null): Expr {
|
||||
var code = "zui.Zui.Handle.global.nest(zui.Id.pos()," + ExprTools.toString(ops) + ")";
|
||||
return Context.parse(code, Context.currentPos());
|
||||
}
|
||||
}
|
971
leenkx/Sources/zui/Nodes.hx
Normal file
971
leenkx/Sources/zui/Nodes.hx
Normal file
@ -0,0 +1,971 @@
|
||||
package zui;
|
||||
|
||||
using zui.GraphicsExtension;
|
||||
|
||||
@:access(zui.Zui)
|
||||
class Nodes {
|
||||
|
||||
public var nodesDrag = false;
|
||||
public var nodesSelected: Array<TNode> = [];
|
||||
public var panX = 0.0;
|
||||
public var panY = 0.0;
|
||||
public var zoom = 1.0;
|
||||
public var uiw = 0;
|
||||
public var uih = 0;
|
||||
public var _inputStarted = false;
|
||||
public var colorPickerCallback: kha.Color->Void = null;
|
||||
var scaleFactor = 1.0;
|
||||
var ELEMENT_H = 25;
|
||||
var dragged = false;
|
||||
var moveOnTop: TNode = null;
|
||||
var linkDrag: TNodeLink = null;
|
||||
var isNewLink = false;
|
||||
var snapFromId = -1;
|
||||
var snapToId = -1;
|
||||
var snapSocket = 0;
|
||||
var snapX = 0.0;
|
||||
var snapY = 0.0;
|
||||
var handle = new Zui.Handle();
|
||||
static var elementsBaked = false;
|
||||
static var socketImage: kha.Image = null;
|
||||
static var socketReleased = false;
|
||||
static var clipboard = "";
|
||||
static var boxSelect = false;
|
||||
static var boxSelectX = 0;
|
||||
static var boxSelectY = 0;
|
||||
static inline var maxButtons = 9;
|
||||
|
||||
public static var excludeRemove: Array<String> = []; // No removal for listed node types
|
||||
public static var onLinkDrag: TNodeLink->Bool->Void = null;
|
||||
public static var onHeaderReleased: TNode->Void = null;
|
||||
public static var onSocketReleased: TNodeSocket->Void = null;
|
||||
public static var onCanvasReleased: Void->Void = null;
|
||||
public static var onNodeRemove: TNode->Void = null;
|
||||
public static var onCanvasControl: Void->CanvasControl = null; // Pan, zoom
|
||||
|
||||
#if zui_translate
|
||||
public static dynamic function tr(id: String, vars: Map<String, String> = null) {
|
||||
return id;
|
||||
}
|
||||
#else
|
||||
public static inline function tr(id: String, vars: Map<String, String> = null) {
|
||||
return id;
|
||||
}
|
||||
#end
|
||||
|
||||
public function new() {}
|
||||
|
||||
public inline function SCALE(): Float {
|
||||
return scaleFactor * zoom;
|
||||
}
|
||||
public inline function PAN_X(): Float {
|
||||
var zoomPan = (1.0 - zoom) * uiw / 2.5;
|
||||
return panX * SCALE() + zoomPan;
|
||||
}
|
||||
public inline function PAN_Y(): Float {
|
||||
var zoomPan = (1.0 - zoom) * uih / 2.5;
|
||||
return panY * SCALE() + zoomPan;
|
||||
}
|
||||
public inline function LINE_H(): Int {
|
||||
return Std.int(ELEMENT_H * SCALE());
|
||||
}
|
||||
function BUTTONS_H(node: TNode): Int {
|
||||
var h = 0.0;
|
||||
for (but in node.buttons) {
|
||||
if (but.type == "RGBA") h += 102 * SCALE() + LINE_H() * 5; // Color wheel + controls
|
||||
else if (but.type == "VECTOR") h += LINE_H() * 4;
|
||||
else if (but.type == "CUSTOM") h += LINE_H() * but.height;
|
||||
else h += LINE_H();
|
||||
}
|
||||
return Std.int(h);
|
||||
}
|
||||
function OUTPUTS_H(sockets: Array<TNodeSocket>, length: Null<Int> = null): Int {
|
||||
var h = 0.0;
|
||||
for (i in 0...(length == null ? sockets.length : length)) {
|
||||
h += LINE_H();
|
||||
}
|
||||
return Std.int(h);
|
||||
}
|
||||
function INPUTS_H(canvas: TNodeCanvas, sockets: Array<TNodeSocket>, length: Null<Int> = null): Int {
|
||||
var h = 0.0;
|
||||
for (i in 0...(length == null ? sockets.length : length)) {
|
||||
if (sockets[i].type == "VECTOR" && sockets[i].display == 1 && !inputLinked(canvas, sockets[i].node_id, i)) h += LINE_H() * 4;
|
||||
else h += LINE_H();
|
||||
}
|
||||
return Std.int(h);
|
||||
}
|
||||
inline function NODE_H(canvas: TNodeCanvas, node: TNode): Int {
|
||||
return Std.int(LINE_H() * 1.2 + INPUTS_H(canvas, node.inputs) + OUTPUTS_H(node.outputs) + BUTTONS_H(node));
|
||||
}
|
||||
inline function NODE_W(node: TNode): Int {
|
||||
return Std.int((node.width != null ? node.width : 140) * SCALE());
|
||||
}
|
||||
inline function NODE_X(node: TNode): Float {
|
||||
return node.x * SCALE() + PAN_X();
|
||||
}
|
||||
inline function NODE_Y(node: TNode): Float {
|
||||
return node.y * SCALE() + PAN_Y();
|
||||
}
|
||||
inline function INPUT_Y(canvas: TNodeCanvas, sockets: Array<TNodeSocket>, pos: Int): Int {
|
||||
return Std.int(LINE_H() * 1.62) + INPUTS_H(canvas, sockets, pos);
|
||||
}
|
||||
inline function OUTPUT_Y(sockets: Array<TNodeSocket>, pos: Int): Int {
|
||||
return Std.int(LINE_H() * 1.62) + OUTPUTS_H(sockets, pos);
|
||||
}
|
||||
public inline function p(f: Float): Float {
|
||||
return f * SCALE();
|
||||
}
|
||||
|
||||
public function getNode(nodes: Array<TNode>, id: Int): TNode {
|
||||
for (node in nodes) if (node.id == id) return node;
|
||||
return null;
|
||||
}
|
||||
|
||||
var nodeId = -1;
|
||||
public function getNodeId(nodes: Array<TNode>): Int {
|
||||
if (nodeId == -1) for (n in nodes) if (nodeId < n.id) nodeId = n.id;
|
||||
return ++nodeId;
|
||||
}
|
||||
|
||||
public function getLinkId(links: Array<TNodeLink>): Int {
|
||||
var id = 0;
|
||||
for (l in links) if (l.id >= id) id = l.id + 1;
|
||||
return id;
|
||||
}
|
||||
|
||||
public function getSocketId(nodes: Array<TNode>): Int {
|
||||
var id = 0;
|
||||
for (n in nodes) {
|
||||
for (s in n.inputs) if (s.id >= id) id = s.id + 1;
|
||||
for (s in n.outputs) if (s.id >= id) id = s.id + 1;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
function inputLinked(canvas: TNodeCanvas, node_id: Int, i: Int): Bool {
|
||||
for (l in canvas.links) if (l.to_id == node_id && l.to_socket == i) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function bakeElements(ui: Zui) {
|
||||
ui.g.end();
|
||||
elementsBaked = true;
|
||||
socketImage = kha.Image.createRenderTarget(24, 24);
|
||||
var g = socketImage.g2;
|
||||
g.begin(true, 0x00000000);
|
||||
g.color = 0xff000000;
|
||||
zui.GraphicsExtension.fillCircle(g, 12, 12, 12);
|
||||
g.color = 0xffffffff;
|
||||
zui.GraphicsExtension.fillCircle(g, 12, 12, 9);
|
||||
g.end();
|
||||
ui.g.begin(false);
|
||||
}
|
||||
|
||||
public function nodeCanvas(ui: Zui, canvas: TNodeCanvas) {
|
||||
if (!elementsBaked) bakeElements(ui);
|
||||
|
||||
var wx = ui._windowX;
|
||||
var wy = ui._windowY;
|
||||
var _inputEnabled = ui.inputEnabled;
|
||||
ui.inputEnabled = _inputEnabled && popupCommands == null;
|
||||
var controls = onCanvasControl != null ? onCanvasControl() : {
|
||||
panX: ui.inputDownR ? ui.inputDX : 0.0,
|
||||
panY: ui.inputDownR ? ui.inputDY : 0.0,
|
||||
zoom: -ui.inputWheelDelta / 10.0
|
||||
};
|
||||
socketReleased = false;
|
||||
|
||||
// Pan canvas
|
||||
if (ui.inputEnabled && (controls.panX != 0.0 || controls.panY != 0.0)) {
|
||||
panX += controls.panX / SCALE();
|
||||
panY += controls.panY / SCALE();
|
||||
}
|
||||
|
||||
// Zoom canvas
|
||||
if (ui.inputEnabled && controls.zoom != 0.0) {
|
||||
zoom += controls.zoom;
|
||||
if (zoom < 0.1) zoom = 0.1;
|
||||
else if (zoom > 1.0) zoom = 1.0;
|
||||
zoom = Math.round(zoom * 10) / 10;
|
||||
uiw = ui._w;
|
||||
uih = ui._h;
|
||||
}
|
||||
scaleFactor = ui.SCALE();
|
||||
ELEMENT_H = ui.t.ELEMENT_H + 2;
|
||||
ui.setScale(SCALE()); // Apply zoomed scale
|
||||
ui.elementsBaked = true;
|
||||
ui.g.font = ui.ops.font;
|
||||
ui.g.fontSize = ui.fontSize;
|
||||
|
||||
for (link in canvas.links) {
|
||||
var from = getNode(canvas.nodes, link.from_id);
|
||||
var to = getNode(canvas.nodes, link.to_id);
|
||||
var fromX = from == null ? ui.inputX : wx + NODE_X(from) + NODE_W(from);
|
||||
var fromY = from == null ? ui.inputY : wy + NODE_Y(from) + OUTPUT_Y(from.outputs, link.from_socket);
|
||||
var toX = to == null ? ui.inputX : wx + NODE_X(to);
|
||||
var toY = to == null ? ui.inputY : wy + NODE_Y(to) + INPUT_Y(canvas, to.inputs, link.to_socket) + OUTPUTS_H(to.outputs) + BUTTONS_H(to);
|
||||
|
||||
// Cull
|
||||
var left = toX > fromX ? fromX : toX;
|
||||
var right = toX > fromX ? toX : fromX;
|
||||
var top = toY > fromY ? fromY : toY;
|
||||
var bottom = toY > fromY ? toY : fromY;
|
||||
if (right < 0 || left > wx + ui._windowW ||
|
||||
bottom < 0 || top > wy + ui._windowH) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Snap to nearest socket
|
||||
if (linkDrag == link) {
|
||||
if (snapFromId != -1) {
|
||||
fromX = snapX;
|
||||
fromY = snapY;
|
||||
}
|
||||
if (snapToId != -1) {
|
||||
toX = snapX;
|
||||
toY = snapY;
|
||||
}
|
||||
snapFromId = snapToId = -1;
|
||||
|
||||
for (node in canvas.nodes) {
|
||||
var inps = node.inputs;
|
||||
var outs = node.outputs;
|
||||
var nodeh = NODE_H(canvas, node);
|
||||
var rx = wx + NODE_X(node) - LINE_H() / 2;
|
||||
var ry = wy + NODE_Y(node) - LINE_H() / 2;
|
||||
var rw = NODE_W(node) + LINE_H();
|
||||
var rh = nodeh + LINE_H();
|
||||
if (ui.getInputInRect(rx, ry, rw, rh)) {
|
||||
if (from == null && node.id != to.id) { // Snap to output
|
||||
for (i in 0...outs.length) {
|
||||
var sx = wx + NODE_X(node) + NODE_W(node);
|
||||
var sy = wy + NODE_Y(node) + OUTPUT_Y(outs, i);
|
||||
var rx = sx - LINE_H() / 2;
|
||||
var ry = sy - LINE_H() / 2;
|
||||
if (ui.getInputInRect(rx, ry, LINE_H(), LINE_H())) {
|
||||
snapX = sx;
|
||||
snapY = sy;
|
||||
snapFromId = node.id;
|
||||
snapSocket = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (to == null && node.id != from.id) { // Snap to input
|
||||
for (i in 0...inps.length) {
|
||||
var sx = wx + NODE_X(node);
|
||||
var sy = wy + NODE_Y(node) + INPUT_Y(canvas, inps, i) + OUTPUTS_H(outs) + BUTTONS_H(node);
|
||||
var rx = sx - LINE_H() / 2;
|
||||
var ry = sy - LINE_H() / 2;
|
||||
if (ui.getInputInRect(rx, ry, LINE_H(), LINE_H())) {
|
||||
snapX = sx;
|
||||
snapY = sy;
|
||||
snapToId = node.id;
|
||||
snapSocket = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var selected = false;
|
||||
for (n in nodesSelected) {
|
||||
if (link.from_id == n.id || link.to_id == n.id) {
|
||||
selected = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
drawLink(ui, fromX - wx, fromY - wy, toX - wx, toY - wy, selected);
|
||||
}
|
||||
|
||||
for (node in canvas.nodes) {
|
||||
// Cull
|
||||
if (NODE_X(node) > ui._windowW || NODE_X(node) + NODE_W(node) < 0 ||
|
||||
NODE_Y(node) > ui._windowH || NODE_Y(node) + NODE_H(canvas, node) < 0) {
|
||||
if (!isSelected(node)) continue;
|
||||
}
|
||||
|
||||
var inps = node.inputs;
|
||||
var outs = node.outputs;
|
||||
|
||||
// Drag node
|
||||
var nodeh = NODE_H(canvas, node);
|
||||
if (ui.inputEnabled && ui.getInputInRect(wx + NODE_X(node) - LINE_H() / 2, wy + NODE_Y(node), NODE_W(node) + LINE_H(), LINE_H())) {
|
||||
if (ui.inputStarted) {
|
||||
if (ui.isShiftDown || ui.isCtrlDown) {
|
||||
// Add to selection or deselect
|
||||
isSelected(node) ?
|
||||
nodesSelected.remove(node) :
|
||||
nodesSelected.push(node);
|
||||
}
|
||||
else if (nodesSelected.length <= 1) {
|
||||
// Selecting single node, otherwise wait for input release
|
||||
nodesSelected = [node];
|
||||
}
|
||||
moveOnTop = node; // Place selected node on top
|
||||
nodesDrag = true;
|
||||
dragged = false;
|
||||
}
|
||||
else if (ui.inputReleased && !ui.isShiftDown && !ui.isCtrlDown && !dragged) {
|
||||
// No drag performed, select single node
|
||||
nodesSelected = [node];
|
||||
if (onHeaderReleased != null) {
|
||||
onHeaderReleased(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ui.inputStarted && ui.getInputInRect(wx + NODE_X(node) - LINE_H() / 2, wy + NODE_Y(node) - LINE_H() / 2, NODE_W(node) + LINE_H(), nodeh + LINE_H())) {
|
||||
// Check sockets
|
||||
if (linkDrag == null) {
|
||||
for (i in 0...outs.length) {
|
||||
var sx = wx + NODE_X(node) + NODE_W(node);
|
||||
var sy = wy + NODE_Y(node) + OUTPUT_Y(outs, i);
|
||||
if (ui.getInputInRect(sx - LINE_H() / 2, sy - LINE_H() / 2, LINE_H(), LINE_H())) {
|
||||
// New link from output
|
||||
var l: TNodeLink = { id: getLinkId(canvas.links), from_id: node.id, from_socket: i, to_id: -1, to_socket: -1 };
|
||||
canvas.links.push(l);
|
||||
linkDrag = l;
|
||||
isNewLink = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (linkDrag == null) {
|
||||
for (i in 0...inps.length) {
|
||||
var sx = wx + NODE_X(node);
|
||||
var sy = wy + NODE_Y(node) + INPUT_Y(canvas, inps, i) + OUTPUTS_H(outs) + BUTTONS_H(node);
|
||||
if (ui.getInputInRect(sx - LINE_H() / 2, sy - LINE_H() / 2, LINE_H(), LINE_H())) {
|
||||
// Already has a link - disconnect
|
||||
for (l in canvas.links) {
|
||||
if (l.to_id == node.id && l.to_socket == i) {
|
||||
l.to_id = l.to_socket = -1;
|
||||
linkDrag = l;
|
||||
isNewLink = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (linkDrag != null) break;
|
||||
// New link from input
|
||||
var l: TNodeLink = {
|
||||
id: getLinkId(canvas.links),
|
||||
from_id: -1,
|
||||
from_socket: -1,
|
||||
to_id: node.id,
|
||||
to_socket: i
|
||||
};
|
||||
canvas.links.push(l);
|
||||
linkDrag = l;
|
||||
isNewLink = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (ui.inputReleased) {
|
||||
if (snapToId != -1) { // Connect to input
|
||||
// Force single link per input
|
||||
for (l in canvas.links) {
|
||||
if (l.to_id == snapToId && l.to_socket == snapSocket) {
|
||||
canvas.links.remove(l);
|
||||
break;
|
||||
}
|
||||
}
|
||||
linkDrag.to_id = snapToId;
|
||||
linkDrag.to_socket = snapSocket;
|
||||
ui.changed = true;
|
||||
}
|
||||
else if (snapFromId != -1) { // Connect to output
|
||||
linkDrag.from_id = snapFromId;
|
||||
linkDrag.from_socket = snapSocket;
|
||||
ui.changed = true;
|
||||
}
|
||||
else if (linkDrag != null) { // Remove dragged link
|
||||
canvas.links.remove(linkDrag);
|
||||
ui.changed = true;
|
||||
if (onLinkDrag != null) {
|
||||
onLinkDrag(linkDrag, isNewLink);
|
||||
}
|
||||
}
|
||||
snapToId = snapFromId = -1;
|
||||
linkDrag = null;
|
||||
nodesDrag = false;
|
||||
}
|
||||
if (nodesDrag && isSelected(node) && !ui.inputDownR) {
|
||||
if (ui.inputDX != 0 || ui.inputDY != 0) {
|
||||
dragged = true;
|
||||
node.x += Std.int(ui.inputDX / SCALE());
|
||||
node.y += Std.int(ui.inputDY / SCALE());
|
||||
}
|
||||
}
|
||||
|
||||
drawNode(ui, node, canvas);
|
||||
}
|
||||
|
||||
if (onCanvasReleased != null && ui.inputEnabled && (ui.inputReleased || ui.inputReleasedR) && !socketReleased) {
|
||||
onCanvasReleased();
|
||||
}
|
||||
|
||||
if (boxSelect) {
|
||||
ui.g.color = 0x223333dd;
|
||||
ui.g.fillRect(boxSelectX, boxSelectY, ui.inputX - boxSelectX - ui._windowX, ui.inputY - boxSelectY - ui._windowY);
|
||||
ui.g.color = 0x773333dd;
|
||||
ui.g.drawRect(boxSelectX, boxSelectY, ui.inputX - boxSelectX - ui._windowX, ui.inputY - boxSelectY - ui._windowY);
|
||||
ui.g.color = 0xffffffff;
|
||||
}
|
||||
if (ui.inputEnabled && ui.inputStarted && !ui.isAltDown && linkDrag == null && !nodesDrag && !ui.changed) {
|
||||
boxSelect = true;
|
||||
boxSelectX = Std.int(ui.inputX - ui._windowX);
|
||||
boxSelectY = Std.int(ui.inputY - ui._windowY);
|
||||
}
|
||||
else if (boxSelect && !ui.inputDown) {
|
||||
boxSelect = false;
|
||||
var nodes: Array<TNode> = [];
|
||||
var left = boxSelectX;
|
||||
var top = boxSelectY;
|
||||
var right = Std.int(ui.inputX - ui._windowX);
|
||||
var bottom = Std.int(ui.inputY - ui._windowY);
|
||||
if (left > right) {
|
||||
var t = left;
|
||||
left = right;
|
||||
right = t;
|
||||
}
|
||||
if (top > bottom) {
|
||||
var t = top;
|
||||
top = bottom;
|
||||
bottom = t;
|
||||
}
|
||||
for (n in canvas.nodes) {
|
||||
if (NODE_X(n) + NODE_W(n) > left && NODE_X(n) < right &&
|
||||
NODE_Y(n) + NODE_H(canvas, n) > top && NODE_Y(n) < bottom) {
|
||||
nodes.push(n);
|
||||
}
|
||||
}
|
||||
(ui.isShiftDown || ui.isCtrlDown) ? for (n in nodes) nodesSelected.push(n) : nodesSelected = nodes;
|
||||
}
|
||||
|
||||
// Place selected node on top
|
||||
if (moveOnTop != null) {
|
||||
canvas.nodes.remove(moveOnTop);
|
||||
canvas.nodes.push(moveOnTop);
|
||||
moveOnTop = null;
|
||||
}
|
||||
|
||||
// Node copy & paste
|
||||
var cutSelected = false;
|
||||
if (Zui.isCopy) {
|
||||
var copyNodes: Array<TNode> = [];
|
||||
for (n in nodesSelected) {
|
||||
if (excludeRemove.indexOf(n.type) >= 0) continue;
|
||||
copyNodes.push(n);
|
||||
}
|
||||
var copyLinks: Array<TNodeLink> = [];
|
||||
for (l in canvas.links) {
|
||||
var from = getNode(nodesSelected, l.from_id);
|
||||
var to = getNode(nodesSelected, l.to_id);
|
||||
if (from != null && excludeRemove.indexOf(from.type) == -1 &&
|
||||
to != null && excludeRemove.indexOf(to.type) == -1) {
|
||||
copyLinks.push(l);
|
||||
}
|
||||
}
|
||||
if (copyNodes.length > 0) {
|
||||
var copyCanvas: TNodeCanvas = {
|
||||
name: canvas.name,
|
||||
nodes: copyNodes,
|
||||
links: copyLinks
|
||||
};
|
||||
clipboard = haxe.Json.stringify(copyCanvas);
|
||||
}
|
||||
cutSelected = Zui.isCut;
|
||||
}
|
||||
if (Zui.isPaste && !ui.isTyping) {
|
||||
var pasteCanvas: TNodeCanvas = null;
|
||||
// Clipboard can contain non-json data
|
||||
try {
|
||||
pasteCanvas = haxe.Json.parse(clipboard);
|
||||
}
|
||||
catch(_) {}
|
||||
if (pasteCanvas != null) {
|
||||
for (l in pasteCanvas.links) {
|
||||
// Assign unique link id
|
||||
l.id = getLinkId(canvas.links);
|
||||
canvas.links.push(l);
|
||||
}
|
||||
var offsetX = Std.int((Std.int(ui.inputX / ui.SCALE()) * SCALE() - wx - PAN_X()) / SCALE()) - pasteCanvas.nodes[pasteCanvas.nodes.length - 1].x;
|
||||
var offsetY = Std.int((Std.int(ui.inputY / ui.SCALE()) * SCALE() - wy - PAN_Y()) / SCALE()) - pasteCanvas.nodes[pasteCanvas.nodes.length - 1].y;
|
||||
for (n in pasteCanvas.nodes) {
|
||||
// Assign unique node id
|
||||
var old_id = n.id;
|
||||
n.id = getNodeId(canvas.nodes);
|
||||
for (soc in n.inputs) {
|
||||
soc.id = getSocketId(canvas.nodes);
|
||||
soc.node_id = n.id;
|
||||
}
|
||||
for (soc in n.outputs) {
|
||||
soc.id = getSocketId(canvas.nodes);
|
||||
soc.node_id = n.id;
|
||||
}
|
||||
for (l in pasteCanvas.links) {
|
||||
if (l.from_id == old_id) l.from_id = n.id;
|
||||
else if (l.to_id == old_id) l.to_id = n.id;
|
||||
}
|
||||
n.x += offsetX;
|
||||
n.y += offsetY;
|
||||
canvas.nodes.push(n);
|
||||
}
|
||||
nodesDrag = true;
|
||||
nodesSelected = pasteCanvas.nodes;
|
||||
ui.changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Select all nodes
|
||||
if (ui.isCtrlDown && ui.key == kha.input.KeyCode.A && !ui.isTyping) {
|
||||
nodesSelected = [];
|
||||
for (n in canvas.nodes) nodesSelected.push(n);
|
||||
}
|
||||
|
||||
// Node removal
|
||||
if (ui.inputEnabled && (ui.isBackspaceDown || ui.isDeleteDown || cutSelected) && !ui.isTyping) {
|
||||
var i = nodesSelected.length - 1;
|
||||
while (i >= 0) {
|
||||
var n = nodesSelected[i--];
|
||||
if (excludeRemove.indexOf(n.type) >= 0) continue;
|
||||
removeNode(n, canvas);
|
||||
ui.changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
ui.setScale(scaleFactor); // Restore non-zoomed scale
|
||||
ui.elementsBaked = true;
|
||||
ui.inputEnabled = _inputEnabled;
|
||||
|
||||
if (popupCommands != null) {
|
||||
ui._x = popupX;
|
||||
ui._y = popupY;
|
||||
ui._w = popupW;
|
||||
|
||||
ui.fill(-6, -6, ui._w / ui.SCALE() + 12, popupH + 12, ui.t.ACCENT_SELECT_COL);
|
||||
ui.fill(-5, -5, ui._w / ui.SCALE() + 10, popupH + 10, ui.t.SEPARATOR_COL);
|
||||
popupCommands(ui);
|
||||
|
||||
var hide = (ui.inputStarted || ui.inputStartedR) && (ui.inputX - wx < popupX - 6 || ui.inputX - wx > popupX + popupW + 6 || ui.inputY - wy < popupY - 6 || ui.inputY - wy > popupY + popupH * ui.SCALE() + 6);
|
||||
if (hide || ui.isEscapeDown) {
|
||||
popupCommands = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve combo items for buttons of type ENUM
|
||||
public static var enumTexts: String->Array<String> = null;
|
||||
|
||||
inline function isSelected(node: TNode): Bool {
|
||||
return nodesSelected.indexOf(node) >= 0;
|
||||
}
|
||||
|
||||
public function drawNode(ui: Zui, node: TNode, canvas: TNodeCanvas) {
|
||||
var wx = ui._windowX;
|
||||
var wy = ui._windowY;
|
||||
var uiX = ui._x;
|
||||
var uiY = ui._y;
|
||||
var uiW = ui._w;
|
||||
var w = NODE_W(node);
|
||||
var g = ui.g;
|
||||
var h = NODE_H(canvas, node);
|
||||
var nx = NODE_X(node);
|
||||
var ny = NODE_Y(node);
|
||||
var text = tr(node.name);
|
||||
var lineh = LINE_H();
|
||||
|
||||
// Disallow input if node is overlapped by another node
|
||||
_inputStarted = ui.inputStarted;
|
||||
if (ui.inputStarted) {
|
||||
for (i in (canvas.nodes.indexOf(node) + 1)...canvas.nodes.length) {
|
||||
var n = canvas.nodes[i];
|
||||
if (NODE_X(n) < ui.inputX - ui._windowX && NODE_X(n) + NODE_W(n) > ui.inputX - ui._windowX &&
|
||||
NODE_Y(n) < ui.inputY - ui._windowY && NODE_Y(n) + NODE_H(canvas, n) > ui.inputY - ui._windowY) {
|
||||
ui.inputStarted = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Outline
|
||||
g.color = isSelected(node) ? ui.t.LABEL_COL : ui.t.CONTEXT_COL;
|
||||
g.fillRect(nx - 1, ny - 1, w + 2, h + 2);
|
||||
|
||||
// Body
|
||||
g.color = ui.t.WINDOW_BG_COL;
|
||||
g.fillRect(nx, ny, w, h);
|
||||
|
||||
// Header line
|
||||
g.color = node.color;
|
||||
g.fillRect(nx, ny + lineh - p(3), w, p(3));
|
||||
|
||||
// Title
|
||||
g.color = ui.t.LABEL_COL;
|
||||
var textw = g.font.width(ui.fontSize, text);
|
||||
g.drawString(text, nx + p(10), ny + p(6));
|
||||
ui._x = nx; // Use the whole line for hovering and not just the drawn string.
|
||||
ui._y = ny;
|
||||
ui._w = w;
|
||||
if (ui.getHover(lineh) && node.tooltip != null) ui.tooltip(tr(node.tooltip));
|
||||
|
||||
ny += lineh * 0.5;
|
||||
|
||||
// Outputs
|
||||
for (out in node.outputs) {
|
||||
ny += lineh;
|
||||
g.color = out.color;
|
||||
g.drawScaledImage(socketImage, nx + w - p(6), ny - p(3), p(12), p(12));
|
||||
}
|
||||
ny -= lineh * node.outputs.length;
|
||||
g.color = ui.t.LABEL_COL;
|
||||
for (out in node.outputs) {
|
||||
ny += lineh;
|
||||
var strw = ui.ops.font.width(ui.fontSize, tr(out.name));
|
||||
g.drawString(tr(out.name), nx + w - strw - p(12), ny - p(3));
|
||||
ui._x = nx;
|
||||
ui._y = ny;
|
||||
ui._w = w;
|
||||
if (ui.getHover(lineh) && out.tooltip != null) ui.tooltip(tr(out.tooltip));
|
||||
|
||||
if (onSocketReleased != null && ui.inputEnabled && (ui.inputReleased || ui.inputReleasedR)) {
|
||||
if (ui.inputX > wx + nx && ui.inputX < wx + nx + w && ui.inputY > wy + ny && ui.inputY < wy + ny + lineh) {
|
||||
onSocketReleased(out);
|
||||
socketReleased = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Buttons
|
||||
var nhandle = handle.nest(node.id);
|
||||
ny -= lineh / 3; // Fix align
|
||||
for (buti in 0...node.buttons.length) {
|
||||
var but = node.buttons[buti];
|
||||
|
||||
if (but.type == "RGBA") {
|
||||
ny += lineh; // 18 + 2 separator
|
||||
ui._x = nx;
|
||||
ui._y = ny;
|
||||
ui._w = w;
|
||||
var val: kha.arrays.Float32Array = node.outputs[but.output].default_value;
|
||||
nhandle.color = kha.Color.fromFloats(val[0], val[1], val[2]);
|
||||
Ext.colorWheel(ui, nhandle, false, null, null, true, function () {
|
||||
colorPickerCallback = function (color: kha.Color) {
|
||||
node.outputs[but.output].default_value[0] = color.R;
|
||||
node.outputs[but.output].default_value[1] = color.G;
|
||||
node.outputs[but.output].default_value[2] = color.B;
|
||||
ui.changed = true;
|
||||
};
|
||||
});
|
||||
val[0] = nhandle.color.R;
|
||||
val[1] = nhandle.color.G;
|
||||
val[2] = nhandle.color.B;
|
||||
}
|
||||
else if (but.type == "VECTOR") {
|
||||
ny += lineh;
|
||||
ui._x = nx;
|
||||
ui._y = ny;
|
||||
ui._w = w;
|
||||
var min = but.min != null ? but.min : 0.0;
|
||||
var max = but.max != null ? but.max : 1.0;
|
||||
var textOff = ui.t.TEXT_OFFSET;
|
||||
ui.t.TEXT_OFFSET = 6;
|
||||
ui.text(tr(but.name));
|
||||
if (ui.isHovered && but.tooltip != null) ui.tooltip(tr(but.tooltip));
|
||||
var val: kha.arrays.Float32Array = but.default_value;
|
||||
val[0] = ui.slider(nhandle.nest(buti).nest(0, {value: val[0]}), "X", min, max, true, 100, true, Left);
|
||||
if (ui.isHovered && but.tooltip != null) ui.tooltip(tr(but.tooltip));
|
||||
val[1] = ui.slider(nhandle.nest(buti).nest(1, {value: val[1]}), "Y", min, max, true, 100, true, Left);
|
||||
if (ui.isHovered && but.tooltip != null) ui.tooltip(tr(but.tooltip));
|
||||
val[2] = ui.slider(nhandle.nest(buti).nest(2, {value: val[2]}), "Z", min, max, true, 100, true, Left);
|
||||
if (ui.isHovered && but.tooltip != null) ui.tooltip(tr(but.tooltip));
|
||||
ui.t.TEXT_OFFSET = textOff;
|
||||
if (but.output != null) node.outputs[but.output].default_value = but.default_value;
|
||||
ny += lineh * 3;
|
||||
}
|
||||
else if (but.type == "VALUE") {
|
||||
ny += lineh;
|
||||
ui._x = nx;
|
||||
ui._y = ny;
|
||||
ui._w = w;
|
||||
var soc = node.outputs[but.output];
|
||||
var min = but.min != null ? but.min : 0.0;
|
||||
var max = but.max != null ? but.max : 1.0;
|
||||
var prec = but.precision != null ? but.precision : 100.0;
|
||||
var textOff = ui.t.TEXT_OFFSET;
|
||||
ui.t.TEXT_OFFSET = 6;
|
||||
soc.default_value = ui.slider(nhandle.nest(buti, {value: soc.default_value}), "Value", min, max, true, prec, true, Left);
|
||||
if (ui.isHovered && but.tooltip != null) ui.tooltip(tr(but.tooltip));
|
||||
ui.t.TEXT_OFFSET = textOff;
|
||||
}
|
||||
else if (but.type == "STRING") {
|
||||
ny += lineh;
|
||||
ui._x = nx;
|
||||
ui._y = ny;
|
||||
ui._w = w;
|
||||
var soc = but.output != null ? node.outputs[but.output] : null;
|
||||
but.default_value = ui.textInput(nhandle.nest(buti, {text: soc != null ? soc.default_value : but.default_value != null ? but.default_value : ""}), tr(but.name));
|
||||
if (soc != null) soc.default_value = but.default_value;
|
||||
if (ui.isHovered && but.tooltip != null) ui.tooltip(tr(but.tooltip));
|
||||
}
|
||||
else if (but.type == "ENUM") {
|
||||
ny += lineh;
|
||||
ui._x = nx;
|
||||
ui._y = ny;
|
||||
ui._w = w;
|
||||
var texts = Std.isOfType(but.data, Array) ? [for (s in cast(but.data, Array<Dynamic>)) tr(s)] : enumTexts(node.type);
|
||||
var buthandle = nhandle.nest(buti);
|
||||
buthandle.position = but.default_value;
|
||||
but.default_value = ui.combo(buthandle, texts, tr(but.name));
|
||||
if (ui.isHovered && but.tooltip != null) ui.tooltip(tr(but.tooltip));
|
||||
}
|
||||
else if (but.type == "BOOL") {
|
||||
ny += lineh;
|
||||
ui._x = nx;
|
||||
ui._y = ny;
|
||||
ui._w = w;
|
||||
but.default_value = ui.check(nhandle.nest(buti, {selected: but.default_value}), tr(but.name));
|
||||
if (ui.isHovered && but.tooltip != null) ui.tooltip(tr(but.tooltip));
|
||||
}
|
||||
else if (but.type == "CUSTOM") { // Calls external function for custom button drawing
|
||||
ny += lineh;
|
||||
ui._x = nx;
|
||||
ui._y = ny;
|
||||
ui._w = w;
|
||||
var dot = but.name.lastIndexOf("."); // TNodeButton.name specifies external function path
|
||||
var fn = Reflect.field(Type.resolveClass(but.name.substr(0, dot)), but.name.substr(dot + 1));
|
||||
fn(ui, this, node);
|
||||
ny += lineh * (but.height - 1); // TNodeButton.height specifies vertical button size
|
||||
}
|
||||
}
|
||||
ny += lineh / 3; // Fix align
|
||||
|
||||
// Inputs
|
||||
for (i in 0...node.inputs.length) {
|
||||
var inp = node.inputs[i];
|
||||
ny += lineh;
|
||||
g.color = inp.color;
|
||||
g.drawScaledImage(socketImage, nx - p(6), ny - p(3), p(12), p(12));
|
||||
var isLinked = inputLinked(canvas, node.id, i);
|
||||
if (!isLinked && inp.type == "VALUE") {
|
||||
ui._x = nx + p(6);
|
||||
ui._y = ny - p(9);
|
||||
ui._w = Std.int(w - p(6));
|
||||
var soc = inp;
|
||||
var min = soc.min != null ? soc.min : 0.0;
|
||||
var max = soc.max != null ? soc.max : 1.0;
|
||||
var prec = soc.precision != null ? soc.precision : 100.0;
|
||||
var textOff = ui.t.TEXT_OFFSET;
|
||||
ui.t.TEXT_OFFSET = 6;
|
||||
soc.default_value = ui.slider(nhandle.nest(maxButtons).nest(i, {value: soc.default_value}), tr(inp.name), min, max, true, prec, true, Left);
|
||||
if (ui.isHovered && inp.tooltip != null) ui.tooltip(tr(inp.tooltip));
|
||||
ui.t.TEXT_OFFSET = textOff;
|
||||
}
|
||||
else if (!isLinked && inp.type == "STRING") {
|
||||
ui._x = nx + p(6);
|
||||
ui._y = ny - p(9);
|
||||
ui._w = Std.int(w - p(6));
|
||||
var soc = inp;
|
||||
var textOff = ui.t.TEXT_OFFSET;
|
||||
ui.t.TEXT_OFFSET = 6;
|
||||
soc.default_value = ui.textInput(nhandle.nest(maxButtons).nest(i, {text: soc.default_value}), tr(inp.name), Left);
|
||||
if (ui.isHovered && inp.tooltip != null) ui.tooltip(tr(inp.tooltip));
|
||||
ui.t.TEXT_OFFSET = textOff;
|
||||
}
|
||||
else if (!isLinked && inp.type == "RGBA") {
|
||||
g.color = ui.t.LABEL_COL;
|
||||
g.drawString(tr(inp.name), nx + p(12), ny - p(3));
|
||||
ui._x = nx;
|
||||
ui._y = ny;
|
||||
ui._w = w;
|
||||
if (ui.getHover(lineh) && inp.tooltip != null) ui.tooltip(tr(inp.tooltip));
|
||||
var soc = inp;
|
||||
g.color = 0xff000000;
|
||||
g.fillRect(nx + w - p(38), ny - p(6), p(36), p(18));
|
||||
var val: kha.arrays.Float32Array = soc.default_value;
|
||||
g.color = kha.Color.fromFloats(val[0], val[1], val[2]);
|
||||
var rx = nx + w - p(37);
|
||||
var ry = ny - p(5);
|
||||
var rw = p(34);
|
||||
var rh = p(16);
|
||||
g.fillRect(rx, ry, rw, rh);
|
||||
var ix = ui.inputX - wx;
|
||||
var iy = ui.inputY - wy;
|
||||
if (ui.inputStarted && ix > rx && iy > ry && ix < rx + rw && iy < ry + rh) {
|
||||
_inputStarted = ui.inputStarted = false;
|
||||
rgbaPopup(ui, nhandle, soc.default_value, Std.int(rx), Std.int(ry + ui.ELEMENT_H()));
|
||||
}
|
||||
}
|
||||
else if (!isLinked && inp.type == "VECTOR" && inp.display == 1) {
|
||||
g.color = ui.t.LABEL_COL;
|
||||
g.drawString(tr(inp.name), nx + p(12), ny - p(3));
|
||||
ui._x = nx;
|
||||
ui._y = ny;
|
||||
ui._w = w;
|
||||
if (ui.getHover(lineh) && inp.tooltip != null) ui.tooltip(tr(inp.tooltip));
|
||||
|
||||
ny += lineh / 2;
|
||||
ui._y = ny;
|
||||
var min = inp.min != null ? inp.min : 0.0;
|
||||
var max = inp.max != null ? inp.max : 1.0;
|
||||
var textOff = ui.t.TEXT_OFFSET;
|
||||
ui.t.TEXT_OFFSET = 6;
|
||||
var val: kha.arrays.Float32Array = inp.default_value;
|
||||
val[0] = ui.slider(nhandle.nest(maxButtons).nest(i).nest(0, {value: val[0]}), "X", min, max, true, 100, true, Left);
|
||||
if (ui.isHovered && inp.tooltip != null) ui.tooltip(tr(inp.tooltip));
|
||||
val[1] = ui.slider(nhandle.nest(maxButtons).nest(i).nest(1, {value: val[1]}), "Y", min, max, true, 100, true, Left);
|
||||
if (ui.isHovered && inp.tooltip != null) ui.tooltip(tr(inp.tooltip));
|
||||
val[2] = ui.slider(nhandle.nest(maxButtons).nest(i).nest(2, {value: val[2]}), "Z", min, max, true, 100, true, Left);
|
||||
if (ui.isHovered && inp.tooltip != null) ui.tooltip(tr(inp.tooltip));
|
||||
ui.t.TEXT_OFFSET = textOff;
|
||||
ny += lineh * 2.5;
|
||||
}
|
||||
else {
|
||||
g.color = ui.t.LABEL_COL;
|
||||
g.drawString(tr(inp.name), nx + p(12), ny - p(3));
|
||||
ui._x = nx;
|
||||
ui._y = ny;
|
||||
ui._w = w;
|
||||
if (ui.getHover(lineh) && inp.tooltip != null) ui.tooltip(tr(inp.tooltip));
|
||||
}
|
||||
if (onSocketReleased != null && ui.inputEnabled && (ui.inputReleased || ui.inputReleasedR)) {
|
||||
if (ui.inputX > wx + nx && ui.inputX < wx + nx + w && ui.inputY > wy + ny && ui.inputY < wy + ny + lineh) {
|
||||
onSocketReleased(inp);
|
||||
socketReleased = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ui._x = uiX;
|
||||
ui._y = uiY;
|
||||
ui._w = uiW;
|
||||
ui.inputStarted = _inputStarted;
|
||||
}
|
||||
|
||||
public function rgbaPopup(ui: Zui, nhandle: zui.Zui.Handle, val: kha.arrays.Float32Array, x: Int, y: Int) {
|
||||
popup(x, y, Std.int(140 * scaleFactor), Std.int(ui.t.ELEMENT_H * 10), function(ui: Zui) {
|
||||
nhandle.color = kha.Color.fromFloats(val[0], val[1], val[2]);
|
||||
Ext.colorWheel(ui, nhandle, false, null, true, function () {
|
||||
colorPickerCallback = function (color: kha.Color) {
|
||||
val[0] = color.R;
|
||||
val[1] = color.G;
|
||||
val[2] = color.B;
|
||||
ui.changed = true;
|
||||
};
|
||||
});
|
||||
val[0] = nhandle.color.R; val[1] = nhandle.color.G; val[2] = nhandle.color.B;
|
||||
});
|
||||
}
|
||||
|
||||
public function drawLink(ui: Zui, x1: Float, y1: Float, x2: Float, y2: Float, highlight: Bool = false) {
|
||||
var g = ui.g;
|
||||
var c1: kha.Color = ui.t.LABEL_COL;
|
||||
var c2: kha.Color = ui.t.ACCENT_SELECT_COL;
|
||||
g.color = highlight ? kha.Color.fromBytes(c1.Rb, c1.Gb, c1.Bb, 210) : kha.Color.fromBytes(c2.Rb, c2.Gb, c2.Bb, 210);
|
||||
if (ui.t.LINK_STYLE == Line) {
|
||||
g.drawLine(x1, y1, x2, y2, 1.0);
|
||||
g.color = highlight ? kha.Color.fromBytes(c1.Rb, c1.Gb, c1.Bb, 150) : kha.Color.fromBytes(c2.Rb, c2.Gb, c2.Bb, 150); // AA
|
||||
g.drawLine(x1 + 0.5, y1, x2 + 0.5, y2, 1.0);
|
||||
g.drawLine(x1 - 0.5, y1, x2 - 0.5, y2, 1.0);
|
||||
g.drawLine(x1, y1 + 0.5, x2, y2 + 0.5, 1.0);
|
||||
g.drawLine(x1, y1 - 0.5, x2, y2 - 0.5, 1.0);
|
||||
}
|
||||
else if (ui.t.LINK_STYLE == CubicBezier) {
|
||||
g.drawCubicBezier([x1, x1 + Math.abs(x1 - x2) / 2, x2 - Math.abs(x1 - x2) / 2, x2], [y1, y1, y2, y2], 30, highlight ? 2.0 : 1.0);
|
||||
}
|
||||
}
|
||||
|
||||
public function removeNode(n: TNode, canvas: TNodeCanvas) {
|
||||
if (n == null) return;
|
||||
var i = 0;
|
||||
while (i < canvas.links.length) {
|
||||
var l = canvas.links[i];
|
||||
if (l.from_id == n.id || l.to_id == n.id) {
|
||||
canvas.links.splice(i, 1);
|
||||
}
|
||||
else i++;
|
||||
}
|
||||
canvas.nodes.remove(n);
|
||||
if (onNodeRemove != null) {
|
||||
onNodeRemove(n);
|
||||
}
|
||||
}
|
||||
|
||||
var popupX = 0;
|
||||
var popupY = 0;
|
||||
var popupW = 0;
|
||||
var popupH = 0;
|
||||
var popupCommands: Zui->Void = null;
|
||||
function popup(x: Int, y: Int, w: Int, h: Int, commands: Zui->Void) {
|
||||
popupX = x;
|
||||
popupY = y;
|
||||
popupW = w;
|
||||
popupH = h;
|
||||
popupCommands = commands;
|
||||
}
|
||||
}
|
||||
|
||||
typedef CanvasControl = {
|
||||
var panX: Float;
|
||||
var panY: Float;
|
||||
var zoom: Float;
|
||||
}
|
||||
|
||||
typedef TNodeCanvas = {
|
||||
var name: String;
|
||||
var nodes: Array<TNode>;
|
||||
var links: Array<TNodeLink>;
|
||||
}
|
||||
|
||||
typedef TNode = {
|
||||
var id: Int;
|
||||
var name: String;
|
||||
var type: String;
|
||||
var x: Float;
|
||||
var y: Float;
|
||||
var inputs: Array<TNodeSocket>;
|
||||
var outputs: Array<TNodeSocket>;
|
||||
var buttons: Array<TNodeButton>;
|
||||
var color: Int;
|
||||
@:optional var width: Null<Float>;
|
||||
@:optional var tooltip: String;
|
||||
}
|
||||
|
||||
typedef TNodeSocket = {
|
||||
var id: Int;
|
||||
var node_id: Int;
|
||||
var name: String;
|
||||
var type: String;
|
||||
var color: Int;
|
||||
var default_value: Dynamic;
|
||||
@:optional var min: Null<Float>;
|
||||
@:optional var max: Null<Float>;
|
||||
@:optional var precision: Null<Float>;
|
||||
@:optional var display: Null<Int>;
|
||||
@:optional var tooltip: String;
|
||||
}
|
||||
|
||||
typedef TNodeLink = {
|
||||
var id: Int;
|
||||
var from_id: Int;
|
||||
var from_socket: Int;
|
||||
var to_id: Int;
|
||||
var to_socket: Int;
|
||||
}
|
||||
|
||||
typedef TNodeButton = {
|
||||
var name: String;
|
||||
var type: String;
|
||||
@:optional var output: Null<Int>;
|
||||
@:optional var default_value: Dynamic;
|
||||
@:optional var data: Dynamic;
|
||||
@:optional var min: Null<Float>;
|
||||
@:optional var max: Null<Float>;
|
||||
@:optional var precision: Null<Float>;
|
||||
@:optional var height: Null<Float>;
|
||||
@:optional var tooltip: String;
|
||||
}
|
79
leenkx/Sources/zui/Themes.hx
Normal file
79
leenkx/Sources/zui/Themes.hx
Normal file
@ -0,0 +1,79 @@
|
||||
package zui;
|
||||
|
||||
class Themes {
|
||||
|
||||
public static var dark: TTheme = {
|
||||
NAME: "Default Dark",
|
||||
WINDOW_BG_COL: 0xff292929,
|
||||
WINDOW_TINT_COL: 0xffffffff,
|
||||
ACCENT_COL: 0xff393939,
|
||||
ACCENT_HOVER_COL: 0xff434343,
|
||||
ACCENT_SELECT_COL: 0xff505050,
|
||||
BUTTON_COL: 0xff383838,
|
||||
BUTTON_TEXT_COL: 0xffe8e7e5,
|
||||
BUTTON_HOVER_COL: 0xff494949,
|
||||
BUTTON_PRESSED_COL: 0xff1b1b1b,
|
||||
TEXT_COL: 0xffe8e7e5,
|
||||
LABEL_COL: 0xffc8c8c8,
|
||||
SEPARATOR_COL: 0xff202020,
|
||||
HIGHLIGHT_COL: 0xff205d9c,
|
||||
CONTEXT_COL: 0xff222222,
|
||||
PANEL_BG_COL: 0xff3b3b3b,
|
||||
FONT_SIZE: 13,
|
||||
ELEMENT_W: 100,
|
||||
ELEMENT_H: 24,
|
||||
ELEMENT_OFFSET: 4,
|
||||
ARROW_SIZE: 5,
|
||||
BUTTON_H: 22,
|
||||
CHECK_SIZE: 15,
|
||||
CHECK_SELECT_SIZE: 8,
|
||||
SCROLL_W: 9,
|
||||
TEXT_OFFSET: 8,
|
||||
TAB_W: 6,
|
||||
FILL_WINDOW_BG: false,
|
||||
FILL_BUTTON_BG: true,
|
||||
FILL_ACCENT_BG: false,
|
||||
LINK_STYLE: Line,
|
||||
FULL_TABS: false
|
||||
};
|
||||
}
|
||||
|
||||
typedef TTheme = {
|
||||
var NAME: String;
|
||||
var WINDOW_BG_COL: Int;
|
||||
var WINDOW_TINT_COL: Int;
|
||||
var ACCENT_COL: Int;
|
||||
var ACCENT_HOVER_COL: Int;
|
||||
var ACCENT_SELECT_COL: Int;
|
||||
var BUTTON_COL: Int;
|
||||
var BUTTON_TEXT_COL: Int;
|
||||
var BUTTON_HOVER_COL: Int;
|
||||
var BUTTON_PRESSED_COL: Int;
|
||||
var TEXT_COL: Int;
|
||||
var LABEL_COL: Int;
|
||||
var SEPARATOR_COL: Int;
|
||||
var HIGHLIGHT_COL: Int;
|
||||
var CONTEXT_COL: Int;
|
||||
var PANEL_BG_COL: Int;
|
||||
var FONT_SIZE: Int;
|
||||
var ELEMENT_W: Int;
|
||||
var ELEMENT_H: Int;
|
||||
var ELEMENT_OFFSET: Int;
|
||||
var ARROW_SIZE: Int;
|
||||
var BUTTON_H: Int;
|
||||
var CHECK_SIZE: Int;
|
||||
var CHECK_SELECT_SIZE: Int;
|
||||
var SCROLL_W: Int;
|
||||
var TEXT_OFFSET: Int;
|
||||
var TAB_W: Int; // Indentation
|
||||
var FILL_WINDOW_BG: Bool;
|
||||
var FILL_BUTTON_BG: Bool;
|
||||
var FILL_ACCENT_BG: Bool;
|
||||
var LINK_STYLE: LinkStyle;
|
||||
var FULL_TABS: Bool; // Make tabs take full window width
|
||||
}
|
||||
|
||||
@:enum abstract LinkStyle(Int) from Int {
|
||||
var Line = 0;
|
||||
var CubicBezier = 1;
|
||||
}
|
2098
leenkx/Sources/zui/Zui.hx
Normal file
2098
leenkx/Sources/zui/Zui.hx
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user