diff --git a/Sources/armory/trait/internal/CanvasScript.hx b/Sources/armory/trait/internal/CanvasScript.hx index ba8415e6..f8c6d82f 100644 --- a/Sources/armory/trait/internal/CanvasScript.hx +++ b/Sources/armory/trait/internal/CanvasScript.hx @@ -3,7 +3,7 @@ package armory.trait.internal; import iron.Trait; #if arm_ui import zui.Zui; -import zui.Canvas; +import armory.ui.Canvas; #end class CanvasScript extends Trait { @@ -62,7 +62,7 @@ class CanvasScript extends Trait { notifyOnRender2D(function(g: kha.graphics2.Graphics) { if (canvas == null) return; - + setCanvasDimensions(kha.System.windowWidth(), kha.System.windowHeight()); var events = Canvas.draw(cui, canvas, g); @@ -121,7 +121,7 @@ class CanvasScript extends Trait { public function setCanvasVisibility(visible: Bool){ for (e in canvas.elements) e.visible = visible; } - + /** * Set dimensions of canvas * @param x Width @@ -140,7 +140,7 @@ class CanvasScript extends Trait { } // Contains data - @:access(zui.Canvas) + @:access(armory.ui.Canvas) @:access(zui.Handle) public function getHandle(name: String): Handle { // Consider this a temporary solution diff --git a/Sources/armory/ui/Canvas.hx b/Sources/armory/ui/Canvas.hx new file mode 100644 index 00000000..2c66cf68 --- /dev/null +++ b/Sources/armory/ui/Canvas.hx @@ -0,0 +1,410 @@ +package armory.ui; + +import zui.Zui; + +using kha.graphics2.GraphicsExtension; + +@:access(zui.Zui) +class Canvas { + + public static var assetMap = new Map(); // kha.Image | kha.Font + public static var themes = new Array(); + static var events:Array = []; + + public static var screenW = -1; + public static var screenH = -1; + public static var locale = "en"; + static var _ui: Zui; + static var h = new zui.Zui.Handle(); // TODO: needs one handle per canvas + + public static function draw(ui: Zui, canvas: TCanvas, g: kha.graphics2.Graphics): Array { + + screenW = kha.System.windowWidth(); + screenH = kha.System.windowHeight(); + + events.resize(0); + + _ui = ui; + + g.end(); + ui.begin(g); // Bake elements + g.begin(false); + + ui.g = g; + + for (elem in canvas.elements) { + if (elem.parent == null) drawElement(ui, canvas, elem); + } + + g.end(); + ui.end(); // Finish drawing + g.begin(false); + + return events; + } + + static function drawElement(ui: Zui, canvas: TCanvas, element: TElement, px = 0.0, py = 0.0) { + + if (element == null || element.visible == false) return; + + var anchorOffset = getAnchorOffset(canvas, element); + px += anchorOffset[0]; + py += anchorOffset[1]; + + ui._x = canvas.x + scaled(element.x) + px; + ui._y = canvas.y + scaled(element.y) + py; + ui._w = scaled(element.width); + + var rotated = element.rotation != null && element.rotation != 0; + if (rotated) ui.g.pushRotation(element.rotation, ui._x + scaled(element.width) / 2, ui._y + scaled(element.height) / 2); + + switch (element.type) { + case Text: + var font = ui.ops.font; + var size = ui.fontSize; + + var fontAsset = element.asset != null && StringTools.endsWith(element.asset, ".ttf"); + if (fontAsset) ui.ops.font = getAsset(canvas, element.asset); + ui.fontSize = scaled(element.height); + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.text(getText(canvas, element), element.alignment); + + ui.ops.font = font; + ui.fontSize = size; + + case Button: + var eh = ui.t.ELEMENT_H; + var bh = ui.t.BUTTON_H; + ui.t.ELEMENT_H = element.height; + ui.t.BUTTON_H = element.height; + ui.t.BUTTON_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.BUTTON_TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).BUTTON_TEXT_COL); + ui.t.BUTTON_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + ui.t.BUTTON_PRESSED_COL = getColor(element.color_press, getTheme(canvas.theme).BUTTON_PRESSED_COL); + if (ui.button(getText(canvas, element), element.alignment)) { + var e = element.event; + if (e != null && e != "") events.push(e); + } + ui.t.ELEMENT_H = eh; + ui.t.BUTTON_H = bh; + + case Image: + var image = getAsset(canvas, element.asset); + var fontAsset = element.asset != null && StringTools.endsWith(element.asset, ".ttf"); + if (image != null && !fontAsset) { + ui.imageScrollAlign = false; + var tint = element.color != null ? element.color : 0xffffffff; + if (ui.image(image, tint, scaled(element.height)) == zui.Zui.State.Released) { + var e = element.event; + if (e != null && e != "") events.push(e); + } + ui.imageScrollAlign = true; + } + + case FRectangle: + var col = ui.g.color; + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.fillRect(ui._x, ui._y, ui._w, scaled(element.height)); + ui.g.color = col; + + case FCircle: + var col = ui.g.color; + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.fillCircle(ui._x + (scaled(element.width) / 2), ui._y + (scaled(element.height) / 2), ui._w / 2); + ui.g.color = col; + + case Rectangle: + var col = ui.g.color; + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.drawRect(ui._x, ui._y, ui._w, scaled(element.height), element.strength); + ui.g.color = col; + + case Circle: + var col = ui.g.color; + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.drawCircle(ui._x+(scaled(element.width) / 2), ui._y + (scaled(element.height) / 2), ui._w / 2, element.strength); + ui.g.color = col; + + case FTriangle: + var col = ui.g.color; + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.fillTriangle(ui._x + (ui._w / 2), ui._y, ui._x, ui._y + scaled(element.height), ui._x + ui._w, ui._y + scaled(element.height)); + ui.g.color = col; + + case Triangle: + var col = ui.g.color; + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.drawLine(ui._x + (ui._w / 2), ui._y, ui._x, ui._y + scaled(element.height), element.strength); + ui.g.drawLine(ui._x, ui._y + scaled(element.height), ui._x + ui._w, ui._y + scaled(element.height), element.strength); + ui.g.drawLine(ui._x + ui._w, ui._y + scaled(element.height), ui._x + (ui._w / 2), ui._y, element.strength); + ui.g.color = col; + + case Check: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + ui.check(h.nest(element.id), getText(canvas, element)); + + case Radio: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + zui.Ext.inlineRadio(ui, h.nest(element.id), getText(canvas, element).split(";")); + + case Combo: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.SEPARATOR_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + ui.combo(h.nest(element.id), getText(canvas, element).split(";")); + + case Slider: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + ui.slider(h.nest(element.id), getText(canvas, element), 0.0, 1.0, true, 100, true, element.alignment); + + case TextInput: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + ui.textInput(h.nest(element.id), getText(canvas, element), element.alignment); + if (h.nest(element.id).changed) { + var e = element.event; + if (e != null && e != "") events.push(e); + } + + case TextArea: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + h.nest(element.id).text = getText(canvas, element); + zui.Ext.textArea(ui,h.nest(element.id), element.alignment,element.editable); + if (h.nest(element.id).changed) { + var e = element.event; + if (e != null && e != "") events.push(e); + } + + case KeyInput: + ui.t.TEXT_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.LABEL_COL = getColor(element.color_text, getTheme(canvas.theme).TEXT_COL); + ui.t.ACCENT_COL = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.t.ACCENT_HOVER_COL = getColor(element.color_hover, getTheme(canvas.theme).BUTTON_HOVER_COL); + Ext.keyInput(ui, h.nest(element.id), getText(canvas, element)); + + case ProgressBar: + var col = ui.g.color; + var progress = element.progress_at; + var totalprogress = element.progress_total; + ui.g.color = getColor(element.color_progress, getTheme(canvas.theme).TEXT_COL); + ui.g.fillRect(ui._x, ui._y, ui._w / totalprogress * Math.min(progress, totalprogress), scaled(element.height)); + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.drawRect(ui._x, ui._y, ui._w, scaled(element.height), element.strength); + ui.g.color = col; + + case CProgressBar: + var col = ui.g.color; + var progress = element.progress_at; + var totalprogress = element.progress_total; + ui.g.color = getColor(element.color_progress, getTheme(canvas.theme).TEXT_COL); + ui.g.drawArc(ui._x + (scaled(element.width) / 2), ui._y + (scaled(element.height) / 2), ui._w / 2, -Math.PI / 2, ((Math.PI * 2) / totalprogress * progress) - Math.PI / 2, element.strength); + ui.g.color = getColor(element.color, getTheme(canvas.theme).BUTTON_COL); + ui.g.fillCircle(ui._x + (scaled(element.width) / 2), ui._y + (scaled(element.height) / 2), (ui._w / 2) - 10); + ui.g.color = col; + case Empty: + } + + if (element.children != null) { + for (id in element.children) { + drawElement(ui, canvas, elemById(canvas, id), scaled(element.x) + px, scaled(element.y) + py); + } + } + + if (rotated) ui.g.popTransformation(); + } + + static inline function getText(canvas: TCanvas, e: TElement): String { + return e.text; + } + + public static function getAsset(canvas: TCanvas, asset: String): Dynamic { // kha.Image | kha.Font { + for (a in canvas.assets) if (a.name == asset) return assetMap.get(a.id); + return null; + } + + static var elemId = -1; + public static function getElementId(canvas: TCanvas): Int { + if (elemId == -1) for (e in canvas.elements) if (elemId < e.id) elemId = e.id; + return ++elemId; + } + + static var assetId = -1; + public static function getAssetId(canvas: TCanvas): Int { + if (assetId == -1) for (a in canvas.assets) if (assetId < a.id) assetId = a.id; + return ++assetId; + } + + static function elemById(canvas: TCanvas, id: Int): TElement { + for (e in canvas.elements) if (e.id == id) return e; + return null; + } + + static inline function scaled(f: Float): Int { + return Std.int(f * _ui.SCALE()); + } + + public static inline function getColor(color: Null, defaultColor: Int): Int { + return color != null ? color : defaultColor; + } + + public static function getTheme(theme: String): zui.Themes.TTheme { + for (t in Canvas.themes) { + if (t.NAME == theme) return t; + } + return null; + } + + /** + Returns the positional scaled offset of the given element based on its anchor setting. + @param canvas The canvas object + @param element The element + @return Array [xOffset, yOffset] + **/ + public static function getAnchorOffset(canvas: TCanvas, element: TElement): Array { + var boxWidth, boxHeight: Float; + var offsetX = 0.0; + var offsetY = 0.0; + + if (element.parent == null) { + boxWidth = canvas.width; + boxHeight = canvas.height; + } + else { + var parent = elemById(canvas, element.parent); + boxWidth = scaled(parent.width); + boxHeight = scaled(parent.height); + } + + switch (element.anchor) { + case Top: + offsetX += boxWidth / 2 - scaled(element.width) / 2; + case TopRight: + offsetX += boxWidth - scaled(element.width); + case CenterLeft: + offsetY += boxHeight / 2 - scaled(element.height) / 2; + case Center: + offsetX += boxWidth / 2 - scaled(element.width) / 2; + offsetY += boxHeight / 2 - scaled(element.height) / 2; + case CenterRight: + offsetX += boxWidth - scaled(element.width); + offsetY += boxHeight / 2 - scaled(element.height) / 2; + case BottomLeft: + offsetY += boxHeight - scaled(element.height); + case Bottom: + offsetX += boxWidth / 2 - scaled(element.width) / 2; + offsetY += boxHeight - scaled(element.height); + case BottomRight: + offsetX += boxWidth - scaled(element.width); + offsetY += boxHeight - scaled(element.height); + } + + return [offsetX, offsetY]; + } +} + +typedef TCanvas = { + var name: String; + var x: Float; + var y: Float; + var width: Int; + var height: Int; + var elements: Array; + var theme: String; + @:optional var assets: Array; + @:optional var locales: Array; +} + +typedef TElement = { + var id: Int; + var type: ElementType; + var name: String; + var x: Float; + var y: Float; + var width: Int; + var height: Int; + @:optional var rotation: Null; + @:optional var text: String; + @:optional var event: String; + // null = follow theme settings + @:optional var color: Null; + @:optional var color_text: Null; + @:optional var color_hover: Null; + @:optional var color_press: Null; + @:optional var color_progress: Null; + @:optional var progress_at: Null; + @:optional var progress_total: Null; + @:optional var strength: Null; + @:optional var alignment: Null; + @:optional var anchor: Null; + @:optional var parent: Null; // id + @:optional var children: Array; // ids + @:optional var asset: String; + @:optional var visible: Null; + @:optional var editable: Null; +} + +typedef TAsset = { + var id: Int; + var name: String; + var file: String; +} + +typedef TLocale = { + var name: String; // "en" + var texts: Array; +} + +typedef TTranslatedText = { + var id: Int; // element id + var text: String; +} + +@:enum abstract ElementType(Int) from Int to Int { + var Text = 0; + var Image = 1; + var Button = 2; + var Empty = 3; + // var HLayout = 4; + // var VLayout = 5; + var Check = 6; + var Radio = 7; + var Combo = 8; + var Slider = 9; + var TextInput = 10; + var KeyInput = 11; + var FRectangle = 12; + var Rectangle = 13; + var FCircle = 14; + var Circle = 15; + var FTriangle = 16; + var Triangle = 17; + var ProgressBar = 18; + var CProgressBar = 19; + var TextArea = 20; +} + +@:enum abstract Anchor(Int) from Int to Int { + var TopLeft = 0; + var Top = 1; + var TopRight = 2; + var CenterLeft = 3; + var Center = 4; + var CenterRight = 5; + var BottomLeft = 6; + var Bottom = 7; + var BottomRight = 8; +} diff --git a/Sources/armory/ui/Ext.hx b/Sources/armory/ui/Ext.hx new file mode 100644 index 00000000..0104ddd5 --- /dev/null +++ b/Sources/armory/ui/Ext.hx @@ -0,0 +1,331 @@ +package armory.ui; + +import zui.Zui; +import kha.input.Keyboard; +import kha.input.KeyCode; + +typedef ListOpts = { + ?addCb: String->Void, + ?removeCb: Int->Void, + ?getNameCb: Int->String, + ?setNameCb: Int->String->Void, + ?getLabelCb: Int->String, + ?itemDrawCb: Handle->Int->Void, + ?showRadio: Bool, // false + ?editable: Bool, // true + ?showAdd: Bool, // true + ?addLabel: String // 'Add' +} + +@:access(zui.Zui) +class Ext { + public static function keyInput(ui: Zui, handle: Handle, label = "", align: Align = Left): Int { + if (!ui.isVisible(ui.ELEMENT_H())) { + ui.endElement(); + return Std.int(handle.value); + } + + var hover = ui.getHover(); + if (hover && Zui.onTextHover != null) Zui.onTextHover(); + ui.g.color = hover ? ui.t.ACCENT_HOVER_COL : ui.t.ACCENT_COL; // Text bg + ui.drawRect(ui.g, ui.t.FILL_ACCENT_BG, ui._x + ui.buttonOffsetY, ui._y + ui.buttonOffsetY, ui._w - ui.buttonOffsetY * 2, ui.BUTTON_H()); + + var startEdit = ui.getReleased() || ui.tabPressed; + if (ui.textSelectedHandle != handle && startEdit) ui.startTextEdit(handle); + if (ui.textSelectedHandle == handle) Ext.listenToKey(ui, handle); + else handle.changed = false; + + if (label != "") { + ui.g.color = ui.t.LABEL_COL; // Label + var labelAlign = align == Align.Right ? Align.Left : Align.Right; + var xOffset = labelAlign == Align.Left ? 7 : 0; + ui.drawString(ui.g, label, xOffset, 0, labelAlign); + } + + handle.text = Ext.keycodeToString(Std.int(handle.value)); + + ui.g.color = ui.t.TEXT_COL; // Text + ui.textSelectedHandle != handle ? ui.drawString(ui.g, handle.text, null, 0, align) : ui.drawString(ui.g, ui.textSelected, null, 0, align); + + ui.endElement(); + + return Std.int(handle.value); + } + + static function listenToKey(ui: Zui, handle: Handle) { + if (ui.isKeyDown) { + handle.value = ui.key; + handle.changed = ui.changed = true; + + ui.textSelectedHandle = null; + ui.isTyping = false; + + if (Keyboard.get() != null) Keyboard.get().hide(); + } + else { + ui.textSelected = "Press a key..."; + } + } + + public static function list(ui: Zui, handle: Handle, ar: Array, ?opts: ListOpts ): Int { + var selected = 0; + if (opts == null) opts = {}; + + var addCb = opts.addCb != null ? opts.addCb : function(name: String) ar.push(name); + var removeCb = opts.removeCb != null ? opts.removeCb : function(i: Int) ar.splice(i, 1); + var getNameCb = opts.getNameCb != null ? opts.getNameCb : function(i: Int) return ar[i]; + var setNameCb = opts.setNameCb != null ? opts.setNameCb : function(i: Int, name: String) ar[i] = name; + var getLabelCb = opts.getLabelCb != null ? opts.getLabelCb : function(i: Int) return ""; + var itemDrawCb = opts.itemDrawCb; + var showRadio = opts.showRadio != null ? opts.showRadio : false; + var editable = opts.editable != null ? opts.editable : true; + var showAdd = opts.showAdd != null ? opts.showAdd : true; + var addLabel = opts.addLabel != null ? opts.addLabel : "Add"; + + var i = 0; + while (i < ar.length) { + if (showRadio) { // Prepend ratio button + ui.row([0.12, 0.68, 0.2]); + if (ui.radio(handle.nest(0), i, "")) { + selected = i; + } + } + else ui.row([0.8, 0.2]); + + var itemHandle = handle.nest(i); + itemHandle.text = getNameCb(i); + editable ? setNameCb(i, ui.textInput(itemHandle, getLabelCb(i))) : ui.text(getNameCb(i)); + if (ui.button("X")) removeCb(i); + else i++; + + if (itemDrawCb != null) itemDrawCb(itemHandle.nest(i), i - 1); + } + if (showAdd && ui.button(addLabel)) addCb("untitled"); + + return selected; + } + + public static function panelList(ui: Zui, handle: Handle, ar: Array, + addCb: String->Void = null, + removeCb: Int->Void = null, + getNameCb: Int->String = null, + setNameCb: Int->String->Void = null, + itemDrawCb: Handle->Int->Void = null, + editable = true, + showAdd = true, + addLabel: String = "Add") { + + if (addCb == null) addCb = function(name: String) { ar.push(name); }; + if (removeCb == null) removeCb = function(i: Int) { ar.splice(i, 1); }; + if (getNameCb == null) getNameCb = function(i: Int) { return ar[i]; }; + if (setNameCb == null) setNameCb = function(i: Int, name: String) { ar[i] = name; }; + + var i = 0; + while (i < ar.length) { + ui.row([0.12, 0.68, 0.2]); + var expanded = ui.panel(handle.nest(i), ""); + + var itemHandle = handle.nest(i); + editable ? setNameCb(i, ui.textInput(itemHandle, getNameCb(i))) : ui.text(getNameCb(i)); + if (ui.button("X")) removeCb(i); + else i++; + + if (itemDrawCb != null && expanded) itemDrawCb(itemHandle.nest(i), i - 1); + } + if (showAdd && ui.button(addLabel)) { + addCb("untitled"); + } + } + + public static function colorField(ui: Zui, handle:Handle, alpha = false): Int { + ui.g.color = handle.color; + + ui.drawRect(ui.g, true, ui._x + 2, ui._y + ui.buttonOffsetY, ui._w - 4, ui.BUTTON_H()); + ui.g.color = ui.getHover() ? ui.t.ACCENT_HOVER_COL : ui.t.ACCENT_COL; + ui.drawRect(ui.g, false, ui._x + 2, ui._y + ui.buttonOffsetY, ui._w - 4, ui.BUTTON_H(), 1.0); + + if (ui.getStarted()) { + Popup.showCustom( + new Zui(ui.ops), + function(ui:Zui) { + zui.Ext.colorWheel(ui, handle, alpha); + }, + Std.int(ui.inputX), Std.int(ui.inputY), 200, 500); + } + + ui.endElement(); + return handle.color; + } + + public static function colorPicker(ui: Zui, handle: Handle, alpha = false): Int { + var r = ui.slider(handle.nest(0, {value: handle.color.R}), "R", 0, 1, true); + var g = ui.slider(handle.nest(1, {value: handle.color.G}), "G", 0, 1, true); + var b = ui.slider(handle.nest(2, {value: handle.color.B}), "B", 0, 1, true); + var a = handle.color.A; + if (alpha) a = ui.slider(handle.nest(3, {value: a}), "A", 0, 1, true); + var col = kha.Color.fromFloats(r, g, b, a); + ui.text("", Right, col); + return col; + } + + /** + Keycodes can be found here: http://api.kha.tech/kha/input/KeyCode.html + **/ + static function keycodeToString(keycode: Int): String { + switch (keycode) { + case -1: return "None"; + case KeyCode.Unknown: return "Unknown"; + case KeyCode.Back: return "Back"; + case KeyCode.Cancel: return "Cancel"; + case KeyCode.Help: return "Help"; + case KeyCode.Backspace: return "Backspace"; + case KeyCode.Tab: return "Tab"; + case KeyCode.Clear: return "Clear"; + case KeyCode.Return: return "Return"; + case KeyCode.Shift: return "Shift"; + case KeyCode.Control: return "Ctrl"; + case KeyCode.Alt: return "Alt"; + case KeyCode.Pause: return "Pause"; + case KeyCode.CapsLock: return "CapsLock"; + case KeyCode.Kana: return "Kana"; + // case KeyCode.Hangul: return "Hangul"; // Hangul == Kana + case KeyCode.Eisu: return "Eisu"; + case KeyCode.Junja: return "Junja"; + case KeyCode.Final: return "Final"; + case KeyCode.Hanja: return "Hanja"; + // case KeyCode.Kanji: return "Kanji"; // Kanji == Hanja + case KeyCode.Escape: return "Esc"; + case KeyCode.Convert: return "Convert"; + case KeyCode.NonConvert: return "NonConvert"; + case KeyCode.Accept: return "Accept"; + case KeyCode.ModeChange: return "ModeChange"; + case KeyCode.Space: return "Space"; + case KeyCode.PageUp: return "PageUp"; + case KeyCode.PageDown: return "PageDown"; + case KeyCode.End: return "End"; + case KeyCode.Home: return "Home"; + case KeyCode.Left: return "Left"; + case KeyCode.Up: return "Up"; + case KeyCode.Right: return "Right"; + case KeyCode.Down: return "Down"; + case KeyCode.Select: return "Select"; + case KeyCode.Print: return "Print"; + case KeyCode.Execute: return "Execute"; + case KeyCode.PrintScreen: return "PrintScreen"; + case KeyCode.Insert: return "Insert"; + case KeyCode.Delete: return "Delete"; + case KeyCode.Colon: return "Colon"; + case KeyCode.Semicolon: return "Semicolon"; + case KeyCode.LessThan: return "LessThan"; + case KeyCode.Equals: return "Equals"; + case KeyCode.GreaterThan: return "GreaterThan"; + case KeyCode.QuestionMark: return "QuestionMark"; + case KeyCode.At: return "At"; + case KeyCode.Win: return "Win"; + case KeyCode.ContextMenu: return "ContextMenu"; + case KeyCode.Sleep: return "Sleep"; + case KeyCode.Numpad0: return "Numpad0"; + case KeyCode.Numpad1: return "Numpad1"; + case KeyCode.Numpad2: return "Numpad2"; + case KeyCode.Numpad3: return "Numpad3"; + case KeyCode.Numpad4: return "Numpad4"; + case KeyCode.Numpad5: return "Numpad5"; + case KeyCode.Numpad6: return "Numpad6"; + case KeyCode.Numpad7: return "Numpad7"; + case KeyCode.Numpad8: return "Numpad8"; + case KeyCode.Numpad9: return "Numpad9"; + case KeyCode.Multiply: return "Multiply"; + case KeyCode.Add: return "Add"; + case KeyCode.Separator: return "Separator"; + case KeyCode.Subtract: return "Subtract"; + case KeyCode.Decimal: return "Decimal"; + case KeyCode.Divide: return "Divide"; + case KeyCode.F1: return "F1"; + case KeyCode.F2: return "F2"; + case KeyCode.F3: return "F3"; + case KeyCode.F4: return "F4"; + case KeyCode.F5: return "F5"; + case KeyCode.F6: return "F6"; + case KeyCode.F7: return "F7"; + case KeyCode.F8: return "F8"; + case KeyCode.F9: return "F9"; + case KeyCode.F10: return "F10"; + case KeyCode.F11: return "F11"; + case KeyCode.F12: return "F12"; + case KeyCode.F13: return "F13"; + case KeyCode.F14: return "F14"; + case KeyCode.F15: return "F15"; + case KeyCode.F16: return "F16"; + case KeyCode.F17: return "F17"; + case KeyCode.F18: return "F18"; + case KeyCode.F19: return "F19"; + case KeyCode.F20: return "F20"; + case KeyCode.F21: return "F21"; + case KeyCode.F22: return "F22"; + case KeyCode.F23: return "F23"; + case KeyCode.F24: return "F24"; + case KeyCode.NumLock: return "NumLock"; + case KeyCode.ScrollLock: return "ScrollLock"; + case KeyCode.WinOemFjJisho: return "WinOemFjJisho"; + case KeyCode.WinOemFjMasshou: return "WinOemFjMasshou"; + case KeyCode.WinOemFjTouroku: return "WinOemFjTouroku"; + case KeyCode.WinOemFjLoya: return "WinOemFjLoya"; + case KeyCode.WinOemFjRoya: return "WinOemFjRoya"; + case KeyCode.Circumflex: return "Circumflex"; + case KeyCode.Exclamation: return "Exclamation"; + case KeyCode.DoubleQuote: return "DoubleQuote"; + case KeyCode.Hash: return "Hash"; + case KeyCode.Dollar: return "Dollar"; + case KeyCode.Percent: return "Percent"; + case KeyCode.Ampersand: return "Ampersand"; + case KeyCode.Underscore: return "Underscore"; + case KeyCode.OpenParen: return "OpenParen"; + case KeyCode.CloseParen: return "CloseParen"; + case KeyCode.Asterisk: return "Asterisk"; + case KeyCode.Plus: return "Plus"; + case KeyCode.Pipe: return "Pipe"; + case KeyCode.HyphenMinus: return "HyphenMinus"; + case KeyCode.OpenCurlyBracket: return "OpenCurlyBracket"; + case KeyCode.CloseCurlyBracket: return "CloseCurlyBracket"; + case KeyCode.Tilde: return "Tilde"; + case KeyCode.VolumeMute: return "VolumeMute"; + case KeyCode.VolumeDown: return "VolumeDown"; + case KeyCode.VolumeUp: return "VolumeUp"; + case KeyCode.Comma: return "Comma"; + case KeyCode.Period: return "Period"; + case KeyCode.Slash: return "Slash"; + case KeyCode.BackQuote: return "BackQuote"; + case KeyCode.OpenBracket: return "OpenBracket"; + case KeyCode.BackSlash: return "BackSlash"; + case KeyCode.CloseBracket: return "CloseBracket"; + case KeyCode.Quote: return "Quote"; + case KeyCode.Meta: return "Meta"; + case KeyCode.AltGr: return "AltGr"; + case KeyCode.WinIcoHelp: return "WinIcoHelp"; + case KeyCode.WinIco00: return "WinIco00"; + case KeyCode.WinIcoClear: return "WinIcoClear"; + case KeyCode.WinOemReset: return "WinOemReset"; + case KeyCode.WinOemJump: return "WinOemJump"; + case KeyCode.WinOemPA1: return "WinOemPA1"; + case KeyCode.WinOemPA2: return "WinOemPA2"; + case KeyCode.WinOemPA3: return "WinOemPA3"; + case KeyCode.WinOemWSCTRL: return "WinOemWSCTRL"; + case KeyCode.WinOemCUSEL: return "WinOemCUSEL"; + case KeyCode.WinOemATTN: return "WinOemATTN"; + case KeyCode.WinOemFinish: return "WinOemFinish"; + case KeyCode.WinOemCopy: return "WinOemCopy"; + case KeyCode.WinOemAuto: return "WinOemAuto"; + case KeyCode.WinOemENLW: return "WinOemENLW"; + case KeyCode.WinOemBackTab: return "WinOemBackTab"; + case KeyCode.ATTN: return "ATTN"; + case KeyCode.CRSEL: return "CRSEL"; + case KeyCode.EXSEL: return "EXSEL"; + case KeyCode.EREOF: return "EREOF"; + case KeyCode.Play: return "Play"; + case KeyCode.Zoom: return "Zoom"; + case KeyCode.PA1: return "PA1"; + case KeyCode.WinOemClear: return "WinOemClear"; + } + return String.fromCharCode(keycode); + } +} diff --git a/Sources/armory/ui/Popup.hx b/Sources/armory/ui/Popup.hx new file mode 100644 index 00000000..1cfa4b49 --- /dev/null +++ b/Sources/armory/ui/Popup.hx @@ -0,0 +1,127 @@ +package armory.ui; + +import zui.Zui; +import kha.System; + +@:access(zui.Zui) +class Popup { + public static var show = false; + + static var ui: Zui = null; + static var hwnd = new Handle(); + static var boxTitle = ""; + static var boxText = ""; + static var boxCommands: Zui->Void = null; + static var modalX = 0; + static var modalY = 0; + static var modalW = 400; + static var modalH = 160; + + public static function render(g: kha.graphics2.Graphics) { + if (boxCommands == null) { + ui.begin(g); + if (ui.window(hwnd, modalX, modalY, modalW, modalH)) { + drawTitle(g); + + for (line in boxText.split("\n")) { + ui.text(line); + } + + ui._y = ui._h - ui.t.BUTTON_H - 10; + ui.row([1/3, 1/3, 1/3]); + ui.endElement(); + if (ui.button("OK")) { + show = false; + } + } + ui.end(); + } + else { + ui.begin(g); + if (ui.window(hwnd, modalX, modalY, modalW, modalH)) { + drawTitle(g); + + ui._y += 10; + boxCommands(ui); + } + ui.end(); + } + } + + public static function drawTitle(g: kha.graphics2.Graphics) { + if (boxTitle != "") { + g.color = ui.t.SEPARATOR_COL; + ui.drawRect(g, true, ui._x, ui._y, ui._w, ui.t.BUTTON_H); + + g.color = ui.t.TEXT_COL; + ui.text(boxTitle); + } + } + + public static function update() { + var inUse = ui.comboSelectedHandle != null; + + // Close popup + if (ui.inputStarted && !inUse) { + if (ui.inputX < modalX || ui.inputX > modalX + modalW || ui.inputY < modalY || ui.inputY > modalY + modalH) { + show = false; + } + } + } + + /** + Displays a message box with a title, a text body and a centered "OK" button. + @param ui the Zui instance for the popup + @param title the title to display + @param text the text to display + **/ + public static function showMessage(ui: Zui, title: String, text: String) { + Popup.ui = ui; + init(); + + boxTitle = title; + boxText = text; + boxCommands = null; + } + + /** + Displays a popup box with custom drawing code. + @param ui the Zui instance for the popup + @param commands the function for drawing the popup's content + @param mx the x position of the popup. -1 = screen center (defaults to -1) + @param my the y position of the popup. -1 = screen center (defaults to -1) + @param mw the width of the popup (defaults to 400) + @param mh the height of the popup (defaults to 160) + **/ + public static function showCustom(ui: Zui, commands: Zui->Void = null, mx = -1, my = -1, mw = 400, mh = 160) { + Popup.ui = ui; + init(mx, my, mw, mh); + + boxTitle = ""; + boxText = ""; + boxCommands = commands; + } + + static function init(mx = -1, my = -1, mw = 400, mh = 160) { + var appW = System.windowWidth(); + var appH = System.windowHeight(); + + modalX = mx; + modalY = my; + modalW = Std.int(mw * ui.SCALE()); + modalH = Std.int(mh * ui.SCALE()); + + // Center popup window if no value is given + if (mx == -1) modalX = Std.int(appW / 2 - modalW / 2); + if (my == -1) modalY = Std.int(appH / 2 - modalH / 2); + + // Limit popup position to screen + modalX = Std.int(Math.max(0, Math.min(modalX, appW - modalW))); + modalY = Std.int(Math.max(0, Math.min(modalY, appH - modalH))); + + hwnd.dragX = 0; + hwnd.dragY = 0; + hwnd.scrollOffset = 0.0; + show = true; + } +}