2099 lines
64 KiB
Haxe
2099 lines
64 KiB
Haxe
|
package zui;
|
||
|
|
||
|
// Immediate Mode UI Library
|
||
|
// https://github.com/leenkx3d/zui
|
||
|
|
||
|
import kha.input.Mouse;
|
||
|
import kha.input.Pen;
|
||
|
import kha.input.Surface;
|
||
|
import kha.input.Keyboard;
|
||
|
import kha.input.KeyCode;
|
||
|
import kha.graphics2.Graphics;
|
||
|
|
||
|
@:structInit
|
||
|
typedef ZuiOptions = {
|
||
|
font: kha.Font,
|
||
|
?theme: zui.Themes.TTheme,
|
||
|
?khaWindowId: Int,
|
||
|
?scaleFactor: Float,
|
||
|
?autoNotifyInput: Bool,
|
||
|
?color_wheel: kha.Image,
|
||
|
?black_white_gradient: kha.Image,
|
||
|
}
|
||
|
|
||
|
class Zui {
|
||
|
public var isScrolling = false; // Use to limit other activities
|
||
|
public var isTyping = false;
|
||
|
public var enabled = true; // Current element state
|
||
|
public var isStarted = false;
|
||
|
public var isPushed = false;
|
||
|
public var isHovered = false;
|
||
|
public var isReleased = false;
|
||
|
public var changed = false; // Global elements change check
|
||
|
public var imageInvertY = false;
|
||
|
public var scrollEnabled = true;
|
||
|
public var alwaysRedraw = false; // Hurts performance
|
||
|
public var highlightOnSelect = true; // Highlight text edit contents on selection
|
||
|
public var tabSwitchEnabled = true; // Allow switching focus to the next element by pressing tab
|
||
|
public var textColoring: TTextColoring = null; // Set coloring scheme for drawString() calls
|
||
|
public var windowBorderTop = 0;
|
||
|
public var windowBorderBottom = 0;
|
||
|
public var windowBorderLeft = 0;
|
||
|
public var windowBorderRight = 0;
|
||
|
var highlightFullRow = false;
|
||
|
public static var current: Zui = null;
|
||
|
public static var onBorderHover: Handle->Int->Void = null; // Mouse over window border, use for resizing
|
||
|
public static var onTextHover: Void->Void = null; // Mouse over text input, use to set I-cursor
|
||
|
public static var onDeselectText: Void->Void = null; // Text editing finished
|
||
|
public static var alwaysRedrawWindow = true; // Redraw cached window texture each frame or on changes only
|
||
|
public static var keyRepeat = true; // Emulate key repeat for non-character keys
|
||
|
public static var dynamicGlyphLoad = true; // Allow text input fields to push new glyphs into the font atlas
|
||
|
public static var touchScroll = false; // Pan with finger to scroll
|
||
|
public static var touchHold = false; // Touch and hold finger for right click
|
||
|
public static var touchTooltip = false; // Show extra tooltips above finger / on-screen keyboard
|
||
|
var touchHoldActivated = false;
|
||
|
var sliderTooltip = false;
|
||
|
var sliderTooltipX = 0.0;
|
||
|
var sliderTooltipY = 0.0;
|
||
|
var sliderTooltipW = 0.0;
|
||
|
static var keyRepeatTime = 0.0;
|
||
|
|
||
|
public var inputRegistered = false;
|
||
|
public var inputEnabled = true;
|
||
|
public var inputX: Float; // Input position
|
||
|
public var inputY: Float;
|
||
|
public var inputStartedX: Float;
|
||
|
public var inputStartedY: Float;
|
||
|
public var inputDX: Float; // Delta
|
||
|
public var inputDY: Float;
|
||
|
public var inputWheelDelta = 0;
|
||
|
public var inputStarted: Bool; // Buttons
|
||
|
public var inputStartedR: Bool;
|
||
|
public var inputReleased: Bool;
|
||
|
public var inputReleasedR: Bool;
|
||
|
public var inputDown: Bool;
|
||
|
public var inputDownR: Bool;
|
||
|
public var penInUse: Bool;
|
||
|
public var isKeyPressed = false; // Keys
|
||
|
public var isKeyDown = false;
|
||
|
public var isShiftDown = false;
|
||
|
public var isCtrlDown = false;
|
||
|
public var isAltDown = false;
|
||
|
public var isADown = false;
|
||
|
public var isBackspaceDown = false;
|
||
|
public var isDeleteDown = false;
|
||
|
public var isEscapeDown = false;
|
||
|
public var isReturnDown = false;
|
||
|
public var isTabDown = false;
|
||
|
public var key: Null<KeyCode> = null;
|
||
|
public var char: String;
|
||
|
static var textToPaste = "";
|
||
|
static var textToCopy = "";
|
||
|
static var isCut = false;
|
||
|
static var isCopy = false;
|
||
|
static var isPaste = false;
|
||
|
static var copyReceiver: Zui = null;
|
||
|
static var copyFrame = 0;
|
||
|
|
||
|
var inputStartedTime = 0.0;
|
||
|
var cursorX = 0; // Text input
|
||
|
var highlightAnchor = 0;
|
||
|
var ratios: Array<Float>; // Splitting rows
|
||
|
var curRatio = -1;
|
||
|
var xBeforeSplit: Float;
|
||
|
var wBeforeSplit: Int;
|
||
|
|
||
|
public var g: Graphics; // Drawing
|
||
|
public var t: zui.Themes.TTheme;
|
||
|
public var ops: ZuiOptions;
|
||
|
var globalG: Graphics;
|
||
|
var rtTextPipeline: kha.graphics4.PipelineState; // Rendering text into rendertargets
|
||
|
|
||
|
var fontSize: Int;
|
||
|
var fontOffsetY: Float; // Precalculated offsets
|
||
|
var arrowOffsetX: Float;
|
||
|
var arrowOffsetY: Float;
|
||
|
var titleOffsetX: Float;
|
||
|
var buttonOffsetY: Float;
|
||
|
var checkOffsetX: Float;
|
||
|
var checkOffsetY: Float;
|
||
|
var checkSelectOffsetX: Float;
|
||
|
var checkSelectOffsetY: Float;
|
||
|
var radioOffsetX: Float;
|
||
|
var radioOffsetY: Float;
|
||
|
var radioSelectOffsetX: Float;
|
||
|
var radioSelectOffsetY: Float;
|
||
|
var scrollAlign = 0.0;
|
||
|
var imageScrollAlign = true;
|
||
|
|
||
|
var _x: Float; // Cursor(stack) position
|
||
|
var _y: Float;
|
||
|
var _w: Int;
|
||
|
var _h: Int;
|
||
|
|
||
|
var _windowX = 0.0; // Window state
|
||
|
var _windowY = 0.0;
|
||
|
var _windowW: Float;
|
||
|
var _windowH: Float;
|
||
|
var currentWindow: Handle;
|
||
|
var windowEnded = true;
|
||
|
var scrollHandle: Handle = null; // Window or slider being scrolled
|
||
|
var dragHandle: Handle = null; // Window being dragged
|
||
|
var windowHeaderW = 0.0;
|
||
|
var windowHeaderH = 0.0;
|
||
|
var restoreX = -1.0;
|
||
|
var restoreY = -1.0;
|
||
|
|
||
|
var textSelectedHandle: Handle = null;
|
||
|
var textSelected: String;
|
||
|
var submitTextHandle: Handle = null;
|
||
|
var textToSubmit = "";
|
||
|
var tabPressed = false;
|
||
|
var tabPressedHandle: Handle = null;
|
||
|
var comboSelectedHandle: Handle = null;
|
||
|
var comboSelectedWindow: Handle = null;
|
||
|
var comboSelectedAlign: Align;
|
||
|
var comboSelectedTexts: Array<String>;
|
||
|
var comboSelectedLabel: String;
|
||
|
var comboSelectedX: Int;
|
||
|
var comboSelectedY: Int;
|
||
|
var comboSelectedW: Int;
|
||
|
var comboSearchBar = false;
|
||
|
var submitComboHandle: Handle = null;
|
||
|
var comboToSubmit = 0;
|
||
|
var comboInitialValue = 0;
|
||
|
var tooltipText = "";
|
||
|
var tooltipImg: kha.Image = null;
|
||
|
var tooltipImgMaxWidth: Null<Int> = null;
|
||
|
var tooltipInvertY = false;
|
||
|
var tooltipX = 0.0;
|
||
|
var tooltipY = 0.0;
|
||
|
var tooltipShown = false;
|
||
|
var tooltipWait = false;
|
||
|
var tooltipTime = 0.0;
|
||
|
var tabNames: Array<String> = null; // Number of tab calls since window begin
|
||
|
var tabColors: Array<Int> = null;
|
||
|
var tabHandle: Handle = null;
|
||
|
var tabScroll = 0.0;
|
||
|
var tabVertical = false;
|
||
|
var sticky = false;
|
||
|
var scissor = false;
|
||
|
|
||
|
var elementsBaked = false;
|
||
|
var checkSelectImage: kha.Image = null;
|
||
|
|
||
|
public function new(ops: ZuiOptions) {
|
||
|
if (ops.theme == null) ops.theme = Themes.dark;
|
||
|
t = ops.theme;
|
||
|
if (ops.khaWindowId == null) ops.khaWindowId = 0;
|
||
|
if (ops.scaleFactor == null) ops.scaleFactor = 1.0;
|
||
|
if (ops.autoNotifyInput == null) ops.autoNotifyInput = true;
|
||
|
this.ops = ops;
|
||
|
setScale(ops.scaleFactor);
|
||
|
if (ops.autoNotifyInput) registerInput();
|
||
|
if (copyReceiver == null) {
|
||
|
copyReceiver = this;
|
||
|
kha.System.notifyOnCutCopyPaste(onCut, onCopy, onPaste);
|
||
|
kha.System.notifyOnFrames(function(frames: Array<kha.Framebuffer>) {
|
||
|
// Set isCopy to false on next frame
|
||
|
if ((isCopy || isPaste) && ++copyFrame > 1) {
|
||
|
isCopy = isCut = isPaste = false;
|
||
|
}
|
||
|
// Clear unpasted text on next frame
|
||
|
else if (copyFrame > 1 && ++copyFrame > 2) {
|
||
|
copyFrame = 0;
|
||
|
textToPaste = "";
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
var rtTextVS = kha.graphics4.Graphics2.createTextVertexStructure();
|
||
|
rtTextPipeline = kha.graphics4.Graphics2.createTextPipeline(rtTextVS);
|
||
|
rtTextPipeline.alphaBlendSource = BlendOne;
|
||
|
rtTextPipeline.compile();
|
||
|
}
|
||
|
|
||
|
public function setScale(factor: Float) {
|
||
|
ops.scaleFactor = factor;
|
||
|
fontSize = FONT_SIZE();
|
||
|
var fontHeight = ops.font.height(fontSize);
|
||
|
fontOffsetY = (ELEMENT_H() - fontHeight) / 2; // Precalculate offsets
|
||
|
arrowOffsetY = (ELEMENT_H() - ARROW_SIZE()) / 2;
|
||
|
arrowOffsetX = arrowOffsetY;
|
||
|
titleOffsetX = (arrowOffsetX * 2 + ARROW_SIZE()) / SCALE();
|
||
|
buttonOffsetY = (ELEMENT_H() - BUTTON_H()) / 2;
|
||
|
checkOffsetY = (ELEMENT_H() - CHECK_SIZE()) / 2;
|
||
|
checkOffsetX = checkOffsetY;
|
||
|
checkSelectOffsetY = (CHECK_SIZE() - CHECK_SELECT_SIZE()) / 2;
|
||
|
checkSelectOffsetX = checkSelectOffsetY;
|
||
|
radioOffsetY = (ELEMENT_H() - CHECK_SIZE()) / 2;
|
||
|
radioOffsetX = radioOffsetY;
|
||
|
radioSelectOffsetY = (CHECK_SIZE() - CHECK_SELECT_SIZE()) / 2;
|
||
|
radioSelectOffsetX = radioSelectOffsetY;
|
||
|
elementsBaked = false;
|
||
|
}
|
||
|
|
||
|
function bakeElements() {
|
||
|
if (checkSelectImage != null) {
|
||
|
checkSelectImage.unload();
|
||
|
}
|
||
|
checkSelectImage = kha.Image.createRenderTarget(Std.int(CHECK_SELECT_SIZE()), Std.int(CHECK_SELECT_SIZE()), null, NoDepthAndStencil, 1);
|
||
|
var g = checkSelectImage.g2;
|
||
|
g.begin(true, 0x00000000);
|
||
|
g.color = t.ACCENT_SELECT_COL;
|
||
|
g.drawLine(0, 0, checkSelectImage.width, checkSelectImage.height, 2 * SCALE());
|
||
|
g.drawLine(checkSelectImage.width, 0, 0, checkSelectImage.height, 2 * SCALE());
|
||
|
g.end();
|
||
|
elementsBaked = true;
|
||
|
}
|
||
|
|
||
|
public function remove() { // Clean up
|
||
|
if (ops.autoNotifyInput) unregisterInput();
|
||
|
}
|
||
|
|
||
|
public function registerInput() {
|
||
|
if (inputRegistered) return;
|
||
|
Mouse.get().notifyWindowed(ops.khaWindowId, onMouseDown, onMouseUp, onMouseMove, onMouseWheel);
|
||
|
if (Pen.get() != null) Pen.get().notify(onPenDown, onPenUp, onPenMove);
|
||
|
Keyboard.get().notify(onKeyDown, onKeyUp, onKeyPress);
|
||
|
#if (kha_android || kha_ios)
|
||
|
if (Surface.get() != null) Surface.get().notify(onTouchDown, onTouchUp, onTouchMove);
|
||
|
#end
|
||
|
// Reset mouse delta on foreground
|
||
|
kha.System.notifyOnApplicationState(function() { inputDX = inputDY = 0; }, null, null, null, null);
|
||
|
inputRegistered = true;
|
||
|
}
|
||
|
|
||
|
public function unregisterInput() {
|
||
|
if (!inputRegistered) return;
|
||
|
Mouse.get().removeWindowed(ops.khaWindowId, onMouseDown, onMouseUp, onMouseMove, onMouseWheel);
|
||
|
if (Pen.get() != null) Pen.get().remove(onPenDown, onPenUp, onPenMove);
|
||
|
Keyboard.get().remove(onKeyDown, onKeyUp, onKeyPress);
|
||
|
#if (kha_android || kha_ios)
|
||
|
if (Surface.get() != null) Surface.get().remove(onTouchDown, onTouchUp, onTouchMove);
|
||
|
#end
|
||
|
endInput();
|
||
|
isShiftDown = isCtrlDown = isAltDown = false;
|
||
|
inputX = inputY = 0;
|
||
|
inputRegistered = false;
|
||
|
}
|
||
|
|
||
|
public function begin(g: Graphics) { // Begin UI drawing
|
||
|
if (!elementsBaked) bakeElements();
|
||
|
changed = false;
|
||
|
globalG = g;
|
||
|
current = this;
|
||
|
_x = 0; // Reset cursor
|
||
|
_y = 0;
|
||
|
_w = 0;
|
||
|
_h = 0;
|
||
|
}
|
||
|
|
||
|
public function end(last = true) { // End drawing
|
||
|
if (!windowEnded) endWindow();
|
||
|
drawCombo(); // Handle active combo
|
||
|
drawTooltip(true);
|
||
|
tabPressedHandle = null;
|
||
|
if (last) endInput();
|
||
|
}
|
||
|
|
||
|
public function beginRegion(g: Graphics, x: Int, y: Int, w: Int) {
|
||
|
if (!elementsBaked) {
|
||
|
g.end();
|
||
|
bakeElements();
|
||
|
g.begin(false);
|
||
|
}
|
||
|
changed = false;
|
||
|
globalG = g;
|
||
|
this.g = g;
|
||
|
currentWindow = null;
|
||
|
tooltipText = "";
|
||
|
tooltipImg = null;
|
||
|
_windowX = 0;
|
||
|
_windowY = 0;
|
||
|
_windowW = w;
|
||
|
_x = x;
|
||
|
_y = y;
|
||
|
_w = w;
|
||
|
}
|
||
|
|
||
|
public function endRegion(last = true) {
|
||
|
drawTooltip(false);
|
||
|
tabPressedHandle = null;
|
||
|
if (last) endInput();
|
||
|
}
|
||
|
|
||
|
// Sticky region ignores window scrolling
|
||
|
public function beginSticky() {
|
||
|
sticky = true;
|
||
|
_y -= currentWindow.scrollOffset;
|
||
|
}
|
||
|
|
||
|
public function endSticky() {
|
||
|
sticky = false;
|
||
|
scissor = true;
|
||
|
g.scissor(0, Std.int(_y), Std.int(_windowW), Std.int(_windowH - _y));
|
||
|
windowHeaderH += _y - windowHeaderH;
|
||
|
_y += currentWindow.scrollOffset;
|
||
|
isHovered = false;
|
||
|
}
|
||
|
|
||
|
function endInput() {
|
||
|
isKeyPressed = false;
|
||
|
inputStarted = false;
|
||
|
inputStartedR = false;
|
||
|
inputReleased = false;
|
||
|
inputReleasedR = false;
|
||
|
inputDX = 0;
|
||
|
inputDY = 0;
|
||
|
inputWheelDelta = 0;
|
||
|
penInUse = false;
|
||
|
if (keyRepeat && isKeyDown && kha.Scheduler.time() - keyRepeatTime > 0.05) {
|
||
|
if (key == KeyCode.Backspace || key == KeyCode.Delete || key == KeyCode.Left || key == KeyCode.Right || key == KeyCode.Up || key == KeyCode.Down) {
|
||
|
keyRepeatTime = kha.Scheduler.time();
|
||
|
isKeyPressed = true;
|
||
|
}
|
||
|
}
|
||
|
if (touchHold && inputDown && inputX == inputStartedX && inputY == inputStartedY && inputStartedTime > 0 && kha.Scheduler.time() - inputStartedTime > 0.7) {
|
||
|
touchHoldActivated = true;
|
||
|
inputReleasedR = true;
|
||
|
inputStartedTime = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function inputChanged(): Bool {
|
||
|
return inputDX != 0 || inputDY != 0 || inputWheelDelta != 0 || inputStarted || inputStartedR || inputReleased || inputReleasedR || inputDown || inputDownR || isKeyPressed;
|
||
|
}
|
||
|
|
||
|
public function windowDirty(handle: Handle, x: Int, y: Int, w: Int, h: Int): Bool {
|
||
|
var wx = x + handle.dragX;
|
||
|
var wy = y + handle.dragY;
|
||
|
var inputChanged = getInputInRect(wx, wy, w, h) && inputChanged();
|
||
|
return alwaysRedraw || isScrolling || inputChanged;
|
||
|
}
|
||
|
|
||
|
// Returns true if redraw is needed
|
||
|
public function window(handle: Handle, x: Int, y: Int, w: Int, h: Int, drag = false): Bool {
|
||
|
if (handle.texture == null || w != handle.texture.width || h != handle.texture.height) {
|
||
|
resize(handle, w, h);
|
||
|
}
|
||
|
|
||
|
if (!windowEnded) endWindow(); // End previous window if necessary
|
||
|
windowEnded = false;
|
||
|
|
||
|
g = handle.texture.g2; // Set g
|
||
|
currentWindow = handle;
|
||
|
_windowX = x + handle.dragX;
|
||
|
_windowY = y + handle.dragY;
|
||
|
_windowW = w;
|
||
|
_windowH = h;
|
||
|
windowHeaderW = 0;
|
||
|
windowHeaderH = 0;
|
||
|
|
||
|
if (windowDirty(handle, x, y, w, h)) {
|
||
|
handle.redraws = 2;
|
||
|
}
|
||
|
|
||
|
if (onBorderHover != null) {
|
||
|
if (getInputInRect(_windowX - 4, _windowY, 8, _windowH)) {
|
||
|
onBorderHover(handle, 0);
|
||
|
}
|
||
|
else if (getInputInRect(_windowX + _windowW - 4, _windowY, 8, _windowH)) {
|
||
|
onBorderHover(handle, 1);
|
||
|
}
|
||
|
else if (getInputInRect(_windowX, _windowY - 4, _windowW, 8)) {
|
||
|
onBorderHover(handle, 2);
|
||
|
}
|
||
|
else if (getInputInRect(_windowX, _windowY + _windowH - 4, _windowW, 8)) {
|
||
|
onBorderHover(handle, 3);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (handle.redraws <= 0) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
_x = 0;
|
||
|
_y = handle.scrollOffset;
|
||
|
if (handle.layout == Horizontal) w = Std.int(ELEMENT_W());
|
||
|
_w = !handle.scrollEnabled ? w : w - SCROLL_W(); // Exclude scrollbar if present
|
||
|
_h = h;
|
||
|
tooltipText = "";
|
||
|
tooltipImg = null;
|
||
|
tabNames = null;
|
||
|
|
||
|
if (t.FILL_WINDOW_BG) {
|
||
|
g.begin(true, t.WINDOW_BG_COL);
|
||
|
}
|
||
|
else {
|
||
|
g.begin(true, 0x00000000);
|
||
|
g.color = t.WINDOW_BG_COL;
|
||
|
g.fillRect(_x, _y - handle.scrollOffset, handle.lastMaxX, handle.lastMaxY);
|
||
|
}
|
||
|
|
||
|
handle.dragEnabled = drag;
|
||
|
if (drag) {
|
||
|
if (inputStarted && getInputInRect(_windowX, _windowY, _windowW, HEADER_DRAG_H())) {
|
||
|
dragHandle = handle;
|
||
|
}
|
||
|
else if (inputReleased) {
|
||
|
dragHandle = null;
|
||
|
}
|
||
|
if (handle == dragHandle) {
|
||
|
handle.redraws = 2;
|
||
|
handle.dragX += Std.int(inputDX);
|
||
|
handle.dragY += Std.int(inputDY);
|
||
|
}
|
||
|
_y += HEADER_DRAG_H(); // Header offset
|
||
|
windowHeaderH += HEADER_DRAG_H();
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
public function endWindow(bindGlobalG = true) {
|
||
|
var handle = currentWindow;
|
||
|
if (handle == null) return;
|
||
|
if (handle.redraws > 0 || isScrolling) {
|
||
|
if (scissor) {
|
||
|
scissor = false;
|
||
|
g.disableScissor();
|
||
|
}
|
||
|
|
||
|
if (tabNames != null) drawTabs();
|
||
|
|
||
|
if (handle.dragEnabled) { // Draggable header
|
||
|
g.color = t.SEPARATOR_COL;
|
||
|
g.fillRect(0, 0, _windowW, HEADER_DRAG_H());
|
||
|
}
|
||
|
|
||
|
var wh = _windowH - windowHeaderH; // Exclude header
|
||
|
var fullHeight = _y - handle.scrollOffset - windowHeaderH;
|
||
|
if (fullHeight < wh || handle.layout == Horizontal || !scrollEnabled) { // Disable scrollbar
|
||
|
handle.scrollEnabled = false;
|
||
|
handle.scrollOffset = 0;
|
||
|
}
|
||
|
else { // Draw window scrollbar if necessary
|
||
|
handle.scrollEnabled = true;
|
||
|
if (tabScroll < 0) { // Restore tab
|
||
|
handle.scrollOffset = tabScroll;
|
||
|
tabScroll = 0;
|
||
|
}
|
||
|
var wy = _windowY + windowHeaderH;
|
||
|
var amountToScroll = fullHeight - wh;
|
||
|
var amountScrolled = -handle.scrollOffset;
|
||
|
var ratio = amountScrolled / amountToScroll;
|
||
|
var barH = wh * Math.abs(wh / fullHeight);
|
||
|
barH = Math.max(barH, ELEMENT_H());
|
||
|
|
||
|
var totalScrollableArea = wh - barH;
|
||
|
var e = amountToScroll / totalScrollableArea;
|
||
|
var barY = totalScrollableArea * ratio + windowHeaderH;
|
||
|
var barFocus = getInputInRect(_windowX + _windowW - SCROLL_W(), barY + _windowY, SCROLL_W(), barH);
|
||
|
|
||
|
if (inputStarted && barFocus) { // Start scrolling
|
||
|
scrollHandle = handle;
|
||
|
isScrolling = true;
|
||
|
}
|
||
|
|
||
|
var scrollDelta: Float = inputWheelDelta;
|
||
|
if (touchScroll && inputDown && inputDY != 0 && inputX > _windowX + windowHeaderW&& inputY > _windowY + windowHeaderH) {
|
||
|
isScrolling = true;
|
||
|
scrollDelta = -inputDY / 20;
|
||
|
}
|
||
|
if (handle == scrollHandle) { // Scroll
|
||
|
scroll(inputDY * e, fullHeight);
|
||
|
}
|
||
|
else if (scrollDelta != 0 && comboSelectedHandle == null &&
|
||
|
getInputInRect(_windowX, wy, _windowW, wh)) { // Wheel
|
||
|
scroll(scrollDelta * ELEMENT_H(), fullHeight);
|
||
|
}
|
||
|
|
||
|
// Stay in bounds
|
||
|
if (handle.scrollOffset > 0) {
|
||
|
handle.scrollOffset = 0;
|
||
|
}
|
||
|
else if (fullHeight + handle.scrollOffset < wh) {
|
||
|
handle.scrollOffset = wh - fullHeight;
|
||
|
}
|
||
|
|
||
|
g.color = t.ACCENT_COL; // Bar
|
||
|
var scrollbarFocus = getInputInRect(_windowX + _windowW - SCROLL_W(), wy, SCROLL_W(), wh);
|
||
|
var barW = (scrollbarFocus || handle == scrollHandle) ? SCROLL_W() : SCROLL_W() / 3;
|
||
|
g.fillRect(_windowW - barW - scrollAlign, barY, barW, barH);
|
||
|
}
|
||
|
|
||
|
handle.lastMaxX = _x;
|
||
|
handle.lastMaxY = _y;
|
||
|
if (handle.layout == Vertical) handle.lastMaxX += _windowW;
|
||
|
else handle.lastMaxY += _windowH;
|
||
|
handle.redraws--;
|
||
|
|
||
|
g.end();
|
||
|
}
|
||
|
|
||
|
windowEnded = true;
|
||
|
|
||
|
// Draw window texture
|
||
|
if (alwaysRedrawWindow || handle.redraws > -4) {
|
||
|
if (bindGlobalG) globalG.begin(false);
|
||
|
globalG.color = t.WINDOW_TINT_COL;
|
||
|
globalG.drawImage(handle.texture, _windowX, _windowY);
|
||
|
if (bindGlobalG) globalG.end();
|
||
|
if (handle.redraws <= 0) handle.redraws--;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function scroll(delta: Float, fullHeight: Float) {
|
||
|
currentWindow.scrollOffset -= delta;
|
||
|
}
|
||
|
|
||
|
public function tab(handle: Handle, text: String, vertical = false, color: Int = -1): Bool {
|
||
|
if (tabNames == null) { // First tab
|
||
|
tabNames = [];
|
||
|
tabColors = [];
|
||
|
tabHandle = handle;
|
||
|
tabVertical = vertical;
|
||
|
_w -= tabVertical ? Std.int(ELEMENT_OFFSET() + ELEMENT_W() - 1 * SCALE()) : 0; // Shrink window area by width of vertical tabs
|
||
|
vertical ?
|
||
|
windowHeaderW += ELEMENT_W() :
|
||
|
windowHeaderH += BUTTON_H() + buttonOffsetY + ELEMENT_OFFSET();
|
||
|
restoreX = inputX; // Mouse in tab header, disable clicks for tab content
|
||
|
restoreY = inputY;
|
||
|
if (!vertical && getInputInRect(_windowX, _windowY, _windowW, windowHeaderH)) {
|
||
|
inputX = inputY = -1;
|
||
|
}
|
||
|
vertical ? { _x += windowHeaderW + 6; _w -= 6; } : _y += windowHeaderH + 3;
|
||
|
}
|
||
|
tabNames.push(text);
|
||
|
tabColors.push(color);
|
||
|
return handle.position == tabNames.length - 1;
|
||
|
}
|
||
|
|
||
|
function drawTabs() {
|
||
|
inputX = restoreX;
|
||
|
inputY = restoreY;
|
||
|
if (currentWindow == null) return;
|
||
|
var tabX = 0.0;
|
||
|
var tabY = 0.0;
|
||
|
var tabHMin = Std.int(BUTTON_H() * 1.1);
|
||
|
var headerH = currentWindow.dragEnabled ? HEADER_DRAG_H() : 0;
|
||
|
var tabH = t.FULL_TABS && tabVertical ? Std.int((_windowH - headerH) / tabNames.length) : tabHMin;
|
||
|
var origy = _y;
|
||
|
_y = headerH;
|
||
|
tabHandle.changed = false;
|
||
|
|
||
|
if (isCtrlDown && isTabDown) { // Next tab
|
||
|
tabHandle.position++;
|
||
|
if (tabHandle.position >= tabNames.length) tabHandle.position = 0;
|
||
|
tabHandle.changed = true;
|
||
|
isTabDown = false;
|
||
|
}
|
||
|
|
||
|
if (tabHandle.position >= tabNames.length) tabHandle.position = tabNames.length - 1;
|
||
|
|
||
|
g.color = t.SEPARATOR_COL; // Tab background
|
||
|
tabVertical ?
|
||
|
g.fillRect(0, _y, ELEMENT_W(), _windowH) :
|
||
|
g.fillRect(0, _y, _windowW, buttonOffsetY + tabH + 2);
|
||
|
|
||
|
g.color = t.ACCENT_COL; // Underline tab buttons
|
||
|
tabVertical ?
|
||
|
g.fillRect(ELEMENT_W(), _y, 1, _windowH) :
|
||
|
g.fillRect(buttonOffsetY, _y + buttonOffsetY + tabH + 2, _windowW - buttonOffsetY * 2, 1);
|
||
|
|
||
|
var basey = tabVertical ? _y : _y + 2;
|
||
|
|
||
|
for (i in 0...tabNames.length) {
|
||
|
_x = tabX;
|
||
|
_y = basey + tabY;
|
||
|
_w = tabVertical ? Std.int(ELEMENT_W() - 1 * SCALE()) :
|
||
|
t.FULL_TABS ? Std.int(_windowW / tabNames.length) :
|
||
|
Std.int(ops.font.width(fontSize, tabNames[i]) + buttonOffsetY * 2 + 18 * SCALE());
|
||
|
var released = getReleased(tabH);
|
||
|
var pushed = getPushed(tabH);
|
||
|
var hover = getHover(tabH);
|
||
|
if (released) {
|
||
|
var h = tabHandle.nest(tabHandle.position); // Restore tab scroll
|
||
|
h.scrollOffset = currentWindow.scrollOffset;
|
||
|
h = tabHandle.nest(i);
|
||
|
tabScroll = h.scrollOffset;
|
||
|
tabHandle.position = i; // Set new tab
|
||
|
currentWindow.redraws = 3;
|
||
|
tabHandle.changed = true;
|
||
|
}
|
||
|
var selected = tabHandle.position == i;
|
||
|
|
||
|
g.color = (pushed || hover) ? t.BUTTON_HOVER_COL :
|
||
|
tabColors[i] != -1 ? tabColors[i] :
|
||
|
selected ? t.WINDOW_BG_COL :
|
||
|
t.SEPARATOR_COL;
|
||
|
tabVertical ?
|
||
|
tabY += tabH + 1 :
|
||
|
tabX += _w + 1;
|
||
|
drawRect(g, true, _x + buttonOffsetY, _y + buttonOffsetY, _w, tabH);
|
||
|
g.color = selected ? t.BUTTON_TEXT_COL : t.LABEL_COL;
|
||
|
drawString(g, tabNames[i], null, (tabH - tabHMin) / 2, t.FULL_TABS ? Align.Center : Align.Left);
|
||
|
|
||
|
if (selected) { // Hide underline for active tab
|
||
|
if (tabVertical) {
|
||
|
// g.color = t.WINDOW_BG_COL;
|
||
|
// g.fillRect(_x + buttonOffsetY + _w - 1, _y + buttonOffsetY - 1, 2, tabH + buttonOffsetY);
|
||
|
g.color = t.HIGHLIGHT_COL;
|
||
|
g.fillRect(_x + buttonOffsetY, _y + buttonOffsetY - 1, 2, tabH + buttonOffsetY);
|
||
|
}
|
||
|
else {
|
||
|
g.color = t.WINDOW_BG_COL;
|
||
|
g.fillRect(_x + buttonOffsetY + 1, _y + buttonOffsetY + tabH, _w - 1, 1);
|
||
|
g.color = t.HIGHLIGHT_COL;
|
||
|
g.fillRect(_x + buttonOffsetY, _y + buttonOffsetY, _w, 2);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
_x = 0; // Restore positions
|
||
|
_y = origy;
|
||
|
_w = Std.int(!currentWindow.scrollEnabled ? _windowW : _windowW - SCROLL_W());
|
||
|
}
|
||
|
|
||
|
public function panel(handle: Handle, text: String, isTree = false, filled = true, pack = true): Bool {
|
||
|
if (!isVisible(ELEMENT_H())) {
|
||
|
endElement();
|
||
|
return handle.selected;
|
||
|
}
|
||
|
if (getReleased()) {
|
||
|
handle.selected = !handle.selected;
|
||
|
handle.changed = changed = true;
|
||
|
}
|
||
|
if (filled) {
|
||
|
g.color = t.PANEL_BG_COL;
|
||
|
drawRect(g, true, _x, _y, _w, ELEMENT_H());
|
||
|
}
|
||
|
|
||
|
isTree ? drawTree(handle.selected) : drawArrow(handle.selected);
|
||
|
|
||
|
g.color = t.LABEL_COL; // Title
|
||
|
drawString(g, text, titleOffsetX, 0);
|
||
|
|
||
|
endElement();
|
||
|
if (pack && !handle.selected) _y -= ELEMENT_OFFSET();
|
||
|
|
||
|
return handle.selected;
|
||
|
}
|
||
|
|
||
|
public function image(image: kha.Image, tint = 0xffffffff, h: Null<Float> = null, sx = 0, sy = 0, sw = 0, sh = 0): State {
|
||
|
var iw = (sw > 0 ? sw : image.width) * SCALE();
|
||
|
var ih = (sh > 0 ? sh : image.height) * SCALE();
|
||
|
var w = Math.min(iw, _w);
|
||
|
var x = _x;
|
||
|
var scroll = currentWindow != null ? currentWindow.scrollEnabled : false;
|
||
|
var r = curRatio == -1 ? 1.0 : getRatio(ratios[curRatio], 1);
|
||
|
if (imageScrollAlign) { // Account for scrollbar size
|
||
|
w = Math.min(iw, _w - buttonOffsetY * 2);
|
||
|
x += buttonOffsetY;
|
||
|
if (!scroll) {
|
||
|
w -= SCROLL_W() * r;
|
||
|
x += SCROLL_W() * r / 2;
|
||
|
}
|
||
|
}
|
||
|
else if (scroll) w += SCROLL_W() * r;
|
||
|
|
||
|
// Image size
|
||
|
var ratio = h == null ?
|
||
|
w / iw :
|
||
|
h / ih;
|
||
|
h == null ?
|
||
|
h = ih * ratio :
|
||
|
w = iw * ratio;
|
||
|
|
||
|
if (!isVisible(h)) {
|
||
|
endElement(h);
|
||
|
return State.Idle;
|
||
|
}
|
||
|
var started = getStarted(h);
|
||
|
var down = getPushed(h);
|
||
|
var released = getReleased(h);
|
||
|
var hover = getHover(h);
|
||
|
if (curRatio == -1 && (started || down || released || hover)) {
|
||
|
if (inputX < _windowX + _x || inputX > _windowX + _x + w) {
|
||
|
started = down = released = hover = false;
|
||
|
}
|
||
|
}
|
||
|
g.color = tint;
|
||
|
if (!enabled) fadeColor();
|
||
|
var h_float: Float = h; // TODO: hashlink fix
|
||
|
if (sw > 0) { // Source rect specified
|
||
|
imageInvertY ?
|
||
|
g.drawScaledSubImage(image, sx, sy, sw, sh, x, _y + h_float, w, -h_float) :
|
||
|
g.drawScaledSubImage(image, sx, sy, sw, sh, x, _y, w, h_float);
|
||
|
}
|
||
|
else {
|
||
|
imageInvertY ?
|
||
|
g.drawScaledImage(image, x, _y + h_float, w, -h_float) :
|
||
|
g.drawScaledImage(image, x, _y, w, h_float);
|
||
|
}
|
||
|
|
||
|
endElement(h);
|
||
|
return started ? State.Started : released ? State.Released : down ? State.Down : hover ? State.Hovered : State.Idle;
|
||
|
}
|
||
|
|
||
|
public function text(text: String, align = Align.Left, bg = 0x00000000): State {
|
||
|
if (text.indexOf("\n") >= 0) {
|
||
|
splitText(text, align, bg);
|
||
|
return State.Idle;
|
||
|
}
|
||
|
var h = Math.max(ELEMENT_H(), ops.font.height(fontSize));
|
||
|
if (!isVisible(h)) {
|
||
|
endElement(h + ELEMENT_OFFSET());
|
||
|
return State.Idle;
|
||
|
}
|
||
|
var started = getStarted(h);
|
||
|
var down = getPushed(h);
|
||
|
var released = getReleased(h);
|
||
|
var hover = getHover(h);
|
||
|
if (bg != 0x0000000) {
|
||
|
g.color = bg;
|
||
|
g.fillRect(_x + buttonOffsetY, _y + buttonOffsetY, _w - buttonOffsetY * 2, BUTTON_H());
|
||
|
}
|
||
|
g.color = t.TEXT_COL;
|
||
|
drawString(g, text, null, 0, align);
|
||
|
|
||
|
endElement(h + ELEMENT_OFFSET());
|
||
|
return started ? State.Started : released ? State.Released : down ? State.Down : State.Idle;
|
||
|
}
|
||
|
|
||
|
inline function splitText(lines: String, align = Align.Left, bg = 0x00000000) {
|
||
|
for (line in lines.split("\n")) text(line, align, bg);
|
||
|
}
|
||
|
|
||
|
function startTextEdit(handle: Handle, align = Align.Left) {
|
||
|
isTyping = true;
|
||
|
submitTextHandle = textSelectedHandle;
|
||
|
textToSubmit = textSelected;
|
||
|
textSelectedHandle = handle;
|
||
|
textSelected = handle.text;
|
||
|
cursorX = handle.text.length;
|
||
|
if (tabPressed) {
|
||
|
tabPressed = false;
|
||
|
isKeyPressed = false; // Prevent text deselect after tab press
|
||
|
}
|
||
|
else if (!highlightOnSelect) { // Set cursor to click location
|
||
|
setCursorToInput(align);
|
||
|
}
|
||
|
tabPressedHandle = handle;
|
||
|
highlightAnchor = highlightOnSelect ? 0 : cursorX;
|
||
|
if (Keyboard.get() != null) Keyboard.get().show();
|
||
|
}
|
||
|
|
||
|
function submitTextEdit() {
|
||
|
submitTextHandle.changed = submitTextHandle.text != textToSubmit;
|
||
|
submitTextHandle.text = textToSubmit;
|
||
|
submitTextHandle = null;
|
||
|
textToSubmit = "";
|
||
|
textSelected = "";
|
||
|
}
|
||
|
|
||
|
function updateTextEdit(align = Align.Left, editable = true, liveUpdate = false) {
|
||
|
var text = textSelected;
|
||
|
if (isKeyPressed) { // Process input
|
||
|
if (key == KeyCode.Left) { // Move cursor
|
||
|
if (cursorX > 0) cursorX--;
|
||
|
}
|
||
|
else if (key == KeyCode.Right) {
|
||
|
if (cursorX < text.length) cursorX++;
|
||
|
}
|
||
|
else if (editable && key == KeyCode.Backspace) { // Remove char
|
||
|
if (cursorX > 0 && highlightAnchor == cursorX) {
|
||
|
text = text.substr(0, cursorX - 1) + text.substr(cursorX, text.length);
|
||
|
cursorX--;
|
||
|
}
|
||
|
else if (highlightAnchor < cursorX) {
|
||
|
text = text.substr(0, highlightAnchor) + text.substr(cursorX, text.length);
|
||
|
cursorX = highlightAnchor;
|
||
|
}
|
||
|
else {
|
||
|
text = text.substr(0, cursorX) + text.substr(highlightAnchor, text.length);
|
||
|
}
|
||
|
}
|
||
|
else if (editable && key == KeyCode.Delete) {
|
||
|
if (highlightAnchor == cursorX) {
|
||
|
text = text.substr(0, cursorX) + text.substr(cursorX + 1);
|
||
|
}
|
||
|
else if (highlightAnchor < cursorX) {
|
||
|
text = text.substr(0, highlightAnchor) + text.substr(cursorX, text.length);
|
||
|
cursorX = highlightAnchor;
|
||
|
}
|
||
|
else {
|
||
|
text = text.substr(0, cursorX) + text.substr(highlightAnchor, text.length);
|
||
|
}
|
||
|
}
|
||
|
else if (key == KeyCode.Return) { // Deselect
|
||
|
deselectText();
|
||
|
}
|
||
|
else if (key == KeyCode.Escape) { // Cancel
|
||
|
textSelected = textSelectedHandle.text;
|
||
|
deselectText();
|
||
|
}
|
||
|
else if (key == KeyCode.Tab && tabSwitchEnabled && !isCtrlDown) { // Next field
|
||
|
tabPressed = true;
|
||
|
deselectText();
|
||
|
key = null;
|
||
|
}
|
||
|
else if (key == KeyCode.Home) {
|
||
|
cursorX = 0;
|
||
|
}
|
||
|
else if (key == KeyCode.End) {
|
||
|
cursorX = text.length;
|
||
|
}
|
||
|
else if (isCtrlDown && isADown) { // Select all
|
||
|
cursorX = text.length;
|
||
|
highlightAnchor = 0;
|
||
|
}
|
||
|
else if (editable && // Write
|
||
|
key != KeyCode.Shift &&
|
||
|
key != KeyCode.CapsLock &&
|
||
|
key != KeyCode.Control &&
|
||
|
key != KeyCode.Meta &&
|
||
|
key != KeyCode.Alt &&
|
||
|
key != KeyCode.Up &&
|
||
|
key != KeyCode.Down &&
|
||
|
char != null &&
|
||
|
char != "" &&
|
||
|
char.charCodeAt(0) >= 32) {
|
||
|
text = text.substr(0, highlightAnchor) + char + text.substr(cursorX);
|
||
|
cursorX = cursorX + 1 > text.length ? text.length : cursorX + 1;
|
||
|
}
|
||
|
var selecting = isShiftDown && (key == KeyCode.Left || key == KeyCode.Right || key == KeyCode.Shift);
|
||
|
// isCtrlDown && isAltDown is the condition for AltGr was pressed
|
||
|
// AltGr is part of the German keyboard layout and part of key combinations like AltGr + e -> €
|
||
|
if (!selecting && (!isCtrlDown || (isCtrlDown && isAltDown))) highlightAnchor = cursorX;
|
||
|
}
|
||
|
|
||
|
if (editable && textToPaste != "") { // Process cut copy paste
|
||
|
text = text.substr(0, highlightAnchor) + textToPaste + text.substr(cursorX);
|
||
|
cursorX += textToPaste.length;
|
||
|
highlightAnchor = cursorX;
|
||
|
textToPaste = "";
|
||
|
isPaste = false;
|
||
|
}
|
||
|
if (highlightAnchor == cursorX) textToCopy = text; // Copy
|
||
|
else if (highlightAnchor < cursorX) textToCopy = text.substring(highlightAnchor, cursorX);
|
||
|
else textToCopy = text.substring(cursorX, highlightAnchor);
|
||
|
if (editable && isCut) { // Cut
|
||
|
if (highlightAnchor == cursorX) text = "";
|
||
|
else if (highlightAnchor < cursorX) {
|
||
|
text = text.substr(0, highlightAnchor) + text.substr(cursorX, text.length);
|
||
|
cursorX = highlightAnchor;
|
||
|
}
|
||
|
else {
|
||
|
text = text.substr(0, cursorX) + text.substr(highlightAnchor, text.length);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var off = TEXT_OFFSET();
|
||
|
var lineHeight = ELEMENT_H();
|
||
|
var cursorHeight = lineHeight - buttonOffsetY * 3.0;
|
||
|
// Draw highlight
|
||
|
if (highlightAnchor != cursorX) {
|
||
|
var istart = cursorX;
|
||
|
var iend = highlightAnchor;
|
||
|
if (highlightAnchor < cursorX) {
|
||
|
istart = highlightAnchor;
|
||
|
iend = cursorX;
|
||
|
}
|
||
|
var hlstr = text.substr(istart, iend - istart);
|
||
|
var hlstrw = ops.font.width(fontSize, hlstr);
|
||
|
var startoff = ops.font.width(fontSize, text.substr(0, istart));
|
||
|
var hlStart = align == Align.Left ? _x + startoff + off : _x + _w - hlstrw - off;
|
||
|
if (align == Align.Right) {
|
||
|
hlStart -= ops.font.width(fontSize, text.substr(iend, text.length));
|
||
|
}
|
||
|
g.color = t.ACCENT_SELECT_COL;
|
||
|
g.fillRect(hlStart, _y + buttonOffsetY * 1.5, hlstrw, cursorHeight);
|
||
|
}
|
||
|
|
||
|
// Draw cursor
|
||
|
var str = align == Align.Left ? text.substr(0, cursorX) : text.substring(cursorX, text.length);
|
||
|
var strw = ops.font.width(fontSize, str);
|
||
|
var cursorX = align == Align.Left ? _x + strw + off : _x + _w - strw - off;
|
||
|
g.color = t.TEXT_COL; // Cursor
|
||
|
g.fillRect(cursorX, _y + buttonOffsetY * 1.5, 2 * SCALE(), cursorHeight);
|
||
|
|
||
|
textSelected = text;
|
||
|
if (liveUpdate && textSelectedHandle != null) {
|
||
|
textSelectedHandle.changed = textSelectedHandle.text != textSelected;
|
||
|
textSelectedHandle.text = textSelected;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function textInput(handle: Handle, label = "", align = Align.Left, editable = true, liveUpdate = false): String {
|
||
|
if (!isVisible(ELEMENT_H())) {
|
||
|
endElement();
|
||
|
return handle.text;
|
||
|
}
|
||
|
|
||
|
var hover = getHover();
|
||
|
if (hover && onTextHover != null) onTextHover();
|
||
|
g.color = hover ? t.ACCENT_HOVER_COL : t.ACCENT_COL; // Text bg
|
||
|
drawRect(g, t.FILL_ACCENT_BG, _x + buttonOffsetY, _y + buttonOffsetY, _w - buttonOffsetY * 2, BUTTON_H());
|
||
|
|
||
|
var released = getReleased();
|
||
|
if (submitTextHandle == handle && released) { // Keep editing selected text
|
||
|
isTyping = true;
|
||
|
textSelectedHandle = submitTextHandle;
|
||
|
submitTextHandle = null;
|
||
|
setCursorToInput(align);
|
||
|
}
|
||
|
var startEdit = released || tabPressed;
|
||
|
handle.changed = false;
|
||
|
|
||
|
if (textSelectedHandle != handle && startEdit) startTextEdit(handle, align);
|
||
|
if (textSelectedHandle == handle) updateTextEdit(align, editable, liveUpdate);
|
||
|
if (submitTextHandle == handle) submitTextEdit();
|
||
|
|
||
|
if (label != "") {
|
||
|
g.color = t.LABEL_COL; // Label
|
||
|
var labelAlign = align == Align.Right ? Align.Left : Align.Right;
|
||
|
drawString(g, label, labelAlign == Align.Left ? null : 0, 0, labelAlign);
|
||
|
}
|
||
|
|
||
|
g.color = t.TEXT_COL; // Text
|
||
|
textSelectedHandle != handle ? drawString(g, handle.text, null, 0, align) : drawString(g, textSelected, null, 0, align, false);
|
||
|
|
||
|
endElement();
|
||
|
return handle.text;
|
||
|
}
|
||
|
|
||
|
function setCursorToInput(align: Align) {
|
||
|
var off = align == Align.Left ? TEXT_OFFSET() : _w - ops.font.width(fontSize, textSelected);
|
||
|
var x = inputX - (_windowX + _x + off);
|
||
|
cursorX = 0;
|
||
|
while (cursorX < textSelected.length && ops.font.width(fontSize, textSelected.substr(0, cursorX)) < x) {
|
||
|
cursorX++;
|
||
|
}
|
||
|
highlightAnchor = cursorX;
|
||
|
}
|
||
|
|
||
|
function deselectText() {
|
||
|
if (textSelectedHandle == null) return;
|
||
|
submitTextHandle = textSelectedHandle;
|
||
|
textToSubmit = textSelected;
|
||
|
textSelectedHandle = null;
|
||
|
isTyping = false;
|
||
|
if (currentWindow != null) currentWindow.redraws = 2;
|
||
|
if (Keyboard.get() != null) Keyboard.get().hide();
|
||
|
highlightAnchor = cursorX;
|
||
|
if (onDeselectText != null) onDeselectText();
|
||
|
}
|
||
|
|
||
|
public function button(text: String, align = Align.Center, label = ""): Bool {
|
||
|
if (!isVisible(ELEMENT_H())) {
|
||
|
endElement();
|
||
|
return false;
|
||
|
}
|
||
|
var released = getReleased();
|
||
|
var pushed = getPushed();
|
||
|
var hover = getHover();
|
||
|
if (released) changed = true;
|
||
|
|
||
|
g.color = pushed ? t.BUTTON_PRESSED_COL :
|
||
|
hover ? t.BUTTON_HOVER_COL :
|
||
|
t.BUTTON_COL;
|
||
|
|
||
|
drawRect(g, t.FILL_BUTTON_BG, _x + buttonOffsetY, _y + buttonOffsetY, _w - buttonOffsetY * 2, BUTTON_H());
|
||
|
|
||
|
g.color = t.BUTTON_TEXT_COL;
|
||
|
drawString(g, text, null, 0, align);
|
||
|
if (label != "") {
|
||
|
g.color = t.LABEL_COL;
|
||
|
drawString(g, label, null, 0, align == Align.Right ? Align.Left : Align.Right);
|
||
|
}
|
||
|
|
||
|
endElement();
|
||
|
|
||
|
return released;
|
||
|
}
|
||
|
|
||
|
public function check(handle: Handle, text: String, label: String = ""): Bool {
|
||
|
if (!isVisible(ELEMENT_H())) {
|
||
|
endElement();
|
||
|
return handle.selected;
|
||
|
}
|
||
|
if (getReleased()) {
|
||
|
handle.selected = !handle.selected;
|
||
|
handle.changed = changed = true;
|
||
|
}
|
||
|
else handle.changed = false;
|
||
|
|
||
|
var hover = getHover();
|
||
|
drawCheck(handle.selected, hover); // Check
|
||
|
|
||
|
g.color = t.TEXT_COL; // Text
|
||
|
drawString(g, text, titleOffsetX, 0, Align.Left);
|
||
|
|
||
|
if (label != "") {
|
||
|
g.color = t.LABEL_COL;
|
||
|
drawString(g, label, null, 0, Align.Right);
|
||
|
}
|
||
|
|
||
|
endElement();
|
||
|
|
||
|
return handle.selected;
|
||
|
}
|
||
|
|
||
|
public function radio(handle: Handle, position: Int, text: String, label: String = ""): Bool {
|
||
|
if (!isVisible(ELEMENT_H())) {
|
||
|
endElement();
|
||
|
return handle.position == position;
|
||
|
}
|
||
|
if (position == 0) {
|
||
|
handle.changed = false;
|
||
|
}
|
||
|
if (getReleased()) {
|
||
|
handle.position = position;
|
||
|
handle.changed = changed = true;
|
||
|
}
|
||
|
|
||
|
var hover = getHover();
|
||
|
drawRadio(handle.position == position, hover); // Radio
|
||
|
|
||
|
g.color = t.TEXT_COL; // Text
|
||
|
drawString(g, text, titleOffsetX, 0);
|
||
|
|
||
|
if (label != "") {
|
||
|
g.color = t.LABEL_COL;
|
||
|
drawString(g, label, null, 0, Align.Right);
|
||
|
}
|
||
|
|
||
|
endElement();
|
||
|
|
||
|
return handle.position == position;
|
||
|
}
|
||
|
|
||
|
public function combo(handle: Handle, texts: Array<String>, label = "", showLabel = false, align = Align.Left, searchBar = true): Int {
|
||
|
if (!isVisible(ELEMENT_H())) {
|
||
|
endElement();
|
||
|
return handle.position;
|
||
|
}
|
||
|
if (getReleased()) {
|
||
|
if (comboSelectedHandle == null) {
|
||
|
inputEnabled = false;
|
||
|
comboSelectedHandle = handle;
|
||
|
comboSelectedWindow = currentWindow;
|
||
|
comboSelectedAlign = align;
|
||
|
comboSelectedTexts = texts;
|
||
|
comboSelectedLabel = label;
|
||
|
comboSelectedX = Std.int(_x + _windowX);
|
||
|
comboSelectedY = Std.int(_y + _windowY + ELEMENT_H());
|
||
|
comboSelectedW = Std.int(_w);
|
||
|
comboSearchBar = searchBar;
|
||
|
for (t in texts) { // Adapt combo list width to combo item width
|
||
|
var w = Std.int(ops.font.width(fontSize, t)) + 10;
|
||
|
if (comboSelectedW < w) comboSelectedW = w;
|
||
|
}
|
||
|
if (comboSelectedW > _w * 2) comboSelectedW = Std.int(_w * 2);
|
||
|
if (comboSelectedW > _w) comboSelectedW += Std.int(TEXT_OFFSET());
|
||
|
comboToSubmit = handle.position;
|
||
|
comboInitialValue = handle.position;
|
||
|
}
|
||
|
}
|
||
|
if (handle == comboSelectedHandle && (isEscapeDown || inputReleasedR)) {
|
||
|
handle.position = comboInitialValue;
|
||
|
handle.changed = changed = true;
|
||
|
submitComboHandle = null;
|
||
|
}
|
||
|
else if (handle == submitComboHandle) {
|
||
|
handle.position = comboToSubmit;
|
||
|
submitComboHandle = null;
|
||
|
handle.changed = changed = true;
|
||
|
}
|
||
|
else handle.changed = false;
|
||
|
|
||
|
var hover = getHover();
|
||
|
if (hover) { // Bg
|
||
|
g.color = t.ACCENT_HOVER_COL;
|
||
|
drawRect(g, t.FILL_ACCENT_BG, _x + buttonOffsetY, _y + buttonOffsetY, _w - buttonOffsetY * 2, BUTTON_H());
|
||
|
}
|
||
|
else {
|
||
|
g.color = t.ACCENT_COL;
|
||
|
drawRect(g, t.FILL_ACCENT_BG, _x + buttonOffsetY, _y + buttonOffsetY, _w - buttonOffsetY * 2, BUTTON_H());
|
||
|
}
|
||
|
|
||
|
var x = _x + _w - arrowOffsetX - 8;
|
||
|
var y = _y + arrowOffsetY + 3;
|
||
|
g.fillTriangle(x, y, x + ARROW_SIZE(), y, x + ARROW_SIZE() / 2, y + ARROW_SIZE() / 2);
|
||
|
|
||
|
if (showLabel && label != "") {
|
||
|
if (align == Align.Left) _x -= 15;
|
||
|
g.color = t.LABEL_COL;
|
||
|
drawString(g, label, null, 0, align == Align.Left ? Align.Right : Align.Left);
|
||
|
if (align == Align.Left) _x += 15;
|
||
|
}
|
||
|
|
||
|
if (align == Align.Right) _x -= 15;
|
||
|
g.color = t.TEXT_COL; // Value
|
||
|
if (handle.position < texts.length) {
|
||
|
drawString(g, texts[handle.position], null, 0, align);
|
||
|
}
|
||
|
if (align == Align.Right) _x += 15;
|
||
|
|
||
|
endElement();
|
||
|
return handle.position;
|
||
|
}
|
||
|
|
||
|
public function slider(handle: Handle, text: String, from = 0.0, to = 1.0, filled = false, precision = 100.0, displayValue = true, align = Align.Right, textEdit = true): Float {
|
||
|
if (!isVisible(ELEMENT_H())) {
|
||
|
endElement();
|
||
|
return handle.value;
|
||
|
}
|
||
|
if (getStarted()) {
|
||
|
scrollHandle = handle;
|
||
|
isScrolling = true;
|
||
|
changed = handle.changed = true;
|
||
|
if (touchTooltip) {
|
||
|
sliderTooltip = true;
|
||
|
sliderTooltipX = _x + _windowX;
|
||
|
sliderTooltipY = _y + _windowY;
|
||
|
sliderTooltipW = _w;
|
||
|
}
|
||
|
}
|
||
|
else handle.changed = false;
|
||
|
|
||
|
#if (!kha_android && !kha_ios)
|
||
|
if (handle == scrollHandle && inputDX != 0) { // Scroll
|
||
|
#else
|
||
|
if (handle == scrollHandle) { // Scroll
|
||
|
#end
|
||
|
var range = to - from;
|
||
|
var sliderX = _x + _windowX + buttonOffsetY;
|
||
|
var sliderW = _w - buttonOffsetY * 2;
|
||
|
var step = range / sliderW;
|
||
|
var value = from + (inputX - sliderX) * step;
|
||
|
handle.value = Math.round(value * precision) / precision;
|
||
|
if (handle.value < from) handle.value = from; // Stay in bounds
|
||
|
else if (handle.value > to) handle.value = to;
|
||
|
handle.changed = changed = true;
|
||
|
}
|
||
|
|
||
|
var hover = getHover();
|
||
|
drawSlider(handle.value, from, to, filled, hover); // Slider
|
||
|
|
||
|
// Text edit
|
||
|
var startEdit = (getReleased() || tabPressed) && textEdit;
|
||
|
if (startEdit) { // Mouse did not move
|
||
|
handle.text = handle.value + "";
|
||
|
startTextEdit(handle);
|
||
|
handle.changed = changed = true;
|
||
|
}
|
||
|
var lalign = align == Align.Left ? Align.Right : Align.Left;
|
||
|
if (textSelectedHandle == handle) {
|
||
|
updateTextEdit(lalign);
|
||
|
}
|
||
|
if (submitTextHandle == handle) {
|
||
|
submitTextEdit();
|
||
|
#if js
|
||
|
try {
|
||
|
handle.value = js.Lib.eval(handle.text);
|
||
|
}
|
||
|
catch(_) {}
|
||
|
#else
|
||
|
handle.value = Std.parseFloat(handle.text);
|
||
|
#end
|
||
|
handle.changed = changed = true;
|
||
|
}
|
||
|
|
||
|
g.color = t.LABEL_COL; // Text
|
||
|
drawString(g, text, null, 0, align);
|
||
|
|
||
|
if (displayValue) {
|
||
|
g.color = t.TEXT_COL; // Value
|
||
|
textSelectedHandle != handle ?
|
||
|
drawString(g, (Math.round(handle.value * precision) / precision) + "", null, 0, lalign) :
|
||
|
drawString(g, textSelected, null, 0, lalign);
|
||
|
}
|
||
|
|
||
|
endElement();
|
||
|
return handle.value;
|
||
|
}
|
||
|
|
||
|
public function separator(h = 4, fill = true) {
|
||
|
if (!isVisible(ELEMENT_H())) {
|
||
|
_y += h * SCALE();
|
||
|
return;
|
||
|
}
|
||
|
if (fill) {
|
||
|
g.color = t.SEPARATOR_COL;
|
||
|
g.fillRect(_x, _y, _w, h * SCALE());
|
||
|
}
|
||
|
_y += h * SCALE();
|
||
|
}
|
||
|
|
||
|
public function tooltip(text: String) {
|
||
|
tooltipText = text;
|
||
|
tooltipY = _y + _windowY;
|
||
|
}
|
||
|
|
||
|
public function tooltipImage(image: kha.Image, maxWidth: Null<Int> = null) {
|
||
|
tooltipImg = image;
|
||
|
tooltipImgMaxWidth = maxWidth;
|
||
|
tooltipInvertY = imageInvertY;
|
||
|
tooltipY = _y + _windowY;
|
||
|
}
|
||
|
|
||
|
function drawArrow(selected: Bool) {
|
||
|
var x = _x + arrowOffsetX;
|
||
|
var y = _y + arrowOffsetY;
|
||
|
g.color = t.TEXT_COL;
|
||
|
if (selected) {
|
||
|
g.fillTriangle(x, y,
|
||
|
x + ARROW_SIZE(), y,
|
||
|
x + ARROW_SIZE() / 2, y + ARROW_SIZE());
|
||
|
}
|
||
|
else {
|
||
|
g.fillTriangle(x, y,
|
||
|
x, y + ARROW_SIZE(),
|
||
|
x + ARROW_SIZE(), y + ARROW_SIZE() / 2);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function drawTree(selected: Bool) {
|
||
|
var SIGN_W = 7 * SCALE();
|
||
|
var x = _x + arrowOffsetX + 1;
|
||
|
var y = _y + arrowOffsetY + 1;
|
||
|
g.color = t.TEXT_COL;
|
||
|
if (selected) {
|
||
|
g.fillRect(x, y + SIGN_W / 2 - 1, SIGN_W, SIGN_W / 8);
|
||
|
}
|
||
|
else {
|
||
|
g.fillRect(x, y + SIGN_W / 2 - 1, SIGN_W, SIGN_W / 8);
|
||
|
g.fillRect(x + SIGN_W / 2 - 1, y, SIGN_W / 8, SIGN_W);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function drawCheck(selected: Bool, hover: Bool) {
|
||
|
var x = _x + checkOffsetX;
|
||
|
var y = _y + checkOffsetY;
|
||
|
|
||
|
g.color = hover ? t.ACCENT_HOVER_COL : t.ACCENT_COL;
|
||
|
drawRect(g, t.FILL_ACCENT_BG, x, y, CHECK_SIZE(), CHECK_SIZE()); // Bg
|
||
|
|
||
|
if (selected) { // Check
|
||
|
g.color = kha.Color.White;
|
||
|
if (!enabled) fadeColor();
|
||
|
var size = Std.int(CHECK_SELECT_SIZE());
|
||
|
g.drawScaledImage(checkSelectImage, x + checkSelectOffsetX, y + checkSelectOffsetY, size, size);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function drawRadio(selected: Bool, hover: Bool) {
|
||
|
var x = _x + radioOffsetX;
|
||
|
var y = _y + radioOffsetY;
|
||
|
g.color = hover ? t.ACCENT_HOVER_COL : t.ACCENT_COL;
|
||
|
drawRect(g, t.FILL_ACCENT_BG, x, y, CHECK_SIZE(), CHECK_SIZE()); // Bg
|
||
|
|
||
|
if (selected) { // Check
|
||
|
g.color = t.ACCENT_SELECT_COL;
|
||
|
if (!enabled) fadeColor();
|
||
|
g.fillRect(x + radioSelectOffsetX, y + radioSelectOffsetY, CHECK_SELECT_SIZE(), CHECK_SELECT_SIZE());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function drawSlider(value: Float, from: Float, to: Float, filled: Bool, hover: Bool) {
|
||
|
var x = _x + buttonOffsetY;
|
||
|
var y = _y + buttonOffsetY;
|
||
|
var w = _w - buttonOffsetY * 2;
|
||
|
|
||
|
g.color = hover ? t.ACCENT_HOVER_COL : t.ACCENT_COL;
|
||
|
drawRect(g, t.FILL_ACCENT_BG, x, y, w, BUTTON_H()); // Bg
|
||
|
|
||
|
g.color = hover ? t.ACCENT_HOVER_COL : t.ACCENT_COL;
|
||
|
var offset = (value - from) / (to - from);
|
||
|
var barW = 8 * SCALE(); // Unfilled bar
|
||
|
var sliderX = filled ? x : x + (w - barW) * offset;
|
||
|
sliderX = Math.max(Math.min(sliderX, x + (w - barW)), x);
|
||
|
var sliderW = filled ? w * offset : barW;
|
||
|
sliderW = Math.max(Math.min(sliderW, w), 0);
|
||
|
drawRect(g, true, sliderX, y, sliderW, BUTTON_H());
|
||
|
}
|
||
|
|
||
|
static var comboFirst = true;
|
||
|
function drawCombo() {
|
||
|
if (comboSelectedHandle == null) return;
|
||
|
var _g = g;
|
||
|
globalG.color = t.SEPARATOR_COL;
|
||
|
globalG.begin(false);
|
||
|
|
||
|
var comboH = (comboSelectedTexts.length + (comboSelectedLabel != "" ? 1 : 0) + (comboSearchBar ? 1 : 0)) * Std.int(ELEMENT_H());
|
||
|
var distTop = comboSelectedY - comboH - Std.int(ELEMENT_H()) - windowBorderTop;
|
||
|
var distBottom = kha.System.windowHeight() - windowBorderBottom - (comboSelectedY + comboH );
|
||
|
var unrollUp = distBottom < 0 && distBottom < distTop;
|
||
|
beginRegion(globalG, comboSelectedX, comboSelectedY, comboSelectedW);
|
||
|
if (isKeyPressed || inputWheelDelta != 0) {
|
||
|
var arrowUp = isKeyPressed && key == (unrollUp ? KeyCode.Down : KeyCode.Up);
|
||
|
var arrowDown = isKeyPressed && key == (unrollUp ? KeyCode.Up : KeyCode.Down);
|
||
|
var wheelUp = (unrollUp && inputWheelDelta > 0) || (!unrollUp && inputWheelDelta < 0);
|
||
|
var wheelDown = (unrollUp && inputWheelDelta < 0) || (!unrollUp && inputWheelDelta > 0);
|
||
|
if ((arrowUp || wheelUp) && comboToSubmit > 0) {
|
||
|
var step = 1;
|
||
|
if (comboSearchBar && textSelected.length > 0) {
|
||
|
var search = textSelected.toLowerCase();
|
||
|
while (comboSelectedTexts[comboToSubmit - step].toLowerCase().indexOf(search) < 0 && comboToSubmit - step > 0)
|
||
|
++step;
|
||
|
|
||
|
// Corner case: Current position is the top one according to the search pattern.
|
||
|
if (comboSelectedTexts[comboToSubmit - step].toLowerCase().indexOf(search) < 0) step = 0;
|
||
|
}
|
||
|
comboToSubmit -= step;
|
||
|
submitComboHandle = comboSelectedHandle;
|
||
|
}
|
||
|
else if ((arrowDown || wheelDown) && comboToSubmit < comboSelectedTexts.length - 1) {
|
||
|
var step = 1;
|
||
|
if (comboSearchBar && textSelected.length > 0) {
|
||
|
var search = textSelected.toLowerCase();
|
||
|
while (comboSelectedTexts[comboToSubmit + step].toLowerCase().indexOf(search) < 0 && comboToSubmit + step < comboSelectedTexts.length - 1)
|
||
|
++step;
|
||
|
|
||
|
// Corner case: Current position is the lowest one according to the search pattern.
|
||
|
if (comboSelectedTexts[comboToSubmit + step].toLowerCase().indexOf(search) < 0) step = 0;
|
||
|
}
|
||
|
|
||
|
comboToSubmit += step;
|
||
|
submitComboHandle = comboSelectedHandle;
|
||
|
}
|
||
|
if (comboSelectedWindow != null) comboSelectedWindow.redraws = 2;
|
||
|
}
|
||
|
|
||
|
inputEnabled = true;
|
||
|
var _BUTTON_COL = t.BUTTON_COL;
|
||
|
var _ELEMENT_OFFSET = t.ELEMENT_OFFSET;
|
||
|
t.ELEMENT_OFFSET = 0;
|
||
|
var unrollRight = _x + comboSelectedW * 2 < kha.System.windowWidth() - windowBorderRight ? 1 : -1;
|
||
|
var resetPosition = false;
|
||
|
var search = "";
|
||
|
if (comboSearchBar) {
|
||
|
if (unrollUp) _y -= ELEMENT_H() * 2;
|
||
|
var comboSearchHandle = Id.handle();
|
||
|
if (comboFirst) comboSearchHandle.text = "";
|
||
|
fill(0, 0, _w / SCALE(), ELEMENT_H() / SCALE(), t.SEPARATOR_COL);
|
||
|
search = textInput(comboSearchHandle, "", Align.Left, true, true).toLowerCase();
|
||
|
if (isReleased) comboFirst = true; // Keep combo open
|
||
|
if (comboFirst) {
|
||
|
#if (!kha_android && !kha_ios)
|
||
|
startTextEdit(comboSearchHandle); // Focus search bar
|
||
|
#end
|
||
|
}
|
||
|
resetPosition = comboSearchHandle.changed;
|
||
|
}
|
||
|
|
||
|
for (i in 0...comboSelectedTexts.length) {
|
||
|
if (search.length > 0 && comboSelectedTexts[i].toLowerCase().indexOf(search) < 0)
|
||
|
continue; // Don't show items that don't fit the current search pattern
|
||
|
|
||
|
if (resetPosition) { // The search has changed, select first entry that matches
|
||
|
comboToSubmit = comboSelectedHandle.position = i;
|
||
|
submitComboHandle = comboSelectedHandle;
|
||
|
resetPosition = false;
|
||
|
}
|
||
|
if (unrollUp) _y -= ELEMENT_H() * 2;
|
||
|
t.BUTTON_COL = i == comboSelectedHandle.position ? t.ACCENT_SELECT_COL : t.SEPARATOR_COL;
|
||
|
fill(0, 0, _w / SCALE(), ELEMENT_H() / SCALE(), t.SEPARATOR_COL);
|
||
|
if (button(comboSelectedTexts[i], comboSelectedAlign)) {
|
||
|
comboToSubmit = i;
|
||
|
submitComboHandle = comboSelectedHandle;
|
||
|
if (comboSelectedWindow != null) comboSelectedWindow.redraws = 2;
|
||
|
break;
|
||
|
}
|
||
|
if (_y + ELEMENT_H() > kha.System.windowHeight() - windowBorderBottom || _y - ELEMENT_H() * 2 < windowBorderTop) {
|
||
|
_x += comboSelectedW * unrollRight; // Next column
|
||
|
_y = comboSelectedY;
|
||
|
}
|
||
|
}
|
||
|
t.BUTTON_COL = _BUTTON_COL;
|
||
|
t.ELEMENT_OFFSET = _ELEMENT_OFFSET;
|
||
|
|
||
|
if (comboSelectedLabel != "") { // Unroll down
|
||
|
if (unrollUp) {
|
||
|
_y -= ELEMENT_H() * 2;
|
||
|
fill(0, 0, _w / SCALE(), ELEMENT_H() / SCALE(), t.SEPARATOR_COL);
|
||
|
g.color = t.LABEL_COL;
|
||
|
drawString(g, comboSelectedLabel, null, 0, Align.Right);
|
||
|
_y += ELEMENT_H();
|
||
|
fill(0, 0, _w / SCALE(), 1 * SCALE(), t.ACCENT_SELECT_COL); // Separator
|
||
|
}
|
||
|
else {
|
||
|
fill(0, 0, _w / SCALE(), ELEMENT_H() / SCALE(), t.SEPARATOR_COL);
|
||
|
fill(0, 0, _w / SCALE(), 1 * SCALE(), t.ACCENT_SELECT_COL); // Separator
|
||
|
g.color = t.LABEL_COL;
|
||
|
drawString(g, comboSelectedLabel, null, 0, Align.Right);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ((inputReleased || inputReleasedR || isEscapeDown || isReturnDown) && !comboFirst) {
|
||
|
comboSelectedHandle = null;
|
||
|
comboFirst = true;
|
||
|
}
|
||
|
else comboFirst = false;
|
||
|
inputEnabled = comboSelectedHandle == null;
|
||
|
endRegion(false);
|
||
|
globalG.end();
|
||
|
g = _g; // Restore
|
||
|
}
|
||
|
|
||
|
function drawTooltip(bindGlobalG: Bool) {
|
||
|
if (sliderTooltip) {
|
||
|
if (bindGlobalG) globalG.begin(false);
|
||
|
globalG.font = ops.font;
|
||
|
globalG.fontSize = fontSize * 2;
|
||
|
var text = (Math.round(scrollHandle.value * 100) / 100) + "";
|
||
|
var xoff = ops.font.width(globalG.fontSize, text) / 2;
|
||
|
var yoff = ops.font.height(globalG.fontSize);
|
||
|
var x = Math.min(Math.max(sliderTooltipX, inputX), sliderTooltipX + sliderTooltipW);
|
||
|
globalG.color = t.ACCENT_COL;
|
||
|
globalG.fillRect(x - xoff, sliderTooltipY - yoff, xoff * 2, yoff);
|
||
|
globalG.color = t.TEXT_COL;
|
||
|
globalG.drawString(text, x - xoff, sliderTooltipY - yoff);
|
||
|
if (bindGlobalG) globalG.end();
|
||
|
}
|
||
|
if (touchTooltip && textSelectedHandle != null) {
|
||
|
if (bindGlobalG) globalG.begin(false);
|
||
|
globalG.font = ops.font;
|
||
|
globalG.fontSize = fontSize * 2;
|
||
|
var xoff = ops.font.width(globalG.fontSize, textSelected) / 2;
|
||
|
var yoff = ops.font.height(globalG.fontSize) / 2;
|
||
|
var x = kha.System.windowWidth() / 2;
|
||
|
var y = kha.System.windowHeight() / 3;
|
||
|
globalG.color = t.ACCENT_COL;
|
||
|
globalG.fillRect(x - xoff, y - yoff, xoff * 2, yoff * 2);
|
||
|
globalG.color = t.TEXT_COL;
|
||
|
globalG.drawString(textSelected, x - xoff, y - yoff);
|
||
|
if (bindGlobalG) globalG.end();
|
||
|
}
|
||
|
|
||
|
if (tooltipText != "" || tooltipImg != null) {
|
||
|
if (inputChanged()) {
|
||
|
tooltipShown = false;
|
||
|
tooltipWait = inputDX == 0 && inputDY == 0; // Wait for movement before showing up again
|
||
|
}
|
||
|
if (!tooltipShown) {
|
||
|
tooltipShown = true;
|
||
|
tooltipX = inputX;
|
||
|
tooltipTime = kha.Scheduler.time();
|
||
|
}
|
||
|
if (!tooltipWait && kha.Scheduler.time() - tooltipTime > TOOLTIP_DELAY()) {
|
||
|
if (tooltipImg != null) drawTooltipImage(bindGlobalG);
|
||
|
if (tooltipText != "") drawTooltipText(bindGlobalG);
|
||
|
}
|
||
|
}
|
||
|
else tooltipShown = false;
|
||
|
}
|
||
|
|
||
|
function drawTooltipText(bindGlobalG: Bool) {
|
||
|
globalG.color = t.TEXT_COL;
|
||
|
var lines = tooltipText.split("\n");
|
||
|
var tooltipW = 0.0;
|
||
|
for (line in lines) {
|
||
|
var lineTooltipW = ops.font.width(fontSize, line);
|
||
|
if (lineTooltipW > tooltipW) tooltipW = lineTooltipW;
|
||
|
}
|
||
|
tooltipX = Math.min(tooltipX, kha.System.windowWidth() - tooltipW - 20);
|
||
|
if (bindGlobalG) globalG.begin(false);
|
||
|
var fontHeight = ops.font.height(fontSize);
|
||
|
var off = 0;
|
||
|
if (tooltipImg != null) {
|
||
|
var w = tooltipImg.width;
|
||
|
if (tooltipImgMaxWidth != null && w > tooltipImgMaxWidth) w = tooltipImgMaxWidth;
|
||
|
off = Std.int(tooltipImg.height * (w / tooltipImg.width));
|
||
|
}
|
||
|
globalG.fillRect(tooltipX, tooltipY + off, tooltipW + 20, fontHeight * lines.length);
|
||
|
globalG.font = ops.font;
|
||
|
globalG.fontSize = fontSize;
|
||
|
globalG.color = t.ACCENT_COL;
|
||
|
for (i in 0...lines.length) {
|
||
|
globalG.drawString(lines[i], tooltipX + 5, tooltipY + off + i * fontSize);
|
||
|
}
|
||
|
if (bindGlobalG) globalG.end();
|
||
|
}
|
||
|
|
||
|
function drawTooltipImage(bindGlobalG: Bool) {
|
||
|
var w = tooltipImg.width;
|
||
|
if (tooltipImgMaxWidth != null && w > tooltipImgMaxWidth) w = tooltipImgMaxWidth;
|
||
|
var h = tooltipImg.height * (w / tooltipImg.width);
|
||
|
tooltipX = Math.min(tooltipX, kha.System.windowWidth() - w - 20);
|
||
|
tooltipY = Math.min(tooltipY, kha.System.windowHeight() - h - 20);
|
||
|
if (bindGlobalG) globalG.begin(false);
|
||
|
globalG.color = 0xff000000;
|
||
|
globalG.fillRect(tooltipX, tooltipY, w, h);
|
||
|
globalG.color = 0xffffffff;
|
||
|
tooltipInvertY ?
|
||
|
globalG.drawScaledImage(tooltipImg, tooltipX, tooltipY + h, w, -h) :
|
||
|
globalG.drawScaledImage(tooltipImg, tooltipX, tooltipY, w, h);
|
||
|
if (bindGlobalG) globalG.end();
|
||
|
}
|
||
|
|
||
|
function drawString(g: Graphics, text: String, xOffset: Null<Float> = null, yOffset: Float = 0, align = Align.Left, truncation = true) {
|
||
|
var fullText = text;
|
||
|
if (truncation) {
|
||
|
while (text.length > 0 && ops.font.width(fontSize, text) > _w - 6 * SCALE()) {
|
||
|
text = text.substr(0, text.length - 1);
|
||
|
}
|
||
|
if (text.length < fullText.length) {
|
||
|
text += "..";
|
||
|
// Strip more to fit ".."
|
||
|
while (text.length > 2 && ops.font.width(fontSize, text) > _w - 10 * SCALE()) {
|
||
|
text = text.substr(0, text.length - 3) + "..";
|
||
|
}
|
||
|
if (isHovered) tooltip(fullText);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (dynamicGlyphLoad) {
|
||
|
for (i in 0...text.length) {
|
||
|
if (text.charCodeAt(i) > 126 && Graphics.fontGlyphs.indexOf(text.charCodeAt(i)) == -1) {
|
||
|
Graphics.fontGlyphs.push(text.charCodeAt(i));
|
||
|
Graphics.fontGlyphs = Graphics.fontGlyphs.copy(); // Trigger atlas update
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (xOffset == null) xOffset = t.TEXT_OFFSET;
|
||
|
xOffset *= SCALE();
|
||
|
g.font = ops.font;
|
||
|
g.fontSize = fontSize;
|
||
|
if (align == Align.Center) xOffset = _w / 2 - ops.font.width(fontSize, text) / 2;
|
||
|
else if (align == Align.Right) xOffset = _w - ops.font.width(fontSize, text) - TEXT_OFFSET();
|
||
|
|
||
|
if (!enabled) fadeColor();
|
||
|
g.pipeline = rtTextPipeline;
|
||
|
|
||
|
if (textColoring == null) {
|
||
|
g.drawString(text, _x + xOffset, _y + fontOffsetY + yOffset);
|
||
|
}
|
||
|
else {
|
||
|
// Monospace fonts only for now
|
||
|
for (coloring in textColoring.colorings) {
|
||
|
var result = extractColoring(text, coloring);
|
||
|
if (result.colored != "") {
|
||
|
g.color = coloring.color;
|
||
|
g.drawString(result.colored, _x + xOffset, _y + fontOffsetY + yOffset);
|
||
|
}
|
||
|
text = result.uncolored;
|
||
|
}
|
||
|
g.color = textColoring.default_color;
|
||
|
g.drawString(text, _x + xOffset, _y + fontOffsetY + yOffset);
|
||
|
}
|
||
|
|
||
|
g.pipeline = null;
|
||
|
}
|
||
|
|
||
|
static function extractColoring(text: String, col: TColoring) {
|
||
|
var res = { colored: "", uncolored: "" };
|
||
|
var coloring = false;
|
||
|
var startFrom = 0;
|
||
|
var startLength = 0;
|
||
|
for (i in 0...text.length) {
|
||
|
var skipFirst = false;
|
||
|
// Check if upcoming text should be colored
|
||
|
var length = checkStart(i, text, col.start);
|
||
|
// Not touching another character
|
||
|
var separatedLeft = i == 0 || !isChar(text.charCodeAt(i - 1));
|
||
|
var separatedRight = i + length >= text.length || !isChar(text.charCodeAt(i + length));
|
||
|
var isSeparated = separatedLeft && separatedRight;
|
||
|
// Start coloring
|
||
|
if (length > 0 && (!coloring || col.end == "") && (!col.separated || isSeparated)) {
|
||
|
coloring = true;
|
||
|
startFrom = i;
|
||
|
startLength = length;
|
||
|
if (col.end != "" && col.end != "\n") skipFirst = true;
|
||
|
}
|
||
|
// End coloring
|
||
|
else if (col.end == "") {
|
||
|
if (i == startFrom + startLength) coloring = false;
|
||
|
}
|
||
|
else if (text.substr(i, col.end.length) == col.end) {
|
||
|
coloring = false;
|
||
|
}
|
||
|
// If true, add current character to colored string
|
||
|
var b = coloring && !skipFirst;
|
||
|
res.colored += b ? text.charAt(i) : " ";
|
||
|
res.uncolored += b ? " " : text.charAt(i);
|
||
|
}
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
static inline function isChar(code: Int): Bool {
|
||
|
return (code >= 65 && code <= 90) || (code >= 97 && code <= 122);
|
||
|
}
|
||
|
|
||
|
static function checkStart(i:Int, text: String, start: Array<String>): Int {
|
||
|
for (s in start) if (text.substr(i, s.length) == s) return s.length;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
function endElement(elementSize: Null<Float> = null) {
|
||
|
if (elementSize == null) elementSize = ELEMENT_H() + ELEMENT_OFFSET();
|
||
|
if (currentWindow == null || currentWindow.layout == Vertical) {
|
||
|
if (curRatio == -1 || (ratios != null && curRatio == ratios.length - 1)) { // New line
|
||
|
_y += elementSize;
|
||
|
|
||
|
if ((ratios != null && curRatio == ratios.length - 1)) { // Last row element
|
||
|
curRatio = -1;
|
||
|
ratios = null;
|
||
|
_x = xBeforeSplit;
|
||
|
_w = wBeforeSplit;
|
||
|
highlightFullRow = false;
|
||
|
}
|
||
|
}
|
||
|
else { // Row
|
||
|
curRatio++;
|
||
|
_x += _w; // More row elements to place
|
||
|
_w = Std.int(getRatio(ratios[curRatio], wBeforeSplit));
|
||
|
}
|
||
|
}
|
||
|
else { // Horizontal
|
||
|
_x += _w + ELEMENT_OFFSET();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Highlight all upcoming elements in the next row on a `mouse-over` event.
|
||
|
**/
|
||
|
public inline function highlightNextRow() {
|
||
|
highlightFullRow = true;
|
||
|
}
|
||
|
|
||
|
inline function getRatio(ratio: Float, dyn: Float): Float {
|
||
|
return ratio < 0 ? -ratio : ratio * dyn;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
Draw the upcoming elements in the same row.
|
||
|
Negative values will be treated as absolute, positive values as ratio to `window width`.
|
||
|
**/
|
||
|
public function row(ratios: Array<Float>) {
|
||
|
this.ratios = ratios;
|
||
|
curRatio = 0;
|
||
|
xBeforeSplit = _x;
|
||
|
wBeforeSplit = _w;
|
||
|
_w = Std.int(getRatio(ratios[curRatio], _w));
|
||
|
}
|
||
|
|
||
|
public function indent(bothSides = true) {
|
||
|
_x += TAB_W();
|
||
|
_w -= TAB_W();
|
||
|
if (bothSides) _w -= TAB_W();
|
||
|
}
|
||
|
|
||
|
public function unindent(bothSides = true) {
|
||
|
_x -= TAB_W();
|
||
|
_w += TAB_W();
|
||
|
if (bothSides) _w += TAB_W();
|
||
|
}
|
||
|
|
||
|
function fadeColor() {
|
||
|
g.color = kha.Color.fromFloats(g.color.R, g.color.G, g.color.B, 0.25);
|
||
|
}
|
||
|
|
||
|
public function fill(x: Float, y: Float, w: Float, h: Float, color: kha.Color) {
|
||
|
g.color = color;
|
||
|
if (!enabled) fadeColor();
|
||
|
g.fillRect(_x + x * SCALE(), _y + y * SCALE() - 1, w * SCALE(), h * SCALE());
|
||
|
g.color = 0xffffffff;
|
||
|
}
|
||
|
|
||
|
public function rect(x: Float, y: Float, w: Float, h: Float, color: kha.Color, strength = 1.0) {
|
||
|
g.color = color;
|
||
|
if (!enabled) fadeColor();
|
||
|
g.drawRect(_x + x * SCALE(), _y + y * SCALE(), w * SCALE(), h * SCALE(), strength);
|
||
|
g.color = 0xffffffff;
|
||
|
}
|
||
|
|
||
|
inline function drawRect(g: Graphics, fill: Bool, x: Float, y: Float, w: Float, h: Float, strength = 0.0) {
|
||
|
if (strength == 0.0) strength = 1;
|
||
|
if (!enabled) fadeColor();
|
||
|
fill ? g.fillRect(x, y - 1, w, h + 1) : g.drawRect(x, y, w, h, strength);
|
||
|
}
|
||
|
|
||
|
function isVisible(elemH: Float): Bool {
|
||
|
if (currentWindow == null) return true;
|
||
|
return (_y + elemH > windowHeaderH && _y < currentWindow.texture.height);
|
||
|
}
|
||
|
|
||
|
function getReleased(elemH = -1.0): Bool { // Input selection
|
||
|
isReleased = enabled && inputEnabled && inputReleased && getHover(elemH) && getInitialHover(elemH);
|
||
|
return isReleased;
|
||
|
}
|
||
|
|
||
|
function getPushed(elemH = -1.0): Bool {
|
||
|
isPushed = enabled && inputEnabled && inputDown && getHover(elemH) && getInitialHover(elemH);
|
||
|
return isPushed;
|
||
|
}
|
||
|
|
||
|
function getStarted(elemH = -1.0): Bool {
|
||
|
isStarted = enabled && inputEnabled && inputStarted && getHover(elemH);
|
||
|
return isStarted;
|
||
|
}
|
||
|
|
||
|
function getInitialHover(elemH = -1.0): Bool {
|
||
|
if (scissor && inputY < _windowY + windowHeaderH) return false;
|
||
|
if (elemH == -1.0) elemH = ELEMENT_H();
|
||
|
return enabled && inputEnabled &&
|
||
|
inputStartedX >= _windowX + _x && inputStartedX < (_windowX + _x + _w) &&
|
||
|
inputStartedY >= _windowY + _y && inputStartedY < (_windowY + _y + elemH);
|
||
|
}
|
||
|
|
||
|
function getHover(elemH = -1.0): Bool {
|
||
|
if (scissor && inputY < _windowY + windowHeaderH) return false;
|
||
|
if (elemH == -1.0) elemH = ELEMENT_H();
|
||
|
isHovered = enabled && inputEnabled &&
|
||
|
inputX >= _windowX + (highlightFullRow ? 0 : _x) && inputX < (_windowX + _x + (highlightFullRow ? _windowW : _w)) &&
|
||
|
inputY >= _windowY + _y && inputY < (_windowY + _y + elemH);
|
||
|
return isHovered;
|
||
|
}
|
||
|
|
||
|
function getInputInRect(x: Float, y: Float, w: Float, h: Float, scale = 1.0): Bool {
|
||
|
return enabled && inputEnabled &&
|
||
|
inputX >= x * scale && inputX < (x + w) * scale &&
|
||
|
inputY >= y * scale && inputY < (y + h) * scale;
|
||
|
}
|
||
|
|
||
|
public function onMouseDown(button: Int, x: Int, y: Int) { // Input events
|
||
|
if (penInUse) return;
|
||
|
button == 0 ? inputStarted = true : inputStartedR = true;
|
||
|
button == 0 ? inputDown = true : inputDownR = true;
|
||
|
inputStartedTime = kha.Scheduler.time();
|
||
|
#if (kha_android || kha_ios)
|
||
|
setInputPosition(x, y);
|
||
|
#end
|
||
|
inputStartedX = x;
|
||
|
inputStartedY = y;
|
||
|
}
|
||
|
|
||
|
public function onMouseUp(button: Int, x: Int, y: Int) {
|
||
|
if (penInUse) return;
|
||
|
|
||
|
if (touchHoldActivated) {
|
||
|
touchHoldActivated = false;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (isScrolling) { // Prevent action when scrolling is active
|
||
|
isScrolling = false;
|
||
|
scrollHandle = null;
|
||
|
sliderTooltip = false;
|
||
|
if (x == inputStartedX && y == inputStartedY) { // Mouse not moved
|
||
|
button == 0 ? inputReleased = true : inputReleasedR = true;
|
||
|
}
|
||
|
}
|
||
|
else {
|
||
|
button == 0 ? inputReleased = true : inputReleasedR = true;
|
||
|
}
|
||
|
button == 0 ? inputDown = false : inputDownR = false;
|
||
|
#if (kha_android || kha_ios)
|
||
|
setInputPosition(x, y);
|
||
|
#end
|
||
|
deselectText();
|
||
|
}
|
||
|
|
||
|
public function onMouseMove(x: Int, y: Int, movementX: Int, movementY: Int) {
|
||
|
#if (!kha_android && !kha_ios)
|
||
|
setInputPosition(x, y);
|
||
|
#end
|
||
|
}
|
||
|
|
||
|
public function onMouseWheel(delta: Int) {
|
||
|
inputWheelDelta = delta;
|
||
|
}
|
||
|
|
||
|
function setInputPosition(x: Int, y: Int) {
|
||
|
inputDX += x - inputX;
|
||
|
inputDY += y - inputY;
|
||
|
inputX = x;
|
||
|
inputY = y;
|
||
|
}
|
||
|
|
||
|
public function onPenDown(x: Int, y: Int, pressure: Float) {
|
||
|
#if (kha_android || kha_ios)
|
||
|
return;
|
||
|
#end
|
||
|
|
||
|
onMouseDown(0, x, y);
|
||
|
}
|
||
|
|
||
|
public function onPenUp(x: Int, y: Int, pressure: Float) {
|
||
|
#if (kha_android || kha_ios)
|
||
|
return;
|
||
|
#end
|
||
|
|
||
|
if (inputStarted) { inputStarted = false; penInUse = true; return; }
|
||
|
onMouseUp(0, x, y);
|
||
|
penInUse = true; // On pen release, additional mouse down & up events are fired at once - filter those out
|
||
|
}
|
||
|
|
||
|
public function onPenMove(x: Int, y: Int, pressure: Float) {
|
||
|
#if (kha_android || kha_ios)
|
||
|
return;
|
||
|
#end
|
||
|
|
||
|
onMouseMove(x, y, 0, 0);
|
||
|
}
|
||
|
|
||
|
public function onKeyDown(code: KeyCode) {
|
||
|
this.key = code;
|
||
|
isKeyPressed = true;
|
||
|
isKeyDown = true;
|
||
|
keyRepeatTime = kha.Scheduler.time() + 0.4;
|
||
|
switch code {
|
||
|
case KeyCode.Shift: isShiftDown = true;
|
||
|
case KeyCode.Control: isCtrlDown = true;
|
||
|
#if kha_darwin
|
||
|
case KeyCode.Meta: isCtrlDown = true;
|
||
|
#end
|
||
|
case KeyCode.Alt: isAltDown = true;
|
||
|
case KeyCode.Backspace: isBackspaceDown = true;
|
||
|
case KeyCode.Delete: isDeleteDown = true;
|
||
|
case KeyCode.Escape: isEscapeDown = true;
|
||
|
case KeyCode.Return: isReturnDown = true;
|
||
|
case KeyCode.Tab: isTabDown = true;
|
||
|
case KeyCode.A: isADown = true;
|
||
|
case KeyCode.Space: char = " ";
|
||
|
#if kha_android_rmb // Detect right mouse button on Android..
|
||
|
case KeyCode.Back: if (!inputDownR) onMouseDown(1, Std.int(inputX), Std.int(inputY));
|
||
|
#end
|
||
|
default:
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function onKeyUp(code: KeyCode) {
|
||
|
isKeyDown = false;
|
||
|
switch code {
|
||
|
case KeyCode.Shift: isShiftDown = false;
|
||
|
case KeyCode.Control: isCtrlDown = false;
|
||
|
#if kha_darwin
|
||
|
case KeyCode.Meta: isCtrlDown = false;
|
||
|
#end
|
||
|
case KeyCode.Alt: isAltDown = false;
|
||
|
case KeyCode.Backspace: isBackspaceDown = false;
|
||
|
case KeyCode.Delete: isDeleteDown = false;
|
||
|
case KeyCode.Escape: isEscapeDown = false;
|
||
|
case KeyCode.Return: isReturnDown = false;
|
||
|
case KeyCode.Tab: isTabDown = false;
|
||
|
case KeyCode.A: isADown = false;
|
||
|
#if kha_android_rmb
|
||
|
case KeyCode.Back: onMouseUp(1, Std.int(inputX), Std.int(inputY));
|
||
|
#end
|
||
|
default:
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function onKeyPress(char: String) {
|
||
|
this.char = char;
|
||
|
isKeyPressed = true;
|
||
|
}
|
||
|
|
||
|
#if (kha_android || kha_ios)
|
||
|
public function onTouchDown(index: Int, x: Int, y: Int) {
|
||
|
// Reset movement delta on touch start
|
||
|
if (index == 0) {
|
||
|
inputDX = 0;
|
||
|
inputDY = 0;
|
||
|
inputX = x;
|
||
|
inputY = y;
|
||
|
}
|
||
|
// Two fingers down - right mouse button
|
||
|
else if (index == 1) {
|
||
|
inputDown = false;
|
||
|
onMouseDown(1, Std.int(inputX), Std.int(inputY));
|
||
|
pinchStarted = true;
|
||
|
pinchTotal = 0.0;
|
||
|
pinchDistance = 0.0;
|
||
|
}
|
||
|
// Three fingers down - middle mouse button
|
||
|
else if (index == 2) {
|
||
|
inputDownR = false;
|
||
|
onMouseDown(2, Std.int(inputX), Std.int(inputY));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function onTouchUp(index: Int, x: Int, y: Int) {
|
||
|
if (index == 1) onMouseUp(1, Std.int(inputX), Std.int(inputY));
|
||
|
}
|
||
|
|
||
|
var pinchDistance = 0.0;
|
||
|
var pinchTotal = 0.0;
|
||
|
var pinchStarted = false;
|
||
|
|
||
|
public function onTouchMove(index: Int, x: Int, y: Int) {
|
||
|
if (index == 0) setInputPosition(x, y);
|
||
|
|
||
|
// Pinch to zoom - mouse wheel
|
||
|
if (index == 1) {
|
||
|
var lastDistance = pinchDistance;
|
||
|
var dx = inputX - x;
|
||
|
var dy = inputY - y;
|
||
|
pinchDistance = Math.sqrt(dx * dx + dy * dy);
|
||
|
pinchTotal += lastDistance != 0 ? lastDistance - pinchDistance : 0;
|
||
|
if (!pinchStarted) {
|
||
|
inputWheelDelta = Std.int(pinchTotal / 50);
|
||
|
if (inputWheelDelta != 0) {
|
||
|
pinchTotal = 0.0;
|
||
|
}
|
||
|
}
|
||
|
pinchStarted = false;
|
||
|
}
|
||
|
}
|
||
|
#end
|
||
|
|
||
|
public function onCut(): String {
|
||
|
isCut = true;
|
||
|
return onCopy();
|
||
|
}
|
||
|
public function onCopy(): String {
|
||
|
isCopy = true;
|
||
|
return textToCopy;
|
||
|
}
|
||
|
public function onPaste(s: String) {
|
||
|
isPaste = true;
|
||
|
textToPaste = s;
|
||
|
}
|
||
|
|
||
|
public inline function ELEMENT_W(): Float {
|
||
|
return t.ELEMENT_W * SCALE();
|
||
|
}
|
||
|
public inline function ELEMENT_H(): Float {
|
||
|
return t.ELEMENT_H * SCALE();
|
||
|
}
|
||
|
public inline function ELEMENT_OFFSET(): Float {
|
||
|
return t.ELEMENT_OFFSET * SCALE();
|
||
|
}
|
||
|
public inline function ARROW_SIZE(): Float {
|
||
|
return t.ARROW_SIZE * SCALE();
|
||
|
}
|
||
|
public inline function BUTTON_H(): Float {
|
||
|
return t.BUTTON_H * SCALE();
|
||
|
}
|
||
|
public inline function CHECK_SIZE(): Float {
|
||
|
return t.CHECK_SIZE * SCALE();
|
||
|
}
|
||
|
public inline function CHECK_SELECT_SIZE(): Float {
|
||
|
return t.CHECK_SELECT_SIZE * SCALE();
|
||
|
}
|
||
|
public inline function FONT_SIZE(): Int {
|
||
|
return Std.int(t.FONT_SIZE * SCALE());
|
||
|
}
|
||
|
public inline function SCROLL_W(): Int {
|
||
|
return Std.int(t.SCROLL_W * SCALE());
|
||
|
}
|
||
|
public inline function TEXT_OFFSET(): Float {
|
||
|
return t.TEXT_OFFSET * SCALE();
|
||
|
}
|
||
|
public inline function TAB_W(): Int {
|
||
|
return Std.int(t.TAB_W * SCALE());
|
||
|
}
|
||
|
public inline function HEADER_DRAG_H(): Int {
|
||
|
return Std.int(15 * SCALE());
|
||
|
}
|
||
|
public inline function SCALE(): Float {
|
||
|
return ops.scaleFactor;
|
||
|
}
|
||
|
inline function TOOLTIP_DELAY(): Float {
|
||
|
return 1.0;
|
||
|
}
|
||
|
|
||
|
public function resize(handle: Handle, w: Int, h: Int) {
|
||
|
handle.redraws = 2;
|
||
|
if (handle.texture != null) handle.texture.unload();
|
||
|
if (w < 1) w = 1;
|
||
|
if (h < 1) h = 1;
|
||
|
handle.texture = kha.Image.createRenderTarget(w, h, kha.graphics4.TextureFormat.RGBA32, kha.graphics4.DepthStencilFormat.NoDepthAndStencil, 1);
|
||
|
handle.texture.g2.imageScaleQuality = kha.graphics2.ImageScaleQuality.High;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
typedef HandleOptions = {
|
||
|
?selected: Bool,
|
||
|
?position: Int,
|
||
|
?value: Float,
|
||
|
?text: String,
|
||
|
?color: kha.Color,
|
||
|
?layout: Layout
|
||
|
}
|
||
|
|
||
|
class Handle {
|
||
|
public var selected = false;
|
||
|
public var position = 0;
|
||
|
public var color = kha.Color.White;
|
||
|
public var value = 0.0;
|
||
|
public var text = "";
|
||
|
public var texture: kha.Image = null;
|
||
|
public var redraws = 2;
|
||
|
public var scrollOffset = 0.0;
|
||
|
public var scrollEnabled = false;
|
||
|
public var layout: Layout = 0;
|
||
|
public var lastMaxX = 0.0;
|
||
|
public var lastMaxY = 0.0;
|
||
|
public var dragEnabled = false;
|
||
|
public var dragX = 0;
|
||
|
public var dragY = 0;
|
||
|
public var changed = false;
|
||
|
var children: Map<Int, Handle>;
|
||
|
|
||
|
public function new(ops: HandleOptions = null) {
|
||
|
if (ops != null) {
|
||
|
if (ops.selected != null) selected = ops.selected;
|
||
|
if (ops.position != null) position = ops.position;
|
||
|
if (ops.value != null) value = ops.value;
|
||
|
if (ops.text != null) text = ops.text;
|
||
|
if (ops.color != null) color = ops.color;
|
||
|
if (ops.layout != null) layout = ops.layout;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public function nest(i: Int, ops: HandleOptions = null): Handle {
|
||
|
if (children == null) children = [];
|
||
|
var c = children.get(i);
|
||
|
if (c == null) {
|
||
|
c = new Handle(ops);
|
||
|
children.set(i, c);
|
||
|
}
|
||
|
return c;
|
||
|
}
|
||
|
|
||
|
public function unnest(i: Int) {
|
||
|
if (children != null) {
|
||
|
children.remove(i);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public static var global = new Handle();
|
||
|
}
|
||
|
|
||
|
@:enum abstract Layout(Int) from Int {
|
||
|
var Vertical = 0;
|
||
|
var Horizontal = 1;
|
||
|
}
|
||
|
|
||
|
@:enum abstract Align(Int) from Int {
|
||
|
var Left = 0;
|
||
|
var Center = 1;
|
||
|
var Right = 2;
|
||
|
}
|
||
|
|
||
|
@:enum abstract State(Int) from Int {
|
||
|
var Idle = 0;
|
||
|
var Started = 1;
|
||
|
var Down = 2;
|
||
|
var Released = 3;
|
||
|
var Hovered = 4;
|
||
|
}
|
||
|
|
||
|
typedef TColoring = {
|
||
|
var color: Int;
|
||
|
var start: Array<String>;
|
||
|
var end: String;
|
||
|
@:optional var separated: Null<Bool>;
|
||
|
}
|
||
|
|
||
|
typedef TTextColoring = {
|
||
|
var colorings: Array<TColoring>;
|
||
|
var default_color: Int;
|
||
|
}
|