diff --git a/core/src/main/java/imgui/Jdsl.java b/core/src/main/java/imgui/Jdsl.java index fe00b99fb..abbe78b51 100644 --- a/core/src/main/java/imgui/Jdsl.java +++ b/core/src/main/java/imgui/Jdsl.java @@ -42,11 +42,11 @@ public static void window(String name, Runnable block) { window(name, null, Flag.empty(), block); } - public static void window(String name, MutableProperty0 open, Runnable block) { + public static void window(String name, MutableReference open, Runnable block) { window(name, open, Flag.empty(), block); } - public static void window(String name, MutableProperty0 open, Flag windowFlags, Runnable block) { + public static void window(String name, MutableReference open, Flag windowFlags, Runnable block) { if (imgui.begin(name, open, windowFlags)) // ~open block.run(); imgui.end(); @@ -255,12 +255,12 @@ public static void imageButton(String srtId, int userTextureId, Vec2 size, Vec2 block.run(); } - public static void checkbox(String label, MutableProperty0 vPtr, Runnable block) { + public static void checkbox(String label, MutableReference vPtr, Runnable block) { if (imgui.checkbox(label, vPtr)) block.run(); } - public static > void checkboxFlags(String label, MutableProperty0> vPtr, Flag flagsValue, Runnable block) { + public static > void checkboxFlags(String label, MutableReference> vPtr, Flag flagsValue, Runnable block) { if (imgui.checkboxFlags(label, vPtr, flagsValue)) block.run(); } @@ -275,7 +275,7 @@ public static void radioButton(String label, boolean active, Runnable block) { block.run(); } - public static void radioButton(String label, MutableProperty0 v, int vButton, Runnable block) { + public static void radioButton(String label, MutableReference v, int vButton, Runnable block) { if (imgui.radioButton(label, v, vButton)) block.run(); } @@ -295,11 +295,11 @@ public static void useCombo(String label, String previewValue, Flag c } } - public static void combo(String label, MutableProperty0 currentItem, String itemsSeparatedByZeros, Runnable block) { + public static void combo(String label, MutableReference currentItem, String itemsSeparatedByZeros, Runnable block) { combo(label, currentItem, itemsSeparatedByZeros, -1, block); } - public static void combo(String label, MutableProperty0 currentItem, String itemsSeparatedByZeros, int heightInItems, Runnable block) { + public static void combo(String label, MutableReference currentItem, String itemsSeparatedByZeros, int heightInItems, Runnable block) { if (imgui.combo(label, currentItem, itemsSeparatedByZeros, heightInItems)) block.run(); } @@ -370,11 +370,11 @@ public static void collapsingHeader(String label, Flag treeNodeFla block.run(); } - public static void collapsingHeader(String label, MutableProperty0 open, Runnable block) { + public static void collapsingHeader(String label, MutableReference open, Runnable block) { collapsingHeader(label, open, Flag.empty(), block); } - public static void collapsingHeader(String label, MutableProperty0 open, Flag treeNodeFlags, Runnable block) { + public static void collapsingHeader(String label, MutableReference open, Flag treeNodeFlags, Runnable block) { if (imgui.collapsingHeader(label, open, treeNodeFlags)) block.run(); } @@ -504,11 +504,11 @@ public static void popupModal(String name, Runnable block) { popupModal(name, null, Flag.empty(), block); } - public static void popupModal(String name, MutableProperty0 pOpen, Runnable block) { + public static void popupModal(String name, MutableReference pOpen, Runnable block) { popupModal(name, pOpen, Flag.empty(), block); } - public static void popupModal(String name, MutableProperty0 pOpen, Flag windowFlags, Runnable block) { + public static void popupModal(String name, MutableReference pOpen, Flag windowFlags, Runnable block) { if (imgui.beginPopupModal(name, pOpen, windowFlags)) { block.run(); imgui.endPopup(); @@ -532,11 +532,11 @@ public static void tabItem(String label, Runnable block) { tabItem(label, null, Flag.empty(), block); } - public static void tabItem(String label, MutableProperty0 pOpen, Runnable block) { + public static void tabItem(String label, MutableReference pOpen, Runnable block) { tabItem(label, pOpen, Flag.empty(), block); } - public static void tabItem(String label, MutableProperty0 pOpen, Flag tabItemFlags, Runnable block) { + public static void tabItem(String label, MutableReference pOpen, Flag tabItemFlags, Runnable block) { if (imgui.beginTabItem(label, pOpen, tabItemFlags)) block.run(); imgui.endTabItem(); diff --git a/core/src/main/java/imgui/MutableProperty0.java b/core/src/main/java/imgui/MutableProperty0.java deleted file mode 100644 index 4e485bff5..000000000 --- a/core/src/main/java/imgui/MutableProperty0.java +++ /dev/null @@ -1,134 +0,0 @@ -package imgui; - -import kotlin.reflect.*; -//import org.jetbrains.annotations.NotNull; -//import org.jetbrains.annotations.Nullable; - -import java.lang.annotation.Annotation; -import java.util.List; -import java.util.Map; - -public class MutableProperty0 implements KMutableProperty0 { - - private T t = null; - - public MutableProperty0() { - } - - public MutableProperty0(T t) { - this.t = t; - } - -// @NotNull - @Override - public Setter getSetter() { - return null; - } - - @Override - public void set(T t) { - this.t = t; - } - -// @NotNull - @Override - public KProperty0.Getter getGetter() { - return null; - } - - @Override - public T get() { - return t; - } - -// @Nullable - @Override - public Object getDelegate() { - return null; - } - - @Override - public T invoke() { - return t; - } - - @Override - public boolean isConst() { - return false; - } - - @Override - public boolean isLateinit() { - return false; - } - - @Override - public boolean isAbstract() { - return false; - } - - @Override - public boolean isFinal() { - return false; - } - - @Override - public boolean isOpen() { - return false; - } - - @Override - public boolean isSuspend() { - return false; - } - -// @NotNull - @Override - public String getName() { - return null; - } - -// @NotNull - @Override - public List getParameters() { - return null; - } - -// @NotNull - @Override - public KType getReturnType() { - return null; - } - -// @NotNull - @Override - public List getTypeParameters() { - return null; - } - -// @Nullable - @Override - public KVisibility getVisibility() { - return null; - } - - @Override - public T call( -// @NotNull - Object... objects) { - return null; - } - - @Override - public T callBy( -// @NotNull - Map map) { - return null; - } - -// @NotNull - @Override - public List getAnnotations() { - return null; - } -} diff --git a/core/src/main/kotlin/imgui/api/colorUtilities.kt b/core/src/main/kotlin/imgui/api/colorUtilities.kt index 8920dc7db..42b0a58fb 100644 --- a/core/src/main/kotlin/imgui/api/colorUtilities.kt +++ b/core/src/main/kotlin/imgui/api/colorUtilities.kt @@ -3,13 +3,10 @@ package imgui.api import glm_.f import glm_.glm import glm_.i +import glm_.vec3.Vec3 import glm_.vec4.Vec4 -import imgui._f -import imgui._f1 -import imgui._f2 -import uno.kotlin.getValue -import uno.kotlin.setValue -import kotlin.reflect.KMutableProperty0 +import imgui.Vec3Setter +import imgui.put /** Color Utilities */ @@ -17,118 +14,90 @@ interface colorUtilities { /** Convert rgb floats ([0-1],[0-1],[0-1]) to hsv floats ([0-1],[0-1],[0-1]), from Foley & van Dam p592 * Optimized http://lolengine.net/blog/2013/01/13/fast-rgb-to-hsv */ - fun colorConvertRGBtoHSV(rgb: FloatArray, hsv: FloatArray = FloatArray(3)): FloatArray = - colorConvertRGBtoHSV(rgb[0], rgb[1], rgb[2], hsv) - - fun colorConvertRGBtoHSV(r_: Float, g_: Float, b_: Float, hsv: FloatArray = FloatArray(3)): FloatArray { - - var k = 0f - var r = r_ - var g = g_ - var b = b_ - if (g < b) { - val tmp = g; g = b; b = tmp - k = -1f - } - if (r < g) { - val tmp = r; r = g; g = tmp - k = -2f / 6f - k - } - - val chroma = r - (if (g < b) g else b) - hsv[0] = glm.abs(k + (g - b) / (6f * chroma + 1e-20f)) - hsv[1] = chroma / (r + 1e-20f) - hsv[2] = r - return hsv + fun colorConvertRGBtoHSV(rgb: Vec3, hsv: Vec3 = Vec3()): Vec3 = colorConvertRGBtoHSV(rgb.r, rgb.g, rgb.b, hsv) + fun colorConvertRGBtoHSV(rgb: Vec4, hsv: Vec4 = Vec4()): Vec4 = colorConvertRGBtoHSV(rgb.r, rgb.g, rgb.b, hsv) + fun colorConvertRGBtoHSV(r: Float, g: Float, b: Float, hsv: Vec3) = hsv.apply { + colorConvertRGBtoHSV(r, g, b, ::put) } - fun FloatArray.rgbToHSV() = colorConvertRGBtoHSV(this, this) + fun colorConvertRGBtoHSV(r: Float, g: Float, b: Float, hsv: Vec4) = hsv.apply { + colorConvertRGBtoHSV(r, g, b, ::put) + } + + fun Vec3.rgbToHSV() = colorConvertRGBtoHSV(this, this) + fun Vec4.rgbToHSV() = colorConvertRGBtoHSV(this, this) /** Convert hsv floats ([0-1],[0-1],[0-1]) to rgb floats ([0-1],[0-1],[0-1]), from Foley & van Dam p593 * also http://en.wikipedia.org/wiki/HSL_and_HSV */ - fun colorConvertHSVtoRGB(hsv: FloatArray, rgb: FloatArray = FloatArray(3)) = colorConvertHSVtoRGB(hsv[0], hsv[1], hsv[2], rgb) + fun colorConvertHSVtoRGB(hsv: Vec3, rgb: Vec3 = Vec3()): Vec3 = colorConvertHSVtoRGB(hsv.x, hsv.y, hsv.z, rgb) + + fun colorConvertHSVtoRGB(hsv: Vec4, rgb: Vec4 = Vec4()): Vec4 = colorConvertHSVtoRGB(hsv.x, hsv.y, hsv.z, rgb) + + fun colorConvertHSVtoRGB(h: Float, s: Float, v: Float, rgb: Vec3 = Vec3()): Vec3 = rgb.apply { + colorConvertHSVtoRGB(h, s, v, ::put) + } - fun colorConvertHSVtoRGB(h: Float, s: Float, v: Float, rgb: FloatArray = FloatArray(3)): FloatArray { - colorConvertHSVtoRGB(h, s, v, ::_f, ::_f1, ::_f2) - return rgb.apply { set(0, _f); set(1, _f1); set(2, _f2) } + fun colorConvertHSVtoRGB(h: Float, s: Float, v: Float, rgb: Vec4 = Vec4(), unit: Unit = Unit): Vec4 = rgb.apply { + colorConvertHSVtoRGB(h, s, v, ::put) } - fun colorConvertHSVtoRGB(h_: Float, s: Float, v: Float, rPtr: KMutableProperty0, gPtr: KMutableProperty0, - bPtr: KMutableProperty0) { - - var r by rPtr - var g by gPtr - var b by bPtr - if (s == 0f) { - // gray - r = v - g = v - b = v - } - - val h = glm.mod(h_, 1f) / (60f / 360f) - val i = h.i - val f = h - i.f - val p = v * (1f - s) - val q = v * (1f - s * f) - val t = v * (1f - s * (1f - f)) - - when (i) { - 0 -> { - r = v; g = t; b = p; } - 1 -> { - r = q; g = v; b = p; } - 2 -> { - r = p; g = v; b = t; } - 3 -> { - r = p; g = q; b = v; } - 4 -> { - r = t; g = p; b = v; } - else -> { - r = v; g = p; b = q; } - } + fun Vec3.hsvToRGB() = colorConvertHSVtoRGB(this, this) + fun Vec4.hsvToRGB() = colorConvertHSVtoRGB(this, this) +} + +inline fun colorConvertRGBtoHSV(rgb: Vec3, hsvSetter: Vec3Setter) { + colorConvertRGBtoHSV(rgb.x, rgb.y, rgb.z, hsvSetter) +} + +inline fun colorConvertRGBtoHSV(rgb: Vec4, hsvSetter: Vec3Setter) { + colorConvertRGBtoHSV(rgb.x, rgb.y, rgb.z, hsvSetter) +} + +inline fun colorConvertRGBtoHSV(r_: Float, g_: Float, b_: Float, hsvSetter: Vec3Setter) { + var k = 0f + var r = r_ + var g = g_ + var b = b_ + if (g < b) { + val tmp = g; g = b; b = tmp + k = -1f } + if (r < g) { + val tmp = r; r = g; g = tmp + k = -2f / 6f - k + } + + val chroma = r - (if (g < b) g else b) + hsvSetter(glm.abs(k + (g - b) / (6f * chroma + 1e-20f)), chroma / (r + 1e-20f), r) +} + +inline fun colorConvertHSVtoRGB(hsv: Vec3, rgbSetter: Vec3Setter) { + colorConvertHSVtoRGB(hsv.x, hsv.y, hsv.z, rgbSetter) +} - fun colorConvertHSVtoRGB(col: Vec4) { - - val h_ = col.x - val s = col.y - val v = col.z - var r: Float - var g: Float - var b: Float - if (s == 0f) { - // gray - r = v - g = v - b = v - } - - val h = glm.mod(h_, 1f) / (60f / 360f) - val i = h.i - val f = h - i.f - val p = v * (1f - s) - val q = v * (1f - s * f) - val t = v * (1f - s * (1f - f)) - - when (i) { - 0 -> { - r = v; g = t; b = p; } - 1 -> { - r = q; g = v; b = p; } - 2 -> { - r = p; g = v; b = t; } - 3 -> { - r = p; g = q; b = v; } - 4 -> { - r = t; g = p; b = v; } - else -> { - r = v; g = p; b = q; } - } - col.x = r - col.y = g - col.z = b +inline fun colorConvertHSVtoRGB(hsv: Vec4, rgbSetter: Vec3Setter) { + colorConvertHSVtoRGB(hsv.x, hsv.y, hsv.z, rgbSetter) +} + +inline fun colorConvertHSVtoRGB(h_: Float, s: Float, v: Float, rgbSetter: Vec3Setter) { + if (s == 0f) { + // gray + return rgbSetter(v, v, v) } - fun FloatArray.hsvToRGB() = colorConvertHSVtoRGB(this, this) + val h = glm.mod(h_, 1f) / (60f / 360f) + val i = h.i + val f = h - i.f + val p = v * (1f - s) + val q = v * (1f - s * f) + val t = v * (1f - s * (1f - f)) + + when (i) { + 0 -> rgbSetter(v, t, p) + 1 -> rgbSetter(q, v, p) + 2 -> rgbSetter(p, v, t) + 3 -> rgbSetter(p, q, v) + 4 -> rgbSetter(t, p, v) + else -> rgbSetter(v, p, q) + } } \ No newline at end of file diff --git a/core/src/main/kotlin/imgui/api/demoDebugInformations.kt b/core/src/main/kotlin/imgui/api/demoDebugInformations.kt index 7addac505..6f13ffdbe 100644 --- a/core/src/main/kotlin/imgui/api/demoDebugInformations.kt +++ b/core/src/main/kotlin/imgui/api/demoDebugInformations.kt @@ -227,16 +227,16 @@ interface demoDebugInformations { checkbox("Show windows rectangles", ::showWindowsRects) sameLine() setNextItemWidth(fontSize * 12) - _i32 = showWindowsRectType.ordinal - showWindowsRects = showWindowsRects || combo("##show_windows_rect_type", ::_i32, WRT.names, WRT.names.size) - showWindowsRectType = WRT.values()[_i32] + val ordinalRef = showWindowsRectType.ordinal.mutableReference + val ordinal by ordinalRef + showWindowsRects = showWindowsRects || combo("##show_windows_rect_type", ordinalRef, WRT.names, WRT.names.size) + showWindowsRectType = WRT.values()[ordinal] if (showWindowsRects) g.navWindow?.let { nav -> bulletText("'${nav.name}':") indent { for (rectN in WRT.values()) { val r = Funcs.getWindowRect(nav, rectN) - text("(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) ${WRT.names[rectN.ordinal]}", - r.min.x, r.min.y, r.max.x, r.max.y, r.width, r.height) + text("(%6.1f,%6.1f) (%6.1f,%6.1f) Size (%6.1f,%6.1f) ${WRT.names[rectN.ordinal]}", r.min.x, r.min.y, r.max.x, r.max.y, r.width, r.height) } } } diff --git a/core/src/main/kotlin/imgui/api/loggingCapture.kt b/core/src/main/kotlin/imgui/api/loggingCapture.kt index 33b2ccd86..2a23b3d34 100644 --- a/core/src/main/kotlin/imgui/api/loggingCapture.kt +++ b/core/src/main/kotlin/imgui/api/loggingCapture.kt @@ -9,7 +9,6 @@ import imgui.ImGui.pushAllowKeyboardFocus import imgui.ImGui.pushID import imgui.ImGui.sameLine import imgui.ImGui.setNextItemWidth -import imgui.ImGui.sliderInt import imgui.classes.Context import imgui.internal.sections.LogType import java.io.File @@ -80,7 +79,7 @@ interface loggingCapture { val logToClipboard = button("Log To Clipboard"); sameLine() pushAllowKeyboardFocus(false) setNextItemWidth(80f) - sliderInt("Default Depth", g::logDepthToExpandDefault, 0, 9) + slider("Default Depth", g::logDepthToExpandDefault, 0, 9) popAllowKeyboardFocus() popID() diff --git a/core/src/main/kotlin/imgui/api/widgetsColorEditorPicker.kt b/core/src/main/kotlin/imgui/api/widgetsColorEditorPicker.kt index bf9cdfe8d..8fa543157 100644 --- a/core/src/main/kotlin/imgui/api/widgetsColorEditorPicker.kt +++ b/core/src/main/kotlin/imgui/api/widgetsColorEditorPicker.kt @@ -6,6 +6,7 @@ import glm_.glm import glm_.i import glm_.max import glm_.vec2.Vec2 +import glm_.vec3.Vec3 import glm_.vec4.Vec4 import glm_.wo import imgui.* @@ -19,13 +20,13 @@ import imgui.ImGui.calcItemWidth import imgui.ImGui.calcTextSize import imgui.ImGui.colorConvertHSVtoRGB import imgui.ImGui.colorConvertRGBtoHSV +import imgui.ImGui.colorEdit4 import imgui.ImGui.colorEditOptionsPopup import imgui.ImGui.colorPickerOptionsPopup import imgui.ImGui.colorTooltip import imgui.ImGui.currentWindow import imgui.ImGui.cursorScreenPos -import imgui.ImGui.dragInt -import imgui.ImGui.dragScalar +import imgui.ImGui.drag import imgui.ImGui.endDragDropSource import imgui.ImGui.endDragDropTarget import imgui.ImGui.endGroup @@ -56,6 +57,7 @@ import imgui.ImGui.renderFrameBorder import imgui.ImGui.renderNavHighlight import imgui.ImGui.rgbToHSV import imgui.ImGui.sameLine +import imgui.ImGui.scanHex import imgui.ImGui.setDragDropPayload import imgui.ImGui.setNextItemWidth import imgui.ImGui.setNextWindowPos @@ -64,6 +66,11 @@ import imgui.ImGui.spacing import imgui.ImGui.style import imgui.ImGui.text import imgui.ImGui.textEx +import imgui.api.widgetsColorEditorPicker.Companion.colorEditRestoreHS +import imgui.api.widgetsColorEditorPicker.Companion.fmtTableFloat +import imgui.api.widgetsColorEditorPicker.Companion.fmtTableInt +import imgui.api.widgetsColorEditorPicker.Companion.ids +import imgui.api.widgetsColorEditorPicker.Companion.renderArrowsForVerticalBar import imgui.classes.DrawList import imgui.internal.* import imgui.internal.classes.Rect @@ -81,771 +88,721 @@ import imgui.InputTextFlag as Itf * document the number of elements that are expected to be accessible. * - You can pass the address of a first float element out of a contiguous structure, e.g. &myvector.x */ interface widgetsColorEditorPicker { - /** 3-4 components color edition. Click on colored squared to open a color picker, right-click for options. * Hint: 'float col[3]' function argument is same as 'float* col'. * You can pass address of first element out of a contiguous set, e.g. &myvector.x */ - fun colorEdit3(label: String, col: Vec4, flags: ColorEditFlags = emptyFlags): Boolean = - colorEdit4(label, col to _fa, flags or Cef.NoAlpha) - .also { col put _fa } + fun colorEdit3(label: String, col: Vec3, flags: ColorEditFlags = emptyFlags): Boolean = colorEdit4(label, col.x, col.y, col.z, 0f, flags or Cef.NoAlpha, col::put) - fun colorEdit3(label: String, col: FloatArray, flags: ColorEditFlags = emptyFlags): Boolean = - colorEdit4(label, col, flags or Cef.NoAlpha) + /** 3-4 components color edition. Click on colored squared to open a color picker, right-click for options. + * Hint: 'float col[3]' function argument is same as 'float* col'. + * You can pass address of first element out of a contiguous set, e.g. &myvector.x */ + fun colorEdit3(label: String, col: Vec4, flags: ColorEditFlags = emptyFlags): Boolean = colorEdit4(label, col.x, col.y, col.z, col.w, flags or Cef.NoAlpha, col::put) /** Edit colors components (each component in 0.0f..1.0f range). * See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ColorEditFlags.NoAlpha flag is set. * With typical options: Left-click on color square to open color picker. Right-click to open option menu. * CTRL-Click over input fields to edit them and TAB to go to next item. */ - fun colorEdit4(label: String, col: Vec4, flags: ColorEditFlags = emptyFlags): Boolean = - colorEdit4(label, col to _fa, flags).also { col put _fa } - fun colorEdit4(label: String, col: FloatArray, flags_: ColorEditFlags = emptyFlags): Boolean { + fun String.scanHex(ints: IntArray, precision: Int, count: Int = ints.size) { + var c = 0 + for (i in 0 until count) { + val end = glm.min((i + 1) * precision, length) + if (c >= end) break + ints[i] = substring(c, end).toInt(16) + c += precision + } + } - val window = currentWindow - if (window.skipItems) - return false + fun colorEdit4(label: String, col: Vec3, flags: ColorEditFlags = emptyFlags): Boolean = colorEdit4(label, col.x, col.y, col.z, 1f, flags, col::put) - val squareSz = frameHeight - val wFull = calcItemWidth() - val wButton = if (flags_ has Cef.NoSmallPreview) 0f else squareSz + style.itemInnerSpacing.x - val wInputs = wFull - wButton - val labelDisplayEnd = findRenderedTextEnd(label) - g.nextItemData.clearFlags() + fun colorEdit4(label: String, col: Vec4, flags: ColorEditFlags = emptyFlags): Boolean = colorEdit4(label, col.x, col.y, col.z, col.w, flags, col::put) - beginGroup() - pushID(label) + fun colorPicker3(label: String, col: Vec3, flags: ColorEditFlags = emptyFlags): Boolean = colorPicker3(label, col.x, col.y, col.z, flags, col::put) - var flags = flags_ + fun colorPicker3(label: String, col: Vec4, flags: ColorEditFlags = emptyFlags): Boolean = colorPicker3(label, col.x, col.y, col.z, flags, col::put) - // If we're not showing any slider there's no point in doing any HSV conversions - val flagsUntouched = flags - if (flags has Cef.NoInputs) flags = (flags wo Cef._DisplayMask) or Cef.DisplayRGB or Cef.NoOptions - - // Context menu: display and modify options (before defaults are applied) - if (flags hasnt Cef.NoOptions) colorEditOptionsPopup(col, flags) - - // Read stored options - if (flags hasnt Cef._DisplayMask) - flags = flags or (g.colorEditOptions and Cef._DisplayMask) - if (flags hasnt Cef._DataTypeMask) - flags = flags or (g.colorEditOptions and Cef._DataTypeMask) - if (flags hasnt Cef._PickerMask) - flags = flags or (g.colorEditOptions and Cef._PickerMask) - if (flags hasnt Cef._InputMask) - flags = flags or (g.colorEditOptions and Cef._InputMask) - flags = flags or (g.colorEditOptions wo (Cef._DisplayMask or Cef._DataTypeMask or Cef._PickerMask or Cef._InputMask)) - assert((flags and Cef._DisplayMask).isPowerOfTwo) { "Check that only 1 is selected" } - assert((flags and Cef._InputMask).isPowerOfTwo) { "Check that only 1 is selected" } - - val alpha = flags hasnt Cef.NoAlpha - val hdr = flags has Cef.HDR - val components = if (alpha) 4 else 3 - - // Convert to the formats we need - val f = floatArrayOf(col[0], col[1], col[2], if (alpha) col[3] else 1f) - if (flags has Cef.InputHSV && flags has Cef.DisplayRGB) - f.hsvToRGB() - else if (flags has Cef.InputRGB && flags has Cef.DisplayHSV) { - // Hue is lost when converting from greyscale rgb (saturation=0). Restore it. - f.rgbToHSV() - colorEditRestoreHS(col, f) - } + /** ColorPicker + * Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. + * (In C++ the 'float col[4]' notation for a function argument is equivalent to 'float* col', we only specify a size to facilitate understanding of the code.) + * FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop + * (if automatic height makes a vertical scrollbar appears, affecting automatic width..) + * FIXME: this is trying to be aware of style.Alpha but not fully correct. Also, the color wheel will have overlapping glitches with (style.Alpha < 1.0) */ + fun colorPicker4(label: String, col: Vec3, flags: ColorEditFlags = emptyFlags, refCol: Vec4?): Boolean = + colorPicker4(label, col.x, col.y, col.z, 1f, flags, refCol, col::put) - val i = IntArray(4) { F32_TO_INT8_UNBOUND(f[it]) } + /** ColorPicker + * Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. + * (In C++ the 'float col[4]' notation for a function argument is equivalent to 'float* col', we only specify a size to facilitate understanding of the code.) + * FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop + * (if automatic height makes a vertical scrollbar appears, affecting automatic width..) + * FIXME: this is trying to be aware of style.Alpha but not fully correct. Also, the color wheel will have overlapping glitches with (style.Alpha < 1.0) */ + fun colorPicker4(label: String, col: Vec4, flags: ColorEditFlags = emptyFlags, refCol: Vec4? = null): Boolean = + colorPicker4(label, col.x, col.y, col.z, col.w, flags, refCol, col::put) - var valueChanged = false - var valueChangedAsFloat = false + fun colorButton(descId: String, col: Vec3, flags: ColorEditFlags = emptyFlags, sizeArg: Vec2 = Vec2()): Boolean = colorButton(descId, col.x, col.y, col.z, 1f, flags, sizeArg) - val pos = Vec2(window.dc.cursorPos) - val inputsOffsetX = if (style.colorButtonPosition == Dir.Left) wButton else 0f - window.dc.cursorPos.x = pos.x + inputsOffsetX + fun colorButton(descId: String, col: Vec4, flags: ColorEditFlags = emptyFlags, sizeArg: Vec2 = Vec2()): Boolean = colorButton(descId, col.x, col.y, col.z, col.w, flags, sizeArg) - if (flags has (Cef.DisplayRGB or Cef.DisplayHSV) && flags hasnt Cef.NoInputs) { + /** initialize current options (generally on application startup) if you want to select a default format, picker + * type, etc. User will be able to change many settings, unless you pass the _NoOptions flag to your calls. */ + fun setColorEditOptions(flags_: ColorEditFlags) { + var flags = flags_ + if (flags hasnt Cef._DisplayMask) flags = flags or (Cef.DefaultOptions and Cef._DisplayMask) + if (flags hasnt Cef._DataTypeMask) flags = flags or (Cef.DefaultOptions and Cef._DataTypeMask) + if (flags hasnt Cef._PickerMask) flags = flags or (Cef.DefaultOptions and Cef._PickerMask) + if (flags hasnt Cef._InputMask) flags = flags or (Cef.DefaultOptions and Cef._InputMask) + assert((flags and Cef._DisplayMask).isPowerOfTwo) { "Check only 1 option is selected" } + assert((flags and Cef._DataTypeMask).isPowerOfTwo) { "Check only 1 option is selected" } + assert((flags and Cef._PickerMask).isPowerOfTwo) { "Check only 1 option is selected" } + assert((flags and Cef._InputMask).isPowerOfTwo) { "Check only 1 option is selected" } + g.colorEditOptions = flags + } - // RGB/HSV 0..255 Sliders - val wItemOne = 1f max floor((wInputs - style.itemInnerSpacing.x * (components - 1)) / components) - val wItemLast = 1f max floor(wInputs - (wItemOne + style.itemInnerSpacing.x) * (components - 1)) + companion object { + val ids = arrayOf("##X", "##Y", "##Z", "##W") + val fmtTableInt = arrayOf(arrayOf("%3d", "%3d", "%3d", "%3d"), // Short display + arrayOf("R:%3d", "G:%3d", "B:%3d", "A:%3d"), // Long display for RGBA + arrayOf("H:%3d", "S:%3d", "V:%3d", "A:%3d")) // Long display for HSVA + val fmtTableFloat = arrayOf(arrayOf("%.3f", "%.3f", "%.3f", "%.3f"), // Short display + arrayOf("R:%.3f", "G:%.3f", "B:%.3f", "A:%.3f"), // Long display for RGBA + arrayOf("H:%.3f", "S:%.3f", "V:%.3f", "A:%.3f")) // Long display for HSVA - val hidePrefix = wItemOne <= calcTextSize(if (flags has Cef.Float) "M:0.000" else "M:000").x - val fmtIdx = if (hidePrefix) 0 else if (flags has Cef.DisplayHSV) 2 else 1 + fun DrawList.renderArrowsForVerticalBar(pos: Vec2, halfSz: Vec2, barW: Float, alpha: Float) { + val alpha8 = F32_TO_INT8_SAT(alpha) + renderArrowPointingAt(Vec2(pos.x + halfSz.x + 1, pos.y), Vec2(halfSz.x + 2, halfSz.y + 1), Dir.Right, COL32(0, 0, 0, alpha8)) + renderArrowPointingAt(Vec2(pos.x + halfSz.x, pos.y), halfSz, Dir.Right, COL32(255, 255, 255, alpha8)) + renderArrowPointingAt(Vec2(pos.x + barW - halfSz.x - 1, pos.y), Vec2(halfSz.x + 2, halfSz.y + 1), Dir.Left, COL32(0, 0, 0, alpha8)) + renderArrowPointingAt(Vec2(pos.x + barW - halfSz.x, pos.y), halfSz, Dir.Left, COL32(255, 255, 255, alpha8)) + } - repeat(components) { n -> - if (n > 0) - sameLine(0f, style.itemInnerSpacing.x) - setNextItemWidth(if (n + 1 < components) wItemOne else wItemLast) + /** ColorEdit supports RGB and HSV inputs. In case of RGB input resulting color may have undefined hue and/or saturation. + * Since widget displays both RGB and HSV values we must preserve hue and saturation to prevent these values resetting. */ + fun colorEditRestoreHS(rgb: Vec3, hsv: Vec3) = colorEditRestoreHS(rgb.r, rgb.g, rgb.b, hsv.x, hsv.y, hsv.z, hsv::put) - // Disable Hue edit when Saturation is zero - // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0. - if (flags has Cef.Float) { - valueChanged /= dragScalar(ids[n], f, n, 1f / 255f, 0f, if (hdr) 0f else 1f, fmtTableFloat[fmtIdx][n]) - valueChangedAsFloat /= valueChanged - } else - valueChanged /= dragInt(ids[n], i, n, 1f, 0, if (hdr) 0 else 255, fmtTableInt[fmtIdx][n]) - if (flags hasnt Cef.NoOptions) - openPopupOnItemClick("context", PopupFlag.MouseButtonRight) - } + /** ColorEdit supports RGB and HSV inputs. In case of RGB input resulting color may have undefined hue and/or saturation. + * Since widget displays both RGB and HSV values we must preserve hue and saturation to prevent these values resetting. */ + fun colorEditRestoreHS(rgb: Vec4, hsv: Vec3) = colorEditRestoreHS(rgb.r, rgb.g, rgb.b, hsv.x, hsv.y, hsv.z, hsv::put) - } else if (flags has Cef.DisplayHEX && flags hasnt Cef.NoInputs) { - // RGB Hexadecimal Input - val buf = when { - alpha -> "#%02X%02X%02X%02X".format(style.locale, glm.clamp(i[0], 0, 255), glm.clamp(i[1], 0, 255), glm.clamp(i[2], 0, 255), glm.clamp(i[3], 0, 255)) - else -> "#%02X%02X%02X".format(style.locale, glm.clamp(i[0], 0, 255), glm.clamp(i[1], 0, 255), glm.clamp(i[2], 0, 255)) - }.toByteArray(64) - setNextItemWidth(wInputs) - if (inputText("##Text", buf, Itf.CharsHexadecimal or Itf.CharsUppercase)) { - valueChanged = true - var p = 0 - val str = buf.cStr - while (str[p] == '#' || str[p].isBlankA) - p++ - i[0] = 0; i[1] = 0; i[2] = 0 - i[3] = 0xFF // alpha default to 255 is not parsed by scanf (e.g. inputting #FFFFFF omitting alpha) - str.substring(p).scanHex(i, if (alpha) 4 else 3, 2) // Treat at unsigned (%X is unsigned) - } - if (flags hasnt Cef.NoOptions) - openPopupOnItemClick("context", PopupFlag.MouseButtonRight) - } + /** ColorEdit supports RGB and HSV inputs. In case of RGB input resulting color may have undefined hue and/or saturation. + * Since widget displays both RGB and HSV values we must preserve hue and saturation to prevent these values resetting. */ + fun colorEditRestoreHS(x: Float, y: Float, z: Float, hsv: Vec3) = colorEditRestoreHS(x, y, z, hsv.x, hsv.y, hsv.z, hsv::put) - var pickerActiveWindow: Window? = null - if (flags hasnt Cef.NoSmallPreview) { - val buttonOffsetX = when { - flags has Cef.NoInputs || style.colorButtonPosition == Dir.Left -> 0f - else -> wInputs + style.itemInnerSpacing.x - } - window.dc.cursorPos.put(pos.x + buttonOffsetX, pos.y) - - val colVec4 = Vec4(col[0], col[1], col[2], if (alpha) col[3] else 1f) - if (colorButton("##ColorButton", colVec4, flags)) - if (flags hasnt Cef.NoPicker) { - // Store current color and open a picker - g.colorPickerRef put colVec4 - openPopup("picker") - setNextWindowPos(g.lastItemData.rect.bl + Vec2(0f, style.itemSpacing.y)) - } - if (flags hasnt Cef.NoOptions) - openPopupOnItemClick("context", PopupFlag.MouseButtonRight) - - if (beginPopup("picker")) - if (g.currentWindow!!.beginCount == 1) { - pickerActiveWindow = g.currentWindow - if (0 != labelDisplayEnd) { - textEx(label, labelDisplayEnd) - spacing() - } - val pickerFlagsToForward = Cef._DataTypeMask or Cef._PickerMask or Cef._InputMask or Cef.HDR or Cef.NoAlpha or Cef.AlphaBar - val pickerFlags = (flagsUntouched and pickerFlagsToForward) or Cef._DisplayMask or Cef._DisplayMask or Cef.NoLabel or Cef.AlphaPreviewHalf - setNextItemWidth(squareSz * 12f) // Use 256 + bar sizes? - val p = g.colorPickerRef to FloatArray(4) - valueChanged /= colorPicker4("##picker", col, pickerFlags, p) - g.colorPickerRef put p - endPopup() - } - } + /** ColorEdit supports RGB and HSV inputs. In case of RGB input resulting color may have undefined hue and/or saturation. + * Since widget displays both RGB and HSV values we must preserve hue and saturation to prevent these values resetting. */ + fun colorEditRestoreHS(x: Float, y: Float, z: Float, hsv: Vec4) = colorEditRestoreHS(x, y, z, hsv.x, hsv.y, hsv.z, hsv::put) - if (0 != labelDisplayEnd && flags hasnt Cef.NoLabel) { - // Position not necessarily next to last submitted button (e.g. if style.ColorButtonPosition == ImGuiDir_Left), - // but we need to use SameLine() to setup baseline correctly. Might want to refactor SameLine() to simplify this. - sameLine(0f, style.itemInnerSpacing.x) - window.dc.cursorPos.x = pos.x + if (flags has Cef.NoInputs) wButton else wFull + style.itemInnerSpacing.x - textEx(label, labelDisplayEnd) - } + /** ColorEdit supports RGB and HSV inputs. In case of RGB input resulting color may have undefined hue and/or saturation. + * Since widget displays both RGB and HSV values we must preserve hue and saturation to prevent these values resetting. */ + fun colorEditRestoreHS(x: Float, y: Float, z: Float, h: Float, s: Float, v: Float, hsvSetter: Vec3Setter) { + var h = h + var s = s + // This check is optional. Suppose we have two color widgets side by side, both widgets display different colors, but both colors have hue and/or saturation undefined. + // With color check: hue/saturation is preserved in one widget. Editing color in one widget would reset hue/saturation in another one. + // Without color check: common hue/saturation would be displayed in all widgets that have hue/saturation undefined. + // g.ColorEditLastColor is stored as ImU32 RGB value: this essentially gives us color equality check with reduced precision. + // Tiny external color changes would not be detected and this check would still pass. This is OK, since we only restore hue/saturation _only_ if they are undefined, + // therefore this change flipping hue/saturation from undefined to a very tiny value would still be represented in color picker. + if (g.colorEditLastColor != floatsToU32(x, y, z, 0f)) return - // Convert back - if (valueChanged && pickerActiveWindow == null) { - if (!valueChangedAsFloat) for (n in 0..3) f[n] = i[n] / 255f - if (flags has Cef.DisplayHSV && flags has Cef.InputRGB) { - g.colorEditLastHue = f[0] - g.colorEditLastSat = f[1] - f.hsvToRGB() - g.colorEditLastColor = Vec4(f[0], f[1], f[2], 0f).u32 - } - if (flags has Cef.DisplayRGB && flags has Cef.InputHSV) - f.rgbToHSV() - col[0] = f[0] - col[1] = f[1] - col[2] = f[2] - if (alpha) col[3] = f[3] + // When s == 0, h is undefined. + // When h == 1 it wraps around to 0. + if (s == 0f || (h == 0f && g.colorEditLastHue == 1f)) h = g.colorEditLastHue + + // When v == 0, s is undefined. + if (v == 0f) s = g.colorEditLastSat + hsvSetter(h, s, v) } - popID() - endGroup() + } +} + +inline fun colorEdit4(label: String, x: Float, y: Float, z: Float, w: Float, flags_: ColorEditFlags = emptyFlags, colSetter: Vec4Setter): Boolean { + val window = currentWindow + if (window.skipItems) return false + + val squareSz = frameHeight + val wFull = calcItemWidth() + val wButton = if (flags_ has Cef.NoSmallPreview) 0f else squareSz + style.itemInnerSpacing.x + val wInputs = wFull - wButton + val labelDisplayEnd = findRenderedTextEnd(label) + g.nextItemData.clearFlags() + + beginGroup() + pushID(label) + + var flags = flags_ + + // If we're not showing any slider there's no point in doing any HSV conversions + if (flags has Cef.NoInputs) flags = (flags wo Cef._DisplayMask) or Cef.DisplayRGB or Cef.NoOptions + + // Context menu: display and modify options (before defaults are applied) + if (flags hasnt Cef.NoOptions) colorEditOptionsPopup(x, y, z, w, flags) + + // Read stored options + if (flags hasnt Cef._DisplayMask) flags = flags or (g.colorEditOptions and Cef._DisplayMask) + if (flags hasnt Cef._DataTypeMask) flags = flags or (g.colorEditOptions and Cef._DataTypeMask) + if (flags hasnt Cef._PickerMask) flags = flags or (g.colorEditOptions and Cef._PickerMask) + if (flags hasnt Cef._InputMask) flags = flags or (g.colorEditOptions and Cef._InputMask) + flags = flags or (g.colorEditOptions wo (Cef._DisplayMask or Cef._DataTypeMask or Cef._PickerMask or Cef._InputMask)) + assert((flags and Cef._DisplayMask).isPowerOfTwo) { "Check that only 1 is selected" } + assert((flags and Cef._InputMask).isPowerOfTwo) { "Check that only 1 is selected" } + + val alpha = flags hasnt Cef.NoAlpha + val hdr = flags has Cef.HDR + val components = if (alpha) 4 else 3 + + // Convert to the formats we need + val f = Vec4(x, y, z, if (alpha) w else 1f) + if (flags has Cef.InputHSV && flags has Cef.DisplayRGB) f.hsvToRGB() + else if (flags has Cef.InputRGB && flags has Cef.DisplayHSV) { + // Hue is lost when converting from greyscale rgb (saturation=0). Restore it. + f.rgbToHSV() + colorEditRestoreHS(x, y, z, f) + } - // Drag and Drop Target - // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test. - if (g.lastItemData.statusFlags has ItemStatusFlag.HoveredRect && beginDragDropTarget()) { - var acceptedDragDrop = false - acceptDragDropPayload(PAYLOAD_TYPE_COLOR_3F)?.let { - val data = it.data!! as Vec4 - for (j in 0..2) // Preserve alpha if any //-V512 //-V1086 - col[j] = data.array[j] - acceptedDragDrop = true - valueChanged = true - } - acceptDragDropPayload(PAYLOAD_TYPE_COLOR_4F)?.let { - val floats = (it.data!! as Vec4).array - for (j in 0 until components) - col[j] = floats[j] - acceptedDragDrop = true - valueChanged = true - } + val i = IntArray(4) { F32_TO_INT8_UNBOUND(f[it]) } - // Drag-drop payloads are always RGB - if (acceptedDragDrop && flags has Cef.InputHSV) - col.rgbToHSV() - endDragDropTarget() - } + var valueChanged = false + var valueChangedAsFloat = false - // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4(). - if (pickerActiveWindow != null && g.activeId != 0 && g.activeIdWindow === pickerActiveWindow) - g.lastItemData.id = g.activeId + val pos = Vec2(window.dc.cursorPos) + val inputsOffsetX = if (style.colorButtonPosition == Dir.Left) wButton else 0f + window.dc.cursorPos.x = pos.x + inputsOffsetX - if (valueChanged && g.lastItemData.id != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId - markItemEdited(g.lastItemData.id) + if (flags has (Cef.DisplayRGB or Cef.DisplayHSV) && flags hasnt Cef.NoInputs) { - return valueChanged - } + // RGB/HSV 0..255 Sliders + val wItemOne = 1f max floor((wInputs - style.itemInnerSpacing.x * (components - 1)) / components) + val wItemLast = 1f max floor(wInputs - (wItemOne + style.itemInnerSpacing.x) * (components - 1)) - fun String.scanHex(ints: IntArray, count: Int = ints.size, precision: Int) { - var c = 0 - for (i in 0 until count) { - val end = glm.min((i + 1) * precision, length) - if (c >= end) - break - ints[i] = substring(c, end).toInt(16) - c += precision - } - } + val hidePrefix = wItemOne <= calcTextSize(if (flags has Cef.Float) "M:0.000" else "M:000").x + val fmtIdx = if (hidePrefix) 0 else if (flags has Cef.DisplayHSV) 2 else 1 - fun colorEditVec4(label: String, col: Vec4, flags: ColorEditFlags = emptyFlags): Boolean { - val col4 = floatArrayOf(col.x, col.y, col.z, col.w) - val valueChanged = colorEdit4(label, col4, flags) - col.x = col4[0] - col.y = col4[1] - col.z = col4[2] - col.w = col4[3] - return valueChanged - } + repeat(components) { n -> + if (n > 0) sameLine(0f, style.itemInnerSpacing.x) + setNextItemWidth(if (n + 1 < components) wItemOne else wItemLast) - fun colorPicker3(label: String, col: Vec4, flags: ColorEditFlags = emptyFlags): Boolean = - colorPicker3(label, col to _fa, flags) - .also { col put _fa } + // Disable Hue edit when Saturation is zero + // FIXME: When ImGuiColorEditFlags_HDR flag is passed HS values snap in weird ways when SV values go below 0. + if (flags has Cef.Float) { + valueChanged /= drag(ids[n], f mutablePropertyAt n, 1f / 255f, 0f, if (hdr) 0f else 1f, fmtTableFloat[fmtIdx][n]) + valueChangedAsFloat /= valueChanged + } else valueChanged /= drag(ids[n], i, n, 1f, 0, if (hdr) 0 else 255, fmtTableInt[fmtIdx][n]) + if (flags hasnt Cef.NoOptions) openPopupOnItemClick("context", PopupFlag.MouseButtonRight) + } - fun colorPicker3(label: String, col: FloatArray, flags: ColorEditFlags = emptyFlags): Boolean { - val col4 = floatArrayOf(*col, 1f) - if (!colorPicker4(label, col4, flags or Cef.NoAlpha)) return false - col[0] = col4[0]; col[1] = col4[1]; col[2] = col4[2] - return true + } else if (flags has Cef.DisplayHEX && flags hasnt Cef.NoInputs) { + // RGB Hexadecimal Input + val buf = when { + alpha -> "#%02X%02X%02X%02X".format(style.locale, glm.clamp(i[0], 0, 255), glm.clamp(i[1], 0, 255), glm.clamp(i[2], 0, 255), glm.clamp(i[3], 0, 255)) + + else -> "#%02X%02X%02X".format(style.locale, glm.clamp(i[0], 0, 255), glm.clamp(i[1], 0, 255), glm.clamp(i[2], 0, 255)) + }.toByteArray(64) + setNextItemWidth(wInputs) + if (inputText("##Text", buf, Itf.CharsHexadecimal or Itf.CharsUppercase)) { + valueChanged = true + var p = 0 + val str = buf.cStr + while (str[p] == '#' || str[p].isBlankA) p++ + i[0] = 0; i[1] = 0; i[2] = 0 + i[3] = 0xFF // alpha default to 255 is not parsed by scanf (e.g. inputting #FFFFFF omitting alpha) + str.substring(p).scanHex(i, 2, if (alpha) 4 else 3) // Treat at unsigned (%X is unsigned) + } + if (flags hasnt Cef.NoOptions) openPopupOnItemClick("context", PopupFlag.MouseButtonRight) } - /** ColorPicker - * Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. - * (In C++ the 'float col[4]' notation for a function argument is equivalent to 'float* col', we only specify a size to facilitate understanding of the code.) - * FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop - * (if automatic height makes a vertical scrollbar appears, affecting automatic width..) - * FIXME: this is trying to be aware of style.Alpha but not fully correct. Also, the color wheel will have overlapping glitches with (style.Alpha < 1.0) */ - fun colorPicker4(label: String, col: Vec4, flags: ColorEditFlags = emptyFlags, refCol: Vec4? = null): Boolean = - colorPicker4(label, col to _fa, flags, refCol?.to(_fa2)) - .also { col put _fa; refCol?.put(_fa2) } - - /** ColorPicker - * Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. - * (In C++ the 'float col[4]' notation for a function argument is equivalent to 'float* col', we only specify a size to facilitate understanding of the code.) - * FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop - * (if automatic height makes a vertical scrollbar appears, affecting automatic width..) - * FIXME: this is trying to be aware of style.Alpha but not fully correct. Also, the color wheel will have overlapping glitches with (style.Alpha < 1.0) */ - fun colorPicker4(label: String, col: FloatArray, flags_: ColorEditFlags = emptyFlags, refCol: FloatArray? = null): Boolean { + var pickerActiveWindow: Window? = null + if (flags hasnt Cef.NoSmallPreview) { + val buttonOffsetX = when { + flags has Cef.NoInputs || style.colorButtonPosition == Dir.Left -> 0f + else -> wInputs + style.itemInnerSpacing.x + } + window.dc.cursorPos.put(pos.x + buttonOffsetX, pos.y) + if (colorButton("##ColorButton", x, y, z, if (alpha) w else 1f, flags)) if (flags hasnt Cef.NoPicker) { + // Store current color and open a picker + g.colorPickerRef.put(x, y, z, if (alpha) w else 1f) + openPopup("picker") + setNextWindowPos(g.lastItemData.rect.bl + Vec2(0f, style.itemSpacing.y)) + } + if (flags hasnt Cef.NoOptions) openPopupOnItemClick("context", PopupFlag.MouseButtonRight) - val window = currentWindow - if (window.skipItems) - return false + if (beginPopup("picker")) if (g.currentWindow!!.beginCount == 1) { + pickerActiveWindow = g.currentWindow + if (0 != labelDisplayEnd) { + textEx(label, labelDisplayEnd) + spacing() + } + val pickerFlagsToForward = Cef._DataTypeMask or Cef._PickerMask or Cef._InputMask or Cef.HDR or Cef.NoAlpha or Cef.AlphaBar + val pickerFlags = (flags_ and pickerFlagsToForward) or Cef._DisplayMask or Cef._DisplayMask or Cef.NoLabel or Cef.AlphaPreviewHalf + setNextItemWidth(squareSz * 12f) // Use 256 + bar sizes? + valueChanged /= colorPicker4("##picker", x, y, z, w, pickerFlags, g.colorPickerRef, colSetter) + endPopup() + } + } - val drawList = window.drawList + if (0 != labelDisplayEnd && flags hasnt Cef.NoLabel) { + // Position not necessarily next to last submitted button (e.g. if style.ColorButtonPosition == ImGuiDir_Left), + // but we need to use SameLine() to setup baseline correctly. Might want to refactor SameLine() to simplify this. + sameLine(0f, style.itemInnerSpacing.x) + window.dc.cursorPos.x = pos.x + if (flags has Cef.NoInputs) wButton else wFull + style.itemInnerSpacing.x + textEx(label, labelDisplayEnd) + } - val width = calcItemWidth() - g.nextItemData.clearFlags() + // Convert back + if (valueChanged && pickerActiveWindow == null) { + if (!valueChangedAsFloat) for (n in 0..3) f[n] = i[n] / 255f + if (flags has Cef.DisplayHSV && flags has Cef.InputRGB) { + g.colorEditLastHue = f[0] + g.colorEditLastSat = f[1] + f.hsvToRGB() + g.colorEditLastColor = floatsToU32(f[0], f[1], f[2], 0f) + } + if (flags has Cef.DisplayRGB && flags has Cef.InputHSV) f.rgbToHSV() + f.into(colSetter, if (alpha) f.w else w) + } + popID() + endGroup() + + // Drag and Drop Target + // NB: The flag test is merely an optional micro-optimization, BeginDragDropTarget() does the same test. + if (g.lastItemData.statusFlags has ItemStatusFlag.HoveredRect && beginDragDropTarget()) { + acceptDragDropPayload(PAYLOAD_TYPE_COLOR_3F)?.let { + val data = it.data!! as Vec4 + // Drag-drop payloads are always RGB + if (flags has Cef.InputHSV) colorConvertRGBtoHSV(data) { h, s, v -> + colSetter(h, s, v, w) + } else data.into(colSetter, w) + valueChanged = true + } + acceptDragDropPayload(PAYLOAD_TYPE_COLOR_4F)?.let { + val data = it.data!! as Vec4 + val w = if (alpha) data.w else w + // Drag-drop payloads are always RGB + if (flags has Cef.InputHSV) colorConvertRGBtoHSV(data) { h, s, v -> + colSetter(h, s, v, w) + } else data.into(colSetter, w) + valueChanged = true + } - pushID(label) - beginGroup() + endDragDropTarget() + } - var flags = flags_ - if (flags hasnt Cef.NoSidePreview) - flags = flags or Cef.NoSmallPreview - - // Context menu: display and store options. - if (flags hasnt Cef.NoOptions) - colorPickerOptionsPopup(col, flags) - - // Read stored options - if (flags hasnt Cef._PickerMask) - flags = flags or ((if (g.colorEditOptions has Cef._PickerMask) g.colorEditOptions else Cef.DefaultOptions) and Cef._PickerMask) - if (flags hasnt Cef._InputMask) - flags = flags or ((if (g.colorEditOptions has Cef._InputMask) g.colorEditOptions else Cef.DefaultOptions) and Cef._InputMask) - assert((flags and Cef._PickerMask).isPowerOfTwo) { "Check that only 1 is selected" } - assert((flags and Cef._InputMask).isPowerOfTwo) // Check that only 1 is selected - if (flags hasnt Cef.NoOptions) - flags = flags or (g.colorEditOptions and Cef.AlphaBar) - - // Setup - val components = if (flags has Cef.NoAlpha) 3 else 4 - val alphaBar = flags has Cef.AlphaBar && flags hasnt Cef.NoAlpha - val pickerPos = Vec2(window.dc.cursorPos) - val squareSz = frameHeight - val barsWidth = squareSz // Arbitrary smallish width of Hue/Alpha picking bars - // Saturation/Value picking box - val svPickerSize = glm.max(barsWidth * 1, width - (if (alphaBar) 2 else 1) * (barsWidth + style.itemInnerSpacing.x)) - val bar0PosX = pickerPos.x + svPickerSize + style.itemInnerSpacing.x - val bar1PosX = bar0PosX + barsWidth + style.itemInnerSpacing.x - val barsTrianglesHalfSz = floor(barsWidth * 0.2f) - - val backupInitialCol = FloatArray(4) { col.getOrElse(it) { 0f } } - - val wheelThickness = svPickerSize * 0.08f - val wheelROuter = svPickerSize * 0.50f - val wheelRInner = wheelROuter - wheelThickness - val wheelCenter = Vec2(pickerPos.x + (svPickerSize + barsWidth) * 0.5f, pickerPos.y + svPickerSize * 0.5f) - - // Note: the triangle is displayed rotated with trianglePa pointing to Hue, but most coordinates stays unrotated for logic. - val triangleR = wheelRInner - (svPickerSize * 0.027f).i - val trianglePa = Vec2(triangleR, 0f) // Hue point. - val trianglePb = Vec2(triangleR * -0.5f, triangleR * -0.866025f) // Black point. - val trianglePc = Vec2(triangleR * -0.5f, triangleR * +0.866025f) // White point. - - val hsv = FloatArray(3) { col[it] } - val rgb = FloatArray(3) { col[it] } - if (flags has Cef.InputRGB) { - // Hue is lost when converting from greyscale rgb (saturation=0). Restore it. - colorConvertRGBtoHSV(rgb, hsv) - colorEditRestoreHS(col, hsv) - } else if (flags has Cef.InputHSV) - colorConvertHSVtoRGB(hsv, rgb) - var (H, S, V) = hsv - var (R, G, B) = rgb // turn to capital as cpp to avoid clashing with ImGui `g` - - var valueChanged = false - var valueChangedH = false - var valueChangedSv = false - - pushItemFlag(ItemFlag.NoNav, true) - if (flags has Cef.PickerHueWheel) { - // Hue wheel + SV triangle logic - invisibleButton("hsv", Vec2(svPickerSize + style.itemInnerSpacing.x + barsWidth, svPickerSize)) - if (isItemActive) { - val initialOff = io.mouseClickedPos[0] - wheelCenter - val currentOff = io.mousePos - wheelCenter - val initialDist2 = initialOff.lengthSqr - if (initialDist2 >= (wheelRInner - 1) * (wheelRInner - 1) && initialDist2 <= (wheelROuter + 1) * (wheelROuter + 1)) { - // Interactive with Hue wheel - H = glm.atan(currentOff.y, currentOff.x) / glm.PIf * 0.5f - if (H < 0f) - H += 1f - valueChanged = true - valueChangedH = true + // When picker is being actively used, use its active id so IsItemActive() will function on ColorEdit4(). + if (pickerActiveWindow != null && g.activeId != 0 && g.activeIdWindow === pickerActiveWindow) g.lastItemData.id = g.activeId + + if (valueChanged && g.lastItemData.id != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId + markItemEdited(g.lastItemData.id) + + return valueChanged +} + +inline fun colorPicker3(label: String, r: Float, g: Float, b: Float, flags: ColorEditFlags = emptyFlags, colSetter: Vec3Setter): Boolean = colorPicker4(label, r, g, b, 1f, flags or Cef.NoAlpha) { x, y, z, _ -> colSetter(x, y, z) } + +/** ColorPicker + * Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. + * (In C++ the 'float col[4]' notation for a function argument is equivalent to 'float* col', we only specify a size to facilitate understanding of the code.) + * FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop + * (if automatic height makes a vertical scrollbar appears, affecting automatic width..) + * FIXME: this is trying to be aware of style.Alpha but not fully correct. Also, the color wheel will have overlapping glitches with (style.Alpha < 1.0) */ +inline fun colorPicker4(label: String, x: Float, y: Float, z: Float, w: Float, flags: ColorEditFlags = emptyFlags, refCol: Vec4? = null, colSetter: Vec4Setter = { _, _, _, _ -> }): Boolean { + val col = Vec4(x, y, z, w) + return colorPicker4(label, col, flags, refCol).also { col into colSetter } +} + +/** ColorPicker + * Note: only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set. + * (In C++ the 'float col[4]' notation for a function argument is equivalent to 'float* col', we only specify a size to facilitate understanding of the code.) + * FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop + * (if automatic height makes a vertical scrollbar appears, affecting automatic width..) + * FIXME: this is trying to be aware of style.Alpha but not fully correct. Also, the color wheel will have overlapping glitches with (style.Alpha < 1.0) */ +fun colorPicker4(label: String, col: Vec4, flags_: ColorEditFlags = emptyFlags, refCol: Vec4? = null): Boolean { + val window = currentWindow + if (window.skipItems) return false + + val drawList = window.drawList + + val width = calcItemWidth() + g.nextItemData.clearFlags() + + pushID(label) + beginGroup() + + var flags = flags_ + if (flags hasnt Cef.NoSidePreview) flags = flags or Cef.NoSmallPreview + + // Context menu: display and store options. + if (flags hasnt Cef.NoOptions) colorPickerOptionsPopup(col, flags) + + // Read stored options + if (flags hasnt Cef._PickerMask) flags = flags or ((if (g.colorEditOptions has Cef._PickerMask) g.colorEditOptions else Cef.DefaultOptions) and Cef._PickerMask) + if (flags hasnt Cef._InputMask) flags = flags or ((if (g.colorEditOptions has Cef._InputMask) g.colorEditOptions else Cef.DefaultOptions) and Cef._InputMask) + assert((flags and Cef._PickerMask).isPowerOfTwo) { "Check that only 1 is selected" } + assert((flags and Cef._InputMask).isPowerOfTwo) // Check that only 1 is selected + if (flags hasnt Cef.NoOptions) flags = flags or (g.colorEditOptions and Cef.AlphaBar) + + // Setup + val components = if (flags has Cef.NoAlpha) 3 else 4 + val alphaBar = flags has Cef.AlphaBar && flags hasnt Cef.NoAlpha + val pickerPos = Vec2(window.dc.cursorPos) + val squareSz = frameHeight + val barsWidth = squareSz // Arbitrary smallish width of Hue/Alpha picking bars + // Saturation/Value picking box + val svPickerSize = glm.max(barsWidth * 1, width - (if (alphaBar) 2 else 1) * (barsWidth + style.itemInnerSpacing.x)) + val bar0PosX = pickerPos.x + svPickerSize + style.itemInnerSpacing.x + val bar1PosX = bar0PosX + barsWidth + style.itemInnerSpacing.x + val barsTrianglesHalfSz = floor(barsWidth * 0.2f) + + val backupInitialCol = Vec4(col) + + val wheelThickness = svPickerSize * 0.08f + val wheelROuter = svPickerSize * 0.50f + val wheelRInner = wheelROuter - wheelThickness + val wheelCenter = Vec2(pickerPos.x + (svPickerSize + barsWidth) * 0.5f, pickerPos.y + svPickerSize * 0.5f) + + // Note: the triangle is displayed rotated with trianglePa pointing to Hue, but most coordinates stays unrotated for logic. + val triangleR = wheelRInner - (svPickerSize * 0.027f).i + val trianglePa = Vec2(triangleR, 0f) // Hue point. + val trianglePb = Vec2(triangleR * -0.5f, triangleR * -0.866025f) // Black point. + val trianglePc = Vec2(triangleR * -0.5f, triangleR * +0.866025f) // White point. + + val hsv = Vec3(col) + val rgb = Vec3(col) + if (flags has Cef.InputRGB) { + // Hue is lost when converting from greyscale rgb (saturation=0). Restore it. + colorConvertRGBtoHSV(rgb, hsv) + colorEditRestoreHS(rgb, hsv) + } else if (flags has Cef.InputHSV) colorConvertHSVtoRGB(hsv, rgb) + var (H, S, V) = hsv + var (R, G, B) = rgb // turn to capital as cpp to avoid clashing with ImGui `g` + + var valueChanged = false + var valueChangedH = false + var valueChangedSv = false + + pushItemFlag(ItemFlag.NoNav, true) + if (flags has Cef.PickerHueWheel) { + // Hue wheel + SV triangle logic + invisibleButton("hsv", Vec2(svPickerSize + style.itemInnerSpacing.x + barsWidth, svPickerSize)) + if (isItemActive) { + val initialOff = io.mouseClickedPos[0] - wheelCenter + val currentOff = io.mousePos - wheelCenter + val initialDist2 = initialOff.lengthSqr + if (initialDist2 >= (wheelRInner - 1) * (wheelRInner - 1) && initialDist2 <= (wheelROuter + 1) * (wheelROuter + 1)) { + // Interactive with Hue wheel + H = glm.atan(currentOff.y, currentOff.x) / glm.PIf * 0.5f + if (H < 0f) H += 1f + valueChanged = true + valueChangedH = true + } + val cosHueAngle = glm.cos(-H * 2f * glm.PIf) + val sinHueAngle = glm.sin(-H * 2f * glm.PIf) + if (triangleContainsPoint(trianglePa, trianglePb, trianglePc, initialOff.rotate(cosHueAngle, sinHueAngle))) { + // Interacting with SV triangle + val currentOffUnrotated = currentOff.rotate(cosHueAngle, sinHueAngle) + if (!triangleContainsPoint(trianglePa, trianglePb, trianglePc, currentOffUnrotated)) { + currentOffUnrotated put triangleClosestPoint(trianglePa, trianglePb, trianglePc, currentOffUnrotated) } - val cosHueAngle = glm.cos(-H * 2f * glm.PIf) - val sinHueAngle = glm.sin(-H * 2f * glm.PIf) - if (triangleContainsPoint(trianglePa, trianglePb, trianglePc, initialOff.rotate(cosHueAngle, sinHueAngle))) { - // Interacting with SV triangle - val currentOffUnrotated = currentOff.rotate(cosHueAngle, sinHueAngle) - if (!triangleContainsPoint(trianglePa, trianglePb, trianglePc, currentOffUnrotated)) - currentOffUnrotated put triangleClosestPoint(trianglePa, trianglePb, trianglePc, currentOffUnrotated) - val (uu, vv, _) = triangleBarycentricCoords(trianglePa, trianglePb, trianglePc, currentOffUnrotated) + triangleBarycentricCoords(trianglePa, trianglePb, trianglePc, currentOffUnrotated) { uu, vv, _ -> V = glm.clamp(1f - vv, 0.0001f, 1f) S = glm.clamp(uu / V, 0.0001f, 1f) - valueChangedSv = true - valueChanged = true } - } - if (flags hasnt Cef.NoOptions) - openPopupOnItemClick("context", PopupFlag.MouseButtonRight) - - } else if (flags has Cef.PickerHueBar) { - // SV rectangle logic - invisibleButton("sv", Vec2(svPickerSize)) - if (isItemActive) { - S = saturate((io.mousePos.x - pickerPos.x) / (svPickerSize - 1)) - V = 1f - saturate((io.mousePos.y - pickerPos.y) / (svPickerSize - 1)) - - // Greatly reduces hue jitter and reset to 0 when hue == 255 and color is rapidly modified using SV square. - if (g.colorEditLastColor == Vec4(col[0], col[1], col[2], 0f).u32) - H = g.colorEditLastHue - valueChangedSv = true; valueChanged = true - } - if (flags hasnt Cef.NoOptions) - openPopupOnItemClick("context", PopupFlag.MouseButtonRight) - // Hue bar logic - cursorScreenPos = Vec2(bar0PosX, pickerPos.y) - invisibleButton("hue", Vec2(barsWidth, svPickerSize)) - if (isItemActive) { - H = saturate((io.mousePos.y - pickerPos.y) / (svPickerSize - 1)) - valueChangedH = true + valueChangedSv = true valueChanged = true } } - - // Alpha bar logic - if (alphaBar) { - cursorScreenPos = Vec2(bar1PosX, pickerPos.y) - invisibleButton("alpha", Vec2(barsWidth, svPickerSize)) - if (isItemActive) { - col[3] = 1f - saturate((io.mousePos.y - pickerPos.y) / (svPickerSize - 1)) - valueChanged = true - } + if (flags hasnt Cef.NoOptions) openPopupOnItemClick("context", PopupFlag.MouseButtonRight) + + } else if (flags has Cef.PickerHueBar) { + // SV rectangle logic + invisibleButton("sv", Vec2(svPickerSize)) + if (isItemActive) { + S = saturate((io.mousePos.x - pickerPos.x) / (svPickerSize - 1)) + V = 1f - saturate((io.mousePos.y - pickerPos.y) / (svPickerSize - 1)) + + // Greatly reduces hue jitter and reset to 0 when hue == 255 and color is rapidly modified using SV square. + if (g.colorEditLastColor == Vec4(col[0], col[1], col[2], 0f).u32) H = g.colorEditLastHue + valueChangedSv = true; valueChanged = true } - popItemFlag() // ItemFlag.NoNav + if (flags hasnt Cef.NoOptions) openPopupOnItemClick("context", PopupFlag.MouseButtonRight) + // Hue bar logic + cursorScreenPos = Vec2(bar0PosX, pickerPos.y) + invisibleButton("hue", Vec2(barsWidth, svPickerSize)) + if (isItemActive) { + H = saturate((io.mousePos.y - pickerPos.y) / (svPickerSize - 1)) + valueChangedH = true + valueChanged = true + } + } - if (flags hasnt Cef.NoSidePreview) { - sameLine(0f, style.itemInnerSpacing.x) - beginGroup() + // Alpha bar logic + if (alphaBar) { + cursorScreenPos = Vec2(bar1PosX, pickerPos.y) + invisibleButton("alpha", Vec2(barsWidth, svPickerSize)) + if (isItemActive) { + col[3] = 1f - saturate((io.mousePos.y - pickerPos.y) / (svPickerSize - 1)) + valueChanged = true } + } + popItemFlag() // ItemFlag.NoNav - if (flags hasnt Cef.NoLabel) { - val labelDisplayEnd = findRenderedTextEnd(label) - if (0 != labelDisplayEnd) { - if (flags has Cef.NoSidePreview) - sameLine(0f, style.itemInnerSpacing.x) - textEx(label, labelDisplayEnd) - } + if (flags hasnt Cef.NoSidePreview) { + sameLine(0f, style.itemInnerSpacing.x) + beginGroup() + } + + if (flags hasnt Cef.NoLabel) { + val labelDisplayEnd = findRenderedTextEnd(label) + if (0 != labelDisplayEnd) { + if (flags has Cef.NoSidePreview) sameLine(0f, style.itemInnerSpacing.x) + textEx(label, labelDisplayEnd) } - if (flags hasnt Cef.NoSidePreview) { - pushItemFlag(ItemFlag.NoNavDefaultFocus, true) - val colV4 = Vec4(col[0], col[1], col[2], if (flags has Cef.NoAlpha) 1f else col[3]) - if (flags has Cef.NoLabel) - text("Current") - - val subFlagsToForward = Cef._InputMask or Cef.HDR or Cef.AlphaPreview or Cef.AlphaPreviewHalf or Cef.NoTooltip - colorButton("##current", colV4, flags and subFlagsToForward, Vec2(squareSz * 3, squareSz * 2)) - refCol?.let { - text("Original") - val refColV4 = Vec4(it[0], it[1], it[2], if (flags has Cef.NoAlpha) 1f else it[3]) - if (colorButton("##original", refColV4, flags and subFlagsToForward, Vec2(squareSz * 3, squareSz * 2))) { - for (i in 0 until components) col[i] = it[i] - valueChanged = true - } + } + if (flags hasnt Cef.NoSidePreview) { + pushItemFlag(ItemFlag.NoNavDefaultFocus, true) + if (flags has Cef.NoLabel) text("Current") + + val subFlagsToForward = Cef._InputMask or Cef.HDR or Cef.AlphaPreview or Cef.AlphaPreviewHalf or Cef.NoTooltip + colorButton("##current", col[0], col[1], col[2], if (flags has Cef.NoAlpha) 1f else col[3], flags and subFlagsToForward, Vec2(squareSz * 3, squareSz * 2)) + if (refCol != null) { + text("Original") + if (colorButton("##original", refCol[0], refCol[1], refCol[2], if (flags has Cef.NoAlpha) 1f else refCol[3], flags and subFlagsToForward, Vec2(squareSz * 3, squareSz * 2))) { + repeat(components) { i -> col[i] = refCol[i] } + valueChanged = true } - popItemFlag() - endGroup() } + popItemFlag() + endGroup() + } - // Convert back color to RGB - if (valueChangedH || valueChangedSv) - if (flags has Cef.InputRGB) { - colorConvertHSVtoRGB(H, S, V, col) - g.colorEditLastHue = H - g.colorEditLastSat = S - g.colorEditLastColor = Vec4(col[0], col[1], col[2], 0f).u32 - } else if (flags has Cef.InputHSV) { - col[0] = H - col[1] = S - col[2] = V - } - - // R,G,B and H,S,V slider color editor - var valueChangedFixHueWrap = false - if (flags hasnt Cef.NoInputs) { - pushItemWidth((if (alphaBar) bar1PosX else bar0PosX) + barsWidth - pickerPos.x) - val subFlagsToForward = Cef._DataTypeMask or Cef._InputMask or Cef.HDR or Cef.NoAlpha or Cef.NoOptions or Cef.NoSmallPreview or - Cef.AlphaPreview or Cef.AlphaPreviewHalf - val subFlags = (flags and subFlagsToForward) or Cef.NoPicker - if (flags has Cef.DisplayRGB || flags hasnt Cef._DisplayMask) - if (colorEdit4("##rgb", col, subFlags or Cef.DisplayRGB)) { - // FIXME: Hackily differentiating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget. - // For the later we don't want to run the hue-wrap canceling code. If you are well versed in HSV picker please provide your input! (See #2050) - valueChangedFixHueWrap = g.activeId != 0 && !g.activeIdAllowOverlap - valueChanged = true - } - if (flags has Cef.DisplayHSV || flags hasnt Cef._DisplayMask) - valueChanged /= colorEdit4("##hsv", col, subFlags or Cef.DisplayHSV) - if (flags has Cef.DisplayHEX || flags hasnt Cef._DisplayMask) - valueChanged /= colorEdit4("##hex", col, subFlags or Cef.DisplayHEX) - popItemWidth() - } + // Convert back color to RGB + if (valueChangedH || valueChangedSv) if (flags has Cef.InputRGB) { + colorConvertHSVtoRGB(H, S, V, col) + g.colorEditLastHue = H + g.colorEditLastSat = S + g.colorEditLastColor = floatsToU32(col.x, col.y, col.z, 0f) + } else if (flags has Cef.InputHSV) { + col[0] = H + col[1] = S + col[2] = V + } - // Try to cancel hue wrap (after ColorEdit4 call), if any - if (valueChangedFixHueWrap && flags has Cef.InputRGB) { - val (newH, newS, newV) = colorConvertRGBtoHSV(col) - if (newH <= 0 && H > 0) { - if (newV <= 0 && V != newV) - colorConvertHSVtoRGB(H, S, if (newV <= 0) V * 0.5f else newV, col) - else if (newS <= 0) - colorConvertHSVtoRGB(H, if (newS <= 0) S * 0.5f else newS, newV, col) - } + // R,G,B and H,S,V slider color editor + var valueChangedFixHueWrap = false + if (flags hasnt Cef.NoInputs) { + pushItemWidth((if (alphaBar) bar1PosX else bar0PosX) + barsWidth - pickerPos.x) + val subFlagsToForward = Cef._DataTypeMask or Cef._InputMask or Cef.HDR or Cef.NoAlpha or Cef.NoOptions or Cef.NoSmallPreview or Cef.AlphaPreview or Cef.AlphaPreviewHalf + val subFlags = (flags and subFlagsToForward) or Cef.NoPicker + if (flags has Cef.DisplayRGB || flags hasnt Cef._DisplayMask) if (colorEdit4("##rgb", col, subFlags or Cef.DisplayRGB)) { + // FIXME: Hackily differentiating using the DragInt (ActiveId != 0 && !ActiveIdAllowOverlap) vs. using the InputText or DropTarget. + // For the later we don't want to run the hue-wrap canceling code. If you are well versed in HSV picker please provide your input! (See #2050) + valueChangedFixHueWrap = g.activeId != 0 && !g.activeIdAllowOverlap + valueChanged = true } + if (flags has Cef.DisplayHSV || flags hasnt Cef._DisplayMask) valueChanged /= colorEdit4("##hsv", col, subFlags or Cef.DisplayHSV) + if (flags has Cef.DisplayHEX || flags hasnt Cef._DisplayMask) valueChanged /= colorEdit4("##hex", col, subFlags or Cef.DisplayHEX) + popItemWidth() + } - if (valueChanged) { - if (flags has Cef.InputRGB) { - R = col[0] - G = col[1] - B = col[2] - colorConvertRGBtoHSV(R, G, B).let { - H = it[0] - S = it[1] - V = it[2] - } - hsv[0] = H; hsv[1] = S; hsv[2] = V - colorEditRestoreHS(col, hsv) // Fix local Hue as display below will use it immediately. - H = hsv[0]; S = hsv[1]; V = hsv[2] - } else if (flags has Cef.InputHSV) { - H = col[0] - S = col[1] - V = col[2] - colorConvertHSVtoRGB(H, S, V).let { - R = it[0] - G = it[1] - B = it[2] - } - } + // Try to cancel hue wrap (after ColorEdit4 call), if any + if (valueChangedFixHueWrap && flags has Cef.InputRGB) { + val (newH, newS, newV) = colorConvertRGBtoHSV(col) + if (newH <= 0 && H > 0) { + if (newV <= 0 && V != newV) colorConvertHSVtoRGB(H, S, V * 0.5f, col) + else if (newS <= 0) colorConvertHSVtoRGB(H, S * 0.5f, newV, col) } + } - val styleAlpha8 = F32_TO_INT8_SAT(style.alpha) - val colBlack = COL32(0, 0, 0, styleAlpha8) - val colWhite = COL32(255, 255, 255, styleAlpha8) - val colMidgrey = COL32(128, 128, 128, styleAlpha8) - val colHues = arrayOf(COL32(255, 0, 0, styleAlpha8), COL32(255, 255, 0, styleAlpha8), COL32(0, 255, 0, styleAlpha8), COL32(0, 255, 255, styleAlpha8), COL32(0, 0, 255, styleAlpha8), COL32(255, 0, 255, styleAlpha8), COL32(255, 0, 0, styleAlpha8)) - - val hueColorF = Vec4(1f, 1f, 1f, style.alpha); colorConvertHSVtoRGB(H, 1f, 1f, hueColorF::x, hueColorF::y, hueColorF::z) - val hueColor32 = hueColorF.u32 - val userCol32StripedOfAlpha = Vec4(R, G, B, style.alpha).u32 // Important: this is still including the main rendering/style alpha!! - - val svCursorPos = Vec2() - - if (flags has Cef.PickerHueWheel) { - // Render Hue Wheel - val aeps = 0.5f / wheelROuter // Half a pixel arc length in radians (2pi cancels out). - val segmentPerArc = glm.max(4, (wheelROuter / 12).i) - for (n in 0..5) { - val a0 = n / 6f * 2f * glm.PIf - aeps - val a1 = (n + 1f) / 6f * 2f * glm.PIf + aeps - val vertStartIdx = drawList.vtxBuffer.size - drawList.pathArcTo(wheelCenter, (wheelRInner + wheelROuter) * 0.5f, a0, a1, segmentPerArc) - drawList.pathStroke(colWhite, thickness = wheelThickness) - val vertEndIdx = drawList.vtxBuffer.size - - // Paint colors over existing vertices - val gradientP0 = Vec2(wheelCenter.x + a0.cos * wheelRInner, wheelCenter.y + a0.sin * wheelRInner) - val gradientP1 = Vec2(wheelCenter.x + a1.cos * wheelRInner, wheelCenter.y + a1.sin * wheelRInner) - drawList.shadeVertsLinearColorGradientKeepAlpha(vertStartIdx, vertEndIdx, gradientP0, gradientP1, colHues[n], colHues[n + 1]) + if (valueChanged) { + if (flags has Cef.InputRGB) { + R = col[0] + G = col[1] + B = col[2] + colorConvertRGBtoHSV(R, G, B) { h, s, v -> + H = h + S = s + V = v } - - // Render Cursor + preview on Hue Wheel - val cosHueAngle = glm.cos(H * 2f * glm.PIf) - val sinHueAngle = glm.sin(H * 2f * glm.PIf) - val hueCursorPos = Vec2(wheelCenter.x + cosHueAngle * (wheelRInner + wheelROuter) * 0.5f, - wheelCenter.y + sinHueAngle * (wheelRInner + wheelROuter) * 0.5f) - val hueCursorRad = wheelThickness * if (valueChangedH) 0.65f else 0.55f - val hueCursorSegments = glm.clamp((hueCursorRad / 1.4f).i, 9, 32) - drawList.addCircleFilled(hueCursorPos, hueCursorRad, hueColor32, hueCursorSegments) - drawList.addCircle(hueCursorPos, hueCursorRad + 1, colMidgrey, hueCursorSegments) - drawList.addCircle(hueCursorPos, hueCursorRad, colWhite, hueCursorSegments) - - // Render SV triangle (rotated according to hue) - val tra = wheelCenter + trianglePa.rotate(cosHueAngle, sinHueAngle) - val trb = wheelCenter + trianglePb.rotate(cosHueAngle, sinHueAngle) - val trc = wheelCenter + trianglePc.rotate(cosHueAngle, sinHueAngle) - val uvWhite = fontTexUvWhitePixel - drawList.primReserve(6, 6) - drawList.primVtx(tra, uvWhite, hueColor32) - drawList.primVtx(trb, uvWhite, hueColor32) - drawList.primVtx(trc, uvWhite, colWhite) - drawList.primVtx(tra, uvWhite, 0) - drawList.primVtx(trb, uvWhite, colBlack) - drawList.primVtx(trc, uvWhite, 0) - drawList.addTriangle(tra, trb, trc, colMidgrey, 1.5f) - svCursorPos put trc.lerp(tra, saturate(S)).lerp(trb, saturate(1 - V)) - } else if (flags has Cef.PickerHueBar) { - // Render SV Square - drawList.addRectFilledMultiColor(pickerPos, pickerPos + svPickerSize, colWhite, hueColor32, hueColor32, colWhite) - drawList.addRectFilledMultiColor(pickerPos, pickerPos + svPickerSize, 0, 0, colBlack, colBlack) - renderFrameBorder(pickerPos, pickerPos + svPickerSize, 0f) - // Sneakily prevent the circle to stick out too much - svCursorPos.x = glm.clamp(floor(pickerPos.x + saturate(S) * svPickerSize + 0.5f), pickerPos.x + 2, pickerPos.x + svPickerSize - 2) - svCursorPos.y = glm.clamp(floor(pickerPos.y + saturate(1 - V) * svPickerSize + 0.5f), pickerPos.y + 2, pickerPos.y + svPickerSize - 2) - - // Render Hue Bar - for (i in 0..5) { - val a = Vec2(bar0PosX, pickerPos.y + i * (svPickerSize / 6)) - val c = Vec2(bar0PosX + barsWidth, pickerPos.y + (i + 1) * (svPickerSize / 6)) - drawList.addRectFilledMultiColor(a, c, colHues[i], colHues[i], colHues[i + 1], colHues[i + 1]) + hsv[0] = H; hsv[1] = S; hsv[2] = V + colorEditRestoreHS(col, hsv) // Fix local Hue as display below will use it immediately. + H = hsv[0]; S = hsv[1]; V = hsv[2] + } else if (flags has Cef.InputHSV) { + H = col[0] + S = col[1] + V = col[2] + colorConvertHSVtoRGB(H, S, V) { r, g, b -> + R = r + G = g + B = b } - val bar0LineY = round(pickerPos.y + H * svPickerSize) - renderFrameBorder(Vec2(bar0PosX, pickerPos.y), Vec2(bar0PosX + barsWidth, pickerPos.y + svPickerSize), 0f) - drawList.renderArrowsForVerticalBar(Vec2(bar0PosX - 1, bar0LineY), Vec2(barsTrianglesHalfSz + 1, barsTrianglesHalfSz), barsWidth + 2f, style.alpha) } + } - // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range) - val svCursorRad = if (valueChangedSv) 10f else 6f - drawList.addCircleFilled(svCursorPos, svCursorRad, userCol32StripedOfAlpha, 12) - drawList.addCircle(svCursorPos, svCursorRad + 1, colMidgrey, 12) - drawList.addCircle(svCursorPos, svCursorRad, colWhite, 12) - - // Render alpha bar - if (alphaBar) { - val alpha = saturate(col[3]) - val bar1Bb = Rect(bar1PosX, pickerPos.y, bar1PosX + barsWidth, pickerPos.y + svPickerSize) - renderColorRectWithAlphaCheckerboard(drawList, bar1Bb.min, bar1Bb.max, 0, bar1Bb.width / 2f, Vec2()) - drawList.addRectFilledMultiColor(bar1Bb.min, bar1Bb.max, userCol32StripedOfAlpha, userCol32StripedOfAlpha, userCol32StripedOfAlpha wo COL32_A_MASK, userCol32StripedOfAlpha wo COL32_A_MASK) - val bar1LineY = round(pickerPos.y + (1f - alpha) * svPickerSize) - renderFrameBorder(bar1Bb.min, bar1Bb.max, 0f) - drawList.renderArrowsForVerticalBar(Vec2(bar1PosX - 1, bar1LineY), Vec2(barsTrianglesHalfSz + 1, barsTrianglesHalfSz), barsWidth + 2f, style.alpha) + val styleAlpha8 = F32_TO_INT8_SAT(style.alpha) + val colBlack = COL32(0, 0, 0, styleAlpha8) + val colWhite = COL32(255, 255, 255, styleAlpha8) + val colMidgrey = COL32(128, 128, 128, styleAlpha8) + val colHues = arrayOf(COL32(255, 0, 0, styleAlpha8), COL32(255, 255, 0, styleAlpha8), COL32(0, 255, 0, styleAlpha8), COL32(0, 255, 255, styleAlpha8), COL32(0, 0, 255, styleAlpha8), COL32(255, 0, 255, styleAlpha8), COL32(255, 0, 0, styleAlpha8)) + + val hueColorF = Vec4(1f, 1f, 1f, style.alpha); colorConvertHSVtoRGB(H, 1f, 1f, hueColorF::put) + val hueColor32 = hueColorF.u32 + val userCol32StripedOfAlpha = floatsToU32(R, G, B, style.alpha) // Important: this is still including the main rendering/style alpha!! + + val svCursorPos = Vec2() + + if (flags has Cef.PickerHueWheel) { + // Render Hue Wheel + val aeps = 0.5f / wheelROuter // Half a pixel arc length in radians (2pi cancels out). + val segmentPerArc = glm.max(4, (wheelROuter / 12).i) + for (n in 0..5) { + val a0 = n / 6f * 2f * glm.PIf - aeps + val a1 = (n + 1f) / 6f * 2f * glm.PIf + aeps + val vertStartIdx = drawList.vtxBuffer.size + drawList.pathArcTo(wheelCenter, (wheelRInner + wheelROuter) * 0.5f, a0, a1, segmentPerArc) + drawList.pathStroke(colWhite, thickness = wheelThickness) + val vertEndIdx = drawList.vtxBuffer.size + + // Paint colors over existing vertices + val gradientP0 = Vec2(wheelCenter.x + a0.cos * wheelRInner, wheelCenter.y + a0.sin * wheelRInner) + val gradientP1 = Vec2(wheelCenter.x + a1.cos * wheelRInner, wheelCenter.y + a1.sin * wheelRInner) + drawList.shadeVertsLinearColorGradientKeepAlpha(vertStartIdx, vertEndIdx, gradientP0, gradientP1, colHues[n], colHues[n + 1]) } - endGroup() - - var compare = true - repeat(components) { if (backupInitialCol[it] != col[it]) compare = false } - if (valueChanged && compare) - valueChanged = false - if (valueChanged && g.lastItemData.id != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId - markItemEdited(g.lastItemData.id) - - popID() + // Render Cursor + preview on Hue Wheel + val cosHueAngle = glm.cos(H * 2f * glm.PIf) + val sinHueAngle = glm.sin(H * 2f * glm.PIf) + val hueCursorPos = Vec2(wheelCenter.x + cosHueAngle * (wheelRInner + wheelROuter) * 0.5f, wheelCenter.y + sinHueAngle * (wheelRInner + wheelROuter) * 0.5f) + val hueCursorRad = wheelThickness * if (valueChangedH) 0.65f else 0.55f + val hueCursorSegments = glm.clamp((hueCursorRad / 1.4f).i, 9, 32) + drawList.addCircleFilled(hueCursorPos, hueCursorRad, hueColor32, hueCursorSegments) + drawList.addCircle(hueCursorPos, hueCursorRad + 1, colMidgrey, hueCursorSegments) + drawList.addCircle(hueCursorPos, hueCursorRad, colWhite, hueCursorSegments) + + // Render SV triangle (rotated according to hue) + val tra = wheelCenter + trianglePa.rotate(cosHueAngle, sinHueAngle) + val trb = wheelCenter + trianglePb.rotate(cosHueAngle, sinHueAngle) + val trc = wheelCenter + trianglePc.rotate(cosHueAngle, sinHueAngle) + val uvWhite = fontTexUvWhitePixel + drawList.primReserve(6, 6) + drawList.primVtx(tra, uvWhite, hueColor32) + drawList.primVtx(trb, uvWhite, hueColor32) + drawList.primVtx(trc, uvWhite, colWhite) + drawList.primVtx(tra, uvWhite, 0) + drawList.primVtx(trb, uvWhite, colBlack) + drawList.primVtx(trc, uvWhite, 0) + drawList.addTriangle(tra, trb, trc, colMidgrey, 1.5f) + svCursorPos put trc.lerp(tra, saturate(S)).lerp(trb, saturate(1 - V)) + } else if (flags has Cef.PickerHueBar) { + // Render SV Square + drawList.addRectFilledMultiColor(pickerPos, pickerPos + svPickerSize, colWhite, hueColor32, hueColor32, colWhite) + drawList.addRectFilledMultiColor(pickerPos, pickerPos + svPickerSize, 0, 0, colBlack, colBlack) + renderFrameBorder(pickerPos, pickerPos + svPickerSize, 0f) + // Sneakily prevent the circle to stick out too much + svCursorPos.x = glm.clamp(floor(pickerPos.x + saturate(S) * svPickerSize + 0.5f), pickerPos.x + 2, pickerPos.x + svPickerSize - 2) + svCursorPos.y = glm.clamp(floor(pickerPos.y + saturate(1 - V) * svPickerSize + 0.5f), pickerPos.y + 2, pickerPos.y + svPickerSize - 2) + + // Render Hue Bar + for (i in 0..5) { + val a = Vec2(bar0PosX, pickerPos.y + i * (svPickerSize / 6)) + val c = Vec2(bar0PosX + barsWidth, pickerPos.y + (i + 1) * (svPickerSize / 6)) + drawList.addRectFilledMultiColor(a, c, colHues[i], colHues[i], colHues[i + 1], colHues[i + 1]) + } + val bar0LineY = round(pickerPos.y + H * svPickerSize) + renderFrameBorder(Vec2(bar0PosX, pickerPos.y), Vec2(bar0PosX + barsWidth, pickerPos.y + svPickerSize), 0f) + drawList.renderArrowsForVerticalBar(Vec2(bar0PosX - 1, bar0LineY), Vec2(barsTrianglesHalfSz + 1, barsTrianglesHalfSz), barsWidth + 2f, style.alpha) + } - return valueChanged + // Render cursor/preview circle (clamp S/V within 0..1 range because floating points colors may lead HSV values to be out of range) + val svCursorRad = if (valueChangedSv) 10f else 6f + drawList.addCircleFilled(svCursorPos, svCursorRad, userCol32StripedOfAlpha, 12) + drawList.addCircle(svCursorPos, svCursorRad + 1, colMidgrey, 12) + drawList.addCircle(svCursorPos, svCursorRad, colWhite, 12) + + // Render alpha bar + if (alphaBar) { + val alpha = saturate(col[3]) + val bar1Bb = Rect(bar1PosX, pickerPos.y, bar1PosX + barsWidth, pickerPos.y + svPickerSize) + renderColorRectWithAlphaCheckerboard(drawList, bar1Bb.min, bar1Bb.max, 0, bar1Bb.width / 2f, Vec2()) + drawList.addRectFilledMultiColor(bar1Bb.min, bar1Bb.max, userCol32StripedOfAlpha, userCol32StripedOfAlpha, userCol32StripedOfAlpha wo COL32_A_MASK, userCol32StripedOfAlpha wo COL32_A_MASK) + val bar1LineY = round(pickerPos.y + (1f - alpha) * svPickerSize) + renderFrameBorder(bar1Bb.min, bar1Bb.max, 0f) + drawList.renderArrowsForVerticalBar(Vec2(bar1PosX - 1, bar1LineY), Vec2(barsTrianglesHalfSz + 1, barsTrianglesHalfSz), barsWidth + 2f, style.alpha) } - /** A little color square. Return true when clicked. - * FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip. - * 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip. - * Note that 'col' may be encoded in HSV if ImGuiColorEditFlags_InputHSV is set. */ - fun colorButton(descId: String, col: Vec4, flags_: ColorEditFlags = emptyFlags, sizeArg: Vec2 = Vec2()): Boolean { + endGroup() - val window = currentWindow - if (window.skipItems) - return false + var compare = true + repeat(components) { if (backupInitialCol[it] != col[it]) compare = false } + if (valueChanged && compare) valueChanged = false + if (valueChanged && g.lastItemData.id != 0) // In case of ID collision, the second EndGroup() won't catch g.ActiveId + markItemEdited(g.lastItemData.id) - val id = window.getID(descId) - val defaultSize = frameHeight - val size = Vec2(if (sizeArg.x == 0f) defaultSize else sizeArg.x, if (sizeArg.y == 0f) defaultSize else sizeArg.y) - val bb = Rect(window.dc.cursorPos, window.dc.cursorPos + size) - itemSize(bb, if (size.y >= defaultSize) style.framePadding.y else 0f) - if (!itemAdd(bb, id)) - return false + popID() - val (pressed, hovered, _) = buttonBehavior(bb, id) + return valueChanged +} - var flags = flags_ - if (flags has Cef.NoAlpha) - flags = flags wo (Cef.AlphaPreview or Cef.AlphaPreviewHalf) - - val colRgb = Vec4(col) - if (flags has Cef.InputHSV) - colorConvertHSVtoRGB(colRgb) - - val colRgbWithoutAlpha = Vec4(colRgb.x, colRgb.y, colRgb.z, 1f) - val gridStep = glm.min(size.x, size.y) / 2.99f - val rounding = glm.min(style.frameRounding, gridStep * 0.5f) - val bbInner = Rect(bb) - var off = 0f - if (flags hasnt Cef.NoBorder) { - off = -0.75f // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts. - bbInner expand off - } - if (flags has Cef.AlphaPreviewHalf && colRgb.w < 1f) { - val midX = round((bbInner.min.x + bbInner.max.x) * 0.5f) - renderColorRectWithAlphaCheckerboard( - window.drawList, Vec2(bbInner.min.x + gridStep, bbInner.min.y), bbInner.max, - getColorU32(colRgb), gridStep, Vec2(-gridStep + off, off), rounding, DrawFlag.RoundCornersRight - ) - window.drawList.addRectFilled(bbInner.min, Vec2(midX, bbInner.max.y), getColorU32(colRgbWithoutAlpha), rounding, DrawFlag.RoundCornersLeft) - } else { - /* Because getColorU32() multiplies by the global style alpha and we don't want to display a checkerboard - if the source code had no alpha */ - val colSource = if (flags has Cef.AlphaPreview) colRgb else colRgbWithoutAlpha - if (colSource.w < 1f) - renderColorRectWithAlphaCheckerboard(window.drawList, bbInner.min, bbInner.max, colSource.u32, gridStep, Vec2(off), rounding) - else - window.drawList.addRectFilled(bbInner.min, bbInner.max, getColorU32(colSource), rounding) - } - renderNavHighlight(bb, id) - // Color button are often in need of some sort of border - if (flags hasnt Cef.NoBorder) - if (g.style.frameBorderSize > 0f) - renderFrameBorder(bb.min, bb.max, rounding) - else - window.drawList.addRect(bb.min, bb.max, Col.FrameBg.u32, rounding) - - /* Drag and Drop Source - NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test. */ - if (g.activeId == id && flags hasnt Cef.NoDragDrop && beginDragDropSource()) { - - if (flags has Cef.NoAlpha) - setDragDropPayload(PAYLOAD_TYPE_COLOR_3F, colRgb, Cond.Once) - else - setDragDropPayload(PAYLOAD_TYPE_COLOR_4F, colRgb, Cond.Once) - colorButton(descId, col, flags) - sameLine() - textEx("Color") - endDragDropSource() - } - // Tooltip - if (flags hasnt Cef.NoTooltip && hovered) { - val pF = floatArrayOf(col.x, col.y, col.z, col.w) - colorTooltip(descId, pF, flags and (Cef._InputMask or Cef.NoAlpha or Cef.AlphaPreview or Cef.AlphaPreviewHalf)) - col.put(pF) - } +/** A little color square. Return true when clicked. + * FIXME: May want to display/ignore the alpha component in the color display? Yet show it in the tooltip. + * 'desc_id' is not called 'label' because we don't display it next to the button, but only in the tooltip. + * Note that 'col' may be encoded in HSV if ImGuiColorEditFlags_InputHSV is set. */ +fun colorButton(descId: String, x: Float, y: Float, z: Float, w: Float, flags_: ColorEditFlags = emptyFlags, sizeArg: Vec2 = Vec2()): Boolean { - return pressed - } + val window = currentWindow + if (window.skipItems) return false - /** initialize current options (generally on application startup) if you want to select a default format, picker - * type, etc. User will be able to change many settings, unless you pass the _NoOptions flag to your calls. */ - fun setColorEditOptions(flags_: ColorEditFlags) { - var flags = flags_ - if (flags hasnt Cef._DisplayMask) - flags = flags or (Cef.DefaultOptions and Cef._DisplayMask) - if (flags hasnt Cef._DataTypeMask) - flags = flags or (Cef.DefaultOptions and Cef._DataTypeMask) - if (flags hasnt Cef._PickerMask) - flags = flags or (Cef.DefaultOptions and Cef._PickerMask) - if (flags hasnt Cef._InputMask) - flags = flags or (Cef.DefaultOptions and Cef._InputMask) - assert((flags and Cef._DisplayMask).isPowerOfTwo) { "Check only 1 option is selected" } - assert((flags and Cef._DataTypeMask).isPowerOfTwo) { "Check only 1 option is selected" } - assert((flags and Cef._PickerMask).isPowerOfTwo) { "Check only 1 option is selected" } - assert((flags and Cef._InputMask).isPowerOfTwo) { "Check only 1 option is selected" } - g.colorEditOptions = flags - } + val id = window.getID(descId) + val defaultSize = frameHeight + val size = Vec2(if (sizeArg.x == 0f) defaultSize else sizeArg.x, if (sizeArg.y == 0f) defaultSize else sizeArg.y) + val bb = Rect(window.dc.cursorPos, window.dc.cursorPos + size) + itemSize(bb, if (size.y >= defaultSize) style.framePadding.y else 0f) + if (!itemAdd(bb, id)) return false - companion object { - val ids = arrayOf("##X", "##Y", "##Z", "##W") - val fmtTableInt = arrayOf( - arrayOf("%3d", "%3d", "%3d", "%3d"), // Short display - arrayOf("R:%3d", "G:%3d", "B:%3d", "A:%3d"), // Long display for RGBA - arrayOf("H:%3d", "S:%3d", "V:%3d", "A:%3d")) // Long display for HSVA - val fmtTableFloat = arrayOf( - arrayOf("%.3f", "%.3f", "%.3f", "%.3f"), // Short display - arrayOf("R:%.3f", "G:%.3f", "B:%.3f", "A:%.3f"), // Long display for RGBA - arrayOf("H:%.3f", "S:%.3f", "V:%.3f", "A:%.3f")) // Long display for HSVA + val (pressed, hovered, _) = buttonBehavior(bb, id) - fun DrawList.renderArrowsForVerticalBar(pos: Vec2, halfSz: Vec2, barW: Float, alpha: Float) { - val alpha8 = F32_TO_INT8_SAT(alpha) - renderArrowPointingAt(Vec2(pos.x + halfSz.x + 1, pos.y), Vec2(halfSz.x + 2, halfSz.y + 1), Dir.Right, COL32(0, 0, 0, alpha8)) - renderArrowPointingAt(Vec2(pos.x + halfSz.x, pos.y), halfSz, Dir.Right, COL32(255, 255, 255, alpha8)) - renderArrowPointingAt(Vec2(pos.x + barW - halfSz.x - 1, pos.y), Vec2(halfSz.x + 2, halfSz.y + 1), Dir.Left, COL32(0, 0, 0, alpha8)) - renderArrowPointingAt(Vec2(pos.x + barW - halfSz.x, pos.y), halfSz, Dir.Left, COL32(255, 255, 255, alpha8)) - } + var flags = flags_ + if (flags has Cef.NoAlpha) flags = flags wo (Cef.AlphaPreview or Cef.AlphaPreviewHalf) - /** ColorEdit supports RGB and HSV inputs. In case of RGB input resulting color may have undefined hue and/or saturation. - * Since widget displays both RGB and HSV values we must preserve hue and saturation to prevent these values resetting. */ - fun colorEditRestoreHS(col: FloatArray, hsv: FloatArray) { - // This check is optional. Suppose we have two color widgets side by side, both widgets display different colors, but both colors have hue and/or saturation undefined. - // With color check: hue/saturation is preserved in one widget. Editing color in one widget would reset hue/saturation in another one. - // Without color check: common hue/saturation would be displayed in all widgets that have hue/saturation undefined. - // g.ColorEditLastColor is stored as ImU32 RGB value: this essentially gives us color equality check with reduced precision. - // Tiny external color changes would not be detected and this check would still pass. This is OK, since we only restore hue/saturation _only_ if they are undefined, - // therefore this change flipping hue/saturation from undefined to a very tiny value would still be represented in color picker. - if (g.colorEditLastColor != Vec4(col[0], col[1], col[2], 0).u32) - return - - val H = 0; - val S = 1; - val V = 2 - // When S == 0, H is undefined. - // When H == 1 it wraps around to 0. - if (hsv[S] == 0f || (hsv[H] == 0f && g.colorEditLastHue == 1f)) - hsv[H] = g.colorEditLastHue - - // When V == 0, S is undefined. - if (hsv[V] == 0f) - hsv[S] = g.colorEditLastSat - } + val colRgb = Vec4(x, y, z, w) + if (flags has Cef.InputHSV) colorConvertHSVtoRGB(colRgb) + + val colRgbWithoutAlpha = Vec4(colRgb.x, colRgb.y, colRgb.z, 1f) + val gridStep = glm.min(size.x, size.y) / 2.99f + val rounding = glm.min(style.frameRounding, gridStep * 0.5f) + val bbInner = Rect(bb) + var off = 0f + if (flags hasnt Cef.NoBorder) { + off = -0.75f // The border (using Col_FrameBg) tends to look off when color is near-opaque and rounding is enabled. This offset seemed like a good middle ground to reduce those artifacts. + bbInner expand off + } + if (flags has Cef.AlphaPreviewHalf && colRgb.w < 1f) { + val midX = round((bbInner.min.x + bbInner.max.x) * 0.5f) + renderColorRectWithAlphaCheckerboard(window.drawList, Vec2(bbInner.min.x + gridStep, bbInner.min.y), bbInner.max, getColorU32(colRgb), gridStep, Vec2(-gridStep + off, off), rounding, DrawFlag.RoundCornersRight) + window.drawList.addRectFilled(bbInner.min, Vec2(midX, bbInner.max.y), getColorU32(colRgbWithoutAlpha), rounding, DrawFlag.RoundCornersLeft) + } else {/* Because getColorU32() multiplies by the global style alpha and we don't want to display a checkerboard + if the source code had no alpha */ + val colSource = if (flags has Cef.AlphaPreview) colRgb else colRgbWithoutAlpha + if (colSource.w < 1f) renderColorRectWithAlphaCheckerboard(window.drawList, bbInner.min, bbInner.max, colSource.u32, gridStep, Vec2(off), rounding) + else window.drawList.addRectFilled(bbInner.min, bbInner.max, getColorU32(colSource), rounding) } -} \ No newline at end of file + renderNavHighlight(bb, id) + // Color button are often in need of some sort of border + if (flags hasnt Cef.NoBorder) if (g.style.frameBorderSize > 0f) renderFrameBorder(bb.min, bb.max, rounding) + else window.drawList.addRect(bb.min, bb.max, Col.FrameBg.u32, rounding) + + /* Drag and Drop Source + NB: The ActiveId test is merely an optional micro-optimization, BeginDragDropSource() does the same test. */ + if (g.activeId == id && flags hasnt Cef.NoDragDrop && beginDragDropSource()) { + + if (flags has Cef.NoAlpha) setDragDropPayload(PAYLOAD_TYPE_COLOR_3F, colRgb, Cond.Once) + else setDragDropPayload(PAYLOAD_TYPE_COLOR_4F, colRgb, Cond.Once) + colorButton(descId, x, y, z, w, flags, sizeArg) + sameLine() + textEx("Color") + endDragDropSource() + } + // Tooltip + if (flags hasnt Cef.NoTooltip && hovered) { + colorTooltip(descId, x, y, z, w, flags and (Cef._InputMask or Cef.NoAlpha or Cef.AlphaPreview or Cef.AlphaPreviewHalf)) + } + + return pressed +} diff --git a/core/src/main/kotlin/imgui/api/widgetsComboBox.kt b/core/src/main/kotlin/imgui/api/widgetsComboBox.kt index 7c1ba4c80..5d37ce367 100644 --- a/core/src/main/kotlin/imgui/api/widgetsComboBox.kt +++ b/core/src/main/kotlin/imgui/api/widgetsComboBox.kt @@ -132,14 +132,7 @@ interface widgetsComboBox { combo(label, currentItem, items.toList(), heightInItems) /** Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0" */ - fun combo(label: String, currentItem: IntArray, itemsSeparatedByZeros: String, heightInItems: Int = -1): Boolean { - _i32 = currentItem[0] - val items = itemsSeparatedByZeros.split(NUL).filter { it.isNotEmpty() } - // FIXME-OPT: Avoid computing this, or at least only when combo is open - val res = combo(label, ::_i32, items, heightInItems) - currentItem[0] = _i32 - return res - } + fun combo(label: String, currentItem: IntArray, itemsSeparatedByZeros: String, heightInItems: Int = -1): Boolean = combo(label, currentItem mutablePropertyAt 0, itemsSeparatedByZeros, heightInItems) fun combo(label: String, currentItem: KMutableProperty0, itemsSeparatedByZeros: String, heightInItems: Int = -1): Boolean { val items = itemsSeparatedByZeros.split(NUL).filter { it.isNotEmpty() } @@ -148,12 +141,8 @@ interface widgetsComboBox { } /** Combo box function. */ - fun combo(label: String, currentItem: IntArray, items: List, popupMaxHeightInItem: Int = -1): Boolean { - _i32 = currentItem[0] - val res = combo(label, ::_i32, items, popupMaxHeightInItem) - currentItem[0] = _i32 - return res - } + fun combo(label: String, currentItem: IntArray, items: List, popupMaxHeightInItem: Int = -1): Boolean = + combo(label, currentItem mutablePropertyAt 0, items, popupMaxHeightInItem) fun combo(label: String, currentItemPtr: KMutableProperty0, items: List, popupMaxHeightInItem: Int = -1): Boolean { @@ -191,9 +180,10 @@ interface widgetsComboBox { var currentItem by pCurrentItem // Call the getter to obtain the preview string which is a parameter to BeginCombo() - var previewValue by ::_s + val previewValueRef = "".mutableReference + val previewValue by previewValueRef if (currentItem >= 0 && currentItem < items.size) - itemsGetter(items, currentItem, ::_s) + itemsGetter(items, currentItem, previewValueRef) // The old Combo() API exposed "popup_max_height_in_items". The new more general BeginCombo() API doesn't have/need it, but we emulate it here. if (popupMaxHeightInItems != -1 && g.nextWindowData.flags hasnt NextWindowDataFlag.HasSizeConstraint) @@ -208,8 +198,9 @@ interface widgetsComboBox { for (i in items.indices) { pushID(i) val itemSelected = i == currentItem - var itemText by ::_s - if (!itemsGetter(items, i, ::_s)) + val itemTextRef = "".mutableReference + var itemText by itemTextRef + if (!itemsGetter(items, i, itemTextRef)) itemText = "*Unknown item*" if (selectable(itemText, itemSelected)) { valueChanged = true diff --git a/core/src/main/kotlin/imgui/api/widgetsDataPlotting.kt b/core/src/main/kotlin/imgui/api/widgetsDataPlotting.kt index 5261d1455..0c11a7d52 100644 --- a/core/src/main/kotlin/imgui/api/widgetsDataPlotting.kt +++ b/core/src/main/kotlin/imgui/api/widgetsDataPlotting.kt @@ -1,46 +1,59 @@ package imgui.api import glm_.vec2.Vec2 -import imgui.ImGui.plotEx +import imgui.internal.api.plotEx import imgui.internal.sections.PlotType // Widgets: Data Plotting // - Consider using ImPlot (https://github.com/epezent/implot) which is much better! interface widgetsDataPlotting { + fun plotLines( + label: String, + values: FloatArray, + valuesOffset: Int = 0, + overlayText: String = "", + scaleMin: Float = Float.MAX_VALUE, + scaleMax: Float = Float.MAX_VALUE, + graphSize: Vec2 = Vec2(), + stride: Int = 1 + ) = plotEx(PlotType.Lines, label, values.size, valuesOffset, overlayText, scaleMin, scaleMax, graphSize) { + values[it * stride] + } - fun plotLines(label: String, values: FloatArray, valuesOffset: Int = 0, overlayText: String = "", scaleMin: Float = Float.MAX_VALUE, - scaleMax: Float = Float.MAX_VALUE, graphSize: Vec2 = Vec2(), stride: Int = 1) = - plotEx(PlotType.Lines, label, PlotArrayData(values, stride), valuesOffset, overlayText, scaleMin, scaleMax, graphSize) - - fun plotLines(label: String, valuesGetter: (idx: Int) -> Float, valuesCount: Int, valuesOffset: Int = 0, - overlayText: String = "", scaleMin: Float = Float.MAX_VALUE, scaleMax: Float = Float.MAX_VALUE, - graphSize: Vec2 = Vec2()) = - plotEx(PlotType.Lines, label, PlotArrayFunc(valuesGetter, valuesCount), valuesOffset, overlayText, scaleMin, scaleMax, graphSize) - - fun plotHistogram(label: String, values: FloatArray, valuesOffset: Int = 0, overlayText: String = "", - scaleMin: Float = Float.MAX_VALUE, scaleMax: Float = Float.MAX_VALUE, graphSize: Vec2 = Vec2(), stride: Int = 1) = - plotEx(PlotType.Histogram, label, PlotArrayData(values, stride), valuesOffset, overlayText, scaleMin, scaleMax, graphSize) - - fun plotHistogram(label: String, valuesGetter: (idx: Int) -> Float, valuesCount: Int, valuesOffset: Int = 0, - overlayText: String = "", scaleMin: Float = Float.MAX_VALUE, scaleMax: Float = Float.MAX_VALUE, - graphSize: Vec2 = Vec2()) = - plotEx(PlotType.Histogram, label, PlotArrayFunc(valuesGetter, valuesCount), valuesOffset, overlayText, scaleMin, scaleMax, graphSize) - - companion object { - - interface PlotArray { - operator fun get(idx: Int): Float - fun count(): Int - } - - class PlotArrayData(val values: FloatArray, val stride: Int) : PlotArray { - override operator fun get(idx: Int): Float = values[idx * stride] - override fun count(): Int = values.size - } - - class PlotArrayFunc(val func: (Int) -> Float, val count: Int) : PlotArray { - override operator fun get(idx: Int): Float = func(idx) - override fun count(): Int = count - } + fun plotHistogram( + label: String, + values: FloatArray, + valuesOffset: Int = 0, + overlayText: String = "", + scaleMin: Float = Float.MAX_VALUE, + scaleMax: Float = Float.MAX_VALUE, + graphSize: Vec2 = Vec2(), + stride: Int = 1 + ) = plotEx(PlotType.Histogram, label, values.size, valuesOffset, overlayText, scaleMin, scaleMax, graphSize) { + values[it * stride] } -} \ No newline at end of file +} + +inline fun plotLines( + label: String, + valuesCount: Int, + valuesOffset: Int = 0, + overlayText: String = "", + scaleMin: Float = Float.MAX_VALUE, + scaleMax: Float = Float.MAX_VALUE, + graphSize: Vec2 = Vec2(), + valuesGetter: (idx: Int) -> Float +) = plotEx(PlotType.Lines, label, valuesCount, valuesOffset, overlayText, scaleMin, scaleMax, graphSize, valuesGetter) + +inline fun plotHistogram( + label: String, + valuesCount: Int, + valuesOffset: Int = 0, + overlayText: String = "", + scaleMin: Float = Float.MAX_VALUE, + scaleMax: Float = Float.MAX_VALUE, + graphSize: Vec2 = Vec2(), + valuesGetter: (idx: Int) -> Float +) = plotEx( + PlotType.Histogram, label, valuesCount, valuesOffset, overlayText, scaleMin, scaleMax, graphSize, valuesGetter +) \ No newline at end of file diff --git a/core/src/main/kotlin/imgui/api/widgetsDrags.kt b/core/src/main/kotlin/imgui/api/widgetsDrags.kt index 3cd68b731..f50c39cf0 100644 --- a/core/src/main/kotlin/imgui/api/widgetsDrags.kt +++ b/core/src/main/kotlin/imgui/api/widgetsDrags.kt @@ -12,10 +12,11 @@ import imgui.* import imgui.ImGui.beginGroup import imgui.ImGui.calcItemWidth import imgui.ImGui.currentWindow +import imgui.ImGui.drag +import imgui.ImGui.dragBehavior import imgui.ImGui.endGroup import imgui.ImGui.findRenderedTextEnd import imgui.ImGui.focusWindow -import imgui.ImGui.format import imgui.ImGui.io import imgui.ImGui.isClicked import imgui.ImGui.isMouseDragPastThreshold @@ -32,14 +33,13 @@ import imgui.ImGui.style import imgui.ImGui.tempInputScalar import imgui.ImGui.testOwner import imgui.ImGui.textEx +import imgui.internal.api.widgetN import imgui.internal.classes.Rect import imgui.internal.sections.* import imgui.static.DRAG_MOUSE_THRESHOLD_FACTOR import uno.kotlin.getValue import kotlin.reflect.KMutableProperty0 -@Suppress("UNCHECKED_CAST") - // Widgets: Drag Sliders // - CTRL+Click on any drag box to turn them into an input box. Manually input values aren't clamped by default and can go off-bounds. Use ImGuiSliderFlags_AlwaysClamp to always clamp. // - For all the Float2/Float3/Float4/Int2/Int3/Int4 versions of every function, note that a 'float v[X]' function argument is the same as 'float* v', @@ -57,65 +57,88 @@ interface widgetsDrags { // [JVM] TODO inline? -> class /** If v_min >= v_max we have no bound */ - fun dragFloat( - label: String, v: KMutableProperty0, vSpeed: Float = 1f, vMin: Float = 0f, - vMax: Float = 0f, format: String? = "%.3f", flags: SliderFlags = emptyFlags - ): Boolean = - dragScalar(label, DataType.Float, v, vSpeed, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - - /** If v_min >= v_max we have no bound */ - fun dragFloat( - label: String, v: FloatArray, ptr: Int, vSpeed: Float = 1f, vMin: Float = 0f, - vMax: Float = 0f, format: String = "%.3f", flags: SliderFlags = emptyFlags - ): Boolean = - withFloat(v, ptr) { pV -> - dragScalar(label, DataType.Float, pV, vSpeed, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - } - - fun dragFloat2( - label: String, v: FloatArray, vSpeed: Float = 1f, vMin: Float = 0f, vMax: Float = 0f, - format: String = "%.3f", flags: SliderFlags = emptyFlags - ): Boolean = - dragScalarN(label, DataType.Float, v, 2, vSpeed, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - - fun dragVec2( - label: String, v: Vec2, vSpeed: Float = 1f, vMin: Float = 0f, vMax: Float = 0f, - format: String = "%.3f", flags: SliderFlags = emptyFlags - ): Boolean = - dragScalarN(label, DataType.Float, v to _fa, 2, vSpeed, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - .also { v put _fa } - - fun dragFloat3( - label: String, v: FloatArray, vSpeed: Float = 1f, vMin: Float = 0f, vMax: Float = 0f, - format: String = "%.3f", flags: SliderFlags = emptyFlags - ): Boolean = - dragScalarN(label, DataType.Float, v, 3, vSpeed, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - - fun dragVec3( - label: String, v: Vec3, vSpeed: Float = 1f, vMin: Float = 0f, vMax: Float = 0f, - format: String = "%.3f", flags: SliderFlags = emptyFlags - ): Boolean = - dragScalarN(label, DataType.Float, v to _fa, 3, vSpeed, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - .also { v put _fa } - - fun dragFloat4( - label: String, v: FloatArray, vSpeed: Float = 1f, vMin: Float = 0f, vMax: Float = 0f, - format: String = "%.3f", flags: SliderFlags = emptyFlags - ): Boolean = - dragScalarN(label, DataType.Float, v, 4, vSpeed, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - - fun dragVec4( - label: String, v: Vec4, vSpeed: Float = 1f, vMin: Float = 0f, vMax: Float = 0f, - format: String = "%.3f", flags: SliderFlags = emptyFlags - ): Boolean = - dragScalarN(label, DataType.Float, v to _fa, 4, vSpeed, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - .also { v put _fa } + fun drag( + label: String, + v: FloatArray, + ptr: Int, + vSpeed: Float = 1f, + vMin: Float = 0f, + vMax: Float = 0f, + format: String = "%.3f", + flags: SliderFlags = emptyFlags + ): Boolean = drag(label, v mutablePropertyAt ptr, vSpeed, vMin, vMax, format, flags) + + fun drag2( + label: String, + v: FloatArray, + vSpeed: Float = 1f, + vMin: Float = 0f, + vMax: Float = 0f, + format: String = "%.3f", + flags: SliderFlags = emptyFlags + ): Boolean = dragN(label, 2, vSpeed, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun drag2( + label: String, + v: Vec2, + vSpeed: Float = 1f, + vMin: Float = 0f, + vMax: Float = 0f, + format: String = "%.3f", + flags: SliderFlags = emptyFlags + ): Boolean = dragN(label, 2, vSpeed, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun drag3( + label: String, + v: FloatArray, + vSpeed: Float = 1f, + vMin: Float = 0f, + vMax: Float = 0f, + format: String = "%.3f", + flags: SliderFlags = emptyFlags + ): Boolean = dragN(label, 3, vSpeed, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun drag3( + label: String, + v: Vec3, + vSpeed: Float = 1f, + vMin: Float = 0f, + vMax: Float = 0f, + format: String = "%.3f", + flags: SliderFlags = emptyFlags + ): Boolean = dragN(label, 3, vSpeed, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun drag4( + label: String, + v: FloatArray, + vSpeed: Float = 1f, + vMin: Float = 0f, + vMax: Float = 0f, + format: String = "%.3f", + flags: SliderFlags = emptyFlags + ): Boolean = dragN(label, 4, vSpeed, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun drag4( + label: String, + v: Vec4, + vSpeed: Float = 1f, + vMin: Float = 0f, + vMax: Float = 0f, + format: String = "%.3f", + flags: SliderFlags = emptyFlags + ): Boolean = dragN(label, 4, vSpeed, vMin, vMax, format, flags, v::mutablePropertyAt) /** NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this. */ - fun dragFloatRange2( - label: String, vCurrentMinPtr: KMutableProperty0, vCurrentMaxPtr: KMutableProperty0, - vSpeed: Float = 1f, vMin: Float = 0f, vMax: Float = 0f, format: String = "%.3f", - formatMax: String = format, flags: SliderFlags = emptyFlags + fun dragRange( + label: String, + vCurrentMinPtr: KMutableProperty0, + vCurrentMaxPtr: KMutableProperty0, + vSpeed: Float = 1f, + vMin: Float = 0f, + vMax: Float = 0f, + format: String = "%.3f", + formatMax: String = format, + flags: SliderFlags = emptyFlags ): Boolean { val vCurrentMin by vCurrentMinPtr @@ -127,21 +150,18 @@ interface widgetsDrags { ImGui.beginGroup() ImGui.pushMultiItemsWidths(2, ImGui.calcItemWidth()) - var minMin = if (vMin >= vMax) -Float.MAX_VALUE else vMin - var minMax = if (vMin >= vMax) vCurrentMax else vMax min vCurrentMax + val minMin = if (vMin >= vMax) -Float.MAX_VALUE else vMin + val minMax = if (vMin >= vMax) vCurrentMax else vMax min vCurrentMax val minFlags = flags or if (minMin == minMax) SliderFlag._ReadOnly else emptyFlags - var valueChanged = dragScalar("##min", DataType.Float, vCurrentMinPtr, vSpeed, - minMin mutableProperty { minMin = it }, minMax mutableProperty { minMax = it }, format, minFlags - ) + var valueChanged = drag("##min", vCurrentMinPtr, vSpeed, minMin, minMax, format, minFlags) ImGui.popItemWidth() ImGui.sameLine(0f, ImGui.style.itemInnerSpacing.x) - var maxMin = if (vMin >= vMax) vCurrentMin else vMin max vCurrentMin - var maxMax = if (vMin >= vMax) Float.MAX_VALUE else vMax + val maxMin = if (vMin >= vMax) vCurrentMin else vMin max vCurrentMin + val maxMax = if (vMin >= vMax) Float.MAX_VALUE else vMax val maxFlags = flags or if (maxMin == maxMax) SliderFlag._ReadOnly else emptyFlags val fmt = formatMax.ifEmpty { format } - valueChanged /= dragScalar("##max", DataType.Float, vCurrentMaxPtr, vSpeed, - maxMin mutableProperty { maxMin = it }, maxMax mutableProperty { maxMax = it }, fmt, maxFlags) + valueChanged /= drag("##max", vCurrentMaxPtr, vSpeed, maxMin, maxMax, fmt, maxFlags) ImGui.popItemWidth() ImGui.sameLine(0f, ImGui.style.itemInnerSpacing.x) @@ -155,62 +175,88 @@ interface widgetsDrags { /** If v_min >= v_max we have no bound * * NB: vSpeed is float to allow adjusting the drag speed with more precision */ - fun dragInt( - label: String, v: IntArray, ptr: Int, vSpeed: Float = 1f, vMin: Int = 0, vMax: Int = 0, - format: String? = "%d", flags: SliderFlags = emptyFlags - ): Boolean = - withInt(v, ptr) { dragInt(label, it, vSpeed, vMin, vMax, format, flags) } - - fun dragInt( - label: String, v: KMutableProperty0, vSpeed: Float = 1f, vMin: Int = 0, vMax: Int = 0, - format: String? = "%d", flags: SliderFlags = emptyFlags - ): Boolean = - dragScalar(label, DataType.Int, v, vSpeed, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - - fun dragInt2( - label: String, v: IntArray, vSpeed: Float = 1f, vMin: Int = 0, vMax: Int = 0, - format: String = "%d", flags: SliderFlags = emptyFlags - ): Boolean = - dragScalarN(label, DataType.Int, v, 2, vSpeed, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - - fun dragVec2i( - label: String, v: Vec2i, vSpeed: Float = 1f, vMin: Int = 0, vMax: Int = 0, - format: String = "%d", flags: SliderFlags = emptyFlags - ): Boolean = - dragScalarN(label, DataType.Int, v to _ia, 2, vSpeed, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - .also { v put _ia } - - fun dragInt3( - label: String, v: IntArray, vSpeed: Float = 1f, vMin: Int = 0, vMax: Int = 0, - format: String = "%d", flags: SliderFlags = emptyFlags - ): Boolean = - dragScalarN(label, DataType.Int, v, 3, vSpeed, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - - fun dragVec3i( - label: String, v: Vec3i, vSpeed: Float = 1f, vMin: Int = 0, vMax: Int = 0, - format: String = "%d", flags: SliderFlags = emptyFlags - ): Boolean = - dragScalarN(label, DataType.Int, v to _ia, 3, vSpeed, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - .also { v put _ia } - - fun dragInt4( - label: String, v: IntArray, vSpeed: Float = 1f, vMin: Int = 0, vMax: Int = 0, - format: String = "%d", flags: SliderFlags = emptyFlags - ): Boolean = - dragScalarN(label, DataType.Int, v, 4, vSpeed, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - - fun dragVec4i( - label: String, v: Vec4i, vSpeed: Float = 1f, vMin: Int = 0, vMax: Int = 0, - format: String = "%d", flags: SliderFlags = emptyFlags - ): Boolean = - dragScalarN(label, DataType.Int, v to _ia, 4, vSpeed, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - .also { v put _ia } + fun drag( + label: String, + v: IntArray, + ptr: Int, + vSpeed: Float = 1f, + vMin: Int = 0, + vMax: Int = 0, + format: String? = "%d", + flags: SliderFlags = emptyFlags + ): Boolean = drag(label, v mutablePropertyAt ptr, vSpeed, vMin, vMax, format, flags) + + fun drag2( + label: String, + v: IntArray, + vSpeed: Float = 1f, + vMin: Int = 0, + vMax: Int = 0, + format: String = "%d", + flags: SliderFlags = emptyFlags + ): Boolean = dragN(label, 2, vSpeed, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun drag2( + label: String, + v: Vec2i, + vSpeed: Float = 1f, + vMin: Int = 0, + vMax: Int = 0, + format: String = "%d", + flags: SliderFlags = emptyFlags + ): Boolean = dragN(label, 2, vSpeed, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun drag3( + label: String, + v: IntArray, + vSpeed: Float = 1f, + vMin: Int = 0, + vMax: Int = 0, + format: String = "%d", + flags: SliderFlags = emptyFlags + ): Boolean = dragN(label, 3, vSpeed, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun drag3( + label: String, + v: Vec3i, + vSpeed: Float = 1f, + vMin: Int = 0, + vMax: Int = 0, + format: String = "%d", + flags: SliderFlags = emptyFlags + ): Boolean = dragN(label, 3, vSpeed, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun drag4( + label: String, + v: IntArray, + vSpeed: Float = 1f, + vMin: Int = 0, + vMax: Int = 0, + format: String = "%d", + flags: SliderFlags = emptyFlags + ): Boolean = dragN(label, 4, vSpeed, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun drag4( + label: String, + v: Vec4i, + vSpeed: Float = 1f, + vMin: Int = 0, + vMax: Int = 0, + format: String = "%d", + flags: SliderFlags = emptyFlags + ): Boolean = dragN(label, 4, vSpeed, vMin, vMax, format, flags, v::mutablePropertyAt) /** NB: You likely want to specify the ImGuiSliderFlags_AlwaysClamp when using this. */ - fun dragIntRange2( - label: String, vCurrentMinPtr: KMutableProperty0, vCurrentMaxPtr: KMutableProperty0, - vSpeed: Float = 1f, vMin: Int = 0, vMax: Int = 0, format: String = "%d", - formatMax: String = format, flags: SliderFlags = emptyFlags + fun dragRange( + label: String, + vCurrentMinPtr: KMutableProperty0, + vCurrentMaxPtr: KMutableProperty0, + vSpeed: Float = 1f, + vMin: Int = 0, + vMax: Int = 0, + format: String = "%d", + formatMax: String = format, + flags: SliderFlags = emptyFlags ): Boolean { val vCurrentMin by vCurrentMinPtr @@ -225,15 +271,15 @@ interface widgetsDrags { val minMin = if (vMin >= vMax) Int.MIN_VALUE else vMin val minMax = if (vMin >= vMax) vCurrentMax else vMax min vCurrentMax val minFlags = flags or if (minMin == minMax) SliderFlag._ReadOnly else emptyFlags - var valueChanged = dragInt("##min", vCurrentMinPtr, vSpeed, minMin, minMax, format, minFlags) + var valueChanged = drag("##min", vCurrentMinPtr, vSpeed, minMin, minMax, format, minFlags) popItemWidth() sameLine(0f, style.itemInnerSpacing.x) val maxMin = if (vMin >= vMax) vCurrentMin else vMin max vCurrentMin val maxMax = if (vMin >= vMax) Int.MAX_VALUE else vMax val maxFlags = flags or if (maxMin == maxMax) SliderFlag._ReadOnly else emptyFlags - val fmt = if (formatMax.isNotEmpty()) formatMax else format - valueChanged /= dragInt("##max", vCurrentMaxPtr, vSpeed, maxMin, maxMax, fmt, maxFlags) + val fmt = formatMax.ifEmpty { format } + valueChanged /= drag("##max", vCurrentMaxPtr, vSpeed, maxMin, maxMax, fmt, maxFlags) popItemWidth() sameLine(0f, style.itemInnerSpacing.x) @@ -250,30 +296,27 @@ interface widgetsDrags { * e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc. * Speed are per-pixel of mouse movement (vSpeed = 0.2f: mouse needs to move by 5 pixels to increase value by 1). * For gamepad/keyboard navigation, minimum speed is Max(vSpeed, minimumStepAtGivenPrecision). */ - fun dragScalar( - label: String, pData: FloatArray, vSpeed: Float = 1f, - pMin: KMutableProperty0? = null, pMax: KMutableProperty0? = null, - format: String? = null, flags: SliderFlags = emptyFlags - ): Boolean = - dragScalar(label, pData, 0, vSpeed, pMin?.get(), pMax?.get(), format, flags) - - /** If vMin >= vMax we have no bound */ - fun dragScalar( - label: String, pData: FloatArray, ptr: Int = 0, vSpeed: Float = 1f, min: Float? = null, max: Float? = null, - format: String? = null, flags: SliderFlags = emptyFlags - ): Boolean = - withFloat(pData, ptr) { - dragScalar(label, DataType.Float, it, vSpeed, min?.asMutableProperty, max?.asMutableProperty, format, flags) - } + fun drag( + label: String, + pData: FloatArray, + vSpeed: Float = 1f, + min: Float = 0f, + max: Float = 0f, + format: String = "%.3f", + flags: SliderFlags = emptyFlags + ): Boolean = drag(label, pData, 0, vSpeed, min, max, format, flags) /** ote: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a Drag widget, p_min and p_max are optional. * Read code of e.g. DragFloat(), DragInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly. */ - fun dragScalar( - label: String, dataType: DataType, pData: KMutableProperty0, vSpeed: Float = 1f, - pMin: KMutableProperty0? = null, pMax: KMutableProperty0? = null, - format_: String? = null, flags: SliderFlags = emptyFlags - ): Boolean - where N : Number, N : Comparable { + fun NumberOps.drag( + label: String, + pData: KMutableProperty0, + vSpeed: Float = 1f, + min: N? = null, + max: N? = null, + format_: String? = null, + flags: SliderFlags = emptyFlags + ): Boolean where N : Number, N : Comparable { val window = ImGui.currentWindow if (window.skipItems) return false @@ -283,7 +326,9 @@ interface widgetsDrags { val labelSize = ImGui.calcTextSize(label, hideTextAfterDoubleHash = true) val frameBb = Rect(window.dc.cursorPos, window.dc.cursorPos + Vec2(w, labelSize.y + style.framePadding.y * 2f)) - val totalBb = Rect(frameBb.min, frameBb.max + Vec2(if (labelSize.x > 0f) style.itemInnerSpacing.x + labelSize.x else 0f, 0f)) + val totalBb = Rect( + frameBb.min, frameBb.max + Vec2(if (labelSize.x > 0f) style.itemInnerSpacing.x + labelSize.x else 0f, 0f) + ) val tempInputAllowed = flags hasnt SliderFlag.NoInput ImGui.itemSize(totalBb, ImGui.style.framePadding.y) @@ -291,22 +336,22 @@ interface widgetsDrags { return false // Default format string when passing NULL - val format = format_ ?: if (dataType == DataType.Float || dataType == DataType.Double) "%f" else "%d" + val format = format_ ?: defaultFormat val hovered = ImGui.itemHoverable(frameBb, id) var tempInputIsActive = tempInputAllowed && ImGui.tempInputIsActive(id) if (!tempInputIsActive) { // Tabbing or CTRL-clicking on Drag turns it into an InputText - val inputRequestedByTabbing = tempInputAllowed && g.lastItemData.statusFlags has ItemStatusFlag.FocusedByTabbing + val inputRequestedByTabbing = + tempInputAllowed && g.lastItemData.statusFlags has ItemStatusFlag.FocusedByTabbing val clicked = hovered && MouseButton.Left.isClicked(id) val doubleClicked = hovered && g.io.mouseClickedCount[0] == 2 && Key.MouseLeft testOwner id - val makeActive = inputRequestedByTabbing || clicked || doubleClicked || g.navActivateId == id || g.navActivateInputId == id - if (makeActive && (clicked || doubleClicked)) - Key.MouseLeft.setOwner(id) - if (makeActive && tempInputAllowed) - if (inputRequestedByTabbing || (clicked && ImGui.io.keyCtrl) || doubleClicked || g.navActivateInputId == id) - tempInputIsActive = true + val makeActive = + inputRequestedByTabbing || clicked || doubleClicked || g.navActivateId == id || g.navActivateInputId == id + if (makeActive && (clicked || doubleClicked)) Key.MouseLeft.setOwner(id) + if (makeActive && tempInputAllowed) if (inputRequestedByTabbing || (clicked && ImGui.io.keyCtrl) || doubleClicked || g.navActivateInputId == id) tempInputIsActive = + true // (Optional) simple click (without moving) turns Drag into an InputText if (io.configDragClickToInputText && tempInputAllowed && !tempInputIsActive) @@ -325,8 +370,14 @@ interface widgetsDrags { if (tempInputIsActive) { // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set - val isClampInput = flags has SliderFlag.AlwaysClamp && (pMin == null || pMax == null || pMin() < pMax()) - return tempInputScalar(frameBb, id, label, dataType, pData, format, pMin.takeIf { isClampInput }, pMax.takeIf { isClampInput }) + val isClampInput = flags has SliderFlag.AlwaysClamp && (min == null || max == null || min < max) + return tempInputScalar(frameBb, + id, + label, + pData, + format, + min.takeIf { isClampInput }, + max.takeIf { isClampInput }) } // Draw frame @@ -339,68 +390,60 @@ interface widgetsDrags { ImGui.renderFrame(frameBb.min, frameBb.max, frameCol.u32, true, ImGui.style.frameRounding) // Drag behavior - val valueChanged = ImGui.dragBehavior(id, dataType, pData, vSpeed, pMin, pMax, format, flags) - if (valueChanged) - ImGui.markItemEdited(id) + val valueChanged = dragBehavior(id, pData, vSpeed, min, max, format, flags) + if (valueChanged) ImGui.markItemEdited(id) // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. - val value = pData.format(dataType, format) - if (g.logEnabled) - logSetNextTextDecoration("{", "}") + val value = pData().format(format) + if (g.logEnabled) logSetNextTextDecoration("{", "}") ImGui.renderTextClipped(frameBb.min, frameBb.max, value, null, Vec2(0.5f)) - if (labelSize.x > 0f) - ImGui.renderText(Vec2(frameBb.max.x + ImGui.style.itemInnerSpacing.x, frameBb.min.y + ImGui.style.framePadding.y), label) + if (labelSize.x > 0f) ImGui.renderText( + Vec2( + frameBb.max.x + ImGui.style.itemInnerSpacing.x, frameBb.min.y + ImGui.style.framePadding.y + ), label + ) IMGUI_TEST_ENGINE_ITEM_INFO(id, label, g.lastItemData.statusFlags) return valueChanged } - - /** Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a Drag widget, - * p_min and p_max are optional. - * Read code of e.g. SliderFloat(), SliderInt() etc. or examples in 'Demo->Widgets->Data Types' to understand - * how to use this function directly. */ - fun dragScalarN( - label: String, dataType: DataType, v: Any, components: Int, vSpeed: Float = 1f, - vMin: KMutableProperty0? = null, vMax: KMutableProperty0? = null, - format: String? = null, flags: SliderFlags = emptyFlags - ): Boolean - where N : Number, N : Comparable { - - val window = ImGui.currentWindow - if (window.skipItems) return false - - var valueChanged = false - ImGui.beginGroup() - ImGui.pushID(label) - ImGui.pushMultiItemsWidths(components, ImGui.calcItemWidth()) - for (i in 0 until components) { - ImGui.pushID(i) - if (i > 0) - ImGui.sameLine(0f, ImGui.style.itemInnerSpacing.x) - when (dataType) { - DataType.Int -> withInt(v as IntArray, i) { - valueChanged /= dragScalar("", dataType, it as KMutableProperty0, vSpeed, vMin, vMax, format, flags) - } - - DataType.Float -> withFloat(v as FloatArray, i) { - valueChanged /= dragScalar("", dataType, it as KMutableProperty0, vSpeed, vMin, vMax, format, flags) - } - - else -> error("invalid") - } - ImGui.popID() - ImGui.popItemWidth() - } - ImGui.popID() - - val labelEnd = ImGui.findRenderedTextEnd(label) - if (0 != labelEnd) { - ImGui.sameLine(0f, ImGui.style.itemInnerSpacing.x) - ImGui.textEx(label, labelEnd) - } - - ImGui.endGroup() - return valueChanged - } -} \ No newline at end of file +} + +inline fun drag( + label: String, + pData: KMutableProperty0, + vSpeed: Float = 1f, + min: N? = null, + max: N? = null, + format_: String? = null, + flags: SliderFlags = emptyFlags, +): Boolean where N : Number, N : Comparable = numberOps().drag(label, pData, vSpeed, min, max, format_, flags).also { if (Any() is N) println() } + +/** Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a Drag widget, + * p_min and p_max are optional. + * Read code of e.g. SliderFloat(), SliderInt() etc. or examples in 'Demo->Widgets->Data Types' to understand + * how to use this function directly. */ +inline fun dragN( + label: String, + components: Int, + vSpeed: Float = 1f, + min: N? = null, + max: N? = null, + format: String? = null, + flags: SliderFlags = emptyFlags, + properties: (Int) -> KMutableProperty0 +): Boolean where N : Number, N : Comparable = + numberOps().dragN(label, components, vSpeed, min, max, format, flags, properties) + +inline fun NumberOps.dragN( + label: String, + components: Int, + vSpeed: Float = 1f, + min: N? = null, + max: N? = null, + format: String? = null, + flags: SliderFlags = emptyFlags, + properties: (Int) -> KMutableProperty0 +): Boolean where N : Number, N : Comparable = widgetN(label, components) { i -> + drag("", properties(i), vSpeed, min, max, format, flags) +} diff --git a/core/src/main/kotlin/imgui/api/widgetsInputWithKeyboard.kt b/core/src/main/kotlin/imgui/api/widgetsInputWithKeyboard.kt index 48c36cf72..fff49022a 100644 --- a/core/src/main/kotlin/imgui/api/widgetsInputWithKeyboard.kt +++ b/core/src/main/kotlin/imgui/api/widgetsInputWithKeyboard.kt @@ -13,34 +13,28 @@ import imgui.ImGui.beginGroup import imgui.ImGui.buttonEx import imgui.ImGui.calcItemWidth import imgui.ImGui.currentWindow -import imgui.ImGui.dataTypeApplyFromText -import imgui.ImGui.dataTypeApplyOp import imgui.ImGui.endDisabled import imgui.ImGui.endGroup import imgui.ImGui.findRenderedTextEnd -import imgui.ImGui.format import imgui.ImGui.frameHeight +import imgui.ImGui.input import imgui.ImGui.inputTextEx import imgui.ImGui.io import imgui.ImGui.markItemEdited import imgui.ImGui.popID -import imgui.ImGui.popItemWidth import imgui.ImGui.pushID -import imgui.ImGui.pushMultiItemsWidths import imgui.ImGui.sameLine import imgui.ImGui.setNextItemWidth import imgui.ImGui.style import imgui.ImGui.textEx +import imgui.internal.api.widgetN import imgui.internal.sections.IMGUI_TEST_ENGINE_ITEM_INFO -import imgui.static.inputScalarDefaultCharsFilter import kool.getValue import kool.setValue import kotlin.reflect.KMutableProperty0 import imgui.InputTextFlag as Itf import imgui.internal.sections.ButtonFlag as Bf -@Suppress("UNCHECKED_CAST") - /** Widgets: Input with Keyboard * - If you want to use InputText() with std::string or any custom dynamic string type, see cpp/imgui_stdlib.h and comments in imgui_demo.cpp. * - Most of the ImGuiInputTextFlags flags are only useful for InputText() and not for InputFloatX, InputIntX, InputDouble etc. */ @@ -97,128 +91,79 @@ interface widgetsInputWithKeyboard { fun inputTextWithHint( label: String, hint: String, buf: String, flags: InputTextSingleFlags = emptyFlags, callback: InputTextCallback? = null, userData: Any? = null - ): Boolean = - inputTextWithHint(label, hint, buf.toByteArray(), flags) + ): Boolean = inputTextWithHint(label, hint, buf.toByteArray(), flags) /** call InputTextMultiline() or InputTextEx() manually if you need multi-line + hint. */ - fun inputTextWithHint( - label: String, hint: String, buf: ByteArray, flags: InputTextSingleFlags = emptyFlags, - callback: InputTextCallback? = null, userData: Any? = null - ): Boolean = inputTextEx(label, hint, buf, Vec2(), flags, callback, userData) - + fun inputTextWithHint(label: String, hint: String, buf: ByteArray, flags: InputTextSingleFlags = emptyFlags, callback: InputTextCallback? = null, userData: Any? = null): Boolean { + return inputTextEx(label, hint, buf, Vec2(), flags, callback, userData) + } - fun inputFloat( - label: String, v: FloatArray, step: Float = 0f, stepFast: Float = 0f, - format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags - ): Boolean = - inputFloat(label, v, 0, step, stepFast, format, flags) + fun input(label: String, v: FloatArray, step: Float = 0f, stepFast: Float = 0f, format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags): Boolean = input(label, v, 0, step, stepFast, format, flags) - fun inputFloat( - label: String, v: FloatArray, ptr: Int = 0, step: Float = 0f, stepFast: Float = 0f, - format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags - ): Boolean = - withFloat(v, ptr) { inputFloat(label, it, step, stepFast, format, flags) } + fun input(label: String, v: FloatArray, ptr: Int = 0, step: Float = 0f, stepFast: Float = 0f, format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags): Boolean = input(label, v mutablePropertyAt ptr, step, stepFast, format, flags) - fun inputFloat( - label: String, v: KMutableProperty0, step: Float = 0f, stepFast: Float = 0f, - format: String = "%.3f", flags_: InputTextSingleFlags = emptyFlags - ): Boolean { + fun input(label: String, v: KMutableProperty0, step: Float = 0f, stepFast: Float = 0f, format: String = "%.3f", flags_: InputTextSingleFlags = emptyFlags): Boolean { val flags = flags_ or Itf.CharsScientific - return inputScalar(label, DataType.Float, v, step.takeIf { it > 0f }, stepFast.takeIf { it > 0f }, format, flags) + return input(label, v, step.takeIf { it > 0f }, stepFast.takeIf { it > 0f }, format, flags) } + fun input2(label: String, v: FloatArray, format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags): Boolean = inputN(label, 2, null, null, format, flags, v::mutablePropertyAt) - fun inputFloat2(label: String, v: FloatArray, format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags): Boolean = - inputScalarN(label, DataType.Float, v, 2, null, null, format, flags) + fun input2(label: String, v: Vec2, format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags): Boolean = inputN(label, 2, null, null, format, flags, v::mutablePropertyAt) - fun inputVec2(label: String, v: Vec2, format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags): Boolean = - inputScalarN(label, DataType.Float, v to _fa, Vec2.length, null, null, format, flags) - .also { v put _fa } + fun input2(label: String, v: Vec3, format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags): Boolean = inputN(label, 2, null, null, format, flags, v::mutablePropertyAt) - fun inputFloat3(label: String, v: FloatArray, format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags): Boolean = - inputScalarN(label, DataType.Float, v, 3, null, null, format, flags) + fun input2(label: String, v: Vec4, format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags): Boolean = inputN(label, 2, null, null, format, flags, v::mutablePropertyAt) - fun inputVec3(label: String, v: Vec3, format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags): Boolean = - inputScalarN(label, DataType.Float, v to _fa, Vec3.length, null, null, format, flags) - .also { v put _fa } + fun input3(label: String, v: FloatArray, format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags): Boolean = inputN(label, 3, null, null, format, flags, v::mutablePropertyAt) - fun inputFloat4(label: String, v: FloatArray, format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags): Boolean = - inputScalarN(label, DataType.Float, v, 4, null, null, format, flags) + fun input3(label: String, v: Vec3, format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags): Boolean = inputN(label, 3, null, null, format, flags, v::mutablePropertyAt) - fun inputVec4(label: String, v: Vec4, format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags): Boolean = - inputScalarN(label, DataType.Float, v to _fa, Vec4.length, null, null, format, flags) - .also { v put _fa } + fun input3(label: String, v: Vec4, format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags): Boolean = inputN(label, 3, null, null, format, flags, v::mutablePropertyAt) - fun inputInt(label: String, v: KMutableProperty0, step: Int = 1, stepFast: Int = 100, flags: InputTextSingleFlags = emptyFlags): Boolean { - /* Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use inputText() + fun input4(label: String, v: FloatArray, format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags): Boolean = inputN(label, 4, null, null, format, flags, v::mutablePropertyAt) + + fun input4(label: String, v: Vec4, format: String = "%.3f", flags: InputTextSingleFlags = emptyFlags): Boolean = inputN(label, 4, null, null, format, flags, v::mutablePropertyAt) + + fun input(label: String, v: KMutableProperty0, step: Int = 1, stepFast: Int = 100, flags: InputTextSingleFlags = emptyFlags): Boolean {/* Hexadecimal input provided as a convenience but the flag name is awkward. Typically you'd use inputText() to parse your own data, if you want to handle prefixes. */ val format = if (flags has Itf.CharsHexadecimal) "%08X" else "%d" - return inputScalar(label, DataType.Int, v, step.takeIf { it > 0f }, stepFast.takeIf { it > 0f }, format, flags) + return input(label, v, step.takeIf { it > 0f }, stepFast.takeIf { it > 0f }, format, flags) } - fun inputInt2(label: String, v: IntArray, flags: InputTextSingleFlags = emptyFlags): Boolean = - inputScalarN(label, DataType.Int, v, 2, null, null, "%d", flags) + fun input(label: String, pData: IntArray, step: Int?, stepFast: Int?, format: String? = null, flags: InputTextSingleFlags = emptyFlags): Boolean = input(label, pData mutablePropertyAt 0, step, stepFast, format, flags) - fun inputVec2i(label: String, v: Vec2i, flags: InputTextSingleFlags = emptyFlags): Boolean = - inputScalarN(label, DataType.Int, v to _ia, Vec2i.length, null, null, "%d", flags) - .also { v put _ia } + fun input2(label: String, v: IntArray, flags: InputTextSingleFlags = emptyFlags): Boolean = inputN(label, 2, null, null, "%d", flags, v::mutablePropertyAt) - fun inputInt3(label: String, v: IntArray, flags: InputTextSingleFlags = emptyFlags): Boolean = - inputScalarN(label, DataType.Int, v, 3, null, null, "%d", flags) + fun input2(label: String, v: Vec2i, flags: InputTextSingleFlags = emptyFlags): Boolean = inputN(label, 2, null, null, "%d", flags, v::mutablePropertyAt) - fun inputVec3i(label: String, v: Vec3i, flags: InputTextSingleFlags = emptyFlags): Boolean = - inputScalarN(label, DataType.Int, v to _ia, Vec3i.length, null, null, "%d", flags) - .also { v put _ia } + fun input3(label: String, v: IntArray, flags: InputTextSingleFlags = emptyFlags): Boolean = inputN(label, 3, null, null, "%d", flags, v::mutablePropertyAt) - fun inputInt4(label: String, v: IntArray, flags: InputTextSingleFlags = emptyFlags): Boolean = - inputScalarN(label, DataType.Int, v, 4, null, null, "%d", flags) + fun input3(label: String, v: Vec3i, flags: InputTextSingleFlags = emptyFlags): Boolean = inputN(label, 3, null, null, "%d", flags, v::mutablePropertyAt) - fun inputVec4i(label: String, v: Vec4i, flags: InputTextSingleFlags = emptyFlags): Boolean = - inputScalarN(label, DataType.Int, v to _ia, Vec4i.length, null, null, "%d", flags) - .also { v put _ia } + fun input4(label: String, v: IntArray, flags: InputTextSingleFlags = emptyFlags): Boolean = inputN(label, 4, null, null, "%d", flags, v::mutablePropertyAt) - fun inputDouble( - label: String, v: KMutableProperty0, step: Double = 0.0, stepFast: Double = 0.0, - format: String? = "%.6f", flags_: InputTextSingleFlags = emptyFlags - ): Boolean { - val flags = flags_ or Itf.CharsScientific - /* Ideally we'd have a minimum decimal precision of 1 to visually denote that this is a float, + fun input4(label: String, v: Vec4i, flags: InputTextSingleFlags = emptyFlags): Boolean = inputN(label, 4, null, null, "%d", flags, v::mutablePropertyAt) + + fun input(label: String, v: KMutableProperty0, step: Double = 0.0, stepFast: Double = 0.0, format: String? = "%.6f", flags_: InputTextSingleFlags = emptyFlags): Boolean { + val flags = flags_ or Itf.CharsScientific/* Ideally we'd have a minimum decimal precision of 1 to visually denote that this is a float, while hiding non-significant digits? %f doesn't have a minimum of 1 */ - return inputScalar(label, DataType.Double, v, step.takeIf { it > 0.0 }, stepFast.takeIf { it > 0.0 }, format, flags) + return input(label, v, step.takeIf { it > 0.0 }, stepFast.takeIf { it > 0.0 }, format, flags) } - fun inputScalar( - label: String, dataType: DataType, pData: IntArray, step: Int?, stepFast: Int?, - format: String? = null, flags: InputTextSingleFlags = emptyFlags - ) - : Boolean where N : Number, N : Comparable = - withInt(pData) { inputScalar(label, dataType, it, step, stepFast, format, flags) } - - fun inputScalar( - label: String, dataType: DataType, pData: KMutableProperty0, step: N? = null, stepFast: N? = null, - format_: String? = null, flags_: InputTextSingleFlags = emptyFlags - ): Boolean where N : Number, N : Comparable { - + fun NumberOps.input(label: String, pData: KMutableProperty0, step: N? = null, stepFast: N? = null, format_: String? = null, flags_: InputTextSingleFlags = emptyFlags): Boolean where N : Number, N : Comparable { var data by pData val window = currentWindow if (window.skipItems) return false - val format = when (format_) { - null -> when (dataType) { - DataType.Float, DataType.Double -> "%f" - else -> "%d" - } - - else -> format_ - } + val format = format_ ?: defaultFormat - val buf = pData.format(dataType, format/*, 64*/).toByteArray(64) + val buf = data.format(format).toByteArray(64) var flags = flags_ // Testing ActiveId as a minor optimization as filtering is not needed until active - if (g.activeId == 0 && flags hasnt (Itf.CharsDecimal or Itf.CharsHexadecimal or Itf.CharsScientific)) - flags /= inputScalarDefaultCharsFilter(dataType, format) - flags = flags or Itf.AutoSelectAll or Itf._NoMarkEdited // We call MarkItemEdited() ourselves by comparing the actual data rather than the string. + if (g.activeId == 0 && flags hasnt (Itf.CharsDecimal or Itf.CharsHexadecimal or Itf.CharsScientific)) flags /= defaultInputCharsFilter(format) + flags /= Itf.AutoSelectAll or Itf._NoMarkEdited // We call MarkItemEdited() ourselves by comparing the actual data rather than the string. var valueChanged = false if (step != null) { @@ -228,7 +173,7 @@ interface widgetsInputWithKeyboard { pushID(label) setNextItemWidth(1f max (calcItemWidth() - (buttonSize + style.itemInnerSpacing.x) * 2)) if (inputText("", buf, flags)) // PushId(label) + "" gives us the expected ID from outside point of view - valueChanged = dataTypeApplyFromText(buf.cStr, dataType, pData, format) + valueChanged = pData.applyFromText(buf.cStr, format) IMGUI_TEST_ENGINE_ITEM_INFO(g.lastItemData.id, label, g.lastItemData.statusFlags) // Step buttons @@ -239,12 +184,12 @@ interface widgetsInputWithKeyboard { beginDisabled() sameLine(0f, style.itemInnerSpacing.x) if (buttonEx("-", Vec2(buttonSize), buttonFlags)) { - data = dataTypeApplyOp(dataType, '-', data, stepFast?.takeIf { io.keyCtrl } ?: step) + data -= stepFast?.takeIf { io.keyCtrl } ?: step valueChanged = true } sameLine(0f, style.itemInnerSpacing.x) if (buttonEx("+", Vec2(buttonSize), buttonFlags)) { - data = dataTypeApplyOp(dataType, '+', data, stepFast?.takeIf { io.keyCtrl } ?: step) + data += stepFast?.takeIf { io.keyCtrl } ?: step valueChanged = true } if (flags has Itf.ReadOnly) @@ -259,49 +204,19 @@ interface widgetsInputWithKeyboard { popID() endGroup() - } else if (inputText(label, buf, flags)) - valueChanged = dataTypeApplyFromText(buf.cStr, dataType, pData, format) + } else if (inputText(label, buf, flags)) valueChanged = pData.applyFromText(buf.cStr, format) - if (valueChanged) - markItemEdited(g.lastItemData.id) + if (valueChanged) markItemEdited(g.lastItemData.id) return valueChanged } - fun inputScalarN( - label: String, dataType: DataType, v: Any, components: Int, step: N? = null, stepFast: N? = null, - format: String? = null, flags: InputTextSingleFlags = emptyFlags - ): Boolean where N : Number, N : Comparable { +} - val window = currentWindow - if (window.skipItems) return false +inline fun input(label: String, pData: KMutableProperty0, step: N? = null, stepFast: N? = null, format_: String? = null, flags_: InputTextSingleFlags = emptyFlags): Boolean where N : Number, N : Comparable = numberOps().input(label, pData, step, stepFast, format_, flags_) - var valueChanged = false - beginGroup() - pushID(label) - pushMultiItemsWidths(components, calcItemWidth()) - for (i in 0 until components) { - pushID(i) - if (i > 0) - sameLine(0f, style.itemInnerSpacing.x) - valueChanged /= when (dataType) { - DataType.Float -> withFloat(v as FloatArray, i) { inputScalar("", dataType, it as KMutableProperty0, step, stepFast, format, flags) } - DataType.Int -> withInt(v as IntArray, i) { inputScalar("", dataType, it as KMutableProperty0, step, stepFast, format, flags) } - else -> error("invalid") - } - sameLine(0f, style.itemInnerSpacing.x) - popID() - popItemWidth() - } - popID() - - val labelEnd = findRenderedTextEnd(label) - if (0 != labelEnd) { - sameLine(0f, style.itemInnerSpacing.x) - textEx(label, labelEnd) - } +inline fun inputN(label: String, components: Int, step: N? = null, stepFast: N? = null, format: String? = null, flags: InputTextSingleFlags = emptyFlags, properties: (Int) -> KMutableProperty0): Boolean where N : Number, N : Comparable = numberOps().inputN(label, components, step, stepFast, format, flags, properties) - endGroup() - return valueChanged - } -} +inline fun NumberOps.inputN(label: String, components: Int, step: N? = null, stepFast: N? = null, format: String? = null, flags: InputTextSingleFlags = emptyFlags, properties: (Int) -> KMutableProperty0): Boolean where N : Number, N : Comparable = widgetN(label, components) { i -> + input("", properties(i), step, stepFast, format, flags) +} \ No newline at end of file diff --git a/core/src/main/kotlin/imgui/api/widgetsListBoxes.kt b/core/src/main/kotlin/imgui/api/widgetsListBoxes.kt index 96fdc6250..4cdeb26cf 100644 --- a/core/src/main/kotlin/imgui/api/widgetsListBoxes.kt +++ b/core/src/main/kotlin/imgui/api/widgetsListBoxes.kt @@ -28,8 +28,8 @@ import imgui.classes.ListClipper import imgui.has import imgui.internal.classes.Rect import imgui.internal.floor -import imgui.withBool -import imgui.withInt +import imgui.mutablePropertyAt +import imgui.mutableReference import kool.getValue import kool.setValue import kotlin.reflect.KMutableProperty0 @@ -90,7 +90,7 @@ interface widgetsListBoxes { } fun listBox(label: String, currentItemPtr: IntArray, items: Array, heightInItems: Int = -1): Boolean = - withInt(currentItemPtr) { listBox(label, it, items, heightInItems) } + listBox(label, currentItemPtr mutablePropertyAt 0, items, heightInItems) /** This is merely a helper around BeginListBox(), EndListBox(). * Considering using those directly to submit custom data or store selection differently. */ @@ -113,19 +113,18 @@ interface widgetsListBoxes { val clipper = ListClipper() clipper.begin(itemsCount, textLineHeightWithSpacing) while (clipper.step()) - for (i in clipper.display) - withBool { itemSelected -> - val itemText = items.getOrElse(i) { "*Unknown item*" } - - pushID(i) - itemSelected.set(i == currentItem) - if (selectable(itemText, itemSelected)) { - currentItem = i - valueChanged = true - } - if (itemSelected()) setItemDefaultFocus() - popID() + for (i in clipper.display) { + val itemText = items.getOrElse(i) { "*Unknown item*" } + pushID(i) + val itemSelectedRef = (i == currentItem).mutableReference + val itemSelected by itemSelectedRef + if (selectable(itemText, itemSelectedRef)) { + currentItem = i + valueChanged = true } + if (itemSelected) setItemDefaultFocus() + popID() + } endListBox() if (valueChanged) diff --git a/core/src/main/kotlin/imgui/api/widgetsMain.kt b/core/src/main/kotlin/imgui/api/widgetsMain.kt index 836abdedc..f2bf6d77e 100644 --- a/core/src/main/kotlin/imgui/api/widgetsMain.kt +++ b/core/src/main/kotlin/imgui/api/widgetsMain.kt @@ -10,6 +10,7 @@ import imgui.ImGui.buttonEx import imgui.ImGui.calcItemSize import imgui.ImGui.calcItemWidth import imgui.ImGui.calcTextSize +import imgui.ImGui.checkboxFlagsT import imgui.ImGui.currentWindow import imgui.ImGui.frameHeight import imgui.ImGui.itemAdd @@ -39,21 +40,6 @@ import kool.setValue import kotlin.reflect.KMutableProperty0 import imgui.internal.sections.ButtonFlag as Bf - -// @formatter:off -val S8_MIN: Int = -128 -val S8_MAX: Int = 127 -val U8_MIN: Int = 0 -val U8_MAX: Int = 0xFF -val S16_MIN: Int = -32768 -val S16_MAX: Int = 32767 -val U16_MIN: Int = 0 -val U16_MAX: Int = 0xFFFF -val S32_MIN: Int = Integer.MIN_VALUE -val S32_MAX: Int = Integer.MAX_VALUE -// @formatter:on - - // Widgets: Main // - Most widgets return true when the value has been changed or when pressed/selected // - You may also use one of the many IsItemXXX functions (e.g. IsItemActive, IsItemHovered, etc.) to query widget state. @@ -98,12 +84,7 @@ interface widgetsMain { fun arrowButton(id: String, dir: Dir): Boolean = arrowButtonEx(id, dir, Vec2(frameHeight), emptyFlags) fun checkbox(label: String, v: BooleanArray) = checkbox(label, v, 0) - fun checkbox(label: String, v: BooleanArray, i: Int): Boolean { - _b = v[i] - return checkbox(label, ::_b).also { - v[i] = _b - } - } + fun checkbox(label: String, v: BooleanArray, i: Int): Boolean = checkbox(label, v mutablePropertyAt i) fun checkbox(label: String, vPtr: KMutableProperty0): Boolean { @@ -161,39 +142,9 @@ interface widgetsMain { // Suppressing the warning since we're in an interface. @Suppress("INAPPLICABLE_JVM_NAME") @JvmName("checkboxFlags") - fun > checkboxFlags(label: String, flags: FlagArray, flagsValue: Flag): Boolean { - _b = flagsValue in flags[0] // ~allOn - val anyOn = flags[0] has flagsValue - val pressed = when { - !_b && anyOn -> { - val window = currentWindow - val backupItemFlags = g.currentItemFlags - g.currentItemFlags = g.currentItemFlags or ItemFlag.MixedValue - checkbox(label, ::_b).also { - g.currentItemFlags = backupItemFlags - } - } - else -> checkbox(label, ::_b) - } - if (pressed) - flags[0] = when { - _b -> flags[0] or flagsValue - else -> flags[0] wo flagsValue - } - return pressed - } + fun > checkboxFlags(label: String, flags: FlagArray, flagsValue: Flag): Boolean = checkboxFlagsT(label, flags mutablePropertyAt 0, flagsValue) - fun > checkboxFlags(label: String, flagsPtr: KMutableProperty0>, flagsValue: Flag): Boolean { - var flags by flagsPtr - val v = booleanArrayOf(flagsValue in flags) - val pressed = checkbox(label, v) - if (pressed) - flags = when { - v[0] -> flags or flagsValue - else -> flags wo flagsValue - } - return pressed - } + fun > checkboxFlags(label: String, flagsPtr: KMutableProperty0>, flagsValue: Flag): Boolean = checkboxFlagsT(label, flagsPtr, flagsValue) /** use with e.g. if (radioButton("one", myValue==1)) myValue = 1 */ fun radioButton(label: String, active: Boolean): Boolean { diff --git a/core/src/main/kotlin/imgui/api/widgetsSelectables.kt b/core/src/main/kotlin/imgui/api/widgetsSelectables.kt index 81c4c6f17..11ea20e8f 100644 --- a/core/src/main/kotlin/imgui/api/widgetsSelectables.kt +++ b/core/src/main/kotlin/imgui/api/widgetsSelectables.kt @@ -29,6 +29,7 @@ import imgui.internal.sections.ItemStatusFlag import imgui.internal.sections.NavHighlightFlag import kool.getValue import kool.setValue +import kotlin.math.max import kotlin.reflect.KMutableProperty0 import imgui.SelectableFlag as Sf import imgui.WindowFlag as Wf diff --git a/core/src/main/kotlin/imgui/api/widgetsSliders.kt b/core/src/main/kotlin/imgui/api/widgetsSliders.kt index 19c6d70e5..c0e0b2ebf 100644 --- a/core/src/main/kotlin/imgui/api/widgetsSliders.kt +++ b/core/src/main/kotlin/imgui/api/widgetsSliders.kt @@ -9,11 +9,14 @@ import glm_.vec3.Vec3i import glm_.vec4.Vec4 import glm_.vec4.Vec4i import imgui.* -import imgui.ImGui.format import imgui.ImGui.isClicked import imgui.ImGui.logSetNextTextDecoration import imgui.ImGui.setOwner +import imgui.ImGui.slider +import imgui.ImGui.sliderBehavior import imgui.ImGui.tempInputScalar +import imgui.ImGui.vSlider +import imgui.internal.api.widgetN import imgui.internal.classes.Rect import imgui.internal.sections.IMGUI_TEST_ENGINE_ITEM_INFO import imgui.internal.sections.ItemFlag @@ -22,157 +25,60 @@ import kool.getValue import kool.setValue import kotlin.reflect.KMutableProperty0 -@Suppress("UNCHECKED_CAST") - // Widgets: Regular Sliders // - CTRL+Click on any slider to turn them into an input box. Manually input values aren't clamped by default and can go off-bounds. Use ImGuiSliderFlags_AlwaysClamp to always clamp. // - Adjust format string to decorate the value with a prefix, a suffix, or adapt the editing and display precision e.g. "%.3f" -> 1.234; "%5.2f secs" -> 01.23 secs; "Biscuit: %.0f" -> Biscuit: 1; etc. // - Format string may also be set to NULL or use the default format ("%f" or "%d"). -// - Legacy: Pre-1.78 there are SliderXXX() function signatures that take a final `float power=1.0f' argument instead of the `ImGuiSliderFlags flags=0' argument. -// If you get a warning converting a float to ImGuiSliderFlags, read https://github.com/ocornut/imgui/issues/3361 interface widgetsSliders { - - /** Adjust format to decorate the value with a prefix or a suffix. * "%.3f" 1.234 * "%5.2f secs" 01.23 secs * "Gold: %.0f" Gold: 1 - * Use power != 1.0f for non-linear sliders. * adjust format to decorate the value with a prefix or a suffix for in-slider labels or unit display. Use power!=1.0 for power curve sliders */ - fun sliderFloat( - label: String, v: FloatArray, ptr: Int, vMin: Float, vMax: Float, - format: String = "%.3f", flags: SliderFlags = emptyFlags - ): Boolean = - withFloat(v, ptr) { sliderFloat(label, it, vMin, vMax, format, flags) } + fun slider(label: String, v: FloatArray, ptr: Int, vMin: Float, vMax: Float, format: String = "%.3f", flags: SliderFlags = emptyFlags): Boolean = slider(label, v mutablePropertyAt ptr, vMin, vMax, format, flags) - /** Adjust format to decorate the value with a prefix or a suffix. - * "%.3f" 1.234 - * "%5.2f secs" 01.23 secs - * "Gold: %.0f" Gold: 1 - * Use power != 1.0f for non-linear sliders. - * adjust format to decorate the value with a prefix or a suffix for in-slider labels or unit display. Use power!=1.0 for power curve sliders */ - fun sliderFloat( - label: String, v: KMutableProperty0, vMin: Float, vMax: Float, - format: String = "%.3f", flags: SliderFlags = emptyFlags - ): Boolean = - sliderScalar(label, DataType.Float, v, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - - fun sliderFloat2( - label: String, v: FloatArray, vMin: Float, vMax: Float, - format: String = "%.3f", flags: SliderFlags = emptyFlags - ): Boolean = - sliderScalarN(label, DataType.Float, v, 2, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - - fun sliderVec2( - label: String, v: Vec2, vMin: Float, vMax: Float, - format: String = "%.3f", flags: SliderFlags = emptyFlags - ): Boolean = - sliderScalarN(label, DataType.Float, v to _fa, 2, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - .also { v put _fa } - - fun sliderFloat3( - label: String, v: FloatArray, vMin: Float, vMax: Float, - format: String = "%.3f", flags: SliderFlags = emptyFlags - ): Boolean = - sliderScalarN(label, DataType.Float, v, 3, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - - fun sliderVec3( - label: String, v: Vec3, vMin: Float, vMax: Float, - format: String = "%.3f", flags: SliderFlags = emptyFlags - ): Boolean = - sliderScalarN(label, DataType.Float, v to _fa, 3, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - .also { v put _fa } - - fun sliderFloat4( - label: String, v: FloatArray, vMin: Float, vMax: Float, - format: String = "%.3f", flags: SliderFlags = emptyFlags - ): Boolean = - sliderScalarN(label, DataType.Float, v, 4, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - - fun sliderVec4( - label: String, v: Vec4, vMin: Float, vMax: Float, - format: String = "%.3f", flags: SliderFlags = emptyFlags - ): Boolean = - sliderScalarN(label, DataType.Float, v to _fa, 4, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - .also { v put _fa } - - fun sliderAngle( - label: String, vRadPtr: KMutableProperty0, vDegreesMin: Float = -360f, vDegreesMax: Float = 360f, - format_: String = "%.0f deg", flags: SliderFlags = emptyFlags - ): Boolean { + fun slider2(label: String, v: FloatArray, vMin: Float, vMax: Float, format: String = "%.3f", flags: SliderFlags = emptyFlags): Boolean = sliderN(label, 2, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun slider2(label: String, v: Vec2, vMin: Float, vMax: Float, format: String = "%.3f", flags: SliderFlags = emptyFlags): Boolean = sliderN(label, 2, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun slider3(label: String, v: FloatArray, vMin: Float, vMax: Float, format: String = "%.3f", flags: SliderFlags = emptyFlags): Boolean = sliderN(label, 3, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun slider3(label: String, v: Vec3, vMin: Float, vMax: Float, format: String = "%.3f", flags: SliderFlags = emptyFlags): Boolean = sliderN(label, 3, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun slider4(label: String, v: FloatArray, vMin: Float, vMax: Float, format: String = "%.3f", flags: SliderFlags = emptyFlags): Boolean = sliderN(label, 4, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun slider4(label: String, v: Vec4, vMin: Float, vMax: Float, format: String = "%.3f", flags: SliderFlags = emptyFlags): Boolean = sliderN(label, 4, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun sliderAngle(label: String, vRadPtr: KMutableProperty0, vDegreesMin: Float = -360f, vDegreesMax: Float = 360f, format_: String = "%.0f deg", flags: SliderFlags = emptyFlags): Boolean { val format = format_.ifEmpty { "%.0f deg" } var vRad by vRadPtr vRad = vRad.deg - return sliderFloat(label, vRadPtr, vDegreesMin, vDegreesMax, format, flags) - .also { vRad = vRad.rad } + return slider(label, vRadPtr, vDegreesMin, vDegreesMax, format, flags).also { vRad = vRad.rad } } - fun sliderInt( - label: String, v: IntArray, ptr: Int, vMin: Int, vMax: Int, - format: String = "%d", flags: SliderFlags = emptyFlags - ): Boolean = - withInt(v, ptr) { sliderInt(label, it, vMin, vMax, format, flags) } - - fun sliderInt( - label: String, v: KMutableProperty0, vMin: Int, vMax: Int, - format: String = "%d", flags: SliderFlags = emptyFlags - ): Boolean = - sliderScalar(label, DataType.Int, v, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - - fun sliderInt2( - label: String, v: IntArray, vMin: Int, vMax: Int, - format: String = "%d", flags: SliderFlags = emptyFlags - ): Boolean = - sliderScalarN(label, DataType.Int, v, 2, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - - fun sliderVec2i( - label: String, v: Vec2i, vMin: Int, vMax: Int, - format: String = "%d", flags: SliderFlags = emptyFlags - ): Boolean = - sliderScalarN(label, DataType.Int, v to _ia, 2, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - .also { v put _ia } - - fun sliderInt3( - label: String, v: IntArray, vMin: Int, vMax: Int, - format: String = "%d", flags: SliderFlags = emptyFlags - ): Boolean = - sliderScalarN(label, DataType.Int, v, 3, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - - fun sliderVec3i( - label: String, v: Vec3i, vMin: Int, vMax: Int, - format: String = "%d", flags: SliderFlags = emptyFlags - ): Boolean = - sliderScalarN(label, DataType.Int, v to _ia, 3, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - .also { v put _ia } - - fun sliderInt4( - label: String, v: IntArray, vMin: Int, vMax: Int, - format: String = "%d", flags: SliderFlags = emptyFlags - ): Boolean = - sliderScalarN(label, DataType.Int, v, 4, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - - fun sliderVec4i( - label: String, v: Vec4i, vMin: Int, vMax: Int, - format: String = "%d", flags: SliderFlags = emptyFlags - ): Boolean = - sliderScalarN(label, DataType.Int, v to _ia, 4, vMin.asMutableProperty, vMax.asMutableProperty, format, flags) - .also { v put _ia } + fun slider(label: String, v: IntArray, ptr: Int, vMin: Int, vMax: Int, format: String = "%d", flags: SliderFlags = emptyFlags): Boolean = slider(label, v mutablePropertyAt ptr, vMin, vMax, format, flags) + + fun slider2(label: String, v: IntArray, vMin: Int, vMax: Int, format: String = "%d", flags: SliderFlags = emptyFlags): Boolean = sliderN(label, 2, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun slider2(label: String, v: Vec2i, vMin: Int, vMax: Int, format: String = "%d", flags: SliderFlags = emptyFlags): Boolean = sliderN(label, 2, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun slider3(label: String, v: IntArray, vMin: Int, vMax: Int, format: String = "%d", flags: SliderFlags = emptyFlags): Boolean = sliderN(label, 3, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun slider3(label: String, v: Vec3i, vMin: Int, vMax: Int, format: String = "%d", flags: SliderFlags = emptyFlags): Boolean = sliderN(label, 3, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun slider4(label: String, v: IntArray, vMin: Int, vMax: Int, format: String = "%d", flags: SliderFlags = emptyFlags): Boolean = sliderN(label, 4, vMin, vMax, format, flags, v::mutablePropertyAt) + + fun slider4(label: String, v: Vec4i, vMin: Int, vMax: Int, format: String = "%d", flags: SliderFlags = emptyFlags): Boolean = sliderN(label, 4, vMin, vMax, format, flags, v::mutablePropertyAt) /** Adjust format to decorate the value with a prefix or a suffix. * "%.3f" 1.234 * "%5.2f secs" 01.23 secs * "Gold: %.0f" Gold: 1 - * Use power != 1.0f for non-linear sliders. * adjust format to decorate the value with a prefix or a suffix for in-slider labels or unit display. Use power!=1.0 for power curve sliders * * Note: p_data, p_min and p_max are _pointers_ to a memory address holding the data. For a slider, they are all required. * Read code of e.g. SliderFloat(), SliderInt() etc. or examples in 'Demo->Widgets->Data Types' to understand how to use this function directly. */ - fun sliderScalar( - label: String, dataType: DataType, pData: KMutableProperty0, - pMin: KMutableProperty0? = null, pMax: KMutableProperty0? = null, - format_: String? = null, flags: SliderFlags = emptyFlags - ): Boolean - where N : Number, N : Comparable { + fun NumberOps.slider(label: String, pData: KMutableProperty0, min: N, max: N, format_: String? = null, flags: SliderFlags = emptyFlags): Boolean where N : Number, N : Comparable { val window = ImGui.currentWindow if (window.skipItems) return false @@ -186,11 +92,10 @@ interface widgetsSliders { val tempInputAllowed = flags hasnt SliderFlag.NoInput ImGui.itemSize(totalBb, ImGui.style.framePadding.y) - if (!ImGui.itemAdd(totalBb, id, frameBb, if (tempInputAllowed) ItemFlag.Inputable else emptyFlags)) - return false + if (!ImGui.itemAdd(totalBb, id, frameBb, if (tempInputAllowed) ItemFlag.Inputable else emptyFlags)) return false // Default format string when passing NULL - val format = format_ ?: if (dataType == DataType.Float || dataType == DataType.Double) "%f" else "%d" + val format = format_ ?: defaultFormat val hovered = ImGui.itemHoverable(frameBb, id) var tempInputIsActive = tempInputAllowed && ImGui.tempInputIsActive(id) @@ -217,7 +122,7 @@ interface widgetsSliders { if (tempInputIsActive) { // Only clamp CTRL+Click input when ImGuiSliderFlags_AlwaysClamp is set val isClampInput = flags has SliderFlag.AlwaysClamp - return tempInputScalar(frameBb, id, label, dataType, pData, format, pMin.takeIf { isClampInput }, pMax.takeIf { isClampInput }) + return tempInputScalar(frameBb, id, label, pData, format, min.takeIf { isClampInput }, max.takeIf { isClampInput }) } // Draw frame @@ -231,9 +136,8 @@ interface widgetsSliders { // Slider behavior val grabBb = Rect() - val valueChanged = ImGui.sliderBehavior(frameBb, id, dataType, pData, pMin!!, pMax!!, format, flags, grabBb) - if (valueChanged) - ImGui.markItemEdited(id) + val valueChanged = sliderBehavior(frameBb, id, pData, min, max, format, flags, grabBb) + if (valueChanged) ImGui.markItemEdited(id) // Render grab if (grabBb.max.x > grabBb.min.x) { @@ -242,9 +146,8 @@ interface widgetsSliders { } // Display value using user-provided display format so user can add prefix/suffix/decorations to the value. - val value = pData.format(dataType, format) - if (g.logEnabled) - logSetNextTextDecoration("{", "}"); + val value = pData().format(format) + if (g.logEnabled) logSetNextTextDecoration("{", "}") ImGui.renderTextClipped(frameBb.min, frameBb.max, value, null, Vec2(0.5f)) if (labelSize.x > 0f) { @@ -256,71 +159,8 @@ interface widgetsSliders { return valueChanged } - /** Add multiple sliders on 1 line for compact edition of multiple components */ - fun sliderScalarN( - label: String, dataType: DataType, pData: Any, components: Int, - pMin: KMutableProperty0? = null, pMax: KMutableProperty0? = null, - format: String? = null, flags: SliderFlags = emptyFlags - ): Boolean - where N : Number, N : Comparable { - - val window = ImGui.currentWindow - if (window.skipItems) return false - - var valueChanged = false - ImGui.beginGroup() - ImGui.pushID(label) - ImGui.pushMultiItemsWidths(components, ImGui.calcItemWidth()) - for (i in 0 until components) { - ImGui.pushID(i) - if (i > 0) - ImGui.sameLine(0f, ImGui.style.itemInnerSpacing.x) - valueChanged /= when (dataType) { - DataType.Int -> withInt(pData as IntArray, i) { - sliderScalar("", dataType, it as KMutableProperty0, pMin, pMax, format, flags) - } - - DataType.Float -> withFloat(pData as FloatArray, i) { - sliderScalar("", dataType, it as KMutableProperty0, pMin, pMax, format, flags) - } - - else -> error("invalid") - } - ImGui.popID() - ImGui.popItemWidth() - } - ImGui.popID() - - val labelEnd = ImGui.findRenderedTextEnd(label) - if (0 != labelEnd) { - ImGui.sameLine(0f, ImGui.style.itemInnerSpacing.x) - ImGui.textEx(label, labelEnd) - } - ImGui.endGroup() - return valueChanged - } - - fun vSliderFloat( - label: String, size: Vec2, v: KMutableProperty0, vMin: Float, vMax: Float, - format: String = "%.3f", flags: SliderFlags = emptyFlags - ): Boolean - where N : Number, N : Comparable = - vSliderScalar(label, size, DataType.Float, v, (vMin as N).asMutableProperty, (vMax as N).asMutableProperty, format, flags) - - fun vSliderInt( - label: String, size: Vec2, v: KMutableProperty0, vMin: Int, vMax: Int, - format: String = "%d", flags: SliderFlags = emptyFlags - ): Boolean - where N : Number, N : Comparable = - vSliderScalar(label, size, DataType.Int, v, (vMin as N).asMutableProperty, (vMax as N).asMutableProperty, format, flags) - /** Internal implementation */ - fun vSliderScalar( - label: String, size: Vec2, dataType: DataType, pData: KMutableProperty0, - pMin: KMutableProperty0? = null, pMax: KMutableProperty0? = null, - format_: String? = null, flags: SliderFlags = emptyFlags - ): Boolean - where N : Number, N : Comparable { + fun NumberOps.vSlider(label: String, size: Vec2, pData: KMutableProperty0, min: N, max: N, format_: String? = null, flags: SliderFlags = emptyFlags): Boolean where N : Number, N : Comparable { val window = ImGui.currentWindow if (window.skipItems) return false @@ -335,7 +175,7 @@ interface widgetsSliders { if (!ImGui.itemAdd(frameBb, id)) return false // Default format string when passing NULL - val format = format_ ?: if (dataType == DataType.Float || dataType == DataType.Double) "%f" else "%d" + val format = format_ ?: defaultFormat val hovered = ImGui.itemHoverable(frameBb, id) val clicked = hovered && MouseButton.Left.isClicked(id) @@ -358,23 +198,32 @@ interface widgetsSliders { ImGui.renderFrame(frameBb.min, frameBb.max, frameCol.u32, true, ImGui.style.frameRounding) // Slider behavior val grabBb = Rect() - val valueChanged = ImGui.sliderBehavior(frameBb, id, dataType, pData, pMin!!, pMax!!, format, flags or SliderFlag._Vertical, grabBb) + val valueChanged = sliderBehavior(frameBb, id, pData, min, max, format, flags or SliderFlag._Vertical, grabBb) - if (valueChanged) - ImGui.markItemEdited(id) + if (valueChanged) ImGui.markItemEdited(id) // Render grab - if (grabBb.max.y > grabBb.min.y) - window.drawList.addRectFilled(grabBb.min, grabBb.max, ImGui.getColorU32(if (g.activeId == id) Col.SliderGrabActive else Col.SliderGrab), ImGui.style.grabRounding) + if (grabBb.max.y > grabBb.min.y) window.drawList.addRectFilled(grabBb.min, grabBb.max, ImGui.getColorU32(if (g.activeId == id) Col.SliderGrabActive else Col.SliderGrab), ImGui.style.grabRounding) /* Display value using user-provided display format so user can add prefix/suffix/decorations to the value. For the vertical slider we allow centered text to overlap the frame padding */ - val value = pData.format(dataType, format) + val value = pData().format(format) val posMin = Vec2(frameBb.min.x, frameBb.min.y + ImGui.style.framePadding.y) ImGui.renderTextClipped(posMin, frameBb.max, value, null, Vec2(0.5f, 0f)) - if (labelSize.x > 0f) - ImGui.renderText(Vec2(frameBb.max.x + ImGui.style.itemInnerSpacing.x, frameBb.min.y + ImGui.style.framePadding.y), label) + if (labelSize.x > 0f) ImGui.renderText(Vec2(frameBb.max.x + ImGui.style.itemInnerSpacing.x, frameBb.min.y + ImGui.style.framePadding.y), label) return valueChanged } -} \ No newline at end of file + +} + +inline fun slider(label: String, pData: KMutableProperty0, min: N, max: N, format_: String? = null, flags: SliderFlags = emptyFlags): Boolean where N : Number, N : Comparable = numberOps().slider(label, pData, min, max, format_, flags) + +/** Add multiple sliders on 1 line for compact edition of multiple components */ +inline fun sliderN(label: String, components: Int, min: N, max: N, format: String? = null, flags: SliderFlags = emptyFlags, properties: (Int) -> KMutableProperty0): Boolean where N : Number, N : Comparable = numberOps().sliderN(label, components, min, max, format, flags, properties) + +inline fun NumberOps.sliderN(label: String, components: Int, min: N, max: N, format: String? = null, flags: SliderFlags = emptyFlags, properties: (Int) -> KMutableProperty0): Boolean where N : Number, N : Comparable = widgetN(label, components) { i -> + slider("", properties(i), min, max, format, flags) +} + +inline fun vSlider(label: String, size: Vec2, pData: KMutableProperty0, min: N, max: N, format_: String? = null, flags: SliderFlags = emptyFlags): Boolean where N : Number, N : Comparable = numberOps().vSlider(label, size, pData, min, max, format_, flags) \ No newline at end of file diff --git a/core/src/main/kotlin/imgui/api/windowScrolling.kt b/core/src/main/kotlin/imgui/api/windowScrolling.kt index 1ff97f4ab..d0319ef79 100644 --- a/core/src/main/kotlin/imgui/api/windowScrolling.kt +++ b/core/src/main/kotlin/imgui/api/windowScrolling.kt @@ -7,7 +7,7 @@ import imgui.ImGui.setScrollFromPosY import imgui.ImGui.setScrollX import imgui.ImGui.setScrollY import imgui.ImGui.style -import imgui.lerp +import imgui.internal.lerp // Windows Scrolling // - Any change of Scroll will be applied at the beginning of next frame in the first call to Begin(). diff --git a/core/src/main/kotlin/imgui/classes/DrawList.kt b/core/src/main/kotlin/imgui/classes/DrawList.kt index 13f338cd3..b8a47ebf1 100644 --- a/core/src/main/kotlin/imgui/classes/DrawList.kt +++ b/core/src/main/kotlin/imgui/classes/DrawList.kt @@ -24,6 +24,7 @@ import uno.kotlin.plusAssign import java.nio.ByteBuffer import java.util.Stack import kotlin.math.ceil +import kotlin.math.max import kotlin.math.sqrt /** A single draw command list (generally one per window, conceptually you may see this as a dynamic "mesh" builder) diff --git a/core/src/main/kotlin/imgui/demo/ShowDemoWindowColumns.kt b/core/src/main/kotlin/imgui/demo/ShowDemoWindowColumns.kt index 6b3106611..2297258cd 100644 --- a/core/src/main/kotlin/imgui/demo/ShowDemoWindowColumns.kt +++ b/core/src/main/kotlin/imgui/demo/ShowDemoWindowColumns.kt @@ -8,11 +8,10 @@ import imgui.ImGui.checkbox import imgui.ImGui.columnIndex import imgui.ImGui.columns import imgui.ImGui.contentRegionAvail -import imgui.ImGui.dragInt import imgui.ImGui.fontSize import imgui.ImGui.getColumnOffset import imgui.ImGui.getColumnWidth -import imgui.ImGui.inputFloat +import imgui.ImGui.input import imgui.ImGui.isItemHovered import imgui.ImGui.nextColumn import imgui.ImGui.sameLine @@ -28,6 +27,7 @@ import imgui.ImGui.treePop import imgui.SelectableFlag import imgui.WindowFlag import imgui.api.demoDebugInformations.Companion.helpMarker +import imgui.api.drag import imgui.classes.ListClipper import imgui.dsl.child import imgui.dsl.collapsingHeader @@ -165,7 +165,7 @@ object ShowDemoWindowColumns { // NB: Future columns API should allow automatic horizontal borders. val linesCount = 3 setNextItemWidth(fontSize * 8) - dragInt("##columns_count", ::columnsCount, 0.1f, 2, 10, "%d columns") + drag("##columns_count", ::columnsCount, 0.1f, 2, 10, "%d columns") if (columnsCount < 2) columnsCount = 2 sameLine() @@ -207,13 +207,13 @@ object ShowDemoWindowColumns { text("ImGui") button("Apple") - inputFloat("red", ::foo, 0.05f, 0f, "%.3f") + input("red", ::foo, 0.05f, 0f, "%.3f") text("An extra line here.") nextColumn() text("Sailor") button("Corniflower") - inputFloat("blue", ::bar, 0.05f, 0f, "%.3f") + input("blue", ::bar, 0.05f, 0f, "%.3f") nextColumn() collapsingHeader("Category A") { text("Blah blah blah") }; nextColumn() diff --git a/core/src/main/kotlin/imgui/demo/ShowDemoWindowInputs.kt b/core/src/main/kotlin/imgui/demo/ShowDemoWindowInputs.kt index e57cafbb4..be7cf2a4e 100644 --- a/core/src/main/kotlin/imgui/demo/ShowDemoWindowInputs.kt +++ b/core/src/main/kotlin/imgui/demo/ShowDemoWindowInputs.kt @@ -2,6 +2,7 @@ package imgui.demo import glm_.i import glm_.vec2.Vec2 +import glm_.vec3.Vec3 import glm_.vec4.Vec4 import imgui.* import imgui.ImGui.beginDisabled @@ -29,11 +30,11 @@ import imgui.ImGui.setNextFrameWantCaptureKeyboard import imgui.ImGui.setNextFrameWantCaptureMouse import imgui.ImGui.setNextItemOpen import imgui.ImGui.setNextItemWidth -import imgui.ImGui.sliderFloat3 -import imgui.ImGui.sliderInt +import imgui.ImGui.slider3 import imgui.ImGui.text import imgui.ImGui.textWrapped import imgui.api.demoDebugInformations.Companion.helpMarker +import imgui.api.slider import imgui.dsl.collapsingHeader import imgui.dsl.treeNode @@ -108,9 +109,9 @@ object ShowDemoWindowInputs { "Notice how normally (when set to none), the value of io.WantCaptureKeyboard would be false when hovering and true when clicking.") val captureOverrideDesc = listOf("None", "Set to false", "Set to true") setNextItemWidth(ImGui.fontSize * 15) - sliderInt("SetNextFrameWantCaptureMouse() on hover", ::captureOverrideMouse, -1, +1, captureOverrideDesc[captureOverrideMouse + 1], SliderFlag.AlwaysClamp) + slider("SetNextFrameWantCaptureMouse() on hover", ::captureOverrideMouse, -1, +1, captureOverrideDesc[captureOverrideMouse + 1], SliderFlag.AlwaysClamp) setNextItemWidth(ImGui.fontSize * 15) - sliderInt("SetNextFrameWantCaptureKeyboard() on hover", ::captureOverrideKeyboard, -1, +1, captureOverrideDesc[captureOverrideKeyboard + 1], SliderFlag.AlwaysClamp) + slider("SetNextFrameWantCaptureKeyboard() on hover", ::captureOverrideKeyboard, -1, +1, captureOverrideDesc[captureOverrideKeyboard + 1], SliderFlag.AlwaysClamp) colorButton("##panel", Vec4(0.7f, 0.1f, 0.7f, 1f), ColorEditFlag.NoTooltip or ColorEditFlag.NoDragDrop, Vec2(128f, 96f)) // Dummy item if (ImGui.isItemHovered() && captureOverrideMouse != -1) @@ -196,7 +197,7 @@ object ShowDemoWindowInputs { object `Focus from code` { var buf = "click on a button to set focus".toByteArray(128) - val f3 = FloatArray(3) + val f3 = Vec3() operator fun invoke() { // IMGUI_DEMO_MARKER("Inputs & Focus/Focus from code"); treeNode("Focus from code") { @@ -228,7 +229,7 @@ object ShowDemoWindowInputs { if (button("Focus on Y")) focusAhead = 1; sameLine() if (button("Focus on Z")) focusAhead = 2 if (focusAhead != -1) setKeyboardFocusHere(focusAhead) - sliderFloat3("Float3", f3, 0f, 1f) + slider3("Float3", f3, 0f, 1f) textWrapped("NB: Cursor & selection are preserved when refocusing last used item in code.") } diff --git a/core/src/main/kotlin/imgui/demo/ShowDemoWindowLayout.kt b/core/src/main/kotlin/imgui/demo/ShowDemoWindowLayout.kt index 33e3fb0e2..9518efaf3 100644 --- a/core/src/main/kotlin/imgui/demo/ShowDemoWindowLayout.kt +++ b/core/src/main/kotlin/imgui/demo/ShowDemoWindowLayout.kt @@ -26,9 +26,7 @@ import imgui.ImGui.combo import imgui.ImGui.contentRegionAvail import imgui.ImGui.cursorScreenPos import imgui.ImGui.cursorStartPos -import imgui.ImGui.dragFloat -import imgui.ImGui.dragInt -import imgui.ImGui.dragVec2 +import imgui.ImGui.drag2 import imgui.ImGui.dummy import imgui.ImGui.end import imgui.ImGui.endChild @@ -73,8 +71,6 @@ import imgui.ImGui.setScrollFromPosY import imgui.ImGui.setScrollHereX import imgui.ImGui.setScrollHereY import imgui.ImGui.setTooltip -import imgui.ImGui.sliderFloat -import imgui.ImGui.sliderInt import imgui.ImGui.smallButton import imgui.ImGui.spacing import imgui.ImGui.style @@ -90,6 +86,8 @@ import imgui.ImGui.windowContentRegionMax import imgui.ImGui.windowDrawList import imgui.ImGui.windowPos import imgui.api.demoDebugInformations.Companion.helpMarker +import imgui.api.drag +import imgui.api.slider import imgui.classes.Color import imgui.demo.showExampleApp.MenuFile import imgui.dsl.child @@ -343,7 +341,7 @@ object ShowDemoWindowLayout { // the POV of the parent window). See 'Demo->Querying Status (Active/Focused/Hovered etc.)' for details. run { setNextItemWidth(fontSize * 8) - dragInt("Offset X", ::offsetX, 1f, -1000, 1000) + drag("Offset X", ::offsetX, 1f, -1000, 1000) ImGui.cursorPosX += offsetX withStyleColor(Col.ChildBg, COL32(255, 0, 0, 100)) { @@ -380,40 +378,40 @@ object ShowDemoWindowLayout { text("SetNextItemWidth/PushItemWidth(100)") sameLine(); helpMarker("Fixed width.") pushItemWidth(100) - dragFloat("float##1b", ::f) + drag("float##1b", ::f) if (showIndentedItems) indent { - dragFloat("float (indented)##1b", ::f) + drag("float (indented)##1b", ::f) } popItemWidth() text("SetNextItemWidth/PushItemWidth(-100)") sameLine(); helpMarker("Align to right edge minus 100") pushItemWidth(-100) - dragFloat("float##2a", ::f) + drag("float##2a", ::f) if (showIndentedItems) indent { - dragFloat("float (indented)##2b", ::f) + drag("float (indented)##2b", ::f) } popItemWidth() text("SetNextItemWidth/PushItemWidth(GetContentRegionAvail().x * 0.5f)") sameLine(); helpMarker("Half of available width.\n(~ right-cursor_pos)\n(works within a column set)") pushItemWidth(contentRegionAvail.x * 0.5f) - dragFloat("float##3a", ::f) + drag("float##3a", ::f) if (showIndentedItems) indent { - dragFloat("float (indented)##3b", ::f) + drag("float (indented)##3b", ::f) } popItemWidth() text("SetNextItemWidth/PushItemWidth(-GetContentRegionAvail().x * 0.5f)") sameLine(); helpMarker("Align to right edge minus half") pushItemWidth(-ImGui.contentRegionAvail.x * 0.5f) - dragFloat("float##4a", ::f) + drag("float##4a", ::f) if (showIndentedItems) indent { - dragFloat("float (indented)##4b", ::f) + drag("float (indented)##4b", ::f) } popItemWidth() @@ -423,10 +421,10 @@ object ShowDemoWindowLayout { sameLine(); helpMarker("Align to right edge") pushItemWidth(-Float.MIN_VALUE) - dragFloat("##float5a", ::f) + drag("##float5a", ::f) if (showIndentedItems) indent { - dragFloat("float (indented)##5b", ::f) + drag("float (indented)##5b", ::f) } popItemWidth() } @@ -488,9 +486,9 @@ object ShowDemoWindowLayout { val items = arrayOf("AAAA", "BBBB", "CCCC", "DDDD") withItemWidth(80f) { combo("Combo", ::item, items); sameLine() - sliderFloat("X", ::f0, 0f, 5f); sameLine() - sliderFloat("Y", ::f1, 0f, 5f); sameLine() - sliderFloat("Z", ::f2, 0f, 5f) + slider("X", ::f0, 0f, 5f); sameLine() + slider("Y", ::f1, 0f, 5f); sameLine() + slider("Z", ::f2, 0f, 5f) } withItemWidth(80f) { @@ -498,9 +496,7 @@ object ShowDemoWindowLayout { for (i in 0..3) { if (i > 0) sameLine() withID(i) { - withInt(selection, i) { - listBox("", it, items) - } + listBox("", selection mutablePropertyAt i, items) } //if (IsItemHovered()) SetTooltip("ListBox %d hovered", i); } @@ -559,13 +555,13 @@ object ShowDemoWindowLayout { checkbox("Track", ::enableTrack) pushItemWidth(100) - sameLine(140); enableTrack = dragInt("##item", ::trackItem, 0.25f, 0, 99, "Item = %d") or enableTrack + sameLine(140); enableTrack = drag("##item", ::trackItem, 0.25f, 0, 99, "Item = %d") or enableTrack var scrollToOff = button("Scroll Offset") - sameLine(140); scrollToOff = dragFloat("##off", ::scrollToOffPx, 1f, 0f, Float.MAX_VALUE, "+%.0f px") or scrollToOff + sameLine(140); scrollToOff = drag("##off", ::scrollToOffPx, 1f, 0f, Float.MAX_VALUE, "+%.0f px") or scrollToOff var scrollToPos = button("Scroll To Pos") - sameLine(140); scrollToPos = dragFloat("##pos", ::scrollToPosPx, 1f, -10f, Float.MAX_VALUE, "X/Y = %.0f px") or scrollToPos + sameLine(140); scrollToPos = drag("##pos", ::scrollToPosPx, 1f, -10f, Float.MAX_VALUE, "X/Y = %.0f px") or scrollToPos popItemWidth() if (scrollToOff || scrollToPos) @@ -642,9 +638,9 @@ object ShowDemoWindowLayout { // Miscellaneous Horizontal Scrolling Demo helpMarker( - "Horizontal scrolling for a window is enabled via the ImGuiWindowFlags_HorizontalScrollbar flag.\n\n" + - "You may want to also explicitly specify content width by using SetNextWindowContentWidth() before Begin().") - sliderInt("Lines", ::lines, 1, 15) + "Horizontal scrolling for a window is enabled via the ImGuiWindowFlags_HorizontalScrollbar flag.\n\n" + + "You may want to also explicitly specify content width by using SetNextWindowContentWidth() before Begin().") + slider("Lines", ::lines, 1, 15) pushStyleVar(StyleVar.FrameRounding, 3f) pushStyleVar(StyleVar.FramePadding, Vec2(2f, 1f)) val scrollingChildSize = Vec2(0f, ImGui.frameHeightWithSpacing * 7 + 30) @@ -713,7 +709,7 @@ object ShowDemoWindowLayout { if (explicitContentSize) { sameLine() setNextItemWidth(100f) - dragFloat("##csx", ::contentsSizeX) + drag("##csx", ::contentsSizeX) val p = cursorScreenPos windowDrawList.addRectFilled(p, Vec2(p.x + 10, p.y + 10), COL32_WHITE) windowDrawList.addRectFilled(Vec2(p.x + contentsSizeX - 10, p.y), Vec2(p.x + contentsSizeX, p.y + 10), COL32_WHITE) @@ -777,7 +773,7 @@ object ShowDemoWindowLayout { val offset = Vec2(30) operator fun invoke() { treeNode("Clipping") { - dragVec2("size", size, 0.5f, 1f, 200f, "%.0f") + drag2("size", size, 0.5f, 1f, 200f, "%.0f") textWrapped("(Click and drag to scroll)") helpMarker( diff --git a/core/src/main/kotlin/imgui/demo/ShowDemoWindowPopus.kt b/core/src/main/kotlin/imgui/demo/ShowDemoWindowPopus.kt index 18ca0e818..6d310499d 100644 --- a/core/src/main/kotlin/imgui/demo/ShowDemoWindowPopus.kt +++ b/core/src/main/kotlin/imgui/demo/ShowDemoWindowPopus.kt @@ -12,7 +12,6 @@ import imgui.ImGui.closeCurrentPopup import imgui.ImGui.collapsingHeader import imgui.ImGui.colorEdit4 import imgui.ImGui.combo -import imgui.ImGui.dragFloat import imgui.ImGui.endMenu import imgui.ImGui.endMenuBar import imgui.ImGui.endPopup @@ -33,6 +32,7 @@ import imgui.ImGui.text import imgui.ImGui.textEx import imgui.ImGui.textWrapped import imgui.api.demoDebugInformations.Companion.helpMarker +import imgui.api.drag import imgui.demo.showExampleApp.MenuFile import imgui.dsl.button import imgui.dsl.menu @@ -200,7 +200,7 @@ object ShowDemoWindowPopups { if (selectable("Set to zero")) value = 0.0f if (selectable("Set to PI")) value = 3.1415f setNextItemWidth(-Float.MIN_VALUE) - dragFloat("##Value", ::value, 0.1f, 0f, 0f) + drag("##Value", ::value, 0.1f, 0f, 0f) } // We can also use OpenPopupOnItemClick() to toggle the visibility of a given popup. @@ -290,7 +290,7 @@ object ShowDemoWindowPopups { // Also demonstrate passing a bool* to BeginPopupModal(), this will create a regular close button which // will close the popup. Note that the visibility state of popups is owned by imgui, so the input value // of the bool actually doesn't matter here. - val unusedOpen = true.asMutableProperty + val unusedOpen = true.mutableReference if (beginPopupModal("Stacked 2", unusedOpen)) { text("Hello from Stacked The Second!") button("Close") { closeCurrentPopup() } diff --git a/core/src/main/kotlin/imgui/demo/ShowDemoWindowTables.kt b/core/src/main/kotlin/imgui/demo/ShowDemoWindowTables.kt index 9d91411e8..11b1a0c14 100644 --- a/core/src/main/kotlin/imgui/demo/ShowDemoWindowTables.kt +++ b/core/src/main/kotlin/imgui/demo/ShowDemoWindowTables.kt @@ -16,9 +16,7 @@ import imgui.ImGui.closeCurrentPopup import imgui.ImGui.collapsingHeader import imgui.ImGui.combo import imgui.ImGui.contentRegionAvail -import imgui.ImGui.dragFloat -import imgui.ImGui.dragInt -import imgui.ImGui.dragVec2 +import imgui.ImGui.drag2 import imgui.ImGui.endDisabled import imgui.ImGui.endTable import imgui.ImGui.indent @@ -41,8 +39,7 @@ import imgui.ImGui.selectable import imgui.ImGui.separator import imgui.ImGui.setNextItemOpen import imgui.ImGui.setNextItemWidth -import imgui.ImGui.sliderFloat -import imgui.ImGui.sliderVec2 +import imgui.ImGui.slider2 import imgui.ImGui.smallButton import imgui.ImGui.spacing import imgui.ImGui.style @@ -68,6 +65,8 @@ import imgui.ImGui.treeNodeEx import imgui.ImGui.treePop import imgui.ImGui.unindent import imgui.api.demoDebugInformations.Companion.helpMarker +import imgui.api.drag +import imgui.api.slider import imgui.classes.DrawList import imgui.classes.ListClipper import imgui.classes.TableSortSpecs @@ -737,7 +736,7 @@ object ShowDemoWindowTables { checkboxFlags("ImGuiTableFlags_RowBg", ::flags2, Tf.RowBg) checkboxFlags("ImGuiTableFlags_Resizable", ::flags2, Tf.Resizable) checkbox("show_widget_frame_bg", ::showWidgetFrameBg) - sliderVec2("CellPadding", cellPadding, 0f, 10f, "%.0f") + slider2("CellPadding", cellPadding, 0f, 10f, "%.0f") } pushStyleVar(StyleVar.CellPadding, cellPadding) @@ -816,14 +815,15 @@ object ShowDemoWindowTables { val f = flagArrayOf(flags) editTableSizingFlags(f, 0) flags = f[0] - _i32 = contentsType1.ordinal - combo("Contents", ::_i32, "Show width\u0000Short Text\u0000Long Text\u0000Button\u0000Fill Button\u0000InputText\u0000") - contentsType1 = ContentsType.values()[_i32] + val ordinalRef = contentsType1.ordinal.mutableReference + val ordinal by ordinalRef + combo("Contents", ordinalRef, "Show width\u0000Short Text\u0000Long Text\u0000Button\u0000Fill Button\u0000InputText\u0000") + contentsType1 = ContentsType.values()[ordinal] if (contentsType1 == ContentsType.FillButton) { sameLine() helpMarker("Be mindful that using right-alignment (e.g. size.x = -FLT_MIN) creates a feedback loop where contents width can feed into auto-column width can feed into contents width.") } - dragInt("Columns", ::columnCount, 0.1f, 1, 64, "%d", SliderFlag.AlwaysClamp) + drag("Columns", ::columnCount, 0.1f, 1, 64, "%d", SliderFlag.AlwaysClamp) checkboxFlags("ImGuiTableFlags_Resizable", ::flags, Tf.Resizable) checkboxFlags("ImGuiTableFlags_PreciseWidths", ::flags, Tf.PreciseWidths) sameLine(); helpMarker("Disable distributing remainder width to stretched columns (width allocation on a 100-wide table with 3 columns: Without this flag: 33,33,34. With this flag: 33,33,33). With larger number of columns, resizing will appear to be less smooth.") @@ -915,9 +915,9 @@ object ShowDemoWindowTables { checkboxFlags("ImGuiTableFlags_ScrollX", ::flags, Tf.ScrollX) checkboxFlags("ImGuiTableFlags_ScrollY", ::flags, Tf.ScrollY) setNextItemWidth(ImGui.frameHeight) - dragInt("freeze_cols", ::freezeCols, 0.2f, 0, 9, null, SliderFlag.NoInput) + drag("freeze_cols", ::freezeCols, 0.2f, 0, 9, null, SliderFlag.NoInput) setNextItemWidth(ImGui.frameHeight) - dragInt("freeze_rows", ::freezeRows, 0.2f, 0, 9, null, SliderFlag.NoInput) + drag("freeze_rows", ::freezeRows, 0.2f, 0, 9, null, SliderFlag.NoInput) } // When using ScrollX or ScrollY we need to specify a size for our table container! @@ -963,7 +963,7 @@ object ShowDemoWindowTables { withID("flags3") { withItemWidth(TEXT_BASE_WIDTH * 30) { checkboxFlags("ImGuiTableFlags_ScrollX", ::flags11, Tf.ScrollX) - dragFloat("inner_width", ::innerWidth0, 1f, 0f, Float.MAX_VALUE, "%.1f") + drag("inner_width", ::innerWidth0, 1f, 0f, Float.MAX_VALUE, "%.1f") } } } @@ -1286,11 +1286,11 @@ object ShowDemoWindowTables { // Draw our contents withID(row) { tableSetColumnIndex(0) - sliderFloat("float0", ::dummyF, 0f, 1f) + slider("float0", ::dummyF, 0f, 1f) tableSetColumnIndex(1) - sliderFloat("float1", ::dummyF, 0f, 1f) + slider("float1", ::dummyF, 0f, 1f) tableSetColumnIndex(2) - sliderFloat("##float2", ::dummyF, 0f, 1f) // No visible label since right-aligned + slider("##float2", ::dummyF, 0f, 1f) // No visible label since right-aligned } } } @@ -1609,11 +1609,11 @@ object ShowDemoWindowTables { checkboxFlags("ImGuiTableFlags_ScrollX", ::flags, Tf.ScrollX) sameLine() setNextItemWidth(ImGui.frameHeight) - dragInt("freeze_cols", ::freezeCols, 0.2f, 0, 9, null, SliderFlag.NoInput) + drag("freeze_cols", ::freezeCols, 0.2f, 0, 9, null, SliderFlag.NoInput) checkboxFlags("ImGuiTableFlags_ScrollY", ::flags, Tf.ScrollY) sameLine() setNextItemWidth(ImGui.frameHeight) - dragInt("freeze_rows", ::freezeRows, 0.2f, 0, 9, null, SliderFlag.NoInput) + drag("freeze_rows", ::freezeRows, 0.2f, 0, 9, null, SliderFlag.NoInput) } treeNodeEx("Sorting:", Tnf.DefaultOpen) { @@ -1627,7 +1627,7 @@ object ShowDemoWindowTables { checkbox("show_headers", ::showHeaders) checkbox("show_wrapped_text", ::showWrappedText) - dragVec2("##OuterSize", outerSizeValue) + drag2("##OuterSize", outerSizeValue) sameLine(0f, style.itemInnerSpacing.x) checkbox("outer_size", ::outerSizeEnabled) sameLine() @@ -1640,12 +1640,12 @@ object ShowDemoWindowTables { // From a user point of view we will tend to use 'inner_width' differently depending on whether our table is embedding scrolling. // toying with this demo we will actually pass 0.0f to the BeginTable() when ScrollX is disabled. - dragFloat("inner_width (when ScrollX active)", ::innerWidthWithScroll, 1f, 0f, Float.MAX_VALUE) + drag("inner_width (when ScrollX active)", ::innerWidthWithScroll, 1f, 0f, Float.MAX_VALUE) - dragFloat("row_min_height", ::rowMinHeight, 1f, 0f, Float.MAX_VALUE) + drag("row_min_height", ::rowMinHeight, 1f, 0f, Float.MAX_VALUE) sameLine(); helpMarker("Specify height of the Selectable item.") - dragInt("items_count", ::itemsCount, 0.1f, 0, 9999) + drag("items_count", ::itemsCount, 0.1f, 0, 9999) combo("items_type (first column)", ::contentsType, contentsTypeNames) //filter.Draw("filter"); } diff --git a/core/src/main/kotlin/imgui/demo/ShowDemoWindowWidgets.kt b/core/src/main/kotlin/imgui/demo/ShowDemoWindowWidgets.kt index 5090ea33b..8d1fb806e 100644 --- a/core/src/main/kotlin/imgui/demo/ShowDemoWindowWidgets.kt +++ b/core/src/main/kotlin/imgui/demo/ShowDemoWindowWidgets.kt @@ -35,18 +35,10 @@ import imgui.ImGui.colorPicker4 import imgui.ImGui.combo import imgui.ImGui.cursorPos import imgui.ImGui.cursorScreenPos -import imgui.ImGui.dragFloat -import imgui.ImGui.dragFloat2 -import imgui.ImGui.dragFloat3 -import imgui.ImGui.dragFloat4 -import imgui.ImGui.dragFloatRange2 -import imgui.ImGui.dragInt -import imgui.ImGui.dragInt2 -import imgui.ImGui.dragInt3 -import imgui.ImGui.dragInt4 -import imgui.ImGui.dragIntRange2 -import imgui.ImGui.dragScalar -import imgui.ImGui.dragVec4 +import imgui.ImGui.drag2 +import imgui.ImGui.drag3 +import imgui.ImGui.drag4 +import imgui.ImGui.dragRange import imgui.ImGui.end import imgui.ImGui.endChild import imgui.ImGui.endCombo @@ -62,16 +54,9 @@ import imgui.ImGui.getMouseDragDelta import imgui.ImGui.image import imgui.ImGui.imageButton import imgui.ImGui.indent -import imgui.ImGui.inputDouble -import imgui.ImGui.inputFloat -import imgui.ImGui.inputFloat2 -import imgui.ImGui.inputFloat3 -import imgui.ImGui.inputFloat4 -import imgui.ImGui.inputInt -import imgui.ImGui.inputInt2 -import imgui.ImGui.inputInt3 -import imgui.ImGui.inputInt4 -import imgui.ImGui.inputScalar +import imgui.ImGui.input2 +import imgui.ImGui.input3 +import imgui.ImGui.input4 import imgui.ImGui.inputText import imgui.ImGui.inputTextMultiline import imgui.ImGui.inputTextWithHint @@ -117,16 +102,10 @@ import imgui.ImGui.setItemDefaultFocus import imgui.ImGui.setNextItemOpen import imgui.ImGui.setNextItemWidth import imgui.ImGui.setTooltip +import imgui.ImGui.slider2 +import imgui.ImGui.slider3 +import imgui.ImGui.slider4 import imgui.ImGui.sliderAngle -import imgui.ImGui.sliderFloat -import imgui.ImGui.sliderFloat2 -import imgui.ImGui.sliderFloat3 -import imgui.ImGui.sliderFloat4 -import imgui.ImGui.sliderInt -import imgui.ImGui.sliderInt2 -import imgui.ImGui.sliderInt3 -import imgui.ImGui.sliderInt4 -import imgui.ImGui.sliderScalar import imgui.ImGui.smallButton import imgui.ImGui.spacing import imgui.ImGui.style @@ -145,9 +124,8 @@ import imgui.ImGui.treeNodeEx import imgui.ImGui.treeNodeToLabelSpacing import imgui.ImGui.treePop import imgui.ImGui.unindent -import imgui.ImGui.vSliderFloat -import imgui.ImGui.vSliderInt import imgui.ImGui.windowDrawList +import imgui.api.* import imgui.api.demoDebugInformations.Companion.helpMarker import imgui.classes.Color import imgui.classes.InputTextCallbackData @@ -283,7 +261,7 @@ object ShowDemoWindowWidgets { var f0 = 0.001f var f1 = 1e10f var d0 = 999999.00000001 - val vec4 = floatArrayOf(0.1f, 0.2f, 0.3f, 0.44f) + val vec4 = Vec4(0.1f, 0.2f, 0.3f, 0.44f) var i1 = 50 var i2 = 42 var f2 = 1f @@ -296,8 +274,8 @@ object ShowDemoWindowWidgets { enum class Element { Fire, Earth, Air, Water } var elem = Element.Fire.ordinal - val col1 = floatArrayOf(1f, 0f, 0.2f) - val col2 = floatArrayOf(0.4f, 0.7f, 0f, 0.5f) + val col1 = Vec3(1f, 0f, 0.2f) + val col2 = Vec4(0.4f, 0.7f, 0f, 0.5f) var itemCurrent = 1 operator fun invoke() { @@ -374,36 +352,36 @@ object ShowDemoWindowWidgets { inputTextWithHint("input text (w/ hint)", "enter text here", str1) - inputInt("input int", ::i0) + input("input int", ::i0) - inputFloat("input float", ::f0, 0.01f, 1f, "%.3f") + input("input float", ::f0, 0.01f, 1f, "%.3f") - inputDouble("input double", ::d0, 0.01, 1.0, "%.8f") + input("input double", ::d0, 0.01, 1.0, "%.8f") - inputFloat("input scientific", ::f1, 0f, 0f, "%e") + input("input scientific", ::f1, 0f, 0f, "%e") sameLine(); helpMarker(""" You can input value using the scientific notation, e.g. \"1e+8\" becomes \"100000000\".""".trimIndent()) - inputFloat3("input float3", vec4) + input3("input float3", vec4) } run { - dragInt("drag int", ::i1, 1f) + drag("drag int", ::i1, 1f) sameLine(); helpMarker(""" Click and drag to edit value. Hold SHIFT/ALT for faster/slower edit. Double-click or CTRL+click to input value.""".trimIndent()) - dragInt("drag int 0..100", ::i2, 1f, 0, 100, "%d%%", SliderFlag.AlwaysClamp) + drag("drag int 0..100", ::i2, 1f, 0, 100, "%d%%", SliderFlag.AlwaysClamp) - dragFloat("drag float", ::f2, 0.005f) - dragFloat("drag small float", ::f3, 0.0001f, 0f, 0f, "%.06f ns") + drag("drag float", ::f2, 0.005f) + drag("drag small float", ::f3, 0.0001f, 0f, 0f, "%.06f ns") } run { - sliderInt("slider int", ::i3, -1, 3) + slider("slider int", ::i3, -1, 3) sameLine(); helpMarker("CTRL+click to input value.") - sliderFloat("slider float", ::f4, 0f, 1f, "ratio = %.3f") + slider("slider float", ::f4, 0f, 1f, "ratio = %.3f") // TODO // sliderFloat("slider float (curve)", ::f5, -10f, 10f, "%.4f", 2f) @@ -413,7 +391,7 @@ object ShowDemoWindowWidgets { // Here we completely omit '%d' from the format string, so it'll only display a name. // This technique can also be used with DragInt(). val elemName = Element.values().getOrNull(elem)?.name ?: "Unknown" - sliderInt("slider enum", ::elem, 0, Element.values().lastIndex, elemName) + slider("slider enum", ::elem, 0, Element.values().lastIndex, elemName) sameLine(); helpMarker("Using the format string parameter to display a name instead of the underlying integer.") } @@ -602,7 +580,7 @@ object ShowDemoWindowWidgets { "for text wrapping follows simple rules suitable for English and possibly other languages.") spacing() - sliderFloat("Wrap width", ::wrapWidth, -20f, 600f, "%.0f") + slider("Wrap width", ::wrapWidth, -20f, 600f, "%.0f") val drawList = windowDrawList for (n in 0..1) { @@ -1166,17 +1144,14 @@ object ShowDemoWindowWidgets { // Submit our regular tabs var n = 0 while (n < activeTabs.size) { - var open = true + val openRef = true.mutableReference + val open by openRef val name = "%04d".format(activeTabs[n]) - _b = open - tabItem(name, ::_b) { + tabItem(name, openRef) { text("This is the $name tab!") } - open = _b - if (!open) - activeTabs.clear() - else - n++ + if (!open) activeTabs.clear() + else n++ } } separator() @@ -1237,10 +1212,13 @@ object ShowDemoWindowWidgets { separator() withItemWidth(fontSize * 8) { combo("func", ::funcType, "Sin\u0000Saw\u0000") } sameLine() - sliderInt("Sample count", ::displayCount, 1, 400) - val func = if (funcType == 0) Funcs3::sin else Funcs3::saw - plotLines("Lines", func, displayCount, 0, "", -1f, 1f, Vec2(0, 80)) - plotHistogram("Histogram", func, displayCount, 0, "", -1f, 1f, Vec2(0, 80)) + slider("Sample count", ::displayCount, 1, 400) + plotLines("Lines", displayCount, 0, "", -1f, 1f, Vec2(0, 80)) { + if (funcType == 0) Funcs3.sin(it) else Funcs3.saw(it) + } + plotHistogram("Histogram", displayCount, 0, "", -1f, 1f, Vec2(0, 80)) { + if (funcType == 0) Funcs3.sin(it) else Funcs3.saw(it) + } separator() // Animate a simple progress bar @@ -1312,7 +1290,7 @@ object ShowDemoWindowWidgets { text("Color button with Custom Picker Popup:") if (savedPaletteInit) savedPalette.forEachIndexed { n, c -> - colorConvertHSVtoRGB(n / 31f, 0.8f, 0.8f, c::x, c::y, c::z) + colorConvertHSVtoRGB(n / 31f, 0.8f, 0.8f, c) savedPalette[n].w = 1f // Alpha } savedPaletteInit = false @@ -1425,7 +1403,7 @@ object ShowDemoWindowWidgets { text("Color widget with InputHSV:") colorEdit4("HSV shown as RGB##1", colorHsv, Cef.DisplayRGB or Cef.InputHSV or Cef.Float) colorEdit4("HSV shown as HSV##1", colorHsv, Cef.DisplayHSV or Cef.InputHSV or Cef.Float) - dragVec4("Raw HSV values", colorHsv, 0.01f, 0f, 1f) + drag4("Raw HSV values", colorHsv, 0.01f, 0f, 1f) } } } @@ -1450,16 +1428,16 @@ object ShowDemoWindowWidgets { // Drags text("Underlying float value: %f", dragF) - dragFloat("DragFloat (0 -> 1)", ::dragF, 0.005f, 0f, 1f, "%.3f", flags) - dragFloat("DragFloat (0 -> +inf)", ::dragF, 0.005f, 0f, Float.MAX_VALUE, "%.3f", flags) - dragFloat("DragFloat (-inf -> 1)", ::dragF, 0.005f, -Float.MAX_VALUE, 1f, "%.3f", flags) - dragFloat("DragFloat (-inf -> +inf)", ::dragF, 0.005f, -Float.MAX_VALUE, +Float.MAX_VALUE, "%.3f", flags) - dragInt("DragInt (0 -> 100)", ::dragI, 0.5f, 0, 100, "%d", flags) + drag("DragFloat (0 -> 1)", ::dragF, 0.005f, 0f, 1f, "%.3f", flags) + drag("DragFloat (0 -> +inf)", ::dragF, 0.005f, 0f, Float.MAX_VALUE, "%.3f", flags) + drag("DragFloat (-inf -> 1)", ::dragF, 0.005f, -Float.MAX_VALUE, 1f, "%.3f", flags) + drag("DragFloat (-inf -> +inf)", ::dragF, 0.005f, -Float.MAX_VALUE, +Float.MAX_VALUE, "%.3f", flags) + drag("DragInt (0 -> 100)", ::dragI, 0.5f, 0, 100, "%d", flags) // Sliders text("Underlying float value: %f", sliderF) - sliderFloat("SliderFloat (0 -> 1)", ::sliderF, 0f, 1f, "%.3f", flags) - sliderInt("SliderInt (0 -> 100)", ::sliderI, 0, 100, "%d", flags) + slider("SliderFloat (0 -> 1)", ::sliderF, 0f, 1f, "%.3f", flags) + slider("SliderInt (0 -> 100)", ::sliderI, 0, 100, "%d", flags) } } } @@ -1471,9 +1449,9 @@ object ShowDemoWindowWidgets { var endI = 1000 operator fun invoke() { treeNode("Range Widgets") { - dragFloatRange2("range float", ::begin, ::end, 0.25f, 0f, 100f, "Min: %.1f %%", "Max: %.1f %%", SliderFlag.AlwaysClamp) - dragIntRange2("range int", ::beginI, ::endI, 5f, 0, 1000, "Min: %d units", "Max: %d units") - dragIntRange2("range int (no bounds)", ::beginI, ::endI, 5f, 0, 0, "Min: %d units", "Max: %d units") + dragRange("range float", ::begin, ::end, 0.25f, 0f, 100f, "Min: %.1f %%", "Max: %.1f %%", SliderFlag.AlwaysClamp) + dragRange("range int", ::beginI, ::endI, 5f, 0, 1000, "Min: %d units", "Max: %d units") + dragRange("range int (no bounds)", ::beginI, ::endI, 5f, 0, 0, "Min: %d units", "Max: %d units") } } } @@ -1512,62 +1490,62 @@ object ShowDemoWindowWidgets { // Note: SliderScalar() functions have a maximum usable range of half the natural type maximum, hence the /2. // @formatter:off - var s8_zero: Byte = 0.b - var s8_one: Byte = 1.b - var s8_fifty: Byte = 50.b - var s8_min: Byte = (-128).b - var s8_max: Byte = 127.b - var u8_zero: Ubyte = Ubyte(0) - var u8_one: Ubyte = Ubyte(1) - var u8_fifty: Ubyte = Ubyte(50) - var u8_min: Ubyte = Ubyte(0) - var u8_max: Ubyte = Ubyte(255) - var s16_zero: Short = 0.s - var s16_one: Short = 1.s - var s16_fifty: Short = 50.s - var s16_min: Short = (-32768).s - var s16_max: Short = 32767.s - var u16_zero: Ushort = Ushort(0) - var u16_one = Ushort(1) - var u16_fifty: Ushort = Ushort(50) - var u16_min: Ushort = Ushort(0) - var u16_max: Ushort = Ushort(65535) - var s32_zero: Int = 0 - var s32_one: Int = 1 - var s32_fifty: Int = 50 - var s32_min: Int = Int.MIN_VALUE / 2 - var s32_max: Int = Int.MAX_VALUE / 2 - var s32_hi_a = Int.MAX_VALUE / 2 - 100 - var s32_hi_b = Int.MAX_VALUE / 2 - var u32_zero: Uint = Uint(0) - var u32_one: Uint = Uint(1) - var u32_fifty: Uint = Uint(50) - var u32_min: Uint = Uint(0) - var u32_max: Uint = Uint.MAX / 2 - var u32_hi_a = Uint.MAX / 2 - 100 - var u32_hi_b: Uint = Uint.MAX / 2 - var s64_zero: Long = 0L - var s64_one: Long = 1L - var s64_fifty: Long = 50L - var s64_min: Long = Long.MIN_VALUE / 2 - var s64_max: Long = Long.MAX_VALUE / 2 - var s64_hi_a: Long = Long.MAX_VALUE / 2 - 100 - var s64_hi_b: Long = Long.MAX_VALUE / 2 - var u64_zero: Ulong = Ulong(0) - var u64_one: Ulong = Ulong(1) - var u64_fifty: Ulong = Ulong(50) - var u64_min: Ulong = Ulong(0) - var u64_max: Ulong = Ulong.MAX / 2 - var u64_hi_a: Ulong = Ulong.MAX / 2 - 100 - var u64_hi_b: Ulong = Ulong.MAX / 2 - var f32_zero: Float = 0f - var f32_one: Float = 1f - var f32_lo_a: Float = -10_000_000_000f - var f32_hi_a: Float = +10_000_000_000f - var f64_zero: Double = 0.0 - var f64_one: Double = 1.0 - var f64_lo_a: Double = -1_000_000_000_000_000.0 - var f64_hi_a: Double = +1_000_000_000_000_000.0 + val s8_zero: Byte = 0.b + val s8_one: Byte = 1.b + val s8_fifty: Byte = 50.b + val s8_min: Byte = (-128).b + val s8_max: Byte = 127.b + val u8_zero: Ubyte = Ubyte(0) + val u8_one: Ubyte = Ubyte(1) + val u8_fifty: Ubyte = Ubyte(50) + val u8_min: Ubyte = Ubyte(0) + val u8_max: Ubyte = Ubyte(255) + val s16_zero: Short = 0.s + val s16_one: Short = 1.s + val s16_fifty: Short = 50.s + val s16_min: Short = (-32768).s + val s16_max: Short = 32767.s + val u16_zero: Ushort = Ushort(0) + val u16_one = Ushort(1) + val u16_fifty: Ushort = Ushort(50) + val u16_min: Ushort = Ushort(0) + val u16_max: Ushort = Ushort(65535) + val s32_zero: Int = 0 + val s32_one: Int = 1 + val s32_fifty: Int = 50 + val s32_min: Int = Int.MIN_VALUE / 2 + val s32_max: Int = Int.MAX_VALUE / 2 + val s32_hi_a = Int.MAX_VALUE / 2 - 100 + val s32_hi_b = Int.MAX_VALUE / 2 + val u32_zero: Uint = Uint(0) + val u32_one: Uint = Uint(1) + val u32_fifty: Uint = Uint(50) + val u32_min: Uint = Uint(0) + val u32_max: Uint = Uint.MAX / 2 + val u32_hi_a = Uint.MAX / 2 - 100 + val u32_hi_b: Uint = Uint.MAX / 2 + val s64_zero: Long = 0L + val s64_one: Long = 1L + val s64_fifty: Long = 50L + val s64_min: Long = Long.MIN_VALUE / 2 + val s64_max: Long = Long.MAX_VALUE / 2 + val s64_hi_a: Long = Long.MAX_VALUE / 2 - 100 + val s64_hi_b: Long = Long.MAX_VALUE / 2 + val u64_zero: Ulong = Ulong(0) + val u64_one: Ulong = Ulong(1) + val u64_fifty: Ulong = Ulong(50) + val u64_min: Ulong = Ulong(0) + val u64_max: Ulong = Ulong.MAX / 2 + val u64_hi_a: Ulong = Ulong.MAX / 2 - 100 + val u64_hi_b: Ulong = Ulong.MAX / 2 + val f32_zero: Float = 0f + val f32_one: Float = 1f + val f32_lo_a: Float = -10_000_000_000f + val f32_hi_a: Float = +10_000_000_000f + val f64_zero: Double = 0.0 + val f64_one: Double = 1.0 + val f64_lo_a: Double = -1_000_000_000_000_000.0 + val f64_hi_a: Double = +1_000_000_000_000_000.0 operator fun invoke() { treeNode("Data Types") { @@ -1577,67 +1555,67 @@ object ShowDemoWindowWidgets { sameLine(); helpMarker( """As with every widget in dear imgui, we never modify values unless there is a user interaction. You can override the clamping limits by using CTRL+Click to input a value.""".trimIndent()) - dragScalar("drag s8", DataType.Byte, ::s8_v, dragSpeed, ::s8_zero.takeIf { dragClamp }, ::s8_fifty.takeIf { dragClamp }) - dragScalar("drag u8", DataType.Ubyte, ::u8_v, dragSpeed, ::u8_zero.takeIf { dragClamp }, ::u8_fifty.takeIf { dragClamp }, "%d ms") - dragScalar("drag s16", DataType.Short, ::s16_v, dragSpeed, ::s16_zero.takeIf { dragClamp }, ::s16_fifty.takeIf { dragClamp }) - dragScalar("drag u16", DataType.Ushort, ::u16_v, dragSpeed, ::u16_zero.takeIf { dragClamp }, ::u16_fifty.takeIf { dragClamp }, "%d ms") - dragScalar("drag s32", DataType.Int, ::s32_v, dragSpeed, ::s32_zero.takeIf { dragClamp }, ::s32_fifty.takeIf { dragClamp }) - dragScalar("drag s32 hex", DataType.Int, ::s32_v, dragSpeed, ::s32_zero.takeIf { dragClamp }, ::s32_fifty.takeIf { dragClamp }, "0x%08X") - dragScalar("drag u32", DataType.Uint, ::u32_v, dragSpeed, ::u32_zero.takeIf { dragClamp }, ::u32_fifty.takeIf { dragClamp }, "%d ms") - dragScalar("drag s64", DataType.Long, ::s64_v, dragSpeed, ::s64_zero.takeIf { dragClamp }, ::s64_fifty.takeIf { dragClamp }) - dragScalar("drag u64", DataType.Ulong, ::u64_v, dragSpeed, ::u64_zero.takeIf { dragClamp }, ::u64_fifty.takeIf { dragClamp }) - dragScalar("drag float", DataType.Float, ::f32_v, 0.005f, ::f32_zero, ::f32_one, "%f") - dragScalar("drag float log", DataType.Float, ::f32_v, 0.005f, ::f32_zero, ::f32_one, "%f", SliderFlag.Logarithmic) - dragScalar("drag double", DataType.Double, ::f64_v, 0.0005f, ::f64_zero, null, "%.10f grams") - dragScalar("drag double log", DataType.Double, ::f64_v, 0.0005f, ::f64_zero, ::f64_one, "0 < %.10f < 1", SliderFlag.Logarithmic) + drag("drag s8", ::s8_v, dragSpeed, s8_zero.takeIf { dragClamp }, s8_fifty.takeIf { dragClamp }) + drag("drag u8", ::u8_v, dragSpeed, u8_zero.takeIf { dragClamp }, u8_fifty.takeIf { dragClamp }, "%d ms") + drag("drag s16", ::s16_v, dragSpeed, s16_zero.takeIf { dragClamp }, s16_fifty.takeIf { dragClamp }) + drag("drag u16", ::u16_v, dragSpeed, u16_zero.takeIf { dragClamp }, u16_fifty.takeIf { dragClamp }, "%d ms") + drag("drag s32", ::s32_v, dragSpeed, s32_zero.takeIf { dragClamp }, s32_fifty.takeIf { dragClamp }) + drag("drag s32 hex", ::s32_v, dragSpeed, s32_zero.takeIf { dragClamp }, s32_fifty.takeIf { dragClamp }, "0x%08X") + drag("drag u32", ::u32_v, dragSpeed, u32_zero.takeIf { dragClamp }, u32_fifty.takeIf { dragClamp }, "%d ms") + drag("drag s64", ::s64_v, dragSpeed, s64_zero.takeIf { dragClamp }, s64_fifty.takeIf { dragClamp }) + drag("drag u64", ::u64_v, dragSpeed, u64_zero.takeIf { dragClamp }, u64_fifty.takeIf { dragClamp }) + drag("drag float", ::f32_v, 0.005f, f32_zero, f32_one, "%f") + drag("drag float log", ::f32_v, 0.005f, f32_zero, f32_one, "%f", SliderFlag.Logarithmic) + drag("drag double", ::f64_v, 0.0005f, f64_zero, null, "%.10f grams") + drag("drag double log", ::f64_v, 0.0005f, f64_zero, f64_one, "0 < %.10f < 1", SliderFlag.Logarithmic) text("Sliders") - sliderScalar("slider s8 full", DataType.Byte, ::s8_v, ::s8_min, ::s8_max, "%d") - sliderScalar("slider u8 full", DataType.Ubyte, ::u8_v, ::u8_min, ::u8_max, "%d") - sliderScalar("slider s16 full", DataType.Short, ::s16_v, ::s16_min, ::s16_max, "%d") - sliderScalar("slider u16 full", DataType.Ushort, ::u16_v, ::u16_min, ::u16_max, "%d") - sliderScalar("slider s32 low", DataType.Int, ::s32_v, ::s32_zero, ::s32_fifty, "%d") - sliderScalar("slider s32 high", DataType.Int, ::s32_v, ::s32_hi_a, ::s32_hi_b, "%d") - sliderScalar("slider s32 full", DataType.Int, ::s32_v, ::s32_min, ::s32_max, "%d") - sliderScalar("slider s32 hex", DataType.Int, ::s32_v, ::s32_zero, ::s32_fifty, "0x%04X") - sliderScalar("slider u32 low", DataType.Uint, ::u32_v, ::u32_zero, ::u32_fifty, "%d") - sliderScalar("slider u32 high", DataType.Uint, ::u32_v, ::u32_hi_a, ::u32_hi_b, "%d") - sliderScalar("slider u32 full", DataType.Uint, ::u32_v, ::u32_min, ::u32_max, "%d") - sliderScalar("slider s64 low", DataType.Long, ::s64_v, ::s64_zero, ::s64_fifty, "%d") - sliderScalar("slider s64 high", DataType.Long, ::s64_v, ::s64_hi_a, ::s64_hi_b, "%d") - sliderScalar("slider s64 full", DataType.Long, ::s64_v, ::s64_min, ::s64_max, "%d") - sliderScalar("slider u64 low", DataType.Ulong, ::u64_v, ::u64_zero, ::u64_fifty, "%d ms") - sliderScalar("slider u64 high", DataType.Ulong, ::u64_v, ::u64_hi_a, ::u64_hi_b, "%d ms") - sliderScalar("slider u64 full", DataType.Ulong, ::u64_v, ::u64_min, ::u64_max, "%d ms") - sliderScalar("slider float low", DataType.Float, ::f32_v, ::f32_zero, ::f32_one) - sliderScalar("slider float low log", DataType.Float, ::f32_v, ::f32_zero, ::f32_one, "%.10f", SliderFlag.Logarithmic) - sliderScalar("slider float high", DataType.Float, ::f32_v, ::f32_lo_a, ::f32_hi_a, "%e") - sliderScalar("slider double low", DataType.Double, ::f64_v, ::f64_zero, ::f64_one, "%.10f grams") - sliderScalar("slider double low log", DataType.Double, ::f64_v, ::f64_zero, ::f64_one, "%.10f", SliderFlag.Logarithmic) - sliderScalar("slider double high", DataType.Double, ::f64_v, ::f64_lo_a, ::f64_hi_a, "%e grams") + slider("slider s8 full", ::s8_v, s8_min, s8_max, "%d") + slider("slider u8 full", ::u8_v, u8_min, u8_max, "%d") + slider("slider s16 full", ::s16_v, s16_min, s16_max, "%d") + slider("slider u16 full", ::u16_v, u16_min, u16_max, "%d") + slider("slider s32 low", ::s32_v, s32_zero, s32_fifty, "%d") + slider("slider s32 high", ::s32_v, s32_hi_a, s32_hi_b, "%d") + slider("slider s32 full", ::s32_v, s32_min, s32_max, "%d") + slider("slider s32 hex", ::s32_v, s32_zero, s32_fifty, "0x%04X") + slider("slider u32 low", ::u32_v, u32_zero, u32_fifty, "%d") + slider("slider u32 high", ::u32_v, u32_hi_a, u32_hi_b, "%d") + slider("slider u32 full", ::u32_v, u32_min, u32_max, "%d") + slider("slider s64 low", ::s64_v, s64_zero, s64_fifty, "%d") + slider("slider s64 high", ::s64_v, s64_hi_a, s64_hi_b, "%d") + slider("slider s64 full", ::s64_v, s64_min, s64_max, "%d") + slider("slider u64 low", ::u64_v, u64_zero, u64_fifty, "%d ms") + slider("slider u64 high", ::u64_v, u64_hi_a, u64_hi_b, "%d ms") + slider("slider u64 full",::u64_v, u64_min, u64_max, "%d ms") + slider("slider float low", ::f32_v, f32_zero, f32_one) + slider("slider float low log",::f32_v, f32_zero, f32_one, "%.10f", SliderFlag.Logarithmic) + slider("slider float high", ::f32_v, f32_lo_a, f32_hi_a, "%e") + slider("slider double low", ::f64_v, f64_zero, f64_one, "%.10f grams") + slider("slider double low log", ::f64_v, f64_zero, f64_one, "%.10f", SliderFlag.Logarithmic) + slider("slider double high", ::f64_v, f64_lo_a, f64_hi_a, "%e grams") text("Sliders (reverse)") - sliderScalar("slider s8 reverse", DataType.Byte, ::s8_v, ::s8_max, ::s8_min, "%d") - sliderScalar("slider u8 reverse", DataType.Ubyte, ::u8_v, ::u8_max, ::u8_min, "%d") // [JVM] %u -> %d - sliderScalar("slider s32 reverse", DataType.Int, ::s32_v, ::s32_fifty, ::s32_zero, "%d") - sliderScalar("slider u32 reverse", DataType.Uint, ::u32_v, ::u32_fifty, ::u32_zero, "%s") // [JVM] %u -> %d - sliderScalar("slider s64 reverse", DataType.Long, ::s64_v, ::s64_fifty, ::s64_zero, "%d") // [JVM] %I64d -> %d - sliderScalar("slider u64 reverse", DataType.Ulong, ::u64_v, ::u64_fifty, ::u64_zero, "%d ms") // [JVM] %I64u -> %d + slider("slider s8 reverse", ::s8_v, s8_max, s8_min, "%d") + slider("slider u8 reverse", ::u8_v, u8_max, u8_min, "%d") // [JVM] %u -> %d + slider("slider s32 reverse", ::s32_v, s32_fifty, s32_zero, "%d") + slider("slider u32 reverse", ::u32_v, u32_fifty, u32_zero, "%s") // [JVM] %u -> %d + slider("slider s64 reverse", ::s64_v, s64_fifty, s64_zero, "%d") // [JVM] %I64d -> %d + slider("slider u64 reverse", ::u64_v, u64_fifty, u64_zero, "%d ms") // [JVM] %I64u -> %d text("Inputs") checkbox("Show step buttons", ::inputsStep) - inputScalar("input s8", DataType.Byte, ::s8_v, s8_one.takeIf { inputsStep }, null, "%d") - inputScalar("input u8", DataType.Ubyte, ::u8_v, u8_one.takeIf { inputsStep }, null, "%d") - inputScalar("input s16", DataType.Short, ::s16_v, s16_one.takeIf { inputsStep }, null, "%d") - inputScalar("input u16", DataType.Ushort, ::u16_v, u16_one.takeIf { inputsStep }, null, "%d") - inputScalar("input s32", DataType.Int, ::s32_v, s32_one.takeIf { inputsStep }, null, "%d") - inputScalar("input s32 hex", DataType.Int, ::s32_v, s32_one.takeIf { inputsStep }, null, "%04X") - inputScalar("input u32", DataType.Uint, ::u32_v, u32_one.takeIf { inputsStep }, null, "%d") - inputScalar("input u32 hex", DataType.Uint, ::u32_v, u32_one.takeIf { inputsStep }, null, "%04X") - inputScalar("input s64", DataType.Long, ::s64_v, s64_one.takeIf { inputsStep }) - inputScalar("input u64", DataType.Ulong, ::u64_v, u64_one.takeIf { inputsStep }) - inputScalar("input float", DataType.Float, ::f32_v, f32_one.takeIf { inputsStep }) - inputScalar("input double", DataType.Double, ::f64_v, f64_one.takeIf { inputsStep }) + input("input s8", ::s8_v, s8_one.takeIf { inputsStep }, null, "%d") + input("input u8", ::u8_v, u8_one.takeIf { inputsStep }, null, "%d") + input("input s16", ::s16_v, s16_one.takeIf { inputsStep }, null, "%d") + input("input u16", ::u16_v, u16_one.takeIf { inputsStep }, null, "%d") + input("input s32", ::s32_v, s32_one.takeIf { inputsStep }, null, "%d") + input("input s32 hex", ::s32_v, s32_one.takeIf { inputsStep }, null, "%04X") + input("input u32", ::u32_v, u32_one.takeIf { inputsStep }, null, "%d") + input("input u32 hex", ::u32_v, u32_one.takeIf { inputsStep }, null, "%04X") + input("input s64", ::s64_v, s64_one.takeIf { inputsStep }) + input("input u64", ::u64_v, u64_one.takeIf { inputsStep }) + input("input float", ::f32_v, f32_one.takeIf { inputsStep }) + input("input double", ::f64_v, f64_one.takeIf { inputsStep }) // @formatter:on } } @@ -1649,28 +1627,28 @@ object ShowDemoWindowWidgets { operator fun invoke() { treeNode("Multi-component Widgets") { - inputFloat2("input float2", vec4f) - dragFloat2("drag float2", vec4f, 0.01f, 0f, 1f) - sliderFloat2("slider float2", vec4f, 0f, 1f) - inputInt2("input int2", vec4i) - dragInt2("drag int2", vec4i, 1f, 0, 255) - sliderInt2("slider int2", vec4i, 0, 255) + input2("input float2", vec4f) + drag2("drag float2", vec4f, 0.01f, 0f, 1f) + slider2("slider float2", vec4f, 0f, 1f) + input2("input int2", vec4i) + drag2("drag int2", vec4i, 1f, 0, 255) + slider2("slider int2", vec4i, 0, 255) spacing() - inputFloat3("input float3", vec4f) - dragFloat3("drag float3", vec4f, 0.01f, 0.0f, 1.0f) - sliderFloat3("slider float3", vec4f, 0.0f, 1.0f) - inputInt3("input int3", vec4i) - dragInt3("drag int3", vec4i, 1f, 0, 255) - sliderInt3("slider int3", vec4i, 0, 255) + input3("input float3", vec4f) + drag3("drag float3", vec4f, 0.01f, 0.0f, 1.0f) + slider3("slider float3", vec4f, 0.0f, 1.0f) + input3("input int3", vec4i) + drag3("drag int3", vec4i, 1f, 0, 255) + slider3("slider int3", vec4i, 0, 255) spacing() - inputFloat4("input float4", vec4f) - dragFloat4("drag float4", vec4f, 0.01f, 0.0f, 1.0f) - sliderFloat4("slider float4", vec4f, 0.0f, 1.0f) - inputInt4("input int4", vec4i) - dragInt4("drag int4", vec4i, 1f, 0, 255) - sliderInt4("slider int4", vec4i, 0, 255) + input4("input float4", vec4f) + drag4("drag float4", vec4f, 0.01f, 0.0f, 1.0f) + slider4("slider float4", vec4f, 0.0f, 1.0f) + input4("input int4", vec4i) + drag4("drag int4", vec4i, 1f, 0, 255) + slider4("slider int4", vec4i, 0, 255) } } } @@ -1685,7 +1663,7 @@ object ShowDemoWindowWidgets { withStyleVar(StyleVar.ItemSpacing, Vec2(spacing)) { - vSliderInt("##int", Vec2(18, 160), ::intValue, 0, 5) + vSlider("##int", Vec2(18, 160), ::intValue, 0, 5) sameLine() withID("set1") { @@ -1698,7 +1676,7 @@ object ShowDemoWindowWidgets { Col.FrameBgActive, Color.hsv(i / 7f, 0.7f, 0.5f), Col.SliderGrab, Color.hsv(i / 7f, 0.9f, 0.9f)) { - withFloat(values, i) { vSliderFloat("##v", Vec2(18, 160), it, 0f, 1f, "") } + vSlider("##v", Vec2(18, 160), values mutablePropertyAt i, 0f, 1f, "") if (isItemActive || isItemHovered()) setTooltip("%.3f", values[i]) } } @@ -1714,9 +1692,7 @@ object ShowDemoWindowWidgets { group { for (ny in 0 until rows) { withID(nx * rows + ny) { - withFloat(values2, nx) { f -> - vSliderFloat("##v", smallSliderSize, f, 0f, 1f, "") - } + vSlider("##v", smallSliderSize, values2 mutablePropertyAt nx, 0f, 1f, "") if (isItemActive || isItemHovered()) setTooltip("%.3f", values2[nx]) } @@ -1731,9 +1707,7 @@ object ShowDemoWindowWidgets { if (i > 0) sameLine() withID(i) { withStyleVar(StyleVar.GrabMinSize, 40f) { - withFloat(values, i) { - vSliderFloat("##v", Vec2(40, 160), it, 0f, 1f, "%.2f\nsec") - } + vSlider("##v", Vec2(40, 160), values mutablePropertyAt i, 0f, 1f, "%.2f\nsec") } } } @@ -1744,16 +1718,13 @@ object ShowDemoWindowWidgets { } object `Drag and Drop` { - val col1 = floatArrayOf(1f, 0f, 0.2f) - val col2 = floatArrayOf(0.4f, 0.7f, 0f, 0.5f) + val col1 = Vec3(1f, 0f, 0.2f) + val col2 = Vec4(0.4f, 0.7f, 0f, 0.5f) enum class Mode { Copy, Move, Swap } var mode = Mode.Copy - val names = arrayOf( - "Bobby", "Beatrice", "Betty", - "Brianna", "Barry", "Bernard", - "Bibi", "Blaine", "Bryn") + val names = arrayOf("Bobby", "Beatrice", "Betty", "Brianna", "Barry", "Bernard", "Bibi", "Blaine", "Bryn") val itemNames = arrayOf("Item One", "Item Two", "Item Three", "Item Four", "Item Five") operator fun invoke() { treeNode("Drag and Drop") { @@ -1841,7 +1812,7 @@ object ShowDemoWindowWidgets { var itemType = 1 var itemDisabled = false var b0 = false - val col = floatArrayOf(1f, 0.5f, 0f, 1f) + val col = Vec4(1f, 0.5f, 0f, 1f) val str = ByteArray(16) var current1 = 1 var current2 = 1 @@ -1864,11 +1835,11 @@ object ShowDemoWindowWidgets { 1 -> button("ITEM: Button") // Testing button 2 -> withButtonRepeat(true) { button("ITEM: Button") } // Testing button (with repeater) 3 -> checkbox("ITEM: Checkbox", ::b0) // Testing checkbox - 4 -> sliderFloat("ITEM: SliderFloat", col, 0, 0f, 1f) // Testing basic item + 4 -> slider("ITEM: SliderFloat", col::x, 0f, 1f) // Testing basic item 5 -> inputText("ITEM: InputText", str) // Testing input text (which handles tabbing) 6 -> inputTextMultiline("ITEM: InputTextMultiline", str) // Testing input text (which uses a child window) - 7 -> inputFloat("ITEM: InputFloat", col, 1f) // Testing +/- buttons on scalar input - 8 -> inputFloat3("ITEM: InputFloat3", col) // Testing multi-component items (IsItemXXX flags are reported merged) + 7 -> input("ITEM: InputFloat", col::x, 1f) // Testing +/- buttons on scalar input + 8 -> input3("ITEM: InputFloat3", col) // Testing multi-component items (IsItemXXX flags are reported merged) 9 -> colorEdit4("ITEM: ColorEdit4", col) // Testing multi-component items (IsItemXXX flags are reported merged) 10 -> selectable("ITEM: Selectable") // Testing selectable item 11 -> menuItem("ITEM: MenuItem") // Testing menu item (they use ImGuiButtonFlags_PressedOnRelease button policy) diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/AutoResize.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/AutoResize.kt index 1aedee089..7872cf7af 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/AutoResize.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/AutoResize.kt @@ -2,7 +2,7 @@ package imgui.demo.showExampleApp import imgui.ImGui.begin import imgui.ImGui.end -import imgui.ImGui.sliderInt +import imgui.ImGui.slider import imgui.ImGui.text import kotlin.reflect.KMutableProperty0 import imgui.WindowFlag as Wf @@ -23,7 +23,7 @@ object AutoResize { Window will resize every-frame to the size of its content. Note that you probably don't want to query the window size to output your content because that would create a feedback loop.""".trimIndent()) - sliderInt("Number of lines", lines, 0, 1, 20) + slider("Number of lines", lines, 0, 1, 20) for (i in 0 until lines[0]) text(" ".repeat(i * 4) + "This is line $i") // Pad with space to extend size horizontally end() diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/ConstrainedResize.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/ConstrainedResize.kt index 8a2542ca0..1acb80f1b 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/ConstrainedResize.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/ConstrainedResize.kt @@ -11,7 +11,6 @@ import imgui.ImGui.button import imgui.ImGui.checkbox import imgui.ImGui.colorButton import imgui.ImGui.combo -import imgui.ImGui.dragInt import imgui.ImGui.end import imgui.ImGui.io import imgui.ImGui.popStyleVar @@ -21,6 +20,7 @@ import imgui.ImGui.setNextItemWidth import imgui.ImGui.setNextWindowSizeConstraints import imgui.ImGui.setWindowSize import imgui.ImGui.text +import imgui.api.drag import imgui.classes.SizeCallbackData import kotlin.reflect.KMutableProperty0 @@ -110,7 +110,7 @@ object ConstrainedResize { setNextItemWidth(ImGui.fontSize * 20) combo("Constraint", ::type, testDesc) setNextItemWidth(ImGui.fontSize * 20) - dragInt("Lines", ::displayLines, 0.2f, 1, 100) + drag("Lines", ::displayLines, 0.2f, 1, 100) checkbox("Auto-resize", ::autoResize) checkbox("Window padding", ::windowPadding) for (i in 0 until displayLines) diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/CustomRendering.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/CustomRendering.kt index f24d3b6c8..4a23814eb 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/CustomRendering.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/CustomRendering.kt @@ -11,7 +11,6 @@ import imgui.ImGui.calcItemWidth import imgui.ImGui.checkbox import imgui.ImGui.colorEdit4 import imgui.ImGui.cursorScreenPos -import imgui.ImGui.dragFloat import imgui.ImGui.dummy import imgui.ImGui.end import imgui.ImGui.endTabBar @@ -27,13 +26,14 @@ import imgui.ImGui.openPopupOnItemClick import imgui.ImGui.popItemWidth import imgui.ImGui.pushItemWidth import imgui.ImGui.sameLine -import imgui.ImGui.sliderInt import imgui.ImGui.style import imgui.ImGui.text import imgui.ImGui.windowDrawList import imgui.ImGui.windowPos import imgui.ImGui.windowSize import imgui.api.demoDebugInformations.Companion.helpMarker +import imgui.api.drag +import imgui.api.slider import imgui.dsl.menuItem import imgui.internal.sections.ButtonFlag import imgui.internal.sections.DrawFlag @@ -103,15 +103,15 @@ object CustomRendering { // Draw a bunch of primitives text("All primitives") - dragFloat("Size", ::sz, 0.2f, 2f, 100f, "%.0f") - dragFloat("Thickness", ::thickness, 0.05f, 1f, 8f, "%.02f") - sliderInt("N-gon sides", ::ngonSides, 3, 12) + drag("Size", ::sz, 0.2f, 2f, 100f, "%.0f") + drag("Thickness", ::thickness, 0.05f, 1f, 8f, "%.02f") + slider("N-gon sides", ::ngonSides, 3, 12) checkbox("##circlesegmentoverride", ::circleSegmentsOverride) sameLine(0f, style.itemInnerSpacing.x) - circleSegmentsOverride = sliderInt("Circle segments override", ::circleSegmentsOverrideV, 3, 40) or circleSegmentsOverride + circleSegmentsOverride = slider("Circle segments override", ::circleSegmentsOverrideV, 3, 40) or circleSegmentsOverride checkbox("##curvessegmentoverride", ::curveSegmentsOverride) sameLine(0f, style.itemInnerSpacing.x) - curveSegmentsOverride = sliderInt("Curves segments override", ::curveSegmentsOverrideV, 3, 40) or curveSegmentsOverride + curveSegmentsOverride = slider("Curves segments override", ::curveSegmentsOverrideV, 3, 40) or curveSegmentsOverride colorEdit4("Color", colf) val p = cursorScreenPos diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/MenuFile.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/MenuFile.kt index c9528011c..850012d30 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/MenuFile.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/MenuFile.kt @@ -8,14 +8,14 @@ import imgui.ImGui.combo import imgui.ImGui.cursorScreenPos import imgui.ImGui.dummy import imgui.ImGui.endChild -import imgui.ImGui.inputFloat +import imgui.ImGui.input import imgui.ImGui.menuItem import imgui.ImGui.sameLine import imgui.ImGui.separator -import imgui.ImGui.sliderFloat import imgui.ImGui.text import imgui.ImGui.textLineHeight import imgui.ImGui.windowDrawList +import imgui.api.slider import imgui.dsl.menu object MenuFile { @@ -49,11 +49,10 @@ object MenuFile { menu("Options") { menuItem("Enabled", "", ::enabled) beginChild("child", Vec2(0, 60), true) - for (i in 0 until 10) - text("Scrolling Text $i") + for (i in 0 until 10) text("Scrolling Text $i") endChild() - sliderFloat("Value", ::float, 0f, 1f) - inputFloat("Input", ::float, 0.1f) + slider("Value", ::float, 0f, 1f) + input("Input", ::float, 0.1f) combo("Combo", ::combo, "Yes\u0000No\u0000Maybe\u0000\u0000") } diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/PropertyEditor.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/PropertyEditor.kt index cf47a0020..8fd3bd733 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/PropertyEditor.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/PropertyEditor.kt @@ -1,13 +1,12 @@ package imgui.demo.showExampleApp import glm_.vec2.Vec2 -import imgui.* +import imgui.Cond import imgui.ImGui.alignTextToFramePadding import imgui.ImGui.begin -import imgui.ImGui.columns -import imgui.ImGui.dragFloat +import imgui.ImGui.drag import imgui.ImGui.end -import imgui.ImGui.inputFloat +import imgui.ImGui.input import imgui.ImGui.nextColumn import imgui.ImGui.popID import imgui.ImGui.popItemWidth @@ -15,7 +14,6 @@ import imgui.ImGui.popStyleVar import imgui.ImGui.pushID import imgui.ImGui.pushItemWidth import imgui.ImGui.pushStyleVar -import imgui.ImGui.separator import imgui.ImGui.setNextWindowSize import imgui.ImGui.tableNextRow import imgui.ImGui.tableSetColumnIndex @@ -23,8 +21,11 @@ import imgui.ImGui.text import imgui.ImGui.treeNode import imgui.ImGui.treeNodeEx import imgui.ImGui.treePop +import imgui.StyleVar +import imgui.TableFlag import imgui.api.demoDebugInformations.Companion.helpMarker import imgui.dsl.table +import imgui.or import kotlin.reflect.KMutableProperty0 import imgui.TreeNodeFlag as Tnf @@ -85,9 +86,9 @@ object PropertyEditor { tableSetColumnIndex(1) pushItemWidth(-Float.MIN_VALUE) if (i >= 5) - inputFloat("##value", placeholderMembers, i, 1f) + input("##value", placeholderMembers, i, 1f) else - dragFloat("##value", placeholderMembers, i, 0.01f) + drag("##value", placeholderMembers, i, 0.01f) popItemWidth() nextColumn() } diff --git a/core/src/main/kotlin/imgui/demo/showExampleApp/StyleEditor.kt b/core/src/main/kotlin/imgui/demo/showExampleApp/StyleEditor.kt index 387e21c80..3cf3384d1 100644 --- a/core/src/main/kotlin/imgui/demo/showExampleApp/StyleEditor.kt +++ b/core/src/main/kotlin/imgui/demo/showExampleApp/StyleEditor.kt @@ -13,10 +13,9 @@ import imgui.ImGui.beginTabItem import imgui.ImGui.bulletText import imgui.ImGui.button import imgui.ImGui.checkbox -import imgui.ImGui.colorEditVec4 +import imgui.ImGui.colorEdit4 import imgui.ImGui.combo import imgui.ImGui.cursorScreenPos -import imgui.ImGui.dragFloat import imgui.ImGui.dummy import imgui.ImGui.endTabBar import imgui.ImGui.endTabItem @@ -38,8 +37,7 @@ import imgui.ImGui.setNextWindowPos import imgui.ImGui.setWindowFontScale import imgui.ImGui.showFontAtlas import imgui.ImGui.showFontSelector -import imgui.ImGui.sliderFloat -import imgui.ImGui.sliderVec2 +import imgui.ImGui.slider2 import imgui.ImGui.spacing import imgui.ImGui.style import imgui.ImGui.text @@ -51,7 +49,9 @@ import imgui.ImGui.windowDrawList import imgui.ImGui.windowWidth import imgui.api.demoDebugInformations.Companion.helpMarker import imgui.api.demoDebugInformations.ShowStyleSelector +import imgui.api.drag import imgui.api.g +import imgui.api.slider import imgui.classes.Style import imgui.classes.TextFilter import imgui.dsl.button @@ -105,8 +105,7 @@ object StyleEditor { showFontSelector("Fonts##Selector") // Simplified Settings (expose floating-pointer border sizes as boolean representing 0.0f or 1.0f) - if (sliderFloat("FrameRounding", style::frameRounding, 0f, 12f, "%.0f")) style.grabRounding = - style.frameRounding // Make GrabRounding always the same value as FrameRounding + if (slider("FrameRounding", style::frameRounding, 0f, 12f, "%.0f")) style.grabRounding = style.frameRounding // Make GrabRounding always the same value as FrameRounding run { border = style.windowBorderSize > 0f if (checkbox("WindowBorder", ::border)) style.windowBorderSize = border.f @@ -139,51 +138,52 @@ object StyleEditor { if (beginTabItem("Sizes")) { text("Main") - sliderVec2("WindowPadding", style.windowPadding, 0f, 20f, "%.0f") - sliderVec2("FramePadding", style.framePadding, 0f, 20f, "%.0f") - sliderVec2("CellPadding", style.cellPadding, 0f, 20f, "%.0f") - sliderVec2("ItemSpacing", style.itemSpacing, 0f, 20f, "%.0f") - sliderVec2("ItemInnerSpacing", style.itemInnerSpacing, 0f, 20f, "%.0f") - sliderVec2("TouchExtraPadding", style.touchExtraPadding, 0f, 10f, "%.0f") - sliderFloat("IndentSpacing", style::indentSpacing, 0f, 30f, "%.0f") - sliderFloat("ScrollbarSize", style::scrollbarSize, 1f, 20f, "%.0f") - sliderFloat("GrabMinSize", style::grabMinSize, 1f, 20f, "%.0f") + slider2("WindowPadding", style.windowPadding, 0f, 20f, "%.0f") + slider2("FramePadding", style.framePadding, 0f, 20f, "%.0f") + slider2("CellPadding", style.cellPadding, 0f, 20f, "%.0f") + slider2("ItemSpacing", style.itemSpacing, 0f, 20f, "%.0f") + slider2("ItemInnerSpacing", style.itemInnerSpacing, 0f, 20f, "%.0f") + slider2("TouchExtraPadding", style.touchExtraPadding, 0f, 10f, "%.0f") + slider("IndentSpacing", style::indentSpacing, 0f, 30f, "%.0f") + slider("ScrollbarSize", style::scrollbarSize, 1f, 20f, "%.0f") + slider("GrabMinSize", style::grabMinSize, 1f, 20f, "%.0f") text("Borders") - sliderFloat("WindowBorderSize", style::windowBorderSize, 0f, 1f, "%.0f") - sliderFloat("ChildBorderSize", style::childBorderSize, 0f, 1f, "%.0f") - sliderFloat("PopupBorderSize", style::popupBorderSize, 0f, 1f, "%.0f") - sliderFloat("FrameBorderSize", style::frameBorderSize, 0f, 1f, "%.0f") - sliderFloat("TabBorderSize", style::tabBorderSize, 0f, 1f, "%.0f") + slider("WindowBorderSize", style::windowBorderSize, 0f, 1f, "%.0f") + slider("ChildBorderSize", style::childBorderSize, 0f, 1f, "%.0f") + slider("PopupBorderSize", style::popupBorderSize, 0f, 1f, "%.0f") + slider("FrameBorderSize", style::frameBorderSize, 0f, 1f, "%.0f") + slider("TabBorderSize", style::tabBorderSize, 0f, 1f, "%.0f") text("Rounding") - sliderFloat("WindowRounding", style::windowRounding, 0f, 12f, "%.0f") - sliderFloat("ChildRounding", style::childRounding, 0f, 12f, "%.0f") - sliderFloat("FrameRounding", style::frameRounding, 0f, 12f, "%.0f") - sliderFloat("PopupRounding", style::popupRounding, 0f, 16f, "%.0f") - sliderFloat("ScrollbarRounding", style::scrollbarRounding, 0f, 12f, "%.0f") - sliderFloat("GrabRounding", style::grabRounding, 0f, 12f, "%.0f") - sliderFloat("LogSliderDeadzone", style::logSliderDeadzone, 0f, 12f, "%.0f") - sliderFloat("TabRounding", style::tabRounding, 0f, 12f, "%.0f") + slider("WindowRounding", style::windowRounding, 0f, 12f, "%.0f") + slider("ChildRounding", style::childRounding, 0f, 12f, "%.0f") + slider("FrameRounding", style::frameRounding, 0f, 12f, "%.0f") + slider("PopupRounding", style::popupRounding, 0f, 16f, "%.0f") + slider("ScrollbarRounding", style::scrollbarRounding, 0f, 12f, "%.0f") + slider("GrabRounding", style::grabRounding, 0f, 12f, "%.0f") + slider("LogSliderDeadzone", style::logSliderDeadzone, 0f, 12f, "%.0f") + slider("TabRounding", style::tabRounding, 0f, 12f, "%.0f") text("Alignment") - sliderVec2("WindowTitleAlign", style.windowTitleAlign, 0f, 1f, "%.2f") + slider2("WindowTitleAlign", style.windowTitleAlign, 0f, 1f, "%.2f") run { - _i32 = style.windowMenuButtonPosition.i + 1 - if (combo("WindowMenuButtonPosition", ::_i32, - "None${NUL}Left${NUL}Right${NUL}") - ) style.windowMenuButtonPosition = Dir.values().first { it.i == _i32 - 1 } + val sideRef = (style.windowMenuButtonPosition.i + 1).mutableReference + val side by sideRef + if (combo("WindowMenuButtonPosition", sideRef, "None${NUL}Left${NUL}Right${NUL}")) { + style.windowMenuButtonPosition = Dir.values().first { it.i == side - 1 } + } } run { - _i32 = style.colorButtonPosition.i - combo("ColorButtonPosition", ::_i32, "Left\u0000Right\u0000") - style.colorButtonPosition = Dir.values().first { it.i == _i32 } + val sideRef = style.colorButtonPosition.i.mutableReference + val side by sideRef + combo("ColorButtonPosition", sideRef, "Left\u0000Right\u0000") + style.colorButtonPosition = Dir.values().first { it.i == side } } - sliderVec2("ButtonTextAlign", style.buttonTextAlign, 0f, 1f, "%.2f") + slider2("ButtonTextAlign", style.buttonTextAlign, 0f, 1f, "%.2f") sameLine(); helpMarker("Alignment applies when a button is larger than its text content.") - sliderVec2("SelectableTextAlign", style.selectableTextAlign, 0f, 1f, "%.2f") + slider2("SelectableTextAlign", style.selectableTextAlign, 0f, 1f, "%.2f") sameLine(); helpMarker("Alignment applies when a selectable is larger than its text content.") text("Safe Area Padding") - sameLine(); helpMarker( - "Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured).") - sliderVec2("DisplaySafeAreaPadding", style.displaySafeAreaPadding, 0f, 30f, "%.0f") + sameLine(); helpMarker("Adjust if you cannot see the edges of your screen (e.g. on a TV where scaling has not been configured).") + slider2("DisplaySafeAreaPadding", style.displaySafeAreaPadding, 0f, 30f, "%.0f") endTabItem() } @@ -231,7 +231,7 @@ object StyleEditor { val name = Col.values()[i].name if (!filter.passFilter(name)) continue withID(i) { - colorEditVec4("##color", style.colors[i], Cef.AlphaBar or alphaFlags) + colorEdit4("##color", style.colors[i], Cef.AlphaBar or alphaFlags) if (style.colors[i] != ref!!.colors[i]) { // Tips: in a real user application, you may want to merge and use an icon font into the main font, // so instead of "Save"/"Revert" you'd use icons! // Read the FAQ and docs/FONTS.txt about using icon fonts. It's really easy and super convenient! @@ -262,12 +262,11 @@ object StyleEditor { helpMarker(""" Those are old settings provided for convenience. However, the _correct_ way of scaling your UI is currently to reload your font at the designed size, rebuild the font atlas, and call style.ScaleAllSizes() on a reference ImGuiStyle structure. - Using those settings here will give you poor quality results.""".trimIndent() - ) + Using those settings here will give you poor quality results.""".trimIndent()) pushItemWidth(fontSize * 8) - if (dragFloat("window scale", ::windowScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", SliderFlag.AlwaysClamp)) // Scale only this window + if (drag("window scale", ::windowScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", SliderFlag.AlwaysClamp)) // Scale only this window setWindowFontScale(windowScale) - dragFloat("global scale", io::fontGlobalScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", SliderFlag.AlwaysClamp) // Scale everything + drag("global scale", io::fontGlobalScale, 0.005f, MIN_SCALE, MAX_SCALE, "%.2f", SliderFlag.AlwaysClamp) // Scale everything popItemWidth() endTabItem() @@ -284,11 +283,11 @@ object StyleEditor { checkbox("Anti-aliased fill", style::antiAliasedFill) pushItemWidth(fontSize * 8) - dragFloat("Curve Tessellation Tolerance", style::curveTessellationTol, 0.02f, 0.1f, 10f, "%.2f") + drag("Curve Tessellation Tolerance", style::curveTessellationTol, 0.02f, 0.1f, 10f, "%.2f") if (style.curveTessellationTol < 10f) style.curveTessellationTol = 0.1f // When editing the "Circle Segment Max Error" value, draw a preview of its effect on auto-tessellated circles. - dragFloat("Circle Tessellation Max Error", style::circleTessellationMaxError, 0.005f, 0.1f, 5f, "%.2f", SliderFlag.AlwaysClamp) + drag("Circle Tessellation Max Error", style::circleTessellationMaxError, 0.005f, 0.1f, 5f, "%.2f", SliderFlag.AlwaysClamp) if (ImGui.isItemActive) { setNextWindowPos(ImGui.cursorScreenPos) tooltip { @@ -324,13 +323,12 @@ object StyleEditor { } } ImGui.sameLine() - helpMarker( - "When drawing circle primitives with \"num_segments == 0\" tesselation will be calculated automatically.") + helpMarker("When drawing circle primitives with \"num_segments == 0\" tesselation will be calculated automatically.") /* Not exposing zero here so user doesn't "lose" the UI (zero alpha clips all widgets). But application code could have a toggle to switch between zero and non-zero. */ - dragFloat("Global Alpha", style::alpha, 0.005f, 0.2f, 1f, "%.2f") - dragFloat("Disabled Alpha", style::disabledAlpha, 0.005f, 0f, 1f, "%.2f"); sameLine(); helpMarker("Additional alpha multiplier for disabled items (multiply over current value of Alpha).") + drag("Global Alpha", style::alpha, 0.005f, 0.2f, 1f, "%.2f") + drag("Disabled Alpha", style::disabledAlpha, 0.005f, 0f, 1f, "%.2f"); sameLine(); helpMarker("Additional alpha multiplier for disabled items (multiply over current value of Alpha).") popItemWidth() endTabItem() @@ -356,7 +354,7 @@ object StyleEditor { // Display details setNextItemWidth(fontSize * 8) - dragFloat("Font scale", font::scale, 0.005f, 0.3f, 2f, "%.1f") + drag("Font scale", font::scale, 0.005f, 0.3f, 2f, "%.1f") sameLine() helpMarker(""" |Note than the default embedded font is NOT meant to be scaled. diff --git a/core/src/main/kotlin/imgui/flags & enumerations.kt b/core/src/main/kotlin/imgui/flags & enumerations.kt index 9f8063d42..418a3c014 100644 --- a/core/src/main/kotlin/imgui/flags & enumerations.kt +++ b/core/src/main/kotlin/imgui/flags & enumerations.kt @@ -5,6 +5,7 @@ import glm_.vec4.Vec4 import imgui.DragDropFlag.* import imgui.ImGui.getColorU32 import imgui.internal.isPowerOfTwo +import unsigned.* import kotlin.internal.NoInfer @@ -1035,6 +1036,12 @@ enum class DataType(val imguiName: String) { _String("[internal] String"), _Pointer("[internal] Pointer"), _ID("[internal] ID"); + val isUnsigned: Boolean + get() = when (this) { + Ubyte, Ushort, Uint, Ulong -> true + else -> false + } + @JvmField val i = ordinal @@ -1043,6 +1050,27 @@ enum class DataType(val imguiName: String) { } } +// Float is the most common data type in imgui, followed by Int, and so we have them at the start +// so that the compiler's dead code elimination gets rid of the rest of the branches. +// In reality, the JVM will optimize the comparisons since they're constant checks, +// So there should be no performance difference in tight loops. +// Also, a minifier like proguard deals with this very easily. +inline fun dataTypeOf(): DataType where D : Number, D : Comparable { + return when(D::class) { + Float::class -> DataType.Float + Int::class -> DataType.Int + Byte::class -> DataType.Byte + Short::class -> DataType.Short + Long::class -> DataType.Long + Double::class -> DataType.Double + Ubyte::class -> DataType.Ubyte + Ushort::class -> DataType.Ushort + Uint::class -> DataType.Uint + Ulong::class -> DataType.Ulong + else -> throw Error("Unsupported data type ${D::class}") + } +} + /** A cardinal direction */ enum class Dir { None, Left, Right, Up, Down; diff --git a/core/src/main/kotlin/imgui/genericMath.kt b/core/src/main/kotlin/imgui/genericMath.kt index 4b9dc1b52..f53da147f 100644 --- a/core/src/main/kotlin/imgui/genericMath.kt +++ b/core/src/main/kotlin/imgui/genericMath.kt @@ -1,238 +1,332 @@ package imgui import glm_.* -import unsigned.Ubyte -import unsigned.Uint -import unsigned.Ulong -import unsigned.Ushort -import java.math.BigInteger -import kotlin.div -import kotlin.minus -import kotlin.plus -import kotlin.times -import imgui.internal.lerp as ilerp - -@Suppress("UNCHECKED_CAST") - -//infix operator fun N.plus(other: N): N where N : Number, N : Comparable = when { -// this is Byte && other is Byte -> (this + other).b -// this is Ubyte && other is Ubyte -> (this + other).b -// this is Short && other is Short -> (this + other).s -// this is Ushort && other is Ushort -> (this + other).s -// this is Int && other is Int -> this + other -// this is Uint && other is Uint -> this + other -// this is Long && other is Long -> this + other -// this is Ulong && other is Ulong -> this + other -// this is Float && other is Float -> this + other -// this is Double && other is Double -> this + other -// this is BigInteger && other is BigInteger -> this + other -// else -> error("Invalid operand types") -//} as N - -infix operator fun Type.minus(other: Type): Type - where Type : Number, Type : Comparable = when { - this is Byte && other is Byte -> this - other - this is Ubyte && other is Ubyte -> this - other - this is Short && other is Short -> this - other - this is Ushort && other is Ushort -> this - other - this is Int && other is Int -> this - other - this is Uint && other is Uint -> this - other - this is Long && other is Long -> this - other - this is Ulong && other is Ulong -> this - other - this is Float && other is Float -> this - other - this is Double && other is Double -> this - other - this is BigInteger && other is BigInteger -> this - other - else -> error("Invalid operand types") -} as Type - -infix operator fun N.times(other: N): N where N : Number, N : Comparable = when { - this is Byte && other is Byte -> (this * other).b - this is Ubyte && other is Ubyte -> (this * other).b - this is Short && other is Short -> (this * other).s - this is Ushort && other is Ushort -> (this * other).s - this is Int && other is Int -> this * other - this is Uint && other is Uint -> this * other - this is Long && other is Long -> this * other - this is Ulong && other is Ulong -> this * other - this is Float && other is Float -> this * other - this is Double && other is Double -> this * other - this is BigInteger && other is BigInteger -> this * other - else -> error("Invalid operand types") -} as N - -infix operator fun N.div(other: N): N where N : Number, N : Comparable = when { - this is Byte && other is Byte -> (this / other).b - this is Ubyte && other is Ubyte -> (this / other).b - this is Short && other is Short -> (this / other).s - this is Ushort && other is Ushort -> (this / other).s - this is Int && other is Int -> this / other - this is Uint && other is Uint -> this / other - this is Long && other is Long -> this / other - this is Ulong && other is Ulong -> this / other - this is Float && other is Float -> this / other - this is Double && other is Double -> this / other - this is BigInteger && other is BigInteger -> this / other - else -> error("Invalid operand types") -} as N - - -infix operator fun N.plus(float: Float): N where N : Number, N : Comparable = when (this) { - is Byte -> (this + float).b - is Ubyte -> (v + float).b - is Short -> (this + float).s - is Ushort -> (v + float).s - is Int -> (this + float).i - is Uint -> (v + float).i - is Long -> (this + float).L - is Ulong -> (v + float).L - is Float -> this + float - is Double -> (this + float).d - else -> error("Invalid operand types") -} as N - -infix operator fun N.plus(double: Double): N where N : Number, N : Comparable = when (this) { - is Byte -> (this + double).b - is Ubyte -> (v + double).b - is Short -> (this + double).s - is Ushort -> (v + double).s - is Int -> (this + double).i - is Uint -> (v + double).i - is Long -> (this + double).L - is Ulong -> (v + double).L - is Float -> (this + double).f - is Double -> this + double - else -> error("Invalid operand types") -} as N - -infix operator fun N.plus(int: Int): N where N : Number, N : Comparable = when (this) { - is Byte -> (this + int).b - is Ubyte -> (v + int).b - is Short -> (this + int).s - is Ushort -> (v + int).s - is Int -> (this + int).i - is Uint -> (v + int).i - is Long -> (this + int).L - is Ulong -> (v + int).L - is Float -> this + int - is Double -> (this + int).d - else -> error("Invalid operand types") -} as N - -infix operator fun N.compareTo(int: Int): Int where N : Number, N : Comparable = when (this) { - is Byte -> compareTo(int) - is Ubyte -> compareTo(int) - is Short -> compareTo(int) - is Ushort -> compareTo(int) - is Int -> compareTo(int) - is Uint -> compareTo(int) - is Long -> compareTo(int) - is Ulong -> compareTo(int.L) - is Float -> compareTo(int) - is Double -> compareTo(int) - else -> error("Invalid operand types") +import imgui.internal.addClampOverflow +import imgui.internal.parseFormatSanitizeForScanning +import imgui.internal.subClampOverflow +import uno.kotlin.NUL +import unsigned.* +import unsigned.parseUnsignedLong +import kotlin.reflect.KMutableProperty0 +import kotlin.math.ln as realLn +import kotlin.math.pow as realPow + +infix fun > N.min(other: N): N = if (this < other) this else other +infix fun > N.max(other: N): N = if (this > other) this else other + +@JvmName("clampReceiver") +fun > N.clamp(min: N?, max: N?): N = when { + min != null && this < min -> min + max != null && this > max -> max + else -> this } -fun Number.`as`(n: N): N where N : Number, N : Comparable = when (n) { - is Byte -> b - is Ubyte -> n.v - is Short -> s - is Ushort -> n.v - is Int -> i - is Uint -> n.v - is Long -> L - is Ulong -> n.v - is Float -> f - is Double -> d - else -> error("invalid") -} as N - -val N.asSigned: N where N : Number, N : Comparable - get() = when (this) { - is Byte, is Short -> i // TODO utypes - else -> this - } as N - -fun clamp(a: N, min: N, max: N): N where N : Number, N : Comparable = - if (a < min) min else if (a > max) max else a - -fun min(a: Type, b: Type): Type - where Type : Number, Type : Comparable = if (a < b) a else b - -fun max(a: Type, b: Type): Type - where Type : Number, Type : Comparable = if (a > b) a else b - -fun lerp(a: N, b: N, t: Float): N where N : Number, N : Comparable = when (a) { - is Byte -> ilerp(a.i, b.i, t).b - is Short -> ilerp(a.i, b.i, t).s - is Int -> ilerp(a, b as Int, t) - is Long -> ilerp(a, b as Long, t) - is Float -> ilerp(a, b as Float, t) - is Double -> ilerp(a, b as Double, t) - else -> error("Invalid type") -} as N - -//infix operator fun Float.times(n: N): N where N : Number, N : Comparable = when (n) { -// is Byte -> (b * n).b -// is Short -> (s * n).s -// is Int -> i * n -// is Long -> L * n -// is Float -> this * n -// is Double -> d * n -// else -> error("Invalid operand types") -//} as N -// -//infix operator fun Float.div(n: N): N where N : Number, N : Comparable = when (n) { -// is Byte -> (b / n).b -// is Short -> (s / n).s -// is Int -> i / n -// is Long -> L / n -// is Float -> this / n -// is Double -> d / n -// else -> error("Invalid operand types") -//} as N - - -infix operator fun Type.compareTo(float: Float): Int where Type : Number, Type : Comparable = when (this) { - is Byte -> compareTo(float) - is Short -> compareTo(float) - is Int -> compareTo(float) - is Long -> compareTo(float) - is Ubyte, is Ushort, is Uint, is Ulong -> f.compareTo(float) // TODO -> unsigned - is Float -> compareTo(float) - is Double -> compareTo(float) - else -> error("invalid") +fun > clamp(value: N, min: N?, max: N?): N = value.clamp(min, max) + +sealed interface NumberOps where N : Number, N : Comparable { + val min: N + val max: N + val zero: N + val one: N + val N.isNegative: Boolean + get() = this < zero + val N.sign: Int + get() = when { + isNegative -> -1 + this == zero -> 0 + else -> 1 + } + val dataType: DataType + val Number.coerced: N + + /** return true if modified */ + fun KMutableProperty0.applyFromText(buf: String, format: String): Boolean { + val initial = get() + val v = parse(buf, format) + return v?.let { v -> + this.set(v) + get() != initial + } == true + } + + fun parse(buf: String, format: String, radix: Int): N + val String.parsed: N + + fun N.format(format: String): String { + // [JVM] we have to filter `.`, since `%.03d` causes `IllegalFormatPrecisionException`, but `%03d` doesn't + return format.replace(".", "").format(when (this) { + // we need to intervene since java printf cant handle %u + is Ubyte -> i + is Ushort -> i + is Uint -> L + is Ulong -> toBigInt() + else -> this + }) + } + + operator fun N.plus(other: N): N + operator fun N.minus(other: N): N + operator fun N.times(other: N): N = with(fpOps) { nTimesN(this@times, other) } + operator fun N.div(other: N): N = with(fpOps) { nDivN(this@div, other) } } -@JvmName("min_") -fun min(n: N, t: Type): Type - where Type : Number, Type : Comparable, - N : Number, N : Comparable = when (t) { - is Byte -> if (n.b < t) n.b else t - is Ubyte -> if (n.ub < t) n.ub else t - is Short -> if (n.s < t) n.s else t - is Ushort -> if (n.us < t) n.us else t - is Int -> if (n.i < t) n.i else t - is Uint -> if (n.ui < t) n.ui else t - is Long -> if (n.L < t) n.L else t - is Ulong -> if (n.ul < t) n.ul else t - is Float -> if (n.f < t) n.f else t - is Double -> if (n.d < t) n.d else t - else -> error("invalid") -} as Type - -@JvmName("max_") -fun max(n: N, t: Type): Type - where Type : Number, Type : Comparable, - N : Number, N : Comparable = when (t) { - is Byte -> if (n.b > t) n.b else t - is Ubyte -> if (n.ub > t) n.ub else t - is Short -> if (n.s > t) n.s else t - is Ushort -> if (n.us > t) n.us else t - is Int -> if (n.i > t) n.i else t - is Uint -> if (n.ui > t) n.ui else t - is Long -> if (n.L > t) n.L else t - is Ulong -> if (n.ul > t) n.ul else t - is Float -> if (n.f > t) n.f else t - is Double -> if (n.d > t) n.d else t +private fun NumberFpOps.nTimesN(n: N, other: N): N where N : Number, N : Comparable, FP : Number, FP : Comparable = (n.fp * other.fp).n +private fun NumberFpOps.nDivN(n: N, other: N): N where N : Number, N : Comparable, FP : Number, FP : Comparable = (n.fp / other.fp).n + +fun NumberOps.parse(buf: String, format: String): N? where N : Number, N : Comparable { + // ImCharIsBlankA + @Suppress("NAME_SHADOWING") val buf = buf.replace(Regex("\\s+"), "").replace("\t", "").removeSuffix("\u0000") + + if (buf.isEmpty()) return null + + val radix = if (format.last().lowercaseChar() == 'x') 16 else 10 + + // Sanitize format + return parse(buf, parseFormatSanitizeForScanning(format), radix) +} + +fun NumberOps<*>.defaultInputCharsFilter(format: String): InputTextFlag.Single { + if (dataType == DataType.Float || dataType == DataType.Double) return InputTextFlag.CharsScientific + return when (if (format.isNotEmpty()) format.last() else NUL) { + 'x', 'X' -> InputTextFlag.CharsHexadecimal + else -> InputTextFlag.CharsDecimal + } +} +val NumberOps<*>.defaultFormat: String + get() = when (dataType) { + DataType.Float, DataType.Double -> "%.3f" + else -> "%d" + } +val NumberOps<*>.isSigned: Boolean + get() = !dataType.isUnsigned + +val NumberOps<*>.isFloatingPoint: Boolean + get() = dataType == DataType.Float || dataType == DataType.Double + +val NumberOps.isSmallerThanInt: Boolean where N : Number, N : Comparable get() = dataType < DataType.Int + +inline fun numberOps(): NumberOps where N : Number, N : Comparable = numberFpOps() +inline fun numberFpOps(): NumberFpOps where N : Number, N : Comparable, FP : Number, FP : Comparable = dataTypeOf().fpOps() +fun DataType.ops(): NumberOps where N : Number, N : Comparable = fpOps() +fun DataType.fpOps(): NumberFpOps where N : Number, N : Comparable, FP : Number, FP : Comparable = when (this) { + DataType.Byte -> ByteOps + DataType.Ubyte -> UbyteOps + DataType.Short -> ShortOps + DataType.Ushort -> UshortOps + DataType.Int -> IntOps + DataType.Uint -> UintOps + DataType.Long -> LongOps + DataType.Ulong -> UlongOps + DataType.Float -> FloatOps + DataType.Double -> DoubleOps else -> error("invalid") -} as Type +} as NumberFpOps + +val NumberOps.fpOps: NumberFpOps where N : Number, N : Comparable + get() = if (this is NumberFpOps<*, *>) this as NumberFpOps else dataType.fpOps() + +sealed interface FloatingPointOps where FP : Number, FP : Comparable { + val Float.fp: FP get() = coercedFp + + val Number.coercedFp: FP + operator fun FP.unaryMinus(): FP + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("fpPlus") + operator fun FP.plus(other: FP): FP + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("fpMinus") + operator fun FP.minus(other: FP): FP + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("fpTimes") + operator fun FP.times(other: FP): FP + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("fpDiv") + operator fun FP.div(other: FP): FP + infix fun FP.pow(other: FP): FP + fun ln(fp: FP): FP + fun abs(fp: FP): FP = when { + fp < 0f.fp -> -fp + else -> fp + } +} + +sealed interface NumberFpOps : NumberOps, FloatingPointOps where N : Number, N : Comparable, FP : Number, FP : Comparable { + // These are defined to signal that no coercion should be happening between the types + // In other words, coerced* properties should usually be avoided + val N.fp: FP get() = coercedFp + val FP.n: N get() = coerced +} + +fun NumberFpOps.lerp(a: N, b: N, t: FP): FP where N : Number, N : Comparable, FP : Number, FP : Comparable = a.fp + (b - a).fp * t +fun FloatingPointOps.lerp(a: FP, b: FP, t: FP): FP where FP : Number, FP : Comparable = a + (b - a) * t + +object ByteOps : NumberFpOps, FloatingPointOps by FloatOps { + override val min = Byte.MIN_VALUE + override val max = Byte.MAX_VALUE + override val zero: Byte = 0 + override val one: Byte = 1 + override val Number.coerced get() = b + override val dataType: DataType = DataType.Byte + override fun parse(buf: String, format: String, radix: Int): Byte = format.format(buf.parseInt(radix)).toByte(radix) + override val String.parsed: Byte get() = b + override fun Byte.plus(other: Byte): Byte = addClampOverflow(i, other.i, min.i, max.i).b + override fun Byte.minus(other: Byte): Byte = subClampOverflow(i, other.i, min.i, max.i).b +} + +object UbyteOps : NumberFpOps, FloatingPointOps by FloatOps { + override val min = Ubyte.MIN + override val max = Ubyte.MAX + override val zero = 0.ub + override val one = 1.ub + override val Ubyte.isNegative: Boolean get() = false + override val Number.coerced get() = ub + override val dataType: DataType = DataType.Ubyte + override fun parse(buf: String, format: String, radix: Int): Ubyte = format.format(buf.parseInt(radix)).toInt(radix).ub + override val String.parsed: Ubyte get() = ub + override fun Ubyte.plus(other: Ubyte): Ubyte = addClampOverflow(i, other.i, min.i, max.i).ub + override fun Ubyte.minus(other: Ubyte): Ubyte = subClampOverflow(i, other.i, min.i, max.i).ub +} + +object ShortOps : NumberFpOps, FloatingPointOps by FloatOps { + override val min = Short.MIN_VALUE + override val max = Short.MAX_VALUE + override val zero: Short = 0 + override val one: Short = 1 + override val Number.coerced get() = s + override val dataType: DataType = DataType.Short + override fun parse(buf: String, format: String, radix: Int): Short = format.format(buf.parseInt(radix)).toShort(radix) + override val String.parsed: Short get() = s + override fun Short.plus(other: Short): Short = addClampOverflow(i, other.i, min.i, max.i).s + override fun Short.minus(other: Short): Short = subClampOverflow(i, other.i, min.i, max.i).s +} + +object UshortOps : NumberFpOps, FloatingPointOps by FloatOps { + override val min = Ushort.MIN + override val max = Ushort.MAX + override val zero = 0.us + override val one = 1.us + override val Ushort.isNegative: Boolean get() = false + override val Number.coerced get() = us + override val dataType: DataType = DataType.Ushort + override fun parse(buf: String, format: String, radix: Int): Ushort = format.format(buf.parseInt(radix)).toInt(radix).us + override val String.parsed: Ushort get() = us + override fun Ushort.plus(other: Ushort): Ushort = addClampOverflow(i, other.i, min.i, max.i).us + override fun Ushort.minus(other: Ushort): Ushort = subClampOverflow(i, other.i, min.i, max.i).us +} + +object IntOps : NumberFpOps, FloatingPointOps by FloatOps { + override val min = Int.MIN_VALUE + override val max = Int.MAX_VALUE + override val zero = 0 + override val one = 1 + override val Number.coerced get() = i + override val dataType: DataType = DataType.Int + override fun parse(buf: String, format: String, radix: Int): Int = format.format(buf.parseInt(radix)).toInt(radix) + override val String.parsed: Int get() = i + override fun Int.plus(other: Int): Int = addClampOverflow(this, other, min, max) + override fun Int.minus(other: Int): Int = subClampOverflow(this, other, min, max) +} + +object UintOps : NumberFpOps, FloatingPointOps by FloatOps { + override val min = Uint.MIN + override val max = Uint.MAX + override val zero = 0.ui + override val one = 1.ui + override val Uint.isNegative: Boolean get() = false + override val Number.coerced get() = ui + override val dataType: DataType = DataType.Uint + override fun parse(buf: String, format: String, radix: Int): Uint = format.format(buf.parseLong(radix)).toLong(radix).ui + override val String.parsed: Uint get() = ui + override fun Uint.plus(other: Uint): Uint = addClampOverflow(L, other.L, min.L, max.L).ui + override fun Uint.minus(other: Uint): Uint = subClampOverflow(L, other.L, min.L, max.L).ui +} + +object LongOps : NumberFpOps, FloatingPointOps by DoubleOps { + override val min = Long.MIN_VALUE + override val max = Long.MAX_VALUE + override val zero = 0L + override val one = 1L + override val Number.coerced get() = L + override val dataType: DataType = DataType.Long + override fun parse(buf: String, format: String, radix: Int): Long = format.format(buf.parseUnsignedLong(radix)).toLong(radix) + override val String.parsed: Long get() = L + override fun Long.plus(other: Long): Long = addClampOverflow(this, other, min, max) + override fun Long.minus(other: Long): Long = subClampOverflow(this, other, min, max) +} + +object UlongOps : NumberFpOps, FloatingPointOps by DoubleOps { + override val min = Ulong.MIN + override val max = Ulong.MAX + override val zero = 0.ul + override val one = 1.ul + override val Ulong.isNegative: Boolean get() = false + override val Number.coerced get() = ul + override val dataType: DataType = DataType.Ulong + override fun parse(buf: String, format: String, radix: Int): Ulong = format.format(buf.parseUnsignedLong(radix)).toBigInteger(radix).ul + override val String.parsed: Ulong get() = ul + override fun Ulong.plus(other: Ulong): Ulong = addClampOverflow(toBigInt(), other.toBigInt(), min.toBigInt(), max.toBigInt()).ul + override fun Ulong.minus(other: Ulong): Ulong = subClampOverflow(toBigInt(), other.toBigInt(), min.toBigInt(), max.toBigInt()).ul +} + +object FloatOps : NumberFpOps { + override val min = -Float.MAX_VALUE + override val max = Float.MAX_VALUE + override val zero = 0f + override val one = 1f + override val Float.fp: Float get() = this + override val Number.coerced get() = f + override val Number.coercedFp: Float get() = f + override val dataType: DataType = DataType.Float + override fun Float.format(format: String): String = format.format(this) + override fun parse(buf: String, format: String, radix: Int): Float = "%f".format(buf.parseFloat).f + override val String.parsed: Float get() = f + override fun Float.unaryMinus(): Float = -this + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("fpPlus") + override fun Float.plus(other: Float): Float = this + other + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("fpMinus") + override fun Float.minus(other: Float): Float = this - other + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("fpTimes") + override fun Float.times(other: Float): Float = this * other + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("fpDiv") + override fun Float.div(other: Float): Float = this / other + override fun Float.pow(other: Float): Float = this.realPow(other) + override fun ln(fp: Float): Float = realLn(fp) +} + +object DoubleOps : NumberFpOps { + override val min = -Double.MAX_VALUE + override val max = Double.MAX_VALUE + override val zero = 0.0 + override val one = 1.0 + override val Number.coerced get() = d + override val Number.coercedFp: Double get() = d + override val dataType: DataType = DataType.Double + override fun Double.format(format: String): String = format.format(this) + override fun parse(buf: String, format: String, radix: Int): Double = "%f".format(buf.parseDouble).d + override val String.parsed: Double get() = d + override fun Double.unaryMinus(): Double = -this + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("fpPlus") + override fun Double.plus(other: Double): Double = this + other + + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("fpMinus") + override fun Double.minus(other: Double): Double = this - other + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("fpTimes") + override fun Double.times(other: Double): Double = this * other + @Suppress("INAPPLICABLE_JVM_NAME") + @JvmName("fpDiv") + override fun Double.div(other: Double): Double = this / other + override fun Double.pow(other: Double): Double = this.realPow(other) + override fun ln(fp: Double): Double = realLn(fp) +} \ No newline at end of file diff --git a/core/src/main/kotlin/imgui/helpers.kt b/core/src/main/kotlin/imgui/helpers.kt index 384587c1b..b431a721e 100644 --- a/core/src/main/kotlin/imgui/helpers.kt +++ b/core/src/main/kotlin/imgui/helpers.kt @@ -3,6 +3,7 @@ package imgui //import com.sun.jdi.VirtualMachine import glm_.L import glm_.b +import glm_.vec3.Vec3 import glm_.vec4.Vec4 import imgui.classes.InputTextCallbackData import imgui.classes.SizeCallbackData @@ -164,12 +165,15 @@ val Int.vec4: Vec4 /** ~ColorConvertFloat4ToU32 */ val Vec4.u32: Int - get() { - var out = F32_TO_INT8_SAT(x) shl COL32_R_SHIFT - out = out or (F32_TO_INT8_SAT(y) shl COL32_G_SHIFT) - out = out or (F32_TO_INT8_SAT(z) shl COL32_B_SHIFT) - return out or (F32_TO_INT8_SAT(w) shl COL32_A_SHIFT) - } + get() = floatsToU32(x, y, z, w) + +/** ~ColorConvertFloat4ToU32 */ +fun floatsToU32(x: Float, y: Float, z: Float, w: Float = 0f): Int { + var out = F32_TO_INT8_SAT(x) shl COL32_R_SHIFT + out = out or (F32_TO_INT8_SAT(y) shl COL32_G_SHIFT) + out = out or (F32_TO_INT8_SAT(z) shl COL32_B_SHIFT) + return out or (F32_TO_INT8_SAT(w) shl COL32_A_SHIFT) +} var imeInProgress = false // var imeLastKey = 0 @@ -236,4 +240,38 @@ operator fun KMutableProperty0.invoke(t: T): KMutableProperty0 { return this } -val ByteArray.cStr get() = String(this, 0, strlen()) \ No newline at end of file +val ByteArray.cStr get() = String(this, 0, strlen()) + +typealias Vec3Setter = (x: Float, y: Float, z: Float) -> Unit +typealias Vec4Setter = (x: Float, y: Float, z: Float, w: Float) -> Unit + +inline infix fun Vec3.into(setter: Vec3Setter) = setter(x, y, z) +inline infix fun Vec4.into(setter: Vec3Setter) = setter(x, y, z) +inline infix fun Vec4.into(setter: Vec4Setter) = setter(x, y, z, w) +inline fun Vec4.into(setter: Vec4Setter, w: Float) = setter(x, y, z, w) + +fun Vec4.put(x: Float, y: Float, z: Float) { + put(x, y, z, w) +} + +fun Vec3.put(x: Float, y: Float, z: Float, w: Float) { + put(x, y, z) +} + +fun FloatArray.put(x: Float, y: Float, z: Float) { + this[0] = x + this[1] = y + this[2] = z +} + +fun FloatArray.put(x: Float, y: Float, z: Float, w: Float) { + this[0] = x + this[1] = y + this[2] = z + this[3] = w +} + +fun FloatArray.put(vararg f: Float) { + f.copyInto(this) + +} \ No newline at end of file diff --git a/core/src/main/kotlin/imgui/imgui.kt b/core/src/main/kotlin/imgui/imgui.kt index 46190dcf0..b59422119 100644 --- a/core/src/main/kotlin/imgui/imgui.kt +++ b/core/src/main/kotlin/imgui/imgui.kt @@ -196,10 +196,8 @@ object ImGui : widgetsWindowDecorations, widgetsLowLevelBehaviors, templateFunctions, - dataTypeHelpers, inputText, color, - plot, shadeFunctions, garbageCollection, debugLog, diff --git a/core/src/main/kotlin/imgui/internal/api/color.kt b/core/src/main/kotlin/imgui/internal/api/color.kt index b465ee8bb..b82bd9a8b 100644 --- a/core/src/main/kotlin/imgui/internal/api/color.kt +++ b/core/src/main/kotlin/imgui/internal/api/color.kt @@ -2,6 +2,7 @@ package imgui.internal.api import glm_.glm import glm_.vec2.Vec2 +import glm_.vec3.Vec3 import glm_.vec4.Vec4 import imgui.* import imgui.ImGui.beginPopup @@ -9,8 +10,6 @@ import imgui.ImGui.beginTooltipEx import imgui.ImGui.button import imgui.ImGui.checkboxFlags import imgui.ImGui.clipboardText -import imgui.ImGui.colorButton -import imgui.ImGui.colorPicker4 import imgui.ImGui.cursorScreenPos import imgui.ImGui.endPopup import imgui.ImGui.endTooltip @@ -28,7 +27,11 @@ import imgui.ImGui.separator import imgui.ImGui.style import imgui.ImGui.text import imgui.ImGui.textEx +import imgui.api.colorButton +import imgui.api.colorPicker4 import imgui.api.g +import imgui.api.gImGui +import imgui.flagArrayOf import imgui.internal.F32_TO_INT8_SAT import imgui.internal.sections.TooltipFlag import imgui.ColorEditFlag as Cef @@ -36,8 +39,12 @@ import imgui.ColorEditFlag as Cef /** Color */ internal interface color { - /** Note: only access 3 floats if ColorEditFlag.NoAlpha flag is set. */ - fun colorTooltip(text: String, col: FloatArray, flags: ColorEditFlags) { + fun colorTooltip(text: String, col: Vec3, flags: ColorEditFlags) = colorTooltip(text, col.x, col.y, col.z, 1f, flags) + + fun colorTooltip(text: String, col: Vec4, flags: ColorEditFlags) = colorTooltip(text, col.x, col.y, col.z, col.w, flags) + + /** Note: only access 3 floats if ColorEditFlag.NoAlpha flag is set. */ + fun colorTooltip(text: String, r: Float, g: Float, b: Float, a: Float, flags: ColorEditFlags) { beginTooltipEx(TooltipFlag.OverridePreviousTooltip) val textEnd = if (text.isEmpty()) findRenderedTextEnd(text) else 0 @@ -45,41 +52,38 @@ internal interface color { textEx(text, textEnd) separator() } - val sz = Vec2(g.fontSize * 3 + style.framePadding.y * 2) - val cf = Vec4(col[0], col[1], col[2], if (flags has Cef.NoAlpha) 1f else col[3]) - val cr = F32_TO_INT8_SAT(col[0]) - val cg = F32_TO_INT8_SAT(col[1]) - val cb = F32_TO_INT8_SAT(col[2]) - val ca = if (flags has Cef.NoAlpha) 255 else F32_TO_INT8_SAT(col[3]) - colorButton("##preview", cf, (flags and (Cef._InputMask or Cef.NoAlpha or Cef.AlphaPreview or Cef.AlphaPreviewHalf)) or Cef.NoTooltip, sz) + val sz = Vec2(gImGui.fontSize * 3 + style.framePadding.y * 2) + val cr = F32_TO_INT8_SAT(r) + val cg = F32_TO_INT8_SAT(g) + val cb = F32_TO_INT8_SAT(b) + val ca = if (flags has Cef.NoAlpha) 255 else F32_TO_INT8_SAT(a) + colorButton("##preview", r, g, b, if (flags has Cef.NoAlpha) 1f else a, (flags and (Cef._InputMask or Cef.NoAlpha or Cef.AlphaPreview or Cef.AlphaPreviewHalf)) or Cef.NoTooltip, sz) sameLine() - if (flags has Cef.InputRGB || flags hasnt Cef._InputMask) - if (flags has Cef.NoAlpha) - text("#%02X%02X%02X\nR: $cr, G: $cg, B: $cb\n(%.3f, %.3f, %.3f)", cr, cg, cb, col[0], col[1], col[2]) - else - text("#%02X%02X%02X%02X\nR:$cr, G:$cg, B:$cb, A:$ca\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, col[0], col[1], col[2], col[3]) - else if (flags has Cef.InputHSV) - if (flags has Cef.NoAlpha) - text("H: %.3f, S: %.3f, V: %.3f", col[0], col[1], col[2]) - else - text("H: %.3f, S: %.3f, V: %.3f, A: %.3f", col[0], col[1], col[2], col[3]) + if (flags has Cef.InputRGB || flags hasnt Cef._InputMask) if (flags has Cef.NoAlpha) text("#%02X%02X%02X\nR: $cr, G: $cg, B: $cb\n(%.3f, %.3f, %.3f)", cr, cg, cb, r, g, b) + else text("#%02X%02X%02X%02X\nR:$cr, G:$cg, B:$cb, A:$ca\n(%.3f, %.3f, %.3f, %.3f)", cr, cg, cb, ca, r, g, b, a) + else if (flags has Cef.InputHSV) if (flags has Cef.NoAlpha) text("H: %.3f, S: %.3f, V: %.3f", r, g, b) + else text("H: %.3f, S: %.3f, V: %.3f, A: %.3f", r, g, b, a) endTooltip() } - /** @param flags ColorEditFlags */ - fun colorEditOptionsPopup(col: FloatArray, flags: ColorEditFlags) { + fun colorEditOptionsPopup(col: Vec3, flags: ColorEditFlags) { + colorEditOptionsPopup(col.x, col.y, col.z, 1f, flags) + } + + fun colorEditOptionsPopup(col: Vec4, flags: ColorEditFlags) { + colorEditOptionsPopup(col.x, col.y, col.z, col.w, flags) + } + + fun colorEditOptionsPopup(r: Float, g: Float, b: Float, a: Float, flags: ColorEditFlags) { val allowOptInputs = flags hasnt Cef._DisplayMask val allowOptDatatype = flags hasnt Cef._DataTypeMask if ((!allowOptInputs && !allowOptDatatype) || !beginPopup("context")) return - var opts = g.colorEditOptions + var opts = gImGui.colorEditOptions if (allowOptInputs) { - if (radioButton("RGB", opts has Cef.DisplayRGB)) - opts = (opts wo Cef._DisplayMask) or Cef.DisplayRGB - if (radioButton("HSV", opts has Cef.DisplayHSV)) - opts = (opts wo Cef._DisplayMask) or Cef.DisplayHSV - if (radioButton("HEX", opts has Cef.DisplayHEX)) - opts = (opts wo Cef._DisplayMask) or Cef.DisplayHEX + if (radioButton("RGB", opts has Cef.DisplayRGB)) opts = (opts wo Cef._DisplayMask) or Cef.DisplayRGB + if (radioButton("HSV", opts has Cef.DisplayHSV)) opts = (opts wo Cef._DisplayMask) or Cef.DisplayHSV + if (radioButton("HEX", opts has Cef.DisplayHEX)) opts = (opts wo Cef._DisplayMask) or Cef.DisplayHEX } if (allowOptDatatype) { if (allowOptInputs) separator() @@ -90,35 +94,34 @@ internal interface color { } if (allowOptInputs || allowOptDatatype) separator() - if (button("Copy as..", Vec2(-1, 0))) - openPopup("Copy") + if (button("Copy as..", Vec2(-1, 0))) openPopup("Copy") if (beginPopup("Copy")) { - val cr = F32_TO_INT8_SAT(col[0]) - val cg = F32_TO_INT8_SAT(col[1]) - val cb = F32_TO_INT8_SAT(col[2]) - val ca = if (flags has Cef.NoAlpha) 255 else F32_TO_INT8_SAT(col[3]) - var buf = "(%.3ff, %.3ff, %.3ff, %.3ff)".format(col[0], col[1], col[2], if (flags has Cef.NoAlpha) 1f else col[3]) - if (selectable(buf)) - clipboardText = buf + val cr = F32_TO_INT8_SAT(r) + val cg = F32_TO_INT8_SAT(g) + val cb = F32_TO_INT8_SAT(b) + val ca = if (flags has Cef.NoAlpha) 255 else F32_TO_INT8_SAT(a) + var buf = "(%.3ff, %.3ff, %.3ff, %.3ff)".format(r, g, b, if (flags has Cef.NoAlpha) 1f else a) + if (selectable(buf)) clipboardText = buf buf = "(%d,%d,%d,%d)".format(cr, cg, cb, ca) - if (selectable(buf)) - clipboardText = buf + if (selectable(buf)) clipboardText = buf buf = "#%02X%02X%02X".format(cr, cg, cb) - if (selectable(buf)) - clipboardText = buf - if (flags hasnt Cef.NoAlpha) { + if (selectable(buf)) clipboardText = buf + if (flags hasnt Cef.NoAlpha) { buf = "#%02X%02X%02X%02X".format(cr, cg, cb, ca) - if (selectable(buf)) - clipboardText = buf + if (selectable(buf)) clipboardText = buf } endPopup() } - g.colorEditOptions = opts + gImGui.colorEditOptions = opts endPopup() } - fun colorPickerOptionsPopup(refCol: FloatArray, flags: ColorEditFlags) { + fun colorPickerOptionsPopup(refCol: Vec3, flags: ColorEditFlags) = colorPickerOptionsPopup(refCol.x, refCol.y, refCol.z, 1f, flags) + + fun colorPickerOptionsPopup(refCol: Vec4, flags: ColorEditFlags) = colorPickerOptionsPopup(refCol.x, refCol.y, refCol.z, refCol.w, flags) + + fun colorPickerOptionsPopup(x: Float, y: Float, z: Float, w: Float, flags: ColorEditFlags) { val allowOptPicker = flags hasnt Cef._PickerMask val allowOptAlphaBar = flags hasnt Cef.NoAlpha && flags hasnt Cef.AlphaBar if ((!allowOptPicker && !allowOptAlphaBar) || !beginPopup("context")) return @@ -130,19 +133,14 @@ internal interface color { // Draw small/thumbnail version of each picker type (over an invisible button for selection) if (pickerType > 0) separator() pushID(pickerType) - var pickerFlags: ColorEditFlags = Cef.NoInputs or Cef.NoOptions or Cef.NoLabel or - Cef.NoSidePreview or (flags and Cef.NoAlpha) + var pickerFlags: ColorEditFlags = Cef.NoInputs or Cef.NoOptions or Cef.NoLabel or Cef.NoSidePreview or (flags and Cef.NoAlpha) if (pickerType == 0) pickerFlags = pickerFlags or Cef.PickerHueBar if (pickerType == 1) pickerFlags = pickerFlags or Cef.PickerHueWheel val backupPos = Vec2(cursorScreenPos) // By default, Selectable() is closing popup - if (selectable("##selectable", false, sizeArg = pickerSize)) - g.colorEditOptions = (g.colorEditOptions wo Cef._PickerMask) or (pickerFlags and Cef._PickerMask) + if (selectable("##selectable", false, sizeArg = pickerSize)) g.colorEditOptions = (g.colorEditOptions wo Cef._PickerMask) or (pickerFlags and Cef._PickerMask) cursorScreenPos = backupPos - val previewingRefCol = Vec4() - for (i in 0..2) previewingRefCol[i] = refCol[i] - if (pickerFlags hasnt Cef.NoAlpha) previewingRefCol[3] = refCol[3] - colorPicker4("##previewing_picker", previewingRefCol, pickerFlags) + colorPicker4("##previewing_picker", x, y, z, w, pickerFlags) popID() } popItemWidth() @@ -155,4 +153,4 @@ internal interface color { } endPopup() } -} \ No newline at end of file +} diff --git a/core/src/main/kotlin/imgui/internal/api/dataTypeHelpers.kt b/core/src/main/kotlin/imgui/internal/api/dataTypeHelpers.kt deleted file mode 100644 index 3898e2ccc..000000000 --- a/core/src/main/kotlin/imgui/internal/api/dataTypeHelpers.kt +++ /dev/null @@ -1,250 +0,0 @@ -package imgui.internal.api - -import glm_.* -import imgui.DataType -import imgui.ImGui.style -import imgui.InputTextFlag -import imgui._i32 -import imgui.api.* -import imgui.has -import imgui.internal.addClampOverflow -import imgui.internal.parseFormatSanitizeForScanning -import imgui.internal.subClampOverflow -import uno.kotlin.NUL -import uno.kotlin.getValue -import uno.kotlin.setValue -import unsigned.* -import unsigned.parseUnsignedLong as avoid -import kotlin.reflect.KMutableProperty0 - -@Suppress("UNCHECKED_CAST") - -/** Data type helpers */ -internal interface dataTypeHelpers { - - // IMGUI_API const ImGuiDataTypeInfo* DataTypeGetInfo(ImGuiDataType data_type); - - /** DataTypeFormatString */ - fun KMutableProperty0<*>.format(dataType: DataType, format_: String): String { - val arg = when (val t = this()) { - // we need to intervene since java printf cant handle %u - is Ubyte -> t.i - is Ushort -> t.i - is Uint -> t.L - is Ulong -> t.toBigInt() - else -> t // normal scalar - } - val format = when(dataType) { - DataType.Float, DataType.Double -> format_ - // [JVM] we have to filter `.`, since `%.03d` causes `IllegalFormatPrecisionException`, but `%03d` doesn't - else -> format_.replace(".", "") - } - return format.format(arg) - } - - fun dataTypeApplyOp(dataType: DataType, op: Char, value1: N, value2: N): N { - assert(op == '+' || op == '-') - return when (dataType) { - /* Signedness doesn't matter when adding or subtracting - Note: on jvm Byte and Short (and their unsigned counterparts use all unsigned under the hood), - so we directly switch to Integers in these cases. We also use some custom clamp min max values because of this - */ - DataType.Byte -> when (op) { - '+' -> addClampOverflow((value1 as Byte).i, (value2 as Byte).i, S8_MIN, S8_MAX).b as N - '-' -> subClampOverflow((value1 as Byte).i, (value2 as Byte).i, S8_MIN, S8_MAX).b as N - else -> throw Error() - } - - DataType.Ubyte -> when (op) { - '+' -> Ubyte(addClampOverflow((value1 as Ubyte).i, (value2 as Ubyte).i, U8_MIN, U8_MAX)) as N - '-' -> Ubyte(subClampOverflow((value1 as Ubyte).i, (value2 as Ubyte).i, U8_MIN, U8_MAX)) as N - else -> throw Error() - } - - DataType.Short -> when (op) { - '+' -> addClampOverflow((value1 as Short).i, (value2 as Short).i, S16_MIN, S16_MAX).s as N - '-' -> subClampOverflow((value1 as Short).i, (value2 as Short).i, S16_MIN, S16_MAX).s as N - else -> throw Error() - } - - DataType.Ushort -> when (op) { - '+' -> Ushort(addClampOverflow((value1 as Ushort).i, (value2 as Ushort).i, U16_MIN, U16_MAX)) as N - '-' -> Ushort(subClampOverflow((value1 as Ushort).i, (value2 as Ushort).i, U16_MIN, U16_MAX)) as N - else -> throw Error() - } - - DataType.Int -> when (op) { - '+' -> addClampOverflow(value1 as Int, value2 as Int, Int.MIN_VALUE, Int.MAX_VALUE) as N - '-' -> subClampOverflow(value1 as Int, value2 as Int, Int.MIN_VALUE, Int.MAX_VALUE) as N - else -> throw Error() - } - - DataType.Uint -> when (op) { - '+' -> Uint(addClampOverflow((value1 as Uint).L, (value2 as Uint).L, 0L, Uint.MAX_VALUE)) as N - '-' -> Uint(subClampOverflow((value1 as Uint).L, (value2 as Uint).L, 0L, Uint.MAX_VALUE)) as N - else -> throw Error() - } - - DataType.Long -> when (op) { - '+' -> addClampOverflow(value1 as Long, value2 as Long, Long.MIN_VALUE, Long.MAX_VALUE) as N - '-' -> subClampOverflow(value1 as Long, value2 as Long, Long.MIN_VALUE, Long.MAX_VALUE) as N - else -> throw Error() - } - - DataType.Ulong -> when (op) { - '+' -> Ulong(addClampOverflow((value1 as Ulong).toBigInt(), (value2 as Ulong).toBigInt(), Ulong.MIN_VALUE, Ulong.MAX_VALUE)) as N - '-' -> Ulong(subClampOverflow((value1 as Ulong).toBigInt(), (value2 as Ulong).toBigInt(), Ulong.MIN_VALUE, Ulong.MAX_VALUE)) as N - else -> throw Error() - } - - DataType.Float -> when (op) { - '+' -> (value1 as Float + value2 as Float) as N - '-' -> (value1 as Float - value2 as Float) as N - else -> throw Error() - } - - DataType.Double -> when (op) { - '+' -> (value1 as Double + value2 as Double) as N - '-' -> (value1 as Double - value2 as Double) as N - else -> throw Error() - } - - else -> error("invalid, this is a private enum value") - } - } - - /** User can input math operators (e.g. +100) to edit a numerical values. - * NB: This is _not_ a full expression evaluator. We should probably add one and replace this dumb mess.. */ - fun dataTypeApplyFromText(buf: String, dataType: DataType, pData: IntArray, format: String): Boolean { - _i32 = pData[0] - return dataTypeApplyFromText(buf, dataType, ::_i32, format) - .also { pData[0] = _i32 } - } - fun dataTypeApplyFromText(buf_: String, dataType: DataType, pData: KMutableProperty0<*>, format_: String): Boolean { - - // ImCharIsBlankA - var buf = buf_.replace(Regex("\\s+"), "") - .replace("\t", "") - if (buf.last() == NUL) - buf = buf.dropLast(1) // termination - - // Copy the value in an opaque buffer so we can compare at the end of the function if it changed at all. - val dataBackup = pData() - - if (buf.isEmpty()) - return false - - // Sanitize format - // For float/double we have to ignore format with precision (e.g. "%.2f") because sscanf doesn't take them in, so force them into %f and %lf -// char format_sanitized[32]; - val format = when(dataType) { - DataType.Float, DataType.Double -> "%f" - else -> parseFormatSanitizeForScanning(format_) - } - - val radix = if (format.last().lowercaseChar() == 'x') 16 else 10 - - when (dataType) { - DataType.Byte -> { - var data by pData as KMutableProperty0 - data = format.format(buf.parseInt(radix)).toByte(radix) - } - - DataType.Ubyte -> { - var data by pData as KMutableProperty0 - data = format.format(buf.parseInt(radix)).toByte(radix).toUbyte() - } - - DataType.Short -> { - var data by pData as KMutableProperty0 - data = format.format(buf.parseInt(radix)).toShort(radix) - } - - DataType.Ushort -> { - var data by pData as KMutableProperty0 - data = format.format(buf.parseInt(radix)).toShort(radix).toUshort() - } - - DataType.Int -> { - var data by pData as KMutableProperty0 - data = format.format(buf.parseLong(radix)).toInt(radix) - } - - DataType.Uint -> { - var data by pData as KMutableProperty0 - data = format.format(buf.parseLong(radix)).toInt(radix).toUint() - } - - DataType.Long -> { - var data by pData as KMutableProperty0 - data = format.format(buf.parseUnsignedLong(radix)).toLong(radix) - } - - DataType.Ulong -> { - var data by pData as KMutableProperty0 - data = format.format(buf.parseUnsignedLong(radix)).toLong(radix).toUlong() - } - - DataType.Float -> { - var data by pData as KMutableProperty0 - data = format.format(buf.parseFloat).toFloat() - } - - DataType.Double -> { - var data by pData as KMutableProperty0 - data = format.format(buf.parseDouble).toDouble() - } - - else -> error("") - } - -// // Small types need a 32-bit buffer to receive the result from scanf() -// int v32 = 0; -// if (sscanf(buf, format, type_info->Size >= 4 ? p_data : &v32) < 1) -// return false; -// if (type_info->Size < 4) -// { -// if (data_type == ImGuiDataType_S8) -// *(ImS8*)p_data = (ImS8)ImClamp(v32, (int)IM_S8_MIN, (int)IM_S8_MAX); -// else if (data_type == ImGuiDataType_U8) -// *(ImU8*)p_data = (ImU8)ImClamp(v32, (int)IM_U8_MIN, (int)IM_U8_MAX); -// else if (data_type == ImGuiDataType_S16) -// *(ImS16*)p_data = (ImS16)ImClamp(v32, (int)IM_S16_MIN, (int)IM_S16_MAX); -// else if (data_type == ImGuiDataType_U16) -// *(ImU16*)p_data = (ImU16)ImClamp(v32, (int)IM_U16_MIN, (int)IM_U16_MAX); -// else -// IM_ASSERT(0); -// } - - return dataBackup != pData() - } - - // useless on JVM with `N : Number, N : Comparable` - // IMGUI_API int DataTypeCompare(ImGuiDataType data_type, const void* arg_1, const void* arg_2); - - fun dataTypeClampT(pV: KMutableProperty0, vMin: N?, vMax: N?): Boolean - where N : Number, N : Comparable { - var v by pV - // Clamp, both sides are optional, return true if modified - return when { - vMin != null && v < vMin -> {; v = vMin; true; } - vMax != null && v > vMax -> {; v = vMax; true; } - else -> false - } - } - - fun dataTypeClamp(dataType: DataType, pData: KMutableProperty0, pMin: N?, pMax: N?): Boolean - where N : Number, N : Comparable = when (dataType) { - DataType.Byte -> dataTypeClampT(pData as KMutableProperty0, pMin as Byte?, pMax as Byte?) - DataType.Ubyte -> dataTypeClampT(pData as KMutableProperty0, pMin as Ubyte?, pMax as Ubyte?) - DataType.Short -> dataTypeClampT(pData as KMutableProperty0, pMin as Short?, pMax as Short?) - DataType.Ushort -> dataTypeClampT(pData as KMutableProperty0, pMin as Ushort?, pMax as Ushort?) - DataType.Int -> dataTypeClampT(pData as KMutableProperty0, pMin as Int?, pMax as Int?) - DataType.Uint -> dataTypeClampT(pData as KMutableProperty0, pMin as Uint?, pMax as Uint?) - DataType.Long -> dataTypeClampT(pData as KMutableProperty0, pMin as Long?, pMax as Long?) - DataType.Ulong -> dataTypeClampT(pData as KMutableProperty0, pMin as Ulong?, pMax as Ulong?) - DataType.Float -> dataTypeClampT(pData as KMutableProperty0, pMin as Float?, pMax as Float?) - DataType.Double -> dataTypeClampT(pData as KMutableProperty0, pMin as Double?, pMax as Double?) - else -> error("invalid") - } -} \ No newline at end of file diff --git a/core/src/main/kotlin/imgui/internal/api/debugTools.kt b/core/src/main/kotlin/imgui/internal/api/debugTools.kt index f8800108e..123d81290 100644 --- a/core/src/main/kotlin/imgui/internal/api/debugTools.kt +++ b/core/src/main/kotlin/imgui/internal/api/debugTools.kt @@ -9,7 +9,6 @@ import imgui.ImGui.beginTooltip import imgui.ImGui.boundSettings import imgui.ImGui.bullet import imgui.ImGui.bulletText -import imgui.ImGui.dragFloat import imgui.ImGui.dummy import imgui.ImGui.end import imgui.ImGui.endChild @@ -58,6 +57,7 @@ import imgui.ImGui.textUnformatted import imgui.ImGui.treeNode import imgui.ImGui.treeNodeEx import imgui.ImGui.treePop +import imgui.api.drag import imgui.api.g import imgui.classes.DrawList import imgui.classes.ListClipper @@ -421,7 +421,7 @@ internal interface debugTools { // Display details setNextItemWidth(ImGui.fontSize * 8) - dragFloat("Font scale", font::scale, 0.005f, 0.3f, 2f, "%.1f") + drag("Font scale", font::scale, 0.005f, 0.3f, 2f, "%.1f") sameLine(); metricsHelpMarker( "Note than the default embedded font is NOT meant to be scaled.\n\n" + "Font are currently rendered into bitmaps at a given size at the time of building the atlas. " + diff --git a/core/src/main/kotlin/imgui/internal/api/inputText.kt b/core/src/main/kotlin/imgui/internal/api/inputText.kt index a9489211f..34dda1f37 100644 --- a/core/src/main/kotlin/imgui/internal/api/inputText.kt +++ b/core/src/main/kotlin/imgui/internal/api/inputText.kt @@ -16,13 +16,10 @@ import imgui.ImGui.calcTextSize import imgui.ImGui.clearActiveID import imgui.ImGui.clipboardText import imgui.ImGui.currentWindow -import imgui.ImGui.dataTypeApplyFromText -import imgui.ImGui.dataTypeClamp import imgui.ImGui.dummy import imgui.ImGui.endChild import imgui.ImGui.endGroup import imgui.ImGui.focusWindow -import imgui.ImGui.format import imgui.ImGui.getColorU32 import imgui.ImGui.getScrollbarID import imgui.ImGui.io @@ -58,7 +55,10 @@ import imgui.internal.classes.InputTextState.K import imgui.internal.classes.LastItemData import imgui.internal.classes.Rect import imgui.internal.sections.* -import imgui.static.* +import imgui.static.inputTextCalcTextLenAndLineCount +import imgui.static.inputTextCalcTextSizeW +import imgui.static.inputTextFilterCharacter +import imgui.static.inputTextReconcileUndoStateAfterUserCallback import imgui.stb.te.clamp import imgui.stb.te.click import imgui.stb.te.cut @@ -102,17 +102,16 @@ internal interface inputText { val isPassword = flags has Itf.Password val isUndoable = flags hasnt Itf.NoUndoRedo val isResizable = flags has Itf.CallbackResize - if (isResizable) - assert(callback != null) { "Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!" } - if (flags has Itf.CallbackCharFilter) - assert(callback != null) { "Must provide a callback if you want a char filter!" } + if (isResizable) assert(callback != null) { "Must provide a callback if you set the ImGuiInputTextFlags_CallbackResize flag!" } + if (flags has Itf.CallbackCharFilter) assert(callback != null) { "Must provide a callback if you want a char filter!" } if (isMultiline) // Open group before calling GetID() because groups tracks id created within their scope (including the scrollbar) beginGroup() val id = window.getID(label) val labelSize = calcTextSize(label, hideTextAfterDoubleHash = true) + // Arbitrary default of 8 lines high for multi-line val h = if (isMultiline) g.fontSize * 8f else labelSize.y - val frameSize = calcItemSize(sizeArg, calcItemWidth(), h + style.framePadding.y * 2f) // Arbitrary default of 8 lines high for multi-line + val frameSize = calcItemSize(sizeArg, calcItemWidth(), h + style.framePadding.y * 2f) val totalSize = Vec2(frameSize.x + if (labelSize.x > 0f) style.itemInnerSpacing.x + labelSize.x else 0f, frameSize.y) val frameBb = Rect(window.dc.cursorPos, window.dc.cursorPos + frameSize) @@ -120,7 +119,8 @@ internal interface inputText { var drawWindow = window val innerSize = Vec2(frameSize) - var bufEnd = 0 + val bufEndRef = 0.mutableReference + val bufEnd by bufEndRef val itemStatusFlags: ItemStatusFlags lateinit var itemDataBackup: LastItemData if (isMultiline) { @@ -211,7 +211,7 @@ internal interface inputText { else if (state.textW.size > buf.size) state.textW[buf.size] = NUL state.textAIsValid = false // TextA is not valid yet (we will display buf until then) - state.curLenW = textStrFromUtf8(state.textW, buf, -1, bufEnd.mutableProperty { bufEnd = it }) + state.curLenW = textStrFromUtf8(state.textW, buf, -1, bufEndRef) state.curLenA = bufEnd // We can't get the result from ImStrncpy() above because it is not UTF-8 aware. Here we'll cut off malformed UTF-8. if (recycleState) @@ -280,7 +280,7 @@ internal interface inputText { state.textW = CharArray(buf.size) else if (state.textW.size > buf.size) state.textW[buf.size] = NUL - state.curLenW = textStrFromUtf8(state.textW, buf, -1, bufEnd.mutableProperty { bufEnd = it }) + state.curLenW = textStrFromUtf8(state.textW, buf, -1, bufEndRef) state.curLenA = bufEnd state.cursorClamp() renderSelection = renderSelection && state.hasSelection @@ -364,10 +364,8 @@ internal interface inputText { state.cursorAnimReset() } else if (io.mouseClicked[0] && !state.selectedAllMouseLock) { if (hovered) { - if (io.keyShift) - state.drag(mouseX, mouseY) - else - state.click(mouseX, mouseY) + if (io.keyShift) state.drag(mouseX, mouseY) + else state.click(mouseX, mouseY) state.cursorAnimReset() } } else if (io.mouseDown[0] && !state.selectedAllMouseLock && (io.mouseDelta.x != 0f || io.mouseDelta.y != 0f)) { // TODO -> glm once anyNotEqual gets fixed @@ -375,31 +373,33 @@ internal interface inputText { state.cursorAnimReset() state.cursorFollow = true } - if (state.selectedAllMouseLock && !io.mouseDown[0]) - state.selectedAllMouseLock = false + if (state.selectedAllMouseLock && !io.mouseDown[0]) state.selectedAllMouseLock = false // We expect backends to emit a Tab key but some also emit a Tab character which we ignore (#2467, #1336) // (For Tab and Enter: Win32/SFML/Allegro are sending both keys and chars, GLFW and SDL are only sending keys. For Space they all send all threes) val ignoreCharInputs = (io.keyCtrl && !io.keyAlt) || (isOsx && io.keySuper) - if (flags has Itf.AllowTabInput && Key.Tab.isPressed && !ignoreCharInputs && !io.keyShift && !isReadOnly) - withChar { - it.set('\t') // Insert TAB - if (inputTextFilterCharacter(it, flags, callback, callbackUserData, InputSource.Keyboard)) - state.onKeyPressed(it().i) + // Insert TAB + if (flags has Itf.AllowTabInput && Key.Tab.isPressed && !ignoreCharInputs && !io.keyShift && !isReadOnly) { + val charRef = '\t'.mutableReference + val char by charRef + if (inputTextFilterCharacter(charRef, flags, callback, callbackUserData, InputSource.Keyboard)) { + state.onKeyPressed(char.i) } + } // Process regular text input (before we check for Return because using some IME will effectively send a Return?) // We ignore CTRL inputs, but need to allow ALT+CTRL as some keyboards (e.g. German) use AltGR (which _is_ Alt+Ctrl) to input certain characters. if (io.inputQueueCharacters.isNotEmpty()) { - if (!ignoreCharInputs && !isReadOnly && !inputRequestedByNav) - for (n in io.inputQueueCharacters.indices) { - // Insert character if they pass filtering - var c = io.inputQueueCharacters[n] - if (c == NUL || c == '\t') // Skip Tab, see above. - continue - if (inputTextFilterCharacter(c mutableProperty { c = it }, flags, callback, callbackUserData, InputSource.Keyboard)) - state.onKeyPressed(c.i) + if (!ignoreCharInputs && !isReadOnly && !inputRequestedByNav) for (n in io.inputQueueCharacters.indices) { + // Insert character if they pass filtering + val cRef = io.inputQueueCharacters[n].mutableReference + val c by cRef + if (c == NUL || c == '\t') // Skip Tab, see above. + continue + if (inputTextFilterCharacter(cRef, flags, callback, callbackUserData, InputSource.Keyboard)) { + state.onKeyPressed(c.i) } + } // Consume characters io.inputQueueCharacters.clear() } @@ -486,17 +486,17 @@ internal interface inputText { val ctrlEnterForNewLine = flags has Itf.CtrlEnterForNewLine if (!isMultiline || isGamepadValidate || (ctrlEnterForNewLine && !io.keyCtrl) || (!ctrlEnterForNewLine && io.keyCtrl)) { validated = true - if (io.configInputTextEnterKeepActive && !isMultiline) - state.selectAll() // No need to scroll - else - clearActiveId = true - - } else if (!isReadOnly) - withChar('\n') { c -> - // Insert new line - if (inputTextFilterCharacter(c, flags, callback, callbackUserData, InputSource.Keyboard)) - state.onKeyPressed(c().i) + if (io.configInputTextEnterKeepActive && !isMultiline) state.selectAll() // No need to scroll + else clearActiveId = true + + } else if (!isReadOnly) { + // Insert new line + val charRef = '\n'.mutableReference + val char by charRef + if (inputTextFilterCharacter(charRef, flags, callback, callbackUserData, InputSource.Keyboard)) { + state.onKeyPressed(char.i) } + } } isCancel -> @@ -547,12 +547,11 @@ internal interface inputText { val clipboardFiltered = CharArray(clipboardLen) var clipboardFilteredLen = 0 for (c in clipboard) { - if (c == NUL) - break - _c = c - if (!inputTextFilterCharacter(::_c, flags, callback, callbackUserData, InputSource.Keyboard)) - continue - clipboardFiltered[clipboardFilteredLen++] = _c + if (c == NUL) break + val cRef = c.mutableReference + val c by cRef + if (!inputTextFilterCharacter(cRef, flags, callback, callbackUserData, InputSource.Keyboard)) continue + clipboardFiltered[clipboardFilteredLen++] = c } if (clipboardFilteredLen > 0) { // If everything was filtered, ignore the pasting operation state.paste(clipboardFiltered, clipboardFilteredLen) @@ -887,16 +886,16 @@ internal interface inputText { if (text[p++] == '\n') break } else { - val rectSize = withInt { - inputTextCalcTextSizeW(g, text, p, textSelectedEnd, it, stopOnNewLine = true).also { p = it() } - } + val remainingRef = 0.mutableReference + val remaining by remainingRef + val rectSize = inputTextCalcTextSizeW(g, text, p, textSelectedEnd, remainingRef, stopOnNewLine = true) + p = remaining // So we can see selected empty lines if (rectSize.x <= 0f) rectSize.x = floor(g.font.getCharAdvance(' ') * 0.5f) val rect = Rect(rectPos + Vec2(0f, bgOffYUp - g.fontSize), rectPos + Vec2(rectSize.x, bgOffYDn)) val clipRect_ = Rect(clipRect) rect clipWith clipRect_ - if (rect overlaps clipRect_) - drawWindow.drawList.addRectFilled(rect.min, rect.max, bgColor) + if (rect overlaps clipRect_) drawWindow.drawList.addRectFilled(rect.min, rect.max, bgColor) } rectPos.x = drawPos.x - drawScroll.x rectPos.y += g.fontSize @@ -1011,44 +1010,39 @@ internal interface inputText { * ImGuiSliderFlags_AlwaysClamp flag is set! * This is intended: this way we allow CTRL+Click manual input to set a value out of bounds, for maximum flexibility. * However this may not be ideal for all uses, as some user code may break on out of bound values. */ - fun tempInputScalar(bb: Rect, id: ID, label: String, dataType: DataType, pData: KMutableProperty0, format_: String, - pClampMin: KMutableProperty0? = null, pClampMax: KMutableProperty0? = null): Boolean - where N : Number, N : Comparable { - + fun NumberOps.tempInputScalar(bb: Rect, id: ID, label: String, pData: KMutableProperty0, format_: String, clampMin: N? = null, clampMax: N? = null): Boolean where N : Number, N : Comparable { + var p by pData // On the first frame, g.TempInputTextId == 0, then on subsequent frames it becomes == id. // We clear ActiveID on the first frame to allow the InputText() taking it back. val init = g.tempInputId != id - if (init) - clearActiveID() + if (init) clearActiveID() val format = parseFormatTrimDecorations(format_) - val dataBuf = pData.format(dataType, format) - .trim() + val dataBuf = p.format(format).trim() - var flags = Itf.AutoSelectAll or Itf._NoMarkEdited - flags /= inputScalarDefaultCharsFilter(dataType, format) + val flags = Itf.AutoSelectAll or Itf._NoMarkEdited or defaultInputCharsFilter(format) val buf = dataBuf.toByteArray(32) var valueChanged = false if (tempInputText(bb, id, label, buf, flags)) { // Backup old value - val dataBackup = pData() + val dataBackup = p // Apply new value (or operations) then clamp - dataTypeApplyFromText(buf.cStr, dataType, pData, format) - if (pClampMin != null || pClampMax != null) { - if (pClampMin != null && pClampMax != null) { - var clampMin by pClampMin - var clampMax by pClampMax + p = parse(buf.cStr, format) ?: p + if (clampMin != null || clampMax != null) { + var clampMin = clampMin + var clampMax = clampMax + if (clampMin != null && clampMax != null) { if (clampMin > clampMax) { val swap = clampMin; clampMin = clampMax; clampMax = swap } } - dataTypeClamp(dataType, pData, pClampMin?.get(), pClampMax?.get()) + p = p.clamp(clampMin, clampMax) } // Only mark as edited if new value is different - valueChanged = dataBackup != pData() + valueChanged = dataBackup != p if (valueChanged) markItemEdited(id) diff --git a/core/src/main/kotlin/imgui/internal/api/menus.kt b/core/src/main/kotlin/imgui/internal/api/menus.kt index 0fbe515a2..7e11c9dee 100644 --- a/core/src/main/kotlin/imgui/internal/api/menus.kt +++ b/core/src/main/kotlin/imgui/internal/api/menus.kt @@ -31,6 +31,7 @@ import imgui.internal.floor import imgui.internal.sections.* import imgui.internal.triangleContainsPoint import kotlin.math.abs +import kotlin.math.min // Menus internal interface menus { diff --git a/core/src/main/kotlin/imgui/internal/api/plot.kt b/core/src/main/kotlin/imgui/internal/api/plot.kt index 7c2715381..86a50e8b2 100644 --- a/core/src/main/kotlin/imgui/internal/api/plot.kt +++ b/core/src/main/kotlin/imgui/internal/api/plot.kt @@ -19,115 +19,109 @@ import imgui.ImGui.renderText import imgui.ImGui.renderTextClipped import imgui.ImGui.setTooltip import imgui.ImGui.style -import imgui.api.widgetsDataPlotting.Companion.PlotArray -import imgui.internal.sections.PlotType import imgui.internal.classes.Rect import imgui.internal.lerp import imgui.internal.saturate +import imgui.internal.sections.PlotType import kotlin.math.min /** Plot */ -internal interface plot { - - fun plotEx(plotType: PlotType, label: String, data: PlotArray, valuesOffset: Int, overlayText: String, - scaleMin_: Float, scaleMax_: Float, frameSize: Vec2): Int { - - val window = currentWindow - if (window.skipItems) return -1 - - var scaleMin = scaleMin_ - var scaleMax = scaleMax_ - val valuesCount = data.count() - - val labelSize = calcTextSize(label, hideTextAfterDoubleHash = true) - if (frameSize.x == 0f) frameSize.x = calcItemWidth() - if (frameSize.y == 0f) frameSize.y = labelSize.y + style.framePadding.y * 2 - - val frameBb = Rect(window.dc.cursorPos, window.dc.cursorPos + frameSize) - val innerBb = Rect(frameBb.min + style.framePadding, frameBb.max - style.framePadding) - val totalBb = Rect(frameBb.min, frameBb.max + Vec2(if (labelSize.x > 0f) style.itemInnerSpacing.x + labelSize.x else 0f, 0)) - itemSize(totalBb, style.framePadding.y) - if (!itemAdd(totalBb, 0, frameBb)) return -1 - val hovered = itemHoverable(innerBb, 0) - - // Determine scale from values if not specified - if (scaleMin == Float.MAX_VALUE || scaleMax == Float.MAX_VALUE) { - var vMin = Float.MAX_VALUE - var vMax = -Float.MAX_VALUE - for (i in 0 until valuesCount) { - val v = data[i] - if (v.isNaN) continue - vMin = vMin min v - vMax = vMax max v - } - if (scaleMin == Float.MAX_VALUE) scaleMin = vMin - if (scaleMax == Float.MAX_VALUE) scaleMax = vMax + +@PublishedApi +internal inline fun plotEx(plotType: PlotType, label: String, valuesCount: Int, valuesOffset: Int, overlayText: String, scaleMin_: Float, scaleMax_: Float, frameSize: Vec2, data: (Int) -> Float): Int { + + val window = currentWindow + if (window.skipItems) return -1 + + var scaleMin = scaleMin_ + var scaleMax = scaleMax_ + + val labelSize = calcTextSize(label, hideTextAfterDoubleHash = true) + if (frameSize.x == 0f) frameSize.x = calcItemWidth() + if (frameSize.y == 0f) frameSize.y = labelSize.y + style.framePadding.y * 2 + + val frameBb = Rect(window.dc.cursorPos, window.dc.cursorPos + frameSize) + val innerBb = Rect(frameBb.min + style.framePadding, frameBb.max - style.framePadding) + val totalBb = Rect(frameBb.min, frameBb.max + Vec2(if (labelSize.x > 0f) style.itemInnerSpacing.x + labelSize.x else 0f, 0)) + itemSize(totalBb, style.framePadding.y) + if (!itemAdd(totalBb, 0, frameBb)) return -1 + val hovered = itemHoverable(innerBb, 0) + + // Determine scale from values if not specified + if (scaleMin == Float.MAX_VALUE || scaleMax == Float.MAX_VALUE) { + var vMin = Float.MAX_VALUE + var vMax = -Float.MAX_VALUE + for (i in 0 until valuesCount) { + val v = data(i) + if (v.isNaN) continue + vMin = vMin min v + vMax = vMax max v } + if (scaleMin == Float.MAX_VALUE) scaleMin = vMin + if (scaleMax == Float.MAX_VALUE) scaleMax = vMax + } - renderFrame(frameBb.min, frameBb.max, Col.FrameBg.u32, true, style.frameRounding) - - val valuesCountMin = if (plotType == PlotType.Lines) 2 else 1 - var idxHovered = -1 - if (valuesCount >= valuesCountMin) { - val resW = min(frameSize.x.i, valuesCount) + if (plotType == PlotType.Lines) -1 else 0 - val itemCount = valuesCount + if (plotType == PlotType.Lines) -1 else 0 - - // Tooltip on hover - if (hovered && io.mousePos in innerBb) { - val t = glm.clamp((io.mousePos.x - innerBb.min.x) / (innerBb.max.x - innerBb.min.x), 0f, 0.9999f) - val vIdx = (t * itemCount).i - assert(vIdx in 0 until valuesCount) - - val v0 = data[(vIdx + valuesOffset) % valuesCount] - val v1 = data[(vIdx + 1 + valuesOffset) % valuesCount] - when (plotType) { - PlotType.Lines -> setTooltip("$vIdx: %8.4g\n${vIdx + 1}: %8.4g", v0, v1) - PlotType.Histogram -> setTooltip("$vIdx: %8.4g", v0) - } - idxHovered = vIdx + renderFrame(frameBb.min, frameBb.max, Col.FrameBg.u32, true, style.frameRounding) + + val valuesCountMin = if (plotType == PlotType.Lines) 2 else 1 + var idxHovered = -1 + if (valuesCount >= valuesCountMin) { + val resW = min(frameSize.x.i, valuesCount) + if (plotType == PlotType.Lines) -1 else 0 + val itemCount = valuesCount + if (plotType == PlotType.Lines) -1 else 0 + + // Tooltip on hover + if (hovered && io.mousePos in innerBb) { + val t = glm.clamp((io.mousePos.x - innerBb.min.x) / (innerBb.max.x - innerBb.min.x), 0f, 0.9999f) + val vIdx = (t * itemCount).i + assert(vIdx in 0 until valuesCount) + + val v0 = data((vIdx + valuesOffset) % valuesCount) + val v1 = data((vIdx + 1 + valuesOffset) % valuesCount) + when (plotType) { + PlotType.Lines -> setTooltip("$vIdx: %8.4g\n${vIdx + 1}: %8.4g", v0, v1) + PlotType.Histogram -> setTooltip("$vIdx: %8.4g", v0) } + idxHovered = vIdx + } - val tStep = 1f / resW - val invScale = if (scaleMin == scaleMax) 0f else 1f / (scaleMax - scaleMin) - - val v0 = data[(0 + valuesOffset) % valuesCount] - var t0 = 0f - // Point in the normalized space of our target rectangle - val tp0 = Vec2(t0, 1f - saturate((v0 - scaleMin) * invScale)) - val histogramZeroLineT = if (scaleMin * scaleMax < 0f) (1 + scaleMin * invScale) else if (scaleMin < 0f) 0f else 1f // Where does the zero line stands - - val colBase = (if (plotType == PlotType.Lines) Col.PlotLines else Col.PlotHistogram).u32 - val colHovered = (if (plotType == PlotType.Lines) Col.PlotLinesHovered else Col.PlotHistogramHovered).u32 - - for (n in 0 until resW) { - val t1 = t0 + tStep - val v1Idx = (t0 * itemCount + 0.5f).i - assert(v1Idx in 0 until valuesCount) - val v1 = data[(v1Idx + valuesOffset + 1) % valuesCount] - val tp1 = Vec2(t1, 1f - saturate((v1 - scaleMin) * invScale)) - - // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU. - val pos0 = innerBb.min.lerp(innerBb.max, tp0) - val pos1 = innerBb.min.lerp(innerBb.max, if (plotType == PlotType.Lines) tp1 else Vec2(tp1.x, histogramZeroLineT)) - when (plotType) { - PlotType.Lines -> window.drawList.addLine(pos0, pos1, if (idxHovered == v1Idx) colHovered else colBase) - PlotType.Histogram -> { - if (pos1.x >= pos0.x + 2f) pos1.x -= 1f - window.drawList.addRectFilled(pos0, pos1, if (idxHovered == v1Idx) colHovered else colBase) - } + val tStep = 1f / resW + val invScale = if (scaleMin == scaleMax) 0f else 1f / (scaleMax - scaleMin) + + val v0 = data((0 + valuesOffset) % valuesCount) + var t0 = 0f + // Point in the normalized space of our target rectangle + val tp0 = Vec2(t0, 1f - saturate((v0 - scaleMin) * invScale)) + val histogramZeroLineT = if (scaleMin * scaleMax < 0f) (1 + scaleMin * invScale) else if (scaleMin < 0f) 0f else 1f // Where does the zero line stands + + val colBase = (if (plotType == PlotType.Lines) Col.PlotLines else Col.PlotHistogram).u32 + val colHovered = (if (plotType == PlotType.Lines) Col.PlotLinesHovered else Col.PlotHistogramHovered).u32 + + for (n in 0 until resW) { + val t1 = t0 + tStep + val v1Idx = (t0 * itemCount + 0.5f).i + assert(v1Idx in 0 until valuesCount) + val v1 = data((v1Idx + valuesOffset + 1) % valuesCount) + val tp1 = Vec2(t1, 1f - saturate((v1 - scaleMin) * invScale)) + + // NB: Draw calls are merged together by the DrawList system. Still, we should render our batch are lower level to save a bit of CPU. + val pos0 = innerBb.min.lerp(innerBb.max, tp0) + val pos1 = innerBb.min.lerp(innerBb.max, if (plotType == PlotType.Lines) tp1 else Vec2(tp1.x, histogramZeroLineT)) + when (plotType) { + PlotType.Lines -> window.drawList.addLine(pos0, pos1, if (idxHovered == v1Idx) colHovered else colBase) + PlotType.Histogram -> { + if (pos1.x >= pos0.x + 2f) pos1.x -= 1f + window.drawList.addRectFilled(pos0, pos1, if (idxHovered == v1Idx) colHovered else colBase) } - t0 = t1 - tp0 put tp1 } + t0 = t1 + tp0 put tp1 } - // Text overlay - if (overlayText.isNotEmpty()) - renderTextClipped(Vec2(frameBb.min.x, frameBb.min.y + style.framePadding.y), frameBb.max, overlayText, null, Vec2(0.5f, 0f)) - if (labelSize.x > 0f) - renderText(Vec2(frameBb.max.x + style.itemInnerSpacing.x, innerBb.min.y), label) - - // Return hovered index or -1 if none are hovered. - // This is currently not exposed in the public API because we need a larger redesign of the whole thing, but in the short-term we are making it available in PlotEx(). - return idxHovered } + // Text overlay + if (overlayText.isNotEmpty()) renderTextClipped(Vec2(frameBb.min.x, frameBb.min.y + style.framePadding.y), frameBb.max, overlayText, null, Vec2(0.5f, 0f)) + if (labelSize.x > 0f) renderText(Vec2(frameBb.max.x + style.itemInnerSpacing.x, innerBb.min.y), label) + + // Return hovered index or -1 if none are hovered. + // This is currently not exposed in the public API because we need a larger redesign of the whole thing, but in the short-term we are making it available in PlotEx(). + return idxHovered } \ No newline at end of file diff --git a/core/src/main/kotlin/imgui/internal/api/popupsModalsTooltips.kt b/core/src/main/kotlin/imgui/internal/api/popupsModalsTooltips.kt index 776397b3e..99a1aa774 100644 --- a/core/src/main/kotlin/imgui/internal/api/popupsModalsTooltips.kt +++ b/core/src/main/kotlin/imgui/internal/api/popupsModalsTooltips.kt @@ -30,6 +30,8 @@ import imgui.static.navCalcPreferredRefPos import imgui.static.navRestoreLastChildNavWindow import uno.kotlin.getValue import uno.kotlin.setValue +import kotlin.math.max +import kotlin.math.min import kotlin.reflect.KMutableProperty0 import imgui.WindowFlag as Wf diff --git a/core/src/main/kotlin/imgui/internal/api/renderHelpers.kt b/core/src/main/kotlin/imgui/internal/api/renderHelpers.kt index 311394d23..e6098fe5c 100644 --- a/core/src/main/kotlin/imgui/internal/api/renderHelpers.kt +++ b/core/src/main/kotlin/imgui/internal/api/renderHelpers.kt @@ -20,6 +20,7 @@ import imgui.internal.classes.Rect import imgui.internal.sections.* import unsigned.toUInt import kotlin.math.max +import kotlin.math.min /** Render helpers * AVOID USING OUTSIDE OF IMGUI.CPP! NOT FOR PUBLIC CONSUMPTION. THOSE FUNCTIONS ARE A MESS. THEIR SIGNATURE AND BEHAVIOR WILL CHANGE, THEY NEED TO BE REFACTORED INTO SOMETHING DECENT. @@ -366,7 +367,7 @@ internal interface renderHelpers { @Deprecated("placeholder: pos gets modified!") fun DrawList.renderCheckMark(pos: Vec2, col: Int, sz_: Float) { - val thickness = imgui.max(sz_ / 5f, 1f) + val thickness = max(sz_ / 5f, 1f) val sz = sz_ - thickness * 0.5f pos += thickness * 0.25f diff --git a/core/src/main/kotlin/imgui/internal/api/tablesInternal.kt b/core/src/main/kotlin/imgui/internal/api/tablesInternal.kt index 72fa80c74..378766f02 100644 --- a/core/src/main/kotlin/imgui/internal/api/tablesInternal.kt +++ b/core/src/main/kotlin/imgui/internal/api/tablesInternal.kt @@ -23,6 +23,8 @@ import imgui.internal.sections.ItemFlag import imgui.internal.sections.NavLayer import imgui.static.* import kool.BYTES +import kotlin.math.max +import kotlin.math.min import imgui.TableFlag as Tf import imgui.TableRowFlag as Trf import imgui.WindowFlag as Wf diff --git a/core/src/main/kotlin/imgui/internal/api/templateFunctions.kt b/core/src/main/kotlin/imgui/internal/api/templateFunctions.kt index 7632f0739..b710c084f 100644 --- a/core/src/main/kotlin/imgui/internal/api/templateFunctions.kt +++ b/core/src/main/kotlin/imgui/internal/api/templateFunctions.kt @@ -1,19 +1,28 @@ -@file:Suppress("UNCHECKED_CAST") - +@file:Suppress("NAME_SHADOWING") package imgui.internal.api -import glm_.* +import glm_.f +import glm_.max +import glm_.pow import imgui.* +import imgui.ImGui.calcItemWidth import imgui.ImGui.checkbox import imgui.ImGui.clearActiveID import imgui.ImGui.currentWindow +import imgui.ImGui.findRenderedTextEnd import imgui.ImGui.getNavTweakPressedAmount import imgui.ImGui.io import imgui.ImGui.isDown import imgui.ImGui.isMouseDragPastThreshold import imgui.ImGui.isMousePosValid +import imgui.ImGui.popItemWidth +import imgui.ImGui.pushMultiItemsWidths +import imgui.ImGui.sameLine import imgui.ImGui.style +import imgui.ImGui.textEx import imgui.api.g +import imgui.dsl.group +import imgui.dsl.withID import imgui.internal.* import imgui.internal.classes.Rect import imgui.internal.sections.Axis @@ -22,2143 +31,254 @@ import imgui.internal.sections.ItemFlag import imgui.internal.sections.get import imgui.static.DRAG_MOUSE_THRESHOLD_FACTOR import imgui.static.getMinimumStepAtDecimalPrecision -import unsigned.Uint -import unsigned.Ulong -import kotlin.math.abs -import kotlin.math.ln -import kotlin.math.pow -import kotlin.reflect.KMutableProperty0 - -/** Template functions are instantiated in imgui_widgets.cpp for a finite number of types. - * To use them externally (for custom widget) you may need an "extern template" statement in your code in order to link to existing instances and silence Clang warnings (see #2036). - * e.g. " extern template IMGUI_API float RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, float v); " */ -internal interface templateFunctions { - - /* - [JVM] I first tried with generics, but it's quite hard and error prone. - Therefore I manually implemented all the required templates. - `DataType` is actually superfluous, but I keep it anyway to reduce distance with the native imgui - */ - - - /** Convert a value v in the output space of a slider into a parametric position on the slider itself - * (the logical opposite of scaleValueFromRatioT) - * template */ - fun scaleRatioFromValueT(dataType: DataType, v: Int, vMin_: Int, vMax_: Int, isLogarithmic: Boolean, - logarithmicZeroEpsilon: Float, zeroDeadzoneHalfsize: Float): Float { - - var vMin = vMin_ - var vMax = vMax_ - - if (vMin == vMax) - return 0f - - val vClamped = if (vMin < vMax) clamp(v, vMin, vMax) else clamp(v, vMax, vMin) - return when { - isLogarithmic -> { - val flipped = vMax < vMin - - if (flipped) { // Handle the case where the range is backwards - val t = vMin - vMin = vMax - vMax = t - } - - // Fudge min/max to avoid getting close to log(0) - var vMinFudged = when { - abs(vMin.f) < logarithmicZeroEpsilon -> if (vMin < 0f) -logarithmicZeroEpsilon else logarithmicZeroEpsilon - else -> vMin.f - } - var vMaxFudged = when { - abs(vMax.f) < logarithmicZeroEpsilon -> if (vMax < 0f) -logarithmicZeroEpsilon else logarithmicZeroEpsilon - else -> vMax.f - } - - // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) - if (vMin == 0 && vMax < 0f) - vMinFudged = -logarithmicZeroEpsilon - else if (vMax == 0 && vMin < 0f) - vMaxFudged = -logarithmicZeroEpsilon - - val result = when { - vClamped <= vMinFudged -> 0f // Workaround for values that are in-range but below our fudge - vClamped >= vMaxFudged -> 1f // Workaround for values that are in-range but above our fudge - (vMin * vMax) < 0f -> { // Range crosses zero, so split into two portions - val zeroPointCenter = -vMin.f / (vMax.f - vMin.f) // The zero point in parametric space. There's an argument we should take the logarithmic nature into account when calculating this, but for now this should do (and the most common case of a symmetrical range works fine) - val zeroPointSnapL = zeroPointCenter - zeroDeadzoneHalfsize - val zeroPointSnapR = zeroPointCenter + zeroDeadzoneHalfsize - when { - v == 0 -> zeroPointCenter // Special case for exactly zero - v < 0f -> (1f - ln(-vClamped.f / logarithmicZeroEpsilon) / ln(-vMinFudged / logarithmicZeroEpsilon)) * zeroPointSnapL - else -> zeroPointSnapR + ln(vClamped.f / logarithmicZeroEpsilon) / ln(vMaxFudged / logarithmicZeroEpsilon) * (1f - zeroPointSnapR) - } - } - // Entirely negative slider - (vMin < 0f) || (vMax < 0f) -> 1f - ln(-vClamped.f / -vMaxFudged) / ln(-vMinFudged / -vMaxFudged) - else -> ln(vClamped.f / vMinFudged) / ln(vMaxFudged / vMinFudged) - } - if (flipped) 1f - result else result - } - // Linear slider - else -> (vClamped - vMin).f / (vMax - vMin).f - } - } - - /** Convert a value v in the output space of a slider into a parametric position on the slider itself - * (the logical opposite of scaleValueFromRatioT) - * template */ - fun scaleRatioFromValueT(dataType: DataType, v: Uint, vMin_: Uint, vMax_: Uint, isLogarithmic: Boolean, - logarithmicZeroEpsilon: Float, zeroDeadzoneHalfsize: Float): Float { - - var vMin = vMin_ - var vMax = vMax_ - - if (vMin == vMax) - return 0f - - val vClamped = if (vMin < vMax) clamp(v, vMin, vMax) else clamp(v, vMax, vMin) - return when { - isLogarithmic -> { - val flipped = vMax < vMin - - if (flipped) { // Handle the case where the range is backwards - val t = vMin - vMin = vMax - vMax = t - } - - // Fudge min/max to avoid getting close to log(0) - val vMinFudged = when { - abs(vMin.f) < logarithmicZeroEpsilon -> /*if (vMin < 0f) -logarithmicZeroEpsilon else */logarithmicZeroEpsilon - else -> vMin.f - } - val vMaxFudged = when { - abs(vMax.f) < logarithmicZeroEpsilon -> /*if (vMax < 0f) -logarithmicZeroEpsilon else */logarithmicZeroEpsilon - else -> vMax.f - } - - // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) - // if (vMin.v == 0 && vMax < 0f) - // vMinFudged = -logarithmicZeroEpsilon - // else if (vMax.v == 0 && vMin < 0f) - // vMaxFudged = -logarithmicZeroEpsilon - - val result = when { - vClamped <= vMinFudged -> 0f // Workaround for values that are in-range but below our fudge - vClamped >= vMaxFudged -> 1f // Workaround for values that are in-range but above our fudge - // (vMin * vMax) < 0f -> { // Range crosses zero, so split into two portions - // val zeroPoint = - vMin.f / ( vMax.f - vMin.f) // The zero point in parametric space. There's an argument we should take the logarithmic nature into account when calculating this, but for now this should do (and the most common case of a symmetrical range works fine) - // when { - // v.v == 0 -> zeroPoint // Special case for exactly zero - // v < 0f -> (1f - ln(- vClamped.f / logarithmicZeroEpsilon) / ln(-vMinFudged / logarithmicZeroEpsilon)) * zeroPoint - // else -> zeroPoint + ln( vClamped.f / logarithmicZeroEpsilon) / ln(vMaxFudged / logarithmicZeroEpsilon) * (1f - zeroPoint) - // } - // } - // Entirely negative slider - // vMin < Uint(0) || vMax < Uint(0) -> 1f - (float)(ImLog(-(FLOATTYPE) v_clamped / - v_max_fudged) / ImLog(-vMinFudged / -vMaxFudged)) - else -> ln(vClamped.f / vMinFudged) / ln(vMaxFudged / vMinFudged) - } - if (flipped) 1f - result else result - } - // Linear slider - else -> (vClamped - vMin).v.f / (vMax - vMin).v.f - } - } - - /** Convert a value v in the output space of a slider into a parametric position on the slider itself - * (the logical opposite of scaleValueFromRatioT) - * template */ - fun scaleRatioFromValueT(dataType: DataType, v: Long, vMin_: Long, vMax_: Long, isLogarithmic: Boolean, - logarithmicZeroEpsilon: Float, zeroDeadzoneHalfsize: Float): Float { - - var vMin = vMin_ - var vMax = vMax_ - - if (vMin == vMax) - return 0f - - val vClamped = if (vMin < vMax) clamp(v, vMin, vMax) else clamp(v, vMax, vMin) - return when { - isLogarithmic -> { - val flipped = vMax < vMin - - if (flipped) { // Handle the case where the range is backwards - val t = vMin - vMin = vMax - vMax = t - } - - // Fudge min/max to avoid getting close to log(0) - var vMinFudged = when { - abs(vMin.d) < logarithmicZeroEpsilon -> if (vMin < 0) -logarithmicZeroEpsilon else logarithmicZeroEpsilon - else -> vMin - }.d - var vMaxFudged = when { - abs(vMax.d) < logarithmicZeroEpsilon -> if (vMax < 0) -logarithmicZeroEpsilon else logarithmicZeroEpsilon - else -> vMax - }.d - - // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) - if (vMin == 0L && vMax < 0L) - vMinFudged = -logarithmicZeroEpsilon.d - else if (vMax == 0L && vMin < 0L) - vMaxFudged = -logarithmicZeroEpsilon.d - - val result = when { - vClamped <= vMinFudged -> 0f // Workaround for values that are in-range but below our fudge - vClamped >= vMaxFudged -> 1f // Workaround for values that are in-range but above our fudge - (vMin * vMax) < 0L -> { // Range crosses zero, so split into two portions - val zeroPointCenter = -vMin.f / (vMax.f - vMin.f) // The zero point in parametric space. There's an argument we should take the logarithmic nature into account when calculating this, but for now this should do (and the most common case of a symmetrical range works fine) - val zeroPointSnapL = zeroPointCenter - zeroDeadzoneHalfsize - val zeroPointSnapR = zeroPointCenter + zeroDeadzoneHalfsize - when { - v == 0L -> zeroPointCenter // Special case for exactly zero - v < 0L -> (1f - (ln(-vClamped.f / logarithmicZeroEpsilon) / ln(-vMinFudged / logarithmicZeroEpsilon)).f) * zeroPointSnapL - else -> zeroPointSnapR + ((ln(vClamped.f / logarithmicZeroEpsilon) / ln(vMaxFudged / logarithmicZeroEpsilon)).f * (1f - zeroPointSnapR)) - } - } - // Entirely negative slider - (vMin < 0L || vMax < 0L) -> 1f - (ln(-vClamped.f / -vMaxFudged) / ln(-vMinFudged / -vMaxFudged)).f - else -> (ln(vClamped.f / vMinFudged) / ln(vMaxFudged / vMinFudged)).f - } - if (flipped) 1f - result else result - } - // Linear slider - else -> ((vClamped - vMin).d / (vMax - vMin).d).f - } - } - - /** Convert a value v in the output space of a slider into a parametric position on the slider itself - * (the logical opposite of scaleValueFromRatioT) - * template */ - fun scaleRatioFromValueT(dataType: DataType, v: Ulong, vMin_: Ulong, vMax_: Ulong, isLogarithmic: Boolean, - logarithmicZeroEpsilon: Float, zeroDeadzoneHalfsize: Float): Float { - - var vMin = vMin_ - var vMax = vMax_ - if (vMin == vMax) - return 0f - - val vClamped = if (vMin < vMax) clamp(v, vMin, vMax) else clamp(v, vMax, vMin) - return when { - isLogarithmic -> { - val flipped = vMax < vMin - - if (flipped) { // Handle the case where the range is backwards - val t = vMin - vMin = vMax - vMax = t - } - - // Fudge min/max to avoid getting close to log(0) - val vMinFudged = when { - abs(vMin.d) < logarithmicZeroEpsilon -> /*if(vMin < 0) -logarithmicZeroEpsilon else */logarithmicZeroEpsilon - else -> vMin - }.d - val vMaxFudged = when { - abs(vMax.d) < logarithmicZeroEpsilon -> /*if(vMax < 0) -logarithmicZeroEpsilon else */logarithmicZeroEpsilon - else -> vMax - }.d - - // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) - // if (vMin.v == 0L && (v_max < 0.0f)) - // vMinFudged = -logarithmic_zero_epsilon; - // else if ((v_max == 0.0f) && (v_min < 0.0f)) - // vMaxFudged = -logarithmic_zero_epsilon; - - val result = when { - vClamped <= vMinFudged.ul -> 0f // Workaround for values that are in-range but below our fudge - vClamped >= vMaxFudged.ul -> 1f // Workaround for values that are in-range but above our fudge - // else if ((v_min * v_max) < 0.0f) // Range crosses zero, so split into two portions - // { - // float zero_point =(-(float) v_min) / ((float) v_max -(float) v_min) // The zero point in parametric space. There's an argument we should take the logarithmic nature into account when calculating this, but for now this should do (and the most common case of a symmetrical range works fine) - // if (v == 0.0f) - // result = zero_point // Special case for exactly zero - // else if (v < 0.0f) - // result = (1.0f - (float)(ImLog(-(FLOATTYPE) v_clamped / logarithmic_zero_epsilon) / ImLog(-vMinFudged / logarithmic_zero_epsilon))) * zero_point - // else - // result = zero_point + ((float)(ImLog((FLOATTYPE) v_clamped / logarithmic_zero_epsilon) / ImLog(vMaxFudged / logarithmic_zero_epsilon)) * (1.0f - zero_point)) - // } - // else if ((v_min < 0.0f) || (v_max < 0.0f)) // Entirely negative slider - // result = 1.0f - (float)(ImLog(-(FLOATTYPE) v_clamped / - v_max_fudged) / ImLog(-vMinFudged / -vMaxFudged)) - else -> (ln(vClamped.d / vMinFudged) / ln(vMaxFudged / vMinFudged)).f - } - if (flipped) 1f - result else result - } - // Linear slider - else -> ((vClamped - vMin).v.d / (vMax - vMin).v.d).f - } - } - - /** Convert a value v in the output space of a slider into a parametric position on the slider itself - * (the logical opposite of scaleValueFromRatioT) - * template */ - fun scaleRatioFromValueT(dataType: DataType, v: Float, vMin_: Float, vMax_: Float, isLogarithmic: Boolean, - logarithmicZeroEpsilon: Float, zeroDeadzoneHalfsize: Float): Float { - - var vMin = vMin_ - var vMax = vMax_ - if (vMin == vMax) - return 0f - - val vClamped = if (vMin < vMax) clamp(v, vMin, vMax) else clamp(v, vMax, vMin) - return when { - isLogarithmic -> { - val flipped = vMax < vMin - - if (flipped) { // Handle the case where the range is backwards - val t = vMin - vMin = vMax - vMax = t - } - - // Fudge min/max to avoid getting close to log(0) - var vMinFudged = when { - abs(vMin) < logarithmicZeroEpsilon -> if (vMin < 0f) -logarithmicZeroEpsilon else logarithmicZeroEpsilon - else -> vMin - } - var vMaxFudged = when { - abs(vMax) < logarithmicZeroEpsilon -> if (vMax < 0f) -logarithmicZeroEpsilon else logarithmicZeroEpsilon - else -> vMax - } - - // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) - if (vMin == 0f && vMax < 0f) - vMinFudged = -logarithmicZeroEpsilon - else if (vMax == 0f && vMin < 0f) - vMaxFudged = -logarithmicZeroEpsilon - - val result = when { - vClamped <= vMinFudged -> 0f // Workaround for values that are in-range but below our fudge - vClamped >= vMaxFudged -> 1f // Workaround for values that are in-range but above our fudge - (vMin * vMax) < 0f -> { // Range crosses zero, so split into two portions - val zeroPointCenter = -vMin / (vMax - vMin) // The zero point in parametric space. There's an argument we should take the logarithmic nature into account when calculating this, but for now this should do (and the most common case of a symmetrical range works fine) - val zeroPointSnapL = zeroPointCenter - zeroDeadzoneHalfsize - val zeroPointSnapR = zeroPointCenter + zeroDeadzoneHalfsize - when { - v == 0f -> zeroPointCenter // Special case for exactly zero - v < 0f -> (1f - ln(-vClamped / logarithmicZeroEpsilon) / ln(-vMinFudged / logarithmicZeroEpsilon)) * zeroPointSnapL - else -> zeroPointSnapR + (ln(vClamped / logarithmicZeroEpsilon) / ln(vMaxFudged / logarithmicZeroEpsilon) * (1f - zeroPointSnapR)) - } - } - // Entirely negative slider - vMin < 0f || vMax < 0f -> 1f - ln(-vClamped / -vMaxFudged) / ln(-vMinFudged / -vMaxFudged) - else -> ln(vClamped / vMinFudged) / ln(vMaxFudged / vMinFudged) - } - if (flipped) 1f - result else result - } - // Linear slider - else -> (vClamped - vMin) / (vMax - vMin) - } - } - - /** Convert a value v in the output space of a slider into a parametric position on the slider itself - * (the logical opposite of scaleValueFromRatioT) - * template */ - fun scaleRatioFromValueT(dataType: DataType, v: Double, vMin_: Double, vMax_: Double, isLogarithmic: Boolean, - logarithmicZeroEpsilon: Float, zeroDeadzoneHalfsize: Float): Float { - - var vMin = vMin_ - var vMax = vMax_ - if (vMin == vMax) - return 0f - - val vClamped = if (vMin < vMax) clamp(v, vMin, vMax) else clamp(v, vMax, vMin) - return when { - isLogarithmic -> { - val flipped = vMax < vMin - - if (flipped) { // Handle the case where the range is backwards - val t = vMin - vMin = vMax - vMax = t - } - - // Fudge min/max to avoid getting close to log(0) - var vMinFudged = when { - abs(vMin) < logarithmicZeroEpsilon -> (if (vMin < 0f) -logarithmicZeroEpsilon else logarithmicZeroEpsilon).d - else -> vMin - } - var vMaxFudged = when { - abs(vMax) < logarithmicZeroEpsilon -> (if (vMax < 0f) -logarithmicZeroEpsilon else logarithmicZeroEpsilon).d - else -> vMax - } - - // Awkward special cases - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) - if (vMin == 0.0 && vMax < 0f) - vMinFudged = -logarithmicZeroEpsilon.d - else if (vMax == 0.0 && vMin < 0f) - vMaxFudged = -logarithmicZeroEpsilon.d - - val result = when { - vClamped <= vMinFudged -> 0f // Workaround for values that are in-range but below our fudge - vClamped >= vMaxFudged -> 1f // Workaround for values that are in-range but above our fudge - (vMin * vMax) < 0f -> { // Range crosses zero, so split into two portions - val zeroPointCenter = -vMin.f / (vMax.f - vMin.f) // The zero point in parametric space. There's an argument we should take the logarithmic nature into account when calculating this, but for now this should do (and the most common case of a symmetrical range works fine) - val zeroPointSnapL = zeroPointCenter - zeroDeadzoneHalfsize - val zeroPointSnapR = zeroPointCenter + zeroDeadzoneHalfsize - when { - v == 0.0 -> zeroPointCenter // Special case for exactly zero - v < 0f -> (1f - (ln(-vClamped / logarithmicZeroEpsilon) / ln(-vMinFudged / logarithmicZeroEpsilon)).f) * zeroPointSnapL - else -> zeroPointSnapR + (ln(vClamped / logarithmicZeroEpsilon) / ln(vMaxFudged / logarithmicZeroEpsilon)).f * (1f - zeroPointSnapR) - } - } - // Entirely negative slider - vMin < 0f || vMax < 0f -> 1f - (ln(-vClamped / -vMaxFudged) / ln(-vMinFudged / -vMaxFudged)).f - else -> (ln(vClamped / vMinFudged) / ln(vMaxFudged / vMinFudged)).f - } - if (flipped) 1f - result else result - } - // Linear slider - else -> ((vClamped - vMin).d / (vMax - vMin).d).f - } - } - - - /** Convert a parametric position on a slider into a value v in the output space (the logical opposite of scaleRatioFromValueT) - * template */ - fun scaleValueFromRatioT(dataType: DataType, t: Float, vMin: Int, vMax: Int, isLogarithmic: Boolean, - logarithmicZeroEpsilon: Float, zeroDeadzoneHalfsize: Float): Int = when { - - // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct" - // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler. - t <= 0f || vMin == vMax -> vMin - t >= 1f -> vMax - isLogarithmic -> { - // Fudge min/max to avoid getting silly results close to zero - var vMinFudged = when { - abs(vMin.f) < logarithmicZeroEpsilon -> if (vMin < 0f) -logarithmicZeroEpsilon else logarithmicZeroEpsilon - else -> vMin.f - } - var vMaxFudged = when { - abs(vMax.f) < logarithmicZeroEpsilon -> if (vMax < 0f) -logarithmicZeroEpsilon else logarithmicZeroEpsilon - else -> vMax.f - } - - val flipped = vMax < vMin // Check if range is "backwards" - if (flipped) { - val swap = vMinFudged - vMinFudged = vMaxFudged - vMaxFudged = swap - } - - // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) - if (vMax == 0 && vMin < 0f) - vMaxFudged = -logarithmicZeroEpsilon - - val tWithFlip = if (flipped) 1f - t else t // t, but flipped if necessary to account for us flipping the range - - when { - vMin * vMax < 0f -> { // Range crosses zero, so we have to do this in two parts - val zeroPointCenter = -(vMin min vMax).f / abs(vMax.f - vMin.f) // The zero point in parametric space - val zeroPointSnapL = zeroPointCenter - zeroDeadzoneHalfsize - val zeroPointSnapR = zeroPointCenter + zeroDeadzoneHalfsize - when { - tWithFlip in zeroPointSnapL..zeroPointSnapR -> 0 // Special case to make getting exactly zero possible (the epsilon prevents it otherwise) - tWithFlip < zeroPointCenter -> -(logarithmicZeroEpsilon * (-vMinFudged / logarithmicZeroEpsilon).pow(1f - tWithFlip / zeroPointSnapL)).i - else -> (logarithmicZeroEpsilon * (vMaxFudged / logarithmicZeroEpsilon).pow((tWithFlip - zeroPointSnapR) / (1f - zeroPointSnapR))).i - } - } - // Entirely negative slider - vMin < 0 || vMax < 0 -> (-(-vMaxFudged * (-vMinFudged / -vMaxFudged).pow(1f - tWithFlip))).i - else -> (vMinFudged * (vMaxFudged / vMinFudged).pow(tWithFlip)).i - } - } - // Linear slider - // ~isFloatingPoint - dataType == DataType.Float || dataType == DataType.Double -> lerp(vMin, vMax, t) - // - For integer values we want the clicking position to match the grab box so we round above - // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property.. - // - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64 - // range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits. - t < 1f -> { - val vNewOffF = (vMax - vMin) * t - vMin + (vNewOffF + if (vMin > vMax) -0.5f else 0.5f).i - } - else -> 0 - } - - /** Convert a parametric position on a slider into a value v in the output space (the logical opposite of scaleRatioFromValueT) - * template */ - fun scaleValueFromRatioT(dataType: DataType, t: Float, vMin: Uint, vMax: Uint, isLogarithmic: Boolean, - logarithmicZeroEpsilon: Float, zeroDeadzoneHalfsize: Float): Uint = when { - - t <= 0f || vMin == vMax -> vMin - t >= 1f -> vMax - isLogarithmic -> { - // Fudge min/max to avoid getting silly results close to zero - var vMinFudged = when { - abs(vMin.f) < logarithmicZeroEpsilon -> /*(v_min < 0.0f)? -logarithmic_zero_epsilon :*/ logarithmicZeroEpsilon - else -> vMin.f - } - var vMaxFudged = when { - abs(vMax.f) < logarithmicZeroEpsilon -> /*((v_max < 0.0f) ? -logarithmic_zero_epsilon :*/ logarithmicZeroEpsilon - else -> vMax.f - } - - val flipped = vMax < vMin // Check if range is "backwards" - if (flipped) { - val swap = vMinFudged - vMinFudged = vMaxFudged - vMaxFudged = swap - } - - // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) - // if ((vMax == 0.0f) && (vMin < 0.0f)) - // vMaxFudged = -logarithmicZeroEpsilon - - val tWithFlip = if (flipped) 1f - t else t // t, but flipped if necessary to account for us flipping the range - - // if ((vMin * vMax) < 0.0f) // Range crosses zero, so we have to do this in two parts - // { - // float zero_point =(-(float) ImMin (vMin, vMax)) / ImAbs((float)v_max-(float)v_min) // The zero point in parametric space - // if (tWithFlip == zero_point) - // result = (TYPE)0.0f // Special case to make getting exactly zero possible (the epsilon prevents it otherwise) - // else if (tWithFlip < zero_point) - // result = (TYPE) - (logarithmicZeroEpsilon * ImPow(-vMinFudged / logarithmicZeroEpsilon, (FLOATTYPE)(1.0f - (tWithFlip / zero_point)))) - // else - // result = (TYPE)(logarithmicZeroEpsilon * ImPow(vMaxFudged / logarithmicZeroEpsilon, (FLOATTYPE)((tWithFlip - zero_point) / (1.0f - zero_point)))) - // } else if ((vMin < 0.0f) || (vMax < 0.0f)) // Entirely negative slider - // result = (TYPE) - (-vMaxFudged * ImPow(-vMinFudged / -vMaxFudged, (FLOATTYPE)(1.0f - tWithFlip))) - // else - Uint(vMinFudged * (vMaxFudged / vMinFudged).pow(tWithFlip)) - } - // Linear slider - // ~isFloatingPoint - dataType == DataType.Float || dataType == DataType.Double -> lerp(vMin, vMax, t) - // - For integer values we want the clicking position to match the grab box so we round above - // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property.. - // - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64 - // range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits. - t < 1f -> { - val vNewOffF = (vMax - vMin).v * t - vMin + (vNewOffF + if (vMin > vMax) -0.5f else 0.5f).i - } - else -> Uint(0) - } - - /** Convert a parametric position on a slider into a value v in the output space (the logical opposite of scaleRatioFromValueT) - * template */ - fun scaleValueFromRatioT(dataType: DataType, t: Float, vMin: Long, vMax: Long, isLogarithmic: Boolean, - logarithmicZeroEpsilon: Float, zeroDeadzoneHalfsize: Float): Long = when { - - // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct" - // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler. - t <= 0.0 || vMin == vMax -> vMin - t >= 1.0 -> vMax - isLogarithmic -> { - // Fudge min/max to avoid getting silly results close to zero - var vMinFudged = when { - abs(vMin.d) < logarithmicZeroEpsilon -> if (vMin < 0L) -logarithmicZeroEpsilon else logarithmicZeroEpsilon - else -> vMin - }.d - var vMaxFudged = when { - abs(vMax.d) < logarithmicZeroEpsilon -> if (vMax < 0L) -logarithmicZeroEpsilon else logarithmicZeroEpsilon - else -> vMax - }.d - - val flipped = vMax < vMin // Check if range is "backwards" - if (flipped) { - val swap = vMinFudged - vMinFudged = vMaxFudged - vMaxFudged = swap - } - - // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) - if (vMax == 0L && vMin < 0L) - vMaxFudged = -logarithmicZeroEpsilon.d - - val tWithFlip = if (flipped) 1f - t else t // t, but flipped if necessary to account for us flipping the range - - when { - vMin * vMax < 0L -> { // Range crosses zero, so we have to do this in two parts - val zeroPointCenter = -(vMin min vMax).f / abs(vMax.f - vMin.f) // The zero point in parametric space - val zeroPointSnapL = zeroPointCenter - zeroDeadzoneHalfsize - val zeroPointSnapR = zeroPointCenter + zeroDeadzoneHalfsize - when { - tWithFlip in zeroPointSnapL..zeroPointSnapR -> 0L // Special case to make getting exactly zero possible (the epsilon prevents it otherwise) - tWithFlip < zeroPointCenter -> -(logarithmicZeroEpsilon * (-vMinFudged / logarithmicZeroEpsilon).pow((1f - tWithFlip / zeroPointSnapL).d)).L - else -> (logarithmicZeroEpsilon * (vMaxFudged / logarithmicZeroEpsilon).pow(((tWithFlip - zeroPointSnapR) / (1f - zeroPointSnapR)).d)).L - } - } - // Entirely negative slider - vMin < 0L || vMax < 0L -> (-(-vMaxFudged * (-vMinFudged / -vMaxFudged.pow((1.0 - tWithFlip))))).L - else -> (vMinFudged * (vMaxFudged / vMinFudged).pow(tWithFlip.d)).L - } - } - // Linear slider - // ~isFloatingPoint - dataType == DataType.Float || dataType == DataType.Double -> lerp(vMin, vMax, t) - // - For integer values we want the clicking position to match the grab box so we round above - // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property.. - // - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64 - // range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits. - t < 1f -> { - val vNewOffF = (vMax - vMin) * t - vMin + (vNewOffF.d + if (vMin > vMax) -0.5 else 0.5).L - } - else -> 0L - } - - /** Convert a parametric position on a slider into a value v in the output space (the logical opposite of scaleRatioFromValueT) - * template */ - fun scaleValueFromRatioT(dataType: DataType, t: Float, vMin: Ulong, vMax: Ulong, isLogarithmic: Boolean, - logarithmicZeroEpsilon: Float, zeroDeadzoneHalfsize: Float): Ulong = when { - - // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct" - // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler. - t <= 0.0 || vMin == vMax -> vMin - t >= 1.0 -> vMax - isLogarithmic -> { - // Fudge min/max to avoid getting silly results close to zero - var vMinFudged = when { - abs(vMin.d) < logarithmicZeroEpsilon -> /*((v_min < 0.0f) ? -logarithmic_zero_epsilon :*/ logarithmicZeroEpsilon - else -> vMin - }.d - var vMaxFudged = when { - abs(vMax.d) < logarithmicZeroEpsilon -> /*((v_max < 0.0f) ? -logarithmic_zero_epsilon :*/ logarithmicZeroEpsilon - else -> vMax - }.d - - val flipped = vMax < vMin // Check if range is "backwards" - if (flipped) { - val swap = vMinFudged - vMinFudged = vMaxFudged - vMaxFudged = swap - } - - // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) - // if ((vMax == 0.0f) && (vMin < 0.0f)) - // vMaxFudged = -logarithmicZeroEpsilon - - val tWithFlip = if (flipped) 1f - t else t // t, but flipped if necessary to account for us flipping the range - - // if ((vMin * vMax) < 0.0f) // Range crosses zero, so we have to do this in two parts - // { - // float zero_point = (-(float)ImMin(vMin, vMax)) / ImAbs((float)v_max - (float)v_min) // The zero point in parametric space - // if (tWithFlip == zero_point) - // result = (TYPE)0.0f // Special case to make getting exactly zero possible (the epsilon prevents it otherwise) - // else if (tWithFlip < zero_point) - // result = (TYPE)-(logarithmicZeroEpsilon * ImPow(-vMinFudged / logarithmicZeroEpsilon, (FLOATTYPE)(1.0f - (tWithFlip / zero_point)))) - // else - // result = (TYPE)(logarithmicZeroEpsilon * ImPow(vMaxFudged / logarithmicZeroEpsilon, (FLOATTYPE)((tWithFlip - zero_point) / (1.0f - zero_point)))) - // } - // else if ((vMin < 0.0f) || (vMax < 0.0f)) // Entirely negative slider - // result = (TYPE)-(-vMaxFudged * ImPow(-vMinFudged / -vMaxFudged, (FLOATTYPE)(1.0f - tWithFlip))) - // else - Ulong(vMinFudged * (vMaxFudged / vMinFudged).pow(tWithFlip.d)) - } - // Linear slider - // isFloatingPoint - dataType == DataType.Float || dataType == DataType.Double -> lerp(vMin, vMax, t) - // - For integer values we want the clicking position to match the grab box so we round above - // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property.. - // - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64 - // range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits. - t < 1f -> { - val vNewOffF = (vMax - vMin).v * t - vMin + (vNewOffF.d + if (vMin > vMax) -0.5f else 0.5f).L - } - else -> Ulong(0) - } - - /** Convert a parametric position on a slider into a value v in the output space (the logical opposite of scaleRatioFromValueT) - * template */ - fun scaleValueFromRatioT(dataType: DataType, t: Float, vMin: Float, vMax: Float, isLogarithmic: Boolean, - logarithmicZeroEpsilon: Float, zeroDeadzoneHalfsize: Float): Float = when { - - // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct" - // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler. - t <= 0f || vMin == vMax -> vMin - t >= 1f -> vMax - isLogarithmic -> { - - // Fudge min/max to avoid getting silly results close to zero - var vMinFudged = when { - abs(vMin) < logarithmicZeroEpsilon -> if (vMin < 0f) -logarithmicZeroEpsilon else logarithmicZeroEpsilon - else -> vMin - } - var vMaxFudged = when { - abs(vMax) < logarithmicZeroEpsilon -> if (vMax < 0f) -logarithmicZeroEpsilon else logarithmicZeroEpsilon - else -> vMax - } - - val flipped = vMax < vMin // Check if range is "backwards" - if (flipped) { - val swap = vMinFudged - vMinFudged = vMaxFudged - vMaxFudged = swap - } - - val tWithFlip = if (flipped) 1f - t else t // t, but flipped if necessary to account for us flipping the range - - when { - vMin * vMax < 0f -> { // Range crosses zero, so we have to do this in two parts - val zeroPointCenter = -(vMin min vMax).f / abs(vMax - vMin) // The zero point in parametric space - val zeroPointSnapL = zeroPointCenter - zeroDeadzoneHalfsize - val zeroPointSnapR = zeroPointCenter + zeroDeadzoneHalfsize - when { - tWithFlip in zeroPointSnapL..zeroPointSnapR -> 0f // Special case to make getting exactly zero possible (the epsilon prevents it otherwise) - tWithFlip < zeroPointCenter -> -(logarithmicZeroEpsilon * (-vMinFudged / logarithmicZeroEpsilon).pow(1f - tWithFlip / zeroPointSnapL)) - else -> logarithmicZeroEpsilon * (vMaxFudged / logarithmicZeroEpsilon).pow((tWithFlip - zeroPointSnapR) / (1f - zeroPointSnapR)) - } - } - // Entirely negative slider - vMin < 0f || vMax < 0f -> -(-vMaxFudged * (-vMinFudged / -vMaxFudged).pow(1f - tWithFlip)) - else -> vMinFudged * (vMaxFudged / vMinFudged).pow(tWithFlip) - } - } - // Linear slider - // ~isFloatingPoint - dataType == DataType.Float || dataType == DataType.Double -> lerp(vMin, vMax, t) - // - For integer values we want the clicking position to match the grab box so we round above - // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property.. - // - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64 - // range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits. - t < 1f -> { - val vNewOffF = (vMax - vMin) * t - vMin + (vNewOffF + if (vMin > vMax) -0.5f else 0.5f) - } - else -> 0f - } - - /** Convert a parametric position on a slider into a value v in the output space (the logical opposite of scaleRatioFromValueT) - * template */ - fun scaleValueFromRatioT(dataType: DataType, t: Float, vMin: Double, vMax: Double, isLogarithmic: Boolean, - logarithmicZeroEpsilon: Float, zeroDeadzoneHalfsize: Float): Double = when { - - // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct" - // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler. - t <= 0f || vMin == vMax -> vMin - t >= 1f -> vMax - isLogarithmic -> { - - // Fudge min/max to avoid getting silly results close to zero - var vMinFudged = when { - abs(vMin) < logarithmicZeroEpsilon -> (if (vMin < 0.0) -logarithmicZeroEpsilon else logarithmicZeroEpsilon).d - else -> vMin - } - var vMaxFudged = when { - abs(vMax) < logarithmicZeroEpsilon -> (if (vMax < 0.0) -logarithmicZeroEpsilon else logarithmicZeroEpsilon).d - else -> vMax - } - - val flipped = vMax < vMin // Check if range is "backwards" - if (flipped) { - val swap = vMinFudged - vMinFudged = vMaxFudged - vMaxFudged = swap - } - - val tWithFlip = if (flipped) 1f - t else t // t, but flipped if necessary to account for us flipping the range - - when { - vMin * vMax < 0.0 -> { // Range crosses zero, so we have to do this in two parts - val zeroPointCenter = -(vMin min vMax).f / abs(vMax.f - vMin.f) // The zero point in parametric space - val zeroPointSnapL = zeroPointCenter - zeroDeadzoneHalfsize - val zeroPointSnapR = zeroPointCenter + zeroDeadzoneHalfsize - when { - tWithFlip in zeroPointSnapL..zeroPointSnapR -> 0.0 // Special case to make getting exactly zero possible (the epsilon prevents it otherwise) - tWithFlip < zeroPointCenter -> -(logarithmicZeroEpsilon * (-vMinFudged / logarithmicZeroEpsilon).pow((1f - tWithFlip / zeroPointSnapL).d)) - else -> logarithmicZeroEpsilon * (vMaxFudged / logarithmicZeroEpsilon).pow(((tWithFlip - zeroPointSnapR) / (1f - zeroPointSnapR)).d) - } - } - // Entirely negative slider - vMin < 0.0 || vMax < 0.0 -> -(-vMaxFudged * (-vMinFudged / -vMaxFudged).pow((1f - tWithFlip).d)) - else -> vMinFudged * (vMaxFudged / vMinFudged).pow(tWithFlip.d) - } - } - // Linear slider - // ~isFloatingPoint - dataType == DataType.Float || dataType == DataType.Double -> lerp(vMin, vMax, t) - // - For integer values we want the clicking position to match the grab box so we round above - // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property.. - // - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64 - // range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits. - t < 1f -> { - val vNewOffF = (vMax - vMin) * t - vMin + (vNewOffF + if (vMin > vMax) -0.5 else 0.5) - } - else -> 0.0 - } - - - /** This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls) - * template */ - fun dragBehaviorT(dataType: DataType, v: KMutableProperty0, vSpeed_: Float, - vMin: Int, vMax: Int, format: String, flags: SliderFlags): Boolean { - - val axis = if (flags has SliderFlag._Vertical) Axis.Y else Axis.X - val isClamped = vMin < vMax - val isLogarithmic = flags has SliderFlag.Logarithmic - val isFloatingPoint = dataType == DataType.Float || dataType == DataType.Double - - // Default tweak speed - var vSpeed = when { - vSpeed_ == 0f && isClamped && vMax - vMin < Float.MAX_VALUE -> (vMax - vMin) * g.dragSpeedDefaultRatio - else -> vSpeed_ - } - - // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings - var adjustDelta = 0f - if (g.activeIdSource == InputSource.Mouse && isMousePosValid() && isMouseDragPastThreshold(MouseButton.Left, io.mouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) { - adjustDelta = io.mouseDelta[axis] - if (io.keyAlt) - adjustDelta *= 1f / 100f - if (io.keyShift) - adjustDelta *= 10f - } else if (g.activeIdSource == InputSource.Nav) { - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 0 - val tweakSlow = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakSlow else Key._NavKeyboardTweakSlow).isDown - val tweakFast = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakFast else Key._NavKeyboardTweakFast).isDown - val tweakFactor = if (tweakSlow) 1f / 1f else if (tweakFast) 10f else 1f - adjustDelta = getNavTweakPressedAmount(axis) * tweakFactor - vSpeed = vSpeed max getMinimumStepAtDecimalPrecision(decimalPrecision) - } - adjustDelta *= vSpeed - - // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter. - if (axis == Axis.Y) - adjustDelta = -adjustDelta - - // For logarithmic use our range is effectively 0..1 so scale the delta into that range - if (isLogarithmic && vMax - vMin < Float.MAX_VALUE && vMax - vMin > 0.000001f) // Epsilon to avoid /0 - adjustDelta /= (vMax - vMin).f - - // Clear current value on activation - // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300. - val isJustActivated = g.activeIdIsJustActivated - val isAlreadyPastLimitsAndPushingOutward = isClamped && ((v() >= vMax && adjustDelta > 0f) || (v() <= vMin && adjustDelta < 0f)) - if (isJustActivated || isAlreadyPastLimitsAndPushingOutward) { - g.dragCurrentAccum = 0f - g.dragCurrentAccumDirty = false - } else if (adjustDelta != 0.0f) { - g.dragCurrentAccum += adjustDelta - g.dragCurrentAccumDirty = true - } - - if (!g.dragCurrentAccumDirty) - return false - - var vCur = v() - var vOldRefForAccumRemainder = 0f - - var logarithmicZeroEpsilon = 0f // Only valid when is_logarithmic is true - val zeroDeadzoneHalfsize = 0f // Drag widgets have no deadzone (as it doesn't make sense) - if (isLogarithmic) { - // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 1 - logarithmicZeroEpsilon = 0.1f pow decimalPrecision.f - - // Convert to parametric space, apply delta, convert back - val vOldParametric = scaleRatioFromValueT(dataType, vCur, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - val vNewParametric = vOldParametric + g.dragCurrentAccum - vCur = scaleValueFromRatioT(dataType, vNewParametric, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - vOldRefForAccumRemainder = vOldParametric - } else - vCur += g.dragCurrentAccum.i - - // Round to user desired precision based on format string - // if (isFloatingPoint && flags hasnt SliderFlag.NoRoundToFormat) - // vCur = roundScalarWithFormatT(format, dataType, vCur) - - // Preserve remainder after rounding has been applied. This also allow slow tweaking of values. - g.dragCurrentAccumDirty = false - g.dragCurrentAccum -= when { - isLogarithmic -> { - // Convert to parametric space, apply delta, convert back - val vNewParametric = scaleRatioFromValueT(dataType, vCur, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - (vNewParametric - vOldRefForAccumRemainder).f - } - else -> (vCur - v()).f - } - - // Lose zero sign for float/double - if (vCur == -0) - vCur = 0 - - // Clamp values (+ handle overflow/wrap-around for integer types) - if (v() != vCur && isClamped) { - if (vCur < vMin || (vCur > v() && adjustDelta < 0f && !isFloatingPoint)) - vCur = vMin - if (vCur > vMax || (vCur < v() && adjustDelta > 0f && !isFloatingPoint)) - vCur = vMax - } - - // Apply result - if (v() == vCur) - return false - v(vCur) - return true - } - - /** This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls) - * template */ - fun dragBehaviorT(dataType: DataType, v: KMutableProperty0, vSpeed_: Float, - vMin: Uint, vMax: Uint, format: String, flags: SliderFlags): Boolean { - - val axis = if (flags has SliderFlag._Vertical) Axis.Y else Axis.X - val isClamped = vMin < vMax - val isLogarithmic = flags has SliderFlag.Logarithmic - val isFloatingPoint = dataType == DataType.Float || dataType == DataType.Double - - // Default tweak speed - var vSpeed = when { - vSpeed_ == 0f && isClamped && (vMax - vMin < Float.MAX_VALUE) -> (vMax - vMin).f * g.dragSpeedDefaultRatio - else -> vSpeed_ - } - - // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings - var adjustDelta = 0f - if (g.activeIdSource == InputSource.Mouse && isMousePosValid() && isMouseDragPastThreshold(MouseButton.Left, io.mouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) { - adjustDelta = io.mouseDelta[axis] - if (io.keyAlt) - adjustDelta *= 1f / 100f - if (io.keyShift) - adjustDelta *= 10f - } else if (g.activeIdSource == InputSource.Nav) { - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 0 - val tweakSlow = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakSlow else Key._NavKeyboardTweakSlow).isDown - val tweakFast = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakFast else Key._NavKeyboardTweakFast).isDown - val tweakFactor = if (tweakSlow) 1f / 1f else if (tweakFast) 10f else 1f - adjustDelta = getNavTweakPressedAmount(axis) * tweakFactor - vSpeed = vSpeed max getMinimumStepAtDecimalPrecision(decimalPrecision) - } - adjustDelta *= vSpeed - - // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter. - if (axis == Axis.Y) - adjustDelta = -adjustDelta - - // For logarithmic use our range is effectively 0..1 so scale the delta into that range - if (isLogarithmic && vMax - vMin < Float.MAX_VALUE && vMax - vMin > 0.000001f) // Epsilon to avoid /0 - adjustDelta /= (vMax - vMin).f - - // Clear current value on activation - // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300. - val isJustActivated = g.activeIdIsJustActivated - val isAlreadyPastLimitsAndPushingOutward = isClamped && ((v() >= vMax && adjustDelta > 0f) || (v() <= vMin && adjustDelta < 0f)) - if (isJustActivated || isAlreadyPastLimitsAndPushingOutward) { - g.dragCurrentAccum = 0f - g.dragCurrentAccumDirty = false - } else if (adjustDelta != 0f) { - g.dragCurrentAccum += adjustDelta - g.dragCurrentAccumDirty = true - } - - if (!g.dragCurrentAccumDirty) - return false - - var vCur = v() - var vOldRefForAccumRemainder = 0f - - var logarithmicZeroEpsilon = 0f // Only valid when is_logarithmic is true - val zeroDeadzoneHalfsize = 0f // Drag widgets have no deadzone (as it doesn't make sense) - if (isLogarithmic) { - // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 1 - logarithmicZeroEpsilon = 0.1f pow decimalPrecision.f - - // Convert to parametric space, apply delta, convert back - val vOldParametric = scaleRatioFromValueT(dataType, vCur, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - val vNewParametric = vOldParametric + g.dragCurrentAccum - vCur = scaleValueFromRatioT(dataType, vNewParametric, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - vOldRefForAccumRemainder = vOldParametric - } else - vCur += g.dragCurrentAccum.i - - // Round to user desired precision based on format string - // if (isFloatingPoint && flags hasnt SliderFlag.NoRoundToFormat) - // vCur = roundScalarWithFormatT(format, dataType, vCur) - - // Preserve remainder after rounding has been applied. This also allow slow tweaking of values. - g.dragCurrentAccumDirty = false - g.dragCurrentAccum -= when { - isLogarithmic -> { - // Convert to parametric space, apply delta, convert back - val vNewParametric = scaleRatioFromValueT(dataType, vCur, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - (vNewParametric - vOldRefForAccumRemainder).f - } - else -> (vCur.v - v().v).f - } - - // Lose zero sign for float/double - if (vCur.v == -0) - vCur.v = 0 - - // Clamp values (+ handle overflow/wrap-around for integer types) - if (v() != vCur && isClamped) { - if (vCur < vMin || (vCur > v() && adjustDelta < 0f && !isFloatingPoint)) - vCur = vMin - if (vCur > vMax || (vCur < v() && adjustDelta > 0f && !isFloatingPoint)) - vCur = vMax - } - - // Apply result - if (v() == vCur) - return false - v(vCur) - return true - } - - /** This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls) - * template */ - fun dragBehaviorT(dataType: DataType, v: KMutableProperty0, vSpeed_: Float, - vMin: Long, vMax: Long, format: String, flags: SliderFlags): Boolean { - - val axis = if (flags has SliderFlag._Vertical) Axis.Y else Axis.X - val isClamped = vMin < vMax - val isLogarithmic = flags has SliderFlag.Logarithmic - val isFloatingPoint = dataType == DataType.Float || dataType == DataType.Double - - // Default tweak speed - var vSpeed = when { - vSpeed_ == 0f && isClamped && vMax - vMin < Float.MAX_VALUE -> ((vMax - vMin) * g.dragSpeedDefaultRatio).f - else -> vSpeed_ - } - - // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings - var adjustDelta = 0f - if (g.activeIdSource == InputSource.Mouse && isMousePosValid() && isMouseDragPastThreshold(MouseButton.Left, io.mouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) { - adjustDelta = io.mouseDelta[axis] - if (io.keyAlt) - adjustDelta *= 1f / 100f - if (io.keyShift) - adjustDelta *= 10f - } else if (g.activeIdSource == InputSource.Nav) { - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 0 - val tweakSlow = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakSlow else Key._NavKeyboardTweakSlow).isDown - val tweakFast = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakFast else Key._NavKeyboardTweakFast).isDown - val tweakFactor = if (tweakSlow) 1f / 1f else if (tweakFast) 10f else 1f - adjustDelta = getNavTweakPressedAmount(axis) * tweakFactor - vSpeed = vSpeed max getMinimumStepAtDecimalPrecision(decimalPrecision) - } - adjustDelta *= vSpeed - - // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter. - if (axis == Axis.Y) - adjustDelta = -adjustDelta - - // For logarithmic use our range is effectively 0..1 so scale the delta into that range - if (isLogarithmic && vMax - vMin < Float.MAX_VALUE && vMax - vMin > 0.000001f) // Epsilon to avoid /0 - adjustDelta /= (vMax - vMin).f - - // Clear current value on activation - // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300. - val isJustActivated = g.activeIdIsJustActivated - val isAlreadyPastLimitsAndPushingOutward = isClamped && ((v() >= vMax && adjustDelta > 0f) || (v() <= vMin && adjustDelta < 0f)) - if (isJustActivated || isAlreadyPastLimitsAndPushingOutward) { - g.dragCurrentAccum = 0f - g.dragCurrentAccumDirty = false - } else if (adjustDelta != 0f) { - g.dragCurrentAccum += adjustDelta - g.dragCurrentAccumDirty = true - } - - if (!g.dragCurrentAccumDirty) - return false - - var vCur = v() - var vOldRefForAccumRemainder = 0.0 - - var logarithmicZeroEpsilon = 0f // Only valid when is_logarithmic is true - val zeroDeadzoneHalfsize = 0f // Drag widgets have no deadzone (as it doesn't make sense) - if (isLogarithmic) { - // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 1 - logarithmicZeroEpsilon = 0.1f pow decimalPrecision.f - - // Convert to parametric space, apply delta, convert back - val vOldParametric = scaleRatioFromValueT(dataType, vCur, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - val vNewParametric = vOldParametric + g.dragCurrentAccum - vCur = scaleValueFromRatioT(dataType, vNewParametric, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - vOldRefForAccumRemainder = vOldParametric.d - } else - vCur += g.dragCurrentAccum.L - - // Round to user desired precision based on format string - // if (isFloatingPoint && flags hasnt SliderFlag.NoRoundToFormat) - // vCur = roundScalarWithFormatT(format, dataType, vCur) - - // Preserve remainder after rounding has been applied. This also allow slow tweaking of values. - g.dragCurrentAccumDirty = false - g.dragCurrentAccum -= when { - isLogarithmic -> { - // Convert to parametric space, apply delta, convert back - val vNewParametric = scaleRatioFromValueT(dataType, vCur, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - (vNewParametric - vOldRefForAccumRemainder).f - } - else -> (vCur - v()).f - } - - // Lose zero sign for float/double - if (vCur == -0L) - vCur = 0L - - // Clamp values (+ handle overflow/wrap-around for integer types) - if (v() != vCur && isClamped) { - if (vCur < vMin || (vCur > v() && adjustDelta < 0f && !isFloatingPoint)) - vCur = vMin - if (vCur > vMax || (vCur < v() && adjustDelta > 0f && !isFloatingPoint)) - vCur = vMax - } - - // Apply result - if (v() == vCur) - return false - v(vCur) - return true - } - - /** This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls) - * template */ - fun dragBehaviorT(dataType: DataType, v: KMutableProperty0, vSpeed_: Float, - vMin: Ulong, vMax: Ulong, format: String, flags: SliderFlags): Boolean { - - val axis = if (flags has SliderFlag._Vertical) Axis.Y else Axis.X - val isClamped = vMin < vMax - val isLogarithmic = flags has SliderFlag.Logarithmic - val isFloatingPoint = dataType == DataType.Float || dataType == DataType.Double - - // Default tweak speed - var vSpeed = when { - vSpeed_ == 0f && isClamped && vMax - vMin < Float.MAX_VALUE -> ((vMax - vMin).v * g.dragSpeedDefaultRatio).f - else -> vSpeed_ - } - - // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings - var adjustDelta = 0f - if (g.activeIdSource == InputSource.Mouse && isMousePosValid() && isMouseDragPastThreshold(MouseButton.Left, io.mouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) { - adjustDelta = io.mouseDelta[axis] - if (io.keyAlt) - adjustDelta *= 1f / 100f - if (io.keyShift) - adjustDelta *= 10f - } else if (g.activeIdSource == InputSource.Nav) { - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 0 - val tweakSlow = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakSlow else Key._NavKeyboardTweakSlow).isDown - val tweakFast = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakFast else Key._NavKeyboardTweakFast).isDown - val tweakFactor = if (tweakSlow) 1f / 1f else if (tweakFast) 10f else 1f - adjustDelta = getNavTweakPressedAmount(axis) * tweakFactor - vSpeed = vSpeed max getMinimumStepAtDecimalPrecision(decimalPrecision) - } - adjustDelta *= vSpeed - - // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter. - if (axis == Axis.Y) - adjustDelta = -adjustDelta - - // For logarithmic use our range is effectively 0..1 so scale the delta into that range - if (isLogarithmic && vMax - vMin < Float.MAX_VALUE && vMax - vMin > 0.000001f) // Epsilon to avoid /0 - adjustDelta /= (vMax - vMin).f - - // Clear current value on activation - // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300. - val isJustActivated = g.activeIdIsJustActivated - val isAlreadyPastLimitsAndPushingOutward = isClamped && ((v() >= vMax && adjustDelta > 0f) || (v() <= vMin && adjustDelta < 0f)) - if (isJustActivated || isAlreadyPastLimitsAndPushingOutward) { - g.dragCurrentAccum = 0f - g.dragCurrentAccumDirty = false - } else if (adjustDelta != 0f) { - g.dragCurrentAccum += adjustDelta - g.dragCurrentAccumDirty = true - } - - if (!g.dragCurrentAccumDirty) - return false - - var vCur = v() - var vOldRefForAccumRemainder = 0.0 - - var logarithmicZeroEpsilon = 0f // Only valid when is_logarithmic is true - val zeroDeadzoneHalfsize = 0f // Drag widgets have no deadzone (as it doesn't make sense) - if (isLogarithmic) { - // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 1 - logarithmicZeroEpsilon = 0.1f pow decimalPrecision.f - - // Convert to parametric space, apply delta, convert back - val vOldParametric = scaleRatioFromValueT(dataType, vCur, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - val vNewParametric = vOldParametric + g.dragCurrentAccum - vCur = scaleValueFromRatioT(dataType, vNewParametric, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - vOldRefForAccumRemainder = vOldParametric.d - } else - vCur += g.dragCurrentAccum.ul - - // Round to user desired precision based on format string - // if (isFloatingPoint && flags hasnt SliderFlag.NoRoundToFormat) - // vCur = roundScalarWithFormatT(format, dataType, vCur) - - // Preserve remainder after rounding has been applied. This also allow slow tweaking of values. - g.dragCurrentAccumDirty = false - g.dragCurrentAccum -= when { - isLogarithmic -> { - // Convert to parametric space, apply delta, convert back - val vNewParametric = scaleRatioFromValueT(dataType, vCur, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - (vNewParametric - vOldRefForAccumRemainder).f - } - else -> (vCur.v - v().v).f - } - - // Lose zero sign for float/double - if (vCur.v == -0L) - vCur.v = 0L - - // Clamp values (+ handle overflow/wrap-around for integer types) - if (v() != vCur && isClamped) { - if (vCur < vMin || (vCur > v() && adjustDelta < 0f && !isFloatingPoint)) - vCur = vMin - if (vCur > vMax || (vCur < v() && adjustDelta > 0f && !isFloatingPoint)) - vCur = vMax - } - - // Apply result - if (v() == vCur) - return false - v(vCur) - return true - } - - /** This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls) - * template */ - fun dragBehaviorT(dataType: DataType, v: KMutableProperty0, vSpeed_: Float, - vMin: Float, vMax: Float, format: String, flags: SliderFlags): Boolean { - - val axis = if (flags has SliderFlag._Vertical) Axis.Y else Axis.X - val isClamped = vMin < vMax - val isLogarithmic = flags has SliderFlag.Logarithmic - val isFloatingPoint = dataType == DataType.Float || dataType == DataType.Double - - // Default tweak speed - var vSpeed = when { - vSpeed_ == 0f && isClamped && vMax - vMin < Float.MAX_VALUE -> (vMax - vMin) * g.dragSpeedDefaultRatio - else -> vSpeed_ - } - - // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings - var adjustDelta = 0f - if (g.activeIdSource == InputSource.Mouse && isMousePosValid() && isMouseDragPastThreshold(MouseButton.Left, io.mouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) { - adjustDelta = io.mouseDelta[axis] - if (io.keyAlt) - adjustDelta *= 1f / 100f - if (io.keyShift) - adjustDelta *= 10f - } else if (g.activeIdSource == InputSource.Nav) { - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 0 - val tweakSlow = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakSlow else Key._NavKeyboardTweakSlow).isDown - val tweakFast = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakFast else Key._NavKeyboardTweakFast).isDown - val tweakFactor = if (tweakSlow) 1f / 1f else if (tweakFast) 10f else 1f - adjustDelta = getNavTweakPressedAmount(axis) * tweakFactor - vSpeed = vSpeed max getMinimumStepAtDecimalPrecision(decimalPrecision) - } - adjustDelta *= vSpeed - - // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter. - if (axis == Axis.Y) - adjustDelta = -adjustDelta - - // For logarithmic use our range is effectively 0..1 so scale the delta into that range - if (isLogarithmic && vMax - vMin < Float.MAX_VALUE && vMax - vMin > 0.000001f) // Epsilon to avoid /0 - adjustDelta /= vMax - vMin - - // Clear current value on activation - // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300. - val isJustActivated = g.activeIdIsJustActivated - val isAlreadyPastLimitsAndPushingOutward = isClamped && ((v() >= vMax && adjustDelta > 0f) || (v() <= vMin && adjustDelta < 0f)) - if (isJustActivated || isAlreadyPastLimitsAndPushingOutward) { - g.dragCurrentAccum = 0f - g.dragCurrentAccumDirty = false - } else if (adjustDelta != 0f) { - g.dragCurrentAccum += adjustDelta - g.dragCurrentAccumDirty = true - } - - if (!g.dragCurrentAccumDirty) - return false - - var vCur = v() - var vOldRefForAccumRemainder = 0f - - var logarithmicZeroEpsilon = 0f // Only valid when is_logarithmic is true - val zeroDeadzoneHalfsize = 0f // Drag widgets have no deadzone (as it doesn't make sense) - if (isLogarithmic) { - // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 1 - logarithmicZeroEpsilon = 0.1f pow decimalPrecision.f - - // Convert to parametric space, apply delta, convert back - val vOldParametric = scaleRatioFromValueT(dataType, vCur, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - val vNewParametric = vOldParametric + g.dragCurrentAccum - vCur = scaleValueFromRatioT(dataType, vNewParametric, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - vOldRefForAccumRemainder = vOldParametric - } else - vCur += g.dragCurrentAccum - - // Round to user desired precision based on format string - if (flags hasnt SliderFlag.NoRoundToFormat) - vCur = roundScalarWithFormatT(format, dataType, vCur) - - // Preserve remainder after rounding has been applied. This also allow slow tweaking of values. - g.dragCurrentAccumDirty = false - g.dragCurrentAccum -= when { - isLogarithmic -> { - // Convert to parametric space, apply delta, convert back - val vNewParametric = scaleRatioFromValueT(dataType, vCur, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - vNewParametric - vOldRefForAccumRemainder - } - else -> vCur - v() - } - - // Lose zero sign for float/double - if (vCur == -0f) - vCur = 0f - - // Clamp values (+ handle overflow/wrap-around for integer types) - if (v() != vCur && isClamped) { - if (vCur < vMin || (vCur > v() && adjustDelta < 0f && !isFloatingPoint)) - vCur = vMin - if (vCur > vMax || (vCur < v() && adjustDelta > 0f && !isFloatingPoint)) - vCur = vMax - } - - // Apply result - if (v() == vCur) - return false - v(vCur) - return true - } - - /** This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls) - * template */ - fun dragBehaviorT(dataType: DataType, v: KMutableProperty0, vSpeed_: Float, - vMin: Double, vMax: Double, format: String, flags: SliderFlags): Boolean { - - val axis = if (flags has SliderFlag._Vertical) Axis.Y else Axis.X - val isClamped = vMin < vMax - val isLogarithmic = flags has SliderFlag.Logarithmic - val isFloatingPoint = dataType == DataType.Float || dataType == DataType.Double - - // Default tweak speed - var vSpeed = when { - vSpeed_ == 0f && isClamped && vMax - vMin < Float.MAX_VALUE -> ((vMax - vMin) * g.dragSpeedDefaultRatio).f - else -> vSpeed_ - } - - // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings - var adjustDelta = 0f - if (g.activeIdSource == InputSource.Mouse && isMousePosValid() && isMouseDragPastThreshold(MouseButton.Left, io.mouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) { - adjustDelta = io.mouseDelta[axis] - if (io.keyAlt) - adjustDelta *= 1f / 100f - if (io.keyShift) - adjustDelta *= 10f - } else if (g.activeIdSource == InputSource.Nav) { - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 0 - val tweakSlow = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakSlow else Key._NavKeyboardTweakSlow).isDown - val tweakFast = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakFast else Key._NavKeyboardTweakFast).isDown - val tweakFactor = if (tweakSlow) 1f / 1f else if (tweakFast) 10f else 1f - adjustDelta = getNavTweakPressedAmount(axis) * tweakFactor - vSpeed = vSpeed max getMinimumStepAtDecimalPrecision(decimalPrecision) - } - adjustDelta *= vSpeed - - // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter. - if (axis == Axis.Y) - adjustDelta = -adjustDelta - - // For logarithmic use our range is effectively 0..1 so scale the delta into that range - if (isLogarithmic && vMax - vMin < Float.MAX_VALUE && vMax - vMin > 0.000001f) // Epsilon to avoid /0 - adjustDelta /= (vMax - vMin).f - - // Clear current value on activation - // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300. - val isJustActivated = g.activeIdIsJustActivated - val isAlreadyPastLimitsAndPushingOutward = isClamped && ((v() >= vMax && adjustDelta > 0f) || (v() <= vMin && adjustDelta < 0f)) - if (isJustActivated || isAlreadyPastLimitsAndPushingOutward) { - g.dragCurrentAccum = 0f - g.dragCurrentAccumDirty = false - } else if (adjustDelta != 0f) { - g.dragCurrentAccum += adjustDelta - g.dragCurrentAccumDirty = true - } - - if (!g.dragCurrentAccumDirty) - return false - - var vCur = v() - var vOldRefForAccumRemainder = 0.0 - - var logarithmicZeroEpsilon = 0f // Only valid when is_logarithmic is true - val zeroDeadzoneHalfsize = 0f // Drag widgets have no deadzone (as it doesn't make sense) - if (isLogarithmic) { - // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 1 - logarithmicZeroEpsilon = 0.1f pow decimalPrecision.f - - // Convert to parametric space, apply delta, convert back - val vOldParametric = scaleRatioFromValueT(dataType, vCur, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - val vNewParametric = vOldParametric + g.dragCurrentAccum - vCur = scaleValueFromRatioT(dataType, vNewParametric, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - vOldRefForAccumRemainder = vOldParametric.d - } else - vCur += g.dragCurrentAccum.d - - // Round to user desired precision based on format string - if (flags hasnt SliderFlag.NoRoundToFormat) - vCur = roundScalarWithFormatT(format, dataType, vCur) - - // Preserve remainder after rounding has been applied. This also allow slow tweaking of values. - g.dragCurrentAccumDirty = false - g.dragCurrentAccum -= when { - isLogarithmic -> { - // Convert to parametric space, apply delta, convert back - val vNewParametric = scaleRatioFromValueT(dataType, vCur, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - (vNewParametric - vOldRefForAccumRemainder).f - } - else -> (vCur - v()).f - } - - // Lose zero sign for float/double - if (vCur == -0.0) - vCur = 0.0 - - // Clamp values (+ handle overflow/wrap-around for integer types) - if (v() != vCur && isClamped) { - if (vCur < vMin || (vCur > v() && adjustDelta < 0f && !isFloatingPoint)) - vCur = vMin - if (vCur > vMax || (vCur < v() && adjustDelta > 0f && !isFloatingPoint)) - vCur = vMax - } - - // Apply result - if (v() == vCur) - return false - v(vCur) - return true - } - - /** Try to move more of the code into shared SliderBehavior() - * template */ - fun sliderBehaviorT(bb: Rect, id: ID, dataType: DataType, v: KMutableProperty0, vMin: Int, vMax: Int, - format: String, flags: SliderFlags, outGrabBb: Rect): Boolean { - - val axis = if (flags has SliderFlag._Vertical) Axis.Y else Axis.X - val isLogarithmic = flags has SliderFlag.Logarithmic - val isFloatingPoint = dataType == DataType.Float || dataType == DataType.Double - val vRange = if (vMin < vMax) vMax - vMin else vMin - vMax - - // Calculate bounds - val grabPadding = 2f // FIXME: Should be part of style. - val sliderSz = (bb.max[axis] - bb.min[axis]) - grabPadding * 2f - var grabSz = style.grabMinSize - if (!isFloatingPoint && vRange >= 0) // v_range < 0 may happen on integer overflows - grabSz = (sliderSz / (vRange + 1)).f max style.grabMinSize // For integer sliders: if possible have the grab size represent 1 unit - grabSz = grabSz min sliderSz - val sliderUsableSz = sliderSz - grabSz - val sliderUsablePosMin = bb.min[axis] + grabPadding + grabSz * 0.5f - val sliderUsablePosMax = bb.max[axis] - grabPadding - grabSz * 0.5f - - var logarithmicZeroEpsilon = 0f // Only valid when is_logarithmic is true - var zeroDeadzoneHalfsize = 0f // Only valid when is_logarithmic is true - if (isLogarithmic) { - // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 1 - logarithmicZeroEpsilon = 0.1f pow decimalPrecision.f - zeroDeadzoneHalfsize = style.logSliderDeadzone * 0.5f / max(sliderUsableSz, 1f) - } - - // Process interacting with the slider - var valueChanged = false - if (g.activeId == id) { - var setNewValue = false - var clickedT = 0f - if (g.activeIdSource == InputSource.Mouse) - if (!io.mouseDown[0]) - clearActiveID() - else { - val mouseAbsPos = io.mousePos[axis] - if (g.activeIdIsJustActivated) { - var grabT: Float = scaleRatioFromValueT(dataType, v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - if (axis == Axis.Y) - grabT = 1f - grabT - val grabPos = lerp(sliderUsablePosMin, sliderUsablePosMax, grabT) - val clickedAroundGrab = mouseAbsPos >= grabPos - grabSz * 0.5f - 1f && mouseAbsPos <= grabPos + grabSz * 0.5f + 1f // No harm being extra generous here. - g.sliderGrabClickOffset = if (clickedAroundGrab && isFloatingPoint) mouseAbsPos - grabPos else 0f - } - if (sliderUsableSz > 0f) - clickedT = saturate((mouseAbsPos - g.sliderGrabClickOffset - sliderUsablePosMin) / sliderUsableSz) - if (axis == Axis.Y) - clickedT = 1f - clickedT - setNewValue = true - } - else if (g.activeIdSource == InputSource.Nav) { - - if (g.activeIdIsJustActivated) { - g.sliderCurrentAccum = 0f // Reset any stored nav delta upon activation - g.sliderCurrentAccumDirty = false - } - - var inputDelta = if (axis == Axis.X) getNavTweakPressedAmount(axis) else -getNavTweakPressedAmount(axis) - if (inputDelta != 0f) { - val tweakSlow = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakSlow else Key._NavKeyboardTweakSlow).isDown - val tweakFast = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakFast else Key._NavKeyboardTweakFast).isDown - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 0 - if (decimalPrecision > 0) { - inputDelta /= 100f // Gamepad/keyboard tweak speeds in % of slider bounds - if (tweakSlow) - inputDelta /= 10f - } else if ((vRange >= -100f && vRange <= 100f) || tweakSlow) - inputDelta = (if (inputDelta < 0f) -1f else +1f) / vRange.f // Gamepad/keyboard tweak speeds in integer steps - else - inputDelta /= 100f - if (tweakFast) - inputDelta *= 10f - - g.sliderCurrentAccum += inputDelta - g.sliderCurrentAccumDirty = true - } - - val delta = g.sliderCurrentAccum - - if (g.navActivatePressedId == id && !g.activeIdIsJustActivated) - clearActiveID() - else if (g.sliderCurrentAccumDirty) { - clickedT = scaleRatioFromValueT(dataType, v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - - if ((clickedT >= 1f && delta > 0f) || (clickedT <= 0f && delta < 0f)) { // This is to avoid applying the saturation when already past the limits - setNewValue = false - g.sliderCurrentAccum = 0f // If pushing up against the limits, don't continue to accumulate - } else { - setNewValue = true - val oldClickedT = clickedT - clickedT = saturate(clickedT + delta) - - // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator - var vNew = scaleValueFromRatioT(dataType, clickedT, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - // if (isFloatingPoint && flags hasnt SliderFlag.NoRoundToFormat) - // vNew = roundScalarWithFormatT(format, dataType, vNew) - val newClickedT = scaleRatioFromValueT(dataType, vNew, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - - g.sliderCurrentAccum -= when { - delta > 0 -> min(newClickedT - oldClickedT, delta) - else -> max(newClickedT - oldClickedT, delta) - } - } - } - } - - if (setNewValue) { - var vNew = scaleValueFromRatioT(dataType, clickedT, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - - // Round to user desired precision based on format string - // if (isFloatingPoint && flags hasnt SliderFlag.NoRoundToFormat) - // vNew = roundScalarWithFormatT(format, dataType, vNew) - - // Apply result - if (v() != vNew) { - v(vNew) - valueChanged = true - } - } - } - - if (sliderSz < 1f) - outGrabBb.put(bb.min, bb.min) - else { - // Output grab position so it can be displayed by the caller - var grabT = scaleRatioFromValueT(dataType, v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - if (axis == Axis.Y) - grabT = 1f - grabT - val grabPos = lerp(sliderUsablePosMin, sliderUsablePosMax, grabT) - if (axis == Axis.X) - outGrabBb.put(grabPos - grabSz * 0.5f, bb.min.y + grabPadding, grabPos + grabSz * 0.5f, bb.max.y - grabPadding) - else - outGrabBb.put(bb.min.x + grabPadding, grabPos - grabSz * 0.5f, bb.max.x - grabPadding, grabPos + grabSz * 0.5f) - } - - return valueChanged - } - - /** FIXME: Move more of the code into SliderBehavior() - * template */ - fun sliderBehaviorT(bb: Rect, id: ID, dataType: DataType, v: KMutableProperty0, vMin: Uint, vMax: Uint, - format: String, flags: SliderFlags, outGrabBb: Rect): Boolean { - - val axis = if (flags has SliderFlag._Vertical) Axis.Y else Axis.X - val isLogarithmic = flags has SliderFlag.Logarithmic - val isFloatingPoint = dataType == DataType.Float || dataType == DataType.Double - val vRange = (if (vMin < vMax) vMax - vMin else vMin - vMax).v - - // Calculate bounds - val grabPadding = 2f // FIXME: Should be part of style. - val sliderSz = (bb.max[axis] - bb.min[axis]) - grabPadding * 2f - var grabSz = style.grabMinSize - if (!isFloatingPoint && vRange >= 0) // v_range < 0 may happen on integer overflows - grabSz = (sliderSz / (vRange + 1)).f max style.grabMinSize // For integer sliders: if possible have the grab size represent 1 unit - grabSz = grabSz min sliderSz - val sliderUsableSz = sliderSz - grabSz - val sliderUsablePosMin = bb.min[axis] + grabPadding + grabSz * 0.5f - val sliderUsablePosMax = bb.max[axis] - grabPadding - grabSz * 0.5f - - var logarithmicZeroEpsilon = 0f // Only valid when is_logarithmic is true - var zeroDeadzoneHalfsize = 0f // Only valid when is_logarithmic is true - if (isLogarithmic) { - // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 1 - logarithmicZeroEpsilon = 0.1f.pow(decimalPrecision.f) - zeroDeadzoneHalfsize = style.logSliderDeadzone * 0.5f / max(sliderUsableSz, 1f) - } - - // Process interacting with the slider - var valueChanged = false - if (g.activeId == id) { - var setNewValue = false - var clickedT = 0f - if (g.activeIdSource == InputSource.Mouse) - if (!io.mouseDown[0]) - clearActiveID() - else { - val mouseAbsPos = io.mousePos[axis] - if (g.activeIdIsJustActivated) { - var grabT: Float = scaleRatioFromValueT(dataType, v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - if (axis == Axis.Y) - grabT = 1f - grabT - val grabPos = lerp(sliderUsablePosMin, sliderUsablePosMax, grabT) - val clickedAroundGrab = mouseAbsPos >= grabPos - grabSz * 0.5f - 1f && mouseAbsPos <= grabPos + grabSz * 0.5f + 1f // No harm being extra generous here. - g.sliderGrabClickOffset = if (clickedAroundGrab && isFloatingPoint) mouseAbsPos - grabPos else 0f - } - if (sliderUsableSz > 0f) - clickedT = saturate((mouseAbsPos - g.sliderGrabClickOffset - sliderUsablePosMin) / sliderUsableSz) - if (axis == Axis.Y) - clickedT = 1f - clickedT - setNewValue = true - } - else if (g.activeIdSource == InputSource.Nav) { - - if (g.activeIdIsJustActivated) { - g.sliderCurrentAccum = 0f // Reset any stored nav delta upon activation - g.sliderCurrentAccumDirty = false - } - - var inputDelta = if (axis == Axis.X) getNavTweakPressedAmount(axis) else -getNavTweakPressedAmount(axis) - if (inputDelta != 0f) { - val tweakSlow = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakSlow else Key._NavKeyboardTweakSlow).isDown - val tweakFast = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakFast else Key._NavKeyboardTweakFast).isDown - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 0 - if (decimalPrecision > 0) { - inputDelta /= 100f // Gamepad/keyboard tweak speeds in % of slider bounds - if (tweakSlow) - inputDelta /= 10f - } else if ((vRange >= -100f && vRange <= 100f) || tweakSlow) - inputDelta = (if (inputDelta < 0f) -1f else +1f) / vRange.f // Gamepad/keyboard tweak speeds in integer steps - else - inputDelta /= 100f - if (tweakFast) - inputDelta *= 10f - - g.sliderCurrentAccum += inputDelta - g.sliderCurrentAccumDirty = true - } - - val delta = g.sliderCurrentAccum - if (g.navActivatePressedId == id && !g.activeIdIsJustActivated) - clearActiveID() - else if (g.sliderCurrentAccumDirty) { - clickedT = scaleRatioFromValueT(dataType, v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - - if ((clickedT >= 1f && delta > 0f) || (clickedT <= 0f && delta < 0f)) { // This is to avoid applying the saturation when already past the limits - setNewValue = false - g.sliderCurrentAccum = 0f // If pushing up against the limits, don't continue to accumulate - } else { - setNewValue = true - val oldClickedT = clickedT - clickedT = saturate(clickedT + delta) - - // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator - var vNew = scaleValueFromRatioT(dataType, clickedT, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - // if (isFloatingPoint && flags hasnt SliderFlag.NoRoundToFormat) - // vNew = roundScalarWithFormatT(format, dataType, vNew) - val newClickedT = scaleRatioFromValueT(dataType, vNew, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - - g.sliderCurrentAccum -= when { - delta > 0 -> min(newClickedT - oldClickedT, delta) - else -> max(newClickedT - oldClickedT, delta) - } - } - - g.sliderCurrentAccumDirty = false - } - } - - if (setNewValue) { - var vNew = scaleValueFromRatioT(dataType, clickedT, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - - // Round to user desired precision based on format string - // if (isFloatingPoint && flags hasnt SliderFlag.NoRoundToFormat) - // vNew = roundScalarWithFormatT(format, dataType, vNew) - - // Apply result - if (v() != vNew) { - v(vNew) - valueChanged = true - } - } - } - - if (sliderSz < 1f) { - outGrabBb.put(bb.min, bb.min) - } else { - // Output grab position so it can be displayed by the caller - var grabT = scaleRatioFromValueT(dataType, v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - if (axis == Axis.Y) - grabT = 1f - grabT - val grabPos = lerp(sliderUsablePosMin, sliderUsablePosMax, grabT) - if (axis == Axis.X) - outGrabBb.put(grabPos - grabSz * 0.5f, bb.min.y + grabPadding, grabPos + grabSz * 0.5f, bb.max.y - grabPadding) - else - outGrabBb.put(bb.min.x + grabPadding, grabPos - grabSz * 0.5f, bb.max.x - grabPadding, grabPos + grabSz * 0.5f) - } - - return valueChanged - } - - /** FIXME: Move more of the code into SliderBehavior() - * template */ - fun sliderBehaviorT(bb: Rect, id: ID, dataType: DataType, v: KMutableProperty0, vMin: Long, vMax: Long, - format: String, flags: SliderFlags, outGrabBb: Rect): Boolean { - - val axis = if (flags has SliderFlag._Vertical) Axis.Y else Axis.X - val isLogarithmic = flags has SliderFlag.Logarithmic - val isFloatingPoint = dataType == DataType.Float || dataType == DataType.Double - val vRange = if (vMin < vMax) vMax - vMin else vMin - vMax - - // Calculate bounds - val grabPadding = 2f // FIXME: Should be part of style. - val sliderSz = (bb.max[axis] - bb.min[axis]) - grabPadding * 2f - var grabSz = style.grabMinSize - if (!isFloatingPoint && vRange >= 0) // v_range < 0 may happen on integer overflows - grabSz = (sliderSz / (vRange + 1)).f max style.grabMinSize // For integer sliders: if possible have the grab size represent 1 unit - grabSz = grabSz min sliderSz - val sliderUsableSz = sliderSz - grabSz - val sliderUsablePosMin = bb.min[axis] + grabPadding + grabSz * 0.5f - val sliderUsablePosMax = bb.max[axis] - grabPadding - grabSz * 0.5f - - var logarithmicZeroEpsilon = 0f // Only valid when is_logarithmic is true - var zeroDeadzoneHalfsize = 0f // Only valid when is_logarithmic is true - if (isLogarithmic) { - // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 1 - logarithmicZeroEpsilon = 0.1f pow decimalPrecision.f - zeroDeadzoneHalfsize = style.logSliderDeadzone * 0.5f / max(sliderUsableSz, 1f) - } - - // Process interacting with the slider - var valueChanged = false - if (g.activeId == id) { - var setNewValue = false - var clickedT = 0f - if (g.activeIdSource == InputSource.Mouse) - if (!io.mouseDown[0]) - clearActiveID() - else { - val mouseAbsPos = io.mousePos[axis] - if (g.activeIdIsJustActivated) { - var grabT: Float = scaleRatioFromValueT(dataType, v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - if (axis == Axis.Y) - grabT = 1f - grabT - val grabPos = lerp(sliderUsablePosMin, sliderUsablePosMax, grabT) - val clickedAroundGrab = mouseAbsPos >= grabPos - grabSz * 0.5f - 1f && mouseAbsPos <= grabPos + grabSz * 0.5f + 1f // No harm being extra generous here. - g.sliderGrabClickOffset = if (clickedAroundGrab && isFloatingPoint) mouseAbsPos - grabPos else 0f - } - if (sliderUsableSz > 0f) - clickedT = saturate((mouseAbsPos - g.sliderGrabClickOffset - sliderUsablePosMin) / sliderUsableSz) - if (axis == Axis.Y) - clickedT = 1f - clickedT - setNewValue = true - } - else if (g.activeIdSource == InputSource.Nav) { - - if (g.activeIdIsJustActivated) { - g.sliderCurrentAccum = 0f // Reset any stored nav delta upon activation - g.sliderCurrentAccumDirty = false - } - - var inputDelta = if (axis == Axis.X) getNavTweakPressedAmount(axis) else -getNavTweakPressedAmount(axis) - if (inputDelta != 0f) { - val tweakSlow = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakSlow else Key._NavKeyboardTweakSlow).isDown - val tweakFast = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakFast else Key._NavKeyboardTweakFast).isDown - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 0 - if (decimalPrecision > 0) { - inputDelta /= 100f // Gamepad/keyboard tweak speeds in % of slider bounds - if (tweakSlow) - inputDelta /= 10f - } else if ((vRange >= -100f && vRange <= 100f) || tweakSlow) - inputDelta = (if (inputDelta < 0f) -1f else +1f) / vRange.f // Gamepad/keyboard tweak speeds in integer steps - else - inputDelta /= 100f - if (tweakFast) - inputDelta *= 10f - - g.sliderCurrentAccum += inputDelta - g.sliderCurrentAccumDirty = true - } - - val delta = g.sliderCurrentAccum - - if (g.navActivatePressedId == id && !g.activeIdIsJustActivated) - clearActiveID() - else if (g.sliderCurrentAccumDirty) { - clickedT = scaleRatioFromValueT(dataType, v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - - if ((clickedT >= 1f && delta > 0f) || (clickedT <= 0f && delta < 0f)) { // This is to avoid applying the saturation when already past the limits - setNewValue = false - g.sliderCurrentAccum = 0f // If pushing up against the limits, don't continue to accumulate - } else { - setNewValue = true - val oldClickedT = clickedT - clickedT = saturate(clickedT + delta) - - // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator - var vNew = scaleValueFromRatioT(dataType, clickedT, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - // if (isFloatingPoint && flags hasnt SliderFlag.NoRoundToFormat) - // vNew = roundScalarWithFormatT(format, dataType, vNew) - val newClickedT = scaleRatioFromValueT(dataType, vNew, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - - g.sliderCurrentAccum -= when { - delta > 0 -> min(newClickedT - oldClickedT, delta) - else -> max(newClickedT - oldClickedT, delta) - } - } - - g.sliderCurrentAccumDirty = false - } - } - - if (setNewValue) { - var vNew = scaleValueFromRatioT(dataType, clickedT, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - - // Round to user desired precision based on format string - // if (isFloatingPoint && flags hasnt SliderFlag.NoRoundToFormat) - // vNew = roundScalarWithFormatT(format, dataType, vNew) - - // Apply result - if (v() != vNew) { - v(vNew) - valueChanged = true - } - } - } - - if (sliderSz < 1f) - outGrabBb.put(bb.min, bb.min) - else { - // Output grab position so it can be displayed by the caller - var grabT = scaleRatioFromValueT(dataType, v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - if (axis == Axis.Y) - grabT = 1f - grabT - val grabPos = lerp(sliderUsablePosMin, sliderUsablePosMax, grabT) - if (axis == Axis.X) - outGrabBb.put(grabPos - grabSz * 0.5f, bb.min.y + grabPadding, grabPos + grabSz * 0.5f, bb.max.y - grabPadding) - else - outGrabBb.put(bb.min.x + grabPadding, grabPos - grabSz * 0.5f, bb.max.x - grabPadding, grabPos + grabSz * 0.5f) - } - - return valueChanged - } - - /** FIXME: Move more of the code into SliderBehavior() - * template */ - fun sliderBehaviorT(bb: Rect, id: ID, dataType: DataType, v: KMutableProperty0, vMin: Ulong, vMax: Ulong, - format: String, flags: SliderFlags, outGrabBb: Rect): Boolean { - - val axis = if (flags has SliderFlag._Vertical) Axis.Y else Axis.X - val isLogarithmic = flags has SliderFlag.Logarithmic - val isFloatingPoint = dataType == DataType.Float || dataType == DataType.Double - val vRange = (if (vMin < vMax) vMax - vMin else vMin - vMax).v +import kotlin.math.max +import kotlin.math.min +import kotlin.reflect.KMutableProperty0 - // Calculate bounds - val grabPadding = 2f // FIXME: Should be part of style. - val sliderSz = (bb.max[axis] - bb.min[axis]) - grabPadding * 2f - var grabSz = style.grabMinSize - if (!isFloatingPoint && vRange >= 0) // v_range < 0 may happen on integer overflows - grabSz = (sliderSz / (vRange + 1)).f max style.grabMinSize // For integer sliders: if possible have the grab size represent 1 unit - grabSz = grabSz min sliderSz - val sliderUsableSz = sliderSz - grabSz - val sliderUsablePosMin = bb.min[axis] + grabPadding + grabSz * 0.5f - val sliderUsablePosMax = bb.max[axis] - grabPadding - grabSz * 0.5f +/** Template functions are instantiated in imgui_widgets.cpp for a finite number of types. + * To use them externally (for custom widget) you may need an "extern template" statement in your code in order to link to existing instances and silence Clang warnings (see #2036). + * e.g. " extern template IMGUI_API float RoundScalarWithFormatT(const char* format, ImGuiDataType data_type, float v); " */ +internal interface templateFunctions { - var logarithmicZeroEpsilon = 0f // Only valid when is_logarithmic is true - var zeroDeadzoneHalfsize = 0f // Only valid when is_logarithmic is true - if (isLogarithmic) { - // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 1 - logarithmicZeroEpsilon = 0.1f pow decimalPrecision.f - zeroDeadzoneHalfsize = style.logSliderDeadzone * 0.5f / max(sliderUsableSz, 1f) - } + /* + // [JVM] Reworked to use generics and NumberOps instead of templates + */ + /** Convert a value v in the output space of a slider into a parametric position on the slider itself + * (the logical opposite of scaleValueFromRatio) + * template */ + fun NumberFpOps.scaleRatioFromValue(v: N, vMin: N, vMax: N, isLogarithmic: Boolean, logarithmicZeroEpsilon: Float, zeroDeadzoneHalfsize: Float): Float where N : Number, N : Comparable, FP : Number, FP : Comparable { + if (vMin == vMax) return 0f - // Process interacting with the slider - var valueChanged = false - if (g.activeId == id) { - var setNewValue = false - var clickedT = 0f - if (g.activeIdSource == InputSource.Mouse) - if (!io.mouseDown[0]) - clearActiveID() - else { - val mouseAbsPos = io.mousePos[axis] - if (g.activeIdIsJustActivated) { - var grabT: Float = scaleRatioFromValueT(dataType, v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - if (axis == Axis.Y) - grabT = 1f - grabT - val grabPos = lerp(sliderUsablePosMin, sliderUsablePosMax, grabT) - val clickedAroundGrab = mouseAbsPos >= grabPos - grabSz * 0.5f - 1f && mouseAbsPos <= grabPos + grabSz * 0.5f + 1f // No harm being extra generous here. - g.sliderGrabClickOffset = if (clickedAroundGrab && isFloatingPoint) mouseAbsPos - grabPos else 0f - } - if (sliderUsableSz > 0f) - clickedT = saturate((mouseAbsPos - g.sliderGrabClickOffset - sliderUsablePosMin) / sliderUsableSz) - if (axis == Axis.Y) - clickedT = 1f - clickedT - setNewValue = true - } - else if (g.activeIdSource == InputSource.Nav) { + val vClamped = if (vMin < vMax) v.clamp(vMin, vMax) else v.clamp(vMax, vMin) + val vClampedFp = vClamped.fp - if (g.activeIdIsJustActivated) { - g.sliderCurrentAccum = 0f // Reset any stored nav delta upon activation - g.sliderCurrentAccumDirty = false - } + // Linear slider + if (!isLogarithmic) return ((vClamped.fp - vMin.fp) / (vMax.fp - vMin.fp)).f + val logarithmicZeroEpsilon = logarithmicZeroEpsilon.fp + val zeroDeadzoneHalfsize = zeroDeadzoneHalfsize.fp - var inputDelta = if (axis == Axis.X) getNavTweakPressedAmount(axis) else -getNavTweakPressedAmount(axis) - if (inputDelta != 0f) { - val tweakSlow = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakSlow else Key._NavKeyboardTweakSlow).isDown - val tweakFast = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakFast else Key._NavKeyboardTweakFast).isDown - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 0 - if (decimalPrecision > 0) { - inputDelta /= 100f // Gamepad/keyboard tweak speeds in % of slider bounds - if (tweakSlow) - inputDelta /= 10f - } else if ((vRange >= -100f && vRange <= 100f) || tweakSlow) - inputDelta = (if (inputDelta < 0f) -1f else +1f) / vRange.f // Gamepad/keyboard tweak speeds in integer steps - else - inputDelta /= 100f - if (tweakFast) - inputDelta *= 10f + // Very minor optimization: we calculate isSigned only once + val isSigned = isSigned - g.sliderCurrentAccum += inputDelta - g.sliderCurrentAccumDirty = true - } + val flipped = vMax < vMin + val vMin = if (flipped) vMax else vMin + val vMax = if (flipped) vMin else vMax + val vMinFp = vMin.fp + val vMaxFp = vMax.fp - val delta = g.sliderCurrentAccum + // Fudge min/max to avoid getting close to log(0) + val vMinFudged = when { + // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) + isSigned && vMax == zero && vMin.isNegative -> -logarithmicZeroEpsilon + abs(vMinFp) < logarithmicZeroEpsilon -> if (isSigned && vMin.isNegative) -logarithmicZeroEpsilon else logarithmicZeroEpsilon + else -> vMinFp + } - if (g.navActivatePressedId == id && !g.activeIdIsJustActivated) - clearActiveID() - else if (g.sliderCurrentAccumDirty) { - clickedT = scaleRatioFromValueT(dataType, v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) + val vMaxFudged = when { + abs(vMaxFp) < logarithmicZeroEpsilon -> if (isSigned && vMax.isNegative) -logarithmicZeroEpsilon else logarithmicZeroEpsilon + else -> vMaxFp + } - if ((clickedT >= 1f && delta > 0f) || (clickedT <= 0f && delta < 0f)) { // This is to avoid applying the saturation when already past the limits - setNewValue = false - g.sliderCurrentAccum = 0f // If pushing up against the limits, don't continue to accumulate - } else { - setNewValue = true - val oldClickedT = clickedT - clickedT = saturate(clickedT + delta) + // This can't happen? I'm pretty sure... because we flip the range if it's backwards, so we can't have a zero min and a negative max + /*if (vMin == zero && vMax.isNegative) + vMinFudged = -logarithmicZeroEpsilon + else*/ - // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator - var vNew = scaleValueFromRatioT(dataType, clickedT, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - // if (isFloatingPoint && flags hasnt SliderFlag.NoRoundToFormat) - // vNew = roundScalarWithFormatT(format, dataType, vNew) - val newClickedT = scaleRatioFromValueT(dataType, vNew, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) + val result: FP = when { + vClampedFp <= vMinFudged -> 0f.fp // Workaround for values that are in-range but below our fudge + vClampedFp >= vMaxFudged -> 1f.fp // Workaround for values that are in-range but above our fudge + isSigned && ((vMin.sign * vMax.sign) < 0) -> { // Range crosses zero, so split into two portions + val zeroPointCenter = -vMinFp / (vMaxFp - vMinFp) // The zero point in parametric space. There's an argument we should take the logarithmic nature into account when calculating this, but for now this should do (and the most common case of a symmetrical range works fine) + val zeroPointSnapL = zeroPointCenter - zeroDeadzoneHalfsize + val zeroPointSnapR = zeroPointCenter + zeroDeadzoneHalfsize + when { + v == zero -> zeroPointCenter // Special case for exactly zero + v.isNegative -> (1f.fp - ln(-vClampedFp / logarithmicZeroEpsilon) / ln(-vMinFudged / logarithmicZeroEpsilon)) * zeroPointSnapL + else -> zeroPointSnapR + ln(vClampedFp / logarithmicZeroEpsilon) / ln(vMaxFudged / logarithmicZeroEpsilon) * (1f.fp - zeroPointSnapR) + } + } + // Entirely negative slider + isSigned && (vMin.isNegative) || (vMax.isNegative) -> 1f.fp - ln(-vClampedFp / -vMaxFudged) / ln(-vMinFudged / -vMaxFudged) + else -> ln(vClampedFp / vMinFudged) / ln(vMaxFudged / vMinFudged) + } + return (if (flipped) 1f.fp - result else result).f + } - g.sliderCurrentAccum -= when { - delta > 0 -> min(newClickedT - oldClickedT, delta) - else -> max(newClickedT - oldClickedT, delta) - } - } + /** Convert a parametric position on a slider into a value v in the output space (the logical opposite of scaleRatioFromValue) + * template */ + fun NumberFpOps.scaleValueFromRatio(t: Float, vMin: N, vMax: N, isLogarithmic: Boolean, logarithmicZeroEpsilon: Float, zeroDeadzoneHalfsize: Float): N where N : Number, N : Comparable, FP : Number, FP : Comparable = when { + // We special-case the extents because otherwise our logarithmic fudging can lead to "mathematically correct" + // but non-intuitive behaviors like a fully-left slider not actually reaching the minimum value. Also generally simpler. + t <= 0f || vMin == vMax -> vMin + t >= 1f -> vMax + isLogarithmic -> { + val isSigned = isSigned + val logarithmicZeroEpsilon = logarithmicZeroEpsilon.fp + val zeroDeadzoneHalfsize = zeroDeadzoneHalfsize.fp + val vMinFp = vMin.fp + val vMaxFp = vMax.fp + // Fudge min/max to avoid getting silly results close to zero + var vMinFudged = when { + abs(vMinFp) < logarithmicZeroEpsilon -> if (isSigned && vMin.isNegative) -logarithmicZeroEpsilon else logarithmicZeroEpsilon + else -> vMinFp + } + var vMaxFudged = when { + abs(vMaxFp) < logarithmicZeroEpsilon -> if (isSigned && vMax.isNegative) -logarithmicZeroEpsilon else logarithmicZeroEpsilon + else -> vMaxFp + } - g.sliderCurrentAccumDirty = false - } + val flipped = vMax < vMin // Check if range is "backwards" + if (flipped) { + val swap = vMinFudged + vMinFudged = vMaxFudged + vMaxFudged = swap } - if (setNewValue) { - var vNew = scaleValueFromRatioT(dataType, clickedT, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) + // Awkward special case - we need ranges of the form (-100 .. 0) to convert to (-100 .. -epsilon), not (-100 .. epsilon) + if (isSigned && vMax == zero && vMin.isNegative) vMaxFudged = -logarithmicZeroEpsilon - // Round to user desired precision based on format string - // if (isFloatingPoint && flags hasnt SliderFlag.NoRoundToFormat) - // vNew = roundScalarWithFormatT(format, dataType, vNew) + // t, but flipped if necessary to account for us flipping the range + val tWithFlip = (if (flipped) 1f - t else t).fp - // Apply result - if (v() != vNew) { - v(vNew) - valueChanged = true + when { + isSigned && ((vMin.sign * vMax.sign) < 0) -> { // Range crosses zero, so we have to do this in two parts + val zeroPointCenter = -(vMin min vMax).fp / abs(vMaxFp - vMinFp) // The zero point in parametric space + val zeroPointSnapL = zeroPointCenter - zeroDeadzoneHalfsize + val zeroPointSnapR = zeroPointCenter + zeroDeadzoneHalfsize + when { + tWithFlip in zeroPointSnapL..zeroPointSnapR -> zero // Special case to make getting exactly zero possible (the epsilon prevents it otherwise) + tWithFlip < zeroPointCenter -> (-(logarithmicZeroEpsilon * (-vMinFudged / logarithmicZeroEpsilon).pow(1f.fp - tWithFlip / zeroPointSnapL))).n + else -> (logarithmicZeroEpsilon * (vMaxFudged / logarithmicZeroEpsilon).pow((tWithFlip - zeroPointSnapR) / (1f.fp - zeroPointSnapR))).n + } } + // Entirely negative slider + isSigned && (vMin.isNegative || vMax.isNegative) -> (-(-vMaxFudged * (-vMinFudged / -vMaxFudged).pow(1f.fp - tWithFlip))).n + else -> (vMinFudged * (vMaxFudged / vMinFudged).pow(tWithFlip)).n } } - - if (sliderSz < 1f) { - outGrabBb.put(bb.min, bb.min) - } else { - // Output grab position so it can be displayed by the caller - var grabT = scaleRatioFromValueT(dataType, v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - if (axis == Axis.Y) - grabT = 1f - grabT - val grabPos = lerp(sliderUsablePosMin, sliderUsablePosMax, grabT) - if (axis == Axis.X) - outGrabBb.put(grabPos - grabSz * 0.5f, bb.min.y + grabPadding, grabPos + grabSz * 0.5f, bb.max.y - grabPadding) - else - outGrabBb.put(bb.min.x + grabPadding, grabPos - grabSz * 0.5f, bb.max.x - grabPadding, grabPos + grabSz * 0.5f) + // Linear slider + // ~isFloatingPoint + isFloatingPoint -> lerp(vMin, vMax, t.fp).n + // - For integer values we want the clicking position to match the grab box so we round above + // This code is carefully tuned to work with large values (e.g. high ranges of U64) while preserving this property.. + // - Not doing a *1.0 multiply at the end of a range as it tends to be lossy. While absolute aiming at a large s64/u64 + // range is going to be imprecise anyway, with this check we at least make the edge values matches expected limits. + t < 1f -> { + val vNewOffF = (vMax.fp - vMin.fp) * t.fp + (vMin.fp + vNewOffF + if (vMin > vMax) (-0.5f).fp else 0.5f.fp).n } - return valueChanged + else -> zero } - /** FIXME: Move more of the code into SliderBehavior() - * template */ - fun sliderBehaviorT(bb: Rect, id: ID, dataType: DataType, v: KMutableProperty0, vMin: Float, vMax: Float, - format: String, flags: SliderFlags, outGrabBb: Rect): Boolean { + /** This is called by DragBehavior() when the widget is active (held by mouse or being manipulated with Nav controls) + * template */ + fun NumberFpOps.dragBehaviorT(v: KMutableProperty0, vSpeed_: Float, vMin: N, vMax: N, format: String, flags: SliderFlags): Boolean where N : Number, N : Comparable, FP : Number, FP : Comparable { val axis = if (flags has SliderFlag._Vertical) Axis.Y else Axis.X + val isClamped = vMin < vMax val isLogarithmic = flags has SliderFlag.Logarithmic - val isFloatingPoint = dataType == DataType.Float || dataType == DataType.Double - val vRange = if (vMin < vMax) vMax - vMin else vMin - vMax - // Calculate bounds - val grabPadding = 2f // FIXME: Should be part of style. - val sliderSz = (bb.max[axis] - bb.min[axis]) - grabPadding * 2f - var grabSz = style.grabMinSize - if (!isFloatingPoint && vRange >= 0) // v_range < 0 may happen on integer overflows - grabSz = (sliderSz / (vRange + 1)).f max style.grabMinSize // For integer sliders: if possible have the grab size represent 1 unit - grabSz = grabSz min sliderSz - val sliderUsableSz = sliderSz - grabSz - val sliderUsablePosMin = bb.min[axis] + grabPadding + grabSz * 0.5f - val sliderUsablePosMax = bb.max[axis] - grabPadding - grabSz * 0.5f + val vRange = vMax.fp - vMin.fp + // Default tweak speed + var vSpeed = when { + vSpeed_ == 0f && isClamped && vRange < Float.MAX_VALUE.fp -> vRange * g.dragSpeedDefaultRatio.fp + else -> vSpeed_.fp + } - var logarithmicZeroEpsilon = 0f // Only valid when is_logarithmic is true - var zeroDeadzoneHalfsize = 0f // Only valid when is_logarithmic is true - if (isLogarithmic) { - // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 1 - logarithmicZeroEpsilon = 0.1f pow decimalPrecision.f - zeroDeadzoneHalfsize = style.logSliderDeadzone * 0.5f / max(sliderUsableSz, 1f) + // Inputs accumulates into g.DragCurrentAccum, which is flushed into the current value as soon as it makes a difference with our precision settings + var adjustDelta = 0f.fp + if (g.activeIdSource == InputSource.Mouse && isMousePosValid() && isMouseDragPastThreshold(MouseButton.Left, io.mouseDragThreshold * DRAG_MOUSE_THRESHOLD_FACTOR)) { + adjustDelta = io.mouseDelta[axis].fp + if (io.keyAlt) adjustDelta *= 1f.fp / 100f.fp + if (io.keyShift) adjustDelta *= 10f.fp + } else if (g.activeIdSource == InputSource.Nav) { + val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 0 + val tweakSlow = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakSlow else Key._NavKeyboardTweakSlow).isDown + val tweakFast = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakFast else Key._NavKeyboardTweakFast).isDown + val tweakFactor = if (tweakSlow) 1f.fp else if (tweakFast) 10f.fp else 1f.fp + adjustDelta = getNavTweakPressedAmount(axis).fp * tweakFactor + vSpeed = vSpeed max getMinimumStepAtDecimalPrecision(decimalPrecision).fp } + adjustDelta *= vSpeed - // Process interacting with the slider - var valueChanged = false - if (g.activeId == id) { - var setNewValue = false - var clickedT = 0f - if (g.activeIdSource == InputSource.Mouse) - if (!io.mouseDown[0]) - clearActiveID() - else { - val mouseAbsPos = io.mousePos[axis] - if (g.activeIdIsJustActivated) { - var grabT: Float = scaleRatioFromValueT(dataType, v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - if (axis == Axis.Y) - grabT = 1f - grabT - val grabPos = lerp(sliderUsablePosMin, sliderUsablePosMax, grabT) - val clickedAroundGrab = mouseAbsPos >= grabPos - grabSz * 0.5f - 1f && mouseAbsPos <= grabPos + grabSz * 0.5f + 1f // No harm being extra generous here. - g.sliderGrabClickOffset = if (clickedAroundGrab && isFloatingPoint) mouseAbsPos - grabPos else 0f - } - if (sliderUsableSz > 0f) - clickedT = saturate((mouseAbsPos - g.sliderGrabClickOffset - sliderUsablePosMin) / sliderUsableSz) - if (axis == Axis.Y) - clickedT = 1f - clickedT - setNewValue = true - } - else if (g.activeIdSource == InputSource.Nav) { + // For vertical drag we currently assume that Up=higher value (like we do with vertical sliders). This may become a parameter. + if (axis == Axis.Y) adjustDelta = -adjustDelta - if (g.activeIdIsJustActivated) { - g.sliderCurrentAccum = 0f // Reset any stored nav delta upon activation - g.sliderCurrentAccumDirty = false - } + // For logarithmic use our range is effectively 0..1 so scale the delta into that range + if (isLogarithmic && vRange < Float.MAX_VALUE.fp && vRange > 0.000001f.fp) // Epsilon to avoid /0 + adjustDelta /= vRange - var inputDelta = if (axis == Axis.X) getNavTweakPressedAmount(axis) else -getNavTweakPressedAmount(axis) - if (inputDelta != 0f) { - val tweakSlow = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakSlow else Key._NavKeyboardTweakSlow).isDown - val tweakFast = (if (g.navInputSource == InputSource.Gamepad) Key._NavGamepadTweakFast else Key._NavKeyboardTweakFast).isDown - val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 0 - if (decimalPrecision > 0) { - inputDelta /= 100f // Gamepad/keyboard tweak speeds in % of slider bounds - if (tweakSlow) - inputDelta /= 10f - } else if ((vRange >= -100f && vRange <= 100f) || tweakSlow) - inputDelta = (if (inputDelta < 0f) -1f else +1f) / vRange.f // Gamepad/keyboard tweak speeds in integer steps - else - inputDelta /= 100f - if (tweakFast) - inputDelta *= 10f + // Clear current value on activation + // Avoid altering values and clamping when we are _already_ past the limits and heading in the same direction, so e.g. if range is 0..255, current value is 300 and we are pushing to the right side, keep the 300. + val isJustActivated = g.activeIdIsJustActivated + val isAlreadyPastLimitsAndPushingOutward = isClamped && ((v() >= vMax && adjustDelta > 0f.fp) || (v() <= vMin && adjustDelta < 0f.fp)) + if (isJustActivated || isAlreadyPastLimitsAndPushingOutward) { + g.dragCurrentAccum = 0f + g.dragCurrentAccumDirty = false + } else if (adjustDelta != 0f.fp) { + g.dragCurrentAccum += adjustDelta.f + g.dragCurrentAccumDirty = true + } - g.sliderCurrentAccum += inputDelta - g.sliderCurrentAccumDirty = true - } + if (!g.dragCurrentAccumDirty) return false - val delta = g.sliderCurrentAccum + var vCur = v() + var vOldRefForAccumRemainder = 0f - if (g.navActivatePressedId == id && !g.activeIdIsJustActivated) - clearActiveID() - else if (g.sliderCurrentAccumDirty) { - clickedT = scaleRatioFromValueT(dataType, v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) + var logarithmicZeroEpsilon = 0f // Only valid when is_logarithmic is true + val zeroDeadzoneHalfsize = 0f // Drag widgets have no deadzone (as it doesn't make sense) + if (isLogarithmic) { + // When using logarithmic sliders, we need to clamp to avoid hitting zero, but our choice of clamp value greatly affects slider precision. We attempt to use the specified precision to estimate a good lower bound. + val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 1 + logarithmicZeroEpsilon = 0.1f pow decimalPrecision.f - if ((clickedT >= 1f && delta > 0f) || (clickedT <= 0f && delta < 0f)) { // This is to avoid applying the saturation when already past the limits - setNewValue = false - g.sliderCurrentAccum = 0f // If pushing up against the limits, don't continue to accumulate - } else { - setNewValue = true - val oldClickedT = clickedT - clickedT = saturate(clickedT + delta) + // Convert to parametric space, apply delta, convert back + val vOldParametric = scaleRatioFromValue(vCur, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) + val vNewParametric = vOldParametric + g.dragCurrentAccum + vCur = scaleValueFromRatio(vNewParametric, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) + vOldRefForAccumRemainder = vOldParametric + } else { + val dragAccum = g.dragCurrentAccum.fp + if (dragAccum < 0f.fp) vCur -= (-dragAccum).n + else vCur += dragAccum.n - // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator - var vNew = scaleValueFromRatioT(dataType, clickedT, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - if (flags has SliderFlag.NoRoundToFormat) - vNew = roundScalarWithFormatT(format, dataType, vNew) - val newClickedT = scaleRatioFromValueT(dataType, vNew, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) + } - g.sliderCurrentAccum -= when { - delta > 0 -> min(newClickedT - oldClickedT, delta) - else -> max(newClickedT - oldClickedT, delta) - } - } + // Round to user desired precision based on format string + if (isFloatingPoint && flags hasnt SliderFlag.NoRoundToFormat) vCur = roundScalarWithFormat(format, vCur) - g.sliderCurrentAccumDirty = false - } + // Preserve remainder after rounding has been applied. This also allow slow tweaking of values. + g.dragCurrentAccumDirty = false + g.dragCurrentAccum -= when { + isLogarithmic -> { + // Convert to parametric space, apply delta, convert back + val vNewParametric = scaleRatioFromValue(vCur, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) + (vNewParametric - vOldRefForAccumRemainder).f } - if (setNewValue) { - var vNew = scaleValueFromRatioT(dataType, clickedT, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - - // Round to user desired precision based on format string - if (flags hasnt SliderFlag.NoRoundToFormat) - vNew = roundScalarWithFormatT(format, dataType, vNew) - - // Apply result - if (v() != vNew) { - v(vNew) - valueChanged = true - } - } + else -> (vCur.fp - v().fp).f } - if (sliderSz < 1f) - outGrabBb.put(bb.min, bb.min) - else { - // Output grab position so it can be displayed by the caller - var grabT = scaleRatioFromValueT(dataType, v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - if (axis == Axis.Y) - grabT = 1f - grabT - val grabPos = lerp(sliderUsablePosMin, sliderUsablePosMax, grabT) - if (axis == Axis.X) - outGrabBb.put(grabPos - grabSz * 0.5f, bb.min.y + grabPadding, grabPos + grabSz * 0.5f, bb.max.y - grabPadding) - else - outGrabBb.put(bb.min.x + grabPadding, grabPos - grabSz * 0.5f, bb.max.x - grabPadding, grabPos + grabSz * 0.5f) + // Lose zero sign for float/double + if (vCur.fp == (-0f).fp) vCur = zero + + // Clamp values (+ handle overflow/wrap-around for integer types) + if (v() != vCur && isClamped) { + if (vCur < vMin || (vCur > v() && adjustDelta < 0f.fp && !isFloatingPoint)) vCur = vMin + if (vCur > vMax || (vCur < v() && adjustDelta > 0f.fp && !isFloatingPoint)) vCur = vMax } - return valueChanged + // Apply result + if (v() == vCur) return false + v(vCur) + return true } - /** FIXME: Move more of the code into SliderBehavior() - * template */ - fun sliderBehaviorT(bb: Rect, id: ID, dataType: DataType, v: KMutableProperty0, vMin: Double, vMax: Double, - format: String, flags: SliderFlags, outGrabBb: Rect): Boolean { + /** Try to move more of the code into shared SliderBehavior() + * template */ + fun NumberFpOps.sliderBehaviorT(bb: Rect, id: ID, v: KMutableProperty0, vMin: N, vMax: N, format: String, flags: SliderFlags, outGrabBb: Rect): Boolean where N : Number, N : Comparable, FP : Number, FP : Comparable { val axis = if (flags has SliderFlag._Vertical) Axis.Y else Axis.X val isLogarithmic = flags has SliderFlag.Logarithmic @@ -2169,8 +289,8 @@ internal interface templateFunctions { val grabPadding = 2f // FIXME: Should be part of style. val sliderSz = (bb.max[axis] - bb.min[axis]) - grabPadding * 2f var grabSz = style.grabMinSize - if (!isFloatingPoint && vRange >= 0) // v_range < 0 may happen on integer overflows - grabSz = (sliderSz / (vRange + 1)).f max style.grabMinSize // For integer sliders: if possible have the grab size represent 1 unit + if (!isFloatingPoint && vRange >= zero) // v_range < 0 may happen on integer overflows + grabSz = (sliderSz.fp / (vRange + one).fp).f max style.grabMinSize // For integer sliders: if possible have the grab size represent 1 unit grabSz = grabSz min sliderSz val sliderUsableSz = sliderSz - grabSz val sliderUsablePosMin = bb.min[axis] + grabPadding + grabSz * 0.5f @@ -2190,25 +310,20 @@ internal interface templateFunctions { if (g.activeId == id) { var setNewValue = false var clickedT = 0f - if (g.activeIdSource == InputSource.Mouse) - if (!io.mouseDown[0]) - clearActiveID() - else { - val mouseAbsPos = io.mousePos[axis] - if (g.activeIdIsJustActivated) { - var grabT: Float = scaleRatioFromValueT(dataType, v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - if (axis == Axis.Y) - grabT = 1f - grabT - val grabPos = lerp(sliderUsablePosMin, sliderUsablePosMax, grabT) - val clickedAroundGrab = mouseAbsPos >= grabPos - grabSz * 0.5f - 1f && mouseAbsPos <= grabPos + grabSz * 0.5f + 1f // No harm being extra generous here. - g.sliderGrabClickOffset = if (clickedAroundGrab && isFloatingPoint) mouseAbsPos - grabPos else 0f - } - if (sliderUsableSz > 0f) - clickedT = saturate((mouseAbsPos - g.sliderGrabClickOffset - sliderUsablePosMin) / sliderUsableSz) - if (axis == Axis.Y) - clickedT = 1f - clickedT - setNewValue = true + if (g.activeIdSource == InputSource.Mouse) if (!io.mouseDown[0]) clearActiveID() + else { + val mouseAbsPos = io.mousePos[axis] + if (g.activeIdIsJustActivated) { + var grabT: Float = scaleRatioFromValue(v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) + if (axis == Axis.Y) grabT = 1f - grabT + val grabPos = lerp(sliderUsablePosMin, sliderUsablePosMax, grabT) + val clickedAroundGrab = mouseAbsPos >= grabPos - grabSz * 0.5f - 1f && mouseAbsPos <= grabPos + grabSz * 0.5f + 1f // No harm being extra generous here. + g.sliderGrabClickOffset = if (clickedAroundGrab && isFloatingPoint) mouseAbsPos - grabPos else 0f } + if (sliderUsableSz > 0f) clickedT = saturate((mouseAbsPos - g.sliderGrabClickOffset - sliderUsablePosMin) / sliderUsableSz) + if (axis == Axis.Y) clickedT = 1f - clickedT + setNewValue = true + } else if (g.activeIdSource == InputSource.Nav) { if (g.activeIdIsJustActivated) { @@ -2223,14 +338,10 @@ internal interface templateFunctions { val decimalPrecision = if (isFloatingPoint) parseFormatPrecision(format, 3) else 0 if (decimalPrecision > 0) { inputDelta /= 100f // Gamepad/keyboard tweak speeds in % of slider bounds - if (tweakSlow) - inputDelta /= 10f - } else if ((vRange >= -100f && vRange <= 100f) || tweakSlow) - inputDelta = (if (inputDelta < 0f) -1f else +1f) / vRange.f // Gamepad/keyboard tweak speeds in integer steps - else - inputDelta /= 100f - if (tweakFast) - inputDelta *= 10f + if (tweakSlow) inputDelta /= 10f + } else if ((vRange.fp >= (-100f).fp && vRange.fp <= 100f.fp) || tweakSlow) inputDelta = (if (inputDelta < 0f) -1f else +1f) / vRange.f // Gamepad/keyboard tweak speeds in integer steps + else inputDelta /= 100f + if (tweakFast) inputDelta *= 10f g.sliderCurrentAccum += inputDelta g.sliderCurrentAccumDirty = true @@ -2238,10 +349,9 @@ internal interface templateFunctions { val delta = g.sliderCurrentAccum - if (g.navActivatePressedId == id && !g.activeIdIsJustActivated) - clearActiveID() + if (g.navActivatePressedId == id && !g.activeIdIsJustActivated) clearActiveID() else if (g.sliderCurrentAccumDirty) { - clickedT = scaleRatioFromValueT(dataType, v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) + clickedT = scaleRatioFromValue(v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) if ((clickedT >= 1f && delta > 0f) || (clickedT <= 0f && delta < 0f)) { // This is to avoid applying the saturation when already past the limits setNewValue = false @@ -2251,29 +361,25 @@ internal interface templateFunctions { val oldClickedT = clickedT clickedT = saturate(clickedT + delta) - // Calculate what our "new" clicked_t will be, and thus how far we actually moved the slider, and subtract this from the accumulator - var vNew = scaleValueFromRatioT(dataType, clickedT, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - if (flags hasnt SliderFlag.NoRoundToFormat) - vNew = roundScalarWithFormatT(format, dataType, vNew) - val newClickedT = scaleRatioFromValueT(dataType, vNew, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) + var vNew = scaleValueFromRatio(clickedT, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) + if (isFloatingPoint && flags hasnt SliderFlag.NoRoundToFormat) vNew = roundScalarWithFormat(format, vNew) + val newClickedT = scaleRatioFromValue(vNew, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) g.sliderCurrentAccum -= when { delta > 0 -> min(newClickedT - oldClickedT, delta) else -> max(newClickedT - oldClickedT, delta) } } - g.sliderCurrentAccumDirty = false } } if (setNewValue) { - var vNew = scaleValueFromRatioT(dataType, clickedT, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) + var vNew = scaleValueFromRatio(clickedT, vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) // Round to user desired precision based on format string - if (flags hasnt SliderFlag.NoRoundToFormat) - vNew = roundScalarWithFormatT(format, dataType, vNew) + if (isFloatingPoint && flags hasnt SliderFlag.NoRoundToFormat) vNew = roundScalarWithFormat(format, vNew) // Apply result if (v() != vNew) { @@ -2283,67 +389,76 @@ internal interface templateFunctions { } } - if (sliderSz < 1f) - outGrabBb.put(bb.min, bb.min) + if (sliderSz < 1f) outGrabBb.put(bb.min, bb.min) else { // Output grab position so it can be displayed by the caller - var grabT = scaleRatioFromValueT(dataType, v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) - if (axis == Axis.Y) - grabT = 1f - grabT + var grabT = scaleRatioFromValue(v(), vMin, vMax, isLogarithmic, logarithmicZeroEpsilon, zeroDeadzoneHalfsize) + if (axis == Axis.Y) grabT = 1f - grabT val grabPos = lerp(sliderUsablePosMin, sliderUsablePosMax, grabT) - if (axis == Axis.X) - outGrabBb.put(grabPos - grabSz * 0.5f, bb.min.y + grabPadding, grabPos + grabSz * 0.5f, bb.max.y - grabPadding) - else - outGrabBb.put(bb.min.x + grabPadding, grabPos - grabSz * 0.5f, bb.max.x - grabPadding, grabPos + grabSz * 0.5f) + if (axis == Axis.X) outGrabBb.put(grabPos - grabSz * 0.5f, bb.min.y + grabPadding, grabPos + grabSz * 0.5f, bb.max.y - grabPadding) + else outGrabBb.put(bb.min.x + grabPadding, grabPos - grabSz * 0.5f, bb.max.x - grabPadding, grabPos + grabSz * 0.5f) } return valueChanged } - /** template */ + fun NumberOps.roundScalarWithFormat(fmt: String, v: N): N where N : Number, N : Comparable { + assert(isFloatingPoint) val fmtStart = parseFormatFindStart2(fmt) if (fmt.getOrNul(fmtStart + 0) != '%' || fmt.getOrNul(fmtStart + 1) == '%') // Don't apply if the value is not visible in the format string return v val fmtEnd = parseFormatFindEnd(fmt, fmtStart) val vStr = fmt.substring(fmtStart, fmtEnd).format(v).trimStart() - return vStr.replace(',', '.').d + return vStr.replace(',', '.').parsed } - fun > checkboxFlagsT(label: String, flagsPtr: KMutableProperty0>, flagsValue: Flag): Boolean { + fun > checkboxFlagsT(label: String, flagsPtr: KMutableProperty0>, flagsValue: Flag): Boolean { var flags by flagsPtr - var allOn = flagsValue in flags - _b = allOn + val allOnRef = (flagsValue in flags).mutableReference + val allOn by allOnRef val anyOn = flags has flagsValue val pressed = when { !allOn && anyOn -> { - val window = currentWindow val backupItemFlags = g.currentItemFlags g.currentItemFlags = g.currentItemFlags or ItemFlag.MixedValue - checkbox(label, ::_b).also { + checkbox(label, allOnRef).also { g.currentItemFlags = backupItemFlags } } - else -> checkbox(label, ::_b) + + else -> checkbox(label, allOnRef) + } + if (pressed) flags = when { + allOn -> flags or flagsValue + else -> flags wo flagsValue } - allOn = _b - if (pressed) - flags = when { - allOn -> flags or flagsValue - else -> flags wo flagsValue - } return pressed } } + +inline fun widgetN(label: String, components: Int, widgets: (Int) -> Boolean): Boolean { + if (currentWindow.skipItems) return false + + var valueChanged = false + group { + withID(label) { + pushMultiItemsWidths(components, calcItemWidth()) + for (i in 0 until components) { + withID(i) { + if (i > 0) sameLine(0f, style.itemInnerSpacing.x) + valueChanged /= widgets(i) + } + popItemWidth() + } + } + + val labelEnd = findRenderedTextEnd(label) + if (0 != labelEnd) { + sameLine(0f, style.itemInnerSpacing.x) + textEx(label, labelEnd) + } + + } + return valueChanged +} \ No newline at end of file diff --git a/core/src/main/kotlin/imgui/internal/api/widgets.kt b/core/src/main/kotlin/imgui/internal/api/widgets.kt index 21ba02c17..ff8011e08 100644 --- a/core/src/main/kotlin/imgui/internal/api/widgets.kt +++ b/core/src/main/kotlin/imgui/internal/api/widgets.kt @@ -30,6 +30,7 @@ import imgui.api.g import imgui.internal.classes.Rect import imgui.internal.sections.* import kotlin.math.max +import kotlin.math.min /** Widgets */ internal interface widgets { diff --git a/core/src/main/kotlin/imgui/internal/api/widgetsLowLevelBehaviors.kt b/core/src/main/kotlin/imgui/internal/api/widgetsLowLevelBehaviors.kt index 430f46102..ee4ed5c30 100644 --- a/core/src/main/kotlin/imgui/internal/api/widgetsLowLevelBehaviors.kt +++ b/core/src/main/kotlin/imgui/internal/api/widgetsLowLevelBehaviors.kt @@ -1,8 +1,8 @@ package imgui.internal.api import gli_.has -import glm_.* import glm_.func.common.max +import glm_.glm import glm_.vec2.Vec2 import imgui.* import imgui.ImGui.calcTextSize @@ -10,6 +10,7 @@ import imgui.ImGui.calcTypematicRepeatAmount import imgui.ImGui.clearActiveID import imgui.ImGui.currentWindow import imgui.ImGui.data +import imgui.ImGui.dragBehavior import imgui.ImGui.dragBehaviorT import imgui.ImGui.findRenderedTextEnd import imgui.ImGui.focusWindow @@ -38,6 +39,7 @@ import imgui.ImGui.setActiveID import imgui.ImGui.setFocusID import imgui.ImGui.setItemAllowOverlap import imgui.ImGui.setOwner +import imgui.ImGui.sliderBehavior import imgui.ImGui.sliderBehaviorT import imgui.ImGui.style import imgui.ImGui.testOwner @@ -49,10 +51,6 @@ import imgui.internal.sections.* import imgui.static.DRAGDROP_HOLD_TO_OPEN_TIMER import kool.getValue import kool.setValue -import unsigned.Ubyte -import unsigned.Uint -import unsigned.Ulong -import unsigned.Ushort import kotlin.math.max import kotlin.reflect.KMutableProperty0 import imgui.TreeNodeFlag as Tnf @@ -281,128 +279,39 @@ internal interface widgetsLowLevelBehaviors { val isDoubleClickRelease = flags has Bf.PressedOnDoubleClick && g.io.mouseReleased[mouseButton.i] && io.mouseClickedLastCount[mouseButton.i] == 2 val isRepeatingAlready = flags has Bf.Repeat && io.mouseDownDurationPrev[mouseButton.i] >= io.keyRepeatDelay // Repeat mode trumps val isButtonAvailOrOwned = mouseButton.key testOwner testOwnerId - if (!isDoubleClickRelease && !isRepeatingAlready && isButtonAvailOrOwned) - pressed = true + if (!isDoubleClickRelease && !isRepeatingAlready && isButtonAvailOrOwned) pressed = true } clearActiveID() } - if (flags hasnt Bf.NoNavFocus) - g.navDisableHighlight = true + if (flags hasnt Bf.NoNavFocus) g.navDisableHighlight = true } else if (g.activeIdSource == InputSource.Nav) // When activated using Nav, we hold on the ActiveID until activation button is released - if (g.navActivateDownId != id) - clearActiveID() - if (pressed) - g.activeIdHasBeenPressedBefore = true + if (g.navActivateDownId != id) clearActiveID() + if (pressed) g.activeIdHasBeenPressedBefore = true } return booleanArrayOf(pressed, hovered, held) } - fun dragBehavior(id: ID, dataType: DataType, pV: FloatArray, ptr: Int, vSpeed: Float, min: Float?, max: Float?, - format: String, flags: SliderFlags): Boolean = - withFloat(pV, ptr) { dragBehavior(id, DataType.Float, it, vSpeed, min?.asMutableProperty, max?.asMutableProperty, format, flags) } + fun dragBehavior(id: ID, pV: FloatArray, ptr: Int, vSpeed: Float, min: Float?, max: Float?, format: String, flags: SliderFlags): Boolean = dragBehavior(id, pV mutablePropertyAt ptr, vSpeed, min, max, format, flags) - fun dragBehavior(id: ID, dataType: DataType, pV: KMutableProperty0, vSpeed: Float, - pMin: KMutableProperty0?, pMax: KMutableProperty0?, - format: String, flags: SliderFlags): Boolean - where N : Number, N : Comparable { + fun NumberOps.dragBehavior(id: ID, pV: KMutableProperty0, vSpeed: Float, min: N?, max: N?, format: String, flags: SliderFlags): Boolean where N : Number, N : Comparable { if (g.activeId == id) // Those are the things we can do easily outside the DragBehaviorT<> template, saves code generation. - if (g.activeIdSource == InputSource.Mouse && !io.mouseDown[0]) - clearActiveID() - else if (g.activeIdSource == InputSource.Nav && g.navActivatePressedId == id && !g.activeIdIsJustActivated) - clearActiveID() + if (g.activeIdSource == InputSource.Mouse && !io.mouseDown[0]) clearActiveID() + else if (g.activeIdSource == InputSource.Nav && g.navActivatePressedId == id && !g.activeIdIsJustActivated) clearActiveID() - if (g.activeId != id) - return false - if (g.lastItemData.inFlags has ItemFlag.ReadOnly || flags has SliderFlag._ReadOnly) - return false - - var v by pV + if (g.activeId != id) return false + if (g.lastItemData.inFlags has ItemFlag.ReadOnly || flags has SliderFlag._ReadOnly) return false - return when (v) { - is Byte -> { - _i32 = v.i - val min = pMin?.get() ?: Byte.MIN_VALUE - val max = pMax?.get() ?: Byte.MAX_VALUE - dragBehaviorT(DataType.Int, ::_i32, vSpeed, min.i, max.i, format, flags).also { - if (it) - v = _i32.b as N - } - } - is Ubyte -> { - _ui.v = v.i - val min = pMin?.get() ?: Ubyte.MIN_VALUE - val max = pMax?.get() ?: Ubyte.MAX_VALUE - dragBehaviorT(DataType.Uint, ::_ui, vSpeed, min.ui, max.ui, format, flags).also { - if (it) - (v as Ubyte).v = _ui.b - } - } - is Short -> { - _i32 = v.i - val min = pMin?.get() ?: Short.MIN_VALUE - val max = pMax?.get() ?: Short.MAX_VALUE - dragBehaviorT(DataType.Int, ::_i32, vSpeed, min.i, max.i, format, flags).also { - if (it) - v = _i32.s as N - } - } - is Ushort -> { - _ui.v = v.i - val min = pMin?.get() ?: Ushort.MIN_VALUE - val max = pMax?.get() ?: Ushort.MAX_VALUE - dragBehaviorT(DataType.Uint, ::_ui, vSpeed, min.ui, max.ui, format, flags).also { - if (it) - (v as Ushort).v = _ui.s - } - } - is Int -> { - val min = pMin?.get() ?: Int.MIN_VALUE - val max = pMax?.get() ?: Int.MAX_VALUE - dragBehaviorT(DataType.Int, pV as KMutableProperty0, vSpeed, min.i, max.i, format, flags) - } - is Uint -> { - val min = pMin?.get() ?: Uint.MIN_VALUE - val max = pMax?.get() ?: Uint.MAX_VALUE - dragBehaviorT(DataType.Uint, pV as KMutableProperty0, vSpeed, min.ui, max.ui, format, flags) - } - is Long -> { - val min = pMin?.get() ?: Long.MIN_VALUE - val max = pMax?.get() ?: Long.MAX_VALUE - dragBehaviorT(DataType.Long, pV as KMutableProperty0, vSpeed, min.L, max.L, format, flags) - } - is Ulong -> { - val min = pMin?.get() ?: Ulong.MIN_VALUE - val max = pMax?.get() ?: Ulong.MAX_VALUE - dragBehaviorT(DataType.Ulong, pV as KMutableProperty0, vSpeed, min.ul, max.ul, format, flags) - } - is Float -> { - val min = pMin?.get() ?: Float.MIN_VALUE - val max = pMax?.get() ?: Float.MAX_VALUE - dragBehaviorT(DataType.Float, pV as KMutableProperty0, vSpeed, min.f, max.f, format, flags) - } - is Double -> { - val min = pMin?.get() ?: Double.MIN_VALUE - val max = pMax?.get() ?: Double.MAX_VALUE - dragBehaviorT(DataType.Double, pV as KMutableProperty0, vSpeed, min.d, max.d, format, flags) - } - else -> error("Invalid") // ~IM_ASSERT(0); return false; - } + return fpOps.dragBehaviorT(pV, vSpeed, min ?: this.min, max ?: this.max, format, flags) } /** For 32-bits and larger types, slider bounds are limited to half the natural type range. * So e.g. an integer Slider between INT_MAX-10 and INT_MAX will fail, but an integer Slider between INT_MAX/2-10 and INT_MAX/2 will be ok. * It would be possible to lift that limitation with some work but it doesn't seem to be worth it for sliders. */ - fun sliderBehavior(bb: Rect, id: ID, pV: FloatArray, pMin: Float, pMax: Float, format: String, - flags: SliderFlags, outGrabBb: Rect): Boolean = - sliderBehavior(bb, id, pV, 0, pMin, pMax, format, flags, outGrabBb) - - fun sliderBehavior(bb: Rect, id: ID, pV: FloatArray, ptr: Int, min: Float, max: Float, - format: String, flags: SliderFlags, outGrabBb: Rect): Boolean = - withFloat(pV, ptr) { - sliderBehavior(bb, id, DataType.Float, it, min.asMutableProperty, max.asMutableProperty, format, flags, outGrabBb) - } + fun sliderBehavior(bb: Rect, id: ID, pV: FloatArray, pMin: Float, pMax: Float, format: String, flags: SliderFlags, outGrabBb: Rect): Boolean = sliderBehavior(bb, id, pV, 0, pMin, pMax, format, flags, outGrabBb) + + fun sliderBehavior(bb: Rect, id: ID, pV: FloatArray, ptr: Int, min: Float, max: Float, format: String, flags: SliderFlags, outGrabBb: Rect): Boolean = sliderBehavior(bb, id, pV mutablePropertyAt ptr, min, max, format, flags, outGrabBb) // fun sliderBehavior(bb: Rect, id: ID, // v: KMutableProperty0, @@ -410,77 +319,19 @@ internal interface widgetsLowLevelBehaviors { // format: String, power: Float, // flags: SliderFlags, outGrabBb: Rect): Boolean where N : Number, N : Comparable = // sliderBehavior(bb, id, DataType.Float, v, vMin, vMax, format, power, flags, outGrabBb) + fun NumberOps.sliderBehavior(bb: Rect, id: ID, pV: KMutableProperty0, min: N, max: N, format: String, flags: SliderFlags, outGrabBb: Rect): Boolean where N : Number, N : Comparable = fpOps.sliderBehavior(bb, id, pV, min, max, format, flags, outGrabBb) - fun sliderBehavior(bb: Rect, id: ID, dataType: DataType, pV: KMutableProperty0, - pMin: KMutableProperty0, pMax: KMutableProperty0, - format: String, flags: SliderFlags, outGrabBb: Rect): Boolean - where N : Number, N : Comparable { - // Those are the things we can do easily outside the SliderBehaviorT<> template, saves code generation. - if (g.lastItemData.inFlags has ItemFlag.ReadOnly || flags has SliderFlag._ReadOnly) - return false - - var v by pV + fun NumberFpOps.sliderBehavior(bb: Rect, id: ID, pV: KMutableProperty0, min: N, max: N, format: String, flags: SliderFlags, outGrabBb: Rect): Boolean where N : Number, N : Comparable, FP : Number, FP : Comparable { + // Those are the things we can do easily outside sliderBehaviorT + if (g.lastItemData.inFlags has ItemFlag.ReadOnly || flags has SliderFlag._ReadOnly) return false - return when (dataType) { - DataType.Byte -> { - _i32 = v.i - sliderBehaviorT(bb, id, dataType, ::_i32, pMin().i, pMax().i, format, flags, outGrabBb).also { - if (it) - v = _i32.b as N - } - } - DataType.Ubyte -> { - _ui.v = v.i - sliderBehaviorT(bb, id, dataType, ::_ui, pMin().ui, pMax().ui, format, flags, outGrabBb).also { - if (it) - (v as Ubyte).v = _ui.b - } - } - DataType.Short -> { - _i32 = v.i - sliderBehaviorT(bb, id, dataType, ::_i32, pMin().i, pMax().i, format, flags, outGrabBb).also { - if (it) - v = _i32.s as N - } - } - DataType.Ushort -> { - _ui.v = v.i - sliderBehaviorT(bb, id, dataType, ::_ui, pMin().ui, pMax().ui, format, flags, outGrabBb).also { - if (it) - (v as Ushort).v = _ui.s - } - } - DataType.Int -> { - assert(pMin() as Int >= Int.MIN_VALUE / 2 && pMax() as Int <= Int.MAX_VALUE / 2) - sliderBehaviorT(bb, id, dataType, pV as KMutableProperty0, pMin() as Int, pMax() as Int, format, flags, outGrabBb) - } - DataType.Uint -> { - assert(pMax() as Uint <= Uint.MAX / 2) - sliderBehaviorT(bb, id, dataType, pV as KMutableProperty0, pMin() as Uint, pMax() as Uint, format, flags, outGrabBb) - } - DataType.Long -> { - assert(pMin() as Long >= Long.MIN_VALUE / 2 && pMax() as Long <= Long.MAX_VALUE / 2) - sliderBehaviorT(bb, id, dataType, pV as KMutableProperty0, pMin() as Long, pMax() as Long, format, flags, outGrabBb) - } - DataType.Ulong -> { - assert(pMax() as Ulong <= Ulong.MAX / 2) - sliderBehaviorT(bb, id, dataType, pV as KMutableProperty0, pMin() as Ulong, pMax() as Ulong, format, flags, outGrabBb) - } - DataType.Float -> { - assert(pMin() as Float >= -Float.MAX_VALUE / 2f && pMax() as Float <= Float.MAX_VALUE / 2f) - sliderBehaviorT(bb, id, dataType, pV as KMutableProperty0, pMin() as Float, pMax() as Float, format, flags, outGrabBb) - } - DataType.Double -> { - assert(pMin() as Double >= -Double.MAX_VALUE / 2f && pMax() as Double <= Double.MAX_VALUE / 2f) - sliderBehaviorT(bb, id, dataType, pV as KMutableProperty0, pMin() as Double, pMax() as Double, format, flags, outGrabBb) - } - else -> throw Error() - } + // We allow the full range for bytes and shorts + assert(isSmallerThanInt || min >= this.min / 2.coerced && max <= this.max / 2.coerced) + return sliderBehaviorT(bb, id, pV, min, max, format, flags, outGrabBb) } /** Using 'hover_visibility_delay' allows us to hide the highlight and mouse cursor for a short time, which can be convenient to reduce visual noise. */ - fun splitterBehavior(bb: Rect, id: ID, axis: Axis, size1ptr: KMutableProperty0, size2ptr: KMutableProperty0, - minSize1: Float, minSize2: Float, hoverExtend: Float = 0f, hoverVisibilityDelay: Float = 0f, bgCol: Int = 0): Boolean { + fun splitterBehavior(bb: Rect, id: ID, axis: Axis, size1ptr: KMutableProperty0, size2ptr: KMutableProperty0, minSize1: Float, minSize2: Float, hoverExtend: Float = 0f, hoverVisibilityDelay: Float = 0f, bgCol: Int = 0): Boolean { var size1 by size1ptr var size2 by size2ptr @@ -756,18 +607,19 @@ internal interface widgetsLowLevelBehaviors { isOpen = g.nextItemData.openVal storage[id] = isOpen treeNodeSetOpen(id, isOpen) - } else - isOpen = storedValue != 0 + } else isOpen = storedValue != 0 } - } else - isOpen = storage[id] ?: (flags has Tnf.DefaultOpen) + } else isOpen = storage[id] ?: (flags has Tnf.DefaultOpen) /* When logging is enabled, we automatically expand tree nodes (but *NOT* collapsing headers.. seems like sensible behavior). NB- If we are above max depth we still allow manually opened nodes to be logged. */ - if (g.logEnabled && flags hasnt Tnf.NoAutoOpenOnLog && (window.dc.treeDepth - g.logDepthRef) < g.logDepthToExpand) - isOpen = true + if (g.logEnabled && flags hasnt Tnf.NoAutoOpenOnLog && (window.dc.treeDepth - g.logDepthRef) < g.logDepthToExpand) isOpen = true return isOpen } -} \ No newline at end of file +} + +inline fun dragBehavior(id: ID, pV: KMutableProperty0, vSpeed: Float, min: N?, max: N?, format: String, flags: SliderFlags): Boolean where N : Number, N : Comparable = numberFpOps().dragBehavior(id, pV, vSpeed, min, max, format, flags) + +inline fun sliderBehavior(bb: Rect, id: ID, pV: KMutableProperty0, min: N, max: N, format: String, flags: SliderFlags, outGrabBb: Rect): Boolean where N : Number, N : Comparable = numberFpOps().sliderBehavior(bb, id, pV, min, max, format, flags, outGrabBb) \ No newline at end of file diff --git a/core/src/main/kotlin/imgui/internal/api/widgetsWindowDecorations.kt b/core/src/main/kotlin/imgui/internal/api/widgetsWindowDecorations.kt index fdb628316..99aba6913 100644 --- a/core/src/main/kotlin/imgui/internal/api/widgetsWindowDecorations.kt +++ b/core/src/main/kotlin/imgui/internal/api/widgetsWindowDecorations.kt @@ -103,9 +103,7 @@ internal interface widgetsWindowDecorations { } val sizeAvail = window.innerRect.max[axis] - window.innerRect.min[axis] val sizeContents = window.contentSize[axis] + window.windowPadding[axis] * 2f - _L = window.scroll[axis].L - scrollbarEx(bb, id, axis, ::_L, sizeAvail.L, sizeContents.L, roundingCorners) - window.scroll[axis] = _L.f + scrollbarEx(bb, id, axis, (window.scroll mutablePropertyAt axis).L, sizeAvail.L, sizeContents.L, roundingCorners) } /** Vertical/Horizontal scrollbar diff --git a/core/src/main/kotlin/imgui/internal/classes/Window.kt b/core/src/main/kotlin/imgui/internal/classes/Window.kt index d5d844f0b..2ac8872be 100644 --- a/core/src/main/kotlin/imgui/internal/classes/Window.kt +++ b/core/src/main/kotlin/imgui/internal/classes/Window.kt @@ -19,6 +19,7 @@ import imgui.internal.* import imgui.internal.sections.* import imgui.static.addTo import java.util.* +import kotlin.math.max import imgui.WindowFlag as Wf /** Storage for one window */ diff --git a/core/src/main/kotlin/imgui/internal/classes/misc.kt b/core/src/main/kotlin/imgui/internal/classes/misc.kt index 8f5750c1a..e1600eadb 100644 --- a/core/src/main/kotlin/imgui/internal/classes/misc.kt +++ b/core/src/main/kotlin/imgui/internal/classes/misc.kt @@ -381,7 +381,7 @@ class WindowStackData { assert(sizeOfFocusScopeStack == g.focusScopeStack.size) { "PushFocusScope/PopFocusScope Mismatch!" } } } -}; +} class ShrinkWidthItem(var index: Int = 0, var width: Float = 0f, diff --git a/core/src/main/kotlin/imgui/internal/generic helpers.kt b/core/src/main/kotlin/imgui/internal/generic helpers.kt index 6f717773e..3804523ae 100644 --- a/core/src/main/kotlin/imgui/internal/generic helpers.kt +++ b/core/src/main/kotlin/imgui/internal/generic helpers.kt @@ -18,6 +18,7 @@ import java.nio.ByteOrder import java.nio.charset.StandardCharsets import java.util.regex.Pattern import kotlin.math.abs +import kotlin.math.min import kotlin.reflect.KMutableProperty0 @@ -841,7 +842,11 @@ fun triangleClosestPoint(a: Vec2, b: Vec2, c: Vec2, p: Vec2): Vec2 { } } -fun triangleBarycentricCoords(a: Vec2, b: Vec2, c: Vec2, p: Vec2): FloatArray { +fun triangleBarycentricCoords(a: Vec2, b: Vec2, c: Vec2, p: Vec2): FloatArray = FloatArray(3).apply { + triangleBarycentricCoords(a, b, c, p, ::put) +} + +fun triangleBarycentricCoords(a: Vec2, b: Vec2, c: Vec2, p: Vec2, setter: Vec3Setter) { val v0 = b - a val v1 = c - a val v2 = p - a @@ -849,7 +854,7 @@ fun triangleBarycentricCoords(a: Vec2, b: Vec2, c: Vec2, p: Vec2): FloatArray { val outV = (v2.x * v1.y - v1.x * v2.y) / denom val outW = (v0.x * v2.y - v2.x * v0.y) / denom val outU = 1f - outV - outW - return floatArrayOf(outU, outV, outW) + setter(outU, outV, outW) } fun triangleArea(a: Vec2, b: Vec2, c: Vec2): Float = abs((a.x * (b.y - c.y)) + (b.x * (c.y - a.y)) + (c.x * (a.y - b.y))) * 0.5f diff --git a/core/src/main/kotlin/imgui/internal/sections/Widgets support flags, enums, data structures.kt b/core/src/main/kotlin/imgui/internal/sections/Widgets support flags, enums, data structures.kt index 58dea8174..77fee3f2f 100644 --- a/core/src/main/kotlin/imgui/internal/sections/Widgets support flags, enums, data structures.kt +++ b/core/src/main/kotlin/imgui/internal/sections/Widgets support flags, enums, data structures.kt @@ -256,6 +256,7 @@ enum class Axis { } operator fun Array.get(index: Axis) = get(index.i) +infix fun Array.mutablePropertyAt(index: Axis) = mutablePropertyAt(index.i) operator fun Array.set(index: Axis, value: T) = set(index.i, value) operator fun Vec2.get(axis: Axis): Float = when (axis) { @@ -264,6 +265,8 @@ operator fun Vec2.get(axis: Axis): Float = when (axis) { else -> throw Error() } +infix fun Vec2.mutablePropertyAt(index: Axis) = mutablePropertyAt(index.i) + operator fun Vec2.set(axis: Axis, float: Float) = when (axis) { Axis.X -> x = float Axis.Y -> y = float diff --git a/core/src/main/kotlin/imgui/ref.kt b/core/src/main/kotlin/imgui/ref.kt deleted file mode 100644 index 2e77e81ed..000000000 --- a/core/src/main/kotlin/imgui/ref.kt +++ /dev/null @@ -1,116 +0,0 @@ -package imgui - -import uno.kotlin.NUL -import unsigned.* -import kotlin.reflect.KMutableProperty0 - - -private var iPtr = 0 -private var fPtr = 0 -private var bPtr = 0 -private var cPtr = 0 - -private val size = 1024 - -private val ints = IntArray(size) -private val floats = FloatArray(size) -private val bools = BooleanArray(size) -private val chars = CharArray(size) - -private var int: Int - get() = ints[iPtr] - set(value) = ints.set(iPtr, value) -private var float: Float - get() = floats[fPtr] - set(value) = floats.set(fPtr, value) -private var bool: Boolean - get() = bools[bPtr] - set(value) = bools.set(bPtr, value) -private var char: Char - get() = chars[cPtr] - set(value) = chars.set(cPtr, value) - -fun withBoolean(bools: BooleanArray, ptr: Int = 0, block: (KMutableProperty0) -> R): R { - bPtr++ - val bool = ::bool - bool.set(bools[ptr]) - return block(bool).also { - bools[ptr] = bool() - bPtr-- - } -} - -fun withFloat(floats: FloatArray, ptr: Int, block: (KMutableProperty0) -> R): R { - fPtr++ - val f = ::float - f.set(floats[ptr]) - return block(f).also { - floats[ptr] = f() - fPtr-- - } -} - -fun withInt(pInt: IntArray, block: (KMutableProperty0) -> R): R = withInt(pInt, 0, block) -fun withInt(pInt: IntArray, ptr: Int, block: (KMutableProperty0) -> R): R { - iPtr++ - val i = ::int - i.set(pInt[ptr]) - return block(i).also { - pInt[ptr] = i() - iPtr-- - } -} - -fun withInt(block: (KMutableProperty0) -> R): R { - iPtr++ - return block(::int).also { iPtr-- } -} - -fun withChar(block: (KMutableProperty0) -> R): R { - cPtr++ - return block(::char).also { cPtr-- } -} - -fun withChar(char: Char, block: (KMutableProperty0) -> R): R { - cPtr++ - imgui.char = char - return block(::char).also { cPtr-- } -} - -fun withBool(block: (KMutableProperty0) -> R): R { - bPtr++ - return block(::bool).also { bPtr-- } -} - -fun withBool(boolean: Boolean, block: (KMutableProperty0) -> R): Boolean { - bPtr++ - bool = boolean - block(::bool) - return bool.also { bPtr-- } -} - - -// static arrays to avoid GC pressure -val _fa = FloatArray(4) -val _fa2 = FloatArray(4) -val _ia = IntArray(4) - - -// static referencable -var _b = false -var _i8 = 0.toByte() -var _u8 = 0.toUbyte() -var _i16 = 0.toShort() -var _u16 = 0.toUshort() -var _i32 = 0 -var _u32 = 0.toUint() -var _i64 = 0L -var _u64 = 0.toUlong() -var _ui = Uint() -var _L = 0L -var _d = 0.0 -var _f = 0f -var _f1 = 0f -var _f2 = 0f -var _c = NUL -var _s = "" \ No newline at end of file diff --git a/core/src/main/kotlin/imgui/static/misc.kt b/core/src/main/kotlin/imgui/static/misc.kt index f77550f35..df9b23af2 100644 --- a/core/src/main/kotlin/imgui/static/misc.kt +++ b/core/src/main/kotlin/imgui/static/misc.kt @@ -26,6 +26,8 @@ import imgui.internal.classes.Rect import imgui.internal.classes.Window import imgui.internal.sections.* import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min import kotlin.reflect.KMutableProperty0 diff --git a/core/src/main/kotlin/imgui/static/widgets.kt b/core/src/main/kotlin/imgui/static/widgets.kt index 3c5b51ac4..9fc836e5d 100644 --- a/core/src/main/kotlin/imgui/static/widgets.kt +++ b/core/src/main/kotlin/imgui/static/widgets.kt @@ -105,12 +105,10 @@ fun inputTextFilterCharacter(char: KMutableProperty0, flags: InputTextFlag // Turn a-z into A-Z if (flags has InputTextFlag.CharsUppercase && c in 'a'..'z') - c = c + ('A' - 'a') + c += ('A' - 'a') if (flags has InputTextFlag.CharsNoBlank && c.isBlankW) return false - - char.set(c) } // Custom callback filter @@ -192,16 +190,6 @@ fun inputTextCalcTextSizeW(ctx: Context, text: CharArray, textBegin: Int, textEn return textSize } -fun inputScalarDefaultCharsFilter(dataType: DataType, format: String): InputTextFlag.Single { - if (dataType == DataType.Float || dataType == DataType.Double) - return InputTextFlag.CharsScientific - val formatLastChar = if (format.isNotEmpty()) format.last() else NUL - return when (formatLastChar) { - 'x', 'X' -> InputTextFlag.CharsHexadecimal - else -> InputTextFlag.CharsDecimal - } -} - // Find the shortest single replacement we can make to get the new text from the old text. // Important: needs to be run before TextW is rewritten with the new characters because calling STB_TEXTEDIT_GETCHAR() at the end. // FIXME: Ideally we should transition toward (1) making InsertChars()/DeleteChars() update undo-stack (2) discourage (and keep reconcile) or obsolete (and remove reconcile) accessing buffer directly. diff --git a/core/src/main/kotlin/imgui/static/widgetsTabBar.kt b/core/src/main/kotlin/imgui/static/widgetsTabBar.kt index 90397741c..e61920dc4 100644 --- a/core/src/main/kotlin/imgui/static/widgetsTabBar.kt +++ b/core/src/main/kotlin/imgui/static/widgetsTabBar.kt @@ -14,6 +14,8 @@ import imgui.internal.hashStr import imgui.internal.linearSweep import imgui.internal.sections.ButtonFlag import kotlin.math.abs +import kotlin.math.max +import kotlin.math.min /** This is called only once a frame before by the first call to ItemTab() * The reason we're not calling it in BeginTabBar() is to leave a chance to the user to call the SetTabItemClosed() functions. diff --git a/core/src/main/kotlin/imgui/tmp.kt b/core/src/main/kotlin/imgui/tmp.kt index 206ba7d3d..a56118d56 100644 --- a/core/src/main/kotlin/imgui/tmp.kt +++ b/core/src/main/kotlin/imgui/tmp.kt @@ -1,6 +1,11 @@ package imgui import gli_.has +import glm_.L +import glm_.f +import glm_.vec2.Vec2t +import glm_.vec3.Vec3t +import glm_.vec4.Vec4t import uno.kotlin.NUL import kotlin.reflect.* @@ -28,59 +33,36 @@ fun String.getOrNul(index: Int): Char = getOrElse(index) { NUL } operator fun Boolean.div(other: Boolean) = or(other) operator fun Boolean.rem(other: Boolean) = and(other) -class Property(val get: () -> V) : KProperty0 { - override val annotations: List - get() = TODO("Not yet implemented") - override val getter: KProperty0.Getter - get() = TODO("Not yet implemented") - override val isAbstract: Boolean - get() = TODO("Not yet implemented") - override val isConst: Boolean - get() = TODO("Not yet implemented") - override val isFinal: Boolean - get() = TODO("Not yet implemented") - override val isLateinit: Boolean - get() = TODO("Not yet implemented") - override val isOpen: Boolean - get() = TODO("Not yet implemented") - override val isSuspend: Boolean - get() = TODO("Not yet implemented") - override val name: String - get() = TODO("Not yet implemented") - override val parameters: List - get() = TODO("Not yet implemented") - override val returnType: KType - get() = TODO("Not yet implemented") - override val typeParameters: List - get() = TODO("Not yet implemented") - override val visibility: KVisibility - get() = TODO("Not yet implemented") - - override fun call(vararg args: Any?): V = TODO("Not yet implemented") - - override fun callBy(args: Map): V = TODO("Not yet implemented") - - override fun get(): V = get.invoke() +infix fun ByteArray.mutablePropertyAt(index: Int) = mutableProperty({ this[index] }) { this[index] = it } +infix fun CharArray.mutablePropertyAt(index: Int) = mutableProperty({ this[index] }) { this[index] = it } +infix fun ShortArray.mutablePropertyAt(index: Int) = mutableProperty({ this[index] }) { this[index] = it } +infix fun IntArray.mutablePropertyAt(index: Int) = mutableProperty({ this[index] }) { this[index] = it } +infix fun LongArray.mutablePropertyAt(index: Int) = mutableProperty({ this[index] }) { this[index] = it } +infix fun FloatArray.mutablePropertyAt(index: Int) = mutableProperty({ this[index] }) { this[index] = it } +infix fun DoubleArray.mutablePropertyAt(index: Int) = mutableProperty({ this[index] }) { this[index] = it } +infix fun BooleanArray.mutablePropertyAt(index: Int) = mutableProperty({ this[index] }) { this[index] = it } +infix fun Array.mutablePropertyAt(index: Int) = mutableProperty({ this[index] }) { this[index] = it } +infix fun MutableList.mutablePropertyAt(index: Int) = mutableProperty({ this[index] }) { this[index] = it } +fun MutableMap.mutablePropertyAt(index: K, default: V) = mutableProperty({ getOrDefault(index, default) }) { this[index] = it } +infix fun Vec2t.mutablePropertyAt(index: Int): MutableProperty { + require(index in 0 until Vec2t.length) { "Vector $this does not have index $index" } + return mutableProperty({ this[index] }) { this[index] = it } +} - override fun getDelegate(): Any = TODO("Not yet implemented") +infix fun Vec3t.mutablePropertyAt(index: Int): MutableProperty { + require(index in 0 until Vec3t.length) { "Vector $this does not have index $index" } + return mutableProperty({ this[index] }) { this[index] = it } +} - override fun invoke(): V = get() +infix fun Vec4t.mutablePropertyAt(index: Int): MutableProperty { + require(index in 0 until Vec4t.length) { "Vector $this does not have index $index" } + return mutableProperty({ this[index] }) { this[index] = it } } -infix fun BooleanArray.mutablePropertyAt(index: Int) = MutableProperty({ this[index] }) { this[index] = it } -infix fun Array.mutablePropertyAt(index: Int) = MutableProperty({ this[index] }) { this[index] = it } @JvmName("mutablePropertyAtFlagArray") -infix fun > FlagArray.mutablePropertyAt(index: Int): MutableProperty> = MutableProperty({ this[index] }) { this[index] = it } -inline val V.asMutableProperty - get() = MutableProperty({ this }) - -infix fun V.mutableProperty(set: (V) -> Unit) = MutableProperty({ this }, set) -val V.mutableReference: MutableReference - get() = MutableReference(this) -//fun mutableProperty(get: () -> V, set: (V) -> Unit = {}) = MutableProperty(get, set) +infix fun > FlagArray.mutablePropertyAt(index: Int) = mutableProperty({ this[index] }) { this[index] = it } -//fun mutableProperty(get: () -> V) = MutableProperty(get) -abstract class MutablePropertyBase : KMutableProperty0 { +abstract class MutableProperty : KMutableProperty0 { override val annotations: List get() = TODO("Not yet implemented") override val getter: KProperty0.Getter @@ -108,7 +90,7 @@ abstract class MutablePropertyBase : KMutableProperty0 { override val parameters: List get() = TODO("Not yet implemented") override val property: KProperty - get() = TODO("Not yet implemented") + get() = this@MutableProperty override val returnType: KType get() = TODO("Not yet implemented") override val typeParameters: List @@ -116,14 +98,56 @@ abstract class MutablePropertyBase : KMutableProperty0 { override val visibility: KVisibility get() = TODO("Not yet implemented") - override fun call(vararg args: Any?): V = TODO("Not yet implemented") + override fun call(vararg args: Any?): V = if (args.isEmpty()) get() + else throw IllegalArgumentException("No arguments expected") - override fun callBy(args: Map): V { - TODO("Not yet implemented") - } + override fun callBy(args: Map): V = if (args.isEmpty()) get() + else throw IllegalArgumentException("No arguments expected") override fun invoke(): V = get() + } + override val setter: KMutableProperty0.Setter + get() = object: KMutableProperty0.Setter { + override val annotations: List + get() = TODO("Not yet implemented") + override val isAbstract: Boolean + get() = TODO("Not yet implemented") + override val isExternal: Boolean + get() = TODO("Not yet implemented") + override val isFinal: Boolean + get() = TODO("Not yet implemented") + override val isInfix: Boolean + get() = TODO("Not yet implemented") + override val isInline: Boolean + get() = TODO("Not yet implemented") + override val isOpen: Boolean + get() = TODO("Not yet implemented") + override val isOperator: Boolean + get() = TODO("Not yet implemented") + override val isSuspend: Boolean + get() = TODO("Not yet implemented") + override val name: String + get() = TODO("Not yet implemented") + override val parameters: List + get() = TODO("Not yet implemented") + override val property: KMutableProperty + get() = this@MutableProperty + override val returnType: KType + get() = TODO("Not yet implemented") + override val typeParameters: List + get() = TODO("Not yet implemented") + override val visibility: KVisibility + get() = TODO("Not yet implemented") + + override fun call(vararg args: Any?): Unit = if (args.size == 1) set(args[0] as V) + else throw IllegalArgumentException("One argument expected") + + override fun callBy(args: Map): Unit = if (args.size == 1) set(args.values.first() as V) + else throw IllegalArgumentException("One argument expected") + + override fun invoke(p1: V): Unit = set(p1) + } override val isAbstract: Boolean get() = TODO("Not yet implemented") @@ -143,27 +167,34 @@ abstract class MutablePropertyBase : KMutableProperty0 { get() = TODO("Not yet implemented") override val returnType: KType get() = TODO("Not yet implemented") - override val setter: KMutableProperty0.Setter - get() = TODO("Not yet implemented") override val typeParameters: List get() = TODO("Not yet implemented") override val visibility: KVisibility get() = TODO("Not yet implemented") - override fun call(vararg args: Any?): V = TODO("Not yet implemented") - override fun callBy(args: Map): V = TODO("Not yet implemented") - override fun getDelegate(): Any = TODO("Not yet implemented") + override fun call(vararg args: Any?): V = if (args.isEmpty()) get() + else throw IllegalArgumentException("No arguments expected") + + override fun callBy(args: Map): V = if (args.isEmpty()) get() + else throw IllegalArgumentException("No arguments expected") + + override fun getDelegate(): Any = this override fun invoke(): V = get() } -class MutableProperty(val get: () -> V, val set: (V) -> Unit = {}) : MutablePropertyBase() { +inline fun mutableProperty(crossinline get: () -> V, crossinline set: (V) -> Unit = {}) = object : MutableProperty() { override fun get(): V = get.invoke() override fun set(value: V) = set.invoke(value) } -class MutableReference(var field: V) : MutablePropertyBase() { +class MutableReference(var field: V) : MutableProperty() { override fun get(): V = field override fun set(value: V) { field = value } -} \ No newline at end of file +} + +val V.mutableReference: MutableReference + get() = MutableReference(this) + +val KMutableProperty0.L get() = mutableProperty({ get().L }, { set(it.f) }) \ No newline at end of file diff --git a/gl/src/test/java/imgui/examples/OpenGL3.java b/gl/src/test/java/imgui/examples/OpenGL3.java index bfc6e93b9..33bf70fcc 100644 --- a/gl/src/test/java/imgui/examples/OpenGL3.java +++ b/gl/src/test/java/imgui/examples/OpenGL3.java @@ -6,7 +6,7 @@ import imgui.Cond; import imgui.Flag; import imgui.ImGui; -import imgui.MutableProperty0; +import imgui.MutableReference; import imgui.classes.Context; import imgui.classes.IO; import imgui.impl.gl.ImplGL3; @@ -39,8 +39,8 @@ public class OpenGL3 { private float[] f = {0f}; private Vec4 clearColor = new Vec4(0.45f, 0.55f, 0.6f, 1f); - // Java users can use both a MutableProperty0 or a Boolean Array - private MutableProperty0 showAnotherWindow = new MutableProperty0<>(false); + // Java users can use both a MutableReference or a Boolean Array + private MutableReference showAnotherWindow = new MutableReference<>(false); private boolean[] showDemo = {true}; private int[] counter = {0}; diff --git a/gl/src/test/kotlin/examples/opengl2.kt b/gl/src/test/kotlin/examples/opengl2.kt index be647f662..c92aebbac 100644 --- a/gl/src/test/kotlin/examples/opengl2.kt +++ b/gl/src/test/kotlin/examples/opengl2.kt @@ -5,10 +5,10 @@ package examples //import org.lwjgl.util.remotery.RemoteryGL import glm_.vec4.Vec4 import gln.checkError -import gln.glClearColor import gln.glViewport import imgui.DEBUG import imgui.ImGui +import imgui.api.slider import imgui.classes.Context import imgui.impl.gl.ImplGL2 import imgui.impl.glfw.ImplGlfw @@ -140,7 +140,7 @@ private class ImGuiOpenGL2 { checkbox("Demo Window", ::showDemo) // Edit bools storing our window open/close state checkbox("Another Window", ::showAnotherWindow) - sliderFloat("float", ::f, 0f, 1f) // Edit 1 float using a slider from 0.0f to 1.0f + slider("float", ::f, 0f, 1f) // Edit 1 float using a slider from 0.0f to 1.0f colorEdit3("clear color", clearColor) // Edit 3 floats representing a color if (button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated) diff --git a/gl/src/test/kotlin/examples/opengl3.kt b/gl/src/test/kotlin/examples/opengl3.kt index fb5e17b1e..ea0915657 100644 --- a/gl/src/test/kotlin/examples/opengl3.kt +++ b/gl/src/test/kotlin/examples/opengl3.kt @@ -6,6 +6,7 @@ import gln.checkError import gln.glViewport import imgui.DEBUG import imgui.ImGui +import imgui.api.slider import imgui.classes.Context import imgui.impl.gl.ImplGL3 import imgui.impl.glfw.ImplGlfw @@ -17,7 +18,6 @@ import uno.glfw.GlfwWindow import uno.glfw.VSync import uno.glfw.glfw import uno.glfw.windowHint.Profile.core -import java.awt.Color //import org.lwjgl.util.remotery.Remotery //import org.lwjgl.util.remotery.RemoteryGL @@ -177,7 +177,7 @@ private class ImGuiOpenGL3 { checkbox("Demo Window", ::showDemoWindow) // Edit bools storing our window open/close state checkbox("Another Window", ::showAnotherWindow) - sliderFloat("float", ::f, 0f, 1f) // Edit 1 float using a slider from 0.0f to 1.0f + slider("float", ::f, 0f, 1f) // Edit 1 float using a slider from 0.0f to 1.0f colorEdit3("clear color", clearColor) // Edit 3 floats representing a color if (button("Button")) // Buttons return true when clicked (most widgets return true when edited/activated)